Initial import of origin Credential loading code.
[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.IOException;
41 import java.security.KeyStore;
42 import java.security.KeyStoreException;
43 import java.security.NoSuchAlgorithmException;
44 import java.security.PrivateKey;
45 import java.security.UnrecoverableKeyException;
46 import java.util.Hashtable;
47
48 import java.security.cert.Certificate;
49 import java.security.cert.CertificateException;
50 import java.security.cert.X509Certificate;
51
52 import org.apache.log4j.Logger;
53 import org.w3c.dom.Element;
54 import org.w3c.dom.Node;
55 import org.w3c.dom.NodeList;
56
57 /**
58  * @author Walter Hoehn
59  *  
60  */
61 public class Credentials {
62
63         public static final String credentialsNamespace = "urn:mace:shibboleth:credentials";
64
65         private static Logger log = Logger.getLogger(Credentials.class.getName());
66         private Hashtable data = new Hashtable();
67
68         public Credentials(Element e) {
69
70                 //TODO talk to Scott about the possibility of changing "resolver" to "loader", to avoid confusion with the AR
71
72                 if (!e.getTagName().equals("Credentials")) {
73                         throw new IllegalArgumentException();
74                 }
75
76                 NodeList resolverNodes = e.getChildNodes();
77                 if (resolverNodes.getLength() <= 0) {
78                         log.error("Credentials configuration inclues no Credential Resolver definitions.");
79                         throw new IllegalArgumentException("Cannot load credentials.");
80                 }
81
82                 for (int i = 0; resolverNodes.getLength() > i; i++) {
83                         if (resolverNodes.item(i).getNodeType() == Node.ELEMENT_NODE) {
84                                 try {
85
86                                         String credentialId = ((Element) resolverNodes.item(i)).getAttribute("id");
87                                         if (credentialId == null || credentialId.equals("")) {
88                                                 log.error("Found credential that was not labeled with a unique \"id\" attribute. Skipping.");
89                                         }
90
91                                         if (data.containsKey(credentialId)) {
92                                                 log.error("Duplicate credential id (" + credentialId + ") found. Skipping");
93                                         }
94
95                                         log.info("Found credential (" + credentialId + "). Loading...");
96                                         data.put(credentialId, CredentialFactory.loadCredential((Element) resolverNodes.item(i)));
97
98                                 } catch (CredentialFactoryException cfe) {
99                                         log.error("Could not load credential, skipping: " + cfe.getMessage());
100                                 } catch (ClassCastException cce) {
101                                         log.error("Problem realizing credential configuration" + cce.getMessage());
102                                 }
103                         }
104                 }
105         }
106
107         public boolean containsCredential(String identifier) {
108                 return data.containsKey(identifier);
109         }
110
111         public Credential getCredential(String identifier) {
112                 return (Credential) data.get(identifier);
113         }
114
115         static class CredentialFactory {
116
117                 private static Logger log = Logger.getLogger(CredentialFactory.class.getName());
118
119                 public static Credential loadCredential(Element e) throws CredentialFactoryException {
120                         if (e.getTagName().equals("KeyInfo")) {
121                                 return new KeyInfoCredentialResolver().loadCredential(e);
122                         }
123
124                         if (e.getTagName().equals("FileCredResolver")) {
125                                 return new FileCredentialResolver().loadCredential(e);
126                         }
127
128                         if (e.getTagName().equals("KeyStoreResolver")) {
129                                 return new KeystoreCredentialResolver().loadCredential(e);
130                         }
131
132                         if (e.getTagName().equals("CustomCredResolver")) {
133                                 return new CustomCredentialResolver().loadCredential(e);
134                         }
135
136                         log.error("Unrecognized Credential Resolver type: " + e.getTagName());
137                         throw new CredentialFactoryException("Failed to load credential.");
138                 }
139
140         }
141
142 }
143
144 class KeyInfoCredentialResolver implements CredentialResolver {
145         private static Logger log = Logger.getLogger(KeyInfoCredentialResolver.class.getName());
146         KeyInfoCredentialResolver() throws CredentialFactoryException {
147                 log.error("Credential Resolver (KeyInfoCredentialResolver) not implemented");
148                 throw new CredentialFactoryException("Failed to load credential.");
149         }
150
151         public Credential loadCredential(Element e) {
152                 return null;
153         }
154 }
155
156 class FileCredentialResolver implements CredentialResolver {
157         private static Logger log = Logger.getLogger(FileCredentialResolver.class.getName());
158         FileCredentialResolver() throws CredentialFactoryException {
159                 log.error("Credential Resolver (FileCredentialResolver) not implemented");
160                 throw new CredentialFactoryException("Failed to load credential.");
161         }
162         public Credential loadCredential(Element e) {
163                 return null;
164         }
165 }
166
167 class KeystoreCredentialResolver implements CredentialResolver {
168
169         private static Logger log = Logger.getLogger(KeystoreCredentialResolver.class.getName());
170
171         public Credential loadCredential(Element e) throws CredentialFactoryException {
172
173                 if (!e.getTagName().equals("KeyStoreResolver")) {
174                         log.error("Invalid Credential Resolver configuration: expected <KeyStoreResolver> .");
175                         throw new CredentialFactoryException("Failed to initialize Credential Resolver.");
176                 }
177
178                 String id = e.getAttribute("id");
179                 if (id == null || id.equals("")) {
180                         log.error("Credential Resolvers require specification of the attribute \"id\".");
181                         throw new CredentialFactoryException("Failed to initialize Credential Resolver.");
182                 }
183
184                 String keyStoreType = e.getAttribute("keyStoreType");
185                 if (keyStoreType == null || keyStoreType.equals("")) {
186                         log.debug("Using default store type for credential.");
187                         keyStoreType = "JKS";
188                 }
189
190                 String path = loadPath(e);
191                 String alias = loadAlias(e);
192                 String keyPassword = loadKeyPassword(e);
193                 String keyStorePassword = loadKeyStorePassword(e);
194
195                 try {
196                         KeyStore keyStore = KeyStore.getInstance(keyStoreType);
197
198                         keyStore.load(new ShibResource(path, this.getClass()).getInputStream(), keyStorePassword.toCharArray());
199
200                         PrivateKey privateKey = (PrivateKey) keyStore.getKey(alias, keyPassword.toCharArray());
201
202                         if (privateKey == null) {
203                                 throw new CredentialFactoryException("No key entry was found with an alias of (" + alias + ").");
204                         }
205
206                         Certificate[] certificates = keyStore.getCertificateChain(alias);
207                         if (certificates == null) {
208                                 throw new CredentialFactoryException(
209                                         "An error occurred while reading the java keystore: No certificate found with the specified alias ("
210                                                 + alias
211                                                 + ").");
212                         }
213
214                         X509Certificate[] x509Certs = new X509Certificate[certificates.length];
215                         for (int i = 0; i < certificates.length; i++) {
216                                 if (certificates[i] instanceof X509Certificate) {
217                                         x509Certs[i] = (X509Certificate) certificates[i];
218                                 } else {
219                                         throw new CredentialFactoryException(
220                                                 "The KeyStore Credential Resolver can only load X509 certificates.  Found an unsupported certificate of type ("
221                                                         + certificates[i]
222                                                         + ").");
223                                 }
224                         }
225
226                         return new Credential(x509Certs, privateKey);
227
228                 } catch (KeyStoreException kse) {
229                         throw new CredentialFactoryException("An error occurred while accessing the java keystore: " + kse);
230                 } catch (NoSuchAlgorithmException nsae) {
231                         throw new CredentialFactoryException("Appropriate JCE provider not found in the java environment: " + nsae);
232                 } catch (CertificateException ce) {
233                         throw new CredentialFactoryException(
234                                 "The java keystore contained a certificate that could not be loaded: " + ce);
235                 } catch (IOException ioe) {
236                         throw new CredentialFactoryException("An error occurred while reading the java keystore: " + ioe);
237                 } catch (UnrecoverableKeyException uke) {
238                         throw new CredentialFactoryException(
239                                 "An error occurred while attempting to load the key from the java keystore: " + uke);
240                 }
241
242         }
243
244         private String loadPath(Element e) throws CredentialFactoryException {
245
246                 NodeList pathElements = e.getElementsByTagNameNS(Credentials.credentialsNamespace, "Path");
247                 if (pathElements.getLength() < 1) {
248                         log.error("KeyStore path not specified.");
249                         throw new CredentialFactoryException("KeyStore Credential Resolver requires a <Path> specification.");
250                 }
251                 if (pathElements.getLength() > 1) {
252                         log.error("Multiple KeyStore path specifications, using first.");
253                 }
254                 Node tnode = pathElements.item(0).getFirstChild();
255                 String path = null;
256                 if (tnode != null && tnode.getNodeType() == Node.TEXT_NODE) {
257                         path = tnode.getNodeValue();
258                 }
259                 if (path == null || path.equals("")) {
260                         log.error("KeyStore path not specified.");
261                         throw new CredentialFactoryException("KeyStore Credential Resolver requires a <Path> specification.");
262                 }
263                 return path;
264         }
265
266         private String loadAlias(Element e) throws CredentialFactoryException {
267
268                 NodeList aliasElements = e.getElementsByTagNameNS(Credentials.credentialsNamespace, "Alias");
269                 if (aliasElements.getLength() < 1) {
270                         log.error("KeyStore key alias not specified.");
271                         throw new CredentialFactoryException("KeyStore Credential Resolver requires an <Alias> specification.");
272                 }
273                 if (aliasElements.getLength() > 1) {
274                         log.error("Multiple KeyStore alias specifications, using first.");
275                 }
276                 Node tnode = aliasElements.item(0).getFirstChild();
277                 String alias = null;
278                 if (tnode != null && tnode.getNodeType() == Node.TEXT_NODE) {
279                         alias = tnode.getNodeValue();
280                 }
281                 if (alias == null || alias.equals("")) {
282                         log.error("KeyStore key alias not specified.");
283                         throw new CredentialFactoryException("KeyStore Credential Resolver requires an <Alias> specification.");
284                 }
285                 return alias;
286         }
287
288         private String loadKeyStorePassword(Element e) throws CredentialFactoryException {
289
290                 NodeList passwordElements = e.getElementsByTagNameNS(Credentials.credentialsNamespace, "KeyStorePassword");
291                 if (passwordElements.getLength() < 1) {
292                         log.error("KeyStore password not specified.");
293                         throw new CredentialFactoryException("KeyStore Credential Resolver requires an <KeyStorePassword> specification.");
294                 }
295                 if (passwordElements.getLength() > 1) {
296                         log.error("Multiple KeyStore password specifications, using first.");
297                 }
298                 Node tnode = passwordElements.item(0).getFirstChild();
299                 String password = null;
300                 if (tnode != null && tnode.getNodeType() == Node.TEXT_NODE) {
301                         password = tnode.getNodeValue();
302                 }
303                 if (password == null || password.equals("")) {
304                         log.error("KeyStore password not specified.");
305                         throw new CredentialFactoryException("KeyStore Credential Resolver requires an <KeyStorePassword> specification.");
306                 }
307                 return password;
308         }
309
310         private String loadKeyPassword(Element e) throws CredentialFactoryException {
311
312                 NodeList passwords = e.getElementsByTagNameNS(Credentials.credentialsNamespace, "KeyPassword");
313                 if (passwords.getLength() < 1) {
314                         log.error("KeyStore key password not specified.");
315                         throw new CredentialFactoryException("KeyStore Credential Resolver requires an <KeyPassword> specification.");
316                 }
317                 if (passwords.getLength() > 1) {
318                         log.error("Multiple KeyStore key password specifications, using first.");
319                 }
320                 Node tnode = passwords.item(0).getFirstChild();
321                 String password = null;
322                 if (tnode != null && tnode.getNodeType() == Node.TEXT_NODE) {
323                         password = tnode.getNodeValue();
324                 }
325                 if (password == null || password.equals("")) {
326                         log.error("KeyStore key password not specified.");
327                         throw new CredentialFactoryException("KeyStore Credential Resolver requires an <KeyPassword> specification.");
328                 }
329                 return password;
330         }
331 }
332
333 class CustomCredentialResolver implements CredentialResolver {
334
335         private static Logger log = Logger.getLogger(CustomCredentialResolver.class.getName());
336         private CredentialResolver resolver;
337
338         public Credential loadCredential(Element e) throws CredentialFactoryException {
339
340                 if (!e.getTagName().equals("CustomCredResolver")) {
341                         log.error("Invalid Credential Resolver configuration: expected <CustomCredResolver> .");
342                         throw new CredentialFactoryException("Failed to initialize Credential Resolver.");
343                 }
344
345                 String id = e.getAttribute("id");
346                 if (id == null || id.equals("")) {
347                         log.error("Credential Resolvers require specification of the attribute \"id\".");
348                         throw new CredentialFactoryException("Failed to initialize Credential Resolver.");
349                 }
350
351                 String className = e.getAttribute("Class");
352                 if (className == null || className.equals("")) {
353                         log.error("Custom Credential Resolver requires specification of the attribute \"Class\".");
354                         throw new CredentialFactoryException("Failed to initialize Credential Resolver.");
355                 }
356
357                 try {
358                         return ((CredentialResolver) Class.forName(className).newInstance()).loadCredential(e);
359
360                 } catch (Exception loaderException) {
361                         log.error(
362                                 "Failed to load Custom Credential Resolver implementation class: " + loaderException.getMessage());
363                         throw new CredentialFactoryException("Failed to initialize Credential Resolver.");
364                 }
365
366         }
367
368 }
369
370 class CredentialFactoryException extends Exception {
371
372         CredentialFactoryException(String message) {
373                 super(message);
374         }
375 }