1 package org.astrogrid.security.wsse;
2
3 import org.apache.commons.discovery.Resource;
4 import org.apache.commons.discovery.ResourceIterator;
5 import org.apache.commons.discovery.jdk.JDKHooks;
6 import org.apache.commons.discovery.resource.DiscoverResources;
7 import org.apache.commons.logging.Log;
8 import org.apache.commons.logging.LogFactory;
9 import org.apache.ws.security.WSSecurityException;
10 import org.apache.ws.security.components.crypto.*;
11
12 import java.io.ByteArrayInputStream;
13 import java.io.FileInputStream;
14 import java.io.IOException;
15 import java.io.InputStream;
16 import java.math.BigInteger;
17 import java.security.GeneralSecurityException;
18 import java.security.InvalidAlgorithmParameterException;
19 import java.security.Key;
20 import java.security.KeyStore;
21 import java.security.KeyStoreException;
22 import java.security.MessageDigest;
23 import java.security.NoSuchAlgorithmException;
24 import java.security.NoSuchProviderException;
25 import java.security.PrivateKey;
26 import java.security.PublicKey;
27 import java.security.interfaces.RSAPublicKey;
28 import java.security.cert.CertPath;
29 import java.security.cert.CertPathValidator;
30 import java.security.cert.CertPathValidatorException;
31 import java.security.cert.Certificate;
32 import java.security.cert.CertificateEncodingException;
33 import java.security.cert.CertificateException;
34 import java.security.cert.CertificateFactory;
35 import java.security.cert.PKIXParameters;
36 import java.security.cert.X509Certificate;
37 import java.util.Enumeration;
38 import java.util.Iterator;
39 import java.util.List;
40 import java.util.Properties;
41 import java.util.Vector;
42
43 public class Uther implements Crypto {
44 private static Log log = LogFactory.getLog(Uther.class);
45 protected static CertificateFactory certFact;
46 protected Properties properties = null;
47 protected KeyStore keystore = null;
48
49
50
51 /***
52 * Singleton certificate factory for this Crypto instance.
53 * <p/>
54 *
55 * @return Returns a <code>CertificateFactory</code> to construct
56 * X509 certficates
57 * @throws WSSecurityException
58 */
59 public synchronized CertificateFactory getCertificateFactory() throws WSSecurityException {
60 if (certFact == null) {
61 try {
62 certFact = CertificateFactory.getInstance("X.509");
63 } catch (CertificateException e) {
64 throw new WSSecurityException(WSSecurityException.SECURITY_TOKEN_UNAVAILABLE,
65 "unsupportedCertType");
66 }
67 }
68 return certFact;
69 }
70
71 /***
72 * load a X509Certificate from the input stream.
73 * <p/>
74 *
75 * @param in The <code>InputStream</code> array containg the X509 data
76 * @return Returns a X509 certificate
77 * @throws WSSecurityException
78 */
79 public X509Certificate loadCertificate(InputStream in) throws WSSecurityException {
80 X509Certificate cert = null;
81 try {
82 cert =
83 (X509Certificate) getCertificateFactory().generateCertificate(in);
84 } catch (CertificateException e) {
85 throw new WSSecurityException(WSSecurityException.SECURITY_TOKEN_UNAVAILABLE,
86 "parseError");
87 }
88 return cert;
89 }
90
91 /***
92 * Construct an array of X509Certificate's from the byte array.
93 * <p/>
94 *
95 * @param data The <code>byte</code> array containg the X509 data
96 * @param reverse If set the first certificate in input data will
97 * the last in the array
98 * @return An array of X509 certificates, ordered according to
99 * the reverse flag
100 * @throws WSSecurityException
101 */
102 public X509Certificate[] getX509Certificates(byte[] data, boolean reverse)
103 throws WSSecurityException {
104 InputStream in = new ByteArrayInputStream(data);
105 CertPath path = null;
106 try {
107 path = getCertificateFactory().generateCertPath(in);
108 } catch (CertificateException e) {
109 throw new WSSecurityException(WSSecurityException.SECURITY_TOKEN_UNAVAILABLE,
110 "parseError");
111 }
112 List l = path.getCertificates();
113 X509Certificate[] certs = new X509Certificate[l.size()];
114 Iterator iterator = l.iterator();
115 for (int i = 0; i < l.size(); i++) {
116 certs[(reverse) ? (l.size() - 1 - i) : i] = (X509Certificate) iterator.next();
117 }
118 return certs;
119 }
120
121 /***
122 * get a byte array given an array of X509 certificates.
123 * <p/>
124 *
125 * @param reverse If set the first certificate in the array data will
126 * the last in the byte array
127 * @param certs The certificates to convert
128 * @return The byte array for the certficates ordered according
129 * to the reverse flag
130 * @throws WSSecurityException
131 */
132 public byte[] getCertificateData(boolean reverse, X509Certificate[] certs)
133 throws WSSecurityException {
134 Vector list = new Vector();
135 for (int i = 0; i < certs.length; i++) {
136 if (reverse) {
137 list.insertElementAt(certs[i], 0);
138 } else {
139 list.add(certs[i]);
140 }
141 }
142 try {
143 CertPath path = getCertificateFactory().generateCertPath(list);
144 return path.getEncoded();
145 } catch (CertificateEncodingException e) {
146 throw new WSSecurityException(WSSecurityException.SECURITY_TOKEN_UNAVAILABLE,
147 "encodeError");
148 } catch (CertificateException e) {
149 throw new WSSecurityException(WSSecurityException.SECURITY_TOKEN_UNAVAILABLE,
150 "parseError");
151 }
152 }
153
154 /***
155 * Gets the private key identified by <code>alias</> and <code>password</code>.
156 * <p/>
157 *
158 * @param alias The alias (<code>KeyStore</code>) of the key owner
159 * @param password The password needed to access the private key
160 * @return The private key
161 * @throws Exception
162 */
163 public PrivateKey getPrivateKey(String alias, String password) throws Exception {
164 if (alias == null) {
165 throw new Exception("alias is null");
166 }
167 boolean b = keystore.isKeyEntry(alias);
168 if (!b) {
169 log.error("Cannot find key for alias: " + alias);
170 throw new Exception("Cannot find key for alias: " + alias);
171 }
172 Key keyTmp = keystore.getKey(alias, password.toCharArray());
173 if (!(keyTmp instanceof PrivateKey)) {
174 throw new Exception("Key is not a private key, alias: " + alias);
175 }
176 return (PrivateKey) keyTmp;
177 }
178
179 private Vector splitAndTrim(String inString) {
180 X509NameTokenizer nmTokens = new X509NameTokenizer(inString);
181 Vector vr = new Vector();
182
183 while (nmTokens.hasMoreTokens()) {
184 vr.add(nmTokens.nextToken());
185 }
186 java.util.Collections.sort(vr);
187 return vr;
188 }
189
190 /***
191 * Lookup a X509 Certificate in the keystore according to a given
192 * the issuer of a Certficate.
193 * <p/>
194 * The search gets all alias names of the keystore and gets the certificate chain
195 * for each alias. Then the Issuer fo each certificate of the chain
196 * is compared with the parameters.
197 *
198 * @param issuer The issuer's name for the certificate
199 * @return alias name of the certificate that matches the issuer name
200 * or null if no such certificate was found.
201 */
202 public String getAliasForX509Cert(String issuer)
203 throws WSSecurityException {
204 return getAliasForX509Cert(issuer, null, false);
205 }
206
207 /***
208 * Lookup a X509 Certificate in the keystore according to a given serial number and
209 * the issuer of a Certficate.
210 * <p/>
211 * The search gets all alias names of the keystore and gets the certificate chain
212 * for each alias. Then the SerialNumber and Issuer fo each certificate of the chain
213 * is compared with the parameters.
214 *
215 * @param issuer The issuer's name for the certificate
216 * @param serialNumber The serial number of the certificate from the named issuer
217 * @return alias name of the certificate that matches serialNumber and issuer name
218 * or null if no such certificate was found.
219 */
220 public String getAliasForX509Cert(String issuer, BigInteger serialNumber)
221 throws WSSecurityException {
222 return getAliasForX509Cert(issuer, serialNumber, true);
223 }
224
225
226
227
228
229
230
231
232
233 private String getAliasForX509Cert(String issuer, BigInteger serialNumber,
234 boolean useSerialNumber)
235 throws WSSecurityException {
236 Vector issuerRDN = splitAndTrim(issuer);
237 X509Certificate x509cert = null;
238 Vector certRDN = null;
239 Certificate cert = null;
240
241 try {
242 for (Enumeration e = keystore.aliases(); e.hasMoreElements();) {
243 String alias = (String) e.nextElement();
244 Certificate[] certs = keystore.getCertificateChain(alias);
245 if (certs == null || certs.length == 0) {
246
247 cert = keystore.getCertificate(alias);
248 if (cert == null) {
249 return null;
250 }
251 } else {
252 cert = certs[0];
253 }
254 if (!(cert instanceof X509Certificate)) {
255 continue;
256 }
257 x509cert = (X509Certificate) cert;
258 if (!useSerialNumber ||
259 useSerialNumber && x509cert.getSerialNumber().compareTo(serialNumber) == 0) {
260 certRDN = splitAndTrim(x509cert.getIssuerDN().getName());
261 if (certRDN.equals(issuerRDN)) {
262 return alias;
263 }
264 }
265 }
266 } catch (KeyStoreException e) {
267 throw new WSSecurityException(WSSecurityException.FAILURE,
268 "keystore");
269 }
270 return null;
271 }
272
273 /***
274 * Lookup a X509 Certificate in the keystore according to a given
275 * SubjectKeyIdentifier.
276 * <p/>
277 * The search gets all alias names of the keystore and gets the certificate chain
278 * or certificate for each alias. Then the SKI for each user certificate
279 * is compared with the SKI parameter.
280 *
281 * @param skiBytes The SKI info bytes
282 * @return alias name of the certificate that matches serialNumber and issuer name
283 * or null if no such certificate was found.
284 * @throws WSSecurityException if problems during keystore handling or wrong certificate (no SKI data)
285 */
286
287 public String getAliasForX509Cert(byte[] skiBytes) throws WSSecurityException {
288 Certificate cert = null;
289 boolean found = false;
290
291 try {
292 for (Enumeration e = keystore.aliases(); e.hasMoreElements();) {
293 String alias = (String) e.nextElement();
294 Certificate[] certs = keystore.getCertificateChain(alias);
295 if (certs == null || certs.length == 0) {
296
297 cert = keystore.getCertificate(alias);
298 if (cert == null) {
299 return null;
300 }
301 } else {
302 cert = certs[0];
303 }
304 if (!(cert instanceof X509Certificate)) {
305 continue;
306 }
307 byte[] data = getSKIBytesFromCert((X509Certificate) cert);
308 if (data.length != skiBytes.length) {
309 continue;
310 }
311 for (int ii = 0; ii < data.length; ii++) {
312 if (data[ii] != skiBytes[ii]) {
313 found = false;
314 break;
315 }
316 found = true;
317 }
318 if (found) {
319 return alias;
320 }
321 }
322 } catch (KeyStoreException e) {
323 throw new WSSecurityException(WSSecurityException.FAILURE,
324 "keystore");
325 }
326 return null;
327 }
328
329 /***
330 * Return a X509 Certificate alias in the keystore according to a given Certificate
331 * <p/>
332 *
333 * @param cert The certificate to lookup
334 * @return alias name of the certificate that matches the given certificate
335 * or null if no such certificate was found.
336 */
337
338
339
340
341 public String getAliasForX509Cert(Certificate cert) throws WSSecurityException {
342 try {
343 String alias = keystore.getCertificateAlias(cert);
344 if (alias != null)
345 return alias;
346
347 Enumeration e = keystore.aliases();
348 while (e.hasMoreElements()) {
349 alias = (String) e.nextElement();
350 X509Certificate cert2 = (X509Certificate) keystore.getCertificate(alias);
351 if (cert2.equals(cert)) {
352 return alias;
353 }
354 }
355 } catch (KeyStoreException e) {
356 throw new WSSecurityException(WSSecurityException.FAILURE,
357 "keystore");
358 }
359 return null;
360 }
361
362 /***
363 * Retrieves the alias name of the default certificate which has been
364 * specified as a property. This should be the certificate that is used for
365 * signature and encryption. This alias corresponds to the certificate that
366 * should be used whenever KeyInfo is not poresent in a signed or
367 * an encrypted message. May return null.
368 *
369 * @return alias name of the default X509 certificate
370 */
371 public String getDefaultX509Alias() {
372 return null;
373 }
374
375 /***
376 * Gets the list of certificates for a given alias.
377 * <p/>
378 *
379 * @param alias Lookup certificate chain for this alias
380 * @return Array of X509 certificates for this alias name, or
381 * null if this alias does not exist in the keystore
382 */
383 public X509Certificate[] getCertificates(String alias) throws WSSecurityException {
384 Certificate[] certs = null;
385 ;
386 try {
387 certs = keystore.getCertificateChain(alias);
388 if (certs == null || certs.length == 0) {
389
390 Certificate cert = keystore.getCertificate(alias);
391 if (cert == null) {
392 return null;
393 }
394 certs = new Certificate[]{cert};
395 }
396 } catch (KeyStoreException e) {
397 throw new WSSecurityException(WSSecurityException.FAILURE,
398 "keystore");
399 }
400 X509Certificate[] x509certs = new X509Certificate[certs.length];
401 for (int i = 0; i < certs.length; i++) {
402 x509certs[i] = (X509Certificate) certs[i];
403 }
404 return x509certs;
405 }
406
407 /***
408 * A Hook for subclasses to set the keystore without having to
409 * load it from an <code>InputStream</code>.
410 *
411 * @param ks existing keystore
412 */
413 public void setKeyStore(KeyStore ks) {
414 keystore = ks;
415 }
416
417 /***
418 * Loads the the keystore from an <code>InputStream </code>.
419 * <p/>
420 *
421 * @param input <code>InputStream</code> to read from
422 * @throws CredentialException
423 */
424 public void load(InputStream input) throws CredentialException {
425 if (input == null) {
426 throw new IllegalArgumentException("input stream cannot be null");
427 }
428 try {
429 String provider = properties.getProperty("org.apache.ws.security.crypto.merlin.keystore.provider");
430 if (provider == null || provider.length() == 0) {
431 keystore = KeyStore.getInstance
432 (properties.getProperty("org.apache.ws.security.crypto.merlin.keystore.type",
433 KeyStore.getDefaultType()));
434 } else {
435 keystore = KeyStore.getInstance
436 (properties.getProperty("org.apache.ws.security.crypto.merlin.keystore.type",
437 KeyStore.getDefaultType()), provider);
438 }
439 String password =
440 properties.getProperty("org.apache.ws.security.crypto.merlin.keystore.password",
441 "security");
442 keystore.load(input, (password == null || password.length() == 0) ? new char[0] : password.toCharArray());
443 } catch (IOException e) {
444 e.printStackTrace();
445 throw new CredentialException(3, "ioError00", e);
446 } catch (GeneralSecurityException e) {
447 e.printStackTrace();
448 throw new CredentialException(3, "secError00", e);
449 } catch (Exception e) {
450 e.printStackTrace();
451 throw new CredentialException(-1, "error00", e);
452 }
453 }
454
455 static String SKI_OID = "2.5.29.14";
456
457 /***
458 * Reads the SubjectKeyIdentifier information from the certificate.
459 * <p/>
460 * If the the certificate does not contain a SKI extension then
461 * try to compute the SKI according to RFC3280 using the
462 * SHA-1 hash value of the public key. The second method described
463 * in RFC3280 is not support. Also only RSA public keys are supported.
464 * If we cannot compute the SKI throw a WSSecurityException.
465 *
466 * @param cert The certificate to read SKI
467 * @return The byte array conating the binary SKI data
468 */
469 public byte[] getSKIBytesFromCert(X509Certificate cert)
470 throws WSSecurityException {
471
472
473
474
475
476 byte[] derEncodedValue = cert.getExtensionValue(SKI_OID);
477
478 if (cert.getVersion() < 3 || derEncodedValue == null) {
479 PublicKey key = cert.getPublicKey();
480 if (!(key instanceof RSAPublicKey)) {
481 throw new WSSecurityException(
482 1,
483 "noSKIHandling",
484 new Object[] { "Support for RSA key only" });
485 }
486 byte[] encoded = key.getEncoded();
487
488 byte[] value = new byte[encoded.length - 22];
489 System.arraycopy(encoded, 22, value, 0, value.length);
490 MessageDigest sha;
491 try {
492 sha = MessageDigest.getInstance("SHA-1");
493 } catch (NoSuchAlgorithmException ex) {
494 throw new WSSecurityException(
495 1,
496 "noSKIHandling",
497 new Object[] { "Wrong certificate version (<3) and no SHA1 message digest availabe" });
498 }
499 sha.reset();
500 sha.update(value);
501 return sha.digest();
502 }
503
504 /***
505 * Strip away first four bytes from the DerValue (tag and length of
506 * ExtensionValue OCTET STRING and KeyIdentifier OCTET STRING)
507 */
508 byte abyte0[] = new byte[derEncodedValue.length - 4];
509
510 System.arraycopy(derEncodedValue, 4, abyte0, 0, abyte0.length);
511 return abyte0;
512 }
513
514 public KeyStore getKeyStore() {
515 return this.keystore;
516 }
517
518 /***
519 * Uses the CertPath API to validate a given certificate chain
520 *
521 * @param certs
522 * Certificate chain to validate
523 * @return true if the certificate chain is valid, false otherwise
524 * @throws WSSecurityException
525 */
526 public boolean validateCertPath(X509Certificate[] certs) throws WSSecurityException {
527
528 try {
529
530 java.util.List certList = java.util.Arrays.asList(certs);
531 CertPath path = this.getCertificateFactory().generateCertPath(certList);
532
533
534 PKIXParameters param = new PKIXParameters(this.keystore);
535
536
537 param.setRevocationEnabled(false);
538
539
540 CertPathValidator certPathValidator = CertPathValidator.getInstance("PKIX");
541 certPathValidator.validate(path, param);
542 } catch (NoSuchAlgorithmException ex) {
543 throw new WSSecurityException(WSSecurityException.FAILURE,
544 "certpath",
545 new Object[]{ex.getMessage()},
546 (Throwable) ex);
547 } catch (CertificateException ex) {
548 throw new WSSecurityException(WSSecurityException.FAILURE,
549 "certpath",
550 new Object[]{ex.getMessage()},
551 (Throwable) ex);
552 } catch (InvalidAlgorithmParameterException ex) {
553 throw new WSSecurityException(WSSecurityException.FAILURE,
554 "certpath",
555 new Object[]{ex.getMessage()},
556 (Throwable) ex);
557 } catch (CertPathValidatorException ex) {
558 throw new WSSecurityException(WSSecurityException.FAILURE,
559 "certpath",
560 new Object[]{ex.getMessage()},
561 (Throwable) ex);
562 } catch (KeyStoreException ex) {
563 throw new WSSecurityException(WSSecurityException.FAILURE,
564 "certpath",
565 new Object[]{ex.getMessage()},
566 (Throwable) ex);
567 }
568
569 return true;
570 }
571
572 /***
573 * Lookup X509 Certificates in the keystore according to a given DN of the subject of the certificate
574 * <p/>
575 * The search gets all alias names of the keystore and gets the certificate (chain)
576 * for each alias. Then the DN of the certificate is compared with the parameters.
577 *
578 * @param subjectDN The DN of subject to look for in the keystore
579 * @return Vector with all alias of certificates with the same DN as given in the parameters
580 * @throws WSSecurityException
581 */
582 public String[] getAliasesForDN(String subjectDN) throws WSSecurityException {
583
584
585 Vector aliases = new Vector();
586
587 Certificate cert = null;
588
589
590 Vector subjectRDN = splitAndTrim(subjectDN);
591
592
593 try {
594 for (Enumeration e = keystore.aliases(); e.hasMoreElements();) {
595 String alias = (String) e.nextElement();
596
597 Certificate[] certs = keystore.getCertificateChain(alias);
598 if (certs == null || certs.length == 0) {
599
600 cert = keystore.getCertificate(alias);
601 if (cert == null) {
602 return null;
603 }
604 certs = new Certificate[]{cert};
605 } else {
606 cert = certs[0];
607 }
608 if (cert instanceof X509Certificate) {
609 Vector foundRDN = splitAndTrim(((X509Certificate) cert).getSubjectDN().getName());
610
611 if (subjectRDN.equals(foundRDN)) {
612 aliases.add(alias);
613 }
614 }
615 }
616 } catch (KeyStoreException e) {
617 throw new WSSecurityException(WSSecurityException.FAILURE,
618 "keystore");
619 }
620
621
622 String[] result = new String[aliases.size()];
623 for (int i = 0; i < aliases.size(); i++) result[i] = (String) aliases.elementAt(i);
624
625 return result;
626 }
627 }
628
629