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 }