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