Credentials file resolver will now load rsa keys in DER-encoded pkcs8.
[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.IOException;
41 import java.io.InputStream;
42 import java.security.KeyFactory;
43 import java.security.KeyStore;
44 import java.security.KeyStoreException;
45 import java.security.NoSuchAlgorithmException;
46 import java.security.PrivateKey;
47 import java.security.UnrecoverableKeyException;
48 import java.security.cert.Certificate;
49 import java.security.cert.CertificateException;
50 import java.security.cert.CertificateFactory;
51 import java.security.cert.X509Certificate;
52 import java.security.spec.PKCS8EncodedKeySpec;
53 import java.util.Collection;
54 import java.util.Hashtable;
55
56 import org.apache.log4j.Logger;
57 import org.w3c.dom.Element;
58 import org.w3c.dom.Node;
59 import org.w3c.dom.NodeList;
60
61 /**
62  * @author Walter Hoehn
63  *  
64  */
65 public class Credentials {
66
67         public static final String credentialsNamespace = "urn:mace:shibboleth:credentials:1.0";
68
69         private static Logger log = Logger.getLogger(Credentials.class.getName());
70         private Hashtable data = new Hashtable();
71
72         public Credentials(Element e) {
73
74                 if (!e.getTagName().equals("Credentials")) {
75                         throw new IllegalArgumentException();
76                 }
77
78                 NodeList resolverNodes = e.getChildNodes();
79                 if (resolverNodes.getLength() <= 0) {
80                         log.error("Credentials configuration inclues no Credential Resolver definitions.");
81                         throw new IllegalArgumentException("Cannot load credentials.");
82                 }
83
84                 for (int i = 0; resolverNodes.getLength() > i; i++) {
85                         if (resolverNodes.item(i).getNodeType() == Node.ELEMENT_NODE) {
86                                 try {
87
88                                         String credentialId = ((Element) resolverNodes.item(i)).getAttribute("Id");
89                                         if (credentialId == null || credentialId.equals("")) {
90                                                 log.error("Found credential that was not labeled with a unique \"Id\" attribute. Skipping.");
91                                         }
92
93                                         if (data.containsKey(credentialId)) {
94                                                 log.error("Duplicate credential id (" + credentialId + ") found. Skipping");
95                                         }
96
97                                         log.info("Found credential (" + credentialId + "). Loading...");
98                                         data.put(credentialId, CredentialFactory.loadCredential((Element) resolverNodes.item(i)));
99
100                                 } catch (CredentialFactoryException cfe) {
101                                         log.error("Could not load credential, skipping: " + cfe.getMessage());
102                                 } catch (ClassCastException cce) {
103                                         log.error("Problem realizing credential configuration" + cce.getMessage());
104                                 }
105                         }
106                 }
107         }
108
109         public boolean containsCredential(String identifier) {
110                 return data.containsKey(identifier);
111         }
112
113         public Credential getCredential(String identifier) {
114                 return (Credential) data.get(identifier);
115         }
116
117         static class CredentialFactory {
118
119                 private static Logger log = Logger.getLogger(CredentialFactory.class.getName());
120
121                 public static Credential loadCredential(Element e) throws CredentialFactoryException {
122                         if (e.getTagName().equals("KeyInfo")) {
123                                 return new KeyInfoCredentialResolver().loadCredential(e);
124                         }
125
126                         if (e.getTagName().equals("FileResolver")) {
127                                 return new FileCredentialResolver().loadCredential(e);
128                         }
129
130                         if (e.getTagName().equals("KeyStoreResolver")) {
131                                 return new KeystoreCredentialResolver().loadCredential(e);
132                         }
133
134                         if (e.getTagName().equals("CustomResolver")) {
135                                 return new CustomCredentialResolver().loadCredential(e);
136                         }
137
138                         log.error("Unrecognized Credential Resolver type: " + e.getTagName());
139                         throw new CredentialFactoryException("Failed to load credential.");
140                 }
141
142         }
143
144 }
145
146 class KeyInfoCredentialResolver implements CredentialResolver {
147         private static Logger log = Logger.getLogger(KeyInfoCredentialResolver.class.getName());
148         KeyInfoCredentialResolver() throws CredentialFactoryException {
149                 log.error("Credential Resolver (KeyInfoCredentialResolver) not implemented");
150                 throw new CredentialFactoryException("Failed to load credential.");
151         }
152
153         public Credential loadCredential(Element e) {
154                 return null;
155         }
156 }
157
158 class FileCredentialResolver implements CredentialResolver {
159         private static Logger log = Logger.getLogger(FileCredentialResolver.class.getName());
160
161         public Credential loadCredential(Element e) throws CredentialFactoryException {
162
163                 if (!e.getTagName().equals("FileResolver")) {
164                         log.error("Invalid Credential Resolver configuration: expected <FileResolver> .");
165                         throw new CredentialFactoryException("Failed to initialize Credential Resolver.");
166                 }
167
168                 String id = e.getAttribute("Id");
169                 if (id == null || id.equals("")) {
170                         log.error("Credential Resolvers require specification of the attribute \"Id\".");
171                         throw new CredentialFactoryException("Failed to initialize Credential Resolver.");
172                 }
173
174                 String certFormat = getCertFormat(e);
175                 String certPath = getCertPath(e);
176
177                 log.debug("Certificate Format: (" + certFormat + ").");
178                 log.debug("Certificate Path: (" + certPath + ").");
179
180                 //TODO provider optional
181                 //TODO other kinds of certs?
182                 //TODO provide a way to specify a separate CA bundle
183                 Collection chain = null;
184                 try {
185                         CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
186                         chain = certFactory.generateCertificates(new ShibResource(certPath, this.getClass()).getInputStream());
187
188                         //TODO probably want to walk the chain and make sure things are kosher
189                         //TODO need to order the chain
190                         if (chain.isEmpty()) {
191                                 log.error("File did not contain any valid certificates.");
192                                 throw new CredentialFactoryException("File did not contain any valid certificates.");
193                         }
194
195                 } catch (IOException p) {
196                         log.error("Could not load resource from specified location (" + certPath + "): " + p);
197                         throw new CredentialFactoryException("Unable to load certificates.");
198                 } catch (CertificateException p) {
199                         log.error("Problem parsing certificate at (" + certPath + "): " + p);
200                         throw new CredentialFactoryException("Unable to load certificates.");
201                 }
202                 String keyFormat = getKeyFormat(e);
203                 String keyPath = getKeyPath(e);
204                 log.debug("Key Format: (" + keyFormat + ").");
205                 log.debug("Key Path: (" + keyPath + ").");
206
207                 String keyAlgorithm = "RSA";
208
209                 //TODO providers?
210                 //TODO support DER, PEM, DER-PKCS8, and PEM-PKCS8?
211                 //TODO DSA
212
213                 PrivateKey key = null;
214
215                 if (keyAlgorithm.equals("RSA") && keyFormat.equals("DER-PKCS8")) {
216                         try {
217                                 key = getRSADERKey(new ShibResource(keyPath, this.getClass()).getInputStream());
218                         } catch (IOException ioe) {
219                                 log.error("Could not load resource from specified location (" + keyPath + "): " + e);
220                                 throw new CredentialFactoryException("Unable to load private key.");
221                         }
222                 } else {
223                         log.error("File credential resolver only supports the RSA keys in DER-encoded PKCS8 format (DER-PKCS8).");
224                         throw new CredentialFactoryException("Failed to initialize Credential Resolver.");
225                 }
226
227                 return new Credential(((X509Certificate[]) chain.toArray(new X509Certificate[0])), key);
228         }
229
230         private PrivateKey getRSADERKey(InputStream inStream) throws CredentialFactoryException {
231
232                 try {
233
234                         KeyFactory keyFactory = KeyFactory.getInstance("RSA");
235                         byte[] inputBuffer = new byte[8];
236                         int i;
237                         ByteContainer inputBytes = new ByteContainer(400);
238                         do {
239                                 i = inStream.read(inputBuffer);
240                                 for (int j = 0; j < i; j++) {
241                                         inputBytes.append(inputBuffer[j]);
242                                 }
243                         } while (i > -1);
244
245                         PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(inputBytes.toByteArray());
246
247                         return keyFactory.generatePrivate(keySpec);
248
249                 } catch (Exception e) {
250                         log.error("Unable to load private key: " + e);
251                         throw new CredentialFactoryException("Unable to load private key.");
252                 }
253
254         }
255
256         private String getCertFormat(Element e) throws CredentialFactoryException {
257
258                 NodeList certificateElements = e.getElementsByTagNameNS(Credentials.credentialsNamespace, "Certificate");
259                 if (certificateElements.getLength() < 1) {
260                         log.error("Certificate not specified.");
261                         throw new CredentialFactoryException("File Credential Resolver requires a <Certificate> specification.");
262                 }
263                 if (certificateElements.getLength() > 1) {
264                         log.error("Multiple Certificate path specifications, using first.");
265                 }
266
267                 String format = ((Element) certificateElements.item(0)).getAttribute("format");
268                 if (format == null || format.equals("")) {
269                         log.debug("No format specified for certificate, using default (PEM) format.");
270                         format = "PEM";
271                 }
272
273                 if ((!format.equals("PEM")) && (!format.equals("DER"))) {
274                         log.error("File credential resolver only supports the (DER) and (PEM) formats.");
275                         throw new CredentialFactoryException("Failed to initialize Credential Resolver.");
276                 }
277
278                 return format;
279         }
280
281         private String getKeyFormat(Element e) throws CredentialFactoryException {
282
283                 NodeList keyElements = e.getElementsByTagNameNS(Credentials.credentialsNamespace, "Key");
284                 if (keyElements.getLength() < 1) {
285                         log.error("Key not specified.");
286                         throw new CredentialFactoryException("File Credential Resolver requires a <Key> specification.");
287                 }
288                 if (keyElements.getLength() > 1) {
289                         log.error("Multiple Keyf path specifications, using first.");
290                 }
291
292                 String format = ((Element) keyElements.item(0)).getAttribute("format");
293                 if (format == null || format.equals("")) {
294                         log.debug("No format specified for certificate, using default (PEM) format.");
295                         format = "PEM";
296                 }
297
298                 if (!format.equals("DER-PKCS8")) {
299                         log.error("File credential resolver currently only supports (DER-PKCS8) format.");
300                         throw new CredentialFactoryException("Failed to initialize Credential Resolver.");
301                 }
302
303                 return format;
304         }
305
306         private String getCertPath(Element e) throws CredentialFactoryException {
307
308                 NodeList certificateElements = e.getElementsByTagNameNS(Credentials.credentialsNamespace, "Certificate");
309                 if (certificateElements.getLength() < 1) {
310                         log.error("Certificate not specified.");
311                         throw new CredentialFactoryException("File Credential Resolver requires a <Certificate> specification.");
312                 }
313                 if (certificateElements.getLength() > 1) {
314                         log.error("Multiple Certificate path specifications, using first.");
315                 }
316
317                 NodeList pathElements =
318                         ((Element) certificateElements.item(0)).getElementsByTagNameNS(Credentials.credentialsNamespace, "Path");
319                 if (pathElements.getLength() < 1) {
320                         log.error("Certificate path not specified.");
321                         throw new CredentialFactoryException("File Credential Resolver requires a <Certificate><Path/></Certificate> specification.");
322                 }
323                 if (pathElements.getLength() > 1) {
324                         log.error("Multiple Certificate path specifications, using first.");
325                 }
326                 Node tnode = pathElements.item(0).getFirstChild();
327                 String path = null;
328                 if (tnode != null && tnode.getNodeType() == Node.TEXT_NODE) {
329                         path = tnode.getNodeValue();
330                 }
331                 if (path == null || path.equals("")) {
332                         log.error("Certificate path not specified.");
333                         throw new CredentialFactoryException("File Credential Resolver requires a <Certificate><Path/></Certificate> specification.");
334                 }
335                 return path;
336         }
337
338         private String getKeyPath(Element e) throws CredentialFactoryException {
339
340                 NodeList keyElements = e.getElementsByTagNameNS(Credentials.credentialsNamespace, "Key");
341                 if (keyElements.getLength() < 1) {
342                         log.error("Key not specified.");
343                         throw new CredentialFactoryException("File Credential Resolver requires a <Key> specification.");
344                 }
345                 if (keyElements.getLength() > 1) {
346                         log.error("Multiple Key path specifications, using first.");
347                 }
348
349                 NodeList pathElements =
350                         ((Element) keyElements.item(0)).getElementsByTagNameNS(Credentials.credentialsNamespace, "Path");
351                 if (pathElements.getLength() < 1) {
352                         log.error("Key path not specified.");
353                         throw new CredentialFactoryException("File Credential Resolver requires a <Key><Path/></Certificate> specification.");
354                 }
355                 if (pathElements.getLength() > 1) {
356                         log.error("Multiple Key path specifications, using first.");
357                 }
358                 Node tnode = pathElements.item(0).getFirstChild();
359                 String path = null;
360                 if (tnode != null && tnode.getNodeType() == Node.TEXT_NODE) {
361                         path = tnode.getNodeValue();
362                 }
363                 if (path == null || path.equals("")) {
364                         log.error("Key path not specified.");
365                         throw new CredentialFactoryException("File Credential Resolver requires a <Key><Path/></Certificate> specification.");
366                 }
367                 return path;
368         }
369
370         /**
371          * Auto-enlarging container for bytes.
372          */
373
374         // Sure makes you wish bytes were first class objects.
375
376         private class ByteContainer {
377
378                 private byte[] buffer;
379                 private int cushion;
380                 private int currentSize = 0;
381
382                 private ByteContainer(int cushion) {
383                         buffer = new byte[cushion];
384                         this.cushion = cushion;
385                 }
386
387                 private void grow() {
388                         log.debug("Growing ByteContainer.");
389                         int newSize = currentSize + cushion;
390                         byte[] b = new byte[newSize];
391                         int toCopy = Math.min(currentSize, newSize);
392                         int i;
393                         for (i = 0; i < toCopy; i++) {
394                                 b[i] = buffer[i];
395                         }
396                         buffer = b;
397                 }
398
399                 /** 
400                  * Returns an array of the bytes in the container. <p>
401                  */
402
403                 private byte[] toByteArray() {
404                         byte[] b = new byte[currentSize];
405                         for (int i = 0; i < currentSize; i++) {
406                                 b[i] = buffer[i];
407                         }
408                         return b;
409                 }
410
411                 /** 
412                  * Add one byte to the end of the container.
413                  */
414
415                 private void append(byte b) {
416                         if (currentSize == buffer.length) {
417                                 grow();
418                         }
419                         buffer[currentSize] = b;
420                         currentSize++;
421                 }
422
423         }
424
425 }
426
427 class KeystoreCredentialResolver implements CredentialResolver {
428
429         private static Logger log = Logger.getLogger(KeystoreCredentialResolver.class.getName());
430
431         public Credential loadCredential(Element e) throws CredentialFactoryException {
432
433                 if (!e.getTagName().equals("KeyStoreResolver")) {
434                         log.error("Invalid Credential Resolver configuration: expected <KeyStoreResolver> .");
435                         throw new CredentialFactoryException("Failed to initialize Credential Resolver.");
436                 }
437
438                 String id = e.getAttribute("Id");
439                 if (id == null || id.equals("")) {
440                         log.error("Credential Resolvers require specification of the attribute \"Id\".");
441                         throw new CredentialFactoryException("Failed to initialize Credential Resolver.");
442                 }
443
444                 String keyStoreType = e.getAttribute("storeType");
445                 if (keyStoreType == null || keyStoreType.equals("")) {
446                         log.debug("Using default store type for credential.");
447                         keyStoreType = "JKS";
448                 }
449
450                 String path = loadPath(e);
451                 String alias = loadAlias(e);
452                 String certAlias = loadCertAlias(e, alias);
453                 String keyPassword = loadKeyPassword(e);
454                 String keyStorePassword = loadKeyStorePassword(e);
455
456                 try {
457                         KeyStore keyStore = KeyStore.getInstance(keyStoreType);
458
459                         keyStore.load(new ShibResource(path, this.getClass()).getInputStream(), keyStorePassword.toCharArray());
460
461                         PrivateKey privateKey = (PrivateKey) keyStore.getKey(alias, keyPassword.toCharArray());
462
463                         if (privateKey == null) {
464                                 throw new CredentialFactoryException("No key entry was found with an alias of (" + alias + ").");
465                         }
466
467                         Certificate[] certificates = keyStore.getCertificateChain(certAlias);
468                         if (certificates == null) {
469                                 throw new CredentialFactoryException(
470                                         "An error occurred while reading the java keystore: No certificate found with the specified alias ("
471                                                 + certAlias
472                                                 + ").");
473                         }
474
475                         X509Certificate[] x509Certs = new X509Certificate[certificates.length];
476                         for (int i = 0; i < certificates.length; i++) {
477                                 if (certificates[i] instanceof X509Certificate) {
478                                         x509Certs[i] = (X509Certificate) certificates[i];
479                                 } else {
480                                         throw new CredentialFactoryException(
481                                                 "The KeyStore Credential Resolver can only load X509 certificates.  Found an unsupported certificate of type ("
482                                                         + certificates[i]
483                                                         + ").");
484                                 }
485                         }
486
487                         return new Credential(x509Certs, privateKey);
488
489                 } catch (KeyStoreException kse) {
490                         throw new CredentialFactoryException("An error occurred while accessing the java keystore: " + kse);
491                 } catch (NoSuchAlgorithmException nsae) {
492                         throw new CredentialFactoryException("Appropriate JCE provider not found in the java environment: " + nsae);
493                 } catch (CertificateException ce) {
494                         throw new CredentialFactoryException(
495                                 "The java keystore contained a certificate that could not be loaded: " + ce);
496                 } catch (IOException ioe) {
497                         throw new CredentialFactoryException("An error occurred while reading the java keystore: " + ioe);
498                 } catch (UnrecoverableKeyException uke) {
499                         throw new CredentialFactoryException(
500                                 "An error occurred while attempting to load the key from the java keystore: " + uke);
501                 }
502
503         }
504
505         private String loadPath(Element e) throws CredentialFactoryException {
506
507                 NodeList pathElements = e.getElementsByTagNameNS(Credentials.credentialsNamespace, "Path");
508                 if (pathElements.getLength() < 1) {
509                         log.error("KeyStore path not specified.");
510                         throw new CredentialFactoryException("KeyStore Credential Resolver requires a <Path> specification.");
511                 }
512                 if (pathElements.getLength() > 1) {
513                         log.error("Multiple KeyStore path specifications, using first.");
514                 }
515                 Node tnode = pathElements.item(0).getFirstChild();
516                 String path = null;
517                 if (tnode != null && tnode.getNodeType() == Node.TEXT_NODE) {
518                         path = tnode.getNodeValue();
519                 }
520                 if (path == null || path.equals("")) {
521                         log.error("KeyStore path not specified.");
522                         throw new CredentialFactoryException("KeyStore Credential Resolver requires a <Path> specification.");
523                 }
524                 return path;
525         }
526
527         private String loadAlias(Element e) throws CredentialFactoryException {
528
529                 NodeList aliasElements = e.getElementsByTagNameNS(Credentials.credentialsNamespace, "KeyAlias");
530                 if (aliasElements.getLength() < 1) {
531                         log.error("KeyStore key alias not specified.");
532                         throw new CredentialFactoryException("KeyStore Credential Resolver requires an <KeyAlias> specification.");
533                 }
534                 if (aliasElements.getLength() > 1) {
535                         log.error("Multiple key alias specifications, using first.");
536                 }
537                 Node tnode = aliasElements.item(0).getFirstChild();
538                 String alias = null;
539                 if (tnode != null && tnode.getNodeType() == Node.TEXT_NODE) {
540                         alias = tnode.getNodeValue();
541                 }
542                 if (alias == null || alias.equals("")) {
543                         log.error("KeyStore key alias not specified.");
544                         throw new CredentialFactoryException("KeyStore Credential Resolver requires an <KeyAlias> specification.");
545                 }
546                 return alias;
547         }
548
549         private String loadCertAlias(Element e, String defaultAlias) throws CredentialFactoryException {
550
551                 NodeList aliasElements = e.getElementsByTagNameNS(Credentials.credentialsNamespace, "CertAlias");
552                 if (aliasElements.getLength() < 1) {
553                         log.debug("KeyStore cert alias not specified, defaulting to key alias.");
554                         return defaultAlias;
555                 }
556
557                 if (aliasElements.getLength() > 1) {
558                         log.error("Multiple cert alias specifications, using first.");
559                 }
560
561                 Node tnode = aliasElements.item(0).getFirstChild();
562                 String alias = null;
563                 if (tnode != null && tnode.getNodeType() == Node.TEXT_NODE) {
564                         alias = tnode.getNodeValue();
565                 }
566                 if (alias == null || alias.equals("")) {
567                         log.debug("KeyStore cert alias not specified, defaulting to key alias.");
568                         return defaultAlias;
569                 }
570                 return alias;
571         }
572
573         private String loadKeyStorePassword(Element e) throws CredentialFactoryException {
574
575                 NodeList passwordElements = e.getElementsByTagNameNS(Credentials.credentialsNamespace, "StorePassword");
576                 if (passwordElements.getLength() < 1) {
577                         log.error("KeyStore password not specified.");
578                         throw new CredentialFactoryException("KeyStore Credential Resolver requires an <StorePassword> specification.");
579                 }
580                 if (passwordElements.getLength() > 1) {
581                         log.error("Multiple KeyStore password specifications, using first.");
582                 }
583                 Node tnode = passwordElements.item(0).getFirstChild();
584                 String password = null;
585                 if (tnode != null && tnode.getNodeType() == Node.TEXT_NODE) {
586                         password = tnode.getNodeValue();
587                 }
588                 if (password == null || password.equals("")) {
589                         log.error("KeyStore password not specified.");
590                         throw new CredentialFactoryException("KeyStore Credential Resolver requires an <StorePassword> specification.");
591                 }
592                 return password;
593         }
594
595         private String loadKeyPassword(Element e) throws CredentialFactoryException {
596
597                 NodeList passwords = e.getElementsByTagNameNS(Credentials.credentialsNamespace, "KeyPassword");
598                 if (passwords.getLength() < 1) {
599                         log.error("KeyStore key password not specified.");
600                         throw new CredentialFactoryException("KeyStore Credential Resolver requires an <KeyPassword> specification.");
601                 }
602                 if (passwords.getLength() > 1) {
603                         log.error("Multiple KeyStore key password specifications, using first.");
604                 }
605                 Node tnode = passwords.item(0).getFirstChild();
606                 String password = null;
607                 if (tnode != null && tnode.getNodeType() == Node.TEXT_NODE) {
608                         password = tnode.getNodeValue();
609                 }
610                 if (password == null || password.equals("")) {
611                         log.error("KeyStore key password not specified.");
612                         throw new CredentialFactoryException("KeyStore Credential Resolver requires an <KeyPassword> specification.");
613                 }
614                 return password;
615         }
616 }
617
618 class CustomCredentialResolver implements CredentialResolver {
619
620         private static Logger log = Logger.getLogger(CustomCredentialResolver.class.getName());
621
622         public Credential loadCredential(Element e) throws CredentialFactoryException {
623
624                 if (!e.getTagName().equals("CustomCredResolver")) {
625                         log.error("Invalid Credential Resolver configuration: expected <CustomCredResolver> .");
626                         throw new CredentialFactoryException("Failed to initialize Credential Resolver.");
627                 }
628
629                 String id = e.getAttribute("id");
630                 if (id == null || id.equals("")) {
631                         log.error("Credential Resolvers require specification of the attribute \"id\".");
632                         throw new CredentialFactoryException("Failed to initialize Credential Resolver.");
633                 }
634
635                 String className = e.getAttribute("Class");
636                 if (className == null || className.equals("")) {
637                         log.error("Custom Credential Resolver requires specification of the attribute \"Class\".");
638                         throw new CredentialFactoryException("Failed to initialize Credential Resolver.");
639                 }
640
641                 try {
642                         return ((CredentialResolver) Class.forName(className).newInstance()).loadCredential(e);
643
644                 } catch (Exception loaderException) {
645                         log.error(
646                                 "Failed to load Custom Credential Resolver implementation class: " + loaderException.getMessage());
647                         throw new CredentialFactoryException("Failed to initialize Credential Resolver.");
648                 }
649
650         }
651
652 }
653
654 class CredentialFactoryException extends Exception {
655
656         CredentialFactoryException(String message) {
657                 super(message);
658         }
659 }