--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.base/share/classes/sun/security/ssl/ClientHello.java Mon Jun 25 13:41:39 2018 -0700
@@ -0,0 +1,1397 @@
+/*
+ * 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.");
+ }
+ }
+}