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