/*
 * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package sun.security.ssl;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.security.PublicKey;
import java.security.cert.CertPathValidatorException;
import java.security.cert.CertPathValidatorException.BasicReason;
import java.security.cert.CertPathValidatorException.Reason;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.CertificateParsingException;
import java.security.cert.X509Certificate;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLProtocolException;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.X509ExtendedTrustManager;
import javax.net.ssl.X509TrustManager;
import javax.security.auth.x500.X500Principal;
import static sun.security.ssl.ClientAuthType.CLIENT_AUTH_REQUIRED;
import sun.security.ssl.ClientHello.ClientHelloMessage;
import sun.security.ssl.SSLHandshake.HandshakeMessage;
import sun.security.ssl.X509Authentication.X509Credentials;
import sun.security.ssl.X509Authentication.X509Possession;

Pack of the CertificateMessage handshake message.
/** * Pack of the CertificateMessage handshake message. */
final class CertificateMessage { static final SSLConsumer t12HandshakeConsumer = new T12CertificateConsumer(); static final HandshakeProducer t12HandshakeProducer = new T12CertificateProducer(); static final SSLConsumer t13HandshakeConsumer = new T13CertificateConsumer(); static final HandshakeProducer t13HandshakeProducer = new T13CertificateProducer();
The Certificate handshake message for TLS 1.2 and previous SSL/TLS protocol versions. In server mode, the certificate handshake message is sent whenever the agreed-upon key exchange method uses certificates for authentication. In client mode, this message is only sent if the server requests a certificate for client authentication. opaque ASN.1Cert<1..2^24-1>; SSL 3.0: struct { ASN.1Cert certificate_list<1..2^24-1>; } Certificate; Note: For SSL 3.0 client authentication, if no suitable certificate is available, the client should send a no_certificate alert instead. This alert is only a warning; however, the server may respond with a fatal handshake failure alert if client authentication is required. TLS 1.0/1.1/1.2: struct { ASN.1Cert certificate_list<0..2^24-1>; } Certificate;
/** * The Certificate handshake message for TLS 1.2 and previous * SSL/TLS protocol versions. * * In server mode, the certificate handshake message is sent whenever the * agreed-upon key exchange method uses certificates for authentication. * In client mode, this message is only sent if the server requests a * certificate for client authentication. * * opaque ASN.1Cert<1..2^24-1>; * * SSL 3.0: * struct { * ASN.1Cert certificate_list<1..2^24-1>; * } Certificate; * Note: For SSL 3.0 client authentication, if no suitable certificate * is available, the client should send a no_certificate alert instead. * This alert is only a warning; however, the server may respond with * a fatal handshake failure alert if client authentication is required. * * TLS 1.0/1.1/1.2: * struct { * ASN.1Cert certificate_list<0..2^24-1>; * } Certificate; */
static final class T12CertificateMessage extends HandshakeMessage { final List<byte[]> encodedCertChain; T12CertificateMessage(HandshakeContext handshakeContext, X509Certificate[] certChain) throws SSLException { super(handshakeContext); List<byte[]> encodedCerts = new ArrayList<>(certChain.length); for (X509Certificate cert : certChain) { try { encodedCerts.add(cert.getEncoded()); } catch (CertificateEncodingException cee) { // unlikely throw handshakeContext.conContext.fatal( Alert.INTERNAL_ERROR, "Could not encode certificate (" + cert.getSubjectX500Principal() + ")", cee); } } this.encodedCertChain = encodedCerts; } T12CertificateMessage(HandshakeContext handshakeContext, ByteBuffer m) throws IOException { super(handshakeContext); int listLen = Record.getInt24(m); if (listLen > m.remaining()) { throw handshakeContext.conContext.fatal( Alert.ILLEGAL_PARAMETER, "Error parsing certificate message:no sufficient data"); } if (listLen > 0) { List<byte[]> encodedCerts = new LinkedList<>(); while (listLen > 0) { byte[] encodedCert = Record.getBytes24(m); listLen -= (3 + encodedCert.length); encodedCerts.add(encodedCert); } this.encodedCertChain = encodedCerts; } else { this.encodedCertChain = Collections.emptyList(); } } @Override public SSLHandshake handshakeType() { return SSLHandshake.CERTIFICATE; } @Override public int messageLength() { int msgLen = 3; for (byte[] encodedCert : encodedCertChain) { msgLen += (encodedCert.length + 3); } return msgLen; } @Override public void send(HandshakeOutStream hos) throws IOException { int listLen = 0; for (byte[] encodedCert : encodedCertChain) { listLen += (encodedCert.length + 3); } hos.putInt24(listLen); for (byte[] encodedCert : encodedCertChain) { hos.putBytes24(encodedCert); } } @Override public String toString() { if (encodedCertChain.isEmpty()) { return "\"Certificates\": <empty list>"; } Object[] x509Certs = new Object[encodedCertChain.size()]; try { CertificateFactory cf = CertificateFactory.getInstance("X.509"); int i = 0; for (byte[] encodedCert : encodedCertChain) { Object obj; try { obj = (X509Certificate)cf.generateCertificate( new ByteArrayInputStream(encodedCert)); } catch (CertificateException ce) { obj = encodedCert; } x509Certs[i++] = obj; } } catch (CertificateException ce) { // no X.509 certificate factory service int i = 0; for (byte[] encodedCert : encodedCertChain) { x509Certs[i++] = encodedCert; } } MessageFormat messageFormat = new MessageFormat( "\"Certificates\": [\n" + "{0}\n" + "]", Locale.ENGLISH); Object[] messageFields = { SSLLogger.toString(x509Certs) }; return messageFormat.format(messageFields); } }
The "Certificate" handshake message producer for TLS 1.2 and previous SSL/TLS protocol versions.
/** * The "Certificate" handshake message producer for TLS 1.2 and * previous SSL/TLS protocol versions. */
private static final class T12CertificateProducer implements HandshakeProducer { // Prevent instantiation of this class. private T12CertificateProducer() { // blank } @Override public byte[] produce(ConnectionContext context, HandshakeMessage message) throws IOException { // The producing happens in handshake context only. HandshakeContext hc = (HandshakeContext)context; if (hc.sslConfig.isClientMode) { return onProduceCertificate( (ClientHandshakeContext)context, message); } else { return onProduceCertificate( (ServerHandshakeContext)context, message); } } private byte[] onProduceCertificate(ServerHandshakeContext shc, SSLHandshake.HandshakeMessage message) throws IOException { X509Possession x509Possession = null; for (SSLPossession possession : shc.handshakePossessions) { if (possession instanceof X509Possession) { x509Possession = (X509Possession)possession; break; } } if (x509Possession == null) { // unlikely throw shc.conContext.fatal(Alert.INTERNAL_ERROR, "No expected X.509 certificate for server authentication"); } shc.handshakeSession.setLocalPrivateKey( x509Possession.popPrivateKey); shc.handshakeSession.setLocalCertificates(x509Possession.popCerts); T12CertificateMessage cm = new T12CertificateMessage(shc, x509Possession.popCerts); if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Produced server Certificate handshake message", cm); } // Output the handshake message. cm.write(shc.handshakeOutput); shc.handshakeOutput.flush(); // The handshake message has been delivered. return null; } private byte[] onProduceCertificate(ClientHandshakeContext chc, SSLHandshake.HandshakeMessage message) throws IOException { X509Possession x509Possession = null; for (SSLPossession possession : chc.handshakePossessions) { if (possession instanceof X509Possession) { x509Possession = (X509Possession)possession; break; } } // Report to the server if no appropriate cert was found. For // SSL 3.0, send a no_certificate alert; TLS 1.0/1.1/1.2 uses // an empty cert chain instead. if (x509Possession == null) { if (chc.negotiatedProtocol.useTLS10PlusSpec()) { if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "No X.509 certificate for client authentication, " + "use empty Certificate message instead"); } x509Possession = new X509Possession(null, new X509Certificate[0]); } else { if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "No X.509 certificate for client authentication, " + "send a no_certificate alert"); } chc.conContext.warning(Alert.NO_CERTIFICATE); return null; } } chc.handshakeSession.setLocalPrivateKey( x509Possession.popPrivateKey); if (x509Possession.popCerts != null && x509Possession.popCerts.length != 0) { chc.handshakeSession.setLocalCertificates( x509Possession.popCerts); } else { chc.handshakeSession.setLocalCertificates(null); } T12CertificateMessage cm = new T12CertificateMessage(chc, x509Possession.popCerts); if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Produced client Certificate handshake message", cm); } // Output the handshake message. cm.write(chc.handshakeOutput); chc.handshakeOutput.flush(); // The handshake message has been delivered. return null; } }
The "Certificate" handshake message consumer for TLS 1.2 and previous SSL/TLS protocol versions.
/** * The "Certificate" handshake message consumer for TLS 1.2 and * previous SSL/TLS protocol versions. */
static final class T12CertificateConsumer implements SSLConsumer { // Prevent instantiation of this class. private T12CertificateConsumer() { // blank } @Override public void consume(ConnectionContext context, ByteBuffer message) throws IOException { // The consuming happens in handshake context only. HandshakeContext hc = (HandshakeContext)context; // clean up this consumer hc.handshakeConsumers.remove(SSLHandshake.CERTIFICATE.id); T12CertificateMessage cm = new T12CertificateMessage(hc, message); if (hc.sslConfig.isClientMode) { if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Consuming server Certificate handshake message", cm); } onCertificate((ClientHandshakeContext)context, cm); } else { if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Consuming client Certificate handshake message", cm); } onCertificate((ServerHandshakeContext)context, cm); } } private void onCertificate(ServerHandshakeContext shc, T12CertificateMessage certificateMessage )throws IOException { List<byte[]> encodedCerts = certificateMessage.encodedCertChain; if (encodedCerts == null || encodedCerts.isEmpty()) { if (shc.sslConfig.clientAuthType != ClientAuthType.CLIENT_AUTH_REQUESTED) { // unexpected or require client authentication throw shc.conContext.fatal(Alert.BAD_CERTIFICATE, "Empty server certificate chain"); } else { return; } } X509Certificate[] x509Certs = new X509Certificate[encodedCerts.size()]; try { CertificateFactory cf = CertificateFactory.getInstance("X.509"); int i = 0; for (byte[] encodedCert : encodedCerts) { x509Certs[i++] = (X509Certificate)cf.generateCertificate( new ByteArrayInputStream(encodedCert)); } } catch (CertificateException ce) { throw shc.conContext.fatal(Alert.BAD_CERTIFICATE, "Failed to parse server certificates", ce); } checkClientCerts(shc, x509Certs); // // update // shc.handshakeCredentials.add( new X509Credentials(x509Certs[0].getPublicKey(), x509Certs)); shc.handshakeSession.setPeerCertificates(x509Certs); } private void onCertificate(ClientHandshakeContext chc, T12CertificateMessage certificateMessage) throws IOException { List<byte[]> encodedCerts = certificateMessage.encodedCertChain; if (encodedCerts == null || encodedCerts.isEmpty()) { throw chc.conContext.fatal(Alert.BAD_CERTIFICATE, "Empty server certificate chain"); } X509Certificate[] x509Certs = new X509Certificate[encodedCerts.size()]; try { CertificateFactory cf = CertificateFactory.getInstance("X.509"); int i = 0; for (byte[] encodedCert : encodedCerts) { x509Certs[i++] = (X509Certificate)cf.generateCertificate( new ByteArrayInputStream(encodedCert)); } } catch (CertificateException ce) { throw chc.conContext.fatal(Alert.BAD_CERTIFICATE, "Failed to parse server certificates", ce); } // Allow server certificate change in client side during // renegotiation after a session-resumption abbreviated // initial handshake? // // DO NOT need to check allowUnsafeServerCertChange here. We only // reserve server certificates when allowUnsafeServerCertChange is // false. if (chc.reservedServerCerts != null && !chc.handshakeSession.useExtendedMasterSecret) { // It is not necessary to check the certificate update if // endpoint identification is enabled. String identityAlg = chc.sslConfig.identificationProtocol; if ((identityAlg == null || identityAlg.isEmpty()) && !isIdentityEquivalent(x509Certs[0], chc.reservedServerCerts[0])) { throw chc.conContext.fatal(Alert.BAD_CERTIFICATE, "server certificate change is restricted " + "during renegotiation"); } } // ask the trust manager to verify the chain if (chc.staplingActive) { // Defer the certificate check until after we've received the // CertificateStatus message. If that message doesn't come in // immediately following this message we will execute the // check from CertificateStatus' absent handler. chc.deferredCerts = x509Certs; } else { // We're not doing stapling, so perform the check right now checkServerCerts(chc, x509Certs); } // // update // chc.handshakeCredentials.add( new X509Credentials(x509Certs[0].getPublicKey(), x509Certs)); chc.handshakeSession.setPeerCertificates(x509Certs); } /* * Whether the certificates can represent the same identity? * * The certificates can be used to represent the same identity: * 1. If the subject alternative names of IP address are present * in both certificates, they should be identical; otherwise, * 2. if the subject alternative names of DNS name are present in * both certificates, they should be identical; otherwise, * 3. if the subject fields are present in both certificates, the * certificate subjects and issuers should be identical. */ private static boolean isIdentityEquivalent(X509Certificate thisCert, X509Certificate prevCert) { if (thisCert.equals(prevCert)) { return true; } // check subject alternative names Collection<List<?>> thisSubjectAltNames = null; try { thisSubjectAltNames = thisCert.getSubjectAlternativeNames(); } catch (CertificateParsingException cpe) { if (SSLLogger.isOn && SSLLogger.isOn("handshake")) { SSLLogger.fine( "Attempt to obtain subjectAltNames extension failed!"); } } Collection<List<?>> prevSubjectAltNames = null; try { prevSubjectAltNames = prevCert.getSubjectAlternativeNames(); } catch (CertificateParsingException cpe) { if (SSLLogger.isOn && SSLLogger.isOn("handshake")) { SSLLogger.fine( "Attempt to obtain subjectAltNames extension failed!"); } } if (thisSubjectAltNames != null && prevSubjectAltNames != null) { // check the iPAddress field in subjectAltName extension // // 7: subject alternative name of type IP. Collection<String> thisSubAltIPAddrs = getSubjectAltNames(thisSubjectAltNames, 7); Collection<String> prevSubAltIPAddrs = getSubjectAltNames(prevSubjectAltNames, 7); if (thisSubAltIPAddrs != null && prevSubAltIPAddrs != null && isEquivalent(thisSubAltIPAddrs, prevSubAltIPAddrs)) { return true; } // check the dNSName field in subjectAltName extension // 2: subject alternative name of type IP. Collection<String> thisSubAltDnsNames = getSubjectAltNames(thisSubjectAltNames, 2); Collection<String> prevSubAltDnsNames = getSubjectAltNames(prevSubjectAltNames, 2); if (thisSubAltDnsNames != null && prevSubAltDnsNames != null && isEquivalent(thisSubAltDnsNames, prevSubAltDnsNames)) { return true; } } // check the certificate subject and issuer X500Principal thisSubject = thisCert.getSubjectX500Principal(); X500Principal prevSubject = prevCert.getSubjectX500Principal(); X500Principal thisIssuer = thisCert.getIssuerX500Principal(); X500Principal prevIssuer = prevCert.getIssuerX500Principal(); return (!thisSubject.getName().isEmpty() && !prevSubject.getName().isEmpty() && thisSubject.equals(prevSubject) && thisIssuer.equals(prevIssuer)); } /* * Returns the subject alternative name of the specified type in the * subjectAltNames extension of a certificate. * * Note that only those subjectAltName types that use String data * should be passed into this function. */ private static Collection<String> getSubjectAltNames( Collection<List<?>> subjectAltNames, int type) { HashSet<String> subAltDnsNames = null; for (List<?> subjectAltName : subjectAltNames) { int subjectAltNameType = (Integer)subjectAltName.get(0); if (subjectAltNameType == type) { String subAltDnsName = (String)subjectAltName.get(1); if ((subAltDnsName != null) && !subAltDnsName.isEmpty()) { if (subAltDnsNames == null) { subAltDnsNames = new HashSet<>(subjectAltNames.size()); } subAltDnsNames.add(subAltDnsName); } } } return subAltDnsNames; } private static boolean isEquivalent(Collection<String> thisSubAltNames, Collection<String> prevSubAltNames) { for (String thisSubAltName : thisSubAltNames) { for (String prevSubAltName : prevSubAltNames) { // Only allow the exactly match. No wildcard character // checking. if (thisSubAltName.equalsIgnoreCase(prevSubAltName)) { return true; } } } return false; }
Perform client-side checking of server certificates.
Params:
  • certs – an array of X509Certificate objects presented by the server in the ServerCertificate message.
Throws:
  • IOException – if a failure occurs during validation or the trust manager associated with the SSLContext is not an X509ExtendedTrustManager.
/** * Perform client-side checking of server certificates. * * @param certs an array of {@code X509Certificate} objects presented * by the server in the ServerCertificate message. * * @throws IOException if a failure occurs during validation or * the trust manager associated with the {@code SSLContext} is not * an {@code X509ExtendedTrustManager}. */
static void checkServerCerts(ClientHandshakeContext chc, X509Certificate[] certs) throws IOException { X509TrustManager tm = chc.sslContext.getX509TrustManager(); // find out the key exchange algorithm used // use "RSA" for non-ephemeral "RSA_EXPORT" String keyExchangeString; if (chc.negotiatedCipherSuite.keyExchange == CipherSuite.KeyExchange.K_RSA_EXPORT || chc.negotiatedCipherSuite.keyExchange == CipherSuite.KeyExchange.K_DHE_RSA_EXPORT) { keyExchangeString = CipherSuite.KeyExchange.K_RSA.name; } else { keyExchangeString = chc.negotiatedCipherSuite.keyExchange.name; } try { if (tm instanceof X509ExtendedTrustManager) { if (chc.conContext.transport instanceof SSLEngine) { SSLEngine engine = (SSLEngine)chc.conContext.transport; ((X509ExtendedTrustManager)tm).checkServerTrusted( certs.clone(), keyExchangeString, engine); } else { SSLSocket socket = (SSLSocket)chc.conContext.transport; ((X509ExtendedTrustManager)tm).checkServerTrusted( certs.clone(), keyExchangeString, socket); } } else { // Unlikely to happen, because we have wrapped the old // X509TrustManager with the new X509ExtendedTrustManager. throw new CertificateException( "Improper X509TrustManager implementation"); } // Once the server certificate chain has been validated, set // the certificate chain in the TLS session. chc.handshakeSession.setPeerCertificates(certs); } catch (CertificateException ce) { throw chc.conContext.fatal(getCertificateAlert(chc, ce), ce); } } private static void checkClientCerts(ServerHandshakeContext shc, X509Certificate[] certs) throws IOException { X509TrustManager tm = shc.sslContext.getX509TrustManager(); // find out the types of client authentication used PublicKey key = certs[0].getPublicKey(); String keyAlgorithm = key.getAlgorithm(); String authType; switch (keyAlgorithm) { case "RSA": case "DSA": case "EC": case "RSASSA-PSS": authType = keyAlgorithm; break; default: // unknown public key type authType = "UNKNOWN"; } try { if (tm instanceof X509ExtendedTrustManager) { if (shc.conContext.transport instanceof SSLEngine) { SSLEngine engine = (SSLEngine)shc.conContext.transport; ((X509ExtendedTrustManager)tm).checkClientTrusted( certs.clone(), authType, engine); } else { SSLSocket socket = (SSLSocket)shc.conContext.transport; ((X509ExtendedTrustManager)tm).checkClientTrusted( certs.clone(), authType, socket); } } else { // Unlikely to happen, because we have wrapped the old // X509TrustManager with the new X509ExtendedTrustManager. throw new CertificateException( "Improper X509TrustManager implementation"); } } catch (CertificateException ce) { throw shc.conContext.fatal(Alert.CERTIFICATE_UNKNOWN, ce); } }
When a failure happens during certificate checking from an X509TrustManager, determine what TLS alert description to use.
Params:
Returns:A byte value corresponding to a TLS alert description number.
/** * When a failure happens during certificate checking from an * {@link X509TrustManager}, determine what TLS alert description * to use. * * @param cexc The exception thrown by the {@link X509TrustManager} * * @return A byte value corresponding to a TLS alert description number. */
private static Alert getCertificateAlert( ClientHandshakeContext chc, CertificateException cexc) { // The specific reason for the failure will determine how to // set the alert description value Alert alert = Alert.CERTIFICATE_UNKNOWN; Throwable baseCause = cexc.getCause(); if (baseCause instanceof CertPathValidatorException) { CertPathValidatorException cpve = (CertPathValidatorException)baseCause; Reason reason = cpve.getReason(); if (reason == BasicReason.REVOKED) { alert = chc.staplingActive ? Alert.BAD_CERT_STATUS_RESPONSE : Alert.CERTIFICATE_REVOKED; } else if ( reason == BasicReason.UNDETERMINED_REVOCATION_STATUS) { alert = chc.staplingActive ? Alert.BAD_CERT_STATUS_RESPONSE : Alert.CERTIFICATE_UNKNOWN; } } return alert; } }
The certificate entry used in Certificate handshake message for TLS 1.3.
/** * The certificate entry used in Certificate handshake message for TLS 1.3. */
static final class CertificateEntry { final byte[] encoded; // encoded cert or public key private final SSLExtensions extensions; CertificateEntry(byte[] encoded, SSLExtensions extensions) { this.encoded = encoded; this.extensions = extensions; } private int getEncodedSize() { int extLen = extensions.length(); if (extLen == 0) { extLen = 2; // empty extensions } return 3 + encoded.length + extLen; } @Override public String toString() { MessageFormat messageFormat = new MessageFormat( "\n'{'\n" + "{0}\n" + // X.509 certificate " \"extensions\": '{'\n" + "{1}\n" + " '}'\n" + "'}',", Locale.ENGLISH); Object x509Certs; try { // Don't support certificate type extension (RawPublicKey) yet. CertificateFactory cf = CertificateFactory.getInstance("X.509"); x509Certs = cf.generateCertificate(new ByteArrayInputStream(encoded)); } catch (CertificateException ce) { // no X.509 certificate factory service x509Certs = encoded; } Object[] messageFields = { SSLLogger.toString(x509Certs), Utilities.indent(extensions.toString(), " ") }; return messageFormat.format(messageFields); } }
The Certificate handshake message for TLS 1.3.
/** * The Certificate handshake message for TLS 1.3. */
static final class T13CertificateMessage extends HandshakeMessage { private final byte[] requestContext; private final List<CertificateEntry> certEntries; T13CertificateMessage(HandshakeContext context, byte[] requestContext, X509Certificate[] certificates) throws SSLException, CertificateException { super(context); this.requestContext = requestContext.clone(); this.certEntries = new LinkedList<>(); for (X509Certificate cert : certificates) { byte[] encoded = cert.getEncoded(); SSLExtensions extensions = new SSLExtensions(this); certEntries.add(new CertificateEntry(encoded, extensions)); } } T13CertificateMessage(HandshakeContext handshakeContext, byte[] requestContext, List<CertificateEntry> certificates) { super(handshakeContext); this.requestContext = requestContext.clone(); this.certEntries = certificates; } T13CertificateMessage(HandshakeContext handshakeContext, ByteBuffer m) throws IOException { super(handshakeContext); // struct { // opaque certificate_request_context<0..2^8-1>; // CertificateEntry certificate_list<0..2^24-1>; // } Certificate; if (m.remaining() < 4) { throw new SSLProtocolException( "Invalid Certificate message: " + "insufficient data (length=" + m.remaining() + ")"); } this.requestContext = Record.getBytes8(m); if (m.remaining() < 3) { throw new SSLProtocolException( "Invalid Certificate message: " + "insufficient certificate entries data (length=" + m.remaining() + ")"); } int listLen = Record.getInt24(m); if (listLen != m.remaining()) { throw new SSLProtocolException( "Invalid Certificate message: " + "incorrect list length (length=" + listLen + ")"); } SSLExtension[] enabledExtensions = handshakeContext.sslConfig.getEnabledExtensions( SSLHandshake.CERTIFICATE); List<CertificateEntry> certList = new LinkedList<>(); while (m.hasRemaining()) { // Note: support only X509 CertificateType right now. byte[] encodedCert = Record.getBytes24(m); if (encodedCert.length == 0) { throw new SSLProtocolException( "Invalid Certificate message: empty cert_data"); } SSLExtensions extensions = new SSLExtensions(this, m, enabledExtensions); certList.add(new CertificateEntry(encodedCert, extensions)); } this.certEntries = Collections.unmodifiableList(certList); } @Override public SSLHandshake handshakeType() { return SSLHandshake.CERTIFICATE; } @Override public int messageLength() { int msgLen = 4 + requestContext.length; for (CertificateEntry entry : certEntries) { msgLen += entry.getEncodedSize(); } return msgLen; } @Override public void send(HandshakeOutStream hos) throws IOException { int entryListLen = 0; for (CertificateEntry entry : certEntries) { entryListLen += entry.getEncodedSize(); } hos.putBytes8(requestContext); hos.putInt24(entryListLen); for (CertificateEntry entry : certEntries) { hos.putBytes24(entry.encoded); // Is it an empty extensions? if (entry.extensions.length() == 0) { hos.putInt16(0); } else { entry.extensions.send(hos); } } } @Override public String toString() { MessageFormat messageFormat = new MessageFormat( "\"Certificate\": '{'\n" + " \"certificate_request_context\": \"{0}\",\n" + " \"certificate_list\": [{1}\n]\n" + "'}'", Locale.ENGLISH); StringBuilder builder = new StringBuilder(512); for (CertificateEntry entry : certEntries) { builder.append(entry.toString()); } Object[] messageFields = { Utilities.toHexString(requestContext), Utilities.indent(builder.toString()) }; return messageFormat.format(messageFields); } }
The "Certificate" handshake message producer for TLS 1.3.
/** * The "Certificate" handshake message producer for TLS 1.3. */
private static final class T13CertificateProducer implements HandshakeProducer { // Prevent instantiation of this class. private T13CertificateProducer() { // blank } @Override public byte[] produce(ConnectionContext context, HandshakeMessage message) throws IOException { // The producing happens in handshake context only. HandshakeContext hc = (HandshakeContext)context; if (hc.sslConfig.isClientMode) { return onProduceCertificate( (ClientHandshakeContext)context, message); } else { return onProduceCertificate( (ServerHandshakeContext)context, message); } } private byte[] onProduceCertificate(ServerHandshakeContext shc, HandshakeMessage message) throws IOException { ClientHelloMessage clientHello = (ClientHelloMessage)message; SSLPossession pos = choosePossession(shc, clientHello); if (pos == null) { throw shc.conContext.fatal(Alert.HANDSHAKE_FAILURE, "No available authentication scheme"); } if (!(pos instanceof X509Possession)) { throw shc.conContext.fatal(Alert.HANDSHAKE_FAILURE, "No X.509 certificate for server authentication"); } X509Possession x509Possession = (X509Possession)pos; X509Certificate[] localCerts = x509Possession.popCerts; if (localCerts == null || localCerts.length == 0) { throw shc.conContext.fatal(Alert.HANDSHAKE_FAILURE, "No X.509 certificate for server authentication"); } // update the context shc.handshakePossessions.add(x509Possession); shc.handshakeSession.setLocalPrivateKey( x509Possession.popPrivateKey); shc.handshakeSession.setLocalCertificates(localCerts); T13CertificateMessage cm; try { cm = new T13CertificateMessage(shc, (new byte[0]), localCerts); } catch (SSLException | CertificateException ce) { throw shc.conContext.fatal(Alert.HANDSHAKE_FAILURE, "Failed to produce server Certificate message", ce); } // Check the OCSP stapling extensions and attempt // to get responses. If the resulting stapleParams is non // null, it implies that stapling is enabled on the server side. shc.stapleParams = StatusResponseManager.processStapling(shc); shc.staplingActive = (shc.stapleParams != null); // Process extensions for each CertificateEntry. // Since there can be multiple CertificateEntries within a // single CT message, we will pin a specific CertificateEntry // into the ServerHandshakeContext so individual extension // producers know which X509Certificate it is processing in // each call. SSLExtension[] enabledCTExts = shc.sslConfig.getEnabledExtensions( SSLHandshake.CERTIFICATE, Arrays.asList(ProtocolVersion.PROTOCOLS_OF_13)); for (CertificateEntry certEnt : cm.certEntries) { shc.currentCertEntry = certEnt; certEnt.extensions.produce(shc, enabledCTExts); } if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine("Produced server Certificate message", cm); } // Output the handshake message. cm.write(shc.handshakeOutput); shc.handshakeOutput.flush(); // The handshake message has been delivered. return null; } private static SSLPossession choosePossession( HandshakeContext hc, ClientHelloMessage clientHello) throws IOException { if (hc.peerRequestedCertSignSchemes == null || hc.peerRequestedCertSignSchemes.isEmpty()) { if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { SSLLogger.warning( "No signature_algorithms(_cert) in ClientHello"); } return null; } Collection<String> checkedKeyTypes = new HashSet<>(); for (SignatureScheme ss : hc.peerRequestedCertSignSchemes) { if (checkedKeyTypes.contains(ss.keyAlgorithm)) { if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { SSLLogger.warning( "Unsupported authentication scheme: " + ss.name); } continue; } // Don't select a signature scheme unless we will be able to // produce a CertificateVerify message later if (SignatureScheme.getPreferableAlgorithm( hc.peerRequestedSignatureSchemes, ss, hc.negotiatedProtocol) == null) { if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { SSLLogger.warning( "Unable to produce CertificateVerify for " + "signature scheme: " + ss.name); } checkedKeyTypes.add(ss.keyAlgorithm); continue; } SSLAuthentication ka = X509Authentication.valueOf(ss); if (ka == null) { if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { SSLLogger.warning( "Unsupported authentication scheme: " + ss.name); } checkedKeyTypes.add(ss.keyAlgorithm); continue; } SSLPossession pos = ka.createPossession(hc); if (pos == null) { if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { SSLLogger.warning( "Unavailable authentication scheme: " + ss.name); } continue; } return pos; } if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { SSLLogger.warning("No available authentication scheme"); } return null; } private byte[] onProduceCertificate(ClientHandshakeContext chc, HandshakeMessage message) throws IOException { ClientHelloMessage clientHello = (ClientHelloMessage)message; SSLPossession pos = choosePossession(chc, clientHello); X509Certificate[] localCerts; if (pos == null) { if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine("No available client authentication scheme"); } localCerts = new X509Certificate[0]; } else { chc.handshakePossessions.add(pos); if (!(pos instanceof X509Possession)) { if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "No X.509 certificate for client authentication"); } localCerts = new X509Certificate[0]; } else { X509Possession x509Possession = (X509Possession)pos; localCerts = x509Possession.popCerts; chc.handshakeSession.setLocalPrivateKey( x509Possession.popPrivateKey); } } if (localCerts != null && localCerts.length != 0) { chc.handshakeSession.setLocalCertificates(localCerts); } else { chc.handshakeSession.setLocalCertificates(null); } T13CertificateMessage cm; try { cm = new T13CertificateMessage( chc, chc.certRequestContext, localCerts); } catch (SSLException | CertificateException ce) { throw chc.conContext.fatal(Alert.HANDSHAKE_FAILURE, "Failed to produce client Certificate message", ce); } if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine("Produced client Certificate message", cm); } // Output the handshake message. cm.write(chc.handshakeOutput); chc.handshakeOutput.flush(); // The handshake message has been delivered. return null; } }
The "Certificate" handshake message consumer for TLS 1.3.
/** * The "Certificate" handshake message consumer for TLS 1.3. */
private static final class T13CertificateConsumer implements SSLConsumer { // Prevent instantiation of this class. private T13CertificateConsumer() { // blank } @Override public void consume(ConnectionContext context, ByteBuffer message) throws IOException { // The consuming happens in handshake context only. HandshakeContext hc = (HandshakeContext)context; // clean up this consumer hc.handshakeConsumers.remove(SSLHandshake.CERTIFICATE.id); T13CertificateMessage cm = new T13CertificateMessage(hc, message); if (hc.sslConfig.isClientMode) { if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Consuming server Certificate handshake message", cm); } onConsumeCertificate((ClientHandshakeContext)context, cm); } else { if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) { SSLLogger.fine( "Consuming client Certificate handshake message", cm); } onConsumeCertificate((ServerHandshakeContext)context, cm); } } private void onConsumeCertificate(ServerHandshakeContext shc, T13CertificateMessage certificateMessage )throws IOException { if (certificateMessage.certEntries == null || certificateMessage.certEntries.isEmpty()) { if (shc.sslConfig.clientAuthType == CLIENT_AUTH_REQUIRED) { throw shc.conContext.fatal(Alert.BAD_CERTIFICATE, "Empty client certificate chain"); } else { // optional client authentication return; } } // check client certificate entries X509Certificate[] cliCerts = checkClientCerts(shc, certificateMessage.certEntries); // // update // shc.handshakeCredentials.add( new X509Credentials(cliCerts[0].getPublicKey(), cliCerts)); shc.handshakeSession.setPeerCertificates(cliCerts); } private void onConsumeCertificate(ClientHandshakeContext chc, T13CertificateMessage certificateMessage )throws IOException { if (certificateMessage.certEntries == null || certificateMessage.certEntries.isEmpty()) { throw chc.conContext.fatal(Alert.BAD_CERTIFICATE, "Empty server certificate chain"); } // Each CertificateEntry will have its own set of extensions // which must be consumed. SSLExtension[] enabledExtensions = chc.sslConfig.getEnabledExtensions(SSLHandshake.CERTIFICATE); for (CertificateEntry certEnt : certificateMessage.certEntries) { certEnt.extensions.consumeOnLoad(chc, enabledExtensions); } // check server certificate entries X509Certificate[] srvCerts = checkServerCerts(chc, certificateMessage.certEntries); // // update // chc.handshakeCredentials.add( new X509Credentials(srvCerts[0].getPublicKey(), srvCerts)); chc.handshakeSession.setPeerCertificates(srvCerts); } private static X509Certificate[] checkClientCerts( ServerHandshakeContext shc, List<CertificateEntry> certEntries) throws IOException { X509Certificate[] certs = new X509Certificate[certEntries.size()]; try { CertificateFactory cf = CertificateFactory.getInstance("X.509"); int i = 0; for (CertificateEntry entry : certEntries) { certs[i++] = (X509Certificate)cf.generateCertificate( new ByteArrayInputStream(entry.encoded)); } } catch (CertificateException ce) { throw shc.conContext.fatal(Alert.BAD_CERTIFICATE, "Failed to parse server certificates", ce); } // find out the types of client authentication used String keyAlgorithm = certs[0].getPublicKey().getAlgorithm(); String authType; switch (keyAlgorithm) { case "RSA": case "DSA": case "EC": case "RSASSA-PSS": authType = keyAlgorithm; break; default: // unknown public key type authType = "UNKNOWN"; } try { X509TrustManager tm = shc.sslContext.getX509TrustManager(); if (tm instanceof X509ExtendedTrustManager) { if (shc.conContext.transport instanceof SSLEngine) { SSLEngine engine = (SSLEngine)shc.conContext.transport; ((X509ExtendedTrustManager)tm).checkClientTrusted( certs.clone(), authType, engine); } else { SSLSocket socket = (SSLSocket)shc.conContext.transport; ((X509ExtendedTrustManager)tm).checkClientTrusted( certs.clone(), authType, socket); } } else { // Unlikely to happen, because we have wrapped the old // X509TrustManager with the new X509ExtendedTrustManager. throw new CertificateException( "Improper X509TrustManager implementation"); } // Once the client certificate chain has been validated, set // the certificate chain in the TLS session. shc.handshakeSession.setPeerCertificates(certs); } catch (CertificateException ce) { throw shc.conContext.fatal(Alert.CERTIFICATE_UNKNOWN, ce); } return certs; } private static X509Certificate[] checkServerCerts( ClientHandshakeContext chc, List<CertificateEntry> certEntries) throws IOException { X509Certificate[] certs = new X509Certificate[certEntries.size()]; try { CertificateFactory cf = CertificateFactory.getInstance("X.509"); int i = 0; for (CertificateEntry entry : certEntries) { certs[i++] = (X509Certificate)cf.generateCertificate( new ByteArrayInputStream(entry.encoded)); } } catch (CertificateException ce) { throw chc.conContext.fatal(Alert.BAD_CERTIFICATE, "Failed to parse server certificates", ce); } // find out the types of server authentication used // // Note that the "UNKNOWN" authentication type is sufficient to // check the required digitalSignature KeyUsage for TLS 1.3. String authType = "UNKNOWN"; try { X509TrustManager tm = chc.sslContext.getX509TrustManager(); if (tm instanceof X509ExtendedTrustManager) { if (chc.conContext.transport instanceof SSLEngine) { SSLEngine engine = (SSLEngine)chc.conContext.transport; ((X509ExtendedTrustManager)tm).checkServerTrusted( certs.clone(), authType, engine); } else { SSLSocket socket = (SSLSocket)chc.conContext.transport; ((X509ExtendedTrustManager)tm).checkServerTrusted( certs.clone(), authType, socket); } } else { // Unlikely to happen, because we have wrapped the old // X509TrustManager with the new X509ExtendedTrustManager. throw new CertificateException( "Improper X509TrustManager implementation"); } // Once the server certificate chain has been validated, set // the certificate chain in the TLS session. chc.handshakeSession.setPeerCertificates(certs); } catch (CertificateException ce) { throw chc.conContext.fatal(getCertificateAlert(chc, ce), ce); } return certs; }
When a failure happens during certificate checking from an X509TrustManager, determine what TLS alert description to use.
Params:
Returns:A byte value corresponding to a TLS alert description number.
/** * When a failure happens during certificate checking from an * {@link X509TrustManager}, determine what TLS alert description * to use. * * @param cexc The exception thrown by the {@link X509TrustManager} * * @return A byte value corresponding to a TLS alert description number. */
private static Alert getCertificateAlert( ClientHandshakeContext chc, CertificateException cexc) { // The specific reason for the failure will determine how to // set the alert description value Alert alert = Alert.CERTIFICATE_UNKNOWN; Throwable baseCause = cexc.getCause(); if (baseCause instanceof CertPathValidatorException) { CertPathValidatorException cpve = (CertPathValidatorException)baseCause; Reason reason = cpve.getReason(); if (reason == BasicReason.REVOKED) { alert = chc.staplingActive ? Alert.BAD_CERT_STATUS_RESPONSE : Alert.CERTIFICATE_REVOKED; } else if ( reason == BasicReason.UNDETERMINED_REVOCATION_STATUS) { alert = chc.staplingActive ? Alert.BAD_CERT_STATUS_RESPONSE : Alert.CERTIFICATE_UNKNOWN; } } return alert; } } }