Push SAML Attribute namespace configuration into the resolver. (Needed for proper...
[java-idp.git] / src / edu / internet2 / middleware / shibboleth / aa / attrresolv / provider / PersistentIDAttributeDefinition.java
1 /*
2  * The Shibboleth License, Version 1. Copyright (c) 2002 University Corporation for Advanced Internet Development, Inc.
3  * All rights reserved Redistribution and use in source and binary forms, with or without modification, are permitted
4  * provided that the following conditions are met: Redistributions of source code must retain the above copyright
5  * notice, this list of conditions and the following disclaimer. Redistributions in binary form must reproduce the above
6  * copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials
7  * provided with the distribution, if any, must include the following acknowledgment: "This product includes software
8  * developed by the University Corporation for Advanced Internet Development <http://www.ucaid.edu>Internet2 Project.
9  * Alternately, this acknowledegement may appear in the software itself, if and wherever such third-party
10  * acknowledgments normally appear. Neither the name of Shibboleth nor the names of its contributors, nor Internet2, nor
11  * the University Corporation for Advanced Internet Development, Inc., nor UCAID may be used to endorse or promote
12  * products derived from this software without specific prior written permission. For written permission, please contact
13  * shibboleth@shibboleth.org Products derived from this software may not be called Shibboleth, Internet2, UCAID, or the
14  * University Corporation for Advanced Internet Development, nor may Shibboleth appear in their name, without prior
15  * written permission of the University Corporation for Advanced Internet Development. THIS SOFTWARE IS PROVIDED BY THE
16  * COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND WITH ALL FAULTS. ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
17  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT ARE
18  * DISCLAIMED AND THE ENTIRE RISK OF SATISFACTORY QUALITY, PERFORMANCE, ACCURACY, AND EFFORT IS WITH LICENSEE. IN NO
19  * EVENT SHALL THE COPYRIGHT OWNER, CONTRIBUTORS OR THE UNIVERSITY CORPORATION FOR ADVANCED INTERNET DEVELOPMENT, INC.
20  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
21  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
23  * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 package edu.internet2.middleware.shibboleth.aa.attrresolv.provider;
27
28 import java.io.IOException;
29 import java.security.KeyStore;
30 import java.security.KeyStoreException;
31 import java.security.MessageDigest;
32 import java.security.NoSuchAlgorithmException;
33 import java.security.Principal;
34 import java.security.UnrecoverableKeyException;
35 import java.security.cert.CertificateException;
36 import java.util.Arrays;
37 import java.util.Iterator;
38
39 import javax.crypto.SecretKey;
40 import javax.naming.NamingException;
41 import javax.naming.directory.Attribute;
42 import javax.naming.directory.Attributes;
43
44 import org.apache.log4j.Logger;
45 import org.bouncycastle.util.encoders.Base64;
46 import org.w3c.dom.Element;
47 import org.w3c.dom.Node;
48 import org.w3c.dom.NodeList;
49
50 import edu.internet2.middleware.shibboleth.aa.attrresolv.AttributeDefinitionPlugIn;
51 import edu.internet2.middleware.shibboleth.aa.attrresolv.AttributeResolver;
52 import edu.internet2.middleware.shibboleth.aa.attrresolv.Dependencies;
53 import edu.internet2.middleware.shibboleth.aa.attrresolv.ResolutionPlugInException;
54 import edu.internet2.middleware.shibboleth.aa.attrresolv.ResolverAttribute;
55 import edu.internet2.middleware.shibboleth.common.ShibResource;
56
57 /**
58  * <code>PersistentIDAttributeDefinition</code> implementation. Provides a persistent, but pseudonymous, identifier
59  * for principals by hashing the principal name, requester, and a fixed secret salt.
60  * 
61  * @author Scott Cantor (cantor.2@osu.edu)
62  */
63 public class PersistentIDAttributeDefinition extends BaseAttributeDefinition implements AttributeDefinitionPlugIn {
64
65         private static Logger log = Logger.getLogger(PersistentIDAttributeDefinition.class.getName());
66         protected byte salt[];
67         protected String localPersistentId = null;
68         protected String scope;
69
70         /**
71          * Constructor for PersistentIDAttributeDefinition. Creates a PlugIn based on configuration information presented in
72          * a DOM Element.
73          */
74         public PersistentIDAttributeDefinition(Element e) throws ResolutionPlugInException {
75
76                 super(e);
77                 localPersistentId = e.getAttributeNS(null, "sourceName");
78
79                 // Make sure we understand how to resolve the local persistent ID for the principal.
80                 if (localPersistentId != null && localPersistentId.length() > 0) {
81                         if (connectorDependencyIds.size() != 1 || !attributeDependencyIds.isEmpty()) {
82                                 log.error("Can't specify the sourceName attribute without a single connector dependency.");
83                                 throw new ResolutionPlugInException("Failed to initialize Attribute Definition PlugIn.");
84                         }
85                 } else if (!connectorDependencyIds.isEmpty()) {
86                         log.error("Can't specify a connector dependency without supplying the sourceName attribute.");
87                         throw new ResolutionPlugInException("Failed to initialize Attribute Definition PlugIn.");
88                 } else if (attributeDependencyIds.size() > 1) {
89                         log.error("Can't specify more than one attribute dependency, this is ambiguous.");
90                         throw new ResolutionPlugInException("Failed to initialize Attribute Definition PlugIn.");
91                 }
92
93                 // Grab user specified scope
94                 scope = e.getAttribute("scope");
95                 if (scope == null || scope.equals("")) {
96                         log.error("Attribute \"scope\" required to configure plugin.");
97                         throw new ResolutionPlugInException("Failed to initialize Attribute Definition PlugIn.");
98                 }
99
100                 // Salt can be either embedded in the element or pulled out of a keystore.
101                 NodeList salts = e.getElementsByTagNameNS(AttributeResolver.resolverNamespace, "Salt");
102                 if (salts == null || salts.getLength() != 1) {
103                         log.error("Missing <Salt> from attribute definition configuration.");
104                         throw new ResolutionPlugInException("Failed to initialize Attribute Definition PlugIn.");
105                 }
106
107                 Element salt = (Element) salts.item(0);
108                 Node child = salt.getFirstChild();
109                 if (child != null && child.getNodeType() == Node.TEXT_NODE && child.getNodeValue() != null
110                                 && child.getNodeValue().length() >= 16) this.salt = child.getNodeValue().getBytes();
111                 else {
112                         String ksPath = salt.getAttributeNS(null, "keyStorePath");
113                         String keyAlias = salt.getAttributeNS(null, "keyStoreKeyAlias");
114                         String ksPass = salt.getAttributeNS(null, "keyStorePassword");
115                         String keyPass = salt.getAttributeNS(null, "keyStoreKeyPassword");
116
117                         if (ksPath == null || ksPath.length() == 0 || keyAlias == null || keyAlias.length() == 0 || ksPass == null
118                                         || ksPass.length() == 0 || keyPass == null || keyPass.length() == 0) {
119
120                                 log.error("Missing <Salt> keyStore attributes from attribute definition configuration.");
121                                 throw new ResolutionPlugInException("Failed to initialize Attribute Definition PlugIn.");
122                         }
123
124                         try {
125                                 KeyStore keyStore = KeyStore.getInstance("JCEKS");
126
127                                 keyStore.load(new ShibResource(ksPath, this.getClass()).getInputStream(), ksPass.toCharArray());
128                                 SecretKey secret = (SecretKey) keyStore.getKey(keyAlias, keyPass.toCharArray());
129
130                                 if (usingDefaultSecret()) {
131                                         log
132                                                         .warn("You are running the PersistentIDAttributeDefinition PlugIn with the default secret key as a salt.  This is UNSAFE!  Please change "
133                                                                         + "this configuration and restart the IdP.");
134                                 }
135                                 this.salt = secret.getEncoded();
136
137                         } catch (KeyStoreException ex) {
138                                 log
139                                                 .error("An error occurred while loading the java keystore.  Unable to initialize Attribute Definition PlugIn: "
140                                                                 + ex);
141                                 throw new ResolutionPlugInException(
142                                                 "An error occurred while loading the java keystore.  Unable to initialize Attribute Definition PlugIn.");
143                         } catch (CertificateException ex) {
144                                 log
145                                                 .error("The java keystore contained corrupted data.  Unable to initialize Attribute Definition PlugIn: "
146                                                                 + ex);
147                                 throw new ResolutionPlugInException(
148                                                 "The java keystore contained corrupted data.  Unable to initialize Attribute Definition PlugIn.");
149                         } catch (NoSuchAlgorithmException ex) {
150                                 log
151                                                 .error("Appropriate JCE provider not found in the java environment. Unable to initialize Attribute Definition PlugIn: "
152                                                                 + ex);
153                                 throw new ResolutionPlugInException(
154                                                 "Appropriate JCE provider not found in the java environment. Unable to initialize Attribute Definition PlugIn.");
155                         } catch (IOException ex) {
156                                 log
157                                                 .error("An error accessing while loading the java keystore.  Unable to initialize Attribute Definition PlugIn: "
158                                                                 + ex);
159                                 throw new ResolutionPlugInException(
160                                                 "An error occurred while accessing the java keystore.  Unable to initialize Attribute Definition PlugIn.");
161                         } catch (UnrecoverableKeyException ex) {
162                                 log
163                                                 .error("Secret could not be loaded from the java keystore.  Verify that the alias and password are correct: "
164                                                                 + ex);
165                                 throw new ResolutionPlugInException(
166                                                 "Secret could not be loaded from the java keystore.  Verify that the alias and password are correct. ");
167                         }
168                 }
169         }
170
171         /**
172          * @see edu.internet2.middleware.shibboleth.aa.attrresolv.AttributeDefinitionPlugIn#resolve(edu.internet2.middleware.shibboleth.aa.attrresolv.ArpAttribute,
173          *      java.security.Principal, java.lang.String, edu.internet2.middleware.shibboleth.aa.attrresolv.Dependencies)
174          */
175         public void resolve(ResolverAttribute attribute, Principal principal, String requester, String responder,
176                         Dependencies depends) throws ResolutionPlugInException {
177
178                 log.debug("Resolving attribute: (" + getId() + ")");
179
180                 if (requester == null || requester.equals("")) {
181                         log.debug("Could not create ID for unauthenticated requester.");
182                         attribute.setResolved();
183                         return;
184                 }
185
186                 String localId = null;
187
188                 // Resolve the correct local persistent identifier.
189                 if (!attributeDependencyIds.isEmpty()) {
190                         ResolverAttribute dep = depends.getAttributeResolution((String) attributeDependencyIds.iterator().next());
191                         if (dep != null) {
192                                 Iterator vals = dep.getValues();
193                                 if (vals.hasNext()) {
194                                         log.debug("Found persistent ID value for attribute (" + getId() + ").");
195                                         localId = (String) vals.next();
196                                         if (vals.hasNext()) {
197                                                 log.error("An attribute dependency of attribute (" + getId()
198                                                                 + ") returned multiple values, expecting only one.");
199                                                 return;
200                                         }
201                                 } else {
202                                         log.error("An attribute dependency of attribute (" + getId()
203                                                         + ") returned no values, expecting one.");
204                                         return;
205                                 }
206                         } else {
207                                 log.error("An attribute dependency of attribute (" + getId()
208                                                 + ") was not included in the dependency chain.");
209                                 return;
210                         }
211                 } else if (!connectorDependencyIds.isEmpty()) {
212                         Attributes attrs = depends.getConnectorResolution((String) connectorDependencyIds.iterator().next());
213                         if (attrs != null) {
214                                 Attribute attr = attrs.get(localPersistentId);
215                                 if (attr != null) {
216                                         if (attr.size() != 1) {
217                                                 log.error("An attribute dependency of attribute (" + getId() + ") returned " + attr.size()
218                                                                 + " values, expecting only one.");
219                                         } else {
220                                                 try {
221                                                         localId = (String) attr.get();
222                                                         log.debug("Found persistent ID value for attribute (" + getId() + ").");
223                                                 } catch (NamingException e) {
224                                                         log.error("A connector dependency of attribute (" + getId() + ") threw an exception: " + e);
225                                                         return;
226                                                 }
227                                         }
228                                 }
229                         } else {
230                                 log.error("A connector dependency of attribute (" + getId() + ") did not return any attributes.");
231                                 return;
232                         }
233                 } else {
234                         localId = principal.getName();
235                 }
236
237                 if (localId == null || localId.equals("")) {
238                         log.error("Specified source data not supplied from dependencies.  Unable to create ID.");
239                         attribute.setResolved();
240                         return;
241                 }
242
243                 standardProcessing(attribute);
244
245                 // Hash the data together to produce the persistent ID.
246                 try {
247                         MessageDigest md = MessageDigest.getInstance("SHA");
248                         md.update(requester.getBytes());
249                         md.update((byte) '!');
250                         md.update(localId.getBytes());
251                         md.update((byte) '!');
252                         String result = new String(Base64.encode(md.digest(salt)));
253
254                         attribute.registerValueHandler(new ScopedStringValueHandler(scope));
255                         attribute.addValue(result.replaceAll(System.getProperty("line.separator"), ""));
256                         attribute.setResolved();
257                 } catch (NoSuchAlgorithmException e) {
258                         log.error("Unable to load SHA-1 hash algorithm.");
259                 }
260         }
261
262         private boolean usingDefaultSecret() {
263
264                 byte[] defaultKey = new byte[]{(byte) 0xC7, (byte) 0x49, (byte) 0x80, (byte) 0xD3, (byte) 0x02, (byte) 0x4A,
265                                 (byte) 0x61, (byte) 0xEF, (byte) 0x25, (byte) 0x5D, (byte) 0xE3, (byte) 0x2F, (byte) 0x57, (byte) 0x51,
266                                 (byte) 0x20, (byte) 0x15, (byte) 0xC7, (byte) 0x49, (byte) 0x80, (byte) 0xD3, (byte) 0x02, (byte) 0x4A,
267                                 (byte) 0x61, (byte) 0xEF};
268                 return Arrays.equals(defaultKey, salt);
269         }
270 }