001: // ========================================================================
002: // Copyright 2000-2005 Mort Bay Consulting Pty. Ltd.
003: // ------------------------------------------------------------------------
004: // Licensed under the Apache License, Version 2.0 (the "License");
005: // you may not use this file except in compliance with the License.
006: // You may obtain a copy of the License at
007: // http://www.apache.org/licenses/LICENSE-2.0
008: // Unless required by applicable law or agreed to in writing, software
009: // distributed under the License is distributed on an "AS IS" BASIS,
010: // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
011: // See the License for the specific language governing permissions and
012: // limitations under the License.
013: // ========================================================================
014:
015: package org.mortbay.jetty.security;
016:
017: import java.io.ByteArrayInputStream;
018: import java.io.IOException;
019: import java.net.InetAddress;
020: import java.net.ServerSocket;
021: import java.net.Socket;
022: import java.net.SocketAddress;
023: import java.security.KeyStore;
024: import java.security.SecureRandom;
025: import java.security.Security;
026: import java.security.cert.X509Certificate;
027: import java.util.ArrayList;
028: import java.util.Arrays;
029: import java.util.Iterator;
030: import java.util.List;
031:
032: import javax.net.ssl.KeyManager;
033: import javax.net.ssl.KeyManagerFactory;
034: import javax.net.ssl.SSLContext;
035: import javax.net.ssl.SSLException;
036: import javax.net.ssl.SSLPeerUnverifiedException;
037: import javax.net.ssl.SSLServerSocket;
038: import javax.net.ssl.SSLServerSocketFactory;
039: import javax.net.ssl.SSLSession;
040: import javax.net.ssl.SSLSocket;
041: import javax.net.ssl.TrustManager;
042: import javax.net.ssl.TrustManagerFactory;
043:
044: import org.mortbay.io.EndPoint;
045: import org.mortbay.io.bio.SocketEndPoint;
046: import org.mortbay.jetty.HttpSchemes;
047: import org.mortbay.jetty.Request;
048: import org.mortbay.jetty.bio.SocketConnector;
049: import org.mortbay.log.Log;
050: import org.mortbay.resource.Resource;
051:
052: /* ------------------------------------------------------------ */
053: /**
054: * JSSE Socket Listener.
055: *
056: * This specialization of HttpListener is an abstract listener that can be used as the basis for a
057: * specific JSSE listener.
058: *
059: * This is heavily based on the work from Court Demas, which in turn is based on the work from Forge
060: * Research.
061: *
062: * @author Greg Wilkins (gregw@mortbay.com)
063: * @author Court Demas (court@kiwiconsulting.com)
064: * @author Forge Research Pty Ltd ACN 003 491 576
065: * @author Jan Hlavat�
066: */
067: public class SslSocketConnector extends SocketConnector {
068: /**
069: * The name of the SSLSession attribute that will contain any cached information.
070: */
071: static final String CACHED_INFO_ATTR = CachedInfo.class.getName();
072:
073: /** String name of key password property. */
074: public static final String KEYPASSWORD_PROPERTY = "jetty.ssl.keypassword";
075:
076: /** String name of keystore password property. */
077: public static final String PASSWORD_PROPERTY = "jetty.ssl.password";
078:
079: /**
080: * Return the chain of X509 certificates used to negotiate the SSL Session.
081: * <p>
082: * Note: in order to do this we must convert a javax.security.cert.X509Certificate[], as used by
083: * JSSE to a java.security.cert.X509Certificate[],as required by the Servlet specs.
084: *
085: * @param sslSession the javax.net.ssl.SSLSession to use as the source of the cert chain.
086: * @return the chain of java.security.cert.X509Certificates used to negotiate the SSL
087: * connection. <br>
088: * Will be null if the chain is missing or empty.
089: */
090: private static X509Certificate[] getCertChain(SSLSession sslSession) {
091: try {
092: javax.security.cert.X509Certificate javaxCerts[] = sslSession
093: .getPeerCertificateChain();
094: if (javaxCerts == null || javaxCerts.length == 0)
095: return null;
096:
097: int length = javaxCerts.length;
098: X509Certificate[] javaCerts = new X509Certificate[length];
099:
100: java.security.cert.CertificateFactory cf = java.security.cert.CertificateFactory
101: .getInstance("X.509");
102: for (int i = 0; i < length; i++) {
103: byte bytes[] = javaxCerts[i].getEncoded();
104: ByteArrayInputStream stream = new ByteArrayInputStream(
105: bytes);
106: javaCerts[i] = (X509Certificate) cf
107: .generateCertificate(stream);
108: }
109:
110: return javaCerts;
111: } catch (SSLPeerUnverifiedException pue) {
112: return null;
113: } catch (Exception e) {
114: Log.warn(Log.EXCEPTION, e);
115: return null;
116: }
117: }
118:
119: /** Default value for the cipher Suites. */
120: private String _excludeCipherSuites[] = null;
121:
122: private String _keystore = null;
123: private String _keystoreType = "JKS"; // type of the key store
124:
125: /** Set to true if we require client certificate authentication. */
126: private boolean _needClientAuth = false;
127: private transient Password _password;
128: private transient Password _keyPassword;
129: private transient Password _trustPassword;
130: private String _protocol = "TLS";
131: private String _provider;
132: private String _secureRandomAlgorithm; // cert algorithm
133: private String _sslKeyManagerFactoryAlgorithm = (Security
134: .getProperty("ssl.KeyManagerFactory.algorithm") == null ? "SunX509"
135: : Security.getProperty("ssl.KeyManagerFactory.algorithm")); // cert algorithm
136: private String _sslTrustManagerFactoryAlgorithm = (Security
137: .getProperty("ssl.TrustManagerFactory.algorithm") == null ? "SunX509"
138: : Security.getProperty("ssl.TrustManagerFactory.algorithm")); // cert algorithm
139:
140: private String _truststore;
141: private String _truststoreType = "JKS"; // type of the key store
142:
143: /** Set to true if we would like client certificate authentication. */
144: private boolean _wantClientAuth = false;
145:
146: /* ------------------------------------------------------------ */
147: /**
148: * Constructor.
149: */
150: public SslSocketConnector() {
151: super ();
152: }
153:
154: /* ------------------------------------------------------------ */
155: protected void configure(Socket socket) throws IOException {
156: super .configure(socket);
157:
158: ((SSLSocket) socket).startHandshake(); // block until SSL handshaking is done
159: }
160:
161: /* ------------------------------------------------------------ */
162: protected SSLServerSocketFactory createFactory() throws Exception {
163: if (_password == null)
164: _password = new Password("");
165: if (_keyPassword == null)
166: _keyPassword = _password;
167: if (_trustPassword == null)
168: _trustPassword = _password;
169:
170: KeyManager[] keyManagers = null;
171: if (_keystore != null) {
172: KeyStore keyStore = KeyStore.getInstance(_keystoreType);
173: if (_password == null)
174: throw new SSLException("_password is not set");
175: keyStore.load(Resource.newResource(_keystore)
176: .getInputStream(), _password.toString()
177: .toCharArray());
178:
179: KeyManagerFactory keyManagerFactory = KeyManagerFactory
180: .getInstance(_sslKeyManagerFactoryAlgorithm);
181: if (_keyPassword == null)
182: throw new SSLException("_keypassword is not set");
183: keyManagerFactory.init(keyStore, _keyPassword.toString()
184: .toCharArray());
185: keyManagers = keyManagerFactory.getKeyManagers();
186: }
187:
188: TrustManager[] trustManagers = null;
189: if (_truststore != null) {
190: KeyStore trustStore = KeyStore.getInstance(_truststoreType);
191: trustStore.load(Resource.newResource(_truststore)
192: .getInputStream(), _trustPassword.toString()
193: .toCharArray());
194:
195: TrustManagerFactory trustManagerFactory = TrustManagerFactory
196: .getInstance(_sslTrustManagerFactoryAlgorithm);
197: trustManagerFactory.init(trustStore);
198: trustManagers = trustManagerFactory.getTrustManagers();
199: }
200:
201: SecureRandom secureRandom = _secureRandomAlgorithm == null ? null
202: : SecureRandom.getInstance(_secureRandomAlgorithm);
203:
204: SSLContext context = _provider == null ? SSLContext
205: .getInstance(_protocol) : SSLContext.getInstance(
206: _protocol, _provider);
207:
208: context.init(keyManagers, trustManagers, secureRandom);
209:
210: return context.getServerSocketFactory();
211: }
212:
213: /* ------------------------------------------------------------ */
214: /**
215: * Allow the Listener a chance to customise the request. before the server does its stuff. <br>
216: * This allows the required attributes to be set for SSL requests. <br>
217: * The requirements of the Servlet specs are:
218: * <ul>
219: * <li> an attribute named "javax.servlet.request.cipher_suite" of type String.</li>
220: * <li> an attribute named "javax.servlet.request.key_size" of type Integer.</li>
221: * <li> an attribute named "javax.servlet.request.X509Certificate" of type
222: * java.security.cert.X509Certificate[]. This is an array of objects of type X509Certificate,
223: * the order of this array is defined as being in ascending order of trust. The first
224: * certificate in the chain is the one set by the client, the next is the one used to
225: * authenticate the first, and so on. </li>
226: * </ul>
227: *
228: * @param endpoint The Socket the request arrived on.
229: * This should be a {@link SocketEndPoint} wrapping a {@link SSLSocket}.
230: * @param request HttpRequest to be customised.
231: */
232: public void customize(EndPoint endpoint, Request request)
233: throws IOException {
234: super .customize(endpoint, request);
235: request.setScheme(HttpSchemes.HTTPS);
236:
237: SocketEndPoint socket_end_point = (SocketEndPoint) endpoint;
238: SSLSocket sslSocket = (SSLSocket) socket_end_point
239: .getTransport();
240:
241: try {
242: SSLSession sslSession = sslSocket.getSession();
243: String cipherSuite = sslSession.getCipherSuite();
244: Integer keySize;
245: X509Certificate[] certs;
246:
247: CachedInfo cachedInfo = (CachedInfo) sslSession
248: .getValue(CACHED_INFO_ATTR);
249: if (cachedInfo != null) {
250: keySize = cachedInfo.getKeySize();
251: certs = cachedInfo.getCerts();
252: } else {
253: keySize = new Integer(ServletSSL
254: .deduceKeyLength(cipherSuite));
255: certs = getCertChain(sslSession);
256: cachedInfo = new CachedInfo(keySize, certs);
257: sslSession.putValue(CACHED_INFO_ATTR, cachedInfo);
258: }
259:
260: if (certs != null)
261: request.setAttribute(
262: "javax.servlet.request.X509Certificate", certs);
263: else if (_needClientAuth) // Sanity check
264: throw new IllegalStateException("no client auth");
265:
266: request.setAttribute("javax.servlet.request.cipher_suite",
267: cipherSuite);
268: request.setAttribute("javax.servlet.request.key_size",
269: keySize);
270: } catch (Exception e) {
271: Log.warn(Log.EXCEPTION, e);
272: }
273: }
274:
275: /* ------------------------------------------------------------ */
276: public String[] getExcludeCipherSuites() {
277: return _excludeCipherSuites;
278: }
279:
280: /* ------------------------------------------------------------ */
281: public String getKeystore() {
282: return _keystore;
283: }
284:
285: /* ------------------------------------------------------------ */
286: public String getKeystoreType() {
287: return (_keystoreType);
288: }
289:
290: /* ------------------------------------------------------------ */
291: public boolean getNeedClientAuth() {
292: return _needClientAuth;
293: }
294:
295: /* ------------------------------------------------------------ */
296: public String getProtocol() {
297: return _protocol;
298: }
299:
300: /* ------------------------------------------------------------ */
301: public String getProvider() {
302: return _provider;
303: }
304:
305: /* ------------------------------------------------------------ */
306: public String getSecureRandomAlgorithm() {
307: return (this ._secureRandomAlgorithm);
308: }
309:
310: /* ------------------------------------------------------------ */
311: public String getSslKeyManagerFactoryAlgorithm() {
312: return (this ._sslKeyManagerFactoryAlgorithm);
313: }
314:
315: /* ------------------------------------------------------------ */
316: public String getSslTrustManagerFactoryAlgorithm() {
317: return (this ._sslTrustManagerFactoryAlgorithm);
318: }
319:
320: /* ------------------------------------------------------------ */
321: public String getTruststore() {
322: return _truststore;
323: }
324:
325: /* ------------------------------------------------------------ */
326: public String getTruststoreType() {
327: return _truststoreType;
328: }
329:
330: /* ------------------------------------------------------------ */
331: public boolean getWantClientAuth() {
332: return _wantClientAuth;
333: }
334:
335: /* ------------------------------------------------------------ */
336: /**
337: * By default, we're confidential, given we speak SSL. But, if we've been told about an
338: * confidential port, and said port is not our port, then we're not. This allows separation of
339: * listeners providing INTEGRAL versus CONFIDENTIAL constraints, such as one SSL listener
340: * configured to require client certs providing CONFIDENTIAL, whereas another SSL listener not
341: * requiring client certs providing mere INTEGRAL constraints.
342: */
343: public boolean isConfidential(Request request) {
344: final int confidentialPort = getConfidentialPort();
345: return confidentialPort == 0
346: || confidentialPort == request.getServerPort();
347: }
348:
349: /* ------------------------------------------------------------ */
350: /**
351: * By default, we're integral, given we speak SSL. But, if we've been told about an integral
352: * port, and said port is not our port, then we're not. This allows separation of listeners
353: * providing INTEGRAL versus CONFIDENTIAL constraints, such as one SSL listener configured to
354: * require client certs providing CONFIDENTIAL, whereas another SSL listener not requiring
355: * client certs providing mere INTEGRAL constraints.
356: */
357: public boolean isIntegral(Request request) {
358: final int integralPort = getIntegralPort();
359: return integralPort == 0
360: || integralPort == request.getServerPort();
361: }
362:
363: /* ------------------------------------------------------------ */
364: /**
365: * @param addr The {@link SocketAddress address} that this server should listen on
366: * @param backlog See {@link ServerSocket#bind(java.net.SocketAddress, int)}
367: * @return A new {@link ServerSocket socket object} bound to the supplied address with all other
368: * settings as per the current configuration of this connector.
369: * @see #setWantClientAuth
370: * @see #setNeedClientAuth
371: * @see #setCipherSuites
372: * @exception IOException
373: */
374:
375: /* ------------------------------------------------------------ */
376: protected ServerSocket newServerSocket(String host, int port,
377: int backlog) throws IOException {
378: SSLServerSocketFactory factory = null;
379: SSLServerSocket socket = null;
380:
381: try {
382: factory = createFactory();
383:
384: socket = (SSLServerSocket) (host == null ? factory
385: .createServerSocket(port, backlog) : factory
386: .createServerSocket(port, backlog, InetAddress
387: .getByName(host)));
388:
389: if (_wantClientAuth)
390: socket.setWantClientAuth(_wantClientAuth);
391: if (_needClientAuth)
392: socket.setNeedClientAuth(_needClientAuth);
393:
394: if (_excludeCipherSuites != null
395: && _excludeCipherSuites.length > 0) {
396: List excludedCSList = Arrays
397: .asList(_excludeCipherSuites);
398: String[] enabledCipherSuites = socket
399: .getEnabledCipherSuites();
400: List enabledCSList = new ArrayList(Arrays
401: .asList(enabledCipherSuites));
402: Iterator exIter = excludedCSList.iterator();
403:
404: while (exIter.hasNext()) {
405: String cipherName = (String) exIter.next();
406: if (enabledCSList.contains(cipherName)) {
407: enabledCSList.remove(cipherName);
408: }
409: }
410: enabledCipherSuites = (String[]) enabledCSList
411: .toArray(new String[enabledCSList.size()]);
412:
413: socket.setEnabledCipherSuites(enabledCipherSuites);
414: }
415:
416: } catch (IOException e) {
417: throw e;
418: } catch (Exception e) {
419: Log.warn(Log.EXCEPTION, e);
420: throw new IOException("Could not create JsseListener: "
421: + e.toString());
422: }
423: return socket;
424: }
425:
426: /* ------------------------------------------------------------ */
427: /**
428: * @author Tony Jiang
429: */
430: public void setExcludeCipherSuites(String[] cipherSuites) {
431: this ._excludeCipherSuites = cipherSuites;
432: }
433:
434: /* ------------------------------------------------------------ */
435: public void setKeyPassword(String password) {
436: _keyPassword = Password.getPassword(KEYPASSWORD_PROPERTY,
437: password, null);
438: }
439:
440: /* ------------------------------------------------------------ */
441: public void setKeystore(String keystore) {
442: _keystore = keystore;
443: }
444:
445: /* ------------------------------------------------------------ */
446: public void setKeystoreType(String keystoreType) {
447: _keystoreType = keystoreType;
448: }
449:
450: /* ------------------------------------------------------------ */
451: /**
452: * Set the value of the needClientAuth property
453: *
454: * @param needClientAuth true iff we require client certificate authentication.
455: */
456: public void setNeedClientAuth(boolean needClientAuth) {
457: _needClientAuth = needClientAuth;
458: }
459:
460: /* ------------------------------------------------------------ */
461: public void setPassword(String password) {
462: _password = Password.getPassword(PASSWORD_PROPERTY, password,
463: null);
464: }
465:
466: /* ------------------------------------------------------------ */
467: public void setTrustPassword(String password) {
468: _trustPassword = Password.getPassword(PASSWORD_PROPERTY,
469: password, null);
470: }
471:
472: /* ------------------------------------------------------------ */
473: public void setProtocol(String protocol) {
474: _protocol = protocol;
475: }
476:
477: /* ------------------------------------------------------------ */
478: public void setProvider(String _provider) {
479: this ._provider = _provider;
480: }
481:
482: /* ------------------------------------------------------------ */
483: public void setSecureRandomAlgorithm(String algorithm) {
484: this ._secureRandomAlgorithm = algorithm;
485: }
486:
487: /* ------------------------------------------------------------ */
488: public void setSslKeyManagerFactoryAlgorithm(String algorithm) {
489: this ._sslKeyManagerFactoryAlgorithm = algorithm;
490: }
491:
492: /* ------------------------------------------------------------ */
493: public void setSslTrustManagerFactoryAlgorithm(String algorithm) {
494: this ._sslTrustManagerFactoryAlgorithm = algorithm;
495: }
496:
497: public void setTruststore(String truststore) {
498: _truststore = truststore;
499: }
500:
501: public void setTruststoreType(String truststoreType) {
502: _truststoreType = truststoreType;
503: }
504:
505: /* ------------------------------------------------------------ */
506: /**
507: * Set the value of the _wantClientAuth property. This property is used when
508: * {@link #newServerSocket(SocketAddress, int) opening server sockets}.
509: *
510: * @param wantClientAuth true iff we want client certificate authentication.
511: * @see SSLServerSocket#setWantClientAuth
512: */
513: public void setWantClientAuth(boolean wantClientAuth) {
514: _wantClientAuth = wantClientAuth;
515: }
516:
517: /**
518: * Simple bundle of information that is cached in the SSLSession. Stores the effective keySize
519: * and the client certificate chain.
520: */
521: private class CachedInfo {
522: private X509Certificate[] _certs;
523: private Integer _keySize;
524:
525: CachedInfo(Integer keySize, X509Certificate[] certs) {
526: this ._keySize = keySize;
527: this ._certs = certs;
528: }
529:
530: X509Certificate[] getCerts() {
531: return _certs;
532: }
533:
534: Integer getKeySize() {
535: return _keySize;
536: }
537: }
538:
539: }
|