File Credential resolver now auto-detects format for DER encoded keys.
[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 encrypted keys
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                 //Examine the ASN.1 Structure to auto-detect the format
338                 //This gets a tad nasty
339                 try {
340                         DerValue root = new DerValue(inputBytes.toByteArray());
341                         if (root.tag != DerValue.tag_Sequence) {
342                                 log.error("Unexpected data type.  Unable to determine key type from data.");
343                                 throw new CredentialFactoryException("Unable to load private key.");
344                         }
345
346                         DerValue[] childValues = new DerValue[3];
347
348                         if (root.data.available() == 0) {
349                                 log.error("Unexpected data type.  Unable to determine key type from data.");
350                                 throw new CredentialFactoryException("Unable to load private key.");
351                         }
352
353                         childValues[0] = root.data.getDerValue();
354
355                         if (childValues[0].tag == DerValue.tag_Sequence) {
356
357                                 //May be encrypted pkcs8... dig further
358                                 if (root.data.available() == 0) {
359                                         log.error("Unexpected data type.  Unable to determine key type from data.");
360                                         throw new CredentialFactoryException("Unable to load private key.");
361                                 }
362                                 childValues[1] = root.data.getDerValue();
363                                 if (childValues[1].tag != DerValue.tag_OctetString) {
364                                         log.error("Unexpected data type.  Unable to determine key type from data.");
365                                         throw new CredentialFactoryException("Unable to load private key.");
366                                 }
367
368                                 if (childValues[0].data.available() == 0) {
369                                         log.error("Unexpected data type.  Unable to determine key type from data.");
370                                         throw new CredentialFactoryException("Unable to load private key.");
371                                 }
372                                 DerValue grandChild = childValues[0].data.getDerValue();
373                                 if (grandChild.tag != DerValue.tag_ObjectId) {
374                                         log.error("Unexpected data type.  Unable to determine key type from data.");
375                                         throw new CredentialFactoryException("Unable to load private key.");
376                                 }
377
378                                 System.err.println("OID: " + grandChild.getOID().toString());
379                                 log.error("Credential load cannot yet read encrypted keys.");
380                                 throw new CredentialFactoryException("Unable to load private key.");
381
382                         } else if (childValues[0].tag == DerValue.tag_Integer) {
383
384                                 //May be pkcs8, rsa, or dsa... dig further
385                                 if (root.data.available() == 0) {
386                                         log.error("Unexpected data type.  Unable to determine key type from data.");
387                                         throw new CredentialFactoryException("Unable to load private key.");
388                                 }
389                                 childValues[1] = root.data.getDerValue();
390                                 if (childValues[1].tag == DerValue.tag_Sequence) {
391                                         //May be pkcs8... dig further
392                                         if (root.data.available() == 0) {
393                                                 log.error("Unexpected data type.  Unable to determine key type from data.");
394                                                 throw new CredentialFactoryException("Unable to load private key.");
395                                         }
396                                         childValues[2] = root.data.getDerValue();
397                                         if (childValues[2].tag != DerValue.tag_OctetString) {
398                                                 log.error("Unexpected data type.  Unable to determine key type from data.");
399                                                 throw new CredentialFactoryException("Unable to load private key.");
400                                         }
401
402                                         if (childValues[1].data.available() == 0) {
403                                                 log.error("Unexpected data type.  Unable to determine key type from data.");
404                                                 throw new CredentialFactoryException("Unable to load private key.");
405                                         }
406                                         DerValue grandChild = childValues[1].data.getDerValue();
407                                         if (grandChild.tag != DerValue.tag_ObjectId) {
408                                                 log.error("Unexpected data type.  Unable to determine key type from data.");
409                                                 throw new CredentialFactoryException("Unable to load private key.");
410                                         }
411
412                                         log.debug("Key appears to be PKCS8. Loading...");
413                                         return getRSAPkcs8DerKey(inputBytes.toByteArray());
414
415                                 } else if (childValues[1].tag == DerValue.tag_Integer) {
416
417                                         //May be rsa or dsa... dig further
418                                         if (root.data.available() == 0
419                                                 || root.data.getDerValue().tag != DerValue.tag_Integer
420                                                 || root.data.available() == 0
421                                                 || root.data.getDerValue().tag != DerValue.tag_Integer
422                                                 || root.data.available() == 0
423                                                 || root.data.getDerValue().tag != DerValue.tag_Integer
424                                                 || root.data.available() == 0
425                                                 || root.data.getDerValue().tag != DerValue.tag_Integer) {
426                                                 log.error("Unexpected data type.  Unable to determine key type from data.");
427                                                 throw new CredentialFactoryException("Unable to load private key.");
428                                         }
429
430                                         if (root.data.available() == 0) {
431
432                                                 log.debug("Key appears to be DSA. Loading...");
433                                                 return getDSARawDerKey(inputBytes.toByteArray());
434
435                                         } else {
436
437                                                 DerValue dsaOverrun = root.data.getDerValue();
438                                                 if (dsaOverrun.tag != DerValue.tag_Integer) {
439                                                         log.error("Unexpected data type.  Unable to determine key type from data.");
440                                                         throw new CredentialFactoryException("Unable to load private key.");
441                                                 }
442
443                                                 log.debug("Key appears to be RSA. Loading...");
444                                                 return getRSARawDerKey(inputBytes.toByteArray());
445                                         }
446
447                                 } else {
448                                         log.error("Unexpected data type.  Unable to determine key type from data.");
449                                         throw new CredentialFactoryException("Unable to load private key.");
450                                 }
451
452                         } else {
453                                 log.error("Unexpected data type.  Unable to determine key type from data.");
454                                 throw new CredentialFactoryException("Unable to load private key.");
455                         }
456
457                 } catch (CredentialFactoryException e) {
458                         log.error("Invalid DER encoding for key: " + e);
459                         throw new CredentialFactoryException("Unable to load private key.");
460                 }
461
462         }
463
464         private PrivateKey getPEMKey(InputStream inStream) throws CredentialFactoryException, IOException {
465
466                 byte[] inputBuffer = new byte[8];
467                 int i;
468                 ByteContainer inputBytes = new ByteContainer(800);
469                 do {
470                         i = inStream.read(inputBuffer);
471                         for (int j = 0; j < i; j++) {
472                                 inputBytes.append(inputBuffer[j]);
473                         }
474                 } while (i > -1);
475
476                 BufferedReader in =
477                         new BufferedReader(new InputStreamReader(new ByteArrayInputStream(inputBytes.toByteArray())));
478                 String str;
479                 while ((str = in.readLine()) != null) {
480
481                         if (str.matches("^.*-----BEGIN PRIVATE KEY-----.*$")) {
482                                 log.debug("Key appears to be in PKCS8 format.");
483                                 in.close();
484                                 return getPkcs8Key(
485                                         singleDerFromPEM(
486                                                 inputBytes.toByteArray(),
487                                                 "-----BEGIN PRIVATE KEY-----",
488                                                 "-----END PRIVATE KEY-----"));
489
490                         } else if (str.matches("^.*-----BEGIN RSA PRIVATE KEY-----.*$")) {
491                                 in.close();
492                                 log.debug("Key appears to be RSA in raw format.");
493                                 return getRSARawDerKey(
494                                         singleDerFromPEM(
495                                                 inputBytes.toByteArray(),
496                                                 "-----BEGIN RSA PRIVATE KEY-----",
497                                                 "-----END RSA PRIVATE KEY-----"));
498                         } else if (str.matches("^.*-----BEGIN DSA PRIVATE KEY-----.*$")) {
499                                 in.close();
500                                 log.debug("Key appears to be DSA in raw format.");
501                                 return getDSARawDerKey(
502                                         singleDerFromPEM(
503                                                 inputBytes.toByteArray(),
504                                                 "-----BEGIN DSA PRIVATE KEY-----",
505                                                 "-----END DSA PRIVATE KEY-----"));
506                         }
507                 }
508                 in.close();
509                 log.error("Unsupported formatting.  Available PEM types are PKCS8, Raw RSA, and Raw DSA.");
510                 throw new CredentialFactoryException("Failed to initialize Credential Resolver.");
511
512         }
513
514         private PrivateKey getRSAPkcs8DerKey(byte[] bytes) throws CredentialFactoryException {
515
516                 try {
517                         KeyFactory keyFactory = KeyFactory.getInstance("RSA");
518                         PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(bytes);
519                         return keyFactory.generatePrivate(keySpec);
520
521                 } catch (Exception e) {
522                         log.error("Unable to load private key: " + e);
523                         throw new CredentialFactoryException("Unable to load private key.");
524                 }
525         }
526
527         private PrivateKey getDSAPkcs8DerKey(byte[] bytes) throws CredentialFactoryException {
528
529                 try {
530                         KeyFactory keyFactory = KeyFactory.getInstance("DSA");
531                         PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(bytes);
532                         return keyFactory.generatePrivate(keySpec);
533
534                 } catch (Exception e) {
535                         log.error("Unable to load private key: " + e);
536                         throw new CredentialFactoryException("Unable to load private key.");
537                 }
538         }
539
540         private PrivateKey getRSARawDerKey(byte[] bytes) throws CredentialFactoryException {
541
542                 try {
543                         DerValue root = new DerValue(bytes);
544                         if (root.tag != DerValue.tag_Sequence) {
545                                 log.error("Unexpected data type.  Unable to load data as an RSA key.");
546                                 throw new CredentialFactoryException("Unable to load private key.");
547                         }
548
549                         DerValue[] childValues = new DerValue[10];
550                         childValues[0] = root.data.getDerValue();
551                         childValues[1] = root.data.getDerValue();
552                         childValues[2] = root.data.getDerValue();
553                         childValues[3] = root.data.getDerValue();
554                         childValues[4] = root.data.getDerValue();
555                         childValues[5] = root.data.getDerValue();
556                         childValues[6] = root.data.getDerValue();
557                         childValues[7] = root.data.getDerValue();
558                         childValues[8] = root.data.getDerValue();
559
560                         //This data is optional.
561                         if (root.data.available() != 0) {
562                                 childValues[9] = root.data.getDerValue();
563                                 if (root.data.available() != 0) {
564                                         log.error("Data overflow.  Unable to load data as an RSA key.");
565                                         throw new CredentialFactoryException("Unable to load private key.");
566                                 }
567                         }
568
569                         if (childValues[0].tag != DerValue.tag_Integer
570                                 || childValues[1].tag != DerValue.tag_Integer
571                                 || childValues[2].tag != DerValue.tag_Integer
572                                 || childValues[3].tag != DerValue.tag_Integer
573                                 || childValues[4].tag != DerValue.tag_Integer
574                                 || childValues[5].tag != DerValue.tag_Integer
575                                 || childValues[6].tag != DerValue.tag_Integer
576                                 || childValues[7].tag != DerValue.tag_Integer
577                                 || childValues[8].tag != DerValue.tag_Integer) {
578                                 log.error("Unexpected data type.  Unable to load data as an RSA key.");
579                                 throw new CredentialFactoryException("Unable to load private key.");
580                         }
581
582                         RSAPrivateCrtKeySpec keySpec =
583                                 new RSAPrivateCrtKeySpec(
584                                         childValues[1].getBigInteger(),
585                                         childValues[2].getBigInteger(),
586                                         childValues[3].getBigInteger(),
587                                         childValues[4].getBigInteger(),
588                                         childValues[5].getBigInteger(),
589                                         childValues[6].getBigInteger(),
590                                         childValues[7].getBigInteger(),
591                                         childValues[8].getBigInteger());
592
593                         KeyFactory keyFactory = KeyFactory.getInstance("RSA");
594
595                         return keyFactory.generatePrivate(keySpec);
596
597                 } catch (IOException e) {
598                         log.error("Invalid DER encoding for RSA key: " + e);
599                         throw new CredentialFactoryException("Unable to load private key.");
600                 } catch (GeneralSecurityException e) {
601                         log.error("Unable to marshall private key: " + e);
602                         throw new CredentialFactoryException("Unable to load private key.");
603                 }
604
605         }
606
607         private PrivateKey getDSARawDerKey(byte[] bytes) throws CredentialFactoryException {
608
609                 try {
610                         DerValue root = new DerValue(bytes);
611                         if (root.tag != DerValue.tag_Sequence) {
612                                 log.error("Unexpected data type.  Unable to load data as an DSA key.");
613                                 throw new CredentialFactoryException("Unable to load private key.");
614                         }
615
616                         DerValue[] childValues = new DerValue[6];
617                         childValues[0] = root.data.getDerValue();
618                         childValues[1] = root.data.getDerValue();
619                         childValues[2] = root.data.getDerValue();
620                         childValues[3] = root.data.getDerValue();
621                         childValues[4] = root.data.getDerValue();
622                         childValues[5] = root.data.getDerValue();
623
624                         if (root.data.available() != 0) {
625                                 log.error("Data overflow.  Unable to load data as an DSA key.");
626                                 throw new CredentialFactoryException("Unable to load private key.");
627                         }
628
629                         if (childValues[0].tag != DerValue.tag_Integer
630                                 || childValues[1].tag != DerValue.tag_Integer
631                                 || childValues[2].tag != DerValue.tag_Integer
632                                 || childValues[3].tag != DerValue.tag_Integer
633                                 || childValues[4].tag != DerValue.tag_Integer
634                                 || childValues[5].tag != DerValue.tag_Integer) {
635                                 log.error("Unexpected data type.  Unable to load data as an DSA key.");
636                                 throw new CredentialFactoryException("Unable to load private key.");
637                         }
638
639                         DSAPrivateKeySpec keySpec =
640                                 new DSAPrivateKeySpec(
641                                         childValues[5].getBigInteger(),
642                                         childValues[1].getBigInteger(),
643                                         childValues[2].getBigInteger(),
644                                         childValues[3].getBigInteger());
645
646                         KeyFactory keyFactory = KeyFactory.getInstance("DSA");
647
648                         return keyFactory.generatePrivate(keySpec);
649
650                 } catch (IOException e) {
651                         log.error("Invalid DER encoding for DSA key: " + e);
652                         throw new CredentialFactoryException("Unable to load private key.");
653                 } catch (GeneralSecurityException e) {
654                         log.error("Unable to marshall private key: " + e);
655                         throw new CredentialFactoryException("Unable to load private key.");
656                 }
657
658         }
659
660         private PrivateKey getPkcs8Key(byte[] bytes) throws CredentialFactoryException {
661
662                 try {
663                         DerValue root = new DerValue(bytes);
664                         if (root.tag != DerValue.tag_Sequence) {
665                                 log.error("Unexpected data type.  Unable to load data as a PKCS8 formatted key.");
666                                 throw new CredentialFactoryException("Unable to load private key.");
667                         }
668
669                         DerValue[] childValues = new DerValue[2];
670                         childValues[0] = root.data.getDerValue();
671                         childValues[1] = root.data.getDerValue();
672
673                         if (childValues[0].tag != DerValue.tag_Integer || childValues[1].tag != DerValue.tag_Sequence) {
674                                 log.error("Unexpected data type.  Unable to load data as a PKCS8 formatted key.");
675                                 throw new CredentialFactoryException("Unable to load private key.");
676                         }
677
678                         DerValue grandChild = childValues[1].data.getDerValue();
679                         if (grandChild.tag != DerValue.tag_ObjectId) {
680                                 log.error("Unexpected data type.  Unable to load data as a PKCS8 formatted key.");
681                                 throw new CredentialFactoryException("Unable to load private key.");
682                         }
683
684                         String keyOID = grandChild.getOID().toString();
685                         if (keyOID.equals(FileCredentialResolver.RSAKey_OID)) {
686                                 log.debug("Found RSA key in PKCS8.");
687                                 return getRSAPkcs8DerKey(bytes);
688                         } else if (keyOID.equals(FileCredentialResolver.DSAKey_OID)) {
689                                 log.debug("Found DSA key in PKCS8.");
690                                 return getDSAPkcs8DerKey(bytes);
691                         } else {
692                                 log.error("Unexpected key type.  Only RSA and DSA keys are supported in PKCS8 format.");
693                                 throw new CredentialFactoryException("Unable to load private key.");
694                         }
695
696                 } catch (IOException e) {
697                         log.error("Invalid DER encoding for PKCS8 formatted key: " + e);
698                         throw new CredentialFactoryException("Unable to load private key.");
699                 }
700         }
701
702         private byte[] singleDerFromPEM(byte[] bytes, String beginToken, String endToken) throws IOException {
703
704                 try {
705
706                         BufferedReader in = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(bytes)));
707                         String str;
708                         boolean insideBase64 = false;
709                         StringBuffer base64Key = null;
710                         while ((str = in.readLine()) != null) {
711
712                                 if (insideBase64) {
713                                         if (str.matches("^.*" + endToken + ".*$")) {
714                                                 break;
715                                         }
716                                         {
717                                                 base64Key.append(str);
718                                         }
719                                 } else if (str.matches("^.*" + beginToken + ".*$")) {
720                                         insideBase64 = true;
721                                         base64Key = new StringBuffer();
722                                 }
723                         }
724                         in.close();
725                         if (base64Key == null || base64Key.length() == 0) {
726                                 log.error("Could not find Base 64 encoded entity.");
727                                 throw new IOException("Could not find Base 64 encoded entity.");
728                         }
729
730                         try {
731                                 BASE64Decoder decoder = new BASE64Decoder();
732                                 return decoder.decodeBuffer(base64Key.toString());
733                         } catch (IOException ioe) {
734                                 log.error("Could not decode Base 64: " + ioe);
735                                 throw new IOException("Could not decode Base 64.");
736                         }
737
738                 } catch (IOException e) {
739                         log.error("Could not load resource from specified location: " + e);
740                         throw new IOException("Could not load resource from specified location.");
741                 }
742
743         }
744
745         private String getCertFormat(Element e) throws CredentialFactoryException {
746
747                 NodeList certificateElements = e.getElementsByTagNameNS(Credentials.credentialsNamespace, "Certificate");
748                 if (certificateElements.getLength() < 1) {
749                         log.error("Certificate not specified.");
750                         throw new CredentialFactoryException("File Credential Resolver requires a <Certificate> specification.");
751                 }
752                 if (certificateElements.getLength() > 1) {
753                         log.error("Multiple Certificate path specifications, using first.");
754                 }
755
756                 String format = ((Element) certificateElements.item(0)).getAttribute("format");
757                 if (format == null || format.equals("")) {
758                         log.debug("No format specified for certificate, using default (PEM) format.");
759                         format = "PEM";
760                 }
761
762                 if ((!format.equals("PEM")) && (!format.equals("DER"))) {
763                         log.error("File credential resolver only supports the (DER) and (PEM) formats.");
764                         throw new CredentialFactoryException("Failed to initialize Credential Resolver.");
765                 }
766
767                 return format;
768         }
769
770         private String getKeyFormat(Element e) throws CredentialFactoryException {
771
772                 NodeList keyElements = e.getElementsByTagNameNS(Credentials.credentialsNamespace, "Key");
773                 if (keyElements.getLength() < 1) {
774                         log.error("Key not specified.");
775                         throw new CredentialFactoryException("File Credential Resolver requires a <Key> specification.");
776                 }
777                 if (keyElements.getLength() > 1) {
778                         log.error("Multiple Keyf path specifications, using first.");
779                 }
780
781                 String format = ((Element) keyElements.item(0)).getAttribute("format");
782                 if (format == null || format.equals("")) {
783                         log.debug("No format specified for certificate, using default (PEM) format.");
784                         format = "PEM";
785                 }
786
787                 if (!((format.equals("DER")) || (format.equals("PEM")))) {
788                         log.error("File credential resolver currently only supports (DER) and (PEM) formats.");
789                         throw new CredentialFactoryException("Failed to initialize Credential Resolver.");
790                 }
791                 return format;
792         }
793
794         private String getCertPath(Element e) throws CredentialFactoryException {
795
796                 NodeList certificateElements = e.getElementsByTagNameNS(Credentials.credentialsNamespace, "Certificate");
797                 if (certificateElements.getLength() < 1) {
798                         log.error("Certificate not specified.");
799                         throw new CredentialFactoryException("File Credential Resolver requires a <Certificate> specification.");
800                 }
801                 if (certificateElements.getLength() > 1) {
802                         log.error("Multiple Certificate path specifications, using first.");
803                 }
804
805                 NodeList pathElements =
806                         ((Element) certificateElements.item(0)).getElementsByTagNameNS(Credentials.credentialsNamespace, "Path");
807                 if (pathElements.getLength() < 1) {
808                         log.error("Certificate path not specified.");
809                         throw new CredentialFactoryException("File Credential Resolver requires a <Certificate><Path/></Certificate> specification.");
810                 }
811                 if (pathElements.getLength() > 1) {
812                         log.error("Multiple Certificate path specifications, using first.");
813                 }
814                 Node tnode = pathElements.item(0).getFirstChild();
815                 String path = null;
816                 if (tnode != null && tnode.getNodeType() == Node.TEXT_NODE) {
817                         path = tnode.getNodeValue();
818                 }
819                 if (path == null || path.equals("")) {
820                         log.error("Certificate path not specified.");
821                         throw new CredentialFactoryException("File Credential Resolver requires a <Certificate><Path/></Certificate> specification.");
822                 }
823                 return path;
824         }
825
826         private String[] getCAPaths(Element e) throws CredentialFactoryException {
827
828                 NodeList certificateElements = e.getElementsByTagNameNS(Credentials.credentialsNamespace, "Certificate");
829                 if (certificateElements.getLength() < 1) {
830                         log.error("Certificate not specified.");
831                         throw new CredentialFactoryException("File Credential Resolver requires a <Certificate> specification.");
832                 }
833                 if (certificateElements.getLength() > 1) {
834                         log.error("Multiple Certificate path specifications, using first.");
835                 }
836
837                 NodeList pathElements =
838                         ((Element) certificateElements.item(0)).getElementsByTagNameNS(Credentials.credentialsNamespace, "CAPath");
839                 if (pathElements.getLength() < 1) {
840                         log.debug("No CA Certificate paths specified.");
841                         return null;
842                 }
843                 ArrayList paths = new ArrayList();
844                 for (int i = 0; i < pathElements.getLength(); i++) {
845                         Node tnode = pathElements.item(i).getFirstChild();
846                         String path = null;
847                         if (tnode != null && tnode.getNodeType() == Node.TEXT_NODE) {
848                                 path = tnode.getNodeValue();
849                         }
850                         if (path != null && !(path.equals(""))) {
851                                 paths.add(path);
852                         }
853                         if (paths.isEmpty()) {
854                                 log.debug("No CA Certificate paths specified.");
855                         }
856                 }
857                 return (String[]) paths.toArray(new String[0]);
858         }
859
860         private String getKeyPath(Element e) throws CredentialFactoryException {
861
862                 NodeList keyElements = e.getElementsByTagNameNS(Credentials.credentialsNamespace, "Key");
863                 if (keyElements.getLength() < 1) {
864                         log.error("Key not specified.");
865                         throw new CredentialFactoryException("File Credential Resolver requires a <Key> specification.");
866                 }
867                 if (keyElements.getLength() > 1) {
868                         log.error("Multiple Key path specifications, using first.");
869                 }
870
871                 NodeList pathElements =
872                         ((Element) keyElements.item(0)).getElementsByTagNameNS(Credentials.credentialsNamespace, "Path");
873                 if (pathElements.getLength() < 1) {
874                         log.error("Key path not specified.");
875                         throw new CredentialFactoryException("File Credential Resolver requires a <Key><Path/></Certificate> specification.");
876                 }
877                 if (pathElements.getLength() > 1) {
878                         log.error("Multiple Key path specifications, using first.");
879                 }
880                 Node tnode = pathElements.item(0).getFirstChild();
881                 String path = null;
882                 if (tnode != null && tnode.getNodeType() == Node.TEXT_NODE) {
883                         path = tnode.getNodeValue();
884                 }
885                 if (path == null || path.equals("")) {
886                         log.error("Key path not specified.");
887                         throw new CredentialFactoryException("File Credential Resolver requires a <Key><Path/></Certificate> specification.");
888                 }
889                 return path;
890         }
891
892         /**
893          * 
894          * Loads a specified bundle of certs individually and returns an array of <code>Certificate</code> objects. This
895          * is needed because the standard <code>CertificateFactory.getCertificates(InputStream)</code> method bails out
896          * when it has trouble loading any cert and cannot handle "comments".
897          */
898         private Certificate[] loadCertificates(InputStream inStream, String certType) throws CredentialFactoryException {
899
900                 ArrayList certificates = new ArrayList();
901
902                 try {
903                         CertificateFactory certFactory = CertificateFactory.getInstance(certType);
904
905                         BufferedReader in = new BufferedReader(new InputStreamReader(inStream));
906                         String str;
907                         boolean insideCert = false;
908                         StringBuffer rawCert = null;
909                         while ((str = in.readLine()) != null) {
910
911                                 if (insideCert) {
912                                         rawCert.append(str);
913                                         rawCert.append(System.getProperty("line.separator"));
914                                         if (str.matches("^.*-----END CERTIFICATE-----.*$")) {
915                                                 insideCert = false;
916                                                 try {
917                                                         Certificate cert =
918                                                                 certFactory.generateCertificate(
919                                                                         new ByteArrayInputStream(rawCert.toString().getBytes()));
920                                                         certificates.add(cert);
921                                                 } catch (CertificateException ce) {
922                                                         log.warn("Failed to load a certificate from the certificate bundle: " + ce);
923                                                         if (log.isDebugEnabled()) {
924                                                                 log.debug(
925                                                                         "Dump of bad certificate: "
926                                                                                 + System.getProperty("line.separator")
927                                                                                 + rawCert.toString());
928                                                         }
929                                                 }
930                                                 continue;
931                                         }
932                                 } else if (str.matches("^.*-----BEGIN CERTIFICATE-----.*$")) {
933                                         insideCert = true;
934                                         rawCert = new StringBuffer();
935                                         rawCert.append(str);
936                                         rawCert.append(System.getProperty("line.separator"));
937                                 }
938                         }
939                         in.close();
940                 } catch (IOException p) {
941                         log.error("Could not load resource from specified location: " + p);
942                         throw new CredentialFactoryException("Unable to load certificates.");
943                 } catch (CertificateException p) {
944                         log.error("Problem loading certificate factory: " + p);
945                         throw new CredentialFactoryException("Unable to load certificates.");
946                 }
947
948                 return (Certificate[]) certificates.toArray(new Certificate[0]);
949         }
950
951         /**
952          * Given an ArrayList containing a base certificate and an array of unordered certificates, populates the ArrayList
953          * with an ordered certificate chain, based on subject and issuer.
954          * 
955          * @param chainSource
956          *            array of certificates to pull from
957          * @param chainDest
958          *            ArrayList containing base certificate
959          * @throws InvalidCertificateChainException
960          *             thrown if a chain cannot be constructed from the specified elements
961          */
962
963         protected void walkChain(X509Certificate[] chainSource, ArrayList chainDest) throws CredentialFactoryException {
964
965                 X509Certificate currentCert = (X509Certificate) chainDest.get(chainDest.size() - 1);
966                 if (currentCert.getSubjectDN().equals(currentCert.getIssuerDN())) {
967                         log.debug("Found self-signed root cert: " + currentCert.getSubjectDN());
968                         return;
969                 } else {
970                         //TODO maybe this should check more than the DN...
971                         for (int i = 0; chainSource.length > i; i++) {
972                                 if (currentCert.getIssuerDN().equals(chainSource[i].getSubjectDN())) {
973                                         chainDest.add(chainSource[i]);
974                                         walkChain(chainSource, chainDest);
975                                         return;
976                                 }
977                         }
978                         log.error("Incomplete certificate chain.");
979                         throw new CredentialFactoryException("Incomplete cerficate chain.");
980                 }
981         }
982
983         /**
984          * Boolean indication of whether a given private key and public key form a valid keypair.
985          * 
986          * @param pubKey
987          *            the public key
988          * @param privKey
989          *            the private key
990          */
991
992         protected boolean isMatchingKey(PublicKey pubKey, PrivateKey privKey) {
993
994                 try {
995                         String controlString = "asdf";
996                         log.debug("Checking for matching private key/public key pair");
997                         Signature signature = null;
998                         try {
999                                 signature = Signature.getInstance(privKey.getAlgorithm());
1000                         } catch (NoSuchAlgorithmException nsae) {
1001                                 log.debug("No provider for (RSA) signature, attempting (MD5withRSA).");
1002                                 if (privKey.getAlgorithm().equals("RSA")) {
1003                                         signature = Signature.getInstance("MD5withRSA");
1004                                 } else {
1005                                         throw nsae;
1006                                 }
1007                         }
1008                         signature.initSign(privKey);
1009                         signature.update(controlString.getBytes());
1010                         byte[] sigBytes = signature.sign();
1011                         signature.initVerify(pubKey);
1012                         signature.update(controlString.getBytes());
1013                         if (signature.verify(sigBytes)) {
1014                                 log.debug("Found match.");
1015                                 return true;
1016                         }
1017                 } catch (Exception e) {
1018                         log.warn(e);
1019                 }
1020                 log.debug("This pair does not match.");
1021                 return false;
1022         }
1023
1024         /**
1025          * Auto-enlarging container for bytes.
1026          */
1027
1028         // Sure makes you wish bytes were first class objects.
1029
1030         private class ByteContainer {
1031
1032                 private byte[] buffer;
1033                 private int cushion;
1034                 private int currentSize = 0;
1035
1036                 private ByteContainer(int cushion) {
1037                         buffer = new byte[cushion];
1038                         this.cushion = cushion;
1039                 }
1040
1041                 private void grow() {
1042                         log.debug("Growing ByteContainer.");
1043                         int newSize = currentSize + cushion;
1044                         byte[] b = new byte[newSize];
1045                         int toCopy = Math.min(currentSize, newSize);
1046                         int i;
1047                         for (i = 0; i < toCopy; i++) {
1048                                 b[i] = buffer[i];
1049                         }
1050                         buffer = b;
1051                 }
1052
1053                 /**
1054                  * Returns an array of the bytes in the container.
1055                  * <p>
1056                  */
1057
1058                 private byte[] toByteArray() {
1059                         byte[] b = new byte[currentSize];
1060                         for (int i = 0; i < currentSize; i++) {
1061                                 b[i] = buffer[i];
1062                         }
1063                         return b;
1064                 }
1065
1066                 /**
1067                  * Add one byte to the end of the container.
1068                  */
1069
1070                 private void append(byte b) {
1071                         if (currentSize == buffer.length) {
1072                                 grow();
1073                         }
1074                         buffer[currentSize] = b;
1075                         currentSize++;
1076                 }
1077
1078         }
1079
1080 }
1081
1082 class KeystoreCredentialResolver implements CredentialResolver {
1083
1084         private static Logger log = Logger.getLogger(KeystoreCredentialResolver.class.getName());
1085
1086         public Credential loadCredential(Element e) throws CredentialFactoryException {
1087
1088                 if (!e.getTagName().equals("KeyStoreResolver")) {
1089                         log.error("Invalid Credential Resolver configuration: expected <KeyStoreResolver> .");
1090                         throw new CredentialFactoryException("Failed to initialize Credential Resolver.");
1091                 }
1092
1093                 String id = e.getAttribute("Id");
1094                 if (id == null || id.equals("")) {
1095                         log.error("Credential Resolvers require specification of the attribute \"Id\".");
1096                         throw new CredentialFactoryException("Failed to initialize Credential Resolver.");
1097                 }
1098
1099                 String keyStoreType = e.getAttribute("storeType");
1100                 if (keyStoreType == null || keyStoreType.equals("")) {
1101                         log.debug("Using default store type for credential.");
1102                         keyStoreType = "JKS";
1103                 }
1104
1105                 String path = loadPath(e);
1106                 String alias = loadAlias(e);
1107                 String certAlias = loadCertAlias(e, alias);
1108                 String keyPassword = loadKeyPassword(e);
1109                 String keyStorePassword = loadKeyStorePassword(e);
1110
1111                 try {
1112                         KeyStore keyStore = KeyStore.getInstance(keyStoreType);
1113
1114                         keyStore.load(new ShibResource(path, this.getClass()).getInputStream(), keyStorePassword.toCharArray());
1115
1116                         PrivateKey privateKey = (PrivateKey) keyStore.getKey(alias, keyPassword.toCharArray());
1117
1118                         if (privateKey == null) {
1119                                 throw new CredentialFactoryException("No key entry was found with an alias of (" + alias + ").");
1120                         }
1121
1122                         Certificate[] certificates = keyStore.getCertificateChain(certAlias);
1123                         if (certificates == null) {
1124                                 throw new CredentialFactoryException(
1125                                         "An error occurred while reading the java keystore: No certificate found with the specified alias ("
1126                                                 + certAlias
1127                                                 + ").");
1128                         }
1129
1130                         X509Certificate[] x509Certs = new X509Certificate[certificates.length];
1131                         for (int i = 0; i < certificates.length; i++) {
1132                                 if (certificates[i] instanceof X509Certificate) {
1133                                         x509Certs[i] = (X509Certificate) certificates[i];
1134                                 } else {
1135                                         throw new CredentialFactoryException(
1136                                                 "The KeyStore Credential Resolver can only load X509 certificates.  Found an unsupported certificate of type ("
1137                                                         + certificates[i]
1138                                                         + ").");
1139                                 }
1140                         }
1141
1142                         return new Credential(x509Certs, privateKey);
1143
1144                 } catch (KeyStoreException kse) {
1145                         throw new CredentialFactoryException("An error occurred while accessing the java keystore: " + kse);
1146                 } catch (NoSuchAlgorithmException nsae) {
1147                         throw new CredentialFactoryException("Appropriate JCE provider not found in the java environment: " + nsae);
1148                 } catch (CertificateException ce) {
1149                         throw new CredentialFactoryException(
1150                                 "The java keystore contained a certificate that could not be loaded: " + ce);
1151                 } catch (IOException ioe) {
1152                         throw new CredentialFactoryException("An error occurred while reading the java keystore: " + ioe);
1153                 } catch (UnrecoverableKeyException uke) {
1154                         throw new CredentialFactoryException(
1155                                 "An error occurred while attempting to load the key from the java keystore: " + uke);
1156                 }
1157
1158         }
1159
1160         private String loadPath(Element e) throws CredentialFactoryException {
1161
1162                 NodeList pathElements = e.getElementsByTagNameNS(Credentials.credentialsNamespace, "Path");
1163                 if (pathElements.getLength() < 1) {
1164                         log.error("KeyStore path not specified.");
1165                         throw new CredentialFactoryException("KeyStore Credential Resolver requires a <Path> specification.");
1166                 }
1167                 if (pathElements.getLength() > 1) {
1168                         log.error("Multiple KeyStore path specifications, using first.");
1169                 }
1170                 Node tnode = pathElements.item(0).getFirstChild();
1171                 String path = null;
1172                 if (tnode != null && tnode.getNodeType() == Node.TEXT_NODE) {
1173                         path = tnode.getNodeValue();
1174                 }
1175                 if (path == null || path.equals("")) {
1176                         log.error("KeyStore path not specified.");
1177                         throw new CredentialFactoryException("KeyStore Credential Resolver requires a <Path> specification.");
1178                 }
1179                 return path;
1180         }
1181
1182         private String loadAlias(Element e) throws CredentialFactoryException {
1183
1184                 NodeList aliasElements = e.getElementsByTagNameNS(Credentials.credentialsNamespace, "KeyAlias");
1185                 if (aliasElements.getLength() < 1) {
1186                         log.error("KeyStore key alias not specified.");
1187                         throw new CredentialFactoryException("KeyStore Credential Resolver requires an <KeyAlias> specification.");
1188                 }
1189                 if (aliasElements.getLength() > 1) {
1190                         log.error("Multiple key alias specifications, using first.");
1191                 }
1192                 Node tnode = aliasElements.item(0).getFirstChild();
1193                 String alias = null;
1194                 if (tnode != null && tnode.getNodeType() == Node.TEXT_NODE) {
1195                         alias = tnode.getNodeValue();
1196                 }
1197                 if (alias == null || alias.equals("")) {
1198                         log.error("KeyStore key alias not specified.");
1199                         throw new CredentialFactoryException("KeyStore Credential Resolver requires an <KeyAlias> specification.");
1200                 }
1201                 return alias;
1202         }
1203
1204         private String loadCertAlias(Element e, String defaultAlias) throws CredentialFactoryException {
1205
1206                 NodeList aliasElements = e.getElementsByTagNameNS(Credentials.credentialsNamespace, "CertAlias");
1207                 if (aliasElements.getLength() < 1) {
1208                         log.debug("KeyStore cert alias not specified, defaulting to key alias.");
1209                         return defaultAlias;
1210                 }
1211
1212                 if (aliasElements.getLength() > 1) {
1213                         log.error("Multiple cert alias specifications, using first.");
1214                 }
1215
1216                 Node tnode = aliasElements.item(0).getFirstChild();
1217                 String alias = null;
1218                 if (tnode != null && tnode.getNodeType() == Node.TEXT_NODE) {
1219                         alias = tnode.getNodeValue();
1220                 }
1221                 if (alias == null || alias.equals("")) {
1222                         log.debug("KeyStore cert alias not specified, defaulting to key alias.");
1223                         return defaultAlias;
1224                 }
1225                 return alias;
1226         }
1227
1228         private String loadKeyStorePassword(Element e) throws CredentialFactoryException {
1229
1230                 NodeList passwordElements = e.getElementsByTagNameNS(Credentials.credentialsNamespace, "StorePassword");
1231                 if (passwordElements.getLength() < 1) {
1232                         log.error("KeyStore password not specified.");
1233                         throw new CredentialFactoryException("KeyStore Credential Resolver requires an <StorePassword> specification.");
1234                 }
1235                 if (passwordElements.getLength() > 1) {
1236                         log.error("Multiple KeyStore password specifications, using first.");
1237                 }
1238                 Node tnode = passwordElements.item(0).getFirstChild();
1239                 String password = null;
1240                 if (tnode != null && tnode.getNodeType() == Node.TEXT_NODE) {
1241                         password = tnode.getNodeValue();
1242                 }
1243                 if (password == null || password.equals("")) {
1244                         log.error("KeyStore password not specified.");
1245                         throw new CredentialFactoryException("KeyStore Credential Resolver requires an <StorePassword> specification.");
1246                 }
1247                 return password;
1248         }
1249
1250         private String loadKeyPassword(Element e) throws CredentialFactoryException {
1251
1252                 NodeList passwords = e.getElementsByTagNameNS(Credentials.credentialsNamespace, "KeyPassword");
1253                 if (passwords.getLength() < 1) {
1254                         log.error("KeyStore key password not specified.");
1255                         throw new CredentialFactoryException("KeyStore Credential Resolver requires an <KeyPassword> specification.");
1256                 }
1257                 if (passwords.getLength() > 1) {
1258                         log.error("Multiple KeyStore key password specifications, using first.");
1259                 }
1260                 Node tnode = passwords.item(0).getFirstChild();
1261                 String password = null;
1262                 if (tnode != null && tnode.getNodeType() == Node.TEXT_NODE) {
1263                         password = tnode.getNodeValue();
1264                 }
1265                 if (password == null || password.equals("")) {
1266                         log.error("KeyStore key password not specified.");
1267                         throw new CredentialFactoryException("KeyStore Credential Resolver requires an <KeyPassword> specification.");
1268                 }
1269                 return password;
1270         }
1271 }
1272
1273 class CustomCredentialResolver implements CredentialResolver {
1274
1275         private static Logger log = Logger.getLogger(CustomCredentialResolver.class.getName());
1276
1277         public Credential loadCredential(Element e) throws CredentialFactoryException {
1278
1279                 if (!e.getTagName().equals("CustomCredResolver")) {
1280                         log.error("Invalid Credential Resolver configuration: expected <CustomCredResolver> .");
1281                         throw new CredentialFactoryException("Failed to initialize Credential Resolver.");
1282                 }
1283
1284                 String id = e.getAttribute("id");
1285                 if (id == null || id.equals("")) {
1286                         log.error("Credential Resolvers require specification of the attribute \"id\".");
1287                         throw new CredentialFactoryException("Failed to initialize Credential Resolver.");
1288                 }
1289
1290                 String className = e.getAttribute("Class");
1291                 if (className == null || className.equals("")) {
1292                         log.error("Custom Credential Resolver requires specification of the attribute \"Class\".");
1293                         throw new CredentialFactoryException("Failed to initialize Credential Resolver.");
1294                 }
1295
1296                 try {
1297                         return ((CredentialResolver) Class.forName(className).newInstance()).loadCredential(e);
1298
1299                 } catch (Exception loaderException) {
1300                         log.error(
1301                                 "Failed to load Custom Credential Resolver implementation class: " + loaderException.getMessage());
1302                         throw new CredentialFactoryException("Failed to initialize Credential Resolver.");
1303                 }
1304
1305         }
1306
1307 }
1308
1309 class CredentialFactoryException extends Exception {
1310
1311         CredentialFactoryException(String message) {
1312                 super(message);
1313         }
1314 }