src/java.base/share/classes/sun/security/ssl/CertificateMessage.java
branchJDK-8145252-TLS13-branch
changeset 56542 56aaa6cb3693
child 56559 a423173d0578
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.base/share/classes/sun/security/ssl/CertificateMessage.java	Fri May 11 15:53:12 2018 -0700
@@ -0,0 +1,1393 @@
+/*
+ * 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 sun.security.ssl.CertificateMessage.T12CertificateMessage;
+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.
+ */
+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;
+     */
+    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
+                    handshakeContext.conContext.fatal(Alert.INTERNAL_ERROR,
+                            "Could not encode certificate (" +
+                            cert.getSubjectX500Principal() + ")", cee);
+                    break;      // make the complier happy
+                }
+            }
+
+            this.encodedCertChain = encodedCerts;
+        }
+
+        T12CertificateMessage(HandshakeContext handshakeContext,
+                ByteBuffer m) throws IOException {
+            super(handshakeContext);
+
+            int listLen = Record.getInt24(m);
+            if (listLen > m.remaining()) {
+                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.
+     */
+    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
+                shc.conContext.fatal(Alert.INTERNAL_ERROR,
+                    "No expected X.509 certificate for server authentication");
+
+                return null;        // make the compiler happy
+            }
+
+            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.
+     */
+    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
+                    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) {
+                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()) {
+                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) {
+                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
+            // flase.
+            if (chc.reservedServerCerts != null) {
+                // It is not necessary to check the certificate update if
+                // endpoint identification is enabled.
+                String identityAlg = chc.sslConfig.identificationProtocol;
+                if ((identityAlg == null || identityAlg.length() == 0) &&
+                        !isIdentityEquivalent(x509Certs[0],
+                                chc.reservedServerCerts[0])) {
+                    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.
+         *
+         * @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) {
+                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;
+            if (keyAlgorithm.equals("RSA")) {
+                authType = "RSA";
+            } else if (keyAlgorithm.equals("DSA")) {
+                authType = "DSA";
+            } else if (keyAlgorithm.equals("EC")) {
+                authType = "EC";
+            } else {
+                // 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) {
+                shc.conContext.fatal(Alert.CERTIFICATE_UNKNOWN, ce);
+            }
+        }
+
+        /**
+         * 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.
+     */
+    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.
+     */
+    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) {
+                // TODO: shall we use the Certificate for the session?
+                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.
+     */
+    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) {
+                shc.conContext.fatal(Alert.HANDSHAKE_FAILURE,
+                        "No available authentication scheme");
+                return null;    // make the complier happy
+            }
+
+            if (!(pos instanceof X509Possession)) {
+                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) {
+                shc.conContext.fatal(Alert.HANDSHAKE_FAILURE,
+                        "No X.509 certificate for server authentication");
+                return null;    // make the complier happy
+            }
+
+            // 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) {
+                shc.conContext.fatal(Alert.HANDSHAKE_FAILURE,
+                        "Failed to produce server Certificate message", ce);
+                return null;    // make the complier happy
+            }
+
+            // 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 scheme: " + ss.name);
+                    }
+                    checkedKeyTypes.add(ss.keyAlgorithm);
+                    continue;
+                }
+
+                SSLAuthentication ka =
+                        X509Authentication.nameOf(ss.keyAlgorithm);
+                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) {
+                chc.conContext.fatal(Alert.HANDSHAKE_FAILURE,
+                        "Failed to produce client Certificate message", ce);
+                return null;    // make the complier happy
+            }
+            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.
+     */
+    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) {
+                    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()) {
+                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));
+                    // TODO: check extensions
+                }
+            } catch (CertificateException ce) {
+                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":
+                    authType = "RSA";
+                    break;
+                case "DSA":
+                    authType = "DSA";
+                    break;
+                case "EC":
+                    authType = "EC";
+                    break;
+                default:
+                    // unknown public key type
+                    authType = "UNKNOWN";
+                    break;
+            }
+
+            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) {
+                // TODO: A more precise alert should be used.
+                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));
+                    // TODO: check extensions
+                }
+            } catch (CertificateException ce) {
+                chc.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":
+                    authType = "RSA";
+                    break;
+                case "DSA":
+                    authType = "DSA";
+                    break;
+                case "EC":
+                    authType = "EC";
+                    break;
+                default:
+                    // unknown public key type
+                    authType = "UNKNOWN";
+                    break;
+            }
+            */
+            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) {
+                chc.conContext.fatal(getCertificateAlert(chc, ce), ce);
+            }
+
+            return certs;
+        }
+
+        /**
+         * 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;
+        }
+    }
+}