Added scope to Persistand ID attribute definition.
[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.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;
80
81 /**
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.
84  * 
85  * @author Scott Cantor (cantor.2@osu.edu)
86  */
87 public class PersistentIDAttributeDefinition extends BaseAttributeDefinition implements AttributeDefinitionPlugIn {
88
89         private static Logger log = Logger.getLogger(PersistentIDAttributeDefinition.class.getName());
90         protected byte salt[];
91         protected String localPersistentId = null;
92         protected String scope;
93
94         /**
95          * Constructor for PersistentIDAttributeDefinition.  Creates a PlugIn based on configuration
96          * information presented in a DOM Element.
97          */
98         public PersistentIDAttributeDefinition(Element e) throws ResolutionPlugInException {
99
100                 super(e);
101                 localPersistentId = e.getAttributeNS(null, "sourceName");
102
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.");
108                         }
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.");
115                 }
116
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.");
122                 }
123
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.");
129                 }
130
131                 Element salt = (Element) salts.item(0);
132                 Node child = salt.getFirstChild();
133                 if (child != null
134                         && child.getNodeType() == Node.TEXT_NODE
135                         && child.getNodeValue() != null
136                         && child.getNodeValue().length() >= 16)
137                         this.salt = child.getNodeValue().getBytes();
138                 else {
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");
143
144                         if (ksPath == null
145                                 || ksPath.length() == 0
146                                 || keyAlias == null
147                                 || keyAlias.length() == 0
148                                 || ksPass == null
149                                 || ksPass.length() == 0
150                                 || keyPass == null
151                                 || keyPass.length() == 0) {
152
153                                 log.error("Missing <Salt> keyStore attributes from attribute definition configuration.");
154                                 throw new ResolutionPlugInException("Failed to initialize Attribute Definition PlugIn.");
155                         }
156
157                         try {
158                                 KeyStore keyStore = KeyStore.getInstance("JCEKS");
159
160                                 keyStore.load(new ShibResource(ksPath, this.getClass()).getInputStream(), ksPass.toCharArray());
161                                 SecretKey secret = (SecretKey) keyStore.getKey(keyAlias, keyPass.toCharArray());
162
163                                 if (usingDefaultSecret()) {
164                                         log.warn(
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.");
167                                 }
168                                 this.salt = secret.getEncoded();
169
170                         } catch (KeyStoreException ex) {
171                                 log.error(
172                                         "An error occurred while loading the java keystore.  Unable to initialize Attribute Definition PlugIn: "
173                                                 + ex);
174                                 throw new ResolutionPlugInException("An error occurred while loading the java keystore.  Unable to initialize Attribute Definition PlugIn.");
175                         } catch (CertificateException ex) {
176                                 log.error(
177                                         "The java keystore contained corrupted data.  Unable to initialize Attribute Definition PlugIn: "
178                                                 + ex);
179                                 throw new ResolutionPlugInException("The java keystore contained corrupted data.  Unable to initialize Attribute Definition PlugIn.");
180                         } catch (NoSuchAlgorithmException ex) {
181                                 log.error(
182                                         "Appropriate JCE provider not found in the java environment. Unable to initialize Attribute Definition PlugIn: "
183                                                 + ex);
184                                 throw new ResolutionPlugInException("Appropriate JCE provider not found in the java environment. Unable to initialize Attribute Definition PlugIn.");
185                         } catch (IOException ex) {
186                                 log.error(
187                                         "An error accessing while loading the java keystore.  Unable to initialize Attribute Definition PlugIn: "
188                                                 + ex);
189                                 throw new ResolutionPlugInException("An error occurred while accessing the java keystore.  Unable to initialize Attribute Definition PlugIn.");
190                         } catch (UnrecoverableKeyException ex) {
191                                 log.error(
192                                         "Secret could not be loaded from the java keystore.  Verify that the alias and password are correct: "
193                                                 + ex);
194                                 throw new ResolutionPlugInException("Secret could not be loaded from the java keystore.  Verify that the alias and password are correct. ");
195                         }
196                 }
197         }
198
199         /**
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)
201          */
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;
206
207                 //Resolve the correct local persistent identifier.
208                 if (!attributeDependencyIds.isEmpty()) {
209                         ResolverAttribute dep = depends.getAttributeResolution((String) attributeDependencyIds.iterator().next());
210                         if (dep != null) {
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()) {
216                                                 log.error(
217                                                         "An attribute dependency of attribute ("
218                                                                 + getId()
219                                                                 + ") returned multiple values, expecting only one.");
220                                                 return;
221                                         }
222                                 } else {
223                                         log.error(
224                                                 "An attribute dependency of attribute (" + getId() + ") returned no values, expecting one.");
225                                         return;
226                                 }
227                         } else {
228                                 log.error(
229                                         "An attribute dependency of attribute (" + getId() + ") was not included in the dependency chain.");
230                                 return;
231                         }
232                 } else if (!connectorDependencyIds.isEmpty()) {
233                         Attributes attrs = depends.getConnectorResolution((String) connectorDependencyIds.iterator().next());
234                         if (attrs != null) {
235                                 Attribute attr = attrs.get(localPersistentId);
236                                 if (attr != null) {
237                                         if (attr.size() != 1) {
238                                                 log.error(
239                                                         "An attribute dependency of attribute ("
240                                                                 + getId()
241                                                                 + ") returned "
242                                                                 + attr.size()
243                                                                 + " values, expecting only one.");
244                                         } else {
245                                                 try {
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);
250                                                         return;
251                                                 }
252                                         }
253                                 }
254                         } else {
255                                 log.error("A connector dependency of attribute (" + getId() + ") did not return any attributes.");
256                                 return;
257                         }
258                 } else {
259                         localId = principal.getName();
260                 }
261
262                 if (lifeTime != -1) {
263                         attribute.setLifetime(lifeTime);
264                 }
265
266                 //Hash the data together to produce the persistent ID.
267                 try {
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));
274
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.");
280                 }
281         }
282
283         private boolean usingDefaultSecret() {
284                 byte[] defaultKey =
285                         new byte[] {
286                                 (byte) 0xC7,
287                                 (byte) 0x49,
288                                 (byte) 0x80,
289                                 (byte) 0xD3,
290                                 (byte) 0x02,
291                                 (byte) 0x4A,
292                                 (byte) 0x61,
293                                 (byte) 0xEF,
294                                 (byte) 0x25,
295                                 (byte) 0x5D,
296                                 (byte) 0xE3,
297                                 (byte) 0x2F,
298                                 (byte) 0x57,
299                                 (byte) 0x51,
300                                 (byte) 0x20,
301                                 (byte) 0x15,
302                                 (byte) 0xC7,
303                                 (byte) 0x49,
304                                 (byte) 0x80,
305                                 (byte) 0xD3,
306                                 (byte) 0x02,
307                                 (byte) 0x4A,
308                                 (byte) 0x61,
309                                 (byte) 0xEF };
310                 return Arrays.equals(defaultKey, salt);
311         }
312 }