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.Dependencies;
76 import edu.internet2.middleware.shibboleth.aa.attrresolv.ResolutionPlugInException;
77 import edu.internet2.middleware.shibboleth.aa.attrresolv.ResolverAttribute;
78 import edu.internet2.middleware.shibboleth.common.ShibResource;
81 * <code>PersistentIDAttributeDefinition</code> implementation. Provides a persistent, but pseudonymous,
82 * identifier for principals by hashing the principal name, requester, and a fixed secret salt.
84 * @author Scott Cantor (cantor.2@osu.edu)
86 public class PersistentIDAttributeDefinition extends BaseAttributeDefinition implements AttributeDefinitionPlugIn {
88 private static Logger log = Logger.getLogger(PersistentIDAttributeDefinition.class.getName());
89 protected byte salt[];
90 protected String localPersistentId = null;
93 * Constructor for PersistentIDAttributeDefinition. Creates a PlugIn based on configuration
94 * information presented in a DOM Element.
96 public PersistentIDAttributeDefinition(Element e) throws ResolutionPlugInException {
99 localPersistentId = e.getAttributeNS(null, "sourceName");
101 //Make sure we understand how to resolve the local persistent ID for the principal.
102 if (localPersistentId != null && localPersistentId.length() > 0) {
103 if (connectorDependencyIds.size() != 1 || !attributeDependencyIds.isEmpty()) {
104 log.error("Can't specify the sourceName attribute without a single connector dependency.");
105 throw new ResolutionPlugInException("Failed to initialize Attribute Definition PlugIn.");
108 else if (!connectorDependencyIds.isEmpty()) {
109 log.error("Can't specify a connector dependency without supplying the sourceName attribute.");
110 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 //Salt can be either embedded in the element or pulled out of a keystore.
118 NodeList salts = e.getElementsByTagNameNS(null, "Salt");
119 if (salts == null || salts.getLength() != 1) {
120 log.error("Missing <Salt> from attribute definition configuration.");
121 throw new ResolutionPlugInException("Failed to initialize Attribute Definition PlugIn.");
124 Element salt = (Element)salts.item(0);
125 Node child = salt.getFirstChild();
126 if (child != null && child.getNodeType()==Node.TEXT_NODE &&
127 child.getNodeValue() != null && child.getNodeValue().length() >= 16)
128 this.salt = child.getNodeValue().getBytes();
130 String ksPath = salt.getAttributeNS(null, "keyStorePath");
131 String keyAlias = salt.getAttributeNS(null, "keyStoreKeyAlias");
132 String ksPass = salt.getAttributeNS(null, "keyStorePassword");
133 String keyPass = salt.getAttributeNS(null, "keyStoreKeyPassword");
135 if (ksPath == null || ksPath.length() == 0 ||
136 keyAlias == null || keyAlias.length() == 0 ||
137 ksPass == null || ksPass.length() == 0 ||
138 keyPass == null || keyPass.length() == 0) {
140 log.error("Missing <Salt> keyStore attributes from attribute definition configuration.");
141 throw new ResolutionPlugInException("Failed to initialize Attribute Definition PlugIn.");
145 KeyStore keyStore = KeyStore.getInstance("JCEKS");
147 keyStore.load(new ShibResource(ksPath, this.getClass()).getInputStream(), ksPass.toCharArray());
148 SecretKey secret = (SecretKey)keyStore.getKey(keyAlias, keyPass.toCharArray());
150 if (usingDefaultSecret()) {
152 "You are running the PersistentIDAttributeDefinition PlugIn with the default secret key as a salt. This is UNSAFE! Please change "
153 + "this configuration and restart the origin.");
155 this.salt = secret.getEncoded();
158 catch (KeyStoreException ex) {
160 "An error occurred while loading the java keystore. Unable to initialize Attribute Definition PlugIn: "
162 throw new ResolutionPlugInException("An error occurred while loading the java keystore. Unable to initialize Attribute Definition PlugIn.");
164 catch (CertificateException ex) {
166 "The java keystore contained corrupted data. Unable to initialize Attribute Definition PlugIn: " + ex);
167 throw new ResolutionPlugInException("The java keystore contained corrupted data. Unable to initialize Attribute Definition PlugIn.");
169 catch (NoSuchAlgorithmException ex) {
171 "Appropriate JCE provider not found in the java environment. Unable to initialize Attribute Definition PlugIn: "
173 throw new ResolutionPlugInException("Appropriate JCE provider not found in the java environment. Unable to initialize Attribute Definition PlugIn.");
175 catch (IOException ex) {
177 "An error accessing while loading the java keystore. Unable to initialize Attribute Definition PlugIn: "
179 throw new ResolutionPlugInException("An error occurred while accessing the java keystore. Unable to initialize Attribute Definition PlugIn.");
181 catch (UnrecoverableKeyException ex) {
183 "Secret could not be loaded from the java keystore. Verify that the alias and password are correct: "
185 throw new ResolutionPlugInException("Secret could not be loaded from the java keystore. Verify that the alias and password are correct. ");
191 * @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)
193 public void resolve(ResolverAttribute attribute, Principal principal, String requester, Dependencies depends)
194 throws ResolutionPlugInException {
195 log.debug("Resolving attribute: (" + getId() + ")");
196 String localId = null;
198 //Resolve the correct local persistent identifier.
199 if (!attributeDependencyIds.isEmpty()) {
200 ResolverAttribute dep = depends.getAttributeResolution((String) attributeDependencyIds.iterator().next());
202 Iterator vals = dep.getValues();
203 if (vals.hasNext()) {
204 log.debug("Found persistent ID value for attribute (" + getId() + ").");
205 localId = (String)vals.next();
206 if (vals.hasNext()) {
207 log.error("An attribute dependency of attribute (" + getId() + ") returned multiple values, expecting only one.");
212 log.error("An attribute dependency of attribute (" + getId() + ") returned no values, expecting one.");
218 "An attribute dependency of attribute (" + getId() + ") was not included in the dependency chain.");
222 else if (!connectorDependencyIds.isEmpty()) {
223 Attributes attrs = depends.getConnectorResolution((String) connectorDependencyIds.iterator().next());
225 Attribute attr = attrs.get(localPersistentId);
227 if (attr.size() != 1) {
228 log.error("An attribute dependency of attribute (" + getId() + ") returned " + attr.size() + " values, expecting only one.");
233 localId = (String)attr.get();
234 log.debug("Found persistent ID value for attribute (" + getId() + ").");
236 catch (NamingException e)
238 log.error("A connector dependency of attribute (" + getId() + ") threw an exception: " + e);
246 "A connector dependency of attribute (" + getId() + ") did not return any attributes.");
251 localId = principal.getName();
254 if (lifeTime != -1) {
255 attribute.setLifetime(lifeTime);
258 //Hash the data together to produce the persistent ID.
261 MessageDigest md = MessageDigest.getInstance("SHA");
262 md.update(requester.getBytes());
263 md.update((byte)'!');
264 md.update(localId.getBytes());
265 md.update((byte)'!');
266 String result = new BASE64Encoder().encode(md.digest(salt));
268 attribute.addValue(result.replaceAll(System.getProperty("line.separator"), ""));
269 attribute.setResolved();
271 catch (NoSuchAlgorithmException e)
273 log.error("Unable to load SHA-1 hash algorithm.");
277 private boolean usingDefaultSecret() {
304 return Arrays.equals(defaultKey, salt);