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