Fixed a small bug where value handler was being set repeatedly.
[java-idp.git] / src / edu / internet2 / middleware / shibboleth / aa / attrresolv / provider / PersistentIDAttributeDefinition.java
1 /* 
2  * The Shibboleth License, Version 1. 
3  * Copyright (c) 2002 
4  * University Corporation for Advanced Internet Development, Inc. 
5  * All rights reserved
6  * 
7  * 
8  * Redistribution and use in source and binary forms, with or without 
9  * modification, are permitted provided that the following conditions are met:
10  * 
11  * Redistributions of source code must retain the above copyright notice, this 
12  * list of conditions and the following disclaimer.
13  * 
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.
22  * 
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
28  * 
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.
33  * 
34  * 
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.
48  */
49
50 package edu.internet2.middleware.shibboleth.aa.attrresolv.provider;
51
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;
62
63 import javax.crypto.SecretKey;
64 import javax.naming.NamingException;
65 import javax.naming.directory.Attribute;
66 import javax.naming.directory.Attributes;
67
68 import org.apache.log4j.Logger;
69 import org.w3c.dom.Element;
70 import org.w3c.dom.Node;
71 import org.w3c.dom.NodeList;
72
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;
79
80 /**
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.
83  * 
84  * @author Scott Cantor (cantor.2@osu.edu)
85  */
86 public class PersistentIDAttributeDefinition extends BaseAttributeDefinition implements AttributeDefinitionPlugIn {
87
88         private static Logger log = Logger.getLogger(PersistentIDAttributeDefinition.class.getName());
89     protected byte salt[];
90     protected String localPersistentId = null;
91
92         /**
93          * Constructor for PersistentIDAttributeDefinition.  Creates a PlugIn based on configuration
94          * information presented in a DOM Element.
95          */
96         public PersistentIDAttributeDefinition(Element e) throws ResolutionPlugInException {
97
98                 super(e);
99         localPersistentId = e.getAttributeNS(null, "sourceName");
100
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.");
106             }
107         }
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.");
111         }
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.");
115         }
116         
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.");
122         }
123         
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();
129         else {
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");
134             
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) {
139
140                 log.error("Missing <Salt> keyStore attributes from attribute definition configuration.");
141                 throw new ResolutionPlugInException("Failed to initialize Attribute Definition PlugIn.");
142             }
143
144             try {
145                 KeyStore keyStore = KeyStore.getInstance("JCEKS");
146     
147                 keyStore.load(new ShibResource(ksPath, this.getClass()).getInputStream(), ksPass.toCharArray());
148                 SecretKey secret = (SecretKey)keyStore.getKey(keyAlias, keyPass.toCharArray());
149     
150                 if (usingDefaultSecret()) {
151                     log.warn(
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.");
154                 }
155                 this.salt = secret.getEncoded();
156     
157             }
158             catch (KeyStoreException ex) {
159                 log.error(
160                     "An error occurred while loading the java keystore.  Unable to initialize Attribute Definition PlugIn: "
161                         + ex);
162                 throw new ResolutionPlugInException("An error occurred while loading the java keystore.  Unable to initialize Attribute Definition PlugIn.");
163             }
164             catch (CertificateException ex) {
165                 log.error(
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.");
168             }
169             catch (NoSuchAlgorithmException ex) {
170                 log.error(
171                     "Appropriate JCE provider not found in the java environment. Unable to initialize Attribute Definition PlugIn: "
172                         + ex);
173                 throw new ResolutionPlugInException("Appropriate JCE provider not found in the java environment. Unable to initialize Attribute Definition PlugIn.");
174             }
175             catch (IOException ex) {
176                 log.error(
177                     "An error accessing while loading the java keystore.  Unable to initialize Attribute Definition PlugIn: "
178                         + ex);
179                 throw new ResolutionPlugInException("An error occurred while accessing the java keystore.  Unable to initialize Attribute Definition PlugIn.");
180             }
181             catch (UnrecoverableKeyException ex) {
182                 log.error(
183                     "Secret could not be loaded from the java keystore.  Verify that the alias and password are correct: "
184                         + ex);
185                 throw new ResolutionPlugInException("Secret could not be loaded from the java keystore.  Verify that the alias and password are correct. ");
186             }
187         }
188         }
189
190         /**
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)
192          */
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;
197
198         //Resolve the correct local persistent identifier.
199         if (!attributeDependencyIds.isEmpty()) {
200             ResolverAttribute dep = depends.getAttributeResolution((String) attributeDependencyIds.iterator().next());
201             if (dep != null) {
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.");
208                         return;
209                     }
210                 }
211                 else {
212                     log.error("An attribute dependency of attribute (" + getId() + ") returned no values, expecting one.");
213                     return;
214                 }
215             }
216             else {
217                 log.error(
218                     "An attribute dependency of attribute (" + getId() + ") was not included in the dependency chain.");
219                 return;
220             }
221         }
222                 else if (!connectorDependencyIds.isEmpty()) {
223             Attributes attrs = depends.getConnectorResolution((String) connectorDependencyIds.iterator().next());
224             if (attrs != null) {
225                 Attribute attr = attrs.get(localPersistentId);
226                 if (attr != null) {
227                     if (attr.size() != 1) {
228                         log.error("An attribute dependency of attribute (" + getId() + ") returned " + attr.size() + " values, expecting only one.");
229                     }
230                     else {
231                         try
232                         {
233                             localId = (String)attr.get();
234                             log.debug("Found persistent ID value for attribute (" + getId() + ").");
235                        }
236                         catch (NamingException e)
237                         {
238                             log.error("A connector dependency of attribute (" + getId() + ") threw an exception: " + e);
239                             return;
240                         }
241                     }
242                 }
243             }
244             else {
245                 log.error(
246                     "A connector dependency of attribute (" + getId() + ") did not return any attributes.");
247                 return;
248             }
249                 }
250         else {
251             localId = principal.getName();
252         }
253
254         if (lifeTime != -1) {
255                 attribute.setLifetime(lifeTime);
256         }
257
258         //Hash the data together to produce the persistent ID.
259         try
260         {
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));
267  
268             attribute.addValue(result.replaceAll(System.getProperty("line.separator"), ""));
269             attribute.setResolved();
270         }
271         catch (NoSuchAlgorithmException e)
272         {
273             log.error("Unable to load SHA-1 hash algorithm.");
274         }
275         }
276
277     private boolean usingDefaultSecret() {
278         byte[] defaultKey =
279             new byte[] {
280                 (byte) 0xC7,
281                 (byte) 0x49,
282                 (byte) 0x80,
283                 (byte) 0xD3,
284                 (byte) 0x02,
285                 (byte) 0x4A,
286                 (byte) 0x61,
287                 (byte) 0xEF,
288                 (byte) 0x25,
289                 (byte) 0x5D,
290                 (byte) 0xE3,
291                 (byte) 0x2F,
292                 (byte) 0x57,
293                 (byte) 0x51,
294                 (byte) 0x20,
295                 (byte) 0x15,
296                 (byte) 0xC7,
297                 (byte) 0x49,
298                 (byte) 0x80,
299                 (byte) 0xD3,
300                 (byte) 0x02,
301                 (byte) 0x4A,
302                 (byte) 0x61,
303                 (byte) 0xEF };
304         return Arrays.equals(defaultKey, salt);
305     }
306 }