View Javadoc

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