KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > mortbay > http > SslListener


1 // ========================================================================
2
// $Id: SslListener.java,v 1.6 2006/03/15 14:09:05 gregwilkins Exp $
3
// Copyright 2000-2004 Mort Bay Consulting Pty. Ltd.
4
// ------------------------------------------------------------------------
5
// Licensed under the Apache License, Version 2.0 (the "License");
6
// you may not use this file except in compliance with the License.
7
// You may obtain a copy of the License at
8
// http://www.apache.org/licenses/LICENSE-2.0
9
// Unless required by applicable law or agreed to in writing, software
10
// distributed under the License is distributed on an "AS IS" BASIS,
11
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
// See the License for the specific language governing permissions and
13
// limitations under the License.
14
// ========================================================================
15
//
16

17 package org.mortbay.http;
18
19 import java.io.ByteArrayInputStream JavaDoc;
20 import java.io.File JavaDoc;
21 import java.io.IOException JavaDoc;
22 import java.net.ServerSocket JavaDoc;
23 import java.net.Socket JavaDoc;
24 import java.security.KeyStore JavaDoc;
25 import java.security.cert.X509Certificate JavaDoc;
26
27 import javax.net.ssl.KeyManagerFactory;
28 import javax.net.ssl.SSLContext;
29 import javax.net.ssl.SSLException;
30 import javax.net.ssl.SSLPeerUnverifiedException;
31 import javax.net.ssl.SSLServerSocket;
32 import javax.net.ssl.SSLServerSocketFactory;
33 import javax.net.ssl.SSLSession;
34 import javax.net.ssl.SSLSocket;
35
36 import org.apache.commons.logging.Log;
37 import org.mortbay.log.LogFactory;
38 import org.mortbay.jetty.servlet.ServletSSL;
39 import org.mortbay.util.InetAddrPort;
40 import org.mortbay.util.LogSupport;
41 import org.mortbay.util.Password;
42 import org.mortbay.util.Resource;
43
44 /* ------------------------------------------------------------ */
45 /**
46  * JSSE Socket Listener.
47  *
48  * This is heavily based on the work from Court Demas, which in turn is based on the work from Forge
49  * Research.
50  *
51  * @version $Id: SslListener.java,v 1.6 2006/03/15 14:09:05 gregwilkins Exp $
52  * @author Greg Wilkins (gregw@mortbay.com)
53  * @author Court Demas (court@kiwiconsulting.com)
54  * @author Forge Research Pty Ltd ACN 003 491 576
55  * @author Jan Hlavatý
56  */

