001: /**
002: * Licensed to the Apache Software Foundation (ASF) under one
003: * or more contributor license agreements. See the NOTICE file
004: * distributed with this work for additional information
005: * regarding copyright ownership. The ASF licenses this file
006: * to you under the Apache License, Version 2.0 (the
007: * "License"); you may not use this file except in compliance
008: * with the License. You may obtain a copy of the License at
009: *
010: * http://www.apache.org/licenses/LICENSE-2.0
011: *
012: * Unless required by applicable law or agreed to in writing,
013: * software distributed under the License is distributed on an
014: * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015: * KIND, either express or implied. See the License for the
016: * specific language governing permissions and limitations
017: * under the License.
018: */package org.apache.cxf.transport.https;
019:
020: import java.io.ByteArrayInputStream;
021: import java.io.ByteArrayOutputStream;
022: import java.io.DataInputStream;
023: import java.io.FileInputStream;
024: import java.io.IOException;
025: import java.lang.reflect.Method;
026: import java.security.KeyManagementException;
027: import java.security.KeyStore;
028: import java.security.NoSuchAlgorithmException;
029: import java.security.cert.Certificate;
030: import java.security.cert.CertificateFactory;
031: import java.security.cert.X509Certificate;
032: import java.util.ArrayList;
033: import java.util.Arrays;
034: import java.util.List;
035: import java.util.logging.Level;
036: import java.util.logging.Logger;
037: import java.util.regex.Matcher;
038: import java.util.regex.Pattern;
039:
040: import javax.net.ssl.KeyManager;
041: import javax.net.ssl.KeyManagerFactory;
042: import javax.net.ssl.SSLContext;
043: import javax.net.ssl.TrustManager;
044: import javax.net.ssl.TrustManagerFactory;
045: import javax.servlet.http.HttpServletRequest;
046:
047: import org.apache.cxf.common.logging.LogUtils;
048: import org.apache.cxf.configuration.security.FiltersType;
049: import org.apache.cxf.message.Message;
050: import org.apache.cxf.security.transport.TLSSessionInfo;
051:
052: /**
053: * Holder for utility methods related to manipulating SSL settings, common
054: * to the connection and listener factories (previously duplicated).
055: */
056: public final class SSLUtils {
057:
058: static final String PKCS12_TYPE = "PKCS12";
059:
060: private static final String DEFAULT_KEYSTORE_TYPE = "PKCS12";
061: private static final String DEFAULT_TRUST_STORE_TYPE = "JKS";
062: private static final String DEFAULT_SECURE_SOCKET_PROTOCOL = "TLSv1";
063: private static final String CERTIFICATE_FACTORY_TYPE = "X.509";
064: private static final String SSL_CIPHER_SUITE_ATTRIBUTE = "javax.servlet.request.cipher_suite";
065: private static final String SSL_PEER_CERT_CHAIN_ATTRIBUTE = "javax.servlet.request.X509Certificate";
066:
067: private static final boolean DEFAULT_REQUIRE_CLIENT_AUTHENTICATION = false;
068: private static final boolean DEFAULT_WANT_CLIENT_AUTHENTICATION = true;
069:
070: /**
071: * By default, only include export-compatible ciphersuites.
072: */
073: private static final List<String> DEFAULT_CIPHERSUITE_FILTERS_INCLUDE = Arrays
074: .asList(new String[] { ".*_EXPORT_.*", ".*_EXPORT1024_.*",
075: ".*_WITH_DES_.*", ".*_WITH_NULL_.*" });
076:
077: private SSLUtils() {
078: }
079:
080: public static KeyManager[] getKeyStoreManagers(
081: String keyStoreLocation, String keyStoreType,
082: String keyStorePassword, String keyPassword,
083: String keyStoreMgrFactoryAlgorithm,
084: String secureSocketProtocol, Logger log) throws Exception {
085: //TODO for performance reasons we should cache
086: // the KeymanagerFactory and TrustManagerFactory
087: if ((keyStorePassword != null) && (keyPassword != null)
088: && (!keyStorePassword.equals(keyPassword))) {
089: LogUtils.log(log, Level.WARNING,
090: "KEY_PASSWORD_NOT_SAME_KEYSTORE_PASSWORD");
091: }
092: KeyManager[] keystoreManagers = null;
093: KeyManagerFactory kmf = KeyManagerFactory
094: .getInstance(keyStoreMgrFactoryAlgorithm);
095: KeyStore ks = KeyStore.getInstance(keyStoreType);
096:
097: if (keyStoreType.equalsIgnoreCase(PKCS12_TYPE)) {
098: FileInputStream fis = new FileInputStream(keyStoreLocation);
099: DataInputStream dis = new DataInputStream(fis);
100: byte[] bytes = new byte[dis.available()];
101: dis.readFully(bytes);
102: ByteArrayInputStream bin = new ByteArrayInputStream(bytes);
103:
104: if (keyStorePassword != null) {
105: keystoreManagers = loadKeyStore(kmf, ks, bin,
106: keyStoreLocation, keyStorePassword, log);
107: }
108: } else {
109: byte[] sslCert = loadClientCredential(keyStoreLocation);
110:
111: if (sslCert != null && sslCert.length > 0
112: && keyStorePassword != null) {
113: ByteArrayInputStream bin = new ByteArrayInputStream(
114: sslCert);
115: keystoreManagers = loadKeyStore(kmf, ks, bin,
116: keyStoreLocation, keyStorePassword, log);
117: }
118: }
119: if ((keyStorePassword == null) && (keyStoreLocation != null)) {
120: LogUtils.log(log, Level.WARNING,
121: "FAILED_TO_LOAD_KEYSTORE_NULL_PASSWORD",
122: keyStoreLocation);
123: }
124: return keystoreManagers;
125: }
126:
127: public static KeyManager[] loadKeyStore(KeyManagerFactory kmf,
128: KeyStore ks, ByteArrayInputStream bin,
129: String keyStoreLocation, String keyStorePassword, Logger log) {
130: KeyManager[] keystoreManagers = null;
131: try {
132: ks.load(bin, keyStorePassword.toCharArray());
133: kmf.init(ks, keyStorePassword.toCharArray());
134: keystoreManagers = kmf.getKeyManagers();
135: LogUtils.log(log, Level.INFO, "LOADED_KEYSTORE",
136: keyStoreLocation);
137: } catch (Exception e) {
138: LogUtils.log(log, Level.WARNING, "FAILED_TO_LOAD_KEYSTORE",
139: new Object[] { keyStoreLocation, e.getMessage() });
140: }
141: return keystoreManagers;
142: }
143:
144: public static TrustManager[] getTrustStoreManagers(boolean pkcs12,
145: String trustStoreType, String trustStoreLocation,
146: String trustStoreMgrFactoryAlgorithm, Logger log)
147: throws Exception {
148: // ********************** Load Trusted CA file **********************
149:
150: TrustManager[] trustStoreManagers = null;
151: KeyStore trustedCertStore = KeyStore
152: .getInstance(trustStoreType);
153:
154: if (pkcs12) {
155: //TODO could support multiple trust cas
156: trustStoreManagers = new TrustManager[1];
157:
158: trustedCertStore.load(null, "".toCharArray());
159: CertificateFactory cf = CertificateFactory
160: .getInstance(CERTIFICATE_FACTORY_TYPE);
161: byte[] caCert = loadCACert(trustStoreLocation);
162: try {
163: if (caCert != null) {
164: ByteArrayInputStream cabin = new ByteArrayInputStream(
165: caCert);
166: X509Certificate cert = (X509Certificate) cf
167: .generateCertificate(cabin);
168: trustedCertStore.setCertificateEntry(cert
169: .getIssuerDN().toString(), cert);
170: cabin.close();
171: }
172: } catch (Exception e) {
173: LogUtils.log(log, Level.WARNING,
174: "FAILED_TO_LOAD_TRUST_STORE", new Object[] {
175: trustStoreLocation, e.getMessage() });
176: }
177: } else {
178: trustedCertStore.load(new FileInputStream(
179: trustStoreLocation), null);
180: }
181:
182: TrustManagerFactory tmf = TrustManagerFactory
183: .getInstance(trustStoreMgrFactoryAlgorithm);
184: tmf.init(trustedCertStore);
185: LogUtils.log(log, Level.INFO, "LOADED_TRUST_STORE",
186: trustStoreLocation);
187: trustStoreManagers = tmf.getTrustManagers();
188:
189: return trustStoreManagers;
190: }
191:
192: protected static byte[] loadClientCredential(String fileName)
193: throws IOException {
194: if (fileName == null) {
195: return null;
196: }
197: FileInputStream in = new FileInputStream(fileName);
198: ByteArrayOutputStream out = new ByteArrayOutputStream();
199: byte[] buf = new byte[512];
200: int i = in.read(buf);
201: while (i > 0) {
202: out.write(buf, 0, i);
203: i = in.read(buf);
204: }
205: in.close();
206: return out.toByteArray();
207: }
208:
209: protected static byte[] loadCACert(String fileName)
210: throws IOException {
211: if (fileName == null) {
212: return null;
213: }
214: FileInputStream in = new FileInputStream(fileName);
215: ByteArrayOutputStream out = new ByteArrayOutputStream();
216: byte[] buf = new byte[512];
217: int i = in.read(buf);
218:
219: while (i > 0) {
220: out.write(buf, 0, i);
221: i = in.read(buf);
222: }
223: in.close();
224: return out.toByteArray();
225: }
226:
227: public static String getKeystore(String keyStoreLocation, Logger log) {
228: String logMsg = null;
229: if (keyStoreLocation != null) {
230: logMsg = "KEY_STORE_SET";
231: } else {
232: keyStoreLocation = System
233: .getProperty("javax.net.ssl.keyStore");
234: if (keyStoreLocation != null) {
235: logMsg = "KEY_STORE_SYSTEM_PROPERTY_SET";
236: } else {
237: keyStoreLocation = System.getProperty("user.home")
238: + "/.keystore";
239: logMsg = "KEY_STORE_NOT_SET";
240: }
241: }
242: LogUtils.log(log, Level.INFO, logMsg, keyStoreLocation);
243: return keyStoreLocation;
244: }
245:
246: public static String getKeystoreType(String keyStoreType, Logger log) {
247: String logMsg = null;
248: if (keyStoreType != null) {
249: logMsg = "KEY_STORE_TYPE_SET";
250: } else {
251: keyStoreType = DEFAULT_KEYSTORE_TYPE;
252: logMsg = "KEY_STORE_TYPE_NOT_SET";
253: }
254: LogUtils.log(log, Level.INFO, logMsg, keyStoreType);
255: return keyStoreType;
256: }
257:
258: public static String getKeystorePassword(String keyStorePassword,
259: Logger log) {
260: String logMsg = null;
261: if (keyStorePassword != null) {
262: logMsg = "KEY_STORE_PASSWORD_SET";
263: } else {
264: keyStorePassword = System
265: .getProperty("javax.net.ssl.keyStorePassword");
266: logMsg = keyStorePassword != null ? "KEY_STORE_PASSWORD_SYSTEM_PROPERTY_SET"
267: : "KEY_STORE_PASSWORD_NOT_SET";
268: }
269: LogUtils.log(log, Level.INFO, logMsg);
270: return keyStorePassword;
271: }
272:
273: public static String getKeyPassword(String keyPassword, Logger log) {
274: String logMsg = null;
275: if (keyPassword != null) {
276: logMsg = "KEY_PASSWORD_SET";
277: } else {
278: keyPassword = System
279: .getProperty("javax.net.ssl.keyStorePassword");
280: logMsg = keyPassword != null ? "KEY_PASSWORD_SYSTEM_PROPERTY_SET"
281: : "KEY_PASSWORD_NOT_SET";
282: }
283: LogUtils.log(log, Level.INFO, logMsg);
284: return keyPassword;
285: }
286:
287: public static String getKeystoreAlgorithm(
288: String keyStoreMgrFactoryAlgorithm, Logger log) {
289: String logMsg = null;
290: if (keyStoreMgrFactoryAlgorithm != null) {
291: logMsg = "KEY_STORE_ALGORITHM_SET";
292: } else {
293: keyStoreMgrFactoryAlgorithm = KeyManagerFactory
294: .getDefaultAlgorithm();
295: logMsg = "KEY_STORE_ALGORITHM_NOT_SET";
296: }
297: LogUtils.log(log, Level.INFO, logMsg,
298: keyStoreMgrFactoryAlgorithm);
299: return keyStoreMgrFactoryAlgorithm;
300: }
301:
302: public static String getTrustStoreAlgorithm(
303: String trustStoreMgrFactoryAlgorithm, Logger log) {
304: String logMsg = null;
305: if (trustStoreMgrFactoryAlgorithm != null) {
306: logMsg = "TRUST_STORE_ALGORITHM_SET";
307: } else {
308: trustStoreMgrFactoryAlgorithm = TrustManagerFactory
309: .getDefaultAlgorithm();
310: logMsg = "TRUST_STORE_ALGORITHM_NOT_SET";
311: }
312: LogUtils.log(log, Level.INFO, logMsg,
313: trustStoreMgrFactoryAlgorithm);
314: return trustStoreMgrFactoryAlgorithm;
315: }
316:
317: public static SSLContext getSSLContext(String protocol,
318: KeyManager[] keyStoreManagers,
319: TrustManager[] trustStoreManagers)
320: throws NoSuchAlgorithmException, KeyManagementException {
321: SSLContext ctx = SSLContext.getInstance(protocol);
322: ctx.init(keyStoreManagers, trustStoreManagers, null);
323: return ctx;
324: }
325:
326: public static String[] getSupportedCipherSuites(SSLContext context) {
327: return context.getSocketFactory().getSupportedCipherSuites();
328: }
329:
330: public static String[] getServerSupportedCipherSuites(
331: SSLContext context) {
332: return context.getServerSocketFactory()
333: .getSupportedCipherSuites();
334: }
335:
336: public static String[] getCiphersuites(
337: List<String> cipherSuitesList,
338: String[] supportedCipherSuites, FiltersType filters,
339: Logger log, boolean exclude) {
340: String[] cipherSuites = null;
341: if (!(cipherSuitesList == null || cipherSuitesList.isEmpty())) {
342: cipherSuites = getCiphersFromList(cipherSuitesList, log);
343: } else {
344: LogUtils.log(log, Level.INFO, "CIPHERSUITES_NOT_SET");
345: if (filters == null) {
346: LogUtils.log(log, Level.INFO,
347: "CIPHERSUITE_FILTERS_NOT_SET");
348: }
349: List<String> filteredCipherSuites = new ArrayList<String>();
350: List<String> excludedCipherSuites = new ArrayList<String>();
351: List<Pattern> includes = filters != null ? compileRegexPatterns(
352: filters.getInclude(), true, log)
353: : compileRegexPatterns(
354: DEFAULT_CIPHERSUITE_FILTERS_INCLUDE, true,
355: log);
356: List<Pattern> excludes = filters != null ? compileRegexPatterns(
357: filters.getExclude(), false, log)
358: : null;
359: for (int i = 0; i < supportedCipherSuites.length; i++) {
360: if (matchesOneOf(supportedCipherSuites[i], includes)
361: && !matchesOneOf(supportedCipherSuites[i],
362: excludes)) {
363: LogUtils.log(log, Level.INFO,
364: "CIPHERSUITE_INCLUDED",
365: supportedCipherSuites[i]);
366: filteredCipherSuites.add(supportedCipherSuites[i]);
367: } else {
368: LogUtils.log(log, Level.INFO,
369: "CIPHERSUITE_EXCLUDED",
370: supportedCipherSuites[i]);
371: excludedCipherSuites.add(supportedCipherSuites[i]);
372: }
373: }
374: LogUtils.log(log, Level.INFO, "CIPHERSUITES_FILTERED",
375: filteredCipherSuites);
376: LogUtils.log(log, Level.INFO, "CIPHERSUITES_EXCLUDED",
377: excludedCipherSuites);
378: if (exclude) {
379: cipherSuites = getCiphersFromList(excludedCipherSuites,
380: log);
381: } else {
382: cipherSuites = getCiphersFromList(filteredCipherSuites,
383: log);
384: }
385: }
386: return cipherSuites;
387: }
388:
389: private static List<Pattern> compileRegexPatterns(
390: List<String> regexes, boolean include, Logger log) {
391: List<Pattern> patterns = new ArrayList<Pattern>();
392: if (regexes != null) {
393: String msg = include ? "CIPHERSUITE_INCLUDE_FILTER"
394: : "CIPHERSUITE_EXCLUDE_FILTER";
395: for (String s : regexes) {
396: LogUtils.log(log, Level.INFO, msg, s);
397: patterns.add(Pattern.compile(s));
398: }
399: }
400: return patterns;
401: }
402:
403: private static boolean matchesOneOf(String s, List<Pattern> patterns) {
404: boolean matches = false;
405: if (patterns != null) {
406: for (Pattern pattern : patterns) {
407: Matcher matcher = pattern.matcher(s);
408: if (matcher.matches()) {
409: matches = true;
410: break;
411: }
412: }
413: }
414: return matches;
415: }
416:
417: private static String[] getCiphersFromList(
418: List<String> cipherSuitesList, Logger log) {
419: int numCipherSuites = cipherSuitesList.size();
420: String[] cipherSuites = new String[numCipherSuites];
421: String ciphsStr = null;
422: for (int i = 0; i < numCipherSuites; i++) {
423: cipherSuites[i] = cipherSuitesList.get(i);
424: if (ciphsStr == null) {
425: ciphsStr = cipherSuites[i];
426: } else {
427: ciphsStr += ", " + cipherSuites[i];
428: }
429: }
430: LogUtils.log(log, Level.INFO, "CIPHERSUITES_SET", ciphsStr);
431: return cipherSuites;
432: }
433:
434: public static String getTrustStore(String trustStoreLocation,
435: Logger log) {
436: String logMsg = null;
437: if (trustStoreLocation != null) {
438: logMsg = "TRUST_STORE_SET";
439: } else {
440: trustStoreLocation = System
441: .getProperty("javax.net.ssl.trustStore");
442: if (trustStoreLocation != null) {
443: logMsg = "TRUST_STORE_SYSTEM_PROPERTY_SET";
444: } else {
445: trustStoreLocation = System.getProperty("java.home")
446: + "/lib/security/cacerts";
447: logMsg = "TRUST_STORE_NOT_SET";
448: }
449: }
450: LogUtils.log(log, Level.INFO, logMsg, trustStoreLocation);
451: return trustStoreLocation;
452: }
453:
454: public static String getTrustStoreType(String trustStoreType,
455: Logger log) {
456: String logMsg = null;
457: if (trustStoreType != null) {
458: logMsg = "TRUST_STORE_TYPE_SET";
459: } else {
460: //Can default to JKS
461: trustStoreType = DEFAULT_TRUST_STORE_TYPE;
462: logMsg = "TRUST_STORE_TYPE_NOT_SET";
463: }
464: LogUtils.log(log, Level.INFO, logMsg, trustStoreType);
465: return trustStoreType;
466: }
467:
468: public static String getSecureSocketProtocol(
469: String secureSocketProtocol, Logger log) {
470: if (secureSocketProtocol != null) {
471: LogUtils.log(log, Level.INFO, "SECURE_SOCKET_PROTOCOL_SET",
472: secureSocketProtocol);
473: } else {
474: LogUtils.log(log, Level.INFO,
475: "SECURE_SOCKET_PROTOCOL_NOT_SET");
476: secureSocketProtocol = DEFAULT_SECURE_SOCKET_PROTOCOL;
477: }
478: return secureSocketProtocol;
479: }
480:
481: public static boolean getRequireClientAuthentication(
482: boolean isSetRequireClientAuthentication,
483: Boolean isRequireClientAuthentication, Logger log) {
484: boolean requireClientAuthentication = DEFAULT_REQUIRE_CLIENT_AUTHENTICATION;
485: if (isSetRequireClientAuthentication) {
486: requireClientAuthentication = isRequireClientAuthentication
487: .booleanValue();
488: LogUtils.log(log, Level.INFO,
489: "REQUIRE_CLIENT_AUTHENTICATION_SET",
490: requireClientAuthentication);
491: } else {
492: LogUtils.log(log, Level.WARNING,
493: "REQUIRE_CLIENT_AUTHENTICATION_NOT_SET");
494: }
495: return requireClientAuthentication;
496: }
497:
498: public static boolean getWantClientAuthentication(
499: boolean isSetWantClientAuthentication,
500: Boolean isWantClientAuthentication, Logger log) {
501: boolean wantClientAuthentication = DEFAULT_WANT_CLIENT_AUTHENTICATION;
502: if (isSetWantClientAuthentication) {
503: wantClientAuthentication = isWantClientAuthentication
504: .booleanValue();
505: LogUtils.log(log, Level.INFO,
506: "WANT_CLIENT_AUTHENTICATION_SET",
507: wantClientAuthentication);
508: } else {
509: LogUtils.log(log, Level.WARNING,
510: "WANT_CLIENT_AUTHENTICATION_NOT_SET");
511: }
512: return wantClientAuthentication;
513: }
514:
515: /**
516: * Propogate in the message a TLSSessionInfo instance representative
517: * of the TLS-specific information in the HTTP request.
518: *
519: * @param req the Jetty request
520: * @param message the Message
521: */
522: public static void propogateSecureSession(
523: HttpServletRequest request, Message message) {
524: final String cipherSuite = (String) request
525: .getAttribute(SSL_CIPHER_SUITE_ATTRIBUTE);
526: if (cipherSuite != null) {
527: final Certificate[] certs = (Certificate[]) request
528: .getAttribute(SSL_PEER_CERT_CHAIN_ATTRIBUTE);
529: message.put(TLSSessionInfo.class, new TLSSessionInfo(
530: cipherSuite, null, certs));
531: }
532: }
533:
534: public static void logUnSupportedPolicies(Object policy,
535: boolean client, String[] unsupported, Logger log) {
536: for (int i = 0; i < unsupported.length; i++) {
537: try {
538: Method method = policy.getClass().getMethod(
539: "isSet" + unsupported[i]);
540: boolean isSet = ((Boolean) method.invoke(policy,
541: (Object[]) null)).booleanValue();
542: logUnSupportedPolicy(isSet, client, unsupported[i], log);
543: } catch (Exception e) {
544: // ignore
545: }
546: }
547: }
548:
549: private static void logUnSupportedPolicy(boolean isSet,
550: boolean client, String policy, Logger log) {
551: if (isSet) {
552: LogUtils.log(log, Level.WARNING,
553: client ? "UNSUPPORTED_SSL_CLIENT_POLICY_DATA"
554: : "UNSUPPORTED_SSL_SERVER_POLICY_DATA",
555: policy);
556: }
557: }
558:
559: public static boolean testAllDataHasSetupMethod(Object policy,
560: String[] unsupported, String[] derivative) {
561: Method[] sslPolicyMethods = policy.getClass()
562: .getDeclaredMethods();
563: Method[] methods = SSLUtils.class.getMethods();
564: boolean ok = true;
565:
566: for (int i = 0; i < sslPolicyMethods.length && ok; i++) {
567: String sslPolicyMethodName = sslPolicyMethods[i].getName();
568: if (sslPolicyMethodName.startsWith("isSet")) {
569: String dataName = sslPolicyMethodName.substring("isSet"
570: .length(), sslPolicyMethodName.length());
571: String this MethodName = "get" + dataName;
572: ok = hasMethod(methods, this MethodName)
573: || isExcluded(unsupported, dataName)
574: || isExcluded(derivative, dataName);
575: }
576: }
577: return ok;
578: }
579:
580: private static boolean hasMethod(Method[] methods, String methodName) {
581: boolean found = false;
582: for (int i = 0; i < methods.length && !found; i++) {
583: found = methods[i].getName().equals(methodName);
584: }
585: return found;
586: }
587:
588: private static boolean isExcluded(String[] excluded, String dataName) {
589: boolean found = false;
590: for (int i = 0; i < excluded.length && !found; i++) {
591: found = excluded[i].equals(dataName);
592: }
593: return found;
594:
595: }
596: }
|