SSLCertificateSocketFactory.java revision 1b52806e21270ccbe90d27f3dd93cbee1a81d09e
1/* 2 * Copyright (C) 2008 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 17package android.net; 18 19import android.os.SystemProperties; 20import android.util.Log; 21 22import java.io.IOException; 23import java.net.InetAddress; 24import java.net.Socket; 25import java.security.KeyManagementException; 26import java.security.cert.X509Certificate; 27 28import javax.net.SocketFactory; 29import javax.net.ssl.HostnameVerifier; 30import javax.net.ssl.HttpsURLConnection; 31import javax.net.ssl.KeyManager; 32import javax.net.ssl.SSLException; 33import javax.net.ssl.SSLPeerUnverifiedException; 34import javax.net.ssl.SSLSession; 35import javax.net.ssl.SSLSocket; 36import javax.net.ssl.SSLSocketFactory; 37import javax.net.ssl.TrustManager; 38import javax.net.ssl.X509TrustManager; 39 40import org.apache.harmony.xnet.provider.jsse.OpenSSLContextImpl; 41import org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl; 42import org.apache.harmony.xnet.provider.jsse.SSLClientSessionCache; 43 44/** 45 * SSLSocketFactory implementation with several extra features: 46 * 47 * <ul> 48 * <li>Timeout specification for SSL handshake operations 49 * <li>Hostname verification in most cases (see WARNINGs below) 50 * <li>Optional SSL session caching with {@link SSLSessionCache} 51 * <li>Optionally bypass all SSL certificate checks 52 * </ul> 53 * 54 * The handshake timeout does not apply to actual TCP socket connection. 55 * If you want a connection timeout as well, use {@link #createSocket()} 56 * and {@link Socket#connect(SocketAddress, int)}, after which you 57 * must verify the identity of the server you are connected to. 58 * 59 * <p class="caution"><b>Most {@link SSLSocketFactory} implementations do not 60 * verify the server's identity, allowing man-in-the-middle attacks.</b> 61 * This implementation does check the server's certificate hostname, but only 62 * for createSocket variants that specify a hostname. When using methods that 63 * use {@link InetAddress} or which return an unconnected socket, you MUST 64 * verify the server's identity yourself to ensure a secure connection.</p> 65 * 66 * <p>One way to verify the server's identity is to use 67 * {@link HttpsURLConnection#getDefaultHostnameVerifier()} to get a 68 * {@link HostnameVerifier} to verify the certificate hostname. 69 * 70 * <p>On development devices, "setprop socket.relaxsslcheck yes" bypasses all 71 * SSL certificate and hostname checks for testing purposes. This setting 72 * requires root access. 73 */ 74public class SSLCertificateSocketFactory extends SSLSocketFactory { 75 private static final String TAG = "SSLCertificateSocketFactory"; 76 77 private static final TrustManager[] INSECURE_TRUST_MANAGER = new TrustManager[] { 78 new X509TrustManager() { 79 public X509Certificate[] getAcceptedIssuers() { return null; } 80 public void checkClientTrusted(X509Certificate[] certs, String authType) { } 81 public void checkServerTrusted(X509Certificate[] certs, String authType) { } 82 } 83 }; 84 85 private static final HostnameVerifier HOSTNAME_VERIFIER = 86 HttpsURLConnection.getDefaultHostnameVerifier(); 87 88 private SSLSocketFactory mInsecureFactory = null; 89 private SSLSocketFactory mSecureFactory = null; 90 private TrustManager[] mTrustManagers = null; 91 private KeyManager[] mKeyManagers = null; 92 93 private final int mHandshakeTimeoutMillis; 94 private final SSLClientSessionCache mSessionCache; 95 private final boolean mSecure; 96 97 /** @deprecated Use {@link #getDefault(int)} instead. */ 98 @Deprecated 99 public SSLCertificateSocketFactory(int handshakeTimeoutMillis) { 100 this(handshakeTimeoutMillis, null, true); 101 } 102 103 private SSLCertificateSocketFactory( 104 int handshakeTimeoutMillis, SSLSessionCache cache, boolean secure) { 105 mHandshakeTimeoutMillis = handshakeTimeoutMillis; 106 mSessionCache = cache == null ? null : cache.mSessionCache; 107 mSecure = secure; 108 } 109 110 /** 111 * Returns a new socket factory instance with an optional handshake timeout. 112 * 113 * @param handshakeTimeoutMillis to use for SSL connection handshake, or 0 114 * for none. The socket timeout is reset to 0 after the handshake. 115 * @return a new SSLSocketFactory with the specified parameters 116 */ 117 public static SocketFactory getDefault(int handshakeTimeoutMillis) { 118 return new SSLCertificateSocketFactory(handshakeTimeoutMillis, null, true); 119 } 120 121 /** 122 * Returns a new socket factory instance with an optional handshake timeout 123 * and SSL session cache. 124 * 125 * @param handshakeTimeoutMillis to use for SSL connection handshake, or 0 126 * for none. The socket timeout is reset to 0 after the handshake. 127 * @param cache The {@link SSLSessionCache} to use, or null for no cache. 128 * @return a new SSLSocketFactory with the specified parameters 129 */ 130 public static SSLSocketFactory getDefault(int handshakeTimeoutMillis, SSLSessionCache cache) { 131 return new SSLCertificateSocketFactory(handshakeTimeoutMillis, cache, true); 132 } 133 134 /** 135 * Returns a new instance of a socket factory with all SSL security checks 136 * disabled, using an optional handshake timeout and SSL session cache. 137 * 138 * <p class="caution"><b>Warning:</b> Sockets created using this factory 139 * are vulnerable to man-in-the-middle attacks!</p> 140 * 141 * @param handshakeTimeoutMillis to use for SSL connection handshake, or 0 142 * for none. The socket timeout is reset to 0 after the handshake. 143 * @param cache The {@link SSLSessionCache} to use, or null for no cache. 144 * @return an insecure SSLSocketFactory with the specified parameters 145 */ 146 public static SSLSocketFactory getInsecure(int handshakeTimeoutMillis, SSLSessionCache cache) { 147 return new SSLCertificateSocketFactory(handshakeTimeoutMillis, cache, false); 148 } 149 150 /** 151 * Returns a socket factory (also named SSLSocketFactory, but in a different 152 * namespace) for use with the Apache HTTP stack. 153 * 154 * @param handshakeTimeoutMillis to use for SSL connection handshake, or 0 155 * for none. The socket timeout is reset to 0 after the handshake. 156 * @param cache The {@link SSLSessionCache} to use, or null for no cache. 157 * @return a new SocketFactory with the specified parameters 158 */ 159 public static org.apache.http.conn.ssl.SSLSocketFactory getHttpSocketFactory( 160 int handshakeTimeoutMillis, SSLSessionCache cache) { 161 return new org.apache.http.conn.ssl.SSLSocketFactory( 162 new SSLCertificateSocketFactory(handshakeTimeoutMillis, cache, true)); 163 } 164 165 /** 166 * Verify the hostname of the certificate used by the other end of a 167 * connected socket. You MUST call this if you did not supply a hostname 168 * to {@link #createSocket()}. It is harmless to call this method 169 * redundantly if the hostname has already been verified. 170 * 171 * <p>Wildcard certificates are allowed to verify any matching hostname, 172 * so "foo.bar.example.com" is verified if the peer has a certificate 173 * for "*.example.com". 174 * 175 * @param socket An SSL socket which has been connected to a server 176 * @param hostname The expected hostname of the remote server 177 * @throws IOException if something goes wrong handshaking with the server 178 * @throws SSLPeerUnverifiedException if the server cannot prove its identity 179 * 180 * @hide 181 */ 182 public static void verifyHostname(Socket socket, String hostname) throws IOException { 183 if (!(socket instanceof SSLSocket)) { 184 throw new IllegalArgumentException("Attempt to verify non-SSL socket"); 185 } 186 187 if (!isSslCheckRelaxed()) { 188 // The code at the start of OpenSSLSocketImpl.startHandshake() 189 // ensures that the call is idempotent, so we can safely call it. 190 SSLSocket ssl = (SSLSocket) socket; 191 ssl.startHandshake(); 192 193 SSLSession session = ssl.getSession(); 194 if (session == null) { 195 throw new SSLException("Cannot verify SSL socket without session"); 196 } 197 if (!HOSTNAME_VERIFIER.verify(hostname, session)) { 198 throw new SSLPeerUnverifiedException("Cannot verify hostname: " + hostname); 199 } 200 } 201 } 202 203 private SSLSocketFactory makeSocketFactory( 204 KeyManager[] keyManagers, TrustManager[] trustManagers) { 205 try { 206 OpenSSLContextImpl sslContext = new OpenSSLContextImpl(); 207 sslContext.engineInit(keyManagers, trustManagers, null); 208 sslContext.engineGetClientSessionContext().setPersistentCache(mSessionCache); 209 return sslContext.engineGetSocketFactory(); 210 } catch (KeyManagementException e) { 211 Log.wtf(TAG, e); 212 return (SSLSocketFactory) SSLSocketFactory.getDefault(); // Fallback 213 } 214 } 215 216 private static boolean isSslCheckRelaxed() { 217 return "1".equals(SystemProperties.get("ro.debuggable")) && 218 "yes".equals(SystemProperties.get("socket.relaxsslcheck")); 219 } 220 221 private synchronized SSLSocketFactory getDelegate() { 222 // Relax the SSL check if instructed (for this factory, or systemwide) 223 if (!mSecure || isSslCheckRelaxed()) { 224 if (mInsecureFactory == null) { 225 if (mSecure) { 226 Log.w(TAG, "*** BYPASSING SSL SECURITY CHECKS (socket.relaxsslcheck=yes) ***"); 227 } else { 228 Log.w(TAG, "Bypassing SSL security checks at caller's request"); 229 } 230 mInsecureFactory = makeSocketFactory(mKeyManagers, INSECURE_TRUST_MANAGER); 231 } 232 return mInsecureFactory; 233 } else { 234 if (mSecureFactory == null) { 235 mSecureFactory = makeSocketFactory(mKeyManagers, mTrustManagers); 236 } 237 return mSecureFactory; 238 } 239 } 240 241 /** 242 * Sets the {@link TrustManager}s to be used for connections made by this factory. 243 * @hide 244 */ 245 public void setTrustManagers(TrustManager[] trustManager) { 246 mTrustManagers = trustManager; 247 248 // Clear out all cached secure factories since configurations have changed. 249 mSecureFactory = null; 250 // Note - insecure factories only ever use the INSECURE_TRUST_MANAGER so they need not 251 // be cleared out here. 252 } 253 254 /** 255 * Sets the {@link KeyManager}s to be used for connections made by this factory. 256 * @hide 257 */ 258 public void setKeyManagers(KeyManager[] keyManagers) { 259 mKeyManagers = keyManagers; 260 261 // Clear out any existing cached factories since configurations have changed. 262 mSecureFactory = null; 263 mInsecureFactory = null; 264 } 265 266 267 /** 268 * {@inheritDoc} 269 * 270 * <p>This method verifies the peer's certificate hostname after connecting 271 * (unless created with {@link #getInsecure(int, SSLSessionCache)}). 272 */ 273 @Override 274 public Socket createSocket(Socket k, String host, int port, boolean close) throws IOException { 275 OpenSSLSocketImpl s = (OpenSSLSocketImpl) getDelegate().createSocket(k, host, port, close); 276 s.setHandshakeTimeout(mHandshakeTimeoutMillis); 277 if (mSecure) { 278 verifyHostname(s, host); 279 } 280 return s; 281 } 282 283 /** 284 * Creates a new socket which is not connected to any remote host. 285 * You must use {@link Socket#connect} to connect the socket. 286 * 287 * <p class="caution"><b>Warning:</b> Hostname verification is not performed 288 * with this method. You MUST verify the server's identity after connecting 289 * the socket to avoid man-in-the-middle attacks.</p> 290 */ 291 @Override 292 public Socket createSocket() throws IOException { 293 OpenSSLSocketImpl s = (OpenSSLSocketImpl) getDelegate().createSocket(); 294 s.setHandshakeTimeout(mHandshakeTimeoutMillis); 295 return s; 296 } 297 298 /** 299 * {@inheritDoc} 300 * 301 * <p class="caution"><b>Warning:</b> Hostname verification is not performed 302 * with this method. You MUST verify the server's identity after connecting 303 * the socket to avoid man-in-the-middle attacks.</p> 304 */ 305 @Override 306 public Socket createSocket(InetAddress addr, int port, InetAddress localAddr, int localPort) 307 throws IOException { 308 OpenSSLSocketImpl s = (OpenSSLSocketImpl) getDelegate().createSocket( 309 addr, port, localAddr, localPort); 310 s.setHandshakeTimeout(mHandshakeTimeoutMillis); 311 return s; 312 } 313 314 /** 315 * {@inheritDoc} 316 * 317 * <p class="caution"><b>Warning:</b> Hostname verification is not performed 318 * with this method. You MUST verify the server's identity after connecting 319 * the socket to avoid man-in-the-middle attacks.</p> 320 */ 321 @Override 322 public Socket createSocket(InetAddress addr, int port) throws IOException { 323 OpenSSLSocketImpl s = (OpenSSLSocketImpl) getDelegate().createSocket(addr, port); 324 s.setHandshakeTimeout(mHandshakeTimeoutMillis); 325 return s; 326 } 327 328 /** 329 * {@inheritDoc} 330 * 331 * <p>This method verifies the peer's certificate hostname after connecting 332 * (unless created with {@link #getInsecure(int, SSLSessionCache)}). 333 */ 334 @Override 335 public Socket createSocket(String host, int port, InetAddress localAddr, int localPort) 336 throws IOException { 337 OpenSSLSocketImpl s = (OpenSSLSocketImpl) getDelegate().createSocket( 338 host, port, localAddr, localPort); 339 s.setHandshakeTimeout(mHandshakeTimeoutMillis); 340 if (mSecure) { 341 verifyHostname(s, host); 342 } 343 return s; 344 } 345 346 /** 347 * {@inheritDoc} 348 * 349 * <p>This method verifies the peer's certificate hostname after connecting 350 * (unless created with {@link #getInsecure(int, SSLSessionCache)}). 351 */ 352 @Override 353 public Socket createSocket(String host, int port) throws IOException { 354 OpenSSLSocketImpl s = (OpenSSLSocketImpl) getDelegate().createSocket(host, port); 355 s.setHandshakeTimeout(mHandshakeTimeoutMillis); 356 if (mSecure) { 357 verifyHostname(s, host); 358 } 359 return s; 360 } 361 362 @Override 363 public String[] getDefaultCipherSuites() { 364 return getDelegate().getSupportedCipherSuites(); 365 } 366 367 @Override 368 public String[] getSupportedCipherSuites() { 369 return getDelegate().getSupportedCipherSuites(); 370 } 371} 372