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: package org.apache.servicemix.soap.handlers.security;
018:
019: import java.io.ByteArrayInputStream;
020: import java.io.InputStream;
021: import java.math.BigInteger;
022: import java.security.InvalidAlgorithmParameterException;
023: import java.security.KeyStore;
024: import java.security.KeyStoreException;
025: import java.security.MessageDigest;
026: import java.security.NoSuchAlgorithmException;
027: import java.security.NoSuchProviderException;
028: import java.security.PrivateKey;
029: import java.security.PublicKey;
030: import java.security.cert.CertPath;
031: import java.security.cert.CertPathValidator;
032: import java.security.cert.CertPathValidatorException;
033: import java.security.cert.Certificate;
034: import java.security.cert.CertificateEncodingException;
035: import java.security.cert.CertificateException;
036: import java.security.cert.CertificateFactory;
037: import java.security.cert.PKIXParameters;
038: import java.security.cert.TrustAnchor;
039: import java.security.cert.X509Certificate;
040: import java.security.interfaces.RSAPublicKey;
041: import java.util.ArrayList;
042: import java.util.Arrays;
043: import java.util.HashSet;
044: import java.util.Iterator;
045: import java.util.List;
046: import java.util.Set;
047: import java.util.Vector;
048:
049: import org.apache.ws.security.WSSecurityException;
050: import org.apache.ws.security.components.crypto.Crypto;
051: import org.apache.ws.security.components.crypto.X509NameTokenizer;
052:
053: public abstract class BaseCrypto implements Crypto {
054:
055: private static final String SKI_OID = "2.5.29.14";
056:
057: private String provider;
058: private CertificateFactory certFact;
059: private String defaultX509Alias;
060:
061: /**
062: * @param defaultX509Alias the defaultX509Alias to set
063: */
064: public void setDefaultX509Alias(String defaultX509Alias) {
065: this .defaultX509Alias = defaultX509Alias;
066: }
067:
068: /**
069: * @return the provider
070: */
071: public String getProvider() {
072: return provider;
073: }
074:
075: /**
076: * @param provider the provider to set
077: */
078: public void setProvider(String provider) {
079: this .provider = provider;
080: }
081:
082: /**
083: * Return a X509 Certificate alias in the keystore according to a given Certificate
084: * <p/>
085: *
086: * @param cert The certificate to lookup
087: * @return alias name of the certificate that matches the given certificate
088: * or null if no such certificate was found.
089: */
090: public String getAliasForX509Cert(Certificate cert)
091: throws WSSecurityException {
092: try {
093: String alias = getCertificateAlias(cert);
094: if (alias != null)
095: return alias;
096: // Use brute force search
097: String[] allAliases = getAliases();
098: for (int i = 0; i < allAliases.length; i++) {
099: Certificate cert2 = getCertificate(alias);
100: if (cert2.equals(cert)) {
101: return alias;
102: }
103: }
104: } catch (KeyStoreException e) {
105: throw new WSSecurityException(WSSecurityException.FAILURE,
106: "keystore");
107: }
108: return null;
109: }
110:
111: /**
112: * Lookup a X509 Certificate in the keystore according to a given
113: * the issuer of a Certficate.
114: * <p/>
115: * The search gets all alias names of the keystore and gets the certificate chain
116: * for each alias. Then the Issuer fo each certificate of the chain
117: * is compared with the parameters.
118: *
119: * @param issuer The issuer's name for the certificate
120: * @return alias name of the certificate that matches the issuer name
121: * or null if no such certificate was found.
122: */
123: public String getAliasForX509Cert(String issuer)
124: throws WSSecurityException {
125: return getAliasForX509Cert(issuer, null, false);
126: }
127:
128: /**
129: * Lookup a X509 Certificate in the keystore according to a given
130: * SubjectKeyIdentifier.
131: * <p/>
132: * The search gets all alias names of the keystore and gets the certificate chain
133: * or certificate for each alias. Then the SKI for each user certificate
134: * is compared with the SKI parameter.
135: *
136: * @param skiBytes The SKI info bytes
137: * @return alias name of the certificate that matches serialNumber and issuer name
138: * or null if no such certificate was found.
139: * @throws org.apache.ws.security.WSSecurityException
140: * if problems during keystore handling or wrong certificate (no SKI data)
141: */
142: public String getAliasForX509Cert(byte[] skiBytes)
143: throws WSSecurityException {
144: Certificate cert = null;
145: try {
146: String[] allAliases = getAliases();
147: for (int i = 0; i < allAliases.length; i++) {
148: String alias = allAliases[i];
149: cert = getCertificateChainOrCertificate(alias);
150: if (cert instanceof X509Certificate) {
151: byte[] data = getSKIBytesFromCert((X509Certificate) cert);
152: if (Arrays.equals(data, skiBytes)) {
153: return alias;
154: }
155: }
156: }
157: } catch (KeyStoreException e) {
158: throw new WSSecurityException(WSSecurityException.FAILURE,
159: "keystore");
160: }
161: return null;
162: }
163:
164: /**
165: * Lookup a X509 Certificate in the keystore according to a given serial number and
166: * the issuer of a Certficate.
167: * <p/>
168: * The search gets all alias names of the keystore and gets the certificate chain
169: * for each alias. Then the SerialNumber and Issuer fo each certificate of the chain
170: * is compared with the parameters.
171: *
172: * @param issuer The issuer's name for the certificate
173: * @param serialNumber The serial number of the certificate from the named issuer
174: * @return alias name of the certificate that matches serialNumber and issuer name
175: * or null if no such certificate was found.
176: */
177: public String getAliasForX509Cert(String issuer,
178: BigInteger serialNumber) throws WSSecurityException {
179: return getAliasForX509Cert(issuer, serialNumber, true);
180: }
181:
182: /**
183: * Lookup a X509 Certificate in the keystore according to a given
184: * Thumbprint.
185: * <p/>
186: * The search gets all alias names of the keystore, then reads the certificate chain
187: * or certificate for each alias. Then the thumbprint for each user certificate
188: * is compared with the thumbprint parameter.
189: *
190: * @param thumb The SHA1 thumbprint info bytes
191: * @return alias name of the certificate that matches the thumbprint
192: * or null if no such certificate was found.
193: * @throws org.apache.ws.security.WSSecurityException
194: * if problems during keystore handling or wrong certificate
195: */
196: public String getAliasForX509CertThumb(byte[] thumb)
197: throws WSSecurityException {
198: Certificate cert = null;
199: MessageDigest sha = null;
200: try {
201: sha = MessageDigest.getInstance("SHA-1");
202: } catch (NoSuchAlgorithmException e1) {
203: throw new WSSecurityException(0, "noSHA1availabe");
204: }
205: try {
206: String[] allAliases = getAliases();
207: for (int i = 0; i < allAliases.length; i++) {
208: String alias = allAliases[i];
209: cert = getCertificateChainOrCertificate(alias);
210: if (cert instanceof X509Certificate) {
211: sha.reset();
212: try {
213: sha.update(cert.getEncoded());
214: } catch (CertificateEncodingException e1) {
215: throw new WSSecurityException(
216: WSSecurityException.SECURITY_TOKEN_UNAVAILABLE,
217: "encodeError");
218: }
219: byte[] data = sha.digest();
220: if (Arrays.equals(data, thumb)) {
221: return alias;
222: }
223: }
224: }
225: } catch (KeyStoreException e) {
226: throw new WSSecurityException(WSSecurityException.FAILURE,
227: "keystore");
228: }
229: return null;
230: }
231:
232: /**
233: * Lookup X509 Certificates in the keystore according to a given DN of the subject of the certificate
234: * <p/>
235: * The search gets all alias names of the keystore and gets the certificate (chain)
236: * for each alias. Then the DN of the certificate is compared with the parameters.
237: *
238: * @param subjectDN The DN of subject to look for in the keystore
239: * @return Vector with all alias of certificates with the same DN as given in the parameters
240: * @throws org.apache.ws.security.WSSecurityException
241: *
242: */
243: public String[] getAliasesForDN(String subjectDN)
244: throws WSSecurityException {
245: // Store the aliases found
246: Vector aliases = new Vector();
247: Certificate cert = null;
248: // The DN to search the keystore for
249: Vector subjectRDN = splitAndTrim(subjectDN);
250: // Look at every certificate in the keystore
251: try {
252: String[] allAliases = getAliases();
253: for (int i = 0; i < allAliases.length; i++) {
254: String alias = allAliases[i];
255: cert = getCertificateChainOrCertificate(alias);
256: if (cert instanceof X509Certificate) {
257: Vector foundRDN = splitAndTrim(((X509Certificate) cert)
258: .getSubjectDN().getName());
259: if (subjectRDN.equals(foundRDN)) {
260: aliases.add(alias);
261: }
262: }
263: }
264: } catch (KeyStoreException e) {
265: throw new WSSecurityException(WSSecurityException.FAILURE,
266: "keystore");
267: }
268: // Convert the vector into an array
269: return (String[]) aliases.toArray(new String[aliases.size()]);
270: }
271:
272: /**
273: * get a byte array given an array of X509 certificates.
274: * <p/>
275: *
276: * @param reverse If set the first certificate in the array data will
277: * the last in the byte array
278: * @param certs The certificates to convert
279: * @return The byte array for the certficates ordered according
280: * to the reverse flag
281: * @throws WSSecurityException
282: */
283: public byte[] getCertificateData(boolean reverse,
284: X509Certificate[] certs) throws WSSecurityException {
285: Vector list = new Vector();
286: for (int i = 0; i < certs.length; i++) {
287: if (reverse) {
288: list.insertElementAt(certs[i], 0);
289: } else {
290: list.add(certs[i]);
291: }
292: }
293: try {
294: CertPath path = getCertificateFactory().generateCertPath(
295: list);
296: return path.getEncoded();
297: } catch (CertificateEncodingException e) {
298: throw new WSSecurityException(
299: WSSecurityException.SECURITY_TOKEN_UNAVAILABLE,
300: "encodeError");
301: } catch (CertificateException e) {
302: throw new WSSecurityException(
303: WSSecurityException.SECURITY_TOKEN_UNAVAILABLE,
304: "parseError");
305: }
306: }
307:
308: /**
309: * Singleton certificate factory for this Crypto instance.
310: * <p/>
311: *
312: * @return Returns a <code>CertificateFactory</code> to construct
313: * X509 certficates
314: * @throws org.apache.ws.security.WSSecurityException
315: *
316: */
317: public synchronized CertificateFactory getCertificateFactory()
318: throws WSSecurityException {
319: if (certFact == null) {
320: try {
321: if (provider == null || provider.length() == 0) {
322: certFact = CertificateFactory.getInstance("X.509");
323: } else {
324: certFact = CertificateFactory.getInstance("X.509",
325: provider);
326: }
327: } catch (CertificateException e) {
328: throw new WSSecurityException(
329: WSSecurityException.SECURITY_TOKEN_UNAVAILABLE,
330: "unsupportedCertType");
331: } catch (NoSuchProviderException e) {
332: throw new WSSecurityException(
333: WSSecurityException.SECURITY_TOKEN_UNAVAILABLE,
334: "noSecProvider");
335: }
336: }
337: return certFact;
338: }
339:
340: /**
341: * Gets the list of certificates for a given alias.
342: * <p/>
343: *
344: * @param alias Lookup certificate chain for this alias
345: * @return Array of X509 certificates for this alias name, or
346: * null if this alias does not exist in the keystore
347: */
348: public X509Certificate[] getCertificates(String alias)
349: throws WSSecurityException {
350: try {
351: Certificate[] certs = getCertificateChain(alias);
352: if (certs != null && certs.length > 0) {
353: List x509certs = new ArrayList();
354: for (int i = 0; i < certs.length; i++) {
355: if (certs[i] instanceof X509Certificate) {
356: x509certs.add(certs[i]);
357: }
358: }
359: return (X509Certificate[]) x509certs
360: .toArray(new X509Certificate[x509certs.size()]);
361: }
362: // no cert chain, so lets check if getCertificate gives us a result.
363: Certificate cert = getCertificate(alias);
364: if (cert instanceof X509Certificate) {
365: return new X509Certificate[] { (X509Certificate) cert };
366: }
367: return null;
368: } catch (KeyStoreException e) {
369: throw new WSSecurityException(WSSecurityException.FAILURE,
370: "keystore");
371: }
372: }
373:
374: public String getDefaultX509Alias() {
375: return defaultX509Alias;
376: }
377:
378: public KeyStore getKeyStore() {
379: return null;
380: }
381:
382: /**
383: * Gets the private key identified by <code>alias</> and <code>password</code>.
384: * <p/>
385: *
386: * @param alias The alias (<code>KeyStore</code>) of the key owner
387: * @param password The password needed to access the private key
388: * @return The private key
389: * @throws Exception
390: */
391: public abstract PrivateKey getPrivateKey(String alias,
392: String password) throws Exception;
393:
394: /**
395: * Reads the SubjectKeyIdentifier information from the certificate.
396: * <p/>
397: * If the the certificate does not contain a SKI extension then
398: * try to compute the SKI according to RFC3280 using the
399: * SHA-1 hash value of the public key. The second method described
400: * in RFC3280 is not support. Also only RSA public keys are supported.
401: * If we cannot compute the SKI throw a WSSecurityException.
402: *
403: * @param cert The certificate to read SKI
404: * @return The byte array conating the binary SKI data
405: */
406: public byte[] getSKIBytesFromCert(X509Certificate cert)
407: throws WSSecurityException {
408: /*
409: * Gets the DER-encoded OCTET string for the extension value (extnValue)
410: * identified by the passed-in oid String. The oid string is represented
411: * by a set of positive whole numbers separated by periods.
412: */
413: byte[] derEncodedValue = cert.getExtensionValue(SKI_OID);
414: if (cert.getVersion() < 3 || derEncodedValue == null) {
415: PublicKey key = cert.getPublicKey();
416: if (!(key instanceof RSAPublicKey)) {
417: throw new WSSecurityException(1, "noSKIHandling",
418: new Object[] { "Support for RSA key only" });
419: }
420: byte[] encoded = key.getEncoded();
421: // remove 22-byte algorithm ID and header
422: byte[] value = new byte[encoded.length - 22];
423: System.arraycopy(encoded, 22, value, 0, value.length);
424: MessageDigest sha;
425: try {
426: sha = MessageDigest.getInstance("SHA-1");
427: } catch (NoSuchAlgorithmException ex) {
428: throw new WSSecurityException(
429: 1,
430: "noSKIHandling",
431: new Object[] { "Wrong certificate version (<3) and no SHA1 message digest availabe" });
432: }
433: sha.reset();
434: sha.update(value);
435: return sha.digest();
436: }
437: /*
438: * Strip away first four bytes from the DerValue (tag and length of
439: * ExtensionValue OCTET STRING and KeyIdentifier OCTET STRING)
440: */
441: byte abyte0[] = new byte[derEncodedValue.length - 4];
442: System.arraycopy(derEncodedValue, 4, abyte0, 0, abyte0.length);
443: return abyte0;
444: }
445:
446: public X509Certificate[] getX509Certificates(byte[] data,
447: boolean reverse) throws WSSecurityException {
448: InputStream in = new ByteArrayInputStream(data);
449: CertPath path = null;
450: try {
451: path = getCertificateFactory().generateCertPath(in);
452: } catch (CertificateException e) {
453: throw new WSSecurityException(
454: WSSecurityException.SECURITY_TOKEN_UNAVAILABLE,
455: "parseError");
456: }
457: List l = path.getCertificates();
458: X509Certificate[] certs = new X509Certificate[l.size()];
459: Iterator iterator = l.iterator();
460: for (int i = 0; i < l.size(); i++) {
461: certs[(reverse) ? (l.size() - 1 - i) : i] = (X509Certificate) iterator
462: .next();
463: }
464: return certs;
465: }
466:
467: /**
468: * load a X509Certificate from the input stream.
469: * <p/>
470: *
471: * @param in The <code>InputStream</code> array containg the X509 data
472: * @return An X509 certificate
473: * @throws WSSecurityException
474: */
475: public X509Certificate loadCertificate(InputStream in)
476: throws WSSecurityException {
477: X509Certificate cert = null;
478: try {
479: cert = (X509Certificate) getCertificateFactory()
480: .generateCertificate(in);
481: } catch (CertificateException e) {
482: throw new WSSecurityException(
483: WSSecurityException.SECURITY_TOKEN_UNAVAILABLE,
484: "parseError");
485: }
486: return cert;
487: }
488:
489: /**
490: * Uses the CertPath API to validate a given certificate chain
491: * <p/>
492: *
493: * @param certs Certificate chain to validate
494: * @return true if the certificate chain is valid, false otherwise
495: * @throws WSSecurityException
496: */
497: public boolean validateCertPath(X509Certificate[] certs)
498: throws WSSecurityException {
499: try {
500: // Generate cert path
501: java.util.List certList = java.util.Arrays.asList(certs);
502: CertPath path = this .getCertificateFactory()
503: .generateCertPath(certList);
504:
505: // Use the certificates in the keystore as TrustAnchors
506: Set hashSet = new HashSet();
507: String[] aliases = getTrustCertificates();
508: for (int i = 0; i < aliases.length; i++) {
509: Certificate cert = getCertificate(aliases[i]);
510: if (cert instanceof X509Certificate) {
511: hashSet.add(new TrustAnchor((X509Certificate) cert,
512: null));
513: }
514: }
515: PKIXParameters param = new PKIXParameters(hashSet);
516: // Do not check a revocation list
517: param.setRevocationEnabled(false);
518: // Verify the trust path using the above settings
519: CertPathValidator certPathValidator;
520: if (provider == null || provider.length() == 0) {
521: certPathValidator = CertPathValidator
522: .getInstance("PKIX");
523: } else {
524: certPathValidator = CertPathValidator.getInstance(
525: "PKIX", provider);
526: }
527: certPathValidator.validate(path, param);
528: } catch (NoSuchProviderException ex) {
529: throw new WSSecurityException(WSSecurityException.FAILURE,
530: "certpath", new Object[] { ex.getMessage() },
531: (Throwable) ex);
532: } catch (NoSuchAlgorithmException ex) {
533: throw new WSSecurityException(WSSecurityException.FAILURE,
534: "certpath", new Object[] { ex.getMessage() },
535: (Throwable) ex);
536: } catch (CertificateException ex) {
537: throw new WSSecurityException(WSSecurityException.FAILURE,
538: "certpath", new Object[] { ex.getMessage() },
539: (Throwable) ex);
540: } catch (InvalidAlgorithmParameterException ex) {
541: throw new WSSecurityException(WSSecurityException.FAILURE,
542: "certpath", new Object[] { ex.getMessage() },
543: (Throwable) ex);
544: } catch (CertPathValidatorException ex) {
545: throw new WSSecurityException(WSSecurityException.FAILURE,
546: "certpath", new Object[] { ex.getMessage() },
547: (Throwable) ex);
548: } catch (KeyStoreException ex) {
549: throw new WSSecurityException(WSSecurityException.FAILURE,
550: "certpath", new Object[] { ex.getMessage() },
551: (Throwable) ex);
552: }
553:
554: return true;
555: }
556:
557: protected Vector splitAndTrim(String inString) {
558: X509NameTokenizer nmTokens = new X509NameTokenizer(inString);
559: Vector vr = new Vector();
560:
561: while (nmTokens.hasMoreTokens()) {
562: vr.add(nmTokens.nextToken());
563: }
564: java.util.Collections.sort(vr);
565: return vr;
566: }
567:
568: protected Certificate getCertificateChainOrCertificate(String alias)
569: throws KeyStoreException {
570: Certificate[] certs = getCertificateChain(alias);
571: Certificate cert = null;
572: if (certs == null || certs.length == 0) {
573: // no cert chain, so lets check if getCertificate gives us a result.
574: cert = getCertificate(alias);
575: if (cert == null) {
576: return null;
577: }
578: } else {
579: cert = certs[0];
580: }
581: return cert;
582: }
583:
584: /*
585: * need to check if "getCertificateChain" also finds certificates that are
586: * used for enryption only, i.e. they may not be signed by a CA
587: * Otherwise we must define a restriction how to use certificate:
588: * each certificate must be signed by a CA or is a self signed Certificate
589: * (this should work as well).
590: * --- remains to be tested in several ways --
591: */
592: private String getAliasForX509Cert(String issuer,
593: BigInteger serialNumber, boolean useSerialNumber)
594: throws WSSecurityException {
595: Vector issuerRDN = splitAndTrim(issuer);
596: X509Certificate x509cert = null;
597: Vector certRDN = null;
598: Certificate cert = null;
599:
600: try {
601: String[] allAliases = getAliases();
602: for (int i = 0; i < allAliases.length; i++) {
603: String alias = allAliases[i];
604: cert = getCertificateChainOrCertificate(alias);
605: if (cert instanceof X509Certificate) {
606: x509cert = (X509Certificate) cert;
607: if (!useSerialNumber
608: || useSerialNumber
609: && x509cert.getSerialNumber().compareTo(
610: serialNumber) == 0) {
611: certRDN = splitAndTrim(x509cert.getIssuerDN()
612: .getName());
613: if (certRDN.equals(issuerRDN)) {
614: return alias;
615: }
616: }
617: }
618: }
619: } catch (KeyStoreException e) {
620: throw new WSSecurityException(WSSecurityException.FAILURE,
621: "keystore");
622: }
623: return null;
624: }
625:
626: protected abstract String[] getAliases() throws KeyStoreException;
627:
628: protected abstract Certificate[] getCertificateChain(String alias)
629: throws KeyStoreException;
630:
631: protected abstract Certificate getCertificate(String alias)
632: throws KeyStoreException;
633:
634: protected abstract String getCertificateAlias(Certificate cert)
635: throws KeyStoreException;
636:
637: protected abstract String[] getTrustCertificates()
638: throws KeyStoreException;
639:
640: }
|