|
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 */ |
|
27 |
|
28 package ch.boye.httpclientandroidlib.conn.ssl; |
|
29 |
|
30 import ch.boye.httpclientandroidlib.annotation.ThreadSafe; |
|
31 |
|
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; |
|
38 |
|
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; |
|
46 |
|
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; |
|
59 |
|
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 { |
|
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 * 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 } |
|
167 |
|
168 private final javax.net.ssl.SSLSocketFactory socketfactory; |
|
169 private final HostNameResolver nameResolver; |
|
170 // TODO: make final |
|
171 private volatile X509HostnameVerifier hostnameVerifier; |
|
172 |
|
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 } |
|
201 |
|
202 SSLContext sslcontext = SSLContext.getInstance(algorithm); |
|
203 sslcontext.init(keymanagers, trustmanagers, random); |
|
204 return sslcontext; |
|
205 } |
|
206 |
|
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 } |
|
214 |
|
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 } |
|
231 |
|
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 } |
|
247 |
|
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 } |
|
264 |
|
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 } |
|
272 |
|
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 } |
|
279 |
|
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 } |
|
285 |
|
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 } |
|
295 |
|
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 } |
|
304 |
|
305 public SSLSocketFactory(final SSLContext sslContext) { |
|
306 this(sslContext, BROWSER_COMPATIBLE_HOSTNAME_VERIFIER); |
|
307 } |
|
308 |
|
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 } |
|
320 |
|
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 } |
|
331 |
|
332 private SSLSocketFactory() { |
|
333 this(createDefaultSSLContext()); |
|
334 } |
|
335 |
|
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 } |
|
344 |
|
345 @Deprecated |
|
346 public Socket createSocket() throws IOException { |
|
347 return this.socketfactory.createSocket(); |
|
348 } |
|
349 |
|
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 } |
|
369 |
|
370 int connTimeout = HttpConnectionParams.getConnectionTimeout(params); |
|
371 int soTimeout = HttpConnectionParams.getSoTimeout(params); |
|
372 |
|
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 } |
|
379 |
|
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 } |
|
387 |
|
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 } |
|
407 |
|
408 |
|
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 } |
|
437 |
|
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 } |
|
458 |
|
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 } |
|
466 |
|
467 public X509HostnameVerifier getHostnameVerifier() { |
|
468 return this.hostnameVerifier; |
|
469 } |
|
470 |
|
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 } |
|
497 |
|
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 } |
|
508 |
|
509 } |