summaryrefslogtreecommitdiffstats
path: root/luni
diff options
context:
space:
mode:
authorNeil Fuller <nfuller@google.com>2014-10-07 11:43:00 +0100
committerNeil Fuller <nfuller@google.com>2014-10-08 18:42:41 +0100
commit6c9609af5f63a759bd50b5f6586f6f52502b4f93 (patch)
treebcaa7d72e7f6ad517f894793a75abb4486cac2d8 /luni
parent14d202995c64ca96ae6e91143fb7224bce5b1e19 (diff)
downloadlibcore-6c9609af5f63a759bd50b5f6586f6f52502b4f93.tar.gz
libcore-6c9609af5f63a759bd50b5f6586f6f52502b4f93.tar.bz2
libcore-6c9609af5f63a759bd50b5f6586f6f52502b4f93.zip
Add support for TLS_FALLBACK_SCSV
Backport of commits: external/conscrypt: 8d7e23e117da591a8d48e6bcda9ed6f58ff1a375 libcore: e6a6e935e98f426c7000b2bf4086f87101f4441c libcore: 957ec8b09833e1c2f2c21380e53c13c9962e6b3e Plus additional changes to: luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLServerSocketImpl.java luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLSocketImpl.java luni/src/test/java/libcore/java/net/URLConnectionTest.java luni/src/test/java/org/apache/harmony/xnet/provider/jsse/CipherSuiteTest.java support/src/test/java/libcore/java/security/StandardNames.java to account for KitKat differences. Bug: 17750026 Change-Id: Ic6e9474275bc3ffec3b5c2d6df1f8d6ffe77bff8
Diffstat (limited to 'luni')
-rw-r--r--luni/src/test/java/libcore/java/net/URLConnectionTest.java291
-rw-r--r--luni/src/test/java/libcore/javax/net/ssl/SSLEngineTest.java3
-rw-r--r--luni/src/test/java/libcore/javax/net/ssl/SSLSocketTest.java92
3 files changed, 314 insertions, 72 deletions
diff --git a/luni/src/test/java/libcore/java/net/URLConnectionTest.java b/luni/src/test/java/libcore/java/net/URLConnectionTest.java
index 7ecdc5916..4ed7f0ee3 100644
--- a/luni/src/test/java/libcore/java/net/URLConnectionTest.java
+++ b/luni/src/test/java/libcore/java/net/URLConnectionTest.java
@@ -39,6 +39,7 @@ import java.net.PasswordAuthentication;
import java.net.ProtocolException;
import java.net.Proxy;
import java.net.ResponseCache;
+import java.net.Socket;
import java.net.SocketTimeoutException;
import java.net.URI;
import java.net.URL;
@@ -59,17 +60,20 @@ import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
+import javax.net.SocketFactory;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLHandshakeException;
import javax.net.ssl.SSLSession;
+import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import junit.framework.TestCase;
import libcore.java.lang.ref.FinalizationTester;
+import libcore.java.security.StandardNames;
import libcore.java.security.TestKeyStore;
import libcore.javax.net.ssl.TestSSLContext;
import tests.net.StuckServer;
@@ -335,49 +339,6 @@ public final class URLConnectionTest extends TestCase {
assertEquals(0, server.takeRequest().getSequenceNumber());
}
- public void testRetryableRequestBodyAfterBrokenConnection() throws Exception {
- // Use SSL to make an alternate route available.
- TestSSLContext testSSLContext = TestSSLContext.create();
- server.useHttps(testSSLContext.serverContext.getSocketFactory(), false);
-
- server.enqueue(new MockResponse().setBody("abc").setSocketPolicy(
- DISCONNECT_AFTER_READING_REQUEST));
- server.enqueue(new MockResponse().setBody("abc"));
- server.play();
-
- HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("").openConnection();
- connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory());
- connection.setDoOutput(true);
- OutputStream out = connection.getOutputStream();
- out.write(new byte[] {1, 2, 3});
- out.close();
- assertContent("abc", connection);
-
- assertEquals(0, server.takeRequest().getSequenceNumber());
- assertEquals(0, server.takeRequest().getSequenceNumber());
- }
-
- public void testNonRetryableRequestBodyAfterBrokenConnection() throws Exception {
- TestSSLContext testSSLContext = TestSSLContext.create();
- server.useHttps(testSSLContext.serverContext.getSocketFactory(), false);
- server.enqueue(new MockResponse().setBody("abc")
- .setSocketPolicy(DISCONNECT_AFTER_READING_REQUEST));
- server.play();
-
- HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/a").openConnection();
- connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory());
- connection.setDoOutput(true);
- connection.setFixedLengthStreamingMode(3);
- OutputStream out = connection.getOutputStream();
- out.write(new byte[] {1, 2, 3});
- out.close();
- try {
- connection.getInputStream();
- fail();
- } catch (IOException expected) {
- }
- }
-
enum WriteKind { BYTE_BY_BYTE, SMALL_BUFFERS, LARGE_BUFFERS }
public void test_chunkedUpload_byteByByte() throws Exception {
@@ -518,28 +479,6 @@ public final class URLConnectionTest extends TestCase {
}
}
- public void testConnectViaHttpsWithSSLFallback() throws IOException, InterruptedException {
- TestSSLContext testSSLContext = TestSSLContext.create();
-
- server.useHttps(testSSLContext.serverContext.getSocketFactory(), false);
- server.enqueue(new MockResponse().setSocketPolicy(DISCONNECT_AT_START));
- server.enqueue(new MockResponse().setBody("this response comes via SSL"));
- server.play();
-
- HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/foo").openConnection();
- connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory());
-
- assertContent("this response comes via SSL", connection);
-
- // The first request will be an incomplete (bookkeeping) request
- // that the server disconnected from at start.
- server.takeRequest();
-
- // The request will be retried.
- RecordedRequest request = server.takeRequest();
- assertEquals("GET /foo HTTP/1.1", request.getRequestLine());
- }
-
/**
* Verify that we don't retry connections on certificate verification errors.
*
@@ -2265,13 +2204,24 @@ public final class URLConnectionTest extends TestCase {
public void testSslFallback() throws Exception {
TestSSLContext testSSLContext = TestSSLContext.create();
- server.useHttps(testSSLContext.serverContext.getSocketFactory(), false);
+
+ // This server socket factory only supports SSLv3. This is to avoid issues due to SCSV
+ // checks. See https://tools.ietf.org/html/draft-ietf-tls-downgrade-scsv-00
+ SSLSocketFactory serverSocketFactory =
+ new LimitedProtocolsSocketFactory(
+ testSSLContext.serverContext.getSocketFactory(),
+ "SSLv3");
+
+ server.useHttps(serverSocketFactory, false);
server.enqueue(new MockResponse().setSocketPolicy(SocketPolicy.FAIL_HANDSHAKE));
server.enqueue(new MockResponse().setBody("This required a 2nd handshake"));
server.play();
HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/").openConnection();
- connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory());
+ // Keep track of the client sockets created so that we can interrogate them.
+ RecordingSocketFactory clientSocketFactory =
+ new RecordingSocketFactory(testSSLContext.clientContext.getSocketFactory());
+ connection.setSSLSocketFactory(clientSocketFactory);
assertEquals("This required a 2nd handshake",
readAscii(connection.getInputStream(), Integer.MAX_VALUE));
@@ -2280,6 +2230,26 @@ public final class URLConnectionTest extends TestCase {
RecordedRequest retry = server.takeRequest();
assertEquals(0, retry.getSequenceNumber());
assertEquals("SSLv3", retry.getSslProtocol());
+
+ // Confirm the client fallback looks ok.
+ List<SSLSocket> createdSockets = clientSocketFactory.getCreatedSockets();
+ assertEquals(2, createdSockets.size());
+ SSLSocket clientSocket1 = createdSockets.get(0);
+ List<String> clientSocket1EnabledProtocols = Arrays.asList(
+ clientSocket1.getEnabledProtocols());
+ assertContains(clientSocket1EnabledProtocols, "TLSv1");
+ List<String> clientSocket1EnabledCiphers =
+ Arrays.asList(clientSocket1.getEnabledCipherSuites());
+ assertContainsNoneMatching(
+ clientSocket1EnabledCiphers, StandardNames.CIPHER_SUITE_FALLBACK);
+
+ SSLSocket clientSocket2 = createdSockets.get(1);
+ List<String> clientSocket2EnabledProtocols =
+ Arrays.asList(clientSocket2.getEnabledProtocols());
+ assertContainsNoneMatching(clientSocket2EnabledProtocols, "TLSv1");
+ List<String> clientSocket2EnabledCiphers =
+ Arrays.asList(clientSocket2.getEnabledCipherSuites());
+ assertContains(clientSocket2EnabledCiphers, StandardNames.CIPHER_SUITE_FALLBACK);
}
public void testInspectSslBeforeConnect() throws Exception {
@@ -2359,12 +2329,12 @@ public final class URLConnectionTest extends TestCase {
assertContent(expected, connection, Integer.MAX_VALUE);
}
- private void assertContains(List<String> headers, String header) {
- assertTrue(headers.toString(), headers.contains(header));
+ private void assertContains(List<String> list, String value) {
+ assertTrue(list.toString(), list.contains(value));
}
- private void assertContainsNoneMatching(List<String> headers, String pattern) {
- for (String header : headers) {
+ private void assertContainsNoneMatching(List<String> list, String pattern) {
+ for (String header : list) {
if (header.matches(pattern)) {
fail("Header " + header + " matches " + pattern);
}
@@ -2513,4 +2483,183 @@ public final class URLConnectionTest extends TestCase {
: null;
}
}
+
+ /**
+ * An SSLSocketFactory that delegates all calls.
+ */
+ private static class DelegatingSSLSocketFactory extends SSLSocketFactory {
+
+ protected final SSLSocketFactory delegate;
+
+ public DelegatingSSLSocketFactory(SSLSocketFactory delegate) {
+ this.delegate = delegate;
+ }
+
+ @Override
+ public String[] getDefaultCipherSuites() {
+ return delegate.getDefaultCipherSuites();
+ }
+
+ @Override
+ public String[] getSupportedCipherSuites() {
+ return delegate.getSupportedCipherSuites();
+ }
+
+ @Override
+ public Socket createSocket(Socket s, String host, int port, boolean autoClose)
+ throws IOException {
+ return delegate.createSocket(s, host, port, autoClose);
+ }
+
+ @Override
+ public Socket createSocket() throws IOException {
+ return delegate.createSocket();
+ }
+
+ @Override
+ public Socket createSocket(String host, int port) throws IOException, UnknownHostException {
+ return delegate.createSocket(host, port);
+ }
+
+ @Override
+ public Socket createSocket(String host, int port, InetAddress localHost,
+ int localPort) throws IOException, UnknownHostException {
+ return delegate.createSocket(host, port, localHost, localPort);
+ }
+
+ @Override
+ public Socket createSocket(InetAddress host, int port) throws IOException {
+ return delegate.createSocket(host, port);
+ }
+
+ @Override
+ public Socket createSocket(InetAddress address, int port,
+ InetAddress localAddress, int localPort) throws IOException {
+ return delegate.createSocket(address, port, localAddress, localPort);
+ }
+
+ }
+
+ /**
+ * An SSLSocketFactory that delegates calls but limits the enabled protocols for any created
+ * sockets.
+ */
+ private static class LimitedProtocolsSocketFactory extends DelegatingSSLSocketFactory {
+
+ private final String[] protocols;
+
+ private LimitedProtocolsSocketFactory(SSLSocketFactory delegate, String... protocols) {
+ super(delegate);
+ this.protocols = protocols;
+ }
+
+ @Override
+ public Socket createSocket(Socket s, String host, int port, boolean autoClose)
+ throws IOException {
+ SSLSocket socket = (SSLSocket) delegate.createSocket(s, host, port, autoClose);
+ socket.setEnabledProtocols(protocols);
+ return socket;
+ }
+
+ @Override
+ public Socket createSocket() throws IOException {
+ SSLSocket socket = (SSLSocket) delegate.createSocket();
+ socket.setEnabledProtocols(protocols);
+ return socket;
+ }
+
+ @Override
+ public Socket createSocket(String host, int port) throws IOException, UnknownHostException {
+ SSLSocket socket = (SSLSocket) delegate.createSocket(host, port);
+ socket.setEnabledProtocols(protocols);
+ return socket;
+ }
+
+ @Override
+ public Socket createSocket(String host, int port, InetAddress localHost,
+ int localPort) throws IOException, UnknownHostException {
+ SSLSocket socket = (SSLSocket) delegate.createSocket(host, port, localHost, localPort);
+ socket.setEnabledProtocols(protocols);
+ return socket;
+ }
+
+ @Override
+ public Socket createSocket(InetAddress host, int port) throws IOException {
+ SSLSocket socket = (SSLSocket) delegate.createSocket(host, port);
+ socket.setEnabledProtocols(protocols);
+ return socket;
+ }
+
+ @Override
+ public Socket createSocket(InetAddress address, int port,
+ InetAddress localAddress, int localPort) throws IOException {
+ SSLSocket socket =
+ (SSLSocket) delegate.createSocket(address, port, localAddress, localPort);
+ socket.setEnabledProtocols(protocols);
+ return socket;
+ }
+ }
+
+ /**
+ * An SSLSocketFactory that delegates calls and keeps a record of any sockets created.
+ */
+ private static class RecordingSocketFactory extends DelegatingSSLSocketFactory {
+
+ private final List<SSLSocket> createdSockets = new ArrayList<SSLSocket>();
+
+ private RecordingSocketFactory(SSLSocketFactory delegate) {
+ super(delegate);
+ }
+
+ @Override
+ public Socket createSocket(Socket s, String host, int port, boolean autoClose)
+ throws IOException {
+ SSLSocket socket = (SSLSocket) delegate.createSocket(s, host, port, autoClose);
+ createdSockets.add(socket);
+ return socket;
+ }
+
+ @Override
+ public Socket createSocket() throws IOException {
+ SSLSocket socket = (SSLSocket) delegate.createSocket();
+ createdSockets.add(socket);
+ return socket;
+ }
+
+ @Override
+ public Socket createSocket(String host, int port) throws IOException, UnknownHostException {
+ SSLSocket socket = (SSLSocket) delegate.createSocket(host, port);
+ createdSockets.add(socket);
+ return socket;
+ }
+
+ @Override
+ public Socket createSocket(String host, int port, InetAddress localHost,
+ int localPort) throws IOException, UnknownHostException {
+ SSLSocket socket = (SSLSocket) delegate.createSocket(host, port, localHost, localPort);
+ createdSockets.add(socket);
+ return socket;
+ }
+
+ @Override
+ public Socket createSocket(InetAddress host, int port) throws IOException {
+ SSLSocket socket = (SSLSocket) delegate.createSocket(host, port);
+ createdSockets.add(socket);
+ return socket;
+ }
+
+ @Override
+ public Socket createSocket(InetAddress address, int port,
+ InetAddress localAddress, int localPort) throws IOException {
+ SSLSocket socket =
+ (SSLSocket) delegate.createSocket(address, port, localAddress, localPort);
+ createdSockets.add(socket);
+ return socket;
+ }
+
+ public List<SSLSocket> getCreatedSockets() {
+ return createdSockets;
+ }
+ }
+
}
diff --git a/luni/src/test/java/libcore/javax/net/ssl/SSLEngineTest.java b/luni/src/test/java/libcore/javax/net/ssl/SSLEngineTest.java
index a015d1973..33a8923e1 100644
--- a/luni/src/test/java/libcore/javax/net/ssl/SSLEngineTest.java
+++ b/luni/src/test/java/libcore/javax/net/ssl/SSLEngineTest.java
@@ -108,7 +108,8 @@ public class SSLEngineTest extends TestCase {
* its own, but instead in conjunction with other
* cipher suites.
*/
- if (cipherSuite.equals(StandardNames.CIPHER_SUITE_SECURE_RENEGOTIATION)) {
+ if (cipherSuite.equals(StandardNames.CIPHER_SUITE_SECURE_RENEGOTIATION)
+ || cipherSuite.equals(StandardNames.CIPHER_SUITE_FALLBACK)) {
continue;
}
/*
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 0ac3bcdee..8e009bdb6 100644
--- a/luni/src/test/java/libcore/javax/net/ssl/SSLSocketTest.java
+++ b/luni/src/test/java/libcore/javax/net/ssl/SSLSocketTest.java
@@ -155,6 +155,14 @@ public class SSLSocketTest extends TestCase {
continue;
}
/*
+ * Similarly with the TLS_FALLBACK_SCSV suite, it is not
+ * a selectable suite, but is used in conjunction with
+ * other cipher suites.
+ */
+ if (cipherSuite.equals(StandardNames.CIPHER_SUITE_FALLBACK)) {
+ continue;
+ }
+ /*
* Kerberos cipher suites require external setup. See "Kerberos Requirements" in
* https://java.sun.com/j2se/1.5.0/docs/guide/security/jsse/JSSERefGuide.html
* #KRBRequire
@@ -1415,6 +1423,90 @@ public class SSLSocketTest extends TestCase {
test.close();
}
+ public void test_SSLSocket_sendsTlsFallbackScsv_Fallback_Success() throws Exception {
+ TestSSLContext context = TestSSLContext.create();
+
+ final SSLSocket client = (SSLSocket)
+ context.clientContext.getSocketFactory().createSocket(context.host, context.port);
+ final SSLSocket server = (SSLSocket) context.serverSocket.accept();
+
+ final String[] serverCipherSuites = server.getEnabledCipherSuites();
+ final String[] clientCipherSuites = new String[serverCipherSuites.length + 1];
+ System.arraycopy(serverCipherSuites, 0, clientCipherSuites, 0, serverCipherSuites.length);
+ clientCipherSuites[serverCipherSuites.length] = StandardNames.CIPHER_SUITE_FALLBACK;
+
+ ExecutorService executor = Executors.newFixedThreadPool(2);
+ Future<Void> s = executor.submit(new Callable<Void>() {
+ public Void call() throws Exception {
+ server.setEnabledProtocols(new String[] { "TLSv1.2" });
+ server.setEnabledCipherSuites(serverCipherSuites);
+ server.startHandshake();
+ return null;
+ }
+ });
+ Future<Void> c = executor.submit(new Callable<Void>() {
+ public Void call() throws Exception {
+ client.setEnabledProtocols(new String[] { "TLSv1.2" });
+ client.setEnabledCipherSuites(clientCipherSuites);
+ client.startHandshake();
+ return null;
+ }
+ });
+ executor.shutdown();
+
+ s.get();
+ c.get();
+ client.close();
+ server.close();
+ context.close();
+ }
+
+ public void test_SSLSocket_sendsTlsFallbackScsv_InappropriateFallback_Failure() throws Exception {
+ TestSSLContext context = TestSSLContext.create();
+
+ final SSLSocket client = (SSLSocket)
+ context.clientContext.getSocketFactory().createSocket(context.host, context.port);
+ final SSLSocket server = (SSLSocket) context.serverSocket.accept();
+
+ final String[] serverCipherSuites = server.getEnabledCipherSuites();
+ final String[] clientCipherSuites = new String[serverCipherSuites.length + 1];
+ System.arraycopy(serverCipherSuites, 0, clientCipherSuites, 0, serverCipherSuites.length);
+ clientCipherSuites[serverCipherSuites.length] = StandardNames.CIPHER_SUITE_FALLBACK;
+
+ ExecutorService executor = Executors.newFixedThreadPool(2);
+ Future<Void> s = executor.submit(new Callable<Void>() {
+ public Void call() throws Exception {
+ server.setEnabledProtocols(new String[] { "TLSv1", "SSLv3" });
+ server.setEnabledCipherSuites(serverCipherSuites);
+ try {
+ server.startHandshake();
+ fail("Should result in inappropriate fallback");
+ } catch (SSLHandshakeException expected) {
+ }
+ return null;
+ }
+ });
+ Future<Void> c = executor.submit(new Callable<Void>() {
+ public Void call() throws Exception {
+ client.setEnabledProtocols(new String[] { "SSLv3" });
+ client.setEnabledCipherSuites(clientCipherSuites);
+ try {
+ client.startHandshake();
+ fail("Should receive TLS alert inappropriate fallback");
+ } catch (SSLHandshakeException expected) {
+ }
+ return null;
+ }
+ });
+ executor.shutdown();
+
+ s.get();
+ c.get();
+ client.close();
+ server.close();
+ context.close();
+ }
+
/**
* Not run by default by JUnit, but can be run by Vogar by
* specifying it explicitly (or with main method below)