--- /dev/null 2018-05-11 10:42:23.849000000 -0700 +++ new/src/java.base/share/classes/sun/security/ssl/ServerHello.java 2018-05-11 15:10:28.409881600 -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 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 prefered; + List proposed; + if (shc.sslConfig.preferLocalCipherSuites) { + prefered = shc.activeCipherSuites; + proposed = clientHello.cipherSuites; + } else { + prefered = clientHello.cipherSuites; + proposed = shc.activeCipherSuites; + } + + List 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 prefered; + List 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 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); + } + } +}