Implemented new persistent id attribute format in accordance with "SAML eduPerson...
authorwassa <wassa@ab3bd59b-922f-494d-bb5f-6f0a3c29deca>
Thu, 19 May 2005 22:46:55 +0000 (22:46 +0000)
committerwassa <wassa@ab3bd59b-922f-494d-bb5f-6f0a3c29deca>
Thu, 19 May 2005 22:46:55 +0000 (22:46 +0000)
Working Draft 02, 25 April 2005".

git-svn-id: https://subversion.switch.ch/svn/shibboleth/java-idp/trunk@1551 ab3bd59b-922f-494d-bb5f-6f0a3c29deca

src/conf/resolver.ldap.xml
src/conf/resolver.xml
src/edu/internet2/middleware/shibboleth/aa/attrresolv/ResolutionPlugInFactory.java
src/edu/internet2/middleware/shibboleth/aa/attrresolv/provider/SAML2PersistentID.java [new file with mode: 0644]
src/schemas/shibboleth-resolver-1.0.xsd

index 8f578bd..a11173a 100644 (file)
        -->
        
        
-       <!-- Example persistent id attribute.  You should change the scope value to match your site's domain 
-                name.  Since this configuration is permanent, some thought is required before deploying in 
-                production. -->
+       <!-- Example persistent id attribute.  Since this configuration is permanent, some thought is required before 
+               deploying in  production. -->    
        <!--
-       <PersistentIDAttributeDefinition id="urn:mace:dir:attribute-def:eduPersonTargetedID" scope="shibdev.edu" sourceName="guid">
-               <DataConnectorDependency requires="directory"/>
+       <SAML2PersistentID id="urn:oid:1.3.6.1.4.1.5923.1.1.1.10" sourceName="guid">
+               <DataConnectorDependency requires="echo"/>
                <Salt keyStorePath="/conf/persistent.jks" keyStoreKeyAlias="handleKey" keyStorePassword="shibhs" keyStoreKeyPassword="shibhs"/>
-       </PersistentIDAttributeDefinition>
+       </SAML2PersistentID>
        -->
        
        
index d0316db..b5babb3 100644 (file)
        -->
        
        
-       <!-- Example persistent id attribute.  You should change the scope value to match your site's domain 
-                name.  Since this configuration is permanent, some thought is required before deploying in 
-                production. -->
+       <!-- Example persistent id attribute.  Since this configuration is permanent, some thought is required before 
+               deploying in  production. -->    
+       <!--
+       <SAML2PersistentID id="urn:oid:1.3.6.1.4.1.5923.1.1.1.10" sourceName="guid">
+               <DataConnectorDependency requires="echo"/>
+               <Salt keyStorePath="/conf/persistent.jks" keyStoreKeyAlias="handleKey" keyStorePassword="shibhs" keyStoreKeyPassword="shibhs"/>
+       </SAML2PersistentID>
+       -->
+       <!--  Deprecated persistent id examle -->
        <!--
        <PersistentIDAttributeDefinition id="urn:mace:dir:attribute-def:eduPersonTargetedID" scope="shibdev.edu" sourceName="guid">
                <DataConnectorDependency requires="echo"/>
index 271bade..31233cc 100644 (file)
@@ -37,6 +37,7 @@ import edu.internet2.middleware.shibboleth.aa.attrresolv.provider.JNDIDirectoryD
 import edu.internet2.middleware.shibboleth.aa.attrresolv.provider.MappedAttributeDefinition;
 import edu.internet2.middleware.shibboleth.aa.attrresolv.provider.PersistentIDAttributeDefinition;
 import edu.internet2.middleware.shibboleth.aa.attrresolv.provider.RegExAttributeDefinition;
