8381796: Enhance Certificate parsing

Reviewed-by: ascarpino, abarashev, rhalade, mdonovan
This commit is contained in:
Jamil Nimeh 2026-04-23 00:55:06 +00:00 committed by bchristi
parent 48d32601cd
commit 7e17c402e4
5 changed files with 261 additions and 35 deletions

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2006, 2025, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2006, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -25,6 +25,7 @@
package sun.security.provider.certpath;
import java.io.FilterInputStream;
import java.io.InputStream;
import java.io.IOException;
import java.net.HttpURLConnection;
@ -188,6 +189,16 @@ class URICertStore extends CertStoreSpi {
return timeoutVal;
}
/**
* Maximum size for a CRL downloaded through a URICertStore
* in bytes. This can be controlled by the com.sun.security.crl.maxSize
* Security or System property. The System property, if set, overrides
* the Security property. The default size is 20MiB.
*/
private static final long MAX_CRL_DOWNLOAD_SIZE =
SecurityProperties.getOverridableLongProp(
"com.sun.security.crl.maxSize", 20971520, debug);
/**
* Enumeration for the allowed schemes we support when following a
* URI from an authorityInfoAccess extension on a certificate.
@ -228,6 +239,13 @@ class URICertStore extends CertStoreSpi {
private static final boolean CA_ISS_ALLOW_ANY;
static {
// Add a debug message for the configured CRL download limit
if (debug != null) {
debug.println("Maximum downloadable CRL size: " +
MAX_CRL_DOWNLOAD_SIZE +
((MAX_CRL_DOWNLOAD_SIZE < 0) ? " (DISABLED)" : ""));
}
boolean allowAny = false;
try {
if (Builder.USE_AIA) {
@ -623,7 +641,19 @@ class URICertStore extends CertStoreSpi {
if (debug != null) {
debug.println("Downloading new CRL...");
}
crl = (X509CRL) factory.generateCRL(in);
InputStream crlIn = (MAX_CRL_DOWNLOAD_SIZE > -1) ?
new SizeLimitedInputStream(in, MAX_CRL_DOWNLOAD_SIZE) :
in;
try {
crl = (X509CRL) factory.generateCRL(crlIn);
} catch (IllegalArgumentException iae) {
// IAE should only be thrown when the CRL exceeds a
// configured maximum length.
if (debug != null) {
debug.println("Discarding CRL: " + iae.getMessage());
crl = null;
}
}
}
return getMatchingCRLs(crl, selector);
} catch (IOException | CRLException e) {
@ -816,4 +846,59 @@ class URICertStore extends CertStoreSpi {
return true;
}
}
/**
* Stream wrapper used when an InputStream passed into a CertificateFactory
* needs to be size limited. It will throw IllegalArgumentException when
* the downloaded resource via the underlying stream exceeds the maximum
* limit.
*/
private static class SizeLimitedInputStream extends FilterInputStream {
private final long maxBytes;
private long bytesRead = 0;
private SizeLimitedInputStream(InputStream in, long maxBytes) {
super(in);
this.maxBytes = maxBytes;
}
@Override
public int read() throws IOException {
if (bytesRead >= maxBytes) {
// We will use IAE here to differentiate this special case
// from other IOEs that the underlying input stream might
// legitimately throw.
throw new IllegalArgumentException("InputStream exceeded max " +
"size of " + maxBytes);
}
int b = super.read();
if (b != -1) {
bytesRead++;
}
return b;
}
@Override
public int read(byte[] b, int off, int len) throws IOException {
if (bytesRead >= maxBytes) {
// We will use IAE here to differentiate this special case
// from other IOEs that the underlying input stream might
// legitimately throw.
throw new IllegalArgumentException("InputStream exceeded max " +
"size of " + maxBytes);
}
long remaining = maxBytes - bytesRead;
int toRead = (int) Math.min(len, remaining);
int n = super.read(b, off, toRead);
if (n != -1) {
bytesRead += n;
}
return n;
}
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2018, 2024, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2018, 2026, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2018 SAP SE. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
@ -139,6 +139,36 @@ public class SecurityProperties {
}
}
/**
* A convenience routine for fetching a numeric value from a Security
* or System property and returning it as a long. The value from the
* property is obtained according to the logic in
* {@link SecurityProperties#getOverridableProperty(String)}
*
* @param prop the property to query
* @param defaultValue the default value
* @param dbg a Debug object, if null no debug messages will be sent
* @return the value of the property as a {@code long}. If a non-numeric
* value is supplied, the default value will be returned.
*/
public static long getOverridableLongProp(String prop, long defaultValue,
Debug dbg) {
long longVal = defaultValue;
try {
String propVal = SecurityProperties.getOverridableProperty(prop);
if (propVal != null) {
longVal = Long.parseLong(propVal);
}
} catch (NumberFormatException nfe) {
// We will use the default, but add a warning debug message
if (dbg != null) {
dbg.println("Warning: Non-numeric value found in property " +
prop + ", using default value of " + defaultValue);
}
}
return longVal;
}
/**
* Convenience method for fetching System property values that are booleans.
*

View File

@ -1714,6 +1714,22 @@ jdk.epkcs8.defaultAlgorithm=PBEWithHmacSHA256AndAES_128
# ldap://ldap.company.com/dc=company,dc=com?caCertificate;binary
com.sun.security.allowedAIALocations=
#
# Certificate Revocation List (CRL) Download Size Limitation
#
# This property sets a size limit for CRLs downloaded via URIs provided
# in the CRL Distribution Points certificate extension. This property
# must be a numeric value that is the size in bytes of the DER-encoded CRL.
# For protocols that can return multi-value responses, such as LDAP, the
# size threshold is the sum of all CRLs downloaded from a single search
# query. CRLs that exceed this length will not be processed during certificate
# path validation. This size limit does not apply to CRLs that are imported
# through non-network-based means. A negative value will disable this size
# limitation. A non-numeric value will be ignored and the default size will
# be used instead. The default size limit is 20MiB.
# This property may be overridden by a System property of the same name.
com.sun.security.crl.maxSize = 20971520
#
# PKCS #8 encoding format for newly created ML-KEM and ML-DSA private keys
#

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2015, 2024, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2015, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -52,6 +52,7 @@ import sun.security.util.HexDumpEncoder;
import sun.security.provider.certpath.X509CertificatePair;
import sun.security.util.Cache;
import sun.security.util.Debug;
import sun.security.util.SecurityProperties;
/**
* Core implementation of a LDAP Cert Store.
@ -96,6 +97,16 @@ final class LDAPCertStoreImpl {
private static final String PROP_DISABLE_APP_RESOURCE_FILES =
"sun.security.certpath.ldap.disable.app.resource.files";
/**
* Maximum size for a CRL downloaded through an LDAPCertStoreImpl
* in bytes. This can be controlled by the com.sun.security.crl.maxSize
* Security or System property. The System property, if set, overrides
* the Security property. The default size is 20MiB.
*/
private static final long MAX_CRL_DOWNLOAD_SIZE =
SecurityProperties.getOverridableLongProp(
"com.sun.security.crl.maxSize", 20971520, debug);
static {
String s = System.getProperty(PROP_LIFETIME);
if (s != null) {
@ -103,6 +114,13 @@ final class LDAPCertStoreImpl {
} else {
LIFETIME = DEFAULT_CACHE_LIFETIME;
}
// Add a debug message for the configured CRL download limit
if (debug != null) {
debug.println("Maximum downloadable CRL size: " +
MAX_CRL_DOWNLOAD_SIZE +
((MAX_CRL_DOWNLOAD_SIZE < 0) ? " (DISABLED)" : ""));
}
}
/**
@ -672,12 +690,12 @@ final class LDAPCertStoreImpl {
return certs;
}
/*
/**
* Gets CRLs from an attribute id and location in the LDAP directory.
* Returns a Collection containing only the CRLs that match the
* specified X509CRLSelector.
*
* @param name the location holding the attribute
* @param request the LDAP request used for this CRL fetch operation
* @param id the attribute identifier
* @param sel a X509CRLSelector that the CRLs must match
* @return a Collection of CRLs found
@ -689,7 +707,26 @@ final class LDAPCertStoreImpl {
/* fetch the encoded crls from storage */
byte[][] encodedCRL;
try {
encodedCRL = request.getValues(id);
byte[][] tmpCrls = request.getValues(id);
if (MAX_CRL_DOWNLOAD_SIZE > -1) {
int totalSize = 0;
for (byte[] tCrl : tmpCrls) {
totalSize += tCrl.length;
}
if (totalSize <= MAX_CRL_DOWNLOAD_SIZE) {
encodedCRL = tmpCrls;
} else {
if (debug != null) {
debug.println("Received " + tmpCrls.length +
" CRL(s). Combined length of " + totalSize +
" exceeds configured maximum. Discarding.");
}
encodedCRL = new byte[0][];
}
} else {
// Download limits disabled
encodedCRL = tmpCrls;
}
} catch (NamingException namingEx) {
throw new CertStoreException(namingEx);
}

View File

@ -42,6 +42,7 @@ import sun.security.x509.AccessDescription;
import sun.security.x509.AlgorithmId;
import sun.security.x509.AuthorityInfoAccessExtension;
import sun.security.x509.AuthorityKeyIdentifierExtension;
import sun.security.x509.CRLDistributionPointsExtension;
import sun.security.x509.GeneralSubtrees;
import sun.security.x509.IPAddressName;
import sun.security.x509.NameConstraintsExtension;
@ -49,6 +50,7 @@ import sun.security.x509.SubjectKeyIdentifierExtension;
import sun.security.x509.BasicConstraintsExtension;
import sun.security.x509.CertificateSerialNumber;
import sun.security.x509.ExtendedKeyUsageExtension;
import sun.security.x509.DistributionPoint;
import sun.security.x509.DNSName;
import sun.security.x509.GeneralName;
import sun.security.x509.GeneralNames;
@ -61,13 +63,13 @@ import sun.security.x509.X500Name;
/**
* Helper class that builds and signs X.509 certificates.
*
* <p>
* A CertificateBuilder is created with a default constructor, and then
* uses additional public methods to set the public key, desired validity
* dates, serial number and extensions. It is expected that the caller will
* have generated the necessary key pairs prior to using a CertificateBuilder
* to generate certificates.
*
* <p>
* The following methods are mandatory before calling build():
* <UL>
* <LI>{@link #setSubjectName(java.lang.String)}
@ -81,12 +83,12 @@ import sun.security.x509.X500Name;
* Additionally, the caller can either provide a {@link List} of
* {@link Extension} objects, or use the helper classes to add specific
* extension types.
*
* <p>
* When all required and desired parameters are set, the
* {@link #build(java.security.cert.X509Certificate, java.security.PrivateKey,
* java.lang.String)} method can be used to create the {@link X509Certificate}
* object.
*
* <p>
* Multiple certificates may be cut from the same settings using subsequent
* calls to the build method. Settings may be cleared using the
* {@link #reset()} method.
@ -112,20 +114,23 @@ public class CertificateBuilder {
KEY_CERT_SIGN,
CRL_SIGN,
ENCIPHER_ONLY,
DECIPHER_ONLY;
DECIPHER_ONLY
}
/**
* Create a new CertificateBuilder instance. This method sets the subject name,
* public key, authority key id, and serial number.
* Create a new {@code CertificateBuilder} instance. This method sets the
* subject name, public key, authority key id, and serial number.
*
* @param subjectName entity associated with the public key
* @param publicKey the entity's public key
* @param caKey public key of certificate signer
* @param keyUsages list of key uses
* @return
* @throws CertificateException
* @throws IOException
* @return a {@code CertificateBuilder} configured with the provided
* parameters
*
* @throws CertificateException if an error occurs when obtaining the
* underlying {@link CertificateFactory}
* @throws IOException if any extension encoding errors occur
*/
public static CertificateBuilder newCertificateBuilder(String subjectName,
PublicKey publicKey, PublicKey caKey, KeyUsage... keyUsages)
@ -151,9 +156,13 @@ public class CertificateBuilder {
/**
* Create a Subject Alternative Name extension for the given DNS name
*
* @param critical Sets the extension to critical or non-critical
* @param dnsName DNS name to use in the extension
* @throws IOException
* @param dnsNames one or more DNS names to use in the extension
* @return a {@code SubjectAlternativeNameExtension} configured with
* the {@code dnsNames} as individual DNSName entries.
*
* @throws IOException if any encoding errors occur
*/
public static SubjectAlternativeNameExtension createDNSSubjectAltNameExt(
boolean critical, String... dnsNames) throws IOException {
@ -166,9 +175,13 @@ public class CertificateBuilder {
/**
* Create a Subject Alternative Name extension for the given IP address
*
* @param critical Sets the extension to critical or non-critical
* @param ipAddresses IP addresses to use in the extension
* @throws IOException
* @param ipAddresses one or more IP addresses to use in the extension
* @return a {@code SubjectAlternativeNameExtension} configured with
* the {@code ipAddresses} as individual IPAddressName entries.
*
* @throws IOException if any encoding errors occur
*/
public static SubjectAlternativeNameExtension createIPSubjectAltNameExt(
boolean critical, String... ipAddresses) throws IOException {
@ -215,6 +228,9 @@ public class CertificateBuilder {
* Set the subject name for the certificate.
*
* @param name The subject name in RFC 2253 format
*
* @throws IllegalArgumentException if any parsing errors on the
* {@code name} parameter occur.
*/
public CertificateBuilder setSubjectName(String name) {
try {
@ -241,6 +257,9 @@ public class CertificateBuilder {
* Set the public key for this certificate.
*
* @param pubKey The {@link PublicKey} to be used on this certificate.
*
* @throws NullPointerException if the {@code pubKey} parameter
* is {@code null}
*/
public CertificateBuilder setPublicKey(PublicKey pubKey) {
publicKey = Objects.requireNonNull(pubKey, "Caught null public key");
@ -252,6 +271,9 @@ public class CertificateBuilder {
*
* @param nbDate A {@link Date} object specifying the start of the
* certificate validity period.
*
* @throws NullPointerException if the {@code nbDate} parameter
* is {@code null}
*/
public CertificateBuilder setNotBefore(Date nbDate) {
Objects.requireNonNull(nbDate, "Caught null notBefore date");
@ -264,6 +286,9 @@ public class CertificateBuilder {
*
* @param naDate A {@link Date} object specifying the end of the
* certificate validity period.
*
* @throws NullPointerException if the {@code naDate} parameter
* is {@code null}
*/
public CertificateBuilder setNotAfter(Date naDate) {
Objects.requireNonNull(naDate, "Caught null notAfter date");
@ -278,6 +303,9 @@ public class CertificateBuilder {
* certificate validity period.
* @param naDate A {@link Date} object specifying the end of the
* certificate validity period.
*
* @throws NullPointerException if either the {@code nbDate} or
* {@code naDate} parameters are {@code null}
*/
public CertificateBuilder setValidity(Date nbDate, Date naDate) {
return setNotBefore(nbDate).setNotAfter(naDate);
@ -292,6 +320,8 @@ public class CertificateBuilder {
* Set the serial number on the certificate.
*
* @param serial A serial number in {@link BigInteger} form.
*
* @throws NullPointerException if {@code serial} is {@code null}
*/
public CertificateBuilder setSerialNumber(BigInteger serial) {
Objects.requireNonNull(serial, "Caught null serial number");
@ -316,6 +346,8 @@ public class CertificateBuilder {
*
* @param extList The {@link List} of extensions to be added to
* the certificate.
*
* @throws NullPointerException if {@code extList} is {@code null}
*/
public CertificateBuilder addExtensions(List<Extension> extList) {
Objects.requireNonNull(extList, "Caught null extension list");
@ -329,6 +361,7 @@ public class CertificateBuilder {
* Helper method to add DNSName types for the SAN extension
*
* @param dnsNames A {@code List} of names to add as DNSName types
*
* @throws IOException if an encoding error occurs.
*/
public CertificateBuilder addSubjectAltNameDNSExt(List<String> dnsNames)
@ -350,6 +383,7 @@ public class CertificateBuilder {
*
* @param ipAddresses A {@code List} of names to add as IPAddress
* types
*
* @throws IOException if an encoding error occurs.
*/
public CertificateBuilder addSubjectAltNameIPExt(List<String> ipAddresses)
@ -365,13 +399,41 @@ public class CertificateBuilder {
return this;
}
/**
* Helper method to add one or more distribution points to the CRL
* Distribution Points extension. This form of the method only supports
* URI name types, but can be extended in the future to support other types.
*
* @param uriNames a list of URIs in String form
* @return the {@code CertificateBuilder} configured to add this
* CRL Distribution Points extension.
*
* @throws IOException if any of the URIs in {@code uriNames} are
* malformed
*/
public CertificateBuilder addCrlDistributionPointsExt(List<String> uriNames)
throws IOException {
if (uriNames != null && !uriNames.isEmpty()) {
GeneralNames gNames = new GeneralNames();
for (String name : uriNames) {
gNames.add(new GeneralName(new URIName(name)));
}
addExtension(new CRLDistributionPointsExtension(List.of(
new DistributionPoint(gNames, null, null))));
}
return this;
}
/**
* Helper method to add one or more OCSP URIs to the Authority Info Access
* certificate extension. Location strings can be in two forms:
* 1) Just a URI by itself: This will be treated as using the OCSP
* <ol>
* <li>Just a URI by itself: This will be treated as using the OCSP
* access description (legacy behavior).
* 2) An access description name (case-insensitive) followed by a
* pipe (|) and the URI (e.g. OCSP|http://ocsp.company.com/revcheck).
* <li>An access description name (case-insensitive) followed by a
* pipe (|) and the URI (e.g.
* {@code OCSP|http://ocsp.company.com/revcheck}).
* </ol>
* Current description names are OCSP and CAISSUER. Others may be
* added later.
*
@ -392,16 +454,12 @@ public class CertificateBuilder {
adObj = AccessDescription.Ad_OCSP_Id;
uriLoc = tokens[0];
} else {
switch (tokens[0].toUpperCase()) {
case "OCSP":
adObj = AccessDescription.Ad_OCSP_Id;
break;
case "CAISSUER":
adObj = AccessDescription.Ad_CAISSUERS_Id;
break;
default:
throw new IOException("Unknown AD: " + tokens[0]);
}
adObj = switch (tokens[0].toUpperCase()) {
case "OCSP" -> AccessDescription.Ad_OCSP_Id;
case "CAISSUER" -> AccessDescription.Ad_CAISSUERS_Id;
default -> throw new IOException("Unknown AD: " +
tokens[0]);
};
uriLoc = tokens[1];
}
acDescList.add(new AccessDescription(adObj,
@ -568,7 +626,7 @@ public class CertificateBuilder {
}
/**
* Encode the contents of the outer-most ASN.1 SEQUENCE:
* Encode the contents of the outermost ASN.1 SEQUENCE:
*
* <PRE>
* Certificate ::= SEQUENCE {