83122db395fd5b0f5e1739d62a9dda9c4abe6b83
[java-idp.git] / src / edu / internet2 / middleware / shibboleth / common / Credentials.java
1 /*
2  * Copyright [2005] [University Corporation for Advanced Internet Development, Inc.]
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 package edu.internet2.middleware.shibboleth.common;
18
19 import java.io.BufferedInputStream;
20 import java.io.BufferedReader;
21 import java.io.ByteArrayInputStream;
22 import java.io.IOException;
23 import java.io.InputStream;
24 import java.io.InputStreamReader;
25 import java.io.StringReader;
26 import java.security.AlgorithmParameters;
27 import java.security.GeneralSecurityException;
28 import java.security.KeyFactory;
29 import java.security.KeyStore;
30 import java.security.KeyStoreException;
31 import java.security.MessageDigest;
32 import java.security.NoSuchAlgorithmException;
33 import java.security.PrivateKey;
34 import java.security.Provider;
35 import java.security.PublicKey;
36 import java.security.Security;
37 import java.security.Signature;
38 import java.security.UnrecoverableKeyException;
39 import java.security.cert.Certificate;
40 import java.security.cert.CertificateException;
41 import java.security.cert.CertificateFactory;
42 import java.security.cert.X509Certificate;
43 import java.security.spec.DSAPrivateKeySpec;
44 import java.security.spec.InvalidKeySpecException;
45 import java.security.spec.PKCS8EncodedKeySpec;
46 import java.security.spec.RSAPrivateCrtKeySpec;
47 import java.util.ArrayList;
48 import java.util.Arrays;
49 import java.util.Enumeration;
50 import java.util.Hashtable;
51 import java.util.List;
52
53 import javax.crypto.BadPaddingException;
54 import javax.crypto.Cipher;
55 import javax.crypto.EncryptedPrivateKeyInfo;
56 import javax.crypto.SecretKey;
57 import javax.crypto.SecretKeyFactory;
58 import javax.crypto.spec.IvParameterSpec;
59 import javax.crypto.spec.PBEKeySpec;
60 import javax.crypto.spec.SecretKeySpec;
61
62 import org.apache.log4j.Logger;
63 import org.bouncycastle.asn1.ASN1InputStream;
64 import org.bouncycastle.asn1.DEREncodable;
65 import org.bouncycastle.asn1.DERInteger;
66 import org.bouncycastle.asn1.DERObject;
67 import org.bouncycastle.asn1.DERObjectIdentifier;
68 import org.bouncycastle.asn1.DEROctetString;
69 import org.bouncycastle.asn1.DERSequence;
70 import org.bouncycastle.asn1.util.ASN1Dump;
71 import org.bouncycastle.util.encoders.Base64;
72 import org.w3c.dom.Element;
73 import org.w3c.dom.Node;
74 import org.w3c.dom.NodeList;
75
76 /**
77  * Uses {@link CredentialResolver}implementations to create {@link Credential}s.
78  * 
79  * @author Walter Hoehn
80  */
81 public class Credentials {
82
83         public static final String credentialsNamespace = "urn:mace:shibboleth:credentials:1.0";
84
85         private static Logger log = Logger.getLogger(Credentials.class.getName());
86         private Hashtable<String, Credential> data = new Hashtable<String, Credential>();
87         private boolean singleMode = false;
88
89         /**
90          * Creates credentials based on XML configuration.
91          * 
92          * @param e
93          *            DOM representation of credentials configuration
94          */
95         public Credentials(Element e) {
96
97                 if (e != null && e.getLocalName().equals("Credential")) {
98                         singleMode = true;
99                 } else if (e == null || !e.getLocalName().equals("Credentials")) { throw new IllegalArgumentException(); }
100
101                 NodeList resolverNodes = e.getChildNodes();
102                 if (resolverNodes.getLength() <= 0) {
103                         log.error("Credentials configuration inclues no Credential Resolver definitions.");
104                         throw new IllegalArgumentException("Cannot load credentials.");
105                 }
106
107                 for (int i = 0; resolverNodes.getLength() > i; i++) {
108                         if (resolverNodes.item(i).getNodeType() == Node.ELEMENT_NODE) {
109                                 try {
110                                         String credentialId = ((Element) resolverNodes.item(i)).getAttribute("Id");
111                                         if (credentialId == null || credentialId.equals("")) {
112                                                 if (singleMode) {
113                                                         credentialId = "SINGLE";
114                                                 } else {
115                                                         log
116                                                                         .error("Found credential that was not labeled with a unique \"Id\" attribute. Skipping.");
117                                                 }
118                                         }
119
120                                         if (data.containsKey(credentialId)) {
121                                                 log.error("Duplicate credential id (" + credentialId + ") found. Skipping");
122                                         }
123
124                                         log.info("Found credential (" + credentialId + "). Loading...");
125                                         data.put(credentialId, CredentialFactory.loadCredential((Element) resolverNodes.item(i)));
126
127                                 } catch (CredentialFactoryException cfe) {
128                                         log.error("Could not load credential, skipping: " + cfe.getMessage());
129                                 } catch (ClassCastException cce) {
130                                         log.error("Problem realizing credential configuration " + cce.getMessage());
131                                 }
132                         }
133                 }
134         }
135
136         public boolean containsCredential(String identifier) {
137
138                 return data.containsKey(identifier);
139         }
140
141         public Credential getCredential(String identifier) {
142
143                 // Default if there is only one credential
144                 if ((identifier == null || identifier.equals("")) && data.size() == 1) { return (Credential) data.values()
145                                 .iterator().next(); }
146
147                 return data.get(identifier);
148         }
149
150         public Credential getCredential() {
151
152                 return data.values().iterator().next();
153         }
154
155         static class CredentialFactory {
156
157                 private static Logger log = Logger.getLogger(CredentialFactory.class.getName());
158
159                 public static Credential loadCredential(Element e) throws CredentialFactoryException {
160
161                         if (e.getLocalName().equals("KeyInfo")) { return new KeyInfoCredentialResolver().loadCredential(e); }
162
163                         if (e.getLocalName().equals("FileResolver")) { return new FileCredentialResolver().loadCredential(e); }
164
165                         if (e.getLocalName().equals("KeyStoreResolver")) { return new KeystoreCredentialResolver()
166                                         .loadCredential(e); }
167
168                         if (e.getLocalName().equals("CustomResolver")) { return new CustomCredentialResolver().loadCredential(e); }
169
170                         log.error("Unrecognized Credential Resolver type: " + e.getTagName());
171                         throw new CredentialFactoryException("Failed to load credential.");
172                 }
173
174         }
175
176 }
177
178 class KeyInfoCredentialResolver implements CredentialResolver {
179
180         private static Logger log = Logger.getLogger(KeyInfoCredentialResolver.class.getName());
181
182         KeyInfoCredentialResolver() throws CredentialFactoryException {
183
184                 log.error("Credential Resolver (KeyInfoCredentialResolver) not implemented");
185                 throw new CredentialFactoryException("Failed to load credential.");
186         }
187
188         public Credential loadCredential(Element e) {
189
190                 return null;
191         }
192 }
193
194 /**
195  * Loads a credential from a file. Supports DER, PEM, encrypted PEM, PKCS8, and encrypted PKCS8 for RSA and DSA.
196  * 
197  * @author Walter Hoehn
198  * @author Chad La Joie
199  */
200
201 class FileCredentialResolver implements CredentialResolver {
202
203         private static Logger log = Logger.getLogger(FileCredentialResolver.class.getName());
204
205         /**
206          * Reads a private key, and certificate information, specified by the configuration element, and creates a security
207          * credential which can then be used for operations such a assertion signing. DER and PEM encoded keys (both
208          * none-encrypted and encrypted) and PEM encoded certificated are supported.
209          * 
210          * @param e
211          *            the credentials configuration element
212          * @throws CredentialFactoryException
213          *             thrown if an error is encountered during any step of the credential creation, exact error specified
214          *             in exception message
215          */
216         public Credential loadCredential(Element e) throws CredentialFactoryException {
217
218                 if (!e.getLocalName().equals("FileResolver")) {
219                         log.error("Invalid Credential Resolver configuration: expected <FileResolver> .");
220                         throw new CredentialFactoryException("Failed to initialize Credential Resolver.");
221                 }
222
223                 PrivateKey key = getPrivateKey(e);
224                 if (key == null) {
225                         log.error("Failed to load private key.");
226                         throw new CredentialFactoryException("Failed to load private key.");
227                 }
228
229                 List<Certificate> certChain = getCertificateChain(e, key);
230
231                 Credential credential = new Credential(((X509Certificate[]) certChain.toArray(new X509Certificate[0])), key);
232                 if (log.isDebugEnabled()) {
233                         log.debug("Credential created");
234                 }
235
236                 return credential;
237         }
238
239         /**
240          * Gets the private key for the credentials. Keys can be in either DER or PEM format and either password protected
241          * (encrypted) or not.
242          * 
243          * @param credentialConfigElement
244          *            the credential configuration element
245          * @return the private key
246          * @throws CredentialFactoryException
247          *             thrown if the private key file can not be found, the private key format can not be determined, or
248          *             some IO error occurs reading from the private key file
249          */
250         private PrivateKey getPrivateKey(Element credentialConfigElement) throws CredentialFactoryException {
251
252                 String keyPath = getKeyPath(credentialConfigElement);
253                 String password = getKeyPassword(credentialConfigElement);
254
255                 InputStream keyStream = null;
256                 try {
257                         if (log.isDebugEnabled()) {
258                                 log.debug("Attempting to load private key from file " + keyPath);
259                         }
260                         keyStream = new ShibResource(keyPath, this.getClass()).getInputStream();
261                         int encoding = getKeyEncodingFormat(credentialConfigElement, keyStream);
262                         EncodedKey encodedKey;
263
264                         switch (encoding) {
265                                 case EncodedKey.DER_ENCODING :
266                                         if (log.isDebugEnabled()) {
267                                                 log.debug("Private key in file " + keyPath + " determined to be DER encoded");
268                                         }
269                                         encodedKey = new DERKey(keyStream, password);
270                                         return encodedKey.getPrivateKey();
271
272                                 case EncodedKey.PEM_ENCODING :
273                                         if (log.isDebugEnabled()) {
274                                                 log.debug("Private key in file " + keyPath + " determined to be PEM encoded");
275                                         }
276                                         encodedKey = new PEMKey(keyStream, password);
277                                         return encodedKey.getPrivateKey();
278
279                                 default :
280                                         log.error("Unable to determine format of private key specified in file " + keyPath);
281                                         throw new CredentialFactoryException("Unable to determine private key format.");
282                         }
283                 } catch (IOException ioe) {
284                         log.error("Could not load credential from specified file (" + keyPath + "): " + ioe);
285                         throw new CredentialFactoryException("Unable to load private key.");
286                 } finally {
287                         if (keyStream != null) {
288                                 try {
289                                         keyStream.close();
290                                 } catch (IOException e1) {
291                                         // ignore
292                                 }
293                         }
294                 }
295         }
296
297         /**
298          * Gets the complete certificate chain specified by the configuration element. Currently only X.509 certificates are
299          * supported.
300          * 
301          * @param credentialConfigElement
302          *            the Credentials configuration element
303          * @param key
304          *            the private key for the certificate
305          * @return the certificate chain as a list of certificates
306          * @throws CredentialFactoryException
307          *             thrown if the certificate files is not found, can not be parsed, or an IOException occurs whils
308          *             reading the file
309          */
310         private List<Certificate> getCertificateChain(Element credentialConfigElement, PrivateKey key)
311                         throws CredentialFactoryException {
312
313                 List<Certificate> certChain = new ArrayList<Certificate>();
314                 String certPath = getCertPath(credentialConfigElement);
315
316                 if (certPath == null || certPath.equals("")) {
317                         if (log.isInfoEnabled()) {
318                                 log.info("No certificates specified.");
319                         }
320                 } else {
321                         if (log.isDebugEnabled()) {
322                                 log.debug("Certificate Path: (" + certPath + ").");
323                         }
324
325                         // A placeholder in case we want to make this configurable
326                         String certType = "X.509";
327
328                         // The loading code should work for other types, but the chain
329                         // construction code would break
330                         if (!certType.equals("X.509")) {
331                                 log.error("File credential resolver only supports the X.509 certificates.");
332                                 throw new CredentialFactoryException("Only X.509 certificates are supported");
333                         }
334
335                         ArrayList<Certificate> allCerts = new ArrayList<Certificate>();
336
337                         try {
338                                 Certificate[] certsFromPath = loadCertificates(new ShibResource(certPath, this.getClass())
339                                                 .getInputStream(), certType);
340
341                                 allCerts.addAll(Arrays.asList(certsFromPath));
342
343                                 // Find the end-entity cert first
344                                 if (certsFromPath == null || certsFromPath.length == 0) {
345                                         log.error("File at (" + certPath + ") did not contain any valid certificates.");
346                                         throw new CredentialFactoryException("File did not contain any valid certificates.");
347                                 }
348
349                                 if (certsFromPath.length == 1) {
350                                         if (log.isDebugEnabled()) {
351                                                 log.debug("Certificate file only contains 1 certificate.");
352                                                 log.debug("Ensuring that it matches the private key.");
353                                         }
354                                         if (!isMatchingKey(certsFromPath[0].getPublicKey(), key)) {
355                                                 log.error("Certificate file " + certPath
356                                                                 + "only contained one certificate and it does not match the private key.");
357                                                 throw new CredentialFactoryException(
358                                                                 "No certificate in chain that matches specified private key");
359                                         }
360                                         certChain.add(certsFromPath[0]);
361                                         if (log.isDebugEnabled()) {
362                                                 log.debug("Successfully identified the end entity cert: "
363                                                                 + ((X509Certificate) certChain.get(0)).getSubjectDN());
364                                         }
365
366                                 } else {
367                                         if (log.isDebugEnabled()) {
368                                                 log.debug("Certificate file contains multiple certificates.");
369                                                 log
370                                                                 .debug("Trying to determine the end-entity cert by the matching certificates against the private key.");
371                                         }
372                                         for (int i = 0; certsFromPath.length > i; i++) {
373                                                 if (isMatchingKey(certsFromPath[i].getPublicKey(), key)) {
374                                                         if (log.isDebugEnabled()) {
375                                                                 log.debug("Found matching end cert: "
376                                                                                 + ((X509Certificate) certsFromPath[i]).getSubjectDN());
377                                                         }
378                                                         certChain.add(certsFromPath[i]);
379                                                 }
380                                         }
381                                         if (certChain.size() < 1) {
382                                                 log.error("Certificate file " + certPath
383                                                                 + "only contained multiple certificates and none matched the private key.");
384                                                 throw new CredentialFactoryException(
385                                                                 "No certificate in chain that matches specified private key");
386                                         }
387                                         if (certChain.size() > 1) {
388                                                 log.error("More than one certificate in chain that matches specified private key");
389                                                 throw new CredentialFactoryException(
390                                                                 "More than one certificate in chain that matches specified private key");
391                                         }
392                                         if (log.isDebugEnabled()) {
393                                                 log.debug("Successfully identified the end entity cert: "
394                                                                 + ((X509Certificate) certChain.get(0)).getSubjectDN());
395                                         }
396                                 }
397
398                                 // Now load additional certs and construct a chain
399                                 String[] caPaths = getCAPaths(credentialConfigElement);
400                                 if (caPaths != null && caPaths.length > 0) {
401                                         if (log.isDebugEnabled()) {
402                                                 log
403                                                                 .debug("Attempting to load certificates from (" + caPaths.length
404                                                                                 + ") CA certificate files.");
405                                         }
406                                         for (int i = 0; i < caPaths.length; i++) {
407                                                 allCerts.addAll(Arrays.asList(loadCertificates(new ShibResource(caPaths[i], this.getClass())
408                                                                 .getInputStream(), certType)));
409                                         }
410                                 }
411
412                                 if (log.isDebugEnabled()) {
413                                         log.debug("Attempting to construct a certificate chain.");
414                                 }
415                                 walkChain((X509Certificate[]) allCerts.toArray(new X509Certificate[0]), certChain);
416
417                                 if (log.isDebugEnabled()) {
418                                         log.debug("Verifying that each link in the cert chain is signed appropriately");
419                                 }
420                                 for (int i = 0; i < certChain.size() - 1; i++) {
421                                         PublicKey pubKey = ((X509Certificate) certChain.get(i + 1)).getPublicKey();
422                                         try {
423                                                 ((X509Certificate) certChain.get(i)).verify(pubKey);
424                                         } catch (Exception se) {
425                                                 log.error("Certificate chain cannot be verified: " + se);
426                                                 throw new CredentialFactoryException("Certificate chain cannot be verified: " + se);
427                                         }
428                                 }
429                                 if (log.isDebugEnabled()) {
430                                         log.debug("All signatures verified. Certificate chain creation successful.");
431                                 }
432
433                                 if (log.isInfoEnabled()) {
434                                         log.info("Successfully loaded certificates.");
435                                 }
436                         } catch (IOException ioe) {
437                                 log.error("Could not load resource from specified location (" + certPath + "): " + ioe);
438                                 throw new CredentialFactoryException("Unable to load certificates.");
439                         }
440                 }
441
442                 return certChain;
443         }
444
445         /**
446          * Determines whether the key is PEM or DER encoded.
447          * 
448          * @param e
449          *            the file credential resolver configuration element
450          * @param keyStream
451          *            an input stream reading the private key
452          * @return the encoding format of the key
453          * @throws CredentialFactoryException
454          *             thrown if the key format can not be determined or the key can not be read
455          */
456         private int getKeyEncodingFormat(Element e, InputStream keyStream) throws CredentialFactoryException {
457
458                 NodeList keyElements = e.getElementsByTagNameNS(Credentials.credentialsNamespace, "Key");
459                 if (keyElements.getLength() < 1) {
460                         log.error("No private key specified in file credential resolver");
461                         throw new CredentialFactoryException("File Credential Resolver requires a <Key> specification.");
462                 }
463
464                 if (keyElements.getLength() > 1) {
465                         log.error("Multiple Key path specifications, using first.");
466                 }
467
468                 String formatStr = ((Element) keyElements.item(0)).getAttribute("format");
469
470                 if (formatStr != null && formatStr.length() > 0) {
471                         if (formatStr.equals("PEM")) {
472                                 return EncodedKey.PEM_ENCODING;
473                         } else if (formatStr.equals("DER")) {
474                                 return EncodedKey.DER_ENCODING;
475                         } else if (formatStr.equals("PKCS12")) {
476                                 log.error("PKCS12 private keys are not yet supported");
477                                 return -1;
478                         }
479                 }
480
481                 if (log.isInfoEnabled()) {
482                         log
483                                         .info("Private key format was not specified in file credential resolver configuration, attempting to auto-detect it.");
484                 }
485                 try {
486                         // Need to mark the stream and then reset it, after getting the
487                         // first byte so that the private key decoder starts reading at
488                         // the correct position
489                         keyStream.mark(2);
490                         int firstByte = keyStream.read();
491                         keyStream.reset();
492
493                         // PEM encoded keys must start with a "-", a decimal value of 45
494                         if (firstByte == 45) { return EncodedKey.PEM_ENCODING; }
495
496                         // DER encoded keys must start with a decimal value of 48
497                         if (firstByte == 48) { return EncodedKey.DER_ENCODING; }
498
499                         // Can not determine type
500                         return -1;
501                 } catch (IOException ioe) {
502                         throw new CredentialFactoryException(
503                                         "Could not determine the type of private key for file credential resolver.");
504                 }
505         }
506
507         /**
508          * Gets the private key password from the Credentials configuration element if one exists.
509          * 
510          * @param e
511          *            the credentials configuration element
512          * @return the password if one is given or an empty string if one is not
513          * @throws CredentialFactoryException
514          *             thrown if no Key element is present in the configuration
515          */
516         private String getKeyPassword(Element e) throws CredentialFactoryException {
517
518                 NodeList keyElements = e.getElementsByTagNameNS(Credentials.credentialsNamespace, "Key");
519                 if (keyElements.getLength() < 1) {
520                         log.error("Key not specified.");
521                         throw new CredentialFactoryException("File Credential Resolver requires a <Key> specification.");
522                 }
523
524                 if (keyElements.getLength() > 1) {
525                         log.error("Multiple Key path specifications, using first.");
526                 }
527
528                 String password = ((Element) keyElements.item(0)).getAttribute("password");
529                 if (password == null) {
530                         password = "";
531                 }
532                 return password;
533         }
534
535         /**
536          * Gets the certificate path from the Credentials configuration element. If multiple paths are specified only the
537          * first one is used.
538          * 
539          * @param e
540          *            the credentials configuration element
541          * @return the certificate path, or null if non is specificed
542          * @throws CredentialFactoryException
543          *             thrown if no Path element is given or it's empty
544          */
545         private String getCertPath(Element e) throws CredentialFactoryException {
546
547                 NodeList certificateElements = e.getElementsByTagNameNS(Credentials.credentialsNamespace, "Certificate");
548                 if (certificateElements.getLength() < 1) {
549                         if (log.isDebugEnabled()) {
550                                 log.debug("No <Certificate> element found.");
551                         }
552                         return null;
553                 }
554
555                 NodeList pathElements = ((Element) certificateElements.item(0)).getElementsByTagNameNS(
556                                 Credentials.credentialsNamespace, "Path");
557
558                 if (pathElements.getLength() < 1) {
559                         log.error("Certificate path not specified.");
560                         throw new CredentialFactoryException(
561                                         "File Credential Resolver requires a <Certificate><Path/></Certificate> specification, none was specified.");
562                 }
563
564                 if (pathElements.getLength() > 1) {
565                         log.error("Multiple Certificate path specifications, using first.");
566                 }
567                 Node tnode = pathElements.item(0).getFirstChild();
568                 String path = null;
569                 if (tnode != null && tnode.getNodeType() == Node.TEXT_NODE) {
570                         path = tnode.getNodeValue();
571                 }
572                 if (path == null || path.equals("")) {
573                         log.error("Certificate path was empty.");
574                         throw new CredentialFactoryException(
575                                         "File Credential Resolver requires a <Certificate><Path/></Certificate> specification, the specified one was empty.");
576                 }
577
578                 return path;
579         }
580
581         /**
582          * Get the CA certificate paths from the Credentials configuration element. Paths should be delimited with the
583          * operating system path delimiter. If multiple Certificate elements are found only the first is used.
584          * 
585          * @param e
586          *            the credentials configuration element
587          * @return an array of CA certificate paths, or null if no certificate path was specified
588          * @throws CredentialFactoryException
589          *             no certificate path was specified
590          */
591         private String[] getCAPaths(Element e) throws CredentialFactoryException {
592
593                 NodeList certificateElements = e.getElementsByTagNameNS(Credentials.credentialsNamespace, "Certificate");
594                 if (certificateElements.getLength() < 1) {
595                         log.error("Certificate not specified.");
596                         throw new CredentialFactoryException("File Credential Resolver requires a <Certificate> specification.");
597                 }
598                 if (certificateElements.getLength() > 1) {
599                         log.error("Multiple Certificate path specifications, using first.");
600                 }
601
602                 NodeList pathElements = ((Element) certificateElements.item(0)).getElementsByTagNameNS(
603                                 Credentials.credentialsNamespace, "CAPath");
604                 if (pathElements.getLength() < 1) {
605                         if (log.isDebugEnabled()) {
606                                 log.debug("No CA Certificate paths specified.");
607                         }
608                         return null;
609                 }
610                 ArrayList<String> paths = new ArrayList<String>();
611                 for (int i = 0; i < pathElements.getLength(); i++) {
612                         Node tnode = pathElements.item(i).getFirstChild();
613                         String path = null;
614                         if (tnode != null && tnode.getNodeType() == Node.TEXT_NODE) {
615                                 path = tnode.getNodeValue();
616                         }
617                         if (path != null && !(path.equals(""))) {
618                                 paths.add(path);
619                         }
620                         if (paths.isEmpty()) {
621                                 if (log.isDebugEnabled()) {
622                                         log.debug("No CA Certificate paths specified.");
623                                 }
624                         }
625                 }
626                 return (String[]) paths.toArray(new String[0]);
627         }
628
629         /**
630          * Gets the path to the private key from the Credentials configuration element. If more than one is specified only
631          * the first one is used.
632          * 
633          * @param e
634          *            the credentials configuration element
635          * @return path to the private key
636          * @throws CredentialFactoryException
637          *             thrown if no path is specified or it's null.
638          */
639         private String getKeyPath(Element e) throws CredentialFactoryException {
640
641                 NodeList keyElements = e.getElementsByTagNameNS(Credentials.credentialsNamespace, "Key");
642                 if (keyElements.getLength() < 1) {
643                         log.error("Key not specified.");
644                         throw new CredentialFactoryException("File Credential Resolver requires a <Key> specification.");
645                 }
646                 if (keyElements.getLength() > 1) {
647                         log.error("Multiple Key path specifications, using first.");
648                 }
649
650                 NodeList pathElements = ((Element) keyElements.item(0)).getElementsByTagNameNS(
651                                 Credentials.credentialsNamespace, "Path");
652                 if (pathElements.getLength() < 1) {
653                         log.error("Key path not specified.");
654                         throw new CredentialFactoryException(
655                                         "File Credential Resolver requires a <Key><Path/></Certificate> specification.");
656                 }
657                 if (pathElements.getLength() > 1) {
658                         log.error("Multiple Key path specifications, using first.");
659                 }
660                 Node tnode = pathElements.item(0).getFirstChild();
661                 String path = null;
662                 if (tnode != null && tnode.getNodeType() == Node.TEXT_NODE) {
663                         path = tnode.getNodeValue();
664                 }
665                 if (path == null || path.equals("")) {
666                         log.error("Key path is empty.");
667                         throw new CredentialFactoryException(
668                                         "File Credential Resolver requires a <Key><Path/></Certificate> specification.");
669                 }
670                 return path;
671         }
672
673         /**
674          * Loads a specified bundle of certs individually and returns an array of {@link Certificate}objects. This is
675          * needed because the standard {@link CertificateFactory#getCertificates(InputStream)}method bails out when it has
676          * trouble loading any cert and cannot handle "comments".
677          */
678         private Certificate[] loadCertificates(InputStream inStream, String certType) throws CredentialFactoryException {
679
680                 ArrayList<Certificate> certificates = new ArrayList<Certificate>();
681
682                 try {
683                         CertificateFactory certFactory = CertificateFactory.getInstance(certType);
684
685                         BufferedReader in = new BufferedReader(new InputStreamReader(inStream));
686                         String str;
687                         boolean insideCert = false;
688                         StringBuffer rawCert = null;
689                         while ((str = in.readLine()) != null) {
690
691                                 if (insideCert) {
692                                         rawCert.append(str);
693                                         rawCert.append(System.getProperty("line.separator"));
694                                         if (str.matches("^.*-----END CERTIFICATE-----.*$")) {
695                                                 insideCert = false;
696                                                 try {
697                                                         Certificate cert = certFactory.generateCertificate(new ByteArrayInputStream(rawCert
698                                                                         .toString().getBytes()));
699                                                         certificates.add(cert);
700                                                 } catch (CertificateException ce) {
701                                                         log.warn("Failed to load a certificate from the certificate bundle: " + ce);
702                                                         if (log.isDebugEnabled()) {
703                                                                 if (log.isDebugEnabled()) {
704                                                                         log.debug("Dump of bad certificate: " + System.getProperty("line.separator")
705                                                                                         + rawCert.toString());
706                                                                 }
707                                                         }
708                                                 }
709                                                 continue;
710                                         }
711                                 } else if (str.matches("^.*-----BEGIN CERTIFICATE-----.*$")) {
712                                         insideCert = true;
713                                         rawCert = new StringBuffer();
714                                         rawCert.append(str);
715                                         rawCert.append(System.getProperty("line.separator"));
716                                 }
717                         }
718                         in.close();
719                 } catch (IOException p) {
720                         log.error("Could not load resource from specified location: " + p);
721                         throw new CredentialFactoryException("Unable to load certificates.");
722                 } catch (CertificateException p) {
723                         log.error("Problem loading certificate factory: " + p);
724                         throw new CredentialFactoryException("Unable to load certificates.");
725                 }
726
727                 return (Certificate[]) certificates.toArray(new Certificate[0]);
728         }
729
730         /**
731          * Given an ArrayList containing a base certificate and an array of unordered certificates, populates the ArrayList
732          * with an ordered certificate chain, based on subject and issuer.
733          * 
734          * @param chainSource
735          *            array of certificates to pull from
736          * @param chainDest
737          *            ArrayList containing base certificate
738          * @throws InvalidCertificateChainException
739          *             thrown if a chain cannot be constructed from the specified elements
740          */
741         protected void walkChain(X509Certificate[] chainSource, List<Certificate> chainDest)
742                         throws CredentialFactoryException {
743
744                 X509Certificate currentCert = (X509Certificate) chainDest.get(chainDest.size() - 1);
745                 if (currentCert.getSubjectDN().equals(currentCert.getIssuerDN())) {
746                         if (log.isDebugEnabled()) {
747                                 log.debug("Found self-signed root cert: " + currentCert.getSubjectDN());
748                         }
749                         return;
750                 } else {
751                         for (int i = 0; chainSource.length > i; i++) {
752                                 if (currentCert.getIssuerDN().equals(chainSource[i].getSubjectDN())) {
753                                         chainDest.add(chainSource[i]);
754                                         walkChain(chainSource, chainDest);
755                                         return;
756                                 }
757                         }
758                         if (log.isDebugEnabled()) {
759                                 log.debug("Certificate chain is incomplete.");
760                         }
761                 }
762         }
763
764         /**
765          * Boolean indication of whether a given private key and public key form a valid keypair.
766          * 
767          * @param pubKey
768          *            the public key
769          * @param privKey
770          *            the private key
771          */
772         protected boolean isMatchingKey(PublicKey pubKey, PrivateKey privKey) {
773
774                 try {
775                         String controlString = "asdf";
776                         if (log.isDebugEnabled()) {
777                                 log.debug("Checking for matching private key/public key pair");
778                         }
779
780                         Signature signature = null;
781                         try {
782                                 signature = Signature.getInstance(privKey.getAlgorithm());
783                         } catch (NoSuchAlgorithmException nsae) {
784                                 if (log.isDebugEnabled()) {
785                                         log.debug("No provider for (RSA) signature, attempting (MD5withRSA).");
786                                 }
787                                 if (privKey.getAlgorithm().equals("RSA")) {
788                                         signature = Signature.getInstance("MD5withRSA");
789                                 } else {
790                                         throw nsae;
791                                 }
792                         }
793                         signature.initSign(privKey);
794                         signature.update(controlString.getBytes());
795                         byte[] sigBytes = signature.sign();
796                         signature.initVerify(pubKey);
797                         signature.update(controlString.getBytes());
798                         if (signature.verify(sigBytes)) {
799                                 if (log.isDebugEnabled()) {
800                                         log.debug("Found match.");
801                                 }
802                                 return true;
803                         }
804                 } catch (Exception e) {
805                         log.warn(e);
806                 }
807                 if (log.isDebugEnabled()) {
808                         log.debug("This pair does not match.");
809                 }
810                 return false;
811         }
812
813         /**
814          * Auto-enlarging container for bytes.
815          */
816         // Sure makes you wish bytes were first class objects.
817         private class ByteContainer {
818
819                 private byte[] buffer;
820                 private int cushion;
821                 private int currentSize = 0;
822
823                 private ByteContainer(int initSize, int growBy) {
824
825                         buffer = new byte[initSize];
826                         this.cushion = growBy;
827                 }
828
829                 private void grow() {
830
831                         int newSize = currentSize + cushion;
832                         byte[] b = new byte[newSize];
833                         int toCopy = Math.min(currentSize, newSize);
834                         int i;
835                         for (i = 0; i < toCopy; i++) {
836                                 b[i] = buffer[i];
837                         }
838                         buffer = b;
839                 }
840
841                 /**
842                  * Returns an array of the bytes in the container.
843                  * <p>
844                  */
845
846                 private byte[] toByteArray() {
847
848                         byte[] b = new byte[currentSize];
849                         System.arraycopy(buffer, 0, b, 0, currentSize);
850                         return b;
851                 }
852
853                 /**
854                  * Add one byte to the end of the container.
855                  */
856
857                 private void append(byte b) {
858
859                         if (currentSize == buffer.length) {
860                                 grow();
861                         }
862                         buffer[currentSize] = b;
863                         currentSize++;
864                 }
865
866         }
867
868         /**
869          * Abstract class representing private keys encoded in formats like PEM and DER.
870          * 
871          * @author Chad La Joie
872          */
873         private abstract class EncodedKey {
874
875                 /**
876                  * DER encoded key
877                  */
878                 public static final int DER_ENCODING = 0;
879
880                 /**
881                  * PEM encoded key
882                  */
883                 public static final int PEM_ENCODING = 1;
884
885                 /**
886                  * OID for DSA keys
887                  */
888                 public final static String DSAKey_OID = "1.2.840.10040.4.1";
889
890                 /**
891                  * OID for RSA keys
892                  */
893                 public final static String RSAKey_OID = "1.2.840.113549.1.1.1";
894
895                 /**
896                  * PKCS8 key format
897                  */
898                 public final static int PKCS8 = 0;
899
900                 /**
901                  * RSA key format
902                  */
903                 public final static int RSA = 1;
904
905                 /**
906                  * DSA key format
907                  */
908                 public final static int DSA = 2;
909
910                 /**
911                  * Key encryption algorithim DES-CDC
912                  */
913                 public final static int DES_CBC = 0;
914
915                 /**
916                  * Key encryption algorithim DES-EDE3-CBC
917                  */
918                 public final static int DES_EDE3_CBC = 1;
919
920                 /**
921                  * Format of the PEM encoded key
922                  */
923                 private int format = -1;
924
925                 /**
926                  * Is the key encrypted?
927                  */
928                 private boolean encrypted;
929
930                 /**
931                  * Password for the encrypted key
932                  */
933                 private String keyPassword;
934
935                 /**
936                  * Encryption algorithim used for this key
937                  */
938                 private int encAlgo = -1;
939
940                 /**
941                  * Initialization vector for the encryption algorithim
942                  */
943                 private String initVector = "";
944
945                 /**
946                  * DER encoded key
947                  */
948                 private byte[] keyBytes;
949
950                 /**
951                  * Gets the format (PKCS8, RSA, DSA) of the key.
952                  * 
953                  * @return format of the key
954                  */
955                 public int getFormat() {
956
957                         return format;
958                 }
959
960                 /**
961                  * Sets the format (PKCS8, RSA, DSA) of the key.
962                  * 
963                  * @param format
964                  *            the format of the key
965                  */
966                 public void setFormat(int format) {
967
968                         this.format = format;
969                 }
970
971                 /**
972                  * Gets whether this PEM key is encrypted.
973                  * 
974                  * @return true if this key is encrypted, false if not
975                  */
976                 public boolean isEncrypted() {
977
978                         return encrypted;
979                 }
980
981                 /**
982                  * Sets whether the key is encrypted.
983                  * 
984                  * @param encrypted
985                  *            whether the key is encrypted
986                  */
987                 public void setEncrypted(boolean encrypted) {
988
989                         this.encrypted = encrypted;
990                 }
991
992                 /**
993                  * Gets the password to decrypt this key
994                  * 
995                  * @return the password to decrypt this key
996                  */
997                 public String getEncryptionPassword() {
998
999                         return keyPassword;
1000                 }
1001
1002                 /**
1003                  * Sets the password to decrypt this key
1004                  * 
1005                  * @param keyPassword
1006                  *            the password to decrypt this key
1007                  */
1008                 public void setEncryptionPassword(String keyPassword) {
1009
1010                         this.keyPassword = keyPassword;
1011                 }
1012
1013                 /**
1014                  * Gets the encryption algorithim used to encrypt the private key.
1015                  * 
1016                  * @return the encryption algorithim used to encrypt the private key
1017                  */
1018                 public int getEncryptionAlgorithim() {
1019
1020                         return encAlgo;
1021                 }
1022
1023                 /**
1024                  * Sets the encryption algorithim used to encrypt the private key.
1025                  * 
1026                  * @param encAlgo
1027                  *            the encryption algorithim used to encrypt the private key
1028                  */
1029                 public void setEncryptionAlgorithim(int encAlgo) {
1030
1031                         this.encAlgo = encAlgo;
1032                 }
1033
1034                 /**
1035                  * Gets the initialization vector used in the encryption of the private key.
1036                  * 
1037                  * @return the initialization vector used in the encryption of the private key
1038                  */
1039                 public String getInitializationVector() {
1040
1041                         return initVector;
1042                 }
1043
1044                 /**
1045                  * Sets the initialization vector used in the encryption of the private key.
1046                  * 
1047                  * @param initVector
1048                  *            ets the initialization vector used in the encryption of the private key
1049                  */
1050                 public void setInitializationVector(String initVector) {
1051
1052                         this.initVector = initVector;
1053                 }
1054
1055                 /**
1056                  * Gets the private key as bytes.
1057                  * 
1058                  * @return the private key as bytes.
1059                  */
1060                 public byte[] getKeyBytes() {
1061
1062                         return keyBytes;
1063                 }
1064
1065                 /**
1066                  * Sets the private key as bytes.
1067                  * 
1068                  * @param keyBytes
1069                  *            the private key as bytes
1070                  */
1071                 public void setKeyBytes(byte[] keyBytes) {
1072
1073                         this.keyBytes = keyBytes;
1074                 }
1075
1076                 /**
1077                  * Gets the private key from this encoded key.
1078                  * 
1079                  * @return the private key from this encoded key
1080                  */
1081                 public abstract PrivateKey getPrivateKey() throws CredentialFactoryException;
1082         }
1083
1084         /**
1085          * Represents a PEM formatted cryptographic key. Used to determine it's format (PKCS8, RSA, DSA), whether it's been
1086          * encrypted or not, get the Base64 encoded key, and then decoded the key into the DER encoded key.
1087          * 
1088          * @author Chad La Joie
1089          */
1090         private class PEMKey extends EncodedKey {
1091
1092                 /**
1093                  * DER encoded key
1094                  */
1095                 private DERKey derKey;
1096
1097                 /**
1098                  * Constructor
1099                  * 
1100                  * @param pemKey
1101                  *            the PEM key
1102                  * @throws CredentialFactoryException
1103                  * @throws CredentialFactoryException
1104                  * @throws IOException
1105                  */
1106                 public PEMKey(String pemKey, String password) throws IOException, CredentialFactoryException {
1107
1108                         setEncryptionPassword(password);
1109                         BufferedReader keyReader = new BufferedReader(new StringReader(pemKey));
1110                         parsePEMKey(keyReader);
1111                 }
1112
1113                 /**
1114                  * Constructor
1115                  * 
1116                  * @param pemKeyStream
1117                  *            and input stream with the PEM key
1118                  * @throws CredentialFactoryException
1119                  * @throws CredentialFactoryException
1120                  * @throws IOException
1121                  */
1122                 public PEMKey(InputStream pemKeyStream, String password) throws IOException, CredentialFactoryException {
1123
1124                         setEncryptionPassword(password);
1125                         BufferedReader keyReader = new BufferedReader(new InputStreamReader(pemKeyStream));
1126                         parsePEMKey(keyReader);
1127                 }
1128
1129                 /**
1130                  * Gets the private key from this PEM encoded key
1131                  * 
1132                  * @throws CredentialFactoryException
1133                  */
1134                 public PrivateKey getPrivateKey() throws CredentialFactoryException {
1135
1136                         return derKey.getPrivateKey();
1137                 }
1138
1139                 /**
1140                  * Parses the PEM key to determine its format, whether it's encrypted, then extract the Base64 encoded key and
1141                  * decodes it into the DER encoded key.
1142                  * 
1143                  * @param keyReader
1144                  *            the PEM key
1145                  * @throws IOException
1146                  *             thrown if there is problem reading the key
1147                  * @throws CredentialFactoryException
1148                  */
1149                 private void parsePEMKey(BufferedReader keyReader) throws IOException, CredentialFactoryException {
1150
1151                         if (log.isDebugEnabled()) {
1152                                 log.debug("Parsing PEM enocded private key");
1153                         }
1154                         String currentLine = keyReader.readLine();
1155
1156                         if (currentLine.matches("^.*-----BEGIN PRIVATE KEY-----.*$")) {
1157                                 if (log.isDebugEnabled()) {
1158                                         log.debug("Key appears to be in PKCS8 format.");
1159                                 }
1160
1161                                 setFormat(PKCS8);
1162                                 setEncrypted(false);
1163
1164                         } else if (currentLine.matches("^.*-----BEGIN ENCRYPTED PRIVATE KEY-----.*$")) {
1165                                 if (log.isDebugEnabled()) {
1166                                         log.debug("Key appears to be in encrypted PKCS8 format.");
1167                                 }
1168                                 setFormat(PKCS8);
1169                                 setEncrypted(true);
1170
1171                         } else if (currentLine.matches("^.*-----BEGIN RSA PRIVATE KEY-----.*$")) {
1172                                 setFormat(RSA);
1173
1174                                 // Mark the stream, if it's not encrypted we need to reset
1175                                 // or lose the first line of the base64 key
1176                                 keyReader.mark(100);
1177                                 currentLine = keyReader.readLine();
1178                                 if (currentLine.matches("^.*Proc-Type: 4,ENCRYPTED.*$")) {
1179                                         if (log.isDebugEnabled()) {
1180                                                 log.debug("Key appears to be encrypted RSA in raw format.");
1181                                         }
1182                                         setEncrypted(true);
1183                                 } else {
1184                                         if (log.isDebugEnabled()) {
1185                                                 log.debug("Key appears to be RSA in raw format.");
1186                                         }
1187                                         keyReader.reset();
1188                                         setEncrypted(false);
1189                                 }
1190
1191                         } else if (currentLine.matches("^.*-----BEGIN DSA PRIVATE KEY-----.*$")) {
1192                                 setFormat(DSA);
1193
1194                                 // Mark the stream, if it's not encrypted we need to reset
1195                                 // or lose the first line of the base64 key
1196                                 keyReader.mark(100);
1197                                 currentLine = keyReader.readLine();
1198                                 if (currentLine.matches("^.*Proc-Type: 4,ENCRYPTED.*$")) {
1199                                         if (log.isDebugEnabled()) {
1200                                                 log.debug("Key appears to be encrypted DSA in raw format.");
1201                                         }
1202                                         setEncrypted(true);
1203                                 } else {
1204                                         if (log.isDebugEnabled()) {
1205                                                 log.debug("Key appears to be DSA in raw format.");
1206                                         }
1207                                         keyReader.reset();
1208                                         setEncrypted(false);
1209                                 }
1210                         }
1211
1212                         // Key is an encrypted RSA or DSA key, need to get the algorithim used
1213                         if (isEncrypted() && (getFormat() == RSA || getFormat() == DSA)) {
1214                                 if (log.isDebugEnabled()) {
1215                                         log.debug("Key data is encrypted RSA or DSA, inspecting encryption properties");
1216                                 }
1217                                 currentLine = keyReader.readLine();
1218                                 String[] components = currentLine.split(":\\s");
1219                                 if (components.length != 2) {
1220                                         log.error("Encrypted key did not contain DEK-Info specification.");
1221                                         // throw new CredentialFactoryException("Unable to load private key.");
1222                                 }
1223                                 String[] cryptData = components[1].split(",");
1224                                 if (cryptData.length != 2 || cryptData[0] == null || cryptData[0].equals("") || cryptData[1] == null
1225                                                 || cryptData[1].equals("")) {
1226                                         log.error("Encrypted key did not contain a proper DEK-Info specification.");
1227                                         // throw new CredentialFactoryException("Unable to load private key.");
1228                                 }
1229                                 if (cryptData[0].equals("DES-CBC")) {
1230                                         if (log.isDebugEnabled()) {
1231                                                 log.debug("Key encryption method determined to be DES-CBC");
1232                                         }
1233                                         setEncryptionAlgorithim(DES_CBC);
1234                                 } else if (cryptData[0].equals("DES-EDE3-CBC")) {
1235                                         if (log.isDebugEnabled()) {
1236                                                 log.debug("Key encryption method determined to be DES-EDE3-CBC");
1237                                         }
1238                                         setEncryptionAlgorithim(DES_EDE3_CBC);
1239                                 } else {
1240                                         setEncryptionAlgorithim(-1);
1241                                         log.error("Key encryption method unknown: " + cryptData[0]);
1242                                 }
1243
1244                                 if (log.isDebugEnabled()) {
1245                                         log.debug("Key encryption algorithim initialization vector determined to be " + cryptData[1]);
1246                                 }
1247                                 setInitializationVector(cryptData[1]);
1248                         }
1249
1250                         // Now that we've parsed the headers, get the base64 encoded key itself
1251                         StringBuffer keyBuf = new StringBuffer();
1252                         while ((currentLine = keyReader.readLine()) != null) {
1253                                 if (currentLine.matches("^.*END.*$")) {
1254                                         break;
1255                                 }
1256
1257                                 keyBuf.append(currentLine);
1258                         }
1259
1260                         String base64Key = keyBuf.toString();
1261                         if (log.isDebugEnabled()) {
1262                                 log.debug("Base64 encoded key: " + base64Key);
1263                         }
1264
1265                         // Base64 decode the key, gives teh DER encoded key data
1266                         if (log.isDebugEnabled()) {
1267                                 log.debug("Base64 decoding key");
1268                         }
1269                         setKeyBytes(Base64.decode(base64Key));
1270
1271                         // If the key was a raw RSA/DSA encrypted we need to decrypt it now
1272                         // If it was a PKCS8 key we'll decrypt it when we parse it's DER data
1273                         if (isEncrypted() && (getFormat() == RSA || getFormat() == DSA)) {
1274                                 if (log.isDebugEnabled()) {
1275                                         log.debug("Decrypting RSA/DSA key");
1276                                 }
1277                                 decryptKey();
1278                         }
1279
1280                         // We now have a key encoded in DER format
1281                         if (log.isDebugEnabled()) {
1282                                 log.debug("PEM key has been decoded into DER encoded data, processing it as DER key");
1283                         }
1284                         derKey = new DERKey(getKeyBytes(), getEncryptionPassword());
1285
1286                         // Close the reader, we're done
1287                         keyReader.close();
1288                 }
1289
1290                 /**
1291                  * Decrypts an encrypted private key.
1292                  * 
1293                  * @throws CredentialFactoryException
1294                  */
1295                 private void decryptKey() throws CredentialFactoryException {
1296
1297                         try {
1298                                 byte[] ivBytes = new byte[8];
1299                                 for (int j = 0; j < 8; j++) {
1300                                         ivBytes[j] = (byte) Integer.parseInt(getInitializationVector().substring(j * 2, j * 2 + 2), 16);
1301                                 }
1302                                 IvParameterSpec paramSpec = new IvParameterSpec(ivBytes);
1303
1304                                 byte[] keyBuffer = new byte[24];
1305                                 // The key generation method (with the IV used as the salt, and
1306                                 // the single proprietary iteration)
1307                                 // is the reason we can't use the pkcs5 providers to read the
1308                                 // OpenSSL encrypted format
1309
1310                                 byte[] keyPass = getEncryptionPassword().getBytes();
1311                                 MessageDigest md = MessageDigest.getInstance("MD5");
1312                                 md.update(keyPass);
1313                                 md.update(paramSpec.getIV());
1314                                 byte[] digested = md.digest();
1315                                 System.arraycopy(digested, 0, keyBuffer, 0, 16);
1316
1317                                 md.update(digested);
1318                                 md.update(keyPass);
1319                                 md.update(paramSpec.getIV());
1320                                 digested = md.digest();
1321                                 System.arraycopy(digested, 0, keyBuffer, 16, 8);
1322
1323                                 SecretKeySpec keySpec = null;
1324                                 Cipher cipher = null;
1325                                 if (getEncryptionAlgorithim() == EncodedKey.DES_CBC) {
1326                                         // Special handling!!!
1327                                         // For DES, we use the same key generation,
1328                                         // then just chop off the end :-)
1329                                         byte[] desBuff = new byte[8];
1330                                         System.arraycopy(keyBuffer, 0, desBuff, 0, 8);
1331                                         keySpec = new SecretKeySpec(desBuff, "DES");
1332                                         cipher = Cipher.getInstance("DES/CBC/PKCS5Padding");
1333                                 }
1334                                 if (getEncryptionAlgorithim() == EncodedKey.DES_EDE3_CBC) {
1335                                         keySpec = new SecretKeySpec(keyBuffer, "DESede");
1336                                         cipher = Cipher.getInstance("DESede/CBC/PKCS5Padding");
1337                                 }
1338
1339                                 cipher.init(Cipher.DECRYPT_MODE, keySpec, paramSpec);
1340                                 byte[] decrypted = cipher.doFinal(getKeyBytes());
1341
1342                                 setEncrypted(false);
1343                                 setKeyBytes(decrypted);
1344                         } catch (BadPaddingException e) {
1345                                 log.error("Incorrect password to unlock private key.", e);
1346                                 throw new CredentialFactoryException("Unable to load private key.");
1347                         } catch (Exception e) {
1348                                 log
1349                                                 .error("Unable to decrypt private key.  Installed JCE implementations don't support the necessary algorithm: "
1350                                                                 + e);
1351                                 throw new CredentialFactoryException("Unable to load private key.");
1352                         }
1353                 }
1354         }
1355
1356         /**
1357          * Represents a DER formatted cryptographic key. Used to determine it's format (PKCS8, RSA, DSA), whether it's been
1358          * encrypted or not
1359          * 
1360          * @author Chad La Joie
1361          */
1362         private class DERKey extends EncodedKey {
1363
1364                 DERSequence rootDerTag;
1365
1366                 /**
1367                  * Constructor.
1368                  * 
1369                  * @param derKeyStream
1370                  *            the inputstream that contains the key
1371                  * @throws IOException
1372                  *             thrown if there is an error reading from the stream
1373                  * @throws CredentialFactoryException
1374                  *             thrown if there is an error parsing the stream data
1375                  */
1376                 public DERKey(InputStream derKeyStream, String password) throws IOException, CredentialFactoryException {
1377
1378                         setEncryptionPassword(password);
1379                         ByteContainer derKey = new ByteContainer(600, 50);
1380
1381                         for (int i = derKeyStream.read(); i != -1; i = derKeyStream.read()) {
1382                                 derKey.append((byte) i);
1383                         }
1384
1385                         setKeyBytes(derKey.toByteArray());
1386                         parseDerKey();
1387                 }
1388
1389                 public DERKey(byte[] derKey, String password) throws IOException, CredentialFactoryException {
1390
1391                         setEncryptionPassword(password);
1392                         setKeyBytes(derKey);
1393                         parseDerKey();
1394                 }
1395
1396                 public PrivateKey getPrivateKey() throws CredentialFactoryException {
1397
1398                         switch (getFormat()) {
1399                                 case EncodedKey.PKCS8 :
1400                                         if (!isEncrypted()) {
1401                                                 return getPkcs8Key();
1402                                         } else {
1403                                                 return getEncryptedPkcs8Key();
1404                                         }
1405
1406                                 case EncodedKey.RSA :
1407                                         return getRSARawDerKey();
1408
1409                                 case EncodedKey.DSA :
1410                                         return getDSARawDerKey();
1411
1412                                 default :
1413                                         throw new CredentialFactoryException("Unable to determine format of DER encoded private key");
1414                         }
1415                 }
1416
1417                 /**
1418                  * Takes a set of ASN.1 encoded bytes and converts them into a DER object.
1419                  * 
1420                  * @param keyBytes
1421                  *            the ASN.1 encoded bytes
1422                  * @return the DER object
1423                  * @throws IOException
1424                  *             thrown if the bytes aren't ASN.1 encoded
1425                  */
1426                 private DERObject getRootDerTag(byte[] keyBytes) throws IOException {
1427
1428                         InputStream derKeyStream = new BufferedInputStream(new ByteArrayInputStream(getKeyBytes()));
1429                         ASN1InputStream asn1Stream = new ASN1InputStream(derKeyStream);
1430                         DERObject derObject = asn1Stream.readObject();
1431                         derKeyStream.close();
1432                         asn1Stream.close();
1433
1434                         return derObject;
1435                 }
1436
1437                 /**
1438                  * Parse the key stream and determines data about the key.
1439                  * 
1440                  * @param derKeyStream
1441                  *            the inputstream that contains the key
1442                  * @throws IOExceptionthrown
1443                  *             if there is an error reading from the stream
1444                  * @throws CredentialFactoryException
1445                  *             thrown if there is an error parsing the stream data
1446                  */
1447                 private void parseDerKey() throws IOException, CredentialFactoryException {
1448
1449                         if (log.isDebugEnabled()) {
1450                                 log.debug("Starting to parse " + getKeyBytes().length + " byte DER formatted key.");
1451                         }
1452                         DERObject derObject = getRootDerTag(getKeyBytes());
1453
1454                         if (log.isDebugEnabled()) {
1455                                 log
1456                                                 .debug("Parsed ASN.1 object which has the following structure:\n"
1457                                                                 + ASN1Dump.dumpAsString(derObject));
1458                         }
1459
1460                         // All supported key formats start with a DER sequence tag
1461                         if (!(derObject instanceof DERSequence)) {
1462                                 log.error("Private key is not in valid DER format, it does not start with a DER sequence");
1463                                 throw new CredentialFactoryException("Private key is not in valid DER format");
1464                         }
1465                         DERSequence rootSeq = (DERSequence) derObject;
1466                         if (rootSeq.size() < 2) {
1467                                 // Valid key in any format will have at least two tags under the root
1468                                 log.error("Private key is not in valid DER format; does not contain more than 2 ASN.1 tags");
1469                                 throw new CredentialFactoryException("Private key is not in valid DER format");
1470                         }
1471
1472                         DERObject firstChild = rootSeq.getObjectAt(0).getDERObject();
1473
1474                         if (firstChild instanceof DERSequence) {
1475                                 if (log.isDebugEnabled()) {
1476                                         log.debug("First ASN.1 tag is a sequence, checking to see if this is an encrypted PKCS8 key");
1477                                 }
1478                                 // Might be encrypted PKCS8, lets check some more
1479                                 DERSequence firstChildSeq = (DERSequence) firstChild;
1480                                 DERObject grandChildObj = firstChildSeq.getObjectAt(0).getDERObject();
1481                                 DERObject secondChild = rootSeq.getObjectAt(1).getDERObject();
1482
1483                                 // Encrypted PKCS8 have an octet string as the second child (from the root)
1484                                 // and an object identifier as the child of the first child from the root
1485                                 if (secondChild instanceof DEROctetString && grandChildObj instanceof DERObjectIdentifier) {
1486                                         if (log.isDebugEnabled()) {
1487                                                 log.debug("DER encoded key determined to be encrypted PKCS8");
1488                                         }
1489                                         rootDerTag = rootSeq;
1490                                         setFormat(PKCS8);
1491                                         setEncrypted(true);
1492                                 }
1493                         } else if (firstChild instanceof DERInteger) {
1494                                 if (log.isDebugEnabled()) {
1495                                         log
1496                                                         .debug("First child ASN.1 tag is a Integer, checking to see if this is an PKCS8, RSA, or DSA key");
1497                                 }
1498                                 // Might be unencrypted PKCS8, RSA, or DSA
1499
1500                                 // Check to see if it's PKCS8 with contains an
1501                                 // Integer, then Sequence, then OctetString
1502                                 if (rootSeq.size() == 3) {
1503                                         if (log.isDebugEnabled()) {
1504                                                 log.debug("First ASN.1 sequence tag has 3 children, checking to see if this is an PKCS8 key");
1505                                         }
1506                                         if (rootSeq.getObjectAt(0).getDERObject() instanceof DERInteger
1507                                                         && rootSeq.getObjectAt(1).getDERObject() instanceof DERSequence
1508                                                         && rootSeq.getObjectAt(2).getDERObject() instanceof DEROctetString) {
1509                                                 if (log.isDebugEnabled()) {
1510                                                         log.debug("DER encoded key determined to be PKCS8");
1511                                                 }
1512                                                 rootDerTag = rootSeq;
1513                                                 setFormat(PKCS8);
1514                                                 setEncrypted(false);
1515                                         }
1516                                 } else {
1517                                         // Might be RSA or DSA. DSA will have 6 Integers
1518                                         // under the root sequences, RSA will have 9
1519                                         Enumeration children = rootSeq.getObjects();
1520                                         DERObject child;
1521                                         boolean allInts = true;
1522
1523                                         while (children.hasMoreElements()) {
1524                                                 child = ((DEREncodable) children.nextElement()).getDERObject();
1525                                                 if (!(child instanceof DERInteger)) {
1526                                                         allInts = false;
1527                                                 }
1528                                         }
1529
1530                                         if (rootSeq.size() == 6) {
1531                                                 if (log.isDebugEnabled()) {
1532                                                         log.debug("First ASN.1 sequence tag has 6 children, checking to see if this is an DSA key");
1533                                                 }
1534                                                 // DSA keys have six integer tags in the root sequence
1535                                                 if (allInts) {
1536                                                         if (log.isDebugEnabled()) {
1537                                                                 log.debug("DER encoded key determined to be raw DSA");
1538                                                         }
1539                                                         rootDerTag = rootSeq;
1540                                                         setFormat(DSA);
1541                                                         setEncrypted(false);
1542                                                 }
1543                                         } else if (rootSeq.size() == 9) {
1544                                                 if (log.isDebugEnabled()) {
1545                                                         log.debug("First ASN.1 sequence tag has 9 children, checking to see if this is an DSA key");
1546                                                 }
1547                                                 // RSA (PKCS1) keys have 9 integer tags in the root sequence
1548                                                 if (allInts) {
1549                                                         if (log.isDebugEnabled()) {
1550                                                                 log.debug("DER encoded key determined to be raw RSA");
1551                                                         }
1552                                                         rootDerTag = rootSeq;
1553                                                         setFormat(RSA);
1554                                                         setEncrypted(false);
1555                                                 }
1556                                         }
1557                                 }
1558                         }
1559
1560                         // If we don't know what the format is now then the stream wasn't a valid DER encoded key
1561                         if (getFormat() == -1) {
1562                                 log.error("Private key is not in valid DER format");
1563                                 throw new CredentialFactoryException("Private key is not in valid DER format");
1564                         }
1565                 }
1566
1567                 /**
1568                  * Gets the private key from a encrypted PKCS8 formatted key.
1569                  * 
1570                  * @param bytes
1571                  *            the PKCS8 formatted key
1572                  * @param password
1573                  *            the password to decrypt the key
1574                  * @return the private key
1575                  * @throws CredentialFactoryException
1576                  *             thrown is there is an error loading the private key
1577                  */
1578                 private PrivateKey getEncryptedPkcs8Key() throws CredentialFactoryException {
1579
1580                         if (log.isDebugEnabled()) {
1581                                 log.debug("Beginning to decrypt encrypted PKCS8 key");
1582                         }
1583                         try {
1584                                 // Convince the JCE provider that it does know how to do
1585                                 // pbeWithMD5AndDES-CBC
1586                                 Provider provider = Security.getProvider("SunJCE");
1587                                 if (provider != null) {
1588                                         provider.setProperty("Alg.Alias.AlgorithmParameters.1.2.840.113549.1.5.3", "PBE");
1589                                         provider.setProperty("Alg.Alias.SecretKeyFactory.1.2.840.113549.1.5.3", "PBEWithMD5AndDES");
1590                                         provider.setProperty("Alg.Alias.Cipher.1.2.840.113549.1.5.3", "PBEWithMD5AndDES");
1591                                 }
1592
1593                                 if (log.isDebugEnabled()) {
1594                                         log.debug("Inspecting key properties");
1595                                 }
1596                                 EncryptedPrivateKeyInfo encryptedKeyInfo = new EncryptedPrivateKeyInfo(getKeyBytes());
1597                                 if (log.isDebugEnabled()) {
1598                                         log.debug("Key encryption Algorithim: " + encryptedKeyInfo.getAlgName());
1599                                         log.debug("Key encryption parameters: " + encryptedKeyInfo.getAlgParameters());
1600                                 }
1601
1602                                 AlgorithmParameters params = encryptedKeyInfo.getAlgParameters();
1603
1604                                 if (params == null) {
1605                                         log.error("Unable to decrypt private key.  Installed JCE implementations don't support the ("
1606                                                         + encryptedKeyInfo.getAlgName() + ") algorithm.");
1607                                         throw new CredentialFactoryException("Unable to load private key; " + encryptedKeyInfo.getAlgName()
1608                                                         + " is not a supported by this JCE");
1609                                 }
1610
1611                                 if (log.isDebugEnabled()) {
1612                                         log.debug("Key encryption properties determined, decrypting key");
1613                                 }
1614                                 SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(encryptedKeyInfo.getAlgName());
1615                                 PBEKeySpec passwordSpec = new PBEKeySpec(getEncryptionPassword().toCharArray());
1616                                 SecretKey key = keyFactory.generateSecret(passwordSpec);
1617
1618                                 Cipher cipher = Cipher.getInstance(encryptedKeyInfo.getAlgName());
1619                                 cipher.init(Cipher.DECRYPT_MODE, key, params);
1620                                 PKCS8EncodedKeySpec decrypted = encryptedKeyInfo.getKeySpec(cipher);
1621
1622                                 if (log.isDebugEnabled()) {
1623                                         log.debug("Key decrypted, key format now non-encrypted PKCS8");
1624                                 }
1625
1626                                 setEncrypted(false);
1627                                 setKeyBytes(decrypted.getEncoded());
1628
1629                                 // Now that we've decrypted the key we've changed the ASN.1 structure
1630                                 // and so need to reread it.
1631                                 rootDerTag = (DERSequence) getRootDerTag(getKeyBytes());
1632
1633                                 return getPkcs8Key();
1634
1635                         } catch (IOException e) {
1636                                 log.error("Invalid DER encoding for PKCS8 formatted encrypted key: " + e);
1637                                 throw new CredentialFactoryException("Unable to load private key; invalid key format.");
1638                         } catch (InvalidKeySpecException e) {
1639                                 log.error("Incorrect password to unlock private key.", e);
1640                                 throw new CredentialFactoryException("Unable to load private key; incorrect key decryption password");
1641                         } catch (GeneralSecurityException e) {
1642                                 log.error("JCE does not support algorithim to decrypt key: " + e);
1643                                 throw new CredentialFactoryException(
1644                                                 "Unable to load private key; JCE does not support algorithim to decrypt key");
1645                         }
1646                 }
1647
1648                 /**
1649                  * Gets the private key from a PKCS8 formatted key.
1650                  * 
1651                  * @param bytes
1652                  *            the PKCS8 formatted key
1653                  * @return the private key
1654                  * @throws CredentialFactoryException
1655                  *             thrown is there is an error loading the private key
1656                  */
1657                 private PrivateKey getPkcs8Key() throws CredentialFactoryException {
1658
1659                         if (log.isDebugEnabled()) {
1660                                 log.debug("Reading unecrypted PKCS8 key to determine if key is RSA or DSA");
1661                         }
1662                         DERSequence childSeq = (DERSequence) rootDerTag.getObjectAt(1).getDERObject();
1663                         DERObjectIdentifier derOID = (DERObjectIdentifier) childSeq.getObjectAt(0).getDERObject();
1664                         String keyOID = derOID.getId();
1665
1666                         if (keyOID.equals(EncodedKey.RSAKey_OID)) {
1667                                 if (log.isDebugEnabled()) {
1668                                         log.debug("Found RSA key in PKCS8.");
1669                                 }
1670                                 return getRSAPkcs8DerKey();
1671                         } else if (keyOID.equals(EncodedKey.DSAKey_OID)) {
1672                                 if (log.isDebugEnabled()) {
1673                                         log.debug("Found DSA key in PKCS8.");
1674                                 }
1675                                 return getDSAPkcs8DerKey();
1676                         } else {
1677                                 log.error("Unexpected key type.  Only RSA and DSA keys are supported in PKCS8 format.");
1678                                 throw new CredentialFactoryException("Unable to load private key; unexpected key type in PKCS8");
1679                         }
1680                 }
1681
1682                 /**
1683                  * Gets a private key from a raw RSA PKCS8 formated DER encoded key.
1684                  * 
1685                  * @param bytes
1686                  *            the encoded key
1687                  * @return the private key
1688                  * @throws CredentialFactoryException
1689                  *             thrown if the private key can not be read
1690                  */
1691                 private PrivateKey getRSAPkcs8DerKey() throws CredentialFactoryException {
1692
1693                         if (log.isDebugEnabled()) {
1694                                 log.debug("Constructing PrivateKey from PKCS8 encoded RSA key data");
1695                         }
1696                         try {
1697                                 KeyFactory keyFactory = KeyFactory.getInstance("RSA");
1698                                 PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(getKeyBytes());
1699                                 return keyFactory.generatePrivate(keySpec);
1700
1701                         } catch (Exception e) {
1702                                 log.error("Unable to load private key: " + e);
1703                                 throw new CredentialFactoryException("Unable to load private key.");
1704                         }
1705                 }
1706
1707                 /**
1708                  * Gets a private key from a raw DSA PKCS8 formated DER encoded key.
1709                  * 
1710                  * @param bytes
1711                  *            the encoded key
1712                  * @return the private key
1713                  * @throws CredentialFactoryException
1714                  *             thrown if the private key can not be read
1715                  */
1716                 private PrivateKey getDSAPkcs8DerKey() throws CredentialFactoryException {
1717
1718                         if (log.isDebugEnabled()) {
1719                                 log.debug("Constructing PrivateKey from PKCS8 encoded DSA key data");
1720                         }
1721
1722                         try {
1723                                 KeyFactory keyFactory = KeyFactory.getInstance("DSA");
1724                                 PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(getKeyBytes());
1725                                 return keyFactory.generatePrivate(keySpec);
1726
1727                         } catch (Exception e) {
1728                                 log.error("Unable to load private key: " + e);
1729                                 throw new CredentialFactoryException("Unable to load private key.");
1730                         }
1731                 }
1732
1733                 /**
1734                  * Converts a raw RSA key encoded in DER format into a private key object.
1735                  * 
1736                  * @param key
1737                  *            the DER encoded key
1738                  * @return the private key
1739                  * @throws CredentialFactoryException
1740                  *             thrown if a key can not be constructed from the input
1741                  */
1742                 private PrivateKey getRSARawDerKey() throws CredentialFactoryException {
1743
1744                         if (log.isDebugEnabled()) {
1745                                 log.debug("Constructing PrivateKey from raw RSA key data");
1746                         }
1747                         try {
1748                                 RSAPrivateCrtKeySpec keySpec = new RSAPrivateCrtKeySpec(((DERInteger) rootDerTag.getObjectAt(1))
1749                                                 .getValue(), ((DERInteger) rootDerTag.getObjectAt(2)).getValue(), ((DERInteger) rootDerTag
1750                                                 .getObjectAt(3)).getValue(), ((DERInteger) rootDerTag.getObjectAt(4)).getValue(),
1751                                                 ((DERInteger) rootDerTag.getObjectAt(5)).getValue(), ((DERInteger) rootDerTag.getObjectAt(6))
1752                                                                 .getValue(), ((DERInteger) rootDerTag.getObjectAt(7)).getValue(),
1753                                                 ((DERInteger) rootDerTag.getObjectAt(8)).getValue());
1754
1755                                 KeyFactory keyFactory = KeyFactory.getInstance("RSA");
1756
1757                                 return keyFactory.generatePrivate(keySpec);
1758
1759                         } catch (GeneralSecurityException e) {
1760                                 log.error("Unable to marshall private key: " + e);
1761                                 throw new CredentialFactoryException("Unable to load private key.");
1762                         }
1763                 }
1764
1765                 /**
1766                  * Converts a raw DSA key encoded in DER format into a private key object.
1767                  * 
1768                  * @param derKey
1769                  *            DER encoded DSA key
1770                  * @return the private key
1771                  * @throws CredentialFactoryException
1772                  *             thrown if a key can not be constructed from the input
1773                  */
1774                 private PrivateKey getDSARawDerKey() throws CredentialFactoryException {
1775
1776                         if (log.isDebugEnabled()) {
1777                                 log.debug("Constructing PrivateKey from raw DSA key data");
1778                         }
1779
1780                         try {
1781                                 DSAPrivateKeySpec keySpec = new DSAPrivateKeySpec(((DERInteger) rootDerTag.getObjectAt(5)).getValue(),
1782                                                 ((DERInteger) rootDerTag.getObjectAt(1)).getValue(), ((DERInteger) rootDerTag.getObjectAt(2))
1783                                                                 .getValue(), ((DERInteger) rootDerTag.getObjectAt(3)).getValue());
1784
1785                                 KeyFactory keyFactory = KeyFactory.getInstance("DSA");
1786
1787                                 return keyFactory.generatePrivate(keySpec);
1788                         } catch (GeneralSecurityException e) {
1789                                 log.error("Unable to marshall private key: " + e);
1790                                 throw new CredentialFactoryException("Unable to load private key.");
1791                         }
1792                 }
1793         }
1794 }
1795
1796 /**
1797  * Loads a credential from a Java keystore.
1798  * 
1799  * @author Walter Hoehn
1800  */
1801
1802 class KeystoreCredentialResolver implements CredentialResolver {
1803
1804         private static Logger log = Logger.getLogger(KeystoreCredentialResolver.class.getName());
1805
1806         public Credential loadCredential(Element e) throws CredentialFactoryException {
1807
1808                 if (!e.getLocalName().equals("KeyStoreResolver")) {
1809                         log.error("Invalid Credential Resolver configuration: expected <KeyStoreResolver> .");
1810                         throw new CredentialFactoryException("Failed to initialize Credential Resolver.");
1811                 }
1812
1813                 String keyStoreType = e.getAttribute("storeType");
1814                 if (keyStoreType == null || keyStoreType.equals("")) {
1815                         log.debug("Using default store type for credential.");
1816                         keyStoreType = "JKS";
1817                 }
1818
1819                 String path = loadPath(e);
1820                 String alias = loadAlias(e);
1821                 String certAlias = loadCertAlias(e, alias);
1822                 String keyPassword = loadKeyPassword(e);
1823                 String keyStorePassword = loadKeyStorePassword(e);
1824
1825                 try {
1826                         KeyStore keyStore = KeyStore.getInstance(keyStoreType);
1827
1828                         keyStore.load(new ShibResource(path, this.getClass()).getInputStream(), keyStorePassword.toCharArray());
1829
1830                         PrivateKey privateKey = (PrivateKey) keyStore.getKey(alias, keyPassword.toCharArray());
1831
1832                         if (privateKey == null) { throw new CredentialFactoryException("No key entry was found with an alias of ("
1833                                         + alias + ")."); }
1834
1835                         Certificate[] certificates = keyStore.getCertificateChain(certAlias);
1836                         if (certificates == null) { throw new CredentialFactoryException(
1837                                         "An error occurred while reading the java keystore: No certificate found with the specified alias ("
1838                                                         + certAlias + ")."); }
1839
1840                         X509Certificate[] x509Certs = new X509Certificate[certificates.length];
1841                         for (int i = 0; i < certificates.length; i++) {
1842                                 if (certificates[i] instanceof X509Certificate) {
1843                                         x509Certs[i] = (X509Certificate) certificates[i];
1844                                 } else {
1845                                         throw new CredentialFactoryException(
1846                                                         "The KeyStore Credential Resolver can only load X509 certificates.  Found an unsupported certificate of type ("
1847                                                                         + certificates[i] + ").");
1848                                 }
1849                         }
1850
1851                         return new Credential(x509Certs, privateKey);
1852
1853                 } catch (KeyStoreException kse) {
1854                         throw new CredentialFactoryException("An error occurred while accessing the java keystore: " + kse);
1855                 } catch (NoSuchAlgorithmException nsae) {
1856                         throw new CredentialFactoryException("Appropriate JCE provider not found in the java environment: " + nsae);
1857                 } catch (CertificateException ce) {
1858                         throw new CredentialFactoryException("The java keystore contained a certificate that could not be loaded: "
1859                                         + ce);
1860                 } catch (IOException ioe) {
1861                         throw new CredentialFactoryException("An error occurred while reading the java keystore: " + ioe);
1862                 } catch (UnrecoverableKeyException uke) {
1863                         throw new CredentialFactoryException(
1864                                         "An error occurred while attempting to load the key from the java keystore: " + uke);
1865                 }
1866
1867         }
1868
1869         private String loadPath(Element e) throws CredentialFactoryException {
1870
1871                 NodeList pathElements = e.getElementsByTagNameNS(Credentials.credentialsNamespace, "Path");
1872                 if (pathElements.getLength() < 1) {
1873                         log.error("KeyStore path not specified.");
1874                         throw new CredentialFactoryException("KeyStore Credential Resolver requires a <Path> specification.");
1875                 }
1876                 if (pathElements.getLength() > 1) {
1877                         log.error("Multiple KeyStore path specifications, using first.");
1878                 }
1879                 Node tnode = pathElements.item(0).getFirstChild();
1880                 String path = null;
1881                 if (tnode != null && tnode.getNodeType() == Node.TEXT_NODE) {
1882                         path = tnode.getNodeValue();
1883                 }
1884                 if (path == null || path.equals("")) {
1885                         log.error("KeyStore path not specified.");
1886                         throw new CredentialFactoryException("KeyStore Credential Resolver requires a <Path> specification.");
1887                 }
1888                 return path;
1889         }
1890
1891         private String loadAlias(Element e) throws CredentialFactoryException {
1892
1893                 NodeList aliasElements = e.getElementsByTagNameNS(Credentials.credentialsNamespace, "KeyAlias");
1894                 if (aliasElements.getLength() < 1) {
1895                         log.error("KeyStore key alias not specified.");
1896                         throw new CredentialFactoryException("KeyStore Credential Resolver requires an <KeyAlias> specification.");
1897                 }
1898                 if (aliasElements.getLength() > 1) {
1899                         log.error("Multiple key alias specifications, using first.");
1900                 }
1901                 Node tnode = aliasElements.item(0).getFirstChild();
1902                 String alias = null;
1903                 if (tnode != null && tnode.getNodeType() == Node.TEXT_NODE) {
1904                         alias = tnode.getNodeValue();
1905                 }
1906                 if (alias == null || alias.equals("")) {
1907                         log.error("KeyStore key alias not specified.");
1908                         throw new CredentialFactoryException("KeyStore Credential Resolver requires an <KeyAlias> specification.");
1909                 }
1910                 return alias;
1911         }
1912
1913         private String loadCertAlias(Element e, String defaultAlias) throws CredentialFactoryException {
1914
1915                 NodeList aliasElements = e.getElementsByTagNameNS(Credentials.credentialsNamespace, "CertAlias");
1916                 if (aliasElements.getLength() < 1) {
1917                         log.debug("KeyStore cert alias not specified, defaulting to key alias.");
1918                         return defaultAlias;
1919                 }
1920
1921                 if (aliasElements.getLength() > 1) {
1922                         log.error("Multiple cert alias specifications, using first.");
1923                 }
1924
1925                 Node tnode = aliasElements.item(0).getFirstChild();
1926                 String alias = null;
1927                 if (tnode != null && tnode.getNodeType() == Node.TEXT_NODE) {
1928                         alias = tnode.getNodeValue();
1929                 }
1930                 if (alias == null || alias.equals("")) {
1931                         log.debug("KeyStore cert alias not specified, defaulting to key alias.");
1932                         return defaultAlias;
1933                 }
1934                 return alias;
1935         }
1936
1937         private String loadKeyStorePassword(Element e) throws CredentialFactoryException {
1938
1939                 NodeList passwordElements = e.getElementsByTagNameNS(Credentials.credentialsNamespace, "StorePassword");
1940                 if (passwordElements.getLength() < 1) {
1941                         log.error("KeyStore password not specified.");
1942                         throw new CredentialFactoryException(
1943                                         "KeyStore Credential Resolver requires an <StorePassword> specification.");
1944                 }
1945                 if (passwordElements.getLength() > 1) {
1946                         log.error("Multiple KeyStore password specifications, using first.");
1947                 }
1948                 Node tnode = passwordElements.item(0).getFirstChild();
1949                 String password = null;
1950                 if (tnode != null && tnode.getNodeType() == Node.TEXT_NODE) {
1951                         password = tnode.getNodeValue();
1952                 }
1953                 if (password == null || password.equals("")) {
1954                         log.error("KeyStore password not specified.");
1955                         throw new CredentialFactoryException(
1956                                         "KeyStore Credential Resolver requires an <StorePassword> specification.");
1957                 }
1958                 return password;
1959         }
1960
1961         private String loadKeyPassword(Element e) throws CredentialFactoryException {
1962
1963                 NodeList passwords = e.getElementsByTagNameNS(Credentials.credentialsNamespace, "KeyPassword");
1964                 if (passwords.getLength() < 1) {
1965                         log.error("KeyStore key password not specified.");
1966                         throw new CredentialFactoryException(
1967                                         "KeyStore Credential Resolver requires an <KeyPassword> specification.");
1968                 }
1969                 if (passwords.getLength() > 1) {
1970                         log.error("Multiple KeyStore key password specifications, using first.");
1971                 }
1972                 Node tnode = passwords.item(0).getFirstChild();
1973                 String password = null;
1974                 if (tnode != null && tnode.getNodeType() == Node.TEXT_NODE) {
1975                         password = tnode.getNodeValue();
1976                 }
1977                 if (password == null || password.equals("")) {
1978                         log.error("KeyStore key password not specified.");
1979                         throw new CredentialFactoryException(
1980                                         "KeyStore Credential Resolver requires an <KeyPassword> specification.");
1981                 }
1982                 return password;
1983         }
1984 }
1985
1986 /**
1987  * Uses implementation specified in the configuration to load a credential.
1988  * 
1989  * @author Walter Hoehn
1990  */
1991
1992 class CustomCredentialResolver implements CredentialResolver {
1993
1994         private static Logger log = Logger.getLogger(CustomCredentialResolver.class.getName());
1995
1996         public Credential loadCredential(Element e) throws CredentialFactoryException {
1997
1998                 if (!e.getLocalName().equals("CustomCredResolver")) {
1999                         log.error("Invalid Credential Resolver configuration: expected <CustomCredResolver> .");
2000                         throw new CredentialFactoryException("Failed to initialize Credential Resolver.");
2001                 }
2002
2003                 String className = e.getAttribute("Class");
2004                 if (className == null || className.equals("")) {
2005                         log.error("Custom Credential Resolver requires specification of the attribute \"Class\".");
2006                         throw new CredentialFactoryException("Failed to initialize Credential Resolver.");
2007                 }
2008
2009                 try {
2010                         return ((CredentialResolver) Class.forName(className).newInstance()).loadCredential(e);
2011
2012                 } catch (Exception loaderException) {
2013                         log
2014                                         .error("Failed to load Custom Credential Resolver implementation class: "
2015                                                         + loaderException.getMessage());
2016                         throw new CredentialFactoryException("Failed to initialize Credential Resolver.");
2017                 }
2018
2019         }
2020
2021 }
2022
2023 class CredentialFactoryException extends Exception {
2024
2025         CredentialFactoryException(String message) {
2026
2027                 super(message);
2028         }
2029 }