Wed, 31 Dec 2014 07:22:50 +0100
Correct previous dual key logic pending first delivery installment.
1 /*
2 * ====================================================================
3 * Licensed to the Apache Software Foundation (ASF) under one
4 * or more contributor license agreements. See the NOTICE file
5 * distributed with this work for additional information
6 * regarding copyright ownership. The ASF licenses this file
7 * to you under the Apache License, Version 2.0 (the
8 * "License"); you may not use this file except in compliance
9 * with the License. You may obtain a copy of the License at
10 *
11 * http://www.apache.org/licenses/LICENSE-2.0
12 *
13 * Unless required by applicable law or agreed to in writing,
14 * software distributed under the License is distributed on an
15 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16 * KIND, either express or implied. See the License for the
17 * specific language governing permissions and limitations
18 * under the License.
19 * ====================================================================
20 *
21 * This software consists of voluntary contributions made by many
22 * individuals on behalf of the Apache Software Foundation. For more
23 * information on the Apache Software Foundation, please see
24 * <http://www.apache.org/>.
25 *
26 */
28 package ch.boye.httpclientandroidlib.conn.ssl;
30 import ch.boye.httpclientandroidlib.annotation.ThreadSafe;
32 import ch.boye.httpclientandroidlib.conn.ConnectTimeoutException;
33 import ch.boye.httpclientandroidlib.conn.scheme.HostNameResolver;
34 import ch.boye.httpclientandroidlib.conn.scheme.LayeredSchemeSocketFactory;
35 import ch.boye.httpclientandroidlib.conn.scheme.LayeredSocketFactory;
36 import ch.boye.httpclientandroidlib.params.HttpConnectionParams;
37 import ch.boye.httpclientandroidlib.params.HttpParams;
39 import javax.net.ssl.KeyManager;
40 import javax.net.ssl.KeyManagerFactory;
41 import javax.net.ssl.SSLContext;
42 import javax.net.ssl.SSLSocket;
43 import javax.net.ssl.TrustManager;
44 import javax.net.ssl.TrustManagerFactory;
45 import javax.net.ssl.X509TrustManager;
47 import java.io.IOException;
48 import java.net.InetAddress;
49 import java.net.InetSocketAddress;
50 import java.net.Socket;
51 import java.net.SocketTimeoutException;
52 import java.net.UnknownHostException;
53 import java.security.KeyManagementException;
54 import java.security.KeyStore;
55 import java.security.KeyStoreException;
56 import java.security.NoSuchAlgorithmException;
57 import java.security.SecureRandom;
58 import java.security.UnrecoverableKeyException;
60 /**
61 * Layered socket factory for TLS/SSL connections.
62 * <p>
63 * SSLSocketFactory can be used to validate the identity of the HTTPS server against a list of
64 * trusted certificates and to authenticate to the HTTPS server using a private key.
65 * <p>
66 * SSLSocketFactory will enable server authentication when supplied with
67 * a {@link KeyStore trust-store} file containing one or several trusted certificates. The client
68 * secure socket will reject the connection during the SSL session handshake if the target HTTPS
69 * server attempts to authenticate itself with a non-trusted certificate.
70 * <p>
71 * Use JDK keytool utility to import a trusted certificate and generate a trust-store file:
72 * <pre>
73 * keytool -import -alias "my server cert" -file server.crt -keystore my.truststore
74 * </pre>
75 * <p>
76 * In special cases the standard trust verification process can be bypassed by using a custom
77 * {@link TrustStrategy}. This interface is primarily intended for allowing self-signed
78 * certificates to be accepted as trusted without having to add them to the trust-store file.
79 * <p>
80 * The following parameters can be used to customize the behavior of this
81 * class:
82 * <ul>
83 * <li>{@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#CONNECTION_TIMEOUT}</li>
84 * <li>{@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#SO_TIMEOUT}</li>
85 * <li>{@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#SO_REUSEADDR}</li>
86 * </ul>
87 * <p>
88 * SSLSocketFactory will enable client authentication when supplied with
89 * a {@link KeyStore key-store} file containing a private key/public certificate
90 * pair. The client secure socket will use the private key to authenticate
91 * itself to the target HTTPS server during the SSL session handshake if
92 * requested to do so by the server.
93 * The target HTTPS server will in its turn verify the certificate presented
94 * by the client in order to establish client's authenticity
95 * <p>
96 * Use the following sequence of actions to generate a key-store file
97 * </p>
98 * <ul>
99 * <li>
100 * <p>
101 * Use JDK keytool utility to generate a new key
102 * <pre>keytool -genkey -v -alias "my client key" -validity 365 -keystore my.keystore</pre>
103 * For simplicity use the same password for the key as that of the key-store
104 * </p>
105 * </li>
106 * <li>
107 * <p>
108 * Issue a certificate signing request (CSR)
109 * <pre>keytool -certreq -alias "my client key" -file mycertreq.csr -keystore my.keystore</pre>
110 * </p>
111 * </li>
112 * <li>
113 * <p>
114 * Send the certificate request to the trusted Certificate Authority for signature.
115 * One may choose to act as her own CA and sign the certificate request using a PKI
116 * tool, such as OpenSSL.
117 * </p>
118 * </li>
119 * <li>
120 * <p>
121 * Import the trusted CA root certificate
122 * <pre>keytool -import -alias "my trusted ca" -file caroot.crt -keystore my.keystore</pre>
123 * </p>
124 * </li>
125 * <li>
126 * <p>
127 * Import the PKCS#7 file containg the complete certificate chain
128 * <pre>keytool -import -alias "my client key" -file mycert.p7 -keystore my.keystore</pre>
129 * </p>
130 * </li>
131 * <li>
132 * <p>
133 * Verify the content the resultant keystore file
134 * <pre>keytool -list -v -keystore my.keystore</pre>
135 * </p>
136 * </li>
137 * </ul>
138 *
139 * @since 4.0
140 */
141 @SuppressWarnings("deprecation")
142 @ThreadSafe
143 public class SSLSocketFactory implements LayeredSchemeSocketFactory, LayeredSocketFactory {
145 public static final String TLS = "TLS";
146 public static final String SSL = "SSL";
147 public static final String SSLV2 = "SSLv2";
149 public static final X509HostnameVerifier ALLOW_ALL_HOSTNAME_VERIFIER
150 = new AllowAllHostnameVerifier();
152 public static final X509HostnameVerifier BROWSER_COMPATIBLE_HOSTNAME_VERIFIER
153 = new BrowserCompatHostnameVerifier();
155 public static final X509HostnameVerifier STRICT_HOSTNAME_VERIFIER
156 = new StrictHostnameVerifier();
158 /**
159 * Gets the default factory, which uses the default JVM settings for secure
160 * connections.
161 *
162 * @return the default factory
163 */
164 public static SSLSocketFactory getSocketFactory() {
165 return new SSLSocketFactory();
166 }
168 private final javax.net.ssl.SSLSocketFactory socketfactory;
169 private final HostNameResolver nameResolver;
170 // TODO: make final
171 private volatile X509HostnameVerifier hostnameVerifier;
173 private static SSLContext createSSLContext(
174 String algorithm,
175 final KeyStore keystore,
176 final String keystorePassword,
177 final KeyStore truststore,
178 final SecureRandom random,
179 final TrustStrategy trustStrategy)
180 throws NoSuchAlgorithmException, KeyStoreException, UnrecoverableKeyException, KeyManagementException {
181 if (algorithm == null) {
182 algorithm = TLS;
183 }
184 KeyManagerFactory kmfactory = KeyManagerFactory.getInstance(
185 KeyManagerFactory.getDefaultAlgorithm());
186 kmfactory.init(keystore, keystorePassword != null ? keystorePassword.toCharArray(): null);
187 KeyManager[] keymanagers = kmfactory.getKeyManagers();
188 TrustManagerFactory tmfactory = TrustManagerFactory.getInstance(
189 TrustManagerFactory.getDefaultAlgorithm());
190 tmfactory.init(truststore);
191 TrustManager[] trustmanagers = tmfactory.getTrustManagers();
192 if (trustmanagers != null && trustStrategy != null) {
193 for (int i = 0; i < trustmanagers.length; i++) {
194 TrustManager tm = trustmanagers[i];
195 if (tm instanceof X509TrustManager) {
196 trustmanagers[i] = new TrustManagerDecorator(
197 (X509TrustManager) tm, trustStrategy);
198 }
199 }
200 }
202 SSLContext sslcontext = SSLContext.getInstance(algorithm);
203 sslcontext.init(keymanagers, trustmanagers, random);
204 return sslcontext;
205 }
207 private static SSLContext createDefaultSSLContext() {
208 try {
209 return createSSLContext(TLS, null, null, null, null, null);
210 } catch (Exception ex) {
211 throw new IllegalStateException("Failure initializing default SSL context", ex);
212 }
213 }
215 /**
216 * @deprecated Use {@link #SSLSocketFactory(String, KeyStore, String, KeyStore, SecureRandom, X509HostnameVerifier)}
217 */
218 @Deprecated
219 public SSLSocketFactory(
220 final String algorithm,
221 final KeyStore keystore,
222 final String keystorePassword,
223 final KeyStore truststore,
224 final SecureRandom random,
225 final HostNameResolver nameResolver)
226 throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException {
227 this(createSSLContext(
228 algorithm, keystore, keystorePassword, truststore, random, null),
229 nameResolver);
230 }
232 /**
233 * @since 4.1
234 */
235 public SSLSocketFactory(
236 String algorithm,
237 final KeyStore keystore,
238 final String keystorePassword,
239 final KeyStore truststore,
240 final SecureRandom random,
241 final X509HostnameVerifier hostnameVerifier)
242 throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException {
243 this(createSSLContext(
244 algorithm, keystore, keystorePassword, truststore, random, null),
245 hostnameVerifier);
246 }
248 /**
249 * @since 4.1
250 */
251 public SSLSocketFactory(
252 String algorithm,
253 final KeyStore keystore,
254 final String keystorePassword,
255 final KeyStore truststore,
256 final SecureRandom random,
257 final TrustStrategy trustStrategy,
258 final X509HostnameVerifier hostnameVerifier)
259 throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException {
260 this(createSSLContext(
261 algorithm, keystore, keystorePassword, truststore, random, trustStrategy),
262 hostnameVerifier);
263 }
265 public SSLSocketFactory(
266 final KeyStore keystore,
267 final String keystorePassword,
268 final KeyStore truststore)
269 throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException {
270 this(TLS, keystore, keystorePassword, truststore, null, null, BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);
271 }
273 public SSLSocketFactory(
274 final KeyStore keystore,
275 final String keystorePassword)
276 throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException{
277 this(TLS, keystore, keystorePassword, null, null, null, BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);
278 }
280 public SSLSocketFactory(
281 final KeyStore truststore)
282 throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException {
283 this(TLS, null, null, truststore, null, null, BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);
284 }
286 /**
287 * @since 4.1
288 */
289 public SSLSocketFactory(
290 final TrustStrategy trustStrategy,
291 final X509HostnameVerifier hostnameVerifier)
292 throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException {
293 this(TLS, null, null, null, null, trustStrategy, hostnameVerifier);
294 }
296 /**
297 * @since 4.1
298 */
299 public SSLSocketFactory(
300 final TrustStrategy trustStrategy)
301 throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException {
302 this(TLS, null, null, null, null, trustStrategy, BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);
303 }
305 public SSLSocketFactory(final SSLContext sslContext) {
306 this(sslContext, BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);
307 }
309 /**
310 * @deprecated Use {@link #SSLSocketFactory(SSLContext)}
311 */
312 @Deprecated
313 public SSLSocketFactory(
314 final SSLContext sslContext, final HostNameResolver nameResolver) {
315 super();
316 this.socketfactory = sslContext.getSocketFactory();
317 this.hostnameVerifier = BROWSER_COMPATIBLE_HOSTNAME_VERIFIER;
318 this.nameResolver = nameResolver;
319 }
321 /**
322 * @since 4.1
323 */
324 public SSLSocketFactory(
325 final SSLContext sslContext, final X509HostnameVerifier hostnameVerifier) {
326 super();
327 this.socketfactory = sslContext.getSocketFactory();
328 this.hostnameVerifier = hostnameVerifier;
329 this.nameResolver = null;
330 }
332 private SSLSocketFactory() {
333 this(createDefaultSSLContext());
334 }
336 /**
337 * @param params Optional parameters. Parameters passed to this method will have no effect.
338 * This method will create a unconnected instance of {@link Socket} class.
339 * @since 4.1
340 */
341 public Socket createSocket(final HttpParams params) throws IOException {
342 return this.socketfactory.createSocket();
343 }
345 @Deprecated
346 public Socket createSocket() throws IOException {
347 return this.socketfactory.createSocket();
348 }
350 /**
351 * @since 4.1
352 */
353 public Socket connectSocket(
354 final Socket socket,
355 final InetSocketAddress remoteAddress,
356 final InetSocketAddress localAddress,
357 final HttpParams params) throws IOException, UnknownHostException, ConnectTimeoutException {
358 if (remoteAddress == null) {
359 throw new IllegalArgumentException("Remote address may not be null");
360 }
361 if (params == null) {
362 throw new IllegalArgumentException("HTTP parameters may not be null");
363 }
364 Socket sock = socket != null ? socket : new Socket();
365 if (localAddress != null) {
366 sock.setReuseAddress(HttpConnectionParams.getSoReuseaddr(params));
367 sock.bind(localAddress);
368 }
370 int connTimeout = HttpConnectionParams.getConnectionTimeout(params);
371 int soTimeout = HttpConnectionParams.getSoTimeout(params);
373 try {
374 sock.setSoTimeout(soTimeout);
375 sock.connect(remoteAddress, connTimeout);
376 } catch (SocketTimeoutException ex) {
377 throw new ConnectTimeoutException("Connect to " + remoteAddress + " timed out");
378 }
380 // HttpInetSocketAddress#toString() returns original hostname value of the remote address
381 String hostname = remoteAddress.toString();
382 int port = remoteAddress.getPort();
383 String s = ":" + port;
384 if (hostname.endsWith(s)) {
385 hostname = hostname.substring(0, hostname.length() - s.length());
386 }
388 SSLSocket sslsock;
389 // Setup SSL layering if necessary
390 if (sock instanceof SSLSocket) {
391 sslsock = (SSLSocket) sock;
392 } else {
393 sslsock = (SSLSocket) this.socketfactory.createSocket(sock, hostname, port, true);
394 }
395 if (this.hostnameVerifier != null) {
396 try {
397 this.hostnameVerifier.verify(hostname, sslsock);
398 // verifyHostName() didn't blowup - good!
399 } catch (IOException iox) {
400 // close the socket before re-throwing the exception
401 try { sslsock.close(); } catch (Exception x) { /*ignore*/ }
402 throw iox;
403 }
404 }
405 return sslsock;
406 }
409 /**
410 * Checks whether a socket connection is secure.
411 * This factory creates TLS/SSL socket connections
412 * which, by default, are considered secure.
413 * <br/>
414 * Derived classes may override this method to perform
415 * runtime checks, for example based on the cypher suite.
416 *
417 * @param sock the connected socket
418 *
419 * @return <code>true</code>
420 *
421 * @throws IllegalArgumentException if the argument is invalid
422 */
423 public boolean isSecure(final Socket sock) throws IllegalArgumentException {
424 if (sock == null) {
425 throw new IllegalArgumentException("Socket may not be null");
426 }
427 // This instanceof check is in line with createSocket() above.
428 if (!(sock instanceof SSLSocket)) {
429 throw new IllegalArgumentException("Socket not created by this factory");
430 }
431 // This check is performed last since it calls the argument object.
432 if (sock.isClosed()) {
433 throw new IllegalArgumentException("Socket is closed");
434 }
435 return true;
436 }
438 /**
439 * @since 4.1
440 */
441 public Socket createLayeredSocket(
442 final Socket socket,
443 final String host,
444 final int port,
445 final boolean autoClose) throws IOException, UnknownHostException {
446 SSLSocket sslSocket = (SSLSocket) this.socketfactory.createSocket(
447 socket,
448 host,
449 port,
450 autoClose
451 );
452 if (this.hostnameVerifier != null) {
453 this.hostnameVerifier.verify(host, sslSocket);
454 }
455 // verifyHostName() didn't blowup - good!
456 return sslSocket;
457 }
459 @Deprecated
460 public void setHostnameVerifier(X509HostnameVerifier hostnameVerifier) {
461 if ( hostnameVerifier == null ) {
462 throw new IllegalArgumentException("Hostname verifier may not be null");
463 }
464 this.hostnameVerifier = hostnameVerifier;
465 }
467 public X509HostnameVerifier getHostnameVerifier() {
468 return this.hostnameVerifier;
469 }
471 /**
472 * @deprecated Use {@link #connectSocket(Socket, InetSocketAddress, InetSocketAddress, HttpParams)}
473 */
474 @Deprecated
475 public Socket connectSocket(
476 final Socket socket,
477 final String host, int port,
478 final InetAddress localAddress, int localPort,
479 final HttpParams params) throws IOException, UnknownHostException, ConnectTimeoutException {
480 InetSocketAddress local = null;
481 if (localAddress != null || localPort > 0) {
482 // we need to bind explicitly
483 if (localPort < 0) {
484 localPort = 0; // indicates "any"
485 }
486 local = new InetSocketAddress(localAddress, localPort);
487 }
488 InetAddress remoteAddress;
489 if (this.nameResolver != null) {
490 remoteAddress = this.nameResolver.resolve(host);
491 } else {
492 remoteAddress = InetAddress.getByName(host);
493 }
494 InetSocketAddress remote = new InetSocketAddress(remoteAddress, port);
495 return connectSocket(socket, remote, local, params);
496 }
498 /**
499 * @deprecated Use {@link #createLayeredSocket(Socket, String, int, boolean)}
500 */
501 @Deprecated
502 public Socket createSocket(
503 final Socket socket,
504 final String host, int port,
505 boolean autoClose) throws IOException, UnknownHostException {
506 return createLayeredSocket(socket, host, port, autoClose);
507 }
509 }