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