Initial import
authorcantor <cantor@ab3bd59b-922f-494d-bb5f-6f0a3c29deca>
Wed, 5 Jun 2002 19:16:53 +0000 (19:16 +0000)
committercantor <cantor@ab3bd59b-922f-494d-bb5f-6f0a3c29deca>
Wed, 5 Jun 2002 19:16:53 +0000 (19:16 +0000)
git-svn-id: https://subversion.switch.ch/svn/shibboleth/java-idp/trunk@31 ab3bd59b-922f-494d-bb5f-6f0a3c29deca

src/edu/internet2/middleware/eduPerson/Init.java [new file with mode: 0755]
src/edu/internet2/middleware/eduPerson/ScopedAttribute.java [new file with mode: 0755]
src/edu/internet2/middleware/eduPerson/XML.java [new file with mode: 0755]
src/edu/internet2/middleware/shibboleth/ClubShibPOSTProfile.java [new file with mode: 0755]
src/edu/internet2/middleware/shibboleth/Constants.java [new file with mode: 0755]
src/edu/internet2/middleware/shibboleth/Init.java [new file with mode: 0755]
src/edu/internet2/middleware/shibboleth/OriginSiteMapper.java [new file with mode: 0755]
src/edu/internet2/middleware/shibboleth/SAMLBindingFactory.java [new file with mode: 0755]
src/edu/internet2/middleware/shibboleth/ShibPOSTProfile.java [new file with mode: 0755]
src/edu/internet2/middleware/shibboleth/ShibPOSTProfileFactory.java [new file with mode: 0755]
src/edu/internet2/middleware/shibboleth/XML.java [new file with mode: 0755]

