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 okhttp3;
17
18 import java.util.Arrays;
19 import java.util.List;
20 import java.util.Objects;
21 import javax.annotation.Nullable;
22 import javax.net.ssl.SSLSocket;
23 import okhttp3.internal.Util;
24
25 import static okhttp3.internal.Util.concat;
26 import static okhttp3.internal.Util.indexOf;
27 import static okhttp3.internal.Util.intersect;
28 import static okhttp3.internal.Util.nonEmptyIntersection;
29
30 /**
31  * Specifies configuration for the socket connection that HTTP traffic travels through. For {@code
32  * https:} URLs, this includes the TLS version and cipher suites to use when negotiating a secure
33  * connection.
34  *
35  * <p>The TLS versions configured in a connection spec are only be used if they are also enabled in
36  * the SSL socket. For example, if an SSL socket does not have TLS 1.3 enabled, it will not be used
37  * even if it is present on the connection spec. The same policy also applies to cipher suites.
38  *
39  * <p>Use {@link Builder#allEnabledTlsVersions()} and {@link Builder#allEnabledCipherSuites} to
40  * defer all feature selection to the underlying SSL socket.
41  *
42  * <p>The configuration of each spec changes with each OkHttp release. This is annoying: upgrading
43  * your OkHttp library can break connectivity to certain web servers! But it’s a necessary annoyance
44  * because the TLS ecosystem is dynamic and staying up to date is necessary to stay secure. See
45  * <a href="https://github.com/square/okhttp/wiki/TLS-Configuration-History">OkHttp's TLS
46  * Configuration History</a> to track these changes.
47  */

