mirror of
https://github.com/openjdk/jdk.git
synced 2025-08-27 06:45:07 +02:00

Co-authored-by: Adam Petcher <adam.petcher@oracle.com> Co-authored-by: Amanda Jiang <amanda.jiang@oracle.com> Co-authored-by: Anthony Scarpino <anthony.scarpino@oracle.com> Co-authored-by: Bradford Wetmore <bradford.wetmore@oracle.com> Co-authored-by: Jamil Nimeh <jamil.j.nimeh@oracle.com> Co-authored-by: John Jiang <sha.jiang@oracle.com> Co-authored-by: Rajan Halade <rajan.halade@oracle.com> Co-authored-by: Sibabrata Sahoo <sibabrata.sahoo@oracle.com> Co-authored-by: Valerie Peng <valerie.peng@oracle.com> Co-authored-by: Weijun Wang <weijun.wang@oracle.com> Reviewed-by: ascarpino, coffeys, dfuchs, jjiang, jnimeh, mullan, rhalade, ssahoo, valeriep, weijun, wetmore, xuelei
1397 lines
57 KiB
Java
1397 lines
57 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.SecureRandom;
|
|
import java.security.cert.X509Certificate;
|
|
import java.text.MessageFormat;
|
|
import java.util.Arrays;
|
|
import java.util.Collections;
|
|
import java.util.LinkedList;
|
|
import java.util.List;
|
|
import java.util.Locale;
|
|
import javax.net.ssl.SSLException;
|
|
import javax.net.ssl.SSLHandshakeException;
|
|
import javax.net.ssl.SSLPeerUnverifiedException;
|
|
import javax.net.ssl.SSLProtocolException;
|
|
import static sun.security.ssl.ClientAuthType.CLIENT_AUTH_REQUIRED;
|
|
import sun.security.ssl.SSLHandshake.HandshakeMessage;
|
|
import sun.security.ssl.SupportedVersionsExtension.CHSupportedVersionsSpec;
|
|
|
|
/**
|
|
* Pack of the ClientHello handshake message.
|
|
*/
|
|
final class ClientHello {
|
|
static final SSLProducer kickstartProducer =
|
|
new ClientHelloKickstartProducer();
|
|
static final SSLConsumer handshakeConsumer =
|
|
new ClientHelloConsumer();
|
|
static final HandshakeProducer handshakeProducer =
|
|
new ClientHelloProducer();
|
|
|
|
private static final HandshakeConsumer t12HandshakeConsumer =
|
|
new T12ClientHelloConsumer();
|
|
private static final HandshakeConsumer t13HandshakeConsumer =
|
|
new T13ClientHelloConsumer();
|
|
private static final HandshakeConsumer d12HandshakeConsumer =
|
|
new D12ClientHelloConsumer();
|
|
private static final HandshakeConsumer d13HandshakeConsumer =
|
|
new D13ClientHelloConsumer();
|
|
|
|
/**
|
|
* The ClientHello handshake message.
|
|
*
|
|
* See RFC 5264/4346/2246/6347 for the specifications.
|
|
*/
|
|
static final class ClientHelloMessage extends HandshakeMessage {
|
|
private final boolean isDTLS;
|
|
|
|
final int clientVersion;
|
|
final RandomCookie clientRandom;
|
|
final SessionId sessionId;
|
|
private byte[] cookie; // DTLS only
|
|
final int[] cipherSuiteIds;
|
|
final List<CipherSuite> cipherSuites; // known cipher suites only
|
|
final byte[] compressionMethod;
|
|
final SSLExtensions extensions;
|
|
|
|
private static final byte[] NULL_COMPRESSION = new byte[] {0};
|
|
|
|
ClientHelloMessage(HandshakeContext handshakeContext,
|
|
int clientVersion, SessionId sessionId,
|
|
List<CipherSuite> cipherSuites, SecureRandom generator) {
|
|
super(handshakeContext);
|
|
this.isDTLS = handshakeContext.sslContext.isDTLS();
|
|
|
|
this.clientVersion = clientVersion;
|
|
this.clientRandom = new RandomCookie(generator);
|
|
this.sessionId = sessionId;
|
|
if (isDTLS) {
|
|
this.cookie = new byte[0];
|
|
} else {
|
|
this.cookie = null;
|
|
}
|
|
|
|
this.cipherSuites = cipherSuites;
|
|
this.cipherSuiteIds = getCipherSuiteIds(cipherSuites);
|
|
this.extensions = new SSLExtensions(this);
|
|
|
|
// Don't support compression.
|
|
this.compressionMethod = NULL_COMPRESSION;
|
|
}
|
|
|
|
/* Read up to the binders in the PSK extension. After this method
|
|
* returns, the ByteBuffer position will be at end of the message
|
|
* fragment that should be hashed to produce the PSK binder values.
|
|
* The client of this method can use this position to determine the
|
|
* message fragment and produce the binder values.
|
|
*/
|
|
static void readPartial(TransportContext tc,
|
|
ByteBuffer m) throws IOException {
|
|
boolean isDTLS = tc.sslContext.isDTLS();
|
|
|
|
// version
|
|
Record.getInt16(m);
|
|
|
|
new RandomCookie(m);
|
|
|
|
// session ID
|
|
Record.getBytes8(m);
|
|
|
|
// DTLS cookie
|
|
if (isDTLS) {
|
|
Record.getBytes8(m);
|
|
}
|
|
|
|
// cipher suite IDs
|
|
Record.getBytes16(m);
|
|
// compression method
|
|
Record.getBytes8(m);
|
|
// read extensions, if present
|
|
if (m.remaining() >= 2) {
|
|
int remaining = Record.getInt16(m);
|
|
while (remaining > 0) {
|
|
int id = Record.getInt16(m);
|
|
int extLen = Record.getInt16(m);
|
|
remaining -= extLen + 4;
|
|
|
|
if (id == SSLExtension.CH_PRE_SHARED_KEY.id) {
|
|
// ensure pre_shared_key is the last extension
|
|
if (remaining > 0) {
|
|
tc.fatal(Alert.ILLEGAL_PARAMETER,
|
|
"pre_shared_key extension is not last");
|
|
}
|
|
// read only up to the IDs
|
|
Record.getBytes16(m);
|
|
return;
|
|
} else {
|
|
m.position(m.position() + extLen);
|
|
|
|
}
|
|
}
|
|
} // Otherwise, ignore the remaining bytes.
|
|
}
|
|
|
|
ClientHelloMessage(HandshakeContext handshakeContext, ByteBuffer m,
|
|
SSLExtension[] supportedExtensions) throws IOException {
|
|
super(handshakeContext);
|
|
this.isDTLS = handshakeContext.sslContext.isDTLS();
|
|
|
|
this.clientVersion = ((m.get() & 0xFF) << 8) | (m.get() & 0xFF);
|
|
this.clientRandom = new RandomCookie(m);
|
|
this.sessionId = new SessionId(Record.getBytes8(m));
|
|
try {
|
|
sessionId.checkLength(clientVersion);
|
|
} catch (SSLProtocolException ex) {
|
|
handshakeContext.conContext.fatal(Alert.ILLEGAL_PARAMETER, ex);
|
|
}
|
|
if (isDTLS) {
|
|
this.cookie = Record.getBytes8(m);
|
|
} else {
|
|
this.cookie = null;
|
|
}
|
|
|
|
byte[] encodedIds = Record.getBytes16(m);
|
|
if (encodedIds.length == 0 || (encodedIds.length & 0x01) != 0) {
|
|
handshakeContext.conContext.fatal(Alert.ILLEGAL_PARAMETER,
|
|
"Invalid ClientHello message");
|
|
}
|
|
|
|
this.cipherSuiteIds = new int[encodedIds.length >> 1];
|
|
for (int i = 0, j = 0; i < encodedIds.length; i++, j++) {
|
|
cipherSuiteIds[j] =
|
|
((encodedIds[i++] & 0xFF) << 8) | (encodedIds[i] & 0xFF);
|
|
}
|
|
this.cipherSuites = getCipherSuites(cipherSuiteIds);
|
|
|
|
this.compressionMethod = Record.getBytes8(m);
|
|
// In TLS 1.3, use of certain extensions is mandatory.
|
|
if (m.hasRemaining()) {
|
|
this.extensions =
|
|
new SSLExtensions(this, m, supportedExtensions);
|
|
} else {
|
|
this.extensions = new SSLExtensions(this);
|
|
}
|
|
}
|
|
|
|
void setHelloCookie(byte[] cookie) {
|
|
this.cookie = cookie;
|
|
}
|
|
|
|
// DTLS 1.0/1.2, for cookie generation.
|
|
byte[] getHelloCookieBytes() {
|
|
HandshakeOutStream hos = new HandshakeOutStream(null);
|
|
try {
|
|
// copied from send() method
|
|
hos.putInt8((byte)((clientVersion >>> 8) & 0xFF));
|
|
hos.putInt8((byte)(clientVersion & 0xFF));
|
|
hos.write(clientRandom.randomBytes, 0, 32);
|
|
hos.putBytes8(sessionId.getId());
|
|
// ignore cookie
|
|
hos.putBytes16(getEncodedCipherSuites());
|
|
hos.putBytes8(compressionMethod);
|
|
extensions.send(hos); // In TLS 1.3, use of certain
|
|
// extensions is mandatory.
|
|
} catch (IOException ioe) {
|
|
// unlikely
|
|
}
|
|
|
|
return hos.toByteArray();
|
|
}
|
|
|
|
// (D)TLS 1.3, for cookie generation.
|
|
byte[] getHeaderBytes() {
|
|
HandshakeOutStream hos = new HandshakeOutStream(null);
|
|
try {
|
|
// copied from send() method
|
|
hos.putInt8((byte)((clientVersion >>> 8) & 0xFF));
|
|
hos.putInt8((byte)(clientVersion & 0xFF));
|
|
hos.write(clientRandom.randomBytes, 0, 32);
|
|
hos.putBytes8(sessionId.getId());
|
|
hos.putBytes16(getEncodedCipherSuites());
|
|
hos.putBytes8(compressionMethod);
|
|
} catch (IOException ioe) {
|
|
// unlikely
|
|
}
|
|
|
|
return hos.toByteArray();
|
|
}
|
|
|
|
private static int[] getCipherSuiteIds(
|
|
List<CipherSuite> cipherSuites) {
|
|
if (cipherSuites != null) {
|
|
int[] ids = new int[cipherSuites.size()];
|
|
int i = 0;
|
|
for (CipherSuite cipherSuite : cipherSuites) {
|
|
ids[i++] = cipherSuite.id;
|
|
}
|
|
|
|
return ids;
|
|
}
|
|
|
|
return new int[0];
|
|
}
|
|
|
|
private static List<CipherSuite> getCipherSuites(int[] ids) {
|
|
List<CipherSuite> cipherSuites = new LinkedList<>();
|
|
for (int id : ids) {
|
|
CipherSuite cipherSuite = CipherSuite.valueOf(id);
|
|
if (cipherSuite != null) {
|
|
cipherSuites.add(cipherSuite);
|
|
}
|
|
}
|
|
|
|
return Collections.unmodifiableList(cipherSuites);
|
|
}
|
|
|
|
private List<String> getCipherSuiteNames() {
|
|
List<String> names = new LinkedList<>();
|
|
for (int id : cipherSuiteIds) {
|
|
names.add(CipherSuite.nameOf(id) +
|
|
"(" + Utilities.byte16HexString(id) + ")"); }
|
|
|
|
return names;
|
|
}
|
|
|
|
private byte[] getEncodedCipherSuites() {
|
|
byte[] encoded = new byte[cipherSuiteIds.length << 1];
|
|
int i = 0;
|
|
for (int id : cipherSuiteIds) {
|
|
encoded[i++] = (byte)(id >> 8);
|
|
encoded[i++] = (byte)id;
|
|
}
|
|
return encoded;
|
|
}
|
|
|
|
@Override
|
|
public SSLHandshake handshakeType() {
|
|
return SSLHandshake.CLIENT_HELLO;
|
|
}
|
|
|
|
@Override
|
|
public int messageLength() {
|
|
/*
|
|
* Add fixed size parts of each field...
|
|
* version + random + session + cipher + compress
|
|
*/
|
|
return (2 + 32 + 1 + 2 + 1
|
|
+ sessionId.length() /* ... + variable parts */
|
|
+ (isDTLS ? (1 + cookie.length) : 0)
|
|
+ (cipherSuiteIds.length * 2)
|
|
+ compressionMethod.length)
|
|
+ extensions.length(); // In TLS 1.3, use of certain
|
|
// extensions is mandatory.
|
|
}
|
|
|
|
@Override
|
|
public void send(HandshakeOutStream hos) throws IOException {
|
|
sendCore(hos);
|
|
extensions.send(hos); // In TLS 1.3, use of certain
|
|
// extensions is mandatory.
|
|
}
|
|
|
|
void sendCore(HandshakeOutStream hos) throws IOException {
|
|
hos.putInt8((byte) (clientVersion >>> 8));
|
|
hos.putInt8((byte) clientVersion);
|
|
hos.write(clientRandom.randomBytes, 0, 32);
|
|
hos.putBytes8(sessionId.getId());
|
|
if (isDTLS) {
|
|
hos.putBytes8(cookie);
|
|
}
|
|
hos.putBytes16(getEncodedCipherSuites());
|
|
hos.putBytes8(compressionMethod);
|
|
}
|
|
|
|
@Override
|
|
public String toString() {
|
|
if (isDTLS) {
|
|
MessageFormat messageFormat = new MessageFormat(
|
|
"\"ClientHello\": '{'\n" +
|
|
" \"client version\" : \"{0}\",\n" +
|
|
" \"random\" : \"{1}\",\n" +
|
|
" \"session id\" : \"{2}\",\n" +
|
|
" \"cookie\" : \"{3}\",\n" +
|
|
" \"cipher suites\" : \"{4}\",\n" +
|
|
" \"compression methods\" : \"{5}\",\n" +
|
|
" \"extensions\" : [\n" +
|
|
"{6}\n" +
|
|
" ]\n" +
|
|
"'}'",
|
|
Locale.ENGLISH);
|
|
Object[] messageFields = {
|
|
ProtocolVersion.nameOf(clientVersion),
|
|
Utilities.toHexString(clientRandom.randomBytes),
|
|
sessionId.toString(),
|
|
Utilities.toHexString(cookie),
|
|
getCipherSuiteNames().toString(),
|
|
Utilities.toHexString(compressionMethod),
|
|
Utilities.indent(Utilities.indent(extensions.toString()))
|
|
};
|
|
|
|
return messageFormat.format(messageFields);
|
|
} else {
|
|
MessageFormat messageFormat = new MessageFormat(
|
|
"\"ClientHello\": '{'\n" +
|
|
" \"client version\" : \"{0}\",\n" +
|
|
" \"random\" : \"{1}\",\n" +
|
|
" \"session id\" : \"{2}\",\n" +
|
|
" \"cipher suites\" : \"{3}\",\n" +
|
|
" \"compression methods\" : \"{4}\",\n" +
|
|
" \"extensions\" : [\n" +
|
|
"{5}\n" +
|
|
" ]\n" +
|
|
"'}'",
|
|
Locale.ENGLISH);
|
|
Object[] messageFields = {
|
|
ProtocolVersion.nameOf(clientVersion),
|
|
Utilities.toHexString(clientRandom.randomBytes),
|
|
sessionId.toString(),
|
|
getCipherSuiteNames().toString(),
|
|
Utilities.toHexString(compressionMethod),
|
|
Utilities.indent(Utilities.indent(extensions.toString()))
|
|
};
|
|
|
|
return messageFormat.format(messageFields);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* The "ClientHello" handshake message kick start producer.
|
|
*/
|
|
private static final
|
|
class ClientHelloKickstartProducer implements SSLProducer {
|
|
// Prevent instantiation of this class.
|
|
private ClientHelloKickstartProducer() {
|
|
// blank
|
|
}
|
|
|
|
// Produce kickstart handshake message.
|
|
@Override
|
|
public byte[] produce(ConnectionContext context) throws IOException {
|
|
// The producing happens in client side only.
|
|
ClientHandshakeContext chc = (ClientHandshakeContext)context;
|
|
|
|
// clean up this producer
|
|
chc.handshakeProducers.remove(SSLHandshake.CLIENT_HELLO.id);
|
|
|
|
// the max protocol version this client is supporting.
|
|
ProtocolVersion maxProtocolVersion = chc.maximumActiveProtocol;
|
|
|
|
// session ID of the ClientHello message
|
|
SessionId sessionId = SSLSessionImpl.nullSession.getSessionId();
|
|
|
|
// a list of cipher suites sent by the client
|
|
List<CipherSuite> cipherSuites = chc.activeCipherSuites;
|
|
|
|
//
|
|
// Try to resume an existing session.
|
|
//
|
|
SSLSessionContextImpl ssci = (SSLSessionContextImpl)
|
|
chc.sslContext.engineGetClientSessionContext();
|
|
SSLSessionImpl session = ssci.get(
|
|
chc.conContext.transport.getPeerHost(),
|
|
chc.conContext.transport.getPeerPort());
|
|
if (session != null) {
|
|
// If unsafe server certificate change is not allowed, reserve
|
|
// current server certificates if the previous handshake is a
|
|
// session-resumption abbreviated initial handshake.
|
|
if (!ClientHandshakeContext.allowUnsafeServerCertChange &&
|
|
session.isSessionResumption()) {
|
|
try {
|
|
// If existing, peer certificate chain cannot be null.
|
|
chc.reservedServerCerts =
|
|
(X509Certificate[])session.getPeerCertificates();
|
|
} catch (SSLPeerUnverifiedException puve) {
|
|
// Maybe not certificate-based, ignore the exception.
|
|
}
|
|
}
|
|
|
|
if (!session.isRejoinable()) {
|
|
session = null;
|
|
if (SSLLogger.isOn &&
|
|
SSLLogger.isOn("ssl,handshake,verbose")) {
|
|
SSLLogger.finest(
|
|
"Can't resume, the session is not rejoinable");
|
|
}
|
|
}
|
|
}
|
|
|
|
CipherSuite sessionSuite = null;
|
|
if (session != null) {
|
|
sessionSuite = session.getSuite();
|
|
if (!chc.isNegotiable(sessionSuite)) {
|
|
session = null;
|
|
if (SSLLogger.isOn &&
|
|
SSLLogger.isOn("ssl,handshake,verbose")) {
|
|
SSLLogger.finest(
|
|
"Can't resume, unavailable session cipher suite");
|
|
}
|
|
}
|
|
}
|
|
|
|
ProtocolVersion sessionVersion = null;
|
|
if (session != null) {
|
|
sessionVersion = session.getProtocolVersion();
|
|
if (!chc.isNegotiable(sessionVersion)) {
|
|
session = null;
|
|
if (SSLLogger.isOn &&
|
|
SSLLogger.isOn("ssl,handshake,verbose")) {
|
|
SSLLogger.finest(
|
|
"Can't resume, unavailable protocol version");
|
|
}
|
|
}
|
|
}
|
|
|
|
if (session != null &&
|
|
!sessionVersion.useTLS13PlusSpec() &&
|
|
SSLConfiguration.useExtendedMasterSecret) {
|
|
|
|
boolean isEmsAvailable = chc.sslConfig.isAvailable(
|
|
SSLExtension.CH_EXTENDED_MASTER_SECRET, sessionVersion);
|
|
if (isEmsAvailable && !session.useExtendedMasterSecret &&
|
|
!SSLConfiguration.allowLegacyResumption) {
|
|
// perform full handshake instead
|
|
//
|
|
// The client SHOULD NOT offer an abbreviated handshake
|
|
// to resume a session that does not use an extended
|
|
// master secret. Instead, it SHOULD offer a full
|
|
// handshake.
|
|
session = null;
|
|
}
|
|
|
|
if ((session != null) &&
|
|
!ClientHandshakeContext.allowUnsafeServerCertChange) {
|
|
// It is fine to move on with abbreviate handshake if
|
|
// endpoint identification is enabled.
|
|
String identityAlg = chc.sslConfig.identificationProtocol;
|
|
if ((identityAlg == null || identityAlg.length() == 0)) {
|
|
if (isEmsAvailable) {
|
|
if (!session.useExtendedMasterSecret) {
|
|
// perform full handshake instead
|
|
session = null;
|
|
} // Otherwise, use extended master secret.
|
|
} else {
|
|
// The extended master secret extension does not
|
|
// apply to SSL 3.0. Perform a full handshake
|
|
// instead.
|
|
//
|
|
// Note that the useExtendedMasterSecret is
|
|
// extended to protect SSL 3.0 connections,
|
|
// by discarding abbreviate handshake.
|
|
session = null;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (session != null) {
|
|
if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake,verbose")) {
|
|
SSLLogger.finest("Try resuming session", session);
|
|
}
|
|
|
|
// only set session id if session is 1.2 or earlier
|
|
if (!session.getProtocolVersion().useTLS13PlusSpec()) {
|
|
sessionId = session.getSessionId();
|
|
}
|
|
if (!maxProtocolVersion.equals(sessionVersion)) {
|
|
maxProtocolVersion = sessionVersion;
|
|
|
|
// Update protocol version number in underlying socket and
|
|
// handshake output stream, so that the output records
|
|
// (at the record layer) have the correct version
|
|
chc.setVersion(sessionVersion);
|
|
}
|
|
|
|
// If no new session is allowed, force use of the previous
|
|
// session ciphersuite, and add the renegotiation SCSV if
|
|
// necessary.
|
|
if (!chc.sslConfig.enableSessionCreation) {
|
|
if (!chc.conContext.isNegotiated &&
|
|
!sessionVersion.useTLS13PlusSpec() &&
|
|
cipherSuites.contains(
|
|
CipherSuite.TLS_EMPTY_RENEGOTIATION_INFO_SCSV)) {
|
|
cipherSuites = Arrays.asList(sessionSuite,
|
|
CipherSuite.TLS_EMPTY_RENEGOTIATION_INFO_SCSV);
|
|
} else { // otherwise, use renegotiation_info extension
|
|
cipherSuites = Arrays.asList(sessionSuite);
|
|
}
|
|
|
|
if (SSLLogger.isOn &&
|
|
SSLLogger.isOn("ssl,handshake,verbose")) {
|
|
SSLLogger.finest(
|
|
"No new session is allowed, so try to resume " +
|
|
"the session cipher suite only", sessionSuite);
|
|
}
|
|
}
|
|
|
|
chc.isResumption = true;
|
|
chc.resumingSession = session;
|
|
}
|
|
|
|
if (session == null) {
|
|
if (!chc.sslConfig.enableSessionCreation) {
|
|
throw new SSLHandshakeException(
|
|
"No new session is allowed and " +
|
|
"no existing session can be resumed");
|
|
}
|
|
|
|
if (maxProtocolVersion.useTLS13PlusSpec() &&
|
|
SSLConfiguration.useCompatibilityMode) {
|
|
// In compatibility mode, the TLS 1.3 legacy_session_id
|
|
// field MUST be non-empty, so a client not offering a
|
|
// pre-TLS 1.3 session MUST generate a new 32-byte value.
|
|
sessionId =
|
|
new SessionId(true, chc.sslContext.getSecureRandom());
|
|
}
|
|
}
|
|
|
|
ProtocolVersion minimumVersion = ProtocolVersion.NONE;
|
|
for (ProtocolVersion pv : chc.activeProtocols) {
|
|
if (minimumVersion == ProtocolVersion.NONE ||
|
|
pv.compare(minimumVersion) < 0) {
|
|
minimumVersion = pv;
|
|
}
|
|
}
|
|
|
|
// exclude SCSV for secure renegotiation
|
|
if (!minimumVersion.useTLS13PlusSpec()) {
|
|
if (chc.conContext.secureRenegotiation &&
|
|
cipherSuites.contains(
|
|
CipherSuite.TLS_EMPTY_RENEGOTIATION_INFO_SCSV)) {
|
|
// The cipherSuites may be unmodifiable
|
|
cipherSuites = new LinkedList<>(cipherSuites);
|
|
cipherSuites.remove(
|
|
CipherSuite.TLS_EMPTY_RENEGOTIATION_INFO_SCSV);
|
|
}
|
|
}
|
|
|
|
// make sure there is a negotiable cipher suite.
|
|
boolean negotiable = false;
|
|
for (CipherSuite suite : cipherSuites) {
|
|
if (chc.isNegotiable(suite)) {
|
|
negotiable = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!negotiable) {
|
|
throw new SSLHandshakeException("No negotiable cipher suite");
|
|
}
|
|
|
|
// Create the handshake message.
|
|
ProtocolVersion clientHelloVersion = maxProtocolVersion;
|
|
if (clientHelloVersion.useTLS13PlusSpec()) {
|
|
// In (D)TLS 1.3, the client indicates its version preferences
|
|
// in the "supported_versions" extension and the client_version
|
|
// (legacy_version) field MUST be set to (D)TLS 1.2.
|
|
if (clientHelloVersion.isDTLS) {
|
|
clientHelloVersion = ProtocolVersion.DTLS12;
|
|
} else {
|
|
clientHelloVersion = ProtocolVersion.TLS12;
|
|
}
|
|
}
|
|
|
|
ClientHelloMessage chm = new ClientHelloMessage(chc,
|
|
clientHelloVersion.id, sessionId, cipherSuites,
|
|
chc.sslContext.getSecureRandom());
|
|
|
|
// cache the client random number for further using
|
|
chc.clientHelloRandom = chm.clientRandom;
|
|
chc.clientHelloVersion = clientHelloVersion.id;
|
|
|
|
// Produce extensions for ClientHello handshake message.
|
|
SSLExtension[] extTypes = chc.sslConfig.getEnabledExtensions(
|
|
SSLHandshake.CLIENT_HELLO, chc.activeProtocols);
|
|
chm.extensions.produce(chc, extTypes);
|
|
|
|
if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
|
|
SSLLogger.fine("Produced ClientHello handshake message", chm);
|
|
}
|
|
|
|
// Output the handshake message.
|
|
chm.write(chc.handshakeOutput);
|
|
chc.handshakeOutput.flush();
|
|
|
|
// Reserve the initial ClientHello message for the follow on
|
|
// cookie exchange if needed.
|
|
chc.initialClientHelloMsg = chm;
|
|
|
|
// What's the expected response?
|
|
chc.handshakeConsumers.put(
|
|
SSLHandshake.SERVER_HELLO.id, SSLHandshake.SERVER_HELLO);
|
|
if (chc.sslContext.isDTLS() &&
|
|
!minimumVersion.useTLS13PlusSpec()) {
|
|
chc.handshakeConsumers.put(
|
|
SSLHandshake.HELLO_VERIFY_REQUEST.id,
|
|
SSLHandshake.HELLO_VERIFY_REQUEST);
|
|
}
|
|
|
|
// The handshake message has been delivered.
|
|
return null;
|
|
}
|
|
}
|
|
|
|
private static final
|
|
class ClientHelloProducer implements HandshakeProducer {
|
|
// Prevent instantiation of this class.
|
|
private ClientHelloProducer() {
|
|
// blank
|
|
}
|
|
|
|
// Response to one of the following handshake message:
|
|
// HelloRequest (SSL 3.0/TLS 1.0/1.1/1.2)
|
|
// ServerHello(HelloRetryRequest) (TLS 1.3)
|
|
// HelloVerifyRequest (DTLS 1.0/1.2)
|
|
@Override
|
|
public byte[] produce(ConnectionContext context,
|
|
HandshakeMessage message) throws IOException {
|
|
// The producing happens in client side only.
|
|
ClientHandshakeContext chc = (ClientHandshakeContext)context;
|
|
|
|
SSLHandshake ht = message.handshakeType();
|
|
if (ht == null) {
|
|
throw new UnsupportedOperationException("Not supported yet.");
|
|
}
|
|
|
|
switch (ht) {
|
|
case HELLO_REQUEST:
|
|
// SSL 3.0/TLS 1.0/1.1/1.2
|
|
try {
|
|
chc.kickstart();
|
|
} catch (IOException ioe) {
|
|
chc.conContext.fatal(Alert.HANDSHAKE_FAILURE, ioe);
|
|
}
|
|
|
|
// The handshake message has been delivered.
|
|
return null;
|
|
case HELLO_VERIFY_REQUEST:
|
|
// DTLS 1.0/1.2
|
|
//
|
|
// The HelloVerifyRequest consumer should have updated the
|
|
// ClientHello handshake message with cookie.
|
|
if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
|
|
SSLLogger.fine(
|
|
"Produced ClientHello(cookie) handshake message",
|
|
chc.initialClientHelloMsg);
|
|
}
|
|
|
|
// Output the handshake message.
|
|
chc.initialClientHelloMsg.write(chc.handshakeOutput);
|
|
chc.handshakeOutput.flush();
|
|
|
|
// What's the expected response?
|
|
chc.handshakeConsumers.put(SSLHandshake.SERVER_HELLO.id,
|
|
SSLHandshake.SERVER_HELLO);
|
|
|
|
ProtocolVersion minimumVersion = ProtocolVersion.NONE;
|
|
for (ProtocolVersion pv : chc.activeProtocols) {
|
|
if (minimumVersion == ProtocolVersion.NONE ||
|
|
pv.compare(minimumVersion) < 0) {
|
|
minimumVersion = pv;
|
|
}
|
|
}
|
|
if (chc.sslContext.isDTLS() &&
|
|
!minimumVersion.useTLS13PlusSpec()) {
|
|
chc.handshakeConsumers.put(
|
|
SSLHandshake.HELLO_VERIFY_REQUEST.id,
|
|
SSLHandshake.HELLO_VERIFY_REQUEST);
|
|
}
|
|
|
|
// The handshake message has been delivered.
|
|
return null;
|
|
case HELLO_RETRY_REQUEST:
|
|
// TLS 1.3
|
|
// The HelloRetryRequest consumer should have updated the
|
|
// ClientHello handshake message with cookie.
|
|
if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
|
|
SSLLogger.fine(
|
|
"Produced ClientHello(HRR) handshake message",
|
|
chc.initialClientHelloMsg);
|
|
}
|
|
|
|
// Output the handshake message.
|
|
chc.initialClientHelloMsg.write(chc.handshakeOutput);
|
|
chc.handshakeOutput.flush();
|
|
|
|
// What's the expected response?
|
|
chc.conContext.consumers.putIfAbsent(
|
|
ContentType.CHANGE_CIPHER_SPEC.id,
|
|
ChangeCipherSpec.t13Consumer);
|
|
chc.handshakeConsumers.put(SSLHandshake.SERVER_HELLO.id,
|
|
SSLHandshake.SERVER_HELLO);
|
|
|
|
// The handshake message has been delivered.
|
|
return null;
|
|
default:
|
|
throw new UnsupportedOperationException(
|
|
"Not supported yet.");
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* The "ClientHello" handshake message consumer.
|
|
*/
|
|
private static final class ClientHelloConsumer implements SSLConsumer {
|
|
// Prevent instantiation of this class.
|
|
private ClientHelloConsumer() {
|
|
// blank
|
|
}
|
|
|
|
@Override
|
|
public void consume(ConnectionContext context,
|
|
ByteBuffer message) throws IOException {
|
|
// The consuming happens in server side only.
|
|
ServerHandshakeContext shc = (ServerHandshakeContext)context;
|
|
|
|
// clean up this consumer
|
|
shc.handshakeConsumers.remove(SSLHandshake.CLIENT_HELLO.id);
|
|
if (!shc.handshakeConsumers.isEmpty()) {
|
|
shc.conContext.fatal(Alert.UNEXPECTED_MESSAGE,
|
|
"No more handshake message allowed " +
|
|
"in a ClientHello flight");
|
|
}
|
|
|
|
// Get enabled extension types in ClientHello handshake message.
|
|
SSLExtension[] enabledExtensions =
|
|
shc.sslConfig.getEnabledExtensions(
|
|
SSLHandshake.CLIENT_HELLO);
|
|
|
|
ClientHelloMessage chm =
|
|
new ClientHelloMessage(shc, message, enabledExtensions);
|
|
if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
|
|
SSLLogger.fine("Consuming ClientHello handshake message", chm);
|
|
}
|
|
|
|
shc.clientHelloVersion = chm.clientVersion;
|
|
onClientHello(shc, chm);
|
|
}
|
|
|
|
private void onClientHello(ServerHandshakeContext context,
|
|
ClientHelloMessage clientHello) throws IOException {
|
|
// Negotiate protocol version.
|
|
//
|
|
// Check and launch SupportedVersions.
|
|
SSLExtension[] extTypes = new SSLExtension[] {
|
|
SSLExtension.CH_SUPPORTED_VERSIONS
|
|
};
|
|
clientHello.extensions.consumeOnLoad(context, extTypes);
|
|
|
|
ProtocolVersion negotiatedProtocol;
|
|
CHSupportedVersionsSpec svs =
|
|
(CHSupportedVersionsSpec)context.handshakeExtensions.get(
|
|
SSLExtension.CH_SUPPORTED_VERSIONS);
|
|
if (svs != null) {
|
|
negotiatedProtocol =
|
|
negotiateProtocol(context, svs.requestedProtocols);
|
|
} else {
|
|
negotiatedProtocol =
|
|
negotiateProtocol(context, clientHello.clientVersion);
|
|
}
|
|
context.negotiatedProtocol = negotiatedProtocol;
|
|
if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
|
|
SSLLogger.fine(
|
|
"Negotiated protocol version: " + negotiatedProtocol.name);
|
|
}
|
|
|
|
// Consume the handshake message for the specific protocol version.
|
|
if (negotiatedProtocol.isDTLS) {
|
|
if (negotiatedProtocol.useTLS13PlusSpec()) {
|
|
d13HandshakeConsumer.consume(context, clientHello);
|
|
} else {
|
|
d12HandshakeConsumer.consume(context, clientHello);
|
|
}
|
|
} else {
|
|
if (negotiatedProtocol.useTLS13PlusSpec()) {
|
|
t13HandshakeConsumer.consume(context, clientHello);
|
|
} else {
|
|
t12HandshakeConsumer.consume(context, clientHello);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Select a protocol version according to the
|
|
// ClientHello.client_version.
|
|
private ProtocolVersion negotiateProtocol(
|
|
ServerHandshakeContext context,
|
|
int clientHelloVersion) throws SSLException {
|
|
|
|
// Per TLS 1.3 specification, server MUST negotiate TLS 1.2 or prior
|
|
// even if ClientHello.client_version is 0x0304 or later.
|
|
int chv = clientHelloVersion;
|
|
if (context.sslContext.isDTLS()) {
|
|
if (chv < ProtocolVersion.DTLS12.id) {
|
|
chv = ProtocolVersion.DTLS12.id;
|
|
}
|
|
} else {
|
|
if (chv > ProtocolVersion.TLS12.id) {
|
|
chv = ProtocolVersion.TLS12.id;
|
|
}
|
|
}
|
|
|
|
// Select a protocol version from the activated protocols.
|
|
ProtocolVersion pv = ProtocolVersion.selectedFrom(
|
|
context.activeProtocols, chv);
|
|
if (pv == null || pv == ProtocolVersion.NONE ||
|
|
pv == ProtocolVersion.SSL20Hello) {
|
|
context.conContext.fatal(Alert.PROTOCOL_VERSION,
|
|
"Client requested protocol " +
|
|
ProtocolVersion.nameOf(clientHelloVersion) +
|
|
" is not enabled or supported in server context");
|
|
}
|
|
|
|
return pv;
|
|
}
|
|
|
|
// Select a protocol version according to the
|
|
// supported_versions extension.
|
|
private ProtocolVersion negotiateProtocol(
|
|
ServerHandshakeContext context,
|
|
int[] clientSupportedVersions) throws SSLException {
|
|
|
|
// The client supported protocol versions are present in client
|
|
// preference order. This implementation chooses to use the server
|
|
// preference of protocol versions instead.
|
|
for (ProtocolVersion spv : context.activeProtocols) {
|
|
if (spv == ProtocolVersion.SSL20Hello) {
|
|
continue;
|
|
}
|
|
for (int cpv : clientSupportedVersions) {
|
|
if (cpv == ProtocolVersion.SSL20Hello.id) {
|
|
continue;
|
|
}
|
|
if (spv.id == cpv) {
|
|
return spv;
|
|
}
|
|
}
|
|
}
|
|
|
|
// No protocol version can be negotiated.
|
|
context.conContext.fatal(Alert.PROTOCOL_VERSION,
|
|
"The client supported protocol versions " + Arrays.toString(
|
|
ProtocolVersion.toStringArray(clientSupportedVersions)) +
|
|
" are not accepted by server preferences " +
|
|
context.activeProtocols);
|
|
|
|
return null; // make the compiler happy
|
|
}
|
|
}
|
|
|
|
/**
|
|
* The "ClientHello" handshake message consumer for TLS 1.2 and
|
|
* prior SSL/TLS protocol versions.
|
|
*/
|
|
private static final
|
|
class T12ClientHelloConsumer implements HandshakeConsumer {
|
|
// Prevent instantiation of this class.
|
|
private T12ClientHelloConsumer() {
|
|
// blank
|
|
}
|
|
|
|
@Override
|
|
public void consume(ConnectionContext context,
|
|
HandshakeMessage message) throws IOException {
|
|
// The consuming happens in server side only.
|
|
ServerHandshakeContext shc = (ServerHandshakeContext)context;
|
|
ClientHelloMessage clientHello = (ClientHelloMessage)message;
|
|
|
|
//
|
|
// validate
|
|
//
|
|
|
|
// Reject client initiated renegotiation?
|
|
//
|
|
// If server side should reject client-initiated renegotiation,
|
|
// send an Alert.HANDSHAKE_FAILURE fatal alert, not a
|
|
// no_renegotiation warning alert (no_renegotiation must be a
|
|
// warning: RFC 2246). no_renegotiation might seem more
|
|
// natural at first, but warnings are not appropriate because
|
|
// the sending party does not know how the receiving party
|
|
// will behave. This state must be treated as a fatal server
|
|
// condition.
|
|
//
|
|
// This will not have any impact on server initiated renegotiation.
|
|
if (shc.conContext.isNegotiated) {
|
|
if (!shc.conContext.secureRenegotiation &&
|
|
!HandshakeContext.allowUnsafeRenegotiation) {
|
|
shc.conContext.fatal(Alert.HANDSHAKE_FAILURE,
|
|
"Unsafe renegotiation is not allowed");
|
|
}
|
|
|
|
if (ServerHandshakeContext.rejectClientInitiatedRenego &&
|
|
!shc.kickstartMessageDelivered) {
|
|
shc.conContext.fatal(Alert.HANDSHAKE_FAILURE,
|
|
"Client initiated renegotiation is not allowed");
|
|
}
|
|
}
|
|
|
|
// Is it an abbreviated handshake?
|
|
if (clientHello.sessionId.length() != 0) {
|
|
SSLSessionImpl previous = ((SSLSessionContextImpl)shc.sslContext
|
|
.engineGetServerSessionContext())
|
|
.get(clientHello.sessionId.getId());
|
|
|
|
boolean resumingSession =
|
|
(previous != null) && previous.isRejoinable();
|
|
if (!resumingSession) {
|
|
if (SSLLogger.isOn &&
|
|
SSLLogger.isOn("ssl,handshake,verbose")) {
|
|
SSLLogger.finest(
|
|
"Can't resume, " +
|
|
"the existing session is not rejoinable");
|
|
}
|
|
}
|
|
// Validate the negotiated protocol version.
|
|
if (resumingSession) {
|
|
ProtocolVersion sessionProtocol =
|
|
previous.getProtocolVersion();
|
|
if (sessionProtocol != shc.negotiatedProtocol) {
|
|
resumingSession = false;
|
|
if (SSLLogger.isOn &&
|
|
SSLLogger.isOn("ssl,handshake,verbose")) {
|
|
SSLLogger.finest(
|
|
"Can't resume, not the same protocol version");
|
|
}
|
|
}
|
|
}
|
|
|
|
// Validate the required client authentication.
|
|
if (resumingSession &&
|
|
(shc.sslConfig.clientAuthType == CLIENT_AUTH_REQUIRED)) {
|
|
try {
|
|
previous.getPeerPrincipal();
|
|
} catch (SSLPeerUnverifiedException e) {
|
|
resumingSession = false;
|
|
if (SSLLogger.isOn &&
|
|
SSLLogger.isOn("ssl,handshake,verbose")) {
|
|
SSLLogger.finest(
|
|
"Can't resume, " +
|
|
"client authentication is required");
|
|
}
|
|
}
|
|
}
|
|
|
|
// Validate that the cached cipher suite.
|
|
if (resumingSession) {
|
|
CipherSuite suite = previous.getSuite();
|
|
if ((!shc.isNegotiable(suite)) ||
|
|
(!clientHello.cipherSuites.contains(suite))) {
|
|
resumingSession = false;
|
|
if (SSLLogger.isOn &&
|
|
SSLLogger.isOn("ssl,handshake,verbose")) {
|
|
SSLLogger.finest(
|
|
"Can't resume, " +
|
|
"the session cipher suite is absent");
|
|
}
|
|
}
|
|
}
|
|
|
|
// So far so good. Note that the handshake extensions may reset
|
|
// the resuming options later.
|
|
shc.isResumption = resumingSession;
|
|
shc.resumingSession = resumingSession ? previous : null;
|
|
}
|
|
|
|
// cache the client random number for further using
|
|
shc.clientHelloRandom = clientHello.clientRandom;
|
|
|
|
// Check and launch ClientHello extensions.
|
|
SSLExtension[] extTypes = shc.sslConfig.getEnabledExtensions(
|
|
SSLHandshake.CLIENT_HELLO);
|
|
clientHello.extensions.consumeOnLoad(shc, extTypes);
|
|
|
|
//
|
|
// update
|
|
//
|
|
if (!shc.conContext.isNegotiated) {
|
|
shc.conContext.protocolVersion = shc.negotiatedProtocol;
|
|
shc.conContext.outputRecord.setVersion(shc.negotiatedProtocol);
|
|
}
|
|
|
|
// update the responders
|
|
//
|
|
// Only need to ServerHello, which may add more responders later.
|
|
// Note that ServerHello and HelloRetryRequest share the same
|
|
// handshake type/id. The ServerHello producer may be replaced
|
|
// by HelloRetryRequest producer if needed.
|
|
shc.handshakeProducers.put(SSLHandshake.SERVER_HELLO.id,
|
|
SSLHandshake.SERVER_HELLO);
|
|
|
|
//
|
|
// produce
|
|
//
|
|
SSLHandshake[] probableHandshakeMessages = new SSLHandshake[] {
|
|
SSLHandshake.SERVER_HELLO,
|
|
|
|
// full handshake messages
|
|
SSLHandshake.CERTIFICATE,
|
|
SSLHandshake.CERTIFICATE_STATUS,
|
|
SSLHandshake.SERVER_KEY_EXCHANGE,
|
|
SSLHandshake.CERTIFICATE_REQUEST,
|
|
SSLHandshake.SERVER_HELLO_DONE,
|
|
|
|
// abbreviated handshake messages
|
|
SSLHandshake.FINISHED
|
|
};
|
|
|
|
for (SSLHandshake hs : probableHandshakeMessages) {
|
|
HandshakeProducer handshakeProducer =
|
|
shc.handshakeProducers.remove(hs.id);
|
|
if (handshakeProducer != null) {
|
|
handshakeProducer.produce(context, clientHello);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* The "ClientHello" handshake message consumer for TLS 1.3.
|
|
*/
|
|
private static final
|
|
class T13ClientHelloConsumer implements HandshakeConsumer {
|
|
// Prevent instantiation of this class.
|
|
private T13ClientHelloConsumer() {
|
|
// blank
|
|
}
|
|
|
|
@Override
|
|
public void consume(ConnectionContext context,
|
|
HandshakeMessage message) throws IOException {
|
|
// The consuming happens in server side only.
|
|
ServerHandshakeContext shc = (ServerHandshakeContext)context;
|
|
ClientHelloMessage clientHello = (ClientHelloMessage)message;
|
|
|
|
// The client may send a dummy change_cipher_spec record
|
|
// immediately after the first ClientHello.
|
|
shc.conContext.consumers.putIfAbsent(
|
|
ContentType.CHANGE_CIPHER_SPEC.id,
|
|
ChangeCipherSpec.t13Consumer);
|
|
|
|
// Is it a resumption?
|
|
//
|
|
// Check and launch the "psk_key_exchange_modes" and
|
|
// "pre_shared_key" extensions first, which will reset the
|
|
// resuming session, no matter the extensions present or not.
|
|
shc.isResumption = true;
|
|
SSLExtension[] extTypes = new SSLExtension[] {
|
|
SSLExtension.PSK_KEY_EXCHANGE_MODES,
|
|
SSLExtension.CH_PRE_SHARED_KEY
|
|
};
|
|
clientHello.extensions.consumeOnLoad(shc, extTypes);
|
|
|
|
// Check and launch ClientHello extensions other than
|
|
// "psk_key_exchange_modes", "pre_shared_key", "protocol_version"
|
|
// and "key_share" extensions.
|
|
//
|
|
// These extensions may discard session resumption, or ask for
|
|
// hello retry.
|
|
extTypes = shc.sslConfig.getExclusiveExtensions(
|
|
SSLHandshake.CLIENT_HELLO,
|
|
Arrays.asList(
|
|
SSLExtension.PSK_KEY_EXCHANGE_MODES,
|
|
SSLExtension.CH_PRE_SHARED_KEY,
|
|
SSLExtension.CH_SUPPORTED_VERSIONS));
|
|
clientHello.extensions.consumeOnLoad(shc, extTypes);
|
|
|
|
if (!shc.handshakeProducers.isEmpty()) {
|
|
// Should be HelloRetryRequest producer.
|
|
goHelloRetryRequest(shc, clientHello);
|
|
} else {
|
|
goServerHello(shc, clientHello);
|
|
}
|
|
}
|
|
|
|
private void goHelloRetryRequest(ServerHandshakeContext shc,
|
|
ClientHelloMessage clientHello) throws IOException {
|
|
HandshakeProducer handshakeProducer =
|
|
shc.handshakeProducers.remove(
|
|
SSLHandshake.HELLO_RETRY_REQUEST.id);
|
|
if (handshakeProducer != null) {
|
|
handshakeProducer.produce(shc, clientHello);
|
|
} else {
|
|
// unlikely
|
|
shc.conContext.fatal(Alert.HANDSHAKE_FAILURE,
|
|
"No HelloRetryRequest producer: " + shc.handshakeProducers);
|
|
}
|
|
|
|
if (!shc.handshakeProducers.isEmpty()) {
|
|
// unlikely, but please double check.
|
|
shc.conContext.fatal(Alert.HANDSHAKE_FAILURE,
|
|
"unknown handshake producers: " + shc.handshakeProducers);
|
|
}
|
|
}
|
|
|
|
private void goServerHello(ServerHandshakeContext shc,
|
|
ClientHelloMessage clientHello) throws IOException {
|
|
//
|
|
// validate
|
|
//
|
|
shc.clientHelloRandom = clientHello.clientRandom;
|
|
|
|
//
|
|
// update
|
|
//
|
|
if (!shc.conContext.isNegotiated) {
|
|
shc.conContext.protocolVersion = shc.negotiatedProtocol;
|
|
shc.conContext.outputRecord.setVersion(shc.negotiatedProtocol);
|
|
}
|
|
|
|
// update the responders
|
|
//
|
|
// Only ServerHello/HelloRetryRequest producer, which adds
|
|
// more responders later.
|
|
shc.handshakeProducers.put(SSLHandshake.SERVER_HELLO.id,
|
|
SSLHandshake.SERVER_HELLO);
|
|
|
|
SSLHandshake[] probableHandshakeMessages = new SSLHandshake[] {
|
|
SSLHandshake.SERVER_HELLO,
|
|
|
|
// full handshake messages
|
|
SSLHandshake.ENCRYPTED_EXTENSIONS,
|
|
SSLHandshake.CERTIFICATE_REQUEST,
|
|
SSLHandshake.CERTIFICATE,
|
|
SSLHandshake.CERTIFICATE_VERIFY,
|
|
SSLHandshake.FINISHED
|
|
};
|
|
|
|
//
|
|
// produce
|
|
//
|
|
for (SSLHandshake hs : probableHandshakeMessages) {
|
|
HandshakeProducer handshakeProducer =
|
|
shc.handshakeProducers.remove(hs.id);
|
|
if (handshakeProducer != null) {
|
|
handshakeProducer.produce(shc, clientHello);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* The "ClientHello" handshake message consumer for DTLS 1.2 and
|
|
* previous DTLS protocol versions.
|
|
*/
|
|
private static final
|
|
class D12ClientHelloConsumer implements HandshakeConsumer {
|
|
// Prevent instantiation of this class.
|
|
private D12ClientHelloConsumer() {
|
|
// blank
|
|
}
|
|
|
|
@Override
|
|
public void consume(ConnectionContext context,
|
|
HandshakeMessage message) throws IOException {
|
|
// The consuming happens in server side only.
|
|
ServerHandshakeContext shc = (ServerHandshakeContext)context;
|
|
ClientHelloMessage clientHello = (ClientHelloMessage)message;
|
|
|
|
//
|
|
// validate
|
|
//
|
|
|
|
// Reject client initiated renegotiation?
|
|
//
|
|
// If server side should reject client-initiated renegotiation,
|
|
// send an Alert.HANDSHAKE_FAILURE fatal alert, not a
|
|
// no_renegotiation warning alert (no_renegotiation must be a
|
|
// warning: RFC 2246). no_renegotiation might seem more
|
|
// natural at first, but warnings are not appropriate because
|
|
// the sending party does not know how the receiving party
|
|
// will behave. This state must be treated as a fatal server
|
|
// condition.
|
|
//
|
|
// This will not have any impact on server initiated renegotiation.
|
|
if (shc.conContext.isNegotiated) {
|
|
if (!shc.conContext.secureRenegotiation &&
|
|
!HandshakeContext.allowUnsafeRenegotiation) {
|
|
shc.conContext.fatal(Alert.HANDSHAKE_FAILURE,
|
|
"Unsafe renegotiation is not allowed");
|
|
}
|
|
|
|
if (ServerHandshakeContext.rejectClientInitiatedRenego &&
|
|
!shc.kickstartMessageDelivered) {
|
|
shc.conContext.fatal(Alert.HANDSHAKE_FAILURE,
|
|
"Client initiated renegotiation is not allowed");
|
|
}
|
|
}
|
|
|
|
// Is it an abbreviated handshake?
|
|
if (clientHello.sessionId.length() != 0) {
|
|
SSLSessionImpl previous = ((SSLSessionContextImpl)shc.sslContext
|
|
.engineGetServerSessionContext())
|
|
.get(clientHello.sessionId.getId());
|
|
|
|
boolean resumingSession =
|
|
(previous != null) && previous.isRejoinable();
|
|
if (!resumingSession) {
|
|
if (SSLLogger.isOn &&
|
|
SSLLogger.isOn("ssl,handshake,verbose")) {
|
|
SSLLogger.finest(
|
|
"Can't resume, " +
|
|
"the existing session is not rejoinable");
|
|
}
|
|
}
|
|
// Validate the negotiated protocol version.
|
|
if (resumingSession) {
|
|
ProtocolVersion sessionProtocol =
|
|
previous.getProtocolVersion();
|
|
if (sessionProtocol != shc.negotiatedProtocol) {
|
|
resumingSession = false;
|
|
if (SSLLogger.isOn &&
|
|
SSLLogger.isOn("ssl,handshake,verbose")) {
|
|
SSLLogger.finest(
|
|
"Can't resume, not the same protocol version");
|
|
}
|
|
}
|
|
}
|
|
|
|
// Validate the required client authentication.
|
|
if (resumingSession &&
|
|
(shc.sslConfig.clientAuthType == CLIENT_AUTH_REQUIRED)) {
|
|
|
|
try {
|
|
previous.getPeerPrincipal();
|
|
} catch (SSLPeerUnverifiedException e) {
|
|
resumingSession = false;
|
|
if (SSLLogger.isOn &&
|
|
SSLLogger.isOn("ssl,handshake,verbose")) {
|
|
SSLLogger.finest(
|
|
"Can't resume, " +
|
|
"client authentication is required");
|
|
}
|
|
}
|
|
}
|
|
|
|
// Validate that the cached cipher suite.
|
|
if (resumingSession) {
|
|
CipherSuite suite = previous.getSuite();
|
|
if ((!shc.isNegotiable(suite)) ||
|
|
(!clientHello.cipherSuites.contains(suite))) {
|
|
resumingSession = false;
|
|
if (SSLLogger.isOn &&
|
|
SSLLogger.isOn("ssl,handshake,verbose")) {
|
|
SSLLogger.finest(
|
|
"Can't resume, " +
|
|
"the session cipher suite is absent");
|
|
}
|
|
}
|
|
}
|
|
|
|
// So far so good. Note that the handshake extensions may reset
|
|
// the resuming options later.
|
|
shc.isResumption = resumingSession;
|
|
shc.resumingSession = resumingSession ? previous : null;
|
|
}
|
|
|
|
HelloCookieManager hcm =
|
|
shc.sslContext.getHelloCookieManager(ProtocolVersion.DTLS10);
|
|
if (!shc.isResumption &&
|
|
!hcm.isCookieValid(shc, clientHello, clientHello.cookie)) {
|
|
//
|
|
// Perform cookie exchange for DTLS handshaking if no cookie
|
|
// or the cookie is invalid in the ClientHello message.
|
|
//
|
|
// update the responders
|
|
shc.handshakeProducers.put(
|
|
SSLHandshake.HELLO_VERIFY_REQUEST.id,
|
|
SSLHandshake.HELLO_VERIFY_REQUEST);
|
|
|
|
//
|
|
// produce response handshake message
|
|
//
|
|
SSLHandshake.HELLO_VERIFY_REQUEST.produce(context, clientHello);
|
|
|
|
return;
|
|
}
|
|
|
|
// cache the client random number for further using
|
|
shc.clientHelloRandom = clientHello.clientRandom;
|
|
|
|
// Check and launch ClientHello extensions.
|
|
SSLExtension[] extTypes = shc.sslConfig.getEnabledExtensions(
|
|
SSLHandshake.CLIENT_HELLO);
|
|
clientHello.extensions.consumeOnLoad(shc, extTypes);
|
|
|
|
//
|
|
// update
|
|
//
|
|
if (!shc.conContext.isNegotiated) {
|
|
shc.conContext.protocolVersion = shc.negotiatedProtocol;
|
|
shc.conContext.outputRecord.setVersion(shc.negotiatedProtocol);
|
|
}
|
|
|
|
// update the responders
|
|
//
|
|
// Only need to ServerHello, which may add more responders later.
|
|
shc.handshakeProducers.put(SSLHandshake.SERVER_HELLO.id,
|
|
SSLHandshake.SERVER_HELLO);
|
|
|
|
//
|
|
// produce
|
|
//
|
|
SSLHandshake[] probableHandshakeMessages = new SSLHandshake[] {
|
|
SSLHandshake.SERVER_HELLO,
|
|
|
|
// full handshake messages
|
|
SSLHandshake.CERTIFICATE,
|
|
SSLHandshake.CERTIFICATE_STATUS,
|
|
SSLHandshake.SERVER_KEY_EXCHANGE,
|
|
SSLHandshake.CERTIFICATE_REQUEST,
|
|
SSLHandshake.SERVER_HELLO_DONE,
|
|
|
|
// abbreviated handshake messages
|
|
SSLHandshake.FINISHED
|
|
};
|
|
|
|
for (SSLHandshake hs : probableHandshakeMessages) {
|
|
HandshakeProducer handshakeProducer =
|
|
shc.handshakeProducers.remove(hs.id);
|
|
if (handshakeProducer != null) {
|
|
handshakeProducer.produce(context, clientHello);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* The "ClientHello" handshake message consumer for DTLS 1.3.
|
|
*/
|
|
private static final
|
|
class D13ClientHelloConsumer implements HandshakeConsumer {
|
|
// Prevent instantiation of this class.
|
|
private D13ClientHelloConsumer() {
|
|
// blank
|
|
}
|
|
|
|
@Override
|
|
public void consume(ConnectionContext context,
|
|
HandshakeMessage message) throws IOException {
|
|
throw new UnsupportedOperationException("Not supported yet.");
|
|
}
|
|
}
|
|
}
|