f3b5eba4ede1d515dc962db0e0d0a522f5e5b4dd
[java-idp.git] / src / edu / internet2 / middleware / shibboleth / common / Credentials.java
1 /*
2  * The Shibboleth License, Version 1. Copyright (c) 2002 University Corporation for Advanced Internet Development, Inc.
3  * All rights reserved
4  * 
5  * 
6  * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
7  * following conditions are met:
8  * 
9  * Redistributions of source code must retain the above copyright notice, this list of conditions and the following
10  * disclaimer.
11  * 
12  * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
13  * disclaimer in the documentation and/or other materials provided with the distribution, if any, must include the
14  * following acknowledgment: "This product includes software developed by the University Corporation for Advanced
15  * Internet Development <http://www.ucaid.edu> Internet2 Project. Alternately, this acknowledegement may appear in the
16  * software itself, if and wherever such third-party acknowledgments normally appear.
17  * 
18  * Neither the name of Shibboleth nor the names of its contributors, nor Internet2, nor the University Corporation for
19  * Advanced Internet Development, Inc., nor UCAID may be used to endorse or promote products derived from this software
20  * without specific prior written permission. For written permission, please contact shibboleth@shibboleth.org
21  * 
22  * Products derived from this software may not be called Shibboleth, Internet2, UCAID, or the University Corporation
23  * for Advanced Internet Development, nor may Shibboleth appear in their name, without prior written permission of the
24  * University Corporation for Advanced Internet Development.
25  * 
26  * 
27  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND WITH ALL FAULTS. ANY EXPRESS OR
28  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
29  * PARTICULAR PURPOSE, AND NON-INFRINGEMENT ARE DISCLAIMED AND THE ENTIRE RISK OF SATISFACTORY QUALITY, PERFORMANCE,
30  * ACCURACY, AND EFFORT IS WITH LICENSEE. IN NO EVENT SHALL THE COPYRIGHT OWNER, CONTRIBUTORS OR THE UNIVERSITY
31  * CORPORATION FOR ADVANCED INTERNET DEVELOPMENT, INC. BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
32  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
33  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
34  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
35  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
36  */
37
38 package edu.internet2.middleware.shibboleth.common;
39
40 import java.io.BufferedReader;
41 import java.io.ByteArrayInputStream;
42 import java.io.IOException;
43 import java.io.InputStream;
44 import java.io.InputStreamReader;
45 import java.security.GeneralSecurityException;
46 import java.security.KeyFactory;
47 import java.security.KeyStore;
48 import java.security.KeyStoreException;
49 import java.security.NoSuchAlgorithmException;
50 import java.security.PrivateKey;
51 import java.security.PublicKey;
52 import java.security.Signature;
53 import java.security.UnrecoverableKeyException;
54 import java.security.cert.Certificate;
55 import java.security.cert.CertificateException;
56 import java.security.cert.CertificateFactory;
57 import java.security.cert.X509Certificate;
58 import java.security.spec.DSAPrivateKeySpec;
59 import java.security.spec.PKCS8EncodedKeySpec;
60 import java.security.spec.RSAPrivateCrtKeySpec;
61 import java.util.ArrayList;
62 import java.util.Arrays;
63 import java.util.Hashtable;
64
65 import org.apache.log4j.Logger;
66 import org.w3c.dom.Element;
67 import org.w3c.dom.Node;
68 import org.w3c.dom.NodeList;
69
70 import sun.misc.BASE64Decoder;
71 import sun.security.util.DerValue;
72
73 /**
74  * @author Walter Hoehn
75  *  
76  */
77 public class Credentials {
78
79         public static final String credentialsNamespace = "urn:mace:shibboleth:credentials:1.0";
80
81         private static Logger log = Logger.getLogger(Credentials.class.getName());
82         private Hashtable data = new Hashtable();
83
84         public Credentials(Element e) {
85
86                 if (!e.getTagName().equals("Credentials")) {
87                         throw new IllegalArgumentException();
88                 }
89
90                 NodeList resolverNodes = e.getChildNodes();
91                 if (resolverNodes.getLength() <= 0) {
92                         log.error("Credentials configuration inclues no Credential Resolver definitions.");
93                         throw new IllegalArgumentException("Cannot load credentials.");
94                 }
95
96                 for (int i = 0; resolverNodes.getLength() > i; i++) {
97                         if (resolverNodes.item(i).getNodeType() == Node.ELEMENT_NODE) {
98                                 try {
99
100                                         String credentialId = ((Element) resolverNodes.item(i)).getAttribute("Id");
101                                         if (credentialId == null || credentialId.equals("")) {
102                                                 log.error("Found credential that was not labeled with a unique \"Id\" attribute. Skipping.");
103                                         }
104
105                                         if (data.containsKey(credentialId)) {
106                                                 log.error("Duplicate credential id (" + credentialId + ") found. Skipping");
107                                         }
108
109                                         log.info("Found credential (" + credentialId + "). Loading...");
110                                         data.put(credentialId, CredentialFactory.loadCredential((Element) resolverNodes.item(i)));
111
112                                 } catch (CredentialFactoryException cfe) {
113                                         log.error("Could not load credential, skipping: " + cfe.getMessage());
114                                 } catch (ClassCastException cce) {
115                                         log.error("Problem realizing credential configuration" + cce.getMessage());
116                                 }
117                         }
118                 }
119         }
120
121         public boolean containsCredential(String identifier) {
122                 return data.containsKey(identifier);
123         }
124
125         public Credential getCredential(String identifier) {
126                 return (Credential) data.get(identifier);
127         }
128
129         static class CredentialFactory {
130
131                 private static Logger log = Logger.getLogger(CredentialFactory.class.getName());
132
133                 public static Credential loadCredential(Element e) throws CredentialFactoryException {
134                         if (e.getTagName().equals("KeyInfo")) {
135                                 return new KeyInfoCredentialResolver().loadCredential(e);
136                         }
137
138                         if (e.getTagName().equals("FileResolver")) {
139                                 return new FileCredentialResolver().loadCredential(e);
140                         }
141
142                         if (e.getTagName().equals("KeyStoreResolver")) {
143                                 return new KeystoreCredentialResolver().loadCredential(e);
144                         }
145
146                         if (e.getTagName().equals("CustomResolver")) {
147                                 return new CustomCredentialResolver().loadCredential(e);
148                         }
149
150                         log.error("Unrecognized Credential Resolver type: " + e.getTagName());
151                         throw new CredentialFactoryException("Failed to load credential.");
152                 }
153
154         }
155
156 }
157
158 class KeyInfoCredentialResolver implements CredentialResolver {
159         private static Logger log = Logger.getLogger(KeyInfoCredentialResolver.class.getName());
160         KeyInfoCredentialResolver() throws CredentialFactoryException {
161                 log.error("Credential Resolver (KeyInfoCredentialResolver) not implemented");
162                 throw new CredentialFactoryException("Failed to load credential.");
163         }
164
165         public Credential loadCredential(Element e) {
166                 return null;
167         }
168 }
169
170 class FileCredentialResolver implements CredentialResolver {
171
172         private static Logger log = Logger.getLogger(FileCredentialResolver.class.getName());
173
174         private static String DSAKey_OID = "1.2.840.10040.4.1";
175         private static String RSAKey_OID = "1.2.840.113549.1.1.1";
176
177         public Credential loadCredential(Element e) throws CredentialFactoryException {
178
179                 if (!e.getTagName().equals("FileResolver")) {
180                         log.error("Invalid Credential Resolver configuration: expected <FileResolver> .");
181                         throw new CredentialFactoryException("Failed to initialize Credential Resolver.");
182                 }
183
184                 String id = e.getAttribute("Id");
185                 if (id == null || id.equals("")) {
186                         log.error("Credential Resolvers require specification of the attribute \"Id\".");
187                         throw new CredentialFactoryException("Failed to initialize Credential Resolver.");
188                 }
189
190                 //Load the key
191                 String keyFormat = getKeyFormat(e);
192                 String keyPath = getKeyPath(e);
193                 log.debug("Key Format: (" + keyFormat + ").");
194                 log.debug("Key Path: (" + keyPath + ").");
195
196                 //TODO support DER, PEM, DER-PKCS8, and PEM-PKCS8?
197
198                 PrivateKey key = null;
199
200                 if (keyFormat.equals("DER")) {
201                         try {
202                                 key = getDERKey(new ShibResource(keyPath, this.getClass()).getInputStream());
203                         } catch (IOException ioe) {
204                                 log.error("Could not load resource from specified location (" + keyPath + "): " + e);
205                                 throw new CredentialFactoryException("Unable to load private key.");
206                         }
207                 } else if (keyFormat.equals("PEM")) {
208                         try {
209                                 key = getPEMKey(new ShibResource(keyPath, this.getClass()).getInputStream());
210                         } catch (IOException ioe) {
211                                 log.error("Could not load resource from specified location (" + keyPath + "): " + e);
212                                 throw new CredentialFactoryException("Unable to load private key.");
213                         }
214                 } else {
215                         log.error("File credential resolver only supports (DER) and (PEM) formats.");
216                         throw new CredentialFactoryException("Failed to initialize Credential Resolver.");
217                 }
218
219                 if (key == null) {
220                         log.error("Failed to load private key.");
221                         throw new CredentialFactoryException("Failed to initialize Credential Resolver.");
222                 }
223
224                 String certFormat = getCertFormat(e);
225                 String certPath = getCertPath(e);
226                 //A placeholder in case we want to make this configurable
227                 String certType = "X.509";
228
229                 log.debug("Certificate Format: (" + certFormat + ").");
230                 log.debug("Certificate Path: (" + certPath + ").");
231
232                 //The loading code should work for other types, but the chain construction code
233                 //would break
234                 if (!certType.equals("X.509")) {
235                         log.error("File credential resolver only supports the X.509 certificates.");
236                         throw new CredentialFactoryException("Failed to initialize Credential Resolver.");
237                 }
238
239                 ArrayList certChain = new ArrayList();
240                 ArrayList allCerts = new ArrayList();
241
242                 try {
243                         Certificate[] certsFromPath =
244                                 loadCertificates(new ShibResource(certPath, this.getClass()).getInputStream(), certType);
245
246                         allCerts.addAll(Arrays.asList(certsFromPath));
247
248                         //Find the end-entity cert first
249                         if (certsFromPath == null || certsFromPath.length == 0) {
250                                 log.error("File at (" + certPath + ") did not contain any valid certificates.");
251                                 throw new CredentialFactoryException("File did not contain any valid certificates.");
252                         }
253
254                         if (certsFromPath.length == 1) {
255                                 log.debug("Certificate file only contains 1 certificate.");
256                                 log.debug("Ensure that it matches the private key.");
257                                 if (!isMatchingKey(certsFromPath[0].getPublicKey(), key)) {
258                                         log.error("Certificate does not match the private key.");
259                                         throw new CredentialFactoryException("File did not contain any valid certificates.");
260                                 }
261                                 certChain.add(certsFromPath[0]);
262                                 log.debug(
263                                         "Successfully identified the end entity cert: "
264                                                 + ((X509Certificate) certChain.get(0)).getSubjectDN());
265
266                         } else {
267                                 log.debug("Certificate file contains multiple certificates.");
268                                 log.debug("Trying to determine the end-entity cert by matching against the private key.");
269                                 for (int i = 0; certsFromPath.length > i; i++) {
270                                         if (isMatchingKey(certsFromPath[i].getPublicKey(), key)) {
271                                                 log.debug("Found matching end cert: " + ((X509Certificate) certsFromPath[i]).getSubjectDN());
272                                                 certChain.add(certsFromPath[i]);
273                                         }
274                                 }
275                                 if (certChain.size() < 1) {
276                                         log.error("No certificate in chain that matches specified private key");
277                                         throw new CredentialFactoryException("No certificate in chain that matches specified private key");
278                                 }
279                                 if (certChain.size() > 1) {
280                                         log.error("More than one certificate in chain that matches specified private key");
281                                         throw new CredentialFactoryException("More than one certificate in chain that matches specified private key");
282                                 }
283                                 log.debug(
284                                         "Successfully identified the end entity cert: "
285                                                 + ((X509Certificate) certChain.get(0)).getSubjectDN());
286                         }
287
288                         //Now load additional certs and construct a chain
289                         String[] caPaths = getCAPaths(e);
290                         if (caPaths != null && caPaths.length > 0) {
291                                 log.debug("Attempting to load certificates from (" + caPaths.length + ") CA certificate files.");
292                                 for (int i = 0; i < caPaths.length; i++) {
293                                         allCerts.addAll(
294                                                 Arrays.asList(
295                                                         loadCertificates(
296                                                                 new ShibResource(caPaths[i], this.getClass()).getInputStream(),
297                                                                 certType)));
298                                 }
299                         }
300
301                         //TODO probably don't want to require a full chain
302                         log.debug("Attempting to construct a certificate chain.");
303                         walkChain((X509Certificate[]) allCerts.toArray(new X509Certificate[0]), certChain);
304
305                         log.info("Verifying that each link in the cert chain is signed appropriately");
306                         for (int i = 0; i < certChain.size() - 1; i++) {
307                                 PublicKey pubKey = ((X509Certificate) certChain.get(i + 1)).getPublicKey();
308                                 try {
309                                         ((X509Certificate) certChain.get(i)).verify(pubKey);
310                                 } catch (Exception se) {
311                                         log.error("Certificate chain cannot be verified: " + se);
312                                         throw new CredentialFactoryException("Certificate chain cannot be verified: " + se);
313                                 }
314                         }
315                         log.debug("All signatures verified. Certificate chain creation successful.");
316
317                 } catch (IOException p) {
318                         log.error("Could not load resource from specified location (" + certPath + "): " + p);
319                         throw new CredentialFactoryException("Unable to load certificates.");
320                 }
321
322                 return new Credential(((X509Certificate[]) certChain.toArray(new X509Certificate[0])), key);
323         }
324
325         private PrivateKey getDERKey(InputStream inStream) throws CredentialFactoryException, IOException {
326
327                 byte[] inputBuffer = new byte[8];
328                 int i;
329                 ByteContainer inputBytes = new ByteContainer(800);
330                 do {
331                         i = inStream.read(inputBuffer);
332                         for (int j = 0; j < i; j++) {
333                                 inputBytes.append(inputBuffer[j]);
334                         }
335                 } while (i > -1);
336
337                 //TODO switch to examining the DER, so we don't get failure messages
338                 //TODO support DSA when switching
339                 try {
340                         return getRSAPkcs8DerKey(inputBytes.toByteArray());
341
342                 } catch (CredentialFactoryException e) {
343                         try {
344                                 log.debug("Unable to load private key as PKCS8, attempting raw RSA.");
345                                 return getRSARawDERKey(inputBytes.toByteArray());
346
347                         } catch (CredentialFactoryException e2) {
348                                 log.debug("Unable to load private key as raw RSA, attempting raw DSA.");
349                                 return getDSARawDERKey(inputBytes.toByteArray());
350                         }
351                 }
352
353         }
354
355         private PrivateKey getPEMKey(InputStream inStream) throws CredentialFactoryException, IOException {
356
357                 byte[] inputBuffer = new byte[8];
358                 int i;
359                 ByteContainer inputBytes = new ByteContainer(800);
360                 do {
361                         i = inStream.read(inputBuffer);
362                         for (int j = 0; j < i; j++) {
363                                 inputBytes.append(inputBuffer[j]);
364                         }
365                 } while (i > -1);
366
367                 BufferedReader in =
368                         new BufferedReader(new InputStreamReader(new ByteArrayInputStream(inputBytes.toByteArray())));
369                 String str;
370                 while ((str = in.readLine()) != null) {
371
372                         if (str.matches("^.*-----BEGIN PRIVATE KEY-----.*$")) {
373                                 log.debug("Key appears to be in PKCS8 format.");
374                                 in.close();
375                                 return getPkcs8Key(
376                                         singleDerFromPEM(
377                                                 inputBytes.toByteArray(),
378                                                 "-----BEGIN PRIVATE KEY-----",
379                                                 "-----END PRIVATE KEY-----"));
380
381                         } else if (str.matches("^.*-----BEGIN RSA PRIVATE KEY-----.*$")) {
382                                 in.close();
383                                 log.debug("Key appears to be RSA in raw format.");
384                                 return getRSARawDERKey(
385                                         singleDerFromPEM(
386                                                 inputBytes.toByteArray(),
387                                                 "-----BEGIN RSA PRIVATE KEY-----",
388                                                 "-----END RSA PRIVATE KEY-----"));
389                         } else if (str.matches("^.*-----BEGIN DSA PRIVATE KEY-----.*$")) {
390                                 in.close();
391                                 log.debug("Key appears to be DSA in raw format.");
392                                 return getDSARawDERKey(
393                                         singleDerFromPEM(
394                                                 inputBytes.toByteArray(),
395                                                 "-----BEGIN DSA PRIVATE KEY-----",
396                                                 "-----END DSA PRIVATE KEY-----"));
397                         }
398                 }
399                 in.close();
400                 log.error("Unsupported formatting.  Available PEM types are PKCS8, Raw RSA, and Raw DSA.");
401                 throw new CredentialFactoryException("Failed to initialize Credential Resolver.");
402
403         }
404
405         private PrivateKey getRSAPkcs8DerKey(byte[] bytes) throws CredentialFactoryException {
406
407                 try {
408                         KeyFactory keyFactory = KeyFactory.getInstance("RSA");
409                         PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(bytes);
410                         return keyFactory.generatePrivate(keySpec);
411
412                 } catch (Exception e) {
413                         log.error("Unable to load private key: " + e);
414                         throw new CredentialFactoryException("Unable to load private key.");
415                 }
416         }
417
418         private PrivateKey getDSAPkcs8DerKey(byte[] bytes) throws CredentialFactoryException {
419
420                 try {
421                         KeyFactory keyFactory = KeyFactory.getInstance("DSA");
422                         PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(bytes);
423                         return keyFactory.generatePrivate(keySpec);
424
425                 } catch (Exception e) {
426                         log.error("Unable to load private key: " + e);
427                         throw new CredentialFactoryException("Unable to load private key.");
428                 }
429         }
430
431         private PrivateKey getRSARawDERKey(byte[] bytes) throws CredentialFactoryException {
432
433                 try {
434                         DerValue root = new DerValue(bytes);
435                         if (root.tag != DerValue.tag_Sequence) {
436                                 log.error("Unexpected data type.  Unable to load data as an RSA key.");
437                                 throw new CredentialFactoryException("Unable to load private key.");
438                         }
439
440                         DerValue[] childValues = new DerValue[10];
441                         childValues[0] = root.data.getDerValue();
442                         childValues[1] = root.data.getDerValue();
443                         childValues[2] = root.data.getDerValue();
444                         childValues[3] = root.data.getDerValue();
445                         childValues[4] = root.data.getDerValue();
446                         childValues[5] = root.data.getDerValue();
447                         childValues[6] = root.data.getDerValue();
448                         childValues[7] = root.data.getDerValue();
449                         childValues[8] = root.data.getDerValue();
450
451                         //This data is optional.
452                         if (root.data.available() != 0) {
453                                 childValues[9] = root.data.getDerValue();
454                                 if (root.data.available() != 0) {
455                                         log.error("Data overflow.  Unable to load data as an RSA key.");
456                                         throw new CredentialFactoryException("Unable to load private key.");
457                                 }
458                         }
459
460                         if (childValues[0].tag != DerValue.tag_Integer
461                                 || childValues[1].tag != DerValue.tag_Integer
462                                 || childValues[2].tag != DerValue.tag_Integer
463                                 || childValues[3].tag != DerValue.tag_Integer
464                                 || childValues[4].tag != DerValue.tag_Integer
465                                 || childValues[5].tag != DerValue.tag_Integer
466                                 || childValues[6].tag != DerValue.tag_Integer
467                                 || childValues[7].tag != DerValue.tag_Integer
468                                 || childValues[8].tag != DerValue.tag_Integer) {
469                                 log.error("Unexpected data type.  Unable to load data as an RSA key.");
470                                 throw new CredentialFactoryException("Unable to load private key.");
471                         }
472
473                         RSAPrivateCrtKeySpec keySpec =
474                                 new RSAPrivateCrtKeySpec(
475                                         childValues[1].getBigInteger(),
476                                         childValues[2].getBigInteger(),
477                                         childValues[3].getBigInteger(),
478                                         childValues[4].getBigInteger(),
479                                         childValues[5].getBigInteger(),
480                                         childValues[6].getBigInteger(),
481                                         childValues[7].getBigInteger(),
482                                         childValues[8].getBigInteger());
483
484                         KeyFactory keyFactory = KeyFactory.getInstance("RSA");
485
486                         return keyFactory.generatePrivate(keySpec);
487
488                 } catch (IOException e) {
489                         log.error("Invalid DER encoding for RSA key: " + e);
490                         throw new CredentialFactoryException("Unable to load private key.");
491                 } catch (GeneralSecurityException e) {
492                         log.error("Unable to marshall private key: " + e);
493                         throw new CredentialFactoryException("Unable to load private key.");
494                 }
495
496         }
497
498         private PrivateKey getDSARawDERKey(byte[] bytes) throws CredentialFactoryException {
499
500                 try {
501                         DerValue root = new DerValue(bytes);
502                         if (root.tag != DerValue.tag_Sequence) {
503                                 log.error("Unexpected data type.  Unable to load data as an DSA key.");
504                                 throw new CredentialFactoryException("Unable to load private key.");
505                         }
506
507                         DerValue[] childValues = new DerValue[6];
508                         childValues[0] = root.data.getDerValue();
509                         childValues[1] = root.data.getDerValue();
510                         childValues[2] = root.data.getDerValue();
511                         childValues[3] = root.data.getDerValue();
512                         childValues[4] = root.data.getDerValue();
513                         childValues[5] = root.data.getDerValue();
514
515                         if (root.data.available() != 0) {
516                                 log.error("Data overflow.  Unable to load data as an DSA key.");
517                                 throw new CredentialFactoryException("Unable to load private key.");
518                         }
519
520                         if (childValues[0].tag != DerValue.tag_Integer
521                                 || childValues[1].tag != DerValue.tag_Integer
522                                 || childValues[2].tag != DerValue.tag_Integer
523                                 || childValues[3].tag != DerValue.tag_Integer
524                                 || childValues[4].tag != DerValue.tag_Integer
525                                 || childValues[5].tag != DerValue.tag_Integer) {
526                                 log.error("Unexpected data type.  Unable to load data as an DSA key.");
527                                 throw new CredentialFactoryException("Unable to load private key.");
528                         }
529
530                         DSAPrivateKeySpec keySpec =
531                                 new DSAPrivateKeySpec(
532                                         childValues[5].getBigInteger(),
533                                         childValues[1].getBigInteger(),
534                                         childValues[2].getBigInteger(),
535                                         childValues[3].getBigInteger());
536
537                         KeyFactory keyFactory = KeyFactory.getInstance("DSA");
538
539                         return keyFactory.generatePrivate(keySpec);
540
541                 } catch (IOException e) {
542                         log.error("Invalid DER encoding for DSA key: " + e);
543                         throw new CredentialFactoryException("Unable to load private key.");
544                 } catch (GeneralSecurityException e) {
545                         log.error("Unable to marshall private key: " + e);
546                         throw new CredentialFactoryException("Unable to load private key.");
547                 }
548
549         }
550
551         private PrivateKey getPkcs8Key(byte[] bytes) throws CredentialFactoryException {
552
553                 try {
554                         DerValue root = new DerValue(bytes);
555                         if (root.tag != DerValue.tag_Sequence) {
556                                 log.error("Unexpected data type.  Unable to load data as a PKCS8 formatted key.");
557                                 throw new CredentialFactoryException("Unable to load private key.");
558                         }
559
560                         DerValue[] childValues = new DerValue[2];
561                         childValues[0] = root.data.getDerValue();
562                         childValues[1] = root.data.getDerValue();
563
564                         if (childValues[0].tag != DerValue.tag_Integer || childValues[1].tag != DerValue.tag_Sequence) {
565                                 log.error("Unexpected data type.  Unable to load data as a PKCS8 formatted key.");
566                                 throw new CredentialFactoryException("Unable to load private key.");
567                         }
568
569                         DerValue grandChild = childValues[1].data.getDerValue();
570                         if (grandChild.tag != DerValue.tag_ObjectId) {
571                                 log.error("Unexpected data type.  Unable to load data as a PKCS8 formatted key.");
572                                 throw new CredentialFactoryException("Unable to load private key.");
573                         }
574
575                         String keyOID = grandChild.getOID().toString();
576                         if (keyOID.equals(FileCredentialResolver.RSAKey_OID)) {
577                                 log.debug("Found RSA key in PKCS8.");
578                                 return getRSAPkcs8DerKey(bytes);
579                         } else if (keyOID.equals(FileCredentialResolver.DSAKey_OID)) {
580                                 log.debug("Found DSA key in PKCS8.");
581                                 return getDSAPkcs8DerKey(bytes);
582                         } else {
583                                 log.error("Unexpected key type.  Only RSA and DSA keys are supported in PKCS8 format.");
584                                 throw new CredentialFactoryException("Unable to load private key.");
585                         }
586
587                 } catch (IOException e) {
588                         log.error("Invalid DER encoding for PKCS8 formatted key: " + e);
589                         throw new CredentialFactoryException("Unable to load private key.");
590                 }
591         }
592
593         private byte[] singleDerFromPEM(byte[] bytes, String beginToken, String endToken) throws IOException {
594
595                 try {
596
597                         BufferedReader in = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(bytes)));
598                         String str;
599                         boolean insideBase64 = false;
600                         StringBuffer base64Key = null;
601                         while ((str = in.readLine()) != null) {
602
603                                 if (insideBase64) {
604                                         if (str.matches("^.*" + endToken + ".*$")) {
605                                                 break;
606                                         }
607                                         {
608                                                 base64Key.append(str);
609                                         }
610                                 } else if (str.matches("^.*" + beginToken + ".*$")) {
611                                         insideBase64 = true;
612                                         base64Key = new StringBuffer();
613                                 }
614                         }
615                         in.close();
616                         if (base64Key == null || base64Key.length() == 0) {
617                                 log.error("Could not find Base 64 encoded entity.");
618                                 throw new IOException("Could not find Base 64 encoded entity.");
619                         }
620
621                         try {
622                                 BASE64Decoder decoder = new BASE64Decoder();
623                                 return decoder.decodeBuffer(base64Key.toString());
624                         } catch (IOException ioe) {
625                                 log.error("Could not decode Base 64: " + ioe);
626                                 throw new IOException("Could not decode Base 64.");
627                         }
628
629                 } catch (IOException e) {
630                         log.error("Could not load resource from specified location: " + e);
631                         throw new IOException("Could not load resource from specified location.");
632                 }
633
634         }
635
636         private String getCertFormat(Element e) throws CredentialFactoryException {
637
638                 NodeList certificateElements = e.getElementsByTagNameNS(Credentials.credentialsNamespace, "Certificate");
639                 if (certificateElements.getLength() < 1) {
640                         log.error("Certificate not specified.");
641                         throw new CredentialFactoryException("File Credential Resolver requires a <Certificate> specification.");
642                 }
643                 if (certificateElements.getLength() > 1) {
644                         log.error("Multiple Certificate path specifications, using first.");
645                 }
646
647                 String format = ((Element) certificateElements.item(0)).getAttribute("format");
648                 if (format == null || format.equals("")) {
649                         log.debug("No format specified for certificate, using default (PEM) format.");
650                         format = "PEM";
651                 }
652
653                 if ((!format.equals("PEM")) && (!format.equals("DER"))) {
654                         log.error("File credential resolver only supports the (DER) and (PEM) formats.");
655                         throw new CredentialFactoryException("Failed to initialize Credential Resolver.");
656                 }
657
658                 return format;
659         }
660
661         private String getKeyFormat(Element e) throws CredentialFactoryException {
662
663                 NodeList keyElements = e.getElementsByTagNameNS(Credentials.credentialsNamespace, "Key");
664                 if (keyElements.getLength() < 1) {
665                         log.error("Key not specified.");
666                         throw new CredentialFactoryException("File Credential Resolver requires a <Key> specification.");
667                 }
668                 if (keyElements.getLength() > 1) {
669                         log.error("Multiple Keyf path specifications, using first.");
670                 }
671
672                 String format = ((Element) keyElements.item(0)).getAttribute("format");
673                 if (format == null || format.equals("")) {
674                         log.debug("No format specified for certificate, using default (PEM) format.");
675                         format = "PEM";
676                 }
677
678                 if (!((format.equals("DER")) || (format.equals("PEM")))) {
679                         log.error("File credential resolver currently only supports (DER) and (PEM) formats.");
680                         throw new CredentialFactoryException("Failed to initialize Credential Resolver.");
681                 }
682                 return format;
683         }
684
685         private String getCertPath(Element e) throws CredentialFactoryException {
686
687                 NodeList certificateElements = e.getElementsByTagNameNS(Credentials.credentialsNamespace, "Certificate");
688                 if (certificateElements.getLength() < 1) {
689                         log.error("Certificate not specified.");
690                         throw new CredentialFactoryException("File Credential Resolver requires a <Certificate> specification.");
691                 }
692                 if (certificateElements.getLength() > 1) {
693                         log.error("Multiple Certificate path specifications, using first.");
694                 }
695
696                 NodeList pathElements =
697                         ((Element) certificateElements.item(0)).getElementsByTagNameNS(Credentials.credentialsNamespace, "Path");
698                 if (pathElements.getLength() < 1) {
699                         log.error("Certificate path not specified.");
700                         throw new CredentialFactoryException("File Credential Resolver requires a <Certificate><Path/></Certificate> specification.");
701                 }
702                 if (pathElements.getLength() > 1) {
703                         log.error("Multiple Certificate path specifications, using first.");
704                 }
705                 Node tnode = pathElements.item(0).getFirstChild();
706                 String path = null;
707                 if (tnode != null && tnode.getNodeType() == Node.TEXT_NODE) {
708                         path = tnode.getNodeValue();
709                 }
710                 if (path == null || path.equals("")) {
711                         log.error("Certificate path not specified.");
712                         throw new CredentialFactoryException("File Credential Resolver requires a <Certificate><Path/></Certificate> specification.");
713                 }
714                 return path;
715         }
716
717         private String[] getCAPaths(Element e) throws CredentialFactoryException {
718
719                 NodeList certificateElements = e.getElementsByTagNameNS(Credentials.credentialsNamespace, "Certificate");
720                 if (certificateElements.getLength() < 1) {
721                         log.error("Certificate not specified.");
722                         throw new CredentialFactoryException("File Credential Resolver requires a <Certificate> specification.");
723                 }
724                 if (certificateElements.getLength() > 1) {
725                         log.error("Multiple Certificate path specifications, using first.");
726                 }
727
728                 NodeList pathElements =
729                         ((Element) certificateElements.item(0)).getElementsByTagNameNS(Credentials.credentialsNamespace, "CAPath");
730                 if (pathElements.getLength() < 1) {
731                         log.debug("No CA Certificate paths specified.");
732                         return null;
733                 }
734                 ArrayList paths = new ArrayList();
735                 for (int i = 0; i < pathElements.getLength(); i++) {
736                         Node tnode = pathElements.item(i).getFirstChild();
737                         String path = null;
738                         if (tnode != null && tnode.getNodeType() == Node.TEXT_NODE) {
739                                 path = tnode.getNodeValue();
740                         }
741                         if (path != null && !(path.equals(""))) {
742                                 paths.add(path);
743                         }
744                         if (paths.isEmpty()) {
745                                 log.debug("No CA Certificate paths specified.");
746                         }
747                 }
748                 return (String[]) paths.toArray(new String[0]);
749         }
750
751         private String getKeyPath(Element e) throws CredentialFactoryException {
752
753                 NodeList keyElements = e.getElementsByTagNameNS(Credentials.credentialsNamespace, "Key");
754                 if (keyElements.getLength() < 1) {
755                         log.error("Key not specified.");
756                         throw new CredentialFactoryException("File Credential Resolver requires a <Key> specification.");
757                 }
758                 if (keyElements.getLength() > 1) {
759                         log.error("Multiple Key path specifications, using first.");
760                 }
761
762                 NodeList pathElements =
763                         ((Element) keyElements.item(0)).getElementsByTagNameNS(Credentials.credentialsNamespace, "Path");
764                 if (pathElements.getLength() < 1) {
765                         log.error("Key path not specified.");
766                         throw new CredentialFactoryException("File Credential Resolver requires a <Key><Path/></Certificate> specification.");
767                 }
768                 if (pathElements.getLength() > 1) {
769                         log.error("Multiple Key path specifications, using first.");
770                 }
771                 Node tnode = pathElements.item(0).getFirstChild();
772                 String path = null;
773                 if (tnode != null && tnode.getNodeType() == Node.TEXT_NODE) {
774                         path = tnode.getNodeValue();
775                 }
776                 if (path == null || path.equals("")) {
777                         log.error("Key path not specified.");
778                         throw new CredentialFactoryException("File Credential Resolver requires a <Key><Path/></Certificate> specification.");
779                 }
780                 return path;
781         }
782
783         /**
784          * 
785          * Loads a specified bundle of certs individually and returns an array of <code>Certificate</code> objects. This
786          * is needed because the standard <code>CertificateFactory.getCertificates(InputStream)</code> method bails out
787          * when it has trouble loading any cert and cannot handle "comments".
788          */
789         private Certificate[] loadCertificates(InputStream inStream, String certType) throws CredentialFactoryException {
790
791                 ArrayList certificates = new ArrayList();
792
793                 try {
794                         CertificateFactory certFactory = CertificateFactory.getInstance(certType);
795
796                         BufferedReader in = new BufferedReader(new InputStreamReader(inStream));
797                         String str;
798                         boolean insideCert = false;
799                         StringBuffer rawCert = null;
800                         while ((str = in.readLine()) != null) {
801
802                                 if (insideCert) {
803                                         rawCert.append(str);
804                                         rawCert.append(System.getProperty("line.separator"));
805                                         if (str.matches("^.*-----END CERTIFICATE-----.*$")) {
806                                                 insideCert = false;
807                                                 try {
808                                                         Certificate cert =
809                                                                 certFactory.generateCertificate(
810                                                                         new ByteArrayInputStream(rawCert.toString().getBytes()));
811                                                         certificates.add(cert);
812                                                 } catch (CertificateException ce) {
813                                                         log.warn("Failed to load a certificate from the certificate bundle: " + ce);
814                                                         if (log.isDebugEnabled()) {
815                                                                 log.debug(
816                                                                         "Dump of bad certificate: "
817                                                                                 + System.getProperty("line.separator")
818                                                                                 + rawCert.toString());
819                                                         }
820                                                 }
821                                                 continue;
822                                         }
823                                 } else if (str.matches("^.*-----BEGIN CERTIFICATE-----.*$")) {
824                                         insideCert = true;
825                                         rawCert = new StringBuffer();
826                                         rawCert.append(str);
827                                         rawCert.append(System.getProperty("line.separator"));
828                                 }
829                         }
830                         in.close();
831                 } catch (IOException p) {
832                         log.error("Could not load resource from specified location: " + p);
833                         throw new CredentialFactoryException("Unable to load certificates.");
834                 } catch (CertificateException p) {
835                         log.error("Problem loading certificate factory: " + p);
836                         throw new CredentialFactoryException("Unable to load certificates.");
837                 }
838
839                 return (Certificate[]) certificates.toArray(new Certificate[0]);
840         }
841
842         /**
843          * Given an ArrayList containing a base certificate and an array of unordered certificates, populates the ArrayList
844          * with an ordered certificate chain, based on subject and issuer.
845          * 
846          * @param chainSource
847          *            array of certificates to pull from
848          * @param chainDest
849          *            ArrayList containing base certificate
850          * @throws InvalidCertificateChainException
851          *             thrown if a chain cannot be constructed from the specified elements
852          */
853
854         protected void walkChain(X509Certificate[] chainSource, ArrayList chainDest) throws CredentialFactoryException {
855
856                 X509Certificate currentCert = (X509Certificate) chainDest.get(chainDest.size() - 1);
857                 if (currentCert.getSubjectDN().equals(currentCert.getIssuerDN())) {
858                         log.debug("Found self-signed root cert: " + currentCert.getSubjectDN());
859                         return;
860                 } else {
861                         //TODO maybe this should check more than the DN...
862                         for (int i = 0; chainSource.length > i; i++) {
863                                 if (currentCert.getIssuerDN().equals(chainSource[i].getSubjectDN())) {
864                                         chainDest.add(chainSource[i]);
865                                         walkChain(chainSource, chainDest);
866                                         return;
867                                 }
868                         }
869                         log.error("Incomplete certificate chain.");
870                         throw new CredentialFactoryException("Incomplete cerficate chain.");
871                 }
872         }
873
874         /**
875          * Boolean indication of whether a given private key and public key form a valid keypair.
876          * 
877          * @param pubKey
878          *            the public key
879          * @param privKey
880          *            the private key
881          */
882
883         protected boolean isMatchingKey(PublicKey pubKey, PrivateKey privKey) {
884
885                 try {
886                         String controlString = "asdf";
887                         log.debug("Checking for matching private key/public key pair");
888                         Signature signature = null;
889                         try {
890                                 signature = Signature.getInstance(privKey.getAlgorithm());
891                         } catch (NoSuchAlgorithmException nsae) {
892                                 log.debug("No provider for (RSA) signature, attempting (MD5withRSA).");
893                                 if (privKey.getAlgorithm().equals("RSA")) {
894                                         signature = Signature.getInstance("MD5withRSA");
895                                 } else {
896                                         throw nsae;
897                                 }
898                         }
899                         signature.initSign(privKey);
900                         signature.update(controlString.getBytes());
901                         byte[] sigBytes = signature.sign();
902                         signature.initVerify(pubKey);
903                         signature.update(controlString.getBytes());
904                         if (signature.verify(sigBytes)) {
905                                 log.debug("Found match.");
906                                 return true;
907                         }
908                 } catch (Exception e) {
909                         log.warn(e);
910                 }
911                 log.debug("This pair does not match.");
912                 return false;
913         }
914
915         /**
916          * Auto-enlarging container for bytes.
917          */
918
919         // Sure makes you wish bytes were first class objects.
920
921         private class ByteContainer {
922
923                 private byte[] buffer;
924                 private int cushion;
925                 private int currentSize = 0;
926
927                 private ByteContainer(int cushion) {
928                         buffer = new byte[cushion];
929                         this.cushion = cushion;
930                 }
931
932                 private void grow() {
933                         log.debug("Growing ByteContainer.");
934                         int newSize = currentSize + cushion;
935                         byte[] b = new byte[newSize];
936                         int toCopy = Math.min(currentSize, newSize);
937                         int i;
938                         for (i = 0; i < toCopy; i++) {
939                                 b[i] = buffer[i];
940                         }
941                         buffer = b;
942                 }
943
944                 /**
945                  * Returns an array of the bytes in the container.
946                  * <p>
947                  */
948
949                 private byte[] toByteArray() {
950                         byte[] b = new byte[currentSize];
951                         for (int i = 0; i < currentSize; i++) {
952                                 b[i] = buffer[i];
953                         }
954                         return b;
955                 }
956
957                 /**
958                  * Add one byte to the end of the container.
959                  */
960
961                 private void append(byte b) {
962                         if (currentSize == buffer.length) {
963                                 grow();
964                         }
965                         buffer[currentSize] = b;
966                         currentSize++;
967                 }
968
969         }
970
971 }
972
973 class KeystoreCredentialResolver implements CredentialResolver {
974
975         private static Logger log = Logger.getLogger(KeystoreCredentialResolver.class.getName());
976
977         public Credential loadCredential(Element e) throws CredentialFactoryException {
978
979                 if (!e.getTagName().equals("KeyStoreResolver")) {
980                         log.error("Invalid Credential Resolver configuration: expected <KeyStoreResolver> .");
981                         throw new CredentialFactoryException("Failed to initialize Credential Resolver.");
982                 }
983
984                 String id = e.getAttribute("Id");
985                 if (id == null || id.equals("")) {
986                         log.error("Credential Resolvers require specification of the attribute \"Id\".");
987                         throw new CredentialFactoryException("Failed to initialize Credential Resolver.");
988                 }
989
990                 String keyStoreType = e.getAttribute("storeType");
991                 if (keyStoreType == null || keyStoreType.equals("")) {
992                         log.debug("Using default store type for credential.");
993                         keyStoreType = "JKS";
994                 }
995
996                 String path = loadPath(e);
997                 String alias = loadAlias(e);
998                 String certAlias = loadCertAlias(e, alias);
999                 String keyPassword = loadKeyPassword(e);
1000                 String keyStorePassword = loadKeyStorePassword(e);
1001
1002                 try {
1003                         KeyStore keyStore = KeyStore.getInstance(keyStoreType);
1004
1005                         keyStore.load(new ShibResource(path, this.getClass()).getInputStream(), keyStorePassword.toCharArray());
1006
1007                         PrivateKey privateKey = (PrivateKey) keyStore.getKey(alias, keyPassword.toCharArray());
1008
1009                         if (privateKey == null) {
1010                                 throw new CredentialFactoryException("No key entry was found with an alias of (" + alias + ").");
1011                         }
1012
1013                         Certificate[] certificates = keyStore.getCertificateChain(certAlias);
1014                         if (certificates == null) {
1015                                 throw new CredentialFactoryException(
1016                                         "An error occurred while reading the java keystore: No certificate found with the specified alias ("
1017                                                 + certAlias
1018                                                 + ").");
1019                         }
1020
1021                         X509Certificate[] x509Certs = new X509Certificate[certificates.length];
1022                         for (int i = 0; i < certificates.length; i++) {
1023                                 if (certificates[i] instanceof X509Certificate) {
1024                                         x509Certs[i] = (X509Certificate) certificates[i];
1025                                 } else {
1026                                         throw new CredentialFactoryException(
1027                                                 "The KeyStore Credential Resolver can only load X509 certificates.  Found an unsupported certificate of type ("
1028                                                         + certificates[i]
1029                                                         + ").");
1030                                 }
1031                         }
1032
1033                         return new Credential(x509Certs, privateKey);
1034
1035                 } catch (KeyStoreException kse) {
1036                         throw new CredentialFactoryException("An error occurred while accessing the java keystore: " + kse);
1037                 } catch (NoSuchAlgorithmException nsae) {
1038                         throw new CredentialFactoryException("Appropriate JCE provider not found in the java environment: " + nsae);
1039                 } catch (CertificateException ce) {
1040                         throw new CredentialFactoryException(
1041                                 "The java keystore contained a certificate that could not be loaded: " + ce);
1042                 } catch (IOException ioe) {
1043                         throw new CredentialFactoryException("An error occurred while reading the java keystore: " + ioe);
1044                 } catch (UnrecoverableKeyException uke) {
1045                         throw new CredentialFactoryException(
1046                                 "An error occurred while attempting to load the key from the java keystore: " + uke);
1047                 }
1048
1049         }
1050
1051         private String loadPath(Element e) throws CredentialFactoryException {
1052
1053                 NodeList pathElements = e.getElementsByTagNameNS(Credentials.credentialsNamespace, "Path");
1054                 if (pathElements.getLength() < 1) {
1055                         log.error("KeyStore path not specified.");
1056                         throw new CredentialFactoryException("KeyStore Credential Resolver requires a <Path> specification.");
1057                 }
1058                 if (pathElements.getLength() > 1) {
1059                         log.error("Multiple KeyStore path specifications, using first.");
1060                 }
1061                 Node tnode = pathElements.item(0).getFirstChild();
1062                 String path = null;
1063                 if (tnode != null && tnode.getNodeType() == Node.TEXT_NODE) {
1064                         path = tnode.getNodeValue();
1065                 }
1066                 if (path == null || path.equals("")) {
1067                         log.error("KeyStore path not specified.");
1068                         throw new CredentialFactoryException("KeyStore Credential Resolver requires a <Path> specification.");
1069                 }
1070                 return path;
1071         }
1072
1073         private String loadAlias(Element e) throws CredentialFactoryException {
1074
1075                 NodeList aliasElements = e.getElementsByTagNameNS(Credentials.credentialsNamespace, "KeyAlias");
1076                 if (aliasElements.getLength() < 1) {
1077                         log.error("KeyStore key alias not specified.");
1078                         throw new CredentialFactoryException("KeyStore Credential Resolver requires an <KeyAlias> specification.");
1079                 }
1080                 if (aliasElements.getLength() > 1) {
1081                         log.error("Multiple key alias specifications, using first.");
1082                 }
1083                 Node tnode = aliasElements.item(0).getFirstChild();
1084                 String alias = null;
1085                 if (tnode != null && tnode.getNodeType() == Node.TEXT_NODE) {
1086                         alias = tnode.getNodeValue();
1087                 }
1088                 if (alias == null || alias.equals("")) {
1089                         log.error("KeyStore key alias not specified.");
1090                         throw new CredentialFactoryException("KeyStore Credential Resolver requires an <KeyAlias> specification.");
1091                 }
1092                 return alias;
1093         }
1094
1095         private String loadCertAlias(Element e, String defaultAlias) throws CredentialFactoryException {
1096
1097                 NodeList aliasElements = e.getElementsByTagNameNS(Credentials.credentialsNamespace, "CertAlias");
1098                 if (aliasElements.getLength() < 1) {
1099                         log.debug("KeyStore cert alias not specified, defaulting to key alias.");
1100                         return defaultAlias;
1101                 }
1102
1103                 if (aliasElements.getLength() > 1) {
1104                         log.error("Multiple cert alias specifications, using first.");
1105                 }
1106
1107                 Node tnode = aliasElements.item(0).getFirstChild();
1108                 String alias = null;
1109                 if (tnode != null && tnode.getNodeType() == Node.TEXT_NODE) {
1110                         alias = tnode.getNodeValue();
1111                 }
1112                 if (alias == null || alias.equals("")) {
1113                         log.debug("KeyStore cert alias not specified, defaulting to key alias.");
1114                         return defaultAlias;
1115                 }
1116                 return alias;
1117         }
1118
1119         private String loadKeyStorePassword(Element e) throws CredentialFactoryException {
1120
1121                 NodeList passwordElements = e.getElementsByTagNameNS(Credentials.credentialsNamespace, "StorePassword");
1122                 if (passwordElements.getLength() < 1) {
1123                         log.error("KeyStore password not specified.");
1124                         throw new CredentialFactoryException("KeyStore Credential Resolver requires an <StorePassword> specification.");
1125                 }
1126                 if (passwordElements.getLength() > 1) {
1127                         log.error("Multiple KeyStore password specifications, using first.");
1128                 }
1129                 Node tnode = passwordElements.item(0).getFirstChild();
1130                 String password = null;
1131                 if (tnode != null && tnode.getNodeType() == Node.TEXT_NODE) {
1132                         password = tnode.getNodeValue();
1133                 }
1134                 if (password == null || password.equals("")) {
1135                         log.error("KeyStore password not specified.");
1136                         throw new CredentialFactoryException("KeyStore Credential Resolver requires an <StorePassword> specification.");
1137                 }
1138                 return password;
1139         }
1140
1141         private String loadKeyPassword(Element e) throws CredentialFactoryException {
1142
1143                 NodeList passwords = e.getElementsByTagNameNS(Credentials.credentialsNamespace, "KeyPassword");
1144                 if (passwords.getLength() < 1) {
1145                         log.error("KeyStore key password not specified.");
1146                         throw new CredentialFactoryException("KeyStore Credential Resolver requires an <KeyPassword> specification.");
1147                 }
1148                 if (passwords.getLength() > 1) {
1149                         log.error("Multiple KeyStore key password specifications, using first.");
1150                 }
1151                 Node tnode = passwords.item(0).getFirstChild();
1152                 String password = null;
1153                 if (tnode != null && tnode.getNodeType() == Node.TEXT_NODE) {
1154                         password = tnode.getNodeValue();
1155                 }
1156                 if (password == null || password.equals("")) {
1157                         log.error("KeyStore key password not specified.");
1158                         throw new CredentialFactoryException("KeyStore Credential Resolver requires an <KeyPassword> specification.");
1159                 }
1160                 return password;
1161         }
1162 }
1163
1164 class CustomCredentialResolver implements CredentialResolver {
1165
1166         private static Logger log = Logger.getLogger(CustomCredentialResolver.class.getName());
1167
1168         public Credential loadCredential(Element e) throws CredentialFactoryException {
1169
1170                 if (!e.getTagName().equals("CustomCredResolver")) {
1171                         log.error("Invalid Credential Resolver configuration: expected <CustomCredResolver> .");
1172                         throw new CredentialFactoryException("Failed to initialize Credential Resolver.");
1173                 }
1174
1175                 String id = e.getAttribute("id");
1176                 if (id == null || id.equals("")) {
1177                         log.error("Credential Resolvers require specification of the attribute \"id\".");
1178                         throw new CredentialFactoryException("Failed to initialize Credential Resolver.");
1179                 }
1180
1181                 String className = e.getAttribute("Class");
1182                 if (className == null || className.equals("")) {
1183                         log.error("Custom Credential Resolver requires specification of the attribute \"Class\".");
1184                         throw new CredentialFactoryException("Failed to initialize Credential Resolver.");
1185                 }
1186
1187                 try {
1188                         return ((CredentialResolver) Class.forName(className).newInstance()).loadCredential(e);
1189
1190                 } catch (Exception loaderException) {
1191                         log.error(
1192                                 "Failed to load Custom Credential Resolver implementation class: " + loaderException.getMessage());
1193                         throw new CredentialFactoryException("Failed to initialize Credential Resolver.");
1194                 }
1195
1196         }
1197
1198 }
1199
1200 class CredentialFactoryException extends Exception {
1201
1202         CredentialFactoryException(String message) {
1203                 super(message);
1204         }
1205 }