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