b29639ec006a701756179c02e6279bc2699fa9af
[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.AlgorithmParameters;
46 import java.security.GeneralSecurityException;
47 import java.security.KeyFactory;
48 import java.security.KeyStore;
49 import java.security.KeyStoreException;
50 import java.security.MessageDigest;
51 import java.security.NoSuchAlgorithmException;
52 import java.security.PrivateKey;
53 import java.security.Provider;
54 import java.security.PublicKey;
55 import java.security.Security;
56 import java.security.Signature;
57 import java.security.UnrecoverableKeyException;
58 import java.security.cert.Certificate;
59 import java.security.cert.CertificateException;
60 import java.security.cert.CertificateFactory;
61 import java.security.cert.X509Certificate;
62 import java.security.spec.DSAPrivateKeySpec;
63 import java.security.spec.InvalidKeySpecException;
64 import java.security.spec.PKCS8EncodedKeySpec;
65 import java.security.spec.RSAPrivateCrtKeySpec;
66 import java.util.ArrayList;
67 import java.util.Arrays;
68 import java.util.Hashtable;
69
70 import javax.crypto.BadPaddingException;
71 import javax.crypto.Cipher;
72 import javax.crypto.EncryptedPrivateKeyInfo;
73 import javax.crypto.SecretKey;
74 import javax.crypto.SecretKeyFactory;
75 import javax.crypto.spec.IvParameterSpec;
76 import javax.crypto.spec.PBEKeySpec;
77 import javax.crypto.spec.SecretKeySpec;
78
79 import org.apache.log4j.Logger;
80 import org.w3c.dom.Element;
81 import org.w3c.dom.Node;
82 import org.w3c.dom.NodeList;
83
84 import sun.misc.BASE64Decoder;
85 import sun.security.util.DerValue;
86
87 /**
88  * Uses {@link CredentialResolver} implementations to create {@link Credential}s.
89  *
90  * @author Walter Hoehn
91  */
92 public class Credentials {
93
94         public static final String credentialsNamespace = "urn:mace:shibboleth:credentials:1.0";
95
96         private static Logger log = Logger.getLogger(Credentials.class.getName());
97         private Hashtable data = new Hashtable();
98
99         /**
100          * Creates credentials based on XML configuration.
101          * @param e DOM representation of credentials configuration
102          */
103         public Credentials(Element e) {
104
105                 if (!e.getLocalName().equals("Credentials")) {
106                         throw new IllegalArgumentException();
107                 }
108
109                 NodeList resolverNodes = e.getChildNodes();
110                 if (resolverNodes.getLength() <= 0) {
111                         log.error("Credentials configuration inclues no Credential Resolver definitions.");
112                         throw new IllegalArgumentException("Cannot load credentials.");
113                 }
114
115                 for (int i = 0; resolverNodes.getLength() > i; i++) {
116                         if (resolverNodes.item(i).getNodeType() == Node.ELEMENT_NODE) {
117                                 try {
118
119                                         String credentialId = ((Element) resolverNodes.item(i)).getAttribute("Id");
120                                         if (credentialId == null || credentialId.equals("")) {
121                                                 log.error("Found credential that was not labeled with a unique \"Id\" attribute. Skipping.");
122                                         }
123
124                                         if (data.containsKey(credentialId)) {
125                                                 log.error("Duplicate credential id (" + credentialId + ") found. Skipping");
126                                         }
127
128                                         log.info("Found credential (" + credentialId + "). Loading...");
129                                         data.put(credentialId, CredentialFactory.loadCredential((Element) resolverNodes.item(i)));
130
131                                 } catch (CredentialFactoryException cfe) {
132                                         log.error("Could not load credential, skipping: " + cfe.getMessage());
133                                 } catch (ClassCastException cce) {
134                                         log.error("Problem realizing credential configuration" + cce.getMessage());
135                                 }
136                         }
137                 }
138         }
139
140         public boolean containsCredential(String identifier) {
141                 return data.containsKey(identifier);
142         }
143
144         public Credential getCredential(String identifier) {
145
146                 // Default if there is only one credential
147                 if ((identifier == null || identifier.equals("")) && data.size() == 1) {
148                         return (Credential) data.values().iterator().next();
149                 }
150
151                 return (Credential) data.get(identifier);
152         }
153
154         static class CredentialFactory {
155
156                 private static Logger log = Logger.getLogger(CredentialFactory.class.getName());
157
158                 public static Credential loadCredential(Element e) throws CredentialFactoryException {
159                         if (e.getLocalName().equals("KeyInfo")) {
160                                 return new KeyInfoCredentialResolver().loadCredential(e);
161                         }
162
163                         if (e.getLocalName().equals("FileResolver")) {
164                                 return new FileCredentialResolver().loadCredential(e);
165                         }
166
167                         if (e.getLocalName().equals("KeyStoreResolver")) {
168                                 return new KeystoreCredentialResolver().loadCredential(e);
169                         }
170
171                         if (e.getLocalName().equals("CustomResolver")) {
172                                 return new CustomCredentialResolver().loadCredential(e);
173                         }
174
175                         log.error("Unrecognized Credential Resolver type: " + e.getTagName());
176                         throw new CredentialFactoryException("Failed to load credential.");
177                 }
178
179         }
180
181 }
182
183 class KeyInfoCredentialResolver implements CredentialResolver {
184         private static Logger log = Logger.getLogger(KeyInfoCredentialResolver.class.getName());
185         KeyInfoCredentialResolver() throws CredentialFactoryException {
186                 log.error("Credential Resolver (KeyInfoCredentialResolver) not implemented");
187                 throw new CredentialFactoryException("Failed to load credential.");
188         }
189
190         public Credential loadCredential(Element e) {
191                 return null;
192         }
193 }
194
195 /**
196  * Loads a credential from a file. Supports DER, PEM, encrypted PEM, PKCS8, and encrypted PKCS8 for RSA and DSA.
197  * @author Walter Hoehn
198  */
199 class FileCredentialResolver implements CredentialResolver {
200
201         private static Logger log = Logger.getLogger(FileCredentialResolver.class.getName());
202
203         private static String DSAKey_OID = "1.2.840.10040.4.1";
204         private static String RSAKey_OID = "1.2.840.113549.1.1.1";
205
206         public Credential loadCredential(Element e) throws CredentialFactoryException {
207
208                 if (!e.getLocalName().equals("FileResolver")) {
209                         log.error("Invalid Credential Resolver configuration: expected <FileResolver> .");
210                         throw new CredentialFactoryException("Failed to initialize Credential Resolver.");
211                 }
212
213                 String id = e.getAttribute("Id");
214                 if (id == null || id.equals("")) {
215                         log.error("Credential Resolvers require specification of the attribute \"Id\".");
216                         throw new CredentialFactoryException("Failed to initialize Credential Resolver.");
217                 }
218
219                 //Load the key
220                 String keyFormat = getKeyFormat(e);
221                 String keyPath = getKeyPath(e);
222                 String password = getKeyPassword(e);
223                 log.debug("Key Format: (" + keyFormat + ").");
224                 log.debug("Key Path: (" + keyPath + ").");
225
226                 PrivateKey key = null; 
227
228                 if (keyFormat.equals("DER")) {
229                         InputStream keyStream = null;
230                         try {
231                                 keyStream = new ShibResource(keyPath, this.getClass()).getInputStream();
232                                 key = getDERKey(keyStream, password);
233                         } catch (IOException ioe) {
234                                 log.error("Could not load resource from specified location (" + keyPath + "): " + e);
235                                 throw new CredentialFactoryException("Unable to load private key.");
236                         } finally {
237                                 if (keyStream != null) {
238                                         try {
239                                                 keyStream.close();
240                                         } catch (IOException e1) {
241                                                 // ignore
242                                         }
243                                 }
244                         }
245                         
246                 } else if (keyFormat.equals("PEM")) {
247                         InputStream keyStream = null;
248                         try {
249                                 keyStream = new ShibResource(keyPath, this.getClass()).getInputStream();
250                                 key = getPEMKey(keyStream, password);
251                         } catch (IOException ioe) {
252                                 log.error("Could not load resource from specified location (" + keyPath + "): " + e);
253                                 throw new CredentialFactoryException("Unable to load private key.");
254                         } finally {
255                                 if (keyStream != null) {
256                                         try {
257                                                 keyStream.close();
258                                         } catch (IOException e1) {
259                                                 // ignore
260                                         }
261                                 }
262                         }
263                         
264                 } else {
265                         log.error("File credential resolver only supports (DER) and (PEM) formats.");
266                         throw new CredentialFactoryException("Failed to initialize Credential Resolver.");
267                 }
268
269                 if (key == null) {
270                         log.error("Failed to load private key.");
271                         throw new CredentialFactoryException("Failed to initialize Credential Resolver.");
272                 }
273                 log.info("Successfully loaded private key.");
274
275                 
276                 ArrayList certChain = new ArrayList();
277                 String certPath = getCertPath(e);
278                 
279                 if (certPath == null || certPath.equals("")) {
280                         log.info("No certificates specified.");
281                 } else {
282
283                 String certFormat = getCertFormat(e);
284                 //A placeholder in case we want to make this configurable
285                 String certType = "X.509";
286
287                 log.debug("Certificate Format: (" + certFormat + ").");
288                 log.debug("Certificate Path: (" + certPath + ").");
289
290                 //The loading code should work for other types, but the chain
291                 // construction code
292                 //would break
293                 if (!certType.equals("X.509")) {
294                         log.error("File credential resolver only supports the X.509 certificates.");
295                         throw new CredentialFactoryException("Failed to initialize Credential Resolver.");
296                 }
297
298
299                 ArrayList allCerts = new ArrayList();
300
301                 try {
302                         Certificate[] certsFromPath =
303                                 loadCertificates(new ShibResource(certPath, this.getClass()).getInputStream(), certType);
304
305                         allCerts.addAll(Arrays.asList(certsFromPath));
306
307                         //Find the end-entity cert first
308                         if (certsFromPath == null || certsFromPath.length == 0) {
309                                 log.error("File at (" + certPath + ") did not contain any valid certificates.");
310                                 throw new CredentialFactoryException("File did not contain any valid certificates.");
311                         }
312
313                         if (certsFromPath.length == 1) {
314                                 log.debug("Certificate file only contains 1 certificate.");
315                                 log.debug("Ensure that it matches the private key.");
316                                 if (!isMatchingKey(certsFromPath[0].getPublicKey(), key)) {
317                                         log.error("Certificate does not match the private key.");
318                                         throw new CredentialFactoryException("File did not contain any valid certificates.");
319                                 }
320                                 certChain.add(certsFromPath[0]);
321                                 log.debug(
322                                         "Successfully identified the end entity cert: "
323                                                 + ((X509Certificate) certChain.get(0)).getSubjectDN());
324
325                         } else {
326                                 log.debug("Certificate file contains multiple certificates.");
327                                 log.debug("Trying to determine the end-entity cert by matching against the private key.");
328                                 for (int i = 0; certsFromPath.length > i; i++) {
329                                         if (isMatchingKey(certsFromPath[i].getPublicKey(), key)) {
330                                                 log.debug("Found matching end cert: " + ((X509Certificate) certsFromPath[i]).getSubjectDN());
331                                                 certChain.add(certsFromPath[i]);
332                                         }
333                                 }
334                                 if (certChain.size() < 1) {
335                                         log.error("No certificate in chain that matches specified private key");
336                                         throw new CredentialFactoryException("No certificate in chain that matches specified private key");
337                                 }
338                                 if (certChain.size() > 1) {
339                                         log.error("More than one certificate in chain that matches specified private key");
340                                         throw new CredentialFactoryException("More than one certificate in chain that matches specified private key");
341                                 }
342                                 log.debug(
343                                         "Successfully identified the end entity cert: "
344                                                 + ((X509Certificate) certChain.get(0)).getSubjectDN());
345                         }
346
347                         //Now load additional certs and construct a chain
348                         String[] caPaths = getCAPaths(e);
349                         if (caPaths != null && caPaths.length > 0) {
350                                 log.debug("Attempting to load certificates from (" + caPaths.length + ") CA certificate files.");
351                                 for (int i = 0; i < caPaths.length; i++) {
352                                         allCerts.addAll(
353                                                 Arrays.asList(
354                                                         loadCertificates(
355                                                                 new ShibResource(caPaths[i], this.getClass()).getInputStream(),
356                                                                 certType)));
357                                 }
358                         }
359
360                         log.debug("Attempting to construct a certificate chain.");
361                         walkChain((X509Certificate[]) allCerts.toArray(new X509Certificate[0]), certChain);
362
363                         log.debug("Verifying that each link in the cert chain is signed appropriately");
364                         for (int i = 0; i < certChain.size() - 1; i++) {
365                                 PublicKey pubKey = ((X509Certificate) certChain.get(i + 1)).getPublicKey();
366                                 try {
367                                         ((X509Certificate) certChain.get(i)).verify(pubKey);
368                                 } catch (Exception se) {
369                                         log.error("Certificate chain cannot be verified: " + se);
370                                         throw new CredentialFactoryException("Certificate chain cannot be verified: " + se);
371                                 }
372                         }
373                         log.debug("All signatures verified. Certificate chain creation successful.");
374                         log.info("Successfully loaded certificates.");
375                 
376
377                 } catch (IOException p) {
378                         log.error("Could not load resource from specified location (" + certPath + "): " + p);
379                         throw new CredentialFactoryException("Unable to load certificates.");
380                 }
381                 }
382                 return new Credential(((X509Certificate[]) certChain.toArray(new X509Certificate[0])), key);
383         }
384
385         private PrivateKey getDERKey(InputStream inStream, String password)
386                 throws CredentialFactoryException, IOException {
387
388                 byte[] inputBuffer = new byte[8];
389                 int i;
390                 ByteContainer inputBytes = new ByteContainer(800);
391                 do {
392                         i = inStream.read(inputBuffer);
393                         for (int j = 0; j < i; j++) {
394                                 inputBytes.append(inputBuffer[j]);
395                         }
396                 } while (i > -1);
397
398                 //Examine the ASN.1 Structure to auto-detect the format
399                 //This gets a tad nasty
400                 try {
401                         DerValue root = new DerValue(inputBytes.toByteArray());
402                         if (root.tag != DerValue.tag_Sequence) {
403                                 log.error("Unexpected data type.  Unable to determine key type from data.");
404                                 throw new CredentialFactoryException("Unable to load private key.");
405                         }
406
407                         DerValue[] childValues = new DerValue[3];
408
409                         if (root.data.available() == 0) {
410                                 log.error("Unexpected data type.  Unable to determine key type from data.");
411                                 throw new CredentialFactoryException("Unable to load private key.");
412                         }
413
414                         childValues[0] = root.data.getDerValue();
415
416                         if (childValues[0].tag == DerValue.tag_Sequence) {
417
418                                 //May be encrypted pkcs8... dig further
419                                 if (root.data.available() == 0) {
420                                         log.error("Unexpected data type.  Unable to determine key type from data.");
421                                         throw new CredentialFactoryException("Unable to load private key.");
422                                 }
423                                 childValues[1] = root.data.getDerValue();
424                                 if (childValues[1].tag != DerValue.tag_OctetString) {
425                                         log.error("Unexpected data type.  Unable to determine key type from data.");
426                                         throw new CredentialFactoryException("Unable to load private key.");
427                                 }
428
429                                 if (childValues[0].data.available() == 0) {
430                                         log.error("Unexpected data type.  Unable to determine key type from data.");
431                                         throw new CredentialFactoryException("Unable to load private key.");
432                                 }
433                                 DerValue grandChild = childValues[0].data.getDerValue();
434                                 if (grandChild.tag != DerValue.tag_ObjectId) {
435                                         log.error("Unexpected data type.  Unable to determine key type from data.");
436                                         throw new CredentialFactoryException("Unable to load private key.");
437                                 }
438
439                                 log.debug("Key appears to be formatted as encrypted PKCS8. Loading...");
440                                 return getEncryptedPkcs8Key(inputBytes.toByteArray(), password.toCharArray());
441
442                         } else if (childValues[0].tag == DerValue.tag_Integer) {
443
444                                 //May be pkcs8, rsa, or dsa... dig further
445                                 if (root.data.available() == 0) {
446                                         log.error("Unexpected data type.  Unable to determine key type from data.");
447                                         throw new CredentialFactoryException("Unable to load private key.");
448                                 }
449                                 childValues[1] = root.data.getDerValue();
450                                 if (childValues[1].tag == DerValue.tag_Sequence) {
451                                         //May be pkcs8... dig further
452                                         if (root.data.available() == 0) {
453                                                 log.error("Unexpected data type.  Unable to determine key type from data.");
454                                                 throw new CredentialFactoryException("Unable to load private key.");
455                                         }
456                                         childValues[2] = root.data.getDerValue();
457                                         if (childValues[2].tag != DerValue.tag_OctetString) {
458                                                 log.error("Unexpected data type.  Unable to determine key type from data.");
459                                                 throw new CredentialFactoryException("Unable to load private key.");
460                                         }
461
462                                         if (childValues[1].data.available() == 0) {
463                                                 log.error("Unexpected data type.  Unable to determine key type from data.");
464                                                 throw new CredentialFactoryException("Unable to load private key.");
465                                         }
466                                         DerValue grandChild = childValues[1].data.getDerValue();
467                                         if (grandChild.tag != DerValue.tag_ObjectId) {
468                                                 log.error("Unexpected data type.  Unable to determine key type from data.");
469                                                 throw new CredentialFactoryException("Unable to load private key.");
470                                         }
471
472                                         log.debug("Key appears to be formatted as PKCS8. Loading...");
473                                         return getRSAPkcs8DerKey(inputBytes.toByteArray());
474
475                                 } else if (childValues[1].tag == DerValue.tag_Integer) {
476
477                                         //May be rsa or dsa... dig further
478                                         if (root.data.available() == 0
479                                                 || root.data.getDerValue().tag != DerValue.tag_Integer
480                                                 || root.data.available() == 0
481                                                 || root.data.getDerValue().tag != DerValue.tag_Integer
482                                                 || root.data.available() == 0
483                                                 || root.data.getDerValue().tag != DerValue.tag_Integer
484                                                 || root.data.available() == 0
485                                                 || root.data.getDerValue().tag != DerValue.tag_Integer) {
486                                                 log.error("Unexpected data type.  Unable to determine key type from data.");
487                                                 throw new CredentialFactoryException("Unable to load private key.");
488                                         }
489
490                                         if (root.data.available() == 0) {
491
492                                                 log.debug("Key appears to be DSA. Loading...");
493                                                 return getDSARawDerKey(inputBytes.toByteArray());
494
495                                         } else {
496
497                                                 DerValue dsaOverrun = root.data.getDerValue();
498                                                 if (dsaOverrun.tag != DerValue.tag_Integer) {
499                                                         log.error("Unexpected data type.  Unable to determine key type from data.");
500                                                         throw new CredentialFactoryException("Unable to load private key.");
501                                                 }
502
503                                                 log.debug("Key appears to be RSA. Loading...");
504                                                 return getRSARawDerKey(inputBytes.toByteArray());
505                                         }
506
507                                 } else {
508                                         log.error("Unexpected data type.  Unable to determine key type from data.");
509                                         throw new CredentialFactoryException("Unable to load private key.");
510                                 }
511
512                         } else {
513                                 log.error("Unexpected data type.  Unable to determine key type from data.");
514                                 throw new CredentialFactoryException("Unable to load private key.");
515                         }
516
517                 } catch (CredentialFactoryException e) {
518                         log.error("Invalid DER encoding for key: " + e);
519                         throw new CredentialFactoryException("Unable to load private key.");
520                 }
521
522         }
523
524         private PrivateKey getPEMKey(InputStream inStream, String password)
525                 throws CredentialFactoryException, IOException {
526
527                 byte[] inputBuffer = new byte[8];
528                 int i;
529                 ByteContainer inputBytes = new ByteContainer(800);
530                 do {
531                         i = inStream.read(inputBuffer);
532                         for (int j = 0; j < i; j++) {
533                                 inputBytes.append(inputBuffer[j]);
534                         }
535                 } while (i > -1);
536
537                 BufferedReader in =
538                         new BufferedReader(new InputStreamReader(new ByteArrayInputStream(inputBytes.toByteArray())));
539                 String str;
540                 while ((str = in.readLine()) != null) {
541
542                         if (str.matches("^.*-----BEGIN PRIVATE KEY-----.*$")) {
543                                 log.debug("Key appears to be in PKCS8 format.");
544                                 in.close();
545                                 return getPkcs8Key(
546                                         singleDerFromPEM(
547                                                 inputBytes.toByteArray(),
548                                                 "-----BEGIN PRIVATE KEY-----",
549                                                 "-----END PRIVATE KEY-----"));
550
551                         } else if (str.matches("^.*-----BEGIN RSA PRIVATE KEY-----.*$")) {
552                                 String nextStr = in.readLine();
553                                 if (nextStr != null && nextStr.matches("^.*Proc-Type: 4,ENCRYPTED.*$")) {
554                                         log.debug("Key appears to be encrypted RSA in raw format.");
555                                         return getRawEncryptedPemKey(inputBytes.toByteArray(), password);
556                                 }
557
558                                 in.close();
559                                 log.debug("Key appears to be RSA in raw format.");
560                                 return getRSARawDerKey(
561                                         singleDerFromPEM(
562                                                 inputBytes.toByteArray(),
563                                                 "-----BEGIN RSA PRIVATE KEY-----",
564                                                 "-----END RSA PRIVATE KEY-----"));
565
566                         } else if (str.matches("^.*-----BEGIN DSA PRIVATE KEY-----.*$")) {
567                                 String nextStr = in.readLine();
568                                 if (nextStr != null && nextStr.matches("^.*Proc-Type: 4,ENCRYPTED.*$")) {
569                                         log.debug("Key appears to be encrypted DSA in raw format.");
570                                         return getRawEncryptedPemKey(inputBytes.toByteArray(), password);
571                                 }
572                                 in.close();
573                                 log.debug("Key appears to be DSA in raw format.");
574                                 return getDSARawDerKey(
575                                         singleDerFromPEM(
576                                                 inputBytes.toByteArray(),
577                                                 "-----BEGIN DSA PRIVATE KEY-----",
578                                                 "-----END DSA PRIVATE KEY-----"));
579
580                         } else if (str.matches("^.*-----BEGIN ENCRYPTED PRIVATE KEY-----.*$")) {
581                                 in.close();
582                                 log.debug("Key appears to be in encrypted PKCS8 format.");
583                                 return getEncryptedPkcs8Key(
584                                         singleDerFromPEM(
585                                                 inputBytes.toByteArray(),
586                                                 "-----BEGIN ENCRYPTED PRIVATE KEY-----",
587                                                 "-----END ENCRYPTED PRIVATE KEY-----"),
588                                         password.toCharArray());
589                         }
590                 }
591                 in.close();
592                 log.error("Unsupported formatting.  Available PEM types are PKCS8, Raw RSA, and Raw DSA.");
593                 throw new CredentialFactoryException("Failed to initialize Credential Resolver.");
594
595         }
596
597         private PrivateKey getRSAPkcs8DerKey(byte[] bytes) throws CredentialFactoryException {
598
599                 try {
600                         KeyFactory keyFactory = KeyFactory.getInstance("RSA");
601                         PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(bytes);
602                         return keyFactory.generatePrivate(keySpec);
603
604                 } catch (Exception e) {
605                         log.error("Unable to load private key: " + e);
606                         throw new CredentialFactoryException("Unable to load private key.");
607                 }
608         }
609
610         private PrivateKey getDSAPkcs8DerKey(byte[] bytes) throws CredentialFactoryException {
611
612                 try {
613                         KeyFactory keyFactory = KeyFactory.getInstance("DSA");
614                         PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(bytes);
615                         return keyFactory.generatePrivate(keySpec);
616
617                 } catch (Exception e) {
618                         log.error("Unable to load private key: " + e);
619                         throw new CredentialFactoryException("Unable to load private key.");
620                 }
621         }
622
623         private PrivateKey getRSARawDerKey(byte[] bytes) throws CredentialFactoryException {
624
625                 try {
626                         DerValue root = new DerValue(bytes);
627                         if (root.tag != DerValue.tag_Sequence) {
628                                 log.error("Unexpected data type.  Unable to load data as an RSA key.");
629                                 throw new CredentialFactoryException("Unable to load private key.");
630                         }
631
632                         DerValue[] childValues = new DerValue[10];
633                         childValues[0] = root.data.getDerValue();
634                         childValues[1] = root.data.getDerValue();
635                         childValues[2] = root.data.getDerValue();
636                         childValues[3] = root.data.getDerValue();
637                         childValues[4] = root.data.getDerValue();
638                         childValues[5] = root.data.getDerValue();
639                         childValues[6] = root.data.getDerValue();
640                         childValues[7] = root.data.getDerValue();
641                         childValues[8] = root.data.getDerValue();
642
643                         //This data is optional.
644                         if (root.data.available() != 0) {
645                                 childValues[9] = root.data.getDerValue();
646                                 if (root.data.available() != 0) {
647                                         log.error("Data overflow.  Unable to load data as an RSA key.");
648                                         throw new CredentialFactoryException("Unable to load private key.");
649                                 }
650                         }
651
652                         if (childValues[0].tag != DerValue.tag_Integer
653                                 || childValues[1].tag != DerValue.tag_Integer
654                                 || childValues[2].tag != DerValue.tag_Integer
655                                 || childValues[3].tag != DerValue.tag_Integer
656                                 || childValues[4].tag != DerValue.tag_Integer
657                                 || childValues[5].tag != DerValue.tag_Integer
658                                 || childValues[6].tag != DerValue.tag_Integer
659                                 || childValues[7].tag != DerValue.tag_Integer
660                                 || childValues[8].tag != DerValue.tag_Integer) {
661                                 log.error("Unexpected data type.  Unable to load data as an RSA key.");
662                                 throw new CredentialFactoryException("Unable to load private key.");
663                         }
664
665                         RSAPrivateCrtKeySpec keySpec =
666                                 new RSAPrivateCrtKeySpec(
667                                         childValues[1].getBigInteger(),
668                                         childValues[2].getBigInteger(),
669                                         childValues[3].getBigInteger(),
670                                         childValues[4].getBigInteger(),
671                                         childValues[5].getBigInteger(),
672                                         childValues[6].getBigInteger(),
673                                         childValues[7].getBigInteger(),
674                                         childValues[8].getBigInteger());
675
676                         KeyFactory keyFactory = KeyFactory.getInstance("RSA");
677
678                         return keyFactory.generatePrivate(keySpec);
679
680                 } catch (IOException e) {
681                         log.error("Invalid DER encoding for RSA key: " + e);
682                         throw new CredentialFactoryException("Unable to load private key.");
683                 } catch (GeneralSecurityException e) {
684                         log.error("Unable to marshall private key: " + e);
685                         throw new CredentialFactoryException("Unable to load private key.");
686                 }
687
688         }
689         private PrivateKey getRawEncryptedPemKey(byte[] bytes, String password) throws CredentialFactoryException {
690
691                 try {
692                         String algorithm = null;
693                         String algParams = null;
694
695                         BufferedReader in = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(bytes)));
696                         String str;
697                         boolean insideBase64 = false;
698                         StringBuffer base64Key = null;
699                         while ((str = in.readLine()) != null) {
700
701                                 if (insideBase64) {
702                                         if (str.matches("^.*Proc-Type: 4,ENCRYPTED.*$")) {
703                                                 continue;
704                                         }
705
706                                         if (str.matches("^.*DEK-Info:.*$")) {
707                                                 String[] components = str.split(":\\s");
708                                                 if (components.length != 2) {
709                                                         log.error("Encrypted key did not contain DEK-Info specification.");
710                                                         throw new CredentialFactoryException("Unable to load private key.");
711                                                 }
712                                                 String[] cryptData = components[1].split(",");
713                                                 if (cryptData.length != 2
714                                                         || cryptData[0] == null
715                                                         || cryptData[0].equals("")
716                                                         || cryptData[1] == null
717                                                         || cryptData[1].equals("")) {
718                                                         log.error("Encrypted key did not contain a proper DEK-Info specification.");
719                                                         throw new CredentialFactoryException("Unable to load private key.");
720                                                 }
721                                                 algorithm = cryptData[0];
722                                                 algParams = cryptData[1];
723                                                 continue;
724                                         }
725                                         if (str.equals("")) {
726                                                 continue;
727                                         }
728
729                                         if (str.matches("^.*-----END [DR]SA PRIVATE KEY-----.*$")) {
730                                                 break;
731                                         }
732                                         {
733                                                 base64Key.append(str);
734                                         }
735                                 } else if (str.matches("^.*-----BEGIN [DR]SA PRIVATE KEY-----.*$")) {
736                                         insideBase64 = true;
737                                         base64Key = new StringBuffer();
738                                 }
739                         }
740                         in.close();
741                         if (base64Key == null || base64Key.length() == 0) {
742                                 log.error("Could not find Base 64 encoded entity.");
743                                 throw new IOException("Could not find Base 64 encoded entity.");
744                         }
745
746                         BASE64Decoder decoder = new BASE64Decoder();
747                         byte[] encryptedBytes = decoder.decodeBuffer(base64Key.toString());
748
749                         byte[] ivBytes = new byte[8];
750                         for (int j = 0; j < 8; j++) {
751                                 ivBytes[j] = (byte) Integer.parseInt(algParams.substring(j * 2, j * 2 + 2), 16);
752                         }
753                         IvParameterSpec paramSpec = new IvParameterSpec(ivBytes);
754
755                         if ((!algorithm.equals("DES-CBC")) && (!algorithm.equals("DES-EDE3-CBC"))) {
756                                 log.error(
757                                         "Connot decrypt key with algorithm ("
758                                                 + algorithm
759                                                 + ").  Supported algorithms for raw (OpenSSL) keys are (DES-CBC) and (DES-EDE3-CBC).");
760                                 throw new CredentialFactoryException("Unable to load private key.");
761                         }
762
763                         byte[] keyBuffer = new byte[24];
764                         //The key generation method (with the IV used as the salt, and
765                         //the single proprietary iteration)
766                         //is the reason we can't use the pkcs5 providers to read the
767                         // OpenSSL encrypted format
768
769                         MessageDigest md = MessageDigest.getInstance("MD5");
770                         md.update(password.getBytes());
771                         md.update(paramSpec.getIV());
772                         byte[] digested = md.digest();
773                         System.arraycopy(digested, 0, keyBuffer, 0, 16);
774
775                         md.update(digested);
776                         md.update(password.getBytes());
777                         md.update(paramSpec.getIV());
778                         digested = md.digest();
779                         System.arraycopy(digested, 0, keyBuffer, 16, 8);
780
781                         SecretKeySpec keySpec = null;
782                         Cipher cipher = null;
783                         if (algorithm.equals("DES-CBC")) {
784                                 //Special handling!!!
785                                 //For DES, we use the same key generation,
786                                 //then just chop off the end :-)
787                                 byte[] desBuff = new byte[8];
788                                 System.arraycopy(keyBuffer, 0, desBuff, 0, 8);
789                                 keySpec = new SecretKeySpec(desBuff, "DES");
790                                 cipher = Cipher.getInstance("DES/CBC/PKCS5Padding");
791                         }
792                         if (algorithm.equals("DES-EDE3-CBC")) {
793                                 keySpec = new SecretKeySpec(keyBuffer, "DESede");
794                                 cipher = Cipher.getInstance("DESede/CBC/PKCS5Padding");
795                         }
796
797                         cipher.init(Cipher.DECRYPT_MODE, keySpec, paramSpec);
798                         byte[] decrypted = cipher.doFinal(encryptedBytes);
799
800                         return getDERKey(new ByteArrayInputStream(decrypted), password);
801
802                 } catch (IOException ioe) {
803                         log.error("Could not decode Base 64: " + ioe);
804                         throw new CredentialFactoryException("Unable to load private key.");
805
806                 } catch (BadPaddingException e) {
807                         log.debug(e.getMessage());
808                         log.error("Incorrect password to unlock private key.");
809                         throw new CredentialFactoryException("Unable to load private key.");
810                 } catch (Exception e) {
811                         log.error(
812                                 "Unable to decrypt private key.  Installed JCE implementations don't support the necessary algorithm: "
813                                         + e);
814                         throw new CredentialFactoryException("Unable to load private key.");
815                 }
816         }
817
818         private PrivateKey getDSARawDerKey(byte[] bytes) throws CredentialFactoryException {
819
820                 try {
821                         DerValue root = new DerValue(bytes);
822                         if (root.tag != DerValue.tag_Sequence) {
823                                 log.error("Unexpected data type.  Unable to load data as an DSA key.");
824                                 throw new CredentialFactoryException("Unable to load private key.");
825                         }
826
827                         DerValue[] childValues = new DerValue[6];
828                         childValues[0] = root.data.getDerValue();
829                         childValues[1] = root.data.getDerValue();
830                         childValues[2] = root.data.getDerValue();
831                         childValues[3] = root.data.getDerValue();
832                         childValues[4] = root.data.getDerValue();
833                         childValues[5] = root.data.getDerValue();
834
835                         if (root.data.available() != 0) {
836                                 log.error("Data overflow.  Unable to load data as an DSA key.");
837                                 throw new CredentialFactoryException("Unable to load private key.");
838                         }
839
840                         if (childValues[0].tag != DerValue.tag_Integer
841                                 || childValues[1].tag != DerValue.tag_Integer
842                                 || childValues[2].tag != DerValue.tag_Integer
843                                 || childValues[3].tag != DerValue.tag_Integer
844                                 || childValues[4].tag != DerValue.tag_Integer
845                                 || childValues[5].tag != DerValue.tag_Integer) {
846                                 log.error("Unexpected data type.  Unable to load data as an DSA key.");
847                                 throw new CredentialFactoryException("Unable to load private key.");
848                         }
849
850                         DSAPrivateKeySpec keySpec =
851                                 new DSAPrivateKeySpec(
852                                         childValues[5].getBigInteger(),
853                                         childValues[1].getBigInteger(),
854                                         childValues[2].getBigInteger(),
855                                         childValues[3].getBigInteger());
856
857                         KeyFactory keyFactory = KeyFactory.getInstance("DSA");
858
859                         return keyFactory.generatePrivate(keySpec);
860
861                 } catch (IOException e) {
862                         log.error("Invalid DER encoding for DSA key: " + e);
863                         throw new CredentialFactoryException("Unable to load private key.");
864                 } catch (GeneralSecurityException e) {
865                         log.error("Unable to marshall private key: " + e);
866                         throw new CredentialFactoryException("Unable to load private key.");
867                 }
868
869         }
870
871         private PrivateKey getPkcs8Key(byte[] bytes) throws CredentialFactoryException {
872
873                 try {
874                         DerValue root = new DerValue(bytes);
875                         if (root.tag != DerValue.tag_Sequence) {
876                                 log.error("Unexpected data type.  Unable to load data as a PKCS8 formatted key.");
877                                 throw new CredentialFactoryException("Unable to load private key.");
878                         }
879
880                         DerValue[] childValues = new DerValue[2];
881                         childValues[0] = root.data.getDerValue();
882                         childValues[1] = root.data.getDerValue();
883
884                         if (childValues[0].tag != DerValue.tag_Integer || childValues[1].tag != DerValue.tag_Sequence) {
885                                 log.error("Unexpected data type.  Unable to load data as a PKCS8 formatted key.");
886                                 throw new CredentialFactoryException("Unable to load private key.");
887                         }
888
889                         DerValue grandChild = childValues[1].data.getDerValue();
890                         if (grandChild.tag != DerValue.tag_ObjectId) {
891                                 log.error("Unexpected data type.  Unable to load data as a PKCS8 formatted key.");
892                                 throw new CredentialFactoryException("Unable to load private key.");
893                         }
894
895                         String keyOID = grandChild.getOID().toString();
896                         if (keyOID.equals(FileCredentialResolver.RSAKey_OID)) {
897                                 log.debug("Found RSA key in PKCS8.");
898                                 return getRSAPkcs8DerKey(bytes);
899                         } else if (keyOID.equals(FileCredentialResolver.DSAKey_OID)) {
900                                 log.debug("Found DSA key in PKCS8.");
901                                 return getDSAPkcs8DerKey(bytes);
902                         } else {
903                                 log.error("Unexpected key type.  Only RSA and DSA keys are supported in PKCS8 format.");
904                                 throw new CredentialFactoryException("Unable to load private key.");
905                         }
906
907                 } catch (IOException e) {
908                         log.error("Invalid DER encoding for PKCS8 formatted key: " + e);
909                         throw new CredentialFactoryException("Unable to load private key.");
910                 }
911         }
912
913         private PrivateKey getEncryptedPkcs8Key(byte[] bytes, char[] password) throws CredentialFactoryException {
914
915                 try {
916
917                         //Convince the JCE provider that it does know how to do
918                         // pbeWithMD5AndDES-CBC
919                         Provider provider = Security.getProvider("SunJCE");
920                         if (provider != null) {
921                                 provider.setProperty("Alg.Alias.AlgorithmParameters.1.2.840.113549.1.5.3", "PBE");
922                                 provider.setProperty("Alg.Alias.SecretKeyFactory.1.2.840.113549.1.5.3", "PBEWithMD5AndDES");
923                                 provider.setProperty("Alg.Alias.Cipher.1.2.840.113549.1.5.3", "PBEWithMD5AndDES");
924                         }
925
926                         EncryptedPrivateKeyInfo encryptedKeyInfo = new EncryptedPrivateKeyInfo(bytes);
927                         AlgorithmParameters params = encryptedKeyInfo.getAlgParameters();
928
929                         if (params == null) {
930                                 log.error(
931                                         "Unable to decrypt private key.  Installed JCE implementations don't support the ("
932                                                 + encryptedKeyInfo.getAlgName()
933                                                 + ") algorithm.");
934                                 throw new CredentialFactoryException("Unable to load private key.");
935                         }
936
937                         SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(encryptedKeyInfo.getAlgName());
938                         PBEKeySpec passwordSpec = new PBEKeySpec(password);
939                         SecretKey key = keyFactory.generateSecret(passwordSpec);
940
941                         Cipher cipher = Cipher.getInstance(encryptedKeyInfo.getAlgName());
942                         cipher.init(Cipher.DECRYPT_MODE, key, params);
943                         PKCS8EncodedKeySpec decrypted = encryptedKeyInfo.getKeySpec(cipher);
944
945                         return getPkcs8Key(decrypted.getEncoded());
946
947                 } catch (IOException e) {
948                         e.printStackTrace();
949                         log.error("Invalid DER encoding for PKCS8 formatted encrypted key: " + e);
950                         throw new CredentialFactoryException("Unable to load private key.");
951                 } catch (InvalidKeySpecException e) {
952                         log.debug(e.getMessage());
953                         log.error("Incorrect password to unlock private key.");
954                         throw new CredentialFactoryException("Unable to load private key.");
955                 } catch (Exception e) {
956                         log.error(
957                                 "Unable to decrypt private key.  Installed JCE implementations don't support the necessary algorithm: "
958                                         + e);
959                         throw new CredentialFactoryException("Unable to load private key.");
960                 }
961
962         }
963
964         private byte[] singleDerFromPEM(byte[] bytes, String beginToken, String endToken) throws IOException {
965
966                 try {
967
968                         BufferedReader in = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(bytes)));
969                         String str;
970                         boolean insideBase64 = false;
971                         StringBuffer base64Key = null;
972                         while ((str = in.readLine()) != null) {
973
974                                 if (insideBase64) {
975                                         if (str.matches("^.*" + endToken + ".*$")) {
976                                                 break;
977                                         }
978                                         {
979                                                 base64Key.append(str);
980                                         }
981                                 } else if (str.matches("^.*" + beginToken + ".*$")) {
982                                         insideBase64 = true;
983                                         base64Key = new StringBuffer();
984                                 }
985                         }
986                         in.close();
987                         if (base64Key == null || base64Key.length() == 0) {
988                                 log.error("Could not find Base 64 encoded entity.");
989                                 throw new IOException("Could not find Base 64 encoded entity.");
990                         }
991
992                         try {
993                                 BASE64Decoder decoder = new BASE64Decoder();
994                                 return decoder.decodeBuffer(base64Key.toString());
995                         } catch (IOException ioe) {
996                                 log.error("Could not decode Base 64: " + ioe);
997                                 throw new IOException("Could not decode Base 64.");
998                         }
999
1000                 } catch (IOException e) {
1001                         log.error("Could not load resource from specified location: " + e);
1002                         throw new IOException("Could not load resource from specified location.");
1003                 }
1004
1005         }
1006
1007         private String getCertFormat(Element e) throws CredentialFactoryException {
1008
1009                 NodeList certificateElements = e.getElementsByTagNameNS(Credentials.credentialsNamespace, "Certificate");
1010                 if (certificateElements.getLength() < 1) {
1011                         log.error("Certificate not specified.");
1012                         throw new CredentialFactoryException("File Credential Resolver requires a <Certificate> specification.");
1013                 }
1014                 if (certificateElements.getLength() > 1) {
1015                         log.error("Multiple Certificate path specifications, using first.");
1016                 }
1017
1018                 String format = ((Element) certificateElements.item(0)).getAttribute("format");
1019                 if (format == null || format.equals("")) {
1020                         log.debug("No format specified for certificate, using default (PEM) format.");
1021                         format = "PEM";
1022                 }
1023
1024                 if ((!format.equals("PEM")) && (!format.equals("DER"))) {
1025                         log.error("File credential resolver only supports the (DER) and (PEM) formats.");
1026                         throw new CredentialFactoryException("Failed to initialize Credential Resolver.");
1027                 }
1028
1029                 return format;
1030         }
1031
1032         private String getKeyFormat(Element e) throws CredentialFactoryException {
1033
1034                 NodeList keyElements = e.getElementsByTagNameNS(Credentials.credentialsNamespace, "Key");
1035                 if (keyElements.getLength() < 1) {
1036                         log.error("Key not specified.");
1037                         throw new CredentialFactoryException("File Credential Resolver requires a <Key> specification.");
1038                 }
1039                 if (keyElements.getLength() > 1) {
1040                         log.error("Multiple Key path specifications, using first.");
1041                 }
1042
1043                 String format = ((Element) keyElements.item(0)).getAttribute("format");
1044                 if (format == null || format.equals("")) {
1045                         log.debug("No format specified for certificate, using default (PEM) format.");
1046                         format = "PEM";
1047                 }
1048
1049                 if (!((format.equals("DER")) || (format.equals("PEM")))) {
1050                         log.error("File credential resolver currently only supports (DER) and (PEM) formats.");
1051                         throw new CredentialFactoryException("Failed to initialize Credential Resolver.");
1052                 }
1053                 return format;
1054         }
1055
1056         private String getKeyPassword(Element e) throws CredentialFactoryException {
1057
1058                 NodeList keyElements = e.getElementsByTagNameNS(Credentials.credentialsNamespace, "Key");
1059                 if (keyElements.getLength() < 1) {
1060                         log.error("Key not specified.");
1061                         throw new CredentialFactoryException("File Credential Resolver requires a <Key> specification.");
1062                 }
1063
1064                 if (keyElements.getLength() > 1) {
1065                         log.error("Multiple Key path specifications, using first.");
1066                 }
1067
1068                 String password = ((Element) keyElements.item(0)).getAttribute("password");
1069                 if (password == null) {
1070                         password = "";
1071                 }
1072                 return password;
1073         }
1074
1075         private String getCertPath(Element e) throws CredentialFactoryException {
1076
1077                 NodeList certificateElements = e.getElementsByTagNameNS(Credentials.credentialsNamespace, "Certificate");
1078                 if (certificateElements.getLength() < 1) {
1079                         log.debug("No <Certificate> element found.");
1080                         return null;
1081                 }
1082                 if (certificateElements.getLength() > 1) {
1083                         log.error("Multiple Certificate path specifications, using first.");
1084                 }
1085
1086                 NodeList pathElements =
1087                         ((Element) certificateElements.item(0)).getElementsByTagNameNS(Credentials.credentialsNamespace, "Path");
1088                 if (pathElements.getLength() < 1) {
1089                         log.error("Certificate path not specified.");
1090                         throw new CredentialFactoryException("File Credential Resolver requires a <Certificate><Path/></Certificate> specification.");
1091                 }
1092                 if (pathElements.getLength() > 1) {
1093                         log.error("Multiple Certificate path specifications, using first.");
1094                 }
1095                 Node tnode = pathElements.item(0).getFirstChild();
1096                 String path = null;
1097                 if (tnode != null && tnode.getNodeType() == Node.TEXT_NODE) {
1098                         path = tnode.getNodeValue();
1099                 }
1100                 if (path == null || path.equals("")) {
1101                         log.error("Certificate path not specified.");
1102                         throw new CredentialFactoryException("File Credential Resolver requires a <Certificate><Path/></Certificate> specification.");
1103                 }
1104                 return path;
1105         }
1106
1107         private String[] getCAPaths(Element e) throws CredentialFactoryException {
1108
1109                 NodeList certificateElements = e.getElementsByTagNameNS(Credentials.credentialsNamespace, "Certificate");
1110                 if (certificateElements.getLength() < 1) {
1111                         log.error("Certificate not specified.");
1112                         throw new CredentialFactoryException("File Credential Resolver requires a <Certificate> specification.");
1113                 }
1114                 if (certificateElements.getLength() > 1) {
1115                         log.error("Multiple Certificate path specifications, using first.");
1116                 }
1117
1118                 NodeList pathElements =
1119                         ((Element) certificateElements.item(0)).getElementsByTagNameNS(Credentials.credentialsNamespace, "CAPath");
1120                 if (pathElements.getLength() < 1) {
1121                         log.debug("No CA Certificate paths specified.");
1122                         return null;
1123                 }
1124                 ArrayList paths = new ArrayList();
1125                 for (int i = 0; i < pathElements.getLength(); i++) {
1126                         Node tnode = pathElements.item(i).getFirstChild();
1127                         String path = null;
1128                         if (tnode != null && tnode.getNodeType() == Node.TEXT_NODE) {
1129                                 path = tnode.getNodeValue();
1130                         }
1131                         if (path != null && !(path.equals(""))) {
1132                                 paths.add(path);
1133                         }
1134                         if (paths.isEmpty()) {
1135                                 log.debug("No CA Certificate paths specified.");
1136                         }
1137                 }
1138                 return (String[]) paths.toArray(new String[0]);
1139         }
1140
1141         private String getKeyPath(Element e) throws CredentialFactoryException {
1142
1143                 NodeList keyElements = e.getElementsByTagNameNS(Credentials.credentialsNamespace, "Key");
1144                 if (keyElements.getLength() < 1) {
1145                         log.error("Key not specified.");
1146                         throw new CredentialFactoryException("File Credential Resolver requires a <Key> specification.");
1147                 }
1148                 if (keyElements.getLength() > 1) {
1149                         log.error("Multiple Key path specifications, using first.");
1150                 }
1151
1152                 NodeList pathElements =
1153                         ((Element) keyElements.item(0)).getElementsByTagNameNS(Credentials.credentialsNamespace, "Path");
1154                 if (pathElements.getLength() < 1) {
1155                         log.error("Key path not specified.");
1156                         throw new CredentialFactoryException("File Credential Resolver requires a <Key><Path/></Certificate> specification.");
1157                 }
1158                 if (pathElements.getLength() > 1) {
1159                         log.error("Multiple Key path specifications, using first.");
1160                 }
1161                 Node tnode = pathElements.item(0).getFirstChild();
1162                 String path = null;
1163                 if (tnode != null && tnode.getNodeType() == Node.TEXT_NODE) {
1164                         path = tnode.getNodeValue();
1165                 }
1166                 if (path == null || path.equals("")) {
1167                         log.error("Key path not specified.");
1168                         throw new CredentialFactoryException("File Credential Resolver requires a <Key><Path/></Certificate> specification.");
1169                 }
1170                 return path;
1171         }
1172
1173         /**
1174          * Loads a specified bundle of certs individually and returns an array of {@link Certificate} objects. This
1175          * is needed because the standard {@link CertificateFactory#getCertificates(InputStream)} method bails out
1176          * when it has trouble loading any cert and cannot handle "comments".
1177          */
1178         private Certificate[] loadCertificates(InputStream inStream, String certType) throws CredentialFactoryException {
1179
1180                 ArrayList certificates = new ArrayList();
1181
1182                 try {
1183                         CertificateFactory certFactory = CertificateFactory.getInstance(certType);
1184
1185                         BufferedReader in = new BufferedReader(new InputStreamReader(inStream));
1186                         String str;
1187                         boolean insideCert = false;
1188                         StringBuffer rawCert = null;
1189                         while ((str = in.readLine()) != null) {
1190
1191                                 if (insideCert) {
1192                                         rawCert.append(str);
1193                                         rawCert.append(System.getProperty("line.separator"));
1194                                         if (str.matches("^.*-----END CERTIFICATE-----.*$")) {
1195                                                 insideCert = false;
1196                                                 try {
1197                                                         Certificate cert =
1198                                                                 certFactory.generateCertificate(
1199                                                                         new ByteArrayInputStream(rawCert.toString().getBytes()));
1200                                                         certificates.add(cert);
1201                                                 } catch (CertificateException ce) {
1202                                                         log.warn("Failed to load a certificate from the certificate bundle: " + ce);
1203                                                         if (log.isDebugEnabled()) {
1204                                                                 log.debug(
1205                                                                         "Dump of bad certificate: "
1206                                                                                 + System.getProperty("line.separator")
1207                                                                                 + rawCert.toString());
1208                                                         }
1209                                                 }
1210                                                 continue;
1211                                         }
1212                                 } else if (str.matches("^.*-----BEGIN CERTIFICATE-----.*$")) {
1213                                         insideCert = true;
1214                                         rawCert = new StringBuffer();
1215                                         rawCert.append(str);
1216                                         rawCert.append(System.getProperty("line.separator"));
1217                                 }
1218                         }
1219                         in.close();
1220                 } catch (IOException p) {
1221                         log.error("Could not load resource from specified location: " + p);
1222                         throw new CredentialFactoryException("Unable to load certificates.");
1223                 } catch (CertificateException p) {
1224                         log.error("Problem loading certificate factory: " + p);
1225                         throw new CredentialFactoryException("Unable to load certificates.");
1226                 }
1227
1228                 return (Certificate[]) certificates.toArray(new Certificate[0]);
1229         }
1230
1231         /**
1232          * Given an ArrayList containing a base certificate and an array of unordered certificates, populates the ArrayList
1233          * with an ordered certificate chain, based on subject and issuer.
1234          * 
1235          * @param chainSource
1236          *            array of certificates to pull from
1237          * @param chainDest
1238          *            ArrayList containing base certificate
1239          * @throws InvalidCertificateChainException
1240          *             thrown if a chain cannot be constructed from the specified elements
1241          */
1242         protected void walkChain(X509Certificate[] chainSource, ArrayList chainDest) throws CredentialFactoryException {
1243
1244                 X509Certificate currentCert = (X509Certificate) chainDest.get(chainDest.size() - 1);
1245                 if (currentCert.getSubjectDN().equals(currentCert.getIssuerDN())) {
1246                         log.debug("Found self-signed root cert: " + currentCert.getSubjectDN());
1247                         return;
1248                 } else {
1249                         for (int i = 0; chainSource.length > i; i++) {
1250                                 if (currentCert.getIssuerDN().equals(chainSource[i].getSubjectDN())) {
1251                                         chainDest.add(chainSource[i]);
1252                                         walkChain(chainSource, chainDest);
1253                                         return;
1254                                 }
1255                         }
1256                         log.debug("Certificate chain is incomplete.");
1257                 }
1258         }
1259
1260         /**
1261          * Boolean indication of whether a given private key and public key form a valid keypair.
1262          * 
1263          * @param pubKey
1264          *            the public key
1265          * @param privKey
1266          *            the private key
1267          */
1268         protected boolean isMatchingKey(PublicKey pubKey, PrivateKey privKey) {
1269
1270                 try {
1271                         String controlString = "asdf";
1272                         log.debug("Checking for matching private key/public key pair");
1273                         Signature signature = null;
1274                         try {
1275                                 signature = Signature.getInstance(privKey.getAlgorithm());
1276                         } catch (NoSuchAlgorithmException nsae) {
1277                                 log.debug("No provider for (RSA) signature, attempting (MD5withRSA).");
1278                                 if (privKey.getAlgorithm().equals("RSA")) {
1279                                         signature = Signature.getInstance("MD5withRSA");
1280                                 } else {
1281                                         throw nsae;
1282                                 }
1283                         }
1284                         signature.initSign(privKey);
1285                         signature.update(controlString.getBytes());
1286                         byte[] sigBytes = signature.sign();
1287                         signature.initVerify(pubKey);
1288                         signature.update(controlString.getBytes());
1289                         if (signature.verify(sigBytes)) {
1290                                 log.debug("Found match.");
1291                                 return true;
1292                         }
1293                 } catch (Exception e) {
1294                         log.warn(e);
1295                 }
1296                 log.debug("This pair does not match.");
1297                 return false;
1298         }
1299
1300         /**
1301          * Auto-enlarging container for bytes.
1302          */
1303
1304         // Sure makes you wish bytes were first class objects.
1305
1306         private class ByteContainer {
1307
1308                 private byte[] buffer;
1309                 private int cushion;
1310                 private int currentSize = 0;
1311
1312                 private ByteContainer(int cushion) {
1313                         buffer = new byte[cushion];
1314                         this.cushion = cushion;
1315                 }
1316
1317                 private void grow() {
1318                         log.debug("Growing ByteContainer.");
1319                         int newSize = currentSize + cushion;
1320                         byte[] b = new byte[newSize];
1321                         int toCopy = Math.min(currentSize, newSize);
1322                         int i;
1323                         for (i = 0; i < toCopy; i++) {
1324                                 b[i] = buffer[i];
1325                         }
1326                         buffer = b;
1327                 }
1328
1329                 /**
1330                  * Returns an array of the bytes in the container.
1331                  * <p>
1332                  */
1333
1334                 private byte[] toByteArray() {
1335                         byte[] b = new byte[currentSize];
1336                         for (int i = 0; i < currentSize; i++) {
1337                                 b[i] = buffer[i];
1338                         }
1339                         return b;
1340                 }
1341
1342                 /**
1343                  * Add one byte to the end of the container.
1344                  */
1345
1346                 private void append(byte b) {
1347                         if (currentSize == buffer.length) {
1348                                 grow();
1349                         }
1350                         buffer[currentSize] = b;
1351                         currentSize++;
1352                 }
1353
1354         }
1355
1356 }
1357
1358 /**
1359  * Loads a credential from a Java keystore. 
1360  * @author Walter Hoehn
1361  */
1362 class KeystoreCredentialResolver implements CredentialResolver {
1363
1364         private static Logger log = Logger.getLogger(KeystoreCredentialResolver.class.getName());
1365
1366         public Credential loadCredential(Element e) throws CredentialFactoryException {
1367
1368                 if (!e.getLocalName().equals("KeyStoreResolver")) {
1369                         log.error("Invalid Credential Resolver configuration: expected <KeyStoreResolver> .");
1370                         throw new CredentialFactoryException("Failed to initialize Credential Resolver.");
1371                 }
1372
1373                 String id = e.getAttribute("Id");
1374                 if (id == null || id.equals("")) {
1375                         log.error("Credential Resolvers require specification of the attribute \"Id\".");
1376                         throw new CredentialFactoryException("Failed to initialize Credential Resolver.");
1377                 }
1378
1379                 String keyStoreType = e.getAttribute("storeType");
1380                 if (keyStoreType == null || keyStoreType.equals("")) {
1381                         log.debug("Using default store type for credential.");
1382                         keyStoreType = "JKS";
1383                 }
1384
1385                 String path = loadPath(e);
1386                 String alias = loadAlias(e);
1387                 String certAlias = loadCertAlias(e, alias);
1388                 String keyPassword = loadKeyPassword(e);
1389                 String keyStorePassword = loadKeyStorePassword(e);
1390
1391                 try {
1392                         KeyStore keyStore = KeyStore.getInstance(keyStoreType);
1393
1394                         keyStore.load(new ShibResource(path, this.getClass()).getInputStream(), keyStorePassword.toCharArray());
1395
1396                         PrivateKey privateKey = (PrivateKey) keyStore.getKey(alias, keyPassword.toCharArray());
1397
1398                         if (privateKey == null) {
1399                                 throw new CredentialFactoryException("No key entry was found with an alias of (" + alias + ").");
1400                         }
1401
1402                         Certificate[] certificates = keyStore.getCertificateChain(certAlias);
1403                         if (certificates == null) {
1404                                 throw new CredentialFactoryException(
1405                                         "An error occurred while reading the java keystore: No certificate found with the specified alias ("
1406                                                 + certAlias
1407                                                 + ").");
1408                         }
1409
1410                         X509Certificate[] x509Certs = new X509Certificate[certificates.length];
1411                         for (int i = 0; i < certificates.length; i++) {
1412                                 if (certificates[i] instanceof X509Certificate) {
1413                                         x509Certs[i] = (X509Certificate) certificates[i];
1414                                 } else {
1415                                         throw new CredentialFactoryException(
1416                                                 "The KeyStore Credential Resolver can only load X509 certificates.  Found an unsupported certificate of type ("
1417                                                         + certificates[i]
1418                                                         + ").");
1419                                 }
1420                         }
1421
1422                         return new Credential(x509Certs, privateKey);
1423
1424                 } catch (KeyStoreException kse) {
1425                         throw new CredentialFactoryException("An error occurred while accessing the java keystore: " + kse);
1426                 } catch (NoSuchAlgorithmException nsae) {
1427                         throw new CredentialFactoryException("Appropriate JCE provider not found in the java environment: " + nsae);
1428                 } catch (CertificateException ce) {
1429                         throw new CredentialFactoryException(
1430                                 "The java keystore contained a certificate that could not be loaded: " + ce);
1431                 } catch (IOException ioe) {
1432                         throw new CredentialFactoryException("An error occurred while reading the java keystore: " + ioe);
1433                 } catch (UnrecoverableKeyException uke) {
1434                         throw new CredentialFactoryException(
1435                                 "An error occurred while attempting to load the key from the java keystore: " + uke);
1436                 }
1437
1438         }
1439
1440         private String loadPath(Element e) throws CredentialFactoryException {
1441
1442                 NodeList pathElements = e.getElementsByTagNameNS(Credentials.credentialsNamespace, "Path");
1443                 if (pathElements.getLength() < 1) {
1444                         log.error("KeyStore path not specified.");
1445                         throw new CredentialFactoryException("KeyStore Credential Resolver requires a <Path> specification.");
1446                 }
1447                 if (pathElements.getLength() > 1) {
1448                         log.error("Multiple KeyStore path specifications, using first.");
1449                 }
1450                 Node tnode = pathElements.item(0).getFirstChild();
1451                 String path = null;
1452                 if (tnode != null && tnode.getNodeType() == Node.TEXT_NODE) {
1453                         path = tnode.getNodeValue();
1454                 }
1455                 if (path == null || path.equals("")) {
1456                         log.error("KeyStore path not specified.");
1457                         throw new CredentialFactoryException("KeyStore Credential Resolver requires a <Path> specification.");
1458                 }
1459                 return path;
1460         }
1461
1462         private String loadAlias(Element e) throws CredentialFactoryException {
1463
1464                 NodeList aliasElements = e.getElementsByTagNameNS(Credentials.credentialsNamespace, "KeyAlias");
1465                 if (aliasElements.getLength() < 1) {
1466                         log.error("KeyStore key alias not specified.");
1467                         throw new CredentialFactoryException("KeyStore Credential Resolver requires an <KeyAlias> specification.");
1468                 }
1469                 if (aliasElements.getLength() > 1) {
1470                         log.error("Multiple key alias specifications, using first.");
1471                 }
1472                 Node tnode = aliasElements.item(0).getFirstChild();
1473                 String alias = null;
1474                 if (tnode != null && tnode.getNodeType() == Node.TEXT_NODE) {
1475                         alias = tnode.getNodeValue();
1476                 }
1477                 if (alias == null || alias.equals("")) {
1478                         log.error("KeyStore key alias not specified.");
1479                         throw new CredentialFactoryException("KeyStore Credential Resolver requires an <KeyAlias> specification.");
1480                 }
1481                 return alias;
1482         }
1483
1484         private String loadCertAlias(Element e, String defaultAlias) throws CredentialFactoryException {
1485
1486                 NodeList aliasElements = e.getElementsByTagNameNS(Credentials.credentialsNamespace, "CertAlias");
1487                 if (aliasElements.getLength() < 1) {
1488                         log.debug("KeyStore cert alias not specified, defaulting to key alias.");
1489                         return defaultAlias;
1490                 }
1491
1492                 if (aliasElements.getLength() > 1) {
1493                         log.error("Multiple cert alias specifications, using first.");
1494                 }
1495
1496                 Node tnode = aliasElements.item(0).getFirstChild();
1497                 String alias = null;
1498                 if (tnode != null && tnode.getNodeType() == Node.TEXT_NODE) {
1499                         alias = tnode.getNodeValue();
1500                 }
1501                 if (alias == null || alias.equals("")) {
1502                         log.debug("KeyStore cert alias not specified, defaulting to key alias.");
1503                         return defaultAlias;
1504                 }
1505                 return alias;
1506         }
1507
1508         private String loadKeyStorePassword(Element e) throws CredentialFactoryException {
1509
1510                 NodeList passwordElements = e.getElementsByTagNameNS(Credentials.credentialsNamespace, "StorePassword");
1511                 if (passwordElements.getLength() < 1) {
1512                         log.error("KeyStore password not specified.");
1513                         throw new CredentialFactoryException("KeyStore Credential Resolver requires an <StorePassword> specification.");
1514                 }
1515                 if (passwordElements.getLength() > 1) {
1516                         log.error("Multiple KeyStore password specifications, using first.");
1517                 }
1518                 Node tnode = passwordElements.item(0).getFirstChild();
1519                 String password = null;
1520                 if (tnode != null && tnode.getNodeType() == Node.TEXT_NODE) {
1521                         password = tnode.getNodeValue();
1522                 }
1523                 if (password == null || password.equals("")) {
1524                         log.error("KeyStore password not specified.");
1525                         throw new CredentialFactoryException("KeyStore Credential Resolver requires an <StorePassword> specification.");
1526                 }
1527                 return password;
1528         }
1529
1530         private String loadKeyPassword(Element e) throws CredentialFactoryException {
1531
1532                 NodeList passwords = e.getElementsByTagNameNS(Credentials.credentialsNamespace, "KeyPassword");
1533                 if (passwords.getLength() < 1) {
1534                         log.error("KeyStore key password not specified.");
1535                         throw new CredentialFactoryException("KeyStore Credential Resolver requires an <KeyPassword> specification.");
1536                 }
1537                 if (passwords.getLength() > 1) {
1538                         log.error("Multiple KeyStore key password specifications, using first.");
1539                 }
1540                 Node tnode = passwords.item(0).getFirstChild();
1541                 String password = null;
1542                 if (tnode != null && tnode.getNodeType() == Node.TEXT_NODE) {
1543                         password = tnode.getNodeValue();
1544                 }
1545                 if (password == null || password.equals("")) {
1546                         log.error("KeyStore key password not specified.");
1547                         throw new CredentialFactoryException("KeyStore Credential Resolver requires an <KeyPassword> specification.");
1548                 }
1549                 return password;
1550         }
1551 }
1552
1553 /**
1554  * Uses implementation specified in the configuration to load a credential.
1555  *
1556  * @author Walter Hoehn
1557  */
1558 class CustomCredentialResolver implements CredentialResolver {
1559
1560         private static Logger log = Logger.getLogger(CustomCredentialResolver.class.getName());
1561
1562         public Credential loadCredential(Element e) throws CredentialFactoryException {
1563
1564                 if (!e.getLocalName().equals("CustomCredResolver")) {
1565                         log.error("Invalid Credential Resolver configuration: expected <CustomCredResolver> .");
1566                         throw new CredentialFactoryException("Failed to initialize Credential Resolver.");
1567                 }
1568
1569                 String id = e.getAttribute("id");
1570                 if (id == null || id.equals("")) {
1571                         log.error("Credential Resolvers require specification of the attribute \"id\".");
1572                         throw new CredentialFactoryException("Failed to initialize Credential Resolver.");
1573                 }
1574
1575                 String className = e.getAttribute("Class");
1576                 if (className == null || className.equals("")) {
1577                         log.error("Custom Credential Resolver requires specification of the attribute \"Class\".");
1578                         throw new CredentialFactoryException("Failed to initialize Credential Resolver.");
1579                 }
1580
1581                 try {
1582                         return ((CredentialResolver) Class.forName(className).newInstance()).loadCredential(e);
1583
1584                 } catch (Exception loaderException) {
1585                         log.error(
1586                                 "Failed to load Custom Credential Resolver implementation class: " + loaderException.getMessage());
1587                         throw new CredentialFactoryException("Failed to initialize Credential Resolver.");
1588                 }
1589
1590         }
1591
1592 }
1593
1594 class CredentialFactoryException extends Exception {
1595
1596         CredentialFactoryException(String message) {
1597                 super(message);
1598         }
1599 }