~ubuntu-branches/ubuntu/lucid/commons-httpclient/lucid

« back to all changes in this revision

Viewing changes to src/contrib/org/apache/commons/httpclient/contrib/ssl/AuthSSLProtocolSocketFactory.java

  • Committer: Bazaar Package Importer
  • Author(s): Barry Hawkins
  • Date: 2005-11-25 13:12:23 UTC
  • Revision ID: james.westby@ubuntu.com-20051125131223-2g7eyo21pqgrohpo
Tags: upstream-2.0.2
ImportĀ upstreamĀ versionĀ 2.0.2

Show diffs side-by-side

added added

removed removed

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