001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017:
018: package org.apache.tomcat.util.net.jsse;
019:
020: import java.io.File;
021: import java.io.FileInputStream;
022: import java.io.FileNotFoundException;
023: import java.io.IOException;
024: import java.io.InputStream;
025: import java.net.InetAddress;
026: import java.net.ServerSocket;
027: import java.net.Socket;
028: import java.net.SocketException;
029: import java.security.KeyStore;
030: import java.security.SecureRandom;
031: import java.security.cert.CRL;
032: import java.security.cert.CRLException;
033: import java.security.cert.CertPathParameters;
034: import java.security.cert.CertStore;
035: import java.security.cert.CertStoreParameters;
036: import java.security.cert.CertificateException;
037: import java.security.cert.CertificateFactory;
038: import java.security.cert.CollectionCertStoreParameters;
039: import java.security.cert.PKIXBuilderParameters;
040: import java.security.cert.X509CertSelector;
041: import java.util.Collection;
042: import java.util.Vector;
043:
044: import javax.net.ssl.CertPathTrustManagerParameters;
045: import javax.net.ssl.KeyManager;
046: import javax.net.ssl.KeyManagerFactory;
047: import javax.net.ssl.ManagerFactoryParameters;
048: import javax.net.ssl.SSLContext;
049: import javax.net.ssl.SSLException;
050: import javax.net.ssl.SSLServerSocket;
051: import javax.net.ssl.SSLServerSocketFactory;
052: import javax.net.ssl.SSLSocket;
053: import javax.net.ssl.TrustManager;
054: import javax.net.ssl.TrustManagerFactory;
055: import javax.net.ssl.X509KeyManager;
056:
057: import org.apache.tomcat.util.res.StringManager;
058:
059: /*
060: 1. Make the JSSE's jars available, either as an installed
061: extension (copy them into jre/lib/ext) or by adding
062: them to the Tomcat classpath.
063: 2. keytool -genkey -alias tomcat -keyalg RSA
064: Use "changeit" as password ( this is the default we use )
065: */
066:
067: /**
068: * SSL server socket factory. It _requires_ a valid RSA key and
069: * JSSE.
070: *
071: * @author Harish Prabandham
072: * @author Costin Manolache
073: * @author Stefan Freyr Stefansson
074: * @author EKR -- renamed to JSSESocketFactory
075: * @author Jan Luehe
076: * @author Bill Barker
077: */
078: public class JSSESocketFactory extends
079: org.apache.tomcat.util.net.ServerSocketFactory {
080:
081: private static StringManager sm = StringManager
082: .getManager("org.apache.tomcat.util.net.jsse.res");
083:
084: // defaults
085: static String defaultProtocol = "TLS";
086: static boolean defaultClientAuth = false;
087: static String defaultKeystoreType = "JKS";
088: private static final String defaultKeystoreFile = System
089: .getProperty("user.home")
090: + "/.keystore";
091: private static final String defaultKeyPass = "changeit";
092: static org.apache.juli.logging.Log log = org.apache.juli.logging.LogFactory
093: .getLog(JSSESocketFactory.class);
094:
095: protected boolean initialized;
096: protected String clientAuth = "false";
097: protected SSLServerSocketFactory sslProxy = null;
098: protected String[] enabledCiphers;
099:
100: /**
101: * Flag to state that we require client authentication.
102: */
103: protected boolean requireClientAuth = false;
104:
105: /**
106: * Flag to state that we would like client authentication.
107: */
108: protected boolean wantClientAuth = false;
109:
110: public JSSESocketFactory() {
111: }
112:
113: public ServerSocket createSocket(int port) throws IOException {
114: if (!initialized)
115: init();
116: ServerSocket socket = sslProxy.createServerSocket(port);
117: initServerSocket(socket);
118: return socket;
119: }
120:
121: public ServerSocket createSocket(int port, int backlog)
122: throws IOException {
123: if (!initialized)
124: init();
125: ServerSocket socket = sslProxy
126: .createServerSocket(port, backlog);
127: initServerSocket(socket);
128: return socket;
129: }
130:
131: public ServerSocket createSocket(int port, int backlog,
132: InetAddress ifAddress) throws IOException {
133: if (!initialized)
134: init();
135: ServerSocket socket = sslProxy.createServerSocket(port,
136: backlog, ifAddress);
137: initServerSocket(socket);
138: return socket;
139: }
140:
141: public Socket acceptSocket(ServerSocket socket) throws IOException {
142: SSLSocket asock = null;
143: try {
144: asock = (SSLSocket) socket.accept();
145: configureClientAuth(asock);
146: } catch (SSLException e) {
147: throw new SocketException("SSL handshake error"
148: + e.toString());
149: }
150: return asock;
151: }
152:
153: public void handshake(Socket sock) throws IOException {
154: ((SSLSocket) sock).startHandshake();
155: }
156:
157: /*
158: * Determines the SSL cipher suites to be enabled.
159: *
160: * @param requestedCiphers Comma-separated list of requested ciphers
161: * @param supportedCiphers Array of supported ciphers
162: *
163: * @return Array of SSL cipher suites to be enabled, or null if none of the
164: * requested ciphers are supported
165: */
166: protected String[] getEnabledCiphers(String requestedCiphers,
167: String[] supportedCiphers) {
168:
169: String[] enabledCiphers = null;
170:
171: if (requestedCiphers != null) {
172: Vector vec = null;
173: String cipher = requestedCiphers;
174: int index = requestedCiphers.indexOf(',');
175: if (index != -1) {
176: int fromIndex = 0;
177: while (index != -1) {
178: cipher = requestedCiphers.substring(fromIndex,
179: index).trim();
180: if (cipher.length() > 0) {
181: /*
182: * Check to see if the requested cipher is among the
183: * supported ciphers, i.e., may be enabled
184: */
185: for (int i = 0; supportedCiphers != null
186: && i < supportedCiphers.length; i++) {
187: if (supportedCiphers[i].equals(cipher)) {
188: if (vec == null) {
189: vec = new Vector();
190: }
191: vec.addElement(cipher);
192: break;
193: }
194: }
195: }
196: fromIndex = index + 1;
197: index = requestedCiphers.indexOf(',', fromIndex);
198: } // while
199: cipher = requestedCiphers.substring(fromIndex);
200: }
201:
202: if (cipher != null) {
203: cipher = cipher.trim();
204: if (cipher.length() > 0) {
205: /*
206: * Check to see if the requested cipher is among the
207: * supported ciphers, i.e., may be enabled
208: */
209: for (int i = 0; supportedCiphers != null
210: && i < supportedCiphers.length; i++) {
211: if (supportedCiphers[i].equals(cipher)) {
212: if (vec == null) {
213: vec = new Vector();
214: }
215: vec.addElement(cipher);
216: break;
217: }
218: }
219: }
220: }
221:
222: if (vec != null) {
223: enabledCiphers = new String[vec.size()];
224: vec.copyInto(enabledCiphers);
225: }
226: } else {
227: enabledCiphers = sslProxy.getDefaultCipherSuites();
228: }
229:
230: return enabledCiphers;
231: }
232:
233: /*
234: * Gets the SSL server's keystore password.
235: */
236: protected String getKeystorePassword() {
237: String keyPass = (String) attributes.get("keypass");
238: if (keyPass == null) {
239: keyPass = defaultKeyPass;
240: }
241: String keystorePass = (String) attributes.get("keystorePass");
242: if (keystorePass == null) {
243: keystorePass = keyPass;
244: }
245: return keystorePass;
246: }
247:
248: /*
249: * Gets the SSL server's keystore.
250: */
251: protected KeyStore getKeystore(String type, String pass)
252: throws IOException {
253:
254: String keystoreFile = (String) attributes.get("keystore");
255: if (keystoreFile == null)
256: keystoreFile = defaultKeystoreFile;
257:
258: return getStore(type, keystoreFile, pass);
259: }
260:
261: /*
262: * Gets the SSL server's truststore.
263: */
264: protected KeyStore getTrustStore(String keystoreType)
265: throws IOException {
266: KeyStore trustStore = null;
267:
268: String trustStoreFile = (String) attributes
269: .get("truststoreFile");
270: if (trustStoreFile == null) {
271: trustStoreFile = System
272: .getProperty("javax.net.ssl.trustStore");
273: }
274: if (log.isDebugEnabled()) {
275: log.debug("Truststore = " + trustStoreFile);
276: }
277: String trustStorePassword = (String) attributes
278: .get("truststorePass");
279: if (trustStorePassword == null) {
280: trustStorePassword = System
281: .getProperty("javax.net.ssl.trustStorePassword");
282: }
283: if (trustStorePassword == null) {
284: trustStorePassword = getKeystorePassword();
285: }
286: if (log.isDebugEnabled()) {
287: log.debug("TrustPass = " + trustStorePassword);
288: }
289: String truststoreType = (String) attributes
290: .get("truststoreType");
291: if (truststoreType == null) {
292: truststoreType = keystoreType;
293: }
294: if (log.isDebugEnabled()) {
295: log.debug("trustType = " + truststoreType);
296: }
297: if (trustStoreFile != null && trustStorePassword != null) {
298: trustStore = getStore(truststoreType, trustStoreFile,
299: trustStorePassword);
300: }
301:
302: return trustStore;
303: }
304:
305: /*
306: * Gets the key- or truststore with the specified type, path, and password.
307: */
308: private KeyStore getStore(String type, String path, String pass)
309: throws IOException {
310:
311: KeyStore ks = null;
312: InputStream istream = null;
313: try {
314: ks = KeyStore.getInstance(type);
315: if (!"PKCS11".equalsIgnoreCase(type)) {
316: File keyStoreFile = new File(path);
317: if (!keyStoreFile.isAbsolute()) {
318: keyStoreFile = new File(System
319: .getProperty("catalina.base"), path);
320: }
321: istream = new FileInputStream(keyStoreFile);
322: }
323:
324: ks.load(istream, pass.toCharArray());
325: } catch (FileNotFoundException fnfe) {
326: throw fnfe;
327: } catch (IOException ioe) {
328: throw ioe;
329: } catch (Exception ex) {
330: log.error("Exception trying to load keystore " + path, ex);
331: throw new IOException("Exception trying to load keystore "
332: + path + ": " + ex.getMessage());
333: } finally {
334: if (istream != null) {
335: try {
336: istream.close();
337: } catch (IOException ioe) {
338: // Do nothing
339: }
340: }
341: }
342:
343: return ks;
344: }
345:
346: /**
347: * Reads the keystore and initializes the SSL socket factory.
348: */
349: void init() throws IOException {
350: try {
351:
352: String clientAuthStr = (String) attributes
353: .get("clientauth");
354: if ("true".equalsIgnoreCase(clientAuthStr)
355: || "yes".equalsIgnoreCase(clientAuthStr)) {
356: requireClientAuth = true;
357: } else if ("want".equalsIgnoreCase(clientAuthStr)) {
358: wantClientAuth = true;
359: }
360:
361: // SSL protocol variant (e.g., TLS, SSL v3, etc.)
362: String protocol = (String) attributes.get("protocol");
363: if (protocol == null) {
364: protocol = defaultProtocol;
365: }
366:
367: // Certificate encoding algorithm (e.g., SunX509)
368: String algorithm = (String) attributes.get("algorithm");
369: if (algorithm == null) {
370: algorithm = KeyManagerFactory.getDefaultAlgorithm();
371: ;
372: }
373:
374: String keystoreType = (String) attributes
375: .get("keystoreType");
376: if (keystoreType == null) {
377: keystoreType = defaultKeystoreType;
378: }
379:
380: String trustAlgorithm = (String) attributes
381: .get("truststoreAlgorithm");
382: if (trustAlgorithm == null) {
383: trustAlgorithm = TrustManagerFactory
384: .getDefaultAlgorithm();
385: }
386: // Create and init SSLContext
387: SSLContext context = SSLContext.getInstance(protocol);
388: context.init(getKeyManagers(keystoreType, algorithm,
389: (String) attributes.get("keyAlias")),
390: getTrustManagers(keystoreType, trustAlgorithm),
391: new SecureRandom());
392:
393: // create proxy
394: sslProxy = context.getServerSocketFactory();
395:
396: // Determine which cipher suites to enable
397: String requestedCiphers = (String) attributes
398: .get("ciphers");
399: enabledCiphers = getEnabledCiphers(requestedCiphers,
400: sslProxy.getSupportedCipherSuites());
401:
402: } catch (Exception e) {
403: if (e instanceof IOException)
404: throw (IOException) e;
405: throw new IOException(e.getMessage());
406: }
407: }
408:
409: /**
410: * Gets the initialized key managers.
411: */
412: protected KeyManager[] getKeyManagers(String keystoreType,
413: String algorithm, String keyAlias) throws Exception {
414:
415: KeyManager[] kms = null;
416:
417: String keystorePass = getKeystorePassword();
418:
419: KeyStore ks = getKeystore(keystoreType, keystorePass);
420: if (keyAlias != null && !ks.isKeyEntry(keyAlias)) {
421: throw new IOException(sm.getString(
422: "jsse.alias_no_key_entry", keyAlias));
423: }
424:
425: KeyManagerFactory kmf = KeyManagerFactory
426: .getInstance(algorithm);
427: kmf.init(ks, keystorePass.toCharArray());
428:
429: kms = kmf.getKeyManagers();
430: if (keyAlias != null) {
431: if (JSSESocketFactory.defaultKeystoreType
432: .equals(keystoreType)) {
433: keyAlias = keyAlias.toLowerCase();
434: }
435: for (int i = 0; i < kms.length; i++) {
436: kms[i] = new JSSEKeyManager((X509KeyManager) kms[i],
437: keyAlias);
438: }
439: }
440:
441: return kms;
442: }
443:
444: /**
445: * Gets the intialized trust managers.
446: */
447: protected TrustManager[] getTrustManagers(String keystoreType,
448: String algorithm) throws Exception {
449: String crlf = (String) attributes.get("crlFile");
450:
451: TrustManager[] tms = null;
452:
453: String truststoreType = (String) attributes
454: .get("truststoreType");
455: if (truststoreType == null) {
456: truststoreType = keystoreType;
457: }
458: KeyStore trustStore = getTrustStore(truststoreType);
459: if (trustStore != null) {
460: if (crlf == null) {
461: TrustManagerFactory tmf = TrustManagerFactory
462: .getInstance(algorithm);
463: tmf.init(trustStore);
464: tms = tmf.getTrustManagers();
465: } else {
466: TrustManagerFactory tmf = TrustManagerFactory
467: .getInstance(algorithm);
468: CertPathParameters params = getParameters(algorithm,
469: crlf, trustStore);
470: ManagerFactoryParameters mfp = new CertPathTrustManagerParameters(
471: params);
472: tmf.init(mfp);
473: tms = tmf.getTrustManagers();
474: }
475: }
476:
477: return tms;
478: }
479:
480: /**
481: * Return the initialization parameters for the TrustManager.
482: * Currently, only the default <code>PKIX</code> is supported.
483: *
484: * @param algorithm The algorithm to get parameters for.
485: * @param crlf The path to the CRL file.
486: * @param trustStore The configured TrustStore.
487: * @return The parameters including the CRLs and TrustStore.
488: */
489: protected CertPathParameters getParameters(String algorithm,
490: String crlf, KeyStore trustStore) throws Exception {
491: CertPathParameters params = null;
492: if ("PKIX".equalsIgnoreCase(algorithm)) {
493: PKIXBuilderParameters xparams = new PKIXBuilderParameters(
494: trustStore, new X509CertSelector());
495: Collection crls = getCRLs(crlf);
496: CertStoreParameters csp = new CollectionCertStoreParameters(
497: crls);
498: CertStore store = CertStore.getInstance("Collection", csp);
499: xparams.addCertStore(store);
500: xparams.setRevocationEnabled(true);
501: String trustLength = (String) attributes
502: .get("trustMaxCertLength");
503: if (trustLength != null) {
504: try {
505: xparams.setMaxPathLength(Integer
506: .parseInt(trustLength));
507: } catch (Exception ex) {
508: log.warn("Bad maxCertLength: " + trustLength);
509: }
510: }
511:
512: params = xparams;
513: } else {
514: throw new CRLException("CRLs not supported for type: "
515: + algorithm);
516: }
517: return params;
518: }
519:
520: /**
521: * Load the collection of CRLs.
522: *
523: */
524: protected Collection<? extends CRL> getCRLs(String crlf)
525: throws IOException, CRLException, CertificateException {
526:
527: File crlFile = new File(crlf);
528: if (!crlFile.isAbsolute()) {
529: crlFile = new File(System.getProperty("catalina.base"),
530: crlf);
531: }
532: Collection<? extends CRL> crls = null;
533: InputStream is = null;
534: try {
535: CertificateFactory cf = CertificateFactory
536: .getInstance("X.509");
537: is = new FileInputStream(crlFile);
538: crls = cf.generateCRLs(is);
539: } catch (IOException iex) {
540: throw iex;
541: } catch (CRLException crle) {
542: throw crle;
543: } catch (CertificateException ce) {
544: throw ce;
545: } finally {
546: if (is != null) {
547: try {
548: is.close();
549: } catch (Exception ex) {
550: }
551: }
552: }
553: return crls;
554: }
555:
556: /**
557: * Set the SSL protocol variants to be enabled.
558: * @param socket the SSLServerSocket.
559: * @param protocols the protocols to use.
560: */
561: protected void setEnabledProtocols(SSLServerSocket socket,
562: String[] protocols) {
563: if (protocols != null) {
564: socket.setEnabledProtocols(protocols);
565: }
566: }
567:
568: /**
569: * Determines the SSL protocol variants to be enabled.
570: *
571: * @param socket The socket to get supported list from.
572: * @param requestedProtocols Comma-separated list of requested SSL
573: * protocol variants
574: *
575: * @return Array of SSL protocol variants to be enabled, or null if none of
576: * the requested protocol variants are supported
577: */
578: protected String[] getEnabledProtocols(SSLServerSocket socket,
579: String requestedProtocols) {
580: String[] supportedProtocols = socket.getSupportedProtocols();
581:
582: String[] enabledProtocols = null;
583:
584: if (requestedProtocols != null) {
585: Vector vec = null;
586: String protocol = requestedProtocols;
587: int index = requestedProtocols.indexOf(',');
588: if (index != -1) {
589: int fromIndex = 0;
590: while (index != -1) {
591: protocol = requestedProtocols.substring(fromIndex,
592: index).trim();
593: if (protocol.length() > 0) {
594: /*
595: * Check to see if the requested protocol is among the
596: * supported protocols, i.e., may be enabled
597: */
598: for (int i = 0; supportedProtocols != null
599: && i < supportedProtocols.length; i++) {
600: if (supportedProtocols[i].equals(protocol)) {
601: if (vec == null) {
602: vec = new Vector();
603: }
604: vec.addElement(protocol);
605: break;
606: }
607: }
608: }
609: fromIndex = index + 1;
610: index = requestedProtocols.indexOf(',', fromIndex);
611: } // while
612: protocol = requestedProtocols.substring(fromIndex);
613: }
614:
615: if (protocol != null) {
616: protocol = protocol.trim();
617: if (protocol.length() > 0) {
618: /*
619: * Check to see if the requested protocol is among the
620: * supported protocols, i.e., may be enabled
621: */
622: for (int i = 0; supportedProtocols != null
623: && i < supportedProtocols.length; i++) {
624: if (supportedProtocols[i].equals(protocol)) {
625: if (vec == null) {
626: vec = new Vector();
627: }
628: vec.addElement(protocol);
629: break;
630: }
631: }
632: }
633: }
634:
635: if (vec != null) {
636: enabledProtocols = new String[vec.size()];
637: vec.copyInto(enabledProtocols);
638: }
639: }
640:
641: return enabledProtocols;
642: }
643:
644: /**
645: * Configure Client authentication for this version of JSSE. The
646: * JSSE included in Java 1.4 supports the 'want' value. Prior
647: * versions of JSSE will treat 'want' as 'false'.
648: * @param socket the SSLServerSocket
649: */
650: protected void configureClientAuth(SSLServerSocket socket) {
651: if (wantClientAuth) {
652: socket.setWantClientAuth(wantClientAuth);
653: } else {
654: socket.setNeedClientAuth(requireClientAuth);
655: }
656: }
657:
658: /**
659: * Configure Client authentication for this version of JSSE. The
660: * JSSE included in Java 1.4 supports the 'want' value. Prior
661: * versions of JSSE will treat 'want' as 'false'.
662: * @param socket the SSLSocket
663: */
664: protected void configureClientAuth(SSLSocket socket) {
665: // Per JavaDocs: SSLSockets returned from
666: // SSLServerSocket.accept() inherit this setting.
667: }
668:
669: /**
670: * Configures the given SSL server socket with the requested cipher suites,
671: * protocol versions, and need for client authentication
672: */
673: private void initServerSocket(ServerSocket ssocket) {
674:
675: SSLServerSocket socket = (SSLServerSocket) ssocket;
676:
677: if (enabledCiphers != null) {
678: socket.setEnabledCipherSuites(enabledCiphers);
679: }
680:
681: String requestedProtocols = (String) attributes
682: .get("protocols");
683: setEnabledProtocols(socket, getEnabledProtocols(socket,
684: requestedProtocols));
685:
686: // we don't know if client auth is needed -
687: // after parsing the request we may re-handshake
688: configureClientAuth(socket);
689: }
690:
691: }
|