diff --git a/src/java.base/share/classes/sun/security/ssl/SunX509KeyManagerImpl.java b/src/java.base/share/classes/sun/security/ssl/SunX509KeyManagerImpl.java index 80eb70984254a..2441ad91fde19 100644 --- a/src/java.base/share/classes/sun/security/ssl/SunX509KeyManagerImpl.java +++ b/src/java.base/share/classes/sun/security/ssl/SunX509KeyManagerImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1999, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1999, 2025, 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 @@ -26,6 +26,7 @@ package sun.security.ssl; import java.net.Socket; +import java.security.AlgorithmConstraints; import java.security.Key; import java.security.KeyStore; import java.security.KeyStoreException; @@ -39,85 +40,58 @@ import java.util.Collections; import java.util.Enumeration; import java.util.HashMap; -import java.util.HashSet; import java.util.List; -import java.util.Locale; import java.util.Map; import java.util.Set; +import javax.net.ssl.SNIServerName; import javax.net.ssl.SSLEngine; -import javax.net.ssl.X509ExtendedKeyManager; import javax.security.auth.x500.X500Principal; /** * An implementation of X509KeyManager backed by a KeyStore. - * + *

* The backing KeyStore is inspected when this object is constructed. * All key entries containing a PrivateKey and a non-empty chain of * X509Certificate are then copied into an internal store. This means * that subsequent modifications of the KeyStore have no effect on the * X509KeyManagerImpl object. - * + *

* Note that this class assumes that all keys are protected by the same * password. - * - * The JSSE handshake code currently calls into this class via - * chooseClientAlias() and chooseServerAlias() to find the certificates to - * use. As implemented here, both always return the first alias returned by - * getClientAliases() and getServerAliases(). In turn, these methods are - * implemented by calling getAliases(), which performs the actual lookup. - * - * Note that this class currently implements no checking of the local - * certificates. In particular, it is *not* guaranteed that: - * . the certificates are within their validity period and not revoked - * . the signatures verify - * . they form a PKIX compliant chain. - * . the certificate extensions allow the certificate to be used for - * the desired purpose. - * - * Chains that fail any of these criteria will probably be rejected by - * the remote peer. + *

+ * Algorithm constraints and certificate checks can be disabled by setting + * "jdk.tls.SunX509KeyManager.certChecking" system property to "false" + * before calling a class constructor. * */ -final class SunX509KeyManagerImpl extends X509ExtendedKeyManager { - private static final String[] STRING0 = new String[0]; +final class SunX509KeyManagerImpl extends X509KeyManagerCertChecking { /* * The credentials from the KeyStore as * Map: String(alias) -> X509Credentials(credentials) */ - private final Map credentialsMap; + private final Map credentialsMap; - /* - * Cached server aliases for the case issuers == null. - * (in the current JSSE implementation, issuers are always null for - * server certs). See chooseServerAlias() for details. - * - * Map: String(keyType) -> String[](alias) - */ - private final Map serverAliasCache; + @Override + protected boolean isCheckingDisabled() { + return "false".equalsIgnoreCase(System.getProperty( + "jdk.tls.SunX509KeyManager.certChecking", "true")); + } /* * Basic container for credentials implemented as an inner class. */ private static class X509Credentials { + final PrivateKey privateKey; final X509Certificate[] certificates; - private final Set issuerX500Principals; X509Credentials(PrivateKey privateKey, X509Certificate[] certificates) { // assert privateKey and certificates != null this.privateKey = privateKey; this.certificates = certificates; - this.issuerX500Principals = HashSet.newHashSet(certificates.length); - for (X509Certificate certificate : certificates) { - issuerX500Principals.add(certificate.getIssuerX500Principal()); - } - } - - Set getIssuerX500Principals() { - return issuerX500Principals; } } @@ -126,14 +100,13 @@ Set getIssuerX500Principals() { NoSuchAlgorithmException, UnrecoverableKeyException { credentialsMap = new HashMap<>(); - serverAliasCache = Collections.synchronizedMap( - new HashMap<>()); + if (ks == null) { return; } for (Enumeration aliases = ks.aliases(); - aliases.hasMoreElements(); ) { + aliases.hasMoreElements(); ) { String alias = aliases.nextElement(); if (!ks.isKeyEntry(alias)) { continue; @@ -153,11 +126,11 @@ Set getIssuerX500Principals() { certs = tmp; } - X509Credentials cred = new X509Credentials((PrivateKey)key, - (X509Certificate[])certs); + X509Credentials cred = new X509Credentials((PrivateKey) key, + (X509Certificate[]) certs); credentialsMap.put(alias, cred); if (SSLLogger.isOn && SSLLogger.isOn("keymanager")) { - SSLLogger.fine("found key for : " + alias, (Object[])certs); + SSLLogger.fine("found key for : " + alias, (Object[]) certs); } } } @@ -205,24 +178,8 @@ public PrivateKey getPrivateKey(String alias) { @Override public String chooseClientAlias(String[] keyTypes, Principal[] issuers, Socket socket) { - /* - * We currently don't do anything with socket, but - * someday we might. It might be a useful hint for - * selecting one of the aliases we get back from - * getClientAliases(). - */ - - if (keyTypes == null) { - return null; - } - - for (int i = 0; i < keyTypes.length; i++) { - String[] aliases = getClientAliases(keyTypes[i], issuers); - if ((aliases != null) && (aliases.length > 0)) { - return aliases[0]; - } - } - return null; + return chooseAlias(getKeyTypes(keyTypes), issuers, CheckType.CLIENT, + getAlgorithmConstraints(socket), null, null); } /* @@ -230,17 +187,12 @@ public String chooseClientAlias(String[] keyTypes, Principal[] issuers, * SSLEngine connection given the public key type * and the list of certificate issuer authorities recognized by * the peer (if any). - * - * @since 1.5 */ @Override - public String chooseEngineClientAlias(String[] keyType, + public String chooseEngineClientAlias(String[] keyTypes, Principal[] issuers, SSLEngine engine) { - /* - * If we ever start using socket as a selection criteria, - * we'll need to adjust this. - */ - return chooseClientAlias(keyType, issuers, null); + return chooseAlias(getKeyTypes(keyTypes), issuers, CheckType.CLIENT, + getAlgorithmConstraints(engine), null, null); } /* @@ -251,35 +203,9 @@ public String chooseEngineClientAlias(String[] keyType, @Override public String chooseServerAlias(String keyType, Principal[] issuers, Socket socket) { - /* - * We currently don't do anything with socket, but - * someday we might. It might be a useful hint for - * selecting one of the aliases we get back from - * getServerAliases(). - */ - if (keyType == null) { - return null; - } - - String[] aliases; - - if (issuers == null || issuers.length == 0) { - aliases = serverAliasCache.get(keyType); - if (aliases == null) { - aliases = getServerAliases(keyType, issuers); - // Cache the result (positive and negative lookups) - if (aliases == null) { - aliases = STRING0; - } - serverAliasCache.put(keyType, aliases); - } - } else { - aliases = getServerAliases(keyType, issuers); - } - if ((aliases != null) && (aliases.length > 0)) { - return aliases[0]; - } - return null; + return chooseAlias(getKeyTypes(keyType), issuers, CheckType.SERVER, + getAlgorithmConstraints(socket), + X509TrustManagerImpl.getRequestedServerNames(socket), "HTTPS"); } /* @@ -287,17 +213,13 @@ public String chooseServerAlias(String keyType, * SSLEngine connection given the public key type * and the list of certificate issuer authorities recognized by * the peer (if any). - * - * @since 1.5 */ @Override public String chooseEngineServerAlias(String keyType, Principal[] issuers, SSLEngine engine) { - /* - * If we ever start using socket as a selection criteria, - * we'll need to adjust this. - */ - return chooseServerAlias(keyType, issuers, null); + return chooseAlias(getKeyTypes(keyType), issuers, CheckType.SERVER, + getAlgorithmConstraints(engine), + X509TrustManagerImpl.getRequestedServerNames(engine), "HTTPS"); } /* @@ -307,7 +229,8 @@ public String chooseEngineServerAlias(String keyType, */ @Override public String[] getClientAliases(String keyType, Principal[] issuers) { - return getAliases(keyType, issuers); + return getAliases(getKeyTypes(keyType), issuers, CheckType.CLIENT, + null, null, null); } /* @@ -317,7 +240,23 @@ public String[] getClientAliases(String keyType, Principal[] issuers) { */ @Override public String[] getServerAliases(String keyType, Principal[] issuers) { - return getAliases(keyType, issuers); + return getAliases(getKeyTypes(keyType), issuers, CheckType.SERVER, + null, null, null); + } + + private String chooseAlias(List keyTypes, Principal[] issuers, + CheckType checkType, AlgorithmConstraints constraints, + List requestedServerNames, String idAlgorithm) { + + String[] aliases = getAliases( + keyTypes, issuers, checkType, + constraints, requestedServerNames, idAlgorithm); + + if (aliases != null && aliases.length > 0) { + return aliases[0]; + } + + return null; } /* @@ -327,103 +266,46 @@ public String[] getServerAliases(String keyType, Principal[] issuers) { * * Issuers come to us in the form of X500Principal[]. */ - private String[] getAliases(String keyType, Principal[] issuers) { - if (keyType == null) { + private String[] getAliases(List keyTypes, Principal[] issuers, + CheckType checkType, AlgorithmConstraints constraints, + List requestedServerNames, + String idAlgorithm) { + + if (keyTypes == null || keyTypes.isEmpty()) { return null; } - if (issuers == null) { - issuers = new X500Principal[0]; - } - if (!(issuers instanceof X500Principal[])) { - // normally, this will never happen but try to recover if it does - issuers = convertPrincipals(issuers); - } - String sigType; - if (keyType.contains("_")) { - int k = keyType.indexOf('_'); - sigType = keyType.substring(k + 1); - keyType = keyType.substring(0, k); - } else { - sigType = null; - } - X500Principal[] x500Issuers = (X500Principal[])issuers; - // the algorithm below does not produce duplicates, so avoid Set - List aliases = new ArrayList<>(); + Set issuerSet = getIssuerSet(issuers); + List results = null; - for (Map.Entry entry : - credentialsMap.entrySet()) { + for (Map.Entry entry : + credentialsMap.entrySet()) { - String alias = entry.getKey(); - X509Credentials credentials = entry.getValue(); - X509Certificate[] certs = credentials.certificates; + EntryStatus status = checkAlias(0, entry.getKey(), + entry.getValue().certificates, + null, keyTypes, issuerSet, checkType, + constraints, requestedServerNames, idAlgorithm); - if (!keyType.equals(certs[0].getPublicKey().getAlgorithm())) { + if (status == null) { continue; } - if (sigType != null) { - if (certs.length > 1) { - // if possible, check the public key in the issuer cert - if (!sigType.equals( - certs[1].getPublicKey().getAlgorithm())) { - continue; - } - } else { - // Check the signature algorithm of the certificate itself. - // Look for the "withRSA" in "SHA1withRSA", etc. - String sigAlgName = - certs[0].getSigAlgName().toUpperCase(Locale.ENGLISH); - String pattern = "WITH" + - sigType.toUpperCase(Locale.ENGLISH); - if (!sigAlgName.contains(pattern)) { - continue; - } - } - } - if (issuers.length == 0) { - // no issuer specified, match all - aliases.add(alias); - if (SSLLogger.isOn && SSLLogger.isOn("keymanager")) { - SSLLogger.fine("matching alias: " + alias); - } - } else { - Set certIssuers = - credentials.getIssuerX500Principals(); - for (int i = 0; i < x500Issuers.length; i++) { - if (certIssuers.contains(issuers[i])) { - aliases.add(alias); - if (SSLLogger.isOn && SSLLogger.isOn("keymanager")) { - SSLLogger.fine("matching alias: " + alias); - } - break; - } - } + if (results == null) { + results = new ArrayList<>(); } - } - String[] aliasStrings = aliases.toArray(STRING0); - return ((aliasStrings.length == 0) ? null : aliasStrings); - } + results.add(status); + } - /* - * Convert an array of Principals to an array of X500Principals, if - * possible. Principals that cannot be converted are ignored. - */ - private static X500Principal[] convertPrincipals(Principal[] principals) { - List list = new ArrayList<>(principals.length); - for (int i = 0; i < principals.length; i++) { - Principal p = principals[i]; - if (p instanceof X500Principal) { - list.add((X500Principal)p); - } else { - try { - list.add(new X500Principal(p.getName())); - } catch (IllegalArgumentException e) { - // ignore - } + if (results == null) { + if (SSLLogger.isOn && SSLLogger.isOn("keymanager")) { + SSLLogger.fine("KeyMgr: no matching key found"); } + return null; } - return list.toArray(new X500Principal[0]); + + // Sort results in order of alias preference. + Collections.sort(results); + return results.stream().map(r -> r.alias).toArray(String[]::new); } } diff --git a/src/java.base/share/classes/sun/security/ssl/X509KeyManagerCertChecking.java b/src/java.base/share/classes/sun/security/ssl/X509KeyManagerCertChecking.java new file mode 100644 index 0000000000000..00a7ae8435239 --- /dev/null +++ b/src/java.base/share/classes/sun/security/ssl/X509KeyManagerCertChecking.java @@ -0,0 +1,579 @@ +/* + * Copyright (c) 2025, 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 sun.security.ssl; + +import java.net.Socket; +import java.security.AlgorithmConstraints; +import java.security.Principal; +import java.security.cert.CertPathValidatorException; +import java.security.cert.Certificate; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Set; +import javax.net.ssl.ExtendedSSLSession; +import javax.net.ssl.SNIHostName; +import javax.net.ssl.SNIServerName; +import javax.net.ssl.SSLEngine; +import javax.net.ssl.SSLSession; +import javax.net.ssl.SSLSocket; +import javax.net.ssl.StandardConstants; +import javax.net.ssl.X509ExtendedKeyManager; +import javax.security.auth.x500.X500Principal; +import sun.security.provider.certpath.AlgorithmChecker; +import sun.security.util.KnownOIDs; +import sun.security.validator.Validator; + +/* + * Layer that adds algorithm constraints and certificate checking functionality + * to a key manager: + * 1) Check against peer supported certificate signature algorithms (sent with + * "signature_algorithms_cert" TLS extension). + * 2) Check against local TLS algorithm constraints ("java.security" config + * file). + * 3) Mark alias results based on validity period and certificate extensions, + * so results can be sorted to find the best match. See "CheckResult" and + * "EntryStatus" for details. + */ + +abstract class X509KeyManagerCertChecking extends X509ExtendedKeyManager { + + // Indicates whether we should skip the certificate checks. + private final boolean checksDisabled; + + protected X509KeyManagerCertChecking() { + checksDisabled = isCheckingDisabled(); + } + + abstract boolean isCheckingDisabled(); + + // Entry point to do all certificate checks. + protected EntryStatus checkAlias(int keyStoreIndex, String alias, + Certificate[] chain, Date verificationDate, List keyTypes, + Set issuerSet, CheckType checkType, + AlgorithmConstraints constraints, + List requestedServerNames, String idAlgorithm) { + + // --- Mandatory checks --- + + if ((chain == null) || (chain.length == 0)) { + return null; + } + + for (Certificate cert : chain) { + if (!(cert instanceof X509Certificate)) { + // Not an X509Certificate, ignore this alias + return null; + } + } + + // Check key type, get key type index. + int keyIndex = -1; + int j = 0; + + for (KeyType keyType : keyTypes) { + if (keyType.matches(chain)) { + keyIndex = j; + break; + } + j++; + } + + if (keyIndex == -1) { + if (SSLLogger.isOn && SSLLogger.isOn("keymanager")) { + SSLLogger.fine("Ignore alias " + alias + + ": key algorithm does not match"); + } + return null; + } + + // Check issuers + if (issuerSet != null && !issuerSet.isEmpty()) { + boolean found = false; + for (Certificate cert : chain) { + X509Certificate xcert = (X509Certificate) cert; + if (issuerSet.contains(xcert.getIssuerX500Principal())) { + found = true; + break; + } + } + if (!found) { + if (SSLLogger.isOn && SSLLogger.isOn("keymanager")) { + SSLLogger.fine( + "Ignore alias " + alias + + ": issuers do not match"); + } + return null; + } + } + + // --- Optional checks, depending on "checksDisabled" toggle --- + + // Check the algorithm constraints + if (constraints != null && + !conformsToAlgorithmConstraints(constraints, chain, + checkType.getValidator())) { + + if (SSLLogger.isOn && SSLLogger.isOn("keymanager")) { + SSLLogger.fine("Ignore alias " + alias + + ": certificate chain does not conform to " + + "algorithm constraints"); + } + return null; + } + + // Endpoint certificate check + CheckResult checkResult = certificateCheck(checkType, + (X509Certificate) chain[0], + verificationDate == null ? new Date() : verificationDate, + requestedServerNames, idAlgorithm); + + return new EntryStatus( + keyStoreIndex, keyIndex, alias, chain, checkResult); + } + + // Gets algorithm constraints of the socket. + protected AlgorithmConstraints getAlgorithmConstraints(Socket socket) { + + if (checksDisabled) { + return null; + } + + if (socket != null && socket.isConnected() && + socket instanceof SSLSocket sslSocket) { + + SSLSession session = sslSocket.getHandshakeSession(); + + if (session != null) { + if (ProtocolVersion.useTLS12PlusSpec(session.getProtocol())) { + String[] peerSupportedSignAlgs = null; + + if (session instanceof ExtendedSSLSession extSession) { + // Peer supported certificate signature algorithms + // sent with "signature_algorithms_cert" TLS extension. + peerSupportedSignAlgs = + extSession.getPeerSupportedSignatureAlgorithms(); + } + + return SSLAlgorithmConstraints.forSocket( + sslSocket, peerSupportedSignAlgs, true); + } + } + + return SSLAlgorithmConstraints.forSocket(sslSocket, true); + } + + return SSLAlgorithmConstraints.DEFAULT; + } + + // Gets algorithm constraints of the engine. + protected AlgorithmConstraints getAlgorithmConstraints(SSLEngine engine) { + + if (checksDisabled) { + return null; + } + + if (engine != null) { + SSLSession session = engine.getHandshakeSession(); + if (session != null) { + if (ProtocolVersion.useTLS12PlusSpec(session.getProtocol())) { + String[] peerSupportedSignAlgs = null; + + if (session instanceof ExtendedSSLSession extSession) { + // Peer supported certificate signature algorithms + // sent with "signature_algorithms_cert" TLS extension. + peerSupportedSignAlgs = + extSession.getPeerSupportedSignatureAlgorithms(); + } + + return SSLAlgorithmConstraints.forEngine( + engine, peerSupportedSignAlgs, true); + } + } + } + + return SSLAlgorithmConstraints.forEngine(engine, true); + } + + // Algorithm constraints check. + private boolean conformsToAlgorithmConstraints( + AlgorithmConstraints constraints, Certificate[] chain, + String variant) { + + if (checksDisabled) { + return true; + } + + AlgorithmChecker checker = new AlgorithmChecker(constraints, variant); + try { + checker.init(false); + } catch (CertPathValidatorException cpve) { + // unlikely to happen + if (SSLLogger.isOn && SSLLogger.isOn("keymanager")) { + SSLLogger.fine( + "Cannot initialize algorithm constraints checker", + cpve); + } + + return false; + } + + // It is a forward checker, so we need to check from trust to target. + for (int i = chain.length - 1; i >= 0; i--) { + Certificate cert = chain[i]; + try { + // We don't care about the unresolved critical extensions. + checker.check(cert, Collections.emptySet()); + } catch (CertPathValidatorException cpve) { + if (SSLLogger.isOn && SSLLogger.isOn("keymanager")) { + SSLLogger.fine("Certificate does not conform to " + + "algorithm constraints", cert, cpve); + } + + return false; + } + } + + return true; + } + + // Certificate check. + private CheckResult certificateCheck( + CheckType checkType, X509Certificate cert, Date date, + List serverNames, String idAlgorithm) { + return checksDisabled ? CheckResult.OK + : checkType.check(cert, date, serverNames, idAlgorithm); + } + + // enum for the result of the extension check + // NOTE: the order of the constants is important as they are used + // for sorting, i.e. OK is best, followed by EXPIRED and EXTENSION_MISMATCH + enum CheckResult { + OK, // ok or not checked + INSENSITIVE, // server name indication insensitive + EXPIRED, // extensions valid but cert expired + EXTENSION_MISMATCH, // extensions invalid (expiration not checked) + } + + // enum for the type of certificate check we want to perform + // (client or server) + // also includes the check code itself + enum CheckType { + + // enum constant for "no check" (currently not used) + NONE(Collections.emptySet()), + + // enum constant for "tls client" check + // valid EKU for TLS client: any, tls_client + CLIENT(new HashSet<>(List.of( + KnownOIDs.anyExtendedKeyUsage.value(), + KnownOIDs.clientAuth.value() + ))), + + // enum constant for "tls server" check + // valid EKU for TLS server: any, tls_server, ns_sgc, ms_sgc + SERVER(new HashSet<>(List.of( + KnownOIDs.anyExtendedKeyUsage.value(), + KnownOIDs.serverAuth.value(), + KnownOIDs.NETSCAPE_ExportApproved.value(), + KnownOIDs.MICROSOFT_ExportApproved.value() + ))); + + // set of valid EKU values for this type + final Set validEku; + + CheckType(Set validEku) { + this.validEku = validEku; + } + + private static boolean getBit(boolean[] keyUsage, int bit) { + return (bit < keyUsage.length) && keyUsage[bit]; + } + + // Check if this certificate is appropriate for this type of use. + // First check extensions, if they match then check expiration. + // NOTE: `conformsToAlgorithmConstraints` call above also does some + // basic keyUsage checks. + CheckResult check(X509Certificate cert, Date date, + List serverNames, String idAlgorithm) { + + if (this == NONE) { + return CheckResult.OK; + } + + // check extensions + try { + // check extended key usage + List certEku = cert.getExtendedKeyUsage(); + if ((certEku != null) && + Collections.disjoint(validEku, certEku)) { + // if extension is present and does not contain any of + // the valid EKU OIDs, return extension_mismatch + return CheckResult.EXTENSION_MISMATCH; + } + + // check key usage + boolean[] ku = cert.getKeyUsage(); + if (ku != null) { + String algorithm = cert.getPublicKey().getAlgorithm(); + boolean supportsDigitalSignature = getBit(ku, 0); + switch (algorithm) { + case "RSA": + // require either signature bit + // or if server also allow key encipherment bit + if (!supportsDigitalSignature) { + if (this == CLIENT || !getBit(ku, 2)) { + return CheckResult.EXTENSION_MISMATCH; + } + } + break; + case "RSASSA-PSS": + if (!supportsDigitalSignature && (this == SERVER)) { + return CheckResult.EXTENSION_MISMATCH; + } + break; + case "DSA": + // require signature bit + if (!supportsDigitalSignature) { + return CheckResult.EXTENSION_MISMATCH; + } + break; + case "DH": + // require key agreement bit + if (!getBit(ku, 4)) { + return CheckResult.EXTENSION_MISMATCH; + } + break; + case "EC": + // require signature bit + if (!supportsDigitalSignature) { + return CheckResult.EXTENSION_MISMATCH; + } + // For servers, also require key agreement. + // This is not totally accurate as the keyAgreement + // bit is only necessary for static ECDH key + // exchange and not ephemeral ECDH. We leave it in + // for now until there are signs that this check + // causes problems for real world EC certificates. + if (this == SERVER && !getBit(ku, 4)) { + return CheckResult.EXTENSION_MISMATCH; + } + break; + } + } + } catch (CertificateException e) { + // extensions unparseable, return failure + return CheckResult.EXTENSION_MISMATCH; + } + + try { + cert.checkValidity(date); + } catch (CertificateException e) { + return CheckResult.EXPIRED; + } + + if (serverNames != null && !serverNames.isEmpty()) { + for (SNIServerName serverName : serverNames) { + if (serverName.getType() == + StandardConstants.SNI_HOST_NAME) { + if (!(serverName instanceof SNIHostName)) { + try { + serverName = new SNIHostName( + serverName.getEncoded()); + } catch (IllegalArgumentException iae) { + // unlikely to happen, just in case ... + if (SSLLogger.isOn && + SSLLogger.isOn("keymanager")) { + SSLLogger.fine("Illegal server name: " + + serverName); + } + + return CheckResult.INSENSITIVE; + } + } + String hostname = + ((SNIHostName) serverName).getAsciiName(); + + try { + X509TrustManagerImpl.checkIdentity(hostname, + cert, idAlgorithm); + } catch (CertificateException e) { + if (SSLLogger.isOn && + SSLLogger.isOn("keymanager")) { + SSLLogger.fine( + "Certificate identity does not match " + + "Server Name Indication (SNI): " + + hostname); + } + return CheckResult.INSENSITIVE; + } + + break; + } + } + } + + return CheckResult.OK; + } + + String getValidator() { + if (this == CLIENT) { + return Validator.VAR_TLS_CLIENT; + } else if (this == SERVER) { + return Validator.VAR_TLS_SERVER; + } + return Validator.VAR_GENERIC; + } + } + + // A candidate match. + // Identifies the entry by key store index and alias + // and includes the result of the certificate check. + protected static class EntryStatus implements Comparable { + + final int keyStoreIndex; + final int keyIndex; + final String alias; + final CheckResult checkResult; + + EntryStatus(int keyStoreIndex, int keyIndex, String alias, + Certificate[] chain, CheckResult checkResult) { + this.keyStoreIndex = keyStoreIndex; + this.keyIndex = keyIndex; + this.alias = alias; + this.checkResult = checkResult; + } + + @Override + public int compareTo(EntryStatus other) { + int result = this.checkResult.compareTo(other.checkResult); + return (result == 0) ? (this.keyIndex - other.keyIndex) : result; + } + + @Override + public String toString() { + String s = alias + " (verified: " + checkResult + ")"; + if (keyStoreIndex == 0) { + return s; + } else { + return "KeyStore #" + keyStoreIndex + ", alias: " + s; + } + } + } + + // Class to help verify that the public key algorithm (and optionally + // the signature algorithm) of a certificate matches what we need. + protected static class KeyType { + + final String keyAlgorithm; + + // In TLS 1.2, the signature algorithm has been obsoleted by the + // supported_signature_algorithms, and the certificate type no longer + // restricts the algorithm used to sign the certificate. + // + // However, because we don't support certificate type checking other + // than rsa_sign, dss_sign and ecdsa_sign, we don't have to check the + // protocol version here. + final String sigKeyAlgorithm; + + KeyType(String algorithm) { + int k = algorithm.indexOf('_'); + if (k == -1) { + keyAlgorithm = algorithm; + sigKeyAlgorithm = null; + } else { + keyAlgorithm = algorithm.substring(0, k); + sigKeyAlgorithm = algorithm.substring(k + 1); + } + } + + boolean matches(Certificate[] chain) { + if (!chain[0].getPublicKey().getAlgorithm().equals(keyAlgorithm)) { + return false; + } + if (sigKeyAlgorithm == null) { + return true; + } + if (chain.length > 1) { + // if possible, check the public key in the issuer cert + return sigKeyAlgorithm.equals( + chain[1].getPublicKey().getAlgorithm()); + } else { + // Check the signature algorithm of the certificate itself. + // Look for the "withRSA" in "SHA1withRSA", etc. + X509Certificate issuer = (X509Certificate) chain[0]; + String sigAlgName = + issuer.getSigAlgName().toUpperCase(Locale.ENGLISH); + String pattern = + "WITH" + sigKeyAlgorithm.toUpperCase(Locale.ENGLISH); + return sigAlgName.endsWith(pattern); + } + } + } + + // Make a list of key types. + protected static List getKeyTypes(String... keyTypes) { + if ((keyTypes == null) || + (keyTypes.length == 0) || (keyTypes[0] == null)) { + return null; + } + List list = new ArrayList<>(keyTypes.length); + for (String keyType : keyTypes) { + list.add(new KeyType(keyType)); + } + return list; + } + + // Make a set out of the array. + protected static Set getIssuerSet(Principal[] issuers) { + + if (issuers != null && issuers.length != 0) { + Set ret = new HashSet<>(issuers.length); + + for (Principal p : issuers) { + if (p instanceof X500Principal) { + ret.add((X500Principal) p); + } else { + // Normally, this will never happen but try to recover if + // it does. + try { + ret.add(new X500Principal(p.getName())); + } catch (Exception e) { + // ignore + } + } + } + return ret.isEmpty() ? null : ret; + } else { + return null; + } + } +} diff --git a/src/java.base/share/classes/sun/security/ssl/X509KeyManagerImpl.java b/src/java.base/share/classes/sun/security/ssl/X509KeyManagerImpl.java index fffbddf7d121c..df6ecaf7a4241 100644 --- a/src/java.base/share/classes/sun/security/ssl/X509KeyManagerImpl.java +++ b/src/java.base/share/classes/sun/security/ssl/X509KeyManagerImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2004, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2004, 2025, 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 @@ -32,18 +32,14 @@ import java.security.KeyStore.Builder; import java.security.KeyStore.Entry; import java.security.KeyStore.PrivateKeyEntry; +import java.security.KeyStoreException; import java.security.Principal; import java.security.PrivateKey; -import java.security.cert.CertPathValidatorException; -import java.security.cert.Certificate; -import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.util.*; import java.util.concurrent.atomic.AtomicLong; import javax.net.ssl.*; -import sun.security.provider.certpath.AlgorithmChecker; -import sun.security.validator.Validator; -import sun.security.util.KnownOIDs; +import javax.security.auth.x500.X500Principal; /** * The new X509 key manager implementation. The main differences to the @@ -62,8 +58,8 @@ * * @author Andreas Sterbenz */ -final class X509KeyManagerImpl extends X509ExtendedKeyManager - implements X509KeyManager { + +final class X509KeyManagerImpl extends X509KeyManagerCertChecking { // for unit testing only, set via privileged reflection private static Date verificationDate; @@ -84,12 +80,15 @@ final class X509KeyManagerImpl extends X509ExtendedKeyManager X509KeyManagerImpl(List builders) { this.builders = builders; uidCounter = new AtomicLong(); - entryCacheMap = Collections.synchronizedMap - (new SizedMap<>()); + entryCacheMap = Collections.synchronizedMap(new SizedMap<>()); } - // LinkedHashMap with a max size of 10 - // see LinkedHashMap JavaDocs + @Override + protected boolean isCheckingDisabled() { + return false; + } + + // LinkedHashMap with a max size of 10, see LinkedHashMap JavaDocs private static class SizedMap extends LinkedHashMap { @java.io.Serial private static final long serialVersionUID = -8211222668790986062L; @@ -120,14 +119,14 @@ public PrivateKey getPrivateKey(String alias) { public String chooseClientAlias(String[] keyTypes, Principal[] issuers, Socket socket) { return chooseAlias(getKeyTypes(keyTypes), issuers, CheckType.CLIENT, - getAlgorithmConstraints(socket)); + getAlgorithmConstraints(socket), null, null); } @Override public String chooseEngineClientAlias(String[] keyTypes, Principal[] issuers, SSLEngine engine) { return chooseAlias(getKeyTypes(keyTypes), issuers, CheckType.CLIENT, - getAlgorithmConstraints(engine)); + getAlgorithmConstraints(engine), null, null); } @Override @@ -168,73 +167,24 @@ public String chooseEngineServerAlias(String keyType, @Override public String[] getClientAliases(String keyType, Principal[] issuers) { - return getAliases(keyType, issuers, CheckType.CLIENT, null); + return getAliases(keyType, issuers, CheckType.CLIENT); } @Override public String[] getServerAliases(String keyType, Principal[] issuers) { - return getAliases(keyType, issuers, CheckType.SERVER, null); + return getAliases(keyType, issuers, CheckType.SERVER); } // // implementation private methods // - // Gets algorithm constraints of the socket. - private AlgorithmConstraints getAlgorithmConstraints(Socket socket) { - if (socket != null && socket.isConnected() && - socket instanceof SSLSocket sslSocket) { - - SSLSession session = sslSocket.getHandshakeSession(); - - if (session != null) { - if (ProtocolVersion.useTLS12PlusSpec(session.getProtocol())) { - String[] peerSupportedSignAlgs = null; - - if (session instanceof ExtendedSSLSession extSession) { - peerSupportedSignAlgs = - extSession.getPeerSupportedSignatureAlgorithms(); - } - - return SSLAlgorithmConstraints.forSocket( - sslSocket, peerSupportedSignAlgs, true); - } - } - - return SSLAlgorithmConstraints.forSocket(sslSocket, true); - } - - return SSLAlgorithmConstraints.DEFAULT; - } - - // Gets algorithm constraints of the engine. - private AlgorithmConstraints getAlgorithmConstraints(SSLEngine engine) { - if (engine != null) { - SSLSession session = engine.getHandshakeSession(); - if (session != null) { - if (ProtocolVersion.useTLS12PlusSpec(session.getProtocol())) { - String[] peerSupportedSignAlgs = null; - - if (session instanceof ExtendedSSLSession extSession) { - peerSupportedSignAlgs = - extSession.getPeerSupportedSignatureAlgorithms(); - } - - return SSLAlgorithmConstraints.forEngine( - engine, peerSupportedSignAlgs, true); - } - } - } - - return SSLAlgorithmConstraints.forEngine(engine, true); - } - // we construct the alias we return to JSSE as seen in the code below // a unique id is included to allow us to reliably cache entries // between the calls to getCertificateChain() and getPrivateKey() // even if tokens are inserted or removed private String makeAlias(EntryStatus entry) { - return uidCounter.incrementAndGet() + "." + entry.builderIndex + "." + return uidCounter.incrementAndGet() + "." + entry.keyStoreIndex + "." + entry.alias; } @@ -279,68 +229,6 @@ private PrivateKeyEntry getEntry(String alias) { } } - // Class to help verify that the public key algorithm (and optionally - // the signature algorithm) of a certificate matches what we need. - private static class KeyType { - - final String keyAlgorithm; - - // In TLS 1.2, the signature algorithm has been obsoleted by the - // supported_signature_algorithms, and the certificate type no longer - // restricts the algorithm used to sign the certificate. - // - // However, because we don't support certificate type checking other - // than rsa_sign, dss_sign and ecdsa_sign, we don't have to check the - // protocol version here. - final String sigKeyAlgorithm; - - KeyType(String algorithm) { - int k = algorithm.indexOf('_'); - if (k == -1) { - keyAlgorithm = algorithm; - sigKeyAlgorithm = null; - } else { - keyAlgorithm = algorithm.substring(0, k); - sigKeyAlgorithm = algorithm.substring(k + 1); - } - } - - boolean matches(Certificate[] chain) { - if (!chain[0].getPublicKey().getAlgorithm().equals(keyAlgorithm)) { - return false; - } - if (sigKeyAlgorithm == null) { - return true; - } - if (chain.length > 1) { - // if possible, check the public key in the issuer cert - return sigKeyAlgorithm.equals( - chain[1].getPublicKey().getAlgorithm()); - } else { - // Check the signature algorithm of the certificate itself. - // Look for the "withRSA" in "SHA1withRSA", etc. - X509Certificate issuer = (X509Certificate)chain[0]; - String sigAlgName = - issuer.getSigAlgName().toUpperCase(Locale.ENGLISH); - String pattern = - "WITH" + sigKeyAlgorithm.toUpperCase(Locale.ENGLISH); - return sigAlgName.contains(pattern); - } - } - } - - private static List getKeyTypes(String ... keyTypes) { - if ((keyTypes == null) || - (keyTypes.length == 0) || (keyTypes[0] == null)) { - return null; - } - List list = new ArrayList<>(keyTypes.length); - for (String keyType : keyTypes) { - list.add(new KeyType(keyType)); - } - return list; - } - /* * Return the best alias that fits the given parameters. * The algorithm we use is: @@ -354,13 +242,6 @@ private static List getKeyTypes(String ... keyTypes) { * with appropriate key usage to certs with the wrong key usage. * return the first one of them. */ - private String chooseAlias(List keyTypeList, Principal[] issuers, - CheckType checkType, AlgorithmConstraints constraints) { - - return chooseAlias(keyTypeList, issuers, - checkType, constraints, null, null); - } - private String chooseAlias(List keyTypeList, Principal[] issuers, CheckType checkType, AlgorithmConstraints constraints, List requestedServerNames, String idAlgorithm) { @@ -369,8 +250,9 @@ private String chooseAlias(List keyTypeList, Principal[] issuers, return null; } - Set issuerSet = getIssuerSet(issuers); + Set issuerSet = getIssuerSet(issuers); List allResults = null; + for (int i = 0, n = builders.size(); i < n; i++) { try { List results = getAliases(i, keyTypeList, @@ -390,7 +272,7 @@ private String chooseAlias(List keyTypeList, Principal[] issuers, } allResults.addAll(results); } - } catch (Exception e) { + } catch (KeyStoreException e) { // ignore } } @@ -415,27 +297,28 @@ private String chooseAlias(List keyTypeList, Principal[] issuers, * and certificates with the wrong extensions). * The perfect matches will be first in the array. */ - public String[] getAliases(String keyType, Principal[] issuers, - CheckType checkType, AlgorithmConstraints constraints) { + private String[] getAliases( + String keyType, Principal[] issuers, CheckType checkType) { + if (keyType == null) { return null; } - Set issuerSet = getIssuerSet(issuers); + Set issuerSet = getIssuerSet(issuers); List keyTypeList = getKeyTypes(keyType); List allResults = null; + for (int i = 0, n = builders.size(); i < n; i++) { try { List results = getAliases(i, keyTypeList, - issuerSet, true, checkType, constraints, - null, null); + issuerSet, true, checkType, null, null, null); if (results != null) { if (allResults == null) { allResults = new ArrayList<>(); } allResults.addAll(results); } - } catch (Exception e) { + } catch (KeyStoreException e) { // ignore } } @@ -462,232 +345,6 @@ private String[] toAliases(List results) { return s; } - // make a Set out of the array - private Set getIssuerSet(Principal[] issuers) { - if ((issuers != null) && (issuers.length != 0)) { - return new HashSet<>(Arrays.asList(issuers)); - } else { - return null; - } - } - - // a candidate match - // identifies the entry by builder and alias - // and includes the result of the certificate check - private static class EntryStatus implements Comparable { - - final int builderIndex; - final int keyIndex; - final String alias; - final CheckResult checkResult; - - EntryStatus(int builderIndex, int keyIndex, String alias, - Certificate[] chain, CheckResult checkResult) { - this.builderIndex = builderIndex; - this.keyIndex = keyIndex; - this.alias = alias; - this.checkResult = checkResult; - } - - @Override - public int compareTo(EntryStatus other) { - int result = this.checkResult.compareTo(other.checkResult); - return (result == 0) ? (this.keyIndex - other.keyIndex) : result; - } - - @Override - public String toString() { - String s = alias + " (verified: " + checkResult + ")"; - if (builderIndex == 0) { - return s; - } else { - return "Builder #" + builderIndex + ", alias: " + s; - } - } - } - - // enum for the type of certificate check we want to perform - // (client or server) - // also includes the check code itself - private enum CheckType { - - // enum constant for "no check" (currently not used) - NONE(Collections.emptySet()), - - // enum constant for "tls client" check - // valid EKU for TLS client: any, tls_client - CLIENT(new HashSet<>(List.of( - KnownOIDs.anyExtendedKeyUsage.value(), - KnownOIDs.clientAuth.value() - ))), - - // enum constant for "tls server" check - // valid EKU for TLS server: any, tls_server, ns_sgc, ms_sgc - SERVER(new HashSet<>(List.of( - KnownOIDs.anyExtendedKeyUsage.value(), - KnownOIDs.serverAuth.value(), - KnownOIDs.NETSCAPE_ExportApproved.value(), - KnownOIDs.MICROSOFT_ExportApproved.value() - ))); - - // set of valid EKU values for this type - final Set validEku; - - CheckType(Set validEku) { - this.validEku = validEku; - } - - private static boolean getBit(boolean[] keyUsage, int bit) { - return (bit < keyUsage.length) && keyUsage[bit]; - } - - // Check if this certificate is appropriate for this type of use - // first check extensions, if they match, check expiration. - // - // Note: we may want to move this code into the sun.security.validator - // package - CheckResult check(X509Certificate cert, Date date, - List serverNames, String idAlgorithm) { - - if (this == NONE) { - return CheckResult.OK; - } - - // check extensions - try { - // check extended key usage - List certEku = cert.getExtendedKeyUsage(); - if ((certEku != null) && - Collections.disjoint(validEku, certEku)) { - // if extension is present and does not contain any of - // the valid EKU OIDs, return extension_mismatch - return CheckResult.EXTENSION_MISMATCH; - } - - // check key usage - boolean[] ku = cert.getKeyUsage(); - if (ku != null) { - String algorithm = cert.getPublicKey().getAlgorithm(); - boolean supportsDigitalSignature = getBit(ku, 0); - switch (algorithm) { - case "RSA": - // require either signature bit - // or if server also allow key encipherment bit - if (!supportsDigitalSignature) { - if (this == CLIENT || !getBit(ku, 2)) { - return CheckResult.EXTENSION_MISMATCH; - } - } - break; - case "RSASSA-PSS": - if (!supportsDigitalSignature && (this == SERVER)) { - return CheckResult.EXTENSION_MISMATCH; - } - break; - case "DSA": - // require signature bit - if (!supportsDigitalSignature) { - return CheckResult.EXTENSION_MISMATCH; - } - break; - case "DH": - // require keyagreement bit - if (!getBit(ku, 4)) { - return CheckResult.EXTENSION_MISMATCH; - } - break; - case "EC": - // require signature bit - if (!supportsDigitalSignature) { - return CheckResult.EXTENSION_MISMATCH; - } - // For servers, also require key agreement. - // This is not totally accurate as the keyAgreement - // bit is only necessary for static ECDH key - // exchange and not ephemeral ECDH. We leave it in - // for now until there are signs that this check - // causes problems for real world EC certificates. - if (this == SERVER && !getBit(ku, 4)) { - return CheckResult.EXTENSION_MISMATCH; - } - break; - } - } - } catch (CertificateException e) { - // extensions unparseable, return failure - return CheckResult.EXTENSION_MISMATCH; - } - - try { - cert.checkValidity(date); - } catch (CertificateException e) { - return CheckResult.EXPIRED; - } - - if (serverNames != null && !serverNames.isEmpty()) { - for (SNIServerName serverName : serverNames) { - if (serverName.getType() == - StandardConstants.SNI_HOST_NAME) { - if (!(serverName instanceof SNIHostName)) { - try { - serverName = - new SNIHostName(serverName.getEncoded()); - } catch (IllegalArgumentException iae) { - // unlikely to happen, just in case ... - if (SSLLogger.isOn && - SSLLogger.isOn("keymanager")) { - SSLLogger.fine( - "Illegal server name: " + serverName); - } - - return CheckResult.INSENSITIVE; - } - } - String hostname = - ((SNIHostName)serverName).getAsciiName(); - - try { - X509TrustManagerImpl.checkIdentity(hostname, - cert, idAlgorithm); - } catch (CertificateException e) { - if (SSLLogger.isOn && - SSLLogger.isOn("keymanager")) { - SSLLogger.fine( - "Certificate identity does not match " + - "Server Name Indication (SNI): " + - hostname); - } - return CheckResult.INSENSITIVE; - } - - break; - } - } - } - - return CheckResult.OK; - } - - public String getValidator() { - if (this == CLIENT) { - return Validator.VAR_TLS_CLIENT; - } else if (this == SERVER) { - return Validator.VAR_TLS_SERVER; - } - return Validator.VAR_GENERIC; - } - } - - // enum for the result of the extension check - // NOTE: the order of the constants is important as they are used - // for sorting, i.e. OK is best, followed by EXPIRED and EXTENSION_MISMATCH - private enum CheckResult { - OK, // ok or not checked - INSENSITIVE, // server name indication insensitive - EXPIRED, // extensions valid but cert expired - EXTENSION_MISMATCH, // extensions invalid (expiration not checked) - } - /* * Return a List of all candidate matches in the specified builder * that fit the parameters. @@ -715,106 +372,42 @@ private enum CheckResult { * matches */ private List getAliases(int builderIndex, - List keyTypes, Set issuerSet, + List keyTypes, Set issuerSet, boolean findAll, CheckType checkType, AlgorithmConstraints constraints, List requestedServerNames, - String idAlgorithm) throws Exception { + String idAlgorithm) throws KeyStoreException { Builder builder = builders.get(builderIndex); KeyStore ks = builder.getKeyStore(); List results = null; - Date date = verificationDate; boolean preferred = false; + for (Enumeration e = ks.aliases(); e.hasMoreElements(); ) { + String alias = e.nextElement(); + // check if it is a key entry (private key or secret key) if (!ks.isKeyEntry(alias)) { continue; } - Certificate[] chain = ks.getCertificateChain(alias); - if ((chain == null) || (chain.length == 0)) { - // must be secret key entry, ignore - continue; - } - - boolean incompatible = false; - for (Certificate cert : chain) { - if (!(cert instanceof X509Certificate)) { - // not an X509Certificate, ignore this alias - incompatible = true; - break; - } - } - if (incompatible) { - continue; - } - - // check keytype - int keyIndex = -1; - int j = 0; - for (KeyType keyType : keyTypes) { - if (keyType.matches(chain)) { - keyIndex = j; - break; - } - j++; - } - if (keyIndex == -1) { - if (SSLLogger.isOn && SSLLogger.isOn("keymanager")) { - SSLLogger.fine("Ignore alias " + alias - + ": key algorithm does not match"); - } - continue; - } - // check issuers - if (issuerSet != null) { - boolean found = false; - for (Certificate cert : chain) { - X509Certificate xcert = (X509Certificate)cert; - if (issuerSet.contains(xcert.getIssuerX500Principal())) { - found = true; - break; - } - } - if (!found) { - if (SSLLogger.isOn && SSLLogger.isOn("keymanager")) { - SSLLogger.fine( - "Ignore alias " + alias - + ": issuers do not match"); - } - continue; - } - } - - // check the algorithm constraints - if (constraints != null && - !conformsToAlgorithmConstraints(constraints, chain, - checkType.getValidator())) { + EntryStatus status = checkAlias(builderIndex, alias, + ks.getCertificateChain(alias), + verificationDate, keyTypes, issuerSet, checkType, + constraints, requestedServerNames, idAlgorithm); - if (SSLLogger.isOn && SSLLogger.isOn("keymanager")) { - SSLLogger.fine("Ignore alias " + alias + - ": certificate list does not conform to " + - "algorithm constraints"); - } + if (status == null) { continue; } - if (date == null) { - date = new Date(); - } - CheckResult checkResult = - checkType.check((X509Certificate)chain[0], date, - requestedServerNames, idAlgorithm); - EntryStatus status = - new EntryStatus(builderIndex, keyIndex, - alias, chain, checkResult); - if (!preferred && checkResult == CheckResult.OK && keyIndex == 0) { + if (!preferred && status.checkResult == CheckResult.OK + && status.keyIndex == 0) { preferred = true; } + if (preferred && !findAll) { - // if we have a good match and do not need all matches, + // If we have a good match and do not need all matches, // return immediately return Collections.singletonList(status); } else { @@ -824,42 +417,7 @@ private List getAliases(int builderIndex, results.add(status); } } - return results; - } - private static boolean conformsToAlgorithmConstraints( - AlgorithmConstraints constraints, Certificate[] chain, - String variant) { - - AlgorithmChecker checker = new AlgorithmChecker(constraints, variant); - try { - checker.init(false); - } catch (CertPathValidatorException cpve) { - // unlikely to happen - if (SSLLogger.isOn && SSLLogger.isOn("keymanager")) { - SSLLogger.fine( - "Cannot initialize algorithm constraints checker", cpve); - } - - return false; - } - - // It is a forward checker, so we need to check from trust to target. - for (int i = chain.length - 1; i >= 0; i--) { - Certificate cert = chain[i]; - try { - // We don't care about the unresolved critical extensions. - checker.check(cert, Collections.emptySet()); - } catch (CertPathValidatorException cpve) { - if (SSLLogger.isOn && SSLLogger.isOn("keymanager")) { - SSLLogger.fine("Certificate does not conform to " + - "algorithm constraints", cert, cpve); - } - - return false; - } - } - - return true; + return results; } } diff --git a/test/jdk/javax/rmi/ssl/SSLSocketParametersTest.java b/test/jdk/javax/rmi/ssl/SSLSocketParametersTest.java index 6da3289458751..3aa7a98c39403 100644 --- a/test/jdk/javax/rmi/ssl/SSLSocketParametersTest.java +++ b/test/jdk/javax/rmi/ssl/SSLSocketParametersTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2004, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2004, 2025, 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 @@ -24,7 +24,8 @@ /* * @test * @bug 5016500 - * @library /test/lib/ + * @library /javax/net/ssl/templates + * /test/lib/ * @summary Test SslRmi[Client|Server]SocketFactory SSL socket parameters. * @run main/othervm SSLSocketParametersTest 1 * @run main/othervm SSLSocketParametersTest 2 @@ -36,8 +37,6 @@ */ import jdk.test.lib.Asserts; -import java.io.IOException; -import java.io.File; import java.io.Serializable; import java.lang.ref.Reference; import java.rmi.ConnectIOException; @@ -49,13 +48,17 @@ import javax.rmi.ssl.SslRMIClientSocketFactory; import javax.rmi.ssl.SslRMIServerSocketFactory; -public class SSLSocketParametersTest implements Serializable { +public class SSLSocketParametersTest extends SSLContextTemplate { + + public SSLSocketParametersTest() throws Exception { + SSLContext.setDefault(createServerSSLContext()); + } public interface Hello extends Remote { String sayHello() throws RemoteException; } - public class HelloImpl implements Hello { + public static class HelloImpl implements Hello { public String sayHello() { return "Hello World!"; } @@ -134,23 +137,7 @@ public void runTest(int testNumber) throws Exception { } public static void main(String[] args) throws Exception { - // Set keystore properties (server-side) - // - final String keystore = System.getProperty("test.src") + - File.separator + "keystore"; - System.out.println("KeyStore = " + keystore); - System.setProperty("javax.net.ssl.keyStore", keystore); - System.setProperty("javax.net.ssl.keyStorePassword", "password"); - - // Set truststore properties (client-side) - // - final String truststore = System.getProperty("test.src") + - File.separator + "truststore"; - System.out.println("TrustStore = " + truststore); - System.setProperty("javax.net.ssl.trustStore", truststore); - System.setProperty("javax.net.ssl.trustStorePassword", "trustword"); - SSLSocketParametersTest test = new SSLSocketParametersTest(); test.runTest(Integer.parseInt(args[0])); } -} \ No newline at end of file +} diff --git a/test/jdk/javax/rmi/ssl/keystore b/test/jdk/javax/rmi/ssl/keystore deleted file mode 100644 index 05f535645827b..0000000000000 Binary files a/test/jdk/javax/rmi/ssl/keystore and /dev/null differ diff --git a/test/jdk/javax/rmi/ssl/truststore b/test/jdk/javax/rmi/ssl/truststore deleted file mode 100644 index 2f5ba3433dc61..0000000000000 Binary files a/test/jdk/javax/rmi/ssl/truststore and /dev/null differ diff --git a/test/jdk/sun/net/www/protocol/https/HttpsClient/ServerIdentityTest.java b/test/jdk/sun/net/www/protocol/https/HttpsClient/ServerIdentityTest.java index 15224e22e6eea..1e0afd0e586a4 100644 --- a/test/jdk/sun/net/www/protocol/https/HttpsClient/ServerIdentityTest.java +++ b/test/jdk/sun/net/www/protocol/https/HttpsClient/ServerIdentityTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2001, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2001, 2025, 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 @@ -31,31 +31,58 @@ * @bug 4328195 * @summary Need to include the alternate subject DN for certs, * https should check for this + * @modules java.base/sun.security.x509 + * java.base/sun.security.util * @library /javax/net/ssl/templates - * @run main/othervm ServerIdentityTest dnsstore localhost - * @run main/othervm ServerIdentityTest ipstore 127.0.0.1 + * /test/lib + * @run main/othervm ServerIdentityTest dns localhost + * @run main/othervm ServerIdentityTest ip 127.0.0.1 * * @author Yingxian Wang */ -import java.io.InputStream; +import static jdk.test.lib.Asserts.fail; + import java.io.BufferedWriter; +import java.io.IOException; +import java.io.InputStream; import java.io.OutputStreamWriter; +import java.math.BigInteger; import java.net.HttpURLConnection; import java.net.InetAddress; import java.net.Proxy; import java.net.URL; import java.net.UnknownHostException; - +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.KeyStore; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.SecureRandom; +import java.security.cert.Certificate; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.Date; +import java.util.List; import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSocket; +import javax.net.ssl.TrustManagerFactory; +import jdk.test.lib.security.CertificateBuilder; +import sun.security.x509.AuthorityKeyIdentifierExtension; +import sun.security.x509.GeneralName; +import sun.security.x509.GeneralNames; +import sun.security.x509.KeyIdentifier; +import sun.security.x509.SerialNumber; +import sun.security.x509.X500Name; public final class ServerIdentityTest extends SSLSocketTemplate { - private static String keystore; private static String hostname; - private static SSLContext context; + private static SSLContext serverContext; /* * Run the test case. @@ -64,7 +91,7 @@ public static void main(String[] args) throws Exception { // Get the customized arguments. initialize(args); - (new ServerIdentityTest()).run(); + new ServerIdentityTest().run(); } ServerIdentityTest() throws UnknownHostException { @@ -95,7 +122,7 @@ protected void runClientApplication(int serverPort) throws Exception { HttpURLConnection urlc = null; InputStream is = null; try { - urlc = (HttpURLConnection)url.openConnection(Proxy.NO_PROXY); + urlc = (HttpURLConnection) url.openConnection(Proxy.NO_PROXY); is = urlc.getInputStream(); } finally { if (is != null) { @@ -109,31 +136,127 @@ protected void runClientApplication(int serverPort) throws Exception { @Override protected SSLContext createServerSSLContext() throws Exception { - return context; - } - - @Override - protected SSLContext createClientSSLContext() throws Exception { - return context; + return serverContext; } private static void initialize(String[] args) throws Exception { - keystore = args[0]; + String mode = args[0]; hostname = args[1]; - String password = "changeit"; - String keyFilename = - System.getProperty("test.src", ".") + "/" + keystore; - String trustFilename = - System.getProperty("test.src", ".") + "/" + keystore; + KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA"); + KeyPair caKeys = kpg.generateKeyPair(); + KeyPair serverKeys = kpg.generateKeyPair(); + KeyPair clientKeys = kpg.generateKeyPair(); + + CertificateBuilder serverCertificateBuilder = customCertificateBuilder( + "CN=server, O=Some-Org, L=Some-City, ST=Some-State, C=US", + serverKeys.getPublic(), caKeys.getPublic()) + .addBasicConstraintsExt(false, false, -1); - System.setProperty("javax.net.ssl.keyStore", keyFilename); - System.setProperty("javax.net.ssl.keyStorePassword", password); - System.setProperty("javax.net.ssl.trustStore", trustFilename); - System.setProperty("javax.net.ssl.trustStorePassword", password); + if (mode.equalsIgnoreCase("dns")) { + serverCertificateBuilder.addSubjectAltNameDNSExt(List.of(hostname)); + } else if (mode.equalsIgnoreCase("ip")) { + serverCertificateBuilder.addSubjectAltNameIPExt(List.of(hostname)); + } else { + fail("Unknown mode: " + mode); + } + + X509Certificate trustedCert = createTrustedCert(caKeys); + + X509Certificate serverCert = serverCertificateBuilder.build( + trustedCert, caKeys.getPrivate(), "SHA256WithRSA"); + + X509Certificate clientCert = customCertificateBuilder( + "CN=localhost, OU=SSL-Client, O=Some-Org, L=Some-City, ST=Some-State, C=US", + clientKeys.getPublic(), caKeys.getPublic()) + .addBasicConstraintsExt(false, false, -1) + .build(trustedCert, caKeys.getPrivate(), "SHA256WithRSA"); + + serverContext = getSSLContext( + trustedCert, serverCert, serverKeys.getPrivate()); + + SSLContext clientContext = getSSLContext( + trustedCert, clientCert, clientKeys.getPrivate()); - context = SSLContext.getDefault(); HttpsURLConnection.setDefaultSSLSocketFactory( - context.getSocketFactory()); + clientContext.getSocketFactory()); } + + private static SSLContext getSSLContext( + X509Certificate trustedCertificate, X509Certificate keyCertificate, + PrivateKey privateKey) + throws Exception { + + // create a key store + KeyStore ks = KeyStore.getInstance("PKCS12"); + ks.load(null, null); + + // import the trusted cert + ks.setCertificateEntry("TLS Signer", trustedCertificate); + + // generate certificate chain + Certificate[] chain = new Certificate[2]; + chain[0] = keyCertificate; + chain[1] = trustedCertificate; + + // import the key entry. + final char[] passphrase = "passphrase".toCharArray(); + ks.setKeyEntry("Whatever", privateKey, passphrase, chain); + + // Using PKIX TrustManager + TrustManagerFactory tmf = TrustManagerFactory.getInstance("PKIX"); + tmf.init(ks); + + // Using PKIX KeyManager + KeyManagerFactory kmf = KeyManagerFactory.getInstance("PKIX"); + + // create SSL context + SSLContext ctx = SSLContext.getInstance("TLS"); + kmf.init(ks, passphrase); + ctx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null); + return ctx; + } + + private static X509Certificate createTrustedCert(KeyPair caKeys) + throws Exception { + SecureRandom random = new SecureRandom(); + + KeyIdentifier kid = new KeyIdentifier(caKeys.getPublic()); + GeneralNames gns = new GeneralNames(); + GeneralName name = new GeneralName(new X500Name( + "O=Some-Org, L=Some-City, ST=Some-State, C=US")); + gns.add(name); + BigInteger serialNumber = BigInteger.valueOf( + random.nextLong(1000000) + 1); + return customCertificateBuilder( + "O=Some-Org, L=Some-City, ST=Some-State, C=US", + caKeys.getPublic(), caKeys.getPublic()) + .setSerialNumber(serialNumber) + .addExtension(new AuthorityKeyIdentifierExtension(kid, gns, + new SerialNumber(serialNumber))) + .addBasicConstraintsExt(true, true, -1) + .build(null, caKeys.getPrivate(), "SHA256WithRSA"); + } + + private static CertificateBuilder customCertificateBuilder( + String subjectName, PublicKey publicKey, PublicKey caKey) + throws CertificateException, IOException { + SecureRandom random = new SecureRandom(); + + CertificateBuilder builder = new CertificateBuilder() + .setSubjectName(subjectName) + .setPublicKey(publicKey) + .setNotBefore( + Date.from(Instant.now().minus(1, ChronoUnit.HOURS))) + .setNotAfter(Date.from(Instant.now().plus(1, ChronoUnit.HOURS))) + .setSerialNumber( + BigInteger.valueOf(random.nextLong(1000000) + 1)) + .addSubjectKeyIdExt(publicKey) + .addAuthorityKeyIdExt(caKey); + builder.addKeyUsageExt( + new boolean[]{true, true, true, true, true, true}); + + return builder; + } + } diff --git a/test/jdk/sun/net/www/protocol/https/HttpsClient/dnsstore b/test/jdk/sun/net/www/protocol/https/HttpsClient/dnsstore deleted file mode 100644 index 5f4fc81c9b7ce..0000000000000 Binary files a/test/jdk/sun/net/www/protocol/https/HttpsClient/dnsstore and /dev/null differ diff --git a/test/jdk/sun/net/www/protocol/https/HttpsClient/ipstore b/test/jdk/sun/net/www/protocol/https/HttpsClient/ipstore deleted file mode 100644 index 04a9508e0a1c4..0000000000000 Binary files a/test/jdk/sun/net/www/protocol/https/HttpsClient/ipstore and /dev/null differ diff --git a/test/jdk/sun/security/mscapi/ShortRSAKeyWithinTLS.java b/test/jdk/sun/security/mscapi/ShortRSAKeyWithinTLS.java index 26180a8fcdcba..5ecbb60227358 100644 --- a/test/jdk/sun/security/mscapi/ShortRSAKeyWithinTLS.java +++ b/test/jdk/sun/security/mscapi/ShortRSAKeyWithinTLS.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2025, 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,7 @@ * @modules java.base/sun.security.util * java.base/sun.security.tools.keytool * java.base/sun.security.x509 + * @library /test/lib * @run main ShortRSAKeyWithinTLS 1024 * @run main ShortRSAKeyWithinTLS 768 * @run main ShortRSAKeyWithinTLS 512 @@ -42,6 +43,7 @@ import javax.net.*; import javax.net.ssl.*; +import jdk.test.lib.security.SecurityUtils; import sun.security.tools.keytool.CertAndKeyGen; import sun.security.util.KeyUtil; import sun.security.x509.X500Name; @@ -233,6 +235,10 @@ private void checkKeySize(KeyStore ks) throws Exception { private static String clientCiperSuite = null; public static void main(String[] args) throws Exception { + // Make sure we don't block the key on algorithm constraints check. + SecurityUtils.removeFromDisabledAlgs("jdk.certpath.disabledAlgorithms", + List.of("RSA keySize < 1024")); + if (debug) { System.setProperty("javax.net.debug", "all"); } diff --git a/test/jdk/sun/security/ssl/SignatureScheme/MD5NotAllowedInTLS13CertificateSignature.java b/test/jdk/sun/security/ssl/SignatureScheme/MD5NotAllowedInTLS13CertificateSignature.java index ed61aace3f476..2fa046e1dc2e1 100644 --- a/test/jdk/sun/security/ssl/SignatureScheme/MD5NotAllowedInTLS13CertificateSignature.java +++ b/test/jdk/sun/security/ssl/SignatureScheme/MD5NotAllowedInTLS13CertificateSignature.java @@ -150,9 +150,11 @@ private static SSLContext getSSLContext( // create SSL context SSLContext ctx = SSLContext.getInstance(protocol); - // Using "SunX509" which doesn't check peer supported signature - // algorithms, so we check against local supported signature + // Disable KeyManager's algorithm constraints checking, + // so we check against local supported signature // algorithms which constitutes the fix being tested. + System.setProperty( + "jdk.tls.SunX509KeyManager.certChecking", "false"); KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509"); kmf.init(ks, passphrase); @@ -166,7 +168,6 @@ private static SSLContext getSSLContext( private void setupCertificates() throws Exception { KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA"); - kpg.initialize(1024); KeyPair caKeys = kpg.generateKeyPair(); this.serverKeys = kpg.generateKeyPair(); this.clientKeys = kpg.generateKeyPair(); @@ -215,7 +216,7 @@ private static CertificateBuilder customCertificateBuilder( CertificateBuilder builder = new CertificateBuilder() .setSubjectName(subjectName) .setPublicKey(publicKey) - .setNotAfter( + .setNotBefore( Date.from(Instant.now().minus(1, ChronoUnit.HOURS))) .setNotAfter(Date.from(Instant.now().plus(1, ChronoUnit.HOURS))) .setSerialNumber( diff --git a/test/jdk/sun/security/ssl/X509KeyManager/AlgorithmConstraintsCheck.java b/test/jdk/sun/security/ssl/X509KeyManager/AlgorithmConstraintsCheck.java new file mode 100644 index 0000000000000..997fde5a07ade --- /dev/null +++ b/test/jdk/sun/security/ssl/X509KeyManager/AlgorithmConstraintsCheck.java @@ -0,0 +1,197 @@ +/* + * Copyright (c) 2025, 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. + */ + +import static jdk.test.lib.Asserts.assertEquals; +import static jdk.test.lib.Asserts.assertNull; + +import java.io.IOException; +import java.math.BigInteger; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.KeyStore; +import java.security.PublicKey; +import java.security.SecureRandom; +import java.security.cert.Certificate; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.Date; +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.X509ExtendedKeyManager; +import javax.net.ssl.X509KeyManager; +import jdk.test.lib.security.CertificateBuilder; +import jdk.test.lib.security.SecurityUtils; +import sun.security.x509.AuthorityKeyIdentifierExtension; +import sun.security.x509.GeneralName; +import sun.security.x509.GeneralNames; +import sun.security.x509.KeyIdentifier; +import sun.security.x509.SerialNumber; +import sun.security.x509.X500Name; + +/* + * @test + * @bug 8359956 + * @summary Support algorithm constraints and certificate checks in SunX509 + * key manager + * @modules java.base/sun.security.x509 + * java.base/sun.security.util + * @library /test/lib + * @run main/othervm AlgorithmConstraintsCheck false SunX509 SHA256withRSA + * @run main/othervm AlgorithmConstraintsCheck true SunX509 SHA256withRSA + * @run main/othervm AlgorithmConstraintsCheck false PKIX SHA256withRSA + * @run main/othervm AlgorithmConstraintsCheck true PKIX SHA256withRSA + */ + +public class AlgorithmConstraintsCheck { + + private static final String CERT_ALIAS = "testalias"; + private static final String KEY_TYPE = "RSA"; + + public static void main(String[] args) throws Exception { + if (args.length != 3) { + throw new RuntimeException("Wrong number of arguments"); + } + + String enabled = args[0]; + String kmAlg = args[1]; + String certSignatureAlg = args[2]; + + System.setProperty("jdk.tls.SunX509KeyManager.certChecking", enabled); + SecurityUtils.addToDisabledTlsAlgs(certSignatureAlg); + + X509ExtendedKeyManager km = (X509ExtendedKeyManager) getKeyManager( + kmAlg, certSignatureAlg); + String serverAlias = km.chooseServerAlias(KEY_TYPE, null, null); + String engineServerAlias = km.chooseEngineServerAlias( + KEY_TYPE, null, null); + String clientAlias = km.chooseClientAlias( + new String[]{KEY_TYPE}, null, null); + String engineClientAlias = km.chooseEngineClientAlias( + new String[]{KEY_TYPE}, null, null); + + // PKIX KeyManager adds a cache prefix to an alias. + String serverAliasPrefix = kmAlg.equalsIgnoreCase("PKIX") ? "1.0." : ""; + String clientAliasPrefix = kmAlg.equalsIgnoreCase("PKIX") ? "2.0." : ""; + + if ("false".equals(enabled) && kmAlg.equals("SunX509")) { + assertEquals(CERT_ALIAS, normalizeAlias(serverAlias)); + assertEquals(CERT_ALIAS, normalizeAlias(engineServerAlias)); + assertEquals(CERT_ALIAS, normalizeAlias(clientAlias)); + assertEquals(CERT_ALIAS, normalizeAlias(engineClientAlias)); + } else { + assertNull(serverAlias); + assertNull(engineServerAlias); + assertNull(clientAlias); + assertNull(engineClientAlias); + } + } + + // PKIX KeyManager adds a cache prefix to an alias. + private static String normalizeAlias(String alias) { + return alias.substring(alias.lastIndexOf(".") + 1); + } + + private static X509KeyManager getKeyManager(String kmAlg, + String certSignatureAlg) throws Exception { + KeyPairGenerator kpg = KeyPairGenerator.getInstance(KEY_TYPE); + KeyPair caKeys = kpg.generateKeyPair(); + KeyPair endpointKeys = kpg.generateKeyPair(); + + X509Certificate trustedCert = createTrustedCert(caKeys, + certSignatureAlg); + + X509Certificate endpointCert = customCertificateBuilder( + "O=Some-Org, L=Some-City, ST=Some-State, C=US", + endpointKeys.getPublic(), caKeys.getPublic()) + .addBasicConstraintsExt(false, false, -1) + .build(trustedCert, caKeys.getPrivate(), certSignatureAlg); + + // create a key store + KeyStore ks = KeyStore.getInstance("PKCS12"); + ks.load(null, null); + + // import the trusted cert + ks.setCertificateEntry("TLS Signer", trustedCert); + + // generate certificate chain + Certificate[] chain = new Certificate[2]; + chain[0] = endpointCert; + chain[1] = trustedCert; + + // import the key entry. + final char[] passphrase = "passphrase".toCharArray(); + ks.setKeyEntry(CERT_ALIAS, endpointKeys.getPrivate(), passphrase, + chain); + + KeyManagerFactory kmf = KeyManagerFactory.getInstance(kmAlg); + kmf.init(ks, passphrase); + + return (X509KeyManager) kmf.getKeyManagers()[0]; + } + + // Certificate-building helper methods. + + private static X509Certificate createTrustedCert(KeyPair caKeys, + String certSignatureAlg) + throws Exception { + SecureRandom random = new SecureRandom(); + + KeyIdentifier kid = new KeyIdentifier(caKeys.getPublic()); + GeneralNames gns = new GeneralNames(); + GeneralName name = new GeneralName(new X500Name( + "O=Some-Org, L=Some-City, ST=Some-State, C=US")); + gns.add(name); + BigInteger serialNumber = BigInteger.valueOf( + random.nextLong(1000000) + 1); + return customCertificateBuilder( + "O=Some-Org, L=Some-City, ST=Some-State, C=US", + caKeys.getPublic(), caKeys.getPublic()) + .setSerialNumber(serialNumber) + .addExtension(new AuthorityKeyIdentifierExtension(kid, gns, + new SerialNumber(serialNumber))) + .addBasicConstraintsExt(true, true, -1) + .build(null, caKeys.getPrivate(), certSignatureAlg); + } + + private static CertificateBuilder customCertificateBuilder( + String subjectName, PublicKey publicKey, PublicKey caKey) + throws CertificateException, IOException { + SecureRandom random = new SecureRandom(); + + CertificateBuilder builder = new CertificateBuilder() + .setSubjectName(subjectName) + .setPublicKey(publicKey) + .setNotBefore( + Date.from(Instant.now().minus(1, ChronoUnit.HOURS))) + .setNotAfter(Date.from(Instant.now().plus(1, ChronoUnit.HOURS))) + .setSerialNumber( + BigInteger.valueOf(random.nextLong(1000000) + 1)) + .addSubjectKeyIdExt(publicKey) + .addAuthorityKeyIdExt(caKey); + builder.addKeyUsageExt( + new boolean[]{true, true, true, true, true, true}); + + return builder; + } +} diff --git a/test/jdk/sun/security/ssl/X509KeyManager/CertChecking.java b/test/jdk/sun/security/ssl/X509KeyManager/CertChecking.java new file mode 100644 index 0000000000000..81e16b925fcb8 --- /dev/null +++ b/test/jdk/sun/security/ssl/X509KeyManager/CertChecking.java @@ -0,0 +1,472 @@ +/* + * Copyright (c) 2025, 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. + */ + +import static jdk.test.lib.Asserts.assertEquals; +import static jdk.test.lib.Asserts.assertNull; + +import com.sun.security.auth.UserPrincipal; +import java.io.IOException; +import java.math.BigInteger; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.KeyStore; +import java.security.Principal; +import java.security.PublicKey; +import java.security.SecureRandom; +import java.security.cert.Certificate; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.X509ExtendedKeyManager; +import javax.net.ssl.X509KeyManager; +import javax.security.auth.x500.X500Principal; +import jdk.test.lib.Asserts; +import jdk.test.lib.security.CertificateBuilder; +import sun.security.x509.AuthorityKeyIdentifierExtension; +import sun.security.x509.GeneralName; +import sun.security.x509.GeneralNames; +import sun.security.x509.KeyIdentifier; +import sun.security.x509.SerialNumber; +import sun.security.x509.X500Name; + +/* + * @test + * @bug 8359956 + * @summary Support algorithm constraints and certificate checks in SunX509 + * key manager + * @modules java.base/sun.security.x509 + * java.base/sun.security.util + * @library /test/lib + * @run main/othervm CertChecking false SunX509 + * @run main/othervm CertChecking true SunX509 + * @run main/othervm CertChecking false PKIX + * @run main/othervm CertChecking true PKIX + */ + +/* + * This class tests against the certificate's expiration, key usage, key type + * and issuers. + */ + +public class CertChecking { + + private static final String PREFERRED_ALIAS = "preferred-alias"; + private static final String EXPIRED_ALIAS = "expired-alias"; + private static final String USAGE_MISMATCH_ALIAS = "usage-mismatch-alias"; + private static final String CA_KEY_TYPE = "RSA"; + private static final String CERT_SIG_ALG = "SHA256withRSA"; + private static final String CA_ISSUER_STRING = + "O=TrustedCert, L=Some-City, ST=Some-State, C=US"; + private static final String EE_ISSUER_STRING = + "O=EndpointCert, L=Some-City, ST=Some-State, C=US"; + private static final String UNKNOWN_ISSUER_STRING = + "O=UnknownCert, L=Some-City, ST=Some-State, C=US"; + + /* + * Certificate KeyUsage reference: + * + * digitalSignature (0), + * nonRepudiation (1), + * keyEncipherment (2), + * dataEncipherment (3), + * keyAgreement (4), + * keyCertSign (5), + * cRLSign (6), + * encipherOnly (7), + * decipherOnly (8) + */ + + private static final boolean[] DEFAULT_KEY_USAGES = + new boolean[]{true, true, true, true, true, true}; + private static final boolean[] NONE_KEY_USAGES = + new boolean[]{false, false, false, false, false, false}; + private static final boolean[] NO_DS_USAGE = + new boolean[]{false, true, true, true, true, true}; + private static final boolean[] NO_DS_NO_KE_USAGE = + new boolean[]{false, true, false, true, true, true}; + private static final boolean[] NO_KA_USAGE = + new boolean[]{true, true, true, true, false, true}; + + + public static void main(String[] args) throws Exception { + if (args.length != 2) { + throw new RuntimeException("Wrong number of arguments"); + } + + String enabled = args[0]; + String kmAlg = args[1]; + + System.setProperty("jdk.tls.SunX509KeyManager.certChecking", enabled); + + // --- Usage and expired test cases -- + + // Both client and server should be checked with no usages at all + usageTestCase(enabled, kmAlg, "RSA", NONE_KEY_USAGES, true, true); + + // Only client should be checked with RSA algorithm and + // no digital signature bit set + usageTestCase(enabled, kmAlg, "RSA", NO_DS_USAGE, false, true); + + // Only server should be checked with RSA algorithm and + // no digital signature bit set + usageTestCase(enabled, kmAlg, "RSASSA-PSS", NO_DS_USAGE, true, false); + + // Both client and server should be checked with DSA algorithm and no + // digital signature bit set + usageTestCase(enabled, kmAlg, "DSA", NO_DS_USAGE, true, true); + + // Both client and server should be checked with EC algorithm and no + // digital signature bit set + usageTestCase(enabled, kmAlg, "EC", NO_DS_USAGE, true, true); + + // Both client and server should be checked with RSA algorithm and + // missing digital signature and key encipherment bits. + usageTestCase(enabled, kmAlg, "RSA", NO_DS_NO_KE_USAGE, true, true); + + // Both client and server should be checked with DH algorithm and no + // key agreement bit set. + usageTestCase(enabled, kmAlg, "DH", NO_KA_USAGE, true, true); + + // Only server should be checked with EC algorithm and + // no digital signature bit set + usageTestCase(enabled, kmAlg, "EC", NO_KA_USAGE, true, false); + + // --- Issuer match test cases --- + + // Check CA issuer match + issuerAndKeyTypeTestCase(enabled, kmAlg, "RSA", "RSA", + new Principal[]{new X500Principal(CA_ISSUER_STRING)}, true); + + // Check CA issuer match with non-X500 principal + issuerAndKeyTypeTestCase(enabled, kmAlg, "RSA", "RSA", + new Principal[]{new UserPrincipal(CA_ISSUER_STRING)}, true); + + // Non-convertable principal should match all + issuerAndKeyTypeTestCase(enabled, kmAlg, "RSA", "RSA", + new Principal[]{new InvalidPrincipal()}, true); + + // Empty issuer array should match all + issuerAndKeyTypeTestCase(enabled, kmAlg, "RSA", "RSA", + new Principal[]{}, true); + + // Null issuer array should match all + issuerAndKeyTypeTestCase(enabled, kmAlg, "RSA", "RSA", null, true); + + // Issuer that is not in the chain should not match. + issuerAndKeyTypeTestCase(enabled, kmAlg, "RSA", "RSA", + new Principal[]{new X500Principal(UNKNOWN_ISSUER_STRING)}, + false); + + // --- Key Type match test cases --- + + // Exact key type match. + issuerAndKeyTypeTestCase(enabled, kmAlg, "EC", "EC", null, true); + + // Key type with a signature algorithm match. + issuerAndKeyTypeTestCase( + enabled, kmAlg, "EC", "EC_" + CA_KEY_TYPE, null, true); + + // Null KeyType should not match. + issuerAndKeyTypeTestCase(enabled, kmAlg, "RSA", null, null, false); + + // Wrong KeyType should not match. + issuerAndKeyTypeTestCase(enabled, kmAlg, "RSA", "EC", null, false); + + // Wrong signature algorithm should not match. + issuerAndKeyTypeTestCase(enabled, kmAlg, "RSA", "RSA_EC", null, false); + + // Correct signature algorithm but incorrect key algorithm + // should not match. + issuerAndKeyTypeTestCase( + enabled, kmAlg, "RSA", "EC_" + CA_KEY_TYPE, null, false); + } + + private static void usageTestCase(String enabled, String kmAlg, + String keyAlg, boolean[] certKeyUsages, boolean checkServer, + boolean checkClient) throws Exception { + + X509ExtendedKeyManager km = (X509ExtendedKeyManager) getKeyManager( + kmAlg, keyAlg, certKeyUsages); + + String chosenServerAlias = km.chooseServerAlias(keyAlg, null, null); + String chosenEngineServerAlias = km.chooseEngineServerAlias( + keyAlg, null, null); + String chosenClientAlias = km.chooseClientAlias( + new String[]{keyAlg}, null, null); + String chosenEngineClientAlias = km.chooseEngineClientAlias( + new String[]{keyAlg}, null, null); + + String[] allServerAliases = km.getServerAliases(keyAlg, null); + String[] allClientAliases = km.getClientAliases(keyAlg, null); + + if ("false".equals(enabled) && kmAlg.equals("SunX509")) { + // Initial order alias returned + assertEquals(USAGE_MISMATCH_ALIAS, + normalizeAlias(chosenServerAlias)); + assertEquals(USAGE_MISMATCH_ALIAS, + normalizeAlias(chosenClientAlias)); + assertEquals(USAGE_MISMATCH_ALIAS, + normalizeAlias(chosenEngineServerAlias)); + assertEquals(USAGE_MISMATCH_ALIAS, + normalizeAlias(chosenEngineClientAlias)); + + // Assert the initial order of all aliases. + assertEquals(USAGE_MISMATCH_ALIAS, + normalizeAlias(allServerAliases[0])); + assertEquals(USAGE_MISMATCH_ALIAS, + normalizeAlias(allClientAliases[0])); + assertEquals(PREFERRED_ALIAS, normalizeAlias(allServerAliases[1])); + assertEquals(PREFERRED_ALIAS, normalizeAlias(allClientAliases[1])); + assertEquals(EXPIRED_ALIAS, normalizeAlias(allServerAliases[2])); + assertEquals(EXPIRED_ALIAS, normalizeAlias(allClientAliases[2])); + + } else { + if (checkServer) { + // Preferred alias returned + assertEquals(PREFERRED_ALIAS, + normalizeAlias(chosenServerAlias)); + assertEquals(PREFERRED_ALIAS, + normalizeAlias(chosenEngineServerAlias)); + + // Assert the correct sorted order of all aliases. + assertEquals(PREFERRED_ALIAS, + normalizeAlias(allServerAliases[0])); + assertEquals(EXPIRED_ALIAS, + normalizeAlias(allServerAliases[1])); + assertEquals(USAGE_MISMATCH_ALIAS, + normalizeAlias(allServerAliases[2])); + } + + if (checkClient) { + // Preferred alias returned + assertEquals(PREFERRED_ALIAS, + normalizeAlias(chosenClientAlias)); + assertEquals(PREFERRED_ALIAS, + normalizeAlias(chosenEngineClientAlias)); + + // Assert the correct sorted order of all aliases. + assertEquals(PREFERRED_ALIAS, + normalizeAlias(allClientAliases[0])); + assertEquals(EXPIRED_ALIAS, + normalizeAlias(allClientAliases[1])); + assertEquals(USAGE_MISMATCH_ALIAS, + normalizeAlias(allClientAliases[2])); + } + } + } + + private static void issuerAndKeyTypeTestCase(String enabled, String kmAlg, + String keyAlg, String keyType, Principal[] issuers, boolean found) + throws Exception { + + X509ExtendedKeyManager km = (X509ExtendedKeyManager) getKeyManager( + kmAlg, keyAlg, NONE_KEY_USAGES); + + List chosenAliases = new ArrayList<>(4); + + chosenAliases.add(km.chooseServerAlias(keyType, issuers, null)); + chosenAliases.add(km.chooseEngineServerAlias(keyType, issuers, null)); + chosenAliases.add( + km.chooseClientAlias(new String[]{keyType}, issuers, null)); + chosenAliases.add(km.chooseEngineClientAlias( + new String[]{keyType}, issuers, null)); + + String[] allServerAliases = km.getServerAliases(keyType, issuers); + String[] allClientAliases = km.getClientAliases(keyType, issuers); + + if (found) { + if ("false".equals(enabled) && kmAlg.equals("SunX509")) { + chosenAliases.forEach(a -> + assertEquals(USAGE_MISMATCH_ALIAS, normalizeAlias(a))); + + // Assert the initial order of all aliases. + assertEquals(USAGE_MISMATCH_ALIAS, + normalizeAlias(allServerAliases[0])); + assertEquals(USAGE_MISMATCH_ALIAS, + normalizeAlias(allClientAliases[0])); + assertEquals(PREFERRED_ALIAS, + normalizeAlias(allServerAliases[1])); + assertEquals(PREFERRED_ALIAS, + normalizeAlias(allClientAliases[1])); + assertEquals(EXPIRED_ALIAS, + normalizeAlias(allServerAliases[2])); + assertEquals(EXPIRED_ALIAS, + normalizeAlias(allClientAliases[2])); + } else { + chosenAliases.forEach(a -> + assertEquals(PREFERRED_ALIAS, normalizeAlias(a))); + + // Assert the correct sorted order of all aliases. + assertEquals(PREFERRED_ALIAS, + normalizeAlias(allServerAliases[0])); + assertEquals(EXPIRED_ALIAS, + normalizeAlias(allServerAliases[1])); + assertEquals(USAGE_MISMATCH_ALIAS, + normalizeAlias(allServerAliases[2])); + } + } else { + chosenAliases.forEach(Asserts::assertNull); + assertNull(allServerAliases); + assertNull(allClientAliases); + } + } + + // PKIX KeyManager adds a cache prefix to an alias. + private static String normalizeAlias(String alias) { + return alias.substring(alias.lastIndexOf(".") + 1); + + } + + private static class InvalidPrincipal implements Principal { + + @Override + public String getName() { + return null; + } + } + + private static X509KeyManager getKeyManager(String kmAlg, + String keyAlg, boolean[] certKeyUsages) + throws Exception { + + // Create a key store. + KeyStore ks = KeyStore.getInstance("PKCS12"); + ks.load(null, null); + + // Generate and set the trusted cert. + KeyPair caKeys = KeyPairGenerator.getInstance(CA_KEY_TYPE) + .generateKeyPair(); + X509Certificate trustedCert = createTrustedCert(caKeys); + ks.setCertificateEntry("CA entry", trustedCert); + + // Generate valid certificate chain. + KeyPairGenerator kpg = KeyPairGenerator.getInstance(keyAlg); + KeyPair validEndpointKeys = kpg.generateKeyPair(); + + X509Certificate validEndpointCert = customCertificateBuilder( + EE_ISSUER_STRING, + validEndpointKeys.getPublic(), caKeys.getPublic(), + Instant.now(), DEFAULT_KEY_USAGES) + .addBasicConstraintsExt(false, false, -1) + .build(trustedCert, caKeys.getPrivate(), CERT_SIG_ALG); + + Certificate[] validChain = new Certificate[2]; + validChain[0] = validEndpointCert; + validChain[1] = trustedCert; + + // Generate expired certificate chain. + KeyPair expiredEndpointKeys = kpg.generateKeyPair(); + + X509Certificate expiredEndpointCert = customCertificateBuilder( + EE_ISSUER_STRING, + expiredEndpointKeys.getPublic(), caKeys.getPublic(), + Instant.now().minus(1, ChronoUnit.DAYS), DEFAULT_KEY_USAGES) + .addBasicConstraintsExt(false, false, -1) + .build(trustedCert, caKeys.getPrivate(), CERT_SIG_ALG); + + Certificate[] expiredChain = new Certificate[2]; + expiredChain[0] = expiredEndpointCert; + expiredChain[1] = trustedCert; + + // Generate usage mismatch certificate chain. + KeyPair usageMismatchEndpointKeys = kpg.generateKeyPair(); + + X509Certificate usageMismatchEndpointCert = customCertificateBuilder( + EE_ISSUER_STRING, + usageMismatchEndpointKeys.getPublic(), caKeys.getPublic(), + Instant.now(), certKeyUsages) + .addBasicConstraintsExt(false, false, -1) + .build(trustedCert, caKeys.getPrivate(), CERT_SIG_ALG); + + Certificate[] usageMismatchChain = new Certificate[2]; + usageMismatchChain[0] = usageMismatchEndpointCert; + usageMismatchChain[1] = trustedCert; + + // Import the key entries, order matters. + final char[] passphrase = "passphrase".toCharArray(); + ks.setKeyEntry(USAGE_MISMATCH_ALIAS, + usageMismatchEndpointKeys.getPrivate(), passphrase, + usageMismatchChain); + ks.setKeyEntry(PREFERRED_ALIAS, validEndpointKeys.getPrivate(), + passphrase, + validChain); + ks.setKeyEntry(EXPIRED_ALIAS, expiredEndpointKeys.getPrivate(), + passphrase, + expiredChain); + + KeyManagerFactory kmf = KeyManagerFactory.getInstance(kmAlg); + kmf.init(ks, passphrase); + + return (X509KeyManager) kmf.getKeyManagers()[0]; + } + + // Certificate-building helper methods. + + private static X509Certificate createTrustedCert(KeyPair caKeys) + throws Exception { + SecureRandom random = new SecureRandom(); + + KeyIdentifier kid = new KeyIdentifier(caKeys.getPublic()); + GeneralNames gns = new GeneralNames(); + GeneralName name = new GeneralName(new X500Name( + "O=Some-Org, L=Some-City, ST=Some-State, C=US")); + gns.add(name); + BigInteger serialNumber = BigInteger.valueOf( + random.nextLong(1000000) + 1); + return customCertificateBuilder( + CA_ISSUER_STRING, + caKeys.getPublic(), caKeys.getPublic(), Instant.now(), + DEFAULT_KEY_USAGES) + .setSerialNumber(serialNumber) + .addExtension(new AuthorityKeyIdentifierExtension(kid, gns, + new SerialNumber(serialNumber))) + .addBasicConstraintsExt(true, true, -1) + .build(null, caKeys.getPrivate(), CERT_SIG_ALG); + } + + private static CertificateBuilder customCertificateBuilder( + String subjectName, PublicKey publicKey, PublicKey caKey, + Instant certDate, boolean[] certKeyUsages) + throws CertificateException, IOException { + SecureRandom random = new SecureRandom(); + + CertificateBuilder builder = new CertificateBuilder() + .setSubjectName(subjectName) + .setPublicKey(publicKey) + .setNotBefore( + Date.from(certDate.minus(1, ChronoUnit.HOURS))) + .setNotAfter(Date.from(certDate.plus(1, ChronoUnit.HOURS))) + .setSerialNumber( + BigInteger.valueOf(random.nextLong(1000000) + 1)) + .addSubjectKeyIdExt(publicKey) + .addAuthorityKeyIdExt(caKey); + builder.addKeyUsageExt(certKeyUsages); + + return builder; + } +} diff --git a/test/jdk/sun/security/ssl/X509KeyManager/PeerConstraintsCheck.java b/test/jdk/sun/security/ssl/X509KeyManager/PeerConstraintsCheck.java new file mode 100644 index 0000000000000..bbfbfe0b46e8a --- /dev/null +++ b/test/jdk/sun/security/ssl/X509KeyManager/PeerConstraintsCheck.java @@ -0,0 +1,280 @@ +/* + * Copyright (c) 2025, 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. + */ + +import static jdk.test.lib.Asserts.assertEquals; +import static jdk.test.lib.Asserts.assertTrue; +import static jdk.test.lib.Utils.runAndCheckException; + +import java.io.IOException; +import java.math.BigInteger; +import java.net.Socket; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.KeyStore; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.SecureRandom; +import java.security.cert.Certificate; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.Date; +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLEngine; +import javax.net.ssl.SSLHandshakeException; +import javax.net.ssl.X509ExtendedTrustManager; +import jdk.test.lib.security.CertificateBuilder; +import sun.security.x509.AuthorityKeyIdentifierExtension; +import sun.security.x509.GeneralName; +import sun.security.x509.GeneralNames; +import sun.security.x509.KeyIdentifier; +import sun.security.x509.SerialNumber; +import sun.security.x509.X500Name; + +/* + * @test + * @bug 8359956 + * @summary Support algorithm constraints and certificate checks in SunX509 + * key manager + * @modules java.base/sun.security.x509 + * java.base/sun.security.util + * @library /javax/net/ssl/templates + * /test/lib + * @run main/othervm PeerConstraintsCheck false SunX509 + * @run main/othervm PeerConstraintsCheck true SunX509 + * @run main/othervm PeerConstraintsCheck false PKIX + * @run main/othervm PeerConstraintsCheck true PKIX + */ + +/* + * This class tests against the peer supported certificate signatures sent in + * "signature_algorithms_cert" extension. + */ + +public class PeerConstraintsCheck extends SSLSocketTemplate { + + private static final String KEY_ALGORITHM = "EC"; + private static final String CLIENT_CERT_SIG_SCHEME = + "ecdsa_secp384r1_sha384"; + private static final String CLIENT_CERT_SIG_ALG = "SHA384withECDSA"; + private static final String SERVER_CERT_SIG_ALG = "SHA256withECDSA"; + private static final String TRUSTED_CERT_SIG_ALG = "SHA512withECDSA"; + + private final String kmAlg; + private X509Certificate trustedCert; + private X509Certificate serverCert; + private X509Certificate clientCert; + private KeyPair serverKeys; + private KeyPair clientKeys; + + protected PeerConstraintsCheck(String kmAlg) throws Exception { + super(); + this.kmAlg = kmAlg; + setupCertificates(); + } + + public static void main(String[] args) throws Exception { + // Make sure both client and server support client's signature scheme, + // so the exception happens later during KeyManager's algorithm check. + System.setProperty( + "jdk.tls.client.SignatureSchemes", CLIENT_CERT_SIG_SCHEME); + System.setProperty( + "jdk.tls.server.SignatureSchemes", CLIENT_CERT_SIG_SCHEME); + + String enabled = args[0]; + String kmAlg = args[1]; + + System.setProperty("jdk.tls.SunX509KeyManager.certChecking", enabled); + + if ("false".equals(enabled) && kmAlg.equals("SunX509")) { + new PeerConstraintsCheck(kmAlg).run(); + } else { + // "jdk.tls.client.SignatureSchemes" and + // "jdk.tls.server.SignatureSchemes" system properties set + // signature schemes for both "signature_algorithms" and + // "signature_algorithms_cert" extensions. Then we fail because + // server's certificate is signed with "SHA256withECDSA" while + // "signature_algorithms_cert" extension only contains an + // "ecdsa_secp384r1_sha384" signature scheme corresponding to + // "SHA384withECDSA" certificate signature. + runAndCheckException( + () -> new PeerConstraintsCheck(kmAlg).run(), + ex -> { + assertTrue(ex instanceof SSLHandshakeException); + assertEquals(ex.getMessage(), "(handshake_failure) " + + "No available authentication scheme"); + } + ); + } + } + + @Override + public SSLContext createServerSSLContext() throws Exception { + return getSSLContext( + trustedCert, serverCert, serverKeys.getPrivate(), kmAlg); + } + + @Override + public SSLContext createClientSSLContext() throws Exception { + return getSSLContext( + trustedCert, clientCert, clientKeys.getPrivate(), kmAlg); + } + + private static SSLContext getSSLContext(X509Certificate trustedCertificate, + X509Certificate keyCertificate, PrivateKey privateKey, String kmAlg) + throws Exception { + + // create a key store + KeyStore ks = KeyStore.getInstance("PKCS12"); + ks.load(null, null); + + // import the trusted cert + ks.setCertificateEntry("TLS Signer", trustedCertificate); + + // generate certificate chain + Certificate[] chain = new Certificate[2]; + chain[0] = keyCertificate; + chain[1] = trustedCertificate; + + // import the key entry. + final char[] passphrase = "passphrase".toCharArray(); + ks.setKeyEntry("Whatever", privateKey, passphrase, chain); + + SSLContext ctx = SSLContext.getInstance("TLS"); + + KeyManagerFactory kmf = KeyManagerFactory.getInstance(kmAlg); + kmf.init(ks, passphrase); + + // Use custom trust-all TrustManager so we perform only KeyManager's + // constraints check. + X509ExtendedTrustManager[] trustAll = new X509ExtendedTrustManager[]{ + new X509ExtendedTrustManager() { + @Override + public void checkClientTrusted(X509Certificate[] chain, + String authType, Socket socket) + throws CertificateException { + } + + @Override + public void checkServerTrusted(X509Certificate[] chain, + String authType, Socket socket) + throws CertificateException { + } + + @Override + public void checkClientTrusted(X509Certificate[] chain, + String authType, SSLEngine engine) + throws CertificateException { + } + + @Override + public void checkServerTrusted(X509Certificate[] chain, + String authType, SSLEngine engine) + throws CertificateException { + } + + public void checkClientTrusted(X509Certificate[] chain, + String authType) throws CertificateException { + } + + public void checkServerTrusted(X509Certificate[] chain, + String authType) throws CertificateException { + } + + public X509Certificate[] getAcceptedIssuers() { + return new X509Certificate[0]; + } + } + }; + + ctx.init(kmf.getKeyManagers(), trustAll, null); + return ctx; + } + + // Certificate-building helper methods. + + private void setupCertificates() throws Exception { + KeyPairGenerator kpg = KeyPairGenerator.getInstance(KEY_ALGORITHM); + KeyPair caKeys = kpg.generateKeyPair(); + this.serverKeys = kpg.generateKeyPair(); + this.clientKeys = kpg.generateKeyPair(); + + this.trustedCert = createTrustedCert(caKeys); + + this.serverCert = customCertificateBuilder( + "O=Some-Org, L=Some-City, ST=Some-State, C=US", + serverKeys.getPublic(), caKeys.getPublic()) + .addBasicConstraintsExt(false, false, -1) + .build(trustedCert, caKeys.getPrivate(), SERVER_CERT_SIG_ALG); + + this.clientCert = customCertificateBuilder( + "CN=localhost, OU=SSL-Client, O=Some-Org, L=Some-City," + + " ST=Some-State, C=US", + clientKeys.getPublic(), caKeys.getPublic()) + .addBasicConstraintsExt(false, false, -1) + .build(trustedCert, caKeys.getPrivate(), CLIENT_CERT_SIG_ALG); + } + + private static X509Certificate createTrustedCert(KeyPair caKeys) + throws Exception { + SecureRandom random = new SecureRandom(); + KeyIdentifier kid = new KeyIdentifier(caKeys.getPublic()); + GeneralNames gns = new GeneralNames(); + GeneralName name = new GeneralName(new X500Name( + "O=Some-Org, L=Some-City, ST=Some-State, C=US")); + gns.add(name); + BigInteger serialNumber = BigInteger.valueOf( + random.nextLong(1000000) + 1); + return customCertificateBuilder( + "O=Some-Org, L=Some-City, ST=Some-State, C=US", + caKeys.getPublic(), caKeys.getPublic()) + .setSerialNumber(serialNumber) + .addExtension(new AuthorityKeyIdentifierExtension(kid, gns, + new SerialNumber(serialNumber))) + .addBasicConstraintsExt(true, true, -1) + .build(null, caKeys.getPrivate(), TRUSTED_CERT_SIG_ALG); + } + + private static CertificateBuilder customCertificateBuilder( + String subjectName, PublicKey publicKey, PublicKey caKey) + throws CertificateException, IOException { + SecureRandom random = new SecureRandom(); + + CertificateBuilder builder = new CertificateBuilder() + .setSubjectName(subjectName) + .setPublicKey(publicKey) + .setNotBefore( + Date.from(Instant.now().minus(1, ChronoUnit.HOURS))) + .setNotAfter(Date.from(Instant.now().plus(1, ChronoUnit.HOURS))) + .setSerialNumber( + BigInteger.valueOf(random.nextLong(1000000) + 1)) + .addSubjectKeyIdExt(publicKey) + .addAuthorityKeyIdExt(caKey); + builder.addKeyUsageExt( + new boolean[]{true, true, true, true, true, true}); + + return builder; + } +} diff --git a/test/jdk/sun/security/ssl/X509TrustManagerImpl/PKIXExtendedTM.java b/test/jdk/sun/security/ssl/X509TrustManagerImpl/PKIXExtendedTM.java index e2e5e066f9c6a..fcc7cbf73f090 100644 --- a/test/jdk/sun/security/ssl/X509TrustManagerImpl/PKIXExtendedTM.java +++ b/test/jdk/sun/security/ssl/X509TrustManagerImpl/PKIXExtendedTM.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 2025, 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 @@ -36,8 +36,6 @@ * @run main/othervm PKIXExtendedTM 3 */ -import java.net.*; -import java.util.*; import java.io.*; import javax.net.ssl.*; import java.security.Security; @@ -1114,6 +1112,10 @@ static class Test { }; public static void main(String args[]) throws Exception { + // Disable KeyManager's algorithm constraints checking as this test + // is about TrustManager's constraints check. + System.setProperty("jdk.tls.SunX509KeyManager.certChecking", "false"); + if (args.length != 1) { throw new Exception("Incorrect number of arguments"); } diff --git a/test/jdk/sun/security/tools/keytool/PrintSSL.java b/test/jdk/sun/security/tools/keytool/PrintSSL.java index 7cdc0a4577104..9403ae62d924a 100644 --- a/test/jdk/sun/security/tools/keytool/PrintSSL.java +++ b/test/jdk/sun/security/tools/keytool/PrintSSL.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008, 2017, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2008, 2025, 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 @@ -48,6 +48,12 @@ public class PrintSSL { public static void main(String[] args) throws Throwable { + // Disable KeyManager's algorithm constraints checking, + // so we can make keytool print certificate with weak + // MD5withRSA signature algorithm. + System.setProperty( + "jdk.tls.SunX509KeyManager.certChecking", "false"); + Files.deleteIfExists(Paths.get("keystore")); // make sure that "-printcert" works with weak algorithms diff --git a/test/lib/jdk/test/lib/security/CertificateBuilder.java b/test/lib/jdk/test/lib/security/CertificateBuilder.java index 60358c9a4eabf..857d585f02973 100644 --- a/test/lib/jdk/test/lib/security/CertificateBuilder.java +++ b/test/lib/jdk/test/lib/security/CertificateBuilder.java @@ -43,6 +43,7 @@ import sun.security.x509.AlgorithmId; import sun.security.x509.AuthorityInfoAccessExtension; import sun.security.x509.AuthorityKeyIdentifierExtension; +import sun.security.x509.IPAddressName; import sun.security.x509.SubjectKeyIdentifierExtension; import sun.security.x509.BasicConstraintsExtension; import sun.security.x509.CertificateSerialNumber; @@ -233,6 +234,26 @@ public CertificateBuilder addSubjectAltNameDNSExt(List dnsNames) return this; } + /** + * Helper method to add IPAddress types for the SAN extension + * + * @param ipAddresses A {@code List} of names to add as IPAddress + * types + * @throws IOException if an encoding error occurs. + */ + public CertificateBuilder addSubjectAltNameIPExt(List ipAddresses) + throws IOException { + if (!ipAddresses.isEmpty()) { + GeneralNames gNames = new GeneralNames(); + for (String name : ipAddresses) { + gNames.add(new GeneralName(new IPAddressName(name))); + } + addExtension(new SubjectAlternativeNameExtension(false, + gNames)); + } + 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: