diff --git a/src/java.base/share/classes/sun/security/ssl/CertificateAuthoritiesExtension.java b/src/java.base/share/classes/sun/security/ssl/CertificateAuthoritiesExtension.java new file mode 100644 index 00000000000..760daf4b3a1 --- /dev/null +++ b/src/java.base/share/classes/sun/security/ssl/CertificateAuthoritiesExtension.java @@ -0,0 +1,408 @@ +/* + * Copyright (c) 2020, 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.io.IOException; +import java.nio.ByteBuffer; +import java.security.cert.X509Certificate; +import java.text.MessageFormat; +import java.util.*; +import javax.net.ssl.SSLProtocolException; +import javax.security.auth.x500.X500Principal; +import sun.security.ssl.SSLExtension.ExtensionConsumer; +import sun.security.ssl.SSLExtension.SSLExtensionSpec; +import sun.security.ssl.SSLHandshake.HandshakeMessage; + +/** + * Pack of the "certificate_authorities" extensions. + */ +final class CertificateAuthoritiesExtension { + static final HandshakeProducer chNetworkProducer = + new CHCertificateAuthoritiesProducer(); + static final ExtensionConsumer chOnLoadConsumer = + new CHCertificateAuthoritiesConsumer(); + + static final HandshakeProducer crNetworkProducer = + new CRCertificateAuthoritiesProducer(); + static final ExtensionConsumer crOnLoadConsumer = + new CRCertificateAuthoritiesConsumer(); + + static final SSLStringizer ssStringizer = + new CertificateAuthoritiesStringizer(); + + /** + * The "certificate_authorities" extension. + */ + static final class CertificateAuthoritiesSpec implements SSLExtensionSpec { + final List authorities; // certificate authorities + + private CertificateAuthoritiesSpec(List authorities) { + this.authorities = authorities; + } + + private CertificateAuthoritiesSpec(HandshakeContext hc, + ByteBuffer m) throws IOException { + if (m.remaining() < 3) { // 2: the length of the list + // 1: at least one byte authorities + throw hc.conContext.fatal(Alert.DECODE_ERROR, + new SSLProtocolException( + "Invalid certificate_authorities extension: " + + "insufficient data")); + } + + int listLen = Record.getInt16(m); + if (listLen == 0) { + throw hc.conContext.fatal(Alert.DECODE_ERROR, + "Invalid certificate_authorities extension: " + + "no certificate authorities"); + } + + if (listLen > m.remaining()) { + throw hc.conContext.fatal(Alert.DECODE_ERROR, + "Invalid certificate_authorities extension: " + + "insufficient data"); + } + + this.authorities = new LinkedList<>(); + while (listLen > 0) { + // opaque DistinguishedName<1..2^16-1>; + byte[] encoded = Record.getBytes16(m); + listLen -= (2 + encoded.length); + authorities.add(encoded); + } + } + + private static List getEncodedAuthorities( + X509Certificate[] trustedCerts) { + List authorities = new ArrayList<>(trustedCerts.length); + int sizeAccount = 0; + for (X509Certificate cert : trustedCerts) { + X500Principal x500Principal = cert.getSubjectX500Principal(); + byte[] encodedPrincipal = x500Principal.getEncoded(); + sizeAccount += encodedPrincipal.length; + if (sizeAccount > 0xFFFF) { // the size limit of this extension + // If there too many trusts CAs such that they exceed the + // size limit of the extension, enabling this extension + // does not really make sense as there is no way to + // indicate the peer certificate selection accurately. + // In such cases, the extension is just ignored, rather + // than fatal close, for better compatibility and + // interoperability. + return Collections.emptyList(); + } + + if (encodedPrincipal.length != 0) { + authorities.add(encodedPrincipal); + } + } + + return authorities; + } + + X500Principal[] getAuthorities() { + X500Principal[] principals = new X500Principal[authorities.size()]; + int i = 0; + for (byte[] encoded : authorities) { + principals[i++] = new X500Principal(encoded); + } + + return principals; + } + + @Override + public String toString() { + MessageFormat messageFormat = new MessageFormat( + "\"certificate authorities\": '['\n{0}']'", Locale.ENGLISH); + StringBuilder builder = new StringBuilder(512); + for (byte[] encoded : authorities) { + X500Principal principal = new X500Principal(encoded); + builder.append(principal.toString()); + builder.append("\n"); + } + Object[] messageFields = { + Utilities.indent(builder.toString()) + }; + + return messageFormat.format(messageFields); + } + } + + private static final + class CertificateAuthoritiesStringizer implements SSLStringizer { + @Override + public String toString(HandshakeContext hc, ByteBuffer buffer) { + try { + return (new CertificateAuthoritiesSpec(hc, buffer)) + .toString(); + } catch (IOException ioe) { + // For debug logging only, so please swallow exceptions. + return ioe.getMessage(); + } + } + } + + /** + * Network data producer of a "certificate_authorities" extension in + * the ClientHello handshake message. + */ + private static final + class CHCertificateAuthoritiesProducer implements HandshakeProducer { + + // Prevent instantiation of this class. + private CHCertificateAuthoritiesProducer() { + // blank + } + + @Override + public byte[] produce(ConnectionContext context, + HandshakeMessage message) throws IOException { + // The producing happens in client side only. + ClientHandshakeContext chc = (ClientHandshakeContext)context; + + // Is it a supported and enabled extension? + if (!chc.sslConfig.isAvailable( + SSLExtension.CH_CERTIFICATE_AUTHORITIES)) { + if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + SSLLogger.fine( + "Ignore unavailable " + + "certificate_authorities extension"); + } + + return null; // ignore the extension + } + + // Produce the extension. + X509Certificate[] caCerts = + chc.sslContext.getX509TrustManager().getAcceptedIssuers(); + if (caCerts.length == 0) { + if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + SSLLogger.fine( + "No available certificate authorities"); + } + + return null; // ignore the extension + } + + List encodedCAs = + CertificateAuthoritiesSpec.getEncodedAuthorities(caCerts); + if (encodedCAs.isEmpty()) { + if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + SSLLogger.warning( + "The number of CAs exceeds the maximum size" + + "of the certificate_authorities extension"); + } + + return null; // ignore the extension + } + + CertificateAuthoritiesSpec spec = + new CertificateAuthoritiesSpec(encodedCAs); + + int vectorLen = 0; + for (byte[] encoded : spec.authorities) { + vectorLen += encoded.length + 2; + } + + byte[] extData = new byte[vectorLen + 2]; + ByteBuffer m = ByteBuffer.wrap(extData); + Record.putInt16(m, vectorLen); + for (byte[] encoded : spec.authorities) { + Record.putBytes16(m, encoded); + } + + // Update the context. + chc.handshakeExtensions.put( + SSLExtension.CH_CERTIFICATE_AUTHORITIES, spec); + + return extData; + } + } + + /** + * Network data consumer of a "certificate_authorities" extension in + * the ClientHello handshake message. + */ + private static final + class CHCertificateAuthoritiesConsumer implements ExtensionConsumer { + + // Prevent instantiation of this class. + private CHCertificateAuthoritiesConsumer() { + // blank + } + + @Override + public void consume(ConnectionContext context, + HandshakeMessage message, ByteBuffer buffer) throws IOException { + + // The consuming happens in server side only. + ServerHandshakeContext shc = (ServerHandshakeContext)context; + + // Is it a supported and enabled extension? + if (!shc.sslConfig.isAvailable( + SSLExtension.CH_CERTIFICATE_AUTHORITIES)) { + if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + SSLLogger.fine( + "Ignore unavailable " + + "certificate_authorities extension"); + } + + return; // ignore the extension + } + + // Parse the extension. + CertificateAuthoritiesSpec spec = + new CertificateAuthoritiesSpec(shc, buffer); + + // Update the context. + shc.peerSupportedAuthorities = spec.getAuthorities(); + shc.handshakeExtensions.put( + SSLExtension.CH_CERTIFICATE_AUTHORITIES, spec); + + // No impact on session resumption. + } + } + + /** + * Network data producer of a "certificate_authorities" extension in + * the CertificateRequest handshake message. + */ + private static final + class CRCertificateAuthoritiesProducer implements HandshakeProducer { + + // Prevent instantiation of this class. + private CRCertificateAuthoritiesProducer() { + // blank + } + + @Override + public byte[] produce(ConnectionContext context, + HandshakeMessage message) throws IOException { + // The producing happens in server side only. + ServerHandshakeContext shc = (ServerHandshakeContext)context; + + // Is it a supported and enabled extension? + if (!shc.sslConfig.isAvailable( + SSLExtension.CR_CERTIFICATE_AUTHORITIES)) { + if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + SSLLogger.fine( + "Ignore unavailable " + + "certificate_authorities extension"); + } + + return null; // ignore the extension + } + + // Produce the extension. + X509Certificate[] caCerts = + shc.sslContext.getX509TrustManager().getAcceptedIssuers(); + if (caCerts.length == 0) { + if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + SSLLogger.fine( + "No available certificate authorities"); + } + + return null; // ignore the extension + } + + List encodedCAs = + CertificateAuthoritiesSpec.getEncodedAuthorities(caCerts); + if (encodedCAs.isEmpty()) { + if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + SSLLogger.warning( + "Too many certificate authorities to use " + + "the certificate_authorities extension"); + } + + return null; // ignore the extension + } + + CertificateAuthoritiesSpec spec = + new CertificateAuthoritiesSpec(encodedCAs); + + int vectorLen = 0; + for (byte[] encoded : spec.authorities) { + vectorLen += encoded.length + 2; + } + + byte[] extData = new byte[vectorLen + 2]; + ByteBuffer m = ByteBuffer.wrap(extData); + Record.putInt16(m, vectorLen); + for (byte[] encoded : spec.authorities) { + Record.putBytes16(m, encoded); + } + + // Update the context. + shc.handshakeExtensions.put( + SSLExtension.CR_CERTIFICATE_AUTHORITIES, spec); + + return extData; + } + } + + /** + * Network data consumer of a "certificate_authorities" extension in + * the CertificateRequest handshake message. + */ + private static final + class CRCertificateAuthoritiesConsumer implements ExtensionConsumer { + + // Prevent instantiation of this class. + private CRCertificateAuthoritiesConsumer() { + // blank + } + + @Override + public void consume(ConnectionContext context, + HandshakeMessage message, ByteBuffer buffer) throws IOException { + + // The consuming happens in client side only. + ClientHandshakeContext chc = (ClientHandshakeContext)context; + + // Is it a supported and enabled extension? + if (!chc.sslConfig.isAvailable( + SSLExtension.CR_CERTIFICATE_AUTHORITIES)) { + if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { + SSLLogger.fine( + "Ignore unavailable " + + "certificate_authorities extension"); + } + + return; // ignore the extension + } + + // Parse the extension. + CertificateAuthoritiesSpec spec = + new CertificateAuthoritiesSpec(chc, buffer); + + // Update the context. + chc.peerSupportedAuthorities = spec.getAuthorities(); + chc.handshakeExtensions.put( + SSLExtension.CR_CERTIFICATE_AUTHORITIES, spec); + + // No impact on session resumption. + } + } +} diff --git a/src/java.base/share/classes/sun/security/ssl/CertificateRequest.java b/src/java.base/share/classes/sun/security/ssl/CertificateRequest.java index e6ca0ed5f0f..9356894b7fd 100644 --- a/src/java.base/share/classes/sun/security/ssl/CertificateRequest.java +++ b/src/java.base/share/classes/sun/security/ssl/CertificateRequest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2020, 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 @@ -200,14 +200,13 @@ String[] getKeyTypes() { } X500Principal[] getAuthorities() { - List principals = - new ArrayList<>(authorities.size()); + X500Principal[] principals = new X500Principal[authorities.size()]; + int i = 0; for (byte[] encoded : authorities) { - X500Principal principal = new X500Principal(encoded); - principals.add(principal); + principals[i++] = new X500Principal(encoded); } - return principals.toArray(new X500Principal[0]); + return principals; } @Override @@ -504,14 +503,13 @@ String[] getKeyTypes() { } X500Principal[] getAuthorities() { - List principals = - new ArrayList<>(authorities.size()); + X500Principal[] principals = new X500Principal[authorities.size()]; + int i = 0; for (byte[] encoded : authorities) { - X500Principal principal = new X500Principal(encoded); - principals.add(principal); + principals[i++] = new X500Principal(encoded); } - return principals.toArray(new X500Principal[0]); + return principals; } @Override diff --git a/src/java.base/share/classes/sun/security/ssl/SSLExtension.java b/src/java.base/share/classes/sun/security/ssl/SSLExtension.java index 1c7bc46cf4c..c46786b3dea 100644 --- a/src/java.base/share/classes/sun/security/ssl/SSLExtension.java +++ b/src/java.base/share/classes/sun/security/ssl/SSLExtension.java @@ -407,7 +407,27 @@ enum SSLExtension implements SSLStringizer { null, PskKeyExchangeModesExtension.chOnTradeAbsence, PskKeyExchangeModesExtension.pkemStringizer), - CERTIFICATE_AUTHORITIES (0x002F, "certificate_authorities"), + + CH_CERTIFICATE_AUTHORITIES (0x002F, "certificate_authorities", + SSLHandshake.CLIENT_HELLO, + ProtocolVersion.PROTOCOLS_OF_13, + CertificateAuthoritiesExtension.chNetworkProducer, + CertificateAuthoritiesExtension.chOnLoadConsumer, + null, + null, + null, + CertificateAuthoritiesExtension.ssStringizer), + + CR_CERTIFICATE_AUTHORITIES (0x002F, "certificate_authorities", + SSLHandshake.CERTIFICATE_REQUEST, + ProtocolVersion.PROTOCOLS_OF_13, + CertificateAuthoritiesExtension.crNetworkProducer, + CertificateAuthoritiesExtension.crOnLoadConsumer, + null, + null, + null, + CertificateAuthoritiesExtension.ssStringizer), + OID_FILTERS (0x0030, "oid_filters"), POST_HANDSHAKE_AUTH (0x0030, "post_handshake_auth"), @@ -725,6 +745,50 @@ static final class ClientExtensions { extensions.remove(CH_MAX_FRAGMENT_LENGTH); } + // To switch on certificate_authorities extension in ClientHello. + // + // Note: Please be careful to enable this extension in ClientHello. + // + // In practice, if the server certificate cannot be validated by + // the underlying programs, the user may manually check the + // certificate in order to access the service. The certificate + // could be accepted manually, and the handshake continues. For + // example, the browsers provide the manual option to accept + // untrusted server certificate. If this extension is enabled in + // the ClientHello handshake message, and the server's certificate + // does not chain back to any of the CAs in the extension, then the + // server will terminate the handshake and close the connection. + // There is no chance for the client to perform the manual check. + // Therefore, enabling this extension in ClientHello may lead to + // unexpected compatibility issues for such cases. + // + // According to TLS 1.3 specification [RFC 8446] the maximum size + // of the certificate_authorities extension is 2^16 bytes. The + // maximum TLS record size is 2^14 bytes. If the handshake + // message is bigger than maximum TLS record size, it should be + // splitted into several records. In fact, some server + // implementations do not allow ClientHello messages bigger than + // the maximum TLS record size and will immediately abort the + // connection with a fatal alert. Therefore, if the client trusts + // too many certificate authorities, there may be unexpected + // interoperability issues. + // + // Furthermore, if the client trusts more CAs such that it exceeds + // the size limit of the extension, enabling this extension in + // client side does not really make sense any longer as there is + // no way to indicate the server certificate selection accurately. + // + // In general, a server does not use multiple certificates issued + // from different CAs. It is not expected to use this extension a + // lot in practice. When there is a need to use this extension + // in ClientHello handshake message, please take care of the + // potential compatibility and interoperability issues above. + enableExtension = Utilities.getBooleanProperty( + "jdk.tls.client.enableCAExtension", false); + if (!enableExtension) { + extensions.remove(CH_CERTIFICATE_AUTHORITIES); + } + defaults = Collections.unmodifiableCollection(extensions); } } diff --git a/src/java.base/share/classes/sun/security/ssl/X509Authentication.java b/src/java.base/share/classes/sun/security/ssl/X509Authentication.java index f00c7247c16..5810a7e0132 100644 --- a/src/java.base/share/classes/sun/security/ssl/X509Authentication.java +++ b/src/java.base/share/classes/sun/security/ssl/X509Authentication.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2020, 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 @@ -230,12 +230,14 @@ private SSLPossession createClientPossession( if (chc.conContext.transport instanceof SSLSocketImpl) { clientAlias = km.chooseClientAlias( new String[] { keyType }, - chc.peerSupportedAuthorities, + chc.peerSupportedAuthorities == null ? null : + chc.peerSupportedAuthorities.clone(), (SSLSocket)chc.conContext.transport); } else if (chc.conContext.transport instanceof SSLEngineImpl) { clientAlias = km.chooseEngineClientAlias( new String[] { keyType }, - chc.peerSupportedAuthorities, + chc.peerSupportedAuthorities == null ? null : + chc.peerSupportedAuthorities.clone(), (SSLEngine)chc.conContext.transport); } @@ -284,10 +286,14 @@ private SSLPossession createServerPossession( String serverAlias = null; if (shc.conContext.transport instanceof SSLSocketImpl) { serverAlias = km.chooseServerAlias(keyType, - null, (SSLSocket)shc.conContext.transport); + shc.peerSupportedAuthorities == null ? null : + shc.peerSupportedAuthorities.clone(), + (SSLSocket)shc.conContext.transport); } else if (shc.conContext.transport instanceof SSLEngineImpl) { serverAlias = km.chooseEngineServerAlias(keyType, - null, (SSLEngine)shc.conContext.transport); + shc.peerSupportedAuthorities == null ? null : + shc.peerSupportedAuthorities.clone(), + (SSLEngine)shc.conContext.transport); } if (serverAlias == null) { diff --git a/test/jdk/javax/net/ssl/templates/SSLContextTemplate.java b/test/jdk/javax/net/ssl/templates/SSLContextTemplate.java index 65e599bc251..f3cad4ddfe1 100644 --- a/test/jdk/javax/net/ssl/templates/SSLContextTemplate.java +++ b/test/jdk/javax/net/ssl/templates/SSLContextTemplate.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2020, 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 @@ -34,9 +34,7 @@ import java.security.cert.CertificateFactory; import java.security.spec.PKCS8EncodedKeySpec; import java.util.Base64; -import javax.net.ssl.KeyManagerFactory; -import javax.net.ssl.SSLContext; -import javax.net.ssl.TrustManagerFactory; +import javax.net.ssl.*; /** * SSLContext template to speed up JSSE tests. @@ -46,10 +44,9 @@ public interface SSLContextTemplate { * Create an instance of SSLContext for client use. */ default SSLContext createClientSSLContext() throws Exception { - return createSSLContext(trustedCertStrs, - endEntityCertStrs, endEntityPrivateKeys, - endEntityPrivateKeyAlgs, - endEntityPrivateKeyNames, + return createSSLContext( + createClientKeyManager(), + createClientTrustManager(), getClientContextParameters()); } @@ -57,13 +54,53 @@ default SSLContext createClientSSLContext() throws Exception { * Create an instance of SSLContext for server use. */ default SSLContext createServerSSLContext() throws Exception { - return createSSLContext(trustedCertStrs, - endEntityCertStrs, endEntityPrivateKeys, + return createSSLContext( + createServerKeyManager(), + createServerTrustManager(), + getServerContextParameters()); + } + + /* + * Create an instance of KeyManager for client use. + */ + default KeyManager createClientKeyManager() throws Exception { + return createKeyManager( + endEntityCertStrs, + endEntityPrivateKeys, + endEntityPrivateKeyAlgs, + endEntityPrivateKeyNames, + getServerContextParameters()); + } + + /* + * Create an instance of TrustManager for client use. + */ + default TrustManager createClientTrustManager() throws Exception { + return createTrustManager( + trustedCertStrs, + getServerContextParameters()); + } + /* + * Create an instance of KeyManager for server use. + */ + default KeyManager createServerKeyManager() throws Exception { + return createKeyManager( + endEntityCertStrs, + endEntityPrivateKeys, endEntityPrivateKeyAlgs, endEntityPrivateKeyNames, getServerContextParameters()); } + /* + * Create an instance of TrustManager for server use. + */ + default TrustManager createServerTrustManager() throws Exception { + return createTrustManager( + trustedCertStrs, + getServerContextParameters()); + } + /* * The parameters used to configure SSLContext. */ @@ -421,80 +458,107 @@ default ContextParameters getServerContextParameters() { * Create an instance of SSLContext with the specified trust/key materials. */ private SSLContext createSSLContext( - String[] trustedMaterials, + KeyManager keyManager, + TrustManager trustManager, + ContextParameters params) throws Exception { + + SSLContext context = SSLContext.getInstance(params.contextProtocol); + context.init( + new KeyManager[] { + keyManager + }, + new TrustManager[] { + trustManager + }, + null); + + return context; + } + + /* + * Create an instance of KeyManager with the specified key materials. + */ + private KeyManager createKeyManager( String[] keyMaterialCerts, String[] keyMaterialKeys, String[] keyMaterialKeyAlgs, String[] keyMaterialKeyNames, ContextParameters params) throws Exception { - KeyStore ts = null; // trust store - KeyStore ks = null; // key store - char passphrase[] = "passphrase".toCharArray(); + char[] passphrase = "passphrase".toCharArray(); // Generate certificate from cert string. CertificateFactory cf = CertificateFactory.getInstance("X.509"); - // Import the trused certs. - ByteArrayInputStream is; - if (trustedMaterials != null && trustedMaterials.length != 0) { - ts = KeyStore.getInstance("JKS"); - ts.load(null, null); - - Certificate[] trustedCert = - new Certificate[trustedMaterials.length]; - for (int i = 0; i < trustedMaterials.length; i++) { - String trustedCertStr = trustedMaterials[i]; - - is = new ByteArrayInputStream(trustedCertStr.getBytes()); - try { - trustedCert[i] = cf.generateCertificate(is); - } finally { - is.close(); - } - - ts.setCertificateEntry("trusted-cert-" + i, trustedCert[i]); - } - } - // Import the key materials. // - // Note that certification pathes bigger than one are not supported yet. - boolean hasKeyMaterials = - (keyMaterialCerts != null) && (keyMaterialCerts.length != 0) && - (keyMaterialKeys != null) && (keyMaterialKeys.length != 0) && - (keyMaterialKeyAlgs != null) && (keyMaterialKeyAlgs.length != 0) && - (keyMaterialCerts.length == keyMaterialKeys.length) && - (keyMaterialCerts.length == keyMaterialKeyAlgs.length); - if (hasKeyMaterials) { - ks = KeyStore.getInstance("JKS"); - ks.load(null, null); - - for (int i = 0; i < keyMaterialCerts.length; i++) { - String keyCertStr = keyMaterialCerts[i]; - - // generate the private key. - PKCS8EncodedKeySpec priKeySpec = new PKCS8EncodedKeySpec( + // Note that certification paths bigger than one are not supported yet. + KeyStore ks = KeyStore.getInstance("JKS"); + ks.load(null, null); + ByteArrayInputStream is; + for (int i = 0; i < keyMaterialCerts.length; i++) { + String keyCertStr = keyMaterialCerts[i]; + + // generate the private key. + PKCS8EncodedKeySpec priKeySpec = new PKCS8EncodedKeySpec( Base64.getMimeDecoder().decode(keyMaterialKeys[i])); - KeyFactory kf = + KeyFactory kf = KeyFactory.getInstance(keyMaterialKeyAlgs[i]); - PrivateKey priKey = kf.generatePrivate(priKeySpec); - - // generate certificate chain - is = new ByteArrayInputStream(keyCertStr.getBytes()); - Certificate keyCert = null; - try { - keyCert = cf.generateCertificate(is); - } finally { - is.close(); - } - - Certificate[] chain = new Certificate[] { keyCert }; - - // import the key entry. - ks.setKeyEntry("cert-" + keyMaterialKeyNames[i], - priKey, passphrase, chain); + PrivateKey priKey = kf.generatePrivate(priKeySpec); + + // generate certificate chain + is = new ByteArrayInputStream(keyCertStr.getBytes()); + Certificate keyCert = null; + try { + keyCert = cf.generateCertificate(is); + } finally { + is.close(); + } + + Certificate[] chain = new Certificate[] { keyCert }; + + // import the key entry. + ks.setKeyEntry("cert-" + keyMaterialKeyNames[i], + priKey, passphrase, chain); + } + + KeyManagerFactory kmf = + KeyManagerFactory.getInstance(params.kmAlgorithm); + kmf.init(ks, passphrase); + + KeyManager[] km = kmf.getKeyManagers(); + + return km[0]; + } + + /* + * Create an instance of TrustManager with the specified trust materials. + */ + private TrustManager createTrustManager( + String[] trustedMaterials, + ContextParameters params) throws Exception { + + // Generate certificate from cert string. + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + + // Import the trusted certs. + KeyStore ts = KeyStore.getInstance("PKCS12"); + ts.load(null, null); + + Certificate[] trustedCert = + new Certificate[trustedMaterials.length]; + ByteArrayInputStream is; + for (int i = 0; i < trustedMaterials.length; i++) { + String trustedCertStr = trustedMaterials[i]; + + is = new ByteArrayInputStream(trustedCertStr.getBytes()); + try { + trustedCert[i] = cf.generateCertificate(is); + } finally { + is.close(); } + + ts.setCertificateEntry("trusted-cert-" + i, trustedCert[i]); } // Create an SSLContext object. @@ -502,17 +566,7 @@ private SSLContext createSSLContext( TrustManagerFactory.getInstance(params.tmAlgorithm); tmf.init(ts); - SSLContext context = SSLContext.getInstance(params.contextProtocol); - if (hasKeyMaterials && ks != null) { - KeyManagerFactory kmf = - KeyManagerFactory.getInstance(params.kmAlgorithm); - kmf.init(ks, passphrase); - - context.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null); - } else { - context.init(null, tmf.getTrustManagers(), null); - } - - return context; + TrustManager[] tms = tmf.getTrustManagers(); + return tms[0]; } } diff --git a/test/jdk/sun/security/ssl/X509KeyManager/CertificateAuthorities.java b/test/jdk/sun/security/ssl/X509KeyManager/CertificateAuthorities.java new file mode 100644 index 00000000000..8bb2f1323e0 --- /dev/null +++ b/test/jdk/sun/security/ssl/X509KeyManager/CertificateAuthorities.java @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2020, 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. + */ + +// +// SunJSSE does not support dynamic system properties, no way to re-use +// system properties in samevm/agentvm mode. +// + +/* + * @test + * @bug 8206925 + * @summary Support the "certificate_authorities" extension + * @library /javax/net/ssl/templates + * @run main/othervm CertificateAuthorities + * @run main/othervm -Djdk.tls.client.enableCAExtension=false + * CertificateAuthorities + * @run main/othervm -Djdk.tls.client.enableCAExtension=true + * CertificateAuthorities + * + * @run main/othervm CertificateAuthorities NEED_CLIENT_AUTH + * @run main/othervm -Djdk.tls.client.enableCAExtension=false + * CertificateAuthorities NEED_CLIENT_AUTH + * @run main/othervm -Djdk.tls.client.enableCAExtension=true + * CertificateAuthorities NEED_CLIENT_AUTH + * + * @run main/othervm CertificateAuthorities WANT_CLIENT_AUTH + * @run main/othervm -Djdk.tls.client.enableCAExtension=false + * CertificateAuthorities WANT_CLIENT_AUTH + * @run main/othervm -Djdk.tls.client.enableCAExtension=true + * CertificateAuthorities WANT_CLIENT_AUTH + */ + +import javax.net.ssl.SSLServerSocket; + +public final class CertificateAuthorities extends SSLSocketTemplate { + final ClientAuthMode clientAuthMode; + + /* + * Run the test case. + */ + public static void main(String[] args) throws Exception { + CertificateAuthorities testCase; + if (args.length != 0) { + testCase = new CertificateAuthorities( + ClientAuthMode.valueOf(args[0])); + } else { + testCase = new CertificateAuthorities( + ClientAuthMode.NO_CLIENT_AUTH); + } + + testCase.run(); + } + + CertificateAuthorities(ClientAuthMode mode) { + this.clientAuthMode = mode; + } + + @Override + protected void configureServerSocket(SSLServerSocket socket) { + if (clientAuthMode == ClientAuthMode.NEED_CLIENT_AUTH) { + socket.setNeedClientAuth(true); + } else if (clientAuthMode == ClientAuthMode.WANT_CLIENT_AUTH) { + socket.setWantClientAuth(true); + } + } + + private static enum ClientAuthMode { + NEED_CLIENT_AUTH, + WANT_CLIENT_AUTH, + NO_CLIENT_AUTH + } +} diff --git a/test/jdk/sun/security/ssl/X509TrustManagerImpl/CacertsLimit.java b/test/jdk/sun/security/ssl/X509TrustManagerImpl/CacertsLimit.java new file mode 100644 index 00000000000..e8a3871c5bb --- /dev/null +++ b/test/jdk/sun/security/ssl/X509TrustManagerImpl/CacertsLimit.java @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8206925 + * @library /javax/net/ssl/templates + * @summary Support the certificate_authorities extension + */ +import javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactory; +import javax.net.ssl.X509TrustManager; +import javax.security.auth.x500.X500Principal; +import java.security.KeyStore; +import java.security.cert.X509Certificate; + +public class CacertsLimit { + public static void main(String[] args) throws Exception { + for (String algorithm : new String[] {"SunX509", "PKIX"}) { + CacertsLimit.ensureLimit(algorithm); + } + } + + private static void ensureLimit(String algorithm) throws Exception { + TrustManagerFactory tmf = TrustManagerFactory.getInstance(algorithm); + tmf.init((KeyStore)null); + TrustManager[] tms = tmf.getTrustManagers(); + + if (tms == null || tms.length == 0) { + throw new Exception("No default key store used for trust manager"); + } + + if (!(tms[0] instanceof X509TrustManager)) { + throw new Exception( + "The trust manger is not an instance of X509TrustManager"); + } + + checkLimit(((X509TrustManager)tms[0]).getAcceptedIssuers()); + } + + private static void checkLimit( + X509Certificate[] trustedCerts) throws Exception { + int sizeAccount = 0; + for (X509Certificate cert : trustedCerts) { + X500Principal x500Principal = cert.getSubjectX500Principal(); + byte[] encodedPrincipal = x500Principal.getEncoded(); + sizeAccount += encodedPrincipal.length; + if (sizeAccount > 0xFFFF) { + throw new Exception( + "There are too many trusted CAs in cacerts. The " + + "certificate_authorities extension cannot be used " + + "for TLS connections. Please rethink about the size" + + "of the cacerts, or have a release note for the " + + "impacted behaviors"); + } else if (sizeAccount > 0x4000) { + throw new Exception( + "There are too many trusted CAs in cacerts. The " + + "certificate_authorities extension cannot be " + + "packaged in one TLS record, which would result in " + + "interoperability issues. Please rethink about the " + + "size of the cacerts, or have a release note for " + + "the impacted behaviors"); + } + } + } +} + diff --git a/test/jdk/sun/security/ssl/X509TrustManagerImpl/TooManyCAs.java b/test/jdk/sun/security/ssl/X509TrustManagerImpl/TooManyCAs.java new file mode 100644 index 00000000000..9e44b1cdc94 --- /dev/null +++ b/test/jdk/sun/security/ssl/X509TrustManagerImpl/TooManyCAs.java @@ -0,0 +1,282 @@ +/* + * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8206925 + * @library /javax/net/ssl/templates + * @summary Support the certificate_authorities extension + * @run main/othervm TooManyCAs + * @run main/othervm -Djdk.tls.client.enableCAExtension=true TooManyCAs + */ +import javax.net.ssl.*; +import javax.security.auth.x500.X500Principal; +import java.io.*; +import java.net.InetAddress; +import java.net.Socket; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.util.Arrays; + +/** + * Check if the connection can be established if the client or server trusts + * more CAs such that it exceeds the size limit of the certificate_authorities + * extension (2^16). + */ +public class TooManyCAs implements SSLContextTemplate { + + private static final String[][][] protocols = { + {{"TLSv1.3"}, {"TLSv1.3"}}, + {{"TLSv1.3", "TLSv1.2"}, {"TLSv1.3"}}, + {{"TLSv1.3"}, {"TLSv1.3", "TLSv1.2"}}, + }; + + private final String[] clientProtocols; + private final String[] serverProtocols; + private final boolean needClientAuth; + + TooManyCAs(int index, boolean needClientAuth) { + this.clientProtocols = protocols[index][0]; + this.serverProtocols = protocols[index][1]; + this.needClientAuth = needClientAuth; + } + + // Servers are configured before clients, increment test case after. + void configureClientSocket(SSLSocket clientSocket) { + System.err.print("Setting client protocol(s): "); + Arrays.stream(clientProtocols).forEachOrdered(System.err::print); + System.err.println(); + + clientSocket.setEnabledProtocols(clientProtocols); + } + + void configureServerSocket(SSLServerSocket serverSocket) { + System.err.print("Setting server protocol(s): "); + Arrays.stream(serverProtocols).forEachOrdered(System.err::print); + System.err.println(); + + serverSocket.setEnabledProtocols(serverProtocols); + if (needClientAuth) { + serverSocket.setNeedClientAuth(true); + } + } + + @Override + public TrustManager createClientTrustManager() throws Exception { + TrustManager trustManager = + SSLContextTemplate.super.createClientTrustManager(); + return new BogusX509TrustManager( + (X509TrustManager)trustManager); + } + + @Override + public TrustManager createServerTrustManager() throws Exception { + TrustManager trustManager = + SSLContextTemplate.super.createServerTrustManager(); + return new BogusX509TrustManager( + (X509TrustManager)trustManager); + } + + /* + * Run the test case. + */ + public static void main(String[] args) throws Exception { + for (int i = 0; i < protocols.length; i++) { + (new TooManyCAs(i, false)).run(); + (new TooManyCAs(i, true)).run(); + } + } + + private void run() throws Exception { + SSLServerSocket listenSocket = null; + SSLSocket serverSocket = null; + ClientSocket clientSocket = null; + try { + SSLServerSocketFactory serversocketfactory = + createServerSSLContext().getServerSocketFactory(); + listenSocket = + (SSLServerSocket)serversocketfactory.createServerSocket(0); + listenSocket.setNeedClientAuth(false); + listenSocket.setEnableSessionCreation(true); + listenSocket.setUseClientMode(false); + configureServerSocket(listenSocket); + + System.err.println("Starting client"); + clientSocket = new ClientSocket(listenSocket.getLocalPort()); + clientSocket.start(); + + System.err.println("Accepting client requests"); + serverSocket = (SSLSocket)listenSocket.accept(); + + if (!clientSocket.isDone) { + System.err.println("Waiting 3 seconds for client "); + Thread.sleep(3000); + } + + System.err.println("Sending data to client ..."); + String serverData = "Hi, I am server"; + BufferedWriter os = new BufferedWriter( + new OutputStreamWriter(serverSocket.getOutputStream())); + os.write(serverData, 0, serverData.length()); + os.newLine(); + os.flush(); + } finally { + if (listenSocket != null) { + listenSocket.close(); + } + + if (serverSocket != null) { + serverSocket.close(); + } + } + + if (clientSocket != null && clientSocket.clientException != null) { + throw clientSocket.clientException; + } + } + + private class ClientSocket extends Thread{ + boolean isDone = false; + int serverPort = 0; + Exception clientException; + + public ClientSocket(int serverPort) { + this.serverPort = serverPort; + } + + @Override + public void run() { + SSLSocket clientSocket = null; + String clientData = "Hi, I am client"; + try { + System.err.println( + "Connecting to server at port " + serverPort); + SSLSocketFactory sslSocketFactory = + createClientSSLContext().getSocketFactory(); + clientSocket = (SSLSocket)sslSocketFactory.createSocket( + InetAddress.getLocalHost(), serverPort); + configureClientSocket(clientSocket); + + System.err.println("Sending data to server ..."); + + BufferedWriter os = new BufferedWriter( + new OutputStreamWriter(clientSocket.getOutputStream())); + os.write(clientData, 0, clientData.length()); + os.newLine(); + os.flush(); + + System.err.println("Reading data from server"); + BufferedReader is = new BufferedReader( + new InputStreamReader(clientSocket.getInputStream())); + String data = is.readLine(); + System.err.println("Received Data from server: " + data); + } catch (Exception e) { + clientException = e; + System.err.println("unexpected client exception: " + e); + } finally { + if (clientSocket != null) { + try { + clientSocket.close(); + System.err.println("client socket closed"); + } catch (IOException ioe) { + clientException = ioe; + } + } + + isDone = true; + } + } + } + + // Construct a bogus trust manager which has more CAs such that exceed + // the size limit of the certificate_authorities extension (2^16). + private static final class BogusX509TrustManager + extends X509ExtendedTrustManager implements X509TrustManager { + private final X509ExtendedTrustManager tm; + + private BogusX509TrustManager(X509TrustManager trustManager) { + this.tm = (X509ExtendedTrustManager)trustManager; + } + + @Override + public void checkClientTrusted(X509Certificate[] chain, + String authType, Socket socket) throws CertificateException { + tm.checkClientTrusted(chain, authType, socket); + } + + @Override + public void checkServerTrusted(X509Certificate[] chain, + String authType, Socket socket) throws CertificateException { + tm.checkServerTrusted(chain, authType, socket); + } + + @Override + public void checkClientTrusted(X509Certificate[] chain, + String authType, SSLEngine sslEngine) throws CertificateException { + + tm.checkClientTrusted(chain, authType, sslEngine); + } + + @Override + public void checkServerTrusted(X509Certificate[] chain, + String authType, SSLEngine sslEngine) throws CertificateException { + + tm.checkServerTrusted(chain, authType, sslEngine); + } + + @Override + public void checkClientTrusted(X509Certificate[] chain, + String authType) throws CertificateException { + tm.checkServerTrusted(chain, authType); + } + + @Override + public void checkServerTrusted(X509Certificate[] chain, + String authType) throws CertificateException { + tm.checkServerTrusted(chain, authType); + } + + @Override + public X509Certificate[] getAcceptedIssuers() { + X509Certificate[] trustedCerts = tm.getAcceptedIssuers(); + int sizeAccount = 0; + for (X509Certificate cert: trustedCerts) { + X500Principal x500Principal = cert.getSubjectX500Principal(); + byte[] encodedPrincipal = x500Principal.getEncoded(); + sizeAccount += encodedPrincipal.length; + } + + // 0xFFFF: the size limit of the certificate_authorities extension + int duplicated = (0xFFFF + sizeAccount) / sizeAccount; + X509Certificate[] returnedCAs = + new X509Certificate[trustedCerts.length * duplicated]; + for (int i = 0; i < duplicated; i++) { + System.arraycopy(trustedCerts, 0, + returnedCAs, + i * trustedCerts.length + 0, trustedCerts.length); + } + + return returnedCAs; + } + } +}