--- 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:
+ *
+ * - providing a raw byte buffer to be parsed, or
+ * - providing a X.509 certificate.
+ *
+ *
+ * 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;
+ }
+}