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