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
//========================================================================
15
package org.mortbay.jetty.security;
17
import java.io.ByteArrayInputStream;
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;
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;
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;
60
/* ------------------------------------------------------------ */
62
* SslSelectChannelConnector.
64
* @author Nik Gonzalez <ngonzalez@exist.com>
65
* @author Greg Wilkins <gregw@mortbay.com>
67
public class SslSelectChannelConnector extends SelectChannelConnector
70
* The name of the SSLSession attribute that will contain any cached
73
static final String CACHED_INFO_ATTR=CachedInfo.class.getName();
75
/** Default value for the keystore location path. */
76
public static final String DEFAULT_KEYSTORE=System.getProperty("user.home")+File.separator+".keystore";
78
/** String name of key password property. */
79
public static final String KEYPASSWORD_PROPERTY="jetty.ssl.keypassword";
81
/** String name of keystore password property. */
82
public static final String PASSWORD_PROPERTY="jetty.ssl.password";
84
/** Default value for the cipher Suites. */
85
private String _excludeCipherSuites[]=null;
87
/** Default value for the keystore location path. */
88
private String _keystore=DEFAULT_KEYSTORE;
89
private String _keystoreType="JKS"; // type of the key store
91
/** Set to true if we require client certificate authentication. */
92
private boolean _needClientAuth=false;
93
private boolean _wantClientAuth=false;
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
106
private String _sslTrustManagerFactoryAlgorithm=(Security.getProperty("ssl.TrustManagerFactory.algorithm")==null?"SunX509":Security
107
.getProperty("ssl.TrustManagerFactory.algorithm")); // cert
110
private String _truststore;
111
private String _truststoreType="JKS"; // type of the key store
112
private SSLContext _context;
114
private int _packetBufferSize;
115
private int _applicationBufferSize;
116
private ConcurrentLinkedQueue<Buffer> _packetBuffers = new ConcurrentLinkedQueue<Buffer>();
117
private ConcurrentLinkedQueue<Buffer> _applicationBuffers = new ConcurrentLinkedQueue<Buffer>();
119
/* ------------------------------------------------------------ */
121
* @see org.mortbay.jetty.AbstractBuffers#getBuffer(int)
123
public Buffer getBuffer(int size)
126
if (size==_applicationBufferSize)
128
buffer = _applicationBuffers.poll();
130
buffer=new IndirectNIOBuffer(size);
132
else if (size==_packetBufferSize)
134
buffer = _packetBuffers.poll();
136
buffer=getUseDirectBuffers()
137
?(NIOBuffer)new DirectNIOBuffer(size)
138
:(NIOBuffer)new IndirectNIOBuffer(size);
141
buffer=super.getBuffer(size);
147
/* ------------------------------------------------------------ */
149
* @see org.mortbay.jetty.AbstractBuffers#returnBuffer(org.mortbay.io.Buffer)
151
public void returnBuffer(Buffer buffer)
154
int size=buffer.capacity();
155
ByteBuffer bbuf = ((NIOBuffer)buffer).getByteBuffer();
159
if (size==_applicationBufferSize)
160
_applicationBuffers.add(buffer);
161
else if (size==_packetBufferSize)
162
_packetBuffers.add(buffer);
164
super.returnBuffer(buffer);
170
* Return the chain of X509 certificates used to negotiate the SSL Session.
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.
177
* the javax.net.ssl.SSLSession to use as the source of the
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.
183
private static X509Certificate[] getCertChain(SSLSession sslSession)
187
javax.security.cert.X509Certificate javaxCerts[]=sslSession.getPeerCertificateChain();
188
if (javaxCerts==null||javaxCerts.length==0)
191
int length=javaxCerts.length;
192
X509Certificate[] javaCerts=new X509Certificate[length];
194
java.security.cert.CertificateFactory cf=java.security.cert.CertificateFactory.getInstance("X.509");
195
for (int i=0; i<length; i++)
197
byte bytes[]=javaxCerts[i].getEncoded();
198
ByteArrayInputStream stream=new ByteArrayInputStream(bytes);
199
javaCerts[i]=(X509Certificate)cf.generateCertificate(stream);
204
catch (SSLPeerUnverifiedException e)
211
Log.warn(Log.EXCEPTION,e);
216
/* ------------------------------------------------------------ */
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:
223
* <li> an attribute named "javax.servlet.request.cipher_suite" of type
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.
235
* The Socket the request arrived on. This should be a
236
* {@link SocketEndPoint} wrapping a {@link SSLSocket}.
238
* HttpRequest to be customised.
240
public void customize(EndPoint endpoint, Request request) throws IOException
242
super.customize(endpoint,request);
243
request.setScheme(HttpSchemes.HTTPS);
245
SslHttpChannelEndPoint sslHttpChannelEndpoint=(SslHttpChannelEndPoint)endpoint;
246
SSLEngine sslEngine=sslHttpChannelEndpoint.getSSLEngine();
250
SSLSession sslSession=sslEngine.getSession();
251
String cipherSuite=sslSession.getCipherSuite();
253
X509Certificate[] certs;
255
CachedInfo cachedInfo=(CachedInfo)sslSession.getValue(CACHED_INFO_ATTR);
256
if (cachedInfo!=null)
258
keySize=cachedInfo.getKeySize();
259
certs=cachedInfo.getCerts();
263
keySize=new Integer(ServletSSL.deduceKeyLength(cipherSuite));
264
certs=getCertChain(sslSession);
265
cachedInfo=new CachedInfo(keySize,certs);
266
sslSession.putValue(CACHED_INFO_ATTR,cachedInfo);
270
request.setAttribute("javax.servlet.request.X509Certificate",certs);
272
request.setAttribute("javax.servlet.request.cipher_suite",cipherSuite);
273
request.setAttribute("javax.servlet.request.key_size",keySize);
277
Log.warn(Log.EXCEPTION,e);
281
/* ------------------------------------------------------------ */
282
public SslSelectChannelConnector()
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
287
// to be safe. TODO investigate
293
* @deprecated As of Java Servlet API 2.0, with no replacement.
296
public String[] getCipherSuites()
298
return getExcludeCipherSuites();
301
public String[] getExcludeCipherSuites()
303
return _excludeCipherSuites;
308
* @deprecated As of Java Servlet API 2.0, with no replacement.
312
public void setCipherSuites(String[] cipherSuites)
314
setExcludeCipherSuites(cipherSuites);
317
public void setExcludeCipherSuites(String[] cipherSuites)
319
this._excludeCipherSuites=cipherSuites;
322
/* ------------------------------------------------------------ */
323
public void setPassword(String password)
325
_password=Password.getPassword(PASSWORD_PROPERTY,password,null);
328
/* ------------------------------------------------------------ */
329
public void setTrustPassword(String password)
331
_trustPassword=Password.getPassword(PASSWORD_PROPERTY,password,null);
334
/* ------------------------------------------------------------ */
335
public void setKeyPassword(String password)
337
_keyPassword=Password.getPassword(KEYPASSWORD_PROPERTY,password,null);
340
/* ------------------------------------------------------------ */
341
public String getAlgorithm()
343
return (this._algorithm);
346
/* ------------------------------------------------------------ */
347
public void setAlgorithm(String algorithm)
349
this._algorithm=algorithm;
352
/* ------------------------------------------------------------ */
353
public String getProtocol()
358
/* ------------------------------------------------------------ */
359
public void setProtocol(String protocol)
364
/* ------------------------------------------------------------ */
365
public void setKeystore(String keystore)
370
/* ------------------------------------------------------------ */
371
public String getKeystore()
376
/* ------------------------------------------------------------ */
377
public String getKeystoreType()
379
return (_keystoreType);
382
/* ------------------------------------------------------------ */
383
public boolean getNeedClientAuth()
385
return _needClientAuth;
388
/* ------------------------------------------------------------ */
389
public boolean getWantClientAuth()
391
return _wantClientAuth;
394
/* ------------------------------------------------------------ */
396
* Set the value of the needClientAuth property
398
* @param needClientAuth
399
* true iff we require client certificate authentication.
401
public void setNeedClientAuth(boolean needClientAuth)
403
_needClientAuth=needClientAuth;
406
public void setWantClientAuth(boolean wantClientAuth)
408
_wantClientAuth=wantClientAuth;
411
/* ------------------------------------------------------------ */
412
public void setKeystoreType(String keystoreType)
414
_keystoreType=keystoreType;
417
/* ------------------------------------------------------------ */
418
public String getProvider()
423
public String getSecureRandomAlgorithm()
425
return (this._secureRandomAlgorithm);
428
/* ------------------------------------------------------------ */
429
public String getSslKeyManagerFactoryAlgorithm()
431
return (this._sslKeyManagerFactoryAlgorithm);
434
/* ------------------------------------------------------------ */
435
public String getSslTrustManagerFactoryAlgorithm()
437
return (this._sslTrustManagerFactoryAlgorithm);
440
/* ------------------------------------------------------------ */
441
public String getTruststore()
446
/* ------------------------------------------------------------ */
447
public String getTruststoreType()
449
return _truststoreType;
452
/* ------------------------------------------------------------ */
453
public void setProvider(String _provider)
455
this._provider=_provider;
458
/* ------------------------------------------------------------ */
459
public void setSecureRandomAlgorithm(String algorithm)
461
this._secureRandomAlgorithm=algorithm;
464
/* ------------------------------------------------------------ */
465
public void setSslKeyManagerFactoryAlgorithm(String algorithm)
467
this._sslKeyManagerFactoryAlgorithm=algorithm;
470
/* ------------------------------------------------------------ */
471
public void setSslTrustManagerFactoryAlgorithm(String algorithm)
473
this._sslTrustManagerFactoryAlgorithm=algorithm;
476
public void setTruststore(String truststore)
478
_truststore=truststore;
481
public void setTruststoreType(String truststoreType)
483
_truststoreType=truststoreType;
486
/* ------------------------------------------------------------ */
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.
495
public boolean isConfidential(Request request)
497
final int confidentialPort=getConfidentialPort();
498
return confidentialPort==0||confidentialPort==request.getServerPort();
501
/* ------------------------------------------------------------ */
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.
510
public boolean isIntegral(Request request)
512
final int integralPort=getIntegralPort();
513
return integralPort==0||integralPort==request.getServerPort();
516
/* ------------------------------------------------------------------------------- */
517
protected SelectChannelEndPoint newEndPoint(SocketChannel channel, SelectSet selectSet, SelectionKey key) throws IOException
519
return new SslHttpChannelEndPoint(this,channel,selectSet,key,createSSLEngine());
522
/* ------------------------------------------------------------------------------- */
523
protected Connection newConnection(SocketChannel channel, SelectChannelEndPoint endpoint)
525
HttpConnection connection=(HttpConnection)super.newConnection(channel,endpoint);
526
((HttpParser)connection.getParser()).setForceContentBuffer(true);
530
/* ------------------------------------------------------------ */
531
protected SSLEngine createSSLEngine() throws IOException
533
SSLEngine engine=null;
536
engine=_context.createSSLEngine();
537
engine.setUseClientMode(false);
540
engine.setWantClientAuth(_wantClientAuth);
542
engine.setNeedClientAuth(_needClientAuth);
544
if (_excludeCipherSuites!=null&&_excludeCipherSuites.length>0)
546
List<String> excludedCSList=Arrays.asList(_excludeCipherSuites);
547
String[] enabledCipherSuites=engine.getEnabledCipherSuites();
548
List<String> enabledCSList=new ArrayList<String>(Arrays.asList(enabledCipherSuites));
550
for (String cipherName : excludedCSList)
552
if (enabledCSList.contains(cipherName))
554
enabledCSList.remove(cipherName);
557
enabledCipherSuites=enabledCSList.toArray(new String[enabledCSList.size()]);
559
engine.setEnabledCipherSuites(enabledCipherSuites);
565
Log.warn("Error creating sslEngine -- closing this connector",e);
567
throw new IllegalStateException(e);
573
protected void doStart() throws Exception
575
_context=createSSLContext();
577
SSLEngine engine=createSSLEngine();
578
SSLSession ssl_session=engine.getSession();
580
setHeaderBufferSize(ssl_session.getApplicationBufferSize());
581
setRequestBufferSize(ssl_session.getApplicationBufferSize());
582
setResponseBufferSize(ssl_session.getApplicationBufferSize());
587
protected SSLContext createSSLContext() throws Exception
589
if (_truststore==null)
591
_truststore=_keystore;
592
_truststoreType=_keystoreType;
595
InputStream keystoreInputStream = null;
597
KeyManager[] keyManagers=null;
598
KeyStore keyStore = null;
603
keystoreInputStream=Resource.newResource(_keystore).getInputStream();
604
keyStore = KeyStore.getInstance(_keystoreType);
605
keyStore.load(keystoreInputStream,_password==null?null:_password.toString().toCharArray());
610
if (keystoreInputStream != null)
611
keystoreInputStream.close();
614
KeyManagerFactory keyManagerFactory=KeyManagerFactory.getInstance(_sslKeyManagerFactoryAlgorithm);
615
keyManagerFactory.init(keyStore,_keyPassword==null?(_password==null?null:_password.toString().toCharArray()):_keyPassword.toString().toCharArray());
616
keyManagers=keyManagerFactory.getKeyManagers();
619
TrustManager[] trustManagers=null;
620
InputStream truststoreInputStream = null;
621
KeyStore trustStore = null;
624
if (_truststore!=null)
626
truststoreInputStream = Resource.newResource(_truststore).getInputStream();
627
trustStore=KeyStore.getInstance(_truststoreType);
628
trustStore.load(truststoreInputStream,_trustPassword==null?null:_trustPassword.toString().toCharArray());
633
if (truststoreInputStream != null)
634
truststoreInputStream.close();
638
TrustManagerFactory trustManagerFactory=TrustManagerFactory.getInstance(_sslTrustManagerFactoryAlgorithm);
639
trustManagerFactory.init(trustStore);
640
trustManagers=trustManagerFactory.getTrustManagers();
642
SecureRandom secureRandom=_secureRandomAlgorithm==null?null:SecureRandom.getInstance(_secureRandomAlgorithm);
643
SSLContext context=_provider==null?SSLContext.getInstance(_protocol):SSLContext.getInstance(_protocol,_provider);
644
context.init(keyManagers,trustManagers,secureRandom);
649
* Simple bundle of information that is cached in the SSLSession. Stores the
650
* effective keySize and the client certificate chain.
652
private class CachedInfo
654
private X509Certificate[] _certs;
655
private Integer _keySize;
657
CachedInfo(Integer keySize, X509Certificate[] certs)
659
this._keySize=keySize;
663
X509Certificate[] getCerts()