001: /*
002: * Copyright 2003-2006 Rick Knowles <winstone-devel at lists sourceforge net>
003: * Distributed under the terms of either:
004: * - the common development and distribution license (CDDL), v1.0; or
005: * - the GNU Lesser General Public License, v2.1 or later
006: */
007: package winstone.ssl;
008:
009: import java.io.File;
010: import java.io.FileInputStream;
011: import java.io.IOException;
012: import java.io.InputStream;
013: import java.net.InetAddress;
014: import java.net.ServerSocket;
015: import java.net.Socket;
016: import java.security.KeyStore;
017: import java.security.cert.Certificate;
018: import java.util.Arrays;
019: import java.util.Enumeration;
020: import java.util.Map;
021:
022: import javax.net.ssl.KeyManagerFactory;
023: import javax.net.ssl.SSLContext;
024: import javax.net.ssl.SSLServerSocket;
025: import javax.net.ssl.SSLServerSocketFactory;
026: import javax.net.ssl.SSLSession;
027: import javax.net.ssl.SSLSocket;
028:
029: import winstone.HostGroup;
030: import winstone.HttpListener;
031: import winstone.Logger;
032: import winstone.ObjectPool;
033: import winstone.WebAppConfiguration;
034: import winstone.WinstoneException;
035: import winstone.WinstoneRequest;
036: import winstone.WinstoneResourceBundle;
037:
038: /**
039: * Implements the main listener daemon thread. This is the class that gets
040: * launched by the command line, and owns the server socket, etc.
041: *
042: * @author <a href="mailto:rick_knowles@hotmail.com">Rick Knowles</a>
043: * @version $Id: HttpsListener.java,v 1.10 2007/06/13 15:27:35 rickknowles Exp $
044: */
045: public class HttpsListener extends HttpListener {
046: private static final WinstoneResourceBundle SSL_RESOURCES = new WinstoneResourceBundle(
047: "winstone.ssl.LocalStrings");
048: private String keystore;
049: private String password;
050: private String keyManagerType;
051:
052: /**
053: * Constructor
054: */
055: public HttpsListener(Map args, ObjectPool objectPool,
056: HostGroup hostGroup) throws IOException {
057: super (args, objectPool, hostGroup);
058: this .keystore = WebAppConfiguration.stringArg(args,
059: getConnectorName() + "KeyStore", "winstone.ks");
060: this .password = WebAppConfiguration.stringArg(args,
061: getConnectorName() + "KeyStorePassword", null);
062: this .keyManagerType = WebAppConfiguration.stringArg(args,
063: getConnectorName() + "KeyManagerType", "SunX509");
064: }
065:
066: /**
067: * The default port to use - this is just so that we can override for the
068: * SSL connector.
069: */
070: protected int getDefaultPort() {
071: return -1; // https disabled by default
072: }
073:
074: /**
075: * The name to use when getting properties - this is just so that we can
076: * override for the SSL connector.
077: */
078: protected String getConnectorScheme() {
079: return "https";
080: }
081:
082: /**
083: * Gets a server socket - this gets as SSL socket instead of the standard
084: * socket returned in the base class.
085: */
086: protected ServerSocket getServerSocket() throws IOException {
087: // Just to make sure it's set before we start
088: SSLContext context = getSSLContext(this .keystore, this .password);
089: SSLServerSocketFactory factory = context
090: .getServerSocketFactory();
091: SSLServerSocket ss = (SSLServerSocket) (this .listenAddress == null ? factory
092: .createServerSocket(this .listenPort, BACKLOG_COUNT)
093: : factory.createServerSocket(this .listenPort,
094: BACKLOG_COUNT, InetAddress
095: .getByName(this .listenAddress)));
096: ss.setEnableSessionCreation(true);
097: ss.setWantClientAuth(true);
098: return ss;
099: }
100:
101: /**
102: * Extracts the relevant socket stuff and adds it to the request object.
103: * This method relies on the base class for everything other than SSL
104: * related attributes
105: */
106: protected void parseSocketInfo(Socket socket, WinstoneRequest req)
107: throws IOException {
108: super .parseSocketInfo(socket, req);
109: if (socket instanceof SSLSocket) {
110: SSLSocket s = (SSLSocket) socket;
111: SSLSession ss = s.getSession();
112: if (ss != null) {
113: Certificate certChain[] = null;
114: try {
115: certChain = ss.getPeerCertificates();
116: } catch (Throwable err) {/* do nothing */
117: }
118:
119: if (certChain != null) {
120: req.setAttribute(
121: "javax.servlet.request.X509Certificate",
122: certChain);
123: req.setAttribute(
124: "javax.servlet.request.cipher_suite", ss
125: .getCipherSuite());
126: req.setAttribute(
127: "javax.servlet.request.ssl_session",
128: new String(ss.getId()));
129: req.setAttribute("javax.servlet.request.key_size",
130: getKeySize(ss.getCipherSuite()));
131: }
132: }
133: req.setIsSecure(true);
134: }
135: }
136:
137: /**
138: * Just a mapping of key sizes for cipher types. Taken indirectly from the
139: * TLS specs.
140: */
141: private Integer getKeySize(String cipherSuite) {
142: if (cipherSuite.indexOf("_WITH_NULL_") != -1)
143: return new Integer(0);
144: else if (cipherSuite.indexOf("_WITH_IDEA_CBC_") != -1)
145: return new Integer(128);
146: else if (cipherSuite.indexOf("_WITH_RC2_CBC_40_") != -1)
147: return new Integer(40);
148: else if (cipherSuite.indexOf("_WITH_RC4_40_") != -1)
149: return new Integer(40);
150: else if (cipherSuite.indexOf("_WITH_RC4_128_") != -1)
151: return new Integer(128);
152: else if (cipherSuite.indexOf("_WITH_DES40_CBC_") != -1)
153: return new Integer(40);
154: else if (cipherSuite.indexOf("_WITH_DES_CBC_") != -1)
155: return new Integer(56);
156: else if (cipherSuite.indexOf("_WITH_3DES_EDE_CBC_") != -1)
157: return new Integer(168);
158: else
159: return null;
160: }
161:
162: /**
163: * Used to get the base ssl context in which to create the server socket.
164: * This is basically just so we can have a custom location for key stores.
165: */
166: public SSLContext getSSLContext(String keyStoreName, String password)
167: throws IOException {
168: try {
169: // Check the key manager factory
170: KeyManagerFactory kmf = KeyManagerFactory
171: .getInstance(this .keyManagerType);
172:
173: File ksFile = new File(keyStoreName);
174: if (!ksFile.exists() || !ksFile.isFile())
175: throw new WinstoneException(SSL_RESOURCES.getString(
176: "HttpsListener.KeyStoreNotFound", ksFile
177: .getPath()));
178: InputStream in = new FileInputStream(ksFile);
179: char[] passwordChars = password == null ? null : password
180: .toCharArray();
181: KeyStore ks = KeyStore.getInstance("JKS");
182: ks.load(in, passwordChars);
183: kmf.init(ks, passwordChars);
184: Logger.log(Logger.FULL_DEBUG, SSL_RESOURCES,
185: "HttpsListener.KeyCount", ks.size() + "");
186: for (Enumeration e = ks.aliases(); e.hasMoreElements();) {
187: String alias = (String) e.nextElement();
188: Logger.log(Logger.FULL_DEBUG, SSL_RESOURCES,
189: "HttpsListener.KeyFound", new String[] { alias,
190: ks.getCertificate(alias) + "" });
191: }
192:
193: SSLContext context = SSLContext.getInstance("SSL");
194: context.init(kmf.getKeyManagers(), null, null);
195: Arrays.fill(passwordChars, 'x');
196: return context;
197: } catch (IOException err) {
198: throw err;
199: } catch (Throwable err) {
200: throw new WinstoneException(SSL_RESOURCES
201: .getString("HttpsListener.ErrorGettingContext"),
202: err);
203: }
204: }
205: }
|