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 * The factory using the default JVM settings for secure connections. 155 */ 156 private static final SSLSocketFactory DEFAULT_FACTORY = new SSLSocketFactory(); 157 158 /** 159 * Gets an singleton instance of the SSLProtocolSocketFactory. 160 * @return a SSLProtocolSocketFactory 161 */ 162 public static SSLSocketFactory getSocketFactory() { 163 return DEFAULT_FACTORY; 164 } 165 166 private final SSLContext sslcontext; 167 private final javax.net.ssl.SSLSocketFactory socketfactory; 168 private final HostNameResolver nameResolver; 169 private X509HostnameVerifier hostnameVerifier = BROWSER_COMPATIBLE_HOSTNAME_VERIFIER; 170 171 public SSLSocketFactory( 172 String algorithm, 173 final KeyStore keystore, 174 final String keystorePassword, 175 final KeyStore truststore, 176 final SecureRandom random, 177 final HostNameResolver nameResolver) 178 throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException 179 { 180 super(); 181 if (algorithm == null) { 182 algorithm = TLS; 183 } 184 KeyManager[] keymanagers = null; 185 if (keystore != null) { 186 keymanagers = createKeyManagers(keystore, keystorePassword); 187 } 188 TrustManager[] trustmanagers = null; 189 if (truststore != null) { 190 trustmanagers = createTrustManagers(truststore); 191 } 192 this.sslcontext = SSLContext.getInstance(algorithm); 193 this.sslcontext.init(keymanagers, trustmanagers, random); 194 this.socketfactory = this.sslcontext.getSocketFactory(); 195 this.nameResolver = nameResolver; 196 } 197 198 public SSLSocketFactory( 199 final KeyStore keystore, 200 final String keystorePassword, 201 final KeyStore truststore) 202 throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException 203 { 204 this(TLS, keystore, keystorePassword, truststore, null, null); 205 } 206 207 public SSLSocketFactory(final KeyStore keystore, final String keystorePassword) 208 throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException 209 { 210 this(TLS, keystore, keystorePassword, null, null, null); 211 } 212 213 public SSLSocketFactory(final KeyStore truststore) 214 throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException 215 { 216 this(TLS, null, null, truststore, null, null); 217 } 218 219 /** 220 * Constructs an HttpClient SSLSocketFactory backed by the given JSSE 221 * SSLSocketFactory. 222 * 223 * @hide 224 */ 225 public SSLSocketFactory(javax.net.ssl.SSLSocketFactory socketfactory) { 226 super(); 227 this.sslcontext = null; 228 this.socketfactory = socketfactory; 229 this.nameResolver = null; 230 } 231 232 /** 233 * Creates the default SSL socket factory. 234 * This constructor is used exclusively to instantiate the factory for 235 * {@link #getSocketFactory getSocketFactory}. 236 */ 237 private SSLSocketFactory() { 238 super(); 239 this.sslcontext = null; 240 this.socketfactory = HttpsURLConnection.getDefaultSSLSocketFactory(); 241 this.nameResolver = null; 242 } 243 244 private static KeyManager[] createKeyManagers(final KeyStore keystore, final String password) 245 throws KeyStoreException, NoSuchAlgorithmException, UnrecoverableKeyException { 246 if (keystore == null) { 247 throw new IllegalArgumentException("Keystore may not be null"); 248 } 249 KeyManagerFactory kmfactory = KeyManagerFactory.getInstance( 250 KeyManagerFactory.getDefaultAlgorithm()); 251 kmfactory.init(keystore, password != null ? password.toCharArray(): null); 252 return kmfactory.getKeyManagers(); 253 } 254 255 private static TrustManager[] createTrustManagers(final KeyStore keystore) 256 throws KeyStoreException, NoSuchAlgorithmException { 257 if (keystore == null) { 258 throw new IllegalArgumentException("Keystore may not be null"); 259 } 260 TrustManagerFactory tmfactory = TrustManagerFactory.getInstance( 261 TrustManagerFactory.getDefaultAlgorithm()); 262 tmfactory.init(keystore); 263 return tmfactory.getTrustManagers(); 264 } 265 266 267 // non-javadoc, see interface org.apache.http.conn.SocketFactory 268 public Socket createSocket() 269 throws IOException { 270 271 // the cast makes sure that the factory is working as expected 272 return (SSLSocket) this.socketfactory.createSocket(); 273 } 274 275 276 // non-javadoc, see interface org.apache.http.conn.SocketFactory 277 public Socket connectSocket( 278 final Socket sock, 279 final String host, 280 final int port, 281 final InetAddress localAddress, 282 int localPort, 283 final HttpParams params 284 ) throws IOException { 285 286 if (host == null) { 287 throw new IllegalArgumentException("Target host may not be null."); 288 } 289 if (params == null) { 290 throw new IllegalArgumentException("Parameters may not be null."); 291 } 292 293 SSLSocket sslsock = (SSLSocket) 294 ((sock != null) ? sock : createSocket()); 295 296 if ((localAddress != null) || (localPort > 0)) { 297 298 // we need to bind explicitly 299 if (localPort < 0) 300 localPort = 0; // indicates "any" 301 302 InetSocketAddress isa = 303 new InetSocketAddress(localAddress, localPort); 304 sslsock.bind(isa); 305 } 306 307 int connTimeout = HttpConnectionParams.getConnectionTimeout(params); 308 int soTimeout = HttpConnectionParams.getSoTimeout(params); 309 310 InetSocketAddress remoteAddress; 311 if (this.nameResolver != null) { 312 remoteAddress = new InetSocketAddress(this.nameResolver.resolve(host), port); 313 } else { 314 remoteAddress = new InetSocketAddress(host, port); 315 } 316 317 sslsock.connect(remoteAddress, connTimeout); 318 319 sslsock.setSoTimeout(soTimeout); 320 try { 321 hostnameVerifier.verify(host, sslsock); 322 // verifyHostName() didn't blowup - good! 323 } catch (IOException iox) { 324 // close the socket before re-throwing the exception 325 try { sslsock.close(); } catch (Exception x) { /*ignore*/ } 326 throw iox; 327 } 328 329 return sslsock; 330 } 331 332 333 /** 334 * Checks whether a socket connection is secure. 335 * This factory creates TLS/SSL socket connections 336 * which, by default, are considered secure. 337 * <br/> 338 * Derived classes may override this method to perform 339 * runtime checks, for example based on the cypher suite. 340 * 341 * @param sock the connected socket 342 * 343 * @return <code>true</code> 344 * 345 * @throws IllegalArgumentException if the argument is invalid 346 */ 347 public boolean isSecure(Socket sock) 348 throws IllegalArgumentException { 349 350 if (sock == null) { 351 throw new IllegalArgumentException("Socket may not be null."); 352 } 353 // This instanceof check is in line with createSocket() above. 354 if (!(sock instanceof SSLSocket)) { 355 throw new IllegalArgumentException 356 ("Socket not created by this factory."); 357 } 358 // This check is performed last since it calls the argument object. 359 if (sock.isClosed()) { 360 throw new IllegalArgumentException("Socket is closed."); 361 } 362 363 return true; 364 365 } // isSecure 366 367 368 // non-javadoc, see interface LayeredSocketFactory 369 public Socket createSocket( 370 final Socket socket, 371 final String host, 372 final int port, 373 final boolean autoClose 374 ) throws IOException, UnknownHostException { 375 SSLSocket sslSocket = (SSLSocket) this.socketfactory.createSocket( 376 socket, 377 host, 378 port, 379 autoClose 380 ); 381 hostnameVerifier.verify(host, sslSocket); 382 // verifyHostName() didn't blowup - good! 383 return sslSocket; 384 } 385 386 public void setHostnameVerifier(X509HostnameVerifier hostnameVerifier) { 387 if ( hostnameVerifier == null ) { 388 throw new IllegalArgumentException("Hostname verifier may not be null"); 389 } 390 this.hostnameVerifier = hostnameVerifier; 391 } 392 393 public X509HostnameVerifier getHostnameVerifier() { 394 return hostnameVerifier; 395 } 396 397 } 398