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