src/java.base/share/classes/sun/security/ssl/ServerHello.java
branchJDK-8145252-TLS13-branch
changeset 56542 56aaa6cb3693
child 56603 f103e0c2be1e
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.base/share/classes/sun/security/ssl/ServerHello.java	Fri May 11 15:53:12 2018 -0700
@@ -0,0 +1,1449 @@
+/*
+ * 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.AlgorithmConstraints;
+import java.security.GeneralSecurityException;
+import java.text.MessageFormat;
+import java.util.Arrays;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Optional;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.IvParameterSpec;
+import javax.net.ssl.SSLException;
+import javax.net.ssl.SSLHandshakeException;
+import sun.security.ssl.CipherSuite.KeyExchange;
+import sun.security.ssl.ClientHello.ClientHelloMessage;
+import sun.security.ssl.SSLCipher.SSLReadCipher;
+import sun.security.ssl.SSLCipher.SSLWriteCipher;
+import sun.security.ssl.SSLHandshake.HandshakeMessage;
+import sun.security.ssl.SupportedVersionsExtension.SHSupportedVersionsSpec;
+
+/**
+ * Pack of the ServertHello/HelloRetryRequest handshake message.
+ */
+final class ServerHello {
+    static final SSLConsumer handshakeConsumer =
+        new ServerHelloConsumer();
+    static final HandshakeProducer t12HandshakeProducer =
+        new T12ServerHelloProducer();
+    static final HandshakeProducer t13HandshakeProducer =
+        new T13ServerHelloProducer();
+    static final HandshakeProducer hrrHandshakeProducer =
+        new T13HelloRetryRequestProducer();
+
+    static final HandshakeProducer hrrReproducer =
+        new T13HelloRetryRequestReproducer();
+
+    private static final HandshakeConsumer t12HandshakeConsumer =
+        new T12ServerHelloConsumer();
+    private static final HandshakeConsumer t13HandshakeConsumer =
+        new T13ServerHelloConsumer();
+
+    private static final HandshakeConsumer d12HandshakeConsumer =
+        new T12ServerHelloConsumer();
+    private static final HandshakeConsumer d13HandshakeConsumer =
+        new T13ServerHelloConsumer();
+
+    private static final HandshakeConsumer t13HrrHandshakeConsumer =
+        new T13HelloRetryRequestConsumer();
+    private static final HandshakeConsumer d13HrrHandshakeConsumer =
+        new T13HelloRetryRequestConsumer();
+
+    /**
+     * The ServertHello handshake message.
+     */
+    static final class ServerHelloMessage extends HandshakeMessage {
+        final ProtocolVersion           serverVersion;      // TLS 1.3 legacy
+        final RandomCookie              serverRandom;
+        final SessionId                 sessionId;          // TLS 1.3 legacy
+        final CipherSuite               cipherSuite;
+        final byte                      compressionMethod;  // TLS 1.3 legacy
+        final SSLExtensions             extensions;
+
+        // The HelloRetryRequest producer needs to use the ClientHello message
+        // for cookie generation.  Please don't use this field for other
+        // purpose unless it is really necessary.
+        final ClientHelloMessage        clientHello;
+
+        // Reserved for HelloRetryRequest consumer.  Please don't use this
+        // field for other purpose unless it is really necessary.
+        final ByteBuffer                handshakeRecord;
+
+        ServerHelloMessage(HandshakeContext context,
+                ProtocolVersion serverVersion, SessionId sessionId,
+                CipherSuite cipherSuite, RandomCookie serverRandom,
+                ClientHelloMessage clientHello) {
+            super(context);
+
+            this.serverVersion = serverVersion;
+            this.serverRandom = serverRandom;
+            this.sessionId = sessionId;
+            this.cipherSuite = cipherSuite;
+            this.compressionMethod = 0x00;      // Don't support compression.
+            this.extensions = new SSLExtensions(this);
+
+            // Reserve the ClientHello message for cookie generation.
+            this.clientHello = clientHello;
+
+            // The handshakeRecord field is used for HelloRetryRequest consumer
+            // only.  It's fine to set it to null for gnerating side of the
+            // ServerHello/HelloRetryRequest message.
+            this.handshakeRecord = null;
+        }
+
+        ServerHelloMessage(HandshakeContext context,
+                ByteBuffer m) throws IOException {
+            super(context);
+
+            // Reserve for HelloRetryRequest consumer if needed.
+            this.handshakeRecord = m.duplicate();
+
+            byte major = m.get();
+            byte minor = m.get();
+            this.serverVersion = ProtocolVersion.valueOf(major, minor);
+            if (this.serverVersion == null) {
+                // The client should only request for known protovol versions.
+                context.conContext.fatal(Alert.PROTOCOL_VERSION,
+                    "Unsupported protocol version: " +
+                    ProtocolVersion.nameOf(major, minor));
+            }
+
+            this.serverRandom = new RandomCookie(m);
+            this.sessionId = new SessionId(Record.getBytes8(m));
+            sessionId.checkLength(serverVersion.id);
+
+
+            int cipherSuiteId = Record.getInt16(m);
+            this.cipherSuite = CipherSuite.valueOf(cipherSuiteId);
+            if (cipherSuite == null || !context.isNegotiable(cipherSuite)) {
+                context.conContext.fatal(Alert.ILLEGAL_PARAMETER,
+                    "Server selected improper ciphersuite " +
+                    CipherSuite.nameOf(cipherSuiteId));
+            }
+
+            this.compressionMethod = m.get();
+            if (compressionMethod != 0) {
+                context.conContext.fatal(Alert.ILLEGAL_PARAMETER,
+                    "compression type not supported, " + compressionMethod);
+            }
+
+            SSLExtension[] supportedExtensions;
+            if (serverRandom.isHelloRetryRequest()) {
+                supportedExtensions = context.sslConfig.getEnabledExtensions(
+                            SSLHandshake.HELLO_RETRY_REQUEST);
+            } else {
+                supportedExtensions = context.sslConfig.getEnabledExtensions(
+                            SSLHandshake.SERVER_HELLO);
+            }
+
+            if (m.hasRemaining()) {
+                this.extensions =
+                    new SSLExtensions(this, m, supportedExtensions);
+            } else {
+                this.extensions = new SSLExtensions(this);
+            }
+
+            // The clientHello field is used for HelloRetryRequest producer
+            // only.  It's fine to set it to null for receiving side of
+            // ServerHello/HelloRetryRequest message.
+            this.clientHello = null;        // not used, let it be null;
+        }
+
+        @Override
+        public SSLHandshake handshakeType() {
+            return serverRandom.isHelloRetryRequest() ?
+                SSLHandshake.HELLO_RETRY_REQUEST : SSLHandshake.SERVER_HELLO;
+        }
+
+        @Override
+        public int messageLength() {
+            // almost fixed header size, except session ID and extensions:
+            //      major + minor = 2
+            //      random = 32
+            //      session ID len field = 1
+            //      cipher suite = 2
+            //      compression = 1
+            //      extensions: if present, 2 + length of extensions
+            // In TLS 1.3, use of certain extensions is mandatory.
+            return 38 + sessionId.length() + extensions.length();
+        }
+
+        @Override
+        public void send(HandshakeOutStream hos) throws IOException {
+            hos.putInt8(serverVersion.major);
+            hos.putInt8(serverVersion.minor);
+            hos.write(serverRandom.randomBytes);
+            hos.putBytes8(sessionId.getId());
+            hos.putInt8((cipherSuite.id >> 8) & 0xFF);
+            hos.putInt8(cipherSuite.id & 0xff);
+            hos.putInt8(compressionMethod);
+
+            extensions.send(hos);           // In TLS 1.3, use of certain
+                                            // extensions is mandatory.
+        }
+
+        @Override
+        public String toString() {
+            MessageFormat messageFormat = new MessageFormat(
+                "\"{0}\": '{'\n" +
+                "  \"server version\"      : \"{1}\",\n" +
+                "  \"random\"              : \"{2}\",\n" +
+                "  \"session id\"          : \"{3}\",\n" +
+                "  \"cipher suite\"        : \"{4}\",\n" +
+                "  \"compression methods\" : \"{5}\",\n" +
+                "  \"extensions\"          : [\n" +
+                "{6}\n" +
+                "  ]\n" +
+                "'}'",
+                Locale.ENGLISH);
+            Object[] messageFields = {
+                serverRandom.isHelloRetryRequest() ?
+                    "HelloRetryRequest" : "ServerHello",
+                serverVersion.name,
+                Utilities.toHexString(serverRandom.randomBytes),
+                sessionId.toString(),
+                cipherSuite.name + "(" +
+                        Utilities.byte16HexString(cipherSuite.id) + ")",
+                Utilities.toHexString(compressionMethod),
+                Utilities.indent(extensions.toString(), "    ")
+            };
+
+            return messageFormat.format(messageFields);
+        }
+    }
+
+    /**
+     * The "ServerHello" handshake message producer.
+     */
+    private static final class T12ServerHelloProducer
+            implements HandshakeProducer {
+
+        // Prevent instantiation of this class.
+        private T12ServerHelloProducer() {
+            // blank
+        }
+
+        @Override
+        public byte[] produce(ConnectionContext context,
+                HandshakeMessage message) throws IOException {
+            // The producing happens in server side only.
+            ServerHandshakeContext shc = (ServerHandshakeContext)context;
+            ClientHelloMessage clientHello = (ClientHelloMessage)message;
+
+            // If client hasn't specified a session we can resume, start a
+            // new one and choose its cipher suite and compression options,
+            // unless new session creation is disabled for this connection!
+            if (!shc.isResumption || shc.resumingSession == null) {
+                if (!shc.sslConfig.enableSessionCreation) {
+                    throw new SSLException(
+                        "Not resumption, and no new session is allowed");
+                }
+
+                if (shc.localSupportedSignAlgs == null) {
+                    shc.localSupportedSignAlgs =
+                        SignatureScheme.getSupportedAlgorithms(
+                                shc.algorithmConstraints, shc.activeProtocols);
+                }
+
+                SSLSessionImpl session =
+                        new SSLSessionImpl(shc, CipherSuite.C_NULL);
+                session.setMaximumPacketSize(shc.sslConfig.maximumPacketSize);
+                shc.handshakeSession = session;
+
+                // consider the handshake extension impact
+                SSLExtension[] enabledExtensions =
+                        shc.sslConfig.getEnabledExtensions(
+                            SSLHandshake.CLIENT_HELLO, shc.negotiatedProtocol);
+                clientHello.extensions.consumeOnTrade(shc, enabledExtensions);
+
+                // negotiate the cipher suite.
+                KeyExchangeProperties credentials =
+                        chooseCipherSuite(shc, clientHello);
+                if (credentials == null) {
+                    shc.conContext.fatal(Alert.HANDSHAKE_FAILURE,
+                            "no cipher suites in common");
+
+                    return null;    // make the compiler happy
+                }
+                shc.negotiatedCipherSuite = credentials.cipherSuite;
+                shc.handshakeKeyExchange = credentials.keyExchange;
+                shc.handshakeSession.setSuite(credentials.cipherSuite);
+                shc.handshakePossessions.addAll(
+                        Arrays.asList(credentials.possessions));
+                shc.handshakeHash.determine(
+                        shc.negotiatedProtocol, shc.negotiatedCipherSuite);
+
+                // Check the incoming 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);
+
+                // update the responders
+                SSLKeyExchange ke = credentials.keyExchange;
+                if (ke != null) {
+                    for (Map.Entry<Byte, HandshakeProducer> me :
+                            ke.getHandshakeProducers(shc)) {
+                        shc.handshakeProducers.put(
+                                me.getKey(), me.getValue());
+                    }
+                }
+
+                if ((ke != null) &&
+                        (shc.sslConfig.clientAuthType !=
+                                ClientAuthType.CLIENT_AUTH_NONE) &&
+                        !shc.negotiatedCipherSuite.isAnonymous()) {
+                    for (SSLHandshake hs :
+                            ke.getRelatedHandshakers(shc)) {
+                        if (hs == SSLHandshake.CERTIFICATE) {
+                            shc.handshakeProducers.put(
+                                    SSLHandshake.CERTIFICATE_REQUEST.id,
+                                    SSLHandshake.CERTIFICATE_REQUEST);
+                            break;
+                        }
+                    }
+                }
+                shc.handshakeProducers.put(SSLHandshake.SERVER_HELLO_DONE.id,
+                        SSLHandshake.SERVER_HELLO_DONE);
+            } else {
+                shc.handshakeSession = shc.resumingSession;
+                shc.negotiatedProtocol =
+                        shc.resumingSession.getProtocolVersion();
+                shc.negotiatedCipherSuite = shc.resumingSession.getSuite();
+                shc.handshakeHash.determine(
+                        shc.negotiatedProtocol, shc.negotiatedCipherSuite);
+            }
+
+            // Generate the ServerHello handshake message.
+            // TODO: not yet consider downgrade protection.
+            ServerHelloMessage shm = new ServerHelloMessage(shc,
+                    shc.negotiatedProtocol,
+                    shc.handshakeSession.getSessionId(),
+                    shc.negotiatedCipherSuite,
+                    new RandomCookie(shc.sslContext.getSecureRandom()),
+                    clientHello);
+            shc.serverHelloRandom = shm.serverRandom;
+
+            // Produce extensions for ServerHello handshake message.
+            SSLExtension[] serverHelloExtensions =
+                shc.sslConfig.getEnabledExtensions(
+                        SSLHandshake.SERVER_HELLO, shc.negotiatedProtocol);
+            shm.extensions.produce(shc, serverHelloExtensions);
+            if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
+                SSLLogger.fine("Produced ServerHello handshake message", shm);
+            }
+
+            // Output the handshake message.
+            shm.write(shc.handshakeOutput);
+            shc.handshakeOutput.flush();
+
+            if (shc.isResumption && shc.resumingSession != null) {
+                SSLTrafficKeyDerivation kdg =
+                    SSLTrafficKeyDerivation.valueOf(shc.negotiatedProtocol);
+                if (kdg == null) {
+                    // unlikely
+                    shc.conContext.fatal(Alert.INTERNAL_ERROR,
+                            "Not supported key derivation: " +
+                            shc.negotiatedProtocol);
+                } else {
+                    shc.handshakeKeyDerivation = kdg.createKeyDerivation(
+                            shc, shc.resumingSession.getMasterSecret());
+                }
+
+                // update the responders
+                shc.handshakeProducers.put(SSLHandshake.FINISHED.id,
+                        SSLHandshake.FINISHED);
+            }
+
+            // The handshake message has been delivered.
+            return null;
+        }
+
+        private static KeyExchangeProperties chooseCipherSuite(
+                ServerHandshakeContext shc,
+                ClientHelloMessage clientHello) throws IOException {
+            List<CipherSuite> prefered;
+            List<CipherSuite> proposed;
+            if (shc.sslConfig.preferLocalCipherSuites) {
+                prefered = shc.activeCipherSuites;
+                proposed = clientHello.cipherSuites;
+            } else {
+                prefered = clientHello.cipherSuites;
+                proposed = shc.activeCipherSuites;
+            }
+
+            List<CipherSuite> legacySuites = new LinkedList<>();
+            for (CipherSuite cs : prefered) {
+                if (!HandshakeContext.isNegotiable(
+                        proposed, shc.negotiatedProtocol, cs)) {
+                    continue;
+                }
+
+                if (shc.sslConfig.clientAuthType ==
+                        ClientAuthType.CLIENT_AUTH_REQUIRED) {
+                    if ((cs.keyExchange == KeyExchange.K_DH_ANON) ||
+                        (cs.keyExchange == KeyExchange.K_ECDH_ANON)) {
+                        continue;
+                    }
+                }
+
+                SSLKeyExchange ke = SSLKeyExchange.valueOf(cs.keyExchange);
+                if (ke == null) {
+                    continue;
+                }
+                if (!ServerHandshakeContext.legacyAlgorithmConstraints.permits(
+                        null, cs.name, null)) {
+                    legacySuites.add(cs);
+                    continue;
+                }
+
+                SSLPossession[] hcds = ke.createPossessions(shc);
+                if ((hcds == null) || (hcds.length == 0)) {
+                    continue;
+                }
+
+                // The cipher suite has been negotiated.
+                if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
+                    SSLLogger.fine("use cipher suite " + cs.name);
+                }
+
+                return new KeyExchangeProperties(cs, ke, hcds);
+            }
+
+            for (CipherSuite cs : legacySuites) {
+                SSLKeyExchange ke = SSLKeyExchange.valueOf(cs.keyExchange);
+                if (ke != null) {
+                    SSLPossession[] hcds = ke.createPossessions(shc);
+                    if ((hcds != null) && (hcds.length != 0)) {
+                        if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
+                            SSLLogger.warning(
+                                "use legacy cipher suite " + cs.name);
+                        }
+                        return new KeyExchangeProperties(cs, ke, hcds);
+                    }
+                }
+            }
+
+            shc.conContext.fatal(Alert.HANDSHAKE_FAILURE,
+                    "no cipher suites in common");
+
+            return null;    // make the compiler happy.
+        }
+
+        private static final class KeyExchangeProperties {
+            final CipherSuite cipherSuite;
+            final SSLKeyExchange keyExchange;
+            final SSLPossession[] possessions;
+
+            private KeyExchangeProperties(CipherSuite cipherSuite,
+                    SSLKeyExchange keyExchange, SSLPossession[] possessions) {
+                this.cipherSuite = cipherSuite;
+                this.keyExchange = keyExchange;
+                this.possessions = possessions;
+            }
+        }
+    }
+
+    /**
+     * The "ServerHello" handshake message producer.
+     */
+    private static final
+            class T13ServerHelloProducer implements HandshakeProducer {
+        // Prevent instantiation of this class.
+        private T13ServerHelloProducer() {
+            // blank
+        }
+
+        @Override
+        public byte[] produce(ConnectionContext context,
+                HandshakeMessage message) throws IOException {
+            // The producing happens in server side only.
+            ServerHandshakeContext shc = (ServerHandshakeContext)context;
+            ClientHelloMessage clientHello = (ClientHelloMessage)message;
+
+            // If client hasn't specified a session we can resume, start a
+            // new one and choose its cipher suite and compression options,
+            // unless new session creation is disabled for this connection!
+            if (!shc.isResumption || shc.resumingSession == null) {
+                if (!shc.sslConfig.enableSessionCreation) {
+                    throw new SSLException(
+                        "Not resumption, and no new session is allowed");
+                }
+
+                if (shc.localSupportedSignAlgs == null) {
+                    shc.localSupportedSignAlgs =
+                        SignatureScheme.getSupportedAlgorithms(
+                                shc.algorithmConstraints, shc.activeProtocols);
+                }
+
+                SSLSessionImpl session =
+                        new SSLSessionImpl(shc, CipherSuite.C_NULL);
+                session.setMaximumPacketSize(shc.sslConfig.maximumPacketSize);
+                shc.handshakeSession = session;
+
+                // consider the handshake extension impact
+                SSLExtension[] enabledExtensions =
+                        shc.sslConfig.getEnabledExtensions(
+                            SSLHandshake.CLIENT_HELLO, shc.negotiatedProtocol);
+                clientHello.extensions.consumeOnTrade(shc, enabledExtensions);
+
+                // negotiate the cipher suite.
+                CipherSuite cipherSuite = chooseCipherSuite(shc, clientHello);
+                if (cipherSuite == null) {
+                    shc.conContext.fatal(Alert.HANDSHAKE_FAILURE,
+                            "no cipher suites in common");
+                    return null;    // make the compiler happy
+                }
+                shc.negotiatedCipherSuite = cipherSuite;
+                shc.handshakeSession.setSuite(cipherSuite);
+                shc.handshakeHash.determine(
+                        shc.negotiatedProtocol, shc.negotiatedCipherSuite);
+            } else {
+                shc.handshakeSession = shc.resumingSession;
+
+                // consider the handshake extension impact
+                SSLExtension[] enabledExtensions =
+                shc.sslConfig.getEnabledExtensions(
+                SSLHandshake.CLIENT_HELLO, shc.negotiatedProtocol);
+                clientHello.extensions.consumeOnTrade(shc, enabledExtensions);
+
+                shc.negotiatedProtocol =
+                        shc.resumingSession.getProtocolVersion();
+                shc.negotiatedCipherSuite = shc.resumingSession.getSuite();
+                shc.handshakeHash.determine(
+                        shc.negotiatedProtocol, shc.negotiatedCipherSuite);
+
+                setUpPskKD(shc, shc.resumingSession.consumePreSharedKey().get());
+
+                // The session can't be resumed again---remove it from cache
+                SSLSessionContextImpl sessionCache = (SSLSessionContextImpl)
+                    shc.sslContext.engineGetServerSessionContext();
+                sessionCache.remove(shc.resumingSession.getSessionId());
+            }
+
+            // update the responders
+            shc.handshakeProducers.put(SSLHandshake.ENCRYPTED_EXTENSIONS.id,
+                    SSLHandshake.ENCRYPTED_EXTENSIONS);
+            shc.handshakeProducers.put(SSLHandshake.FINISHED.id,
+                    SSLHandshake.FINISHED);
+
+            // TODO: not yet consider downgrade protection.
+            ServerHelloMessage shm = new ServerHelloMessage(shc,
+                    ProtocolVersion.TLS12,      // use legacy version
+                    clientHello.sessionId,      // echo back
+                    shc.negotiatedCipherSuite,
+                    new RandomCookie(shc.sslContext.getSecureRandom()),
+                    clientHello);
+            shc.serverHelloRandom = shm.serverRandom;
+
+            // Produce extensions for ServerHello handshake message.
+            SSLExtension[] serverHelloExtensions =
+                    shc.sslConfig.getEnabledExtensions(
+                        SSLHandshake.SERVER_HELLO, shc.negotiatedProtocol);
+            shm.extensions.produce(shc, serverHelloExtensions);
+            if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
+                SSLLogger.fine("Produced ServerHello handshake message", shm);
+            }
+
+            // Output the handshake message.
+            shm.write(shc.handshakeOutput);
+            shc.handshakeOutput.flush();
+
+            // Change client/server handshake traffic secrets.
+            // Refresh handshake hash
+            shc.handshakeHash.update();
+
+            // Change client/server handshake traffic secrets.
+            SSLKeyExchange ke = shc.handshakeKeyExchange;
+            if (ke == null) {
+                // unlikely
+                shc.conContext.fatal(Alert.INTERNAL_ERROR,
+                        "Not negotiated key shares");
+                return null;    // make the compiler happy
+            }
+
+            SSLKeyDerivation handshakeKD = ke.createKeyDerivation(shc);
+            SecretKey handshakeSecret = handshakeKD.deriveKey(
+                    "TlsHandshakeSecret", null);
+
+            SSLTrafficKeyDerivation kdg =
+                SSLTrafficKeyDerivation.valueOf(shc.negotiatedProtocol);
+            if (kdg == null) {
+                // unlikely
+                shc.conContext.fatal(Alert.INTERNAL_ERROR,
+                        "Not supported key derivation: " +
+                        shc.negotiatedProtocol);
+                return null;    // make the compiler happy
+            }
+
+            SSLKeyDerivation kd =
+                    new SSLSecretDerivation(shc, handshakeSecret);
+
+            // update the handshake traffic read keys.
+            SecretKey readSecret = kd.deriveKey(
+                    "TlsClientHandshakeTrafficSecret", null);
+            SSLKeyDerivation readKD =
+                    kdg.createKeyDerivation(shc, readSecret);
+            SecretKey readKey = readKD.deriveKey(
+                    "TlsKey", null);
+            SecretKey readIvSecret = readKD.deriveKey(
+                    "TlsIv", null);
+            IvParameterSpec readIv =
+                    new IvParameterSpec(readIvSecret.getEncoded());
+            SSLReadCipher readCipher;
+            try {
+                readCipher =
+                    shc.negotiatedCipherSuite.bulkCipher.createReadCipher(
+                        Authenticator.valueOf(shc.negotiatedProtocol),
+                        shc.negotiatedProtocol, readKey, readIv,
+                        shc.sslContext.getSecureRandom());
+            } catch (GeneralSecurityException gse) {
+                // unlikely
+                shc.conContext.fatal(Alert.HANDSHAKE_FAILURE,
+                        "Missing cipher algorithm", gse);
+                return null;    // make the compiler happy
+            }
+
+            shc.baseReadSecret = readSecret;
+            shc.conContext.inputRecord.changeReadCiphers(readCipher);
+
+            // update the handshake traffic write secret.
+            SecretKey writeSecret = kd.deriveKey(
+                    "TlsServerHandshakeTrafficSecret", null);
+            SSLKeyDerivation writeKD =
+                    kdg.createKeyDerivation(shc, writeSecret);
+            SecretKey writeKey = writeKD.deriveKey(
+                    "TlsKey", null);
+            SecretKey writeIvSecret = writeKD.deriveKey(
+                    "TlsIv", null);
+            IvParameterSpec writeIv =
+                    new IvParameterSpec(writeIvSecret.getEncoded());
+            SSLWriteCipher writeCipher;
+            try {
+                writeCipher =
+                    shc.negotiatedCipherSuite.bulkCipher.createWriteCipher(
+                        Authenticator.valueOf(shc.negotiatedProtocol),
+                        shc.negotiatedProtocol, writeKey, writeIv,
+                        shc.sslContext.getSecureRandom());
+            } catch (GeneralSecurityException gse) {
+                // unlikely
+                shc.conContext.fatal(Alert.HANDSHAKE_FAILURE,
+                        "Missing cipher algorithm", gse);
+                return null;    //  make the compiler happy
+            }
+
+            shc.baseWriteSecret = writeSecret;
+            shc.conContext.outputRecord.changeWriteCiphers(
+                    writeCipher, (clientHello.sessionId.length() != 0));
+
+            // Update the context for master key derivation.
+            shc.handshakeKeyDerivation = kd;
+
+            // The handshake message has been delivered.
+            return null;
+        }
+
+        private static CipherSuite chooseCipherSuite(
+                ServerHandshakeContext shc,
+                ClientHelloMessage clientHello) throws IOException {
+            List<CipherSuite> prefered;
+            List<CipherSuite> proposed;
+            if (shc.sslConfig.preferLocalCipherSuites) {
+                prefered = shc.activeCipherSuites;
+                proposed = clientHello.cipherSuites;
+            } else {
+                prefered = clientHello.cipherSuites;
+                proposed = shc.activeCipherSuites;
+            }
+
+            CipherSuite legacySuite = null;
+            AlgorithmConstraints legacyConstraints =
+                    ServerHandshakeContext.legacyAlgorithmConstraints;
+            for (CipherSuite cs : prefered) {
+                if (!HandshakeContext.isNegotiable(
+                        proposed, shc.negotiatedProtocol, cs)) {
+                    continue;
+                }
+
+                if ((legacySuite == null) &&
+                        !legacyConstraints.permits(null, cs.name, null)) {
+                    legacySuite = cs;
+                    continue;
+                }
+
+                // The cipher suite has been negotiated.
+                if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
+                    SSLLogger.fine("use cipher suite " + cs.name);
+                }
+                return cs;
+            }
+
+            if (legacySuite != null) {
+                if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
+                    SSLLogger.warning(
+                            "use legacy cipher suite " + legacySuite.name);
+                }
+                return legacySuite;
+            }
+
+            // no cipher suites in common
+            return null;
+        }
+    }
+
+    /**
+     * The "HelloRetryRequest" handshake message producer.
+     */
+    private static final
+            class T13HelloRetryRequestProducer implements HandshakeProducer {
+        // Prevent instantiation of this class.
+        private T13HelloRetryRequestProducer() {
+            // blank
+        }
+
+        @Override
+        public byte[] produce(ConnectionContext context,
+                HandshakeMessage message) throws IOException {
+            ServerHandshakeContext shc = (ServerHandshakeContext) context;
+            ClientHelloMessage clientHello = (ClientHelloMessage) message;
+
+            // negotiate the cipher suite.
+            CipherSuite cipherSuite =
+                    T13ServerHelloProducer.chooseCipherSuite(shc, clientHello);
+            if (cipherSuite == null) {
+                shc.conContext.fatal(Alert.HANDSHAKE_FAILURE,
+                        "no cipher suites in common for hello retry request");
+                return null;    // make the compiler happy
+            }
+
+            ServerHelloMessage hhrm = new ServerHelloMessage(shc,
+                    ProtocolVersion.TLS12,      // use legacy version
+                    clientHello.sessionId,      //  echo back
+                    cipherSuite,
+                    RandomCookie.hrrRandom,
+                    clientHello
+            );
+
+            shc.negotiatedCipherSuite = cipherSuite;
+            shc.handshakeHash.determine(
+                    shc.negotiatedProtocol, shc.negotiatedCipherSuite);
+
+            // Produce extensions for HelloRetryRequest handshake message.
+            SSLExtension[] serverHelloExtensions =
+                shc.sslConfig.getEnabledExtensions(
+                    SSLHandshake.HELLO_RETRY_REQUEST, shc.negotiatedProtocol);
+            hhrm.extensions.produce(shc, serverHelloExtensions);
+            if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
+                SSLLogger.fine(
+                        "Produced HelloRetryRequest handshake message", hhrm);
+            }
+
+            // Output the handshake message.
+            hhrm.write(shc.handshakeOutput);
+            shc.handshakeOutput.flush();
+
+            // TODO: stateless, clean up the handshake context?
+            shc.handshakeHash.finish();     // forgot about the handshake hash
+            shc.handshakeExtensions.clear();
+
+            // What's the expected response?
+            shc.handshakeConsumers.put(
+                    SSLHandshake.CLIENT_HELLO.id, SSLHandshake.CLIENT_HELLO);
+
+            // The handshake message has been delivered.
+            return null;
+        }
+    }
+
+    /**
+     * The "HelloRetryRequest" handshake message reproducer.
+     */
+    private static final
+            class T13HelloRetryRequestReproducer implements HandshakeProducer {
+        // Prevent instantiation of this class.
+        private T13HelloRetryRequestReproducer() {
+            // blank
+        }
+
+        @Override
+        public byte[] produce(ConnectionContext context,
+                HandshakeMessage message) throws IOException {
+            ServerHandshakeContext shc = (ServerHandshakeContext) context;
+            ClientHelloMessage clientHello = (ClientHelloMessage) message;
+
+            // negotiate the cipher suite.
+            CipherSuite cipherSuite = shc.negotiatedCipherSuite;
+            ServerHelloMessage hhrm = new ServerHelloMessage(shc,
+                    ProtocolVersion.TLS12,      // use legacy version
+                    clientHello.sessionId,      //  echo back
+                    cipherSuite,
+                    RandomCookie.hrrRandom,
+                    clientHello
+            );
+
+            // Produce extensions for HelloRetryRequest handshake message.
+            SSLExtension[] serverHelloExtensions =
+                shc.sslConfig.getEnabledExtensions(
+                    SSLHandshake.MESSAGE_HASH, shc.negotiatedProtocol);
+            hhrm.extensions.produce(shc, serverHelloExtensions);
+            if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
+                SSLLogger.fine(
+                        "Reproduced HelloRetryRequest handshake message", hhrm);
+            }
+
+            HandshakeOutStream hos = new HandshakeOutStream(null);
+            hhrm.write(hos);
+
+            return hos.toByteArray();
+        }
+    }
+
+    /**
+     * The "ServerHello" handshake message consumer.
+     */
+    private static final
+            class ServerHelloConsumer implements SSLConsumer {
+        // Prevent instantiation of this class.
+        private ServerHelloConsumer() {
+            // blank
+        }
+
+        @Override
+        public void consume(ConnectionContext context,
+                ByteBuffer message) throws IOException {
+            // The consuming happens in client side only.
+            ClientHandshakeContext chc = (ClientHandshakeContext)context;
+
+            // clean up this consumer
+            chc.handshakeConsumers.remove(SSLHandshake.SERVER_HELLO.id);
+            if (!chc.handshakeConsumers.isEmpty()) {
+                // DTLS 1.0/1.2
+                chc.handshakeConsumers.remove(
+                        SSLHandshake.HELLO_VERIFY_REQUEST.id);
+            }
+            if (!chc.handshakeConsumers.isEmpty()) {
+                chc.conContext.fatal(Alert.UNEXPECTED_MESSAGE,
+                    "No more message expected before ServerHello is processed");
+            }
+
+            int startPos = message.position();
+            ServerHelloMessage shm = new ServerHelloMessage(chc, message);
+            if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
+                SSLLogger.fine("Consuming ServerHello handshake message", shm);
+            }
+
+            if (shm.serverRandom.isHelloRetryRequest()) {
+                onHelloRetryRequest(chc, shm);
+            } else {
+                onServerHello(chc, shm);
+            }
+        }
+
+        private void onHelloRetryRequest(ClientHandshakeContext chc,
+                ServerHelloMessage helloRetryRequest) throws IOException {
+            // Negotiate protocol version.
+            //
+            // Check and lanuch SupportedVersions.
+            SSLExtension[] extTypes = new SSLExtension[] {
+                    SSLExtension.HRR_SUPPORTED_VERSIONS
+                };
+            helloRetryRequest.extensions.consumeOnLoad(chc, extTypes);
+
+            ProtocolVersion serverVersion;
+            SHSupportedVersionsSpec svs =
+                    (SHSupportedVersionsSpec)chc.handshakeExtensions.get(
+                            SSLExtension.HRR_SUPPORTED_VERSIONS);
+            if (svs != null) {
+                serverVersion =            // could be null
+                        ProtocolVersion.valueOf(svs.selectedVersion);
+            } else {
+                serverVersion = helloRetryRequest.serverVersion;
+            }
+
+            if (!chc.activeProtocols.contains(serverVersion)) {
+                chc.conContext.fatal(Alert.PROTOCOL_VERSION,
+                    "The server selected protocol version " + serverVersion +
+                    " is not accepted by client preferences " +
+                    chc.activeProtocols);
+            }
+
+            if (!serverVersion.useTLS13PlusSpec()) {
+                chc.conContext.fatal(Alert.PROTOCOL_VERSION,
+                    "Unexpected HelloRetryRequest for " + serverVersion.name);
+            }
+
+            chc.negotiatedProtocol = serverVersion;
+            if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
+                SSLLogger.fine(
+                    "Negotiated protocol version: " + serverVersion.name);
+            }
+
+            // TLS 1.3 key share extension may have produced client
+            // possessions for TLS 1.3 key exchanges.
+            //
+            // Clean up before producing new client key share possessions.
+            chc.handshakePossessions.clear();
+
+            if (serverVersion.isDTLS) {
+                d13HrrHandshakeConsumer.consume(chc, helloRetryRequest);
+            } else {
+                t13HrrHandshakeConsumer.consume(chc, helloRetryRequest);
+            }
+        }
+
+        private void onServerHello(ClientHandshakeContext chc,
+                ServerHelloMessage serverHello) throws IOException {
+            // Negotiate protocol version.
+            //
+            // Check and lanuch SupportedVersions.
+            SSLExtension[] extTypes = new SSLExtension[] {
+                    SSLExtension.SH_SUPPORTED_VERSIONS
+                };
+            serverHello.extensions.consumeOnLoad(chc, extTypes);
+
+            ProtocolVersion serverVersion;
+            SHSupportedVersionsSpec svs =
+                    (SHSupportedVersionsSpec)chc.handshakeExtensions.get(
+                            SSLExtension.SH_SUPPORTED_VERSIONS);
+            if (svs != null) {
+                serverVersion =            // could be null
+                        ProtocolVersion.valueOf(svs.selectedVersion);
+            } else {
+                serverVersion = serverHello.serverVersion;
+            }
+
+            if (!chc.activeProtocols.contains(serverVersion)) {
+                chc.conContext.fatal(Alert.PROTOCOL_VERSION,
+                    "The server selected protocol version " + serverVersion +
+                    " is not accepted by client preferences " +
+                    chc.activeProtocols);
+            }
+
+            chc.negotiatedProtocol = serverVersion;
+            if (!chc.conContext.isNegotiated) {
+                chc.conContext.protocolVersion = chc.negotiatedProtocol;
+                chc.conContext.outputRecord.setVersion(chc.negotiatedProtocol);
+            }
+            if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
+                SSLLogger.fine(
+                    "Negotiated protocol version: " + serverVersion.name);
+            }
+
+            // Consume the handshake message for the specific protocol version.
+            if (serverVersion.isDTLS) {
+                if (serverVersion.useTLS13PlusSpec()) {
+                    d13HandshakeConsumer.consume(chc, serverHello);
+                } else {
+                    // TLS 1.3 key share extension may have produced client
+                    // possessions for TLS 1.3 key exchanges.
+                    chc.handshakePossessions.clear();
+
+                    d12HandshakeConsumer.consume(chc, serverHello);
+                }
+            } else {
+                if (serverVersion.useTLS13PlusSpec()) {
+                    t13HandshakeConsumer.consume(chc, serverHello);
+                } else {
+                    // TLS 1.3 key share extension may have produced client
+                    // possessions for TLS 1.3 key exchanges.
+                    chc.handshakePossessions.clear();
+
+                    t12HandshakeConsumer.consume(chc, serverHello);
+                }
+            }
+        }
+    }
+
+    private static final
+            class T12ServerHelloConsumer implements HandshakeConsumer {
+        // Prevent instantiation of this class.
+        private T12ServerHelloConsumer() {
+            // blank
+        }
+
+        @Override
+        public void consume(ConnectionContext context,
+                HandshakeMessage message) throws IOException {
+            // The consuming happens in client side only.
+            ClientHandshakeContext chc = (ClientHandshakeContext)context;
+            ServerHelloMessage serverHello = (ServerHelloMessage)message;
+            if (!chc.isNegotiable(serverHello.serverVersion)) {
+                chc.conContext.fatal(Alert.PROTOCOL_VERSION,
+                    "Server chose " + serverHello.serverVersion +
+                    ", but that protocol version is not enabled or " +
+                    "not supported by the client.");
+            }
+
+            // chc.negotiatedProtocol = serverHello.serverVersion;
+            chc.negotiatedCipherSuite = serverHello.cipherSuite;
+            chc.handshakeHash.determine(
+                    chc.negotiatedProtocol, chc.negotiatedCipherSuite);
+            chc.serverHelloRandom = serverHello.serverRandom;
+            if (chc.negotiatedCipherSuite.keyExchange == null) {
+                chc.conContext.fatal(Alert.PROTOCOL_VERSION,
+                    "TLS 1.2 or prior version does not support the " +
+                    "server cipher suite: " + chc.negotiatedCipherSuite.name);
+            }
+
+            //
+            // validate
+            //
+
+            // Check and lanuch the "renegotiation_info" extension.
+            SSLExtension[] extTypes = new SSLExtension[] {
+                    SSLExtension.SH_RENEGOTIATION_INFO
+                };
+            serverHello.extensions.consumeOnLoad(chc, extTypes);
+
+            // Is it session resuming?
+            if (chc.resumingSession != null) {
+                // we tried to resume, let's see what the server decided
+                if (serverHello.sessionId.equals(
+                        chc.resumingSession.getSessionId())) {
+                    // server resumed the session, let's make sure everything
+                    // checks out
+
+                    // Verify that the session ciphers are unchanged.
+                    CipherSuite sessionSuite = chc.resumingSession.getSuite();
+                    if (chc.negotiatedCipherSuite != sessionSuite) {
+                        chc.conContext.fatal(Alert.PROTOCOL_VERSION,
+                            "Server returned wrong cipher suite for session");
+                    }
+
+                    // verify protocol version match
+                    ProtocolVersion sessionVersion =
+                            chc.resumingSession.getProtocolVersion();
+                    if (chc.negotiatedProtocol != sessionVersion) {
+                        chc.conContext.fatal(Alert.PROTOCOL_VERSION,
+                            "Server resumed with wrong protocol version");
+                    }
+
+                    // looks fine;  resume it.
+                    chc.isResumption = true;
+                    chc.resumingSession.setAsSessionResumption(true);
+                    chc.handshakeSession = chc.resumingSession;
+                } else {
+                    // we wanted to resume, but the server refused
+                    //
+                    // Invalidate the session for initial handshake in case
+                    // of reusing next time.
+                    if (chc.resumingSession != null) {
+                        chc.resumingSession.invalidate();
+                        chc.resumingSession = null;
+                    }
+                    chc.isResumption = false;
+                    if (!chc.sslConfig.enableSessionCreation) {
+                        chc.conContext.fatal(Alert.PROTOCOL_VERSION,
+                            "New session creation is disabled");
+                    }
+                }
+            }
+
+            // Check and launch ClientHello extensions.
+            extTypes = chc.sslConfig.getEnabledExtensions(
+                    SSLHandshake.SERVER_HELLO);
+            serverHello.extensions.consumeOnLoad(chc, extTypes);
+
+            if (!chc.isResumption) {
+                if (chc.resumingSession != null) {
+                    // in case the resumption happens next time.
+                    chc.resumingSession.invalidate();
+                    chc.resumingSession = null;
+                }
+
+                if (!chc.sslConfig.enableSessionCreation) {
+                    chc.conContext.fatal(Alert.PROTOCOL_VERSION,
+                        "New session creation is disabled");
+                }
+                chc.handshakeSession = new SSLSessionImpl(chc,
+                        chc.negotiatedCipherSuite,
+                        serverHello.sessionId);
+                chc.handshakeSession.setMaximumPacketSize(
+                        chc.sslConfig.maximumPacketSize);
+            }
+
+            //
+            // update
+            //
+            serverHello.extensions.consumeOnTrade(chc, extTypes);
+
+            // update the consumers and producers
+            if (chc.isResumption) {
+                SSLTrafficKeyDerivation kdg =
+                        SSLTrafficKeyDerivation.valueOf(chc.negotiatedProtocol);
+                if (kdg == null) {
+                    // unlikely
+                    chc.conContext.fatal(Alert.INTERNAL_ERROR,
+                            "Not supported key derivation: " +
+                            chc.negotiatedProtocol);
+                } else {
+                    chc.handshakeKeyDerivation = kdg.createKeyDerivation(
+                            chc, chc.resumingSession.getMasterSecret());
+                }
+
+                chc.conContext.consumers.putIfAbsent(
+                        ContentType.CHANGE_CIPHER_SPEC.id,
+                        ChangeCipherSpec.t10Consumer);
+                chc.handshakeConsumers.put(
+                        SSLHandshake.FINISHED.id,
+                        SSLHandshake.FINISHED);
+            } else {
+                SSLKeyExchange ke = SSLKeyExchange.valueOf(
+                        chc.negotiatedCipherSuite.keyExchange);
+                chc.handshakeKeyExchange = ke;
+                if (ke != null) {
+                    for (SSLHandshake handshake :
+                            ke.getRelatedHandshakers(chc)) {
+                        chc.handshakeConsumers.put(handshake.id, handshake);
+                    }
+                }
+
+                chc.handshakeConsumers.put(SSLHandshake.SERVER_HELLO_DONE.id,
+                        SSLHandshake.SERVER_HELLO_DONE);
+            }
+
+            //
+            // produce
+            //
+            // Need no new handshake message producers here.
+        }
+    }
+
+    private static void setUpPskKD(HandshakeContext hc,
+            SecretKey psk) throws SSLHandshakeException {
+
+        if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
+            SSLLogger.fine("Using PSK to derive early secret");
+        }
+
+        try {
+            CipherSuite.HashAlg hashAlg = hc.negotiatedCipherSuite.hashAlg;
+            HKDF hkdf = new HKDF(hashAlg.name);
+            byte[] zeros = new byte[hashAlg.hashLength];
+            SecretKey earlySecret = hkdf.extract(zeros, psk, "TlsEarlySecret");
+            hc.handshakeKeyDerivation = new SSLSecretDerivation(hc, earlySecret);
+        } catch  (GeneralSecurityException gse) {
+            throw (SSLHandshakeException) new SSLHandshakeException(
+                "Could not generate secret").initCause(gse);
+        }
+    }
+
+    private static final
+            class T13ServerHelloConsumer implements HandshakeConsumer {
+        // Prevent instantiation of this class.
+        private T13ServerHelloConsumer() {
+            // blank
+        }
+
+        @Override
+        public void consume(ConnectionContext context,
+                HandshakeMessage message) throws IOException {
+            // The consuming happens in client side only.
+            ClientHandshakeContext chc = (ClientHandshakeContext)context;
+            ServerHelloMessage serverHello = (ServerHelloMessage)message;
+            if (serverHello.serverVersion != ProtocolVersion.TLS12) {
+                chc.conContext.fatal(Alert.PROTOCOL_VERSION,
+                    "The ServerHello.legacy_version field is not TLS 1.2");
+            }
+
+            chc.negotiatedCipherSuite = serverHello.cipherSuite;
+            chc.handshakeHash.determine(
+                    chc.negotiatedProtocol, chc.negotiatedCipherSuite);
+            chc.serverHelloRandom = serverHello.serverRandom;
+
+            //
+            // validate
+            //
+
+            // Check and launch ServerHello extensions.
+            SSLExtension[] extTypes = chc.sslConfig.getEnabledExtensions(
+                    SSLHandshake.SERVER_HELLO);
+            serverHello.extensions.consumeOnLoad(chc, extTypes);
+            if (!chc.isResumption) {
+                if (chc.resumingSession != null) {
+                    // in case the resumption happens next time.
+                    chc.resumingSession.invalidate();
+                    chc.resumingSession = null;
+                }
+
+                if (!chc.sslConfig.enableSessionCreation) {
+                    chc.conContext.fatal(Alert.PROTOCOL_VERSION,
+                        "New session creation is disabled");
+                }
+                chc.handshakeSession = new SSLSessionImpl(chc,
+                        chc.negotiatedCipherSuite,
+                        serverHello.sessionId);
+                chc.handshakeSession.setMaximumPacketSize(
+                        chc.sslConfig.maximumPacketSize);
+            } else {
+                // The PSK is consumed to allow it to be deleted
+                Optional<SecretKey> psk = chc.resumingSession.consumePreSharedKey();
+                if(!psk.isPresent()) {
+                    chc.conContext.fatal(Alert.INTERNAL_ERROR,
+                    "No PSK available. Unable to resume.");
+                }
+
+                chc.handshakeSession = chc.resumingSession;
+
+                setUpPskKD(chc, psk.get());
+            }
+
+            //
+            // update
+            //
+            serverHello.extensions.consumeOnTrade(chc, extTypes);
+
+            // Change client/server handshake traffic secrets.
+            // Refresh handshake hash
+            chc.handshakeHash.update();
+
+            SSLKeyExchange ke = chc.handshakeKeyExchange;
+            if (ke == null) {
+                // unlikely
+                chc.conContext.fatal(Alert.INTERNAL_ERROR,
+                        "Not negotiated key shares");
+                return;     // make the compiler happy
+            }
+
+            SSLKeyDerivation handshakeKD = ke.createKeyDerivation(chc);
+            SecretKey handshakeSecret = handshakeKD.deriveKey(
+                    "TlsHandshakeSecret", null);
+            SSLTrafficKeyDerivation kdg =
+                SSLTrafficKeyDerivation.valueOf(chc.negotiatedProtocol);
+            if (kdg == null) {
+                // unlikely
+                chc.conContext.fatal(Alert.INTERNAL_ERROR,
+                        "Not supported key derivation: " +
+                        chc.negotiatedProtocol);
+                return;     // make the compiler happy
+            }
+
+            SSLKeyDerivation secretKD =
+                    new SSLSecretDerivation(chc, handshakeSecret);
+
+            // update the handshake traffic read keys.
+            SecretKey readSecret = secretKD.deriveKey(
+                    "TlsServerHandshakeTrafficSecret", null);
+
+            SSLKeyDerivation readKD =
+                    kdg.createKeyDerivation(chc, readSecret);
+            SecretKey readKey = readKD.deriveKey(
+                    "TlsKey", null);
+            SecretKey readIvSecret = readKD.deriveKey(
+                    "TlsIv", null);
+            IvParameterSpec readIv =
+                    new IvParameterSpec(readIvSecret.getEncoded());
+            SSLReadCipher readCipher;
+            try {
+                readCipher =
+                    chc.negotiatedCipherSuite.bulkCipher.createReadCipher(
+                        Authenticator.valueOf(chc.negotiatedProtocol),
+                        chc.negotiatedProtocol, readKey, readIv,
+                        chc.sslContext.getSecureRandom());
+            } catch (GeneralSecurityException gse) {
+                // unlikely
+                chc.conContext.fatal(Alert.HANDSHAKE_FAILURE,
+                        "Missing cipher algorithm", gse);
+                return;     // make the compiler happy
+            }
+
+            chc.baseReadSecret = readSecret;
+            chc.conContext.inputRecord.changeReadCiphers(readCipher);
+
+            // update the handshake traffic write keys.
+            SecretKey writeSecret = secretKD.deriveKey(
+                    "TlsClientHandshakeTrafficSecret", null);
+            SSLKeyDerivation writeKD =
+                    kdg.createKeyDerivation(chc, writeSecret);
+            SecretKey writeKey = writeKD.deriveKey(
+                    "TlsKey", null);
+            SecretKey writeIvSecret = writeKD.deriveKey(
+                    "TlsIv", null);
+            IvParameterSpec writeIv =
+                    new IvParameterSpec(writeIvSecret.getEncoded());
+            SSLWriteCipher writeCipher;
+            try {
+                writeCipher =
+                    chc.negotiatedCipherSuite.bulkCipher.createWriteCipher(
+                        Authenticator.valueOf(chc.negotiatedProtocol),
+                        chc.negotiatedProtocol, writeKey, writeIv,
+                        chc.sslContext.getSecureRandom());
+            } catch (GeneralSecurityException gse) {
+                // unlikely
+                chc.conContext.fatal(Alert.HANDSHAKE_FAILURE,
+                        "Missing cipher algorithm", gse);
+                return;     //  make the compiler happy
+            }
+
+            chc.baseWriteSecret = writeSecret;
+            chc.conContext.outputRecord.changeWriteCiphers(
+                    writeCipher, (serverHello.sessionId.length() != 0));
+
+            // Should use resumption_master_secret for TLS 1.3.
+            // chc.handshakeSession.setMasterSecret(masterSecret);
+
+            // Update the context for master key derivation.
+            chc.handshakeKeyDerivation = secretKD;
+
+            // update the consumers and producers
+            //
+            // The server sends a dummy change_cipher_spec record immediately
+            // after its first handshake message.  This may either be after a
+            // ServerHello or a HelloRetryRequest.
+            chc.conContext.consumers.putIfAbsent(
+                    ContentType.CHANGE_CIPHER_SPEC.id,
+                    ChangeCipherSpec.t13Consumer);
+
+            chc.handshakeConsumers.put(
+                    SSLHandshake.ENCRYPTED_EXTENSIONS.id,
+                    SSLHandshake.ENCRYPTED_EXTENSIONS);
+
+            // TODO: Optional cert authentication, when not PSK
+            chc.handshakeConsumers.put(
+                    SSLHandshake.CERTIFICATE_REQUEST.id,
+                    SSLHandshake.CERTIFICATE_REQUEST);
+            chc.handshakeConsumers.put(
+                    SSLHandshake.CERTIFICATE.id,
+                    SSLHandshake.CERTIFICATE);
+            chc.handshakeConsumers.put(
+                    SSLHandshake.CERTIFICATE_VERIFY.id,
+                    SSLHandshake.CERTIFICATE_VERIFY);
+
+            chc.handshakeConsumers.put(
+                    SSLHandshake.FINISHED.id,
+                    SSLHandshake.FINISHED);
+
+            //
+            // produce
+            //
+            // Need no new handshake message producers here.
+        }
+    }
+
+    private static final
+            class T13HelloRetryRequestConsumer implements HandshakeConsumer {
+        // Prevent instantiation of this class.
+        private T13HelloRetryRequestConsumer() {
+            // blank
+        }
+
+        @Override
+        public void consume(ConnectionContext context,
+                HandshakeMessage message) throws IOException {
+            // The consuming happens in client side only.
+            ClientHandshakeContext chc = (ClientHandshakeContext)context;
+            ServerHelloMessage helloRetryRequest = (ServerHelloMessage)message;
+            if (helloRetryRequest.serverVersion != ProtocolVersion.TLS12) {
+                chc.conContext.fatal(Alert.PROTOCOL_VERSION,
+                    "The HelloRetryRequest.legacy_version is not TLS 1.2");
+            }
+
+            chc.negotiatedCipherSuite = helloRetryRequest.cipherSuite;
+
+            //
+            // validate
+            //
+
+            // Check and launch ClientHello extensions.
+            SSLExtension[] extTypes = chc.sslConfig.getEnabledExtensions(
+                    SSLHandshake.HELLO_RETRY_REQUEST);
+            helloRetryRequest.extensions.consumeOnLoad(chc, extTypes);
+
+            //
+            // update
+            //
+            helloRetryRequest.extensions.consumeOnTrade(chc, extTypes);
+
+            // Change client/server handshake traffic secrets.
+            // Refresh handshake hash
+            chc.handshakeHash.finish();     // reset the handshake hash
+
+            // calculate the transcript hash of the 1st ClientHello message
+            HandshakeOutStream hos = new HandshakeOutStream(null);
+            try {
+                chc.initialClientHelloMsg.write(hos);
+            } catch (IOException ioe) {
+                // unlikely
+                chc.conContext.fatal(Alert.HANDSHAKE_FAILURE,
+                    "Failed to construct message hash", ioe);
+            }
+            chc.handshakeHash.deliver(hos.toByteArray());
+            chc.handshakeHash.determine(
+                    chc.negotiatedProtocol, chc.negotiatedCipherSuite);
+            byte[] clientHelloHash = chc.handshakeHash.digest();
+
+            // calculate the message_hash
+            //
+            // Transcript-Hash(ClientHello1, HelloRetryRequest, ... Mn) =
+            //   Hash(message_hash ||    /* Handshake type */
+            //     00 00 Hash.length ||  /* Handshake message length (bytes) */
+            //     Hash(ClientHello1) || /* Hash of ClientHello1 */
+            //     HelloRetryRequest || ... || Mn)
+            int hashLen = chc.negotiatedCipherSuite.hashAlg.hashLength;
+            byte[] hashedClientHello = new byte[4 + hashLen];
+            hashedClientHello[0] = SSLHandshake.MESSAGE_HASH.id;
+            hashedClientHello[1] = (byte)0x00;
+            hashedClientHello[2] = (byte)0x00;
+            hashedClientHello[3] = (byte)(hashLen & 0xFF);
+            System.arraycopy(clientHelloHash, 0,
+                    hashedClientHello, 4, hashLen);
+
+            chc.handshakeHash.finish();     // reset the handshake hash
+            chc.handshakeHash.deliver(hashedClientHello);
+
+            int hrrBodyLen = helloRetryRequest.handshakeRecord.remaining();
+            byte[] hrrMessage = new byte[4 + hrrBodyLen];
+            hrrMessage[0] = SSLHandshake.HELLO_RETRY_REQUEST.id;
+            hrrMessage[1] = (byte)((hrrBodyLen >> 16) & 0xFF);
+            hrrMessage[2] = (byte)((hrrBodyLen >> 8) & 0xFF);
+            hrrMessage[3] = (byte)(hrrBodyLen & 0xFF);
+
+            ByteBuffer hrrBody = helloRetryRequest.handshakeRecord.duplicate();
+            hrrBody.get(hrrMessage, 4, hrrBodyLen);
+
+            chc.handshakeHash.receive(hrrMessage);
+
+            // Update the initial ClientHello handshake message.
+            chc.initialClientHelloMsg.extensions.reproduce(chc,
+                    new SSLExtension[] {
+                        SSLExtension.CH_COOKIE,
+                        SSLExtension.CH_KEY_SHARE,
+                        SSLExtension.CH_PRE_SHARED_KEY
+                    });
+
+            //
+            // produce response handshake message
+            //
+            SSLHandshake.CLIENT_HELLO.produce(context, helloRetryRequest);
+        }
+    }
+}