diff --git a/mockwebserver/src/main/java/com/squareup/okhttp/internal/spdy/SpdyServer.java b/mockwebserver/src/main/java/com/squareup/okhttp/internal/spdy/SpdyServer.java index 9689f27af..e86994a5f 100644 --- a/mockwebserver/src/main/java/com/squareup/okhttp/internal/spdy/SpdyServer.java +++ b/mockwebserver/src/main/java/com/squareup/okhttp/internal/spdy/SpdyServer.java @@ -67,7 +67,7 @@ public final class SpdyServer implements IncomingStreamHandler { (SSLSocket) sslSocketFactory.createSocket(socket, socket.getInetAddress().getHostAddress(), socket.getPort(), true); sslSocket.setUseClientMode(false); - Platform.get().setProtocols(sslSocket, spdyProtocols); + Platform.get().configureTlsExtensions(sslSocket, null, spdyProtocols); sslSocket.startHandshake(); String protocolString = Platform.get().getSelectedProtocol(sslSocket); protocol = protocolString != null ? Protocol.get(protocolString) : null; diff --git a/mockwebserver/src/main/java/com/squareup/okhttp/mockwebserver/MockWebServer.java b/mockwebserver/src/main/java/com/squareup/okhttp/mockwebserver/MockWebServer.java index a80d7d6de..5f83a1fc5 100644 --- a/mockwebserver/src/main/java/com/squareup/okhttp/mockwebserver/MockWebServer.java +++ b/mockwebserver/src/main/java/com/squareup/okhttp/mockwebserver/MockWebServer.java @@ -369,7 +369,7 @@ public final class MockWebServer { openClientSockets.put(socket, true); if (protocolNegotiationEnabled) { - Platform.get().setProtocols(sslSocket, protocols); + Platform.get().configureTlsExtensions(sslSocket, null, protocols); } sslSocket.startHandshake(); diff --git a/okhttp-tests/src/test/java/com/squareup/okhttp/ConnectionPoolTest.java b/okhttp-tests/src/test/java/com/squareup/okhttp/ConnectionPoolTest.java index 62abb53ed..e14a4669e 100644 --- a/okhttp-tests/src/test/java/com/squareup/okhttp/ConnectionPoolTest.java +++ b/okhttp-tests/src/test/java/com/squareup/okhttp/ConnectionPoolTest.java @@ -31,7 +31,6 @@ import org.junit.After; import org.junit.Before; import org.junit.Test; -import static com.squareup.okhttp.internal.http.RouteSelector.TLS_V1; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; @@ -88,8 +87,10 @@ public final class ConnectionPoolTest { spdySocketAddress = new InetSocketAddress(InetAddress.getByName(spdyServer.getHostName()), spdyServer.getPort()); - Route httpRoute = new Route(httpAddress, Proxy.NO_PROXY, httpSocketAddress, TLS_V1); - Route spdyRoute = new Route(spdyAddress, Proxy.NO_PROXY, spdySocketAddress, TLS_V1); + Route httpRoute = new Route(httpAddress, Proxy.NO_PROXY, httpSocketAddress, + TlsConfiguration.PREFERRED); + Route spdyRoute = new Route(spdyAddress, Proxy.NO_PROXY, spdySocketAddress, + TlsConfiguration.PREFERRED); pool = new ConnectionPool(poolSize, KEEP_ALIVE_DURATION_MS); httpA = new Connection(pool, httpRoute); httpA.connect(200, 200, 200, null); @@ -134,8 +135,8 @@ public final class ConnectionPoolTest { Connection connection = pool.get(httpAddress); assertNull(connection); - connection = new Connection( - pool, new Route(httpAddress, Proxy.NO_PROXY, httpSocketAddress, TLS_V1)); + connection = new Connection(pool, new Route(httpAddress, Proxy.NO_PROXY, httpSocketAddress, + TlsConfiguration.PREFERRED)); connection.connect(200, 200, 200, null); connection.setOwner(owner); assertEquals(0, pool.getConnectionCount()); diff --git a/okhttp-tests/src/test/java/com/squareup/okhttp/internal/http/RouteSelectorTest.java b/okhttp-tests/src/test/java/com/squareup/okhttp/internal/http/RouteSelectorTest.java index 398fdde6d..c7affd450 100644 --- a/okhttp-tests/src/test/java/com/squareup/okhttp/internal/http/RouteSelectorTest.java +++ b/okhttp-tests/src/test/java/com/squareup/okhttp/internal/http/RouteSelectorTest.java @@ -17,12 +17,12 @@ package com.squareup.okhttp.internal.http; import com.squareup.okhttp.Address; import com.squareup.okhttp.Authenticator; -import com.squareup.okhttp.CertificatePinner; import com.squareup.okhttp.Connection; import com.squareup.okhttp.ConnectionPool; import com.squareup.okhttp.OkHttpClient; import com.squareup.okhttp.Protocol; import com.squareup.okhttp.Request; +import com.squareup.okhttp.TlsConfiguration; import com.squareup.okhttp.internal.Internal; import com.squareup.okhttp.internal.Network; import com.squareup.okhttp.internal.RouteDatabase; @@ -45,8 +45,6 @@ import javax.net.ssl.SSLSocketFactory; import org.junit.Before; import org.junit.Test; -import static com.squareup.okhttp.internal.http.RouteSelector.SSL_V3; -import static com.squareup.okhttp.internal.http.RouteSelector.TLS_V1; import static java.net.Proxy.NO_PROXY; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -110,7 +108,7 @@ public final class RouteSelectorTest { assertTrue(routeSelector.hasNext()); dns.inetAddresses = makeFakeAddresses(255, 1); assertConnection(routeSelector.nextUnconnected(), address, NO_PROXY, dns.inetAddresses[0], - uriPort, SSL_V3); + uriPort, TlsConfiguration.FALLBACK); dns.assertRequests(uriHost); assertFalse(routeSelector.hasNext()); @@ -131,7 +129,7 @@ public final class RouteSelectorTest { routeDatabase.failed(connection.getRoute()); routeSelector = RouteSelector.get(httpRequest, client); assertConnection(routeSelector.nextUnconnected(), address, NO_PROXY, dns.inetAddresses[0], - uriPort, SSL_V3); + uriPort, TlsConfiguration.FALLBACK); assertFalse(routeSelector.hasNext()); try { routeSelector.nextUnconnected(); @@ -149,9 +147,9 @@ public final class RouteSelectorTest { assertTrue(routeSelector.hasNext()); dns.inetAddresses = makeFakeAddresses(255, 2); assertConnection(routeSelector.nextUnconnected(), address, proxyA, dns.inetAddresses[0], - proxyAPort, SSL_V3); + proxyAPort, TlsConfiguration.FALLBACK); assertConnection(routeSelector.nextUnconnected(), address, proxyA, dns.inetAddresses[1], - proxyAPort, SSL_V3); + proxyAPort, TlsConfiguration.FALLBACK); assertFalse(routeSelector.hasNext()); dns.assertRequests(proxyAHost); @@ -167,9 +165,9 @@ public final class RouteSelectorTest { assertTrue(routeSelector.hasNext()); dns.inetAddresses = makeFakeAddresses(255, 2); assertConnection(routeSelector.nextUnconnected(), address, NO_PROXY, dns.inetAddresses[0], - uriPort, SSL_V3); + uriPort, TlsConfiguration.FALLBACK); assertConnection(routeSelector.nextUnconnected(), address, NO_PROXY, dns.inetAddresses[1], - uriPort, SSL_V3); + uriPort, TlsConfiguration.FALLBACK); assertFalse(routeSelector.hasNext()); dns.assertRequests(uriHost); @@ -186,7 +184,7 @@ public final class RouteSelectorTest { assertTrue(routeSelector.hasNext()); dns.inetAddresses = makeFakeAddresses(255, 1); assertConnection(routeSelector.nextUnconnected(), address, NO_PROXY, dns.inetAddresses[0], - uriPort, SSL_V3); + uriPort, TlsConfiguration.FALLBACK); dns.assertRequests(uriHost); assertFalse(routeSelector.hasNext()); @@ -199,9 +197,9 @@ public final class RouteSelectorTest { assertTrue(routeSelector.hasNext()); dns.inetAddresses = makeFakeAddresses(255, 2); assertConnection(routeSelector.nextUnconnected(), address, NO_PROXY, dns.inetAddresses[0], - uriPort, SSL_V3); + uriPort, TlsConfiguration.FALLBACK); assertConnection(routeSelector.nextUnconnected(), address, NO_PROXY, dns.inetAddresses[1], - uriPort, SSL_V3); + uriPort, TlsConfiguration.FALLBACK); assertFalse(routeSelector.hasNext()); dns.assertRequests(uriHost); @@ -220,23 +218,23 @@ public final class RouteSelectorTest { assertTrue(routeSelector.hasNext()); dns.inetAddresses = makeFakeAddresses(255, 2); assertConnection(routeSelector.nextUnconnected(), address, proxyA, dns.inetAddresses[0], proxyAPort, - SSL_V3); + TlsConfiguration.FALLBACK); assertConnection(routeSelector.nextUnconnected(), address, proxyA, dns.inetAddresses[1], proxyAPort, - SSL_V3); + TlsConfiguration.FALLBACK); dns.assertRequests(proxyAHost); // Next try the IP address of the second proxy. assertTrue(routeSelector.hasNext()); dns.inetAddresses = makeFakeAddresses(254, 1); assertConnection(routeSelector.nextUnconnected(), address, proxyB, dns.inetAddresses[0], proxyBPort, - SSL_V3); + TlsConfiguration.FALLBACK); dns.assertRequests(proxyBHost); // Finally try the only IP address of the origin server. assertTrue(routeSelector.hasNext()); dns.inetAddresses = makeFakeAddresses(253, 1); assertConnection(routeSelector.nextUnconnected(), address, NO_PROXY, dns.inetAddresses[0], uriPort, - SSL_V3); + TlsConfiguration.FALLBACK); dns.assertRequests(uriHost); assertFalse(routeSelector.hasNext()); @@ -253,7 +251,7 @@ public final class RouteSelectorTest { assertTrue(routeSelector.hasNext()); dns.inetAddresses = makeFakeAddresses(255, 1); assertConnection(routeSelector.nextUnconnected(), address, NO_PROXY, dns.inetAddresses[0], uriPort, - SSL_V3); + TlsConfiguration.FALLBACK); dns.assertRequests(uriHost); assertFalse(routeSelector.hasNext()); @@ -270,8 +268,8 @@ public final class RouteSelectorTest { assertTrue(routeSelector.hasNext()); dns.inetAddresses = makeFakeAddresses(255, 1); - assertConnection(routeSelector.nextUnconnected(), address, proxyA, dns.inetAddresses[0], proxyAPort, - SSL_V3); + assertConnection(routeSelector.nextUnconnected(), address, proxyA, dns.inetAddresses[0], + proxyAPort, TlsConfiguration.FALLBACK); dns.assertRequests(proxyAHost); assertTrue(routeSelector.hasNext()); @@ -285,14 +283,14 @@ public final class RouteSelectorTest { assertTrue(routeSelector.hasNext()); dns.inetAddresses = makeFakeAddresses(255, 1); - assertConnection(routeSelector.nextUnconnected(), address, proxyA, dns.inetAddresses[0], proxyAPort, - SSL_V3); + assertConnection(routeSelector.nextUnconnected(), address, proxyA, dns.inetAddresses[0], + proxyAPort, TlsConfiguration.FALLBACK); dns.assertRequests(proxyAHost); assertTrue(routeSelector.hasNext()); dns.inetAddresses = makeFakeAddresses(254, 1); - assertConnection(routeSelector.nextUnconnected(), address, NO_PROXY, dns.inetAddresses[0], uriPort, - SSL_V3); + assertConnection(routeSelector.nextUnconnected(), address, NO_PROXY, dns.inetAddresses[0], + uriPort, TlsConfiguration.FALLBACK); dns.assertRequests(uriHost); assertFalse(routeSelector.hasNext()); @@ -306,7 +304,7 @@ public final class RouteSelectorTest { dns.inetAddresses = makeFakeAddresses(255, 1); Connection connection = routeSelector.nextUnconnected(); routeSelector.connectFailed(connection, new IOException("Non SSL exception")); - assertTrue(routeDatabase.failedRoutesCount() == 2); + assertEquals(RouteSelector.TLS_CONFIGURATIONS.size(), routeDatabase.failedRoutesCount()); assertFalse(routeSelector.hasNext()); } @@ -331,38 +329,38 @@ public final class RouteSelectorTest { // Proxy A dns.inetAddresses = makeFakeAddresses(255, 2); assertConnection(routeSelector.nextUnconnected(), address, proxyA, dns.inetAddresses[0], - proxyAPort, TLS_V1); + proxyAPort, TlsConfiguration.PREFERRED); dns.assertRequests(proxyAHost); assertConnection(routeSelector.nextUnconnected(), address, proxyA, dns.inetAddresses[0], - proxyAPort, SSL_V3); + proxyAPort, TlsConfiguration.FALLBACK); assertConnection(routeSelector.nextUnconnected(), address, proxyA, dns.inetAddresses[1], - proxyAPort, TLS_V1); + proxyAPort, TlsConfiguration.PREFERRED); assertConnection(routeSelector.nextUnconnected(), address, proxyA, dns.inetAddresses[1], - proxyAPort, SSL_V3); + proxyAPort, TlsConfiguration.FALLBACK); // Proxy B dns.inetAddresses = makeFakeAddresses(254, 2); assertConnection(routeSelector.nextUnconnected(), address, proxyB, dns.inetAddresses[0], - proxyBPort, TLS_V1); + proxyBPort, TlsConfiguration.PREFERRED); dns.assertRequests(proxyBHost); assertConnection(routeSelector.nextUnconnected(), address, proxyB, dns.inetAddresses[0], - proxyBPort, SSL_V3); + proxyBPort, TlsConfiguration.FALLBACK); assertConnection(routeSelector.nextUnconnected(), address, proxyB, dns.inetAddresses[1], - proxyBPort, TLS_V1); + proxyBPort, TlsConfiguration.PREFERRED); assertConnection(routeSelector.nextUnconnected(), address, proxyB, dns.inetAddresses[1], - proxyBPort, SSL_V3); + proxyBPort, TlsConfiguration.FALLBACK); // Origin dns.inetAddresses = makeFakeAddresses(253, 2); assertConnection(routeSelector.nextUnconnected(), address, NO_PROXY, dns.inetAddresses[0], - uriPort, TLS_V1); + uriPort, TlsConfiguration.PREFERRED); dns.assertRequests(uriHost); assertConnection(routeSelector.nextUnconnected(), address, NO_PROXY, dns.inetAddresses[0], - uriPort, SSL_V3); + uriPort, TlsConfiguration.FALLBACK); assertConnection(routeSelector.nextUnconnected(), address, NO_PROXY, dns.inetAddresses[1], - uriPort, TLS_V1); + uriPort, TlsConfiguration.PREFERRED); assertConnection(routeSelector.nextUnconnected(), address, NO_PROXY, dns.inetAddresses[1], - uriPort, SSL_V3); + uriPort, TlsConfiguration.FALLBACK); assertFalse(routeSelector.hasNext()); } @@ -397,12 +395,12 @@ public final class RouteSelectorTest { } private void assertConnection(Connection connection, Address address, Proxy proxy, - InetAddress socketAddress, int socketPort, String tlsVersion) { + InetAddress socketAddress, int socketPort, TlsConfiguration tlsConfiguration) { assertEquals(address, connection.getRoute().getAddress()); assertEquals(proxy, connection.getRoute().getProxy()); assertEquals(socketAddress, connection.getRoute().getSocketAddress().getAddress()); assertEquals(socketPort, connection.getRoute().getSocketAddress().getPort()); - assertEquals(tlsVersion, connection.getRoute().getTlsVersion()); + assertEquals(tlsConfiguration, connection.getRoute().getTlsConfiguration()); } /** Returns an address that's without an SSL socket factory or hostname verifier. */ diff --git a/okhttp-tests/src/test/java/com/squareup/okhttp/internal/http/URLConnectionTest.java b/okhttp-tests/src/test/java/com/squareup/okhttp/internal/http/URLConnectionTest.java index 05b1f51db..fad64dec6 100644 --- a/okhttp-tests/src/test/java/com/squareup/okhttp/internal/http/URLConnectionTest.java +++ b/okhttp-tests/src/test/java/com/squareup/okhttp/internal/http/URLConnectionTest.java @@ -25,7 +25,9 @@ import com.squareup.okhttp.OkHttpClient; import com.squareup.okhttp.OkUrlFactory; import com.squareup.okhttp.Protocol; import com.squareup.okhttp.Response; +import com.squareup.okhttp.TlsConfiguration; import com.squareup.okhttp.internal.Internal; +import com.squareup.okhttp.internal.Network; import com.squareup.okhttp.internal.RecordingAuthenticator; import com.squareup.okhttp.internal.RecordingHostnameVerifier; import com.squareup.okhttp.internal.RecordingOkAuthenticator; @@ -856,12 +858,11 @@ public final class URLConnectionTest { .setSocketPolicy(SocketPolicy.UPGRADE_TO_SSL_AT_END) .setBody("bogus proxy connect response content"); - // Enqueue a pair of responses for every IP address held by localhost, because the - // route selector will try each in sequence. - // TODO: use the fake Dns implementation instead of a loop - for (InetAddress inetAddress : InetAddress.getAllByName(server.getHostName())) { - server.enqueue(response); // For the first TLS tolerant connection - server.enqueue(response); // For the backwards-compatible SSLv3 retry + // Configure a single IP address for the host, so we don't retry once for each. Enqueue a + // response for all TLS configurations that will be attempted. + Internal.instance.setNetwork(client.client(), networkWithFirstAddressOnly()); + for (TlsConfiguration tlsConfiguration : RouteSelector.TLS_CONFIGURATIONS) { + server.enqueue(response); } server.play(); client.client().setProxy(server.toProxyAddress()); @@ -3296,4 +3297,14 @@ public final class URLConnectionTest { server.setProtocolNegotiationEnabled(true); server.setProtocols(client.client().getProtocols()); } + + /** Returns a network that resolves only one IP address per host. */ + private Network networkWithFirstAddressOnly() { + return new Network() { + @Override public InetAddress[] resolveInetAddresses(String host) throws UnknownHostException { + InetAddress[] allInetAddresses = Network.DEFAULT.resolveInetAddresses(host); + return new InetAddress[] { allInetAddresses[0] }; + } + }; + } } diff --git a/okhttp/src/main/java/com/squareup/okhttp/Connection.java b/okhttp/src/main/java/com/squareup/okhttp/Connection.java index 96a8e6841..5cef4b9ac 100644 --- a/okhttp/src/main/java/com/squareup/okhttp/Connection.java +++ b/okhttp/src/main/java/com/squareup/okhttp/Connection.java @@ -17,7 +17,6 @@ package com.squareup.okhttp; import com.squareup.okhttp.internal.Platform; -import com.squareup.okhttp.internal.Util; import com.squareup.okhttp.internal.http.HttpConnection; import com.squareup.okhttp.internal.http.HttpEngine; import com.squareup.okhttp.internal.http.HttpTransport; @@ -29,14 +28,10 @@ import java.io.IOException; import java.net.Proxy; import java.net.Socket; import java.net.URL; -import java.util.Arrays; -import java.util.List; -import java.util.concurrent.atomic.AtomicReference; import javax.net.ssl.SSLSocket; import static com.squareup.okhttp.internal.Util.getDefaultPort; import static com.squareup.okhttp.internal.Util.getEffectivePort; -import static com.squareup.okhttp.internal.Util.immutableList; import static java.net.HttpURLConnection.HTTP_OK; import static java.net.HttpURLConnection.HTTP_PROXY_AUTH; @@ -67,39 +62,6 @@ import static java.net.HttpURLConnection.HTTP_PROXY_AUTH; * should the attempt fail. */ public final class Connection { - /** - * This is a subset of the cipher suites supported in Chrome 37, current as of 2014-10-5. All of - * these suites are available on Android L; earlier releases support a subset of these suites. - * https://github.com/square/okhttp/issues/330 - */ - private static final List CIPHER_SUITES = immutableList( - "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", // 0xC0,0x2B Android L - "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", // 0xC0,0x2F Android L - "TLS_DHE_RSA_WITH_AES_128_GCM_SHA256", // 0x00,0x9E Android L - "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA", // 0xC0,0x0A Android 4.0 - "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", // 0xC0,0x09 Android 4.0 - "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", // 0xC0,0x13 Android 4.0 - "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", // 0xC0,0x14 Android 4.0 - "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA", // 0xC0,0x07 Android 4.0 - "TLS_ECDHE_RSA_WITH_RC4_128_SHA", // 0xC0,0x11 Android 4.0 - "TLS_DHE_RSA_WITH_AES_128_CBC_SHA", // 0x00,0x33 Android 2.3 - "TLS_DHE_DSS_WITH_AES_128_CBC_SHA", // 0x00,0x32 Android 2.3 - "TLS_DHE_RSA_WITH_AES_256_CBC_SHA", // 0x00,0x39 Android 2.3 - "TLS_RSA_WITH_AES_128_GCM_SHA256", // 0x00,0x9C Android L - "TLS_RSA_WITH_AES_128_CBC_SHA", // 0x00,0x2F Android 2.3 - "TLS_RSA_WITH_AES_256_CBC_SHA", // 0x00,0x35 Android 2.3 - "SSL_RSA_WITH_3DES_EDE_CBC_SHA", // 0x00,0x0A Android 2.3 (Deprecated in L) - "SSL_RSA_WITH_RC4_128_SHA", // 0x00,0x05 Android 2.3 - "SSL_RSA_WITH_RC4_128_MD5" // 0x00,0x04 Android 2.3 (Deprecated in L) - ); - - /** - * Cache of the intersection between {@link #CIPHER_SUITES} and the platform's supported suites. - * It's possible that the platform hosts multiple implementations of {@link SSLSocket}, in which - * case this cache will be incorrect. - */ - private static final AtomicReference SELECTED_CIPHER_SUITES = new AtomicReference<>(); - private final ConnectionPool pool; private final Route route; @@ -265,13 +227,9 @@ public final class Connection { socket = route.address.sslSocketFactory .createSocket(socket, route.address.uriHost, route.address.uriPort, true /* autoClose */); SSLSocket sslSocket = (SSLSocket) socket; - sslSocket.setEnabledCipherSuites(cipherSuites(sslSocket)); - platform.configureTls(sslSocket, route.address.uriHost, route.tlsVersion); - boolean useNpn = route.supportsNpn(); - if (useNpn) { - platform.setProtocols(sslSocket, route.address.protocols); - } + // Configure the socket's ciphers, TLS versions, and extensions. + route.tlsConfiguration.apply(sslSocket, route); // Force handshake. This can throw! sslSocket.startHandshake(); @@ -288,7 +246,8 @@ public final class Connection { handshake = Handshake.get(sslSocket.getSession()); String maybeProtocol; - if (useNpn && (maybeProtocol = platform.getSelectedProtocol(sslSocket)) != null) { + if (route.tlsConfiguration.supportsTlsExtensions() + && (maybeProtocol = platform.getSelectedProtocol(sslSocket)) != null) { protocol = Protocol.get(maybeProtocol); // Throws IOE on unknown. } @@ -302,17 +261,6 @@ public final class Connection { } } - private String[] cipherSuites(SSLSocket sslSocket) { - String[] result = SELECTED_CIPHER_SUITES.get(); - if (result == null) { - List intersection = Util.intersect(CIPHER_SUITES, - Arrays.asList(sslSocket.getSupportedCipherSuites())); - result = intersection.toArray(new String[intersection.size()]); - SELECTED_CIPHER_SUITES.set(result); - } - return result; - } - /** Returns true if {@link #connect} has been attempted on this connection. */ boolean isConnected() { return connected; diff --git a/okhttp/src/main/java/com/squareup/okhttp/OkHttpClient.java b/okhttp/src/main/java/com/squareup/okhttp/OkHttpClient.java index 53f52a439..90f98e260 100644 --- a/okhttp/src/main/java/com/squareup/okhttp/OkHttpClient.java +++ b/okhttp/src/main/java/com/squareup/okhttp/OkHttpClient.java @@ -148,25 +148,26 @@ public class OkHttpClient implements Cloneable { } private OkHttpClient(OkHttpClient okHttpClient) { - this.routeDatabase = okHttpClient.routeDatabase(); - this.dispatcher = okHttpClient.getDispatcher(); - this.proxy = okHttpClient.getProxy(); - this.protocols = okHttpClient.getProtocols(); - this.proxySelector = okHttpClient.getProxySelector(); - this.cookieHandler = okHttpClient.getCookieHandler(); - this.cache = okHttpClient.getCache(); + this.routeDatabase = okHttpClient.routeDatabase; + this.dispatcher = okHttpClient.dispatcher; + this.proxy = okHttpClient.proxy; + this.protocols = okHttpClient.protocols; + this.proxySelector = okHttpClient.proxySelector; + this.cookieHandler = okHttpClient.cookieHandler; + this.cache = okHttpClient.cache; this.internalCache = cache != null ? cache.internalCache : okHttpClient.internalCache; - this.socketFactory = okHttpClient.getSocketFactory(); - this.sslSocketFactory = okHttpClient.getSslSocketFactory(); - this.hostnameVerifier = okHttpClient.getHostnameVerifier(); - this.certificatePinner = okHttpClient.getCertificatePinner(); - this.authenticator = okHttpClient.getAuthenticator(); - this.connectionPool = okHttpClient.getConnectionPool(); - this.followSslRedirects = okHttpClient.getFollowSslRedirects(); - this.followRedirects = okHttpClient.getFollowRedirects(); - this.connectTimeout = okHttpClient.getConnectTimeout(); - this.readTimeout = okHttpClient.getReadTimeout(); - this.writeTimeout = okHttpClient.getWriteTimeout(); + this.socketFactory = okHttpClient.socketFactory; + this.sslSocketFactory = okHttpClient.sslSocketFactory; + this.hostnameVerifier = okHttpClient.hostnameVerifier; + this.certificatePinner = okHttpClient.certificatePinner; + this.authenticator = okHttpClient.authenticator; + this.connectionPool = okHttpClient.connectionPool; + this.network = okHttpClient.network; + this.followSslRedirects = okHttpClient.followSslRedirects; + this.followRedirects = okHttpClient.followRedirects; + this.connectTimeout = okHttpClient.connectTimeout; + this.readTimeout = okHttpClient.readTimeout; + this.writeTimeout = okHttpClient.writeTimeout; } /** diff --git a/okhttp/src/main/java/com/squareup/okhttp/Route.java b/okhttp/src/main/java/com/squareup/okhttp/Route.java index b6c42f530..9d4462b1c 100644 --- a/okhttp/src/main/java/com/squareup/okhttp/Route.java +++ b/okhttp/src/main/java/com/squareup/okhttp/Route.java @@ -15,7 +15,6 @@ */ package com.squareup.okhttp; -import com.squareup.okhttp.internal.http.RouteSelector; import java.net.InetSocketAddress; import java.net.Proxy; @@ -29,8 +28,8 @@ import java.net.Proxy; *
  • IP address: whether connecting directly to an origin * server or a proxy, opening a socket requires an IP address. The DNS * server may return multiple IP addresses to attempt. - *
  • TLS version: which TLS version to attempt with the - * HTTPS connection. + *
  • TLS configuration: which cipher suites and TLS + * versions to attempt with the HTTPS connection. * * Each route is a specific selection of these options. */ @@ -38,18 +37,25 @@ public final class Route { final Address address; final Proxy proxy; final InetSocketAddress inetSocketAddress; - final String tlsVersion; + final TlsConfiguration tlsConfiguration; + /** @deprecated replaced with a constructor that takes a {@link TlsConfiguration}. */ public Route(Address address, Proxy proxy, InetSocketAddress inetSocketAddress, String tlsVersion) { + this(address, proxy, inetSocketAddress, + tlsVersion.equals("SSLv3") ? TlsConfiguration.PREFERRED : TlsConfiguration.FALLBACK); + } + + public Route(Address address, Proxy proxy, InetSocketAddress inetSocketAddress, + TlsConfiguration tlsConfiguration) { if (address == null) throw new NullPointerException("address == null"); if (proxy == null) throw new NullPointerException("proxy == null"); if (inetSocketAddress == null) throw new NullPointerException("inetSocketAddress == null"); - if (tlsVersion == null) throw new NullPointerException("tlsVersion == null"); + if (tlsConfiguration == null) throw new NullPointerException("tlsConfiguration == null"); this.address = address; this.proxy = proxy; this.inetSocketAddress = inetSocketAddress; - this.tlsVersion = tlsVersion; + this.tlsConfiguration = tlsConfiguration; } public Address getAddress() { @@ -67,16 +73,17 @@ public final class Route { return proxy; } + /** @deprecated replaced with {@link #getTlsConfiguration()}. */ + public String getTlsVersion() { + return tlsConfiguration.tlsVersions().get(0); + } + public InetSocketAddress getSocketAddress() { return inetSocketAddress; } - public String getTlsVersion() { - return tlsVersion; - } - - boolean supportsNpn() { - return !tlsVersion.equals(RouteSelector.SSL_V3); + public TlsConfiguration getTlsConfiguration() { + return tlsConfiguration; } /** @@ -93,7 +100,7 @@ public final class Route { return address.equals(other.address) && proxy.equals(other.proxy) && inetSocketAddress.equals(other.inetSocketAddress) - && tlsVersion.equals(other.tlsVersion); + && tlsConfiguration.equals(other.tlsConfiguration); } return false; } @@ -103,7 +110,7 @@ public final class Route { result = 31 * result + address.hashCode(); result = 31 * result + proxy.hashCode(); result = 31 * result + inetSocketAddress.hashCode(); - result = 31 * result + tlsVersion.hashCode(); + result = 31 * result + tlsConfiguration.hashCode(); return result; } } diff --git a/okhttp/src/main/java/com/squareup/okhttp/TlsConfiguration.java b/okhttp/src/main/java/com/squareup/okhttp/TlsConfiguration.java new file mode 100644 index 000000000..19df8381e --- /dev/null +++ b/okhttp/src/main/java/com/squareup/okhttp/TlsConfiguration.java @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2014 Square, Inc. + * + * 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 com.squareup.okhttp; + +import com.squareup.okhttp.internal.Platform; +import com.squareup.okhttp.internal.Util; +import java.util.Arrays; +import java.util.List; +import javax.net.ssl.SSLSocket; + +/** + * The TLS version and ciphers used to negotiate a secure connection. */ +public final class TlsConfiguration { + /** + * This is a subset of the cipher suites supported in Chrome 37, current as of 2014-10-5. All of + * these suites are available on Android L; earlier releases support a subset of these suites. + * https://github.com/square/okhttp/issues/330 + */ + private static final String[] CIPHER_SUITES = new String[] { + "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", // 0xC0,0x2B Android L + "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", // 0xC0,0x2F Android L + "TLS_DHE_RSA_WITH_AES_128_GCM_SHA256", // 0x00,0x9E Android L + "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA", // 0xC0,0x0A Android 4.0 + "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", // 0xC0,0x09 Android 4.0 + "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", // 0xC0,0x13 Android 4.0 + "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", // 0xC0,0x14 Android 4.0 + "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA", // 0xC0,0x07 Android 4.0 + "TLS_ECDHE_RSA_WITH_RC4_128_SHA", // 0xC0,0x11 Android 4.0 + "TLS_DHE_RSA_WITH_AES_128_CBC_SHA", // 0x00,0x33 Android 2.3 + "TLS_DHE_DSS_WITH_AES_128_CBC_SHA", // 0x00,0x32 Android 2.3 + "TLS_DHE_RSA_WITH_AES_256_CBC_SHA", // 0x00,0x39 Android 2.3 + "TLS_RSA_WITH_AES_128_GCM_SHA256", // 0x00,0x9C Android L + "TLS_RSA_WITH_AES_128_CBC_SHA", // 0x00,0x2F Android 2.3 + "TLS_RSA_WITH_AES_256_CBC_SHA", // 0x00,0x35 Android 2.3 + "SSL_RSA_WITH_3DES_EDE_CBC_SHA", // 0x00,0x0A Android 2.3 (Deprecated in L) + "SSL_RSA_WITH_RC4_128_SHA", // 0x00,0x05 Android 2.3 + "SSL_RSA_WITH_RC4_128_MD5" // 0x00,0x04 Android 2.3 (Deprecated in L) + }; + + private static final String TLS_1_2 = "TLSv1.2"; // 2008. + private static final String TLS_1_1 = "TLSv1.1"; // 2006. + private static final String TLS_1_0 = "TLSv1"; // 1999. + private static final String SSL_3_0 = "SSLv3"; // 1996. + + /** A modern TLS configuration with extensions like SNI and ALPN available. */ + public static final TlsConfiguration PREFERRED = new TlsConfiguration( + CIPHER_SUITES, new String[] { TLS_1_2, TLS_1_1, TLS_1_0, SSL_3_0 }, true); + + /** A backwards-compatible fallback configuration for interop with obsolete servers. */ + public static final TlsConfiguration FALLBACK = new TlsConfiguration( + CIPHER_SUITES, new String[] { SSL_3_0 }, true); + + private final String[] cipherSuites; + private final String[] tlsVersions; + private final boolean supportsTlsExtensions; + + /** + * Caches the subset of this TLS configuration that's supported by the platform. It's possible + * that the platform hosts multiple implementations of {@link SSLSocket}, in which case this cache + * will be incorrect. + */ + private TlsConfiguration supportedConfiguration; + + private TlsConfiguration(String[] cipherSuites, String[] tlsVersions, + boolean supportsTlsExtensions) { + this.cipherSuites = cipherSuites; + this.tlsVersions = tlsVersions; + this.supportsTlsExtensions = supportsTlsExtensions; + } + + public List cipherSuites() { + return Util.immutableList(cipherSuites); + } + + public List tlsVersions() { + return Util.immutableList(tlsVersions); + } + + public boolean supportsTlsExtensions() { + return supportsTlsExtensions; + } + + /** Applies this configuration to {@code sslSocket} for {@code route}. */ + public void apply(SSLSocket sslSocket, Route route) { + TlsConfiguration configurationToApply = supportedConfiguration; + if (configurationToApply == null) { + configurationToApply = supportedConfiguration(sslSocket); + supportedConfiguration = configurationToApply; + } + + sslSocket.setEnabledProtocols(configurationToApply.tlsVersions); + sslSocket.setEnabledCipherSuites(configurationToApply.cipherSuites); + + Platform platform = Platform.get(); + if (configurationToApply.supportsTlsExtensions) { + platform.configureTlsExtensions(sslSocket, route.address.uriHost, route.address.protocols); + } + } + + /** + * Returns a copy of this that omits cipher suites and TLS versions not + * supported by {@code sslSocket}. + */ + private TlsConfiguration supportedConfiguration(SSLSocket sslSocket) { + List supportedCipherSuites = Util.intersect(Arrays.asList(cipherSuites), + Arrays.asList(sslSocket.getSupportedCipherSuites())); + List supportedTlsVersions = Util.intersect(Arrays.asList(tlsVersions), + Arrays.asList(sslSocket.getSupportedProtocols())); + return new TlsConfiguration( + supportedCipherSuites.toArray(new String[supportedCipherSuites.size()]), + supportedTlsVersions.toArray(new String[supportedTlsVersions.size()]), + supportsTlsExtensions); + } +} diff --git a/okhttp/src/main/java/com/squareup/okhttp/internal/Platform.java b/okhttp/src/main/java/com/squareup/okhttp/internal/Platform.java index 70ae3ee6d..a433c0e07 100644 --- a/okhttp/src/main/java/com/squareup/okhttp/internal/Platform.java +++ b/okhttp/src/main/java/com/squareup/okhttp/internal/Platform.java @@ -37,6 +37,7 @@ import okio.Buffer; /** * Access to Platform-specific features necessary for SPDY and advanced TLS. + * This includes Server Name Indication (SNI) and session tickets. * *

    ALPN and NPN

    * This class uses TLS extensions ALPN and NPN to negotiate the upgrade from @@ -84,17 +85,13 @@ public class Platform { } /** - * Configure the TLS connection to use {@code tlsVersion}. We also bundle - * certain extensions with certain versions. In particular, we enable Server - * Name Indication (SNI) and Next Protocol Negotiation (NPN) with TLSv1 on - * platforms that support them. + * Configure TLS extensions on {@code sslSocket} for {@code route}. + * + * @param hostname non-null for client-side handshakes; null for + * server-side handshakes. */ - public void configureTls(SSLSocket socket, String uriHost, String tlsVersion) { - // We don't call setEnabledProtocols("TLSv1") on the assumption that that's - // the default. TODO: confirm this and support more TLS versions. - if (tlsVersion.equals("SSLv3")) { - socket.setEnabledProtocols(new String[] {"SSLv3"}); - } + public void configureTlsExtensions(SSLSocket sslSocket, String hostname, + List protocols) { } /** Returns the negotiated protocol, or null if no protocol was negotiated. */ @@ -102,13 +99,6 @@ public class Platform { return null; } - /** - * Sets client-supported protocols on a socket to send to a server. The - * protocols are only sent if the socket implementation supports ALPN or NPN. - */ - public void setProtocols(SSLSocket socket, List protocols) { - } - public void connectSocket(Socket socket, InetSocketAddress address, int connectTimeout) throws IOException { socket.connect(address, connectTimeout); @@ -227,31 +217,32 @@ public class Platform { } } - @Override public void configureTls(SSLSocket socket, String uriHost, String tlsVersion) { - super.configureTls(socket, uriHost, tlsVersion); + @Override public void configureTlsExtensions( + SSLSocket sslSocket, String hostname, List protocols) { + if (!openSslSocketClass.isInstance(sslSocket)) return; - if (tlsVersion.equals("TLSv1") && openSslSocketClass.isInstance(socket)) { + // Enable SNI and session tickets. + if (hostname != null) { try { - setUseSessionTickets.invoke(socket, true); - setHostname.invoke(socket, uriHost); + setUseSessionTickets.invoke(sslSocket, true); + setHostname.invoke(sslSocket, hostname); } catch (InvocationTargetException e) { - throw new RuntimeException(e); + throw new RuntimeException(e.getCause()); } catch (IllegalAccessException e) { throw new AssertionError(e); } } - } - @Override public void setProtocols(SSLSocket socket, List protocols) { - if (setNpnProtocols == null) return; - if (!openSslSocketClass.isInstance(socket)) return; - try { - Object[] parameters = { concatLengthPrefixed(protocols) }; - setNpnProtocols.invoke(socket, parameters); - } catch (IllegalAccessException e) { - throw new AssertionError(e); - } catch (InvocationTargetException e) { - throw new RuntimeException(e); + // Enable NPN. + if (setNpnProtocols != null) { + try { + Object[] parameters = { concatLengthPrefixed(protocols) }; + setNpnProtocols.invoke(sslSocket, parameters); + } catch (IllegalAccessException e) { + throw new AssertionError(e); + } catch (InvocationTargetException e) { + throw new RuntimeException(e.getCause()); + } } } @@ -263,7 +254,7 @@ public class Platform { if (npnResult == null) return null; return new String(npnResult, Util.UTF_8); } catch (InvocationTargetException e) { - throw new RuntimeException(e); + throw new RuntimeException(e.getCause()); } catch (IllegalAccessException e) { throw new AssertionError(e); } @@ -277,7 +268,7 @@ public class Platform { } catch (IllegalAccessException e) { throw new RuntimeException(e); } catch (InvocationTargetException e) { - throw new RuntimeException(e); + throw new RuntimeException(e.getCause()); } } @@ -289,7 +280,7 @@ public class Platform { } catch (IllegalAccessException e) { throw new RuntimeException(e); } catch (InvocationTargetException e) { - throw new RuntimeException(e); + throw new RuntimeException(e.getCause()); } } } @@ -312,17 +303,18 @@ public class Platform { this.serverProviderClass = serverProviderClass; } - @Override public void setProtocols(SSLSocket socket, List protocols) { + @Override public void configureTlsExtensions( + SSLSocket sslSocket, String hostname, List protocols) { + List names = new ArrayList<>(protocols.size()); + for (int i = 0, size = protocols.size(); i < size; i++) { + Protocol protocol = protocols.get(i); + if (protocol == Protocol.HTTP_1_0) continue; // No HTTP/1.0 for NPN or ALPN. + names.add(protocol.toString()); + } try { - List names = new ArrayList<>(protocols.size()); - for (int i = 0, size = protocols.size(); i < size; i++) { - Protocol protocol = protocols.get(i); - if (protocol == Protocol.HTTP_1_0) continue; // No HTTP/1.0 for NPN or ALPN. - names.add(protocol.toString()); - } Object provider = Proxy.newProxyInstance(Platform.class.getClassLoader(), new Class[] { clientProviderClass, serverProviderClass }, new JettyNegoProvider(names)); - putMethod.invoke(null, socket, provider); + putMethod.invoke(null, sslSocket, provider); } catch (InvocationTargetException e) { throw new AssertionError(e); } catch (IllegalAccessException e) { diff --git a/okhttp/src/main/java/com/squareup/okhttp/internal/http/RouteSelector.java b/okhttp/src/main/java/com/squareup/okhttp/internal/http/RouteSelector.java index 2043878ba..64fb5f7aa 100644 --- a/okhttp/src/main/java/com/squareup/okhttp/internal/http/RouteSelector.java +++ b/okhttp/src/main/java/com/squareup/okhttp/internal/http/RouteSelector.java @@ -19,12 +19,14 @@ import com.squareup.okhttp.Address; import com.squareup.okhttp.CertificatePinner; import com.squareup.okhttp.Connection; import com.squareup.okhttp.ConnectionPool; -import com.squareup.okhttp.internal.Network; import com.squareup.okhttp.OkHttpClient; import com.squareup.okhttp.Request; import com.squareup.okhttp.Route; +import com.squareup.okhttp.TlsConfiguration; import com.squareup.okhttp.internal.Internal; +import com.squareup.okhttp.internal.Network; import com.squareup.okhttp.internal.RouteDatabase; +import com.squareup.okhttp.internal.Util; import java.io.IOException; import java.net.InetAddress; import java.net.InetSocketAddress; @@ -50,8 +52,8 @@ import static com.squareup.okhttp.internal.Util.getEffectivePort; * recycled. */ public final class RouteSelector { - public static final String TLS_V1 = "TLSv1"; - public static final String SSL_V3 = "SSLv3"; + public static final List TLS_CONFIGURATIONS = Util.immutableList( + TlsConfiguration.PREFERRED, TlsConfiguration.FALLBACK); private final Address address; private final URI uri; @@ -76,8 +78,8 @@ public final class RouteSelector { private int nextSocketAddressIndex; private int socketPort; - /* TLS version to attempt with the connection. */ - private String nextTlsVersion; + /* TLS configuration to attempt with the connection. */ + private int nextTlsConfigurationIndex = TLS_CONFIGURATIONS.size(); /* State for negotiating failed routes */ private final List postponedRoutes = new ArrayList<>(); @@ -122,7 +124,7 @@ public final class RouteSelector { * least one route. */ public boolean hasNext() { - return hasNextTlsVersion() + return hasNextTlsConfiguration() || hasNextInetSocketAddress() || hasNextProxy() || hasNextPostponed(); @@ -148,7 +150,7 @@ public final class RouteSelector { } // Compute the next route to attempt. - if (!hasNextTlsVersion()) { + if (!hasNextTlsConfiguration()) { if (!hasNextInetSocketAddress()) { if (!hasNextProxy()) { if (!hasNextPostponed()) { @@ -160,11 +162,11 @@ public final class RouteSelector { resetNextInetSocketAddress(lastProxy); } lastInetSocketAddress = nextInetSocketAddress(); - resetNextTlsVersion(); + resetNextTlsConfiguration(); } - String tlsVersion = nextTlsVersion(); - Route route = new Route(address, lastProxy, lastInetSocketAddress, tlsVersion); + TlsConfiguration tlsConfiguration = nextTlsConfiguration(); + Route route = new Route(address, lastProxy, lastInetSocketAddress, tlsConfiguration); if (routeDatabase.shouldPostpone(route)) { postponedRoutes.add(route); // We will only recurse in order to skip previously failed routes. They will be @@ -195,8 +197,9 @@ public final class RouteSelector { // the next route only changes the TLS mode, we shouldn't even attempt it. // This suppresses it in both this selector and also in the route database. if (!(failure instanceof SSLHandshakeException) && !(failure instanceof SSLProtocolException)) { - while (hasNextTlsVersion()) { - Route toSuppress = new Route(address, lastProxy, lastInetSocketAddress, nextTlsVersion()); + while (hasNextTlsConfiguration()) { + Route toSuppress = new Route(address, lastProxy, lastInetSocketAddress, + nextTlsConfiguration()); routeDatabase.failed(toSuppress); } } @@ -286,31 +289,25 @@ public final class RouteSelector { } /** - * Resets {@link #nextTlsVersion} to the first option. For routes that don't - * use SSL, this returns {@link #SSL_V3} so that there is no SSL fallback. + * Resets {@link #nextTlsConfiguration} to the first option. For routes that + * don't use TLS, this returns the fallback parameters so that there are no + * unnecessary retries. */ - private void resetNextTlsVersion() { - nextTlsVersion = (address.getSslSocketFactory() != null) ? TLS_V1 : SSL_V3; + private void resetNextTlsConfiguration() { + nextTlsConfigurationIndex = address.getSslSocketFactory() != null + ? 0 + : TLS_CONFIGURATIONS.size() - 1; } - /** Returns true if there's another TLS version to try. */ - private boolean hasNextTlsVersion() { - return nextTlsVersion != null; + /** Returns true if there's another TLS configuration to try. */ + private boolean hasNextTlsConfiguration() { + return nextTlsConfigurationIndex < TLS_CONFIGURATIONS.size(); } - /** Returns the next TLS mode to try. */ - private String nextTlsVersion() { - if (nextTlsVersion == null) { - throw new IllegalStateException("No next TLS version"); - } else if (nextTlsVersion.equals(TLS_V1)) { - nextTlsVersion = SSL_V3; - return TLS_V1; - } else if (nextTlsVersion.equals(SSL_V3)) { - nextTlsVersion = null; // So that hasNextTlsVersion() returns false. - return SSL_V3; - } else { - throw new AssertionError(); - } + /** Returns the next TLS configuration to try. */ + private TlsConfiguration nextTlsConfiguration() { + if (!hasNextTlsConfiguration()) throw new IllegalStateException("No next TLS configuration"); + return TLS_CONFIGURATIONS.get(nextTlsConfigurationIndex++); } /** Returns true if there is another postponed route to try. */