mirror of
https://github.com/openjdk/jdk.git
synced 2025-08-28 15:24:43 +02:00
1477 lines
62 KiB
Java
1477 lines
62 KiB
Java
/*
|
|
* 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 javax.net.ssl.SSLProtocolException;
|
|
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.
|
|
throw 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));
|
|
try {
|
|
sessionId.checkLength(serverVersion.id);
|
|
} catch (SSLProtocolException ex) {
|
|
throw handshakeContext.conContext.fatal(
|
|
Alert.ILLEGAL_PARAMETER, ex);
|
|
}
|
|
|
|
int cipherSuiteId = Record.getInt16(m);
|
|
this.cipherSuite = CipherSuite.valueOf(cipherSuiteId);
|
|
if (cipherSuite == null || !context.isNegotiable(cipherSuite)) {
|
|
throw context.conContext.fatal(Alert.ILLEGAL_PARAMETER,
|
|
"Server selected improper ciphersuite " +
|
|
CipherSuite.nameOf(cipherSuiteId));
|
|
}
|
|
|
|
this.compressionMethod = m.get();
|
|
if (compressionMethod != 0) {
|
|
throw 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) {
|
|
throw shc.conContext.fatal(Alert.HANDSHAKE_FAILURE,
|
|
"no cipher suites in common");
|
|
}
|
|
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
|
|
throw 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);
|
|
}
|
|
}
|
|
}
|
|
|
|
throw shc.conContext.fatal(Alert.HANDSHAKE_FAILURE,
|
|
"no cipher suites in common");
|
|
}
|
|
|
|
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) {
|
|
throw shc.conContext.fatal(Alert.HANDSHAKE_FAILURE,
|
|
"no cipher suites in common");
|
|
}
|
|
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
|
|
throw shc.conContext.fatal(Alert.INTERNAL_ERROR,
|
|
"Not negotiated key shares");
|
|
}
|
|
|
|
SSLKeyDerivation handshakeKD = ke.createKeyDerivation(shc);
|
|
SecretKey handshakeSecret = handshakeKD.deriveKey(
|
|
"TlsHandshakeSecret", null);
|
|
|
|
SSLTrafficKeyDerivation kdg =
|
|
SSLTrafficKeyDerivation.valueOf(shc.negotiatedProtocol);
|
|
if (kdg == null) {
|
|
// unlikely
|
|
throw shc.conContext.fatal(Alert.INTERNAL_ERROR,
|
|
"Not supported key derivation: " +
|
|
shc.negotiatedProtocol);
|
|
}
|
|
|
|
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
|
|
throw shc.conContext.fatal(Alert.HANDSHAKE_FAILURE,
|
|
"Missing cipher algorithm", gse);
|
|
}
|
|
|
|
if (readCipher == null) {
|
|
throw shc.conContext.fatal(Alert.ILLEGAL_PARAMETER,
|
|
"Illegal cipher suite (" + shc.negotiatedCipherSuite +
|
|
") and protocol version (" + shc.negotiatedProtocol +
|
|
")");
|
|
}
|
|
|
|
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
|
|
throw shc.conContext.fatal(Alert.HANDSHAKE_FAILURE,
|
|
"Missing cipher algorithm", gse);
|
|
}
|
|
|
|
if (writeCipher == null) {
|
|
throw shc.conContext.fatal(Alert.ILLEGAL_PARAMETER,
|
|
"Illegal cipher suite (" + shc.negotiatedCipherSuite +
|
|
") and protocol version (" + shc.negotiatedProtocol +
|
|
")");
|
|
}
|
|
|
|
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) {
|
|
throw shc.conContext.fatal(Alert.HANDSHAKE_FAILURE,
|
|
"no cipher suites in common for hello retry request");
|
|
}
|
|
|
|
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()) {
|
|
throw 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)) {
|
|
throw chc.conContext.fatal(Alert.PROTOCOL_VERSION,
|
|
"The server selected protocol version " + serverVersion +
|
|
" is not accepted by client preferences " +
|
|
chc.activeProtocols);
|
|
}
|
|
|
|
if (!serverVersion.useTLS13PlusSpec()) {
|
|
throw 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)) {
|
|
throw 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)) {
|
|
throw 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)) {
|
|
throw 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) {
|
|
throw 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) {
|
|
throw 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) {
|
|
throw 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) {
|
|
throw 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) {
|
|
throw 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
|
|
throw 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) {
|
|
throw 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) {
|
|
throw 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()) {
|
|
throw 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
|
|
throw chc.conContext.fatal(Alert.INTERNAL_ERROR,
|
|
"Not negotiated key shares");
|
|
}
|
|
|
|
SSLKeyDerivation handshakeKD = ke.createKeyDerivation(chc);
|
|
SecretKey handshakeSecret = handshakeKD.deriveKey(
|
|
"TlsHandshakeSecret", null);
|
|
SSLTrafficKeyDerivation kdg =
|
|
SSLTrafficKeyDerivation.valueOf(chc.negotiatedProtocol);
|
|
if (kdg == null) {
|
|
// unlikely
|
|
throw chc.conContext.fatal(Alert.INTERNAL_ERROR,
|
|
"Not supported key derivation: " +
|
|
chc.negotiatedProtocol);
|
|
}
|
|
|
|
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
|
|
throw chc.conContext.fatal(Alert.HANDSHAKE_FAILURE,
|
|
"Missing cipher algorithm", gse);
|
|
}
|
|
|
|
if (readCipher == null) {
|
|
throw chc.conContext.fatal(Alert.ILLEGAL_PARAMETER,
|
|
"Illegal cipher suite (" + chc.negotiatedCipherSuite +
|
|
") and protocol version (" + chc.negotiatedProtocol +
|
|
")");
|
|
}
|
|
|
|
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
|
|
throw chc.conContext.fatal(Alert.HANDSHAKE_FAILURE,
|
|
"Missing cipher algorithm", gse);
|
|
}
|
|
|
|
if (writeCipher == null) {
|
|
throw chc.conContext.fatal(Alert.ILLEGAL_PARAMETER,
|
|
"Illegal cipher suite (" + chc.negotiatedCipherSuite +
|
|
") and protocol version (" + chc.negotiatedProtocol +
|
|
")");
|
|
}
|
|
|
|
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) {
|
|
throw 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
|
|
throw 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);
|
|
}
|
|
}
|
|
}
|