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