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         Socket socket = _serverSocket.accept();
171         configure(socket);
172         
173         Connection connection=new SslConnection(socket);
174         connection.dispatch();
175     }
176     
177     /* ------------------------------------------------------------ */
178     protected void configure(Socket socket)
179         throws IOException
180     {   
181         super.configure(socket);
182     }
183 
184     /* ------------------------------------------------------------ */
185     protected SSLServerSocketFactory createFactory() 
186         throws Exception
187     {
188         if (_truststore==null)
189         {
190             _truststore=_keystore;
191             _truststoreType=_keystoreType;
192         }
193 
194         KeyManager[] keyManagers = null;
195         InputStream keystoreInputStream = null;
196         if (_keystore != null)
197         	keystoreInputStream = Resource.newResource(_keystore).getInputStream();
198         KeyStore keyStore = KeyStore.getInstance(_keystoreType);
199         keyStore.load(keystoreInputStream, _password==null?null:_password.toString().toCharArray());
200 
201         KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(_sslKeyManagerFactoryAlgorithm);        
202         keyManagerFactory.init(keyStore,_keyPassword==null?null:_keyPassword.toString().toCharArray());
203         keyManagers = keyManagerFactory.getKeyManagers();
204 
205         TrustManager[] trustManagers = null;
206         InputStream truststoreInputStream = null;
207         if (_truststore != null)
208         	truststoreInputStream = Resource.newResource(_truststore).getInputStream();
209         KeyStore trustStore = KeyStore.getInstance(_truststoreType);
210         trustStore.load(truststoreInputStream,_trustPassword==null?null:_trustPassword.toString().toCharArray());
211         
212         TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(_sslTrustManagerFactoryAlgorithm);
213         trustManagerFactory.init(trustStore);
214         trustManagers = trustManagerFactory.getTrustManagers();
215         
216 
217         SecureRandom secureRandom = _secureRandomAlgorithm==null?null:SecureRandom.getInstance(_secureRandomAlgorithm);
218 
219         SSLContext context = _provider==null?SSLContext.getInstance(_protocol):SSLContext.getInstance(_protocol, _provider);
220 
221         context.init(keyManagers, trustManagers, secureRandom);
222 
223         return context.getServerSocketFactory();
224     }
225 
226     /* ------------------------------------------------------------ */
227     /**
228      * Allow the Listener a chance to customise the request. before the server does its stuff. <br>
229      * This allows the required attributes to be set for SSL requests. <br>
230      * The requirements of the Servlet specs are:
231      * <ul>
232      * <li> an attribute named "javax.servlet.request.cipher_suite" of type String.</li>
233      * <li> an attribute named "javax.servlet.request.key_size" of type Integer.</li>
234      * <li> an attribute named "javax.servlet.request.X509Certificate" of type
235      * java.security.cert.X509Certificate[]. This is an array of objects of type X509Certificate,
236      * the order of this array is defined as being in ascending order of trust. The first
237      * certificate in the chain is the one set by the client, the next is the one used to
238      * authenticate the first, and so on. </li>
239      * </ul>
240      * 
241      * @param endpoint The Socket the request arrived on. 
242      *        This should be a {@link SocketEndPoint} wrapping a {@link SSLSocket}.
243      * @param request HttpRequest to be customised.
244      */
245     public void customize(EndPoint endpoint, Request request)
246         throws IOException
247     {
248         super.customize(endpoint, request);
249         request.setScheme(HttpSchemes.HTTPS);
250         
251         SocketEndPoint socket_end_point = (SocketEndPoint)endpoint;
252         SSLSocket sslSocket = (SSLSocket)socket_end_point.getTransport();
253         
254         try
255         {
256             SSLSession sslSession = sslSocket.getSession();
257             String cipherSuite = sslSession.getCipherSuite();
258             Integer keySize;
259             X509Certificate[] certs;
260 
261             CachedInfo cachedInfo = (CachedInfo) sslSession.getValue(CACHED_INFO_ATTR);
262             if (cachedInfo != null)
263             {
264                 keySize = cachedInfo.getKeySize();
265                 certs = cachedInfo.getCerts();
266             }
267             else
268             {
269                 keySize = new Integer(ServletSSL.deduceKeyLength(cipherSuite));
270                 certs = getCertChain(sslSession);
271                 cachedInfo = new CachedInfo(keySize, certs);
272                 sslSession.putValue(CACHED_INFO_ATTR, cachedInfo);
273             }
274 
275             if (certs != null)
276                 request.setAttribute("javax.servlet.request.X509Certificate", certs);
277             else if (_needClientAuth) // Sanity check
278                 throw new IllegalStateException("no client auth");
279 
280             request.setAttribute("javax.servlet.request.cipher_suite", cipherSuite);
281             request.setAttribute("javax.servlet.request.key_size", keySize);
282         }
283         catch (Exception e)
284         {
285             Log.warn(Log.EXCEPTION, e);
286         }
287     }
288 
289     /* ------------------------------------------------------------ */    
290     public String[] getExcludeCipherSuites() {
291         return _excludeCipherSuites;
292     }
293 
294     /* ------------------------------------------------------------ */
295     public String getKeystore()
296     {
297         return _keystore;
298     }
299 
300     /* ------------------------------------------------------------ */
301     public String getKeystoreType() 
302     {
303         return (_keystoreType);
304     }
305 
306     /* ------------------------------------------------------------ */
307     public boolean getNeedClientAuth()
308     {
309         return _needClientAuth;
310     }
311 
312     /* ------------------------------------------------------------ */
313     public String getProtocol() 
314     {
315         return _protocol;
316     }
317 
318     /* ------------------------------------------------------------ */
319     public String getProvider() {
320 	return _provider;
321     }
322 
323     /* ------------------------------------------------------------ */
324     public String getSecureRandomAlgorithm() 
325     {
326         return (this._secureRandomAlgorithm);
327     }
328 
329     /* ------------------------------------------------------------ */
330     public String getSslKeyManagerFactoryAlgorithm() 
331     {
332         return (this._sslKeyManagerFactoryAlgorithm);
333     }
334 
335     /* ------------------------------------------------------------ */
336     public String getSslTrustManagerFactoryAlgorithm() 
337     {
338         return (this._sslTrustManagerFactoryAlgorithm);
339     }
340 
341     /* ------------------------------------------------------------ */
342     public String getTruststore()
343     {
344         return _truststore;
345     }
346 
347     /* ------------------------------------------------------------ */
348     public String getTruststoreType()
349     {
350         return _truststoreType;
351     }
352 
353     /* ------------------------------------------------------------ */
354     public boolean getWantClientAuth()
355     {
356         return _wantClientAuth;
357     }
358 
359     /* ------------------------------------------------------------ */
360     /**
361      * By default, we're confidential, given we speak SSL. But, if we've been told about an
362      * confidential port, and said port is not our port, then we're not. This allows separation of
363      * listeners providing INTEGRAL versus CONFIDENTIAL constraints, such as one SSL listener
364      * configured to require client certs providing CONFIDENTIAL, whereas another SSL listener not
365      * requiring client certs providing mere INTEGRAL constraints.
366      */
367     public boolean isConfidential(Request request)
368     {
369         final int confidentialPort = getConfidentialPort();
370         return confidentialPort == 0 || confidentialPort == request.getServerPort();
371     }
372     
373     /* ------------------------------------------------------------ */
374     /**
375      * By default, we're integral, given we speak SSL. But, if we've been told about an integral
376      * port, and said port is not our port, then we're not. This allows separation of listeners
377      * providing INTEGRAL versus CONFIDENTIAL constraints, such as one SSL listener configured to
378      * require client certs providing CONFIDENTIAL, whereas another SSL listener not requiring
379      * client certs providing mere INTEGRAL constraints.
380      */
381     public boolean isIntegral(Request request)
382     {
383         final int integralPort = getIntegralPort();
384         return integralPort == 0 || integralPort == request.getServerPort();
385     }
386     
387     /* ------------------------------------------------------------ */
388     /**
389      * @param addr The {@link SocketAddress address} that this server should listen on 
390      * @param backlog See {@link ServerSocket#bind(java.net.SocketAddress, int)}
391      * @return A new {@link ServerSocket socket object} bound to the supplied address with all other
392      * settings as per the current configuration of this connector. 
393      * @see #setWantClientAuth
394      * @see #setNeedClientAuth
395      * @see #setCipherSuites
396      * @exception IOException
397      */
398 
399     /* ------------------------------------------------------------ */
400     protected ServerSocket newServerSocket(String host, int port,int backlog) throws IOException
401     {
402         SSLServerSocketFactory factory = null;
403         SSLServerSocket socket = null;
404 
405         try
406         {
407             factory = createFactory();
408 
409             socket = (SSLServerSocket) (host==null?
410                             factory.createServerSocket(port,backlog):
411                             factory.createServerSocket(port,backlog,InetAddress.getByName(host)));
412 
413             if (_wantClientAuth)
414                 socket.setWantClientAuth(_wantClientAuth);
415             if (_needClientAuth)
416                 socket.setNeedClientAuth(_needClientAuth);
417 
418             if (_excludeCipherSuites != null && _excludeCipherSuites.length >0) 
419             {
420                 List excludedCSList = Arrays.asList(_excludeCipherSuites);
421                 String[] enabledCipherSuites = socket.getEnabledCipherSuites();
422             	List enabledCSList = new ArrayList(Arrays.asList(enabledCipherSuites));
423             	Iterator exIter = excludedCSList.iterator();
424 
425                 while (exIter.hasNext())
426             	{
427             	    String cipherName = (String)exIter.next();
428                     if (enabledCSList.contains(cipherName))
429                     {
430                         enabledCSList.remove(cipherName);
431                     }
432             	}
433                 enabledCipherSuites = (String[])enabledCSList.toArray(new String[enabledCSList.size()]);
434 
435                 socket.setEnabledCipherSuites(enabledCipherSuites);
436             }
437             
438         }
439         catch (IOException e)
440         {
441             throw e;
442         }
443         catch (Exception e)
444         {
445             Log.warn(Log.EXCEPTION, e);
446             throw new IOException("Could not create JsseListener: " + e.toString());
447         }
448         return socket;
449     }
450 
451     /* ------------------------------------------------------------ */
452     /** 
453      * @author Tony Jiang
454      */
455     public void setExcludeCipherSuites(String[] cipherSuites) {
456         this._excludeCipherSuites = cipherSuites;
457     }
458 
459     /* ------------------------------------------------------------ */
460     public void setKeyPassword(String password)
461     {
462         _keyPassword = Password.getPassword(KEYPASSWORD_PROPERTY,password,null);
463     }
464 
465     /* ------------------------------------------------------------ */
466     /**
467      * @param keystore The resource path to the keystore, or null for built in keystores.
468      */
469     public void setKeystore(String keystore)
470     {
471         _keystore = keystore;
472     }
473 
474     /* ------------------------------------------------------------ */
475     public void setKeystoreType(String keystoreType) 
476     {
477         _keystoreType = keystoreType;
478     }
479 
480     /* ------------------------------------------------------------ */
481     /**
482      * Set the value of the needClientAuth property
483      * 
484      * @param needClientAuth true iff we require client certificate authentication.
485      */
486     public void setNeedClientAuth(boolean needClientAuth)
487     {
488         _needClientAuth = needClientAuth;
489     }
490     
491     /* ------------------------------------------------------------ */
492     public void setPassword(String password)
493     {
494         _password = Password.getPassword(PASSWORD_PROPERTY,password,null);
495     }
496     
497     /* ------------------------------------------------------------ */
498     public void setTrustPassword(String password)
499     {
500         _trustPassword = Password.getPassword(PASSWORD_PROPERTY,password,null);
501     }
502 
503     /* ------------------------------------------------------------ */
504     public void setProtocol(String protocol) 
505     {
506         _protocol = protocol;
507     }
508 
509     /* ------------------------------------------------------------ */
510     public void setProvider(String _provider) {
511 	this._provider = _provider;
512     }
513 
514     /* ------------------------------------------------------------ */
515     public void setSecureRandomAlgorithm(String algorithm) 
516     {
517         this._secureRandomAlgorithm = algorithm;
518     }
519 
520     /* ------------------------------------------------------------ */
521     public void setSslKeyManagerFactoryAlgorithm(String algorithm) 
522     {
523         this._sslKeyManagerFactoryAlgorithm = algorithm;
524     }
525     
526     /* ------------------------------------------------------------ */
527     public void setSslTrustManagerFactoryAlgorithm(String algorithm) 
528     {
529         this._sslTrustManagerFactoryAlgorithm = algorithm;
530     }
531 
532 
533     public void setTruststore(String truststore)
534     {
535         _truststore = truststore;
536     }
537     
538 
539     public void setTruststoreType(String truststoreType)
540     {
541         _truststoreType = truststoreType;
542     }
543 
544     /* ------------------------------------------------------------ */
545     /**
546      * Set the value of the _wantClientAuth property. This property is used when
547      * {@link #newServerSocket(SocketAddress, int) opening server sockets}.
548      * 
549      * @param wantClientAuth true iff we want client certificate authentication.
550      * @see SSLServerSocket#setWantClientAuth
551      */
552     public void setWantClientAuth(boolean wantClientAuth)
553     {
554         _wantClientAuth = wantClientAuth;
555     }
556 
557     /**
558      * Set the time in milliseconds for so_timeout during ssl handshaking
559      * @param msec a non-zero value will be used to set so_timeout during
560      * ssl handshakes. A zero value means the maxIdleTime is used instead.
561      */
562     public void setHandshakeTimeout (int msec)
563     {
564         _handshakeTimeout = msec;
565     }
566     
567     
568     public int getHandshakeTimeout ()
569     {
570         return _handshakeTimeout;
571     }
572     /**
573      * Simple bundle of information that is cached in the SSLSession. Stores the effective keySize
574      * and the client certificate chain.
575      */
576     private class CachedInfo
577     {
578         private X509Certificate[] _certs;
579         private Integer _keySize;
580 
581         CachedInfo(Integer keySize, X509Certificate[] certs)
582         {
583             this._keySize = keySize;
584             this._certs = certs;
585         }
586 
587         X509Certificate[] getCerts()
588         {
589             return _certs;
590         }
591 
592         Integer getKeySize()
593         {
594             return _keySize;
595         }
596     }
597     
598     
599     public class SslConnection extends Connection
600     {
601         public SslConnection(Socket socket) throws IOException
602         {
603             super(socket);
604         }
605         
606         public void run()
607         {
608             try
609             {
610                 int handshakeTimeout = getHandshakeTimeout();
611                 int oldTimeout = _socket.getSoTimeout();
612                 if (handshakeTimeout > 0)            
613                     _socket.setSoTimeout(handshakeTimeout);
614 
615                 ((SSLSocket)_socket).startHandshake();
616 
617                 if (handshakeTimeout>0)
618                     _socket.setSoTimeout(oldTimeout);
619 
620                 super.run();
621             }
622             catch (SSLException e)
623             {
624                 Log.debug(e); 
625                 try{close();}
626                 catch(IOException e2){Log.ignore(e2);}
627             }
628             catch (IOException e)
629             {
630                 Log.debug(e);
631                 try{close();}
632                 catch(IOException e2){Log.ignore(e2);}
633             } 
634         }
635     }
636 
637 }