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