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