Initial import of origin Credential loading code.
authorwassa <wassa@ab3bd59b-922f-494d-bb5f-6f0a3c29deca>
Thu, 20 Nov 2003 05:38:26 +0000 (05:38 +0000)
committerwassa <wassa@ab3bd59b-922f-494d-bb5f-6f0a3c29deca>
Thu, 20 Nov 2003 05:38:26 +0000 (05:38 +0000)
git-svn-id: https://subversion.switch.ch/svn/shibboleth/java-idp/trunk@793 ab3bd59b-922f-494d-bb5f-6f0a3c29deca

data/credentials1.xml [new file with mode: 0644]
src/edu/internet2/middleware/shibboleth/common/Credential.java [new file with mode: 0644]
src/edu/internet2/middleware/shibboleth/common/CredentialResolver.java [new file with mode: 0644]
src/edu/internet2/middleware/shibboleth/common/Credentials.java [new file with mode: 0644]
tests/edu/internet2/middleware/shibboleth/common/CredentialsTests.java [new file with mode: 0644]

diff --git a/data/credentials1.xml b/data/credentials1.xml
new file mode 100644 (file)
index 0000000..e30c8ca
--- /dev/null
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Credentials xmlns="urn:mace:shibboleth:credentials" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" 
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
+       xsi:schemaLocation="urn:mace:shibboleth:credentials shibboleth.xsd">
+       
+       <KeyStoreResolver id="test" keyStoreType="JKS">
+               <Path>/conf/keystore.jks</Path>
+               <Alias>shibhs</Alias>
+               <KeyStorePassword>shibhs</KeyStorePassword>
+               <KeyPassword>shibhs</KeyPassword>
+       </KeyStoreResolver>
+</Credentials>
\ No newline at end of file
diff --git a/src/edu/internet2/middleware/shibboleth/common/Credential.java b/src/edu/internet2/middleware/shibboleth/common/Credential.java
new file mode 100644 (file)
index 0000000..b5bdc06
--- /dev/null
@@ -0,0 +1,84 @@
+/*
+ * The Shibboleth License, Version 1. Copyright (c) 2002 University Corporation for Advanced Internet Development, Inc.
+ * All rights reserved
+ * 
+ * 
+ * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
+ * following conditions are met:
+ * 
+ * Redistributions of source code must retain the above copyright notice, this list of conditions and the following
+ * disclaimer.
+ * 
+ * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided with the distribution, if any, must include the
+ * following acknowledgment: "This product includes software developed by the University Corporation for Advanced
+ * Internet Development <http://www.ucaid.edu> Internet2 Project. Alternately, this acknowledegement may appear in the
+ * software itself, if and wherever such third-party acknowledgments normally appear.
+ * 
+ * Neither the name of Shibboleth nor the names of its contributors, nor Internet2, nor the University Corporation for
+ * Advanced Internet Development, Inc., nor UCAID may be used to endorse or promote products derived from this software
+ * without specific prior written permission. For written permission, please contact shibboleth@shibboleth.org
+ * 
+ * Products derived from this software may not be called Shibboleth, Internet2, UCAID, or the University Corporation
+ * for Advanced Internet Development, nor may Shibboleth appear in their name, without prior written permission of the
+ * University Corporation for Advanced Internet Development.
+ * 
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND WITH ALL FAULTS. ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+ * PARTICULAR PURPOSE, AND NON-INFRINGEMENT ARE DISCLAIMED AND THE ENTIRE RISK OF SATISFACTORY QUALITY, PERFORMANCE,
+ * ACCURACY, AND EFFORT IS WITH LICENSEE. IN NO EVENT SHALL THE COPYRIGHT OWNER, CONTRIBUTORS OR THE UNIVERSITY
+ * CORPORATION FOR ADVANCED INTERNET DEVELOPMENT, INC. BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package edu.internet2.middleware.shibboleth.common;
+
+import java.security.Key;
+import java.security.PrivateKey;
+import java.security.cert.X509Certificate;
+
+/**
+ * @author Walter Hoehn
+ *  
+ */
+public class Credential {
+
+       public static int X509 = 0;
+
+       private int type;
+       private Key key;
+       private X509Certificate[] certs;
+
+       public Credential(X509Certificate[] certChain, PrivateKey key) {
+               type = X509;
+               certs = certChain;
+               this.key = key;
+       }
+
+       public int getCredentialType() {
+               return type;
+       }
+
+       public String getKeyAlgorithm() {
+               return key.getAlgorithm();
+       }
+
+       public PrivateKey getPrivateKey() {
+               if (key instanceof PrivateKey) {
+                       return (PrivateKey) key;
+               }
+               return null;
+       }
+
+       public X509Certificate getX509Certificate() {
+               return certs[0];
+       }
+
+       public X509Certificate[] getX509CertificateChain() {
+               return certs;
+       }
+}
\ No newline at end of file
diff --git a/src/edu/internet2/middleware/shibboleth/common/CredentialResolver.java b/src/edu/internet2/middleware/shibboleth/common/CredentialResolver.java
new file mode 100644 (file)
index 0000000..a46fcfc
--- /dev/null
@@ -0,0 +1,48 @@
+/*
+ * The Shibboleth License, Version 1. Copyright (c) 2002 University Corporation for Advanced Internet Development, Inc.
+ * All rights reserved
+ * 
+ * 
+ * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
+ * following conditions are met:
+ * 
+ * Redistributions of source code must retain the above copyright notice, this list of conditions and the following
+ * disclaimer.
+ * 
+ * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided with the distribution, if any, must include the
+ * following acknowledgment: "This product includes software developed by the University Corporation for Advanced
+ * Internet Development <http://www.ucaid.edu> Internet2 Project. Alternately, this acknowledegement may appear in the
+ * software itself, if and wherever such third-party acknowledgments normally appear.
+ * 
+ * Neither the name of Shibboleth nor the names of its contributors, nor Internet2, nor the University Corporation for
+ * Advanced Internet Development, Inc., nor UCAID may be used to endorse or promote products derived from this software
+ * without specific prior written permission. For written permission, please contact shibboleth@shibboleth.org
+ * 
+ * Products derived from this software may not be called Shibboleth, Internet2, UCAID, or the University Corporation
+ * for Advanced Internet Development, nor may Shibboleth appear in their name, without prior written permission of the
+ * University Corporation for Advanced Internet Development.
+ * 
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND WITH ALL FAULTS. ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+ * PARTICULAR PURPOSE, AND NON-INFRINGEMENT ARE DISCLAIMED AND THE ENTIRE RISK OF SATISFACTORY QUALITY, PERFORMANCE,
+ * ACCURACY, AND EFFORT IS WITH LICENSEE. IN NO EVENT SHALL THE COPYRIGHT OWNER, CONTRIBUTORS OR THE UNIVERSITY
+ * CORPORATION FOR ADVANCED INTERNET DEVELOPMENT, INC. BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package edu.internet2.middleware.shibboleth.common;
+
+import org.w3c.dom.Element;
+
+/**
+ * @author Walter Hoehn
+ *  
+ */
+public interface CredentialResolver {
+       Credential loadCredential(Element e) throws CredentialFactoryException;
+}
\ No newline at end of file
diff --git a/src/edu/internet2/middleware/shibboleth/common/Credentials.java b/src/edu/internet2/middleware/shibboleth/common/Credentials.java
new file mode 100644 (file)
index 0000000..e7be474
--- /dev/null
@@ -0,0 +1,375 @@
+/*
+ * The Shibboleth License, Version 1. Copyright (c) 2002 University Corporation for Advanced Internet Development, Inc.
+ * All rights reserved
+ * 
+ * 
+ * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
+ * following conditions are met:
+ * 
+ * Redistributions of source code must retain the above copyright notice, this list of conditions and the following
+ * disclaimer.
+ * 
+ * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided with the distribution, if any, must include the
+ * following acknowledgment: "This product includes software developed by the University Corporation for Advanced
+ * Internet Development <http://www.ucaid.edu> Internet2 Project. Alternately, this acknowledegement may appear in the
+ * software itself, if and wherever such third-party acknowledgments normally appear.
+ * 
+ * Neither the name of Shibboleth nor the names of its contributors, nor Internet2, nor the University Corporation for
+ * Advanced Internet Development, Inc., nor UCAID may be used to endorse or promote products derived from this software
+ * without specific prior written permission. For written permission, please contact shibboleth@shibboleth.org
+ * 
+ * Products derived from this software may not be called Shibboleth, Internet2, UCAID, or the University Corporation
+ * for Advanced Internet Development, nor may Shibboleth appear in their name, without prior written permission of the
+ * University Corporation for Advanced Internet Development.
+ * 
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND WITH ALL FAULTS. ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+ * PARTICULAR PURPOSE, AND NON-INFRINGEMENT ARE DISCLAIMED AND THE ENTIRE RISK OF SATISFACTORY QUALITY, PERFORMANCE,
+ * ACCURACY, AND EFFORT IS WITH LICENSEE. IN NO EVENT SHALL THE COPYRIGHT OWNER, CONTRIBUTORS OR THE UNIVERSITY
+ * CORPORATION FOR ADVANCED INTERNET DEVELOPMENT, INC. BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package edu.internet2.middleware.shibboleth.common;
+
+import java.io.IOException;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.UnrecoverableKeyException;
+import java.util.Hashtable;
+
+import java.security.cert.Certificate;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+
+import org.apache.log4j.Logger;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+/**
+ * @author Walter Hoehn
+ *  
+ */
+public class Credentials {
+
+       public static final String credentialsNamespace = "urn:mace:shibboleth:credentials";
+
+       private static Logger log = Logger.getLogger(Credentials.class.getName());
+       private Hashtable data = new Hashtable();
+
+       public Credentials(Element e) {
+
+               //TODO talk to Scott about the possibility of changing "resolver" to "loader", to avoid confusion with the AR
+
+               if (!e.getTagName().equals("Credentials")) {
+                       throw new IllegalArgumentException();
+               }
+
+               NodeList resolverNodes = e.getChildNodes();
+               if (resolverNodes.getLength() <= 0) {
+                       log.error("Credentials configuration inclues no Credential Resolver definitions.");
+                       throw new IllegalArgumentException("Cannot load credentials.");
+               }
+
+               for (int i = 0; resolverNodes.getLength() > i; i++) {
+                       if (resolverNodes.item(i).getNodeType() == Node.ELEMENT_NODE) {
+                               try {
+
+                                       String credentialId = ((Element) resolverNodes.item(i)).getAttribute("id");
+                                       if (credentialId == null || credentialId.equals("")) {
+                                               log.error("Found credential that was not labeled with a unique \"id\" attribute. Skipping.");
+                                       }
+
+                                       if (data.containsKey(credentialId)) {
+                                               log.error("Duplicate credential id (" + credentialId + ") found. Skipping");
+                                       }
+
+                                       log.info("Found credential (" + credentialId + "). Loading...");
+                                       data.put(credentialId, CredentialFactory.loadCredential((Element) resolverNodes.item(i)));
+
+                               } catch (CredentialFactoryException cfe) {
+                                       log.error("Could not load credential, skipping: " + cfe.getMessage());
+                               } catch (ClassCastException cce) {
+                                       log.error("Problem realizing credential configuration" + cce.getMessage());
+                               }
+                       }
+               }
+       }
+
+       public boolean containsCredential(String identifier) {
+               return data.containsKey(identifier);
+       }
+
+       public Credential getCredential(String identifier) {
+               return (Credential) data.get(identifier);
+       }
+
+       static class CredentialFactory {
+
+               private static Logger log = Logger.getLogger(CredentialFactory.class.getName());
+
+               public static Credential loadCredential(Element e) throws CredentialFactoryException {
+                       if (e.getTagName().equals("KeyInfo")) {
+                               return new KeyInfoCredentialResolver().loadCredential(e);
+                       }
+
+                       if (e.getTagName().equals("FileCredResolver")) {
+                               return new FileCredentialResolver().loadCredential(e);
+                       }
+
+                       if (e.getTagName().equals("KeyStoreResolver")) {
+                               return new KeystoreCredentialResolver().loadCredential(e);
+                       }
+
+                       if (e.getTagName().equals("CustomCredResolver")) {
+                               return new CustomCredentialResolver().loadCredential(e);
+                       }
+
+                       log.error("Unrecognized Credential Resolver type: " + e.getTagName());
+                       throw new CredentialFactoryException("Failed to load credential.");
+               }
+
+       }
+
+}
+
+class KeyInfoCredentialResolver implements CredentialResolver {
+       private static Logger log = Logger.getLogger(KeyInfoCredentialResolver.class.getName());
+       KeyInfoCredentialResolver() throws CredentialFactoryException {
+               log.error("Credential Resolver (KeyInfoCredentialResolver) not implemented");
+               throw new CredentialFactoryException("Failed to load credential.");
+       }
+
+       public Credential loadCredential(Element e) {
+               return null;
+       }
+}
+
+class FileCredentialResolver implements CredentialResolver {
+       private static Logger log = Logger.getLogger(FileCredentialResolver.class.getName());
+       FileCredentialResolver() throws CredentialFactoryException {
+               log.error("Credential Resolver (FileCredentialResolver) not implemented");
+               throw new CredentialFactoryException("Failed to load credential.");
+       }
+       public Credential loadCredential(Element e) {
+               return null;
+       }
+}
+
+class KeystoreCredentialResolver implements CredentialResolver {
+
+       private static Logger log = Logger.getLogger(KeystoreCredentialResolver.class.getName());
+
+       public Credential loadCredential(Element e) throws CredentialFactoryException {
+
+               if (!e.getTagName().equals("KeyStoreResolver")) {
+                       log.error("Invalid Credential Resolver configuration: expected <KeyStoreResolver> .");
+                       throw new CredentialFactoryException("Failed to initialize Credential Resolver.");
+               }
+
+               String id = e.getAttribute("id");
+               if (id == null || id.equals("")) {
+                       log.error("Credential Resolvers require specification of the attribute \"id\".");
+                       throw new CredentialFactoryException("Failed to initialize Credential Resolver.");
+               }
+
+               String keyStoreType = e.getAttribute("keyStoreType");
+               if (keyStoreType == null || keyStoreType.equals("")) {
+                       log.debug("Using default store type for credential.");
+                       keyStoreType = "JKS";
+               }
+
+               String path = loadPath(e);
+               String alias = loadAlias(e);
+               String keyPassword = loadKeyPassword(e);
+               String keyStorePassword = loadKeyStorePassword(e);
+
+               try {
+                       KeyStore keyStore = KeyStore.getInstance(keyStoreType);
+
+                       keyStore.load(new ShibResource(path, this.getClass()).getInputStream(), keyStorePassword.toCharArray());
+
+                       PrivateKey privateKey = (PrivateKey) keyStore.getKey(alias, keyPassword.toCharArray());
+
+                       if (privateKey == null) {
+                               throw new CredentialFactoryException("No key entry was found with an alias of (" + alias + ").");
+                       }
+
+                       Certificate[] certificates = keyStore.getCertificateChain(alias);
+                       if (certificates == null) {
+                               throw new CredentialFactoryException(
+                                       "An error occurred while reading the java keystore: No certificate found with the specified alias ("
+                                               + alias
+                                               + ").");
+                       }
+
+                       X509Certificate[] x509Certs = new X509Certificate[certificates.length];
+                       for (int i = 0; i < certificates.length; i++) {
+                               if (certificates[i] instanceof X509Certificate) {
+                                       x509Certs[i] = (X509Certificate) certificates[i];
+                               } else {
+                                       throw new CredentialFactoryException(
+                                               "The KeyStore Credential Resolver can only load X509 certificates.  Found an unsupported certificate of type ("
+                                                       + certificates[i]
+                                                       + ").");
+                               }
+                       }
+
+                       return new Credential(x509Certs, privateKey);
+
+               } catch (KeyStoreException kse) {
+                       throw new CredentialFactoryException("An error occurred while accessing the java keystore: " + kse);
+               } catch (NoSuchAlgorithmException nsae) {
+                       throw new CredentialFactoryException("Appropriate JCE provider not found in the java environment: " + nsae);
+               } catch (CertificateException ce) {
+                       throw new CredentialFactoryException(
+                               "The java keystore contained a certificate that could not be loaded: " + ce);
+               } catch (IOException ioe) {
+                       throw new CredentialFactoryException("An error occurred while reading the java keystore: " + ioe);
+               } catch (UnrecoverableKeyException uke) {
+                       throw new CredentialFactoryException(
+                               "An error occurred while attempting to load the key from the java keystore: " + uke);
+               }
+
+       }
+
+       private String loadPath(Element e) throws CredentialFactoryException {
+
+               NodeList pathElements = e.getElementsByTagNameNS(Credentials.credentialsNamespace, "Path");
+               if (pathElements.getLength() < 1) {
+                       log.error("KeyStore path not specified.");
+                       throw new CredentialFactoryException("KeyStore Credential Resolver requires a <Path> specification.");
+               }
+               if (pathElements.getLength() > 1) {
+                       log.error("Multiple KeyStore path specifications, using first.");
+               }
+               Node tnode = pathElements.item(0).getFirstChild();
+               String path = null;
+               if (tnode != null && tnode.getNodeType() == Node.TEXT_NODE) {
+                       path = tnode.getNodeValue();
+               }
+               if (path == null || path.equals("")) {
+                       log.error("KeyStore path not specified.");
+                       throw new CredentialFactoryException("KeyStore Credential Resolver requires a <Path> specification.");
+               }
+               return path;
+       }
+
+       private String loadAlias(Element e) throws CredentialFactoryException {
+
+               NodeList aliasElements = e.getElementsByTagNameNS(Credentials.credentialsNamespace, "Alias");
+               if (aliasElements.getLength() < 1) {
+                       log.error("KeyStore key alias not specified.");
+                       throw new CredentialFactoryException("KeyStore Credential Resolver requires an <Alias> specification.");
+               }
+               if (aliasElements.getLength() > 1) {
+                       log.error("Multiple KeyStore alias specifications, using first.");
+               }
+               Node tnode = aliasElements.item(0).getFirstChild();
+               String alias = null;
+               if (tnode != null && tnode.getNodeType() == Node.TEXT_NODE) {
+                       alias = tnode.getNodeValue();
+               }
+               if (alias == null || alias.equals("")) {
+                       log.error("KeyStore key alias not specified.");
+                       throw new CredentialFactoryException("KeyStore Credential Resolver requires an <Alias> specification.");
+               }
+               return alias;
+       }
+
+       private String loadKeyStorePassword(Element e) throws CredentialFactoryException {
+
+               NodeList passwordElements = e.getElementsByTagNameNS(Credentials.credentialsNamespace, "KeyStorePassword");
+               if (passwordElements.getLength() < 1) {
+                       log.error("KeyStore password not specified.");
+                       throw new CredentialFactoryException("KeyStore Credential Resolver requires an <KeyStorePassword> specification.");
+               }
+               if (passwordElements.getLength() > 1) {
+                       log.error("Multiple KeyStore password specifications, using first.");
+               }
+               Node tnode = passwordElements.item(0).getFirstChild();
+               String password = null;
+               if (tnode != null && tnode.getNodeType() == Node.TEXT_NODE) {
+                       password = tnode.getNodeValue();
+               }
+               if (password == null || password.equals("")) {
+                       log.error("KeyStore password not specified.");
+                       throw new CredentialFactoryException("KeyStore Credential Resolver requires an <KeyStorePassword> specification.");
+               }
+               return password;
+       }
+
+       private String loadKeyPassword(Element e) throws CredentialFactoryException {
+
+               NodeList passwords = e.getElementsByTagNameNS(Credentials.credentialsNamespace, "KeyPassword");
+               if (passwords.getLength() < 1) {
+                       log.error("KeyStore key password not specified.");
+                       throw new CredentialFactoryException("KeyStore Credential Resolver requires an <KeyPassword> specification.");
+               }
+               if (passwords.getLength() > 1) {
+                       log.error("Multiple KeyStore key password specifications, using first.");
+               }
+               Node tnode = passwords.item(0).getFirstChild();
+               String password = null;
+               if (tnode != null && tnode.getNodeType() == Node.TEXT_NODE) {
+                       password = tnode.getNodeValue();
+               }
+               if (password == null || password.equals("")) {
+                       log.error("KeyStore key password not specified.");
+                       throw new CredentialFactoryException("KeyStore Credential Resolver requires an <KeyPassword> specification.");
+               }
+               return password;
+       }
+}
+
+class CustomCredentialResolver implements CredentialResolver {
+
+       private static Logger log = Logger.getLogger(CustomCredentialResolver.class.getName());
+       private CredentialResolver resolver;
+
+       public Credential loadCredential(Element e) throws CredentialFactoryException {
+
+               if (!e.getTagName().equals("CustomCredResolver")) {
+                       log.error("Invalid Credential Resolver configuration: expected <CustomCredResolver> .");
+                       throw new CredentialFactoryException("Failed to initialize Credential Resolver.");
+               }
+
+               String id = e.getAttribute("id");
+               if (id == null || id.equals("")) {
+                       log.error("Credential Resolvers require specification of the attribute \"id\".");
+                       throw new CredentialFactoryException("Failed to initialize Credential Resolver.");
+               }
+
+               String className = e.getAttribute("Class");
+               if (className == null || className.equals("")) {
+                       log.error("Custom Credential Resolver requires specification of the attribute \"Class\".");
+                       throw new CredentialFactoryException("Failed to initialize Credential Resolver.");
+               }
+
+               try {
+                       return ((CredentialResolver) Class.forName(className).newInstance()).loadCredential(e);
+
+               } catch (Exception loaderException) {
+                       log.error(
+                               "Failed to load Custom Credential Resolver implementation class: " + loaderException.getMessage());
+                       throw new CredentialFactoryException("Failed to initialize Credential Resolver.");
+               }
+
+       }
+
+}
+
+class CredentialFactoryException extends Exception {
+
+       CredentialFactoryException(String message) {
+               super(message);
+       }
+}
diff --git a/tests/edu/internet2/middleware/shibboleth/common/CredentialsTests.java b/tests/edu/internet2/middleware/shibboleth/common/CredentialsTests.java
new file mode 100644 (file)
index 0000000..479236a
--- /dev/null
@@ -0,0 +1,158 @@
+/*
+ * The Shibboleth License, Version 1. Copyright (c) 2002 University Corporation for Advanced Internet Development, Inc.
+ * All rights reserved
+ * 
+ * 
+ * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
+ * following conditions are met:
+ * 
+ * Redistributions of source code must retain the above copyright notice, this list of conditions and the following
+ * disclaimer.
+ * 
+ * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided with the distribution, if any, must include the
+ * following acknowledgment: "This product includes software developed by the University Corporation for Advanced
+ * Internet Development <http://www.ucaid.edu> Internet2 Project. Alternately, this acknowledegement may appear in the
+ * software itself, if and wherever such third-party acknowledgments normally appear.
+ * 
+ * Neither the name of Shibboleth nor the names of its contributors, nor Internet2, nor the University Corporation for
+ * Advanced Internet Development, Inc., nor UCAID may be used to endorse or promote products derived from this software
+ * without specific prior written permission. For written permission, please contact shibboleth@shibboleth.org
+ * 
+ * Products derived from this software may not be called Shibboleth, Internet2, UCAID, or the University Corporation
+ * for Advanced Internet Development, nor may Shibboleth appear in their name, without prior written permission of the
+ * University Corporation for Advanced Internet Development.
+ * 
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND WITH ALL FAULTS. ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+ * PARTICULAR PURPOSE, AND NON-INFRINGEMENT ARE DISCLAIMED AND THE ENTIRE RISK OF SATISFACTORY QUALITY, PERFORMANCE,
+ * ACCURACY, AND EFFORT IS WITH LICENSEE. IN NO EVENT SHALL THE COPYRIGHT OWNER, CONTRIBUTORS OR THE UNIVERSITY
+ * CORPORATION FOR ADVANCED INTERNET DEVELOPMENT, INC. BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package edu.internet2.middleware.shibboleth.common;
+
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.InputStream;
+
+import junit.framework.TestCase;
+
+import org.apache.log4j.BasicConfigurator;
+import org.apache.log4j.Level;
+import org.apache.log4j.Logger;
+import org.apache.xerces.parsers.DOMParser;
+import org.xml.sax.EntityResolver;
+import org.xml.sax.ErrorHandler;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+import org.xml.sax.SAXParseException;
+
+/**
+ * Validation suite for the <code>Credentials</code> interface.
+ * 
+ * @author Walter Hoehn
+ */
+
+public class CredentialsTests extends TestCase {
+
+       private DOMParser parser = new DOMParser();
+
+       public CredentialsTests(String name) {
+               super(name);
+               BasicConfigurator.resetConfiguration();
+               BasicConfigurator.configure();
+               //TODO turn this off later
+               Logger.getRootLogger().setLevel(Level.DEBUG);
+       }
+
+       public static void main(String[] args) {
+               junit.textui.TestRunner.run(CredentialsTests.class);
+               BasicConfigurator.configure();
+               //TODO turn this off later
+               Logger.getRootLogger().setLevel(Level.DEBUG);
+       }
+
+       /**
+        * @see junit.framework.TestCase#setUp()
+        */
+       protected void setUp() throws Exception {
+               super.setUp();
+               try {
+                       //TODO turn this back on when you get the schema worked out
+                       parser.setFeature("http://xml.org/sax/features/validation", false);
+                       parser.setFeature("http://apache.org/xml/features/validation/schema", false);
+                       parser.setEntityResolver(new EntityResolver() {
+                               public InputSource resolveEntity(String publicId, String systemId) throws SAXException {
+
+                                       if (systemId.endsWith("shibboleth-credentials.xsd")) {
+                                               InputStream stream;
+                                               try {
+                                                       stream = new FileInputStream("src/schemas/shibboleth-credentials.xsd");
+                                                       if (stream != null) {
+                                                               return new InputSource(stream);
+                                                       }
+                                                       throw new SAXException("Could not load entity: Null input stream");
+                                               } catch (FileNotFoundException e) {
+                                                       throw new SAXException("Could not load entity: " + e);
+                                               }
+                                       } else {
+                                               return null;
+                                       }
+                               }
+                       });
+
+                       parser.setErrorHandler(new ErrorHandler() {
+                               public void error(SAXParseException arg0) throws SAXException {
+                                       throw new SAXException("Error parsing xml file: " + arg0);
+                               }
+                               public void fatalError(SAXParseException arg0) throws SAXException {
+                                       throw new SAXException("Error parsing xml file: " + arg0);
+                               }
+                               public void warning(SAXParseException arg0) throws SAXException {
+                                       throw new SAXException("Error parsing xml file: " + arg0);
+                               }
+                       });
+               } catch (Exception e) {
+                       fail("Failed to setup xml parser: " + e);
+               }
+
+       }
+
+       public void testKeyStoreX509() {
+
+               try {
+                       InputStream inStream = new FileInputStream("data/credentials1.xml");
+                       parser.parse(new InputSource(inStream));
+                       Credentials credentials = new Credentials(parser.getDocument().getDocumentElement());
+
+                       assertTrue("Credential could not be found.", credentials.containsCredential("test"));
+                       Credential credential = credentials.getCredential("test");
+
+                       assertTrue(
+                               "Credential was loaded with an incorrect type.",
+                               credential.getCredentialType() == Credential.X509);
+                       assertNotNull("Private key was not loaded correctly.", credential.getPrivateKey());
+                       assertEquals(
+                               "Unexpected X509 certificate found.",
+                               credential.getX509Certificate().getSubjectDN().getName(),
+                               "CN=shib2.internet2.edu, OU=Unknown, O=Unknown, ST=Unknown, C=Unknown");
+                       assertEquals(
+                               "Unexpected certificate chain length.",
+                               new Integer(credential.getX509CertificateChain().length),
+                               new Integer(3));
+                       assertEquals(
+                               "Unexpected X509 certificate found.",
+                               credential.getX509CertificateChain()[2].getSubjectDN().getName(),
+                               "CN=HEPKI Master CA -- 20020701A, OU=Division of Information Technology, O=University of Wisconsin, L=Madison, ST=Wisconsin, C=US");
+               } catch (Exception e) {
+                       fail("Failed to load credentials: " + e);
+               }
+       }
+
+}