1
0
mirror of https://github.com/square/okhttp.git synced 2026-01-12 10:23:16 +03:00

Merge pull request #1087 from square/jwilson_1012_tls_12

Revise how TLS connections are negotiated.
This commit is contained in:
Jesse Wilson
2014-10-12 23:29:50 -04:00
committed by Jesse Wilson
11 changed files with 298 additions and 216 deletions

View File

@@ -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;

View File

@@ -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();

View File

@@ -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());

View File

@@ -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. */

View File

@@ -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] };
}
};
}
}

View File

@@ -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<String> 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<String[]> 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<String> 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;

View File

@@ -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;
}
/**

View File

@@ -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;
* <li><strong>IP address:</strong> 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.
* <li><strong>TLS version:</strong> which TLS version to attempt with the
* HTTPS connection.
* <li><strong>TLS configuration:</strong> which cipher suites and TLS
* versions to attempt with the HTTPS connection.
* </ul>
* 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;
}
}

View File

@@ -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<String> cipherSuites() {
return Util.immutableList(cipherSuites);
}
public List<String> 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<String> supportedCipherSuites = Util.intersect(Arrays.asList(cipherSuites),
Arrays.asList(sslSocket.getSupportedCipherSuites()));
List<String> 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);
}
}

View File

@@ -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.
*
* <h3>ALPN and NPN</h3>
* 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<Protocol> 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<Protocol> 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<Protocol> 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<Protocol> 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<Protocol> protocols) {
@Override public void configureTlsExtensions(
SSLSocket sslSocket, String hostname, List<Protocol> protocols) {
List<String> 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<String> 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) {

View File

@@ -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<TlsConfiguration> 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<Route> 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. */