48 public final class ConnectionSpec {
49
50   // Most secure but generally supported list.
51   private static final CipherSuite[] RESTRICTED_CIPHER_SUITES = new CipherSuite[] {
52       // TLSv1.3.
53       CipherSuite.TLS_AES_128_GCM_SHA256,
54       CipherSuite.TLS_AES_256_GCM_SHA384,
55       CipherSuite.TLS_CHACHA20_POLY1305_SHA256,
56
57       // TLSv1.0, TLSv1.1, TLSv1.2.
58       CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
59       CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
60       CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
61       CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
62       CipherSuite.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,
63       CipherSuite.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256
64   };
65
66   // This is nearly equal to the cipher suites supported in Chrome 72, current as of 2019-02-24.
67   // See https://tinyurl.com/okhttp-cipher-suites for availability.
68   private static final CipherSuite[] APPROVED_CIPHER_SUITES = new CipherSuite[] {
69       // TLSv1.3.
70       CipherSuite.TLS_AES_128_GCM_SHA256,
71       CipherSuite.TLS_AES_256_GCM_SHA384,
72       CipherSuite.TLS_CHACHA20_POLY1305_SHA256,
73
74       // TLSv1.0, TLSv1.1, TLSv1.2.
75       CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
76       CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
77       CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
78       CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
79       CipherSuite.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,
80       CipherSuite.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
81
82       // Note that the following cipher suites are all on HTTP/2's bad cipher suites list. We'll
83       // continue to include them until better suites are commonly available.
84       CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
85       CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
86       CipherSuite.TLS_RSA_WITH_AES_128_GCM_SHA256,
87       CipherSuite.TLS_RSA_WITH_AES_256_GCM_SHA384,
88       CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA,
89       CipherSuite.TLS_RSA_WITH_AES_256_CBC_SHA,
90       CipherSuite.TLS_RSA_WITH_3DES_EDE_CBC_SHA,
91   };
92
93   /** A secure TLS connection that requires a recent client platform and a recent server. */
94   public static final ConnectionSpec RESTRICTED_TLS = new Builder(true)
95       .cipherSuites(RESTRICTED_CIPHER_SUITES)
96       .tlsVersions(TlsVersion.TLS_1_3, TlsVersion.TLS_1_2)
97       .supportsTlsExtensions(true)
98       .build();
99
100   /**
101    * A modern TLS configuration that works on most client platforms and can connect to most servers.
102    * This is OkHttp's default configuration.
103    */

104   public static final ConnectionSpec MODERN_TLS = new Builder(true)
105       .cipherSuites(APPROVED_CIPHER_SUITES)
106       .tlsVersions(TlsVersion.TLS_1_3, TlsVersion.TLS_1_2)
107       .supportsTlsExtensions(true)
108       .build();
109
110   /**
111    * A backwards-compatible fallback configuration that works on obsolete client platforms and can
112    * connect to obsolete servers. When possible, prefer to upgrade your client platform or server
113    * rather than using this configuration.
114    */

115   public static final ConnectionSpec COMPATIBLE_TLS = new Builder(true)
116       .cipherSuites(APPROVED_CIPHER_SUITES)
117       .tlsVersions(TlsVersion.TLS_1_3, TlsVersion.TLS_1_2, TlsVersion.TLS_1_1, TlsVersion.TLS_1_0)
118       .supportsTlsExtensions(true)
119       .build();
120
121   /** Unencrypted, unauthenticated connections for {@code http:} URLs. */
122   public static final ConnectionSpec CLEARTEXT = new Builder(false).build();
123
124   final boolean tls;
125   final boolean supportsTlsExtensions;
126   final @Nullable String[] cipherSuites;
127   final @Nullable String[] tlsVersions;
128
129   ConnectionSpec(Builder builder) {
130     this.tls = builder.tls;
131     this.cipherSuites = builder.cipherSuites;
132     this.tlsVersions = builder.tlsVersions;
133     this.supportsTlsExtensions = builder.supportsTlsExtensions;
134   }
135
136   public boolean isTls() {
137     return tls;
138   }
139
140   /**
141    * Returns the cipher suites to use for a connection. Returns null if all of the SSL socket's
142    * enabled cipher suites should be used.
143    */

144   public @Nullable List<CipherSuite> cipherSuites() {
145     return cipherSuites != null ? CipherSuite.forJavaNames(cipherSuites) : null;
146   }
147
148   /**
149    * Returns the TLS versions to use when negotiating a connection. Returns null if all of the SSL
150    * socket's enabled TLS versions should be used.
151    */

152   public @Nullable List<TlsVersion> tlsVersions() {
153     return tlsVersions != null ? TlsVersion.forJavaNames(tlsVersions) : null;
154   }
155
156   public boolean supportsTlsExtensions() {
157     return supportsTlsExtensions;
158   }
159
160   /** Applies this spec to {@code sslSocket}. */
161   void apply(SSLSocket sslSocket, boolean isFallback) {
162     ConnectionSpec specToApply = supportedSpec(sslSocket, isFallback);
163
164     if (specToApply.tlsVersions != null) {
165       sslSocket.setEnabledProtocols(specToApply.tlsVersions);
166     }
167     if (specToApply.cipherSuites != null) {
168       sslSocket.setEnabledCipherSuites(specToApply.cipherSuites);
169     }
170   }
171
172   /**
173    * Returns a copy of this that omits cipher suites and TLS versions not enabled by {@code
174    * sslSocket}.
175    */

176   private ConnectionSpec supportedSpec(SSLSocket sslSocket, boolean isFallback) {
177     String[] cipherSuitesIntersection = cipherSuites != null
178         ? intersect(CipherSuite.ORDER_BY_NAME, sslSocket.getEnabledCipherSuites(), cipherSuites)
179         : sslSocket.getEnabledCipherSuites();
180     String[] tlsVersionsIntersection = tlsVersions != null
181         ? intersect(Util.NATURAL_ORDER, sslSocket.getEnabledProtocols(), tlsVersions)
182         : sslSocket.getEnabledProtocols();
183
184     // In accordance with https://tools.ietf.org/html/draft-ietf-tls-downgrade-scsv-00
185     // the SCSV cipher is added to signal that a protocol fallback has taken place.
186     String[] supportedCipherSuites = sslSocket.getSupportedCipherSuites();
187     int indexOfFallbackScsv = indexOf(
188         CipherSuite.ORDER_BY_NAME, supportedCipherSuites, "TLS_FALLBACK_SCSV");
189     if (isFallback && indexOfFallbackScsv != -1) {
190       cipherSuitesIntersection = concat(
191           cipherSuitesIntersection, supportedCipherSuites[indexOfFallbackScsv]);
192     }
193
194     return new Builder(this)
195         .cipherSuites(cipherSuitesIntersection)
196         .tlsVersions(tlsVersionsIntersection)
197         .build();
198   }
199
200   /**
201    * Returns {@code trueif the socket, as currently configured, supports this connection spec. In
202    * order for a socket to be compatible the enabled cipher suites and protocols must intersect.
203    *
204    * <p>For cipher suites, at least one of the {@link #cipherSuites() required cipher suites} must
205    * match the socket's enabled cipher suites. If there are no required cipher suites the socket
206    * must have at least one cipher suite enabled.
207    *
208    * <p>For protocols, at least one of the {@link #tlsVersions() required protocols} must match the
209    * socket's enabled protocols.
210    */

211   public boolean isCompatible(SSLSocket socket) {
212     if (!tls) {
213       return false;
214     }
215
216     if (tlsVersions != null && !nonEmptyIntersection(
217         Util.NATURAL_ORDER, tlsVersions, socket.getEnabledProtocols())) {
218       return false;
219     }
220
221     if (cipherSuites != null && !nonEmptyIntersection(
222         CipherSuite.ORDER_BY_NAME, cipherSuites, socket.getEnabledCipherSuites())) {
223       return false;
224     }
225
226     return true;
227   }
228
229   @Override public boolean equals(@Nullable Object other) {
230     if (!(other instanceof ConnectionSpec)) return false;
231     if (other == thisreturn true;
232
233     ConnectionSpec that = (ConnectionSpec) other;
234     if (this.tls != that.tls) return false;
235
236     if (tls) {
237       if (!Arrays.equals(this.cipherSuites, that.cipherSuites)) return false;
238       if (!Arrays.equals(this.tlsVersions, that.tlsVersions)) return false;
239       if (this.supportsTlsExtensions != that.supportsTlsExtensions) return false;
240     }
241
242     return true;
243   }
244
245   @Override public int hashCode() {
246     int result = 17;
247     if (tls) {
248       result = 31 * result + Arrays.hashCode(cipherSuites);
249       result = 31 * result + Arrays.hashCode(tlsVersions);
250       result = 31 * result + (supportsTlsExtensions ? 0 : 1);
251     }
252     return result;
253   }
254
255   @Override public String toString() {
256     if (!tls) {
257       return "ConnectionSpec()";
258     }
259
260     return "ConnectionSpec("
261         + "cipherSuites=" + Objects.toString(cipherSuites(), "[all enabled]")
262         + ", tlsVersions=" + Objects.toString(tlsVersions(), "[all enabled]")
263         + ", supportsTlsExtensions=" + supportsTlsExtensions
264         + ")";
265   }
266
267   public static final class Builder {
268     boolean tls;
269     @Nullable String[] cipherSuites;
270     @Nullable String[] tlsVersions;
271     boolean supportsTlsExtensions;
272
273     Builder(boolean tls) {
274       this.tls = tls;
275     }
276
277     public Builder(ConnectionSpec connectionSpec) {
278       this.tls = connectionSpec.tls;
279       this.cipherSuites = connectionSpec.cipherSuites;
280       this.tlsVersions = connectionSpec.tlsVersions;
281       this.supportsTlsExtensions = connectionSpec.supportsTlsExtensions;
282     }
283
284     public Builder allEnabledCipherSuites() {
285       if (!tls) throw new IllegalStateException("no cipher suites for cleartext connections");
286       this.cipherSuites = null;
287       return this;
288     }
289
290     public Builder cipherSuites(CipherSuite... cipherSuites) {
291       if (!tls) throw new IllegalStateException("no cipher suites for cleartext connections");
292
293       String[] strings = new String[cipherSuites.length];
294       for (int i = 0; i < cipherSuites.length; i++) {
295         strings[i] = cipherSuites[i].javaName;
296       }
297       return cipherSuites(strings);
298     }
299
300     public Builder cipherSuites(String... cipherSuites) {
301       if (!tls) throw new IllegalStateException("no cipher suites for cleartext connections");
302
303       if (cipherSuites.length == 0) {
304         throw new IllegalArgumentException("At least one cipher suite is required");
305       }
306
307       this.cipherSuites = cipherSuites.clone(); // Defensive copy.
308       return this;
309     }
310
311     public Builder allEnabledTlsVersions() {
312       if (!tls) throw new IllegalStateException("no TLS versions for cleartext connections");
313       this.tlsVersions = null;
314       return this;
315     }
316
317     public Builder tlsVersions(TlsVersion... tlsVersions) {
318       if (!tls) throw new IllegalStateException("no TLS versions for cleartext connections");
319
320       String[] strings = new String[tlsVersions.length];
321       for (int i = 0; i < tlsVersions.length; i++) {
322         strings[i] = tlsVersions[i].javaName;
323       }
324
325       return tlsVersions(strings);
326     }
327
328     public Builder tlsVersions(String... tlsVersions) {
329       if (!tls) throw new IllegalStateException("no TLS versions for cleartext connections");
330
331       if (tlsVersions.length == 0) {
332         throw new IllegalArgumentException("At least one TLS version is required");
333       }
334
335       this.tlsVersions = tlsVersions.clone(); // Defensive copy.
336       return this;
337     }
338
339     /**
340      * @deprecated since OkHttp 3.13 all TLS-connections are expected to support TLS extensions.
341      *     In a future release setting this to true will be unnecessary and setting it to false will
342      *     have no effect.
343      */

344     public Builder supportsTlsExtensions(boolean supportsTlsExtensions) {
345       if (!tls) throw new IllegalStateException("no TLS extensions for cleartext connections");
346       this.supportsTlsExtensions = supportsTlsExtensions;
347       return this;
348     }
349
350     public ConnectionSpec build() {
351       return new ConnectionSpec(this);
352     }
353   }
354 }
355