--- /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);
+ }
+ }
+}