src/java.base/share/classes/sun/security/ssl/ClientHello.java
changeset 50768 68fa3d4026ea
child 52170 2990f1e1c325
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.base/share/classes/sun/security/ssl/ClientHello.java	Mon Jun 25 13:41:39 2018 -0700
@@ -0,0 +1,1397 @@
+/*
+ * 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.IOException;
+import java.nio.ByteBuffer;
+import java.security.SecureRandom;
+import java.security.cert.X509Certificate;
+import java.text.MessageFormat;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Locale;
+import javax.net.ssl.SSLException;
+import javax.net.ssl.SSLHandshakeException;
+import javax.net.ssl.SSLPeerUnverifiedException;
+import javax.net.ssl.SSLProtocolException;
+import static sun.security.ssl.ClientAuthType.CLIENT_AUTH_REQUIRED;
+import sun.security.ssl.SSLHandshake.HandshakeMessage;
+import sun.security.ssl.SupportedVersionsExtension.CHSupportedVersionsSpec;
+
+/**
+ * Pack of the ClientHello handshake message.
+ */
+final class ClientHello {
+    static final SSLProducer kickstartProducer =
+        new ClientHelloKickstartProducer();
+    static final SSLConsumer handshakeConsumer =
+        new ClientHelloConsumer();
+    static final HandshakeProducer handshakeProducer =
+        new ClientHelloProducer();
+
+    private static final HandshakeConsumer t12HandshakeConsumer =
+            new T12ClientHelloConsumer();
+    private static final HandshakeConsumer t13HandshakeConsumer =
+            new T13ClientHelloConsumer();
+    private static final HandshakeConsumer d12HandshakeConsumer =
+            new D12ClientHelloConsumer();
+    private static final HandshakeConsumer d13HandshakeConsumer =
+            new D13ClientHelloConsumer();
+
+    /**
+     * The ClientHello handshake message.
+     *
+     * See RFC 5264/4346/2246/6347 for the specifications.
+     */
+    static final class ClientHelloMessage extends HandshakeMessage {
+        private final boolean       isDTLS;
+
+        final int                   clientVersion;
+        final RandomCookie          clientRandom;
+        final SessionId             sessionId;
+        private byte[]              cookie;         // DTLS only
+        final int[]                 cipherSuiteIds;
+        final List<CipherSuite>     cipherSuites;   // known cipher suites only
+        final byte[]                compressionMethod;
+        final SSLExtensions         extensions;
+
+        private static final byte[]  NULL_COMPRESSION = new byte[] {0};
+
+        ClientHelloMessage(HandshakeContext handshakeContext,
+                int clientVersion, SessionId sessionId,
+                List<CipherSuite> cipherSuites, SecureRandom generator) {
+            super(handshakeContext);
+            this.isDTLS = handshakeContext.sslContext.isDTLS();
+
+            this.clientVersion = clientVersion;
+            this.clientRandom = new RandomCookie(generator);
+            this.sessionId = sessionId;
+            if (isDTLS) {
+                this.cookie = new byte[0];
+            } else {
+                this.cookie = null;
+            }
+
+            this.cipherSuites = cipherSuites;
+            this.cipherSuiteIds = getCipherSuiteIds(cipherSuites);
+            this.extensions = new SSLExtensions(this);
+
+            // Don't support compression.
+            this.compressionMethod = NULL_COMPRESSION;
+        }
+
+        /* Read up to the binders in the PSK extension. After this method
+         * returns, the ByteBuffer position will be at end of the message
+         * fragment that should be hashed to produce the PSK binder values.
+         * The client of this method can use this position to determine the
+         * message fragment and produce the binder values.
+         */
+        static void readPartial(TransportContext tc,
+                ByteBuffer m) throws IOException {
+            boolean isDTLS = tc.sslContext.isDTLS();
+
+            // version
+            Record.getInt16(m);
+
+            new RandomCookie(m);
+
+            // session ID
+            Record.getBytes8(m);
+
+            // DTLS cookie
+            if (isDTLS) {
+                Record.getBytes8(m);
+            }
+
+            // cipher suite IDs
+            Record.getBytes16(m);
+            // compression method
+            Record.getBytes8(m);
+            // read extensions, if present
+            if (m.remaining() >= 2) {
+                int remaining = Record.getInt16(m);
+                while (remaining > 0) {
+                    int id = Record.getInt16(m);
+                    int extLen = Record.getInt16(m);
+                    remaining -= extLen + 4;
+
+                    if (id == SSLExtension.CH_PRE_SHARED_KEY.id) {
+                        // ensure pre_shared_key is the last extension
+                        if (remaining > 0) {
+                            tc.fatal(Alert.ILLEGAL_PARAMETER,
+                            "pre_shared_key extension is not last");
+                        }
+                        // read only up to the IDs
+                        Record.getBytes16(m);
+                        return;
+                    } else {
+                        m.position(m.position() + extLen);
+
+                    }
+                }
+            }   // Otherwise, ignore the remaining bytes.
+        }
+
+        ClientHelloMessage(HandshakeContext handshakeContext, ByteBuffer m,
+                SSLExtension[] supportedExtensions) throws IOException {
+            super(handshakeContext);
+            this.isDTLS = handshakeContext.sslContext.isDTLS();
+
+            this.clientVersion = ((m.get() & 0xFF) << 8) | (m.get() & 0xFF);
+            this.clientRandom = new RandomCookie(m);
+            this.sessionId = new SessionId(Record.getBytes8(m));
+            try {
+                sessionId.checkLength(clientVersion);
+            } catch (SSLProtocolException ex) {
+                handshakeContext.conContext.fatal(Alert.ILLEGAL_PARAMETER, ex);
+            }
+            if (isDTLS) {
+                this.cookie = Record.getBytes8(m);
+            } else {
+                this.cookie = null;
+            }
+
+            byte[] encodedIds = Record.getBytes16(m);
+            if (encodedIds.length == 0 || (encodedIds.length & 0x01) != 0) {
+                handshakeContext.conContext.fatal(Alert.ILLEGAL_PARAMETER,
+                    "Invalid ClientHello message");
+            }
+
+            this.cipherSuiteIds = new int[encodedIds.length >> 1];
+            for (int i = 0, j = 0; i < encodedIds.length; i++, j++) {
+                cipherSuiteIds[j] =
+                    ((encodedIds[i++] & 0xFF) << 8) | (encodedIds[i] & 0xFF);
+            }
+            this.cipherSuites = getCipherSuites(cipherSuiteIds);
+
+            this.compressionMethod = Record.getBytes8(m);
+            // In TLS 1.3, use of certain extensions is mandatory.
+            if (m.hasRemaining()) {
+                this.extensions =
+                        new SSLExtensions(this, m, supportedExtensions);
+            } else {
+                this.extensions = new SSLExtensions(this);
+            }
+        }
+
+        void setHelloCookie(byte[] cookie) {
+            this.cookie = cookie;
+        }
+
+        // DTLS 1.0/1.2, for cookie generation.
+        byte[] getHelloCookieBytes() {
+            HandshakeOutStream hos = new HandshakeOutStream(null);
+            try {
+                // copied from send() method
+                hos.putInt8((byte)((clientVersion >>> 8) & 0xFF));
+                hos.putInt8((byte)(clientVersion & 0xFF));
+                hos.write(clientRandom.randomBytes, 0, 32);
+                hos.putBytes8(sessionId.getId());
+                // ignore cookie
+                hos.putBytes16(getEncodedCipherSuites());
+                hos.putBytes8(compressionMethod);
+                extensions.send(hos);       // In TLS 1.3, use of certain
+                                            // extensions is mandatory.
+            } catch (IOException ioe) {
+                // unlikely
+            }
+
+            return hos.toByteArray();
+        }
+
+        // (D)TLS 1.3, for cookie generation.
+        byte[] getHeaderBytes() {
+            HandshakeOutStream hos = new HandshakeOutStream(null);
+            try {
+                // copied from send() method
+                hos.putInt8((byte)((clientVersion >>> 8) & 0xFF));
+                hos.putInt8((byte)(clientVersion & 0xFF));
+                hos.write(clientRandom.randomBytes, 0, 32);
+                hos.putBytes8(sessionId.getId());
+                hos.putBytes16(getEncodedCipherSuites());
+                hos.putBytes8(compressionMethod);
+            } catch (IOException ioe) {
+                // unlikely
+            }
+
+            return hos.toByteArray();
+        }
+
+        private static int[] getCipherSuiteIds(
+                List<CipherSuite> cipherSuites) {
+            if (cipherSuites != null) {
+                int[] ids = new int[cipherSuites.size()];
+                int i = 0;
+                for (CipherSuite cipherSuite : cipherSuites) {
+                    ids[i++] = cipherSuite.id;
+                }
+
+                return ids;
+            }
+
+            return new int[0];
+        }
+
+        private static List<CipherSuite> getCipherSuites(int[] ids) {
+            List<CipherSuite> cipherSuites = new LinkedList<>();
+            for (int id : ids) {
+                CipherSuite cipherSuite = CipherSuite.valueOf(id);
+                if (cipherSuite != null) {
+                    cipherSuites.add(cipherSuite);
+                }
+            }
+
+            return Collections.unmodifiableList(cipherSuites);
+        }
+
+        private List<String> getCipherSuiteNames() {
+            List<String> names = new LinkedList<>();
+            for (int id : cipherSuiteIds) {
+                names.add(CipherSuite.nameOf(id) +
+                        "(" + Utilities.byte16HexString(id) + ")");            }
+
+            return names;
+        }
+
+        private byte[] getEncodedCipherSuites() {
+            byte[] encoded = new byte[cipherSuiteIds.length << 1];
+            int i = 0;
+            for (int id : cipherSuiteIds) {
+                encoded[i++] = (byte)(id >> 8);
+                encoded[i++] = (byte)id;
+            }
+            return encoded;
+        }
+
+        @Override
+        public SSLHandshake handshakeType() {
+            return SSLHandshake.CLIENT_HELLO;
+        }
+
+        @Override
+        public int messageLength() {
+            /*
+             * Add fixed size parts of each field...
+             * version + random + session + cipher + compress
+             */
+            return (2 + 32 + 1 + 2 + 1
+                + sessionId.length()        /* ... + variable parts */
+                + (isDTLS ? (1 + cookie.length) : 0)
+                + (cipherSuiteIds.length * 2)
+                + compressionMethod.length)
+                + extensions.length();      // In TLS 1.3, use of certain
+                                            // extensions is mandatory.
+        }
+
+        @Override
+        public void send(HandshakeOutStream hos) throws IOException {
+            sendCore(hos);
+            extensions.send(hos);       // In TLS 1.3, use of certain
+                                        // extensions is mandatory.
+        }
+
+        void sendCore(HandshakeOutStream hos) throws IOException {
+            hos.putInt8((byte) (clientVersion >>> 8));
+            hos.putInt8((byte) clientVersion);
+            hos.write(clientRandom.randomBytes, 0, 32);
+            hos.putBytes8(sessionId.getId());
+            if (isDTLS) {
+                hos.putBytes8(cookie);
+            }
+            hos.putBytes16(getEncodedCipherSuites());
+            hos.putBytes8(compressionMethod);
+        }
+
+        @Override
+        public String toString() {
+            if (isDTLS) {
+                MessageFormat messageFormat = new MessageFormat(
+                    "\"ClientHello\": '{'\n" +
+                    "  \"client version\"      : \"{0}\",\n" +
+                    "  \"random\"              : \"{1}\",\n" +
+                    "  \"session id\"          : \"{2}\",\n" +
+                    "  \"cookie\"              : \"{3}\",\n" +
+                    "  \"cipher suites\"       : \"{4}\",\n" +
+                    "  \"compression methods\" : \"{5}\",\n" +
+                    "  \"extensions\"          : [\n" +
+                    "{6}\n" +
+                    "  ]\n" +
+                    "'}'",
+                    Locale.ENGLISH);
+                Object[] messageFields = {
+                    ProtocolVersion.nameOf(clientVersion),
+                    Utilities.toHexString(clientRandom.randomBytes),
+                    sessionId.toString(),
+                    Utilities.toHexString(cookie),
+                    getCipherSuiteNames().toString(),
+                    Utilities.toHexString(compressionMethod),
+                    Utilities.indent(Utilities.indent(extensions.toString()))
+                };
+
+                return messageFormat.format(messageFields);
+            } else {
+                MessageFormat messageFormat = new MessageFormat(
+                    "\"ClientHello\": '{'\n" +
+                    "  \"client version\"      : \"{0}\",\n" +
+                    "  \"random\"              : \"{1}\",\n" +
+                    "  \"session id\"          : \"{2}\",\n" +
+                    "  \"cipher suites\"       : \"{3}\",\n" +
+                    "  \"compression methods\" : \"{4}\",\n" +
+                    "  \"extensions\"          : [\n" +
+                    "{5}\n" +
+                    "  ]\n" +
+                    "'}'",
+                    Locale.ENGLISH);
+                Object[] messageFields = {
+                    ProtocolVersion.nameOf(clientVersion),
+                    Utilities.toHexString(clientRandom.randomBytes),
+                    sessionId.toString(),
+                    getCipherSuiteNames().toString(),
+                    Utilities.toHexString(compressionMethod),
+                    Utilities.indent(Utilities.indent(extensions.toString()))
+                };
+
+                return messageFormat.format(messageFields);
+            }
+        }
+    }
+
+    /**
+     * The "ClientHello" handshake message kick start producer.
+     */
+    private static final
+            class ClientHelloKickstartProducer implements SSLProducer {
+        // Prevent instantiation of this class.
+        private ClientHelloKickstartProducer() {
+            // blank
+        }
+
+        // Produce kickstart handshake message.
+        @Override
+        public byte[] produce(ConnectionContext context) throws IOException {
+            // The producing happens in client side only.
+            ClientHandshakeContext chc = (ClientHandshakeContext)context;
+
+            // clean up this producer
+            chc.handshakeProducers.remove(SSLHandshake.CLIENT_HELLO.id);
+
+            // the max protocol version this client is supporting.
+            ProtocolVersion maxProtocolVersion = chc.maximumActiveProtocol;
+
+            // session ID of the ClientHello message
+            SessionId sessionId = SSLSessionImpl.nullSession.getSessionId();
+
+            // a list of cipher suites sent by the client
+            List<CipherSuite> cipherSuites = chc.activeCipherSuites;
+
+            //
+            // Try to resume an existing session.
+            //
+            SSLSessionContextImpl ssci = (SSLSessionContextImpl)
+                    chc.sslContext.engineGetClientSessionContext();
+            SSLSessionImpl session = ssci.get(
+                    chc.conContext.transport.getPeerHost(),
+                    chc.conContext.transport.getPeerPort());
+            if (session != null) {
+                // If unsafe server certificate change is not allowed, reserve
+                // current server certificates if the previous handshake is a
+                // session-resumption abbreviated initial handshake.
+                if (!ClientHandshakeContext.allowUnsafeServerCertChange &&
+                        session.isSessionResumption()) {
+                    try {
+                        // If existing, peer certificate chain cannot be null.
+                        chc.reservedServerCerts =
+                            (X509Certificate[])session.getPeerCertificates();
+                    } catch (SSLPeerUnverifiedException puve) {
+                        // Maybe not certificate-based, ignore the exception.
+                    }
+                }
+
+                if (!session.isRejoinable()) {
+                    session = null;
+                    if (SSLLogger.isOn &&
+                            SSLLogger.isOn("ssl,handshake,verbose")) {
+                        SSLLogger.finest(
+                            "Can't resume, the session is not rejoinable");
+                    }
+                }
+            }
+
+            CipherSuite sessionSuite = null;
+            if (session != null) {
+                sessionSuite = session.getSuite();
+                if (!chc.isNegotiable(sessionSuite)) {
+                    session = null;
+                    if (SSLLogger.isOn &&
+                            SSLLogger.isOn("ssl,handshake,verbose")) {
+                        SSLLogger.finest(
+                            "Can't resume, unavailable session cipher suite");
+                    }
+                }
+            }
+
+            ProtocolVersion sessionVersion = null;
+            if (session != null) {
+                sessionVersion = session.getProtocolVersion();
+                if (!chc.isNegotiable(sessionVersion)) {
+                    session = null;
+                    if (SSLLogger.isOn &&
+                            SSLLogger.isOn("ssl,handshake,verbose")) {
+                        SSLLogger.finest(
+                            "Can't resume, unavailable protocol version");
+                    }
+                }
+            }
+
+            if (session != null &&
+                !sessionVersion.useTLS13PlusSpec() &&
+                SSLConfiguration.useExtendedMasterSecret) {
+
+                boolean isEmsAvailable = chc.sslConfig.isAvailable(
+                    SSLExtension.CH_EXTENDED_MASTER_SECRET, sessionVersion);
+                if (isEmsAvailable && !session.useExtendedMasterSecret &&
+                        !SSLConfiguration.allowLegacyResumption) {
+                    // perform full handshake instead
+                    //
+                    // The client SHOULD NOT offer an abbreviated handshake
+                    // to resume a session that does not use an extended
+                    // master secret.  Instead, it SHOULD offer a full
+                    // handshake.
+                     session = null;
+                }
+
+                if ((session != null) &&
+                        !ClientHandshakeContext.allowUnsafeServerCertChange) {
+                    // It is fine to move on with abbreviate handshake if
+                    // endpoint identification is enabled.
+                    String identityAlg = chc.sslConfig.identificationProtocol;
+                    if ((identityAlg == null || identityAlg.length() == 0)) {
+                        if (isEmsAvailable) {
+                            if (!session.useExtendedMasterSecret) {
+                                // perform full handshake instead
+                                session = null;
+                            }   // Otherwise, use extended master secret.
+                        } else {
+                            // The extended master secret extension does not
+                            // apply to SSL 3.0.  Perform a full handshake
+                            // instead.
+                            //
+                            // Note that the useExtendedMasterSecret is
+                            // extended to protect SSL 3.0 connections,
+                            // by discarding abbreviate handshake.
+                            session = null;
+                        }
+                    }
+                }
+            }
+
+            if (session != null) {
+                if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake,verbose")) {
+                    SSLLogger.finest("Try resuming session", session);
+                }
+
+                // only set session id if session is 1.2 or earlier
+                if (!session.getProtocolVersion().useTLS13PlusSpec()) {
+                    sessionId = session.getSessionId();
+                }
+                if (!maxProtocolVersion.equals(sessionVersion)) {
+                    maxProtocolVersion = sessionVersion;
+
+                    // Update protocol version number in underlying socket and
+                    // handshake output stream, so that the output records
+                    // (at the record layer) have the correct version
+                    chc.setVersion(sessionVersion);
+                }
+
+                // If no new session is allowed, force use of the previous
+                // session ciphersuite, and add the renegotiation SCSV if
+                // necessary.
+                if (!chc.sslConfig.enableSessionCreation) {
+                    if (!chc.conContext.isNegotiated &&
+                        !sessionVersion.useTLS13PlusSpec() &&
+                        cipherSuites.contains(
+                            CipherSuite.TLS_EMPTY_RENEGOTIATION_INFO_SCSV)) {
+                        cipherSuites = Arrays.asList(sessionSuite,
+                            CipherSuite.TLS_EMPTY_RENEGOTIATION_INFO_SCSV);
+                    } else {    // otherwise, use renegotiation_info extension
+                        cipherSuites = Arrays.asList(sessionSuite);
+                    }
+
+                    if (SSLLogger.isOn &&
+                            SSLLogger.isOn("ssl,handshake,verbose")) {
+                        SSLLogger.finest(
+                            "No new session is allowed, so try to resume " +
+                            "the session cipher suite only", sessionSuite);
+                    }
+                }
+
+                chc.isResumption = true;
+                chc.resumingSession = session;
+            }
+
+            if (session == null) {
+                if (!chc.sslConfig.enableSessionCreation) {
+                    throw new SSLHandshakeException(
+                            "No new session is allowed and " +
+                            "no existing session can be resumed");
+                }
+
+                if (maxProtocolVersion.useTLS13PlusSpec() &&
+                        SSLConfiguration.useCompatibilityMode) {
+                    // In compatibility mode, the TLS 1.3 legacy_session_id
+                    // field MUST be non-empty, so a client not offering a
+                    // pre-TLS 1.3 session MUST generate a new 32-byte value.
+                    sessionId =
+                        new SessionId(true, chc.sslContext.getSecureRandom());
+                }
+            }
+
+            ProtocolVersion minimumVersion = ProtocolVersion.NONE;
+            for (ProtocolVersion pv : chc.activeProtocols) {
+                if (minimumVersion == ProtocolVersion.NONE ||
+                        pv.compare(minimumVersion) < 0) {
+                    minimumVersion = pv;
+                }
+            }
+
+            // exclude SCSV for secure renegotiation
+            if (!minimumVersion.useTLS13PlusSpec()) {
+                if (chc.conContext.secureRenegotiation &&
+                        cipherSuites.contains(
+                            CipherSuite.TLS_EMPTY_RENEGOTIATION_INFO_SCSV)) {
+                    // The cipherSuites may be unmodifiable
+                    cipherSuites = new LinkedList<>(cipherSuites);
+                    cipherSuites.remove(
+                            CipherSuite.TLS_EMPTY_RENEGOTIATION_INFO_SCSV);
+                }
+            }
+
+            // make sure there is a negotiable cipher suite.
+            boolean negotiable = false;
+            for (CipherSuite suite : cipherSuites) {
+                if (chc.isNegotiable(suite)) {
+                    negotiable = true;
+                    break;
+                }
+            }
+            if (!negotiable) {
+                throw new SSLHandshakeException("No negotiable cipher suite");
+            }
+
+            // Create the handshake message.
+            ProtocolVersion clientHelloVersion = maxProtocolVersion;
+            if (clientHelloVersion.useTLS13PlusSpec()) {
+                // In (D)TLS 1.3, the client indicates its version preferences
+                // in the "supported_versions" extension and the client_version
+                // (legacy_version) field MUST be set to (D)TLS 1.2.
+                if (clientHelloVersion.isDTLS) {
+                    clientHelloVersion = ProtocolVersion.DTLS12;
+                } else {
+                    clientHelloVersion = ProtocolVersion.TLS12;
+                }
+            }
+
+            ClientHelloMessage chm = new ClientHelloMessage(chc,
+                    clientHelloVersion.id, sessionId, cipherSuites,
+                    chc.sslContext.getSecureRandom());
+
+            // cache the client random number for further using
+            chc.clientHelloRandom = chm.clientRandom;
+            chc.clientHelloVersion = clientHelloVersion.id;
+
+            // Produce extensions for ClientHello handshake message.
+            SSLExtension[] extTypes = chc.sslConfig.getEnabledExtensions(
+                    SSLHandshake.CLIENT_HELLO, chc.activeProtocols);
+            chm.extensions.produce(chc, extTypes);
+
+            if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
+                SSLLogger.fine("Produced ClientHello handshake message", chm);
+            }
+
+            // Output the handshake message.
+            chm.write(chc.handshakeOutput);
+            chc.handshakeOutput.flush();
+
+            // Reserve the initial ClientHello message for the follow on
+            // cookie exchange if needed.
+            chc.initialClientHelloMsg = chm;
+
+            // What's the expected response?
+            chc.handshakeConsumers.put(
+                    SSLHandshake.SERVER_HELLO.id, SSLHandshake.SERVER_HELLO);
+            if (chc.sslContext.isDTLS() &&
+                    !minimumVersion.useTLS13PlusSpec()) {
+                chc.handshakeConsumers.put(
+                        SSLHandshake.HELLO_VERIFY_REQUEST.id,
+                        SSLHandshake.HELLO_VERIFY_REQUEST);
+            }
+
+            // The handshake message has been delivered.
+            return null;
+        }
+    }
+
+    private static final
+            class ClientHelloProducer implements HandshakeProducer {
+        // Prevent instantiation of this class.
+        private ClientHelloProducer() {
+            // blank
+        }
+
+        // Response to one of the following handshake message:
+        //     HelloRequest                     (SSL 3.0/TLS 1.0/1.1/1.2)
+        //     ServerHello(HelloRetryRequest)   (TLS 1.3)
+        //     HelloVerifyRequest               (DTLS 1.0/1.2)
+        @Override
+        public byte[] produce(ConnectionContext context,
+                HandshakeMessage message) throws IOException {
+            // The producing happens in client side only.
+            ClientHandshakeContext chc = (ClientHandshakeContext)context;
+
+            SSLHandshake ht = message.handshakeType();
+            if (ht == null) {
+                throw new UnsupportedOperationException("Not supported yet.");
+            }
+
+            switch (ht) {
+                case HELLO_REQUEST:
+                    // SSL 3.0/TLS 1.0/1.1/1.2
+                    try {
+                        chc.kickstart();
+                    } catch (IOException ioe) {
+                        chc.conContext.fatal(Alert.HANDSHAKE_FAILURE, ioe);
+                    }
+
+                    // The handshake message has been delivered.
+                    return null;
+                case HELLO_VERIFY_REQUEST:
+                    // DTLS 1.0/1.2
+                    //
+                    // The HelloVerifyRequest consumer should have updated the
+                    // ClientHello handshake message with cookie.
+                    if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
+                        SSLLogger.fine(
+                            "Produced ClientHello(cookie) handshake message",
+                            chc.initialClientHelloMsg);
+                    }
+
+                    // Output the handshake message.
+                    chc.initialClientHelloMsg.write(chc.handshakeOutput);
+                    chc.handshakeOutput.flush();
+
+                    // What's the expected response?
+                    chc.handshakeConsumers.put(SSLHandshake.SERVER_HELLO.id,
+                            SSLHandshake.SERVER_HELLO);
+
+                    ProtocolVersion minimumVersion = ProtocolVersion.NONE;
+                    for (ProtocolVersion pv : chc.activeProtocols) {
+                        if (minimumVersion == ProtocolVersion.NONE ||
+                                pv.compare(minimumVersion) < 0) {
+                            minimumVersion = pv;
+                        }
+                    }
+                    if (chc.sslContext.isDTLS() &&
+                            !minimumVersion.useTLS13PlusSpec()) {
+                        chc.handshakeConsumers.put(
+                                SSLHandshake.HELLO_VERIFY_REQUEST.id,
+                                SSLHandshake.HELLO_VERIFY_REQUEST);
+                    }
+
+                    // The handshake message has been delivered.
+                    return null;
+                case HELLO_RETRY_REQUEST:
+                    // TLS 1.3
+                    // The HelloRetryRequest consumer should have updated the
+                    // ClientHello handshake message with cookie.
+                    if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
+                        SSLLogger.fine(
+                            "Produced ClientHello(HRR) handshake message",
+                            chc.initialClientHelloMsg);
+                    }
+
+                    // Output the handshake message.
+                    chc.initialClientHelloMsg.write(chc.handshakeOutput);
+                    chc.handshakeOutput.flush();
+
+                    // What's the expected response?
+                    chc.conContext.consumers.putIfAbsent(
+                            ContentType.CHANGE_CIPHER_SPEC.id,
+                            ChangeCipherSpec.t13Consumer);
+                    chc.handshakeConsumers.put(SSLHandshake.SERVER_HELLO.id,
+                            SSLHandshake.SERVER_HELLO);
+
+                    // The handshake message has been delivered.
+                    return null;
+                default:
+                    throw new UnsupportedOperationException(
+                            "Not supported yet.");
+            }
+        }
+    }
+
+    /**
+     * The "ClientHello" handshake message consumer.
+     */
+    private static final class ClientHelloConsumer implements SSLConsumer {
+        // Prevent instantiation of this class.
+        private ClientHelloConsumer() {
+            // blank
+        }
+
+        @Override
+        public void consume(ConnectionContext context,
+                ByteBuffer message) throws IOException {
+            // The consuming happens in server side only.
+            ServerHandshakeContext shc = (ServerHandshakeContext)context;
+
+            // clean up this consumer
+            shc.handshakeConsumers.remove(SSLHandshake.CLIENT_HELLO.id);
+            if (!shc.handshakeConsumers.isEmpty()) {
+                shc.conContext.fatal(Alert.UNEXPECTED_MESSAGE,
+                        "No more handshake message allowed " +
+                        "in a ClientHello flight");
+            }
+
+            // Get enabled extension types in ClientHello handshake message.
+            SSLExtension[] enabledExtensions =
+                    shc.sslConfig.getEnabledExtensions(
+                            SSLHandshake.CLIENT_HELLO);
+
+            ClientHelloMessage chm =
+                    new ClientHelloMessage(shc, message, enabledExtensions);
+            if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
+                SSLLogger.fine("Consuming ClientHello handshake message", chm);
+            }
+
+            shc.clientHelloVersion = chm.clientVersion;
+            onClientHello(shc, chm);
+        }
+
+        private void onClientHello(ServerHandshakeContext context,
+                ClientHelloMessage clientHello) throws IOException {
+            // Negotiate protocol version.
+            //
+            // Check and launch SupportedVersions.
+            SSLExtension[] extTypes = new SSLExtension[] {
+                    SSLExtension.CH_SUPPORTED_VERSIONS
+                };
+            clientHello.extensions.consumeOnLoad(context, extTypes);
+
+            ProtocolVersion negotiatedProtocol;
+            CHSupportedVersionsSpec svs =
+                    (CHSupportedVersionsSpec)context.handshakeExtensions.get(
+                            SSLExtension.CH_SUPPORTED_VERSIONS);
+            if (svs != null) {
+                negotiatedProtocol =
+                        negotiateProtocol(context, svs.requestedProtocols);
+            } else {
+                negotiatedProtocol =
+                        negotiateProtocol(context, clientHello.clientVersion);
+            }
+            context.negotiatedProtocol = negotiatedProtocol;
+            if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
+                SSLLogger.fine(
+                    "Negotiated protocol version: " + negotiatedProtocol.name);
+            }
+
+            // Consume the handshake message for the specific protocol version.
+            if (negotiatedProtocol.isDTLS) {
+                if (negotiatedProtocol.useTLS13PlusSpec()) {
+                    d13HandshakeConsumer.consume(context, clientHello);
+                } else {
+                    d12HandshakeConsumer.consume(context, clientHello);
+                }
+            } else {
+                if (negotiatedProtocol.useTLS13PlusSpec()) {
+                    t13HandshakeConsumer.consume(context, clientHello);
+                } else {
+                    t12HandshakeConsumer.consume(context, clientHello);
+                }
+            }
+        }
+
+        // Select a protocol version according to the
+        // ClientHello.client_version.
+        private ProtocolVersion negotiateProtocol(
+                ServerHandshakeContext context,
+                int clientHelloVersion) throws SSLException {
+
+            // Per TLS 1.3 specification, server MUST negotiate TLS 1.2 or prior
+            // even if ClientHello.client_version is 0x0304 or later.
+            int chv = clientHelloVersion;
+            if (context.sslContext.isDTLS()) {
+                if (chv < ProtocolVersion.DTLS12.id) {
+                    chv = ProtocolVersion.DTLS12.id;
+                }
+            } else {
+                if (chv > ProtocolVersion.TLS12.id) {
+                    chv = ProtocolVersion.TLS12.id;
+                }
+            }
+
+            // Select a protocol version from the activated protocols.
+            ProtocolVersion pv = ProtocolVersion.selectedFrom(
+                    context.activeProtocols, chv);
+            if (pv == null || pv == ProtocolVersion.NONE ||
+                    pv == ProtocolVersion.SSL20Hello) {
+                context.conContext.fatal(Alert.PROTOCOL_VERSION,
+                    "Client requested protocol " +
+                    ProtocolVersion.nameOf(clientHelloVersion) +
+                    " is not enabled or supported in server context");
+            }
+
+            return pv;
+        }
+
+        // Select a protocol version according to the
+        // supported_versions extension.
+        private ProtocolVersion negotiateProtocol(
+                ServerHandshakeContext context,
+                int[] clientSupportedVersions) throws SSLException {
+
+            // The client supported protocol versions are present in client
+            // preference order.  This implementation chooses to use the server
+            // preference of protocol versions instead.
+            for (ProtocolVersion spv : context.activeProtocols) {
+                if (spv == ProtocolVersion.SSL20Hello) {
+                    continue;
+                }
+                for (int cpv : clientSupportedVersions) {
+                    if (cpv == ProtocolVersion.SSL20Hello.id) {
+                        continue;
+                    }
+                    if (spv.id == cpv) {
+                        return spv;
+                    }
+                }
+            }
+
+            // No protocol version can be negotiated.
+            context.conContext.fatal(Alert.PROTOCOL_VERSION,
+                "The client supported protocol versions " + Arrays.toString(
+                    ProtocolVersion.toStringArray(clientSupportedVersions)) +
+                " are not accepted by server preferences " +
+                context.activeProtocols);
+
+            return null;        // make the compiler happy
+        }
+    }
+
+    /**
+     * The "ClientHello" handshake message consumer for TLS 1.2 and
+     * prior SSL/TLS protocol versions.
+     */
+    private static final
+            class T12ClientHelloConsumer implements HandshakeConsumer {
+        // Prevent instantiation of this class.
+        private T12ClientHelloConsumer() {
+            // blank
+        }
+
+        @Override
+        public void consume(ConnectionContext context,
+                HandshakeMessage message) throws IOException {
+            // The consuming happens in server side only.
+            ServerHandshakeContext shc = (ServerHandshakeContext)context;
+            ClientHelloMessage clientHello = (ClientHelloMessage)message;
+
+            //
+            // validate
+            //
+
+            // Reject client initiated renegotiation?
+            //
+            // If server side should reject client-initiated renegotiation,
+            // send an Alert.HANDSHAKE_FAILURE fatal alert, not a
+            // no_renegotiation warning alert (no_renegotiation must be a
+            // warning: RFC 2246).  no_renegotiation might seem more
+            // natural at first, but warnings are not appropriate because
+            // the sending party does not know how the receiving party
+            // will behave.  This state must be treated as a fatal server
+            // condition.
+            //
+            // This will not have any impact on server initiated renegotiation.
+            if (shc.conContext.isNegotiated) {
+                if (!shc.conContext.secureRenegotiation &&
+                        !HandshakeContext.allowUnsafeRenegotiation) {
+                    shc.conContext.fatal(Alert.HANDSHAKE_FAILURE,
+                            "Unsafe renegotiation is not allowed");
+                }
+
+                if (ServerHandshakeContext.rejectClientInitiatedRenego &&
+                        !shc.kickstartMessageDelivered) {
+                    shc.conContext.fatal(Alert.HANDSHAKE_FAILURE,
+                            "Client initiated renegotiation is not allowed");
+                }
+            }
+
+            // Is it an abbreviated handshake?
+            if (clientHello.sessionId.length() != 0) {
+                SSLSessionImpl previous = ((SSLSessionContextImpl)shc.sslContext
+                            .engineGetServerSessionContext())
+                            .get(clientHello.sessionId.getId());
+
+                boolean resumingSession =
+                        (previous != null) && previous.isRejoinable();
+                if (!resumingSession) {
+                    if (SSLLogger.isOn &&
+                            SSLLogger.isOn("ssl,handshake,verbose")) {
+                        SSLLogger.finest(
+                                "Can't resume, " +
+                                "the existing session is not rejoinable");
+                    }
+                }
+                // Validate the negotiated protocol version.
+                if (resumingSession) {
+                    ProtocolVersion sessionProtocol =
+                            previous.getProtocolVersion();
+                    if (sessionProtocol != shc.negotiatedProtocol) {
+                        resumingSession = false;
+                        if (SSLLogger.isOn &&
+                                SSLLogger.isOn("ssl,handshake,verbose")) {
+                            SSLLogger.finest(
+                                "Can't resume, not the same protocol version");
+                        }
+                    }
+                }
+
+                // Validate the required client authentication.
+                if (resumingSession &&
+                    (shc.sslConfig.clientAuthType == CLIENT_AUTH_REQUIRED)) {
+                    try {
+                        previous.getPeerPrincipal();
+                    } catch (SSLPeerUnverifiedException e) {
+                        resumingSession = false;
+                        if (SSLLogger.isOn &&
+                                SSLLogger.isOn("ssl,handshake,verbose")) {
+                            SSLLogger.finest(
+                                "Can't resume, " +
+                                "client authentication is required");
+                        }
+                    }
+                }
+
+                // Validate that the cached cipher suite.
+                if (resumingSession) {
+                    CipherSuite suite = previous.getSuite();
+                    if ((!shc.isNegotiable(suite)) ||
+                            (!clientHello.cipherSuites.contains(suite))) {
+                        resumingSession = false;
+                        if (SSLLogger.isOn &&
+                                SSLLogger.isOn("ssl,handshake,verbose")) {
+                            SSLLogger.finest(
+                                "Can't resume, " +
+                                "the session cipher suite is absent");
+                        }
+                    }
+                }
+
+                // So far so good.  Note that the handshake extensions may reset
+                // the resuming options later.
+                shc.isResumption = resumingSession;
+                shc.resumingSession = resumingSession ? previous : null;
+            }
+
+            // cache the client random number for further using
+            shc.clientHelloRandom = clientHello.clientRandom;
+
+            // Check and launch ClientHello extensions.
+            SSLExtension[] extTypes = shc.sslConfig.getEnabledExtensions(
+                    SSLHandshake.CLIENT_HELLO);
+            clientHello.extensions.consumeOnLoad(shc, extTypes);
+
+            //
+            // update
+            //
+            if (!shc.conContext.isNegotiated) {
+                shc.conContext.protocolVersion = shc.negotiatedProtocol;
+                shc.conContext.outputRecord.setVersion(shc.negotiatedProtocol);
+            }
+
+            // update the responders
+            //
+            // Only need to ServerHello, which may add more responders later.
+            // Note that ServerHello and HelloRetryRequest share the same
+            // handshake type/id.  The ServerHello producer may be replaced
+            // by HelloRetryRequest producer if needed.
+            shc.handshakeProducers.put(SSLHandshake.SERVER_HELLO.id,
+                    SSLHandshake.SERVER_HELLO);
+
+            //
+            // produce
+            //
+            SSLHandshake[] probableHandshakeMessages = new SSLHandshake[] {
+                SSLHandshake.SERVER_HELLO,
+
+                // full handshake messages
+                SSLHandshake.CERTIFICATE,
+                SSLHandshake.CERTIFICATE_STATUS,
+                SSLHandshake.SERVER_KEY_EXCHANGE,
+                SSLHandshake.CERTIFICATE_REQUEST,
+                SSLHandshake.SERVER_HELLO_DONE,
+
+                // abbreviated handshake messages
+                SSLHandshake.FINISHED
+            };
+
+            for (SSLHandshake hs : probableHandshakeMessages) {
+                HandshakeProducer handshakeProducer =
+                        shc.handshakeProducers.remove(hs.id);
+                if (handshakeProducer != null) {
+                    handshakeProducer.produce(context, clientHello);
+                }
+            }
+        }
+    }
+
+    /**
+     * The "ClientHello" handshake message consumer for TLS 1.3.
+     */
+    private static final
+            class T13ClientHelloConsumer implements HandshakeConsumer {
+        // Prevent instantiation of this class.
+        private T13ClientHelloConsumer() {
+            // blank
+        }
+
+        @Override
+        public void consume(ConnectionContext context,
+                HandshakeMessage message) throws IOException {
+            // The consuming happens in server side only.
+            ServerHandshakeContext shc = (ServerHandshakeContext)context;
+            ClientHelloMessage clientHello = (ClientHelloMessage)message;
+
+            // The client may send a dummy change_cipher_spec record
+            // immediately after the first ClientHello.
+            shc.conContext.consumers.putIfAbsent(
+                    ContentType.CHANGE_CIPHER_SPEC.id,
+                    ChangeCipherSpec.t13Consumer);
+
+            // Is it a resumption?
+            //
+            // Check and launch the "psk_key_exchange_modes" and
+            // "pre_shared_key" extensions first, which will reset the
+            // resuming session, no matter the extensions present or not.
+            shc.isResumption = true;
+            SSLExtension[] extTypes = new SSLExtension[] {
+                    SSLExtension.PSK_KEY_EXCHANGE_MODES,
+                    SSLExtension.CH_PRE_SHARED_KEY
+                };
+            clientHello.extensions.consumeOnLoad(shc, extTypes);
+
+            // Check and launch ClientHello extensions other than
+            // "psk_key_exchange_modes", "pre_shared_key", "protocol_version"
+            // and "key_share" extensions.
+            //
+            // These extensions may discard session resumption, or ask for
+            // hello retry.
+            extTypes = shc.sslConfig.getExclusiveExtensions(
+                    SSLHandshake.CLIENT_HELLO,
+                    Arrays.asList(
+                            SSLExtension.PSK_KEY_EXCHANGE_MODES,
+                            SSLExtension.CH_PRE_SHARED_KEY,
+                            SSLExtension.CH_SUPPORTED_VERSIONS));
+            clientHello.extensions.consumeOnLoad(shc, extTypes);
+
+            if (!shc.handshakeProducers.isEmpty()) {
+                // Should be HelloRetryRequest producer.
+                goHelloRetryRequest(shc, clientHello);
+            } else {
+                goServerHello(shc, clientHello);
+            }
+        }
+
+        private void goHelloRetryRequest(ServerHandshakeContext shc,
+                ClientHelloMessage clientHello) throws IOException {
+            HandshakeProducer handshakeProducer =
+                    shc.handshakeProducers.remove(
+                            SSLHandshake.HELLO_RETRY_REQUEST.id);
+            if (handshakeProducer != null) {
+                    handshakeProducer.produce(shc, clientHello);
+            } else {
+                // unlikely
+                shc.conContext.fatal(Alert.HANDSHAKE_FAILURE,
+                    "No HelloRetryRequest producer: " + shc.handshakeProducers);
+            }
+
+            if (!shc.handshakeProducers.isEmpty()) {
+                // unlikely, but please double check.
+                shc.conContext.fatal(Alert.HANDSHAKE_FAILURE,
+                    "unknown handshake producers: " + shc.handshakeProducers);
+            }
+        }
+
+        private void goServerHello(ServerHandshakeContext shc,
+                ClientHelloMessage clientHello) throws IOException {
+            //
+            // validate
+            //
+            shc.clientHelloRandom = clientHello.clientRandom;
+
+            //
+            // update
+            //
+            if (!shc.conContext.isNegotiated) {
+                shc.conContext.protocolVersion = shc.negotiatedProtocol;
+                shc.conContext.outputRecord.setVersion(shc.negotiatedProtocol);
+            }
+
+            // update the responders
+            //
+            // Only ServerHello/HelloRetryRequest producer, which adds
+            // more responders later.
+            shc.handshakeProducers.put(SSLHandshake.SERVER_HELLO.id,
+                SSLHandshake.SERVER_HELLO);
+
+            SSLHandshake[] probableHandshakeMessages = new SSLHandshake[] {
+                SSLHandshake.SERVER_HELLO,
+
+                // full handshake messages
+                SSLHandshake.ENCRYPTED_EXTENSIONS,
+                SSLHandshake.CERTIFICATE_REQUEST,
+                SSLHandshake.CERTIFICATE,
+                SSLHandshake.CERTIFICATE_VERIFY,
+                SSLHandshake.FINISHED
+            };
+
+            //
+            // produce
+            //
+            for (SSLHandshake hs : probableHandshakeMessages) {
+                HandshakeProducer handshakeProducer =
+                        shc.handshakeProducers.remove(hs.id);
+                if (handshakeProducer != null) {
+                    handshakeProducer.produce(shc, clientHello);
+                }
+            }
+        }
+    }
+
+    /**
+     * The "ClientHello" handshake message consumer for DTLS 1.2 and
+     * previous DTLS protocol versions.
+     */
+    private static final
+            class D12ClientHelloConsumer implements HandshakeConsumer {
+        // Prevent instantiation of this class.
+        private D12ClientHelloConsumer() {
+            // blank
+        }
+
+        @Override
+        public void consume(ConnectionContext context,
+                HandshakeMessage message) throws IOException {
+            // The consuming happens in server side only.
+            ServerHandshakeContext shc = (ServerHandshakeContext)context;
+            ClientHelloMessage clientHello = (ClientHelloMessage)message;
+
+            //
+            // validate
+            //
+
+            // Reject client initiated renegotiation?
+            //
+            // If server side should reject client-initiated renegotiation,
+            // send an Alert.HANDSHAKE_FAILURE fatal alert, not a
+            // no_renegotiation warning alert (no_renegotiation must be a
+            // warning: RFC 2246).  no_renegotiation might seem more
+            // natural at first, but warnings are not appropriate because
+            // the sending party does not know how the receiving party
+            // will behave.  This state must be treated as a fatal server
+            // condition.
+            //
+            // This will not have any impact on server initiated renegotiation.
+            if (shc.conContext.isNegotiated) {
+                if (!shc.conContext.secureRenegotiation &&
+                        !HandshakeContext.allowUnsafeRenegotiation) {
+                    shc.conContext.fatal(Alert.HANDSHAKE_FAILURE,
+                            "Unsafe renegotiation is not allowed");
+                }
+
+                if (ServerHandshakeContext.rejectClientInitiatedRenego &&
+                        !shc.kickstartMessageDelivered) {
+                    shc.conContext.fatal(Alert.HANDSHAKE_FAILURE,
+                            "Client initiated renegotiation is not allowed");
+                }
+            }
+
+            // Is it an abbreviated handshake?
+            if (clientHello.sessionId.length() != 0) {
+                SSLSessionImpl previous = ((SSLSessionContextImpl)shc.sslContext
+                            .engineGetServerSessionContext())
+                            .get(clientHello.sessionId.getId());
+
+                boolean resumingSession =
+                        (previous != null) && previous.isRejoinable();
+                if (!resumingSession) {
+                    if (SSLLogger.isOn &&
+                            SSLLogger.isOn("ssl,handshake,verbose")) {
+                        SSLLogger.finest(
+                            "Can't resume, " +
+                            "the existing session is not rejoinable");
+                    }
+                }
+                // Validate the negotiated protocol version.
+                if (resumingSession) {
+                    ProtocolVersion sessionProtocol =
+                            previous.getProtocolVersion();
+                    if (sessionProtocol != shc.negotiatedProtocol) {
+                        resumingSession = false;
+                        if (SSLLogger.isOn &&
+                                SSLLogger.isOn("ssl,handshake,verbose")) {
+                            SSLLogger.finest(
+                                "Can't resume, not the same protocol version");
+                        }
+                    }
+                }
+
+                // Validate the required client authentication.
+                if (resumingSession &&
+                    (shc.sslConfig.clientAuthType == CLIENT_AUTH_REQUIRED)) {
+
+                    try {
+                        previous.getPeerPrincipal();
+                    } catch (SSLPeerUnverifiedException e) {
+                        resumingSession = false;
+                        if (SSLLogger.isOn &&
+                                SSLLogger.isOn("ssl,handshake,verbose")) {
+                            SSLLogger.finest(
+                                "Can't resume, " +
+                                "client authentication is required");
+                        }
+                    }
+                }
+
+                // Validate that the cached cipher suite.
+                if (resumingSession) {
+                    CipherSuite suite = previous.getSuite();
+                    if ((!shc.isNegotiable(suite)) ||
+                            (!clientHello.cipherSuites.contains(suite))) {
+                        resumingSession = false;
+                        if (SSLLogger.isOn &&
+                                SSLLogger.isOn("ssl,handshake,verbose")) {
+                            SSLLogger.finest(
+                                "Can't resume, " +
+                                "the session cipher suite is absent");
+                        }
+                    }
+                }
+
+                // So far so good.  Note that the handshake extensions may reset
+                // the resuming options later.
+                shc.isResumption = resumingSession;
+                shc.resumingSession = resumingSession ? previous : null;
+            }
+
+            HelloCookieManager hcm =
+                shc.sslContext.getHelloCookieManager(ProtocolVersion.DTLS10);
+            if (!shc.isResumption &&
+                !hcm.isCookieValid(shc, clientHello, clientHello.cookie)) {
+                //
+                // Perform cookie exchange for DTLS handshaking if no cookie
+                // or the cookie is invalid in the ClientHello message.
+                //
+                // update the responders
+                shc.handshakeProducers.put(
+                        SSLHandshake.HELLO_VERIFY_REQUEST.id,
+                        SSLHandshake.HELLO_VERIFY_REQUEST);
+
+                //
+                // produce response handshake message
+                //
+                SSLHandshake.HELLO_VERIFY_REQUEST.produce(context, clientHello);
+
+                return;
+            }
+
+            // cache the client random number for further using
+            shc.clientHelloRandom = clientHello.clientRandom;
+
+            // Check and launch ClientHello extensions.
+            SSLExtension[] extTypes = shc.sslConfig.getEnabledExtensions(
+                    SSLHandshake.CLIENT_HELLO);
+            clientHello.extensions.consumeOnLoad(shc, extTypes);
+
+            //
+            // update
+            //
+            if (!shc.conContext.isNegotiated) {
+                shc.conContext.protocolVersion = shc.negotiatedProtocol;
+                shc.conContext.outputRecord.setVersion(shc.negotiatedProtocol);
+            }
+
+            // update the responders
+            //
+            // Only need to ServerHello, which may add more responders later.
+            shc.handshakeProducers.put(SSLHandshake.SERVER_HELLO.id,
+                    SSLHandshake.SERVER_HELLO);
+
+            //
+            // produce
+            //
+            SSLHandshake[] probableHandshakeMessages = new SSLHandshake[] {
+                SSLHandshake.SERVER_HELLO,
+
+                // full handshake messages
+                SSLHandshake.CERTIFICATE,
+                SSLHandshake.CERTIFICATE_STATUS,
+                SSLHandshake.SERVER_KEY_EXCHANGE,
+                SSLHandshake.CERTIFICATE_REQUEST,
+                SSLHandshake.SERVER_HELLO_DONE,
+
+                // abbreviated handshake messages
+                SSLHandshake.FINISHED
+            };
+
+            for (SSLHandshake hs : probableHandshakeMessages) {
+                HandshakeProducer handshakeProducer =
+                        shc.handshakeProducers.remove(hs.id);
+                if (handshakeProducer != null) {
+                    handshakeProducer.produce(context, clientHello);
+                }
+            }
+        }
+    }
+
+    /**
+     * The "ClientHello" handshake message consumer for DTLS 1.3.
+     */
+    private static final
+            class D13ClientHelloConsumer implements HandshakeConsumer {
+        // Prevent instantiation of this class.
+        private D13ClientHelloConsumer() {
+            // blank
+        }
+
+        @Override
+        public void consume(ConnectionContext context,
+                HandshakeMessage message) throws IOException {
+            throw new UnsupportedOperationException("Not supported yet.");
+        }
+    }
+}