View Javadoc

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      * need to check if "getCertificateChain" also finds certificates that are
227      * used for enryption only, i.e. they may not be signed by a CA
228      * Otherwise we must define a restriction how to use certificate:
229      * each certificate must be signed by a CA or is a self signed Certificate
230      * (this should work as well).
231      * --- remains to be tested in several ways --
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                     // no cert chain, so lets check if getCertificate gives us a  result.
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                     // no cert chain, so lets check if getCertificate gives us a  result.
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      * See comment above
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             // Use brute force search
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                 // no cert chain, so lets check if getCertificate gives us a  result.
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 		 * Gets the DER-encoded OCTET string for the extension value (extnValue)
473 		 * identified by the passed-in oid String. The oid string is represented
474 		 * by a set of positive whole numbers separated by periods.
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 			// remove 22-byte algorithm ID and header
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             // Generate cert path
530             java.util.List certList = java.util.Arrays.asList(certs);
531             CertPath path = this.getCertificateFactory().generateCertPath(certList);
532 
533             // Use the certificates in the keystore as TrustAnchors
534             PKIXParameters param = new PKIXParameters(this.keystore);
535 
536             // Do not check a revocation list
537             param.setRevocationEnabled(false);
538 
539             // Verify the trust path using the above settings            
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         // Store the aliases found
585         Vector aliases = new Vector();
586 
587         Certificate cert = null;
588         
589         // The DN to search the keystore for
590         Vector subjectRDN = splitAndTrim(subjectDN);
591 
592         // Look at every certificate in the keystore
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                     // no cert chain, so lets check if getCertificate gives us a  result.
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         // Convert the vector into an array
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