View Javadoc

1   // ========================================================================
2   // Copyright 2000-2005 Mort Bay Consulting Pty. Ltd.
3   // ------------------------------------------------------------------------
4   // Licensed under the Apache License, Version 2.0 (the "License");
5   // you may not use this file except in compliance with the License.
6   // You may obtain a copy of the License at 
7   // http://www.apache.org/licenses/LICENSE-2.0
8   // Unless required by applicable law or agreed to in writing, software
9   // distributed under the License is distributed on an "AS IS" BASIS,
10  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11  // See the License for the specific language governing permissions and
12  // limitations under the License.
13  // ========================================================================
14  
15  package org.mortbay.jetty.security;
16  
17  import java.io.ByteArrayInputStream;
18  import java.io.File;
19  import java.io.IOException;
20  import java.io.InputStream;
21  import java.net.InetAddress;
22  import java.net.ServerSocket;
23  import java.net.Socket;
24  import java.net.SocketAddress;
25  import java.security.KeyStore;
26  import java.security.SecureRandom;
27  import java.security.Security;
28  import java.security.cert.X509Certificate;
29  import java.util.ArrayList;
30  import java.util.Arrays;
31  import java.util.Iterator;
32  import java.util.List;
33  
34  import javax.net.ssl.KeyManager;
35  import javax.net.ssl.KeyManagerFactory;
36  import javax.net.ssl.SSLContext;
37  import javax.net.ssl.SSLException;
38  import javax.net.ssl.SSLPeerUnverifiedException;
39  import javax.net.ssl.SSLServerSocket;
40  import javax.net.ssl.SSLServerSocketFactory;
41  import javax.net.ssl.SSLSession;
42  import javax.net.ssl.SSLSocket;
43  import javax.net.ssl.TrustManager;
44  import javax.net.ssl.TrustManagerFactory;
45  
46  import org.mortbay.io.EndPoint;
47  import org.mortbay.io.bio.SocketEndPoint;
48  import org.mortbay.jetty.HttpSchemes;
49  import org.mortbay.jetty.Request;
50  import org.mortbay.jetty.bio.SocketConnector;
51  import org.mortbay.log.Log;
52  import org.mortbay.resource.Resource;
53  
54  /* ------------------------------------------------------------ */
55  /**
56   * JSSE Socket Listener.
57   * 
58   * This specialization of HttpListener is an abstract listener that can be used as the basis for a
59   * specific JSSE listener.
60   * 
61   * This is heavily based on the work from Court Demas, which in turn is based on the work from Forge
62   * Research.
63   * 
64   * @author Greg Wilkins (gregw@mortbay.com)
65   * @author Court Demas (court@kiwiconsulting.com)
66   * @author Forge Research Pty Ltd ACN 003 491 576
67   * @author Jan Hlavat�
68   */
69  public class SslSocketConnector extends SocketConnector
70  {
71      /**
72       * The name of the SSLSession attribute that will contain any cached information.
73       */
74      static final String CACHED_INFO_ATTR = CachedInfo.class.getName();
75  
76      /** Default value for the keystore location path. */
77      public static final String DEFAULT_KEYSTORE = System.getProperty("user.home") + File.separator
78              + ".keystore";
79  
80      /** String name of key password property. */
81      public static final String KEYPASSWORD_PROPERTY = "jetty.ssl.keypassword";
82  
83      /** String name of keystore password property. */
84      public static final String PASSWORD_PROPERTY = "jetty.ssl.password";
85  
86      /**
87       * Return the chain of X509 certificates used to negotiate the SSL Session.
88       * <p>
89       * Note: in order to do this we must convert a javax.security.cert.X509Certificate[], as used by
90       * JSSE to a java.security.cert.X509Certificate[],as required by the Servlet specs.
91       * 
92       * @param sslSession the javax.net.ssl.SSLSession to use as the source of the cert chain.
93       * @return the chain of java.security.cert.X509Certificates used to negotiate the SSL
94       *         connection. <br>
95       *         Will be null if the chain is missing or empty.
96       */
97      private static X509Certificate[] getCertChain(SSLSession sslSession)
98      {
99          try
100         {
101             javax.security.cert.X509Certificate javaxCerts[] = sslSession.getPeerCertificateChain();
102             if (javaxCerts == null || javaxCerts.length == 0)
103                 return null;
104 
105             int length = javaxCerts.length;
106             X509Certificate[] javaCerts = new X509Certificate[length];
107 
108             java.security.cert.CertificateFactory cf = java.security.cert.CertificateFactory.getInstance("X.509");
109             for (int i = 0; i < length; i++)
110             {
111                 byte bytes[] = javaxCerts[i].getEncoded();
112                 ByteArrayInputStream stream = new ByteArrayInputStream(bytes);
113                 javaCerts[i] = (X509Certificate) cf.generateCertificate(stream);
114             }
115 
116             return javaCerts;
117         }
118         catch (SSLPeerUnverifiedException pue)
119         {
120             return null;
121         }
122         catch (Exception e)
123         {
124             Log.warn(Log.EXCEPTION, e);
125             return null;
126         }
127     }
128 
129 
130     /** Default value for the cipher Suites. */
131     private String _excludeCipherSuites[] = null;
132     
133     /** Default value for the keystore location path. */
134     private String _keystore=DEFAULT_KEYSTORE ;
135     private String _keystoreType = "JKS"; // type of the key store
136     
137     /** Set to true if we require client certificate authentication. */
138     private boolean _needClientAuth = false;
139     private transient Password _password;
140     private transient Password _keyPassword;
141     private transient Password _trustPassword;
142     private String _protocol= "TLS";
143     private String _provider;
144     private String _secureRandomAlgorithm; // cert algorithm
145     private String _sslKeyManagerFactoryAlgorithm = (Security.getProperty("ssl.KeyManagerFactory.algorithm")==null?"SunX509":Security.getProperty("ssl.KeyManagerFactory.algorithm")); // cert algorithm
146     private String _sslTrustManagerFactoryAlgorithm = (Security.getProperty("ssl.TrustManagerFactory.algorithm")==null?"SunX509":Security.getProperty("ssl.TrustManagerFactory.algorithm")); // cert algorithm
147     
148     private String _truststore;
149     private String _truststoreType = "JKS"; // type of the key store
150 
151     /** Set to true if we would like client certificate authentication. */
152     private boolean _wantClientAuth = false;
153     private int _handshakeTimeout = 0; //0 means use maxIdleTime
154 
155 
156     /* ------------------------------------------------------------ */
157     /**
158      * Constructor.
159      */
160     public SslSocketConnector()
161     {
162         super();
163     }
164 
165 
166     /* ------------------------------------------------------------ */
167     public void accept(int acceptorID)
168         throws IOException, InterruptedException
169     {   
170         try
171         {
172             Socket socket = _serverSocket.accept();
173             configure(socket);
174 
175             Connection connection=new SslConnection(socket);
176             connection.dispatch();
177         }
178         catch(SSLException e)
179         {
180             Log.warn(e);
181             try
182             {
183                 stop();
184             }
185             catch(Exception e2)
186             {
187                 Log.warn(e2);
188                 throw new IllegalStateException(e2.getMessage());
189             }
190         }
191     }
192     
193     /* ------------------------------------------------------------ */
194     protected void configure(Socket socket)
195         throws IOException
196     {   
197         super.configure(socket);
198     }
199 
200     /* ------------------------------------------------------------ */
201     protected SSLServerSocketFactory createFactory() 
202         throws Exception
203     {
204         if (_truststore==null)
205         {
206             _truststore=_keystore;
207             _truststoreType=_keystoreType;
208         }
209 
210         KeyManager[] keyManagers = null;
211         InputStream keystoreInputStream = null;
212         if (_keystore != null)
213         	keystoreInputStream = Resource.newResource(_keystore).getInputStream();
214         KeyStore keyStore = KeyStore.getInstance(_keystoreType);
215         keyStore.load(keystoreInputStream, _password==null?null:_password.toString().toCharArray());
216 
217         KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(_sslKeyManagerFactoryAlgorithm);        
218         keyManagerFactory.init(keyStore,_keyPassword==null?null:_keyPassword.toString().toCharArray());
219         keyManagers = keyManagerFactory.getKeyManagers();
220 
221         TrustManager[] trustManagers = null;
222         InputStream truststoreInputStream = null;
223         if (_truststore != null)
224         	truststoreInputStream = Resource.newResource(_truststore).getInputStream();
225         KeyStore trustStore = KeyStore.getInstance(_truststoreType);
226         trustStore.load(truststoreInputStream,_trustPassword==null?null:_trustPassword.toString().toCharArray());
227         
228         TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(_sslTrustManagerFactoryAlgorithm);
229         trustManagerFactory.init(trustStore);
230         trustManagers = trustManagerFactory.getTrustManagers();
231         
232 
233         SecureRandom secureRandom = _secureRandomAlgorithm==null?null:SecureRandom.getInstance(_secureRandomAlgorithm);
234 
235         SSLContext context = _provider==null?SSLContext.getInstance(_protocol):SSLContext.getInstance(_protocol, _provider);
236 
237         context.init(keyManagers, trustManagers, secureRandom);
238 
239         return context.getServerSocketFactory();
240     }
241 
242     /* ------------------------------------------------------------ */
243     /**
244      * Allow the Listener a chance to customise the request. before the server does its stuff. <br>
245      * This allows the required attributes to be set for SSL requests. <br>
246      * The requirements of the Servlet specs are:
247      * <ul>
248      * <li> an attribute named "javax.servlet.request.cipher_suite" of type String.</li>
249      * <li> an attribute named "javax.servlet.request.key_size" of type Integer.</li>
250      * <li> an attribute named "javax.servlet.request.X509Certificate" of type
251      * java.security.cert.X509Certificate[]. This is an array of objects of type X509Certificate,
252      * the order of this array is defined as being in ascending order of trust. The first
253      * certificate in the chain is the one set by the client, the next is the one used to
254      * authenticate the first, and so on. </li>
255      * </ul>
256      * 
257      * @param endpoint The Socket the request arrived on. 
258      *        This should be a {@link SocketEndPoint} wrapping a {@link SSLSocket}.
259      * @param request HttpRequest to be customised.
260      */
261     public void customize(EndPoint endpoint, Request request)
262         throws IOException
263     {
264         super.customize(endpoint, request);
265         request.setScheme(HttpSchemes.HTTPS);
266         
267         SocketEndPoint socket_end_point = (SocketEndPoint)endpoint;
268         SSLSocket sslSocket = (SSLSocket)socket_end_point.getTransport();
269         
270         try
271         {
272             SSLSession sslSession = sslSocket.getSession();
273             String cipherSuite = sslSession.getCipherSuite();
274             Integer keySize;
275             X509Certificate[] certs;
276 
277             CachedInfo cachedInfo = (CachedInfo) sslSession.getValue(CACHED_INFO_ATTR);
278             if (cachedInfo != null)
279             {
280                 keySize = cachedInfo.getKeySize();
281                 certs = cachedInfo.getCerts();
282             }
283             else
284             {
285                 keySize = new Integer(ServletSSL.deduceKeyLength(cipherSuite));
286                 certs = getCertChain(sslSession);
287                 cachedInfo = new CachedInfo(keySize, certs);
288                 sslSession.putValue(CACHED_INFO_ATTR, cachedInfo);
289             }
290 
291             if (certs != null)
292                 request.setAttribute("javax.servlet.request.X509Certificate", certs);
293             else if (_needClientAuth) // Sanity check
294                 throw new IllegalStateException("no client auth");
295 
296             request.setAttribute("javax.servlet.request.cipher_suite", cipherSuite);
297             request.setAttribute("javax.servlet.request.key_size", keySize);
298         }
299         catch (Exception e)
300         {
301             Log.warn(Log.EXCEPTION, e);
302         }
303     }
304 
305     /* ------------------------------------------------------------ */    
306     public String[] getExcludeCipherSuites() {
307         return _excludeCipherSuites;
308     }
309 
310     /* ------------------------------------------------------------ */
311     public String getKeystore()
312     {
313         return _keystore;
314     }
315 
316     /* ------------------------------------------------------------ */
317     public String getKeystoreType() 
318     {
319         return (_keystoreType);
320     }
321 
322     /* ------------------------------------------------------------ */
323     public boolean getNeedClientAuth()
324     {
325         return _needClientAuth;
326     }
327 
328     /* ------------------------------------------------------------ */
329     public String getProtocol() 
330     {
331         return _protocol;
332     }
333 
334     /* ------------------------------------------------------------ */
335     public String getProvider() {
336 	return _provider;
337     }
338 
339     /* ------------------------------------------------------------ */
340     public String getSecureRandomAlgorithm() 
341     {
342         return (this._secureRandomAlgorithm);
343     }
344 
345     /* ------------------------------------------------------------ */
346     public String getSslKeyManagerFactoryAlgorithm() 
347     {
348         return (this._sslKeyManagerFactoryAlgorithm);
349     }
350 
351     /* ------------------------------------------------------------ */
352     public String getSslTrustManagerFactoryAlgorithm() 
353     {
354         return (this._sslTrustManagerFactoryAlgorithm);
355     }
356 
357     /* ------------------------------------------------------------ */
358     public String getTruststore()
359     {
360         return _truststore;
361     }
362 
363     /* ------------------------------------------------------------ */
364     public String getTruststoreType()
365     {
366         return _truststoreType;
367     }
368 
369     /* ------------------------------------------------------------ */
370     public boolean getWantClientAuth()
371     {
372         return _wantClientAuth;
373     }
374 
375     /* ------------------------------------------------------------ */
376     /**
377      * By default, we're confidential, given we speak SSL. But, if we've been told about an
378      * confidential port, and said port is not our port, then we're not. This allows separation of
379      * listeners providing INTEGRAL versus CONFIDENTIAL constraints, such as one SSL listener
380      * configured to require client certs providing CONFIDENTIAL, whereas another SSL listener not
381      * requiring client certs providing mere INTEGRAL constraints.
382      */
383     public boolean isConfidential(Request request)
384     {
385         final int confidentialPort = getConfidentialPort();
386         return confidentialPort == 0 || confidentialPort == request.getServerPort();
387     }
388     
389     /* ------------------------------------------------------------ */
390     /**
391      * By default, we're integral, given we speak SSL. But, if we've been told about an integral
392      * port, and said port is not our port, then we're not. This allows separation of listeners
393      * providing INTEGRAL versus CONFIDENTIAL constraints, such as one SSL listener configured to
394      * require client certs providing CONFIDENTIAL, whereas another SSL listener not requiring
395      * client certs providing mere INTEGRAL constraints.
396      */
397     public boolean isIntegral(Request request)
398     {
399         final int integralPort = getIntegralPort();
400         return integralPort == 0 || integralPort == request.getServerPort();
401     }
402     
403     /* ------------------------------------------------------------ */
404     /**
405      * @param addr The {@link SocketAddress address} that this server should listen on 
406      * @param backlog See {@link ServerSocket#bind(java.net.SocketAddress, int)}
407      * @return A new {@link ServerSocket socket object} bound to the supplied address with all other
408      * settings as per the current configuration of this connector. 
409      * @see #setWantClientAuth
410      * @see #setNeedClientAuth
411      * @see #setCipherSuites
412      * @exception IOException
413      */
414 
415     /* ------------------------------------------------------------ */
416     protected ServerSocket newServerSocket(String host, int port,int backlog) throws IOException
417     {
418         SSLServerSocketFactory factory = null;
419         SSLServerSocket socket = null;
420 
421         try
422         {
423             factory = createFactory();
424 
425             socket = (SSLServerSocket) (host==null?
426                             factory.createServerSocket(port,backlog):
427                             factory.createServerSocket(port,backlog,InetAddress.getByName(host)));
428 
429             if (_wantClientAuth)
430                 socket.setWantClientAuth(_wantClientAuth);
431             if (_needClientAuth)
432                 socket.setNeedClientAuth(_needClientAuth);
433 
434             if (_excludeCipherSuites != null && _excludeCipherSuites.length >0) 
435             {
436                 List excludedCSList = Arrays.asList(_excludeCipherSuites);
437                 String[] enabledCipherSuites = socket.getEnabledCipherSuites();
438             	List enabledCSList = new ArrayList(Arrays.asList(enabledCipherSuites));
439             	Iterator exIter = excludedCSList.iterator();
440 
441                 while (exIter.hasNext())
442             	{
443             	    String cipherName = (String)exIter.next();
444                     if (enabledCSList.contains(cipherName))
445                     {
446                         enabledCSList.remove(cipherName);
447                     }
448             	}
449                 enabledCipherSuites = (String[])enabledCSList.toArray(new String[enabledCSList.size()]);
450 
451                 socket.setEnabledCipherSuites(enabledCipherSuites);
452             }
453             
454         }
455         catch (IOException e)
456         {
457             throw e;
458         }
459         catch (Exception e)
460         {
461             Log.warn(Log.EXCEPTION, e);
462             throw new IOException("Could not create JsseListener: " + e.toString());
463         }
464         return socket;
465     }
466 
467     /* ------------------------------------------------------------ */
468     /** 
469      * @author Tony Jiang
470      */
471     public void setExcludeCipherSuites(String[] cipherSuites) {
472         this._excludeCipherSuites = cipherSuites;
473     }
474 
475     /* ------------------------------------------------------------ */
476     public void setKeyPassword(String password)
477     {
478         _keyPassword = Password.getPassword(KEYPASSWORD_PROPERTY,password,null);
479     }
480 
481     /* ------------------------------------------------------------ */
482     /**
483      * @param keystore The resource path to the keystore, or null for built in keystores.
484      */
485     public void setKeystore(String keystore)
486     {
487         _keystore = keystore;
488     }
489 
490     /* ------------------------------------------------------------ */
491     public void setKeystoreType(String keystoreType) 
492     {
493         _keystoreType = keystoreType;
494     }
495 
496     /* ------------------------------------------------------------ */
497     /**
498      * Set the value of the needClientAuth property
499      * 
500      * @param needClientAuth true iff we require client certificate authentication.
501      */
502     public void setNeedClientAuth(boolean needClientAuth)
503     {
504         _needClientAuth = needClientAuth;
505     }
506     
507     /* ------------------------------------------------------------ */
508     public void setPassword(String password)
509     {
510         _password = Password.getPassword(PASSWORD_PROPERTY,password,null);
511     }
512     
513     /* ------------------------------------------------------------ */
514     public void setTrustPassword(String password)
515     {
516         _trustPassword = Password.getPassword(PASSWORD_PROPERTY,password,null);
517     }
518 
519     /* ------------------------------------------------------------ */
520     public void setProtocol(String protocol) 
521     {
522         _protocol = protocol;
523     }
524 
525     /* ------------------------------------------------------------ */
526     public void setProvider(String _provider) {
527 	this._provider = _provider;
528     }
529 
530     /* ------------------------------------------------------------ */
531     public void setSecureRandomAlgorithm(String algorithm) 
532     {
533         this._secureRandomAlgorithm = algorithm;
534     }
535 
536     /* ------------------------------------------------------------ */
537     public void setSslKeyManagerFactoryAlgorithm(String algorithm) 
538     {
539         this._sslKeyManagerFactoryAlgorithm = algorithm;
540     }
541     
542     /* ------------------------------------------------------------ */
543     public void setSslTrustManagerFactoryAlgorithm(String algorithm) 
544     {
545         this._sslTrustManagerFactoryAlgorithm = algorithm;
546     }
547 
548 
549     public void setTruststore(String truststore)
550     {
551         _truststore = truststore;
552     }
553     
554 
555     public void setTruststoreType(String truststoreType)
556     {
557         _truststoreType = truststoreType;
558     }
559 
560     /* ------------------------------------------------------------ */
561     /**
562      * Set the value of the _wantClientAuth property. This property is used when
563      * {@link #newServerSocket(SocketAddress, int) opening server sockets}.
564      * 
565      * @param wantClientAuth true iff we want client certificate authentication.
566      * @see SSLServerSocket#setWantClientAuth
567      */
568     public void setWantClientAuth(boolean wantClientAuth)
569     {
570         _wantClientAuth = wantClientAuth;
571     }
572 
573     /**
574      * Set the time in milliseconds for so_timeout during ssl handshaking
575      * @param msec a non-zero value will be used to set so_timeout during
576      * ssl handshakes. A zero value means the maxIdleTime is used instead.
577      */
578     public void setHandshakeTimeout (int msec)
579     {
580         _handshakeTimeout = msec;
581     }
582     
583     
584     public int getHandshakeTimeout ()
585     {
586         return _handshakeTimeout;
587     }
588     /**
589      * Simple bundle of information that is cached in the SSLSession. Stores the effective keySize
590      * and the client certificate chain.
591      */
592     private class CachedInfo
593     {
594         private X509Certificate[] _certs;
595         private Integer _keySize;
596 
597         CachedInfo(Integer keySize, X509Certificate[] certs)
598         {
599             this._keySize = keySize;
600             this._certs = certs;
601         }
602 
603         X509Certificate[] getCerts()
604         {
605             return _certs;
606         }
607 
608         Integer getKeySize()
609         {
610             return _keySize;
611         }
612     }
613     
614     
615     public class SslConnection extends Connection
616     {
617         public SslConnection(Socket socket) throws IOException
618         {
619             super(socket);
620         }
621         
622         public void run()
623         {
624             try
625             {
626                 int handshakeTimeout = getHandshakeTimeout();
627                 int oldTimeout = _socket.getSoTimeout();
628                 if (handshakeTimeout > 0)            
629                     _socket.setSoTimeout(handshakeTimeout);
630 
631                 ((SSLSocket)_socket).startHandshake();
632 
633                 if (handshakeTimeout>0)
634                     _socket.setSoTimeout(oldTimeout);
635 
636                 super.run();
637             }
638             catch (SSLException e)
639             {
640                 Log.warn(e); 
641                 try{close();}
642                 catch(IOException e2){Log.ignore(e2);}
643             }
644             catch (IOException e)
645             {
646                 Log.debug(e);
647                 try{close();}
648                 catch(IOException e2){Log.ignore(e2);}
649             } 
650         }
651     }
652 
653 }