Home | History | Annotate | Download | only in ssl
      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