8206929: Check session context for TLS 1.3 session resumption
Summary: additional checks to prevent TLS 1.3 sessions from being resumed when they shouldn't
Reviewed-by: xuelei
--- a/src/java.base/share/classes/sun/security/ssl/PostHandshakeContext.java Tue Jul 17 19:25:45 2018 +0300
+++ b/src/java.base/share/classes/sun/security/ssl/PostHandshakeContext.java Tue Jul 17 13:04:40 2018 -0400
@@ -27,6 +27,7 @@
import java.io.IOException;
import java.nio.ByteBuffer;
+import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.Map;
@@ -46,6 +47,9 @@
"Post-handshake not supported in " + negotiatedProtocol.name);
}
+ this.localSupportedSignAlgs = new ArrayList<SignatureScheme>(
+ context.conSession.getLocalSupportedSignatureSchemes());
+
handshakeConsumers = new LinkedHashMap<>(consumers);
handshakeFinished = true;
}
--- a/src/java.base/share/classes/sun/security/ssl/PreSharedKeyExtension.java Tue Jul 17 19:25:45 2018 +0300
+++ b/src/java.base/share/classes/sun/security/ssl/PreSharedKeyExtension.java Tue Jul 17 13:04:40 2018 -0400
@@ -33,8 +33,11 @@
import java.util.Locale;
import java.util.Arrays;
import java.util.Optional;
+import java.util.Collection;
import javax.crypto.Mac;
import javax.crypto.SecretKey;
+import javax.net.ssl.SSLPeerUnverifiedException;
+import static sun.security.ssl.ClientAuthType.CLIENT_AUTH_REQUIRED;
import sun.security.ssl.ClientHello.ClientHelloMessage;
import sun.security.ssl.SSLExtension.ExtensionConsumer;
import sun.security.ssl.SSLExtension.SSLExtensionSpec;
@@ -167,7 +170,7 @@
int getIdsEncodedLength() {
int idEncodedLength = 0;
- for(PskIdentity curId : identities) {
+ for (PskIdentity curId : identities) {
idEncodedLength += curId.getEncodedLength();
}
@@ -190,7 +193,7 @@
byte[] buffer = new byte[encodedLength];
ByteBuffer m = ByteBuffer.wrap(buffer);
Record.putInt16(m, idsEncodedLength);
- for(PskIdentity curId : identities) {
+ for (PskIdentity curId : identities) {
curId.writeEncoded(m);
}
Record.putInt16(m, bindersEncodedLength);
@@ -220,7 +223,7 @@
String identitiesString() {
StringBuilder result = new StringBuilder();
- for(PskIdentity curId : identities) {
+ for (PskIdentity curId : identities) {
result.append(curId.toString() + "\n");
}
@@ -229,7 +232,7 @@
String bindersString() {
StringBuilder result = new StringBuilder();
- for(byte[] curBinder : binders) {
+ for (byte[] curBinder : binders) {
result.append("{" + Utilities.toHexString(curBinder) + "}\n");
}
@@ -328,6 +331,7 @@
public void consume(ConnectionContext context,
HandshakeMessage message,
ByteBuffer buffer) throws IOException {
+ ClientHelloMessage clientHello = (ClientHelloMessage) message;
ServerHandshakeContext shc = (ServerHandshakeContext)context;
// Is it a supported and enabled extension?
if (!shc.sslConfig.isAvailable(SSLExtension.CH_PRE_SHARED_KEY)) {
@@ -367,8 +371,7 @@
int idIndex = 0;
for (PskIdentity requestedId : pskSpec.identities) {
SSLSessionImpl s = sessionCache.get(requestedId.identity);
- if (s != null && s.isRejoinable() &&
- s.getPreSharedKey().isPresent()) {
+ if (s != null && canRejoin(clientHello, shc, s)) {
if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
SSLLogger.fine("Resuming session: ", s);
}
@@ -392,8 +395,66 @@
// update the context
shc.handshakeExtensions.put(
- SSLExtension.CH_PRE_SHARED_KEY, pskSpec);
+ SSLExtension.CH_PRE_SHARED_KEY, pskSpec);
+ }
+ }
+
+ private static boolean canRejoin(ClientHelloMessage clientHello,
+ ServerHandshakeContext shc, SSLSessionImpl s) {
+
+ boolean result = s.isRejoinable() && s.getPreSharedKey().isPresent();
+
+ // Check protocol version
+ if (result && s.getProtocolVersion() != shc.negotiatedProtocol) {
+ if (SSLLogger.isOn &&
+ SSLLogger.isOn("ssl,handshake,verbose")) {
+
+ SSLLogger.finest("Can't resume, incorrect protocol version");
+ }
+ result = false;
}
+
+ // Validate the required client authentication.
+ if (result &&
+ (shc.sslConfig.clientAuthType == CLIENT_AUTH_REQUIRED)) {
+ try {
+ s.getPeerPrincipal();
+ } catch (SSLPeerUnverifiedException e) {
+ if (SSLLogger.isOn &&
+ SSLLogger.isOn("ssl,handshake,verbose")) {
+ SSLLogger.finest(
+ "Can't resume, " +
+ "client authentication is required");
+ }
+ result = false;
+ }
+
+ // Make sure the list of supported signature algorithms matches
+ Collection<SignatureScheme> sessionSigAlgs =
+ s.getLocalSupportedSignatureSchemes();
+ if (result &&
+ !shc.localSupportedSignAlgs.containsAll(sessionSigAlgs)) {
+
+ if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
+ SSLLogger.fine("Can't resume. Session uses different " +
+ "signature algorithms");
+ }
+ result = false;
+ }
+ }
+
+ // Ensure cipher suite can be negotiated
+ if (result && (!shc.isNegotiable(s.getSuite()) ||
+ !clientHello.cipherSuites.contains(s.getSuite()))) {
+ if (SSLLogger.isOn &&
+ SSLLogger.isOn("ssl,handshake,verbose")) {
+ SSLLogger.finest(
+ "Can't resume, unavailable session cipher suite");
+ }
+ result = false;
+ }
+
+ return result;
}
private static final
@@ -547,6 +608,18 @@
return null;
}
+ // Make sure the list of supported signature algorithms matches
+ Collection<SignatureScheme> sessionSigAlgs =
+ chc.resumingSession.getLocalSupportedSignatureSchemes();
+ if (!chc.localSupportedSignAlgs.containsAll(sessionSigAlgs)) {
+ if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
+ SSLLogger.fine("Existing session uses different " +
+ "signature algorithms");
+ }
+ return null;
+ }
+
+ // The session must have a pre-shared key
Optional<SecretKey> pskOpt = chc.resumingSession.getPreSharedKey();
if (!pskOpt.isPresent()) {
if (SSLLogger.isOn && SSLLogger.isOn("ssl,handshake")) {
@@ -658,7 +731,7 @@
} catch (NoSuchAlgorithmException | InvalidKeyException ex) {
throw new IOException(ex);
}
- } catch(GeneralSecurityException ex) {
+ } catch (GeneralSecurityException ex) {
throw new IOException(ex);
}
}
--- a/src/java.base/share/classes/sun/security/ssl/SSLSessionImpl.java Tue Jul 17 19:25:45 2018 +0300
+++ b/src/java.base/share/classes/sun/security/ssl/SSLSessionImpl.java Tue Jul 17 13:04:40 2018 -0400
@@ -96,7 +96,7 @@
private boolean invalidated;
private X509Certificate[] localCerts;
private PrivateKey localPrivateKey;
- private final String[] localSupportedSignAlgs;
+ private final Collection<SignatureScheme> localSupportedSignAlgs;
private String[] peerSupportedSignAlgs; // for certificate
private boolean useDefaultPeerSignAlgs = false;
private List<byte[]> statusResponses;
@@ -144,7 +144,7 @@
this.sessionId = new SessionId(false, null);
this.host = null;
this.port = -1;
- this.localSupportedSignAlgs = new String[0];
+ this.localSupportedSignAlgs = Collections.emptySet();
this.serverNameIndication = null;
this.requestedServerNames = Collections.<SNIServerName>emptyList();
this.useExtendedMasterSecret = false;
@@ -179,8 +179,9 @@
this.sessionId = id;
this.host = hc.conContext.transport.getPeerHost();
this.port = hc.conContext.transport.getPeerPort();
- this.localSupportedSignAlgs =
- SignatureScheme.getAlgorithmNames(hc.localSupportedSignAlgs);
+ this.localSupportedSignAlgs = hc.localSupportedSignAlgs == null ?
+ Collections.emptySet() :
+ Collections.unmodifiableCollection(hc.localSupportedSignAlgs);
this.serverNameIndication = hc.negotiatedServerName;
this.requestedServerNames = Collections.<SNIServerName>unmodifiableList(
hc.getRequestedServerNames());
@@ -969,16 +970,20 @@
}
/**
- * Gets an array of supported signature algorithms that the local side is
- * willing to verify.
+ * Gets an array of supported signature algorithm names that the local
+ * side is willing to verify.
*/
@Override
public String[] getLocalSupportedSignatureAlgorithms() {
- if (localSupportedSignAlgs != null) {
- return localSupportedSignAlgs.clone();
- }
+ return SignatureScheme.getAlgorithmNames(localSupportedSignAlgs);
+ }
- return new String[0];
+ /**
+ * Gets an array of supported signature schemes that the local side is
+ * willing to verify.
+ */
+ public Collection<SignatureScheme> getLocalSupportedSignatureSchemes() {
+ return localSupportedSignAlgs;
}
/**
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/sun/security/ssl/SSLSessionImpl/ResumeChecksClient.java Tue Jul 17 13:04:40 2018 -0400
@@ -0,0 +1,269 @@
+/*
+ * Copyright (c) 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.
+ *
+ * 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.
+ */
+
+/*
+ * @test
+ * @bug 8206929
+ * @summary ensure that client only resumes a session if certain properties
+ * of the session are compatible with the new connection
+ * @run main/othervm -Djdk.tls.client.protocols=TLSv1.2 ResumeChecksClient BASIC
+ * @run main/othervm -Djdk.tls.client.protocols=TLSv1.3 ResumeChecksClient BASIC
+ * @run main/othervm ResumeChecksClient BASIC
+ * @run main/othervm ResumeChecksClient VERSION_2_TO_3
+ * @run main/othervm ResumeChecksClient VERSION_3_TO_2
+ * @run main/othervm -Djdk.tls.client.protocols=TLSv1.3 ResumeChecksClient CIPHER_SUITE
+ * @run main/othervm -Djdk.tls.client.protocols=TLSv1.3 ResumeChecksClient SIGNATURE_SCHEME
+ *
+ */
+
+import javax.net.*;
+import javax.net.ssl.*;
+import java.io.*;
+import java.security.*;
+import java.net.*;
+import java.util.*;
+
+public class ResumeChecksClient {
+
+ static String pathToStores = "../../../../javax/net/ssl/etc";
+ static String keyStoreFile = "keystore";
+ static String trustStoreFile = "truststore";
+ static String passwd = "passphrase";
+
+ enum TestMode {
+ BASIC,
+ VERSION_2_TO_3,
+ VERSION_3_TO_2,
+ CIPHER_SUITE,
+ SIGNATURE_SCHEME
+ }
+
+ public static void main(String[] args) throws Exception {
+
+ TestMode mode = TestMode.valueOf(args[0]);
+
+ String keyFilename =
+ System.getProperty("test.src", "./") + "/" + pathToStores +
+ "/" + keyStoreFile;
+ String trustFilename =
+ System.getProperty("test.src", "./") + "/" + pathToStores +
+ "/" + trustStoreFile;
+
+ System.setProperty("javax.net.ssl.keyStore", keyFilename);
+ System.setProperty("javax.net.ssl.keyStorePassword", passwd);
+ System.setProperty("javax.net.ssl.trustStore", trustFilename);
+ System.setProperty("javax.net.ssl.trustStorePassword", passwd);
+
+ Server server = startServer();
+ server.signal();
+ SSLContext sslContext = SSLContext.getDefault();
+ while (!server.started) {
+ Thread.yield();
+ }
+ connect(sslContext, server.port, mode, false);
+
+ server.signal();
+ long secondStartTime = System.currentTimeMillis();
+ Thread.sleep(10);
+ SSLSession secondSession = connect(sslContext, server.port, mode, true);
+
+ server.go = false;
+ server.signal();
+
+ switch (mode) {
+ case BASIC:
+ // fail if session is not resumed
+ if (secondSession.getCreationTime() > secondStartTime) {
+ throw new RuntimeException("Session was not reused");
+ }
+ break;
+ case VERSION_2_TO_3:
+ case VERSION_3_TO_2:
+ case CIPHER_SUITE:
+ case SIGNATURE_SCHEME:
+ // fail if a new session is not created
+ if (secondSession.getCreationTime() <= secondStartTime) {
+ throw new RuntimeException("Existing session was used");
+ }
+ break;
+ default:
+ throw new RuntimeException("unknown mode: " + mode);
+ }
+ }
+
+ private static class NoSig implements AlgorithmConstraints {
+
+ private final String alg;
+
+ NoSig(String alg) {
+ this.alg = alg;
+ }
+
+
+ private boolean test(String a) {
+ return !a.toLowerCase().contains(alg.toLowerCase());
+ }
+
+ public boolean permits(Set<CryptoPrimitive> primitives, Key key) {
+ return true;
+ }
+ public boolean permits(Set<CryptoPrimitive> primitives,
+ String algorithm, AlgorithmParameters parameters) {
+
+ return test(algorithm);
+ }
+ public boolean permits(Set<CryptoPrimitive> primitives,
+ String algorithm, Key key, AlgorithmParameters parameters) {
+
+ return test(algorithm);
+ }
+ }
+
+ private static SSLSession connect(SSLContext sslContext, int port,
+ TestMode mode, boolean second) {
+
+ try {
+ SSLSocket sock = (SSLSocket)
+ sslContext.getSocketFactory().createSocket();
+ SSLParameters params = sock.getSSLParameters();
+
+ switch (mode) {
+ case BASIC:
+ // do nothing to ensure resumption works
+ break;
+ case VERSION_2_TO_3:
+ if (second) {
+ params.setProtocols(new String[] {"TLSv1.3"});
+ } else {
+ params.setProtocols(new String[] {"TLSv1.2"});
+ }
+ break;
+ case VERSION_3_TO_2:
+ if (second) {
+ params.setProtocols(new String[] {"TLSv1.2"});
+ } else {
+ params.setProtocols(new String[] {"TLSv1.3"});
+ }
+ break;
+ case CIPHER_SUITE:
+ if (second) {
+ params.setCipherSuites(
+ new String[] {"TLS_AES_256_GCM_SHA384"});
+ } else {
+ params.setCipherSuites(
+ new String[] {"TLS_AES_128_GCM_SHA256"});
+ }
+ break;
+ case SIGNATURE_SCHEME:
+ AlgorithmConstraints constraints =
+ params.getAlgorithmConstraints();
+ if (second) {
+ params.setAlgorithmConstraints(new NoSig("ecdsa"));
+ } else {
+ params.setAlgorithmConstraints(new NoSig("rsa"));
+ }
+ break;
+ default:
+ throw new RuntimeException("unknown mode: " + mode);
+ }
+ sock.setSSLParameters(params);
+ sock.connect(new InetSocketAddress("localhost", port));
+ PrintWriter out = new PrintWriter(
+ new OutputStreamWriter(sock.getOutputStream()));
+ out.println("message");
+ out.flush();
+ BufferedReader reader = new BufferedReader(
+ new InputStreamReader(sock.getInputStream()));
+ String inMsg = reader.readLine();
+ System.out.println("Client received: " + inMsg);
+ SSLSession result = sock.getSession();
+ sock.close();
+ return result;
+ } catch (Exception ex) {
+ // unexpected exception
+ throw new RuntimeException(ex);
+ }
+ }
+
+ private static Server startServer() {
+ Server server = new Server();
+ new Thread(server).start();
+ return server;
+ }
+
+ private static class Server implements Runnable {
+
+ public volatile boolean go = true;
+ private boolean signal = false;
+ public volatile int port = 0;
+ public volatile boolean started = false;
+
+ private synchronized void waitForSignal() {
+ while (!signal) {
+ try {
+ wait();
+ } catch (InterruptedException ex) {
+ // do nothing
+ }
+ }
+ signal = false;
+ }
+ public synchronized void signal() {
+ signal = true;
+ notify();
+ }
+
+ public void run() {
+ try {
+
+ SSLContext sc = SSLContext.getDefault();
+ ServerSocketFactory fac = sc.getServerSocketFactory();
+ SSLServerSocket ssock = (SSLServerSocket)
+ fac.createServerSocket(0);
+ this.port = ssock.getLocalPort();
+
+ waitForSignal();
+ started = true;
+ while (go) {
+ try {
+ System.out.println("Waiting for connection");
+ Socket sock = ssock.accept();
+ BufferedReader reader = new BufferedReader(
+ new InputStreamReader(sock.getInputStream()));
+ String line = reader.readLine();
+ System.out.println("server read: " + line);
+ PrintWriter out = new PrintWriter(
+ new OutputStreamWriter(sock.getOutputStream()));
+ out.println(line);
+ out.flush();
+ waitForSignal();
+ } catch (Exception ex) {
+ ex.printStackTrace();
+ }
+ }
+ } catch (Exception ex) {
+ throw new RuntimeException(ex);
+ }
+ }
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/sun/security/ssl/SSLSessionImpl/ResumeChecksServer.java Tue Jul 17 13:04:40 2018 -0400
@@ -0,0 +1,307 @@
+/*
+ * Copyright (c) 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.
+ *
+ * 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.
+ */
+
+/*
+ * @test
+ * @bug 8206929
+ * @summary ensure that server only resumes a session if certain properties
+ * of the session are compatible with the new connection
+ * @run main/othervm -Djdk.tls.client.protocols=TLSv1.2 ResumeChecksServer BASIC
+ * @run main/othervm -Djdk.tls.client.protocols=TLSv1.3 ResumeChecksServer BASIC
+ * @run main/othervm ResumeChecksServer BASIC
+ * @run main/othervm -Djdk.tls.client.protocols=TLSv1.2 ResumeChecksServer CLIENT_AUTH
+ * @run main/othervm -Djdk.tls.client.protocols=TLSv1.3 ResumeChecksServer CLIENT_AUTH
+ * @run main/othervm ResumeChecksServer CLIENT_AUTH
+ * @run main/othervm ResumeChecksServer VERSION_2_TO_3
+ * @run main/othervm ResumeChecksServer VERSION_3_TO_2
+ * @run main/othervm -Djdk.tls.client.protocols=TLSv1.3 ResumeChecksServer CIPHER_SUITE
+ * @run main/othervm -Djdk.tls.client.protocols=TLSv1.3 ResumeChecksServer SIGNATURE_SCHEME
+ *
+ */
+
+import javax.net.*;
+import javax.net.ssl.*;
+import java.io.*;
+import java.security.*;
+import java.net.*;
+import java.util.*;
+
+public class ResumeChecksServer {
+
+ static String pathToStores = "../../../../javax/net/ssl/etc";
+ static String keyStoreFile = "keystore";
+ static String trustStoreFile = "truststore";
+ static String passwd = "passphrase";
+
+ enum TestMode {
+ BASIC,
+ CLIENT_AUTH,
+ VERSION_2_TO_3,
+ VERSION_3_TO_2,
+ CIPHER_SUITE,
+ SIGNATURE_SCHEME
+ }
+
+ public static void main(String[] args) throws Exception {
+
+ TestMode mode = TestMode.valueOf(args[0]);
+
+ String keyFilename =
+ System.getProperty("test.src", "./") + "/" + pathToStores +
+ "/" + keyStoreFile;
+ String trustFilename =
+ System.getProperty("test.src", "./") + "/" + pathToStores +
+ "/" + trustStoreFile;
+
+ System.setProperty("javax.net.ssl.keyStore", keyFilename);
+ System.setProperty("javax.net.ssl.keyStorePassword", passwd);
+ System.setProperty("javax.net.ssl.trustStore", trustFilename);
+ System.setProperty("javax.net.ssl.trustStorePassword", passwd);
+
+ SSLSession secondSession = null;
+
+ SSLContext sslContext = SSLContext.getDefault();
+ ServerSocketFactory fac = sslContext.getServerSocketFactory();
+ SSLServerSocket ssock = (SSLServerSocket)
+ fac.createServerSocket(0);
+
+ Client client = startClient(ssock.getLocalPort());
+
+ try {
+ connect(client, ssock, mode, false);
+ } catch (Exception ex) {
+ throw new RuntimeException(ex);
+ }
+
+ long secondStartTime = System.currentTimeMillis();
+ Thread.sleep(10);
+ try {
+ secondSession = connect(client, ssock, mode, true);
+ } catch (SSLHandshakeException ex) {
+ // this is expected
+ } catch (Exception ex) {
+ throw new RuntimeException(ex);
+ }
+
+ client.go = false;
+ client.signal();
+
+ switch (mode) {
+ case BASIC:
+ // fail if session is not resumed
+ if (secondSession.getCreationTime() > secondStartTime) {
+ throw new RuntimeException("Session was not reused");
+ }
+ break;
+ case CLIENT_AUTH:
+ // throws an exception if the client is not authenticated
+ secondSession.getPeerCertificates();
+ break;
+ case VERSION_2_TO_3:
+ case VERSION_3_TO_2:
+ case CIPHER_SUITE:
+ case SIGNATURE_SCHEME:
+ // fail if a new session is not created
+ if (secondSession.getCreationTime() <= secondStartTime) {
+ throw new RuntimeException("Existing session was used");
+ }
+ break;
+ default:
+ throw new RuntimeException("unknown mode: " + mode);
+ }
+ }
+
+ private static class NoSig implements AlgorithmConstraints {
+
+ private final String alg;
+
+ NoSig(String alg) {
+ this.alg = alg;
+ }
+
+
+ private boolean test(String a) {
+ return !a.toLowerCase().contains(alg.toLowerCase());
+ }
+
+ public boolean permits(Set<CryptoPrimitive> primitives, Key key) {
+ return true;
+ }
+ public boolean permits(Set<CryptoPrimitive> primitives,
+ String algorithm, AlgorithmParameters parameters) {
+
+ return test(algorithm);
+ }
+ public boolean permits(Set<CryptoPrimitive> primitives,
+ String algorithm, Key key, AlgorithmParameters parameters) {
+
+ return test(algorithm);
+ }
+ }
+
+ private static SSLSession connect(Client client, SSLServerSocket ssock,
+ TestMode mode, boolean second) throws Exception {
+
+ try {
+ client.signal();
+ System.out.println("Waiting for connection");
+ SSLSocket sock = (SSLSocket) ssock.accept();
+ SSLParameters params = sock.getSSLParameters();
+
+ switch (mode) {
+ case BASIC:
+ // do nothing to ensure resumption works
+ break;
+ case CLIENT_AUTH:
+ if (second) {
+ params.setNeedClientAuth(true);
+ } else {
+ params.setNeedClientAuth(false);
+ }
+ break;
+ case VERSION_2_TO_3:
+ if (second) {
+ params.setProtocols(new String[] {"TLSv1.3"});
+ } else {
+ params.setProtocols(new String[] {"TLSv1.2"});
+ }
+ break;
+ case VERSION_3_TO_2:
+ if (second) {
+ params.setProtocols(new String[] {"TLSv1.2"});
+ } else {
+ params.setProtocols(new String[] {"TLSv1.3"});
+ }
+ break;
+ case CIPHER_SUITE:
+ if (second) {
+ params.setCipherSuites(
+ new String[] {"TLS_AES_128_GCM_SHA256"});
+ } else {
+ params.setCipherSuites(
+ new String[] {"TLS_AES_256_GCM_SHA384"});
+ }
+ break;
+ case SIGNATURE_SCHEME:
+ params.setNeedClientAuth(true);
+ AlgorithmConstraints constraints =
+ params.getAlgorithmConstraints();
+ if (second) {
+ params.setAlgorithmConstraints(new NoSig("ecdsa"));
+ } else {
+ params.setAlgorithmConstraints(new NoSig("rsa"));
+ }
+ break;
+ default:
+ throw new RuntimeException("unknown mode: " + mode);
+ }
+ sock.setSSLParameters(params);
+ BufferedReader reader = new BufferedReader(
+ new InputStreamReader(sock.getInputStream()));
+ String line = reader.readLine();
+ System.out.println("server read: " + line);
+ PrintWriter out = new PrintWriter(
+ new OutputStreamWriter(sock.getOutputStream()));
+ out.println(line);
+ out.flush();
+ out.close();
+ SSLSession result = sock.getSession();
+ sock.close();
+ return result;
+ } catch (SSLHandshakeException ex) {
+ if (!second) {
+ throw ex;
+ }
+ }
+ return null;
+ }
+
+ private static Client startClient(int port) {
+ Client client = new Client(port);
+ new Thread(client).start();
+ return client;
+ }
+
+ private static class Client implements Runnable {
+
+ public volatile boolean go = true;
+ private boolean signal = false;
+ private final int port;
+
+ Client(int port) {
+ this.port = port;
+ }
+
+ private synchronized void waitForSignal() {
+ while (!signal) {
+ try {
+ wait();
+ } catch (InterruptedException ex) {
+ // do nothing
+ }
+ }
+ signal = false;
+
+ try {
+ Thread.sleep(1000);
+ } catch (InterruptedException ex) {
+ // do nothing
+ }
+ }
+ public synchronized void signal() {
+ signal = true;
+ notify();
+ }
+
+ public void run() {
+ try {
+
+ SSLContext sc = SSLContext.getDefault();
+
+ waitForSignal();
+ while (go) {
+ try {
+ SSLSocket sock = (SSLSocket)
+ sc.getSocketFactory().createSocket();
+ sock.connect(new InetSocketAddress("localhost", port));
+ PrintWriter out = new PrintWriter(
+ new OutputStreamWriter(sock.getOutputStream()));
+ out.println("message");
+ out.flush();
+ BufferedReader reader = new BufferedReader(
+ new InputStreamReader(sock.getInputStream()));
+ String inMsg = reader.readLine();
+ System.out.println("Client received: " + inMsg);
+ out.close();
+ sock.close();
+ waitForSignal();
+ } catch (Exception ex) {
+ ex.printStackTrace();
+ }
+ }
+ } catch (Exception ex) {
+ throw new RuntimeException(ex);
+ }
+ }
+ }
+}