+import edu.internet2.middleware.shibboleth.aa.attrresolv.provider.SAML2PersistentID;
 import edu.internet2.middleware.shibboleth.aa.attrresolv.provider.SimpleAttributeDefinition;
 
 /**
@@ -56,6 +57,8 @@ public class ResolutionPlugInFactory {
                if (e.getTagName().equals("CustomAttributeDefinition")) { return new CustomAttributeDefinition(e); }
 
                if (e.getTagName().equals("SimpleAttributeDefinition")) { return new SimpleAttributeDefinition(e); }
+               
+               if (e.getTagName().equals("SAML2PersistentID")) { return new SAML2PersistentID(e); }
 
                if (e.getTagName().equals("PersistentIDAttributeDefinition")) { return new PersistentIDAttributeDefinition(e); }
 
diff --git a/src/edu/internet2/middleware/shibboleth/aa/attrresolv/provider/SAML2PersistentID.java b/src/edu/internet2/middleware/shibboleth/aa/attrresolv/provider/SAML2PersistentID.java
new file mode 100644 (file)
index 0000000..0f65d20
--- /dev/null
@@ -0,0 +1,278 @@
+/*
+ * The Shibboleth License, Version 1. Copyright (c) 2002 University Corporation for Advanced Internet Development, Inc.
+ * All rights reserved Redistribution and use in source and binary forms, with or without modification, are permitted
+ * provided that the following conditions are met: Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer. Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials
+ * provided with the distribution, if any, must include the following acknowledgment: "This product includes software
+ * developed by the University Corporation for Advanced Internet Development <http://www.ucaid.edu>Internet2 Project.
+ * Alternately, this acknowledegement may appear in the software itself, if and wherever such third-party
+ * acknowledgments normally appear. Neither the name of Shibboleth nor the names of its contributors, nor Internet2, nor
+ * the University Corporation for Advanced Internet Development, Inc., nor UCAID may be used to endorse or promote
+ * products derived from this software without specific prior written permission. For written permission, please contact
+ * shibboleth@shibboleth.org Products derived from this software may not be called Shibboleth, Internet2, UCAID, or the
+ * University Corporation for Advanced Internet Development, nor may Shibboleth appear in their name, without prior
+ * written permission of the University Corporation for Advanced Internet Development. THIS SOFTWARE IS PROVIDED BY THE
+ * COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND WITH ALL FAULTS. ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT ARE
+ * DISCLAIMED AND THE ENTIRE RISK OF SATISFACTORY QUALITY, PERFORMANCE, ACCURACY, AND EFFORT IS WITH LICENSEE. IN NO
+ * EVENT SHALL THE COPYRIGHT OWNER, CONTRIBUTORS OR THE UNIVERSITY CORPORATION FOR ADVANCED INTERNET DEVELOPMENT, INC.
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package edu.internet2.middleware.shibboleth.aa.attrresolv.provider;
+
+import java.io.IOException;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.Principal;
+import java.security.UnrecoverableKeyException;
+import java.security.cert.CertificateException;
+import java.util.Arrays;
+import java.util.Iterator;
+
+import javax.crypto.SecretKey;
+import javax.naming.NamingException;
+import javax.naming.directory.Attribute;
+import javax.naming.directory.Attributes;
+
+import org.apache.log4j.Logger;
+import org.bouncycastle.util.encoders.Base64;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import edu.internet2.middleware.shibboleth.aa.attrresolv.AttributeDefinitionPlugIn;
+import edu.internet2.middleware.shibboleth.aa.attrresolv.AttributeResolver;
+import edu.internet2.middleware.shibboleth.aa.attrresolv.Dependencies;
+import edu.internet2.middleware.shibboleth.aa.attrresolv.ResolutionPlugInException;
+import edu.internet2.middleware.shibboleth.aa.attrresolv.ResolverAttribute;
+import edu.internet2.middleware.shibboleth.common.ShibResource;
+
+/**
+ * <code>AttributeDefinition</code> implementation that provides a persistent, but pseudonymous, identifier for
+ * principals by hashing the principal name, requester, and a fixed secret salt. Forward compatible with SAML2 persisten
+ * Namde Identifiers.
+ * 
+ * @author Scott Cantor (cantor.2@osu.edu)
+ * @author Walter Hoehn
+ */
+public class SAML2PersistentID extends BaseAttributeDefinition implements AttributeDefinitionPlugIn {
+
+       private static Logger log = Logger.getLogger(SAML2PersistentID.class.getName());
+       protected byte salt[];
+       protected String localPersistentId = null;
+
+       /**
+        * Constructor for SAML2PersistentID. Creates a PlugIn based on configuration information presented in a DOM
+        * Element.
+        */
+       public SAML2PersistentID(Element e) throws ResolutionPlugInException {
+
+               super(e);
+               localPersistentId = e.getAttributeNS(null, "sourceName");
+
+               // Make sure we understand how to resolve the local persistent ID for the principal.
+               if (localPersistentId != null && localPersistentId.length() > 0) {
+                       if (connectorDependencyIds.size() != 1 || !attributeDependencyIds.isEmpty()) {
+                               log.error("Can't specify the sourceName attribute without a single connector dependency.");
+                               throw new ResolutionPlugInException("Failed to initialize Attribute Definition PlugIn.");
+                       }
+               } else if (!connectorDependencyIds.isEmpty()) {
+                       log.error("Can't specify a connector dependency without supplying the sourceName attribute.");
+                       throw new ResolutionPlugInException("Failed to initialize Attribute Definition PlugIn.");
+               } else if (attributeDependencyIds.size() > 1) {
+                       log.error("Can't specify more than one attribute dependency, this is ambiguous.");
+                       throw new ResolutionPlugInException("Failed to initialize Attribute Definition PlugIn.");
+               }
+
+               // Salt can be either embedded in the element or pulled out of a keystore.
+               NodeList salts = e.getElementsByTagNameNS(AttributeResolver.resolverNamespace, "Salt");
+               if (salts == null || salts.getLength() != 1) {
+                       log.error("Missing <Salt> from attribute definition configuration.");
+                       throw new ResolutionPlugInException("Failed to initialize Attribute Definition PlugIn.");
+               }
+
+               Element salt = (Element) salts.item(0);
+               Node child = salt.getFirstChild();
+               if (child != null && child.getNodeType() == Node.TEXT_NODE && child.getNodeValue() != null
+                               && child.getNodeValue().length() >= 16) this.salt = child.getNodeValue().getBytes();
+               else {
+                       String ksPath = salt.getAttributeNS(null, "keyStorePath");
+                       String keyAlias = salt.getAttributeNS(null, "keyStoreKeyAlias");
+                       String ksPass = salt.getAttributeNS(null, "keyStorePassword");
+                       String keyPass = salt.getAttributeNS(null, "keyStoreKeyPassword");
+
+                       if (ksPath == null || ksPath.length() == 0 || keyAlias == null || keyAlias.length() == 0 || ksPass == null
+                                       || ksPass.length() == 0 || keyPass == null || keyPass.length() == 0) {
+
+                               log.error("Missing <Salt> keyStore attributes from attribute definition configuration.");
+                               throw new ResolutionPlugInException("Failed to initialize Attribute Definition PlugIn.");
+                       }
+
+                       try {
+                               KeyStore keyStore = KeyStore.getInstance("JCEKS");
+
+                               keyStore.load(new ShibResource(ksPath, this.getClass()).getInputStream(), ksPass.toCharArray());
+                               SecretKey secret = (SecretKey) keyStore.getKey(keyAlias, keyPass.toCharArray());
+
+                               if (usingDefaultSecret()) {
+                                       log.warn("You are running the SAML2PersistentID PlugIn with the default "
+                                                       + "secret key as a salt.  This is UNSAFE!  Please change "
+                                                       + "this configuration and restart the IdP.");
+                               }
+                               this.salt = secret.getEncoded();
+
+                       } catch (KeyStoreException ex) {
+                               log.error("An error occurred while loading the java keystore.  Unable to initialize "
+                                               + "Attribute Definition PlugIn: " + ex);
+                               throw new ResolutionPlugInException(
+                                               "An error occurred while loading the java keystore.  Unable to initialize Attribute Definition PlugIn.");
+                       } catch (CertificateException ex) {
+                               log.error("The java keystore contained corrupted data.  Unable to initialize "
+                                               + "Attribute Definition PlugIn: " + ex);
+                               throw new ResolutionPlugInException(
+                                               "The java keystore contained corrupted data.  Unable to initialize Attribute Definition PlugIn.");
+                       } catch (NoSuchAlgorithmException ex) {
+                               log.error("Appropriate JCE provider not found in the java environment. Unable to initialize "
+                                               + "Attribute Definition PlugIn: " + ex);
+                               throw new ResolutionPlugInException(
+                                               "Appropriate JCE provider not found in the java environment. Unable to initialize Attribute Definition PlugIn.");
+                       } catch (IOException ex) {
+                               log.error("An error accessing while loading the java keystore.  Unable to initialize "
+                                               + "Attribute Definition PlugIn: " + ex);
+                               throw new ResolutionPlugInException(
+                                               "An error occurred while accessing the java keystore.  Unable to initialize Attribute Definition PlugIn.");
+                       } catch (UnrecoverableKeyException ex) {
+                               log.error("Secret could not be loaded from the java keystore.  Verify that the alias and "
+                                               + "password are correct: " + ex);
+                               throw new ResolutionPlugInException(
+                                               "Secret could not be loaded from the java keystore.  Verify that the alias and password are correct. ");
+                       }
+               }
+       }
+
+       /**
+        * @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)
+        */
+       public void resolve(ResolverAttribute attribute, Principal principal, String requester, String responder,
+                       Dependencies depends) throws ResolutionPlugInException {
+
+               log.debug("Resolving attribute: (" + getId() + ")");
+
+               if (requester == null || requester.equals("")) {
+                       log.debug("Could not create persistent ID for unauthenticated requester.");
+                       attribute.setResolved();
+                       return;
+               }
+
+               if (responder == null || responder.equals("")) {
+                       log.error("Could not create persistent ID for null responder.");
+                       attribute.setResolved();
+                       return;
+               }
+
+               String localId = null;
+
+               // Resolve the correct local persistent identifier.
+               if (!attributeDependencyIds.isEmpty()) {
+                       ResolverAttribute dep = depends.getAttributeResolution((String) attributeDependencyIds.iterator().next());
+                       if (dep != null) {
+                               Iterator vals = dep.getValues();
+                               if (vals.hasNext()) {
+                                       log.debug("Found persistent ID value for attribute (" + getId() + ").");
+                                       localId = (String) vals.next();
+                                       if (vals.hasNext()) {
+                                               log.error("An attribute dependency of attribute (" + getId()
+                                                               + ") returned multiple values, expecting only one.");
+                                               return;
+                                       }
+                               } else {
+                                       log.error("An attribute dependency of attribute (" + getId()
+                                                       + ") returned no values, expecting one.");
+                                       return;
+                               }
+                       } else {
+                               log.error("An attribute dependency of attribute (" + getId()
+                                               + ") was not included in the dependency chain.");
+                               return;
+                       }
+               } else if (!connectorDependencyIds.isEmpty()) {
+                       Attributes attrs = depends.getConnectorResolution((String) connectorDependencyIds.iterator().next());
+                       if (attrs != null) {
+                               Attribute attr = attrs.get(localPersistentId);
+                               if (attr != null) {
+                                       if (attr.size() != 1) {
+                                               log.error("An attribute dependency of attribute (" + getId() + ") returned " + attr.size()
+                                                               + " values, expecting only one.");
+                                       } else {
+                                               try {
+                                                       localId = (String) attr.get();
+                                                       log.debug("Found persistent ID value for attribute (" + getId() + ").");
+                                               } catch (NamingException e) {
+                                                       log.error("A connector dependency of attribute (" + getId() + ") threw an exception: " + e);
+                                                       return;
+                                               }
+                                       }
+                               }
+                       } else {
+                               log.error("A connector dependency of attribute (" + getId() + ") did not return any attributes.");
+                               return;
+                       }
+               } else {
+                       localId = principal.getName();
+               }
+
+               if (localId == null || localId.equals("")) {
+                       log.error("Specified source data not supplied from dependencies.  Unable to create persistent ID.");
+                       attribute.setResolved();
+                       return;
+               }
+
+               if (lifeTime != -1) {
+                       attribute.setLifetime(lifeTime);
+               }
+
+               // Hash the data together to produce the persistent ID.
+               try {
+                       MessageDigest md = MessageDigest.getInstance("SHA");
+                       md.update(requester.getBytes());
+                       md.update((byte) '!');
+                       md.update(localId.getBytes());
+                       md.update((byte) '!');
+                       String result = new String(Base64.encode(md.digest(salt)));
+
+                       // :-/ String-ified SAML2 persistent NameId format
+                       StringBuffer id = new StringBuffer();
+                       id.append("<saml2:NameID Format=\"urn:oasis:names:tc:SAML:2.0:nameid-format:persistent\" NameQualifier=\"");
+                       id.append(responder);
+                       id.append("\" SPNameQualifier=\"");
+                       id.append(requester);
+                       id.append("\" >");
+                       id.append(result.replaceAll(System.getProperty("line.separator"), ""));
+                       id.append("</saml2:NameID>");
+
+                       attribute.addValue(id.toString());
+                       attribute.setResolved();
+
+               } catch (NoSuchAlgorithmException e) {
+                       log.error("Unable to load SHA-1 hash algorithm.");
+                       return;
+               }
+       }
+
+       private boolean usingDefaultSecret() {
+
+               byte[] defaultKey = new byte[]{(byte) 0xC7, (byte) 0x49, (byte) 0x80, (byte) 0xD3, (byte) 0x02, (byte) 0x4A,
+                               (byte) 0x61, (byte) 0xEF, (byte) 0x25, (byte) 0x5D, (byte) 0xE3, (byte) 0x2F, (byte) 0x57, (byte) 0x51,
+                               (byte) 0x20, (byte) 0x15, (byte) 0xC7, (byte) 0x49, (byte) 0x80, (byte) 0xD3, (byte) 0x02, (byte) 0x4A,
+                               (byte) 0x61, (byte) 0xEF};
+               return Arrays.equals(defaultKey, salt);
+       }
+}
index 743da0b..5e40fd3 100644 (file)
                                                </xs:complexContent>
                                        </xs:complexType>
                                </xs:element>
