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