diff --git a/okhttp/src/main/java/com/squareup/okhttp/ConnectionConfiguration.java b/okhttp/src/main/java/com/squareup/okhttp/ConnectionConfiguration.java index 3fa4b41ab6a9..2360812bb502 100644 --- a/okhttp/src/main/java/com/squareup/okhttp/ConnectionConfiguration.java +++ b/okhttp/src/main/java/com/squareup/okhttp/ConnectionConfiguration.java @@ -27,53 +27,52 @@ * when negotiating a secure connection. */ public final class ConnectionConfiguration { - /** - * 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 ConnectionConfiguration MODERN_TLS = new ConnectionConfiguration( - true, CIPHER_SUITES, new String[] { TLS_1_2, TLS_1_1, TLS_1_0, SSL_3_0 }, true); + public static final ConnectionConfiguration MODERN_TLS = new Builder(true) + .cipherSuites( + // 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 + "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) + ) + .tlsVersions(TLS_1_2, TLS_1_1, TLS_1_0, SSL_3_0) + .supportsTlsExtensions(true) + .build(); /** A backwards-compatible fallback configuration for interop with obsolete servers. */ - public static final ConnectionConfiguration COMPATIBLE_TLS = new ConnectionConfiguration( - true, CIPHER_SUITES, new String[] { SSL_3_0 }, true); + public static final ConnectionConfiguration COMPATIBLE_TLS = new Builder(MODERN_TLS) + .tlsVersions(SSL_3_0) + .build(); /** Unencrypted, unauthenticated connections for {@code http:} URLs. */ - public static final ConnectionConfiguration CLEARTEXT = new ConnectionConfiguration( - false, new String[0], new String[0], false); + public static final ConnectionConfiguration CLEARTEXT = new Builder(false).build(); - private final boolean tls; + final boolean tls; private final String[] cipherSuites; private final String[] tlsVersions; - private final boolean supportsTlsExtensions; + final boolean supportsTlsExtensions; /** * Caches the subset of this configuration that's supported by the host @@ -82,19 +81,11 @@ public final class ConnectionConfiguration { */ private ConnectionConfiguration supportedConfiguration; - private ConnectionConfiguration(boolean tls, String[] cipherSuites, String[] tlsVersions, - boolean supportsTlsExtensions) { - this.tls = tls; - this.cipherSuites = cipherSuites; - this.tlsVersions = tlsVersions; - this.supportsTlsExtensions = supportsTlsExtensions; - - if (tls && (cipherSuites.length == 0 || tlsVersions.length == 0)) { - throw new IllegalArgumentException("Unexpected configuration: " + this); - } - if (!tls && (cipherSuites.length != 0 || tlsVersions.length != 0 || supportsTlsExtensions)) { - throw new IllegalArgumentException("Unexpected configuration: " + this); - } + private ConnectionConfiguration(Builder builder) { + this.tls = builder.tls; + this.cipherSuites = builder.cipherSuites; + this.tlsVersions = builder.tlsVersions; + this.supportsTlsExtensions = builder.supportsTlsExtensions; } public boolean isTls() { @@ -114,7 +105,7 @@ public boolean supportsTlsExtensions() { } /** Applies this configuration to {@code sslSocket} for {@code route}. */ - public void apply(SSLSocket sslSocket, Route route) { + void apply(SSLSocket sslSocket, Route route) { ConnectionConfiguration configurationToApply = supportedConfiguration; if (configurationToApply == null) { configurationToApply = supportedConfiguration(sslSocket); @@ -139,17 +130,85 @@ private ConnectionConfiguration supportedConfiguration(SSLSocket sslSocket) { Arrays.asList(sslSocket.getSupportedCipherSuites())); List supportedTlsVersions = Util.intersect(Arrays.asList(tlsVersions), Arrays.asList(sslSocket.getSupportedProtocols())); - return new ConnectionConfiguration(tls, - supportedCipherSuites.toArray(new String[supportedCipherSuites.size()]), - supportedTlsVersions.toArray(new String[supportedTlsVersions.size()]), - supportsTlsExtensions); + return new Builder(this) + .cipherSuites(supportedCipherSuites.toArray(new String[supportedCipherSuites.size()])) + .tlsVersions(supportedTlsVersions.toArray(new String[supportedTlsVersions.size()])) + .build(); + } + + @Override public boolean equals(Object other) { + if (!(other instanceof ConnectionConfiguration)) return false; + + ConnectionConfiguration that = (ConnectionConfiguration) other; + if (this.tls != that.tls) return false; + + if (tls) { + if (!Arrays.equals(this.cipherSuites, that.cipherSuites)) return false; + if (!Arrays.equals(this.tlsVersions, that.tlsVersions)) return false; + if (this.supportsTlsExtensions != that.supportsTlsExtensions) return false; + } + + return true; + } + + @Override public int hashCode() { + int result = 17; + if (tls) { + result = 31 * result + Arrays.hashCode(cipherSuites); + result = 31 * result + Arrays.hashCode(tlsVersions); + result = 31 * result + (supportsTlsExtensions ? 0 : 1); + } + return result; } @Override public String toString() { - return "ConnectionConfiguration(tls=" + tls - + ", cipherSuites=" + Arrays.toString(cipherSuites) - + ", tlsVersions=" + Arrays.toString(tlsVersions) - + ", supportsTlsExtensions=" + supportsTlsExtensions - + ")"; + if (tls) { + return "ConnectionConfiguration(cipherSuites=" + Arrays.toString(cipherSuites) + + ", tlsVersions=" + Arrays.toString(tlsVersions) + + ", supportsTlsExtensions=" + supportsTlsExtensions + + ")"; + } else { + return "ConnectionConfiguration()"; + } + } + + public static final class Builder { + private boolean tls; + private String[] cipherSuites; + private String[] tlsVersions; + private boolean supportsTlsExtensions; + + private Builder(boolean tls) { + this.tls = tls; + } + + public Builder(ConnectionConfiguration connectionConfiguration) { + this.tls = connectionConfiguration.tls; + this.cipherSuites = connectionConfiguration.cipherSuites; + this.tlsVersions = connectionConfiguration.tlsVersions; + this.supportsTlsExtensions = connectionConfiguration.supportsTlsExtensions; + } + + public Builder cipherSuites(String... cipherSuites) { + if (!tls) throw new IllegalStateException("no cipher suites for cleartext connections"); + this.cipherSuites = cipherSuites.clone(); // Defensive copy. + return this; + } + + public Builder tlsVersions(String... tlsVersions) { + if (!tls) throw new IllegalStateException("no TLS versions for cleartext connections"); + this.tlsVersions = tlsVersions.clone(); // Defensive copy. + return this; + } + + public Builder supportsTlsExtensions(boolean supportsTlsExtensions) { + if (!tls) throw new IllegalStateException("no TLS extensions for cleartext connections"); + this.supportsTlsExtensions = supportsTlsExtensions; + return this; + } + + public ConnectionConfiguration build() { + return new ConnectionConfiguration(this); + } } } diff --git a/okhttp/src/main/java/com/squareup/okhttp/OkHttpClient.java b/okhttp/src/main/java/com/squareup/okhttp/OkHttpClient.java index 02695b77888b..b51c8b8ea1b9 100644 --- a/okhttp/src/main/java/com/squareup/okhttp/OkHttpClient.java +++ b/okhttp/src/main/java/com/squareup/okhttp/OkHttpClient.java @@ -406,12 +406,7 @@ public final boolean getFollowSslRedirects() { return followSslRedirects; } - /** - * Configure this client to follow redirects. - * - *

If unset, redirects will not be followed. This is the equivalent as the - * built-in {@code HttpURLConnection}'s default. - */ + /** Configure this client to follow redirects. If unset, redirects be followed. */ public final void setFollowRedirects(boolean followRedirects) { this.followRedirects = followRedirects; }