Credentials file resolver will now load rsa keys in DER-encoded pkcs8.
[java-idp.git] / src / edu / internet2 / middleware / shibboleth / common / Credentials.java
index 3542bef..fe599ce 100644 (file)
@@ -45,15 +45,13 @@ import java.security.KeyStoreException;
 import java.security.NoSuchAlgorithmException;
 import java.security.PrivateKey;
 import java.security.UnrecoverableKeyException;
-import java.util.Collection;
-import java.util.Hashtable;
-import java.util.Iterator;
-
 import java.security.cert.Certificate;
 import java.security.cert.CertificateException;
 import java.security.cert.CertificateFactory;
 import java.security.cert.X509Certificate;
 import java.security.spec.PKCS8EncodedKeySpec;
+import java.util.Collection;
+import java.util.Hashtable;
 
 import org.apache.log4j.Logger;
 import org.w3c.dom.Element;
@@ -66,15 +64,13 @@ import org.w3c.dom.NodeList;
  */
 public class Credentials {
 
-       public static final String credentialsNamespace = "urn:mace:shibboleth:credentials";
+       public static final String credentialsNamespace = "urn:mace:shibboleth:credentials:1.0";
 
        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();
                }
@@ -89,9 +85,9 @@ public class Credentials {
                        if (resolverNodes.item(i).getNodeType() == Node.ELEMENT_NODE) {
                                try {
 
-                                       String credentialId = ((Element) resolverNodes.item(i)).getAttribute("id");
+                                       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.");
+                                               log.error("Found credential that was not labeled with a unique \"Id\" attribute. Skipping.");
                                        }
 
                                        if (data.containsKey(credentialId)) {
@@ -127,7 +123,7 @@ public class Credentials {
                                return new KeyInfoCredentialResolver().loadCredential(e);
                        }
 
-                       if (e.getTagName().equals("FileCredResolver")) {
+                       if (e.getTagName().equals("FileResolver")) {
                                return new FileCredentialResolver().loadCredential(e);
                        }
 
@@ -135,7 +131,7 @@ public class Credentials {
                                return new KeystoreCredentialResolver().loadCredential(e);
                        }
 
-                       if (e.getTagName().equals("CustomCredResolver")) {
+                       if (e.getTagName().equals("CustomResolver")) {
                                return new CustomCredentialResolver().loadCredential(e);
                        }
 
@@ -164,72 +160,99 @@ class FileCredentialResolver implements CredentialResolver {
 
        public Credential loadCredential(Element e) throws CredentialFactoryException {
 
-               if (!e.getTagName().equals("FileCredResolver")) {
-                       log.error("Invalid Credential Resolver configuration: expected <FileCredResolver> .");
+               if (!e.getTagName().equals("FileResolver")) {
+                       log.error("Invalid Credential Resolver configuration: expected <FileResolver> .");
                        throw new CredentialFactoryException("Failed to initialize Credential Resolver.");
                }
 
-               String id = e.getAttribute("id");
+               String id = e.getAttribute("Id");
                if (id == null || id.equals("")) {
-                       log.error("Credential Resolvers require specification of the attribute \"id\".");
+                       log.error("Credential Resolvers require specification of the attribute \"Id\".");
                        throw new CredentialFactoryException("Failed to initialize Credential Resolver.");
                }
 
                String certFormat = getCertFormat(e);
                String certPath = getCertPath(e);
-               String keyFormat = "DER";
-               String keyPath = "/conf/test.pemkey";
+
                log.debug("Certificate Format: (" + certFormat + ").");
                log.debug("Certificate Path: (" + certPath + ").");
-               
+
                //TODO provider optional
                //TODO other kinds of certs?
+               //TODO provide a way to specify a separate CA bundle
+               Collection chain = null;
                try {
-               CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
-               Collection chain = certFactory.generateCertificates(new ShibResource(certPath, this.getClass()).getInputStream());
-               if (chain.isEmpty()) {
-                       log.error("File did not contain any valid certificates.");
-                       throw new CredentialFactoryException("File did not contain any valid certificates.");
-               }
-               Iterator iterator = chain.iterator();
-               while (iterator.hasNext()) {
-                       System.err.println(((X509Certificate)iterator.next()).getSubjectDN());
-               }
-               //TODO remove this
-               }catch (Exception p) {
-                       System.err.println(p);
-                       p.printStackTrace();
-               }
-               
+                       CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
+                       chain = certFactory.generateCertificates(new ShibResource(certPath, this.getClass()).getInputStream());
+
+                       //TODO probably want to walk the chain and make sure things are kosher
+                       //TODO need to order the chain
+                       if (chain.isEmpty()) {
+                               log.error("File did not contain any valid certificates.");
+                               throw new CredentialFactoryException("File did not contain any valid certificates.");
+                       }
+
+               } catch (IOException p) {
+                       log.error("Could not load resource from specified location (" + certPath + "): " + p);
+                       throw new CredentialFactoryException("Unable to load certificates.");
+               } catch (CertificateException p) {
+                       log.error("Problem parsing certificate at (" + certPath + "): " + p);
+                       throw new CredentialFactoryException("Unable to load certificates.");
+               }
+               String keyFormat = getKeyFormat(e);
+               String keyPath = getKeyPath(e);
+               log.debug("Key Format: (" + keyFormat + ").");
+               log.debug("Key Path: (" + keyPath + ").");
+
+               String keyAlgorithm = "RSA";
+
+               //TODO providers?
+               //TODO support DER, PEM, DER-PKCS8, and PEM-PKCS8?
+               //TODO DSA
+
+               PrivateKey key = null;
+
+               if (keyAlgorithm.equals("RSA") && keyFormat.equals("DER-PKCS8")) {
+                       try {
+                               key = getRSADERKey(new ShibResource(keyPath, this.getClass()).getInputStream());
+                       } catch (IOException ioe) {
+                               log.error("Could not load resource from specified location (" + keyPath + "): " + e);
+                               throw new CredentialFactoryException("Unable to load private key.");
+                       }
+               } else {
+                       log.error("File credential resolver only supports the RSA keys in DER-encoded PKCS8 format (DER-PKCS8).");
+                       throw new CredentialFactoryException("Failed to initialize Credential Resolver.");
+               }
+
+               return new Credential(((X509Certificate[]) chain.toArray(new X509Certificate[0])), key);
+       }
+
+       private PrivateKey getRSADERKey(InputStream inStream) throws CredentialFactoryException {
+
                try {
-                       
-                       //TODO provider??
-                       //TODO other algorithms
-                                       KeyFactory keyFactory = KeyFactory.getInstance("RSA");
-                                       InputStream keyStream = new ShibResource(certPath, this.getClass()).getInputStream();
-                                       byte[] inputBuffer = new byte[8];
-                                       int i;
-                                       ByteContainer inputBytes = new ByteContainer(400);
-                                       do {
-                                               i = keyStream.read(inputBuffer);
-                                               for (int j = 0; j < i; j++) {
-                                                       inputBytes.append(inputBuffer[j]);
-                                               }
-                                       } while (i > -1);
-
-//TODO other encodings?
-                                       PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(inputBytes.toByteArray());
-                                       PrivateKey key = keyFactory.generatePrivate(keySpec);
-
-                               } catch (Exception p) {
-                                       log.error("Problem reading private key: " + p.getMessage());
-                                       //throw new ExtKeyToolException("Problem reading private key.  Keys should be DER encoded pkcs8 or DER encoded native format.");
+
+                       KeyFactory keyFactory = KeyFactory.getInstance("RSA");
+                       byte[] inputBuffer = new byte[8];
+                       int i;
+                       ByteContainer inputBytes = new ByteContainer(400);
+                       do {
+                               i = inStream.read(inputBuffer);
+                               for (int j = 0; j < i; j++) {
+                                       inputBytes.append(inputBuffer[j]);
                                }
-               
+                       } while (i > -1);
+
+                       PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(inputBytes.toByteArray());
+
+                       return keyFactory.generatePrivate(keySpec);
+
+               } catch (Exception e) {
+                       log.error("Unable to load private key: " + e);
+                       throw new CredentialFactoryException("Unable to load private key.");
+               }
 
-               return new Credential(null, null);
        }
-       
+
        private String getCertFormat(Element e) throws CredentialFactoryException {
 
                NodeList certificateElements = e.getElementsByTagNameNS(Credentials.credentialsNamespace, "Certificate");
@@ -240,18 +263,43 @@ class FileCredentialResolver implements CredentialResolver {
                if (certificateElements.getLength() > 1) {
                        log.error("Multiple Certificate path specifications, using first.");
                }
-               
-               String format = ((Element)certificateElements.item(0)).getAttribute("id");
+
+               String format = ((Element) certificateElements.item(0)).getAttribute("format");
                if (format == null || format.equals("")) {
                        log.debug("No format specified for certificate, using default (PEM) format.");
                        format = "PEM";
                }
-               
+
                if ((!format.equals("PEM")) && (!format.equals("DER"))) {
                        log.error("File credential resolver only supports the (DER) and (PEM) formats.");
                        throw new CredentialFactoryException("Failed to initialize Credential Resolver.");
                }
-               
+
+               return format;
+       }
+
+       private String getKeyFormat(Element e) throws CredentialFactoryException {
+
+               NodeList keyElements = e.getElementsByTagNameNS(Credentials.credentialsNamespace, "Key");
+               if (keyElements.getLength() < 1) {
+                       log.error("Key not specified.");
+                       throw new CredentialFactoryException("File Credential Resolver requires a <Key> specification.");
+               }
+               if (keyElements.getLength() > 1) {
+                       log.error("Multiple Keyf path specifications, using first.");
+               }
+
+               String format = ((Element) keyElements.item(0)).getAttribute("format");
+               if (format == null || format.equals("")) {
+                       log.debug("No format specified for certificate, using default (PEM) format.");
+                       format = "PEM";
+               }
+
+               if (!format.equals("DER-PKCS8")) {
+                       log.error("File credential resolver currently only supports (DER-PKCS8) format.");
+                       throw new CredentialFactoryException("Failed to initialize Credential Resolver.");
+               }
+
                return format;
        }
 
@@ -265,8 +313,9 @@ class FileCredentialResolver implements CredentialResolver {
                if (certificateElements.getLength() > 1) {
                        log.error("Multiple Certificate path specifications, using first.");
                }
-               
-               NodeList pathElements = ((Element) certificateElements.item(0)).getElementsByTagNameNS(Credentials.credentialsNamespace, "Path");
+
+               NodeList pathElements =
+                       ((Element) certificateElements.item(0)).getElementsByTagNameNS(Credentials.credentialsNamespace, "Path");
                if (pathElements.getLength() < 1) {
                        log.error("Certificate path not specified.");
                        throw new CredentialFactoryException("File Credential Resolver requires a <Certificate><Path/></Certificate> specification.");
@@ -285,9 +334,39 @@ class FileCredentialResolver implements CredentialResolver {
                }
                return path;
        }
-       
-       
-       
+
+       private String getKeyPath(Element e) throws CredentialFactoryException {
+
+               NodeList keyElements = e.getElementsByTagNameNS(Credentials.credentialsNamespace, "Key");
+               if (keyElements.getLength() < 1) {
+                       log.error("Key not specified.");
+                       throw new CredentialFactoryException("File Credential Resolver requires a <Key> specification.");
+               }
+               if (keyElements.getLength() > 1) {
+                       log.error("Multiple Key path specifications, using first.");
+               }
+
+               NodeList pathElements =
+                       ((Element) keyElements.item(0)).getElementsByTagNameNS(Credentials.credentialsNamespace, "Path");
+               if (pathElements.getLength() < 1) {
+                       log.error("Key path not specified.");
+                       throw new CredentialFactoryException("File Credential Resolver requires a <Key><Path/></Certificate> specification.");
+               }
+               if (pathElements.getLength() > 1) {
+                       log.error("Multiple Key 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("Key path not specified.");
+                       throw new CredentialFactoryException("File Credential Resolver requires a <Key><Path/></Certificate> specification.");
+               }
+               return path;
+       }
+
        /**
         * Auto-enlarging container for bytes.
         */
@@ -342,9 +421,7 @@ class FileCredentialResolver implements CredentialResolver {
                }
 
        }
-       
-       
-       
+
 }
 
 class KeystoreCredentialResolver implements CredentialResolver {
@@ -358,13 +435,13 @@ class KeystoreCredentialResolver implements CredentialResolver {
                        throw new CredentialFactoryException("Failed to initialize Credential Resolver.");
                }
 
-               String id = e.getAttribute("id");
+               String id = e.getAttribute("Id");
                if (id == null || id.equals("")) {
-                       log.error("Credential Resolvers require specification of the attribute \"id\".");
+                       log.error("Credential Resolvers require specification of the attribute \"Id\".");
                        throw new CredentialFactoryException("Failed to initialize Credential Resolver.");
                }
 
-               String keyStoreType = e.getAttribute("keyStoreType");
+               String keyStoreType = e.getAttribute("storeType");
                if (keyStoreType == null || keyStoreType.equals("")) {
                        log.debug("Using default store type for credential.");
                        keyStoreType = "JKS";
@@ -372,6 +449,7 @@ class KeystoreCredentialResolver implements CredentialResolver {
 
                String path = loadPath(e);
                String alias = loadAlias(e);
+               String certAlias = loadCertAlias(e, alias);
                String keyPassword = loadKeyPassword(e);
                String keyStorePassword = loadKeyStorePassword(e);
 
@@ -386,11 +464,11 @@ class KeystoreCredentialResolver implements CredentialResolver {
                                throw new CredentialFactoryException("No key entry was found with an alias of (" + alias + ").");
                        }
 
-                       Certificate[] certificates = keyStore.getCertificateChain(alias);
+                       Certificate[] certificates = keyStore.getCertificateChain(certAlias);
                        if (certificates == null) {
                                throw new CredentialFactoryException(
                                        "An error occurred while reading the java keystore: No certificate found with the specified alias ("
-                                               + alias
+                                               + certAlias
                                                + ").");
                        }
 
@@ -448,13 +526,13 @@ class KeystoreCredentialResolver implements CredentialResolver {
 
        private String loadAlias(Element e) throws CredentialFactoryException {
 
-               NodeList aliasElements = e.getElementsByTagNameNS(Credentials.credentialsNamespace, "Alias");
+               NodeList aliasElements = e.getElementsByTagNameNS(Credentials.credentialsNamespace, "KeyAlias");
                if (aliasElements.getLength() < 1) {
                        log.error("KeyStore key alias not specified.");
-                       throw new CredentialFactoryException("KeyStore Credential Resolver requires an <Alias> specification.");
+                       throw new CredentialFactoryException("KeyStore Credential Resolver requires an <KeyAlias> specification.");
                }
                if (aliasElements.getLength() > 1) {
-                       log.error("Multiple KeyStore alias specifications, using first.");
+                       log.error("Multiple key alias specifications, using first.");
                }
                Node tnode = aliasElements.item(0).getFirstChild();
                String alias = null;
@@ -463,17 +541,41 @@ class KeystoreCredentialResolver implements CredentialResolver {
                }
                if (alias == null || alias.equals("")) {
                        log.error("KeyStore key alias not specified.");
-                       throw new CredentialFactoryException("KeyStore Credential Resolver requires an <Alias> specification.");
+                       throw new CredentialFactoryException("KeyStore Credential Resolver requires an <KeyAlias> specification.");
+               }
+               return alias;
+       }
+
+       private String loadCertAlias(Element e, String defaultAlias) throws CredentialFactoryException {
+
+               NodeList aliasElements = e.getElementsByTagNameNS(Credentials.credentialsNamespace, "CertAlias");
+               if (aliasElements.getLength() < 1) {
+                       log.debug("KeyStore cert alias not specified, defaulting to key alias.");
+                       return defaultAlias;
+               }
+
+               if (aliasElements.getLength() > 1) {
+                       log.error("Multiple cert 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.debug("KeyStore cert alias not specified, defaulting to key alias.");
+                       return defaultAlias;
                }
                return alias;
        }
 
        private String loadKeyStorePassword(Element e) throws CredentialFactoryException {
 
-               NodeList passwordElements = e.getElementsByTagNameNS(Credentials.credentialsNamespace, "KeyStorePassword");
+               NodeList passwordElements = e.getElementsByTagNameNS(Credentials.credentialsNamespace, "StorePassword");
                if (passwordElements.getLength() < 1) {
                        log.error("KeyStore password not specified.");
-                       throw new CredentialFactoryException("KeyStore Credential Resolver requires an <KeyStorePassword> specification.");
+                       throw new CredentialFactoryException("KeyStore Credential Resolver requires an <StorePassword> specification.");
                }
                if (passwordElements.getLength() > 1) {
                        log.error("Multiple KeyStore password specifications, using first.");
@@ -485,7 +587,7 @@ class KeystoreCredentialResolver implements CredentialResolver {
                }
                if (password == null || password.equals("")) {
                        log.error("KeyStore password not specified.");
-                       throw new CredentialFactoryException("KeyStore Credential Resolver requires an <KeyStorePassword> specification.");
+                       throw new CredentialFactoryException("KeyStore Credential Resolver requires an <StorePassword> specification.");
                }
                return password;
        }
@@ -516,7 +618,6 @@ class KeystoreCredentialResolver implements CredentialResolver {
 class CustomCredentialResolver implements CredentialResolver {
 
        private static Logger log = Logger.getLogger(CustomCredentialResolver.class.getName());
-       private CredentialResolver resolver;
 
        public Credential loadCredential(Element e) throws CredentialFactoryException {