• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2014 Square, Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.squareup.okhttp;
17 
18 import com.squareup.okhttp.internal.Util;
19 import java.util.Arrays;
20 import java.util.List;
21 import javax.net.ssl.SSLSocket;
22 
23 import static com.squareup.okhttp.internal.Util.concat;
24 import static com.squareup.okhttp.internal.Util.contains;
25 
26 /**
27  * Specifies configuration for the socket connection that HTTP traffic travels through. For {@code
28  * https:} URLs, this includes the TLS version and cipher suites to use when negotiating a secure
29  * connection.
30  *
31  * <p>The TLS versions configured in a connection spec are only be used if they are also enabled in
32  * the SSL socket. For example, if an SSL socket does not have TLS 1.2 enabled, it will not be used
33  * even if it is present on the connection spec. The same policy also applies to cipher suites.
34  *
35  * <p>Use {@link Builder#allEnabledTlsVersions()} and {@link Builder#allEnabledCipherSuites} to
36  * defer all feature selection to the underlying SSL socket.
37  */
38 public final class ConnectionSpec {
39 
40   // This is a subset of the cipher suites supported in Chrome 46, current as of 2015-11-05.
41   // All of these suites are available on Android 5.0; earlier releases support a subset of
42   // these suites. https://github.com/square/okhttp/issues/330
43   private static final CipherSuite[] APPROVED_CIPHER_SUITES = new CipherSuite[] {
44       CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
45       CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
46       CipherSuite.TLS_DHE_RSA_WITH_AES_128_GCM_SHA256,
47 
48       // Note that the following cipher suites are all on HTTP/2's bad cipher suites list. We'll
49       // continue to include them until better suites are commonly available. For example, none
50       // of the better cipher suites listed above shipped with Android 4.4 or Java 7.
51       CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
52       CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
53       CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
54       CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
55       CipherSuite.TLS_DHE_RSA_WITH_AES_128_CBC_SHA,
56       CipherSuite.TLS_DHE_RSA_WITH_AES_256_CBC_SHA,
57       CipherSuite.TLS_RSA_WITH_AES_128_GCM_SHA256,
58       CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA,
59       CipherSuite.TLS_RSA_WITH_AES_256_CBC_SHA,
60       CipherSuite.TLS_RSA_WITH_3DES_EDE_CBC_SHA,
61   };
62 
63   /** A modern TLS connection with extensions like SNI and ALPN available. */
64   public static final ConnectionSpec MODERN_TLS = new Builder(true)
65       .cipherSuites(APPROVED_CIPHER_SUITES)
66       .tlsVersions(TlsVersion.TLS_1_2, TlsVersion.TLS_1_1, TlsVersion.TLS_1_0)
67       .supportsTlsExtensions(true)
68       .build();
69 
70   /** A backwards-compatible fallback connection for interop with obsolete servers. */
71   public static final ConnectionSpec COMPATIBLE_TLS = new Builder(MODERN_TLS)
72       .tlsVersions(TlsVersion.TLS_1_0)
73       .supportsTlsExtensions(true)
74       .build();
75 
76   /** Unencrypted, unauthenticated connections for {@code http:} URLs. */
77   public static final ConnectionSpec CLEARTEXT = new Builder(false).build();
78 
79   private final boolean tls;
80   private final boolean supportsTlsExtensions;
81   private final String[] cipherSuites;
82   private final String[] tlsVersions;
83 
ConnectionSpec(Builder builder)84   private ConnectionSpec(Builder builder) {
85     this.tls = builder.tls;
86     this.cipherSuites = builder.cipherSuites;
87     this.tlsVersions = builder.tlsVersions;
88     this.supportsTlsExtensions = builder.supportsTlsExtensions;
89   }
90 
isTls()91   public boolean isTls() {
92     return tls;
93   }
94 
95   /**
96    * Returns the cipher suites to use for a connection. Returns {@code null} if all of the SSL
97    * socket's enabled cipher suites should be used.
98    */
cipherSuites()99   public List<CipherSuite> cipherSuites() {
100     if (cipherSuites == null) return null;
101 
102     CipherSuite[] result = new CipherSuite[cipherSuites.length];
103     for (int i = 0; i < cipherSuites.length; i++) {
104       result[i] = CipherSuite.forJavaName(cipherSuites[i]);
105     }
106     return Util.immutableList(result);
107   }
108 
109   /**
110    * Returns the TLS versions to use when negotiating a connection. Returns {@code null} if all of
111    * the SSL socket's enabled TLS versions should be used.
112    */
tlsVersions()113   public List<TlsVersion> tlsVersions() {
114     if (tlsVersions == null) return null;
115 
116     TlsVersion[] result = new TlsVersion[tlsVersions.length];
117     for (int i = 0; i < tlsVersions.length; i++) {
118       result[i] = TlsVersion.forJavaName(tlsVersions[i]);
119     }
120     return Util.immutableList(result);
121   }
122 
supportsTlsExtensions()123   public boolean supportsTlsExtensions() {
124     return supportsTlsExtensions;
125   }
126 
127   /** Applies this spec to {@code sslSocket}. */
apply(SSLSocket sslSocket, boolean isFallback)128   void apply(SSLSocket sslSocket, boolean isFallback) {
129     ConnectionSpec specToApply = supportedSpec(sslSocket, isFallback);
130 
131     if (specToApply.tlsVersions != null) {
132       sslSocket.setEnabledProtocols(specToApply.tlsVersions);
133     }
134     if (specToApply.cipherSuites != null) {
135       sslSocket.setEnabledCipherSuites(specToApply.cipherSuites);
136     }
137   }
138 
139   /**
140    * Returns a copy of this that omits cipher suites and TLS versions not enabled by {@code
141    * sslSocket}.
142    */
supportedSpec(SSLSocket sslSocket, boolean isFallback)143   private ConnectionSpec supportedSpec(SSLSocket sslSocket, boolean isFallback) {
144     String[] cipherSuitesIntersection = cipherSuites != null
145         ? Util.intersect(String.class, cipherSuites, sslSocket.getEnabledCipherSuites())
146         : sslSocket.getEnabledCipherSuites();
147     String[] tlsVersionsIntersection = tlsVersions != null
148         ? Util.intersect(String.class, tlsVersions, sslSocket.getEnabledProtocols())
149         : sslSocket.getEnabledProtocols();
150 
151     // In accordance with https://tools.ietf.org/html/draft-ietf-tls-downgrade-scsv-00
152     // the SCSV cipher is added to signal that a protocol fallback has taken place.
153     if (isFallback && contains(sslSocket.getSupportedCipherSuites(), "TLS_FALLBACK_SCSV")) {
154       cipherSuitesIntersection = concat(cipherSuitesIntersection, "TLS_FALLBACK_SCSV");
155     }
156 
157     return new Builder(this)
158         .cipherSuites(cipherSuitesIntersection)
159         .tlsVersions(tlsVersionsIntersection)
160         .build();
161   }
162 
163   /**
164    * Returns {@code true} if the socket, as currently configured, supports this connection spec.
165    * In order for a socket to be compatible the enabled cipher suites and protocols must intersect.
166    *
167    * <p>For cipher suites, at least one of the {@link #cipherSuites() required cipher suites} must
168    * match the socket's enabled cipher suites. If there are no required cipher suites the socket
169    * must have at least one cipher suite enabled.
170    *
171    * <p>For protocols, at least one of the {@link #tlsVersions() required protocols} must match the
172    * socket's enabled protocols.
173    */
isCompatible(SSLSocket socket)174   public boolean isCompatible(SSLSocket socket) {
175     if (!tls) {
176       return false;
177     }
178 
179     if (tlsVersions != null
180         && !nonEmptyIntersection(tlsVersions, socket.getEnabledProtocols())) {
181       return false;
182     }
183 
184     if (cipherSuites != null
185         && !nonEmptyIntersection(cipherSuites, socket.getEnabledCipherSuites())) {
186       return false;
187     }
188 
189     return true;
190   }
191 
192   /**
193    * An N*M intersection that terminates if any intersection is found. The sizes of both
194    * arguments are assumed to be so small, and the likelihood of an intersection so great, that it
195    * is not worth the CPU cost of sorting or the memory cost of hashing.
196    */
nonEmptyIntersection(String[] a, String[] b)197   private static boolean nonEmptyIntersection(String[] a, String[] b) {
198     if (a == null || b == null || a.length == 0 || b.length == 0) {
199       return false;
200     }
201     for (String toFind : a) {
202       if (contains(b, toFind)) {
203         return true;
204       }
205     }
206     return false;
207   }
208 
equals(Object other)209   @Override public boolean equals(Object other) {
210     if (!(other instanceof ConnectionSpec)) return false;
211     if (other == this) return true;
212 
213     ConnectionSpec that = (ConnectionSpec) other;
214     if (this.tls != that.tls) return false;
215 
216     if (tls) {
217       if (!Arrays.equals(this.cipherSuites, that.cipherSuites)) return false;
218       if (!Arrays.equals(this.tlsVersions, that.tlsVersions)) return false;
219       if (this.supportsTlsExtensions != that.supportsTlsExtensions) return false;
220     }
221 
222     return true;
223   }
224 
hashCode()225   @Override public int hashCode() {
226     int result = 17;
227     if (tls) {
228       result = 31 * result + Arrays.hashCode(cipherSuites);
229       result = 31 * result + Arrays.hashCode(tlsVersions);
230       result = 31 * result + (supportsTlsExtensions ? 0 : 1);
231     }
232     return result;
233   }
234 
toString()235   @Override public String toString() {
236     if (!tls) {
237       return "ConnectionSpec()";
238     }
239 
240     String cipherSuitesString = cipherSuites != null ? cipherSuites().toString() : "[all enabled]";
241     String tlsVersionsString = tlsVersions != null ? tlsVersions().toString() : "[all enabled]";
242     return "ConnectionSpec("
243         + "cipherSuites=" + cipherSuitesString
244         + ", tlsVersions=" + tlsVersionsString
245         + ", supportsTlsExtensions=" + supportsTlsExtensions
246         + ")";
247   }
248 
249   public static final class Builder {
250     private boolean tls;
251     private String[] cipherSuites;
252     private String[] tlsVersions;
253     private boolean supportsTlsExtensions;
254 
Builder(boolean tls)255     Builder(boolean tls) {
256       this.tls = tls;
257     }
258 
Builder(ConnectionSpec connectionSpec)259     public Builder(ConnectionSpec connectionSpec) {
260       this.tls = connectionSpec.tls;
261       this.cipherSuites = connectionSpec.cipherSuites;
262       this.tlsVersions = connectionSpec.tlsVersions;
263       this.supportsTlsExtensions = connectionSpec.supportsTlsExtensions;
264     }
265 
allEnabledCipherSuites()266     public Builder allEnabledCipherSuites() {
267       if (!tls) throw new IllegalStateException("no cipher suites for cleartext connections");
268       this.cipherSuites = null;
269       return this;
270     }
271 
cipherSuites(CipherSuite... cipherSuites)272     public Builder cipherSuites(CipherSuite... cipherSuites) {
273       if (!tls) throw new IllegalStateException("no cipher suites for cleartext connections");
274 
275       String[] strings = new String[cipherSuites.length];
276       for (int i = 0; i < cipherSuites.length; i++) {
277         strings[i] = cipherSuites[i].javaName;
278       }
279       return cipherSuites(strings);
280     }
281 
cipherSuites(String... cipherSuites)282     public Builder cipherSuites(String... cipherSuites) {
283       if (!tls) throw new IllegalStateException("no cipher suites for cleartext connections");
284 
285       if (cipherSuites.length == 0) {
286         throw new IllegalArgumentException("At least one cipher suite is required");
287       }
288 
289       this.cipherSuites = cipherSuites.clone(); // Defensive copy.
290       return this;
291     }
292 
allEnabledTlsVersions()293     public Builder allEnabledTlsVersions() {
294       if (!tls) throw new IllegalStateException("no TLS versions for cleartext connections");
295       this.tlsVersions = null;
296       return this;
297     }
298 
tlsVersions(TlsVersion... tlsVersions)299     public Builder tlsVersions(TlsVersion... tlsVersions) {
300       if (!tls) throw new IllegalStateException("no TLS versions for cleartext connections");
301 
302       String[] strings = new String[tlsVersions.length];
303       for (int i = 0; i < tlsVersions.length; i++) {
304         strings[i] = tlsVersions[i].javaName;
305       }
306 
307       return tlsVersions(strings);
308     }
309 
tlsVersions(String... tlsVersions)310     public Builder tlsVersions(String... tlsVersions) {
311       if (!tls) throw new IllegalStateException("no TLS versions for cleartext connections");
312 
313       if (tlsVersions.length == 0) {
314         throw new IllegalArgumentException("At least one TLS version is required");
315       }
316 
317       this.tlsVersions = tlsVersions.clone(); // Defensive copy.
318       return this;
319     }
320 
supportsTlsExtensions(boolean supportsTlsExtensions)321     public Builder supportsTlsExtensions(boolean supportsTlsExtensions) {
322       if (!tls) throw new IllegalStateException("no TLS extensions for cleartext connections");
323       this.supportsTlsExtensions = supportsTlsExtensions;
324       return this;
325     }
326 
build()327     public ConnectionSpec build() {
328       return new ConnectionSpec(this);
329     }
330   }
331 }
332