SSLCertificateSocketFactory.java revision fd901f735c858af8ec8884b2e7ab71a46a9639e6
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; 21import java.io.IOException; 22import java.net.InetAddress; 23import java.net.Socket; 24import java.net.SocketException; 25import java.security.KeyManagementException; 26import java.security.cert.X509Certificate; 27import javax.net.SocketFactory; 28import javax.net.ssl.HostnameVerifier; 29import javax.net.ssl.HttpsURLConnection; 30import javax.net.ssl.KeyManager; 31import javax.net.ssl.SSLException; 32import javax.net.ssl.SSLPeerUnverifiedException; 33import javax.net.ssl.SSLSession; 34import javax.net.ssl.SSLSocket; 35import javax.net.ssl.SSLSocketFactory; 36import javax.net.ssl.TrustManager; 37import javax.net.ssl.X509TrustManager; 38import org.apache.harmony.xnet.provider.jsse.OpenSSLContextImpl; 39import org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl; 40import org.apache.harmony.xnet.provider.jsse.SSLClientSessionCache; 41 42/** 43 * SSLSocketFactory implementation with several extra features: 44 * 45 * <ul> 46 * <li>Timeout specification for SSL handshake operations 47 * <li>Hostname verification in most cases (see WARNINGs below) 48 * <li>Optional SSL session caching with {@link SSLSessionCache} 49 * <li>Optionally bypass all SSL certificate checks 50 * </ul> 51 * 52 * The handshake timeout does not apply to actual TCP socket connection. 53 * If you want a connection timeout as well, use {@link #createSocket()} 54 * and {@link Socket#connect(SocketAddress, int)}, after which you 55 * must verify the identity of the server you are connected to. 56 * 57 * <p class="caution"><b>Most {@link SSLSocketFactory} implementations do not 58 * verify the server's identity, allowing man-in-the-middle attacks.</b> 59 * This implementation does check the server's certificate hostname, but only 60 * for createSocket variants that specify a hostname. When using methods that 61 * use {@link InetAddress} or which return an unconnected socket, you MUST 62 * verify the server's identity yourself to ensure a secure connection.</p> 63 * 64 * <p>One way to verify the server's identity is to use 65 * {@link HttpsURLConnection#getDefaultHostnameVerifier()} to get a 66 * {@link HostnameVerifier} to verify the certificate hostname. 67 * 68 * <p>On development devices, "setprop socket.relaxsslcheck yes" bypasses all 69 * SSL certificate and hostname checks for testing purposes. This setting 70 * requires root access. 71 */ 72public class SSLCertificateSocketFactory extends SSLSocketFactory { 73 private static final String TAG = "SSLCertificateSocketFactory"; 74 75 private static final TrustManager[] INSECURE_TRUST_MANAGER = new TrustManager[] { 76 new X509TrustManager() { 77 public X509Certificate[] getAcceptedIssuers() { return null; } 78 public void checkClientTrusted(X509Certificate[] certs, String authType) { } 79 public void checkServerTrusted(X509Certificate[] certs, String authType) { } 80 } 81 }; 82 83 private static final HostnameVerifier HOSTNAME_VERIFIER = 84 HttpsURLConnection.getDefaultHostnameVerifier(); 85 86 private SSLSocketFactory mInsecureFactory = null; 87 private SSLSocketFactory mSecureFactory = null; 88 private TrustManager[] mTrustManagers = null; 89 private KeyManager[] mKeyManagers = null; 90 private byte[] mNpnProtocols = null; 91 92 private final int mHandshakeTimeoutMillis; 93 private final int mWriteTimeoutMillis; 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, 105 int writeTimeoutMillis, 106 SSLSessionCache cache, 107 boolean secure) { 108 mHandshakeTimeoutMillis = handshakeTimeoutMillis; 109 mWriteTimeoutMillis = writeTimeoutMillis; 110 mSessionCache = cache == null ? null : cache.mSessionCache; 111 mSecure = secure; 112 } 113 114 private SSLCertificateSocketFactory( 115 int handshakeTimeoutMillis, SSLSessionCache cache, boolean secure) { 116 this(handshakeTimeoutMillis, 0, cache, secure); 117 } 118 119 /** 120 * Returns a new socket factory instance with an optional handshake timeout. 121 * 122 * @param handshakeTimeoutMillis to use for SSL connection handshake, or 0 123 * for none. The socket timeout is reset to 0 after the handshake. 124 * @return a new SSLSocketFactory with the specified parameters 125 */ 126 public static SocketFactory getDefault(int handshakeTimeoutMillis) { 127 return new SSLCertificateSocketFactory(handshakeTimeoutMillis, null, true); 128 } 129 130 /** 131 * Returns a new socket factory instance with an optional handshake timeout 132 * and SSL session cache. 133 * 134 * @param handshakeTimeoutMillis to use for SSL connection handshake, or 0 135 * for none. The socket timeout is reset to 0 after the handshake. 136 * @param cache The {@link SSLSessionCache} to use, or null for no cache. 137 * @return a new SSLSocketFactory with the specified parameters 138 */ 139 public static SSLSocketFactory getDefault(int handshakeTimeoutMillis, SSLSessionCache cache) { 140 return new SSLCertificateSocketFactory(handshakeTimeoutMillis, cache, true); 141 } 142 143 /** 144 * Returns a new instance of a socket factory with all SSL security checks 145 * disabled, using an optional handshake timeout and SSL session cache. 146 * 147 * <p class="caution"><b>Warning:</b> Sockets created using this factory 148 * are vulnerable to man-in-the-middle attacks!</p> 149 * 150 * @param handshakeTimeoutMillis to use for SSL connection handshake, or 0 151 * for none. The socket timeout is reset to 0 after the handshake. 152 * @param cache The {@link SSLSessionCache} to use, or null for no cache. 153 * @return an insecure SSLSocketFactory with the specified parameters 154 */ 155 public static SSLSocketFactory getInsecure(int handshakeTimeoutMillis, SSLSessionCache cache) { 156 return new SSLCertificateSocketFactory(handshakeTimeoutMillis, cache, false); 157 } 158 159 /** 160 * Returns a socket factory (also named SSLSocketFactory, but in a different 161 * namespace) for use with the Apache HTTP stack. 162 * 163 * @param handshakeTimeoutMillis to use for SSL connection handshake, or 0 164 * for none. The socket timeout is reset to 0 after the handshake. 165 * @param cache The {@link SSLSessionCache} to use, or null for no cache. 166 * @return a new SocketFactory with the specified parameters 167 */ 168 public static org.apache.http.conn.ssl.SSLSocketFactory getHttpSocketFactory( 169 int handshakeTimeoutMillis, SSLSessionCache cache) { 170 return new org.apache.http.conn.ssl.SSLSocketFactory( 171 new SSLCertificateSocketFactory(handshakeTimeoutMillis, cache, true)); 172 } 173 174 /** 175 * Returns a socket factory (also named SSLSocketFactory, but in a different 176 * namespace) for use with the Apache HTTP stack. 177 * 178 * @param handshakeTimeoutMillis to use for SSL connection handshake, or 0 179 * for none. The socket timeout is reset to 0 after the handshake. 180 * @param writeTimeoutMillis the desired write timeout in milliseconds or 0 for none. 181 * @param cache The {@link SSLSessionCache} to use, or null for no cache. 182 * @return a new SocketFactory with the specified parameters 183 */ 184 public static org.apache.http.conn.ssl.SSLSocketFactory getHttpSocketFactory( 185 int handshakeTimeoutMillis, 186 int writeTimeoutMillis, 187 SSLSessionCache cache) { 188 return new org.apache.http.conn.ssl.SSLSocketFactory(new SSLCertificateSocketFactory( 189 handshakeTimeoutMillis, writeTimeoutMillis, cache, true)); 190 } 191 192 /** 193 * Verify the hostname of the certificate used by the other end of a 194 * connected socket. You MUST call this if you did not supply a hostname 195 * to {@link #createSocket()}. It is harmless to call this method 196 * redundantly if the hostname has already been verified. 197 * 198 * <p>Wildcard certificates are allowed to verify any matching hostname, 199 * so "foo.bar.example.com" is verified if the peer has a certificate 200 * for "*.example.com". 201 * 202 * @param socket An SSL socket which has been connected to a server 203 * @param hostname The expected hostname of the remote server 204 * @throws IOException if something goes wrong handshaking with the server 205 * @throws SSLPeerUnverifiedException if the server cannot prove its identity 206 * 207 * @hide 208 */ 209 public static void verifyHostname(Socket socket, String hostname) throws IOException { 210 if (!(socket instanceof SSLSocket)) { 211 throw new IllegalArgumentException("Attempt to verify non-SSL socket"); 212 } 213 214 if (!isSslCheckRelaxed()) { 215 // The code at the start of OpenSSLSocketImpl.startHandshake() 216 // ensures that the call is idempotent, so we can safely call it. 217 SSLSocket ssl = (SSLSocket) socket; 218 ssl.startHandshake(); 219 220 SSLSession session = ssl.getSession(); 221 if (session == null) { 222 throw new SSLException("Cannot verify SSL socket without session"); 223 } 224 if (!HOSTNAME_VERIFIER.verify(hostname, session)) { 225 throw new SSLPeerUnverifiedException("Cannot verify hostname: " + hostname); 226 } 227 } 228 } 229 230 private SSLSocketFactory makeSocketFactory( 231 KeyManager[] keyManagers, TrustManager[] trustManagers) { 232 try { 233 OpenSSLContextImpl sslContext = new OpenSSLContextImpl(); 234 sslContext.engineInit(keyManagers, trustManagers, null); 235 sslContext.engineGetClientSessionContext().setPersistentCache(mSessionCache); 236 return sslContext.engineGetSocketFactory(); 237 } catch (KeyManagementException e) { 238 Log.wtf(TAG, e); 239 return (SSLSocketFactory) SSLSocketFactory.getDefault(); // Fallback 240 } 241 } 242 243 private static boolean isSslCheckRelaxed() { 244 return "1".equals(SystemProperties.get("ro.debuggable")) && 245 "yes".equals(SystemProperties.get("socket.relaxsslcheck")); 246 } 247 248 private synchronized SSLSocketFactory getDelegate() { 249 // Relax the SSL check if instructed (for this factory, or systemwide) 250 if (!mSecure || isSslCheckRelaxed()) { 251 if (mInsecureFactory == null) { 252 if (mSecure) { 253 Log.w(TAG, "*** BYPASSING SSL SECURITY CHECKS (socket.relaxsslcheck=yes) ***"); 254 } else { 255 Log.w(TAG, "Bypassing SSL security checks at caller's request"); 256 } 257 mInsecureFactory = makeSocketFactory(mKeyManagers, INSECURE_TRUST_MANAGER); 258 } 259 return mInsecureFactory; 260 } else { 261 if (mSecureFactory == null) { 262 mSecureFactory = makeSocketFactory(mKeyManagers, mTrustManagers); 263 } 264 return mSecureFactory; 265 } 266 } 267 268 /** 269 * Sets the {@link TrustManager}s to be used for connections made by this factory. 270 */ 271 public void setTrustManagers(TrustManager[] trustManager) { 272 mTrustManagers = trustManager; 273 274 // Clear out all cached secure factories since configurations have changed. 275 mSecureFactory = null; 276 // Note - insecure factories only ever use the INSECURE_TRUST_MANAGER so they need not 277 // be cleared out here. 278 } 279 280 /** 281 * Sets the <a href="http://technotes.googlecode.com/git/nextprotoneg.html">Next 282 * Protocol Negotiation (NPN)</a> protocols that this peer is interested in. 283 * 284 * <p>For servers this is the sequence of protocols to advertise as 285 * supported, in order of preference. This list is sent unencrypted to 286 * all clients that support NPN. 287 * 288 * <p>For clients this is a list of supported protocols to match against the 289 * server's list. If there is no protocol supported by both client and 290 * server then the first protocol in the client's list will be selected. 291 * The order of the client's protocols is otherwise insignificant. 292 * 293 * @param npnProtocols a non-empty list of protocol byte arrays. All arrays 294 * must be non-empty and of length less than 256. 295 */ 296 public void setNpnProtocols(byte[][] npnProtocols) { 297 this.mNpnProtocols = toNpnProtocolsList(npnProtocols); 298 } 299 300 /** 301 * Returns an array containing the concatenation of length-prefixed byte 302 * strings. 303 */ 304 static byte[] toNpnProtocolsList(byte[]... npnProtocols) { 305 if (npnProtocols.length == 0) { 306 throw new IllegalArgumentException("npnProtocols.length == 0"); 307 } 308 int totalLength = 0; 309 for (byte[] s : npnProtocols) { 310 if (s.length == 0 || s.length > 255) { 311 throw new IllegalArgumentException("s.length == 0 || s.length > 255: " + s.length); 312 } 313 totalLength += 1 + s.length; 314 } 315 byte[] result = new byte[totalLength]; 316 int pos = 0; 317 for (byte[] s : npnProtocols) { 318 result[pos++] = (byte) s.length; 319 for (byte b : s) { 320 result[pos++] = b; 321 } 322 } 323 return result; 324 } 325 326 /** 327 * Returns the <a href="http://technotes.googlecode.com/git/nextprotoneg.html">Next 328 * Protocol Negotiation (NPN)</a> protocol selected by client and server, or 329 * null if no protocol was negotiated. 330 * 331 * @param socket a socket created by this factory. 332 * @throws IllegalArgumentException if the socket was not created by this factory. 333 */ 334 public byte[] getNpnSelectedProtocol(Socket socket) { 335 return castToOpenSSLSocket(socket).getNpnSelectedProtocol(); 336 } 337 338 /** 339 * Sets the {@link KeyManager}s to be used for connections made by this factory. 340 */ 341 public void setKeyManagers(KeyManager[] keyManagers) { 342 mKeyManagers = keyManagers; 343 344 // Clear out any existing cached factories since configurations have changed. 345 mSecureFactory = null; 346 mInsecureFactory = null; 347 } 348 349 /** 350 * Enables <a href="http://tools.ietf.org/html/rfc5077#section-3.2">session ticket</a> 351 * support on the given socket. 352 * 353 * @param socket a socket created by this factory 354 * @param useSessionTickets {@code true} to enable session ticket support on this socket. 355 * @throws IllegalArgumentException if the socket was not created by this factory. 356 */ 357 public void setUseSessionTickets(Socket socket, boolean useSessionTickets) { 358 castToOpenSSLSocket(socket).setUseSessionTickets(useSessionTickets); 359 } 360 361 /** 362 * Turns on <a href="http://tools.ietf.org/html/rfc6066#section-3">Server 363 * Name Indication (SNI)</a> on a given socket. 364 * 365 * @param socket a socket created by this factory. 366 * @param hostName the desired SNI hostname, null to disable. 367 * @throws IllegalArgumentException if the socket was not created by this factory. 368 */ 369 public void setHostname(Socket socket, String hostName) { 370 castToOpenSSLSocket(socket).setHostname(hostName); 371 } 372 373 /** 374 * Sets this socket's SO_SNDTIMEO write timeout in milliseconds. 375 * Use 0 for no timeout. 376 * To take effect, this option must be set before the blocking method was called. 377 * 378 * @param socket a socket created by this factory. 379 * @param writeTimeoutMilliseconds the desired write timeout in milliseconds. 380 * @throws IllegalArgumentException if the socket was not created by this factory. 381 */ 382 public void setSoWriteTimeout(Socket socket, int writeTimeoutMilliseconds) 383 throws SocketException { 384 castToOpenSSLSocket(socket).setSoWriteTimeout(writeTimeoutMilliseconds); 385 } 386 387 private static OpenSSLSocketImpl castToOpenSSLSocket(Socket socket) { 388 if (!(socket instanceof OpenSSLSocketImpl)) { 389 throw new IllegalArgumentException("Socket not created by this factory: " 390 + socket); 391 } 392 393 return (OpenSSLSocketImpl) socket; 394 } 395 396 /** 397 * {@inheritDoc} 398 * 399 * <p>This method verifies the peer's certificate hostname after connecting 400 * (unless created with {@link #getInsecure(int, SSLSessionCache)}). 401 */ 402 @Override 403 public Socket createSocket(Socket k, String host, int port, boolean close) throws IOException { 404 OpenSSLSocketImpl s = (OpenSSLSocketImpl) getDelegate().createSocket(k, host, port, close); 405 s.setNpnProtocols(mNpnProtocols); 406 s.setHandshakeTimeout(mHandshakeTimeoutMillis); 407 s.setSoWriteTimeout(mWriteTimeoutMillis); 408 if (mSecure) { 409 verifyHostname(s, host); 410 } 411 return s; 412 } 413 414 /** 415 * Creates a new socket which is not connected to any remote host. 416 * You must use {@link Socket#connect} to connect the socket. 417 * 418 * <p class="caution"><b>Warning:</b> Hostname verification is not performed 419 * with this method. You MUST verify the server's identity after connecting 420 * the socket to avoid man-in-the-middle attacks.</p> 421 */ 422 @Override 423 public Socket createSocket() throws IOException { 424 OpenSSLSocketImpl s = (OpenSSLSocketImpl) getDelegate().createSocket(); 425 s.setNpnProtocols(mNpnProtocols); 426 s.setHandshakeTimeout(mHandshakeTimeoutMillis); 427 s.setSoWriteTimeout(mWriteTimeoutMillis); 428 return s; 429 } 430 431 /** 432 * {@inheritDoc} 433 * 434 * <p class="caution"><b>Warning:</b> Hostname verification is not performed 435 * with this method. You MUST verify the server's identity after connecting 436 * the socket to avoid man-in-the-middle attacks.</p> 437 */ 438 @Override 439 public Socket createSocket(InetAddress addr, int port, InetAddress localAddr, int localPort) 440 throws IOException { 441 OpenSSLSocketImpl s = (OpenSSLSocketImpl) getDelegate().createSocket( 442 addr, port, localAddr, localPort); 443 s.setNpnProtocols(mNpnProtocols); 444 s.setHandshakeTimeout(mHandshakeTimeoutMillis); 445 s.setSoWriteTimeout(mWriteTimeoutMillis); 446 return s; 447 } 448 449 /** 450 * {@inheritDoc} 451 * 452 * <p class="caution"><b>Warning:</b> Hostname verification is not performed 453 * with this method. You MUST verify the server's identity after connecting 454 * the socket to avoid man-in-the-middle attacks.</p> 455 */ 456 @Override 457 public Socket createSocket(InetAddress addr, int port) throws IOException { 458 OpenSSLSocketImpl s = (OpenSSLSocketImpl) getDelegate().createSocket(addr, port); 459 s.setNpnProtocols(mNpnProtocols); 460 s.setHandshakeTimeout(mHandshakeTimeoutMillis); 461 s.setSoWriteTimeout(mWriteTimeoutMillis); 462 return s; 463 } 464 465 /** 466 * {@inheritDoc} 467 * 468 * <p>This method verifies the peer's certificate hostname after connecting 469 * (unless created with {@link #getInsecure(int, SSLSessionCache)}). 470 */ 471 @Override 472 public Socket createSocket(String host, int port, InetAddress localAddr, int localPort) 473 throws IOException { 474 OpenSSLSocketImpl s = (OpenSSLSocketImpl) getDelegate().createSocket( 475 host, port, localAddr, localPort); 476 s.setNpnProtocols(mNpnProtocols); 477 s.setHandshakeTimeout(mHandshakeTimeoutMillis); 478 s.setSoWriteTimeout(mWriteTimeoutMillis); 479 if (mSecure) { 480 verifyHostname(s, host); 481 } 482 return s; 483 } 484 485 /** 486 * {@inheritDoc} 487 * 488 * <p>This method verifies the peer's certificate hostname after connecting 489 * (unless created with {@link #getInsecure(int, SSLSessionCache)}). 490 */ 491 @Override 492 public Socket createSocket(String host, int port) throws IOException { 493 OpenSSLSocketImpl s = (OpenSSLSocketImpl) getDelegate().createSocket(host, port); 494 s.setNpnProtocols(mNpnProtocols); 495 s.setHandshakeTimeout(mHandshakeTimeoutMillis); 496 s.setSoWriteTimeout(mWriteTimeoutMillis); 497 if (mSecure) { 498 verifyHostname(s, host); 499 } 500 return s; 501 } 502 503 @Override 504 public String[] getDefaultCipherSuites() { 505 return getDelegate().getSupportedCipherSuites(); 506 } 507 508 @Override 509 public String[] getSupportedCipherSuites() { 510 return getDelegate().getSupportedCipherSuites(); 511 } 512} 513