View Javadoc

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