--- old/src/java.base/share/classes/javax/net/ssl/ExtendedSSLSession.java 2017-07-18 14:01:56.351786852 -0300 +++ new/src/java.base/share/classes/javax/net/ssl/ExtendedSSLSession.java 2017-07-18 14:01:56.259786608 -0300 @@ -87,6 +87,37 @@ public abstract String[] getPeerSupportedSignatureAlgorithms(); /** + * Obtains an array of {@link CertificateAuthority} objects + * representing certificate authority indications sent by either + * a Client or a Server within a TLS session. + *

+ * A Client may optionally send this information as part of a + * {@code ClientHello} message, during the TLS handshake. A + * Server may optionally send this information as part of a + * {@code CertificateRequest} message, during the TLS handshake. + *

+ * If certificate authorities indications are available on a TLS + * session, the Client or Server certificate selection is expected + * to be based on it. + *

+ * This is part of Certificate Authorities TLS extension (TLS 1.3). + * See further information in + * TLS 1.3. + * + * @see CertificateAuthority + * + * @return an array of CertificateAuthority objects with certificate + * authorities indications for this TLS session. + * Returns {@code null} if no information regarding + * certificate authorities is available. + * @throws UnsupportedOperationException if the underlying provider + * does not implement the operation + */ + public CertificateAuthority[] getCertificateAuthorities() { + throw new UnsupportedOperationException(); + } + + /** * Obtains a {@link List} containing all {@link SNIServerName}s * of the requested Server Name Indication (SNI) extension. *

--- old/src/java.base/share/classes/javax/net/ssl/SSLParameters.java 2017-07-18 14:01:56.683787734 -0300 +++ new/src/java.base/share/classes/javax/net/ssl/SSLParameters.java 2017-07-18 14:01:56.588787481 -0300 @@ -88,6 +88,7 @@ private boolean enableRetransmissions = true; private int maximumPacketSize = 0; private String[] applicationProtocols = new String[0]; + private boolean useCertificateAuthorities = false; /** * Constructs SSLParameters. @@ -675,4 +676,55 @@ } applicationProtocols = tempProtocols; } + + /** + * Returns whether or not the use of Certificate Authorities + * TLS extension is enabled on either a Client or a Server. + *

+ * When the extension is enabled on a Client, information + * regarding its trusted certificate authorities is sent in + * a {@code ClientHello} message, during the TLS handshake. + *

+ * When the extension is enabled on a Server, information + * regarding its trusted certificate authorities is sent in + * a {@code CertificateRequest} message, during the TLS handshake + * (assuming the Server requires client certificate authentication). + *

+ * This is part of Certificate Authorities TLS extension (TLS 1.3). + * See further information in + * TLS 1.3. + * + * @return a boolean value indicating whether or not the use of + * Certificate Authorities TLS extension is enabled. + */ + public boolean getUseCertificateAuthorities() { + return useCertificateAuthorities; + } + + /** + * Sets whether or not the use of Certificate Authorities + * TLS extension should be enabled on either a Client or a Server. + *

+ * When the extension is enabled on a Client, information + * regarding its trusted certificate authorities is sent in + * a {@code ClientHello} message, during the TLS handshake. + *

+ * When the extension is enabled on a Server, information + * regarding its trusted certificate authorities is sent in + * a {@code CertificateRequest} message, during the TLS handshake + * (assuming the Server requires Client certificate authentication). + *

+ * This is part of Certificate Authorities TLS extension (TLS 1.3). + * See further information in + * TLS 1.3. + * + * @param useCertificateAuthorities whether or not the use of Certificate + * Authorities TLS extension should be enabled. + * @throws UnsupportedOperationException if the underlying provider + * does not implement the operation. + */ + public void setUseCertificateAuthorities(boolean useCertificateAuthorities) { + this.useCertificateAuthorities = useCertificateAuthorities; + } + } --- old/src/java.base/share/classes/sun/security/ssl/ClientHandshaker.java 2017-07-18 14:01:57.003788583 -0300 +++ new/src/java.base/share/classes/sun/security/ssl/ClientHandshaker.java 2017-07-18 14:01:56.912788341 -0300 @@ -777,7 +777,8 @@ && (type != ExtensionType.EXT_ALPN) && (type != ExtensionType.EXT_RENEGOTIATION_INFO) && (type != ExtensionType.EXT_STATUS_REQUEST) - && (type != ExtensionType.EXT_STATUS_REQUEST_V2)) { + && (type != ExtensionType.EXT_STATUS_REQUEST_V2) + && (type != ExtensionType.EXT_CERTIFICATE_AUTHORITIES)) { // Note: Better to check client requested extensions rather // than all supported extensions. fatalSE(Alerts.alert_unsupported_extension, @@ -1526,6 +1527,43 @@ } } + if ((conn != null && conn.getSSLParameters().getUseCertificateAuthorities()) || + (engine != null && engine.getSSLParameters().getUseCertificateAuthorities())) { + if (maxProtocolVersion.useTLS12PlusSpec()) { + ArrayList certificateAuthorities = new ArrayList<>(); + final X509TrustManager tm = sslContext.getX509TrustManager(); + final X509Certificate[] acceptedIssuers = tm.getAcceptedIssuers(); + for (X509Certificate acceptedIssuer : acceptedIssuers) { + try { + CertificateAuthorityImpl candidateCertificateAuthority = + new CertificateAuthorityImpl(acceptedIssuer); + boolean candidateIsPresent = false; + // Check that a Certificate Authority with the same + // distinguished name has not been added + for (CertificateAuthority addedCertificateAuthority : certificateAuthorities) { + if (addedCertificateAuthority.equals(candidateCertificateAuthority)) { + candidateIsPresent = true; + break; + } + } + if (!candidateIsPresent) { + certificateAuthorities.add( + candidateCertificateAuthority); + } + } catch (Exception e) { + // Certificate Authorities on the client-side are a best-effort. + // However, no exception is expected. + } + } + if (certificateAuthorities.size() > 0) { + CertificateAuthority[] certificateAuthoritiesArray = + new CertificateAuthority[certificateAuthorities.size()]; + certificateAuthorities.toArray(certificateAuthoritiesArray); + clientHelloMessage.addCertificateAuthoritiesExtension(certificateAuthoritiesArray); + } + } + } + // add signature_algorithm extension if (maxProtocolVersion.useTLS12PlusSpec()) { // we will always send the signature_algorithm extension --- old/src/java.base/share/classes/sun/security/ssl/ExtensionType.java 2017-07-18 14:01:57.336789467 -0300 +++ new/src/java.base/share/classes/sun/security/ssl/ExtensionType.java 2017-07-18 14:01:57.247789231 -0300 @@ -105,6 +105,10 @@ static final ExtensionType EXT_STATUS_REQUEST_V2 = e(0x0011, "status_request_v2"); // IANA registry value: 17 + // extensions defined in TLS 1.3 + static final ExtensionType EXT_CERTIFICATE_AUTHORITIES = + e(0x002f, "certificate_authorities");// IANA registry value: 47 + // extensions defined in RFC 5746 static final ExtensionType EXT_RENEGOTIATION_INFO = e(0xff01, "renegotiation_info"); // IANA registry value: 65281 --- old/src/java.base/share/classes/sun/security/ssl/HandshakeMessage.java 2017-07-18 14:01:57.649790298 -0300 +++ new/src/java.base/share/classes/sun/security/ssl/HandshakeMessage.java 2017-07-18 14:01:57.558790056 -0300 @@ -344,6 +344,11 @@ return cipherSuites; } + // add Certificate Authorities extension + void addCertificateAuthoritiesExtension(CertificateAuthority[] certificateAuthorities) { + extensions.add(new CertificateAuthoritiesExtension(certificateAuthorities)); + } + // add renegotiation_info extension void addRenegotiationInfoExtension(byte[] clientVerifyData) { HelloExtension renegotiationInfo = new RenegotiationInfoExtension( --- old/src/java.base/share/classes/sun/security/ssl/HelloExtensions.java 2017-07-18 14:01:57.984791187 -0300 +++ new/src/java.base/share/classes/sun/security/ssl/HelloExtensions.java 2017-07-18 14:01:57.894790948 -0300 @@ -93,6 +93,8 @@ extension = new CertStatusReqExtension(s, extlen); } else if (extType == ExtensionType.EXT_STATUS_REQUEST_V2) { extension = new CertStatusReqListV2Extension(s, extlen); + } else if (extType == ExtensionType.EXT_CERTIFICATE_AUTHORITIES) { + extension = new CertificateAuthoritiesExtension(s, extlen); } else { extension = new UnknownExtension(s, extlen, extType); } --- old/src/java.base/share/classes/sun/security/ssl/SSLEngineImpl.java 2017-07-18 14:01:58.303792034 -0300 +++ new/src/java.base/share/classes/sun/security/ssl/SSLEngineImpl.java 2017-07-18 14:01:58.213791795 -0300 @@ -199,6 +199,9 @@ Collection sniMatchers = Collections.emptyList(); + // Is the use of Certificate Authorities TLS extension enabled? + private boolean useCertificateAuthorities = false; + // Configured application protocol values String[] applicationProtocols = new String[0]; @@ -2208,7 +2211,7 @@ params.setEnableRetransmissions(enableRetransmissions); params.setMaximumPacketSize(maximumPacketSize); params.setApplicationProtocols(applicationProtocols); - + params.setUseCertificateAuthorities(useCertificateAuthorities); return params; } @@ -2233,6 +2236,8 @@ maximumPacketSize = outputRecord.getMaxPacketSize(); } + useCertificateAuthorities = params.getUseCertificateAuthorities(); + List sniNames = params.getServerNames(); if (sniNames != null) { serverNames = sniNames; --- old/src/java.base/share/classes/sun/security/ssl/SSLSessionImpl.java 2017-07-18 14:01:58.639792926 -0300 +++ new/src/java.base/share/classes/sun/security/ssl/SSLSessionImpl.java 2017-07-18 14:01:58.548792684 -0300 @@ -48,6 +48,7 @@ import javax.net.ssl.SSLSessionBindingEvent; import javax.net.ssl.SSLPeerUnverifiedException; import javax.net.ssl.SSLPermission; +import javax.net.ssl.CertificateAuthority; import javax.net.ssl.ExtendedSSLSession; import javax.net.ssl.SNIServerName; @@ -107,6 +108,7 @@ private PrivateKey localPrivateKey; private String[] localSupportedSignAlgs; private String[] peerSupportedSignAlgs; + private CertificateAuthority[] certificateAuthorities; private List requestedServerNames; private List statusResponses; @@ -223,6 +225,17 @@ SignatureAndHashAlgorithm.getAlgorithmNames(algorithms); } + /** + * Sets the certificate authority indications for a TLS session (sent + * by either a Client or a Server). This information will be used for + * certificate selection during the TLS handshake. + * + * @param certificateAuthorities certificate authority indications. + */ + void setCertificateAuthorities(CertificateAuthority[] certificateAuthorities) { + this.certificateAuthorities = certificateAuthorities; + } + void setRequestedServerNames(List requestedServerNames) { this.requestedServerNames = new ArrayList<>(requestedServerNames); } @@ -939,6 +952,19 @@ } /** + * Gets the certificate authority indications for a TLS session (sent + * by either a Client or a Server). This information will be used for + * certificate selection during the TLS handshake. May be {@code null} + * certificate authority indications were not set. + * + * @return certificate authority indications for a TLS session. + */ + @Override + public CertificateAuthority[] getCertificateAuthorities() { + return this.certificateAuthorities; + } + + /** * Obtains a List containing all {@link SNIServerName}s * of the requested Server Name Indication (SNI) extension. */ --- old/src/java.base/share/classes/sun/security/ssl/SSLSocketImpl.java 2017-07-18 14:01:58.962793783 -0300 +++ new/src/java.base/share/classes/sun/security/ssl/SSLSocketImpl.java 2017-07-18 14:01:58.872793544 -0300 @@ -216,6 +216,10 @@ // Is the sniMatchers set to empty with SSLParameters.setSNIMatchers()? private boolean noSniMatcher = false; + // Is the use of Certificate Authorities TLS extension enabled? + // Use variable getter method to query the value. + private boolean useCertificateAuthorities = false; + // Configured application protocol values String[] applicationProtocols = new String[0]; @@ -2579,6 +2583,8 @@ params.setEndpointIdentificationAlgorithm(identificationProtocol); params.setAlgorithmConstraints(algorithmConstraints); + params.setUseCertificateAuthorities(useCertificateAuthorities); + if (sniMatchers.isEmpty() && !noSniMatcher) { // 'null' indicates none has been set params.setSNIMatchers(null); @@ -2624,6 +2630,8 @@ maximumPacketSize = outputRecord.getMaxPacketSize(); } + useCertificateAuthorities = params.getUseCertificateAuthorities(); + List sniNames = params.getServerNames(); if (sniNames != null) { noSniExtension = sniNames.isEmpty(); --- old/src/java.base/share/classes/sun/security/ssl/ServerHandshaker.java 2017-07-18 14:01:59.320794734 -0300 +++ new/src/java.base/share/classes/sun/security/ssl/ServerHandshaker.java 2017-07-18 14:01:59.226794484 -0300 @@ -794,6 +794,17 @@ // algorithms in chooseCipherSuite() } + // set Certificate Authorities + if (protocolVersion.useTLS12PlusSpec()) { + final CertificateAuthoritiesExtension certificateAuthoritiesExtension = + (CertificateAuthoritiesExtension)mesg.extensions. + get(ExtensionType.EXT_CERTIFICATE_AUTHORITIES); + if (certificateAuthoritiesExtension != null) { + session.setCertificateAuthorities( + certificateAuthoritiesExtension.getCertificateAuthorities()); + } + } + // set the server name indication in the session List clientHelloSNI = Collections.emptyList(); --- old/src/java.base/share/classes/sun/security/ssl/X509KeyManagerImpl.java 2017-07-18 14:01:59.669795660 -0300 +++ new/src/java.base/share/classes/sun/security/ssl/X509KeyManagerImpl.java 2017-07-18 14:01:59.578795418 -0300 @@ -54,7 +54,7 @@ * . it makes an effort to choose the key that matches best, i.e. one that * is not expired and has the appropriate certificate extensions. * - * Note that this code is not explicitly performance optimzied yet. + * Note that this code is not explicitly performance optimized yet. * * @author Andreas Sterbenz */ @@ -135,6 +135,7 @@ Principal[] issuers, Socket socket) { return chooseAlias(getKeyTypes(keyType), issuers, CheckType.SERVER, getAlgorithmConstraints(socket), + getCertificateAuthorities(socket), X509TrustManagerImpl.getRequestedServerNames(socket), "HTTPS"); // The SNI HostName is a fully qualified domain name. // The certificate selection scheme for SNI HostName @@ -153,6 +154,7 @@ Principal[] issuers, SSLEngine engine) { return chooseAlias(getKeyTypes(keyType), issuers, CheckType.SERVER, getAlgorithmConstraints(engine), + getCertificateAuthorities(engine), X509TrustManagerImpl.getRequestedServerNames(engine), "HTTPS"); // The SNI HostName is a fully qualified domain name. // The certificate selection scheme for SNI HostName @@ -238,6 +240,40 @@ return new SSLAlgorithmConstraints(engine, true); } + private CertificateAuthority[] getCertificateAuthorities(Socket socket) { + if(socket != null && socket.isConnected() && socket instanceof SSLSocket){ + final SSLSocket sslSocket = (SSLSocket)socket; + final SSLParameters sslParameters = sslSocket.getSSLParameters(); + if (sslParameters.getUseCertificateAuthorities()) { + return getCertificateAuthorities(sslSocket.getHandshakeSession()); + } + } + return null; + } + + private CertificateAuthority[] getCertificateAuthorities(SSLEngine engine) { + if (engine != null) { + final SSLParameters sslParameters = engine.getSSLParameters(); + if (sslParameters.getUseCertificateAuthorities()) { + return getCertificateAuthorities(engine.getHandshakeSession()); + } + } + return null; + } + + private CertificateAuthority[] getCertificateAuthorities(SSLSession session) { + if (session != null) { + final ProtocolVersion protocolVersion = + ProtocolVersion.valueOf(session.getProtocol()); + if (protocolVersion.useTLS12PlusSpec()) { + if (session instanceof ExtendedSSLSession) { + return ((ExtendedSSLSession)session).getCertificateAuthorities(); + } + } + } + return null; + } + // 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() @@ -364,11 +400,12 @@ CheckType checkType, AlgorithmConstraints constraints) { return chooseAlias(keyTypeList, issuers, - checkType, constraints, null, null); + checkType, constraints, null, null, null); } private String chooseAlias(List keyTypeList, Principal[] issuers, CheckType checkType, AlgorithmConstraints constraints, + CertificateAuthority[] certificateAuthorities, List requestedServerNames, String idAlgorithm) { if (keyTypeList == null || keyTypeList.isEmpty()) { @@ -381,7 +418,8 @@ try { List results = getAliases(i, keyTypeList, issuerSet, false, checkType, constraints, - requestedServerNames, idAlgorithm); + certificateAuthorities, requestedServerNames, + idAlgorithm); if (results != null) { // the results will either be a single perfect match // or 1 or more imperfect matches @@ -436,7 +474,7 @@ try { List results = getAliases(i, keyTypeList, issuerSet, true, checkType, constraints, - null, null); + null, null, null); if (results != null) { if (allResults == null) { allResults = new ArrayList(); @@ -713,6 +751,7 @@ List keyTypes, Set issuerSet, boolean findAll, CheckType checkType, AlgorithmConstraints constraints, + CertificateAuthority[] certificateAuthorities, List requestedServerNames, String idAlgorithm) throws Exception { @@ -781,6 +820,33 @@ continue; } } + + // check that certificate chain has an indicated certificate authority + // (if indications are available) + if (certificateAuthorities != null) { + boolean foundCertificateAuthority = false; + // Iterate the certificate chain starting from root certificate + look_for_ca: + for (int i = chain.length; i > 0;) { + i--; + final X509Certificate cert = (X509Certificate) chain[i]; + // Can safely cast because of + // previous check for X509Certificate + for (CertificateAuthority certificateAuthority : certificateAuthorities) { + try { + if (certificateAuthority.implies(cert)) { + foundCertificateAuthority = true; + break look_for_ca; + } + } catch (Exception e1) { + // Certificate matching is a best-effort. Continue looking for matches. + } + } + } + if (foundCertificateAuthority == false) { + continue; + } + } // check the algorithm constraints if (constraints != null && --- /dev/null 2017-07-18 10:08:09.829212503 -0300 +++ new/src/java.base/share/classes/javax/net/ssl/CertificateAuthority.java 2017-07-18 14:01:59.907796292 -0300 @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2017, Red Hat, Inc. and/or its affiliates. + * + * 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. + */ +package javax.net.ssl; + +import java.security.cert.CertificateEncodingException; +import java.security.cert.X509Certificate; + +/** + * {@code CertificateAuthority} class contains information to + * identify a single certificate in the context of the Certificate Authorities + * TLS Extension (TLS 1.3). + *

+ * Client and servers may optionally provide one or multiple indications + * regarding its trusted certification authorities (either root or subordinate) + * as a hint for the other side to select a certificate. This decreases + * the probability of a handshake failure caused by an untrusted certificate + * chain. + *

+ * This class can be mapped to a single element of the {@code authorities} member + * in {@code CertificateAuthoritiesExtension} structure, as specified in + * TLS 1.3. + * + * @see CertificateAuthoritiesExtension + * + * @author Martin Balao (mbalao@redhat.com) + */ +public interface CertificateAuthority { + + /** + * Returns a byte array containing a single certificate authority indication, + * encoded according to + * TLS 1.3. + * + * @return byte array containing a single certificate authority indication. + */ + public byte[] getEncoded(); + + /** + * Returns whether or not a X.509 certificate is indicated by a + * single certificate authority indication. This decision is based + * on the certificate's subject distinguished name. Thus, it is + * possible that multiple certificates match a given certificate + * authority indication. Further decision on which certificate to + * use is based on other TLS session parameters such as supported + * ciphersuites. + * + * @param certificate X.509 certificate to be checked against a single + * certificate authority indication. + * @return whether or not a X.509 certificate is indicated by a + * single certificate authority indication. + * @throws CertificateEncodingException X.509 certificate is not correctly + * encoded. + * @throws Exception an internal error occurred while matching the X.509 + * certificate against a certificate authority indication. + * This generic exception is unlikely to be thrown and may be caused + * by errors when encoding DER values. + */ + public boolean implies(X509Certificate certificate) + throws CertificateEncodingException, Exception; + +} --- /dev/null 2017-07-18 10:08:09.829212503 -0300 +++ new/src/java.base/share/classes/sun/security/ssl/CertificateAuthoritiesExtension.java 2017-07-18 14:02:00.222797128 -0300 @@ -0,0 +1,171 @@ +/* + * Copyright (c) 2017, Red Hat, Inc. and/or its affiliates. + * + * 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. + */ + +package sun.security.ssl; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import javax.net.ssl.SSLProtocolException; +import javax.net.ssl.CertificateAuthority; + +/** + * {@code CertificateAuthoritiesExtension} class represents the + * Certificate Authorities TLS extension (TLS 1.3), specified by + * TLS 1.3. + *

+ * Clients and servers may optionally use this extension to provide the + * other side information regarding its trusted certification authorities + * (either root or subordinate), as a hint for certificate selection. + * This decreases the probability of a handshake failure caused by an + * untrusted certificate chain. + * + * @see CertificateAuthority + * + * @author Martin Balao (mbalao@redhat.com) + */ + +final class CertificateAuthoritiesExtension extends HelloExtension { + + private final CertificateAuthority[] certificateAuthorities; + + private final int certificateAuthoritiesLength; // in bytes + + static CertificateAuthority[] parseCertificateAuthorities( + byte[] certificateAuthoritiesRawData) throws IOException { + final List parsedCertificateAuthorities = new ArrayList<>(); + int parsedCertificateAuthoritiesPtr = 0; + while (parsedCertificateAuthoritiesPtr < certificateAuthoritiesRawData.length) { + final int parsedCertificateAuthorityLength = + (certificateAuthoritiesRawData[parsedCertificateAuthoritiesPtr] << 8) + + certificateAuthoritiesRawData[parsedCertificateAuthoritiesPtr + 1]; + parsedCertificateAuthoritiesPtr += 2; // 2 bytes for length + final CertificateAuthorityImpl ca = + new CertificateAuthorityImpl(certificateAuthoritiesRawData, + parsedCertificateAuthoritiesPtr, parsedCertificateAuthorityLength); + parsedCertificateAuthoritiesPtr += parsedCertificateAuthorityLength; + parsedCertificateAuthorities.add(ca); + } + final CertificateAuthority[] parsedCertificateAuthoritiesArray = + new CertificateAuthority[parsedCertificateAuthorities.size()]; + return parsedCertificateAuthorities.toArray(parsedCertificateAuthoritiesArray); + } + + CertificateAuthoritiesExtension(HandshakeInStream s, int len) throws IOException { + super(ExtensionType.EXT_CERTIFICATE_AUTHORITIES); + + // check the extension length + if (len >= 2) { + certificateAuthoritiesLength = s.getInt16(); + if (certificateAuthoritiesLength != len - 2 || certificateAuthoritiesLength < 1) { + // Lengths must be consistent. + throw new SSLProtocolException("Malformed " + type + " extension"); + } + final byte[] certificateAuthoritiesRawData = new byte[certificateAuthoritiesLength]; + if (s.read(certificateAuthoritiesRawData, 0, + certificateAuthoritiesRawData.length) != certificateAuthoritiesRawData.length) { + throw new SSLProtocolException("Error when reading Certificate Authorities data"); + } + certificateAuthorities = parseCertificateAuthorities(certificateAuthoritiesRawData); + } else if (len == 0) { + certificateAuthoritiesLength = 0; + certificateAuthorities = null; + } else { + throw new SSLProtocolException("Malformed " + type + " extension"); + } + } + + CertificateAuthoritiesExtension(CertificateAuthority[] certificateAuthorities) { + super(ExtensionType.EXT_CERTIFICATE_AUTHORITIES); + this.certificateAuthorities = certificateAuthorities; + if (this.certificateAuthorities != null) { + int certificateAuthoritiesTotalLength = 0; + for (CertificateAuthority ca : this.certificateAuthorities) { + certificateAuthoritiesTotalLength += ca.getEncoded().length; + } + if (certificateAuthoritiesTotalLength + 2 > 0xFFFF) { + // extension_data has a ceiling of 2^16-1 bytes, and 2 bytes are + // reserved for authorities length + throw new IllegalArgumentException( + "Certificate Authorities data length cannot exceed (65535 - 2) bytes."); + } + certificateAuthoritiesLength = certificateAuthoritiesTotalLength; + } else { + certificateAuthoritiesLength = 0; + } + } + + @Override + int length() { + int baseLength = 2 + 2; // extension_type + extension_data length + if (certificateAuthoritiesLength == 0) { + return baseLength; + } + // baseLength + authorities length + Certificate Authorities + // data length + return baseLength + 2 + certificateAuthoritiesLength; + } + + @Override + void send(HandshakeOutStream s) throws IOException { + + // ExtensionType extension_type; + s.putInt16(type.id); + + // opaque extension_data<0..2^16-1>; + // extension_data is CertificateAuthoritiesExtension + // + if (certificateAuthoritiesLength > 0) { + + // extension_data length + // 2 bytes for authorities length + s.putInt16(certificateAuthoritiesLength + 2); + + // struct { + // DistinguishedName authorities<3..2^16-1>; + // } CertificateAuthoritiesExtension; + // + s.putInt16(certificateAuthoritiesLength); + + for (CertificateAuthority ca : certificateAuthorities) { + final byte[] caData = ca.getEncoded(); + s.write(caData, 0, caData.length); + } + } else { + s.putInt16(0); // extension_data length is 0 + } + } + + CertificateAuthority[] getCertificateAuthorities() { + return certificateAuthorities; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("Extension " + type); + return sb.toString(); + } +} --- /dev/null 2017-07-18 10:08:09.829212503 -0300 +++ new/src/java.base/share/classes/sun/security/ssl/CertificateAuthorityImpl.java 2017-07-18 14:02:00.534797956 -0300 @@ -0,0 +1,138 @@ +/* + * Copyright (c) 2017, Red Hat, Inc. and/or its affiliates. + * + * 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. + */ + +package sun.security.ssl; + +import java.io.IOException; +import java.security.cert.CertificateEncodingException; +import java.security.cert.X509Certificate; +import java.util.Arrays; +import java.util.Objects; + +import javax.net.ssl.CertificateAuthority; + +import sun.security.util.DerOutputStream; +import sun.security.util.DerValue; + +/** + * Implementation for {@link CertificateAuthority} interface. + *

+ * A {@code CertificateAuthorityImpl} instance can be constructed + * by either: + *

+ *

+ * When constructing an {@code CertificateAuthority} instance from + * a raw byte buffer, the certificate authority distinguished name + * is normalized to UTF-8 encoding for later comparisons. + *

+ * {@code CertificateAuthorityImpl#implies(X509Certificate)} method + * builds a new {@code CertificateAuthorityImpl} instance, based on the + * X.509 certificate to be matched, and compares encoded byte arrays + * to decide if the certificate was indicated. + *

+ * See further information in + * TLS 1.3. + * + * @see CertificateAuthoritiesExtension + * @see CertificateAuthority + * + * @author Martin Balao (mbalao@redhat.com) + */ +final class CertificateAuthorityImpl implements CertificateAuthority { + + private static final String exceptionMessage = "Certificate Authority internal exception."; + + private byte[] encodedData; + + CertificateAuthorityImpl(byte[] encodedData, int offset, int length) throws IOException { + + // Normalize the distinguished name in UTF-8 encoding + // so we can later compare CertificateAuthority objects. + final byte[] certificateDNDerEncoded = new byte[length]; + System.arraycopy(encodedData, offset, certificateDNDerEncoded, 0, + certificateDNDerEncoded.length); + final DerValue certificateDNDerValue = new DerValue(certificateDNDerEncoded); + + byte[] certificateDNDerEncodedUTF8 = null; + try (DerOutputStream out = new DerOutputStream()) { + out.putUTF8String(certificateDNDerValue.getAsString()); + certificateDNDerEncodedUTF8 = out.toByteArray(); + } + + this.encodedData = new byte[2 + certificateDNDerEncodedUTF8.length]; + this.encodedData[0] = (byte) ((certificateDNDerEncodedUTF8.length & 0xFF00) >> 8); + this.encodedData[1] = (byte) (certificateDNDerEncodedUTF8.length & 0xFF); + System.arraycopy(certificateDNDerEncodedUTF8, 0, this.encodedData, 2, + certificateDNDerEncodedUTF8.length); + } + + CertificateAuthorityImpl(X509Certificate trustedCertificate) + throws CertificateEncodingException, Exception { + try (DerOutputStream out = new DerOutputStream()) { + out.putUTF8String(trustedCertificate.getSubjectDN().getName()); + final byte[] derEncodedName = out.toByteArray(); + if (derEncodedName.length <= 0 || derEncodedName.length > 0xFFFF) { + throw new IllegalArgumentException( + "Subject distinguished name on the trusted certificate has an invalid length."); + } + encodedData = new byte[2 + derEncodedName.length]; // 2 bytes for + // data length + encodedData[0] = (byte) ((derEncodedName.length & 0xFF00) >> 8); + encodedData[1] = (byte) (derEncodedName.length & 0xFF); + System.arraycopy(derEncodedName, 0, encodedData, 2, derEncodedName.length); + } catch (IOException e) { + throw new Exception(exceptionMessage, e); + } + } + + @Override + public byte[] getEncoded() { + return encodedData; + } + + @Override + public boolean implies(X509Certificate certificate) + throws CertificateEncodingException, Exception { + final CertificateAuthorityImpl certificateAuthority = + new CertificateAuthorityImpl(certificate); + return certificateAuthority.equals(this); + } + + @Override + public boolean equals(Object o) { + if (o.getClass().equals(this.getClass())) { + return Arrays.equals(((CertificateAuthority) o).getEncoded(), this.getEncoded()); + } + return false; + } + + @Override + public int hashCode() { + return Objects.hashCode(encodedData); + } + +} --- /dev/null 2017-07-18 10:08:09.829212503 -0300 +++ new/test/sun/security/ssl/ClientHandshaker/CertificateAuthoritiesClientTest.java 2017-07-18 14:02:00.864798832 -0300 @@ -0,0 +1,330 @@ +/* + * Copyright (c) 2017, Red Hat, Inc. and/or its affiliates. + * + * 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 8046295 + * @summary Certificate Authorities TLS extension (TLS 1.3) test (client-side). + * @author Martin Balao (mbalao@redhat.com) + * + */ + +import java.io.FileInputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Map; +import java.security.KeyStore; +import java.security.MessageDigest; +import java.security.cert.Certificate; +import java.security.cert.X509Certificate; + +import javax.net.ssl.CertificateAuthority; +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLEngineResult.HandshakeStatus; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLEngine; +import javax.net.ssl.SSLEngineResult; +import javax.net.ssl.SSLParameters; +import javax.net.ssl.SSLEngineResult.Status; + +import javax.net.ssl.SSLSession; +import javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactory; +import javax.net.ssl.X509TrustManager; + +import javax.net.ssl.SSLEngineResult.HandshakeStatus; + +final public class CertificateAuthoritiesClientTest { + + // Test data + + private static final String pathToStores = "../../../../javax/net/ssl/etc"; + private static final String keyStoreFile = "keystore"; + private static final String trustStoreFile = "truststore"; + private static final String passwd = "passphrase"; + + private static final String keyFilename = System.getProperty("test.src", "./") + "/" + pathToStores + "/" + + keyStoreFile; + private static String trustFilename = System.getProperty("test.src", "./") + "/" + pathToStores + "/" + + trustStoreFile; + + private static KeyStore ks; + private static KeyStore ts; + private static char[] passphrase; + + private static int certificateAuthoritiesDataLength; + + // Test expected data + + private static byte[] expectedCertificateAuthoritiesHeader = null; + private static ArrayList certificateAuthoritiesDataExpected = null; + + private static class CertificateAuthorityExpectedData { + public int length; + public byte[] encodedData; + + public CertificateAuthorityExpectedData(byte[] encodedDerDN) { + length = encodedDerDN.length + 2; // 2 bytes for length + encodedData = new byte[length]; + encodedData[0] = (byte) ((encodedDerDN.length & 0xFF00) >> 8); + encodedData[1] = (byte) (encodedDerDN.length & 0xFF); + System.arraycopy(encodedDerDN, 0, encodedData, 2, encodedDerDN.length); + } + } + + // Test methods + + public static void main(String args[]) throws Exception { + + initializeTestData(); + + initializeTestExpectedData(); + + final byte[] clientHelloMessage = getClientHelloMessage(); + + showBinaryPacket(clientHelloMessage); + + assertExpectedDataInClientHelloMessage(clientHelloMessage); + + System.out.println("Test passed - OK"); + } + + private static void initializeTestData() throws Exception { + ks = KeyStore.getInstance("JKS"); + ts = KeyStore.getInstance("JKS"); + passphrase = "passphrase".toCharArray(); + ks.load(new FileInputStream(keyFilename), passphrase); + ts.load(new FileInputStream(trustFilename), passphrase); + } + + private static void initializeTestExpectedData() throws Exception { + certificateAuthoritiesDataLength = 0; + certificateAuthoritiesDataExpected = new ArrayList<>(); + final Enumeration trustStoreAliases = ts.aliases(); + while (trustStoreAliases.hasMoreElements()) { + final String trustStoreAlias = trustStoreAliases.nextElement(); + if (ts.isCertificateEntry(trustStoreAlias)) { + final Certificate c = ts.getCertificate(trustStoreAlias); + if (c instanceof X509Certificate) { + final X509Certificate x509c = (X509Certificate) c; + byte[] certificateDerEncodedName = getDerUTF8Encoded( + x509c.getSubjectDN().getName().getBytes("UTF8")); + CertificateAuthorityExpectedData certificateAuthorityExpectedData = + new CertificateAuthorityExpectedData(certificateDerEncodedName); + // Do not add duplicates + boolean isPresent = false; + for (CertificateAuthorityExpectedData caed : certificateAuthoritiesDataExpected) { + if (Arrays.equals(caed.encodedData, certificateAuthorityExpectedData.encodedData)) { + isPresent = true; + break; + } + } + if (!isPresent) { + certificateAuthoritiesDataLength += certificateAuthorityExpectedData.length; + certificateAuthoritiesDataExpected.add(certificateAuthorityExpectedData); + } + } + } + } + if (certificateAuthoritiesDataExpected.size() == 0) { + throw new Exception("No X.509 certificates found for this test."); + } + expectedCertificateAuthoritiesHeader = new byte[2 + 2]; // 2 bytes for extension_data + // length and 2 bytes for + // authority data length + expectedCertificateAuthoritiesHeader[0] = (byte) ((certificateAuthoritiesDataLength + 2 & 0xFF00) >> 8); + expectedCertificateAuthoritiesHeader[1] = (byte) (certificateAuthoritiesDataLength + 2 & 0xFF); + expectedCertificateAuthoritiesHeader[2] = (byte) ((certificateAuthoritiesDataLength & 0xFF00) >> 8); + expectedCertificateAuthoritiesHeader[3] = (byte) (certificateAuthoritiesDataLength & 0xFF); + + System.out.println("Extension HEADER:"); + showBinaryPacket(expectedCertificateAuthoritiesHeader); + System.out.println("............................."); + for (int i = 0; i < certificateAuthoritiesDataExpected.size(); i++) { + System.out.println("CERTIFICATE - Data length: " + certificateAuthoritiesDataExpected.get(i).length); + showBinaryPacket(certificateAuthoritiesDataExpected.get(i).encodedData); + System.out.println("............................."); + } + } + + private static byte[] getClientHelloMessage() throws Exception { + final SSLEngine ssle = createSSLEngine(); + final ByteBuffer clientOut = ByteBuffer.allocate(ssle.getSession().getPacketBufferSize()); + + ssle.beginHandshake(); + if (ssle.getHandshakeStatus() != HandshakeStatus.NEED_WRAP) { + throw new Exception("Unexpected status when beginning Handshake."); + } + + final SSLEngineResult result = ssle.wrap(ByteBuffer.wrap("".getBytes()), clientOut); + if (result.getStatus() != Status.OK || result.getHandshakeStatus() != HandshakeStatus.NEED_UNWRAP) { + throw new Exception("Unexpected status when getting Client Hello."); + } + + return clientOut.array(); + } + + static private SSLEngine createSSLEngine() throws Exception { + final SSLEngine ssle; + final KeyManagerFactory kmf = KeyManagerFactory.getInstance("PKIX"); + kmf.init(ks, passphrase); + + final TrustManagerFactory tmf = TrustManagerFactory.getInstance("PKIX"); + tmf.init(ts); + + final SSLContext sslCtx = SSLContext.getInstance("TLSv1.2"); + + sslCtx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null); + + ssle = sslCtx.createSSLEngine(); + ssle.setUseClientMode(true); + SSLParameters sslParameters = ssle.getSSLParameters(); + sslParameters.setUseCertificateAuthorities(true); + ssle.setSSLParameters(sslParameters); + + return ssle; + } + + private static void showBinaryPacket(byte[] binaryPacket) throws Exception { + int columnCounter = 0; + System.out.println("Binary packet dump:"); + for (byte binaryPacketByte : binaryPacket) { + System.out.print(String.format("%02X ", binaryPacketByte)); + if ((columnCounter + 1) % 8 == 0) { + System.out.print("\n"); + } + columnCounter++; + } + System.out.print("\n"); + } + + private static void assertExpectedDataInClientHelloMessage(byte[] clientHelloMessage) throws Exception { + final int[] certificateAuthoritiesDataIndices = new int[certificateAuthoritiesDataExpected.size()]; + Map indicesAuthoritiesDataMap = new HashMap<>(); + // Find offsets at which Authority Data starts + for (int i = 0; i < certificateAuthoritiesDataIndices.length; i++) { + certificateAuthoritiesDataIndices[i] = indexOf( + certificateAuthoritiesDataExpected.get(i).encodedData, clientHelloMessage, + (i > 0 ? certificateAuthoritiesDataIndices[i-1] + 1 : 0)); + indicesAuthoritiesDataMap.put(certificateAuthoritiesDataIndices[i], + certificateAuthoritiesDataExpected.get(i)); + } + // Sort them from lower to higher + Arrays.sort(certificateAuthoritiesDataIndices); + for (int i = 0; i < certificateAuthoritiesDataIndices.length; i++) { + // The distance between offsets must be the Authority Data length + if (i < certificateAuthoritiesDataIndices.length - 1) { + if (certificateAuthoritiesDataIndices[i] + + indicesAuthoritiesDataMap.get(certificateAuthoritiesDataIndices[i]).length + != certificateAuthoritiesDataIndices[i + 1]) { + throw new Exception("Unexpected value"); + } + } + System.out.println("Index: " + certificateAuthoritiesDataIndices[i]); + } + + // Assert the Certificate Authorities TLS extension header bytes + // (identifier + lengths) + for (int i = 0; i < expectedCertificateAuthoritiesHeader.length; i++) { + System.out.println("Comparing: " + + String.format("%02X ", clientHelloMessage[certificateAuthoritiesDataIndices[0] + - expectedCertificateAuthoritiesHeader.length + i]) + + " with " + String.format("%02X ", expectedCertificateAuthoritiesHeader[i])); + if (clientHelloMessage[certificateAuthoritiesDataIndices[0] - expectedCertificateAuthoritiesHeader.length + + i] != expectedCertificateAuthoritiesHeader[i]) { + throw new Exception("Unexpected value in Certificate Authorities TLS extension header."); + } + } + } + + private static int indexOf(byte[] needle, byte[] haystack, int offset) { + int foundAt = -1; + int needlePos = 0; + int haystackPos = 0; + for (byte haystackByte : haystack) { + if(haystackPos >= offset) { + if (haystackByte == needle[needlePos]) { + needlePos++; + if (needlePos == needle.length) { + return haystackPos - (needle.length - 1); + } + } else if (haystackByte == needle[0]) { + needlePos = 1; + } else { + needlePos = 0; + } + } + haystackPos++; + } + return foundAt; + } + + private static byte[] getDerUTF8Encoded(byte[] derUTF8Value) throws IOException { + byte[] derLengthArray = getDerLength(derUTF8Value.length); + byte[] derUTF8Encoded = new byte[1 + derLengthArray.length + derUTF8Value.length]; + derUTF8Encoded[0] = (byte)0x0C; + System.arraycopy(derLengthArray, 0, derUTF8Encoded, 1, derLengthArray.length); + System.arraycopy(derUTF8Value, 0, derUTF8Encoded, 1 + derLengthArray.length, derUTF8Value.length); + return derUTF8Encoded; + } + + // Function based on sun.security.util.DerOutputStream class + private static byte[] getDerLength(int len) throws IOException { + ArrayList out = new ArrayList<>(); + if (len < 128) { + out.add((byte)len); + + } else if (len < (1 << 8)) { + out.add((byte)0x081); + out.add((byte)len); + + } else if (len < (1 << 16)) { + out.add((byte)0x082); + out.add((byte)(len >> 8)); + out.add((byte)len); + + } else if (len < (1 << 24)) { + out.add((byte)0x083); + out.add((byte)(len >> 16)); + out.add((byte)(len >> 8)); + out.add((byte)len); + + } else { + out.add((byte)0x084); + out.add((byte)(len >> 24)); + out.add((byte)(len >> 16)); + out.add((byte)(len >> 8)); + out.add((byte)len); + } + byte[] derLengthArray = new byte[out.size()]; + for (int i = 0; i < out.size(); i++) { + derLengthArray[i] = out.get(i); + } + return derLengthArray; + } +} --- /dev/null 2017-07-18 10:08:09.829212503 -0300 +++ new/test/sun/security/ssl/ServerHandshaker/CertificateAuthoritiesServerTest.java 2017-07-18 14:02:01.186799687 -0300 @@ -0,0 +1,334 @@ +/* + * Copyright (c) 2017, Red Hat, Inc. and/or its affiliates. + * + * 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 8046295 + * @summary Certificate Authorities TLS extension (TLS 1.3) test (server-side). + * @author Martin Balao (mbalao@redhat.com) + * + */ + +import java.io.ByteArrayInputStream; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Base64; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Map; +import java.security.Key; +import java.security.KeyStore; +import java.security.MessageDigest; +import java.security.cert.Certificate; +import java.security.cert.X509Certificate; + +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLEngineResult.HandshakeStatus; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLEngine; +import javax.net.ssl.SSLEngineResult; +import javax.net.ssl.SSLParameters; +import javax.net.ssl.SSLEngineResult.Status; +import javax.net.ssl.SSLSession; +import javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactory; +import javax.net.ssl.X509TrustManager; + +import javax.net.ssl.SSLEngineResult.HandshakeStatus; + +final public class CertificateAuthoritiesServerTest { + + // Test data + + private static final String ksString = + "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"; + private static KeyStore ks; + private static KeyStore ts; + private static char[] passphrase; + + private static SSLEngine clientSSLEngine; + private static SSLEngine serverSSLEngine; + + // Test expected data + + private static X509Certificate expectedCertificate; + + // Test methods + + public static void main(String args[]) throws Exception { + + initializeTestData(); + + final byte[] serverResponse = getServerResponse(); + + if (indexOf(expectedCertificate.getEncoded(), serverResponse) == -1) { + throw new Exception("Certificate presented by Server is different than expected."); + } + + System.out.println("Test passed - OK"); + } + + private static void initializeTestData() throws Exception { + ks = KeyStore.getInstance("JKS"); + ts = KeyStore.getInstance("JKS"); + passphrase = "123456".toCharArray(); + ks.load(new ByteArrayInputStream(Base64.getDecoder().decode(ksString)), passphrase); + ts.load(new ByteArrayInputStream(Base64.getDecoder().decode(ksString)), passphrase); + + // Check that the Key Store has the required keys and certificates for + // this test. + Map ksRequiredCertificates = new HashMap<>(); + ksRequiredCertificates.put("EC", false); + ksRequiredCertificates.put("RSA", false); + Map ksRequiredKeys = new HashMap<>(); + ksRequiredKeys.put("EC", false); + ksRequiredKeys.put("RSA", false); + + Enumeration kdAliases = ks.aliases(); + while (kdAliases.hasMoreElements()) { + String ksAlias = kdAliases.nextElement(); + + if (ks.isKeyEntry(ksAlias)) { + Key k = ks.getKey(ksAlias, passphrase); + if (ksRequiredKeys.containsKey(k.getAlgorithm())) { + ksRequiredKeys.put(k.getAlgorithm(), true); + } + } + + Certificate ksCertificate = ks.getCertificate(ksAlias); + if (ksCertificate instanceof X509Certificate) { + X509Certificate x509KsCertificate = (X509Certificate) ksCertificate; + if (ksRequiredCertificates.containsKey(x509KsCertificate.getPublicKey().getAlgorithm())) { + ksRequiredCertificates.put(x509KsCertificate.getPublicKey().getAlgorithm(), true); + } + if (x509KsCertificate.getPublicKey().getAlgorithm().equals("RSA")) { + expectedCertificate = x509KsCertificate; + } + } + + } + + AtomicBoolean keyStoreHasRequiredKeys = new AtomicBoolean(true); + ksRequiredCertificates.forEach((k, v) -> { + if (v == false) { + keyStoreHasRequiredKeys.set(false); + } + }); + + ksRequiredKeys.forEach((k, v) -> { + if (v == false) { + keyStoreHasRequiredKeys.set(false); + } + }); + + if (keyStoreHasRequiredKeys.get() == false) { + throw new Exception("Keystore needs EC and RSA keys for this test"); + } + + System.out.println("Expected certificate: "); + System.out.println(expectedCertificate); + showBinaryPacket(expectedCertificate.getEncoded()); + + clientSSLEngine = createSSLEngine(true); + serverSSLEngine = createSSLEngine(false); + + // EC is preferred over RSA and supported by both client and server. + // However, client will send a Certificate Authority indication for a RSA certificate + // only. + // Check that the server takes into account the Certificate Authorities TLS + // extension and returns a RSA certificate to the client. + String[] preferredSuites = new String[] { "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256", + "TLS_RSA_WITH_AES_128_CBC_SHA256" }; + + serverSSLEngine.setEnabledCipherSuites(preferredSuites); + clientSSLEngine.setEnabledCipherSuites(preferredSuites); + + } + + private static ByteBuffer getClientHello() throws Exception { + ByteBuffer clientOut = ByteBuffer.allocate(clientSSLEngine.getSession().getPacketBufferSize()); + + clientSSLEngine.beginHandshake(); + if (clientSSLEngine.getHandshakeStatus() != HandshakeStatus.NEED_WRAP) { + throw new Exception("Unexpected status when beginning Handshake."); + } + + SSLEngineResult result = clientSSLEngine.wrap(ByteBuffer.wrap("".getBytes()), clientOut); + if (result.getStatus() != Status.OK || result.getHandshakeStatus() != HandshakeStatus.NEED_UNWRAP) { + throw new Exception("Unexpected status when getting Client Hello."); + } + + return ByteBuffer.wrap(clientOut.array()); + } + + private static byte[] getServerResponse() throws Exception { + final ByteBuffer clientHello = getClientHello(); + + ByteBuffer serverOut = ByteBuffer.allocate(serverSSLEngine.getSession().getPacketBufferSize()); + + serverSSLEngine.beginHandshake(); + if (serverSSLEngine.getHandshakeStatus() != HandshakeStatus.NEED_UNWRAP) { + throw new Exception("Unexpected status when beginning Handshake."); + } + + SSLEngineResult result = serverSSLEngine.unwrap(clientHello, serverOut); + + if (serverSSLEngine.getHandshakeStatus() == HandshakeStatus.NEED_TASK) { + Runnable runnable; + while ((runnable = serverSSLEngine.getDelegatedTask()) != null) { + runnable.run(); + } + } + + if (serverSSLEngine.getHandshakeStatus() != HandshakeStatus.NEED_WRAP) { + throw new Exception("Unexpected status when beginning Handshake."); + } + + result = serverSSLEngine.wrap(ByteBuffer.wrap("".getBytes()), serverOut); + + System.out.println("Server response: "); + showBinaryPacket(serverOut.array()); + + return serverOut.array(); + } + + static private SSLEngine createSSLEngine(boolean client) throws Exception { + SSLEngine ssle; + KeyManagerFactory kmf = KeyManagerFactory.getInstance("PKIX"); + kmf.init(ks, passphrase); + + TrustManagerFactory tmf = TrustManagerFactory.getInstance("PKIX"); + + if (client) { + // Only trust in Server's RSA certificate + final KeyStore rsaKs = KeyStore.getInstance("JKS"); + rsaKs.load(null, null); + rsaKs.setCertificateEntry("test", expectedCertificate); + tmf.init(rsaKs); + } else { + tmf.init(ts); + } + + SSLContext sslCtx = SSLContext.getInstance("TLSv1.2"); + + sslCtx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null); + + ssle = sslCtx.createSSLEngine(); + ssle.setUseClientMode(client); + SSLParameters sslParameters = ssle.getSSLParameters(); + sslParameters.setUseCertificateAuthorities(true); + ssle.setSSLParameters(sslParameters); + + return ssle; + } + + private static void showBinaryPacket(byte[] binaryPacket) throws Exception { + int columnCounter = 0; + System.out.println("Binary packet dump:"); + for (byte binaryPacketByte : binaryPacket) { + System.out.print(String.format("%02X ", binaryPacketByte)); + if ((columnCounter + 1) % 8 == 0) { + System.out.print("\n"); + } + columnCounter++; + } + System.out.print("\n"); + } + + private static int indexOf(byte[] needle, byte[] haystack) { + int foundAt = -1; + int needlePos = 0; + int haystackPos = 0; + for (byte haystackByte : haystack) { + if (haystackByte == needle[needlePos]) { + needlePos++; + if (needlePos == needle.length) { + return haystackPos - (needle.length - 1); + } + } else if (haystackByte == needle[0]) { + needlePos = 1; + } else { + needlePos = 0; + } + haystackPos++; + } + return foundAt; + } +}