diff --git a/src/java.base/share/classes/javax/security/auth/x500/X500Principal.java b/src/java.base/share/classes/javax/security/auth/x500/X500Principal.java index f58a37f0997..efc5b7891b8 100644 --- a/src/java.base/share/classes/javax/security/auth/x500/X500Principal.java +++ b/src/java.base/share/classes/javax/security/auth/x500/X500Principal.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 2024, 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 @@ -29,6 +29,8 @@ import java.io.*; import java.security.Principal; import java.util.Collections; import java.util.Map; +import jdk.internal.access.JavaxSecurityAccess; +import jdk.internal.access.SharedSecrets; import sun.security.x509.X500Name; import sun.security.util.*; @@ -82,16 +84,31 @@ public final class X500Principal implements Principal, java.io.Serializable { /** * The X500Name representing this principal. * - * NOTE: this field is reflectively accessed from within X500Name. + * NOTE: this field is accessed using shared secrets from within X500Name. */ private transient X500Name thisX500Name; + static { + // Set up JavaxSecurityAccess in SharedSecrets + SharedSecrets.setJavaxSecurityAccess( + new JavaxSecurityAccess() { + @Override + public X500Name asX500Name(X500Principal principal) { + return principal.thisX500Name; + } + @Override + public X500Principal asX500Principal(X500Name name) { + return new X500Principal(name); + } + }); + } + /** * Creates an X500Principal by wrapping an X500Name. * * NOTE: The constructor is package private. It is intended to be accessed - * using privileged reflection from classes in sun.security.*. - * Currently, it is referenced from sun.security.x509.X500Name.asX500Principal(). + * using shared secrets from classes in sun.security.*. Currently, it is + * referenced from sun.security.x509.X500Name.asX500Principal(). */ X500Principal(X500Name x500Name) { thisX500Name = x500Name; diff --git a/src/java.base/share/classes/jdk/internal/access/JavaxSecurityAccess.java b/src/java.base/share/classes/jdk/internal/access/JavaxSecurityAccess.java new file mode 100644 index 00000000000..004b6db375b --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/access/JavaxSecurityAccess.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2024, 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.access; + +import javax.security.auth.x500.X500Principal; +import sun.security.x509.X500Name; + +public interface JavaxSecurityAccess { + X500Name asX500Name(X500Principal p); + X500Principal asX500Principal(X500Name n); +} diff --git a/src/java.base/share/classes/jdk/internal/access/SharedSecrets.java b/src/java.base/share/classes/jdk/internal/access/SharedSecrets.java index 919d758a6e3..eb0a7821d0d 100644 --- a/src/java.base/share/classes/jdk/internal/access/SharedSecrets.java +++ b/src/java.base/share/classes/jdk/internal/access/SharedSecrets.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2002, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2002, 2024, 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 @@ -44,6 +44,7 @@ import java.io.PrintWriter; import java.io.RandomAccessFile; import java.security.ProtectionDomain; import java.security.Signature; +import javax.security.auth.x500.X500Principal; /** A repository of "shared secrets", which are a mechanism for calling implementation-private methods in another package without @@ -89,6 +90,7 @@ public class SharedSecrets { private static JavaSecuritySpecAccess javaSecuritySpecAccess; private static JavaxCryptoSealedObjectAccess javaxCryptoSealedObjectAccess; private static JavaxCryptoSpecAccess javaxCryptoSpecAccess; + private static JavaxSecurityAccess javaxSecurityAccess; private static JavaTemplateAccess javaTemplateAccess; public static void setJavaUtilCollectionAccess(JavaUtilCollectionAccess juca) { @@ -517,6 +519,19 @@ public class SharedSecrets { return access; } + public static void setJavaxSecurityAccess(JavaxSecurityAccess jsa) { + javaxSecurityAccess = jsa; + } + + public static JavaxSecurityAccess getJavaxSecurityAccess() { + var access = javaxSecurityAccess; + if (access == null) { + ensureClassInitialized(X500Principal.class); + access = javaxSecurityAccess; + } + return access; + } + public static void setJavaTemplateAccess(JavaTemplateAccess jta) { javaTemplateAccess = jta; } diff --git a/src/java.base/share/classes/sun/security/provider/certpath/Builder.java b/src/java.base/share/classes/sun/security/provider/certpath/Builder.java index 6860637bed8..71e8b615a36 100644 --- a/src/java.base/share/classes/sun/security/provider/certpath/Builder.java +++ b/src/java.base/share/classes/sun/security/provider/certpath/Builder.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 2024, 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 @@ -33,13 +33,6 @@ import java.util.*; import sun.security.action.GetBooleanAction; import sun.security.provider.certpath.PKIX.BuilderParams; import sun.security.util.Debug; -import sun.security.x509.GeneralNames; -import sun.security.x509.GeneralNameInterface; -import sun.security.x509.GeneralSubtrees; -import sun.security.x509.NameConstraintsExtension; -import sun.security.x509.SubjectAlternativeNameExtension; -import sun.security.x509.X500Name; -import sun.security.x509.X509CertImpl; /** * Abstract class representing a builder, which is able to retrieve @@ -126,246 +119,6 @@ abstract class Builder { abstract void removeFinalCertFromPath (LinkedList certPathList); - /** - * get distance of one GeneralName from another - * - * @param base GeneralName at base of subtree - * @param test GeneralName to be tested against base - * @param incomparable the value to return if the names are - * incomparable - * @return distance of test name from base, where 0 - * means exact match, 1 means test is an immediate - * child of base, 2 means test is a grandchild, etc. - * -1 means test is a parent of base, -2 means test - * is a grandparent, etc. - */ - static int distance(GeneralNameInterface base, - GeneralNameInterface test, int incomparable) - { - switch (base.constrains(test)) { - case GeneralNameInterface.NAME_DIFF_TYPE: - if (debug != null) { - debug.println("Builder.distance(): Names are different types"); - } - return incomparable; - case GeneralNameInterface.NAME_SAME_TYPE: - if (debug != null) { - debug.println("Builder.distance(): Names are same type but " + - "in different subtrees"); - } - return incomparable; - case GeneralNameInterface.NAME_MATCH: - return 0; - case GeneralNameInterface.NAME_WIDENS: - case GeneralNameInterface.NAME_NARROWS: - break; - default: // should never occur - return incomparable; - } - - /* names are in same subtree */ - return test.subtreeDepth() - base.subtreeDepth(); - } - - /** - * get hop distance of one GeneralName from another in links where - * the names need not have an ancestor/descendant relationship. - * For example, the hop distance from ou=D,ou=C,o=B,c=US to - * ou=F,ou=E,ou=C,o=B,c=US is 3: D->C, C->E, E->F. The hop distance - * from ou=C,o=B,c=US to ou=D,ou=C,o=B,c=US is -1: C->D - * - * @param base GeneralName - * @param test GeneralName to be tested against base - * @param incomparable the value to return if the names are - * incomparable - * @return distance of test name from base measured in hops in the - * namespace hierarchy, where 0 means exact match. Result - * is positive if path is some number of up hops followed by - * some number of down hops; result is negative if path is - * some number of down hops. - */ - static int hops(GeneralNameInterface base, GeneralNameInterface test, - int incomparable) - { - int baseRtest = base.constrains(test); - switch (baseRtest) { - case GeneralNameInterface.NAME_DIFF_TYPE: - if (debug != null) { - debug.println("Builder.hops(): Names are different types"); - } - return incomparable; - case GeneralNameInterface.NAME_SAME_TYPE: - /* base and test are in different subtrees */ - break; - case GeneralNameInterface.NAME_MATCH: - /* base matches test */ - return 0; - case GeneralNameInterface.NAME_WIDENS: - /* base is ancestor of test */ - case GeneralNameInterface.NAME_NARROWS: - /* base is descendant of test */ - return test.subtreeDepth() - base.subtreeDepth(); - default: // should never occur - return incomparable; - } - - /* names are in different subtrees */ - if (base.getType() != GeneralNameInterface.NAME_DIRECTORY) { - if (debug != null) { - debug.println("Builder.hops(): hopDistance not implemented " + - "for this name type"); - } - return incomparable; - } - X500Name baseName = (X500Name)base; - X500Name testName = (X500Name)test; - X500Name commonName = baseName.commonAncestor(testName); - if (commonName == null) { - if (debug != null) { - debug.println("Builder.hops(): Names are in different " + - "namespaces"); - } - return incomparable; - } else { - int commonDistance = commonName.subtreeDepth(); - int baseDistance = baseName.subtreeDepth(); - int testDistance = testName.subtreeDepth(); - return baseDistance + testDistance - (2 * commonDistance); - } - } - - /** - * Determine how close a given certificate gets you toward - * a given target. - * - * @param constraints Current NameConstraints; if null, - * then caller must verify NameConstraints - * independently, realizing that this certificate - * may not actually lead to the target at all. - * @param cert Candidate certificate for chain - * @param target GeneralNameInterface name of target - * @return distance from this certificate to target: - * - *

Note that the subject and/or subjectAltName of the - * candidate cert does not have to be an ancestor of the - * target in order to be a CA that can issue a certificate to - * the target. In these cases, the target distance is calculated - * by inspecting the NameConstraints extension in the candidate - * certificate. For example, suppose the target is an X.500 DN with - * a value of "CN=mullan,OU=ireland,O=sun,C=us" and the - * NameConstraints extension in the candidate certificate - * includes a permitted component of "O=sun,C=us", which implies - * that the candidate certificate is allowed to issue certs in - * the "O=sun,C=us" namespace. The target distance is 3 - * ((distance of permitted NC from target) + 1). - * The (+1) is added to distinguish the result from the case - * which returns (0). - * @throws IOException if certificate does not get closer - */ - static int targetDistance(NameConstraintsExtension constraints, - X509Certificate cert, GeneralNameInterface target) - throws IOException - { - /* ensure that certificate satisfies existing name constraints */ - if (constraints != null && !constraints.verify(cert)) { - throw new IOException("certificate does not satisfy existing name " - + "constraints"); - } - - X509CertImpl certImpl; - try { - certImpl = X509CertImpl.toImpl(cert); - } catch (CertificateException e) { - throw new IOException("Invalid certificate", e); - } - /* see if certificate subject matches target */ - X500Name subject = X500Name.asX500Name(certImpl.getSubjectX500Principal()); - if (subject.equals(target)) { - /* match! */ - return 0; - } - - SubjectAlternativeNameExtension altNameExt = - certImpl.getSubjectAlternativeNameExtension(); - if (altNameExt != null) { - GeneralNames altNames = altNameExt.getNames(); - /* see if any alternative name matches target */ - if (altNames != null) { - for (int j = 0, n = altNames.size(); j < n; j++) { - GeneralNameInterface altName = altNames.get(j).getName(); - if (altName.equals(target)) { - return 0; - } - } - } - } - - - /* no exact match; see if certificate can get us to target */ - - /* first, get NameConstraints out of certificate */ - NameConstraintsExtension ncExt = certImpl.getNameConstraintsExtension(); - if (ncExt == null) { - return -1; - } - - /* merge certificate's NameConstraints with current NameConstraints */ - if (constraints != null) { - constraints.merge(ncExt); - } else { - // Make sure we do a clone here, because we're probably - // going to modify this object later, and we don't want to - // be sharing it with a Certificate object! - constraints = (NameConstraintsExtension) ncExt.clone(); - } - - if (debug != null) { - debug.println("Builder.targetDistance() merged constraints: " - + constraints); - } - /* reduce permitted by excluded */ - GeneralSubtrees permitted = constraints.getPermittedSubtrees(); - GeneralSubtrees excluded = constraints.getExcludedSubtrees(); - if (permitted != null) { - permitted.reduce(excluded); - } - if (debug != null) { - debug.println("Builder.targetDistance() reduced constraints: " - + permitted); - } - /* see if new merged constraints allow target */ - if (!constraints.verify(target)) { - throw new IOException("New certificate not allowed to sign " - + "certificate for target"); - } - /* find distance to target, if any, in permitted */ - if (permitted == null) { - /* certificate is unconstrained; could sign for anything */ - return -1; - } - for (int i = 0, n = permitted.size(); i < n; i++) { - GeneralNameInterface perName = permitted.get(i).getName().getName(); - int distance = distance(perName, target, -1); - if (distance >= 0) { - return distance + 1; - } - } - /* no matching type in permitted; cert holder could certify target */ - return -1; - } - /** * This method can be used as an optimization to filter out * certificates that do not have policies which are valid. diff --git a/src/java.base/share/classes/sun/security/provider/certpath/ForwardBuilder.java b/src/java.base/share/classes/sun/security/provider/certpath/ForwardBuilder.java index 52a073d3b78..7dc829788a5 100644 --- a/src/java.base/share/classes/sun/security/provider/certpath/ForwardBuilder.java +++ b/src/java.base/share/classes/sun/security/provider/certpath/ForwardBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 2024, 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 @@ -44,10 +44,13 @@ import javax.security.auth.x500.X500Principal; import jdk.internal.misc.ThreadTracker; import sun.security.provider.certpath.PKIX.BuilderParams; import sun.security.util.Debug; +import sun.security.util.ObjectIdentifier; import sun.security.x509.AccessDescription; import sun.security.x509.AuthorityInfoAccessExtension; import sun.security.x509.AuthorityKeyIdentifierExtension; +import sun.security.x509.AVA; import static sun.security.x509.PKIXExtensions.*; +import sun.security.x509.RDN; import sun.security.x509.X500Name; import sun.security.x509.X509CertImpl; @@ -60,7 +63,7 @@ import sun.security.x509.X509CertImpl; * @author Yassir Elley * @author Sean Mullan */ -final class ForwardBuilder extends Builder { +public final class ForwardBuilder extends Builder { private static final Debug debug = Debug.getInstance("certpath"); private final Set trustedCerts; @@ -412,29 +415,15 @@ final class ForwardBuilder extends Builder { * 2) Issuer matches a trusted subject * Issuer: ou=D,ou=C,o=B,c=A * - * 3) Issuer is a descendant of a trusted subject (in order of - * number of links to the trusted subject) - * a) Issuer: ou=E,ou=D,ou=C,o=B,c=A [links=1] - * b) Issuer: ou=F,ou=E,ou=D,ou=C,ou=B,c=A [links=2] - * - * 4) Issuer is an ancestor of a trusted subject (in order of number of - * links to the trusted subject) - * a) Issuer: ou=C,o=B,c=A [links=1] - * b) Issuer: o=B,c=A [links=2] - * - * 5) Issuer is in the same namespace as a trusted subject (in order of - * number of links to the trusted subject) + * 3) Issuer is in the same namespace as a trusted subject (in order of + * number of links to the trusted subject). If the last RDN of the + * common ancestor is geographical, then it is skipped and the next + * trusted certificate is checked. * a) Issuer: ou=G,ou=C,o=B,c=A [links=2] * b) Issuer: ou=H,o=B,c=A [links=3] + * c) Issuer: ou=H,o=D,c=A [skipped, only geographical c=A is same] * - * 6) Issuer is an ancestor of certificate subject (in order of number - * of links to the certificate subject) - * a) Issuer: ou=K,o=J,c=A - * Subject: ou=L,ou=K,o=J,c=A - * b) Issuer: o=J,c=A - * Subject: ou=L,ou=K,0=J,c=A - * - * 7) Any other certificates + * 4) Any other certificates */ static class PKIXCertComparator implements Comparator { @@ -471,8 +460,8 @@ final class ForwardBuilder extends Builder { } /** - * @param oCert1 First X509Certificate to be compared - * @param oCert2 Second X509Certificate to be compared + * @param oCert1 first X509Certificate to be compared + * @param oCert2 second X509Certificate to be compared * @return -1 if oCert1 is preferable to oCert2, or * if oCert1 and oCert2 are equally preferable (in this * case it doesn't matter which is preferable, but we don't @@ -482,8 +471,6 @@ final class ForwardBuilder extends Builder { * 0 if oCert1.equals(oCert2). We only return 0 if the * certs are equal so that this comparator behaves * correctly when used in a SortedSet. - * @throws ClassCastException if either argument is not of type - * X509Certificate */ @Override public int compare(X509Certificate oCert1, X509Certificate oCert2) { @@ -503,170 +490,128 @@ final class ForwardBuilder extends Builder { X500Principal cIssuer1 = oCert1.getIssuerX500Principal(); X500Principal cIssuer2 = oCert2.getIssuerX500Principal(); - X500Name cIssuer1Name = X500Name.asX500Name(cIssuer1); - X500Name cIssuer2Name = X500Name.asX500Name(cIssuer2); - - if (debug != null) { - debug.println(METHOD_NME + " o1 Issuer: " + cIssuer1); - debug.println(METHOD_NME + " o2 Issuer: " + cIssuer2); - } /* If one cert's issuer matches a trusted subject, then it is * preferable. */ if (debug != null) { + debug.println(METHOD_NME + " cert1 Issuer: " + cIssuer1); + debug.println(METHOD_NME + " cert2 Issuer: " + cIssuer2); debug.println(METHOD_NME + " MATCH TRUSTED SUBJECT TEST..."); } - boolean m1 = trustedSubjectDNs.contains(cIssuer1); - boolean m2 = trustedSubjectDNs.contains(cIssuer2); - if (debug != null) { - debug.println(METHOD_NME + " m1: " + m1); - debug.println(METHOD_NME + " m2: " + m2); + if (trustedSubjectDNs.contains(cIssuer1)) { + return -1; } - if (m1 && m2) { - return -1; - } else if (m1) { - return -1; - } else if (m2) { + if (trustedSubjectDNs.contains(cIssuer2)) { return 1; } - /* If one cert's issuer is a naming descendant of a trusted subject, - * then it is preferable, in order of increasing naming distance. - */ - if (debug != null) { - debug.println(METHOD_NME + " NAMING DESCENDANT TEST..."); - } - for (X500Principal tSubject : trustedSubjectDNs) { - X500Name tSubjectName = X500Name.asX500Name(tSubject); - int distanceTto1 = - Builder.distance(tSubjectName, cIssuer1Name, -1); - int distanceTto2 = - Builder.distance(tSubjectName, cIssuer2Name, -1); - if (debug != null) { - debug.println(METHOD_NME +" distanceTto1: " + distanceTto1); - debug.println(METHOD_NME +" distanceTto2: " + distanceTto2); - } - if (distanceTto1 > 0 || distanceTto2 > 0) { - // at least one is positive - if (distanceTto2 <= 0) { // only d1 is positive - return -1; - } else if (distanceTto1 <= 0) { // only d2 is positive - return 1; - } else { // all positive - return distanceTto1 > distanceTto2 ? 1 : -1; - } - } - } - - /* If one cert's issuer is a naming ancestor of a trusted subject, - * then it is preferable, in order of increasing naming distance. - */ - if (debug != null) { - debug.println(METHOD_NME + " NAMING ANCESTOR TEST..."); - } - for (X500Principal tSubject : trustedSubjectDNs) { - X500Name tSubjectName = X500Name.asX500Name(tSubject); - - int distanceTto1 = Builder.distance - (tSubjectName, cIssuer1Name, Integer.MAX_VALUE); - int distanceTto2 = Builder.distance - (tSubjectName, cIssuer2Name, Integer.MAX_VALUE); - if (debug != null) { - debug.println(METHOD_NME +" distanceTto1: " + distanceTto1); - debug.println(METHOD_NME +" distanceTto2: " + distanceTto2); - } - if (distanceTto1 < 0 || distanceTto2 < 0) { - // at least one is negative - if (distanceTto2 >= 0) { // only d1 is negative - return -1; - } else if (distanceTto1 >= 0) { // only d2 is negative - return 1; - } else { // all negative - return distanceTto1 < distanceTto2 ? 1 : -1; - } - } - } - /* If one cert's issuer is in the same namespace as a trusted * subject, then it is preferable, in order of increasing naming * distance. */ + String debugMsg = null; if (debug != null) { - debug.println(METHOD_NME +" SAME NAMESPACE AS TRUSTED TEST..."); + debug.println(METHOD_NME + " SAME NAMESPACE AS TRUSTED TEST..."); + debugMsg = METHOD_NME + " distance (number of " + + "RDNs) from cert%1$s issuer to trusted subject %2$s: %3$d"; } + + X500Name cIssuer1Name = X500Name.asX500Name(cIssuer1); + X500Name cIssuer2Name = X500Name.asX500Name(cIssuer2); + // Note that we stop searching if we find a trust anchor that + // has a common non-geographical ancestor on the basis that there + // is a good chance that this path is the one we want. for (X500Principal tSubject : trustedSubjectDNs) { X500Name tSubjectName = X500Name.asX500Name(tSubject); - X500Name tAo1 = tSubjectName.commonAncestor(cIssuer1Name); - X500Name tAo2 = tSubjectName.commonAncestor(cIssuer2Name); + int d1 = distanceToCommonAncestor(tSubjectName, cIssuer1Name); + int d2 = distanceToCommonAncestor(tSubjectName, cIssuer2Name); if (debug != null) { - debug.println(METHOD_NME +" tAo1: " + tAo1); - debug.println(METHOD_NME +" tAo2: " + tAo2); - } - if (tAo1 != null || tAo2 != null) { - if (tAo1 != null && tAo2 != null) { - int hopsTto1 = Builder.hops - (tSubjectName, cIssuer1Name, Integer.MAX_VALUE); - int hopsTto2 = Builder.hops - (tSubjectName, cIssuer2Name, Integer.MAX_VALUE); - if (debug != null) { - debug.println(METHOD_NME +" hopsTto1: " + hopsTto1); - debug.println(METHOD_NME +" hopsTto2: " + hopsTto2); - } - if (hopsTto1 == hopsTto2) { - } else if (hopsTto1 > hopsTto2) { - return 1; - } else { // hopsTto1 < hopsTto2 - return -1; - } - } else if (tAo1 == null) { - return 1; - } else { - return -1; + if (d1 != -1) { + debug.println(String.format(debugMsg, "1", tSubject, d1)); + } + if (d2 != -1) { + debug.println(String.format(debugMsg, "2", tSubject, d2)); } } - } - - - /* If one cert's issuer is an ancestor of that cert's subject, - * then it is preferable, in order of increasing naming distance. - */ - if (debug != null) { - debug.println(METHOD_NME+" CERT ISSUER/SUBJECT COMPARISON TEST..."); - } - X500Principal cSubject1 = oCert1.getSubjectX500Principal(); - X500Principal cSubject2 = oCert2.getSubjectX500Principal(); - X500Name cSubject1Name = X500Name.asX500Name(cSubject1); - X500Name cSubject2Name = X500Name.asX500Name(cSubject2); - - if (debug != null) { - debug.println(METHOD_NME + " o1 Subject: " + cSubject1); - debug.println(METHOD_NME + " o2 Subject: " + cSubject2); - } - int distanceStoI1 = Builder.distance - (cSubject1Name, cIssuer1Name, Integer.MAX_VALUE); - int distanceStoI2 = Builder.distance - (cSubject2Name, cIssuer2Name, Integer.MAX_VALUE); - if (debug != null) { - debug.println(METHOD_NME + " distanceStoI1: " + distanceStoI1); - debug.println(METHOD_NME + " distanceStoI2: " + distanceStoI2); - } - if (distanceStoI2 > distanceStoI1) { - return -1; - } else if (distanceStoI2 < distanceStoI1) { - return 1; + if (d1 == -1 && d2 == -1) { + // neither cert has a common non-geographical ancestor with + // trust anchor, so continue checking other trust anchors + continue; + } + if (d1 != -1) { + if (d2 != -1) { + // both certs share a common non-geographical ancestor + // with trust anchor. Prefer the one that is closer + // to the trust anchor. + return (d1 > d2) ? 1 : -1; + } else { + // cert1 shares a common non-geographical ancestor with + // trust anchor, so it is preferred. + return -1; + } + } else if (d2 != -1) { + // cert2 shares a common non-geographical ancestor with + // trust anchor, so it is preferred. + return 1; + } } /* Otherwise, certs are equally preferable. */ if (debug != null) { - debug.println(METHOD_NME + " no tests matched; RETURN 0"); + debug.println(METHOD_NME + " no tests matched; RETURN -1"); } return -1; } } + /** + * Returns the distance (number of RDNs) from the issuer's DN to the + * common non-geographical ancestor of the trust anchor and issuer's DN. + * + * @param anchor the anchor's DN + * @param issuer the issuer's DN + * @return the distance or -1 if no common ancestor or an attribute of the + * last RDN of the common ancestor is geographical + */ + private static int distanceToCommonAncestor(X500Name anchor, X500Name issuer) { + List anchorRdns = anchor.rdns(); + List issuerRdns = issuer.rdns(); + int minLen = Math.min(anchorRdns.size(), issuerRdns.size()); + if (minLen == 0) { + return -1; + } + + // Compare names from highest RDN down the naming tree. + int i = 0; + for (; i < minLen; i++) { + RDN rdn = anchorRdns.get(i); + if (!rdn.equals(issuerRdns.get(i))) { + if (i == 0) { + return -1; + } else { + break; + } + } + } + + // check if last RDN is geographical + RDN lastRDN = anchorRdns.get(i - 1); + for (AVA ava : lastRDN.avas()) { + ObjectIdentifier oid = ava.getObjectIdentifier(); + if (oid.equals(X500Name.countryName_oid) || + oid.equals(X500Name.stateName_oid) || + oid.equals(X500Name.localityName_oid) || + oid.equals(X500Name.streetAddress_oid)) { + return -1; + } + } + + return issuer.size() - i; + } + /** * Verifies a matching certificate. * diff --git a/src/java.base/share/classes/sun/security/x509/X500Name.java b/src/java.base/share/classes/sun/security/x509/X500Name.java index bf115bf761b..8a407e2263a 100644 --- a/src/java.base/share/classes/sun/security/x509/X500Name.java +++ b/src/java.base/share/classes/sun/security/x509/X500Name.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1996, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1996, 2024, 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,17 +25,14 @@ package sun.security.x509; -import java.lang.reflect.*; import java.io.IOException; -import java.security.PrivilegedExceptionAction; -import java.security.AccessController; import java.security.Principal; import java.util.*; -import java.util.StringJoiner; - -import sun.security.util.*; import javax.security.auth.x500.X500Principal; +import jdk.internal.access.SharedSecrets; +import sun.security.util.*; + /** * Note: As of 1.4, the public class, * javax.security.auth.x500.X500Principal, @@ -1272,120 +1269,22 @@ public class X500Name implements GeneralNameInterface, Principal { return names.length; } - /** - * Return lowest common ancestor of this name and other name - * - * @param other another X500Name - * @return X500Name of lowest common ancestor; null if none - */ - public X500Name commonAncestor(X500Name other) { - - if (other == null) { - return null; - } - int otherLen = other.names.length; - int thisLen = this.names.length; - if (thisLen == 0 || otherLen == 0) { - return null; - } - int minLen = Math.min(thisLen, otherLen); - - //Compare names from highest RDN down the naming tree - //Note that these are stored in RDN[0]... - int i=0; - for (; i < minLen; i++) { - if (!names[i].equals(other.names[i])) { - if (i == 0) { - return null; - } else { - break; - } - } - } - - //Copy matching RDNs into new RDN array - RDN[] ancestor = new RDN[i]; - System.arraycopy(names, 0, ancestor, 0, i); - - X500Name commonAncestor; - try { - commonAncestor = new X500Name(ancestor); - } catch (IOException ioe) { - return null; - } - return commonAncestor; - } - - /** - * Constructor object for use by asX500Principal(). - */ - private static final Constructor principalConstructor; - - /** - * Field object for use by asX500Name(). - */ - private static final Field principalField; - - /** - * Retrieve the Constructor and Field we need for reflective access - * and make them accessible. - */ - static { - PrivilegedExceptionAction pa = - () -> { - Class pClass = X500Principal.class; - Class[] args = new Class[] { X500Name.class }; - Constructor cons = - pClass.getDeclaredConstructor(args); - cons.setAccessible(true); - Field field = pClass.getDeclaredField("thisX500Name"); - field.setAccessible(true); - return new Object[] {cons, field}; - }; - try { - @SuppressWarnings("removal") - Object[] result = AccessController.doPrivileged(pa); - @SuppressWarnings("unchecked") - Constructor constr = - (Constructor)result[0]; - principalConstructor = constr; - principalField = (Field)result[1]; - } catch (Exception e) { - throw new InternalError("Could not obtain X500Principal access", e); - } - } - /** * Get an X500Principal backed by this X500Name. - * - * Note that we are using privileged reflection to access the hidden - * package private constructor in X500Principal. */ public X500Principal asX500Principal() { if (x500Principal == null) { - try { - Object[] args = new Object[] {this}; - x500Principal = principalConstructor.newInstance(args); - } catch (Exception e) { - throw new RuntimeException("Unexpected exception", e); - } + x500Principal = + SharedSecrets.getJavaxSecurityAccess().asX500Principal(this); } return x500Principal; } /** * Get the X500Name contained in the given X500Principal. - * - * Note that the X500Name is retrieved using reflection. */ public static X500Name asX500Name(X500Principal p) { - try { - X500Name name = (X500Name)principalField.get(p); - name.x500Principal = p; - return name; - } catch (Exception e) { - throw new RuntimeException("Unexpected exception", e); - } + return SharedSecrets.getJavaxSecurityAccess().asX500Name(p); } } diff --git a/test/jdk/java/security/testlibrary/CertificateBuilder.java b/test/jdk/java/security/testlibrary/CertificateBuilder.java index df485b06e0c..e1b1211a9d5 100644 --- a/test/jdk/java/security/testlibrary/CertificateBuilder.java +++ b/test/jdk/java/security/testlibrary/CertificateBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2024, 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 @@ -30,6 +30,8 @@ import java.security.cert.X509Certificate; import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; import java.security.cert.Extension; +import java.time.temporal.ChronoUnit; +import java.time.Instant; import javax.security.auth.x500.X500Principal; import java.math.BigInteger; @@ -43,6 +45,7 @@ import sun.security.x509.AuthorityInfoAccessExtension; import sun.security.x509.AuthorityKeyIdentifierExtension; import sun.security.x509.SubjectKeyIdentifierExtension; import sun.security.x509.BasicConstraintsExtension; +import sun.security.x509.CertificateSerialNumber; import sun.security.x509.ExtendedKeyUsageExtension; import sun.security.x509.DNSName; import sun.security.x509.GeneralName; @@ -499,7 +502,9 @@ public class CertificateBuilder { } // Serial Number - SerialNumber sn = new SerialNumber(serialNumber); + CertificateSerialNumber sn = (serialNumber != null) ? + new CertificateSerialNumber(serialNumber) : + CertificateSerialNumber.newRandom64bit(new SecureRandom()); sn.encode(tbsCertItems); // Algorithm ID @@ -516,8 +521,12 @@ public class CertificateBuilder { // Validity period (set as UTCTime) DerOutputStream valSeq = new DerOutputStream(); - valSeq.putUTCTime(notBefore); - valSeq.putUTCTime(notAfter); + Instant now = Instant.now(); + Date startDate = (notBefore != null) ? notBefore : Date.from(now); + valSeq.putUTCTime(startDate); + Date endDate = (notAfter != null) ? notAfter : + Date.from(now.plus(90, ChronoUnit.DAYS)); + valSeq.putUTCTime(endDate); tbsCertItems.write(DerValue.tag_Sequence, valSeq); // Subject Name @@ -557,6 +566,10 @@ public class CertificateBuilder { */ private void encodeExtensions(DerOutputStream tbsStream) throws IOException { + + if (extensions.isEmpty()) { + return; + } DerOutputStream extSequence = new DerOutputStream(); DerOutputStream extItems = new DerOutputStream(); diff --git a/test/jdk/sun/security/provider/certpath/PKIXCertComparator/Order.java b/test/jdk/sun/security/provider/certpath/PKIXCertComparator/Order.java new file mode 100644 index 00000000000..b353a8748c9 --- /dev/null +++ b/test/jdk/sun/security/provider/certpath/PKIXCertComparator/Order.java @@ -0,0 +1,186 @@ +/* + * Copyright (c) 2024, 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8317431 + * @summary Verify order of PKIXCertComparator sorting algorithm + * @modules java.base/sun.security.provider.certpath:+open + * java.base/sun.security.x509 + * java.base/sun.security.util + * @library /test/lib ../../../../../java/security/testlibrary + * @build CertificateBuilder + * @run main Order + */ + +import java.lang.reflect.Constructor; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.cert.X509Certificate; +import java.util.Comparator; +import java.util.Set; +import javax.security.auth.x500.X500Principal; +import sun.security.x509.X509CertImpl; + +import jdk.test.lib.Asserts; +import sun.security.testlibrary.CertificateBuilder; + +public class Order { + + private record CertAndKeyPair(X509Certificate cert, KeyPair keyPair) {} + + private static KeyPairGenerator kpg; + + public static void main(String[] args) throws Exception { + kpg = KeyPairGenerator.getInstance("RSA"); + kpg.initialize(2048); + + // Create top-level root CA cert with KIDs (Subject and Auth KeyIds) + // A root CA doesn't usually have an Auth KeyId but for this test, + // it doesn't matter. + CertAndKeyPair rootCA = + createCert(null, "CN=Root CA, O=Java, C=US", true, true); + System.out.println(rootCA.cert); + + // Create intermediate CA cert with KIDs, issued by root CA + CertAndKeyPair javaCA = + createCert(rootCA, "CN=Java CA, O=Java, C=US", true, true); + System.out.println(javaCA.cert); + + // Create intermediate CA cert without KIDs, issued by root CA. + // This CA has the same DN/public key as the CA with KIDs. + CertAndKeyPair javaCAWoKids = createCert(rootCA, + "CN=Java CA, O=Java, C=US", true, false, javaCA.keyPair); + System.out.println(javaCAWoKids.cert); + + // Create another intermediate CA cert without KIDs, issued by root CA. + CertAndKeyPair openJDKCAWoKids = createCert(rootCA, + "CN=OpenJDK CA, O=OpenJDK, C=US", true, false); + System.out.println(openJDKCAWoKids.cert); + + // Create another intermediate CA with KIDs, issued by Java CA + CertAndKeyPair secCA = createCert(javaCAWoKids, + "CN=Security CA, OU=Security, O=Java, C=US", true, true); + System.out.println(secCA.cert); + + // Cross certify Java CA with OpenJDK CA + CertAndKeyPair javaCAIssuedByOpenJDKCA = createCert(openJDKCAWoKids, + "CN=Java CA, O=Java, C=US", true, false, javaCA.keyPair); + System.out.println(javaCAIssuedByOpenJDKCA.cert); + + // Cross certify Security CA with OpenJDK CA + CertAndKeyPair secCAIssuedByOpenJDKCA = createCert(openJDKCAWoKids, + "CN=Security CA, OU=Security, O=Java, C=US", true, false, secCA.keyPair); + System.out.println(secCAIssuedByOpenJDKCA.cert); + + // Create end entity cert without KIDs issued by Security CA. + CertAndKeyPair ee = createCert(secCA, + "CN=EE, OU=Security, O=Java, C=US", false, false); + System.out.println(ee.cert); + + // Create another end entity cert without KIDs issued by Java CA. + // This EE has the same DN/public key as the one above. + CertAndKeyPair eeIssuedByJavaCA = createCert(javaCA, + "CN=EE, OU=Security, O=Java, C=US", false, false); + System.out.println(eeIssuedByJavaCA.cert); + + Constructor ctor = getPKIXCertComparatorCtor(); + Set trustedSubjects = + Set.of(new X500Principal("CN=Root CA, O=Java, C=US")); + + System.out.println("Test that equal certs are treated the same"); + Comparator c = (Comparator) ctor.newInstance(trustedSubjects, + secCA.cert); + Asserts.assertTrue(c.compare(javaCA.cert, javaCA.cert) == 0); + + System.out.println("Test that cert with matching kids is preferred"); + Asserts.assertTrue(c.compare(javaCA.cert, javaCAWoKids.cert) == -1); + Asserts.assertTrue(c.compare(javaCAWoKids.cert, javaCA.cert) == 1); + + System.out.println("Test that cert issued by anchor is preferred"); + Asserts.assertTrue( + c.compare(javaCAWoKids.cert, javaCAIssuedByOpenJDKCA.cert) == -1); + Asserts.assertTrue( + c.compare(javaCAIssuedByOpenJDKCA.cert, javaCAWoKids.cert) == 1); + + System.out.println( + "Test that cert issuer in same namespace as anchor is preferred"); + c = (Comparator) ctor.newInstance(trustedSubjects, ee.cert); + Asserts.assertTrue( + c.compare(secCA.cert, secCAIssuedByOpenJDKCA.cert) == -1); + Asserts.assertTrue( + c.compare(secCAIssuedByOpenJDKCA.cert, secCA.cert) == 1); + + System.out.println( + "Test cert issuer in same namespace closest to root is preferred"); + Asserts.assertTrue(c.compare(eeIssuedByJavaCA.cert, ee.cert) == -1); + Asserts.assertTrue(c.compare(ee.cert, eeIssuedByJavaCA.cert) == 1); + } + + private static boolean[] CA_KEY_USAGE = + new boolean[] {true,false,false,false,false,true,true,false,false}; + private static boolean[] EE_KEY_USAGE = + new boolean[] {true,false,false,false,false,false,false,false,false}; + + private static CertAndKeyPair createCert(CertAndKeyPair issuer, + String subjectDn, boolean ca, boolean kids) throws Exception { + + KeyPair kp = kpg.generateKeyPair(); + return createCert(issuer, subjectDn, ca, kids, kp); + } + + private static CertAndKeyPair createCert(CertAndKeyPair issuer, + String subjectDn, boolean ca, boolean kids, KeyPair kp) + throws Exception { + + if (issuer == null) { + issuer = new CertAndKeyPair(null, kp); + } + CertificateBuilder cb = new CertificateBuilder() + .setSubjectName(subjectDn) + .setPublicKey(kp.getPublic()); + + if (ca) { + cb = cb.addBasicConstraintsExt(true, true, -1) + .addKeyUsageExt(CA_KEY_USAGE); + } else { + cb = cb.addBasicConstraintsExt(true, false, -1) + .addKeyUsageExt(EE_KEY_USAGE); + } + if (kids) { + cb = cb.addAuthorityKeyIdExt(issuer.keyPair.getPublic()) + .addSubjectKeyIdExt(kp.getPublic()); + } + X509Certificate cert = + cb.build(issuer.cert, issuer.keyPair.getPrivate(), "SHA256withRSA"); + return new CertAndKeyPair(cert, kp); + } + + private static Constructor getPKIXCertComparatorCtor() throws Exception { + var cl = Class.forName( + "sun.security.provider.certpath.ForwardBuilder$PKIXCertComparator"); + var c = cl.getDeclaredConstructor(Set.class, X509CertImpl.class); + c.setAccessible(true); + return c; + } +}