/[Apache-SVN]/httpcomponents/oac.hc3x/trunk/src/contrib/org/apache/commons/httpclient/contrib/ssl/AuthSSLProtocolSocketFactory.java
ViewVC logotype

Contents of /httpcomponents/oac.hc3x/trunk/src/contrib/org/apache/commons/httpclient/contrib/ssl/AuthSSLProtocolSocketFactory.java

Parent Directory Parent Directory | Revision Log Revision Log


Revision 608014 - (show annotations)
Wed Jan 2 05:48:53 2008 UTC (22 months, 3 weeks ago) by rolandw
File size: 17605 byte(s)
replaced CVS keyword Header with SVN keyword HeadURL
1 /*
2 * $HeadURL$
3 * $Revision$
4 * $Date$
5 *
6 * ====================================================================
7 *
8 * Licensed to the Apache Software Foundation (ASF) under one or more
9 * contributor license agreements. See the NOTICE file distributed with
10 * this work for additional information regarding copyright ownership.
11 * The ASF licenses this file to You under the Apache License, Version 2.0
12 * (the "License"); you may not use this file except in compliance with
13 * 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, software
18 * distributed under the License is distributed on an "AS IS" BASIS,
19 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20 * See the License for the specific language governing permissions and
21 * limitations under the License.
22 * ====================================================================
23 *
24 * This software consists of voluntary contributions made by many
25 * individuals on behalf of the Apache Software Foundation. For more
26 * information on the Apache Software Foundation, please see
27 * <http://www.apache.org/>.
28 *
29 */
30
31 package org.apache.commons.httpclient.contrib.ssl;
32
33 import java.io.IOException;
34 import java.io.InputStream;
35 import java.net.InetAddress;
36 import java.net.InetSocketAddress;
37 import java.net.Socket;
38 import java.net.SocketAddress;
39 import java.net.URL;
40 import java.net.UnknownHostException;
41 import java.security.GeneralSecurityException;
42 import java.security.KeyStore;
43 import java.security.KeyStoreException;
44 import java.security.NoSuchAlgorithmException;
45 import java.security.UnrecoverableKeyException;
46 import java.security.cert.Certificate;
47 import java.security.cert.CertificateException;
48 import java.security.cert.X509Certificate;
49 import java.util.Enumeration;
50
51 import org.apache.commons.httpclient.ConnectTimeoutException;
52 import org.apache.commons.httpclient.params.HttpConnectionParams;
53 import org.apache.commons.httpclient.protocol.SecureProtocolSocketFactory;
54 import org.apache.commons.logging.Log;
55 import org.apache.commons.logging.LogFactory;
56
57 import javax.net.SocketFactory;
58 import javax.net.ssl.KeyManager;
59 import javax.net.ssl.KeyManagerFactory;
60 import javax.net.ssl.SSLContext;
61 import javax.net.ssl.TrustManager;
62 import javax.net.ssl.TrustManagerFactory;
63 import javax.net.ssl.X509TrustManager;
64
65 /**
66 * <p>
67 * AuthSSLProtocolSocketFactory can be used to validate the identity of the HTTPS
68 * server against a list of trusted certificates and to authenticate to the HTTPS
69 * server using a private key.
70 * </p>
71 *
72 * <p>
73 * AuthSSLProtocolSocketFactory will enable server authentication when supplied with
74 * a {@link KeyStore truststore} file containg one or several trusted certificates.
75 * The client secure socket will reject the connection during the SSL session handshake
76 * if the target HTTPS server attempts to authenticate itself with a non-trusted
77 * certificate.
78 * </p>
79 *
80 * <p>
81 * Use JDK keytool utility to import a trusted certificate and generate a truststore file:
82 * <pre>
83 * keytool -import -alias "my server cert" -file server.crt -keystore my.truststore
84 * </pre>
85 * </p>
86 *
87 * <p>
88 * AuthSSLProtocolSocketFactory will enable client authentication when supplied with
89 * a {@link KeyStore keystore} file containg a private key/public certificate pair.
90 * The client secure socket will use the private key to authenticate itself to the target
91 * HTTPS server during the SSL session handshake if requested to do so by the server.
92 * The target HTTPS server will in its turn verify the certificate presented by the client
93 * in order to establish client's authenticity
94 * </p>
95 *
96 * <p>
97 * Use the following sequence of actions to generate a keystore file
98 * </p>
99 * <ul>
100 * <li>
101 * <p>
102 * Use JDK keytool utility to generate a new key
103 * <pre>keytool -genkey -v -alias "my client key" -validity 365 -keystore my.keystore</pre>
104 * For simplicity use the same password for the key as that of the keystore
105 * </p>
106 * </li>
107 * <li>
108 * <p>
109 * Issue a certificate signing request (CSR)
110 * <pre>keytool -certreq -alias "my client key" -file mycertreq.csr -keystore my.keystore</pre>
111 * </p>
112 * </li>
113 * <li>
114 * <p>
115 * Send the certificate request to the trusted Certificate Authority for signature.
116 * One may choose to act as her own CA and sign the certificate request using a PKI
117 * tool, such as OpenSSL.
118 * </p>
119 * </li>
120 * <li>
121 * <p>
122 * Import the trusted CA root certificate
123 * <pre>keytool -import -alias "my trusted ca" -file caroot.crt -keystore my.keystore</pre>
124 * </p>
125 * </li>
126 * <li>
127 * <p>
128 * Import the PKCS#7 file containg the complete certificate chain
129 * <pre>keytool -import -alias "my client key" -file mycert.p7 -keystore my.keystore</pre>
130 * </p>
131 * </li>
132 * <li>
133 * <p>
134 * Verify the content the resultant keystore file
135 * <pre>keytool -list -v -keystore my.keystore</pre>
136 * </p>
137 * </li>
138 * </ul>
139 * <p>
140 * Example of using custom protocol socket factory for a specific host:
141 * <pre>
142 * Protocol authhttps = new Protocol("https",
143 * new AuthSSLProtocolSocketFactory(
144 * new URL("file:my.keystore"), "mypassword",
145 * new URL("file:my.truststore"), "mypassword"), 443);
146 *
147 * HttpClient client = new HttpClient();
148 * client.getHostConfiguration().setHost("localhost", 443, authhttps);
149 * // use relative url only
150 * GetMethod httpget = new GetMethod("/");
151 * client.executeMethod(httpget);
152 * </pre>
153 * </p>
154 * <p>
155 * Example of using custom protocol socket factory per default instead of the standard one:
156 * <pre>
157 * Protocol authhttps = new Protocol("https",
158 * new AuthSSLProtocolSocketFactory(
159 * new URL("file:my.keystore"), "mypassword",
160 * new URL("file:my.truststore"), "mypassword"), 443);
161 * Protocol.registerProtocol("https", authhttps);
162 *
163 * HttpClient client = new HttpClient();
164 * GetMethod httpget = new GetMethod("https://localhost/");
165 * client.executeMethod(httpget);
166 * </pre>
167 * </p>
168 * @author <a href="mailto:oleg -at- ural.ru">Oleg Kalnichevski</a>
169 *
170 * <p>
171 * DISCLAIMER: HttpClient developers DO NOT actively support this component.
172 * The component is provided as a reference material, which may be inappropriate
173 * for use without additional customization.
174 * </p>
175 */
176
177 public class AuthSSLProtocolSocketFactory implements SecureProtocolSocketFactory {
178
179 /** Log object for this class. */
180 private static final Log LOG = LogFactory.getLog(AuthSSLProtocolSocketFactory.class);
181
182 private URL keystoreUrl = null;
183 private String keystorePassword = null;
184 private URL truststoreUrl = null;
185 private String truststorePassword = null;
186 private SSLContext sslcontext = null;
187
188 /**
189 * Constructor for AuthSSLProtocolSocketFactory. Either a keystore or truststore file
190 * must be given. Otherwise SSL context initialization error will result.
191 *
192 * @param keystoreUrl URL of the keystore file. May be <tt>null</tt> if HTTPS client
193 * authentication is not to be used.
194 * @param keystorePassword Password to unlock the keystore. IMPORTANT: this implementation
195 * assumes that the same password is used to protect the key and the keystore itself.
196 * @param truststoreUrl URL of the truststore file. May be <tt>null</tt> if HTTPS server
197 * authentication is not to be used.
198 * @param truststorePassword Password to unlock the truststore.
199 */
200 public AuthSSLProtocolSocketFactory(
201 final URL keystoreUrl, final String keystorePassword,
202 final URL truststoreUrl, final String truststorePassword)
203 {
204 super();
205 this.keystoreUrl = keystoreUrl;
206 this.keystorePassword = keystorePassword;
207 this.truststoreUrl = truststoreUrl;
208 this.truststorePassword = truststorePassword;
209 }
210
211 private static KeyStore createKeyStore(final URL url, final String password)
212 throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException
213 {
214 if (url == null) {
215 throw new IllegalArgumentException("Keystore url may not be null");
216 }
217 LOG.debug("Initializing key store");
218 KeyStore keystore = KeyStore.getInstance("jks");
219 InputStream is = null;
220 try {
221 is = url.openStream();
222 keystore.load(is, password != null ? password.toCharArray(): null);
223 } finally {
224 if (is != null) is.close();
225 }
226 return keystore;
227 }
228
229 private static KeyManager[] createKeyManagers(final KeyStore keystore, final String password)
230 throws KeyStoreException, NoSuchAlgorithmException, UnrecoverableKeyException
231 {
232 if (keystore == null) {
233 throw new IllegalArgumentException("Keystore may not be null");
234 }
235 LOG.debug("Initializing key manager");
236 KeyManagerFactory kmfactory = KeyManagerFactory.getInstance(
237 KeyManagerFactory.getDefaultAlgorithm());
238 kmfactory.init(keystore, password != null ? password.toCharArray(): null);
239 return kmfactory.getKeyManagers();
240 }
241
242 private static TrustManager[] createTrustManagers(final KeyStore keystore)
243 throws KeyStoreException, NoSuchAlgorithmException
244 {
245 if (keystore == null) {
246 throw new IllegalArgumentException("Keystore may not be null");
247 }
248 LOG.debug("Initializing trust manager");
249 TrustManagerFactory tmfactory = TrustManagerFactory.getInstance(
250 TrustManagerFactory.getDefaultAlgorithm());
251 tmfactory.init(keystore);
252 TrustManager[] trustmanagers = tmfactory.getTrustManagers();
253 for (int i = 0; i < trustmanagers.length; i++) {
254 if (trustmanagers[i] instanceof X509TrustManager) {
255 trustmanagers[i] = new AuthSSLX509TrustManager(
256 (X509TrustManager)trustmanagers[i]);
257 }
258 }
259 return trustmanagers;
260 }
261
262 private SSLContext createSSLContext() {
263 try {
264 KeyManager[] keymanagers = null;
265 TrustManager[] trustmanagers = null;
266 if (this.keystoreUrl != null) {
267 KeyStore keystore = createKeyStore(this.keystoreUrl, this.keystorePassword);
268 if (LOG.isDebugEnabled()) {
269 Enumeration aliases = keystore.aliases();
270 while (aliases.hasMoreElements()) {
271 String alias = (String)aliases.nextElement();
272 Certificate[] certs = keystore.getCertificateChain(alias);
273 if (certs != null) {
274 LOG.debug("Certificate chain '" + alias + "':");
275 for (int c = 0; c < certs.length; c++) {
276 if (certs[c] instanceof X509Certificate) {
277 X509Certificate cert = (X509Certificate)certs[c];
278 LOG.debug(" Certificate " + (c + 1) + ":");
279 LOG.debug(" Subject DN: " + cert.getSubjectDN());
280 LOG.debug(" Signature Algorithm: " + cert.getSigAlgName());
281 LOG.debug(" Valid from: " + cert.getNotBefore() );
282 LOG.debug(" Valid until: " + cert.getNotAfter());
283 LOG.debug(" Issuer: " + cert.getIssuerDN());
284 }
285 }
286 }
287 }
288 }
289 keymanagers = createKeyManagers(keystore, this.keystorePassword);
290 }
291 if (this.truststoreUrl != null) {
292 KeyStore keystore = createKeyStore(this.truststoreUrl, this.truststorePassword);
293 if (LOG.isDebugEnabled()) {
294 Enumeration aliases = keystore.aliases();
295 while (aliases.hasMoreElements()) {
296 String alias = (String)aliases.nextElement();
297 LOG.debug("Trusted certificate '" + alias + "':");
298 Certificate trustedcert = keystore.getCertificate(alias);
299 if (trustedcert != null && trustedcert instanceof X509Certificate) {
300 X509Certificate cert = (X509Certificate)trustedcert;
301 LOG.debug(" Subject DN: " + cert.getSubjectDN());
302 LOG.debug(" Signature Algorithm: " + cert.getSigAlgName());
303 LOG.debug(" Valid from: " + cert.getNotBefore() );
304 LOG.debug(" Valid until: " + cert.getNotAfter());
305 LOG.debug(" Issuer: " + cert.getIssuerDN());
306 }
307 }
308 }
309 trustmanagers = createTrustManagers(keystore);
310 }
311 SSLContext sslcontext = SSLContext.getInstance("SSL");
312 sslcontext.init(keymanagers, trustmanagers, null);
313 return sslcontext;
314 } catch (NoSuchAlgorithmException e) {
315 LOG.error(e.getMessage(), e);
316 throw new AuthSSLInitializationError("Unsupported algorithm exception: " + e.getMessage());
317 } catch (KeyStoreException e) {
318 LOG.error(e.getMessage(), e);
319 throw new AuthSSLInitializationError("Keystore exception: " + e.getMessage());
320 } catch (GeneralSecurityException e) {
321 LOG.error(e.getMessage(), e);
322 throw new AuthSSLInitializationError("Key management exception: " + e.getMessage());
323 } catch (IOException e) {
324 LOG.error(e.getMessage(), e);
325 throw new AuthSSLInitializationError("I/O error reading keystore/truststore file: " + e.getMessage());
326 }
327 }
328
329 private SSLContext getSSLContext() {
330 if (this.sslcontext == null) {
331 this.sslcontext = createSSLContext();
332 }
333 return this.sslcontext;
334 }
335
336 /**
337 * Attempts to get a new socket connection to the given host within the given time limit.
338 * <p>
339 * To circumvent the limitations of older JREs that do not support connect timeout a
340 * controller thread is executed. The controller thread attempts to create a new socket
341 * within the given limit of time. If socket constructor does not return until the
342 * timeout expires, the controller terminates and throws an {@link ConnectTimeoutException}
343 * </p>
344 *
345 * @param host the host name/IP
346 * @param port the port on the host
347 * @param clientHost the local host name/IP to bind the socket to
348 * @param clientPort the port on the local machine
349 * @param params {@link HttpConnectionParams Http connection parameters}
350 *
351 * @return Socket a new socket
352 *
353 * @throws IOException if an I/O error occurs while creating the socket
354 * @throws UnknownHostException if the IP address of the host cannot be
355 * determined
356 */
357 public Socket createSocket(
358 final String host,
359 final int port,
360 final InetAddress localAddress,
361 final int localPort,
362 final HttpConnectionParams params
363 ) throws IOException, UnknownHostException, ConnectTimeoutException {
364 if (params == null) {
365 throw new IllegalArgumentException("Parameters may not be null");
366 }
367 int timeout = params.getConnectionTimeout();
368 SocketFactory socketfactory = getSSLContext().getSocketFactory();
369 if (timeout == 0) {
370 return socketfactory.createSocket(host, port, localAddress, localPort);
371 } else {
372 Socket socket = socketfactory.createSocket();
373 SocketAddress localaddr = new InetSocketAddress(localAddress, localPort);
374 SocketAddress remoteaddr = new InetSocketAddress(host, port);
375 socket.bind(localaddr);
376 socket.connect(remoteaddr, timeout);
377 return socket;
378 }
379 }
380
381 /**
382 * @see SecureProtocolSocketFactory#createSocket(java.lang.String,int,java.net.InetAddress,int)
383 */
384 public Socket createSocket(
385 String host,
386 int port,
387 InetAddress clientHost,
388 int clientPort)
389 throws IOException, UnknownHostException
390 {
391 return getSSLContext().getSocketFactory().createSocket(
392 host,
393 port,
394 clientHost,
395 clientPort
396 );
397 }
398
399 /**
400 * @see SecureProtocolSocketFactory#createSocket(java.lang.String,int)
401 */
402 public Socket createSocket(String host, int port)
403 throws IOException, UnknownHostException
404 {
405 return getSSLContext().getSocketFactory().createSocket(
406 host,
407 port
408 );
409 }
410
411 /**
412 * @see SecureProtocolSocketFactory#createSocket(java.net.Socket,java.lang.String,int,boolean)
413 */
414 public Socket createSocket(
415 Socket socket,
416 String host,
417 int port,
418 boolean autoClose)
419 throws IOException, UnknownHostException
420 {
421 return getSSLContext().getSocketFactory().createSocket(
422 socket,
423 host,
424 port,
425 autoClose
426 );
427 }
428 }

Properties

Name Value
svn:eol-style native
svn:keywords Date Author Id Revision HeadURL

apache@apache.org
ViewVC Help
Powered by ViewVC 1.1.2