michael@0: /* michael@0: * ==================================================================== michael@0: * Licensed to the Apache Software Foundation (ASF) under one michael@0: * or more contributor license agreements. See the NOTICE file michael@0: * distributed with this work for additional information michael@0: * regarding copyright ownership. The ASF licenses this file michael@0: * to you under the Apache License, Version 2.0 (the michael@0: * "License"); you may not use this file except in compliance michael@0: * with the License. You may obtain a copy of the License at michael@0: * michael@0: * http://www.apache.org/licenses/LICENSE-2.0 michael@0: * michael@0: * Unless required by applicable law or agreed to in writing, michael@0: * software distributed under the License is distributed on an michael@0: * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY michael@0: * KIND, either express or implied. See the License for the michael@0: * specific language governing permissions and limitations michael@0: * under the License. michael@0: * ==================================================================== michael@0: * michael@0: * This software consists of voluntary contributions made by many michael@0: * individuals on behalf of the Apache Software Foundation. For more michael@0: * information on the Apache Software Foundation, please see michael@0: * . michael@0: * michael@0: */ michael@0: michael@0: package ch.boye.httpclientandroidlib.conn.ssl; michael@0: michael@0: import ch.boye.httpclientandroidlib.annotation.ThreadSafe; michael@0: michael@0: import ch.boye.httpclientandroidlib.conn.ConnectTimeoutException; michael@0: import ch.boye.httpclientandroidlib.conn.scheme.HostNameResolver; michael@0: import ch.boye.httpclientandroidlib.conn.scheme.LayeredSchemeSocketFactory; michael@0: import ch.boye.httpclientandroidlib.conn.scheme.LayeredSocketFactory; michael@0: import ch.boye.httpclientandroidlib.params.HttpConnectionParams; michael@0: import ch.boye.httpclientandroidlib.params.HttpParams; michael@0: michael@0: import javax.net.ssl.KeyManager; michael@0: import javax.net.ssl.KeyManagerFactory; michael@0: import javax.net.ssl.SSLContext; michael@0: import javax.net.ssl.SSLSocket; michael@0: import javax.net.ssl.TrustManager; michael@0: import javax.net.ssl.TrustManagerFactory; michael@0: import javax.net.ssl.X509TrustManager; michael@0: michael@0: import java.io.IOException; michael@0: import java.net.InetAddress; michael@0: import java.net.InetSocketAddress; michael@0: import java.net.Socket; michael@0: import java.net.SocketTimeoutException; michael@0: import java.net.UnknownHostException; michael@0: import java.security.KeyManagementException; michael@0: import java.security.KeyStore; michael@0: import java.security.KeyStoreException; michael@0: import java.security.NoSuchAlgorithmException; michael@0: import java.security.SecureRandom; michael@0: import java.security.UnrecoverableKeyException; michael@0: michael@0: /** michael@0: * Layered socket factory for TLS/SSL connections. michael@0: *

michael@0: * SSLSocketFactory can be used to validate the identity of the HTTPS server against a list of michael@0: * trusted certificates and to authenticate to the HTTPS server using a private key. michael@0: *

michael@0: * SSLSocketFactory will enable server authentication when supplied with michael@0: * a {@link KeyStore trust-store} file containing one or several trusted certificates. The client michael@0: * secure socket will reject the connection during the SSL session handshake if the target HTTPS michael@0: * server attempts to authenticate itself with a non-trusted certificate. michael@0: *

michael@0: * Use JDK keytool utility to import a trusted certificate and generate a trust-store file: michael@0: *

michael@0:  *     keytool -import -alias "my server cert" -file server.crt -keystore my.truststore
michael@0:  *    
michael@0: *

michael@0: * In special cases the standard trust verification process can be bypassed by using a custom michael@0: * {@link TrustStrategy}. This interface is primarily intended for allowing self-signed michael@0: * certificates to be accepted as trusted without having to add them to the trust-store file. michael@0: *

michael@0: * The following parameters can be used to customize the behavior of this michael@0: * class: michael@0: *

michael@0: *

michael@0: * SSLSocketFactory will enable client authentication when supplied with michael@0: * a {@link KeyStore key-store} file containing a private key/public certificate michael@0: * pair. The client secure socket will use the private key to authenticate michael@0: * itself to the target HTTPS server during the SSL session handshake if michael@0: * requested to do so by the server. michael@0: * The target HTTPS server will in its turn verify the certificate presented michael@0: * by the client in order to establish client's authenticity michael@0: *

michael@0: * Use the following sequence of actions to generate a key-store file michael@0: *

michael@0: * michael@0: * michael@0: * @since 4.0 michael@0: */ michael@0: @SuppressWarnings("deprecation") michael@0: @ThreadSafe michael@0: public class SSLSocketFactory implements LayeredSchemeSocketFactory, LayeredSocketFactory { michael@0: michael@0: public static final String TLS = "TLS"; michael@0: public static final String SSL = "SSL"; michael@0: public static final String SSLV2 = "SSLv2"; michael@0: michael@0: public static final X509HostnameVerifier ALLOW_ALL_HOSTNAME_VERIFIER michael@0: = new AllowAllHostnameVerifier(); michael@0: michael@0: public static final X509HostnameVerifier BROWSER_COMPATIBLE_HOSTNAME_VERIFIER michael@0: = new BrowserCompatHostnameVerifier(); michael@0: michael@0: public static final X509HostnameVerifier STRICT_HOSTNAME_VERIFIER michael@0: = new StrictHostnameVerifier(); michael@0: michael@0: /** michael@0: * Gets the default factory, which uses the default JVM settings for secure michael@0: * connections. michael@0: * michael@0: * @return the default factory michael@0: */ michael@0: public static SSLSocketFactory getSocketFactory() { michael@0: return new SSLSocketFactory(); michael@0: } michael@0: michael@0: private final javax.net.ssl.SSLSocketFactory socketfactory; michael@0: private final HostNameResolver nameResolver; michael@0: // TODO: make final michael@0: private volatile X509HostnameVerifier hostnameVerifier; michael@0: michael@0: private static SSLContext createSSLContext( michael@0: String algorithm, michael@0: final KeyStore keystore, michael@0: final String keystorePassword, michael@0: final KeyStore truststore, michael@0: final SecureRandom random, michael@0: final TrustStrategy trustStrategy) michael@0: throws NoSuchAlgorithmException, KeyStoreException, UnrecoverableKeyException, KeyManagementException { michael@0: if (algorithm == null) { michael@0: algorithm = TLS; michael@0: } michael@0: KeyManagerFactory kmfactory = KeyManagerFactory.getInstance( michael@0: KeyManagerFactory.getDefaultAlgorithm()); michael@0: kmfactory.init(keystore, keystorePassword != null ? keystorePassword.toCharArray(): null); michael@0: KeyManager[] keymanagers = kmfactory.getKeyManagers(); michael@0: TrustManagerFactory tmfactory = TrustManagerFactory.getInstance( michael@0: TrustManagerFactory.getDefaultAlgorithm()); michael@0: tmfactory.init(truststore); michael@0: TrustManager[] trustmanagers = tmfactory.getTrustManagers(); michael@0: if (trustmanagers != null && trustStrategy != null) { michael@0: for (int i = 0; i < trustmanagers.length; i++) { michael@0: TrustManager tm = trustmanagers[i]; michael@0: if (tm instanceof X509TrustManager) { michael@0: trustmanagers[i] = new TrustManagerDecorator( michael@0: (X509TrustManager) tm, trustStrategy); michael@0: } michael@0: } michael@0: } michael@0: michael@0: SSLContext sslcontext = SSLContext.getInstance(algorithm); michael@0: sslcontext.init(keymanagers, trustmanagers, random); michael@0: return sslcontext; michael@0: } michael@0: michael@0: private static SSLContext createDefaultSSLContext() { michael@0: try { michael@0: return createSSLContext(TLS, null, null, null, null, null); michael@0: } catch (Exception ex) { michael@0: throw new IllegalStateException("Failure initializing default SSL context", ex); michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * @deprecated Use {@link #SSLSocketFactory(String, KeyStore, String, KeyStore, SecureRandom, X509HostnameVerifier)} michael@0: */ michael@0: @Deprecated michael@0: public SSLSocketFactory( michael@0: final String algorithm, michael@0: final KeyStore keystore, michael@0: final String keystorePassword, michael@0: final KeyStore truststore, michael@0: final SecureRandom random, michael@0: final HostNameResolver nameResolver) michael@0: throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException { michael@0: this(createSSLContext( michael@0: algorithm, keystore, keystorePassword, truststore, random, null), michael@0: nameResolver); michael@0: } michael@0: michael@0: /** michael@0: * @since 4.1 michael@0: */ michael@0: public SSLSocketFactory( michael@0: String algorithm, michael@0: final KeyStore keystore, michael@0: final String keystorePassword, michael@0: final KeyStore truststore, michael@0: final SecureRandom random, michael@0: final X509HostnameVerifier hostnameVerifier) michael@0: throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException { michael@0: this(createSSLContext( michael@0: algorithm, keystore, keystorePassword, truststore, random, null), michael@0: hostnameVerifier); michael@0: } michael@0: michael@0: /** michael@0: * @since 4.1 michael@0: */ michael@0: public SSLSocketFactory( michael@0: String algorithm, michael@0: final KeyStore keystore, michael@0: final String keystorePassword, michael@0: final KeyStore truststore, michael@0: final SecureRandom random, michael@0: final TrustStrategy trustStrategy, michael@0: final X509HostnameVerifier hostnameVerifier) michael@0: throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException { michael@0: this(createSSLContext( michael@0: algorithm, keystore, keystorePassword, truststore, random, trustStrategy), michael@0: hostnameVerifier); michael@0: } michael@0: michael@0: public SSLSocketFactory( michael@0: final KeyStore keystore, michael@0: final String keystorePassword, michael@0: final KeyStore truststore) michael@0: throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException { michael@0: this(TLS, keystore, keystorePassword, truststore, null, null, BROWSER_COMPATIBLE_HOSTNAME_VERIFIER); michael@0: } michael@0: michael@0: public SSLSocketFactory( michael@0: final KeyStore keystore, michael@0: final String keystorePassword) michael@0: throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException{ michael@0: this(TLS, keystore, keystorePassword, null, null, null, BROWSER_COMPATIBLE_HOSTNAME_VERIFIER); michael@0: } michael@0: michael@0: public SSLSocketFactory( michael@0: final KeyStore truststore) michael@0: throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException { michael@0: this(TLS, null, null, truststore, null, null, BROWSER_COMPATIBLE_HOSTNAME_VERIFIER); michael@0: } michael@0: michael@0: /** michael@0: * @since 4.1 michael@0: */ michael@0: public SSLSocketFactory( michael@0: final TrustStrategy trustStrategy, michael@0: final X509HostnameVerifier hostnameVerifier) michael@0: throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException { michael@0: this(TLS, null, null, null, null, trustStrategy, hostnameVerifier); michael@0: } michael@0: michael@0: /** michael@0: * @since 4.1 michael@0: */ michael@0: public SSLSocketFactory( michael@0: final TrustStrategy trustStrategy) michael@0: throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException { michael@0: this(TLS, null, null, null, null, trustStrategy, BROWSER_COMPATIBLE_HOSTNAME_VERIFIER); michael@0: } michael@0: michael@0: public SSLSocketFactory(final SSLContext sslContext) { michael@0: this(sslContext, BROWSER_COMPATIBLE_HOSTNAME_VERIFIER); michael@0: } michael@0: michael@0: /** michael@0: * @deprecated Use {@link #SSLSocketFactory(SSLContext)} michael@0: */ michael@0: @Deprecated michael@0: public SSLSocketFactory( michael@0: final SSLContext sslContext, final HostNameResolver nameResolver) { michael@0: super(); michael@0: this.socketfactory = sslContext.getSocketFactory(); michael@0: this.hostnameVerifier = BROWSER_COMPATIBLE_HOSTNAME_VERIFIER; michael@0: this.nameResolver = nameResolver; michael@0: } michael@0: michael@0: /** michael@0: * @since 4.1 michael@0: */ michael@0: public SSLSocketFactory( michael@0: final SSLContext sslContext, final X509HostnameVerifier hostnameVerifier) { michael@0: super(); michael@0: this.socketfactory = sslContext.getSocketFactory(); michael@0: this.hostnameVerifier = hostnameVerifier; michael@0: this.nameResolver = null; michael@0: } michael@0: michael@0: private SSLSocketFactory() { michael@0: this(createDefaultSSLContext()); michael@0: } michael@0: michael@0: /** michael@0: * @param params Optional parameters. Parameters passed to this method will have no effect. michael@0: * This method will create a unconnected instance of {@link Socket} class. michael@0: * @since 4.1 michael@0: */ michael@0: public Socket createSocket(final HttpParams params) throws IOException { michael@0: return this.socketfactory.createSocket(); michael@0: } michael@0: michael@0: @Deprecated michael@0: public Socket createSocket() throws IOException { michael@0: return this.socketfactory.createSocket(); michael@0: } michael@0: michael@0: /** michael@0: * @since 4.1 michael@0: */ michael@0: public Socket connectSocket( michael@0: final Socket socket, michael@0: final InetSocketAddress remoteAddress, michael@0: final InetSocketAddress localAddress, michael@0: final HttpParams params) throws IOException, UnknownHostException, ConnectTimeoutException { michael@0: if (remoteAddress == null) { michael@0: throw new IllegalArgumentException("Remote address may not be null"); michael@0: } michael@0: if (params == null) { michael@0: throw new IllegalArgumentException("HTTP parameters may not be null"); michael@0: } michael@0: Socket sock = socket != null ? socket : new Socket(); michael@0: if (localAddress != null) { michael@0: sock.setReuseAddress(HttpConnectionParams.getSoReuseaddr(params)); michael@0: sock.bind(localAddress); michael@0: } michael@0: michael@0: int connTimeout = HttpConnectionParams.getConnectionTimeout(params); michael@0: int soTimeout = HttpConnectionParams.getSoTimeout(params); michael@0: michael@0: try { michael@0: sock.setSoTimeout(soTimeout); michael@0: sock.connect(remoteAddress, connTimeout); michael@0: } catch (SocketTimeoutException ex) { michael@0: throw new ConnectTimeoutException("Connect to " + remoteAddress + " timed out"); michael@0: } michael@0: michael@0: // HttpInetSocketAddress#toString() returns original hostname value of the remote address michael@0: String hostname = remoteAddress.toString(); michael@0: int port = remoteAddress.getPort(); michael@0: String s = ":" + port; michael@0: if (hostname.endsWith(s)) { michael@0: hostname = hostname.substring(0, hostname.length() - s.length()); michael@0: } michael@0: michael@0: SSLSocket sslsock; michael@0: // Setup SSL layering if necessary michael@0: if (sock instanceof SSLSocket) { michael@0: sslsock = (SSLSocket) sock; michael@0: } else { michael@0: sslsock = (SSLSocket) this.socketfactory.createSocket(sock, hostname, port, true); michael@0: } michael@0: if (this.hostnameVerifier != null) { michael@0: try { michael@0: this.hostnameVerifier.verify(hostname, sslsock); michael@0: // verifyHostName() didn't blowup - good! michael@0: } catch (IOException iox) { michael@0: // close the socket before re-throwing the exception michael@0: try { sslsock.close(); } catch (Exception x) { /*ignore*/ } michael@0: throw iox; michael@0: } michael@0: } michael@0: return sslsock; michael@0: } michael@0: michael@0: michael@0: /** michael@0: * Checks whether a socket connection is secure. michael@0: * This factory creates TLS/SSL socket connections michael@0: * which, by default, are considered secure. michael@0: *
michael@0: * Derived classes may override this method to perform michael@0: * runtime checks, for example based on the cypher suite. michael@0: * michael@0: * @param sock the connected socket michael@0: * michael@0: * @return true michael@0: * michael@0: * @throws IllegalArgumentException if the argument is invalid michael@0: */ michael@0: public boolean isSecure(final Socket sock) throws IllegalArgumentException { michael@0: if (sock == null) { michael@0: throw new IllegalArgumentException("Socket may not be null"); michael@0: } michael@0: // This instanceof check is in line with createSocket() above. michael@0: if (!(sock instanceof SSLSocket)) { michael@0: throw new IllegalArgumentException("Socket not created by this factory"); michael@0: } michael@0: // This check is performed last since it calls the argument object. michael@0: if (sock.isClosed()) { michael@0: throw new IllegalArgumentException("Socket is closed"); michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: /** michael@0: * @since 4.1 michael@0: */ michael@0: public Socket createLayeredSocket( michael@0: final Socket socket, michael@0: final String host, michael@0: final int port, michael@0: final boolean autoClose) throws IOException, UnknownHostException { michael@0: SSLSocket sslSocket = (SSLSocket) this.socketfactory.createSocket( michael@0: socket, michael@0: host, michael@0: port, michael@0: autoClose michael@0: ); michael@0: if (this.hostnameVerifier != null) { michael@0: this.hostnameVerifier.verify(host, sslSocket); michael@0: } michael@0: // verifyHostName() didn't blowup - good! michael@0: return sslSocket; michael@0: } michael@0: michael@0: @Deprecated michael@0: public void setHostnameVerifier(X509HostnameVerifier hostnameVerifier) { michael@0: if ( hostnameVerifier == null ) { michael@0: throw new IllegalArgumentException("Hostname verifier may not be null"); michael@0: } michael@0: this.hostnameVerifier = hostnameVerifier; michael@0: } michael@0: michael@0: public X509HostnameVerifier getHostnameVerifier() { michael@0: return this.hostnameVerifier; michael@0: } michael@0: michael@0: /** michael@0: * @deprecated Use {@link #connectSocket(Socket, InetSocketAddress, InetSocketAddress, HttpParams)} michael@0: */ michael@0: @Deprecated michael@0: public Socket connectSocket( michael@0: final Socket socket, michael@0: final String host, int port, michael@0: final InetAddress localAddress, int localPort, michael@0: final HttpParams params) throws IOException, UnknownHostException, ConnectTimeoutException { michael@0: InetSocketAddress local = null; michael@0: if (localAddress != null || localPort > 0) { michael@0: // we need to bind explicitly michael@0: if (localPort < 0) { michael@0: localPort = 0; // indicates "any" michael@0: } michael@0: local = new InetSocketAddress(localAddress, localPort); michael@0: } michael@0: InetAddress remoteAddress; michael@0: if (this.nameResolver != null) { michael@0: remoteAddress = this.nameResolver.resolve(host); michael@0: } else { michael@0: remoteAddress = InetAddress.getByName(host); michael@0: } michael@0: InetSocketAddress remote = new InetSocketAddress(remoteAddress, port); michael@0: return connectSocket(socket, remote, local, params); michael@0: } michael@0: michael@0: /** michael@0: * @deprecated Use {@link #createLayeredSocket(Socket, String, int, boolean)} michael@0: */ michael@0: @Deprecated michael@0: public Socket createSocket( michael@0: final Socket socket, michael@0: final String host, int port, michael@0: boolean autoClose) throws IOException, UnknownHostException { michael@0: return createLayeredSocket(socket, host, port, autoClose); michael@0: } michael@0: michael@0: }