blob: 5ce4d5ff8c8223d81de22a3d54eec62fd1431d6a [file] [log] [blame]
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.conscrypt.javax.net.ssl;
import static org.conscrypt.TestUtils.osName;
import static org.conscrypt.TestUtils.isOsx;
import static org.conscrypt.TestUtils.isLinux;
import static org.conscrypt.TestUtils.isWindows;
import static org.conscrypt.TestUtils.UTF_8;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.junit.Assume.assumeFalse;
import static org.junit.Assume.assumeNoException;
import static org.junit.Assume.assumeTrue;
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.Thread.UncaughtExceptionHandler;
import java.lang.reflect.Method;
import java.net.ConnectException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.Principal;
import java.security.PrivateKey;
import java.security.Security;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import javax.net.ServerSocketFactory;
import javax.net.SocketFactory;
import javax.net.ssl.ExtendedSSLSession;
import javax.net.ssl.HandshakeCompletedEvent;
import javax.net.ssl.HandshakeCompletedListener;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.KeyManager;
import javax.net.ssl.SNIHostName;
import javax.net.ssl.SNIServerName;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLHandshakeException;
import javax.net.ssl.SSLParameters;
import javax.net.ssl.SSLPeerUnverifiedException;
import javax.net.ssl.SSLServerSocket;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.StandardConstants;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509ExtendedKeyManager;
import javax.net.ssl.X509KeyManager;
import javax.net.ssl.X509TrustManager;
import org.conscrypt.Conscrypt;
import org.conscrypt.TestUtils;
import org.conscrypt.java.security.StandardNames;
import org.conscrypt.java.security.TestKeyStore;
import org.conscrypt.testing.OpaqueProvider;
import org.conscrypt.tlswire.TlsTester;
import org.conscrypt.tlswire.handshake.AlpnHelloExtension;
import org.conscrypt.tlswire.handshake.ClientHello;
import org.conscrypt.tlswire.handshake.HandshakeMessage;
import org.conscrypt.tlswire.handshake.HelloExtension;
import org.conscrypt.tlswire.handshake.ServerNameHelloExtension;
import org.conscrypt.tlswire.record.TlsProtocols;
import org.conscrypt.tlswire.record.TlsRecord;
import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import tests.net.DelegatingSSLSocketFactory;
import tests.util.ForEachRunner;
import tests.util.Pair;
/**
* Tests for SSLSocket classes that ensure the TLS 1.2 and TLS 1.3 implementations
* are compatible.
*/
@RunWith(Parameterized.class)
public class SSLSocketVersionCompatibilityTest {
@Parameterized.Parameters(name = "{index}: {0} client, {1} server")
public static Iterable<Object[]> data() {
return Arrays.asList(new Object[][] {
{ "TLSv1.2", "TLSv1.2" },
{ "TLSv1.2", "TLSv1.3" },
{ "TLSv1.3", "TLSv1.2" },
{ "TLSv1.3", "TLSv1.3" },
});
}
private final String clientVersion;
private final String serverVersion;
private ExecutorService executor;
private ThreadGroup threadGroup;
public SSLSocketVersionCompatibilityTest(String clientVersion, String serverVersion) {
this.clientVersion = clientVersion;
this.serverVersion = serverVersion;
}
@Before
public void setup() {
threadGroup = new ThreadGroup("SSLSocketVersionedTest");
executor = Executors.newCachedThreadPool(new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
return new Thread(threadGroup, r);
}
});
}
@After
public void teardown() throws InterruptedException {
executor.shutdownNow();
executor.awaitTermination(5, TimeUnit.SECONDS);
}
@Test
public void test_SSLSocket_startHandshake() throws Exception {
final TestSSLContext c = new TestSSLContext.Builder()
.clientProtocol(clientVersion)
.serverProtocol(serverVersion).build();
SSLSocket client =
(SSLSocket) c.clientContext.getSocketFactory().createSocket(c.host, c.port);
final SSLSocket server = (SSLSocket) c.serverSocket.accept();
Future<Void> future = runAsync(new Callable<Void>() {
@Override
public Void call() throws Exception {
server.startHandshake();
assertNotNull(server.getSession());
assertNull(server.getHandshakeSession());
try {
server.getSession().getPeerCertificates();
fail();
} catch (SSLPeerUnverifiedException expected) {
// Ignored.
}
Certificate[] localCertificates = server.getSession().getLocalCertificates();
assertNotNull(localCertificates);
TestKeyStore.assertChainLength(localCertificates);
assertNotNull(localCertificates[0]);
TestSSLContext
.assertServerCertificateChain(c.serverTrustManager, localCertificates);
TestSSLContext.assertCertificateInKeyStore(localCertificates[0], c.serverKeyStore);
return null;
}
});
client.startHandshake();
assertNotNull(client.getSession());
assertNull(client.getSession().getLocalCertificates());
Certificate[] peerCertificates = client.getSession().getPeerCertificates();
assertNotNull(peerCertificates);
TestKeyStore.assertChainLength(peerCertificates);
assertNotNull(peerCertificates[0]);
TestSSLContext.assertServerCertificateChain(c.clientTrustManager, peerCertificates);
TestSSLContext.assertCertificateInKeyStore(peerCertificates[0], c.serverKeyStore);
future.get();
client.close();
server.close();
c.close();
}
private static final class SSLServerSessionIdCallable implements Callable<byte[]> {
private final SSLSocket server;
private SSLServerSessionIdCallable(SSLSocket server) {
this.server = server;
}
@Override
public byte[] call() throws Exception {
server.startHandshake();
assertNotNull(server.getSession());
assertNotNull(server.getSession().getId());
return server.getSession().getId();
}
}
@Test
public void test_SSLSocket_confirmSessionReuse() throws Exception {
final TestSSLContext c = new TestSSLContext.Builder()
.clientProtocol(clientVersion)
.serverProtocol(serverVersion)
.build();
final SSLSocket client1 = (SSLSocket) c.clientContext.getSocketFactory().createSocket(
c.host.getHostName(), c.port);
final SSLSocket server1 = (SSLSocket) c.serverSocket.accept();
final Future<byte[]> future1 = runAsync(new SSLServerSessionIdCallable(server1));
client1.startHandshake();
assertNotNull(client1.getSession());
assertNotNull(client1.getSession().getId());
final byte[] clientSessionId1 = client1.getSession().getId();
final byte[] serverSessionId1 = future1.get();
assertTrue(Arrays.equals(clientSessionId1, serverSessionId1));
client1.close();
server1.close();
final SSLSocket client2 = (SSLSocket) c.clientContext.getSocketFactory().createSocket(
c.host.getHostName(), c.port);
final SSLSocket server2 = (SSLSocket) c.serverSocket.accept();
final Future<byte[]> future2 = runAsync(new SSLServerSessionIdCallable(server2));
client2.startHandshake();
assertNotNull(client2.getSession());
assertNotNull(client2.getSession().getId());
final byte[] clientSessionId2 = client2.getSession().getId();
final byte[] serverSessionId2 = future2.get();
assertTrue(Arrays.equals(clientSessionId2, serverSessionId2));
client2.close();
server2.close();
assertTrue(Arrays.equals(clientSessionId1, clientSessionId2));
c.close();
}
@Test
public void test_SSLSocket_NoEnabledCipherSuites_Failure() throws Exception {
TestSSLContext c = TestSSLContext.newBuilder()
.useDefaults(false)
.clientContext(defaultInit(SSLContext.getInstance(clientVersion)))
.serverContext(defaultInit(SSLContext.getInstance(serverVersion)))
.build();
SSLSocket client =
(SSLSocket) c.clientContext.getSocketFactory().createSocket(c.host, c.port);
client.setEnabledCipherSuites(new String[0]);
final SSLSocket server = (SSLSocket) c.serverSocket.accept();
Future<Void> future = runAsync(new Callable<Void>() {
@Override
public Void call() throws Exception {
try {
server.startHandshake();
fail();
} catch (SSLHandshakeException expected) {
// Ignored.
}
return null;
}
});
try {
client.startHandshake();
fail();
} catch (SSLHandshakeException expected) {
// Ignored.
}
future.get();
server.close();
client.close();
c.close();
}
@Test
public void test_SSLSocket_startHandshake_noKeyStore() throws Exception {
TestSSLContext c = TestSSLContext.newBuilder()
.useDefaults(false)
.clientContext(defaultInit(SSLContext.getInstance(clientVersion)))
.serverContext(defaultInit(SSLContext.getInstance(serverVersion)))
.build();
SSLSocket client =
(SSLSocket) c.clientContext.getSocketFactory().createSocket(c.host, c.port);
final SSLSocket server = (SSLSocket) c.serverSocket.accept();
Future<Void> future = runAsync(new Callable<Void>() {
@Override
public Void call() throws Exception {
try {
server.startHandshake();
fail();
} catch (SSLHandshakeException expected) {
// Ignored.
}
return null;
}
});
try {
client.startHandshake();
fail();
} catch (SSLHandshakeException expected) {
// Ignored.
}
future.get();
server.close();
client.close();
c.close();
}
@Test
public void test_SSLSocket_startHandshake_noClientCertificate() throws Exception {
final TestSSLContext c = new TestSSLContext.Builder()
.clientProtocol(clientVersion)
.serverProtocol(serverVersion)
.build();
SSLContext clientContext = c.clientContext;
SSLSocket client =
(SSLSocket) clientContext.getSocketFactory().createSocket(c.host, c.port);
final SSLSocket server = (SSLSocket) c.serverSocket.accept();
Future<Void> future = runAsync(new Callable<Void>() {
@Override
public Void call() throws Exception {
server.startHandshake();
return null;
}
});
client.startHandshake();
future.get();
client.close();
server.close();
c.close();
}
@Test
public void test_SSLSocket_HandshakeCompletedListener() throws Exception {
final TestSSLContext c = new TestSSLContext.Builder()
.clientProtocol(clientVersion)
.serverProtocol(serverVersion)
.build();
final SSLSocket client =
(SSLSocket) c.clientContext.getSocketFactory().createSocket(c.host, c.port);
final SSLSocket server = (SSLSocket) c.serverSocket.accept();
Future<Void> future = runAsync(new Callable<Void>() {
@Override
public Void call() throws Exception {
server.startHandshake();
return null;
}
});
final boolean[] handshakeCompletedListenerCalled = new boolean[1];
client.addHandshakeCompletedListener(new HandshakeCompletedListener() {
@Override
public void handshakeCompleted(HandshakeCompletedEvent event) {
SSLSocket socket = null;
try {
SSLSession session = event.getSession();
String cipherSuite = event.getCipherSuite();
Certificate[] localCertificates = event.getLocalCertificates();
Certificate[] peerCertificates = event.getPeerCertificates();
javax.security.cert.X509Certificate[] peerCertificateChain =
event.getPeerCertificateChain();
Principal peerPrincipal = event.getPeerPrincipal();
Principal localPrincipal = event.getLocalPrincipal();
socket = event.getSocket();
assertNotNull(session);
byte[] id = session.getId();
assertNotNull(id);
if (negotiatedVersion().equals("TLSv1.2")) {
// Session ticket delivery happens inside the handshake in TLS 1.2,
// but outside it for TLS 1.3.
assertEquals(32, id.length);
assertNotNull(c.clientContext.getClientSessionContext().getSession(id));
} else {
assertEquals(0, id.length);
}
assertNotNull(cipherSuite);
assertTrue(Arrays.asList(client.getEnabledCipherSuites())
.contains(cipherSuite));
assertTrue(Arrays.asList(c.serverSocket.getEnabledCipherSuites())
.contains(cipherSuite));
assertNull(localCertificates);
assertNotNull(peerCertificates);
TestKeyStore.assertChainLength(peerCertificates);
assertNotNull(peerCertificates[0]);
TestSSLContext
.assertServerCertificateChain(c.clientTrustManager, peerCertificates);
TestSSLContext
.assertCertificateInKeyStore(peerCertificates[0], c.serverKeyStore);
assertNotNull(peerCertificateChain);
TestKeyStore.assertChainLength(peerCertificateChain);
assertNotNull(peerCertificateChain[0]);
TestSSLContext.assertCertificateInKeyStore(
peerCertificateChain[0].getSubjectDN(), c.serverKeyStore);
assertNotNull(peerPrincipal);
TestSSLContext.assertCertificateInKeyStore(peerPrincipal, c.serverKeyStore);
assertNull(localPrincipal);
assertNotNull(socket);
assertSame(client, socket);
assertNull(socket.getHandshakeSession());
} catch (RuntimeException e) {
throw e;
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
synchronized (handshakeCompletedListenerCalled) {
handshakeCompletedListenerCalled[0] = true;
handshakeCompletedListenerCalled.notify();
}
handshakeCompletedListenerCalled[0] = true;
if (socket != null) {
socket.removeHandshakeCompletedListener(this);
}
}
}
});
client.startHandshake();
future.get();
if (negotiatedVersion().equals("TLSv1.2")) {
assertNotNull(
c.serverContext.getServerSessionContext()
.getSession(client.getSession().getId()));
}
synchronized (handshakeCompletedListenerCalled) {
while (!handshakeCompletedListenerCalled[0]) {
handshakeCompletedListenerCalled.wait();
}
}
client.close();
server.close();
c.close();
}
private static final class TestUncaughtExceptionHandler implements UncaughtExceptionHandler {
Throwable actualException;
@Override
public void uncaughtException(Thread thread, Throwable ex) {
assertNull(actualException);
actualException = ex;
}
}
@Test
public void test_SSLSocket_HandshakeCompletedListener_RuntimeException() throws Exception {
final Thread self = Thread.currentThread();
final UncaughtExceptionHandler original = self.getUncaughtExceptionHandler();
final RuntimeException expectedException = new RuntimeException("expected");
final TestUncaughtExceptionHandler test = new TestUncaughtExceptionHandler();
self.setUncaughtExceptionHandler(test);
final TestSSLContext c = new TestSSLContext.Builder()
.clientProtocol(clientVersion)
.serverProtocol(serverVersion)
.build();
final SSLSocket client =
(SSLSocket) c.clientContext.getSocketFactory().createSocket(c.host, c.port);
final SSLSocket server = (SSLSocket) c.serverSocket.accept();
Future<Void> future = runAsync(new Callable<Void>() {
@Override
public Void call() throws Exception {
server.startHandshake();
return null;
}
});
client.addHandshakeCompletedListener(new HandshakeCompletedListener() {
@Override
public void handshakeCompleted(HandshakeCompletedEvent event) {
throw expectedException;
}
});
client.startHandshake();
future.get();
client.close();
server.close();
c.close();
assertSame(expectedException, test.actualException);
self.setUncaughtExceptionHandler(original);
}
@Test
public void test_SSLSocket_getUseClientMode() throws Exception {
final TestSSLContext c = new TestSSLContext.Builder()
.clientProtocol(clientVersion)
.serverProtocol(serverVersion)
.build();
SSLSocket client =
(SSLSocket) c.clientContext.getSocketFactory().createSocket(c.host, c.port);
SSLSocket server = (SSLSocket) c.serverSocket.accept();
assertTrue(client.getUseClientMode());
assertFalse(server.getUseClientMode());
client.close();
server.close();
c.close();
}
@Test
public void testClientMode_normal() throws Exception {
// Client is client and server is server.
test_SSLSocket_setUseClientMode(true, false);
}
@Test(expected = SSLHandshakeException.class)
public void testClientMode_reverse() throws Exception {
// Client is server and server is client.
test_SSLSocket_setUseClientMode(false, true);
}
@Test(expected = SSLHandshakeException.class)
public void testClientMode_bothClient() throws Exception {
test_SSLSocket_setUseClientMode(true, true);
}
@Test
public void testClientMode_bothServer() throws Exception {
try {
test_SSLSocket_setUseClientMode(false, false);
fail();
} catch (SocketTimeoutException expected) {
// Ignore
} catch (SSLHandshakeException expected) {
// Depending on the timing of the socket closures, this can happen as well.
assertTrue("Unexpected handshake error: " + expected.getMessage(),
expected.getMessage().toLowerCase().contains("connection closed"));
}
}
private void test_SSLSocket_setUseClientMode(
final boolean clientClientMode, final boolean serverClientMode) throws Exception {
final TestSSLContext c = new TestSSLContext.Builder()
.clientProtocol(clientVersion)
.serverProtocol(serverVersion)
.build();
SSLSocket client =
(SSLSocket) c.clientContext.getSocketFactory().createSocket(c.host, c.port);
final SSLSocket server = (SSLSocket) c.serverSocket.accept();
Future<IOException> future = runAsync(new Callable<IOException>() {
@Override
public IOException call() throws Exception {
try {
if (!serverClientMode) {
server.setSoTimeout(1000);
}
server.setUseClientMode(serverClientMode);
server.startHandshake();
return null;
} catch (SSLHandshakeException e) {
return e;
} catch (SocketTimeoutException e) {
return e;
}
}
});
if (!clientClientMode) {
client.setSoTimeout(1000);
}
client.setUseClientMode(clientClientMode);
client.startHandshake();
IOException ioe = future.get();
if (ioe != null) {
throw ioe;
}
client.close();
server.close();
c.close();
}
@Test
public void test_SSLSocket_clientAuth() throws Exception {
TestSSLContext c = new TestSSLContext.Builder()
.client(TestKeyStore.getClientCertificate())
.server(TestKeyStore.getServer())
.clientProtocol(clientVersion)
.serverProtocol(serverVersion)
.build();
SSLSocket client =
(SSLSocket) c.clientContext.getSocketFactory().createSocket(c.host, c.port);
final SSLSocket server = (SSLSocket) c.serverSocket.accept();
Future<Void> future = runAsync(new Callable<Void>() {
@Override
public Void call() throws Exception {
assertFalse(server.getWantClientAuth());
assertFalse(server.getNeedClientAuth());
// confirm turning one on by itself
server.setWantClientAuth(true);
assertTrue(server.getWantClientAuth());
assertFalse(server.getNeedClientAuth());
// confirm turning setting on toggles the other
server.setNeedClientAuth(true);
assertFalse(server.getWantClientAuth());
assertTrue(server.getNeedClientAuth());
// confirm toggling back
server.setWantClientAuth(true);
assertTrue(server.getWantClientAuth());
assertFalse(server.getNeedClientAuth());
server.startHandshake();
return null;
}
});
client.startHandshake();
assertNotNull(client.getSession().getLocalCertificates());
TestKeyStore.assertChainLength(client.getSession().getLocalCertificates());
TestSSLContext.assertClientCertificateChain(
c.clientTrustManager, client.getSession().getLocalCertificates());
future.get();
client.close();
server.close();
c.close();
}
@Test
public void test_SSLSocket_clientAuth_bogusAlias() throws Exception {
final TestSSLContext c = new TestSSLContext.Builder()
.clientProtocol(clientVersion)
.serverProtocol(serverVersion)
.build();
SSLContext clientContext = SSLContext.getInstance(clientVersion);
X509ExtendedKeyManager keyManager = new X509ExtendedKeyManager() {
@Override
public String chooseClientAlias(String[] keyType, Principal[] issuers, Socket socket) {
return "bogus";
}
@Override
public String chooseServerAlias(String keyType, Principal[] issuers, Socket socket) {
throw new AssertionError();
}
@Override
public X509Certificate[] getCertificateChain(String alias) {
// return null for "bogus" alias
return null;
}
@Override
public String[] getClientAliases(String keyType, Principal[] issuers) {
throw new AssertionError();
}
@Override
public String[] getServerAliases(String keyType, Principal[] issuers) {
throw new AssertionError();
}
@Override
public String chooseEngineClientAlias(String[] keyType, Principal[] issuers,
SSLEngine engine) {
throw new AssertionError();
}
@Override
public String chooseEngineServerAlias(String keyType, Principal[] issuers,
SSLEngine engine) {
throw new AssertionError();
}
@Override
public PrivateKey getPrivateKey(String alias) {
// return null for "bogus" alias
return null;
}
};
clientContext.init(
new KeyManager[] {keyManager}, new TrustManager[] {c.clientTrustManager}, null);
SSLSocket client =
(SSLSocket) clientContext.getSocketFactory().createSocket(c.host, c.port);
final SSLSocket server = (SSLSocket) c.serverSocket.accept();
Future<Void> future = runAsync(new Callable<Void>() {
@Override
public Void call() throws Exception {
try {
server.setNeedClientAuth(true);
server.startHandshake();
fail();
} catch (SSLHandshakeException expected) {
// Ignored.
}
return null;
}
});
try {
client.startHandshake();
// In TLS 1.3, the alert will only show up once we try to use the connection, since
// the client finishes the handshake without feedback from the server
client.getInputStream().read();
fail();
} catch (SSLException expected) {
// before we would get a NullPointerException from passing
// due to the null PrivateKey return by the X509KeyManager.
}
future.get();
client.close();
server.close();
c.close();
}
@Test
public void test_SSLSocket_clientAuth_OpaqueKey_RSA() throws Exception {
run_SSLSocket_clientAuth_OpaqueKey(TestKeyStore.getClientCertificate());
}
@Test
public void test_SSLSocket_clientAuth_OpaqueKey_EC_RSA() throws Exception {
run_SSLSocket_clientAuth_OpaqueKey(TestKeyStore.getClientEcRsaCertificate());
}
@Test
public void test_SSLSocket_clientAuth_OpaqueKey_EC_EC() throws Exception {
run_SSLSocket_clientAuth_OpaqueKey(TestKeyStore.getClientEcEcCertificate());
}
private void run_SSLSocket_clientAuth_OpaqueKey(TestKeyStore keyStore) throws Exception {
// OpaqueProvider will not be allowed to operate if the VM we're running on
// requires Oracle signatures for provider jars, since we don't sign the test jar.
TestUtils.assumeAllowsUnsignedCrypto();
try {
Security.insertProviderAt(new OpaqueProvider(), 1);
final TestSSLContext c = new TestSSLContext.Builder()
.client(keyStore)
.server(TestKeyStore.getServer())
.clientProtocol(clientVersion)
.serverProtocol(serverVersion)
.build();
SSLContext clientContext = SSLContext.getInstance("TLS");
final X509KeyManager delegateKeyManager = (X509KeyManager) c.clientKeyManagers[0];
X509ExtendedKeyManager keyManager = new X509ExtendedKeyManager() {
@Override
public String chooseClientAlias(
String[] keyType, Principal[] issuers, Socket socket) {
return delegateKeyManager.chooseClientAlias(keyType, issuers, socket);
}
@Override
public String chooseServerAlias(
String keyType, Principal[] issuers, Socket socket) {
return delegateKeyManager.chooseServerAlias(keyType, issuers, socket);
}
@Override
public X509Certificate[] getCertificateChain(String alias) {
return delegateKeyManager.getCertificateChain(alias);
}
@Override
public String[] getClientAliases(String keyType, Principal[] issuers) {
return delegateKeyManager.getClientAliases(keyType, issuers);
}
@Override
public String[] getServerAliases(String keyType, Principal[] issuers) {
return delegateKeyManager.getServerAliases(keyType, issuers);
}
@Override
public PrivateKey getPrivateKey(String alias) {
PrivateKey privKey = delegateKeyManager.getPrivateKey(alias);
return OpaqueProvider.wrapKey(privKey);
}
@Override
public String chooseEngineClientAlias(String[] keyType, Principal[] issuers,
SSLEngine engine) {
throw new AssertionError();
}
@Override
public String chooseEngineServerAlias(String keyType, Principal[] issuers,
SSLEngine engine) {
throw new AssertionError();
}
};
clientContext.init(
new KeyManager[] {keyManager}, new TrustManager[] {c.clientTrustManager}, null);
SSLSocket client =
(SSLSocket) clientContext.getSocketFactory().createSocket(c.host, c.port);
final SSLSocket server = (SSLSocket) c.serverSocket.accept();
Future<Void> future = runAsync(new Callable<Void>() {
@Override
public Void call() throws Exception {
server.setNeedClientAuth(true);
server.startHandshake();
return null;
}
});
client.startHandshake();
assertNotNull(client.getSession().getLocalCertificates());
TestKeyStore.assertChainLength(client.getSession().getLocalCertificates());
TestSSLContext.assertClientCertificateChain(
c.clientTrustManager, client.getSession().getLocalCertificates());
future.get();
client.close();
server.close();
c.close();
} finally {
Security.removeProvider(OpaqueProvider.NAME);
}
}
@Test
public void test_SSLSocket_TrustManagerRuntimeException() throws Exception {
final TestSSLContext c = new TestSSLContext.Builder()
.clientProtocol(clientVersion)
.serverProtocol(serverVersion)
.build();
SSLContext clientContext = SSLContext.getInstance("TLS");
X509TrustManager trustManager = new X509TrustManager() {
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType)
throws CertificateException {
throw new AssertionError();
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType)
throws CertificateException {
throw new RuntimeException(); // throw a RuntimeException from custom TrustManager
}
@Override
public X509Certificate[] getAcceptedIssuers() {
throw new AssertionError();
}
};
clientContext.init(null, new TrustManager[] {trustManager}, null);
SSLSocket client =
(SSLSocket) clientContext.getSocketFactory().createSocket(c.host, c.port);
final SSLSocket server = (SSLSocket) c.serverSocket.accept();
Future<Void> future = runAsync(new Callable<Void>() {
@Override
public Void call() throws Exception {
try {
server.startHandshake();
fail();
} catch (SSLHandshakeException expected) {
// Ignored.
}
return null;
}
});
try {
client.startHandshake();
fail();
} catch (SSLHandshakeException expected) {
// before we would get a RuntimeException from checkServerTrusted.
}
future.get();
client.close();
server.close();
c.close();
}
@Test
public void test_SSLSocket_getEnableSessionCreation() throws Exception {
final TestSSLContext c = new TestSSLContext.Builder()
.clientProtocol(clientVersion)
.serverProtocol(serverVersion)
.build();
SSLSocket client =
(SSLSocket) c.clientContext.getSocketFactory().createSocket(c.host, c.port);
SSLSocket server = (SSLSocket) c.serverSocket.accept();
assertTrue(client.getEnableSessionCreation());
assertTrue(server.getEnableSessionCreation());
client.close();
server.close();
c.close();
}
@Test
public void test_SSLSocket_setEnableSessionCreation_server() throws Exception {
final TestSSLContext c = new TestSSLContext.Builder()
.clientProtocol(clientVersion)
.serverProtocol(serverVersion)
.build();
SSLSocket client =
(SSLSocket) c.clientContext.getSocketFactory().createSocket(c.host, c.port);
final SSLSocket server = (SSLSocket) c.serverSocket.accept();
Future<Void> future = runAsync(new Callable<Void>() {
@Override
public Void call() throws Exception {
server.setEnableSessionCreation(false);
try {
server.startHandshake();
fail();
} catch (SSLException expected) {
// Ignored.
}
return null;
}
});
try {
client.startHandshake();
fail();
} catch (SSLException expected) {
// Ignored.
}
future.get();
client.close();
server.close();
c.close();
}
@Test
public void test_SSLSocket_setEnableSessionCreation_client() throws Exception {
final TestSSLContext c = new TestSSLContext.Builder()
.clientProtocol(clientVersion)
.serverProtocol(serverVersion)
.build();
SSLSocket client =
(SSLSocket) c.clientContext.getSocketFactory().createSocket(c.host, c.port);
final SSLSocket server = (SSLSocket) c.serverSocket.accept();
Future<Void> future = runAsync(new Callable<Void>() {
@Override
public Void call() throws Exception {
try {
server.startHandshake();
fail();
} catch (SSLException expected) {
// Ignored.
}
return null;
}
});
client.setEnableSessionCreation(false);
try {
client.startHandshake();
fail();
} catch (SSLException expected) {
// Ignored.
}
future.get();
client.close();
server.close();
c.close();
}
@Test
public void test_SSLSocket_close() throws Exception {
final TestSSLContext c = new TestSSLContext.Builder()
.clientProtocol(clientVersion)
.serverProtocol(serverVersion)
.build();
TestSSLSocketPair pair = TestSSLSocketPair.create(c).connect();
SSLSocket server = pair.server;
SSLSocket client = pair.client;
assertFalse(server.isClosed());
assertFalse(client.isClosed());
InputStream input = client.getInputStream();
OutputStream output = client.getOutputStream();
server.close();
client.close();
assertTrue(server.isClosed());
assertTrue(client.isClosed());
// close after close is okay...
server.close();
client.close();
// ...so are a lot of other operations...
HandshakeCompletedListener l = new HandshakeCompletedListener() {
@Override
public void handshakeCompleted(HandshakeCompletedEvent e) {
}
};
client.addHandshakeCompletedListener(l);
assertNotNull(client.getEnabledCipherSuites());
assertNotNull(client.getEnabledProtocols());
client.getEnableSessionCreation();
client.getNeedClientAuth();
assertNotNull(client.getSession());
assertNotNull(client.getSSLParameters());
assertNotNull(client.getSupportedProtocols());
client.getUseClientMode();
client.getWantClientAuth();
client.removeHandshakeCompletedListener(l);
client.setEnabledCipherSuites(new String[0]);
client.setEnabledProtocols(new String[0]);
client.setEnableSessionCreation(false);
client.setNeedClientAuth(false);
client.setSSLParameters(client.getSSLParameters());
client.setWantClientAuth(false);
// ...but some operations are expected to give SocketException...
try {
client.startHandshake();
fail();
} catch (SocketException expected) {
// Ignored.
}
try {
client.getInputStream();
fail();
} catch (SocketException expected) {
// Ignored.
}
try {
client.getOutputStream();
fail();
} catch (SocketException expected) {
// Ignored.
}
try {
@SuppressWarnings("unused")
int value = input.read();
fail();
} catch (SocketException expected) {
// Ignored.
}
try {
@SuppressWarnings("unused")
int bytesRead = input.read(null, -1, -1);
fail();
} catch (NullPointerException expected) {
// Ignored.
} catch (SocketException expected) {
// Ignored.
}
try {
output.write(-1);
fail();
} catch (SocketException expected) {
// Ignored.
}
try {
output.write(null, -1, -1);
fail();
} catch (NullPointerException expected) {
// Ignored.
} catch (SocketException expected) {
// Ignored.
}
// ... and one gives IllegalArgumentException
try {
client.setUseClientMode(false);
fail();
} catch (IllegalArgumentException expected) {
// Ignored.
}
pair.close();
}
@Test
public void test_SSLSocket_ShutdownInput() throws Exception {
// Fdsocket throws SslException rather than returning EOF after input shutdown
// on Windows, but we won't be fixing it as that implementation is already deprecated.
assumeFalse("Skipping shutdownInput() test on Windows", isWindows());
final TestSSLContext c = new TestSSLContext.Builder()
.clientProtocol(clientVersion)
.serverProtocol(serverVersion)
.build();
byte[] buffer = new byte[1];
TestSSLSocketPair pair = TestSSLSocketPair.create(c).connect();
SSLSocket server = pair.server;
SSLSocket client = pair.client;
assertFalse(server.isClosed());
assertFalse(client.isClosed());
InputStream input = client.getInputStream();
client.shutdownInput();
assertFalse(client.isClosed());
assertFalse(server.isClosed());
// Shutdown after shutdown is not OK
SocketException exception = assertThrows(SocketException.class, client::shutdownInput);
assertTrue(exception.getMessage().contains("already shutdown"));
// The following operations should succeed, same as after close()
HandshakeCompletedListener listener = e -> { };
client.addHandshakeCompletedListener(listener);
assertNotNull(client.getEnabledCipherSuites());
assertNotNull(client.getEnabledProtocols());
client.getEnableSessionCreation();
client.getNeedClientAuth();
assertNotNull(client.getSession());
assertNotNull(client.getSSLParameters());
assertNotNull(client.getSupportedProtocols());
client.getUseClientMode();
client.getWantClientAuth();
client.removeHandshakeCompletedListener(listener);
client.setEnabledCipherSuites(new String[0]);
client.setEnabledProtocols(new String[0]);
client.setEnableSessionCreation(false);
client.setNeedClientAuth(false);
client.setSSLParameters(client.getSSLParameters());
client.setWantClientAuth(false);
// The following operations should succeed, unlike after close()
client.startHandshake();
client.getInputStream();
client.getOutputStream();
assertEquals(-1, input.read());
assertEquals(-1, input.read(buffer));
assertEquals(0, input.available());
pair.close();
}
@Test
public void test_SSLSocket_ShutdownOutput() throws Exception {
final TestSSLContext c = new TestSSLContext.Builder()
.clientProtocol(clientVersion)
.serverProtocol(serverVersion)
.build();
byte[] buffer = new byte[1];
TestSSLSocketPair pair = TestSSLSocketPair.create(c).connect();
SSLSocket server = pair.server;
SSLSocket client = pair.client;
assertFalse(server.isClosed());
assertFalse(client.isClosed());
OutputStream output = client.getOutputStream();
client.shutdownOutput();
assertFalse(client.isClosed());
assertFalse(server.isClosed());
// Shutdown after shutdown is not OK
SocketException exception = assertThrows(SocketException.class, client::shutdownOutput);
assertTrue(exception.getMessage().contains("already shutdown"));
// The following operations should succeed, same as after close()
HandshakeCompletedListener listener = e -> { };
client.addHandshakeCompletedListener(listener);
assertNotNull(client.getEnabledCipherSuites());
assertNotNull(client.getEnabledProtocols());
client.getEnableSessionCreation();
client.getNeedClientAuth();
assertNotNull(client.getSession());
assertNotNull(client.getSSLParameters());
assertNotNull(client.getSupportedProtocols());
client.getUseClientMode();
client.getWantClientAuth();
client.removeHandshakeCompletedListener(listener);
client.setEnabledCipherSuites(new String[0]);
client.setEnabledProtocols(new String[0]);
client.setEnableSessionCreation(false);
client.setNeedClientAuth(false);
client.setSSLParameters(client.getSSLParameters());
client.setWantClientAuth(false);
// The following operations should succeed, unlike after close()
client.startHandshake();
client.getInputStream();
client.getOutputStream();
// Any output should fail
try {
output.write(buffer);
fail();
} catch (SocketException | SSLException expected) {
// Expected.
// SocketException is correct but the old fd-based implementation
// throws SSLException, and it's not worth changing it at this late stage.
}
pair.close();
}
/**
* b/3350645 Test to confirm that an SSLSocket.close() performing
* an SSL_shutdown does not throw an IOException if the peer
* socket has been closed.
*/
@Test
public void test_SSLSocket_shutdownCloseOnClosedPeer() throws Exception {
final TestSSLContext c = new TestSSLContext.Builder()
.clientProtocol(clientVersion)
.serverProtocol(serverVersion)
.build();
final Socket underlying = new Socket(c.host, c.port);
final SSLSocket wrapping = (SSLSocket) c.clientContext.getSocketFactory().createSocket(
underlying, c.host.getHostName(), c.port, false);
Future<Void> clientFuture = runAsync(new Callable<Void>() {
@Override
public Void call() throws Exception {
wrapping.startHandshake();
wrapping.getOutputStream().write(42);
// close the underlying socket,
// so that no SSL shutdown is sent
underlying.close();
wrapping.close();
return null;
}
});
SSLSocket server = (SSLSocket) c.serverSocket.accept();
server.startHandshake();
@SuppressWarnings("unused")
int value = server.getInputStream().read();
// wait for thread to finish so we know client is closed.
clientFuture.get();
// close should cause an SSL_shutdown which will fail
// because the peer has closed, but it shouldn't throw.
server.close();
}
@Test
public void test_SSLSocket_endpointIdentification_Success() throws Exception {
TestUtils.assumeSetEndpointIdentificationAlgorithmAvailable();
// The default hostname verifier on OpenJDK just rejects all hostnames,
// which is not helpful, so replace with a basic functional one.
HostnameVerifier oldDefault = HttpsURLConnection.getDefaultHostnameVerifier();
HttpsURLConnection.setDefaultHostnameVerifier(new TestHostnameVerifier());
try {
final TestSSLContext c = new TestSSLContext.Builder()
.clientProtocol(clientVersion)
.serverProtocol(serverVersion)
.build();
SSLSocket client = (SSLSocket) c.clientContext.getSocketFactory().createSocket();
SSLParameters p = client.getSSLParameters();
p.setEndpointIdentificationAlgorithm("HTTPS");
client.setSSLParameters(p);
client.connect(new InetSocketAddress(c.host, c.port));
final SSLSocket server = (SSLSocket) c.serverSocket.accept();
Future<Void> future = runAsync(new Callable<Void>() {
@Override
public Void call() throws Exception {
server.startHandshake();
assertNotNull(server.getSession());
try {
server.getSession().getPeerCertificates();
fail();
} catch (SSLPeerUnverifiedException expected) {
// Ignored.
}
Certificate[] localCertificates = server.getSession().getLocalCertificates();
assertNotNull(localCertificates);
TestKeyStore.assertChainLength(localCertificates);
assertNotNull(localCertificates[0]);
TestSSLContext
.assertCertificateInKeyStore(localCertificates[0], c.serverKeyStore);
return null;
}
});
client.startHandshake();
assertNotNull(client.getSession());
assertNull(client.getSession().getLocalCertificates());
Certificate[] peerCertificates = client.getSession().getPeerCertificates();
assertNotNull(peerCertificates);
TestKeyStore.assertChainLength(peerCertificates);
assertNotNull(peerCertificates[0]);
TestSSLContext.assertCertificateInKeyStore(peerCertificates[0], c.serverKeyStore);
future.get();
client.close();
server.close();
c.close();
} finally {
HttpsURLConnection.setDefaultHostnameVerifier(oldDefault);
}
}
@Test
public void test_SSLSocket_endpointIdentification_Failure() throws Exception {
TestUtils.assumeSetEndpointIdentificationAlgorithmAvailable();
// The default hostname verifier on OpenJDK just rejects all hostnames,
// which is not helpful, so replace with a basic functional one.
HostnameVerifier oldDefault = HttpsURLConnection.getDefaultHostnameVerifier();
HttpsURLConnection.setDefaultHostnameVerifier(new TestHostnameVerifier());
try {
final TestSSLContext c = new TestSSLContext.Builder()
.clientProtocol(clientVersion)
.serverProtocol(serverVersion)
.build();
SSLSocket client = (SSLSocket) c.clientContext.getSocketFactory().createSocket();
SSLParameters p = client.getSSLParameters();
p.setEndpointIdentificationAlgorithm("HTTPS");
client.setSSLParameters(p);
client.connect(c.getLoopbackAsHostname("unmatched.example.com", c.port));
final SSLSocket server = (SSLSocket) c.serverSocket.accept();
Future<Void> future = runAsync(new Callable<Void>() {
@Override
public Void call() throws Exception {
try {
server.startHandshake();
fail("Should receive SSLHandshakeException as server");
} catch (SSLHandshakeException expected) {
// Ignored.
}
return null;
}
});
try {
client.startHandshake();
fail("Should throw when hostname does not match expected");
} catch (SSLHandshakeException expected) {
// Ignored.
} finally {
try {
future.get();
} finally {
client.close();
server.close();
c.close();
}
}
} finally {
HttpsURLConnection.setDefaultHostnameVerifier(oldDefault);
}
}
@Test(expected = SocketTimeoutException.class)
public void test_SSLSocket_setSoWriteTimeout() throws Exception {
// Only run this test on Linux since it relies on non-posix methods.
assumeTrue("Test only runs on Linux. Current OS: " + osName(), isLinux());
// In jb-mr2 it was found that we need to also set SO_RCVBUF
// to a minimal size or the write would not block.
final int receiveBufferSize = 128;
TestSSLContext c = TestSSLContext.newBuilder()
.serverReceiveBufferSize(receiveBufferSize)
.clientProtocol(clientVersion)
.serverProtocol(serverVersion)
.build();
final SSLSocket client =
(SSLSocket) c.clientContext.getSocketFactory().createSocket(c.host, c.port);
// Try to make the client SO_SNDBUF size as small as possible
// (it can default to 512k or even megabytes). Note that
// socket(7) says that the kernel will double the request to
// leave room for its own book keeping and that the minimal
// value will be 2048. Also note that tcp(7) says the value
// needs to be set before connect(2).
int sendBufferSize = 1024;
client.setSendBufferSize(sendBufferSize);
sendBufferSize = client.getSendBufferSize();
// Start the handshake.
final SSLSocket server = (SSLSocket) c.serverSocket.accept();
Future<Void> future = runAsync(new Callable<Void>() {
@Override
public Void call() throws Exception {
client.startHandshake();
return null;
}
});
server.startHandshake();
assertTrue(isConscryptSocket(client));
// The concrete class that Conscrypt returns has methods on it that have no
// equivalent on the public API (like setSoWriteTimeout), so users have
// previously used reflection to access those otherwise-inaccessible methods
// on that class. The concrete class used to be named OpenSSLSocketImpl, so
// check that OpenSSLSocketImpl is still in the class hierarchy so applications
// that rely on getting that class back still work.
Class<?> superClass = client.getClass();
do {
superClass = superClass.getSuperclass();
} while (superClass != Object.class && !superClass.getName().endsWith("OpenSSLSocketImpl"));
assertEquals("OpenSSLSocketImpl", superClass.getSimpleName());
try {
setWriteTimeout(client, 1);
// Add extra space to the write to exceed the send buffer
// size and cause the write to block.
final int extra = 1;
client.getOutputStream().write(new byte[sendBufferSize + extra]);
} finally {
future.get();
client.close();
server.close();
c.close();
}
}
@Test
public void test_SSLSocket_reusedNpnSocket() throws Exception {
byte[] npnProtocols = new byte[] {
8, 'h', 't', 't', 'p', '/', '1', '.', '1'
};
final TestSSLContext c = new TestSSLContext.Builder()
.clientProtocol(clientVersion)
.serverProtocol(serverVersion)
.build();
SSLSocket client = (SSLSocket) c.clientContext.getSocketFactory().createSocket();
assertTrue(isConscryptSocket(client));
Class<?> actualClass = client.getClass();
Method setNpnProtocols = actualClass.getMethod("setNpnProtocols", byte[].class);
ExecutorService executor = Executors.newSingleThreadExecutor();
// First connection with NPN set on client and server
{
setNpnProtocols.invoke(client, npnProtocols);
client.connect(new InetSocketAddress(c.host, c.port));
final SSLSocket server = (SSLSocket) c.serverSocket.accept();
assertTrue(isConscryptSocket(server));
setNpnProtocols.invoke(server, npnProtocols);
Future<Void> future = executor.submit(new Callable<Void>() {
@Override
public Void call() throws Exception {
server.startHandshake();
return null;
}
});
client.startHandshake();
future.get();
client.close();
server.close();
}
// Second connection with client NPN already set on the SSL context, but
// without server NPN set.
{
SSLServerSocket serverSocket = (SSLServerSocket) c.serverContext
.getServerSocketFactory().createServerSocket(0);
InetAddress host = InetAddress.getLocalHost();
int port = serverSocket.getLocalPort();
client = (SSLSocket) c.clientContext.getSocketFactory().createSocket();
client.connect(new InetSocketAddress(host, port));
final SSLSocket server = (SSLSocket) serverSocket.accept();
Future<Void> future = executor.submit(new Callable<Void>() {
@Override
public Void call() throws Exception {
server.startHandshake();
return null;
}
});
client.startHandshake();
future.get();
client.close();
server.close();
serverSocket.close();
}
c.close();
}
// TODO(nmittler): Conscrypt socket read may return -1 instead of SocketException.
@Test
public void test_SSLSocket_interrupt_readUnderlyingAndCloseUnderlying() throws Exception {
test_SSLSocket_interrupt_case(true, true);
}
// TODO(nmittler): Conscrypt socket read may return -1 instead of SocketException.
@Test
public void test_SSLSocket_interrupt_readUnderlyingAndCloseWrapper() throws Exception {
test_SSLSocket_interrupt_case(true, false);
}
// TODO(nmittler): FD socket gets stuck in read on Windows and OSX.
@Test
public void test_SSLSocket_interrupt_readWrapperAndCloseUnderlying() throws Exception {
test_SSLSocket_interrupt_case(false, true);
}
// TODO(nmittler): Conscrypt socket read may return -1 instead of SocketException.
@Test
public void test_SSLSocket_interrupt_readWrapperAndCloseWrapper() throws Exception {
test_SSLSocket_interrupt_case(false, false);
}
private void test_SSLSocket_interrupt_case(boolean readUnderlying, boolean closeUnderlying)
throws Exception {
final int readingTimeoutMillis = 5000;
final TestSSLContext c = new TestSSLContext.Builder()
.clientProtocol(clientVersion)
.serverProtocol(serverVersion)
.build();
final Socket underlying = new Socket(c.host, c.port);
final SSLSocket clientWrapping =
(SSLSocket) c.clientContext.getSocketFactory().createSocket(
underlying, c.host.getHostName(), c.port, true);
if (isConscryptFdSocket(clientWrapping) && !readUnderlying && closeUnderlying) {
// TODO(nmittler): FD socket gets stuck in the read on Windows and OSX.
assumeFalse("Skipping interrupt test on Windows", isWindows());
assumeFalse("Skipping interrupt test on OSX", isOsx());
}
SSLSocket server = (SSLSocket) c.serverSocket.accept();
// Start the handshake.
Future<Integer> handshakeFuture = runAsync(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
clientWrapping.startHandshake();
return clientWrapping.getInputStream().read();
}
});
server.startHandshake();
// TLS 1.3 sends some post-handshake management messages, so send a single byte through
// to process through those messages.
server.getOutputStream().write(42);
assertEquals(42, handshakeFuture.get().intValue());
final Socket toRead = readUnderlying ? underlying : clientWrapping;
final Socket toClose = closeUnderlying ? underlying : clientWrapping;
// Schedule the socket to be closed in 1 second.
Future<Void> future = runAsync(new Callable<Void>() {
@Override
public Void call() throws Exception {
Thread.sleep(1000);
toClose.close();
return null;
}
});
// Read from the socket.
try {
toRead.setSoTimeout(readingTimeoutMillis);
int read = toRead.getInputStream().read();
// In the case of reading the wrapper and closing the underlying socket,
// there is a race condition between the reading thread being woken and
// reading the socket again and the closing thread marking the file descriptor
// as invalid. If the latter happens first, a SocketException is thrown,
// but if the former happens first it just looks like the peer closed the
// connection and a -1 return is acceptable.
if (read != -1 || readUnderlying || !closeUnderlying) {
fail();
}
} catch (SocketTimeoutException e) {
throw e;
} catch (IOException expected) {
// Expected
}
future.get();
server.close();
underlying.close();
server.close();
}
/**
* b/7014266 Test to confirm that an SSLSocket.close() on one
* thread will interrupt another thread blocked reading on the same
* socket.
*/
// TODO(nmittler): Interrupts do not work with the engine-based socket.
@Test
public void test_SSLSocket_interrupt_read_withoutAutoClose() throws Exception {
final int readingTimeoutMillis = 5000;
final TestSSLContext c = new TestSSLContext.Builder()
.clientProtocol(clientVersion)
.serverProtocol(serverVersion)
.build();
final Socket underlying = new Socket(c.host, c.port);
final SSLSocket wrapping = (SSLSocket) c.clientContext.getSocketFactory().createSocket(
underlying, c.host.getHostName(), c.port, false);
// TODO(nmittler): Interrupts do not work with the engine-based socket.
assumeFalse(isConscryptEngineSocket(wrapping));
Future<Void> clientFuture = runAsync(new Callable<Void>() {
@Override
public Void call() throws Exception {
wrapping.startHandshake();
try {
wrapping.setSoTimeout(readingTimeoutMillis);
wrapping.getInputStream().read();
fail();
} catch (SocketException expected) {
// Conscrypt throws an exception complaining that the socket is closed
// if it's interrupted by a close() in the middle of a read()
assertTrue(expected.getMessage().contains("closed"));
}
return null;
}
});
SSLSocket server = (SSLSocket) c.serverSocket.accept();
server.startHandshake();
// Wait for the client to at least be in the "read" method before calling close()
Thread[] threads = new Thread[1];
threadGroup.enumerate(threads);
if (threads[0] != null) {
boolean clientInRead = false;
while (!clientInRead) {
StackTraceElement[] elements = threads[0].getStackTrace();
for (StackTraceElement element : elements) {
if ("read".equals(element.getMethodName())) {
// The client might be executing "read" but still not have reached the
// point in which it's blocked reading. This is causing flakiness
// (b/24367646). Delaying for a fraction of the timeout.
Thread.sleep(1000);
clientInRead = true;
break;
}
}
}
}
wrapping.close();
clientFuture.get();
server.close();
}
/**
* Test to confirm that an SSLSocket.close() on one
* thread will interrupt another thread blocked writing on the same
* socket.
*
* Currently disabled: If the victim thread is not actually blocked in a write
* call then ConscryptEngineSocket can corrupt the output due to unsynchronized
* concurrent access to the socket's output stream and cause flakes: b/161347005
* TODO(prb): Re-enable after underlying bug resolved
*
* See also b/147323301 where close() triggered an infinite loop instead.
*/
@Test
@Ignore
public void test_SSLSocket_interrupt_write_withAutoclose() throws Exception {
final TestSSLContext c = new TestSSLContext.Builder()
.clientProtocol(clientVersion)
.serverProtocol(serverVersion)
.build();
final Socket underlying = new Socket(c.host, c.port);
final SSLSocket wrapping = (SSLSocket) c.clientContext.getSocketFactory().createSocket(
underlying, c.host.getHostName(), c.port, true);
final byte[] data = new byte[1024 * 64];
Future<Void> clientFuture = runAsync(new Callable<Void>() {
@Override
public Void call() throws Exception {
wrapping.startHandshake();
try {
for (int i = 0; i < 64; i++) {
wrapping.getOutputStream().write(data);
}
// Failure here means that no exception was thrown, so the data buffer is
// probably too small.
fail();
} catch (SocketException expected) {
assertTrue(expected.getMessage().contains("closed"));
}
return null;
}
});
SSLSocket server = (SSLSocket) c.serverSocket.accept();
server.startHandshake();
// Read one byte so that both ends are in a fully connected state and data has
// started to flow, and then close the socket from this thread.
int unused = server.getInputStream().read();
wrapping.close();
clientFuture.get();
server.close();
}
@Test
public void test_SSLSocket_ClientHello_record_size() throws Exception {
// This test checks the size of ClientHello of the default SSLSocket. TLS/SSL handshakes
// with older/unpatched F5/BIG-IP appliances are known to stall and time out when
// the fragment containing ClientHello is between 256 and 511 (inclusive) bytes long.
SSLContext sslContext = SSLContext.getInstance(clientVersion);
sslContext.init(null, null, null);
SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
sslSocketFactory = new DelegatingSSLSocketFactory(sslSocketFactory) {
@Override
protected SSLSocket configureSocket(SSLSocket socket) {
// Enable SNI extension on the socket (this is typically enabled by default)
// to increase the size of ClientHello.
setHostname(socket);
// Enable Session Tickets extension on the socket (this is typically enabled
// by default) to increase the size of ClientHello.
enableSessionTickets(socket);
return socket;
}
};
TlsRecord firstReceivedTlsRecord = TlsTester.captureTlsHandshakeFirstTlsRecord(executor, sslSocketFactory);
assertEquals("TLS record type", TlsProtocols.HANDSHAKE, firstReceivedTlsRecord.type);
HandshakeMessage handshakeMessage = HandshakeMessage.read(
new DataInputStream(new ByteArrayInputStream(firstReceivedTlsRecord.fragment)));
assertEquals(
"HandshakeMessage type", HandshakeMessage.TYPE_CLIENT_HELLO, handshakeMessage.type);
int fragmentLength = firstReceivedTlsRecord.fragment.length;
if ((fragmentLength >= 256) && (fragmentLength <= 511)) {
fail("Fragment containing ClientHello is of dangerous length: " + fragmentLength
+ " bytes");
}
}
@Test
public void test_SSLSocket_ClientHello_SNI() throws Exception {
ForEachRunner.runNamed(new ForEachRunner.Callback<SSLSocketFactory>() {
@Override
public void run(SSLSocketFactory sslSocketFactory) throws Exception {
ClientHello clientHello = TlsTester
.captureTlsHandshakeClientHello(executor, sslSocketFactory);
ServerNameHelloExtension sniExtension =
(ServerNameHelloExtension) clientHello.findExtensionByType(
HelloExtension.TYPE_SERVER_NAME);
assertNotNull(sniExtension);
assertEquals(
Collections.singletonList("localhost.localdomain"), sniExtension.hostnames);
}
}, getSSLSocketFactoriesToTest());
}
@Test
public void test_SSLSocket_ClientHello_ALPN() throws Exception {
final String[] protocolList = new String[] { "h2", "http/1.1" };
ForEachRunner.runNamed(new ForEachRunner.Callback<SSLSocketFactory>() {
@Override
public void run(SSLSocketFactory sslSocketFactory) throws Exception {
ClientHello clientHello = TlsTester.captureTlsHandshakeClientHello(executor,
new DelegatingSSLSocketFactory(sslSocketFactory) {
@Override public SSLSocket configureSocket(SSLSocket socket) {
Conscrypt.setApplicationProtocols(socket, protocolList);
return socket;
}
});
AlpnHelloExtension alpnExtension =
(AlpnHelloExtension) clientHello.findExtensionByType(
HelloExtension.TYPE_APPLICATION_LAYER_PROTOCOL_NEGOTIATION);
assertNotNull(alpnExtension);
assertEquals(Arrays.asList(protocolList), alpnExtension.protocols);
}
}, getSSLSocketFactoriesToTest());
}
private List<Pair<String, SSLSocketFactory>> getSSLSocketFactoriesToTest()
throws NoSuchAlgorithmException, KeyManagementException {
List<Pair<String, SSLSocketFactory>> result =
new ArrayList<Pair<String, SSLSocketFactory>>();
result.add(Pair.of("default", (SSLSocketFactory) SSLSocketFactory.getDefault()));
for (String sslContextProtocol : StandardNames.SSL_CONTEXT_PROTOCOLS) {
SSLContext sslContext = SSLContext.getInstance(sslContextProtocol);
if (StandardNames.SSL_CONTEXT_PROTOCOLS_DEFAULT.equals(sslContextProtocol)) {
continue;
}
sslContext.init(null, null, null);
result.add(Pair.of("SSLContext(\"" + sslContext.getProtocol() + "\")",
sslContext.getSocketFactory()));
}
return result;
}
// http://b/18428603
@Test
public void test_SSLSocket_getPortWithSNI() throws Exception {
TestSSLContext context = new TestSSLContext.Builder()
.clientProtocol(clientVersion)
.serverProtocol(serverVersion)
.build();
SSLSocket client =
(SSLSocket) context.clientContext.getSocketFactory().createSocket();
try {
client.connect(new InetSocketAddress(context.host, context.port));
setHostname(client);
assertTrue(client.getPort() > 0);
} finally {
client.close();
context.close();
}
}
@Test
public void test_SSLSocket_SNIHostName() throws Exception {
TestUtils.assumeSNIHostnameAvailable();
final TestSSLContext c = new TestSSLContext.Builder()
.clientProtocol(clientVersion)
.serverProtocol(serverVersion)
.build();
final SSLSocket client = (SSLSocket) c.clientContext.getSocketFactory().createSocket();
SSLParameters clientParams = client.getSSLParameters();
clientParams.setServerNames(
Collections.singletonList((SNIServerName) new SNIHostName("www.example.com")));
client.setSSLParameters(clientParams);
SSLParameters serverParams = c.serverSocket.getSSLParameters();
serverParams.setSNIMatchers(
Collections.singletonList(SNIHostName.createSNIMatcher("www\\.example\\.com")));
c.serverSocket.setSSLParameters(serverParams);
client.connect(new InetSocketAddress(c.host, c.port));
final SSLSocket server = (SSLSocket) c.serverSocket.accept();
@SuppressWarnings("unused")
Future<?> future = runAsync(new Callable<Object>() {
@Override
public Object call() throws Exception {
client.startHandshake();
return null;
}
});
server.startHandshake();
SSLSession serverSession = server.getSession();
assertTrue(serverSession instanceof ExtendedSSLSession);
ExtendedSSLSession extendedServerSession = (ExtendedSSLSession) serverSession;
List<SNIServerName> requestedNames = extendedServerSession.getRequestedServerNames();
assertNotNull(requestedNames);
assertEquals(1, requestedNames.size());
SNIServerName serverName = requestedNames.get(0);
assertEquals(StandardConstants.SNI_HOST_NAME, serverName.getType());
assertTrue(serverName instanceof SNIHostName);
SNIHostName serverHostName = (SNIHostName) serverName;
assertEquals("www.example.com", serverHostName.getAsciiName());
}
@Test
public void test_SSLSocket_ClientGetsAlertDuringHandshake_HasGoodExceptionMessage()
throws Exception {
TestSSLContext context = new TestSSLContext.Builder()
.clientProtocol(clientVersion)
.serverProtocol(serverVersion)
.build();
final ServerSocket listener = ServerSocketFactory.getDefault().createServerSocket(0);
final SSLSocket client = (SSLSocket) context.clientContext.getSocketFactory().createSocket(
context.host, listener.getLocalPort());
final Socket server = listener.accept();
Future<Void> c = runAsync(new Callable<Void>() {
@Override
public Void call() throws Exception {
try {
client.startHandshake();
fail("Should receive handshake exception");
} catch (SSLHandshakeException expected) {
assertFalse(expected.getMessage().contains("SSL_ERROR_ZERO_RETURN"));
assertFalse(expected.getMessage().contains("You should never see this."));
}
return null;
}
});
Future<Void> s = runAsync(new Callable<Void>() {
@Override
public Void call() throws Exception {
// Wait until the client sends something.
byte[] scratch = new byte[8192];
@SuppressWarnings("unused")
int bytesRead = server.getInputStream().read(scratch);
// Write a bogus TLS alert:
// TLSv1.2 Record Layer: Alert (Level: Warning, Description: Protocol Version)
server.getOutputStream()
.write(new byte[]{0x15, 0x03, 0x03, 0x00, 0x02, 0x01, 0x46});
// TLSv1.2 Record Layer: Alert (Level: Warning, Description: Close Notify)
server.getOutputStream()
.write(new byte[]{0x15, 0x03, 0x03, 0x00, 0x02, 0x01, 0x00});
return null;
}
});
c.get(5, TimeUnit.SECONDS);
s.get(5, TimeUnit.SECONDS);
client.close();
server.close();
listener.close();
context.close();
}
@Test
public void test_SSLSocket_ServerGetsAlertDuringHandshake_HasGoodExceptionMessage()
throws Exception {
TestSSLContext context = new TestSSLContext.Builder()
.clientProtocol(clientVersion)
.serverProtocol(serverVersion)
.build();
final Socket client = SocketFactory.getDefault().createSocket(context.host, context.port);
final SSLSocket server = (SSLSocket) context.serverSocket.accept();
Future<Void> s = runAsync(new Callable<Void>() {
@Override
public Void call() throws Exception {
try {
server.startHandshake();
fail("Should receive handshake exception");
} catch (SSLHandshakeException expected) {
assertFalse(expected.getMessage().contains("SSL_ERROR_ZERO_RETURN"));
assertFalse(expected.getMessage().contains("You should never see this."));
}
return null;
}
});
Future<Void> c = runAsync(new Callable<Void>() {
@Override
public Void call() throws Exception {
// Send bogus ClientHello:
// TLSv1.2 Record Layer: Handshake Protocol: Client Hello
client.getOutputStream().write(new byte[]{
(byte) 0x16, (byte) 0x03, (byte) 0x01, (byte) 0x00, (byte) 0xb9,
(byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0xb5, (byte) 0x03,
(byte) 0x03, (byte) 0x5a, (byte) 0x31, (byte) 0xba, (byte) 0x44,
(byte) 0x24, (byte) 0xfd, (byte) 0xf0, (byte) 0x56, (byte) 0x46,
(byte) 0xea, (byte) 0xee, (byte) 0x1c, (byte) 0x62, (byte) 0x8f,
(byte) 0x18, (byte) 0x04, (byte) 0xbd, (byte) 0x1c, (byte) 0xbc,
(byte) 0xbf, (byte) 0x6d, (byte) 0x84, (byte) 0x12, (byte) 0xe9,
(byte) 0x94, (byte) 0xf5, (byte) 0x1c, (byte) 0x15, (byte) 0x3e,
(byte) 0x79, (byte) 0x01, (byte) 0xe2, (byte) 0x00, (byte) 0x00,
(byte) 0x28, (byte) 0xc0, (byte) 0x2b, (byte) 0xc0, (byte) 0x2c,
(byte) 0xc0, (byte) 0x2f, (byte) 0xc0, (byte) 0x30, (byte) 0x00,
(byte) 0x9e, (byte) 0x00, (byte) 0x9f, (byte) 0xc0, (byte) 0x09,
(byte) 0xc0, (byte) 0x0a, (byte) 0xc0, (byte) 0x13, (byte) 0xc0,
(byte) 0x14, (byte) 0x00, (byte) 0x33, (byte) 0x00, (byte) 0x39,
(byte) 0xc0, (byte) 0x07, (byte) 0xc0, (byte) 0x11, (byte) 0x00,
(byte) 0x9c, (byte) 0x00, (byte) 0x9d, (byte) 0x00, (byte) 0x2f,
(byte) 0x00, (byte) 0x35, (byte) 0x00, (byte) 0x05, (byte) 0x00,
(byte) 0xff, (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x64,
(byte) 0x00, (byte) 0x0b, (byte) 0x00, (byte) 0x04, (byte) 0x03,
(byte) 0x00, (byte) 0x01, (byte) 0x02, (byte) 0x00, (byte) 0x0a,
(byte) 0x00, (byte) 0x34, (byte) 0x00, (byte) 0x32, (byte) 0x00,
(byte) 0x0e, (byte) 0x00, (byte) 0x0d, (byte) 0x00, (byte) 0x19,
(byte) 0x00, (byte) 0x0b, (byte) 0x00, (byte) 0x0c, (byte) 0x00,
(byte) 0x18, (byte) 0x00, (byte) 0x09, (byte) 0x00, (byte) 0x0a,
(byte) 0x00, (byte) 0x16, (byte) 0x00, (byte) 0x17, (byte) 0x00,
(byte) 0x08, (byte) 0x00, (byte) 0x06, (byte) 0x00, (byte) 0x07,
(byte) 0x00, (byte) 0x14, (byte) 0x00, (byte) 0x15, (byte) 0x00,
(byte) 0x04, (byte) 0x00, (byte) 0x05, (byte) 0x00, (byte) 0x12,
(byte) 0x00, (byte) 0x13, (byte) 0x00, (byte) 0x01, (byte) 0x00,
(byte) 0x02, (byte) 0x00, (byte) 0x03, (byte) 0x00, (byte) 0x0f,
(byte) 0x00, (byte) 0x10, (byte) 0x00, (byte) 0x11, (byte) 0x00,
(byte) 0x0d, (byte) 0x00, (byte) 0x20, (byte) 0x00, (byte) 0x1e,
(byte) 0x06, (byte) 0x01, (byte) 0x06, (byte) 0x02, (byte) 0x06,
(byte) 0x03, (byte) 0x05, (byte) 0x01, (byte) 0x05, (byte) 0x02,
(byte) 0x05, (byte) 0x03, (byte) 0x04, (byte) 0x01, (byte) 0x04,
(byte) 0x02, (byte) 0x04, (byte) 0x03, (byte) 0x03, (byte) 0x01,
(byte) 0x03, (byte) 0x02, (byte) 0x03, (byte) 0x03, (byte) 0x02,
(byte) 0x01, (byte) 0x02, (byte) 0x02, (byte) 0x02, (byte) 0x03,
});
// Wait until the server sends something.
byte[] scratch = new byte[8192];
@SuppressWarnings("unused")
int bytesRead = client.getInputStream().read(scratch);
// Write a bogus TLS alert:
// TLSv1.2 Record Layer: Alert (Level: Warning, Description:
// Protocol Version)
client.getOutputStream()
.write(new byte[]{0x15, 0x03, 0x03, 0x00, 0x02, 0x01, 0x46});
// TLSv1.2 Record Layer: Alert (Level: Warning, Description:
// Close Notify)
client.getOutputStream()
.write(new byte[]{0x15, 0x03, 0x03, 0x00, 0x02, 0x01, 0x00});
return null;
}
});
c.get(5, TimeUnit.SECONDS);
s.get(5, TimeUnit.SECONDS);
client.close();
server.close();
context.close();
}
@Test
public void test_SSLSocket_SSLv3Unsupported() throws Exception {
TestSSLContext context = new TestSSLContext.Builder()
.clientProtocol(clientVersion)
.serverProtocol(serverVersion)
.build();
final SSLSocket client =
(SSLSocket) context.clientContext.getSocketFactory().createSocket();
// For app compatibility, SSLv3 is stripped out when setting only.
client.setEnabledProtocols(new String[] {"SSLv3"});
assertEquals(0, client.getEnabledProtocols().length);
try {
client.setEnabledProtocols(new String[] {"SSL"});
fail("SSLSocket should not support SSL protocol");
} catch (IllegalArgumentException expected) {
// Ignored.
}
}
// Under some circumstances, the file descriptor socket may get finalized but still
// be reused by the JDK's built-in HTTP connection reuse code. Ensure that a
// SocketException is thrown if that happens.
@Test
public void test_SSLSocket_finalizeThrowsProperException() throws Exception {
TestSSLContext context = new TestSSLContext.Builder()
.clientProtocol(clientVersion)
.serverProtocol(serverVersion)
.build();
TestSSLSocketPair test = TestSSLSocketPair.create(context).connect();
try {
if (isConscryptFdSocket(test.client)) {
// The finalize method might be declared on a superclass rather than this
// class.
Method method = null;
Class<?> clazz = test.client.getClass();
while (clazz != null) {
try {
method = clazz.getDeclaredMethod("finalize");
break;
} catch (NoSuchMethodException e) {
// Try the superclass
}
clazz = clazz.getSuperclass();
}
assertNotNull(method);
method.setAccessible(true);
method.invoke(test.client);
try {
test.client.getOutputStream().write(new byte[] { 0x01 });
fail("The socket shouldn't work after being finalized");
} catch (SocketException expected) {
// Expected
}
}
} finally {
test.close();
}
}
@Test
public void test_SSLSocket_TlsUnique() throws Exception {
// tls_unique isn't supported in TLS 1.3
assumeTlsV1_2Connection();
TestSSLContext context = new TestSSLContext.Builder()
.clientProtocol(clientVersion)
.serverProtocol(serverVersion)
.build();
TestSSLSocketPair pair = TestSSLSocketPair.create(context);
try {
assertNull(Conscrypt.getTlsUnique(pair.client));
assertNull(Conscrypt.getTlsUnique(pair.server));
pair.connect();
byte[] clientTlsUnique = Conscrypt.getTlsUnique(pair.client);
byte[] serverTlsUnique = Conscrypt.getTlsUnique(pair.server);
assertNotNull(clientTlsUnique);
assertNotNull(serverTlsUnique);
assertArrayEquals(clientTlsUnique, serverTlsUnique);
} finally {
pair.close();
}
}
// Tests that all cipher suites have a 12-byte tls-unique channel binding value. If this
// test fails, that means some cipher suite has been added that uses a customized verify_data
// length and we need to update MAX_TLS_UNIQUE_LENGTH in native_crypto.cc to account for that.
@Test
public void test_SSLSocket_TlsUniqueLength() throws Exception {
// tls_unique isn't supported in TLS 1.3
assumeTlsV1_2Connection();
// note the rare usage of non-RSA keys
TestKeyStore testKeyStore = new TestKeyStore.Builder()
.keyAlgorithms("RSA", "DSA", "EC", "EC_RSA")
.aliasPrefix("rsa-dsa-ec")
.ca(true)
.build();
KeyManager pskKeyManager =
PSKKeyManagerProxy.getConscryptPSKKeyManager(new PSKKeyManagerProxy() {
@Override
protected SecretKey getKey(
String identityHint, String identity, Socket socket) {
return newKey();
}
@Override
protected SecretKey getKey(
String identityHint, String identity, SSLEngine engine) {
return newKey();
}
private SecretKey newKey() {
return new SecretKeySpec("Just an arbitrary key".getBytes(UTF_8), "RAW");
}
});
TestSSLContext c = TestSSLContext.newBuilder()
.client(testKeyStore)
.server(testKeyStore)
.clientProtocol(clientVersion)
.serverProtocol(serverVersion)
.additionalClientKeyManagers(new KeyManager[] {pskKeyManager})
.additionalServerKeyManagers(new KeyManager[] {pskKeyManager})
.build();
for (String cipherSuite : c.clientContext.getSocketFactory().getSupportedCipherSuites()) {
if (cipherSuite.equals(StandardNames.CIPHER_SUITE_FALLBACK)
|| cipherSuite.equals(StandardNames.CIPHER_SUITE_SECURE_RENEGOTIATION)) {
continue;
}
/*
* tls_unique only works on 1.2, so skip TLS 1.3 cipher suites.
*/
if (StandardNames.CIPHER_SUITES_TLS13.contains(cipherSuite)) {
continue;
}
TestSSLSocketPair pair = TestSSLSocketPair.create(c);
try {
String[] cipherSuites =
new String[] {cipherSuite, StandardNames.CIPHER_SUITE_SECURE_RENEGOTIATION};
pair.connect(cipherSuites, cipherSuites);
assertEquals(cipherSuite, pair.client.getSession().getCipherSuite());
byte[] clientTlsUnique = Conscrypt.getTlsUnique(pair.client);
byte[] serverTlsUnique = Conscrypt.getTlsUnique(pair.server);
assertNotNull(clientTlsUnique);
assertNotNull(serverTlsUnique);
assertArrayEquals(clientTlsUnique, serverTlsUnique);
assertEquals(12, clientTlsUnique.length);
} catch (Exception e) {
throw new AssertionError("Cipher suite is " + cipherSuite, e);
} finally {
pair.client.close();
pair.server.close();
}
}
}
@Test
public void test_SSLSocket_EKM() throws Exception {
TestSSLContext context = new TestSSLContext.Builder()
.clientProtocol(clientVersion)
.serverProtocol(serverVersion)
.build();
TestSSLSocketPair pair = TestSSLSocketPair.create(context);
try {
// No EKM values available before handshaking
assertNull(Conscrypt.exportKeyingMaterial(pair.client, "FOO", null, 20));
assertNull(Conscrypt.exportKeyingMaterial(pair.server, "FOO", null, 20));
pair.connect();
byte[] clientEkm = Conscrypt.exportKeyingMaterial(pair.client, "FOO", null, 20);
byte[] serverEkm = Conscrypt.exportKeyingMaterial(pair.server, "FOO", null, 20);
assertNotNull(clientEkm);
assertNotNull(serverEkm);
assertEquals(20, clientEkm.length);
assertEquals(20, serverEkm.length);
assertArrayEquals(clientEkm, serverEkm);
byte[] clientContextEkm = Conscrypt.exportKeyingMaterial(
pair.client, "FOO", new byte[0], 20);
byte[] serverContextEkm = Conscrypt.exportKeyingMaterial(
pair.server, "FOO", new byte[0], 20);
assertNotNull(clientContextEkm);
assertNotNull(serverContextEkm);
assertEquals(20, clientContextEkm.length);
assertEquals(20, serverContextEkm.length);
assertArrayEquals(clientContextEkm, serverContextEkm);
// In TLS 1.2, an empty context and a null context are different (RFC 5705, section 4),
// but in TLS 1.3 they are the same (RFC 8446, section 7.5).
if ("TLSv1.2".equals(negotiatedVersion())) {
assertFalse(Arrays.equals(clientEkm, clientContextEkm));
} else {
assertTrue(Arrays.equals(clientEkm, clientContextEkm));
}
} finally {
pair.close();
}
}
// Tests that a socket will close cleanly even if it fails to create due to an
// internal IOException
@Test
public void test_SSLSocket_CloseCleanlyOnConstructorFailure() throws Exception {
TestSSLContext c = new TestSSLContext.Builder()
.clientProtocol(clientVersion)
.serverProtocol(serverVersion)
.build();
try {
c.clientContext.getSocketFactory().createSocket(c.host, 1);
fail();
} catch (ConnectException ignored) {
// Ignored.
}
}
private static void setWriteTimeout(Object socket, int timeout) {
Exception ex = null;
try {
Method method = socket.getClass().getMethod("setSoWriteTimeout", int.class);
method.setAccessible(true);
method.invoke(socket, timeout);
} catch (Exception e) {
ex = e;
}
// Engine-based socket currently has the method but throws UnsupportedOperationException.
assumeNoException("Client socket does not support setting write timeout", ex);
}
private static void setHostname(SSLSocket socket) {
try {
Method method = socket.getClass().getMethod("setHostname", String.class);
method.setAccessible(true);
method.invoke(socket, "sslsockettest.androidcts.google.com");
} catch (NoSuchMethodException ignored) {
// Ignored.
} catch (Exception e) {
throw new RuntimeException("Failed to enable SNI", e);
}
}
private static void enableSessionTickets(SSLSocket socket) {
try {
Method method =
socket.getClass().getMethod("setUseSessionTickets", boolean.class);
method.setAccessible(true);
method.invoke(socket, true);
} catch (NoSuchMethodException ignored) {
// Ignored.
} catch (Exception e) {
throw new RuntimeException("Failed to enable Session Tickets", e);
}
}
private static boolean isConscryptSocket(SSLSocket socket) {
return isConscryptFdSocket(socket) || isConscryptEngineSocket(socket);
}
private static boolean isConscryptFdSocket(SSLSocket socket) {
Class<?> clazz = socket.getClass();
while (clazz != Object.class && !"ConscryptFileDescriptorSocket".equals(clazz.getSimpleName())) {
clazz = clazz.getSuperclass();
}
return "ConscryptFileDescriptorSocket".equals(clazz.getSimpleName());
}
private static boolean isConscryptEngineSocket(SSLSocket socket) {
Class<?> clazz = socket.getClass();
while (clazz != Object.class && !"ConscryptEngineSocket".equals(clazz.getSimpleName())) {
clazz = clazz.getSuperclass();
}
return "ConscryptEngineSocket".equals(clazz.getSimpleName());
}
private <T> Future<T> runAsync(Callable<T> callable) {
return executor.submit(callable);
}
private static SSLContext defaultInit(SSLContext context) throws KeyManagementException {
context.init(null, null, null);
return context;
}
// Assumes that the negotiated connection will be TLS 1.2
private void assumeTlsV1_2Connection() {
assumeTrue("TLSv1.2".equals(negotiatedVersion()));
}
/**
* Returns the version that a connection between {@code clientVersion} and
* {@code serverVersion} should produce.
*/
private String negotiatedVersion() {
if (clientVersion.equals("TLSv1.3") && serverVersion.equals("TLSv1.3")) {
return "TLSv1.3";
} else {
return "TLSv1.2";
}
}
}