--- /dev/null
+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();
+ }
+}
+
--- /dev/null
+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;
+ }
+}
+
--- /dev/null
+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");
+ }
+ }
+}
+
--- /dev/null
+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()
+ );
+ }
+}
+
--- /dev/null
+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";
+}
+
--- /dev/null
+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();
+ }
+}
+
--- /dev/null
+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();
+}
+
--- /dev/null
+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();
+ }
+}
+
--- /dev/null
+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;
+ }
+ }
+}
+
--- /dev/null
+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);
+ }
+}
+
--- /dev/null
+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");
+ }
+ }
+}
+