001: package org.mortbay.jetty.security;
002:
003: import java.io.ByteArrayInputStream;
004: import java.io.File;
005: import java.io.IOException;
006: import java.nio.channels.SelectionKey;
007: import java.nio.channels.SocketChannel;
008: import java.security.KeyStore;
009: import java.security.SecureRandom;
010: import java.security.Security;
011: import java.security.cert.X509Certificate;
012: import java.util.ArrayList;
013: import java.util.Arrays;
014: import java.util.List;
015:
016: import javax.net.ssl.KeyManager;
017: import javax.net.ssl.KeyManagerFactory;
018: import javax.net.ssl.SSLContext;
019: import javax.net.ssl.SSLEngine;
020: import javax.net.ssl.SSLException;
021: import javax.net.ssl.SSLPeerUnverifiedException;
022: import javax.net.ssl.SSLSession;
023: import javax.net.ssl.SSLSocket;
024: import javax.net.ssl.TrustManager;
025: import javax.net.ssl.TrustManagerFactory;
026:
027: import org.mortbay.io.EndPoint;
028: import org.mortbay.io.bio.SocketEndPoint;
029: import org.mortbay.io.nio.SelectChannelEndPoint;
030: import org.mortbay.io.nio.SelectorManager.SelectSet;
031: import org.mortbay.jetty.Connector;
032: import org.mortbay.jetty.Handler;
033: import org.mortbay.jetty.HttpSchemes;
034: import org.mortbay.jetty.Request;
035: import org.mortbay.jetty.Server;
036: import org.mortbay.jetty.handler.ContextHandlerCollection;
037: import org.mortbay.jetty.handler.DefaultHandler;
038: import org.mortbay.jetty.handler.HandlerCollection;
039: import org.mortbay.jetty.nio.SelectChannelConnector;
040: import org.mortbay.jetty.webapp.WebAppContext;
041: import org.mortbay.log.Log;
042: import org.mortbay.resource.Resource;
043:
044: /* ------------------------------------------------------------ */
045: /** SslSelectChannelConnector.
046: *
047: * @author Nik Gonzalez <ngonzalez@exist.com>
048: * @author Greg Wilkins <gregw@mortbay.com>
049: */
050: public class SslSelectChannelConnector extends SelectChannelConnector {
051: /**
052: * The name of the SSLSession attribute that will contain any cached information.
053: */
054: static final String CACHED_INFO_ATTR = CachedInfo.class.getName();
055:
056: /** Default value for the keystore location path. */
057: public static final String DEFAULT_KEYSTORE = System
058: .getProperty("user.home")
059: + File.separator + ".keystore";
060:
061: /** String name of key password property. */
062: public static final String KEYPASSWORD_PROPERTY = "jetty.ssl.keypassword";
063:
064: /** String name of keystore password property. */
065: public static final String PASSWORD_PROPERTY = "jetty.ssl.password";
066:
067: /** Default value for the cipher Suites. */
068: private String _excludeCipherSuites[] = null;
069:
070: /** Default value for the keystore location path. */
071: private String _keystore = DEFAULT_KEYSTORE;
072: private String _keystoreType = "JKS"; // type of the key store
073:
074: /** Set to true if we require client certificate authentication. */
075: private boolean _needClientAuth = false;
076:
077: private transient Password _password;
078: private transient Password _keyPassword;
079: private transient Password _trustPassword;
080: private String _protocol = "TLS";
081: private String _algorithm = "SunX509"; // cert algorithm
082: private String _provider;
083: private String _secureRandomAlgorithm; // cert algorithm
084: private String _sslKeyManagerFactoryAlgorithm = (Security
085: .getProperty("ssl.KeyManagerFactory.algorithm") == null ? "SunX509"
086: : Security.getProperty("ssl.KeyManagerFactory.algorithm")); // cert algorithm
087:
088: private String _sslTrustManagerFactoryAlgorithm = (Security
089: .getProperty("ssl.TrustManagerFactory.algorithm") == null ? "SunX509"
090: : Security.getProperty("ssl.TrustManagerFactory.algorithm")); // cert algorithm
091:
092: private String _truststore;
093: private String _truststoreType = "JKS"; // type of the key store
094:
095: /**
096: * Return the chain of X509 certificates used to negotiate the SSL Session.
097: * <p>
098: * Note: in order to do this we must convert a javax.security.cert.X509Certificate[], as used by
099: * JSSE to a java.security.cert.X509Certificate[],as required by the Servlet specs.
100: *
101: * @param sslSession the javax.net.ssl.SSLSession to use as the source of the cert chain.
102: * @return the chain of java.security.cert.X509Certificates used to negotiate the SSL
103: * connection. <br>
104: * Will be null if the chain is missing or empty.
105: */
106: private static X509Certificate[] getCertChain(SSLSession sslSession) {
107: try {
108: javax.security.cert.X509Certificate javaxCerts[] = sslSession
109: .getPeerCertificateChain();
110: if (javaxCerts == null || javaxCerts.length == 0)
111: return null;
112:
113: int length = javaxCerts.length;
114: X509Certificate[] javaCerts = new X509Certificate[length];
115:
116: java.security.cert.CertificateFactory cf = java.security.cert.CertificateFactory
117: .getInstance("X.509");
118: for (int i = 0; i < length; i++) {
119: byte bytes[] = javaxCerts[i].getEncoded();
120: ByteArrayInputStream stream = new ByteArrayInputStream(
121: bytes);
122: javaCerts[i] = (X509Certificate) cf
123: .generateCertificate(stream);
124: }
125:
126: return javaCerts;
127: } catch (SSLPeerUnverifiedException pue) {
128: return null;
129: } catch (Exception e) {
130: Log.warn(Log.EXCEPTION, e);
131: return null;
132: }
133: }
134:
135: /* ------------------------------------------------------------ */
136: /**
137: * Allow the Listener a chance to customise the request. before the server does its stuff. <br>
138: * This allows the required attributes to be set for SSL requests. <br>
139: * The requirements of the Servlet specs are:
140: * <ul>
141: * <li> an attribute named "javax.servlet.request.cipher_suite" of type String.</li>
142: * <li> an attribute named "javax.servlet.request.key_size" of type Integer.</li>
143: * <li> an attribute named "javax.servlet.request.X509Certificate" of type
144: * java.security.cert.X509Certificate[]. This is an array of objects of type X509Certificate,
145: * the order of this array is defined as being in ascending order of trust. The first
146: * certificate in the chain is the one set by the client, the next is the one used to
147: * authenticate the first, and so on. </li>
148: * </ul>
149: *
150: * @param endpoint The Socket the request arrived on.
151: * This should be a {@link SocketEndPoint} wrapping a {@link SSLSocket}.
152: * @param request HttpRequest to be customised.
153: */
154: public void customize(EndPoint endpoint, Request request)
155: throws IOException {
156: super .customize(endpoint, request);
157: request.setScheme(HttpSchemes.HTTPS);
158:
159: SslHttpChannelEndPoint sslHttpChannelEndpoint = (SslHttpChannelEndPoint) endpoint;
160: SSLEngine sslEngine = sslHttpChannelEndpoint.getSSLEngine();
161:
162: try {
163: SSLSession sslSession = sslEngine.getSession();
164: String cipherSuite = sslSession.getCipherSuite();
165: Integer keySize;
166: X509Certificate[] certs;
167:
168: CachedInfo cachedInfo = (CachedInfo) sslSession
169: .getValue(CACHED_INFO_ATTR);
170: if (cachedInfo != null) {
171: keySize = cachedInfo.getKeySize();
172: certs = cachedInfo.getCerts();
173: } else {
174: keySize = new Integer(ServletSSL
175: .deduceKeyLength(cipherSuite));
176: certs = getCertChain(sslSession);
177: cachedInfo = new CachedInfo(keySize, certs);
178: sslSession.putValue(CACHED_INFO_ATTR, cachedInfo);
179: }
180:
181: if (certs != null)
182: request.setAttribute(
183: "javax.servlet.request.X509Certificate", certs);
184: else if (_needClientAuth) // Sanity check
185: throw new IllegalStateException("no client auth");
186:
187: request.setAttribute("javax.servlet.request.cipher_suite",
188: cipherSuite);
189: request.setAttribute("javax.servlet.request.key_size",
190: keySize);
191: } catch (Exception e) {
192: Log.warn(Log.EXCEPTION, e);
193: }
194: }
195:
196: /* ------------------------------------------------------------ */
197: public SslSelectChannelConnector() {
198: Log
199: .info("The SslSelectChannelConnector is a BETA quality release");
200: setHeaderBufferSize(32768);
201: setRequestBufferSize(65536);
202: }
203:
204: /* ------------------------------------------------------------ */
205: public String[] getCipherSuites() {
206: return _excludeCipherSuites;
207: }
208:
209: /* ------------------------------------------------------------ */
210: /**
211: * @author Tony Jiang
212: */
213: public void setCipherSuites(String[] cipherSuites) {
214: this ._excludeCipherSuites = cipherSuites;
215: }
216:
217: /* ------------------------------------------------------------ */
218: public void setPassword(String password) {
219: _password = Password.getPassword(PASSWORD_PROPERTY, password,
220: null);
221: }
222:
223: /* ------------------------------------------------------------ */
224: public void setTrustPassword(String password) {
225: _trustPassword = Password.getPassword(PASSWORD_PROPERTY,
226: password, null);
227: }
228:
229: /* ------------------------------------------------------------ */
230: public void setKeyPassword(String password) {
231: _keyPassword = Password.getPassword(KEYPASSWORD_PROPERTY,
232: password, null);
233: }
234:
235: /* ------------------------------------------------------------ */
236: public String getAlgorithm() {
237: return (this ._algorithm);
238: }
239:
240: /* ------------------------------------------------------------ */
241: public void setAlgorithm(String algorithm) {
242: this ._algorithm = algorithm;
243: }
244:
245: /* ------------------------------------------------------------ */
246: public String getProtocol() {
247: return _protocol;
248: }
249:
250: /* ------------------------------------------------------------ */
251: public void setProtocol(String protocol) {
252: _protocol = protocol;
253: }
254:
255: /* ------------------------------------------------------------ */
256: public void setKeystore(String keystore) {
257: _keystore = keystore;
258: }
259:
260: /* ------------------------------------------------------------ */
261: public String getKeystore() {
262: return _keystore;
263: }
264:
265: /* ------------------------------------------------------------ */
266: public String getKeystoreType() {
267: return (_keystoreType);
268: }
269:
270: /* ------------------------------------------------------------ */
271: public boolean getNeedClientAuth() {
272: return _needClientAuth;
273: }
274:
275: /* ------------------------------------------------------------ */
276: /**
277: * Set the value of the needClientAuth property
278: *
279: * @param needClientAuth true iff we require client certificate authentication.
280: */
281: public void setNeedClientAuth(boolean needClientAuth) {
282: _needClientAuth = needClientAuth;
283: }
284:
285: /* ------------------------------------------------------------ */
286: public void setKeystoreType(String keystoreType) {
287: _keystoreType = keystoreType;
288: }
289:
290: /* ------------------------------------------------------------ */
291: public String getProvider() {
292: return _provider;
293: }
294:
295: public String getSecureRandomAlgorithm() {
296: return (this ._secureRandomAlgorithm);
297: }
298:
299: /* ------------------------------------------------------------ */
300: public String getSslKeyManagerFactoryAlgorithm() {
301: return (this ._sslKeyManagerFactoryAlgorithm);
302: }
303:
304: /* ------------------------------------------------------------ */
305: public String getSslTrustManagerFactoryAlgorithm() {
306: return (this ._sslTrustManagerFactoryAlgorithm);
307: }
308:
309: /* ------------------------------------------------------------ */
310: public String getTruststore() {
311: return _truststore;
312: }
313:
314: /* ------------------------------------------------------------ */
315: public String getTruststoreType() {
316: return _truststoreType;
317: }
318:
319: /* ------------------------------------------------------------ */
320: public void setProvider(String _provider) {
321: this ._provider = _provider;
322: }
323:
324: /* ------------------------------------------------------------ */
325: public void setSecureRandomAlgorithm(String algorithm) {
326: this ._secureRandomAlgorithm = algorithm;
327: }
328:
329: /* ------------------------------------------------------------ */
330: public void setSslKeyManagerFactoryAlgorithm(String algorithm) {
331: this ._sslKeyManagerFactoryAlgorithm = algorithm;
332: }
333:
334: /* ------------------------------------------------------------ */
335: public void setSslTrustManagerFactoryAlgorithm(String algorithm) {
336: this ._sslTrustManagerFactoryAlgorithm = algorithm;
337: }
338:
339: public void setTruststore(String truststore) {
340: _truststore = truststore;
341: }
342:
343: public void setTruststoreType(String truststoreType) {
344: _truststoreType = truststoreType;
345: }
346:
347: protected SelectChannelEndPoint newEndPoint(SocketChannel channel,
348: SelectSet selectSet, SelectionKey key) throws IOException {
349: return new SslHttpChannelEndPoint(channel, selectSet, key,
350: createSSLEngine());
351: }
352:
353: /* ------------------------------------------------------------ */
354: protected SSLEngine createSSLEngine() throws IOException {
355: SSLEngine engine = null;
356: try {
357: if (_password == null)
358: _password = new Password("");
359: if (_keyPassword == null)
360: _keyPassword = _password;
361: if (_trustPassword == null)
362: _trustPassword = _password;
363:
364: if (_truststore == null) {
365: _truststore = _keystore;
366: _truststoreType = _keystoreType;
367: }
368:
369: KeyManager[] keyManagers = null;
370: if (_keystore != null) {
371: KeyStore keyStore = KeyStore.getInstance(_keystoreType);
372: if (_password == null)
373: throw new SSLException("_password is not set");
374: keyStore.load(Resource.newResource(_keystore)
375: .getInputStream(), _password.toString()
376: .toCharArray());
377:
378: KeyManagerFactory keyManagerFactory = KeyManagerFactory
379: .getInstance(_sslKeyManagerFactoryAlgorithm);
380: if (_keyPassword == null)
381: throw new SSLException("_keypassword is not set");
382: keyManagerFactory.init(keyStore, _keyPassword
383: .toString().toCharArray());
384: keyManagers = keyManagerFactory.getKeyManagers();
385: }
386:
387: TrustManager[] trustManagers = null;
388: if (_truststore != null) {
389: KeyStore trustStore = KeyStore
390: .getInstance(_truststoreType);
391: trustStore.load(Resource.newResource(_truststore)
392: .getInputStream(), _trustPassword.toString()
393: .toCharArray());
394:
395: TrustManagerFactory trustManagerFactory = TrustManagerFactory
396: .getInstance(_sslTrustManagerFactoryAlgorithm);
397: trustManagerFactory.init(trustStore);
398: trustManagers = trustManagerFactory.getTrustManagers();
399: }
400:
401: SecureRandom secureRandom = _secureRandomAlgorithm == null ? null
402: : SecureRandom.getInstance(_secureRandomAlgorithm);
403:
404: SSLContext context = _provider == null ? SSLContext
405: .getInstance(_protocol) : SSLContext.getInstance(
406: _protocol, _provider);
407:
408: context.init(keyManagers, trustManagers, secureRandom);
409:
410: engine = context.createSSLEngine();
411:
412: if (_excludeCipherSuites != null
413: && _excludeCipherSuites.length > 0) {
414: List<String> excludedCSList = Arrays
415: .asList(_excludeCipherSuites);
416: String[] enabledCipherSuites = engine
417: .getEnabledCipherSuites();
418: List<String> enabledCSList = new ArrayList<String>(
419: Arrays.asList(enabledCipherSuites));
420:
421: for (String cipherName : excludedCSList) {
422: if (enabledCSList.contains(cipherName)) {
423: enabledCSList.remove(cipherName);
424: }
425: }
426: enabledCipherSuites = enabledCSList
427: .toArray(new String[enabledCSList.size()]);
428:
429: engine.setEnabledCipherSuites(enabledCipherSuites);
430: }
431:
432: } catch (Exception e) {
433: Log.debug(e);
434: }
435: return engine;
436: }
437:
438: @Override
439: protected void doStart() throws Exception {
440: super .doStart();
441: }
442:
443: /* TODO temp main - just to help testing */
444: public static void main(String[] args) throws Exception {
445: Server server = new Server();
446: SslSelectChannelConnector connector = new SslSelectChannelConnector();
447: connector.setKeystore("C:/jeprox/jetty_6.0/etc/keystore");
448: connector.setPassword("OBF:1vny1zlo1x8e1vnw1vn61x8g1zlu1vn4");
449: connector.setKeyPassword("OBF:1u2u1wml1z7s1z7a1wnl1u2g");
450:
451: connector.setPort(8443);
452: server.setConnectors(new Connector[] { connector });
453: HandlerCollection handlers = new HandlerCollection();
454: ContextHandlerCollection contexts = new ContextHandlerCollection();
455: handlers.setHandlers(new Handler[] { contexts,
456: new DefaultHandler() });
457: server.setHandler(handlers);
458:
459: HashUserRealm userRealm = new HashUserRealm();
460: userRealm.setName("Test Realm");
461: userRealm.setConfig("C:/jeprox/jetty_6.0/etc/realm.properties");
462: server.setUserRealms(new UserRealm[] { userRealm });
463:
464: WebAppContext.addWebApplications(server,
465: "C:/jeprox/jetty_6.0/webapps",
466: "C:/jeprox/jetty_6.0/etc/webdefault.xml", false, false);
467: System.setProperty("DEBUG", "true");
468: server.start();
469: server.join();
470: }
471:
472: /**
473: * Simple bundle of information that is cached in the SSLSession. Stores the effective keySize
474: * and the client certificate chain.
475: */
476: private class CachedInfo {
477: private X509Certificate[] _certs;
478: private Integer _keySize;
479:
480: CachedInfo(Integer keySize, X509Certificate[] certs) {
481: this ._keySize = keySize;
482: this ._certs = certs;
483: }
484:
485: X509Certificate[] getCerts() {
486: return _certs;
487: }
488:
489: Integer getKeySize() {
490: return _keySize;
491: }
492: }
493:
494: }
|