1 /* 2 * Copyright (C) 2014 The Android Open Source Project 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.android.emailcommon.utility; 18 19 import com.android.mail.utils.LogUtils; 20 21 import java.io.IOException; 22 import java.lang.reflect.Method; 23 import java.net.InetAddress; 24 import java.net.Socket; 25 import java.net.UnknownHostException; 26 import java.security.KeyManagementException; 27 import java.security.NoSuchAlgorithmException; 28 import java.util.ArrayList; 29 import java.util.Arrays; 30 import java.util.HashSet; 31 import java.util.List; 32 import java.util.Set; 33 34 import javax.net.ssl.*; 35 import javax.net.ssl.SSLSocketFactory; 36 37 public class SSLSocketFactoryWrapper extends javax.net.ssl.SSLSocketFactory { 38 private final SSLSocketFactory mFactory; 39 private final boolean mSecure; 40 private final int mHandshakeTimeout; 41 private final String[] mDefaultCipherSuites; 42 43 private final String[] DEPRECATED_CIPHER_SUITES_TO_ENABLE = new String[] { 44 "TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA", 45 "TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA", 46 "SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA", 47 "SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA", 48 "SSL_RSA_WITH_3DES_EDE_CBC_SHA", 49 "SSL_RSA_WITH_RC4_128_MD5", 50 "TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA", 51 "TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA", 52 "TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA", 53 "TLS_ECDH_ECDSA_WITH_RC4_128_SHA", 54 "TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA", 55 "TLS_ECDH_RSA_WITH_AES_128_CBC_SHA", 56 "TLS_ECDH_RSA_WITH_AES_256_CBC_SHA", 57 "TLS_ECDH_RSA_WITH_RC4_128_SHA", 58 "SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA", 59 "SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA", 60 "SSL_RSA_EXPORT_WITH_DES40_CBC_SHA", 61 "SSL_RSA_EXPORT_WITH_RC4_40_MD5", 62 "SSL_DHE_DSS_WITH_DES_CBC_SHA", 63 "SSL_DHE_RSA_WITH_DES_CBC_SHA", 64 "SSL_RSA_WITH_DES_CBC_SHA" 65 }; 66 SSLSocketFactoryWrapper(final SSLSocketFactory factory, final boolean secure, int handshakeTimeout)67 SSLSocketFactoryWrapper(final SSLSocketFactory factory, final boolean secure, 68 int handshakeTimeout) { 69 mFactory = factory; 70 mSecure = secure; 71 mHandshakeTimeout = handshakeTimeout; 72 73 // Find the base factory's list of defaultCipherSuites, and merge our extras with it. 74 // Remember that the order is important. We'll add our extras at the end, and only 75 // if they weren't already in the base factory's list. 76 final String[] baseDefaultCipherSuites = mFactory.getDefaultCipherSuites(); 77 final List<String> fullCipherSuiteList = new ArrayList<String>(Arrays.asList( 78 mFactory.getDefaultCipherSuites())); 79 final Set<String> baseDefaultCipherSuiteSet = new HashSet<String>(fullCipherSuiteList); 80 81 final String[] baseSupportedCipherSuites = mFactory.getSupportedCipherSuites(); 82 final Set<String> baseSupportedCipherSuiteSet = new HashSet<String>(Arrays.asList( 83 mFactory.getSupportedCipherSuites())); 84 85 for (String cipherSuite : DEPRECATED_CIPHER_SUITES_TO_ENABLE) { 86 if (baseSupportedCipherSuiteSet.contains(cipherSuite) && 87 !baseDefaultCipherSuiteSet.contains(cipherSuite)) { 88 fullCipherSuiteList.add(cipherSuite); 89 } 90 } 91 mDefaultCipherSuites = new String[fullCipherSuiteList.size()]; 92 fullCipherSuiteList.toArray(mDefaultCipherSuites); 93 } 94 getDefault(final KeyManager[] keyManagers, int handshakeTimeout)95 public static SSLSocketFactory getDefault(final KeyManager[] keyManagers, int handshakeTimeout) 96 throws NoSuchAlgorithmException, KeyManagementException{ 97 final SSLContext context = SSLContext.getInstance("TLS"); 98 context.init(keyManagers, null, null); 99 return new SSLSocketFactoryWrapper(context.getSocketFactory(), true, handshakeTimeout); 100 } 101 getInsecure(final KeyManager[] keyManagers, final TrustManager[] trustManagers, int handshakeTimeout)102 public static SSLSocketFactory getInsecure(final KeyManager[] keyManagers, 103 final TrustManager[] trustManagers, 104 int handshakeTimeout) 105 throws NoSuchAlgorithmException, KeyManagementException { 106 final SSLContext context = SSLContext.getInstance("TLS"); 107 context.init(keyManagers, trustManagers, null); 108 return new SSLSocketFactoryWrapper(context.getSocketFactory(), false, handshakeTimeout); 109 } 110 createSocket()111 public Socket createSocket()throws IOException { 112 return mFactory.createSocket(); 113 } 114 createSocket(final Socket socket, final String host, final int port, final boolean autoClose)115 public Socket createSocket(final Socket socket, final String host, final int port, 116 final boolean autoClose) throws IOException { 117 final SSLSocket sslSocket = (SSLSocket)mFactory.createSocket(socket, host, port, autoClose); 118 setHandshakeTimeout(sslSocket, mHandshakeTimeout); 119 sslSocket.setEnabledCipherSuites(mDefaultCipherSuites); 120 if (mSecure) { 121 verifyHostname(sslSocket, host); 122 } 123 return sslSocket; 124 } 125 126 @Override createSocket(String host, int port)127 public Socket createSocket(String host, int port) throws IOException, UnknownHostException { 128 final SSLSocket sslSocket = (SSLSocket)mFactory.createSocket(host, port); 129 setHandshakeTimeout(sslSocket, mHandshakeTimeout); 130 sslSocket.setEnabledCipherSuites(mDefaultCipherSuites); 131 if (mSecure) { 132 verifyHostname(sslSocket, host); 133 } 134 return sslSocket; 135 } 136 137 @Override createSocket(String host, int i, InetAddress inetAddress, int i2)138 public Socket createSocket(String host, int i, InetAddress inetAddress, int i2) throws 139 IOException, UnknownHostException { 140 final SSLSocket sslSocket = (SSLSocket)mFactory.createSocket(host, i, inetAddress, i2); 141 setHandshakeTimeout(sslSocket, mHandshakeTimeout); 142 sslSocket.setEnabledCipherSuites(mDefaultCipherSuites); 143 if (mSecure) { 144 verifyHostname(sslSocket, host); 145 } 146 return sslSocket; 147 } 148 149 @Override createSocket(InetAddress inetAddress, int i)150 public Socket createSocket(InetAddress inetAddress, int i) throws IOException { 151 final SSLSocket sslSocket = (SSLSocket)mFactory.createSocket(inetAddress, i); 152 setHandshakeTimeout(sslSocket, mHandshakeTimeout); 153 sslSocket.setEnabledCipherSuites(mDefaultCipherSuites); 154 return sslSocket; 155 } 156 157 @Override createSocket(InetAddress inetAddress, int i, InetAddress inetAddress2, int i2)158 public Socket createSocket(InetAddress inetAddress, int i, InetAddress inetAddress2, int i2) 159 throws IOException { 160 final SSLSocket sslSocket = (SSLSocket)mFactory.createSocket(inetAddress, i, inetAddress2, 161 i2); 162 setHandshakeTimeout(sslSocket, mHandshakeTimeout); 163 sslSocket.setEnabledCipherSuites(mDefaultCipherSuites); 164 return sslSocket; 165 } 166 getDefaultCipherSuites()167 public String[] getDefaultCipherSuites() { 168 return mDefaultCipherSuites.clone(); 169 } 170 getSupportedCipherSuites()171 public String[] getSupportedCipherSuites() { 172 return mFactory.getSupportedCipherSuites(); 173 } 174 175 /** 176 * Attempt to set the hostname of the socket. 177 * @param sslSocket The SSLSocket 178 * @param hostname the hostname 179 * @return true if able to set the hostname, false if not. 180 */ potentiallyEnableSni(SSLSocket sslSocket, String hostname)181 public static boolean potentiallyEnableSni(SSLSocket sslSocket, String hostname) { 182 try { 183 // Many implementations of SSLSocket support setHostname, although it is not part of 184 // the class definition. We will attempt to setHostname using reflection. If the 185 // particular SSLSocket implementation we are using does not support this meethod, 186 // we'll fail and return false. 187 sslSocket.getClass().getMethod("setHostname", String.class).invoke(sslSocket, hostname); 188 return true; 189 } catch (Exception ignored) { 190 return false; 191 } 192 } 193 194 /** 195 * Attempt to enable session tickets. 196 * @param sslSocket the SSLSocket. 197 * @return true if able to enable session tickets, false otherwise. 198 */ potentiallyEnableSessionTickets(SSLSocket sslSocket)199 public static boolean potentiallyEnableSessionTickets(SSLSocket sslSocket) { 200 try { 201 // Many implementations of SSLSocket support setUseSessionTickets, although it is not 202 // part of the class definition. We will attempt to setHostname using reflection. If the 203 // particular SSLSocket implementation we are using does not support this meethod, 204 // we'll fail and return false. 205 sslSocket.getClass().getMethod("setUseSessionTickets", boolean.class) 206 .invoke(sslSocket, true); 207 return true; 208 } catch (Exception e) { 209 return false; 210 } 211 } 212 213 /** 214 * Verify the hostname of the certificate used by the other end of a 215 * connected socket. You MUST call this if you did not supply a hostname 216 * to {@link #createSocket()}. It is harmless to call this method 217 * redundantly if the hostname has already been verified. 218 * 219 * @param socket An SSL socket which has been connected to a server 220 * @param hostname The expected hostname of the remote server 221 * @throws IOException if something goes wrong handshaking with the server 222 * @throws SSLPeerUnverifiedException if the server cannot prove its identity 223 * 224 * @hide 225 */ verifyHostname(Socket socket, String hostname)226 public static void verifyHostname(Socket socket, String hostname) throws IOException { 227 if (!(socket instanceof SSLSocket)) { 228 throw new IllegalArgumentException("Attempt to verify non-SSL socket"); 229 } 230 231 // The code at the start of OpenSSLSocketImpl.startHandshake() 232 // ensures that the call is idempotent, so we can safely call it. 233 SSLSocket ssl = (SSLSocket) socket; 234 ssl.startHandshake(); 235 236 SSLSession session = ssl.getSession(); 237 if (session == null) { 238 throw new SSLException("Cannot verify SSL socket without session"); 239 } 240 LogUtils.d(LogUtils.TAG, "using cipherSuite %s", session.getCipherSuite()); 241 if (!HttpsURLConnection.getDefaultHostnameVerifier().verify(hostname, session)) { 242 throw new SSLPeerUnverifiedException("Cannot verify hostname: " + hostname); 243 } 244 } 245 setHandshakeTimeout(SSLSocket sslSocket, int timeout)246 private void setHandshakeTimeout(SSLSocket sslSocket, int timeout) { 247 try { 248 // Most implementations of SSLSocket support setHandshakeTimeout(), but it is not 249 // actually part of the class definition. We will attempt to set it using reflection. 250 // If the particular implementation of SSLSocket we are using does not support this 251 // function, then we will just have to use the default handshake timeout. 252 sslSocket.getClass().getMethod("setHandshakeTimeout", int.class).invoke(sslSocket, 253 timeout); 254 } catch (Exception e) { 255 LogUtils.w(LogUtils.TAG, e, "unable to set handshake timeout"); 256 } 257 } 258 } 259