• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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