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