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