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