1 /* 2 * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/conn/ssl/SSLSocketFactory.java $ 3 * $Revision: 659194 $ 4 * $Date: 2008-05-22 11:33:47 -0700 (Thu, 22 May 2008) $ 5 * 6 * ==================================================================== 7 * Licensed to the Apache Software Foundation (ASF) under one 8 * or more contributor license agreements. See the NOTICE file 9 * distributed with this work for additional information 10 * regarding copyright ownership. The ASF licenses this file 11 * to you under the Apache License, Version 2.0 (the 12 * "License"); you may not use this file except in compliance 13 * with the License. You may obtain a copy of the License at 14 * 15 * http://www.apache.org/licenses/LICENSE-2.0 16 * 17 * Unless required by applicable law or agreed to in writing, 18 * software distributed under the License is distributed on an 19 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 20 * KIND, either express or implied. See the License for the 21 * specific language governing permissions and limitations 22 * under the License. 23 * ==================================================================== 24 * 25 * This software consists of voluntary contributions made by many 26 * individuals on behalf of the Apache Software Foundation. For more 27 * information on the Apache Software Foundation, please see 28 * <http://www.apache.org/>. 29 * 30 */ 31 32 package org.apache.http.conn.ssl; 33 34 import org.apache.http.conn.scheme.HostNameResolver; 35 import org.apache.http.conn.scheme.LayeredSocketFactory; 36 import org.apache.http.params.HttpConnectionParams; 37 import org.apache.http.params.HttpParams; 38 39 import javax.net.ssl.HttpsURLConnection; 40 import javax.net.ssl.KeyManager; 41 import javax.net.ssl.KeyManagerFactory; 42 import javax.net.ssl.SSLContext; 43 import javax.net.ssl.SSLSocket; 44 import javax.net.ssl.TrustManager; 45 import javax.net.ssl.TrustManagerFactory; 46 import java.io.IOException; 47 import java.net.InetAddress; 48 import java.net.InetSocketAddress; 49 import java.net.Socket; 50 import java.net.UnknownHostException; 51 import java.security.KeyManagementException; 52 import java.security.KeyStore; 53 import java.security.KeyStoreException; 54 import java.security.NoSuchAlgorithmException; 55 import java.security.SecureRandom; 56 import java.security.UnrecoverableKeyException; 57 58 /** 59 * Layered socket factory for TLS/SSL connections, based on JSSE. 60 *. 61 * <p> 62 * SSLSocketFactory can be used to validate the identity of the HTTPS 63 * server against a list of trusted certificates and to authenticate to 64 * the HTTPS server using a private key. 65 * </p> 66 * 67 * <p> 68 * SSLSocketFactory will enable server authentication when supplied with 69 * a {@link KeyStore truststore} file containg one or several trusted 70 * certificates. The client secure socket will reject the connection during 71 * the SSL session handshake if the target HTTPS server attempts to 72 * authenticate itself with a non-trusted certificate. 73 * </p> 74 * 75 * <p> 76 * Use JDK keytool utility to import a trusted certificate and generate a truststore file: 77 * <pre> 78 * keytool -import -alias "my server cert" -file server.crt -keystore my.truststore 79 * </pre> 80 * </p> 81 * 82 * <p> 83 * SSLSocketFactory will enable client authentication when supplied with 84 * a {@link KeyStore keystore} file containg a private key/public certificate 85 * pair. The client secure socket will use the private key to authenticate 86 * itself to the target HTTPS server during the SSL session handshake if 87 * requested to do so by the server. 88 * The target HTTPS server will in its turn verify the certificate presented 89 * by the client in order to establish client's authenticity 90 * </p> 91 * 92 * <p> 93 * Use the following sequence of actions to generate a keystore file 94 * </p> 95 * <ul> 96 * <li> 97 * <p> 98 * Use JDK keytool utility to generate a new key 99 * <pre>keytool -genkey -v -alias "my client key" -validity 365 -keystore my.keystore</pre> 100 * For simplicity use the same password for the key as that of the keystore 101 * </p> 102 * </li> 103 * <li> 104 * <p> 105 * Issue a certificate signing request (CSR) 106 * <pre>keytool -certreq -alias "my client key" -file mycertreq.csr -keystore my.keystore</pre> 107 * </p> 108 * </li> 109 * <li> 110 * <p> 111 * Send the certificate request to the trusted Certificate Authority for signature. 112 * One may choose to act as her own CA and sign the certificate request using a PKI 113 * tool, such as OpenSSL. 114 * </p> 115 * </li> 116 * <li> 117 * <p> 118 * Import the trusted CA root certificate 119 * <pre>keytool -import -alias "my trusted ca" -file caroot.crt -keystore my.keystore</pre> 120 * </p> 121 * </li> 122 * <li> 123 * <p> 124 * Import the PKCS#7 file containg the complete certificate chain 125 * <pre>keytool -import -alias "my client key" -file mycert.p7 -keystore my.keystore</pre> 126 * </p> 127 * </li> 128 * <li> 129 * <p> 130 * Verify the content the resultant keystore file 131 * <pre>keytool -list -v -keystore my.keystore</pre> 132 * </p> 133 * </li> 134 * </ul> 135 * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> 136 * @author Julius Davies 137 */ 138 139 public class SSLSocketFactory implements LayeredSocketFactory { 140 141 public static final String TLS = "TLS"; 142 public static final String SSL = "SSL"; 143 public static final String SSLV2 = "SSLv2"; 144 145 public static final X509HostnameVerifier ALLOW_ALL_HOSTNAME_VERIFIER 146 = new AllowAllHostnameVerifier(); 147 148 public static final X509HostnameVerifier BROWSER_COMPATIBLE_HOSTNAME_VERIFIER 149 = new BrowserCompatHostnameVerifier(); 150 151 public static final X509HostnameVerifier STRICT_HOSTNAME_VERIFIER 152 = new StrictHostnameVerifier(); 153 154 /* 155 * Put defaults into holder class to avoid class preloading creating an 156 * instance of the classes referenced. 157 */ 158 private static class NoPreloadHolder { 159 /** 160 * The factory using the default JVM settings for secure connections. 161 */ 162 private static final SSLSocketFactory DEFAULT_FACTORY = new SSLSocketFactory(); 163 } 164 165 /** 166 * Gets an singleton instance of the SSLProtocolSocketFactory. 167 * @return a SSLProtocolSocketFactory 168 */ 169 public static SSLSocketFactory getSocketFactory() { 170 return NoPreloadHolder.DEFAULT_FACTORY; 171 } 172 173 private final SSLContext sslcontext; 174 private final javax.net.ssl.SSLSocketFactory socketfactory; 175 private final HostNameResolver nameResolver; 176 private X509HostnameVerifier hostnameVerifier = BROWSER_COMPATIBLE_HOSTNAME_VERIFIER; 177 178 public SSLSocketFactory( 179 String algorithm, 180 final KeyStore keystore, 181 final String keystorePassword, 182 final KeyStore truststore, 183 final SecureRandom random, 184 final HostNameResolver nameResolver) 185 throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException 186 { 187 super(); 188 if (algorithm == null) { 189 algorithm = TLS; 190 } 191 KeyManager[] keymanagers = null; 192 if (keystore != null) { 193 keymanagers = createKeyManagers(keystore, keystorePassword); 194 } 195 TrustManager[] trustmanagers = null; 196 if (truststore != null) { 197 trustmanagers = createTrustManagers(truststore); 198 } 199 this.sslcontext = SSLContext.getInstance(algorithm); 200 this.sslcontext.init(keymanagers, trustmanagers, random); 201 this.socketfactory = this.sslcontext.getSocketFactory(); 202 this.nameResolver = nameResolver; 203 } 204 205 public SSLSocketFactory( 206 final KeyStore keystore, 207 final String keystorePassword, 208 final KeyStore truststore) 209 throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException 210 { 211 this(TLS, keystore, keystorePassword, truststore, null, null); 212 } 213 214 public SSLSocketFactory(final KeyStore keystore, final String keystorePassword) 215 throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException 216 { 217 this(TLS, keystore, keystorePassword, null, null, null); 218 } 219 220 public SSLSocketFactory(final KeyStore truststore) 221 throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException 222 { 223 this(TLS, null, null, truststore, null, null); 224 } 225 226 /** 227 * Constructs an HttpClient SSLSocketFactory backed by the given JSSE 228 * SSLSocketFactory. 229 * 230 * @hide 231 */ 232 public SSLSocketFactory(javax.net.ssl.SSLSocketFactory socketfactory) { 233 super(); 234 this.sslcontext = null; 235 this.socketfactory = socketfactory; 236 this.nameResolver = null; 237 } 238 239 /** 240 * Creates the default SSL socket factory. 241 * This constructor is used exclusively to instantiate the factory for 242 * {@link #getSocketFactory getSocketFactory}. 243 */ 244 private SSLSocketFactory() { 245 super(); 246 this.sslcontext = null; 247 this.socketfactory = HttpsURLConnection.getDefaultSSLSocketFactory(); 248 this.nameResolver = null; 249 } 250 251 private static KeyManager[] createKeyManagers(final KeyStore keystore, final String password) 252 throws KeyStoreException, NoSuchAlgorithmException, UnrecoverableKeyException { 253 if (keystore == null) { 254 throw new IllegalArgumentException("Keystore may not be null"); 255 } 256 KeyManagerFactory kmfactory = KeyManagerFactory.getInstance( 257 KeyManagerFactory.getDefaultAlgorithm()); 258 kmfactory.init(keystore, password != null ? password.toCharArray(): null); 259 return kmfactory.getKeyManagers(); 260 } 261 262 private static TrustManager[] createTrustManagers(final KeyStore keystore) 263 throws KeyStoreException, NoSuchAlgorithmException { 264 if (keystore == null) { 265 throw new IllegalArgumentException("Keystore may not be null"); 266 } 267 TrustManagerFactory tmfactory = TrustManagerFactory.getInstance( 268 TrustManagerFactory.getDefaultAlgorithm()); 269 tmfactory.init(keystore); 270 return tmfactory.getTrustManagers(); 271 } 272 273 274 // non-javadoc, see interface org.apache.http.conn.SocketFactory 275 public Socket createSocket() 276 throws IOException { 277 278 // the cast makes sure that the factory is working as expected 279 return (SSLSocket) this.socketfactory.createSocket(); 280 } 281 282 283 // non-javadoc, see interface org.apache.http.conn.SocketFactory 284 public Socket connectSocket( 285 final Socket sock, 286 final String host, 287 final int port, 288 final InetAddress localAddress, 289 int localPort, 290 final HttpParams params 291 ) throws IOException { 292 293 if (host == null) { 294 throw new IllegalArgumentException("Target host may not be null."); 295 } 296 if (params == null) { 297 throw new IllegalArgumentException("Parameters may not be null."); 298 } 299 300 SSLSocket sslsock = (SSLSocket) 301 ((sock != null) ? sock : createSocket()); 302 303 if ((localAddress != null) || (localPort > 0)) { 304 305 // we need to bind explicitly 306 if (localPort < 0) 307 localPort = 0; // indicates "any" 308 309 InetSocketAddress isa = 310 new InetSocketAddress(localAddress, localPort); 311 sslsock.bind(isa); 312 } 313 314 int connTimeout = HttpConnectionParams.getConnectionTimeout(params); 315 int soTimeout = HttpConnectionParams.getSoTimeout(params); 316 317 InetSocketAddress remoteAddress; 318 if (this.nameResolver != null) { 319 remoteAddress = new InetSocketAddress(this.nameResolver.resolve(host), port); 320 } else { 321 remoteAddress = new InetSocketAddress(host, port); 322 } 323 324 sslsock.connect(remoteAddress, connTimeout); 325 326 sslsock.setSoTimeout(soTimeout); 327 try { 328 hostnameVerifier.verify(host, sslsock); 329 // verifyHostName() didn't blowup - good! 330 } catch (IOException iox) { 331 // close the socket before re-throwing the exception 332 try { sslsock.close(); } catch (Exception x) { /*ignore*/ } 333 throw iox; 334 } 335 336 return sslsock; 337 } 338 339 340 /** 341 * Checks whether a socket connection is secure. 342 * This factory creates TLS/SSL socket connections 343 * which, by default, are considered secure. 344 * <br/> 345 * Derived classes may override this method to perform 346 * runtime checks, for example based on the cypher suite. 347 * 348 * @param sock the connected socket 349 * 350 * @return <code>true</code> 351 * 352 * @throws IllegalArgumentException if the argument is invalid 353 */ 354 public boolean isSecure(Socket sock) 355 throws IllegalArgumentException { 356 357 if (sock == null) { 358 throw new IllegalArgumentException("Socket may not be null."); 359 } 360 // This instanceof check is in line with createSocket() above. 361 if (!(sock instanceof SSLSocket)) { 362 throw new IllegalArgumentException 363 ("Socket not created by this factory."); 364 } 365 // This check is performed last since it calls the argument object. 366 if (sock.isClosed()) { 367 throw new IllegalArgumentException("Socket is closed."); 368 } 369 370 return true; 371 372 } // isSecure 373 374 375 // non-javadoc, see interface LayeredSocketFactory 376 public Socket createSocket( 377 final Socket socket, 378 final String host, 379 final int port, 380 final boolean autoClose 381 ) throws IOException, UnknownHostException { 382 SSLSocket sslSocket = (SSLSocket) this.socketfactory.createSocket( 383 socket, 384 host, 385 port, 386 autoClose 387 ); 388 hostnameVerifier.verify(host, sslSocket); 389 // verifyHostName() didn't blowup - good! 390 return sslSocket; 391 } 392 393 public void setHostnameVerifier(X509HostnameVerifier hostnameVerifier) { 394 if ( hostnameVerifier == null ) { 395 throw new IllegalArgumentException("Hostname verifier may not be null"); 396 } 397 this.hostnameVerifier = hostnameVerifier; 398 } 399 400 public X509HostnameVerifier getHostnameVerifier() { 401 return hostnameVerifier; 402 } 403 404 } 405