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      * 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