• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2015 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 
17 package com.squareup.okhttp.internal;
18 
19 import com.squareup.okhttp.ConnectionSpec;
20 import java.io.IOException;
21 import java.io.InterruptedIOException;
22 import java.net.ProtocolException;
23 import java.net.UnknownServiceException;
24 import java.security.cert.CertificateException;
25 import java.util.Arrays;
26 import java.util.List;
27 import javax.net.ssl.SSLHandshakeException;
28 import javax.net.ssl.SSLPeerUnverifiedException;
29 import javax.net.ssl.SSLProtocolException;
30 import javax.net.ssl.SSLSocket;
31 
32 /**
33  * Handles the connection spec fallback strategy: When a secure socket connection fails
34  * due to a handshake / protocol problem the connection may be retried with different protocols.
35  * Instances are stateful and should be created and used for a single connection attempt.
36  */
37 public final class ConnectionSpecSelector {
38 
39   private final List<ConnectionSpec> connectionSpecs;
40   private int nextModeIndex;
41   private boolean isFallbackPossible;
42   private boolean isFallback;
43 
ConnectionSpecSelector(List<ConnectionSpec> connectionSpecs)44   public ConnectionSpecSelector(List<ConnectionSpec> connectionSpecs) {
45     this.nextModeIndex = 0;
46     this.connectionSpecs = connectionSpecs;
47   }
48 
49   /**
50    * Configures the supplied {@link SSLSocket} to connect to the specified host using an appropriate
51    * {@link ConnectionSpec}. Returns the chosen {@link ConnectionSpec}, never {@code null}.
52    *
53    * @throws IOException if the socket does not support any of the TLS modes available
54    */
configureSecureSocket(SSLSocket sslSocket)55   public ConnectionSpec configureSecureSocket(SSLSocket sslSocket) throws IOException {
56     ConnectionSpec tlsConfiguration = null;
57     for (int i = nextModeIndex, size = connectionSpecs.size(); i < size; i++) {
58       ConnectionSpec connectionSpec = connectionSpecs.get(i);
59       if (connectionSpec.isCompatible(sslSocket)) {
60         tlsConfiguration = connectionSpec;
61         nextModeIndex = i + 1;
62         break;
63       }
64     }
65 
66     if (tlsConfiguration == null) {
67       // This may be the first time a connection has been attempted and the socket does not support
68       // any the required protocols, or it may be a retry (but this socket supports fewer
69       // protocols than was suggested by a prior socket).
70       throw new UnknownServiceException(
71           "Unable to find acceptable protocols. isFallback=" + isFallback
72               + ", modes=" + connectionSpecs
73               + ", supported protocols=" + Arrays.toString(sslSocket.getEnabledProtocols()));
74     }
75 
76     isFallbackPossible = isFallbackPossible(sslSocket);
77 
78     Internal.instance.apply(tlsConfiguration, sslSocket, isFallback);
79 
80     return tlsConfiguration;
81   }
82 
83   /**
84    * Reports a failure to complete a connection. Determines the next {@link ConnectionSpec} to
85    * try, if any.
86    *
87    * @return {@code true} if the connection should be retried using
88    *     {@link #configureSecureSocket(SSLSocket)} or {@code false} if not
89    */
connectionFailed(IOException e)90   public boolean connectionFailed(IOException e) {
91     // Any future attempt to connect using this strategy will be a fallback attempt.
92     isFallback = true;
93 
94     if (!isFallbackPossible) {
95       return false;
96     }
97 
98     // If there was a protocol problem, don't recover.
99     if (e instanceof ProtocolException) {
100       return false;
101     }
102 
103     // If there was an interruption or timeout (SocketTimeoutException), don't recover.
104     // For the socket connect timeout case we do not try the same host with a different
105     // ConnectionSpec: we assume it is unreachable.
106     if (e instanceof InterruptedIOException) {
107       return false;
108     }
109 
110     // Look for known client-side or negotiation errors that are unlikely to be fixed by trying
111     // again with a different connection spec.
112     if (e instanceof SSLHandshakeException) {
113       // If the problem was a CertificateException from the X509TrustManager,
114       // do not retry.
115       if (e.getCause() instanceof CertificateException) {
116         return false;
117       }
118     }
119     if (e instanceof SSLPeerUnverifiedException) {
120       // e.g. a certificate pinning error.
121       return false;
122     }
123 
124 
125     // On Android, SSLProtocolExceptions can be caused by TLS_FALLBACK_SCSV failures, which means we
126     // retry those when we probably should not.
127     return (e instanceof SSLHandshakeException || e instanceof SSLProtocolException);
128   }
129 
130   /**
131    * Returns {@code true} if any later {@link ConnectionSpec} in the fallback strategy looks
132    * possible based on the supplied {@link SSLSocket}. It assumes that a future socket will have the
133    * same capabilities as the supplied socket.
134    */
isFallbackPossible(SSLSocket socket)135   private boolean isFallbackPossible(SSLSocket socket) {
136     for (int i = nextModeIndex; i < connectionSpecs.size(); i++) {
137       if (connectionSpecs.get(i).isCompatible(socket)) {
138         return true;
139       }
140     }
141     return false;
142   }
143 }
144