1
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
48 public final class ConnectionSpec {
49
50
51 private static final CipherSuite[] RESTRICTED_CIPHER_SUITES = new CipherSuite[] {
52
53 CipherSuite.TLS_AES_128_GCM_SHA256,
54 CipherSuite.TLS_AES_256_GCM_SHA384,
55 CipherSuite.TLS_CHACHA20_POLY1305_SHA256,
56
57
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
67
68 private static final CipherSuite[] APPROVED_CIPHER_SUITES = new CipherSuite[] {
69
70 CipherSuite.TLS_AES_128_GCM_SHA256,
71 CipherSuite.TLS_AES_256_GCM_SHA384,
72 CipherSuite.TLS_CHACHA20_POLY1305_SHA256,
73
74
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
83
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
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
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
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
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
144 public @Nullable List<CipherSuite> cipherSuites() {
145 return cipherSuites != null ? CipherSuite.forJavaNames(cipherSuites) : null;
146 }
147
148
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
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
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
185
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
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 == this) return 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();
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();
336 return this;
337 }
338
339
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