2 * The Shibboleth License, Version 1.
4 * University Corporation for Advanced Internet Development, Inc.
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions are met:
11 * Redistributions of source code must retain the above copyright notice, this
12 * list of conditions and the following disclaimer.
14 * Redistributions in binary form must reproduce the above copyright notice,
15 * this list of conditions and the following disclaimer in the documentation
16 * and/or other materials provided with the distribution, if any, must include
17 * the following acknowledgment: "This product includes software developed by
18 * the University Corporation for Advanced Internet Development
19 * <http://www.ucaid.edu>Internet2 Project. Alternately, this acknowledegement
20 * may appear in the software itself, if and wherever such third-party
21 * acknowledgments normally appear.
23 * Neither the name of Shibboleth nor the names of its contributors, nor
24 * Internet2, nor the University Corporation for Advanced Internet Development,
25 * Inc., nor UCAID may be used to endorse or promote products derived from this
26 * software without specific prior written permission. For written permission,
27 * please contact shibboleth@shibboleth.org
29 * Products derived from this software may not be called Shibboleth, Internet2,
30 * UCAID, or the University Corporation for Advanced Internet Development, nor
31 * may Shibboleth appear in their name, without prior written permission of the
32 * University Corporation for Advanced Internet Development.
35 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
36 * AND WITH ALL FAULTS. ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
37 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
38 * PARTICULAR PURPOSE, AND NON-INFRINGEMENT ARE DISCLAIMED AND THE ENTIRE RISK
39 * OF SATISFACTORY QUALITY, PERFORMANCE, ACCURACY, AND EFFORT IS WITH LICENSEE.
40 * IN NO EVENT SHALL THE COPYRIGHT OWNER, CONTRIBUTORS OR THE UNIVERSITY
41 * CORPORATION FOR ADVANCED INTERNET DEVELOPMENT, INC. BE LIABLE FOR ANY DIRECT,
42 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
43 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
44 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
45 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
46 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
47 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
50 package edu.internet2.middleware.shibboleth.aa.attrresolv.provider;
52 import java.io.IOException;
53 import java.security.KeyStore;
54 import java.security.KeyStoreException;
55 import java.security.MessageDigest;
56 import java.security.NoSuchAlgorithmException;
57 import java.security.Principal;
58 import java.security.UnrecoverableKeyException;
59 import java.security.cert.CertificateException;
60 import java.util.Arrays;
61 import java.util.Iterator;
63 import javax.crypto.SecretKey;
64 import javax.naming.NamingException;
65 import javax.naming.directory.Attribute;
66 import javax.naming.directory.Attributes;
68 import org.apache.log4j.Logger;
69 import org.w3c.dom.Element;
70 import org.w3c.dom.Node;
71 import org.w3c.dom.NodeList;
73 import sun.misc.BASE64Encoder;
74 import edu.internet2.middleware.shibboleth.aa.attrresolv.AttributeDefinitionPlugIn;
75 import edu.internet2.middleware.shibboleth.aa.attrresolv.AttributeResolver;
76 import edu.internet2.middleware.shibboleth.aa.attrresolv.Dependencies;
77 import edu.internet2.middleware.shibboleth.aa.attrresolv.ResolutionPlugInException;
78 import edu.internet2.middleware.shibboleth.aa.attrresolv.ResolverAttribute;
79 import edu.internet2.middleware.shibboleth.common.ShibResource;
82 * <code>PersistentIDAttributeDefinition</code> implementation. Provides a persistent, but pseudonymous,
83 * identifier for principals by hashing the principal name, requester, and a fixed secret salt.
85 * @author Scott Cantor (cantor.2@osu.edu)
87 public class PersistentIDAttributeDefinition extends BaseAttributeDefinition implements AttributeDefinitionPlugIn {
89 private static Logger log = Logger.getLogger(PersistentIDAttributeDefinition.class.getName());
90 protected byte salt[];
91 protected String localPersistentId = null;
92 protected String scope;
95 * Constructor for PersistentIDAttributeDefinition. Creates a PlugIn based on configuration
96 * information presented in a DOM Element.
98 public PersistentIDAttributeDefinition(Element e) throws ResolutionPlugInException {
101 localPersistentId = e.getAttributeNS(null, "sourceName");
103 //Make sure we understand how to resolve the local persistent ID for the principal.
104 if (localPersistentId != null && localPersistentId.length() > 0) {
105 if (connectorDependencyIds.size() != 1 || !attributeDependencyIds.isEmpty()) {
106 log.error("Can't specify the sourceName attribute without a single connector dependency.");
107 throw new ResolutionPlugInException("Failed to initialize Attribute Definition PlugIn.");
109 } else if (!connectorDependencyIds.isEmpty()) {
110 log.error("Can't specify a connector dependency without supplying the sourceName attribute.");
111 throw new ResolutionPlugInException("Failed to initialize Attribute Definition PlugIn.");
112 } else if (attributeDependencyIds.size() > 1) {
113 log.error("Can't specify more than one attribute dependency, this is ambiguous.");
114 throw new ResolutionPlugInException("Failed to initialize Attribute Definition PlugIn.");
117 //Grab user specified scope
118 scope = e.getAttribute("scope");
119 if (scope == null || scope.equals("")) {
120 log.error("Attribute \"scope\" required to configure plugin.");
121 throw new ResolutionPlugInException("Failed to initialize Attribute Definition PlugIn.");
124 //Salt can be either embedded in the element or pulled out of a keystore.
125 NodeList salts = e.getElementsByTagNameNS(AttributeResolver.resolverNamespace, "Salt");
126 if (salts == null || salts.getLength() != 1) {
127 log.error("Missing <Salt> from attribute definition configuration.");
128 throw new ResolutionPlugInException("Failed to initialize Attribute Definition PlugIn.");
131 Element salt = (Element) salts.item(0);
132 Node child = salt.getFirstChild();
134 && child.getNodeType() == Node.TEXT_NODE
135 && child.getNodeValue() != null
136 && child.getNodeValue().length() >= 16)
137 this.salt = child.getNodeValue().getBytes();
139 String ksPath = salt.getAttributeNS(null, "keyStorePath");
140 String keyAlias = salt.getAttributeNS(null, "keyStoreKeyAlias");
141 String ksPass = salt.getAttributeNS(null, "keyStorePassword");
142 String keyPass = salt.getAttributeNS(null, "keyStoreKeyPassword");
145 || ksPath.length() == 0
147 || keyAlias.length() == 0
149 || ksPass.length() == 0
151 || keyPass.length() == 0) {
153 log.error("Missing <Salt> keyStore attributes from attribute definition configuration.");
154 throw new ResolutionPlugInException("Failed to initialize Attribute Definition PlugIn.");
158 KeyStore keyStore = KeyStore.getInstance("JCEKS");
160 keyStore.load(new ShibResource(ksPath, this.getClass()).getInputStream(), ksPass.toCharArray());
161 SecretKey secret = (SecretKey) keyStore.getKey(keyAlias, keyPass.toCharArray());
163 if (usingDefaultSecret()) {
165 "You are running the PersistentIDAttributeDefinition PlugIn with the default secret key as a salt. This is UNSAFE! Please change "
166 + "this configuration and restart the origin.");
168 this.salt = secret.getEncoded();
170 } catch (KeyStoreException ex) {
172 "An error occurred while loading the java keystore. Unable to initialize Attribute Definition PlugIn: "
174 throw new ResolutionPlugInException("An error occurred while loading the java keystore. Unable to initialize Attribute Definition PlugIn.");
175 } catch (CertificateException ex) {
177 "The java keystore contained corrupted data. Unable to initialize Attribute Definition PlugIn: "
179 throw new ResolutionPlugInException("The java keystore contained corrupted data. Unable to initialize Attribute Definition PlugIn.");
180 } catch (NoSuchAlgorithmException ex) {
182 "Appropriate JCE provider not found in the java environment. Unable to initialize Attribute Definition PlugIn: "
184 throw new ResolutionPlugInException("Appropriate JCE provider not found in the java environment. Unable to initialize Attribute Definition PlugIn.");
185 } catch (IOException ex) {
187 "An error accessing while loading the java keystore. Unable to initialize Attribute Definition PlugIn: "
189 throw new ResolutionPlugInException("An error occurred while accessing the java keystore. Unable to initialize Attribute Definition PlugIn.");
190 } catch (UnrecoverableKeyException ex) {
192 "Secret could not be loaded from the java keystore. Verify that the alias and password are correct: "
194 throw new ResolutionPlugInException("Secret could not be loaded from the java keystore. Verify that the alias and password are correct. ");
200 * @see edu.internet2.middleware.shibboleth.aa.attrresolv.AttributeDefinitionPlugIn#resolve(edu.internet2.middleware.shibboleth.aa.attrresolv.ArpAttribute, java.security.Principal, java.lang.String, edu.internet2.middleware.shibboleth.aa.attrresolv.Dependencies)
202 public void resolve(ResolverAttribute attribute, Principal principal, String requester, Dependencies depends)
203 throws ResolutionPlugInException {
204 log.debug("Resolving attribute: (" + getId() + ")");
205 String localId = null;
207 //Resolve the correct local persistent identifier.
208 if (!attributeDependencyIds.isEmpty()) {
209 ResolverAttribute dep = depends.getAttributeResolution((String) attributeDependencyIds.iterator().next());
211 Iterator vals = dep.getValues();
212 if (vals.hasNext()) {
213 log.debug("Found persistent ID value for attribute (" + getId() + ").");
214 localId = (String) vals.next();
215 if (vals.hasNext()) {
217 "An attribute dependency of attribute ("
219 + ") returned multiple values, expecting only one.");
224 "An attribute dependency of attribute (" + getId() + ") returned no values, expecting one.");
229 "An attribute dependency of attribute (" + getId() + ") was not included in the dependency chain.");
232 } else if (!connectorDependencyIds.isEmpty()) {
233 Attributes attrs = depends.getConnectorResolution((String) connectorDependencyIds.iterator().next());
235 Attribute attr = attrs.get(localPersistentId);
237 if (attr.size() != 1) {
239 "An attribute dependency of attribute ("
243 + " values, expecting only one.");
246 localId = (String) attr.get();
247 log.debug("Found persistent ID value for attribute (" + getId() + ").");
248 } catch (NamingException e) {
249 log.error("A connector dependency of attribute (" + getId() + ") threw an exception: " + e);
255 log.error("A connector dependency of attribute (" + getId() + ") did not return any attributes.");
259 localId = principal.getName();
262 if (lifeTime != -1) {
263 attribute.setLifetime(lifeTime);
266 //Hash the data together to produce the persistent ID.
268 MessageDigest md = MessageDigest.getInstance("SHA");
269 md.update(requester.getBytes());
270 md.update((byte) '!');
271 md.update(localId.getBytes());
272 md.update((byte) '!');
273 String result = new BASE64Encoder().encode(md.digest(salt));
275 attribute.registerValueHandler(new ScopedStringValueHandler(scope));
276 attribute.addValue(result.replaceAll(System.getProperty("line.separator"), ""));
277 attribute.setResolved();
278 } catch (NoSuchAlgorithmException e) {
279 log.error("Unable to load SHA-1 hash algorithm.");
283 private boolean usingDefaultSecret() {
310 return Arrays.equals(defaultKey, salt);