+                               <xs:element name="SAML2PersistentID" minOccurs="0" maxOccurs="unbounded">
+                                       <xs:complexType>
+                                               <xs:complexContent>
+                                                       <xs:extension base="resolver:BaseAttributeDefinition">
+                                                               <xs:sequence>
+                                                                       <xs:element name="Salt">
+                                                                               <xs:complexType>
+                                                                                       <xs:simpleContent>
+                                                                                               <xs:extension base="xs:string">
+                                                                                                       <xs:attribute name="keyStorePath" type="xs:string" use="optional"/>
+                                                                                                       <xs:attribute name="keyStoreKeyAlias" type="xs:string" 
+                                                                                                               use="optional"/>
+                                                                                                       <xs:attribute name="keyStorePassword" type="xs:string" 
+                                                                                                               use="optional"/>
+                                                                                                       <xs:attribute name="keyStoreKeyPassword" type="xs:string" 
+                                                                                                               use="optional"/>
+                                                                                               </xs:extension>
+                                                                                       </xs:simpleContent>
+                                                                               </xs:complexType>
+                                                                       </xs:element>
+                                                               </xs:sequence>
+                                                               <xs:attribute name="sourceName" type="xs:string" use="optional"/>
+                                                       </xs:extension>
+                                               </xs:complexContent>
+                                       </xs:complexType>
+                               </xs:element>
                                <xs:element name="PersistentIDAttributeDefinition" minOccurs="0" maxOccurs="unbounded">
                                        <xs:complexType>
                                                <xs:complexContent>