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