337 lines
12 KiB
Java
337 lines
12 KiB
Java
![]() |
/* GENERATED SOURCE. DO NOT MODIFY. */
|
||
|
/*
|
||
|
* 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.android.okhttp;
|
||
|
|
||
|
import com.android.okhttp.internal.Util;
|
||
|
import java.util.Arrays;
|
||
|
import java.util.List;
|
||
|
import javax.net.ssl.SSLSocket;
|
||
|
|
||
|
import static com.android.okhttp.internal.Util.concat;
|
||
|
import static com.android.okhttp.internal.Util.contains;
|
||
|
|
||
|
/**
|
||
|
* Specifies configuration for the socket connection that HTTP traffic travels through. For {@code
|
||
|
* https:} URLs, this includes the TLS version and cipher suites to use when negotiating a secure
|
||
|
* connection.
|
||
|
*
|
||
|
* <p>The TLS versions configured in a connection spec are only be used if they are also enabled in
|
||
|
* the SSL socket. For example, if an SSL socket does not have TLS 1.2 enabled, it will not be used
|
||
|
* even if it is present on the connection spec. The same policy also applies to cipher suites.
|
||
|
*
|
||
|
* <p>Use {@link Builder#allEnabledTlsVersions()} and {@link Builder#allEnabledCipherSuites} to
|
||
|
* defer all feature selection to the underlying SSL socket.
|
||
|
* @hide This class is not part of the Android public SDK API
|
||
|
*/
|
||
|
public final class ConnectionSpec {
|
||
|
|
||
|
// This is a subset of the cipher suites supported in Chrome 46, current as of 2015-11-05.
|
||
|
// All of these suites are available on Android 5.0; earlier releases support a subset of
|
||
|
// these suites. https://github.com/square/okhttp/issues/330
|
||
|
private static final CipherSuite[] APPROVED_CIPHER_SUITES = new CipherSuite[] {
|
||
|
CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
|
||
|
CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
|
||
|
CipherSuite.TLS_DHE_RSA_WITH_AES_128_GCM_SHA256,
|
||
|
|
||
|
// Note that the following cipher suites are all on HTTP/2's bad cipher suites list. We'll
|
||
|
// continue to include them until better suites are commonly available. For example, none
|
||
|
// of the better cipher suites listed above shipped with Android 4.4 or Java 7.
|
||
|
CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
|
||
|
CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
|
||
|
CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
|
||
|
CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
|
||
|
CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA,
|
||
|
CipherSuite.TLS_DHE_RSA_WITH_AES_256_CBC_SHA,
|
||
|
CipherSuite.TLS_RSA_WITH_AES_128_GCM_SHA256,
|
||
|
CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA,
|
||
|
CipherSuite.TLS_RSA_WITH_AES_256_CBC_SHA,
|
||
|
CipherSuite.TLS_RSA_WITH_3DES_EDE_CBC_SHA,
|
||
|
};
|
||
|
|
||
|
/** A modern TLS connection with extensions like SNI and ALPN available. */
|
||
|
public static final ConnectionSpec MODERN_TLS = new Builder(true)
|
||
|
.cipherSuites(APPROVED_CIPHER_SUITES)
|
||
|
.tlsVersions(TlsVersion.TLS_1_2, TlsVersion.TLS_1_1, TlsVersion.TLS_1_0)
|
||
|
.supportsTlsExtensions(true)
|
||
|
.build();
|
||
|
|
||
|
/** A backwards-compatible fallback connection for interop with obsolete servers. */
|
||
|
public static final ConnectionSpec COMPATIBLE_TLS = new Builder(MODERN_TLS)
|
||
|
.tlsVersions(TlsVersion.TLS_1_0)
|
||
|
.supportsTlsExtensions(true)
|
||
|
.build();
|
||
|
|
||
|
/** Unencrypted, unauthenticated connections for {@code http:} URLs. */
|
||
|
public static final ConnectionSpec CLEARTEXT = new Builder(false).build();
|
||
|
|
||
|
private final boolean tls;
|
||
|
private final boolean supportsTlsExtensions;
|
||
|
private final String[] cipherSuites;
|
||
|
private final String[] tlsVersions;
|
||
|
|
||
|
private ConnectionSpec(Builder builder) {
|
||
|
this.tls = builder.tls;
|
||
|
this.cipherSuites = builder.cipherSuites;
|
||
|
this.tlsVersions = builder.tlsVersions;
|
||
|
this.supportsTlsExtensions = builder.supportsTlsExtensions;
|
||
|
}
|
||
|
|
||
|
public boolean isTls() {
|
||
|
return tls;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the cipher suites to use for a connection. Returns {@code null} if all of the SSL
|
||
|
* socket's enabled cipher suites should be used.
|
||
|
*/
|
||
|
public List<CipherSuite> cipherSuites() {
|
||
|
if (cipherSuites == null) return null;
|
||
|
|
||
|
CipherSuite[] result = new CipherSuite[cipherSuites.length];
|
||
|
for (int i = 0; i < cipherSuites.length; i++) {
|
||
|
result[i] = CipherSuite.forJavaName(cipherSuites[i]);
|
||
|
}
|
||
|
return Util.immutableList(result);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the TLS versions to use when negotiating a connection. Returns {@code null} if all of
|
||
|
* the SSL socket's enabled TLS versions should be used.
|
||
|
*/
|
||
|
public List<TlsVersion> tlsVersions() {
|
||
|
if (tlsVersions == null) return null;
|
||
|
|
||
|
TlsVersion[] result = new TlsVersion[tlsVersions.length];
|
||
|
for (int i = 0; i < tlsVersions.length; i++) {
|
||
|
result[i] = TlsVersion.forJavaName(tlsVersions[i]);
|
||
|
}
|
||
|
return Util.immutableList(result);
|
||
|
}
|
||
|
|
||
|
public boolean supportsTlsExtensions() {
|
||
|
return supportsTlsExtensions;
|
||
|
}
|
||
|
|
||
|
/** Applies this spec to {@code sslSocket}. */
|
||
|
void apply(SSLSocket sslSocket, boolean isFallback) {
|
||
|
ConnectionSpec specToApply = supportedSpec(sslSocket, isFallback);
|
||
|
|
||
|
if (specToApply.tlsVersions != null) {
|
||
|
sslSocket.setEnabledProtocols(specToApply.tlsVersions);
|
||
|
}
|
||
|
if (specToApply.cipherSuites != null) {
|
||
|
sslSocket.setEnabledCipherSuites(specToApply.cipherSuites);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns a copy of this that omits cipher suites and TLS versions not enabled by {@code
|
||
|
* sslSocket}.
|
||
|
*/
|
||
|
private ConnectionSpec supportedSpec(SSLSocket sslSocket, boolean isFallback) {
|
||
|
String[] cipherSuitesIntersection = cipherSuites != null
|
||
|
? Util.intersect(String.class, cipherSuites, sslSocket.getEnabledCipherSuites())
|
||
|
: sslSocket.getEnabledCipherSuites();
|
||
|
String[] tlsVersionsIntersection = tlsVersions != null
|
||
|
? Util.intersect(String.class, tlsVersions, sslSocket.getEnabledProtocols())
|
||
|
: sslSocket.getEnabledProtocols();
|
||
|
|
||
|
// In accordance with https://tools.ietf.org/html/draft-ietf-tls-downgrade-scsv-00
|
||
|
// the SCSV cipher is added to signal that a protocol fallback has taken place.
|
||
|
if (isFallback && contains(sslSocket.getSupportedCipherSuites(), "TLS_FALLBACK_SCSV")) {
|
||
|
cipherSuitesIntersection = concat(cipherSuitesIntersection, "TLS_FALLBACK_SCSV");
|
||
|
}
|
||
|
|
||
|
return new Builder(this)
|
||
|
.cipherSuites(cipherSuitesIntersection)
|
||
|
.tlsVersions(tlsVersionsIntersection)
|
||
|
.build();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns {@code true} if the socket, as currently configured, supports this connection spec.
|
||
|
* In order for a socket to be compatible the enabled cipher suites and protocols must intersect.
|
||
|
*
|
||
|
* <p>For cipher suites, at least one of the {@link #cipherSuites() required cipher suites} must
|
||
|
* match the socket's enabled cipher suites. If there are no required cipher suites the socket
|
||
|
* must have at least one cipher suite enabled.
|
||
|
*
|
||
|
* <p>For protocols, at least one of the {@link #tlsVersions() required protocols} must match the
|
||
|
* socket's enabled protocols.
|
||
|
*/
|
||
|
public boolean isCompatible(SSLSocket socket) {
|
||
|
if (!tls) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if (tlsVersions != null
|
||
|
&& !nonEmptyIntersection(tlsVersions, socket.getEnabledProtocols())) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if (cipherSuites != null
|
||
|
&& !nonEmptyIntersection(cipherSuites, socket.getEnabledCipherSuites())) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* An N*M intersection that terminates if any intersection is found. The sizes of both
|
||
|
* arguments are assumed to be so small, and the likelihood of an intersection so great, that it
|
||
|
* is not worth the CPU cost of sorting or the memory cost of hashing.
|
||
|
*/
|
||
|
private static boolean nonEmptyIntersection(String[] a, String[] b) {
|
||
|
if (a == null || b == null || a.length == 0 || b.length == 0) {
|
||
|
return false;
|
||
|
}
|
||
|
for (String toFind : a) {
|
||
|
if (contains(b, toFind)) {
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
@Override public boolean equals(Object other) {
|
||
|
if (!(other instanceof ConnectionSpec)) return false;
|
||
|
if (other == this) return true;
|
||
|
|
||
|
ConnectionSpec that = (ConnectionSpec) 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() {
|
||
|
if (!tls) {
|
||
|
return "ConnectionSpec()";
|
||
|
}
|
||
|
|
||
|
String cipherSuitesString = cipherSuites != null ? cipherSuites().toString() : "[all enabled]";
|
||
|
String tlsVersionsString = tlsVersions != null ? tlsVersions().toString() : "[all enabled]";
|
||
|
return "ConnectionSpec("
|
||
|
+ "cipherSuites=" + cipherSuitesString
|
||
|
+ ", tlsVersions=" + tlsVersionsString
|
||
|
+ ", supportsTlsExtensions=" + supportsTlsExtensions
|
||
|
+ ")";
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @hide This class is not part of the Android public SDK API
|
||
|
*/
|
||
|
public static final class Builder {
|
||
|
private boolean tls;
|
||
|
private String[] cipherSuites;
|
||
|
private String[] tlsVersions;
|
||
|
private boolean supportsTlsExtensions;
|
||
|
|
||
|
Builder(boolean tls) {
|
||
|
this.tls = tls;
|
||
|
}
|
||
|
|
||
|
public Builder(ConnectionSpec connectionSpec) {
|
||
|
this.tls = connectionSpec.tls;
|
||
|
this.cipherSuites = connectionSpec.cipherSuites;
|
||
|
this.tlsVersions = connectionSpec.tlsVersions;
|
||
|
this.supportsTlsExtensions = connectionSpec.supportsTlsExtensions;
|
||
|
}
|
||
|
|
||
|
public Builder allEnabledCipherSuites() {
|
||
|
if (!tls) throw new IllegalStateException("no cipher suites for cleartext connections");
|
||
|
this.cipherSuites = null;
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
public Builder cipherSuites(CipherSuite... cipherSuites) {
|
||
|
if (!tls) throw new IllegalStateException("no cipher suites for cleartext connections");
|
||
|
|
||
|
String[] strings = new String[cipherSuites.length];
|
||
|
for (int i = 0; i < cipherSuites.length; i++) {
|
||
|
strings[i] = cipherSuites[i].javaName;
|
||
|
}
|
||
|
return cipherSuites(strings);
|
||
|
}
|
||
|
|
||
|
public Builder cipherSuites(String... cipherSuites) {
|
||
|
if (!tls) throw new IllegalStateException("no cipher suites for cleartext connections");
|
||
|
|
||
|
if (cipherSuites.length == 0) {
|
||
|
throw new IllegalArgumentException("At least one cipher suite is required");
|
||
|
}
|
||
|
|
||
|
this.cipherSuites = cipherSuites.clone(); // Defensive copy.
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
public Builder allEnabledTlsVersions() {
|
||
|
if (!tls) throw new IllegalStateException("no TLS versions for cleartext connections");
|
||
|
this.tlsVersions = null;
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
public Builder tlsVersions(TlsVersion... tlsVersions) {
|
||
|
if (!tls) throw new IllegalStateException("no TLS versions for cleartext connections");
|
||
|
|
||
|
String[] strings = new String[tlsVersions.length];
|
||
|
for (int i = 0; i < tlsVersions.length; i++) {
|
||
|
strings[i] = tlsVersions[i].javaName;
|
||
|
}
|
||
|
|
||
|
return tlsVersions(strings);
|
||
|
}
|
||
|
|
||
|
public Builder tlsVersions(String... tlsVersions) {
|
||
|
if (!tls) throw new IllegalStateException("no TLS versions for cleartext connections");
|
||
|
|
||
|
if (tlsVersions.length == 0) {
|
||
|
throw new IllegalArgumentException("At least one TLS version is required");
|
||
|
}
|
||
|
|
||
|
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 ConnectionSpec build() {
|
||
|
return new ConnectionSpec(this);
|
||
|
}
|
||
|
}
|
||
|
}
|