57 public class SslListener extends SocketListener
58 {
59     private static Log log = LogFactory.getLog(SslListener.class);
60     
61     /** Default value for the cipher Suites. */
62     private String JavaDoc cipherSuites[] = null;
63
64     /** Default value for the keystore location path. */
65     public static final String JavaDoc DEFAULT_KEYSTORE = System.getProperty("user.home") + File.separator
66             + ".keystore";
67
68     /** String name of keystore password property. */
69     public static final String JavaDoc PASSWORD_PROPERTY = "jetty.ssl.password";
70
71     /** String name of key password property. */
72     public static final String JavaDoc KEYPASSWORD_PROPERTY = "jetty.ssl.keypassword";
73
74     /**
75      * The name of the SSLSession attribute that will contain any cached information.
76      */

77     static final String JavaDoc CACHED_INFO_ATTR = CachedInfo.class.getName();
78
79
80     private String JavaDoc _keystore=DEFAULT_KEYSTORE ;
81     private transient Password _password;
82     private transient Password _keypassword;
83     private boolean _needClientAuth = false; // Set to true if we require client certificate authentication.
84
private boolean _wantClientAuth = false; // Set to true if we would like client certificate authentication.
85
private String JavaDoc _protocol= "TLS";
86     private String JavaDoc _algorithm = "SunX509"; // cert algorithm
87
private String JavaDoc _keystoreType = "JKS"; // type of the key store
88
private String JavaDoc _provider = null;
89
90
91
92     /* ------------------------------------------------------------ */
93     /**
94      * Constructor.
95      */

96     public SslListener()
97     {
98         super();
99         setDefaultScheme(HttpMessage.__SSL_SCHEME);
100     }
101
102     /* ------------------------------------------------------------ */
103     /**
104      * Constructor.
105      *
106      * @param p_address
107      */

108     public SslListener(InetAddrPort p_address)
109     {
110         super(p_address);
111         if (p_address.getPort() == 0)
112         {
113             p_address.setPort(443);
114             setPort(443);
115         }
116         setDefaultScheme(HttpMessage.__SSL_SCHEME);
117     }
118
119     /* ------------------------------------------------------------ */
120     public String JavaDoc[] getCipherSuites() {
121         return cipherSuites;
122     }
123
124     /* ------------------------------------------------------------ */
125     /**
126      * @author Tony Jiang
127      */

128     public void setCipherSuites(String JavaDoc[] cipherSuites) {
129         this.cipherSuites = cipherSuites;
130     }
131
132     /* ------------------------------------------------------------ */
133     public void setPassword(String JavaDoc password)
134     {
135         _password = Password.getPassword(PASSWORD_PROPERTY,password,null);
136     }
137
138     /* ------------------------------------------------------------ */
139     public void setKeyPassword(String JavaDoc password)
140     {
141         _keypassword = Password.getPassword(KEYPASSWORD_PROPERTY,password,null);
142     }
143
144     /* ------------------------------------------------------------ */
145     public String JavaDoc getAlgorithm()
146     {
147         return (this._algorithm);
148     }
149
150     /* ------------------------------------------------------------ */
151     public void setAlgorithm(String JavaDoc algorithm)
152     {
153         this._algorithm = algorithm;
154     }
155
156     /* ------------------------------------------------------------ */
157     public String JavaDoc getProtocol()
158     {
159         return _protocol;
160     }
161
162     /* ------------------------------------------------------------ */
163     public void setProtocol(String JavaDoc protocol)
164     {
165         _protocol = protocol;
166     }
167
168     /* ------------------------------------------------------------ */
169     public void setKeystore(String JavaDoc keystore)
170     {
171         _keystore = keystore;
172     }
173     
174     /* ------------------------------------------------------------ */
175     public String JavaDoc getKeystore()
176     {
177         return _keystore;
178     }
179     
180     /* ------------------------------------------------------------ */
181     public String JavaDoc getKeystoreType()
182     {
183         return (_keystoreType);
184     }
185
186     /* ------------------------------------------------------------ */
187     public void setKeystoreType(String JavaDoc keystoreType)
188     {
189         _keystoreType = keystoreType;
190     }
191     
192     /* ------------------------------------------------------------ */
193     /**
194      * Set the value of the needClientAuth property
195      *
196      * @param needClientAuth true iff we require client certificate authentication.
197      */

198     public void setNeedClientAuth(boolean needClientAuth)
199     {
200         _needClientAuth = needClientAuth;
201     }
202
203     /* ------------------------------------------------------------ */
204     public boolean getNeedClientAuth()
205     {
206         return _needClientAuth;
207     }
208     
209     /* ------------------------------------------------------------ */
210     /**
211      * Set the value of the needClientAuth property
212      *
213      * @param wantClientAuth true iff we would like client certificate authentication.
214      */

215     public void setWantClientAuth(boolean wantClientAuth)
216     {
217         _wantClientAuth = wantClientAuth;
218     }
219
220     /* ------------------------------------------------------------ */
221     public boolean getWantClientAuth()
222     {
223         return _wantClientAuth;
224     }
225
226     /* ------------------------------------------------------------ */
227     /**
228      * By default, we're integral, given we speak SSL. But, if we've been told about an integral
229      * port, and said port is not our port, then we're not. This allows separation of listeners
230      * providing INTEGRAL versus CONFIDENTIAL constraints, such as one SSL listener configured to
231      * require client certs providing CONFIDENTIAL, whereas another SSL listener not requiring
232      * client certs providing mere INTEGRAL constraints.
233      */

234     public boolean isIntegral(HttpConnection connection)
235     {
236         final int integralPort = getIntegralPort();
237         return integralPort == 0 || integralPort == getPort();
238     }
239
240     /* ------------------------------------------------------------ */
241     /**
242      * By default, we're confidential, given we speak SSL. But, if we've been told about an
243      * confidential port, and said port is not our port, then we're not. This allows separation of
244      * listeners providing INTEGRAL versus CONFIDENTIAL constraints, such as one SSL listener
245      * configured to require client certs providing CONFIDENTIAL, whereas another SSL listener not
246      * requiring client certs providing mere INTEGRAL constraints.
247      */

248     public boolean isConfidential(HttpConnection connection)
249     {
250         final int confidentialPort = getConfidentialPort();
251         return confidentialPort == 0 || confidentialPort == getPort();
252     }
253
254     /* ------------------------------------------------------------ */
255     protected SSLServerSocketFactory createFactory()
256         throws Exception JavaDoc
257     {
258         SSLContext context;
259         if (_provider == null) {
260             context = SSLContext.getInstance(_protocol);
261         } else {
262             context = SSLContext.getInstance(_protocol, _provider);
263         }
264
265         KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(_algorithm);
266         KeyStore JavaDoc keyStore = KeyStore.getInstance(_keystoreType);
267         keyStore.load(Resource.newResource(_keystore).getInputStream(), _password.toString().toCharArray());
268         keyManagerFactory.init(keyStore,_keypassword.toString().toCharArray());
269         
270         context.init(keyManagerFactory.getKeyManagers(), null, new java.security.SecureRandom JavaDoc());
271
272         return context.getServerSocketFactory();
273     }
274
275     /* ------------------------------------------------------------ */
276     /**
277      * @param p_address
278      * @param p_acceptQueueSize
279      * @return @exception IOException
280      */

281     protected ServerSocket JavaDoc newServerSocket(InetAddrPort p_address, int p_acceptQueueSize)
282             throws IOException JavaDoc
283     {
284         SSLServerSocketFactory factory = null;
285         SSLServerSocket socket = null;
286
287         try
288         {
289             factory = createFactory();
290
291             if (p_address == null)
292             {
293                 socket = (SSLServerSocket) factory.createServerSocket(0, p_acceptQueueSize);
294             }
295             else
296             {
297                 socket = (SSLServerSocket) factory.createServerSocket(p_address.getPort(),
298                         p_acceptQueueSize, p_address.getInetAddress());
299             }
300
301             if (_needClientAuth)
302                 socket.setNeedClientAuth(true);
303             else if (_wantClientAuth)
304                 socket.setWantClientAuth(true);
305             
306             if(cipherSuites != null && cipherSuites.length >0) {
307                 socket.setEnabledCipherSuites(cipherSuites);
308                 for ( int i=0; i<cipherSuites.length; i++ ) {
309                     log.debug("SslListener enabled ciphersuite: " + cipherSuites[i]);
310                 }
311             }
312         }
313         catch (IOException JavaDoc e)
314         {
315             throw e;
316         }
317         catch (Exception JavaDoc e)
318         {
319             log.warn(LogSupport.EXCEPTION, e);
320             throw new IOException JavaDoc("Could not create JsseListener: " + e.toString());
321         }
322         return socket;
323     }
324
325     /* ------------------------------------------------------------ */
326     /**
327      * @param p_serverSocket
328      * @return @exception IOException
329      */

330     protected Socket JavaDoc accept(ServerSocket JavaDoc p_serverSocket) throws IOException JavaDoc
331     {
332         try
333         {
334             SSLSocket s = (SSLSocket) p_serverSocket.accept();
335             if (getMaxIdleTimeMs() > 0) s.setSoTimeout(getMaxIdleTimeMs());
336             s.startHandshake(); // block until SSL handshaking is done
337
return s;
338         }
339         catch (SSLException e)
340         {
341             log.warn(LogSupport.EXCEPTION, e);
342             throw new IOException JavaDoc(e.getMessage());
343         }
344     }
345
346     /* ------------------------------------------------------------ */
347     /**
348      * Allow the Listener a chance to customise the request. before the server does its stuff. <br>
349      * This allows the required attributes to be set for SSL requests. <br>
350      * The requirements of the Servlet specs are:
351      * <ul>
352      * <li>an attribute named "javax.servlet.request.cipher_suite" of type String.</li>
353      * <li>an attribute named "javax.servlet.request.key_size" of type Integer.</li>
354      * <li>an attribute named "javax.servlet.request.X509Certificate" of type
355      * java.security.cert.X509Certificate[]. This is an array of objects of type X509Certificate,
356      * the order of this array is defined as being in ascending order of trust. The first
357      * certificate in the chain is the one set by the client, the next is the one used to
358      * authenticate the first, and so on.</li>
359      * </ul>
360      *
361      * @param socket The Socket the request arrived on. This should be a javax.net.ssl.SSLSocket.
362      * @param request HttpRequest to be customised.
363      */

364     protected void customizeRequest(Socket JavaDoc socket, HttpRequest request)
365     {
366         super.customizeRequest(socket, request);
367
368         if (!(socket instanceof javax.net.ssl.SSLSocket)) return; // I'm tempted to let it throw an
369
// exception...
370

371         try
372         {
373             SSLSocket sslSocket = (SSLSocket) socket;
374             SSLSession sslSession = sslSocket.getSession();
375             String JavaDoc cipherSuite = sslSession.getCipherSuite();
376             Integer JavaDoc keySize;
377             X509Certificate JavaDoc[] certs;
378
379             CachedInfo cachedInfo = (CachedInfo) sslSession.getValue(CACHED_INFO_ATTR);
380             if (cachedInfo != null)
381             {
382                 keySize = cachedInfo.getKeySize();
383                 certs = cachedInfo.getCerts();
384             }
385             else
386             {
387                 keySize = new Integer JavaDoc(ServletSSL.deduceKeyLength(cipherSuite));
388                 certs = getCertChain(sslSession);
389                 cachedInfo = new CachedInfo(keySize, certs);
390                 sslSession.putValue(CACHED_INFO_ATTR, cachedInfo);
391             }
392
393             if (certs != null)
394                 request.setAttribute("javax.servlet.request.X509Certificate", certs);
395             else if (_needClientAuth) // Sanity check
396
throw new HttpException(HttpResponse.__403_Forbidden);
397
398             request.setAttribute("javax.servlet.request.cipher_suite", cipherSuite);
399             request.setAttribute("javax.servlet.request.key_size", keySize);
400         }
401         catch (Exception JavaDoc e)
402         {
403             log.warn(LogSupport.EXCEPTION, e);
404         }
405     }
406
407     /**
408      * Return the chain of X509 certificates used to negotiate the SSL Session.
409      * <p>
410      * Note: in order to do this we must convert a javax.security.cert.X509Certificate[], as used by
411      * JSSE to a java.security.cert.X509Certificate[],as required by the Servlet specs.
412      *
413      * @param sslSession the javax.net.ssl.SSLSession to use as the source of the cert chain.
414      * @return the chain of java.security.cert.X509Certificates used to negotiate the SSL
415      * connection. <br>
416      * Will be null if the chain is missing or empty.
417      */

418     private static X509Certificate JavaDoc[] getCertChain(SSLSession sslSession)
419     {
420         try
421         {
422             javax.security.cert.X509Certificate javaxCerts[] = sslSession.getPeerCertificateChain();
423             if (javaxCerts == null || javaxCerts.length == 0) return null;
424
425             int length = javaxCerts.length;
426             X509Certificate JavaDoc[] javaCerts = new X509Certificate JavaDoc[length];
427
428             java.security.cert.CertificateFactory JavaDoc cf = java.security.cert.CertificateFactory
429                     .getInstance("X.509");
430             for (int i = 0; i < length; i++)
431             {
432                 byte bytes[] = javaxCerts[i].getEncoded();
433                 ByteArrayInputStream JavaDoc stream = new ByteArrayInputStream JavaDoc(bytes);
434                 javaCerts[i] = (X509Certificate JavaDoc) cf.generateCertificate(stream);
435             }
436
437             return javaCerts;
438         }
439         catch (SSLPeerUnverifiedException pue)
440         {
441             return null;
442         }
443         catch (Exception JavaDoc e)
444         {
445             log.warn(LogSupport.EXCEPTION, e);
446             return null;
447         }
448     }
449
450     /**
451      * Simple bundle of information that is cached in the SSLSession. Stores the effective keySize
452      * and the client certificate chain.
453      */

454     private class CachedInfo
455     {
456         private Integer JavaDoc _keySize;
457         private X509Certificate JavaDoc[] _certs;
458
459         CachedInfo(Integer JavaDoc keySize, X509Certificate JavaDoc[] certs)
460         {
461             this._keySize = keySize;
462             this._certs = certs;
463         }
464
465         Integer JavaDoc getKeySize()
466         {
467             return _keySize;
468         }
469
470         X509Certificate JavaDoc[] getCerts()
471         {
472             return _certs;
473         }
474     }
475
476     public String JavaDoc getProvider() {
477         return _provider;
478     }
479
480     public void setProvider(String JavaDoc _provider) {
481         this._provider = _provider;
482     }
483 }
484
Popular Tags