View Javadoc

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