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