8196584: TLS 1.3 Implementation
Reviewed-by: ascarpino, coffeys, dfuchs, jjiang, jnimeh, mullan, rhalade, ssahoo, valeriep, weijun, wetmore, xuelei
Contributed-by: Adam Petcher <adam.petcher@oracle.com>, Amanda Jiang <amanda.jiang@oracle.com>, Anthony Scarpino <anthony.scarpino@oracle.com>, Bradford Wetmore <bradford.wetmore@oracle.com>, Jamil Nimeh <jamil.j.nimeh@oracle.com>, John Jiang <sha.jiang@oracle.com>, Rajan Halade <rajan.halade@oracle.com>, Sibabrata Sahoo <sibabrata.sahoo@oracle.com>, Valerie Peng <valerie.peng@oracle.com>, Weijun Wang <weijun.wang@oracle.com>, Xuelei Fan <xuelei.fan@oracle.com>
/*
* 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 ServerHello/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 ServerHello 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 generating 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 protocol 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.
ServerHelloMessage shm = new ServerHelloMessage(shc,
shc.negotiatedProtocol,
shc.handshakeSession.getSessionId(),
shc.negotiatedCipherSuite,
new RandomCookie(shc),
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> preferred;
List<CipherSuite> proposed;
if (shc.sslConfig.preferLocalCipherSuites) {
preferred = shc.activeCipherSuites;
proposed = clientHello.cipherSuites;
} else {
preferred = clientHello.cipherSuites;
proposed = shc.activeCipherSuites;
}
List<CipherSuite> legacySuites = new LinkedList<>();
for (CipherSuite cs : preferred) {
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, shc.negotiatedProtocol);
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, shc.negotiatedProtocol);
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);
// Generate the ServerHello handshake message.
ServerHelloMessage shm = new ServerHelloMessage(shc,
ProtocolVersion.TLS12, // use legacy version
clientHello.sessionId, // echo back
shc.negotiatedCipherSuite,
new RandomCookie(shc),
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> preferred;
List<CipherSuite> proposed;
if (shc.sslConfig.preferLocalCipherSuites) {
preferred = shc.activeCipherSuites;
proposed = clientHello.cipherSuites;
} else {
preferred = clientHello.cipherSuites;
proposed = shc.activeCipherSuites;
}
CipherSuite legacySuite = null;
AlgorithmConstraints legacyConstraints =
ServerHandshakeContext.legacyAlgorithmConstraints;
for (CipherSuite cs : preferred) {
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();
// Stateless, shall we clean up the handshake context as well?
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");
}
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 launch 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 launch 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);
}
if (serverHello.serverRandom.isVersionDowngrade(chc)) {
chc.conContext.fatal(Alert.ILLEGAL_PARAMETER,
"A potential protocol version downgrade attack");
}
// 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 launch 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.negotiatedProtocol);
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);
// Support cert authentication only, 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);
}
}
}