diff options
author | Alex Klyubin <klyubin@google.com> | 2014-12-02 11:15:42 -0800 |
---|---|---|
committer | Alex Klyubin <klyubin@google.com> | 2015-01-22 15:51:24 -0800 |
commit | b1fe85cc976c676eb50ff886596c93e04fd71d82 (patch) | |
tree | 7435f453030ed20fce5f08b7e646c51fbe8acd10 | |
parent | 194940746a618d361b26838e2c6ff04c358adff7 (diff) | |
download | libcore-b1fe85cc976c676eb50ff886596c93e04fd71d82.zip libcore-b1fe85cc976c676eb50ff886596c93e04fd71d82.tar.gz libcore-b1fe85cc976c676eb50ff886596c93e04fd71d82.tar.bz2 |
Add SSLSocket tests which inspect emitted ClientHello fields.
This CL adds basic tests which capture the ClientHello emitted by
SSLSocket and assert that the various fields are as expected. In
particular, this CL adds tests for:
* client protocol version,
* cipher suite list,
* compression methods,
* server_name extension (SNI).
Change-Id: I387c44363ad26f064885f9bfa28572da37871078
6 files changed, 470 insertions, 98 deletions
diff --git a/luni/src/test/java/libcore/javax/net/ssl/SSLSocketTest.java b/luni/src/test/java/libcore/javax/net/ssl/SSLSocketTest.java index dabcdc6..8e4519d 100644 --- a/luni/src/test/java/libcore/javax/net/ssl/SSLSocketTest.java +++ b/luni/src/test/java/libcore/javax/net/ssl/SSLSocketTest.java @@ -28,15 +28,18 @@ import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.ServerSocket; import java.net.Socket; -import java.net.SocketAddress; 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.cert.Certificate; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; +import java.util.ArrayList; import java.util.Arrays; +import java.util.List; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -68,9 +71,18 @@ import libcore.io.IoUtils; import libcore.io.Streams; import libcore.java.security.StandardNames; import libcore.java.security.TestKeyStore; +import libcore.tlswire.handshake.CipherSuite; +import libcore.tlswire.handshake.ClientHello; +import libcore.tlswire.handshake.CompressionMethod; import libcore.tlswire.handshake.HandshakeMessage; +import libcore.tlswire.handshake.HelloExtension; +import libcore.tlswire.handshake.ServerNameHelloExtension; import libcore.tlswire.record.TlsProtocols; import libcore.tlswire.record.TlsRecord; +import libcore.tlswire.util.TlsProtocolVersion; +import tests.util.ForEachRunner; +import tests.util.DelegatingSSLSocketFactory; +import tests.util.Pair; public class SSLSocketTest extends TestCase { @@ -1463,11 +1475,147 @@ public class SSLSocketTest extends TestCase { test.close(); } - public void test_SSLSocket_ClientHello_size() throws Exception { + 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("TLS"); + sslContext.init(null, null, null); + SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory(); + sslSocketFactory = new DelegatingSSLSocketFactory(sslSocketFactory) { + @Override + protected void configureSocket(SSLSocket socket) { + // Enable SNI extension on the socket (this is typically enabled by default) + // to increase the size of ClientHello. + try { + Method setHostname = + socket.getClass().getMethod("setHostname", String.class); + setHostname.invoke(socket, "sslsockettest.androidcts.google.com"); + } catch (NoSuchMethodException ignored) { + } catch (Exception e) { + throw new RuntimeException("Failed to enable SNI", e); + } + + // Enable Session Tickets extension on the socket (this is typically enabled + // by default) to increase the size of ClientHello. + try { + Method setUseSessionTickets = + socket.getClass().getMethod( + "setUseSessionTickets", boolean.class); + setUseSessionTickets.invoke(socket, true); + } catch (NoSuchMethodException ignored) { + } catch (Exception e) { + throw new RuntimeException("Failed to enable Session Tickets", e); + } + } + }; + + TlsRecord firstReceivedTlsRecord = captureTlsHandshakeFirstTlsRecord(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"); + } + } + + public void test_SSLSocket_ClientHello_cipherSuites() throws Exception { + ForEachRunner.runNamed(new ForEachRunner.Callback<SSLSocketFactory>() { + @Override + public void run(SSLSocketFactory sslSocketFactory) throws Exception { + ClientHello clientHello = captureTlsHandshakeClientHello(sslSocketFactory); + String[] cipherSuites = new String[clientHello.cipherSuites.size()]; + for (int i = 0; i < clientHello.cipherSuites.size(); i++) { + CipherSuite cipherSuite = clientHello.cipherSuites.get(i); + cipherSuites[i] = cipherSuite.getAndroidName(); + } + StandardNames.assertDefaultCipherSuites(cipherSuites); + } + }, getSSLSocketFactoriesToTest()); + } + + public void test_SSLSocket_ClientHello_clientProtocolVersion() throws Exception { + ForEachRunner.runNamed(new ForEachRunner.Callback<SSLSocketFactory>() { + @Override + public void run(SSLSocketFactory sslSocketFactory) throws Exception { + ClientHello clientHello = captureTlsHandshakeClientHello(sslSocketFactory); + assertEquals(TlsProtocolVersion.TLSv1_2, clientHello.clientVersion); + } + }, getSSLSocketFactoriesToTest()); + } + + public void test_SSLSocket_ClientHello_compressionMethods() throws Exception { + ForEachRunner.runNamed(new ForEachRunner.Callback<SSLSocketFactory>() { + @Override + public void run(SSLSocketFactory sslSocketFactory) throws Exception { + ClientHello clientHello = captureTlsHandshakeClientHello(sslSocketFactory); + assertEquals(Arrays.asList(CompressionMethod.NULL), clientHello.compressionMethods); + } + }, getSSLSocketFactoriesToTest()); + } + + public void test_SSLSocket_ClientHello_SNI() throws Exception { + ForEachRunner.runNamed(new ForEachRunner.Callback<SSLSocketFactory>() { + @Override + public void run(SSLSocketFactory sslSocketFactory) throws Exception { + ClientHello clientHello = captureTlsHandshakeClientHello(sslSocketFactory); + ServerNameHelloExtension sniExtension = (ServerNameHelloExtension) + clientHello.findExtensionByType(HelloExtension.TYPE_SERVER_NAME); + assertNotNull(sniExtension); + assertEquals(Arrays.asList("localhost.localdomain"), sniExtension.hostnames); + } + }, 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; + } + + private ClientHello captureTlsHandshakeClientHello(SSLSocketFactory sslSocketFactory) + throws Exception { + TlsRecord record = captureTlsHandshakeFirstTlsRecord(sslSocketFactory); + assertEquals("TLS record type", TlsProtocols.HANDSHAKE, record.type); + ByteArrayInputStream fragmentIn = new ByteArrayInputStream(record.fragment); + HandshakeMessage handshakeMessage = HandshakeMessage.read(new DataInputStream(fragmentIn)); + assertEquals("HandshakeMessage type", + HandshakeMessage.TYPE_CLIENT_HELLO, handshakeMessage.type); + // Assert that the fragment does not contain any more messages + assertEquals(0, fragmentIn.available()); + + return (ClientHello) handshakeMessage; + } + + private TlsRecord captureTlsHandshakeFirstTlsRecord(SSLSocketFactory sslSocketFactory) + throws Exception { + byte[] firstReceivedChunk = captureTlsHandshakeFirstTransmittedChunkBytes(sslSocketFactory); + ByteArrayInputStream firstReceivedChunkIn = new ByteArrayInputStream(firstReceivedChunk); + TlsRecord record = TlsRecord.read(new DataInputStream(firstReceivedChunkIn)); + // Assert that the chunk does not contain any more data + assertEquals(0, firstReceivedChunkIn.available()); + + return record; + } + + private byte[] captureTlsHandshakeFirstTransmittedChunkBytes( + final SSLSocketFactory sslSocketFactory) throws Exception { // Since there's no straightforward way to obtain a ClientHello from SSLSocket, this test // does the following: // 1. Creates a listening server socket (a plain one rather than a TLS/SSL one). @@ -1511,33 +1659,19 @@ public class SSLSocketTest extends TestCase { executorService.submit(new Callable<Void>() { @Override public Void call() throws Exception { - SSLContext sslContext = SSLContext.getInstance("TLS"); - sslContext.init(null, null, null); - SSLSocket client = (SSLSocket) sslContext.getSocketFactory().createSocket(); + Socket client = new Socket(); sockets[0] = client; try { - // Enable SNI extension on the socket (this is typically enabled by default) - // to increase the size of ClientHello. - try { - Method setHostname = - client.getClass().getMethod("setHostname", String.class); - setHostname.invoke(client, "sslsockettest.androidcts.google.com"); - } catch (NoSuchMethodException ignored) {} - - // Enable Session Tickets extension on the socket (this is typically enabled - // by default) to increase the size of ClientHello. - try { - Method setUseSessionTickets = - client.getClass().getMethod( - "setUseSessionTickets", boolean.class); - setUseSessionTickets.invoke(client, true); - } catch (NoSuchMethodException ignored) {} - client.connect(finalListeningSocket.getLocalSocketAddress()); // Initiate the TLS/SSL handshake which is expected to fail as soon as the // server socket receives a ClientHello. try { - client.startHandshake(); + SSLSocket sslSocket = (SSLSocket) sslSocketFactory.createSocket( + client, + "localhost.localdomain", + finalListeningSocket.getLocalPort(), + true); + sslSocket.startHandshake(); fail(); return null; } catch (IOException expected) {} @@ -1556,22 +1690,7 @@ public class SSLSocketTest extends TestCase { }); // Wait for the ClientHello to arrive - byte[] firstReceivedChunk = readFirstReceivedChunkFuture.get(10, TimeUnit.SECONDS); - - // Check for ClientHello length that may cause handshake to fail/time out with older - // F5/BIG-IP appliances. - TlsRecord firstReceivedTlsRecord = TlsRecord.read( - new DataInputStream(new ByteArrayInputStream(firstReceivedChunk))); - 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"); - } + return readFirstReceivedChunkFuture.get(10, TimeUnit.SECONDS); } finally { executorService.shutdownNow(); IoUtils.closeQuietly(listeningSocket); diff --git a/support/src/test/java/libcore/tlswire/handshake/CipherSuite.java b/support/src/test/java/libcore/tlswire/handshake/CipherSuite.java index 959379d..88ce2f2 100644 --- a/support/src/test/java/libcore/tlswire/handshake/CipherSuite.java +++ b/support/src/test/java/libcore/tlswire/handshake/CipherSuite.java @@ -27,33 +27,41 @@ public class CipherSuite { // https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml private static final CipherSuite[] CIPHER_SUITES = new CipherSuite[] { new CipherSuite(0x0000, "TLS_NULL_WITH_NULL_NULL"), - new CipherSuite(0x0001, "TLS_RSA_WITH_NULL_MD5"), - new CipherSuite(0x0002, "TLS_RSA_WITH_NULL_SHA"), - new CipherSuite(0x0003, "TLS_RSA_EXPORT_WITH_RC4_40_MD5"), - new CipherSuite(0x0004, "TLS_RSA_WITH_RC4_128_MD5"), - new CipherSuite(0x0005, "TLS_RSA_WITH_RC4_128_SHA"), + new CipherSuite(0x0001, "TLS_RSA_WITH_NULL_MD5", "SSL_RSA_WITH_NULL_MD5"), + new CipherSuite(0x0002, "TLS_RSA_WITH_NULL_SHA", "SSL_RSA_WITH_NULL_SHA"), + new CipherSuite(0x0003, "TLS_RSA_EXPORT_WITH_RC4_40_MD5", "SSL_RSA_EXPORT_WITH_RC4_40_MD5"), + new CipherSuite(0x0004, "TLS_RSA_WITH_RC4_128_MD5", "SSL_RSA_WITH_RC4_128_MD5"), + new CipherSuite(0x0005, "TLS_RSA_WITH_RC4_128_SHA", "SSL_RSA_WITH_RC4_128_SHA"), new CipherSuite(0x0006, "TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5"), new CipherSuite(0x0007, "TLS_RSA_WITH_IDEA_CBC_SHA"), - new CipherSuite(0x0008, "TLS_RSA_EXPORT_WITH_DES40_CBC_SHA"), - new CipherSuite(0x0009, "TLS_RSA_WITH_DES_CBC_SHA"), - new CipherSuite(0x000a, "TLS_RSA_WITH_3DES_EDE_CBC_SHA"), + new CipherSuite(0x0008, "TLS_RSA_EXPORT_WITH_DES40_CBC_SHA", + "SSL_RSA_EXPORT_WITH_DES40_CBC_SHA"), + new CipherSuite(0x0009, "TLS_RSA_WITH_DES_CBC_SHA", "SSL_RSA_WITH_DES_CBC_SHA"), + new CipherSuite(0x000a, "TLS_RSA_WITH_3DES_EDE_CBC_SHA", "SSL_RSA_WITH_3DES_EDE_CBC_SHA"), new CipherSuite(0x000b, "TLS_DH_DSS_EXPORT_WITH_DES40_CBC_SHA"), new CipherSuite(0x000c, "TLS_DH_DSS_WITH_DES_CBC_SHA"), new CipherSuite(0x000d, "TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA"), new CipherSuite(0x000e, "TLS_DH_RSA_EXPORT_WITH_DES40_CBC_SHA"), new CipherSuite(0x000f, "TLS_DH_RSA_WITH_DES_CBC_SHA"), new CipherSuite(0x0010, "TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA"), - new CipherSuite(0x0011, "TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA"), - new CipherSuite(0x0012, "TLS_DHE_DSS_WITH_DES_CBC_SHA"), - new CipherSuite(0x0013, "TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA"), - new CipherSuite(0x0014, "TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA"), - new CipherSuite(0x0015, "TLS_DHE_RSA_WITH_DES_CBC_SHA"), - new CipherSuite(0x0016, "TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA"), - new CipherSuite(0x0017, "TLS_DH_anon_EXPORT_WITH_RC4_40_MD5"), - new CipherSuite(0x0018, "TLS_DH_anon_WITH_RC4_128_MD5"), - new CipherSuite(0x0019, "TLS_DH_anon_EXPORT_WITH_DES40_CBC_SHA"), - new CipherSuite(0x001a, "TLS_DH_anon_WITH_DES_CBC_SHA"), - new CipherSuite(0x001b, "TLS_DH_anon_WITH_3DES_EDE_CBC_SHA"), + new CipherSuite(0x0011, "TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA", + "SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA"), + new CipherSuite(0x0012, "TLS_DHE_DSS_WITH_DES_CBC_SHA", "SSL_DHE_DSS_WITH_DES_CBC_SHA"), + new CipherSuite(0x0013, "TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA", + "SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA"), + new CipherSuite(0x0014, "TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA", + "SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA"), + new CipherSuite(0x0015, "TLS_DHE_RSA_WITH_DES_CBC_SHA", "SSL_DHE_RSA_WITH_DES_CBC_SHA"), + new CipherSuite(0x0016, "TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA", + "SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA"), + new CipherSuite(0x0017, "TLS_DH_anon_EXPORT_WITH_RC4_40_MD5", + "SSL_DH_anon_EXPORT_WITH_RC4_40_MD5"), + new CipherSuite(0x0018, "TLS_DH_anon_WITH_RC4_128_MD5", "SSL_DH_anon_WITH_RC4_128_MD5"), + new CipherSuite(0x0019, "TLS_DH_anon_EXPORT_WITH_DES40_CBC_SHA", + "SSL_DH_anon_EXPORT_WITH_DES40_CBC_SHA"), + new CipherSuite(0x001a, "TLS_DH_anon_WITH_DES_CBC_SHA", "SSL_DH_anon_WITH_DES_CBC_SHA"), + new CipherSuite(0x001b, "TLS_DH_anon_WITH_3DES_EDE_CBC_SHA", + "SSL_DH_anon_WITH_3DES_EDE_CBC_SHA"), new CipherSuite(0x001e, "TLS_KRB5_WITH_DES_CBC_SHA"), new CipherSuite(0x001f, "TLS_KRB5_WITH_3DES_EDE_CBC_SHA"), new CipherSuite(0x0020, "TLS_KRB5_WITH_RC4_128_SHA"), @@ -369,59 +377,38 @@ public class CipherSuite { throw new RuntimeException( "Cipher suite multiply defined: " + Integer.toHexString(cipherSuite.code)); } - if (byName.put(cipherSuite.name, cipherSuite) != null) { + String name = cipherSuite.name; + if (byName.put(name, cipherSuite) != null) { throw new RuntimeException( "Cipher suite multiply defined: " + cipherSuite.name); } + String androidName = cipherSuite.getAndroidName(); + if (!name.equals(androidName)) { + if (byName.put(androidName, cipherSuite) != null) { + throw new RuntimeException( + "Cipher suite multiply defined: " + cipherSuite.androidName); + } + } } - // Add alternative names used in Android's platform-default TLS/SSL stack. - addAltName(byName, - "TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA", "SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA"); - addAltName(byName, - "TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA", "SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA"); - addAltName(byName, "TLS_DHE_DSS_WITH_DES_CBC_SHA", "SSL_DHE_DSS_WITH_DES_CBC_SHA"); - addAltName(byName, - "TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA", "SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA"); - addAltName(byName, - "TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA", "SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA"); - addAltName(byName, "TLS_DHE_RSA_WITH_DES_CBC_SHA", "SSL_DHE_RSA_WITH_DES_CBC_SHA"); - addAltName(byName, - "TLS_DH_anon_EXPORT_WITH_DES40_CBC_SHA", "SSL_DH_anon_EXPORT_WITH_DES40_CBC_SHA"); - addAltName(byName, - "TLS_DH_anon_EXPORT_WITH_RC4_40_MD5", "SSL_DH_anon_EXPORT_WITH_RC4_40_MD5"); - addAltName(byName, - "TLS_DH_anon_WITH_3DES_EDE_CBC_SHA", "SSL_DH_anon_WITH_3DES_EDE_CBC_SHA"); - addAltName(byName, "TLS_DH_anon_WITH_DES_CBC_SHA", "SSL_DH_anon_WITH_DES_CBC_SHA"); - addAltName(byName, "TLS_DH_anon_WITH_RC4_128_MD5", "SSL_DH_anon_WITH_RC4_128_MD5"); - addAltName(byName, - "TLS_RSA_EXPORT_WITH_DES40_CBC_SHA", "SSL_RSA_EXPORT_WITH_DES40_CBC_SHA"); - addAltName(byName, "TLS_RSA_EXPORT_WITH_RC4_40_MD5", "SSL_RSA_EXPORT_WITH_RC4_40_MD5"); - addAltName(byName, "TLS_RSA_WITH_3DES_EDE_CBC_SHA", "SSL_RSA_WITH_3DES_EDE_CBC_SHA"); - addAltName(byName, "TLS_RSA_WITH_DES_CBC_SHA", "SSL_RSA_WITH_DES_CBC_SHA"); - addAltName(byName, "TLS_RSA_WITH_NULL_MD5", "SSL_RSA_WITH_NULL_MD5"); - addAltName(byName, "TLS_RSA_WITH_NULL_SHA", "SSL_RSA_WITH_NULL_SHA"); - addAltName(byName, "TLS_RSA_WITH_RC4_128_MD5", "SSL_RSA_WITH_RC4_128_MD5"); - addAltName(byName, "TLS_RSA_WITH_RC4_128_SHA", "SSL_RSA_WITH_RC4_128_SHA"); - CODE_TO_CIPHER_SUITE = byCode; NAME_TO_CIPHER_SUITE = byName; } - private static void addAltName(Map<String, CipherSuite> byName, String name, String altName) { - CipherSuite cipherSuite = byName.get(name); - if (cipherSuite == null) { - throw new IllegalArgumentException("Cipher suite not found: " + name); - } - byName.put(altName, cipherSuite); - } - public final int code; public final String name; + private final String androidName; private CipherSuite(int code, String name) { this.code = code; this.name = name; + this.androidName = null; + } + + private CipherSuite(int code, String name, String androidName) { + this.code = code; + this.name = name; + this.androidName = androidName; } public static CipherSuite valueOf(String name) { @@ -440,6 +427,10 @@ public class CipherSuite { return new CipherSuite(code, Integer.toHexString(code)); } + public String getAndroidName() { + return (androidName != null) ? androidName : name; + } + @Override public String toString() { return name; diff --git a/support/src/test/java/libcore/tlswire/handshake/ServerNameHelloExtension.java b/support/src/test/java/libcore/tlswire/handshake/ServerNameHelloExtension.java index afc9cfe..5b06246 100644 --- a/support/src/test/java/libcore/tlswire/handshake/ServerNameHelloExtension.java +++ b/support/src/test/java/libcore/tlswire/handshake/ServerNameHelloExtension.java @@ -29,7 +29,7 @@ import java.util.List; public class ServerNameHelloExtension extends HelloExtension { private static final int TYPE_HOST_NAME = 0; - private List<String> hostnames; + public List<String> hostnames; @Override protected void parseData() throws IOException { diff --git a/support/src/test/java/tests/util/DelegatingSSLSocketFactory.java b/support/src/test/java/tests/util/DelegatingSSLSocketFactory.java new file mode 100644 index 0000000..5513210 --- /dev/null +++ b/support/src/test/java/tests/util/DelegatingSSLSocketFactory.java @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2014 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 tests.util; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.Socket; +import java.net.UnknownHostException; + +import javax.net.ssl.SSLSocket; +import javax.net.ssl.SSLSocketFactory; + +/** + * {@link SSLSocketFactory} which delegates all invocations to the provided delegate + * {@code SSLSocketFactory}. + */ +public class DelegatingSSLSocketFactory extends SSLSocketFactory { + + private final SSLSocketFactory mDelegate; + + public DelegatingSSLSocketFactory(SSLSocketFactory delegate) { + this.mDelegate = delegate; + } + + /** + * Invoked after obtaining a socket from the delegate and before returning it to the caller. + * + * <p>The default implementation does nothing. + */ + protected void configureSocket(@SuppressWarnings("unused") SSLSocket socket) {} + + @Override + public String[] getDefaultCipherSuites() { + return mDelegate.getDefaultCipherSuites(); + } + + @Override + public String[] getSupportedCipherSuites() { + return mDelegate.getSupportedCipherSuites(); + } + + @Override + public Socket createSocket() throws IOException { + SSLSocket socket = (SSLSocket) mDelegate.createSocket(); + configureSocket(socket); + return socket; + } + + @Override + public Socket createSocket(Socket s, String host, int port, boolean autoClose) + throws IOException { + SSLSocket socket = (SSLSocket) mDelegate.createSocket(s, host, port, autoClose); + configureSocket(socket); + return socket; + } + + @Override + public Socket createSocket(String host, int port) throws IOException, UnknownHostException { + SSLSocket socket = (SSLSocket) mDelegate.createSocket(host, port); + configureSocket(socket); + return socket; + } + + @Override + public Socket createSocket(String host, int port, InetAddress localHost, int localPort) + throws IOException, UnknownHostException { + SSLSocket socket = (SSLSocket) mDelegate.createSocket(host, port, localHost, localPort); + configureSocket(socket); + return socket; + } + + @Override + public Socket createSocket(InetAddress host, int port) throws IOException { + SSLSocket socket = (SSLSocket) mDelegate.createSocket(host, port); + configureSocket(socket); + return socket; + } + + @Override + public Socket createSocket(InetAddress address, int port, InetAddress localAddress, + int localPort) throws IOException { + SSLSocket socket = + (SSLSocket) mDelegate.createSocket(address, port, localAddress, localPort); + configureSocket(socket); + return socket; + } +} diff --git a/support/src/test/java/tests/util/ForEachRunner.java b/support/src/test/java/tests/util/ForEachRunner.java new file mode 100644 index 0000000..2c222b2 --- /dev/null +++ b/support/src/test/java/tests/util/ForEachRunner.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2015 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 tests.util; + +/** + * Runner which executes the provided code under test (via a callback) for each provided input + * value. + */ +public final class ForEachRunner { + + /** + * Callback parameterized with a value. + */ + public interface Callback<T> { + /** + * Invokes the callback for the provided value. + */ + void run(T value) throws Exception; + } + + private ForEachRunner() {} + + /** + * Invokes the provided callback for each of the provided named values. + * + * @param namesAndValues named values represented as name-value pairs. + * + * @param <T> type of value. + */ + public static <T> void runNamed(Callback<T> callback, Iterable<Pair<String, T>> namesAndValues) + throws Exception { + for (Pair<String, T> nameAndValue : namesAndValues) { + try { + callback.run(nameAndValue.getSecond()); + } catch (Throwable e) { + throw new Exception("Failed for " + nameAndValue.getFirst() + ": " + e.getMessage(), e); + } + } + } +} diff --git a/support/src/test/java/tests/util/Pair.java b/support/src/test/java/tests/util/Pair.java new file mode 100644 index 0000000..9b0906d --- /dev/null +++ b/support/src/test/java/tests/util/Pair.java @@ -0,0 +1,107 @@ +/* + * 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 tests.util; + +/** + * Pair of typed values. + * + * <p>Pairs are obtained using {@link #of(Object, Object) of}. + * + * @param <F> type of the first value. + * @param <S> type of the second value. + */ +public class Pair<F, S> { + private final F mFirst; + private final S mSecond; + + private Pair(F first, S second) { + mFirst = first; + mSecond = second; + } + + /** + * Gets the pair consisting of the two provided values. + * + * @param first first value or {@code null}. + * @param second second value or {@code null}. + */ + public static <F, S> Pair<F, S> of(F first, S second) { + return new Pair<F, S>(first, second); + } + + /** + * Gets the first value from this pair. + * + * @return value or {@code null}. + */ + public F getFirst() { + return mFirst; + } + + /** + * Gets the second value from this pair. + * + * @return value or {@code null}. + */ + public S getSecond() { + return mSecond; + } + + @Override + public String toString() { + return "Pair[" + mFirst + ", " + mSecond + "]"; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((mFirst == null) ? 0 : mFirst.hashCode()); + result = prime * result + ((mSecond == null) ? 0 : mSecond.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + @SuppressWarnings("rawtypes") + Pair other = (Pair) obj; + if (mFirst == null) { + if (other.mFirst != null) { + return false; + } + } else if (!mFirst.equals(other.mFirst)) { + return false; + } + if (mSecond == null) { + if (other.mSecond != null) { + return false; + } + } else if (!mSecond.equals(other.mSecond)) { + return false; + } + return true; + } +} |