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