diff --git a/src/java.base/share/classes/sun/security/provider/certpath/URICertStore.java b/src/java.base/share/classes/sun/security/provider/certpath/URICertStore.java index 3e1fc8db164..6eb95f92246 100644 --- a/src/java.base/share/classes/sun/security/provider/certpath/URICertStore.java +++ b/src/java.base/share/classes/sun/security/provider/certpath/URICertStore.java @@ -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; + } + } } diff --git a/src/java.base/share/classes/sun/security/util/SecurityProperties.java b/src/java.base/share/classes/sun/security/util/SecurityProperties.java index 98bc71d829b..da69ecbf5d6 100644 --- a/src/java.base/share/classes/sun/security/util/SecurityProperties.java +++ b/src/java.base/share/classes/sun/security/util/SecurityProperties.java @@ -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. * diff --git a/src/java.base/share/conf/security/java.security b/src/java.base/share/conf/security/java.security index 26842d0c845..2fc908c6bf9 100644 --- a/src/java.base/share/conf/security/java.security +++ b/src/java.base/share/conf/security/java.security @@ -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 # diff --git a/src/java.naming/share/classes/sun/security/provider/certpath/ldap/LDAPCertStoreImpl.java b/src/java.naming/share/classes/sun/security/provider/certpath/ldap/LDAPCertStoreImpl.java index 8f18e04760a..ebf09e57bd1 100644 --- a/src/java.naming/share/classes/sun/security/provider/certpath/ldap/LDAPCertStoreImpl.java +++ b/src/java.naming/share/classes/sun/security/provider/certpath/ldap/LDAPCertStoreImpl.java @@ -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); } diff --git a/test/lib/jdk/test/lib/security/CertificateBuilder.java b/test/lib/jdk/test/lib/security/CertificateBuilder.java index a2d2a7d9eb1..86aaba2a0b1 100644 --- a/test/lib/jdk/test/lib/security/CertificateBuilder.java +++ b/test/lib/jdk/test/lib/security/CertificateBuilder.java @@ -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. - * + *
* 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. - * + *
* The following methods are mandatory before calling build(): *
* 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. - * + *
* 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
+ *
* 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:
*
*
* Certificate ::= SEQUENCE {