diff --git a/src/edu/internet2/middleware/eduPerson/Init.java b/src/edu/internet2/middleware/eduPerson/Init.java
new file mode 100755 (executable)
index 0000000..d52867f
--- /dev/null
@@ -0,0 +1,39 @@
+package edu.internet2.middleware.eduPerson;
+
+import javax.xml.parsers.ParserConfigurationException;
+
+/**
+ *  Handles one-time library initialization
+ *
+ * @author     Scott Cantor
+ * @created    May 18, 2002
+ */
+public class Init
+{
+    private static boolean initialized = false;
+
+    /**  Initializes library */
+    public static synchronized void init()
+    {
+        if (initialized)
+            return;
+
+        initialized = true;
+        
+        edu.internet2.middleware.shibboleth.Init.init();
+        try
+        {
+            org.opensaml.XML.parserPool.registerExtension(XML.EDUPERSON_NS, XML.EDUPERSON_SCHEMA_ID, new XML.SchemaResolver());
+        }
+        catch (ParserConfigurationException e)
+        {
+            throw new RuntimeException("Init.init() unable to register extension schema");
+        }
+    }
+
+    static
+    {
+        Init.init();
+    }
+}
+
diff --git a/src/edu/internet2/middleware/eduPerson/ScopedAttribute.java b/src/edu/internet2/middleware/eduPerson/ScopedAttribute.java
new file mode 100755 (executable)
index 0000000..c60f008
--- /dev/null
@@ -0,0 +1,155 @@
+package edu.internet2.middleware.eduPerson;
+
+import java.util.Iterator;
+import java.util.Vector;
+import org.opensaml.*;
+import org.w3c.dom.*;
+
+/**
+ *  Basic implementation of a scoped, eduPerson SAML attribute
+ *
+ * @author     Scott Cantor
+ * @created    May 9, 2002
+ */
+public class ScopedAttribute extends SAMLAttribute
+{
+    /**  Default attribute scope */
+    protected String defaultScope = null;
+
+    /**  Scopes of the attribute values */
+    protected Vector scopes = new Vector();
+
+    /**
+     *  Constructor for the ScopedAttribute object
+     *
+     * @param  name               Name of attribute
+     * @param  namespace          Namespace/qualifier of attribute
+     * @param  type               The schema type of attribute value(s)
+     * @param  lifetime           Effective lifetime of attribute's value(s) in
+     *      seconds (0 means infinite)
+     * @param  values             An array of attribute values
+     * @param  defaultScope       The default scope to apply for values
+     * @param  scopes             Scopes of the attribute values
+     * @exception  SAMLException  Thrown if attribute cannot be built from the
+     *      supplied information
+     */
+    public ScopedAttribute(String name, String namespace, QName type, long lifetime, Object[] values,
+                           String defaultScope, String[] scopes)
+        throws SAMLException
+    {
+        super(name, namespace, type, lifetime, values);
+        this.defaultScope = defaultScope;
+
+        for (int i = 0; scopes != null && i < scopes.length; i++)
+            this.scopes.add(scopes[i]);
+    }
+
+    /**
+     *  Reconstructs and validates an attribute from a DOM tree<P>
+     *
+     *  Overrides the basic implementation to handle the same simple types, but
+     *  also picks up scope.
+     *
+     * @param  e                  A DOM Attribute element
+     * @exception  SAMLException  Thrown if the attribute cannot be constructed
+     */
+    public ScopedAttribute(Element e)
+        throws SAMLException
+    {
+        super(e);
+
+        // Default scope comes from subject.
+        NodeList nlist = ((Element)e.getParentNode()).getElementsByTagNameNS(org.opensaml.XML.SAML_NS, "NameIdentifier");
+        if (nlist.getLength() != 1)
+            throw new InvalidAssertionException(SAMLException.RESPONDER, "ScopedAttribute() can't find saml:NameIdentifier in enclosing statement");
+        defaultScope = ((Element)nlist.item(0)).getAttributeNS(null, "NameQualifier");
+    }
+
+    /**
+     *  Gets the values of the SAML Attribute, serialized as strings with the
+     *  effective scope appended
+     *
+     * @return    The array of values
+     */
+    public Object[] getValues()
+    {
+        if (values == null)
+            return null;
+
+        Object[] bufs = new Object[values.size()];
+        for (int i = 0; i < values.size(); i++)
+        {
+            if (values.get(i) != null)
+            {
+                if (scopes != null && i < scopes.size() && scopes.get(i) != null)
+                    bufs[i] = values.get(i).toString() + "@" + scopes.get(i);
+                else
+                    bufs[i] = values.get(i).toString() + "@" + defaultScope;
+            }
+        }
+        return bufs;
+    }
+
+    /**
+     *  Attribute acceptance hook used while consuming attributes from an
+     *  assertion. Base class simply accepts anything. Override for desired
+     *  behavior.
+     *
+     * @param  e  An AttributeValue element to check
+     * @return    true iff the value is deemed acceptable
+     */
+    public boolean accept(Element e)
+    {
+        return true;
+    }
+
+    /**
+     *  Adds a value to the state of the SAML Attribute<P>
+     *
+     *  This class supports a simple text node content model with a Scope
+     *  attribute
+     *
+     * @param  e  The AttributeValue element containing the value to add
+     * @return    true iff the value was understood
+     */
+    public boolean addValue(Element e)
+    {
+        if (super.addValue(e))
+        {
+            scopes.add(e.getAttributeNS(null,"Scope"));
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     *  Overridden method to return a DOM tree representing the attribute<P>
+     *
+     *  Because attributes are generalized, this base method only handles simple
+     *  attributes whose values are of uniform simple type and expressed in the
+     *  DOM as a single text node within the AttributeValue element(s). The
+     *  values are serialized using the toString() method.<P>
+     *
+     *  SAML applications should override this class and reimplement or
+     *  supplement this method to handle other requirements.
+     *
+     * @param  doc  A Document object to use in manufacturing the tree
+     * @return      Root "Attribute" element of a DOM tree
+     */
+    public Node toDOM(Document doc)
+    {
+        super.toDOM(doc);
+
+        NodeList nlist = ((Element)root).getElementsByTagNameNS(org.opensaml.XML.SAML_NS, "AttributeValue");
+        for (int i = 0; i < nlist.getLength(); i++)
+        {
+            ((Element)nlist.item(i)).removeAttributeNS(null, "Scope");
+            String scope=scopes.get(i).toString();
+            if (scope != null && !scope.equals(defaultScope))
+                ((Element)nlist.item(i)).setAttributeNS(null, "Scope", scope);
+        }
+
+        return root;
+    }
+}
+
diff --git a/src/edu/internet2/middleware/eduPerson/XML.java b/src/edu/internet2/middleware/eduPerson/XML.java
new file mode 100755 (executable)
index 0000000..d1da5e7
--- /dev/null
@@ -0,0 +1,77 @@
+package edu.internet2.middleware.eduPerson;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import org.xml.sax.EntityResolver;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+
+/**
+ *  Utility class for XML constants and schema handling
+ *
+ * @author     Scott Cantor
+ * @created    May 18, 2002
+ */
+public class XML
+{
+    /**  eduPerson XML namespace */
+    public final static String EDUPERSON_NS = "urn:mace:eduPerson:1.0";
+
+    /**  eduPerson XML schema identifier */
+    public final static String EDUPERSON_SCHEMA_ID = "eduPerson.xsd";
+
+    private static byte[] eduPerson_schema;
+
+    /**
+     *  Custom schema resolver class
+     *
+     * @author     Scott Cantor
+     * @created    May 18, 2002
+     */
+    protected static class SchemaResolver implements EntityResolver
+    {
+        /**
+         *  A customized entity resolver for the Shibboleth extension schema
+         *
+         * @param  publicId                 The public identifier of the entity
+         * @param  systemId                 The system identifier of the entity
+         * @return                          A source of bytes for the entity or
+         *      null
+         * @exception  SAXException         Raised if an XML parsing problem
+         *      occurs
+         * @exception  java.io.IOException  Raised if an I/O problem is detected
+         */
+        public InputSource resolveEntity(String publicId, String systemId)
+            throws SAXException, java.io.IOException
+        {
+            InputSource src = null;
+            if (systemId.endsWith('/' + EDUPERSON_SCHEMA_ID) && eduPerson_schema != null)
+                src = new InputSource(new ByteArrayInputStream(eduPerson_schema));
+            return src;
+        }
+    }
+
+    static
+    {
+        try
+        {
+            StringBuffer buf = new StringBuffer(1024);
+            InputStream xmlin = XML.class.getResourceAsStream("/schemas/" + EDUPERSON_SCHEMA_ID);
+            if (xmlin == null)
+                throw new RuntimeException("XML static initializer unable to locate eduPerson schema");
+            else
+            {
+                int b;
+                while ((b = xmlin.read()) != -1)
+                    buf.append((char)b);
+                eduPerson_schema = buf.toString().getBytes();
+                xmlin.close();
+            }
+        }
+        catch (java.io.IOException e)
+        {
+            throw new RuntimeException("XML static initializer caught an I/O error");
+        }
+    }
+}
+
diff --git a/src/edu/internet2/middleware/shibboleth/ClubShibPOSTProfile.java b/src/edu/internet2/middleware/shibboleth/ClubShibPOSTProfile.java
new file mode 100755 (executable)
index 0000000..f544550
--- /dev/null
@@ -0,0 +1,144 @@
+package edu.internet2.middleware.shibboleth;
+
+import java.util.Date;
+import java.security.Key;
+import java.security.KeyStore;
+import java.security.cert.X509Certificate;
+import java.security.interfaces.RSAPrivateKey;
+import org.apache.xml.security.signature.SignedInfo;
+import org.apache.xml.security.signature.XMLSignature;
+import org.opensaml.*;
+import org.w3c.dom.*;
+
+/**
+ *  ClubShib-specific POST browser profile implementation
+ *
+ * @author     Scott Cantor
+ * @created    April 11, 2002
+ */
+public class ClubShibPOSTProfile extends ShibPOSTProfile
+{
+    /**
+     *  SHIRE-side constructor for a ClubShibPOSTProfile object
+     *
+     * @param  policies           Array of policy URIs that the implementation
+     *      must support
+     * @param  mapper             Interface between profile and trust base
+     * @param  receiver           URL of SHIRE
+     * @param  ttlSeconds         Length of time in seconds allowed to elapse
+     *      from issuance of SAML response
+     * @exception  SAMLException  Raised if a profile implementation cannot be
+     *      constructed from the supplied information
+     */
+    public ClubShibPOSTProfile(String[] policies, OriginSiteMapper mapper, String receiver, int ttlSeconds)
+        throws SAMLException
+    {
+        super(policies, mapper, receiver, ttlSeconds);
+        int i;
+        for (i = 0; i < policies.length; i++)
+            if (policies[i].equals(Constants.POLICY_CLUBSHIB))
+                break;
+        if (i == policies.length)
+            throw new SAMLException(SAMLException.REQUESTER, "ClubShibPOSTProfile() policy array must include Club Shib");
+    }
+
+    /**
+     *  HS-side constructor for a ClubShibPOSTProfile object
+     *
+     * @param  policies           Array of policy URIs that the implementation
+     *      must support
+     * @param  issuer             "Official" name of issuing origin site
+     * @exception  SAMLException  Raised if a profile implementation cannot be
+     *      constructed from the supplied information
+     */
+    public ClubShibPOSTProfile(String[] policies, String issuer)
+        throws SAMLException
+    {
+        super(policies, issuer);
+        int i;
+        for (i = 0; i < policies.length; i++)
+            if (policies[i].equals(Constants.POLICY_CLUBSHIB))
+                break;
+        if (i == policies.length)
+            throw new SAMLException(SAMLException.RESPONDER, "ClubShibPOSTProfile() policy array must include Club Shib");
+    }
+
+    /**
+     *  Used by HS to generate a signed SAML response conforming to the POST
+     *  profile<P>
+     *
+     *  Club Shib specifies use of the RSA algorithm with RSA public keys and
+     *  X.509 certificates.
+     *
+     * @param  recipient          URL of intended consumer
+     * @param  name               Name of subject
+     * @param  nameQualifier      Federates or qualifies subject name (optional)
+     * @param  subjectIP          Client address of subject (optional)
+     * @param  authMethod         URI of authentication method being asserted
+     * @param  authInstant        Date and time of authentication being asserted
+     * @param  bindings           Array of SAML authorities the relying party
+     *      may contact (optional)
+     * @param  responseKey        A secret or private key to use in response
+     *      signature or MAC
+     * @param  responseCert       A public key certificate to enclose with the
+     *      response (optional)
+     * @param  assertionKey       A secret or private key to use in assertion
+     *      signature or MAC (optional)
+     * @param  assertionCert      A public key certificate to enclose with the
+     *      assertion (optional)
+     * @return                    SAML response to send to accepting site
+     * @exception  SAMLException  Base class of exceptions that may be thrown
+     *      during processing
+     */
+    public SAMLResponse prepare(String recipient,
+                                String name,
+                                String nameQualifier,
+                                String subjectIP,
+                                String authMethod,
+                                Date authInstant,
+                                SAMLAuthorityBinding[] bindings,
+                                Key responseKey, X509Certificate responseCert,
+                                Key assertionKey, X509Certificate assertionCert
+                                )
+        throws SAMLException
+    {
+        if (responseKey == null || !(responseKey instanceof RSAPrivateKey))
+            throw new InvalidCryptoException(SAMLException.RESPONDER, "ClubShibPOSTProfile.prepare() requires the response key be an RSA private key");
+        if (assertionKey != null && !(assertionKey instanceof RSAPrivateKey))
+            throw new InvalidCryptoException(SAMLException.RESPONDER, "ClubShibPOSTProfile.prepare() requires the assertion key be an RSA private key");
+
+        return super.prepare(
+            recipient,
+            name,
+            nameQualifier,
+            subjectIP,
+            authMethod,
+            authInstant,
+            bindings,
+            responseKey,
+            responseCert,
+            assertionKey,
+            assertionCert);
+    }
+
+    /**
+     *  Club Shib signature verification implements additional checks for the
+     *  RSA and SHA-1 algorithms.
+     *
+     * @param  obj         The object containing the signature
+     * @param  signerName  The name of the signer
+     * @param  ks          A keystore containing trusted root certificates
+     * @param  knownKey    An explicit key to use if a certificate cannot be
+     *      found
+     * @return             The result of signature verification
+     */
+    protected boolean verifySignature(SAMLSignedObject obj, String signerName, KeyStore ks, Key knownKey)
+    {
+        if (!super.verifySignature(obj, signerName, ks, knownKey))
+            return false;
+        return XMLSignature.ALGO_ID_SIGNATURE_RSA_SHA1.equals(
+            obj.getSignature().getSignedInfo().getSignatureMethodURI()
+            );
+    }
+}
+
diff --git a/src/edu/internet2/middleware/shibboleth/Constants.java b/src/edu/internet2/middleware/shibboleth/Constants.java
new file mode 100755 (executable)
index 0000000..79a7cea
--- /dev/null
@@ -0,0 +1,17 @@
+package edu.internet2.middleware.shibboleth;
+
+/**
+ *  Collection of Shibboleth constants
+ *
+ * @author     Scott Cantor
+ * @created    November 25, 2001
+ */
+public final class Constants
+{
+    /**  Club Shibboleth policy */
+    public final static String POLICY_CLUBSHIB = "http://middleware.internet2.edu/shibboleth/clubs/clubshib/2002/05/";
+
+    /**  Shibboleth AttributeNamespace indicating URI-based naming of Attributes */
+    public final static String SHIB_ATTRIBUTE_NAMESPACE_URI = "urn:mace:shibboleth:1.0:attributeNamespace:uri";
+}
+
diff --git a/src/edu/internet2/middleware/shibboleth/Init.java b/src/edu/internet2/middleware/shibboleth/Init.java
new file mode 100755 (executable)
index 0000000..f597a52
--- /dev/null
@@ -0,0 +1,39 @@
+package edu.internet2.middleware.shibboleth;
+
+import javax.xml.parsers.ParserConfigurationException;
+
+/**
+ *  Handles one-time library initialization
+ *
+ * @author     Scott Cantor
+ * @created    May 18, 2002
+ */
+public class Init
+{
+    private static boolean initialized = false;
+
+    /**  Initializes library */
+    public static synchronized void init()
+    {
+        if (initialized)
+            return;
+
+        initialized = true;
+        
+        org.opensaml.Init.init();
+        try
+        {
+            org.opensaml.XML.parserPool.registerExtension(XML.SHIB_NS, XML.SHIB_SCHEMA_ID, new XML.SchemaResolver());
+        }
+        catch (ParserConfigurationException e)
+        {
+            throw new RuntimeException("Init.init() unable to register extension schema");
+        }
+    }
+
+    static
+    {
+        Init.init();
+    }
+}
+
diff --git a/src/edu/internet2/middleware/shibboleth/OriginSiteMapper.java b/src/edu/internet2/middleware/shibboleth/OriginSiteMapper.java
new file mode 100755 (executable)
index 0000000..e557884
--- /dev/null
@@ -0,0 +1,60 @@
+package edu.internet2.middleware.shibboleth;
+
+import java.security.Key;
+import java.security.KeyStore;
+import java.util.Iterator;
+
+/**
+ *  Used by a Shibboleth SHIRE implementation to validate origin site
+ *  information and locate signature verification keys when validating responses
+ *  and assertions from a Handle Service<P>
+ *
+ *  The interface MUST be thread-safe.
+ *
+ * @author     Scott Cantor
+ * @created    January 24, 2002
+ */
+public interface OriginSiteMapper
+{
+    /**
+     *  Provides an iterator over the trusted Handle Services for the specified
+     *  origin site
+     *
+     * @param  originSite  The DNS name of the origin site to query
+     * @return             An iterator over the Handle Service DNS names
+     */
+    public abstract Iterator getHandleServiceNames(String originSite);
+
+    /**
+     *  Returns a preconfigured key to use in verifying a signature created by
+     *  the specified HS<P>
+     *
+     *  Any key returned is implicitly trusted and a certificate signed by
+     *  another trusted entity is not sought or required
+     *
+     * @param  handleService  Description of Parameter
+     * @return                A trusted key (probably public but could be
+     *      secret) or null
+     */
+    public abstract Key getHandleServiceKey(String handleService);
+
+    /**
+     *  Provides an iterator over the security domain expressions for which the
+     *  specified origin site is considered to be authoritative
+     *
+     * @param  originSite  The DNS name of the origin site to query
+     * @return             An iterator over a set of regular expression strings
+     */
+    public abstract Iterator getSecurityDomains(String originSite);
+
+    /**
+     *  Gets a key store containing certificate entries that are trusted to sign
+     *  Handle Service certificates that are encountered during processing<P>
+     *
+     *
+     *
+     * @return    A key store containing trusted certificate issuers
+     */
+    public abstract KeyStore getTrustedRoots();
+}
+
diff --git a/src/edu/internet2/middleware/shibboleth/SAMLBindingFactory.java b/src/edu/internet2/middleware/shibboleth/SAMLBindingFactory.java
new file mode 100755 (executable)
index 0000000..7ea919f
--- /dev/null
@@ -0,0 +1,34 @@
+package edu.internet2.middleware.shibboleth;
+
+import org.opensaml.SAMLBinding;
+import org.opensaml.SAMLSOAPBinding;
+
+/**
+ *  Used by Shibboleth SHAR/AA to locate a SAML binding implementation
+ *
+ * @author     Scott Cantor
+ * @created    April 10, 2002
+ */
+public class SAMLBindingFactory
+{
+    /**
+     *  Gets a compatible binding implementation for the specified protocol and
+     *  policies
+     *
+     * @param  protocol                          URI of SAML binding protocol
+     * @param  policies                          Array of policy URIs that the
+     *      implementation must support
+     * @return                                   A compatible binding
+     *      implementation or null if one cannot be found
+     */
+    public static SAMLBinding getInstance(String protocol, String[] policies)
+    {
+        // Current version only knows about SOAP binding and Club Shib...
+        if (protocol == null || !protocol.equals(SAMLBinding.SAML_SOAP_HTTPS))
+            return null;
+        if (policies==null || policies.length!=1 || !policies[0].equals(Constants.POLICY_CLUBSHIB))
+            return null;
+        return new SAMLSOAPBinding();
+    }
+}
+
diff --git a/src/edu/internet2/middleware/shibboleth/ShibPOSTProfile.java b/src/edu/internet2/middleware/shibboleth/ShibPOSTProfile.java
new file mode 100755 (executable)
index 0000000..747682d
--- /dev/null
@@ -0,0 +1,379 @@
+package edu.internet2.middleware.shibboleth;
+import java.security.GeneralSecurityException;
+import java.security.Key;
+import java.security.KeyStore;
+import java.security.PrivateKey;
+import java.security.cert.Certificate;
+import java.security.cert.X509Certificate;
+
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.Iterator;
+import javax.crypto.SecretKey;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.ParserConfigurationException;
+import org.apache.xml.security.exceptions.XMLSecurityException;
+import org.apache.xml.security.keys.KeyInfo;
+import org.apache.xml.security.signature.XMLSignature;
+import org.opensaml.*;
+import org.w3c.dom.*;
+
+/**
+ *  Basic Shibboleth POST browser profile implementation with basic support for
+ *  signing
+ *
+ * @author     Scott Cantor
+ * @created    April 11, 2002
+ */
+public class ShibPOSTProfile
+{
+    /**  XML Signature algorithm to apply */
+    protected String algorithm = XMLSignature.ALGO_ID_SIGNATURE_RSA_SHA1;
+
+    /**  Policy URIs to attach or check against */
+    protected String[] policies = null;
+
+    /**  Official name of issuing site */
+    protected String issuer = null;
+
+    /**  Abstract interface into trust base */
+    protected OriginSiteMapper mapper = null;
+
+    /**  The URL of the receiving SHIRE */
+    protected String receiver = null;
+
+    /**  Seconds allowed to elapse from issuance of response */
+    protected int ttlSeconds = 0;
+
+    /**
+     *  SHIRE-side constructor for a ShibPOSTProfile object
+     *
+     * @param  policies           Array of policy URIs that the implementation
+     *      must support
+     * @param  mapper             Interface between profile and trust base
+     * @param  receiver           URL of SHIRE
+     * @param  ttlSeconds         Length of time in seconds allowed to elapse
+     *      from issuance of SAML response
+     * @exception  SAMLException  Raised if a profile implementation cannot be
+     *      constructed from the supplied information
+     */
+    public ShibPOSTProfile(String[] policies, OriginSiteMapper mapper, String receiver, int ttlSeconds)
+        throws SAMLException
+    {
+        if (policies == null || policies.length == 0 || mapper == null ||
+            receiver == null || receiver.length() == 0 || ttlSeconds <= 0)
+            throw new SAMLException(SAMLException.REQUESTER, "ShibPOSTProfile() found a null or invalid argument");
+
+        this.mapper = mapper;
+        this.receiver = receiver;
+        this.ttlSeconds = ttlSeconds;
+        this.policies = new String[policies.length];
+        System.arraycopy(policies, 0, this.policies, 0, policies.length);
+    }
+
+    /**
+     *  HS-side constructor for a ShibPOSTProfile object
+     *
+     * @param  policies           Array of policy URIs that the implementation
+     *      must support
+     * @param  issuer             "Official" name of issuing origin site
+     * @exception  SAMLException  Raised if a profile implementation cannot be
+     *      constructed from the supplied information
+     */
+    public ShibPOSTProfile(String[] policies, String issuer)
+        throws SAMLException
+    {
+        if (policies == null || policies.length == 0 || issuer == null || issuer.length() == 0)
+            throw new SAMLException(SAMLException.REQUESTER, "ShibPOSTProfile() found a null or invalid argument");
+        this.issuer = issuer;
+        this.policies = new String[policies.length];
+        System.arraycopy(policies, 0, this.policies, 0, policies.length);
+    }
+
+    /**
+     *  Locates the first AuthenticationStatement in the response and validates
+     *  the statement and the enclosing assertion with respect to the POST
+     *  profile
+     *
+     * @param  r                  The response to the accepting site
+     * @return                    An authentication statement
+     * @exception  SAMLException  Base class of exceptions that may be thrown
+     *      during processing
+     */
+    public SAMLAuthenticationStatement getSSOStatement(SAMLResponse r)
+        throws SAMLException
+    {
+        return SAMLPOSTProfile.getSSOStatement(r, policies);
+    }
+
+    /**
+     *  Parse a Base-64 encoded buffer back into a SAML response and test its
+     *  validity against the POST profile, including use of the default replay
+     *  cache<P>
+     *
+     *  Also does trust evaluation based on the information available from the
+     *  origin site mapper, in accordance with general Shibboleth processing
+     *  semantics. Club-specific processing must be performed in a subclass.<P>
+     *
+     *
+     *
+     * @param  buf                A Base-64 encoded buffer containing a SAML
+     *      response
+     * @return                    SAML response sent by origin site
+     * @exception  SAMLException  Thrown if the response cannot be understood or
+     *      accepted
+     */
+    public SAMLResponse accept(byte[] buf)
+        throws SAMLException
+    {
+        // The built-in SAML functionality will do most of the basic non-crypto checks.
+        // Note that if the response only contains a status error, it gets tossed out
+        // as an exception.
+        SAMLResponse r = SAMLPOSTProfile.accept(buf, receiver, ttlSeconds);
+
+        // Now we do some more non-crypto (ie. cheap) work to match up the origin site
+        // with its associated data. If we can't even find a SSO statement in the response
+        // we just return the response to the caller, who will presumably notice this.
+        SAMLAuthenticationStatement sso = SAMLPOSTProfile.getSSOStatement(r, policies);
+        if (sso == null)
+            return r;
+
+        // Kind of clunky, we need to get the assertion containing the SSO statement,
+        // currently in a brute force way...
+        SAMLAssertion assertion = null;
+        SAMLAssertion[] assertions = r.getAssertions();
+        for (int i = 0; assertion == null && i < assertions.length; i++)
+        {
+            SAMLStatement[] states = assertions[i].getStatements();
+            for (int j = 0; j < states.length; j++)
+            {
+                if (states[j] == sso)
+                {
+                    assertion = assertions[i];
+                    break;
+                }
+            }
+        }
+
+        // Check for replay.
+        if (!checkReplayCache(assertion.getAssertionID(), new Date(assertion.getNotOnOrAfter().getTime() + 300000)))
+            throw new SAMLException(SAMLException.RESPONDER, "ShibPOSTProfile.accept() detected a replayed SSO assertion");
+
+        // Examine the subject information.
+        SAMLSubject subject = sso.getSubject();
+        if (subject.getNameQualifier() == null)
+            throw new SAMLException(SAMLException.RESPONDER, "ShibPOSTProfile.accept() requires subject name qualifier");
+
+        String originSite = subject.getNameQualifier();
+        String handleService = assertion.getIssuer();
+
+        // Is this a trusted HS?
+        Iterator hsNames = mapper.getHandleServiceNames(originSite);
+        boolean bFound = false;
+        while (!bFound && hsNames != null && hsNames.hasNext())
+            if (hsNames.next().equals(handleService))
+                bFound = true;
+        if (!bFound)
+            throw new SAMLException(SAMLException.RESPONDER, "ShibPOSTProfile.accept() detected an untrusted HS for the origin site");
+
+        Key hsKey = mapper.getHandleServiceKey(handleService);
+        KeyStore ks = mapper.getTrustedRoots();
+
+        // Signature verification now takes place. We check the assertion and the response.
+        // Assertion signing is optional, response signing is mandatory.
+        if (assertion.getSignature() != null && !verifySignature(assertion, handleService, ks, hsKey))
+            throw new SAMLException(SAMLException.RESPONDER, "ShibPOSTProfile.accept() detected an invalid assertion signature");
+        if (!verifySignature(r, handleService, ks, hsKey))
+            throw new SAMLException(SAMLException.RESPONDER, "ShibPOSTProfile.accept() detected an invalid response signature");
+
+        return r;
+    }
+
+    /**
+     *  Used by HS to generate a signed SAML response conforming to the POST
+     *  profile<P>
+     *
+     *
+     *
+     * @param  recipient          URL of intended consumer
+     * @param  name               Name of subject
+     * @param  nameQualifier      Federates or qualifies subject name (optional)
+     * @param  subjectIP          Client address of subject (optional)
+     * @param  authMethod         URI of authentication method being asserted
+     * @param  authInstant        Date and time of authentication being asserted
+     * @param  bindings           Array of SAML authorities the relying party
+     *      may contact (optional)
+     * @param  responseKey        A secret or private key to use in response
+     *      signature or MAC
+     * @param  responseCert       A public key certificate to enclose with the
+     *      response (optional)
+     * @param  assertionKey       A secret or private key to use in assertion
+     *      signature or MAC (optional)
+     * @param  assertionCert      A public key certificate to enclose with the
+     *      assertion (optional)
+     * @return                    SAML response to send to accepting site
+     * @exception  SAMLException  Base class of exceptions that may be thrown
+     *      during processing
+     */
+    public SAMLResponse prepare(String recipient,
+                                String name,
+                                String nameQualifier,
+                                String subjectIP,
+                                String authMethod,
+                                Date authInstant,
+                                SAMLAuthorityBinding[] bindings,
+                                Key responseKey, X509Certificate responseCert,
+                                Key assertionKey, X509Certificate assertionCert
+                                )
+        throws SAMLException
+    {
+        if (responseKey == null || (!(responseKey instanceof PrivateKey) && !(responseKey instanceof SecretKey)))
+            throw new InvalidCryptoException(SAMLException.RESPONDER, "ShibPOSTProfile.prepare() requires a response key (private or secret)");
+        if (assertionKey != null && !(assertionKey instanceof PrivateKey) && !(assertionKey instanceof SecretKey))
+            throw new InvalidCryptoException(SAMLException.RESPONDER, "ShibPOSTProfile.prepare() detected an invalid type of assertion key");
+
+        DocumentBuilder builder = null;
+        try
+        {
+            builder = org.opensaml.XML.parserPool.get();
+            Document doc = builder.newDocument();
+
+            XMLSignature rsig = new XMLSignature(doc, null, algorithm);
+            XMLSignature asig = null;
+            if (assertionKey != null)
+                asig = new XMLSignature(doc, null, algorithm);
+
+            SAMLResponse r = SAMLPOSTProfile.prepare(
+                recipient,
+                issuer,
+                policies,
+                name,
+                nameQualifier,
+                null,
+                subjectIP,
+                authMethod,
+                authInstant,
+                bindings,
+                rsig,
+                asig);
+            r.toDOM(doc);
+            if (asig != null)
+            {
+                if (assertionCert != null)
+                    asig.addKeyInfo(assertionCert);
+                if (assertionKey instanceof PrivateKey)
+                    asig.sign((PrivateKey)assertionKey);
+                else
+                    asig.sign((SecretKey)assertionKey);
+            }
+            if (responseCert != null)
+                rsig.addKeyInfo(responseCert);
+            if (responseKey instanceof PrivateKey)
+                rsig.sign((PrivateKey)responseKey);
+            else
+                rsig.sign((SecretKey)responseKey);
+            return r;
+        }
+        catch (ParserConfigurationException pce)
+        {
+            throw new SAMLException(SAMLException.RESPONDER, "ShibPOSTProfile.prepare() unable to obtain XML parser instance: " + pce.getMessage(), pce);
+        }
+        catch (XMLSecurityException e)
+        {
+            throw new InvalidCryptoException(SAMLException.RESPONDER, "ShibPOSTProfile.prepare() detected an XML security problem during signature creation", e);
+        }
+        finally
+        {
+            if (builder != null)
+                org.opensaml.XML.parserPool.put(builder);
+        }
+    }
+
+    /**
+     *  Searches the replay cache for the specified assertion ID and inserts a
+     *  newly seen ID into the cache<P>
+     *
+     *  Also performs garbage collection of the cache by deleting expired
+     *  entries.
+     *
+     * @param  expires      The datetime at which the specified assertion ID can
+     *      be flushed
+     * @param  assertionID  Description of Parameter
+     * @return              true iff the assertion has not been seen before
+     */
+    protected synchronized boolean checkReplayCache(String assertionID, Date expires)
+    {
+        // Default implementation uses the basic replay cache implementation.
+        return SAMLPOSTProfile.checkReplayCache(assertionID, expires);
+    }
+
+    /**
+     *  Default signature verification algorithm uses an embedded X509
+     *  certificate or an explicit key to verify the signature. The certificate
+     *  is examined to insure the subject CN matches the signer, and that it is
+     *  signed by a trusted CA
+     *
+     * @param  obj         The object containing the signature
+     * @param  signerName  The name of the signer
+     * @param  ks          A keystore containing trusted root certificates
+     * @param  knownKey    An explicit key to use if a certificate cannot be
+     *      found
+     * @return             The result of signature verification
+     */
+    protected boolean verifySignature(SAMLSignedObject obj, String signerName, KeyStore ks, Key knownKey)
+    {
+        try
+        {
+            XMLSignature sig = (obj != null) ? obj.getSignature() : null;
+            if (sig == null)
+                return false;
+            KeyInfo ki = sig.getKeyInfo();
+            if (ks != null && ki != null)
+            {
+                X509Certificate cert = ki.getX509Certificate();
+                if (cert != null)
+                {
+                    cert.checkValidity();
+                    if (!sig.checkSignatureValue(cert))
+                        return false;
+                    if (signerName != null)
+                    {
+                        String dname = cert.getSubjectDN().getName();
+                        String cname = "CN=" + signerName;
+                        if (!dname.equalsIgnoreCase(cname) && !dname.regionMatches(true, 0, cname + ',', 0, cname.length() + 1))
+                            return false;
+                    }
+
+                    String iname = cert.getIssuerDN().getName();
+                    for (Enumeration aliases = ks.aliases(); aliases.hasMoreElements(); )
+                    {
+                        String alias = (String)aliases.nextElement();
+                        if (!ks.isCertificateEntry(alias))
+                            continue;
+                        Certificate cacert = ks.getCertificate(alias);
+                        if (!(cacert instanceof X509Certificate))
+                            continue;
+                        ((X509Certificate)cacert).checkValidity();
+                        if (iname.equals(((X509Certificate)cacert).getSubjectDN().getName()))
+                        {
+                            cert.verify(cacert.getPublicKey());
+                            return true;
+                        }
+                    }
+
+                    return false;
+                }
+            }
+            return (knownKey != null) ? sig.checkSignatureValue(knownKey) : false;
+        }
+        catch (XMLSecurityException e)
+        {
+            return false;
+        }
+        catch (GeneralSecurityException e)
+        {
+            return false;
+        }
+    }
+}
+
diff --git a/src/edu/internet2/middleware/shibboleth/ShibPOSTProfileFactory.java b/src/edu/internet2/middleware/shibboleth/ShibPOSTProfileFactory.java
new file mode 100755 (executable)
index 0000000..04cfc68
--- /dev/null
@@ -0,0 +1,70 @@
+package edu.internet2.middleware.shibboleth;
+
+import java.security.Key;
+import org.opensaml.SAMLException;
+import org.opensaml.SAMLPOSTProfile;
+
+/**
+ *  Used by Shibboleth HS/SHIRE to locate a Shibboleth POST profile
+ *  implementation
+ *
+ * @author     Scott Cantor
+ * @created    April 10, 2002
+ */
+public class ShibPOSTProfileFactory
+{
+    /**
+     *  Gets a compatible SHIRE-side profile implementation for the specified
+     *  policies
+     *
+     * @param  policies           Array of policy URIs that the implementation
+     *      must support
+     * @param  mapper             Interface between profile and trust base
+     * @param  receiver           URL of SHIRE
+     * @param  ttlSeconds         Length of time in seconds allowed to elapse
+     *      from issuance of SAML response
+     * @return                    A compatible profile implementation or null if
+     *      one cannot be found
+     * @exception  SAMLException  Raised if a profile implementation cannot be
+     *      constructed from the supplied information
+     */
+    public static ShibPOSTProfile getInstance(String[] policies, OriginSiteMapper mapper,
+                                              String receiver, int ttlSeconds)
+        throws SAMLException
+    {
+        // Current version only knows about Club Shib...
+        if (policies == null || policies.length != 1 || !policies[0].equals(Constants.POLICY_CLUBSHIB))
+            return null;
+
+        if (mapper == null || receiver == null || ttlSeconds <= 0)
+            return null;
+
+        return new ClubShibPOSTProfile(policies, mapper, receiver, ttlSeconds);
+    }
+
+    /**
+     *  Gets a compatible HS-side profile implementation for the specified
+     *  policies
+     *
+     * @param  policies           Array of policy URIs that the implementation
+     *      must support
+     * @param  issuer             "Official" name of issuing origin site
+     * @return                    A compatible profile implementation or null if
+     *      one cannot be found
+     * @exception  SAMLException  Raised if a profile implementation cannot be
+     *      constructed from the supplied information
+     */
+    public static ShibPOSTProfile getInstance(String[] policies, String issuer)
+        throws SAMLException
+    {
+        // Current version only knows about Club Shib...
+        if (policies == null || policies.length != 1 || !policies[0].equals(Constants.POLICY_CLUBSHIB))
+            return null;
+
+        if (issuer == null || issuer.length() == 0)
+            return null;
+
+        return new ClubShibPOSTProfile(policies, issuer);
+    }
+}
+
diff --git a/src/edu/internet2/middleware/shibboleth/XML.java b/src/edu/internet2/middleware/shibboleth/XML.java
new file mode 100755 (executable)
index 0000000..13063d9
--- /dev/null
@@ -0,0 +1,77 @@
+package edu.internet2.middleware.shibboleth;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import org.xml.sax.EntityResolver;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+
+/**
+ *  Utility class for XML constants and schema handling
+ *
+ * @author     Scott Cantor
+ * @created    January 2, 2002
+ */
+public class XML
+{
+    /**  Shibboleth extension XML namespace */
+    public final static String SHIB_NS = "urn:mace:shibboleth:1.0";
+
+    /**  Shibboleth XML schema identifier */
+    public final static String SHIB_SCHEMA_ID = "shibboleth.xsd";
+
+    private static byte[] Shib_schema;
+
+    /**
+     *  Custom schema resolver class
+     *
+     * @author     Scott Cantor
+     * @created    May 18, 2002
+     */
+    protected static class SchemaResolver implements EntityResolver
+    {
+        /**
+         *  A customized entity resolver for the Shibboleth extension schema
+         *
+         * @param  publicId                 The public identifier of the entity
+         * @param  systemId                 The system identifier of the entity
+         * @return                          A source of bytes for the entity or
+         *      null
+         * @exception  SAXException         Raised if an XML parsing problem
+         *      occurs
+         * @exception  java.io.IOException  Raised if an I/O problem is detected
+         */
+        public InputSource resolveEntity(String publicId, String systemId)
+            throws SAXException, java.io.IOException
+        {
+            InputSource src = null;
+            if (systemId.endsWith('/' + SHIB_SCHEMA_ID) && Shib_schema != null)
+                src = new InputSource(new ByteArrayInputStream(Shib_schema));
+            return src;
+        }
+    }
+
+    static
+    {
+        try
+        {
+            StringBuffer buf = new StringBuffer(1024);
+            InputStream xmlin = XML.class.getResourceAsStream("/schemas/" + SHIB_SCHEMA_ID);
+            if (xmlin == null)
+                throw new RuntimeException("XML static initializer unable to locate Shibboleth schema");
+            else
+            {
+                int b;
+                while ((b = xmlin.read()) != -1)
+                    buf.append((char)b);
+                Shib_schema = buf.toString().getBytes();
+                xmlin.close();
+            }
+        }
+        catch (java.io.IOException e)
+        {
+            throw new RuntimeException("XML static initializer caught an I/O error");
+        }
+    }
+}
+