fcf5bb40ae58e1144d90cbc90c1e1797e6705487
[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                 
206                 if (requester == null || requester.equals("")) {
207                         log.debug("Could not create ID for unauthenticated requester.");
208                         attribute.setResolved();
209                         return;
210                 }
211                 
212                 System.out.println(principal.getName() + requester);
213                 
214                 String localId = null;
215
216                 //Resolve the correct local persistent identifier.
217                 if (!attributeDependencyIds.isEmpty()) {
218                         ResolverAttribute dep = depends.getAttributeResolution((String) attributeDependencyIds.iterator().next());
219                         if (dep != null) {
220                                 Iterator vals = dep.getValues();
221                                 if (vals.hasNext()) {
222                                         log.debug("Found persistent ID value for attribute (" + getId() + ").");
223                                         localId = (String) vals.next();
224                                         if (vals.hasNext()) {
225                                                 log.error(
226                                                         "An attribute dependency of attribute ("
227                                                                 + getId()
228                                                                 + ") returned multiple values, expecting only one.");
229                                                 return;
230                                         }
231                                 } else {
232                                         log.error(
233                                                 "An attribute dependency of attribute (" + getId() + ") returned no values, expecting one.");
234                                         return;
235                                 }
236                         } else {
237                                 log.error(
238                                         "An attribute dependency of attribute (" + getId() + ") was not included in the dependency chain.");
239                                 return;
240                         }
241                 } else if (!connectorDependencyIds.isEmpty()) {
242                         Attributes attrs = depends.getConnectorResolution((String) connectorDependencyIds.iterator().next());
243                         if (attrs != null) {
244                                 Attribute attr = attrs.get(localPersistentId);
245                                 if (attr != null) {
246                                         if (attr.size() != 1) {
247                                                 log.error(
248                                                         "An attribute dependency of attribute ("
249                                                                 + getId()
250                                                                 + ") returned "
251                                                                 + attr.size()
252                                                                 + " values, expecting only one.");
253                                         } else {
254                                                 try {
255                                                         localId = (String) attr.get();
256                                                         log.debug("Found persistent ID value for attribute (" + getId() + ").");
257                                                 } catch (NamingException e) {
258                                                         log.error("A connector dependency of attribute (" + getId() + ") threw an exception: " + e);
259                                                         return;
260                                                 }
261                                         }
262                                 }
263                         } else {
264                                 log.error("A connector dependency of attribute (" + getId() + ") did not return any attributes.");
265                                 return;
266                         }
267                 } else {
268                         localId = principal.getName();
269                 }
270
271                 if (localId == null || localId.equals("")) {
272                         log.error("Specified source data not supplied from dependencies.  Unable to create ID.");
273                         attribute.setResolved();
274                         return;
275                 }
276
277                 if (lifeTime != -1) {
278                         attribute.setLifetime(lifeTime);
279                 }
280
281                 //Hash the data together to produce the persistent ID.
282                 try {
283                         MessageDigest md = MessageDigest.getInstance("SHA");
284                         md.update(requester.getBytes());
285                         md.update((byte) '!');
286                         md.update(localId.getBytes());
287                         md.update((byte) '!');
288                         String result = new BASE64Encoder().encode(md.digest(salt));
289
290                         attribute.registerValueHandler(new ScopedStringValueHandler(scope));
291                         attribute.addValue(result.replaceAll(System.getProperty("line.separator"), ""));
292                         attribute.setResolved();
293                 } catch (NoSuchAlgorithmException e) {
294                         log.error("Unable to load SHA-1 hash algorithm.");
295                 }
296         }
297
298         private boolean usingDefaultSecret() {
299                 byte[] defaultKey =
300                         new byte[] {
301                                 (byte) 0xC7,
302                                 (byte) 0x49,
303                                 (byte) 0x80,
304                                 (byte) 0xD3,
305                                 (byte) 0x02,
306                                 (byte) 0x4A,
307                                 (byte) 0x61,
308                                 (byte) 0xEF,
309                                 (byte) 0x25,
310                                 (byte) 0x5D,
311                                 (byte) 0xE3,
312                                 (byte) 0x2F,
313                                 (byte) 0x57,
314                                 (byte) 0x51,
315                                 (byte) 0x20,
316                                 (byte) 0x15,
317                                 (byte) 0xC7,
318                                 (byte) 0x49,
319                                 (byte) 0x80,
320                                 (byte) 0xD3,
321                                 (byte) 0x02,
322                                 (byte) 0x4A,
323                                 (byte) 0x61,
324                                 (byte) 0xEF };
325                 return Arrays.equals(defaultKey, salt);
326         }
327 }