summaryrefslogtreecommitdiffstats
path: root/lib/SAML2
diff options
context:
space:
mode:
authorOlav Morken <olav.morken@uninett.no>2009-08-03 12:46:28 +0000
committerOlav Morken <olav.morken@uninett.no>2009-08-03 12:46:28 +0000
commitba9f1ca2c1a3a5f430c83a79ef98d8a2b170a835 (patch)
tree53313c4b469da5185d9c070a690d6621dc99584c /lib/SAML2
parent9dc5e9ca9b6d6349fa1dc45b587e22da6c7a343b (diff)
downloadsimplesamlphp-ba9f1ca2c1a3a5f430c83a79ef98d8a2b170a835.zip
simplesamlphp-ba9f1ca2c1a3a5f430c83a79ef98d8a2b170a835.tar.gz
simplesamlphp-ba9f1ca2c1a3a5f430c83a79ef98d8a2b170a835.tar.bz2
Add library for dealing with SAML 2 messages.
git-svn-id: https://simplesamlphp.googlecode.com/svn/trunk@1605 44740490-163a-0410-bde0-09ae8108e29a
Diffstat (limited to 'lib/SAML2')
-rw-r--r--lib/SAML2/Assertion.php1075
-rw-r--r--lib/SAML2/AuthnRequest.php253
-rw-r--r--lib/SAML2/Binding.php111
-rw-r--r--lib/SAML2/Const.php11
-rw-r--r--lib/SAML2/EncryptedAssertion.php160
-rw-r--r--lib/SAML2/HTTPPost.php104
-rw-r--r--lib/SAML2/HTTPRedirect.php229
-rw-r--r--lib/SAML2/LogoutRequest.php123
-rw-r--r--lib/SAML2/LogoutResponse.php25
-rw-r--r--lib/SAML2/Message.php474
-rw-r--r--lib/SAML2/Request.php17
-rw-r--r--lib/SAML2/Response.php86
-rw-r--r--lib/SAML2/SignedElement.php31
-rw-r--r--lib/SAML2/StatusResponse.php195
-rw-r--r--lib/SAML2/Utils.php244
15 files changed, 3138 insertions, 0 deletions
diff --git a/lib/SAML2/Assertion.php b/lib/SAML2/Assertion.php
new file mode 100644
index 0000000..0fc0dbc
--- /dev/null
+++ b/lib/SAML2/Assertion.php
@@ -0,0 +1,1075 @@
+<?php
+
+/**
+ * Class representing a SAML 2 assertion.
+ *
+ * @package simpleSAMLphp
+ * @version $Id$
+ */
+class SAML2_Assertion implements SAML2_SignedElement {
+
+ /**
+ * The identifier of this assertion.
+ *
+ * @var string
+ */
+ private $id;
+
+
+ /**
+ * The issue timestamp of this assertion, as an UNIX timestamp.
+ *
+ * @var int
+ */
+ private $issueInstant;
+
+
+ /**
+ * The entity id of the issuer of this assertion.
+ *
+ * @var string
+ */
+ private $issuer;
+
+
+ /**
+ * The NameId of the subject in the assertion.
+ *
+ * If the NameId is NULL, no subject was included in the assertion.
+ *
+ * @var array|NULL
+ */
+ private $nameId;
+
+
+ /**
+ * The earliest time this assertion is valid, as an UNIX timestamp.
+ *
+ * @var int
+ */
+ private $notBefore;
+
+
+ /**
+ * The time this assertion expires, as an UNIX timestamp.
+ *
+ * @var int
+ */
+ private $notOnOrAfter;
+
+
+ /**
+ * The destination URL for this assertion.
+ *
+ * @var string|NULL
+ */
+ private $destination;
+
+
+ /**
+ * The id of the request this assertion is sent as a response to.
+ *
+ * This should be NULL if this isn't a response to a request.
+ *
+ * @var string|NULL
+ */
+ private $inResponseTo;
+
+
+ /**
+ * The set of audiences that are allowed to receive this assertion.
+ *
+ * This is an array of valid service providers.
+ *
+ * If no restrictions on the audience are present, this variable contains NULL.
+ *
+ * @var array|NULL
+ */
+ private $validAudiences;
+
+
+ /**
+ * The session expiration timestamp.
+ *
+ * @var int|NULL
+ */
+ private $sessionNotOnOrAfter;
+
+
+ /**
+ * The session index for this user on the IdP.
+ *
+ * Contains NULL if no session index is present.
+ *
+ * @var string|NULL
+ */
+ private $sessionIndex;
+
+
+ /**
+ * The authentication context for this assertion.
+ *
+ * @var string|NULL
+ */
+ private $authnContext;
+
+
+ /**
+ * The attributes, as an associative array.
+ *
+ * @var array
+ */
+ private $attributes;
+
+
+ /**
+ * The NameFormat used on all attributes.
+ *
+ * If more than one NameFormat is used, this will contain
+ * the unspecified nameformat.
+ *
+ * @var string
+ */
+ private $nameFormat;
+
+
+ /**
+ * The private key we should use to sign the assertion.
+ *
+ * The private key can be NULL, in which case the assertion is sent unsigned.
+ *
+ * @var XMLSecurityKey|NULL
+ */
+ private $signatureKey;
+
+
+ /**
+ * List of certificates that should be included in the assertion.
+ *
+ * @var array
+ */
+ private $certificates;
+
+
+ /**
+ * The data needed to verify the signature.
+ *
+ * @var array|NULL
+ */
+ private $signatureData;
+
+
+
+ /**
+ * Constructor for SAML 2 assertions.
+ *
+ * @param DOMElement|NULL $xml The input assertion.
+ */
+ public function __construct(DOMElement $xml = NULL) {
+
+ $this->id = SimpleSAML_Utilities::generateID();
+ $this->issueInstant = time();
+ $this->issuer = '';
+ $this->attributes = array();
+ $this->nameFormat = SAML2_Const::NAMEFORMAT_UNSPECIFIED;
+ $this->certificates = array();
+
+ if ($xml === NULL) {
+ return;
+ }
+
+ if (!$xml->hasAttribute('ID')) {
+ throw new Exception('Missing ID attribute on SAML assertion.');
+ }
+ $this->id = $xml->getAttribute('ID');
+
+ if ($xml->getAttribute('Version') !== '2.0') {
+ /* Currently a very strict check. */
+ throw new Exception('Unsupported version: ' . $xml->getAttribute('Version'));
+ }
+
+ $this->issueInstant = SimpleSAML_Utilities::parseSAML2Time($xml->getAttribute('IssueInstant'));
+
+ $issuer = SAML2_Utils::xpQuery($xml, './saml:Issuer');
+ if (empty($issuer)) {
+ throw new Exception('Missing <saml:Issuer> in assertion.');
+ }
+ $this->issuer = $issuer[0]->textContent;
+
+ $this->parseSubject($xml);
+ $this->parseConditions($xml);
+ $this->parseAuthnStatement($xml);
+ $this->parseAttributes($xml);
+ $this->parseSignature($xml);
+ }
+
+
+ /**
+ * Parse subject in assertion.
+ *
+ * @param DOMElement $xml The assertion XML element.
+ */
+ private function parseSubject(DOMElement $xml) {
+
+ $subject = SAML2_Utils::xpQuery($xml, './saml:Subject');
+ if (empty($subject)) {
+ /* No Subject node. */
+ return;
+ } elseif (count($subject) > 1) {
+ throw new Exception('More than one <saml:Subject> in <saml:Assertion>.');
+ }
+ $subject = $subject[0];
+
+ $nameId = SAML2_Utils::xpQuery($subject, './saml:NameID');
+ if (empty($nameId)) {
+ throw new Exception('Missing <saml:NameID> in <saml:Subject>.');
+ } elseif (count($nameId) > 1) {
+ throw new Exception('More than one <saml:NameID> in <saml:Subject>.');
+ }
+ $nameId = $nameId[0];
+ $this->nameId = SAML2_Utils::parseNameId($nameId);
+
+ $subjectConfirmation = SAML2_Utils::xpQuery($subject, './saml:SubjectConfirmation');
+ if (empty($subjectConfirmation)) {
+ throw new Exception('Missing <saml:SubjectConfirmation> in <saml:Subject>.');
+ } elseif (count($subjectConfirmation) > 1) {
+ throw new Exception('More than one <saml:SubjectConfirmation> in <saml:Subject>.');
+ }
+ $subjectConfirmation = $subjectConfirmation[0];
+
+ if (!$subjectConfirmation->hasAttribute('Method')) {
+ throw new Exception('Missing required attribute "Method" on <saml:SubjectConfirmation>-node.');
+ }
+ $method = $subjectConfirmation->getAttribute('Method');
+
+ if ($method !== SAML2_Const::CM_BEARER) {
+ throw new Exception('Unsupported subject confirmation method: ' . var_export($method, TRUE));
+ }
+
+ $confirmationData = SAML2_Utils::xpQuery($subjectConfirmation, './saml:SubjectConfirmationData');
+ if (empty($confirmationData)) {
+ return;
+ } elseif (count($confirmationData) > 1) {
+ throw new Exception('More than one <saml:SubjectConfirmationData> in <saml:SubjectConfirmation> is currently unsupported.');
+ }
+ $confirmationData = $confirmationData[0];
+
+ if ($confirmationData->hasAttribute('NotBefore')) {
+ $notBefore = SimpleSAML_Utilities::parseSAML2Time($confirmationData->getAttribute('NotBefore'));
+ if ($this->notBefore === NULL || $this->notBefore < $notBefore) {
+ $this->notBefore = $notBefore;
+ }
+ }
+ if ($confirmationData->hasAttribute('NotOnOrAfter')) {
+ $notOnOrAfter = SimpleSAML_Utilities::parseSAML2Time($confirmationData->getAttribute('NotOnOrAfter'));
+ if ($this->notOnOrAfter === NULL || $this->notOnOrAfter > $notOnOrAfter) {
+ $this->notOnOrAfter = $notOnOrAfter;
+ }
+ }
+ if ($confirmationData->hasAttribute('InResponseTo')) {
+ $this->inResponseTo = $confirmationData->getAttribute('InResponseTo');;
+ }
+ if ($confirmationData->hasAttribute('Recipient')) {
+ $this->destination = $confirmationData->getAttribute('Recipient');;
+ }
+ }
+
+
+ /**
+ * Parse conditions in assertion.
+ *
+ * @param DOMElement $xml The assertion XML element.
+ */
+ private function parseConditions(DOMElement $xml) {
+
+ $conditions = SAML2_Utils::xpQuery($xml, './saml:Conditions');
+ if (empty($conditions)) {
+ /* No <saml:Conditions> node. */
+ return;
+ } elseif (count($conditions) > 1) {
+ throw new Exception('More than one <saml:Conditions> in <saml:Assertion>.');
+ }
+ $conditions = $conditions[0];
+
+ if ($conditions->hasAttribute('NotBefore')) {
+ $notBefore = SimpleSAML_Utilities::parseSAML2Time($conditions->getAttribute('NotBefore'));
+ if ($this->notBefore === NULL || $this->notBefore < $notBefore) {
+ $this->notBefore = $notBefore;
+ }
+ }
+ if ($conditions->hasAttribute('NotOnOrAfter')) {
+ $notOnOrAfter = SimpleSAML_Utilities::parseSAML2Time($conditions->getAttribute('NotOnOrAfter'));
+ if ($this->notOnOrAfter === NULL || $this->notOnOrAfter > $notOnOrAfter) {
+ $this->notOnOrAfter = $notOnOrAfter;
+ }
+ }
+
+
+ for ($node = $conditions->firstChild; $node !== NULL; $node = $node->nextSibling) {
+ if ($node instanceof DOMText) {
+ continue;
+ }
+ if ($node->namespaceURI !== SAML2_Const::NS_SAML) {
+ throw new Exception('Unknown namespace of condition: ' . var_export($node->namespaceURI, TRUE));
+ }
+ switch ($node->localName) {
+ case 'AudienceRestriction':
+ $audiences = SAML2_Utils::xpQuery($node, './saml:Audience');
+ foreach ($audiences as &$audience) {
+ $audience = $audience->textContent;
+ }
+ if ($this->validAudiences === NULL) {
+ /* The first (and probably last) AudienceRestriction element. */
+ $this->validAudiences = $audiences;
+
+ } else {
+ /*
+ * The set of AudienceRestriction are ANDed together, so we need
+ * the subset that are present in all of them.
+ */
+ $this->validAudiences = array_intersect($this->validAudiences, $audiences);
+ }
+ break;
+ case 'OneTimeUse':
+ /* Currently ignored. */
+ break;
+ case 'ProxyRestriction':
+ /* Currently ignored. */
+ break;
+ default:
+ throw new Exception('Unknown condition: ' . var_export($node->localName, TRUE));
+ }
+ }
+
+ }
+
+
+ /**
+ * Parse AuthnStatement in assertion.
+ *
+ * @param DOMElement $xml The assertion XML element.
+ */
+ private function parseAuthnStatement(DOMElement $xml) {
+
+ $as = SAML2_Utils::xpQuery($xml, './saml:AuthnStatement');
+ if (empty($as)) {
+ return;
+ } elseif (count($as) > 1) {
+ throw new Exception('More that one <saml:AuthnStatement> in <saml:Assertion> not supported.');
+ }
+ $as = $as[0];
+ $this->authnStatement = array();
+
+ if (!$as->hasAttribute('AuthnInstant')) {
+ throw new Exception('Missing required AuthnInstant attribute on <saml:AuthnStatement>.');
+ }
+
+ if ($as->hasAttribute('SessionNotOnOrAfter')) {
+ $this->sessionNotOnOrAfter = SimpleSAML_Utilities::parseSAML2Time($as->getAttribute('SessionNotOnOrAfter'));
+ }
+
+ if ($as->hasAttribute('SessionIndex')) {
+ $this->sessionIndex = $as->getAttribute('SessionIndex');
+ }
+
+ $ac = SAML2_Utils::xpQuery($as, './saml:AuthnContext');
+ if (empty($ac)) {
+ throw new Exception('Missing required <saml:AuthnContext> in <saml:AuthnStatement>.');
+ } elseif (count($ac) > 1) {
+ throw new Exception('More than one <saml:AuthnContext> in <saml:AuthnStatement>.');
+ }
+ $ac = $ac[0];
+
+ $accr = SAML2_Utils::xpQuery($ac, './saml:AuthnContextClassRef');
+ if (empty($accr)) {
+ throw new Exception('Missing almost-required <saml:AuthnContextClassRef> in <saml:AuthnContext>.');
+ } elseif (count($accr) > 1) {
+ throw new Exception('More than one <saml:AuthnContextClassRef> in <saml:AuthnContext>.');
+ }
+ $accr = $accr[0];
+
+ $this->authnContext = $accr->textContent;
+ }
+
+
+ /**
+ * Parse attribute statements in assertion.
+ *
+ * @param DOMElement $xml The XML element with the assertion.
+ */
+ private function parseAttributes(DOMElement $xml) {
+
+ $firstAttribute = TRUE;
+ $attributes = SAML2_Utils::xpQuery($xml, './saml:AttributeStatement/saml:Attribute');
+ foreach ($attributes as $attribute) {
+ if (!$attribute->hasAttribute('Name')) {
+ throw new Exception('Missing name on <saml:Attribute> element.');
+ }
+ $name = $attribute->getAttribute('Name');
+
+ if ($attribute->hasAttribute('NameFormat')) {
+ $nameFormat = $attribute->getAttribute('NameFormat');
+ } else {
+ $nameFormat = SAML2_Const::NAMEFORMAT_UNSPECIFIED;
+ }
+
+ if ($firstAttribute) {
+ $this->nameFormat = $nameFormat;
+ $firstAttribute = FALSE;
+ } else {
+ if ($this->nameFormat !== $nameFormat) {
+ $this->nameFormat = SAML2_Const::NAMEFORMAT_UNSPECIFIED;
+ }
+ }
+
+ if (!array_key_exists($name, $this->attributes)) {
+ $this->attributes[$name] = array();
+ }
+
+ $values = SAML2_Utils::xpQuery($attribute, './saml:AttributeValue');
+ foreach ($values as $value) {
+ $this->attributes[$name][] = $value->textContent;
+ }
+ }
+ }
+
+
+ /**
+ * Parse signature on assertion.
+ *
+ * @param DOMElement $xml The assertion XML element.
+ */
+ private function parseSignature(DOMElement $xml) {
+
+ /* Validate the signature element of the message. */
+ $sig = SAML2_Utils::validateElement($xml);
+ if ($sig !== FALSE) {
+ $this->certificates = $sig['Certificates'];
+ $this->signatureData = $sig;
+ }
+ }
+
+
+ /**
+ * Validate this assertion against a public key.
+ *
+ * If no signature was present on the assertion, we will return FALSE.
+ * Otherwise, TRUE will be returned. An exception is thrown if the
+ * signature validation fails.
+ *
+ * @param XMLSecurityKey $key The key we should check against.
+ * @return boolean TRUE if successful, FALSE if it is unsigned.
+ */
+ public function validate(XMLSecurityKey $key) {
+ assert('$key->type === XMLSecurityKey::RSA_SHA1');
+
+ if ($this->signatureData === NULL) {
+ return FALSE;
+ }
+
+ SAML2_Utils::validateSignature($this->signatureData, $key);
+
+ return TRUE;
+ }
+
+
+ /**
+ * Retrieve the identifier of this assertion.
+ *
+ * @return string The identifier of this assertion.
+ */
+ public function getId() {
+ return $this->id;
+ }
+
+
+ /**
+ * Set the identifier of this assertion.
+ *
+ * @param string $id The new identifier of this assertion.
+ */
+ public function setId($id) {
+ assert('is_string($id)');
+
+ $this->id = $id;
+ }
+
+
+ /**
+ * Retrieve the issue timestamp of this assertion.
+ *
+ * @return int The issue timestamp of this assertion, as an UNIX timestamp.
+ */
+ public function getIssueInstant() {
+ return $this->issueInstant;
+ }
+
+
+ /**
+ * Set the issue timestamp of this assertion.
+ *
+ * @param int $issueInstant The new issue timestamp of this assertion, as an UNIX timestamp.
+ */
+ public function setIssueInstant($issueInstant) {
+ assert('is_int($issueInstant)');
+
+ $this->issueInstant = $issueInstant;
+ }
+
+
+ /**
+ * Retrieve the issuer if this assertion.
+ *
+ * @return string The issuer of this assertion.
+ */
+ public function getIssuer() {
+ return $this->issuer;
+ }
+
+
+ /**
+ * Set the issuer of this message.
+ *
+ * @param string $issuer The new issuer of this assertion.
+ */
+ public function setIssuer($issuer) {
+ assert('is_string($issuer)');
+
+ $this->issuer = $issuer;
+ }
+
+
+ /**
+ * Retrieve the NameId of the subject in the assertion.
+ *
+ * The returned NameId is in the format used by SAML2_Utils::addNameId().
+ *
+ * @see SAML2_Utils::addNameId()
+ * @return array|NULL The name identifier of the assertion.
+ */
+ public function getNameId() {
+ return $this->nameId;
+ }
+
+
+ /**
+ * Set the NameId of the subject in the assertion.
+ *
+ * The NameId must be in the format accepted by SAML2_Utils::addNameId().
+ *
+ * @see SAML2_Utils::addNameId()
+ * @param array|NULL $nameId The name identifier of the assertion.
+ */
+ public function setNameId($nameId) {
+ assert('is_array($nameId) || is_null($nameId)');
+
+ $this->nameId = $nameId;
+ }
+
+
+ /**
+ * Retrieve the earliest timestamp this assertion is valid.
+ *
+ * This function returns NULL if there are no restrictions on how early the
+ * assertion can be used.
+ *
+ * @return int|NULL The earliest timestamp this assertion is valid.
+ */
+ public function getNotBefore() {
+
+ return $this->notBefore;
+ }
+
+
+ /**
+ * Set the earliest timestamp this assertion can be used.
+ *
+ * Set this to NULL if no limit is required.
+ *
+ * @param int|NULL $notBefore The earliest timestamp this assertion is valid.
+ */
+ public function setNotBefore($notBefore) {
+ assert('is_int($notBefore) || is_null($notBefore)');
+
+ $this->notBefore = $notBefore;
+ }
+
+
+ /**
+ * Retrieve the expiration timestamp of this assertion.
+ *
+ * This function returns NULL if there are no restrictions on how
+ * late the assertion can be used.
+ *
+ * @return int|NULL The latest timestamp this assertion is valid.
+ */
+ public function getNotOnOrAfter() {
+
+ return $this->notOnOrAfter;
+ }
+
+
+ /**
+ * Set the expiration timestamp of this assertion.
+ *
+ * Set this to NULL if no limit is required.
+ *
+ * @param int|NULL $notOnOrAfter The latest timestamp this assertion is valid.
+ */
+ public function setNotOnOrAfter($notOnOrAfter) {
+ assert('is_int($notOnOrAfter) || is_null($notOnOrAfter)');
+
+ $this->notOnOrAfter = $notOnOrAfter;
+ }
+
+
+ /**
+ * Retrieve the destination URL of this assertion.
+ *
+ * This function returns NULL if there are no restrictions on which URL can
+ * receive the assertion.
+ *
+ * @return string|NULL The destination URL of this assertion.
+ */
+ public function getDestination() {
+
+ return $this->destination;
+ }
+
+
+ /**
+ * Set the destination URL of this assertion.
+ *
+ * @return string|NULL The destination URL of this assertion.
+ */
+ public function setDestination($destination) {
+ assert('is_string($destination) || is_null($destination)');
+
+ $this->destination = $destination;
+ }
+
+
+ /**
+ * Retrieve the request this assertion is sent in response to.
+ *
+ * Can be NULL, in which case this assertion isn't sent in response to a specific request.
+ *
+ * @return string|NULL The id of the request this assertion is sent in response to.
+ */
+ public function getInResponseTo() {
+
+ return $this->inResponseTo;
+ }
+
+
+ /**
+ * Set the request this assertion is sent in response to.
+ *
+ * Can be set to NULL, in which case this assertion isn't sent in response to a specific request.
+ *
+ * @param string|NULL $inResponseTo The id of the request this assertion is sent in response to.
+ */
+ public function setInResponseTo($inResponseTo) {
+ assert('is_string($inResponseTo) || is_null($inResponseTo)');
+
+ $this->inResponseTo = $inResponseTo;
+ }
+
+
+ /**
+ * Retrieve the audiences that are allowed to receive this assertion.
+ *
+ * This may be NULL, in which case all audiences are allowed.
+ *
+ * @return array|NULL The allowed audiences.
+ */
+ public function getValidAudiences() {
+
+ return $this->validAudiences;
+ }
+
+
+ /**
+ * Set the audiences that are allowed to receive this assertion.
+ *
+ * This may be NULL, in which case all audiences are allowed.
+ *
+ * @param array|NULL $validAudiences The allowed audiences.
+ */
+ public function setValidAudiences(array $validAudiences = NULL) {
+
+ $this->validAudiences = $validAudiences;
+ }
+
+
+ /**
+ * Retrieve the session expiration timestamp.
+ *
+ * This function returns NULL if there are no restrictions on the
+ * session lifetime.
+ *
+ * @return int|NULL The latest timestamp this session is valid.
+ */
+ public function getSessionNotOnOrAfter() {
+
+ return $this->sessionNotOnOrAfter;
+ }
+
+
+ /**
+ * Set the session expiration timestamp.
+ *
+ * Set this to NULL if no limit is required.
+ *
+ * @param int|NULL $sessionLifetime The latest timestamp this session is valid.
+ */
+ public function setSessionNotOnOrAfter($sessionNotOnOrAfter) {
+ assert('is_int($sessionNotOnOrAfter) || is_null($sessionNotOnOrAfter)');
+
+ $this->sessionNotOnOrAfter = $sessionNotOnOrAfter;
+ }
+
+
+ /**
+ * Retrieve the session index of the user at the IdP.
+ *
+ * @return string|NULL The session index of the user at the IdP.
+ */
+ public function getSessionIndex() {
+
+ return $this->sessionIndex;
+ }
+
+
+ /**
+ * Set the session index of the user at the IdP.
+ *
+ * Note that the authentication context must be set before the
+ * session index can be inluded in the assertion.
+ *
+ * @param string|NULL $sessionIndex The session index of the user at the IdP.
+ */
+ public function setSessionIndex($sessionIndex) {
+ assert('is_string($sessionIndex) || is_null($sessionIndex)');
+
+ $this->sessionIndex = $sessionIndex;
+ }
+
+
+ /**
+ * Retrieve the authentication method used to authenticate the user.
+ *
+ * This will return NULL if no authentication statement was
+ * included in the assertion.
+ *
+ * @return string|NULL The authentication method.
+ */
+ public function getAuthnContext() {
+
+ return $this->authnContext;
+ }
+
+
+ /**
+ * Set the authentication method used to authenticate the user.
+ *
+ * If this is set to NULL, no authentication statement will be
+ * included in the assertion. The default is NULL.
+ *
+ * @param string|NULL $authnContext The authentication method.
+ */
+ public function setAuthnContext($authnContext) {
+ assert('is_string($authnContext) || is_null($authnContext)');
+
+ $this->authnContext = $authnContext;
+ }
+
+
+ /**
+ * Retrieve all attributes.
+ *
+ * @return array All attributes, as an associative array.
+ */
+ public function getAttributes() {
+
+ return $this->attributes;
+ }
+
+
+ /**
+ * Replace all attributes.
+ *
+ * @param array $attributes All new attributes, as an associative array.
+ */
+ public function setAttributes(array $attributes) {
+
+ $this->attributes = $attributes;
+ }
+
+
+ /**
+ * Retrieve the NameFormat used on all attributes.
+ *
+ * If more than one NameFormat is used in the received attributes, this
+ * returns the unspecified NameFormat.
+ *
+ * @return string The NameFormat used on all attributes.
+ */
+ public function getAttributeNameFormat() {
+ return $this->nameFormat;
+ }
+
+
+ /**
+ * Set the NameFormat used on all attributes.
+ *
+ * @param string $nameFormat The NameFormat used on all attributes.
+ */
+ public function setAttributeNameFormat($nameFormat) {
+ assert('is_string($nameFormat)');
+
+ $this->nameFormat = $nameFormat;
+ }
+
+
+ /**
+ * Retrieve the private key we should use to sign the assertion.
+ *
+ * @return XMLSecurityKey|NULL The key, or NULL if no key is specified.
+ */
+ public function getSignatureKey() {
+ return $this->signatureKey;
+ }
+
+
+ /**
+ * Set the private key we should use to sign the assertion.
+ *
+ * If the key is NULL, the assertion will be sent unsigned.
+ *
+ * @param XMLSecurityKey|NULL $key
+ */
+ public function setSignatureKey(XMLsecurityKey $signatureKey = NULL) {
+ $this->signatureKey = $signatureKey;
+ }
+
+
+ /**
+ * Set the certificates that should be included in the assertion.
+ *
+ * The certificates should be strings with the PEM encoded data.
+ *
+ * @param array $certificates An array of certificates.
+ */
+ public function setCertificates(array $certificates) {
+ $this->certificates = $certificates;
+ }
+
+
+ /**
+ * Retrieve the certificates that are included in the assertion.
+ *
+ * @return array An array of certificates.
+ */
+ public function getCertificates() {
+ return $this->certificates;
+ }
+
+
+ /**
+ * Convert this assertion to an XML element.
+ *
+ * @return DOMElement This assertion.
+ */
+ public function toXML() {
+
+ $document = new DOMDocument();
+
+ $root = $document->createElementNS(SAML2_Const::NS_SAML, 'saml:' . 'Assertion');
+ $document->appendChild($root);
+
+ /* Ugly hack to add another namespace declaration to the root element. */
+ $root->setAttributeNS(SAML2_Const::NS_SAMLP, 'samlp:tmp', 'tmp');
+ $root->removeAttributeNS(SAML2_Const::NS_SAMLP, 'tmp');
+ $root->setAttributeNS(SAML2_Const::NS_XSI, 'xsi:tmp', 'tmp');
+ $root->removeAttributeNS(SAML2_Const::NS_XSI, 'tmp');
+ $root->setAttributeNS(SAML2_Const::NS_XS, 'xs:tmp', 'tmp');
+ $root->removeAttributeNS(SAML2_Const::NS_XS, 'tmp');
+
+ $root->setAttribute('ID', $this->id);
+ $root->setAttribute('Version', '2.0');
+ $root->setAttribute('IssueInstant', gmdate('Y-m-d\TH:i:s\Z', $this->issueInstant));
+
+ $issuer = $document->createElementNS(SAML2_Const::NS_SAML, 'saml:Issuer');
+ $issuer->appendChild($document->createTextNode($this->issuer));
+ $root->appendChild($issuer);
+
+ $this->addSubject($root);
+ $this->addConditions($root);
+ $this->addAuthnStatement($root);
+ $this->addAttributeStatement($root);
+
+ if ($this->signatureKey !== NULL) {
+ SAML2_Utils::insertSignature($this->signatureKey, $this->certificates, $root, $issuer->nextSibling);
+ }
+
+ return $root;
+ }
+
+
+ /**
+ * Add a Subject-node to the assertion.
+ *
+ * @param DOMElement $root The assertion element we should add the subject to.
+ */
+ private function addSubject(DOMElement $root) {
+
+ if ($this->nameId === NULL) {
+ /* We don't have anything to create a Subject node for. */
+ return;
+ }
+
+ $subject = $root->ownerDocument->createElementNS(SAML2_Const::NS_SAML, 'saml:Subject');
+ $root->appendChild($subject);
+
+ SAML2_Utils::addNameId($subject, $this->nameId);
+
+ $sc = $root->ownerDocument->createElementNS(SAML2_Const::NS_SAML, 'saml:SubjectConfirmation');
+ $subject->appendChild($sc);
+
+ $sc->setAttribute('Method', SAML2_Const::CM_BEARER);
+
+ $scd = $root->ownerDocument->createElementNS(SAML2_Const::NS_SAML, 'saml:SubjectConfirmationData');
+ $sc->appendChild($scd);
+
+ if ($this->notBefore !== NULL) {
+ $scd->setAttribute('NotBefore', gmdate('Y-m-d\TH:i:s\Z', $this->notBefore));
+ }
+ if ($this->notOnOrAfter !== NULL) {
+ $scd->setAttribute('NotOnOrAfter', gmdate('Y-m-d\TH:i:s\Z', $this->notOnOrAfter));
+ }
+ if ($this->destination !== NULL) {
+ $scd->setAttribute('Recipient', $this->destination);
+ }
+ if ($this->inResponseTo !== NULL) {
+ $scd->setAttribute('InResponseTo', $this->inResponseTo);
+ }
+ }
+
+
+ /**
+ * Add a Conditions-node to the assertion.
+ *
+ * @param DOMElement $root The assertion element we should add the conditions to.
+ */
+ private function addConditions(DOMElement $root) {
+
+ $document = $root->ownerDocument;
+
+ $conditions = $document->createElementNS(SAML2_Const::NS_SAML, 'saml:Conditions');
+ $root->appendChild($conditions);
+
+ if ($this->notBefore !== NULL) {
+ $conditions->setAttribute('NotBefore', gmdate('Y-m-d\TH:i:s\Z', $this->notBefore));
+ }
+ if ($this->notOnOrAfter !== NULL) {
+ $conditions->setAttribute('NotOnOrAfter', gmdate('Y-m-d\TH:i:s\Z', $this->notOnOrAfter));
+ }
+
+ if ($this->validAudiences !== NULL) {
+ $ar = $document->createElementNS(SAML2_Const::NS_SAML, 'saml:AudienceRestriction');
+ $conditions->appendChild($ar);
+
+ foreach ($this->validAudiences as $audience) {
+ $a = $document->createElementNS(SAML2_Const::NS_SAML, 'saml:Audience');
+ $ar->appendChild($a);
+
+ $a->appendChild($document->createTextNode($audience));
+ }
+ }
+ }
+
+
+ /**
+ * Add a AuthnStatement-node to the assertion.
+ *
+ * @param DOMElement $root The assertion element we should add the authentication statement to.
+ */
+ private function addAuthnStatement(DOMElement $root) {
+
+ if ($this->authnContext === NULL) {
+ /* No authentication context => no authentication statement. */
+ return;
+ }
+
+ $document = $root->ownerDocument;
+
+ $as = $document->createElementNS(SAML2_Const::NS_SAML, 'saml:AuthnStatement');
+ $root->appendChild($as);
+
+ $as->setAttribute('AuthnInstant', gmdate('Y-m-d\TH:i:s\Z', $this->issueInstant));
+
+ if ($this->sessionNotOnOrAfter !== NULL) {
+ $as->setAttribute('SessionNotOnOrAfter', gmdate('Y-m-d\TH:i:s\Z', $this->sessionNotOnOrAfter));
+ }
+ if ($this->sessionIndex !== NULL) {
+ $as->setAttribute('SessionIndex', $this->sessionIndex);
+ }
+
+ $ac = $document->createElementNS(SAML2_Const::NS_SAML, 'saml:AuthnContext');
+ $as->appendChild($ac);
+
+ $accr = $document->createElementNS(SAML2_Const::NS_SAML, 'saml:AuthnContextClassRef');
+ $ac->appendChild($accr);
+
+ $accr->appendChild($document->createTextNode($this->authnContext));
+ }
+
+
+ /**
+ * Add an AttributeStatement-node to the assertion.
+ *
+ * @param DOMElement $root The assertion element we should add the subject to.
+ */
+ private function addAttributeStatement(DOMElement $root) {
+
+ if (empty($this->attributes)) {
+ return;
+ }
+
+ $document = $root->ownerDocument;
+
+ $attributeStatement = $document->createElementNS(SAML2_Const::NS_SAML, 'saml:AttributeStatement');
+ $root->appendChild($attributeStatement);
+
+ foreach ($this->attributes as $name => $values) {
+ $attribute = $document->createElementNS(SAML2_Const::NS_SAML, 'saml:Attribute');
+ $attributeStatement->appendChild($attribute);
+ $attribute->setAttribute('Name', $name);
+
+ if ($this->nameFormat !== SAML2_Const::NAMEFORMAT_UNSPECIFIED) {
+ $attribute->setAttribute('NameFormat', $this->nameFormat);
+ }
+
+ foreach ($values as $value) {
+ if (is_string($value)) {
+ $type = 'xs:string';
+ } elseif (is_int($value)) {
+ $type = 'xs:integer';
+ } else {
+ $type = NULL;
+ }
+
+ $attributeValue = $document->createElementNS(SAML2_Const::NS_SAML, 'saml:AttributeValue');
+ $attribute->appendChild($attributeValue);
+ if ($type !== NULL) {
+ $attributeValue->setAttributeNS(SAML2_Const::NS_XSI, 'xsi:type', $type);
+ }
+ $attributeValue->appendChild($document->createTextNode($value));
+ }
+ }
+ }
+
+}
+
+?> \ No newline at end of file
diff --git a/lib/SAML2/AuthnRequest.php b/lib/SAML2/AuthnRequest.php
new file mode 100644
index 0000000..ca7bf9d
--- /dev/null
+++ b/lib/SAML2/AuthnRequest.php
@@ -0,0 +1,253 @@
+<?php
+
+/**
+ * Class for SAML 2 authentication request messages.
+ *
+ * @package simpleSAMLphp
+ * @version $Id$
+ */
+class SAML2_AuthnRequest extends SAML2_Request {
+
+ /**
+ * The options for what type of name identifier should be returned.
+ *
+ * @var array
+ */
+ private $nameIdPolicy;
+
+ /**
+ * Whether the Identity Provider must authenticate the user again.
+ *
+ * @var bool
+ */
+ private $forceAuthn;
+
+
+ /**
+ * Set to TRUE if this request is passive.
+ *
+ * @var bool.
+ */
+ private $isPassive;
+
+
+ /**
+ * The URL of the asertion consumer service where the response should be delivered.
+ *
+ * @var string|NULL
+ */
+ private $assertionConsumerServiceURL;
+
+
+ /**
+ * What binding should be used when sending the response.
+ *
+ * @var string|NULL
+ */
+ private $protocolBinding;
+
+
+ /**
+ * Constructor for SAML 2 authentication request messages.
+ *
+ * @param DOMElement|NULL $xml The input message.
+ */
+ public function __construct(DOMElement $xml = NULL) {
+ parent::__construct('AuthnRequest', $xml);
+
+ $this->nameIdPolicy = array();
+ $this->forceAuthn = FALSE;
+ $this->isPassive = FALSE;
+
+ if ($xml === NULL) {
+ return;
+ }
+
+ $this->forceAuthn = SAML2_Utils::parseBoolean($xml, 'ForceAuthn', FALSE);
+ $this->isPassive = SAML2_Utils::parseBoolean($xml, 'IsPassive', FALSE);
+
+ if ($xml->hasAttribute('AssertionConsumerServiceURL')) {
+ $this->assertionConsumerServiceURL = $xml->getAttribute('AssertionConsumerServiceURL');
+ }
+
+ if ($xml->hasAttribute('ProtocolBinding')) {
+ $this->protocolBinding = $xml->getAttribute('ProtocolBinding');
+ }
+
+ $nameIdPolicy = SAML2_Utils::xpQuery($xml, './samlp:NameIDPolicy');
+ if (!empty($nameIdPolicy)) {
+ $nameIdPolicy = $nameIdPolicy[0];
+ if ($nameIdPolicy->hasAttribute('Format')) {
+ $this->nameIdPolicy['Format'] = $nameIdPolicy->getAttribute('Format');
+ }
+ if ($nameIdPolicy->hasAttribute('SPNameQualifier')) {
+ $this->nameIdPolicy['SPNameQualifier'] = $nameIdPolicy->getAttribute('SPNameQualifier');
+ }
+ if ($nameIdPolicy->hasAttribute('AllowCreate')) {
+ $this->nameIdPolicy['AllowCreate'] = SAML2_Utils::parseBoolean($nameIdPolicy, 'AllowCreate', FALSE);
+ }
+ }
+ }
+
+
+ /**
+ * Retrieve the NameIdPolicy.
+ *
+ * @see SAML2_AuthnRequest::setNameIdPolicy()
+ * @return array The NameIdPolicy.
+ */
+ public function getNameIdPolicy() {
+ return $this->nameIdPolicy;
+ }
+
+
+ /**
+ * Set the NameIDPolicy.
+ *
+ * This function accepts an array with the following options:
+ * - 'Format'
+ * - 'SPNameQualifier'
+ * - 'AllowCreate'
+ *
+ * @param array $nameIdPolicy The NameIDPolicy.
+ */
+ public function setNameIdPolicy(array $nameIdPolicy) {
+
+ $this->nameIdPolicy = $nameIdPolicy;
+ }
+
+
+ /**
+ * Retrieve the value of the ForceAuthn attribute.
+ *
+ * @return bool The ForceAuthn attribute.
+ */
+ public function getForceAuthn() {
+ return $this->forceAuthn;
+ }
+
+
+ /**
+ * Set the value of the ForceAuthn attribute.
+ *
+ * @param bool $forceAuthn The ForceAuthn attribute.
+ */
+ public function setForceAuthn($forceAuthn) {
+ assert('is_bool($forceAuthn)');
+
+ $this->forceAuthn = $forceAuthn;
+ }
+
+
+ /**
+ * Retrieve the value of the IsPassive attribute.
+ *
+ * @return bool The IsPassive attribute.
+ */
+ public function getIsPassive() {
+ return $this->isPassive;
+ }
+
+
+ /**
+ * Set the value of the IsPassive attribute.
+ *
+ * @param bool $isPassive The IsPassive attribute.
+ */
+ public function setIsPassive($isPassive) {
+ assert('is_bool($isPassive)');
+
+ $this->isPassive = $isPassive;
+ }
+
+
+ /**
+ * Retrieve the value of the AssertionConsumerServiceURL attribute.
+ *
+ * @return string|NULL The AssertionConsumerServiceURL attribute.
+ */
+ public function getAssertionConsumerServiceURL() {
+ return $this->assertionConsumerServiceURL;
+ }
+
+
+ /**
+ * Set the value of the AssertionConsumerServiceURL attribute.
+ *
+ * @param string|NULL $assertionConsumerServiceURL The AssertionConsumerServiceURL attribute.
+ */
+ public function setAssertionConsumerServiceURL($assertionConsumerServiceURL) {
+ assert('is_string($assertionConsumerServiceURL) || is_null($assertionConsumerServiceURL)');
+
+ $this->assertionConsumerServiceURL = $assertionConsumerServiceURL;
+ }
+
+
+ /**
+ * Retrieve the value of the ProtocolBinding attribute.
+ *
+ * @return string|NULL The ProtocolBinding attribute.
+ */
+ public function getProtocolBinding() {
+ return $this->protocolBinding;
+ }
+
+
+ /**
+ * Set the value of the ProtocolBinding attribute.
+ *
+ * @param string $protocolBinding The ProtocolBinding attribute.
+ */
+ public function setProtocolBinding($protocolBinding) {
+ assert('is_string($protocolBinding) || is_null($protocolBinding)');
+
+ $this->protocolBinding = $protocolBinding;
+ }
+
+
+ /**
+ * Convert this authentication request to an XML element.
+ *
+ * @return DOMElement This authentication request.
+ */
+ public function toUnsignedXML() {
+
+ $root = parent::toUnsignedXML();
+
+ if ($this->forceAuthn) {
+ $root->setAttribute('ForceAuthn', 'true');
+ }
+
+ if ($this->isPassive) {
+ $root->setAttribute('IsPassive', 'true');
+ }
+
+ if ($this->assertionConsumerServiceURL !== NULL) {
+ $root->setAttribute('AssertionConsumerServiceURL', $this->assertionConsumerServiceURL);
+ }
+
+ if ($this->protocolBinding !== NULL) {
+ $root->setAttribute('ProtocolBinding', $this->protocolBinding);
+ }
+
+ if (!empty($this->nameIdPolicy)) {
+ $nameIdPolicy = $this->document->createElementNS(SAML2_Const::NS_SAMLP, 'NameIDPolicy');
+ if (array_key_exists('Format', $this->nameIdPolicy)) {
+ $nameIdPolicy->setAttribute('Format', $this->nameIdPolicy['Format']);
+ }
+ if (array_key_exists('SPNameQualifier', $this->nameIdPolicy)) {
+ $nameIdPolicy->setAttribute('SPNameQualifier', $this->nameIdPolicy['SPNameQualifier']);
+ }
+ if (array_key_exists('AllowCreate', $this->nameIdPolicy) && $this->nameIdPolicy['AllowCreate']) {
+ $nameIdPolicy->setAttribute('AllowCreate', 'true');
+ }
+ $root->appendChild($nameIdPolicy);
+ }
+
+ return $root;
+ }
+
+}
+
+
+?> \ No newline at end of file
diff --git a/lib/SAML2/Binding.php b/lib/SAML2/Binding.php
new file mode 100644
index 0000000..957ec4d
--- /dev/null
+++ b/lib/SAML2/Binding.php
@@ -0,0 +1,111 @@
+<?php
+
+/**
+ * Base class for SAML 2 bindings.
+ *
+ * @package simpleSAMLphp
+ * @version $Id$
+ */
+abstract class SAML2_Binding {
+
+ /**
+ * The destination of messages.
+ *
+ * This can be NULL, in which case the destination in the message is used.
+ */
+ protected $destination;
+
+
+ /**
+ * Retrieve a binding with the given URN.
+ *
+ * Will throw an exception if it is unable to locate the binding.
+ *
+ * @param string $urn The URN of the binding.
+ * @return SAML2_Binding The binding.
+ */
+ public static function getBinding($urn) {
+ assert('is_string($urn)');
+
+ switch ($urn) {
+ case SAML2_Const::BINDING_HTTP_POST:
+ return new SAML2_HTTPPost();
+ case SAML2_Const::BINDING_HTTP_REDIRECT:
+ return new SAML2_HTTPRedirect();
+ default:
+ throw new Exception('Unsupported binding: ' . var_export($urn, TRUE));
+ }
+ }
+
+
+ /**
+ * Guess the current binding.
+ *
+ * This function guesses the current binding and creates an instance
+ * of SAML2_Binding matching that binding.
+ *
+ * An exception will be thrown if it is unable to guess the binding.
+ *
+ * @return SAML2_Binding The binding.
+ */
+ public static function getCurrentBinding() {
+ switch ($_SERVER['REQUEST_METHOD']) {
+ case 'GET':
+ return new SAML2_HTTPRedirect();
+ case 'POST':
+ return new SAML2_HTTPPost();
+ default:
+ throw new Exception('Unable to find the current binding.');
+ }
+ }
+
+
+ /**
+ * Retrieve the destination of a message.
+ *
+ * @return string|NULL $destination The destination the message will be delivered to.
+ */
+ public function getDestination() {
+
+ return $this->destination;
+ }
+
+
+ /**
+ * Override the destination of a message.
+ *
+ * Set to NULL to use the destination set in the message.
+ *
+ * @param string|NULL $destination The destination the message should be delivered to.
+ */
+ public function setDestination($destination) {
+ assert('is_string($destination) || is_null($destination)');
+
+ $this->destination = $destination;
+ }
+
+
+ /**
+ * Send a SAML 2 message.
+ *
+ * This function will send a message using the specified binding.
+ * The message will be delivered to the destination set in the message.
+ *
+ * @param SAML2_Message $message The message which should be sent.
+ */
+ abstract public function send(SAML2_Message $message);
+
+
+ /**
+ * Receive a SAML 2 message.
+ *
+ * This function will extract the message from the current request.
+ * An exception will be thrown if we are unable to process the message.
+ *
+ * @return SAML2_Message The received message.
+ */
+ abstract public function receive();
+
+}
+
+?> \ No newline at end of file
diff --git a/lib/SAML2/Const.php b/lib/SAML2/Const.php
index cdb6aac..c88d61d 100644
--- a/lib/SAML2/Const.php
+++ b/lib/SAML2/Const.php
@@ -9,6 +9,17 @@
class SAML2_Const {
/**
+ * Password authentication context.
+ */
+ const AC_PASSWORD = 'urn:oasis:names:tc:SAML:2.0:ac:classes:Password';
+
+ /**
+ * Unspecified authentication context.
+ */
+ const AC_UNSPECIFIED = 'urn:oasis:names:tc:SAML:2.0:ac:classes:unspecified';
+
+
+ /**
* The URN for the HTTP-POST binding.
*/
const BINDING_HTTP_POST = 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST';
diff --git a/lib/SAML2/EncryptedAssertion.php b/lib/SAML2/EncryptedAssertion.php
new file mode 100644
index 0000000..5c22dfe
--- /dev/null
+++ b/lib/SAML2/EncryptedAssertion.php
@@ -0,0 +1,160 @@
+<?php
+
+/**
+ * Class handling encrypted assertions.
+ *
+ * @package simpleSAMLphp
+ * @version $Id$
+ */
+class SAML2_EncryptedAssertion {
+
+ /**
+ * The current encrypted assertion.
+ *
+ * @var DOMElement
+ */
+ private $encryptedData;
+
+
+ /**
+ * Constructor for SAML 2 encrypted assertions.
+ *
+ * @param DOMElement|NULL $xml The encrypted assertion XML element.
+ */
+ public function __construct(DOMElement $xml = NULL) {
+ if ($xml === NULL) {
+ return;
+ }
+
+ $data = SAML2_Utils::xpQuery($xml, './xenc:EncryptedData');
+ if (count($data) === 0) {
+ throw new Exception('Missing encrypted data in <saml:EncryptedAssertion>.');
+ } elseif (count($data) > 1) {
+ throw new Exception('More than one encrypted data element in <saml:EncryptedAssertion>.');
+ }
+ $this->encryptedData = $data[0];
+ }
+
+
+ /**
+ * Set the assertion.
+ *
+ * @param SAML2_Assertion $assertion The assertion.
+ * @param XMLSecurityKey $key The key we should use to encrypt the assertion.
+ */
+ public function setAssertion(SAML2_Assertion $assertion, XMLSecurityKey $key) {
+
+ $xml = $assertion->toXML();
+
+ $enc = new XMLSecEnc();
+ $enc->setNode($xml);
+ $enc->type = XMLSecEnc::Element;
+
+ switch ($key->type) {
+ case XMLSecurityKey::TRIPLEDES_CBC:
+ case XMLSecurityKey::AES128_CBC:
+ case XMLSecurityKey::AES192_CBC:
+ case XMLSecurityKey::AES256_CBC:
+ $symmetricKey = $key;
+ break;
+
+ case XMLSecurityKey::RSA_1_5:
+ $symmetricKey = new XMLSecurityKey(XMLSecurityKey::AES128_CBC);
+ $symmetricKey->generateSessionKey();
+
+ $enc->encryptKey($key, $symmetricKey);
+
+ break;
+
+ default:
+ throw new Exception('Unknown key type for encryption: ' . $key->type);
+ }
+
+ $this->encryptedData = $enc->encryptNode($symmetricKey);
+ }
+
+
+ /**
+ * Retrieve the assertion.
+ *
+ * @param XMLSecurityKey $key The key we should use to decrypt the assertion.
+ * @return SAML2_Assertion The decrypted assertion.
+ */
+ public function getAssertion(XMLSecurityKey $inputKey) {
+
+ $enc = new XMLSecEnc();
+
+ $enc->setNode($this->encryptedData);
+ $enc->type = $this->encryptedData->getAttribute("Type");
+
+ $symmetricKey = $enc->locateKey($this->encryptedData);
+ if (!$symmetricKey) {
+ throw new Exception('Could not locate key algorithm in encrypted data.');
+ }
+
+ $symmetricKeyInfo = $enc->locateKeyInfo($symmetricKey);
+ if (!$symmetricKeyInfo) {
+ throw new Exception('Could not locate <dsig:KeyInfo> for the encrypted key.');
+ }
+
+ if ($symmetricKeyInfo->isEncrypted) {
+ /* Make sure that the input key format is the same as the one used to encrypt the key. */
+ if ($inputKey->getAlgorith() !== $symmetricKeyInfo->getAlgorith()) {
+ throw new Exception('Algorithm mismatch between input key and key used to encrypt ' .
+ ' the symmetric key for the message. Key was: ' .
+ var_export($inputKey->getAlgorith(), TRUE) . '; message was: ' .
+ var_export($symmetricKeyInfo->getAlgorith(), TRUE));
+ }
+
+ $encKey = $symmetricKeyInfo->encryptedCtx;
+ $symmetricKeyInfo->key = $inputKey->key;
+ $key = $encKey->decryptKey($symmetricKeyInfo);
+ $symmetricKey->loadkey($key);
+ } else {
+ /* Make sure that the input key has the correct format. */
+ if ($inputKey->getAlgorith() !== $symmetricKey->getAlgorith()) {
+ throw new Exception('Algorithm mismatch between input key and key in message. ' .
+ 'Key was: ' . var_export($inputKey->getAlgorith(), TRUE) . '; message was: ' .
+ var_export($symmetricKey->getAlgorith(), TRUE));
+ }
+ $symmetricKey = $inputKey;
+ }
+
+ $decrypted = $enc->decryptNode($symmetricKey, FALSE);
+
+ /*
+ * This is a workaround for the case where only a subset of the XML
+ * tree was serialized for encryption. In that case, we may miss the
+ * namespaces needed to parse the XML.
+ */
+ $xml = '<root xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">'.$decrypted.'</root>';
+ $newDoc = new DOMDocument();
+ if (!$newDoc->loadXML($xml)) {
+ throw new Exception('Failed to parse decrypted XML. Maybe the wrong sharedkey was used?');
+ }
+ $assertionXML = $newDoc->firstChild->firstChild;
+ if ($assertionXML === NULL) {
+ throw new Exception('Missing encrypted assertion within <saml:EncryptedAssertion>.');
+ }
+ return new SAML2_Assertion($assertionXML);
+ }
+
+
+ /**
+ * Convert this encrypted assertion to an XML element.
+ *
+ * @return DOMElement This encrypted assertion.
+ */
+ public function toXML() {
+
+ $document = new DOMDocument();
+
+ $root = $document->createElementNS(SAML2_Const::NS_SAML, 'saml:' . 'EncryptedAssertion');
+ $document->appendChild($root);
+
+ $root->appendChild($document->importNode($this->encryptedData, TRUE));
+
+ return $root;
+ }
+
+} \ No newline at end of file
diff --git a/lib/SAML2/HTTPPost.php b/lib/SAML2/HTTPPost.php
new file mode 100644
index 0000000..3a0a85c
--- /dev/null
+++ b/lib/SAML2/HTTPPost.php
@@ -0,0 +1,104 @@
+<?php
+
+/**
+ * Class which implements the HTTP-POST binding.
+ *
+ * @package simpleSAMLphp
+ * @version $Id$
+ */
+class SAML2_HTTPPost extends SAML2_Binding {
+
+ /**
+ * Send a SAML 2 message using the HTTP-POST binding.
+ *
+ * Note: This function never returns.
+ *
+ * @param SAML2_Message $message The message we should send.
+ */
+ public function send(SAML2_Message $message) {
+
+ if ($this->destination === NULL) {
+ $destination = $message->getDestination();
+ } else {
+ $destination = $this->destination;
+ }
+ $relayState = $message->getRelayState();
+
+ $msgStr = $message->toSignedXML();
+ $msgStr = $msgStr->ownerDocument->saveXML($msgStr);
+ $msgStr = base64_encode($msgStr);
+ $msgStr = htmlspecialchars($msgStr);
+
+ if ($message instanceof SAML2_Request) {
+ $msgType = 'SAMLRequest';
+ } else {
+ $msgType = 'SAMLResponse';
+ }
+
+ $destination = htmlspecialchars($destination);
+
+ if ($relayState !== NULL) {
+ $relayState = '<input type="hidden" name="RelayState" value="' . htmlspecialchars($relayState) . '">';
+ } else {
+ $relayState = '';
+ }
+
+ $out = <<<END
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+<meta http-equiv="content-type" content="text/html; charset=utf-8" />
+<title>POST data</title>
+</head>
+<body onload="document.forms[0].submit()">
+<noscript>
+<p><strong>Note:</strong> Since your browser does not support JavaScript, you must press the button below once to proceed.</p>
+</noscript>
+<form method="post" action="$destination">
+<input type="hidden" name="$msgType" value="$msgStr" />
+$relayState
+<noscript><input type="submit" value="Submit" /></noscript>
+</form>
+</body>
+</html>
+END;
+ echo($out);
+ exit(0);
+ }
+
+
+ /**
+ * Receive a SAML 2 message sent using the HTTP-POST binding.
+ *
+ * Throws an exception if it is unable receive the message.
+ *
+ * @return SAML2_Message The received message.
+ */
+ public function receive() {
+
+ if (array_key_exists('SAMLRequest', $_POST)) {
+ $msg = $_POST['SAMLRequest'];
+ } elseif (array_key_exists('SAMLResponse', $_POST)) {
+ $msg = $_POST['SAMLResponse'];
+ } else {
+ throw new Exception('Missing SAMLRequest or SAMLResponse parameter.');
+ }
+
+ $msg = base64_decode($msg);
+
+ $document = new DOMDocument();
+ $document->loadXML($msg);
+ $xml = $document->firstChild;
+
+ $msg = SAML2_Message::fromXML($xml);
+
+ if (array_key_exists('RelayState', $_POST)) {
+ $msg->setRelayState($_POST['RelayState']);
+ }
+
+ return $msg;
+ }
+
+}
+
+?> \ No newline at end of file
diff --git a/lib/SAML2/HTTPRedirect.php b/lib/SAML2/HTTPRedirect.php
new file mode 100644
index 0000000..71a32b4
--- /dev/null
+++ b/lib/SAML2/HTTPRedirect.php
@@ -0,0 +1,229 @@
+<?php
+
+/**
+ * Class which implements the HTTP-Redirect binding.
+ *
+ * @package simpleSAMLphp
+ * @version $Id$
+ */
+class SAML2_HTTPRedirect extends SAML2_Binding {
+
+ const DEFLATE = 'urn:oasis:names:tc:SAML:2.0:bindings:URL-Encoding:DEFLATE';
+
+ /**
+ * Create the redirect URL for a message.
+ *
+ * @param SAML2_Message $message The message.
+ * @return string The URL the user should be redirected to in order to send a message.
+ */
+ public function getRedirectURL(SAML2_Message $message) {
+
+ if ($this->destination === NULL) {
+ $destination = $message->getDestination();
+ } else {
+ $destination = $this->destination;
+ }
+
+ $relayState = $message->getRelayState();
+
+ $key = $message->getSignatureKey();
+
+ $msgStr = $message->toUnsignedXML();
+ $msgStr = $msgStr->ownerDocument->saveXML($msgStr);
+ $msgStr = gzdeflate($msgStr);
+ $msgStr = base64_encode($msgStr);
+
+ /* Build the query string. */
+
+ if ($message instanceof SAML2_Request) {
+ $msg = 'SAMLRequest=';
+ } else {
+ $msg = 'SAMLResponse=';
+ }
+ $msg .= urlencode($msgStr);
+
+ if ($relayState !== NULL) {
+ $msg .= '&RelayState=' . urlencode($relayState);
+ }
+
+ if ($key !== NULL) {
+ /* Add the signature. */
+ $msg .= '&SigAlg=' . urlencode(XMLSecurityKey::RSA_SHA1);
+
+ $signature = $key->signData($msg);
+ $msg .= '&Signature=' . urlencode(base64_encode($signature));
+ }
+
+ if (strpos($destination, '?') === FALSE) {
+ $destination .= '?' . $msg;
+ } else {
+ $destination .= '&' . $msg;
+ }
+
+ return $destination;
+ }
+
+
+ /**
+ * Send a SAML 2 message using the HTTP-Redirect binding.
+ *
+ * Note: This function never returns.
+ *
+ * @param SAML2_Message $message The message we should send.
+ */
+ public function send(SAML2_Message $message) {
+
+ $destination = $this->getRedirectURL($message);
+ SimpleSAML_Utilities::redirect($destination);
+ }
+
+
+ /**
+ * Receive a SAML 2 message sent using the HTTP-Redirect binding.
+ *
+ * Throws an exception if it is unable receive the message.
+ *
+ * @return SAML2_Message The received message.
+ */
+ public function receive() {
+
+ $data = self::parseQuery();
+
+ if (array_key_exists('SAMLRequest', $data)) {
+ $msg = $data['SAMLRequest'];
+ } elseif (array_key_exists('SAMLResponse', $data)) {
+ $msg = $data['SAMLResponse'];
+ } else {
+ throw new Execption('Missing SAMLRequest or SAMLResponse parameter.');
+ }
+
+ if (array_key_exists('SAMLEncoding', $data)) {
+ $encoding = $data['SAMLEncoding'];
+ } else {
+ $encoding = self::DEFLATE;
+ }
+
+ $msg = base64_decode($msg);
+ switch ($encoding) {
+ case self::DEFLATE:
+ $msg = gzinflate($msg);
+ break;
+ default:
+ throw new Exception('Unknown SAMLEncoding: ' . var_export($encoding, TRUE));
+ }
+
+ $document = new DOMDocument();
+ $document->loadXML($msg);
+ $xml = $document->firstChild;
+
+ $msg = SAML2_Message::fromXML($xml);
+
+ if (array_key_exists('Signature', $data)) {
+ /* Save the signature validation data until we need it. */
+ $signatureValidationData = array(
+ 'Signature' => $data['Signature'],
+ 'Query' => $data['SignedQuery'],
+ );
+ }
+
+
+ if (array_key_exists('RelayState', $data)) {
+ $msg->setRelayState($data['RelayState']);
+ }
+
+ if (array_key_exists('Signature', $data)) {
+ if (!array_key_exists('SigAlg', $data)) {
+ throw new Exception('Missing signature algorithm.');
+ }
+
+ $signData = array(
+ 'Signature' => $data['Signature'],
+ 'SigAlg' => $data['SigAlg'],
+ 'Query' => $data['SignedQuery'],
+ );
+ $msg->addValidator(array(get_class($this), 'validateSignature'), $signData);
+ }
+
+ return $msg;
+ }
+
+
+ /**
+ * Helper function to parse query data.
+ *
+ * This function returns the query string split into key=>value pairs.
+ * It also adds a new parameter, SignedQuery, which contains the data that is
+ * signed.
+ *
+ * @return string The query data that is signed.
+ */
+ private static function parseQuery() {
+ /*
+ * Parse the query string. We need to do this ourself, so that we get access
+ * to the raw (urlencoded) values. This is required because different software
+ * can urlencode to different values.
+ */
+ $data = array();
+ $relayState = '';
+ $sigAlg = '';
+ foreach (explode('&', $_SERVER['QUERY_STRING']) as $e) {
+ list($name, $value) = explode('=', $e, 2);
+ $name = urldecode($name);
+ $data[$name] = urldecode($value);
+
+ switch ($name) {
+ case 'SAMLRequest':
+ case 'SAMLResponse':
+ $sigQuery = $name . '=' . $value;
+ break;
+ case 'RelayState':
+ $relayState = '&RelayState=' . $value;
+ break;
+ case 'SigAlg':
+ $sigAlg = '&SigAlg=' . $value;
+ break;
+ }
+ }
+
+ $data['SignedQuery'] = $sigQuery . $relayState . $sigAlg;
+
+ return $data;
+ }
+
+
+ /**
+ * Validate the signature on a HTTP-Redirect message.
+ *
+ * Throws an exception if we are unable to validate the signature.
+ *
+ * @param array $data The data we need to validate the query string.
+ * @param XMLSecurityKey $key The key we should validate the query against.
+ */
+ public static function validateSignature(array $data, XMLSecurityKey $key) {
+ assert('array_key_exists("Query", $data)');
+ assert('array_key_exists("SigAlg", $data)');
+ assert('array_key_exists("Signature", $data)');
+
+ $query = $data['Query'];
+ $sigAlg = $data['SigAlg'];
+ $signature = $data['Signature'];
+
+ $signature = base64_decode($signature);
+
+ switch ($sigAlg) {
+ case XMLSecurityKey::RSA_SHA1:
+ if ($key->type !== XMLSecurityKey::RSA_SHA1) {
+ throw new Exception('Invalid key type for validating signature on query string.');
+ }
+ if (!$key->verifySignature($query,$signature)) {
+ throw new Exception('Unable to validate signature on query string.');
+ }
+ break;
+ default:
+ throw new Exception('Unknown signature algorithm: ' . var_export($sigAlg, TRUE));
+ }
+ }
+
+}
+
+?> \ No newline at end of file
diff --git a/lib/SAML2/LogoutRequest.php b/lib/SAML2/LogoutRequest.php
new file mode 100644
index 0000000..f239c9a
--- /dev/null
+++ b/lib/SAML2/LogoutRequest.php
@@ -0,0 +1,123 @@
+<?php
+
+/**
+ * Class for SAML 2 logout request messages.
+ *
+ * @package simpleSAMLphp
+ * @version $Id$
+ */
+class SAML2_LogoutRequest extends SAML2_Request {
+
+
+ /**
+ * The name identifier of the session that should be terminated.
+ *
+ * @var array
+ */
+ private $nameId;
+
+
+ /**
+ * The session index of the session that should be terminated.
+ *
+ * @var string|NULL
+ */
+ private $sessionIndex;
+
+
+ /**
+ * Constructor for SAML 2 logout request messages.
+ *
+ * @param DOMElement|NULL $xml The input message.
+ */
+ public function __construct(DOMElement $xml = NULL) {
+ parent::__construct('LogoutRequest', $xml);
+
+ if ($xml === NULL) {
+ return;
+ }
+
+ $nameId = SAML2_Utils::xpQuery($xml, './saml:NameID');
+ if (empty($nameId)) {
+ throw new Exception('Missing NameID in logout request.');
+ }
+ $this->nameId = SAML2_Utils::parseNameId($nameId[0]);
+
+ $sessionIndex = SAML2_Utils::xpQuery($xml, './samlp:SessionIndex');
+ if (!empty($sessionIndex)) {
+ $this->sessionIndex = $sessionIndex[0]->textContent;
+ }
+ }
+
+
+ /**
+ * Retrieve the name identifier of the session that should be terminated.
+ *
+ * @return array The name identifier of the session that should be terminated.
+ */
+ public function getNameId() {
+ return $this->nameId;
+ }
+
+
+ /**
+ * Set the name identifier of the session that should be terminated.
+ *
+ * The name identifier must be in the format accepted by SAML2_message::buildNameId().
+ *
+ * @see SAML2_message::buildNameId()
+ * @param array $nameId The name identifier of the session that should be terminated.
+ */
+ public function setNameId($nameId) {
+ assert('is_array($nameId)');
+
+ $this->nameId = $nameId;
+ }
+
+
+ /**
+ * Retrieve the sesion index of the session that should be terminated.
+ *
+ * @return string|NULL The sesion index of the session that should be terminated.
+ */
+ public function getSessionIndex() {
+ return $this->sessionIndex;
+ }
+
+
+ /**
+ * Set the sesion index of the session that should be terminated.
+ *
+ * @param string|NULL $sessionIndex The sesion index of the session that should be terminated.
+ */
+ public function setSessionIndex($sessionIndex) {
+ assert('is_string($sessionIndex)');
+
+ $this->sessionIndex = $sessionIndex;
+ }
+
+
+ /**
+ * Convert this logout request message to an XML element.
+ *
+ * @return DOMElement This logout request.
+ */
+ public function toUnsignedXML() {
+
+ $root = parent::toUnsignedXML();
+
+ SAML2_Utils::addNameId($root, $this->nameId);
+
+ if ($this->sessionIndex !== NULL) {
+ $sessionIndex = $this->document->createElementNS(SAML2_Const::NS_SAMLP, 'SessionIndex');
+ $sessionIndex->appendChild($this->document->createTextNode($this->sessionIndex));
+ $root->appendChild($sessionIndex);
+ }
+
+ return $root;
+ }
+
+}
+
+
+?> \ No newline at end of file
diff --git a/lib/SAML2/LogoutResponse.php b/lib/SAML2/LogoutResponse.php
new file mode 100644
index 0000000..b242d67
--- /dev/null
+++ b/lib/SAML2/LogoutResponse.php
@@ -0,0 +1,25 @@
+<?php
+
+/**
+ * Class for SAML 2 LogoutResponse messages.
+ *
+ * @package simpleSAMLphp
+ * @version $Id$
+ */
+class SAML2_LogoutResponse extends SAML2_StatusResponse {
+
+ /**
+ * Constructor for SAML 2 response messages.
+ *
+ * @param string $tagName The tag name of the root element.
+ * @param DOMElement|NULL $xml The input message.
+ */
+ public function __construct(DOMElement $xml = NULL) {
+ parent::__construct('LogoutResponse', $xml);
+
+ /* No new fields added by LogoutResponse. */
+ }
+
+}
+
+?> \ No newline at end of file
diff --git a/lib/SAML2/Message.php b/lib/SAML2/Message.php
new file mode 100644
index 0000000..2a4c262
--- /dev/null
+++ b/lib/SAML2/Message.php
@@ -0,0 +1,474 @@
+<?php
+
+/**
+ * Base class for all SAML 2 messages.
+ *
+ * Implements what is common between the samlp:RequestAbstractType and
+ * samlp:StatusResponseType element types.
+ *
+ * @package simpleSAMLphp
+ * @version $Id$
+ */
+abstract class SAML2_Message implements SAML2_SignedElement {
+
+ /**
+ * The name of the root element of the DOM tree for the message.
+ *
+ * Used when creating a DOM tree from the message.
+ *
+ * @var string
+ */
+ private $tagName;
+
+
+ /**
+ * The identifier of this message.
+ *
+ * @var string
+ */
+ private $id;
+
+
+ /**
+ * The issue timestamp of this message, as an UNIX timestamp.
+ *
+ * @var int
+ */
+ private $issueInstant;
+
+
+ /**
+ * The destination URL of this message if it is known.
+ *
+ * @var string|NULL
+ */
+ private $destination;
+
+
+ /**
+ * The entity id of the issuer of this message, or NULL if unknown.
+ *
+ * @var string|NULL
+ */
+ private $issuer;
+
+
+ /**
+ * The RelayState associated with this message.
+ *
+ * @var string|NULL
+ */
+ private $relayState;
+
+
+ /**
+ * The DOMDocument we are currently building.
+ *
+ * This variable is used while generating XML from this message. It holds the
+ * DOMDocument of the XML we are generating.
+ *
+ * @var DOMDocument
+ */
+ protected $document;
+
+
+ /**
+ * The private key we should use to sign the message.
+ *
+ * The private key can be NULL, in which case the message is sent unsigned.
+ *
+ * @var XMLSecurityKey|NULL
+ */
+ private $signatureKey;
+
+
+ /**
+ * List of certificates that should be included in the message.
+ *
+ * @var array
+ */
+ private $certificates;
+
+
+ /**
+ * Available methods for validating this message.
+ *
+ * @var array
+ */
+ private $validators;
+
+
+ /**
+ * Initialize a message.
+ *
+ * This constructor takes an optional parameter with a DOMElement. If this
+ * parameter is given, the message will be initialized with data from that
+ * XML element.
+ *
+ * If no XML element is given, the message is initialized with suitable
+ * default values.
+ *
+ * @param string $tagName The tag name of the root element.
+ * @param DOMElement|NULL $xml The input message.
+ */
+ protected function __construct($tagName, DOMElement $xml = NULL) {
+ assert('is_string($tagName)');
+ $this->tagName = $tagName;
+
+ $this->id = SimpleSAML_Utilities::generateID();
+ $this->issueInstant = time();
+ $this->certificates = array();
+ $this->validators = array();
+
+ if ($xml === NULL) {
+ return;
+ }
+
+ if (!$xml->hasAttribute('ID')) {
+ throw new Exception('Missing ID attribute on SAML message.');
+ }
+ $this->id = $xml->getAttribute('ID');
+
+ if ($xml->getAttribute('Version') !== '2.0') {
+ /* Currently a very strict check. */
+ throw new Exception('Unsupported version: ' . $xml->getAttribute('Version'));
+ }
+
+ $this->issueInstant = SimpleSAML_Utilities::parseSAML2Time($xml->getAttribute('IssueInstant'));
+
+ if ($xml->hasAttribute('Destination')) {
+ $this->destination = $xml->getAttribute('Destination');
+ }
+
+ $issuer = SAML2_Utils::xpQuery($xml, './saml:Issuer');
+ if (!empty($issuer)) {
+ $this->issuer = $issuer[0]->textContent;
+ }
+
+
+ /* Validate the signature element of the message. */
+ $sig = SAML2_Utils::validateElement($xml);
+ if ($sig !== FALSE) {
+ $this->certificates = $sig['Certificates'];
+ $this->validators[] = array(
+ 'Function' => array('SAML2_Utils', 'validateSignature'),
+ 'Data' => $sig,
+ );
+ }
+
+ }
+
+
+ /**
+ * Add a method for validating this message.
+ *
+ * This function is used by the HTTP-Redirect binding, to make it possible to
+ * check the signature against the one included in the query string.
+ *
+ * @param callback $function The function which should be called.
+ * @param mixed $data The data that should be included as the first parameter to the function.
+ */
+ public function addValidator($function, $data) {
+ assert('is_callable($function)');
+
+ $this->validators[] = array(
+ 'Function' => $function,
+ 'Data' => $data,
+ );
+ }
+
+
+ /**
+ * Validate this message against a public key.
+ *
+ * TRUE is returned on success, FALSE is returned if we don't have any
+ * signature we can validate. An exception is thrown if the signature
+ * validation fails.
+ *
+ * @param XMLSecurityKey $key The key we should check against.
+ * @return boolean TRUE on success, FALSE when we don't have a signature.
+ */
+ public function validate(XMLSecurityKey $key) {
+
+ if (count($this->validators) === 0) {
+ return FALSE;
+ }
+
+ $exceptions = array();
+
+ foreach ($this->validators as $validator) {
+ $function = $validator['Function'];
+ $data = $validator['Data'];
+
+ try {
+ call_user_func($function, $data, $key);
+ /* We were able to validate the message with this validator. */
+ return TRUE;
+ } catch (Exception $e) {
+ $exceptions[] = $e;
+ }
+ }
+
+ /* No validators were able to validate the message. */
+ throw $exceptions[0];
+ }
+
+
+ /**
+ * Retrieve the identifier of this message.
+ *
+ * @return string The identifier of this message.
+ */
+ public function getId() {
+ return $this->id;
+ }
+
+
+ /**
+ * Set the identifier of this message.
+ *
+ * @param string $id The new identifier of this message.
+ */
+ public function setId($id) {
+ assert('is_string($id)');
+
+ $this->id = $id;
+ }
+
+
+ /**
+ * Retrieve the issue timestamp of this message.
+ *
+ * @return int The issue timestamp of this message, as an UNIX timestamp.
+ */
+ public function getIssueInstant() {
+ return $this->issueInstant;
+ }
+
+
+ /**
+ * Set the issue timestamp of this message.
+ *
+ * @param int $issueInstant The new issue timestamp of this message, as an UNIX timestamp.
+ */
+ public function setIssueInstant($issueInstant) {
+ assert('is_int($issueInstant)');
+
+ $this->issueInstant = $issueInstant;
+ }
+
+
+ /**
+ * Retrieve the destination of this message.
+ *
+ * @return string|NULL The destination of this message, or NULL if no destination is given.
+ */
+ public function getDestination() {
+ return $this->destination;
+ }
+
+
+ /**
+ * Set the destination of this message.
+ *
+ * @param string|NULL $destination The new destination of this message.
+ */
+ public function setDestination($destination) {
+ assert('is_string($destination) || is_null($destination)');
+
+ $this->destination = $destination;
+ }
+
+
+ /**
+ * Retrieve the issuer if this message.
+ *
+ * @return string|NULL The issuer of this message, or NULL if no issuer is given.
+ */
+ public function getIssuer() {
+ return $this->issuer;
+ }
+
+
+ /**
+ * Set the issuer of this message.
+ *
+ * @param string|NULL $issuer The new issuer of this message.
+ */
+ public function setIssuer($issuer) {
+ assert('is_string($issuer) || is_null($issuer)');
+
+ $this->issuer = $issuer;
+ }
+
+
+ /**
+ * Retrieve the RelayState associated with this message.
+ *
+ * @return string|NULL The RelayState, or NULL if no RelayState is given.
+ */
+ public function getRelayState() {
+ return $this->relayState;
+ }
+
+
+ /**
+ * Set the RelayState associated with this message.
+ *
+ * @param string|NULL $relayState The new RelayState.
+ */
+ public function setRelayState($relayState) {
+ assert('is_string($relayState) || is_null($relayState)');
+
+ $this->relayState = $relayState;
+ }
+
+
+ /**
+ * Convert this message to an unsigned XML document.
+ *
+ * This method does not sign the resulting XML document.
+ *
+ * @return DOMElement The root element of the DOM tree.
+ */
+ public function toUnsignedXML() {
+
+ $this->document = new DOMDocument();
+
+ $root = $this->document->createElementNS(SAML2_Const::NS_SAMLP, 'samlp:' . $this->tagName);
+ $this->document->appendChild($root);
+
+ /* Ugly hack to add another namespace declaration to the root element. */
+ $root->setAttributeNS(SAML2_Const::NS_SAML, 'saml:tmp', 'tmp');
+ $root->removeAttributeNS(SAML2_Const::NS_SAML, 'tmp');
+
+ $root->setAttribute('ID', $this->id);
+ $root->setAttribute('Version', '2.0');
+ $root->setAttribute('IssueInstant', gmdate('Y-m-d\TH:i:s\Z', $this->issueInstant));
+
+ if ($this->destination !== NULL) {
+ $root->setAttribute('Destination', $this->destination);
+ }
+
+ if ($this->issuer !== NULL) {
+ $issuer = $this->document->createElementNS(SAML2_Const::NS_SAML, 'saml:Issuer');
+ $issuer->appendChild($this->document->createTextNode($this->issuer));
+ $root->appendChild($issuer);
+ }
+
+ return $root;
+ }
+
+
+ /**
+ * Convert this message to a signed XML document.
+ *
+ * This method sign the resulting XML document if the private key for
+ * the signature is set.
+ *
+ * @return DOMElement The root element of the DOM tree.
+ */
+ public function toSignedXML() {
+
+ $root = $this->toUnsignedXML();
+
+ if ($this->signatureKey === NULL) {
+ /* We don't have a key to sign it with. */
+ return $root;
+ }
+
+
+ /* Find the position we should insert the signature node at. */
+ if ($this->issuer !== NULL) {
+ /*
+ * We have an issuer node. The signature node should come
+ * after the issuer node.
+ */
+ $issuerNode = $root->firstChild;
+ $insertBefore = $issuerNode->nextSibling;
+ } else {
+ /* No issuer node - the signature element should be the first element. */
+ $insertBefore = $root->firstChild;
+ }
+
+
+ SAML2_Utils::insertSignature($this->signatureKey, $this->certificates, $root, $insertBefore);
+
+ return $root;
+ }
+
+
+ /**
+ * Retrieve the private key we should use to sign the message.
+ *
+ * @return XMLSecurityKey|NULL The key, or NULL if no key is specified.
+ */
+ public function getSignatureKey() {
+ return $this->signatureKey;
+ }
+
+
+ /**
+ * Set the private key we should use to sign the message.
+ *
+ * If the key is NULL, the message will be sent unsigned.
+ *
+ * @param XMLSecurityKey|NULL $key
+ */
+ public function setSignatureKey(XMLsecurityKey $signatureKey = NULL) {
+ $this->signatureKey = $signatureKey;
+ }
+
+
+ /**
+ * Set the certificates that should be included in the message.
+ *
+ * The certificates should be strings with the PEM encoded data.
+ *
+ * @param array $certificates An array of certificates.
+ */
+ public function setCertificates(array $certificates) {
+ $this->certificates = $certificates;
+ }
+
+
+ /**
+ * Retrieve the certificates that are included in the message.
+ *
+ * @return array An array of certificates.
+ */
+ public function getCertificates() {
+ return $this->certificates;
+ }
+
+
+ /**
+ * Convert an XML element into a message.
+ *
+ * @param DOMElement $xml The root XML element.
+ * @return SAML2_Message The message.
+ */
+ public static function fromXML(DOMElement $xml) {
+
+ if ($xml->namespaceURI !== SAML2_Const::NS_SAMLP) {
+ throw new Exception('Unknown namespace of SAML message: ' . var_export($xml->namespaceURI, TRUE));
+ }
+
+ switch ($xml->localName) {
+ case 'AuthnRequest':
+ return new SAML2_AuthnRequest($xml);
+ case 'LogoutResponse':
+ return new SAML2_LogoutResponse($xml);
+ case 'LogoutRequest':
+ return new SAML2_LogoutRequest($xml);
+ case 'Response':
+ return new SAML2_Response($xml);
+ default:
+ throw new Exception('Unknown SAML message: ' . var_export($xml->localName, TRUE));
+ }
+
+ }
+
+}
+
+?> \ No newline at end of file
diff --git a/lib/SAML2/Request.php b/lib/SAML2/Request.php
new file mode 100644
index 0000000..a38f51f
--- /dev/null
+++ b/lib/SAML2/Request.php
@@ -0,0 +1,17 @@
+<?php
+
+/**
+ * Base class for all SAML 2 request messages.
+ *
+ * Implements samlp:RequestAbstractType. All of the elements in that type is
+ * stored in the SAML2_Message class, and this class is therefore empty. It
+ * is included mainly to make it easy to separate requests from responses.
+ *
+ * @package simpleSAMLphp
+ * @version $Id$
+ */
+abstract class SAML2_Request extends SAML2_Message {
+
+}
+
+?> \ No newline at end of file
diff --git a/lib/SAML2/Response.php b/lib/SAML2/Response.php
new file mode 100644
index 0000000..d45c73f
--- /dev/null
+++ b/lib/SAML2/Response.php
@@ -0,0 +1,86 @@
+<?php
+
+/**
+ * Class for SAML 2 Response messages.
+ *
+ * @package simpleSAMLphp
+ * @version $Id$
+ */
+class SAML2_Response extends SAML2_StatusResponse {
+
+ /**
+ * The assertions in this response.
+ */
+ private $assertions;
+
+
+ /**
+ * Constructor for SAML 2 response messages.
+ *
+ * @param DOMElement|NULL $xml The input message.
+ */
+ public function __construct(DOMElement $xml = NULL) {
+ parent::__construct('Response', $xml);
+
+ $this->assertions = array();
+
+ if ($xml === NULL) {
+ return;
+ }
+
+ for ($node = $xml->firstChild; $node !== NULL; $node = $node->nextSibling) {
+ if ($node->namespaceURI !== SAML2_Const::NS_SAML) {
+ continue;
+ }
+
+ if ($node->localName === 'Assertion') {
+ $this->assertions[] = new SAML2_Assertion($node);
+ } elseif($node->localName === 'EncryptedAssertion') {
+ $this->assertions[] = new SAML2_EncryptedAssertion($node);
+ }
+ }
+ }
+
+
+ /**
+ * Retrieve the assertions in this response.
+ *
+ * @return array Array of SAML2_Assertion and SAML2_EncryptedAssertion objects.
+ */
+ public function getAssertions() {
+ return $this->assertions;
+ }
+
+
+ /**
+ * Set the assertions that should be included in this response.
+ *
+ * @param array The assertions.
+ */
+ public function setAssertions(array $assertions) {
+
+ $this->assertions = $assertions;
+ }
+
+
+ /**
+ * Convert the response message to an XML element.
+ *
+ * @return DOMElement This response.
+ */
+ public function toUnsignedXML() {
+
+ $root = parent::toUnsignedXML();
+
+ foreach ($this->assertions as $assertion) {
+ $node = $assertion->toXML();
+ $node = $root->ownerDocument->importNode($node, TRUE);
+ $root->appendChild($node);
+ }
+
+ return $root;
+ }
+
+}
+
+?> \ No newline at end of file
diff --git a/lib/SAML2/SignedElement.php b/lib/SAML2/SignedElement.php
new file mode 100644
index 0000000..aef6eaf
--- /dev/null
+++ b/lib/SAML2/SignedElement.php
@@ -0,0 +1,31 @@
+<?php
+
+
+/**
+ * Interface to a SAML 2 element which may be signed.
+ *
+ * @package simpleSAMLphp
+ * @version $Id$
+ */
+interface SAML2_SignedElement {
+
+ /**
+ * Validate this element against a public key.
+ *
+ * If no signature is present, FALSE is returned. If a signature is present,
+ * but cannot be verified, an exception will be thrown.
+ *
+ * @param XMLSecurityKey $key The key we should check against.
+ * @return boolean TRUE if successful, FALSE if we don't have a signature that can be verified.
+ */
+ public function validate(XMLSecurityKey $key);
+
+
+ /**
+ * Retrieve the certificates that are included in the element (if any).
+ *
+ * @return array An array of certificates.
+ */
+ public function getCertificates();
+
+} \ No newline at end of file
diff --git a/lib/SAML2/StatusResponse.php b/lib/SAML2/StatusResponse.php
new file mode 100644
index 0000000..8093da3
--- /dev/null
+++ b/lib/SAML2/StatusResponse.php
@@ -0,0 +1,195 @@
+<?php
+
+/**
+ * Base class for all SAML 2 response messages.
+ *
+ * Implements samlp:StatusResponseType. All of the elements in that type is
+ * stored in the SAML2_Message class, and this class is therefore more
+ * or less empty. It is included mainly to make it easy to separate requests from
+ * responses.
+ *
+ * The status code is represented as an array on the following form:
+ * array(
+ * 'Code' => '<top-level status code>',
+ * 'SubCode' => '<second-level status code>',
+ * 'Message' => '<status message>',
+ * )
+ *
+ * Only the 'Code' field is required. The others will be set to NULL if they
+ * aren't present.
+ *
+ * @package simpleSAMLphp
+ * @version $Id$
+ */
+abstract class SAML2_StatusResponse extends SAML2_Message {
+
+ /**
+ * The ID of the request this is a response to, or NULL if this is an unsolicited response.
+ *
+ * @var string|NULL
+ */
+ private $inResponseTo;
+
+
+ /**
+ * The status code of the response.
+ *
+ * @var array
+ */
+ private $status;
+
+
+ /**
+ * Constructor for SAML 2 response messages.
+ *
+ * @param string $tagName The tag name of the root element.
+ * @param DOMElement|NULL $xml The input message.
+ */
+ protected function __construct($tagName, DOMElement $xml = NULL) {
+ parent::__construct($tagName, $xml);
+
+ $this->status = array(
+ 'Code' => SAML2_Const::STATUS_SUCCESS,
+ 'SubCode' => NULL,
+ 'Message' => NULL,
+ );
+
+ if ($xml === NULL) {
+ return;
+ }
+
+ if ($xml->hasAttribute('InResponseTo')) {
+ $this->inResponseTo = $xml->getAttribute('InResponseTo');
+ }
+
+ $status = SAML2_Utils::xpQuery($xml, './samlp:Status');
+ if (empty($status)) {
+ throw new Exception('Missing status code on response.');
+ }
+ $status = $status[0];
+
+ $statusCode = SAML2_Utils::xpQuery($status, './samlp:StatusCode');
+ if (empty($statusCode)) {
+ throw new Exception('Missing status code in status element.');
+ }
+ $statusCode = $statusCode[0];
+
+ $this->status['Code'] = $statusCode->getAttribute('Value');
+
+ $subCode = SAML2_Utils::xpQuery($statusCode, './samlp:StatusCode');
+ if (!empty($subCode)) {
+ $this->status['SubCode'] = $subCode[0]->getAttribute('Value');
+ }
+
+ $message = SAML2_Utils::xpQuery($status, './samlp:StatusMessage');
+ if (!empty($message)) {
+ $this->status['Message'] = $message[0]->textContent;
+ }
+ }
+
+
+ /**
+ * Determine whether this is a successful response.
+ *
+ * @return boolean TRUE if the status code is success, FALSE if not.
+ */
+ public function isSuccess() {
+ assert('array_key_exists("Code", $this->status)');
+
+ if ($this->status['Code'] === SAML2_Const::STATUS_SUCCESS) {
+ return TRUE;
+ }
+
+ return FALSE;
+ }
+
+
+ /**
+ * Retrieve the ID of the request this is a response to.
+ *
+ * @return string|NULL The ID of the request.
+ */
+ public function getInResponseTo() {
+ return $this->inResponseTo;
+ }
+
+
+ /**
+ * Set the ID of the request this is a response to.
+ *
+ * @param string|NULL $inResponseTo The ID of the request.
+ */
+ public function setInResponseTo($inResponseTo) {
+ assert('is_string($inResponseTo) || is_null($inResponseTo)');
+
+ $this->inResponseTo = $inResponseTo;
+ }
+
+
+ /**
+ * Retrieve the status code.
+ *
+ * @return array The status code.
+ */
+ public function getStatus() {
+ return $this->status;
+ }
+
+
+ /**
+ * Set the status code.
+ *
+ * @param array $status The status code.
+ */
+ public function setStatus(array $status) {
+ assert('array_key_exists("Code", $status)');
+
+ $this->status = $status;
+ if (!array_key_exists('SubCode', $status)) {
+ $this->status['SubCode'] = NULL;
+ }
+ if (!array_key_exists('Message', $status)) {
+ $this->status['Message'] = NULL;
+ }
+ }
+
+
+ /**
+ * Convert status response message to an XML element.
+ *
+ * @return DOMElement This status response.
+ */
+ public function toUnsignedXML() {
+
+ $root = parent::toUnsignedXML();
+
+ if ($this->inResponseTo !== NULL) {
+ $root->setAttribute('InResponseTo', $this->inResponseTo);
+ }
+
+ $status = $this->document->createElementNS(SAML2_Const::NS_SAMLP, 'Status');
+ $root->appendChild($status);
+
+ $statusCode = $this->document->createElementNS(SAML2_Const::NS_SAMLP, 'StatusCode');
+ $statusCode->setAttribute('Value', $this->status['Code']);
+ $status->appendChild($statusCode);
+
+ if (!is_null($this->status['SubCode'])) {
+ $subStatusCode = $this->document->createElementNS(SAML2_Const::NS_SAMLP, 'StatusCode');
+ $subStatusCode->setAttribute('Value', $this->status['SubCode']);
+ $statusCode->appendChild($subStatusCode);
+ }
+
+ if (!is_null($this->status['Message'])) {
+ $statusMessage = $this->document->createElementNS(SAML2_Const::NS_SAMLP, 'StatusMessage');
+ $statusMessage->appendChild($this->document->createTextNode($this->status['Message']));
+ $status->appendChild($statusMessage);
+ }
+
+ return $root;
+ }
+
+
+}
+
+?> \ No newline at end of file
diff --git a/lib/SAML2/Utils.php b/lib/SAML2/Utils.php
new file mode 100644
index 0000000..164c31a
--- /dev/null
+++ b/lib/SAML2/Utils.php
@@ -0,0 +1,244 @@
+<?php
+
+/**
+ * Helper functions for the SAML2 library.
+ *
+ * @package simpleSAMLphp
+ * @version $Id$
+ */
+class SAML2_Utils {
+
+ /**
+ * Check the Signature in a XML element.
+ *
+ * This function expects the XML element to contain a Signature-element
+ * which contains a reference to the XML-element. This is common for both
+ * messages and assertions.
+ *
+ * Note that this function only validates the element itself. It does not
+ * check this against any local keys.
+ *
+ * If no Signature-element is located, this function will return FALSE. All
+ * other validation errors result in an exception. On successful validation
+ * an array will be returned. This array contains the information required to
+ * check the signature against a public key.
+ *
+ * @param DOMElement $root The element which should be validated.
+ * @return array|FALSE An array with information about the Signature-element.
+ */
+ public static function validateElement(DOMElement $root) {
+
+ /* Create an XML security object. */
+ $objXMLSecDSig = new XMLSecurityDSig();
+
+ /* Both SAML messages and SAML assertions use the 'ID' attribute. */
+ $objXMLSecDSig->idKeys[] = 'ID';
+
+ /* Locate the XMLDSig Signature element to be used. */
+ $signatureElement = self::xpQuery($root, './ds:Signature');
+ if (count($signatureElement) === 0) {
+ /* We don't have a signature element ot validate. */
+ return FALSE;
+ } elseif (count($signatureElement) > 1) {
+ throw new Exception('XMLSec: more than one signature element in root.');
+ }
+ $signatureElement = $signatureElement[0];
+ $objXMLSecDSig->sigNode = $signatureElement;
+
+ /* Canonicalize the XMLDSig SignedInfo element in the message. */
+ $objXMLSecDSig->canonicalizeSignedInfo();
+
+ /* Validate referenced xml nodes. */
+ if (!$objXMLSecDSig->validateReference()) {
+ throw new Exception('XMLsec: digest validation failed');
+ }
+
+ /* Check that $root is one of the signed nodes. */
+ $rootSigned = FALSE;
+ foreach ($objXMLSecDSig->getValidatedNodes() as $signedNode) {
+ if ($signedNode->isSameNode($root)) {
+ $rootSigned = TRUE;
+ break;
+ }
+ }
+ if (!$rootSigned) {
+ throw new Exception('XMLSec: The root element is not signed.');
+ }
+
+ /* Now we extract all available X509 certificates in the signature element. */
+ $certificates = array();
+ foreach (self::xpQuery($signatureElement, './ds:KeyInfo/ds:X509Data/ds:X509Certificate') as $certNode) {
+ $certData = $certNode->textContent;
+ $certData = str_replace(array("\r", "\n", "\t", ' '), '', $certData);
+ $certificates[] = $certData;
+ }
+
+ $ret = array(
+ 'Signature' => $objXMLSecDSig,
+ 'Certificates' => $certificates,
+ );
+
+ return $ret;
+ }
+
+
+ /**
+ * Check a signature against a key.
+ *
+ * An exception is thrown if we are unable to validate the signature.
+ *
+ * @param array $info The information returned by the validateElement()-function.
+ * @param XMLSecurityKey $key The publickey that should validate the Signature object.
+ */
+ public static function validateSignature(array $info, XMLSecurityKey $key) {
+ assert('array_key_exists("Signature", $info)');
+
+ $objXMLSecDSig = $info['Signature'];
+
+ /* Check the signature. */
+ if (! $objXMLSecDSig->verify($key)) {
+ throw new Exception("Unable to validate Signature");
+ }
+ }
+
+
+ /**
+ * Do an XPath query on an XML node.
+ *
+ * @param DOMNode $node The XML node.
+ * @param string $query The query.
+ * @return array Array with matching DOM nodes.
+ */
+ public static function xpQuery(DOMNode $node, $query) {
+ assert('is_string($query)');
+ static $xpCache = NULL;
+
+ if ($xpCache === NULL || !$xpCache->document->isSameNode($node->ownerDocument)) {
+ $xpCache = new DOMXPath($node->ownerDocument);
+ $xpCache->registerNamespace('samlp', SAML2_Const::NS_SAMLP);
+ $xpCache->registerNamespace('saml', SAML2_Const::NS_SAML);
+ $xpCache->registerNamespace('ds', XMLSecurityDSig::XMLDSIGNS);
+ $xpCache->registerNamespace('xenc', XMLSecEnc::XMLENCNS);
+ }
+
+ $results = $xpCache->query($query, $node);
+ $ret = array();
+ for ($i = 0; $i < $results->length; $i++) {
+ $ret[$i] = $results->item($i);
+ }
+
+ return $ret;
+ }
+
+
+ /**
+ * Parse a boolean attribute.
+ *
+ * @param DOMElement $node The element we should fetch the attribute from.
+ * @param string $attributeName The name of the attribute.
+ * @param mixed $default The value that should be returned if the attribute doesn't exist.
+ * @return bool|mixed The value of the attribute, or $default if the attribute doesn't exist.
+ */
+ public static function parseBoolean(DOMElement $node, $attributeName, $default = NULL) {
+ assert('is_string($attributeName)');
+
+ if (!$node->hasAttribute($attributeName)) {
+ return $default;
+ }
+ $value = $node->getAttribute($attributeName);
+ switch (strtolower($value)) {
+ case '0':
+ case 'false':
+ return FALSE;
+ case '1':
+ case 'true':
+ return TRUE;
+ default:
+ throw new Exception('Invalid value of boolean attribute ' . var_export($attributeName, TRUE) . ': ' . var_export($value, TRUE));
+ }
+ }
+
+
+ /**
+ * Create a NameID element.
+ *
+ * The NameId array can have the following elements: 'Value', 'Format',
+ * 'NameQualifier, 'SPNameQualifier'
+ *
+ * Only the 'Value'-element is required.
+ *
+ * @param DOMElement $node The DOM node we should append the NameId to.
+ * @param array $nameId The name identifier.
+ */
+ public static function addNameId(DOMElement $node, array $nameId) {
+ assert('array_key_exists("Value", $nameId)');
+
+ $xml = $node->ownerDocument->createElementNS(SAML2_Const::NS_SAML, 'saml:NameID');
+ $node->appendChild($xml);
+
+ if (array_key_exists('NameQualifier', $nameId)) {
+ $xml->setAttribute('NameQualifier', $nameId['NameQualifier']);
+ }
+ if (array_key_exists('SPNameQualifier', $nameId)) {
+ $xml->setAttribute('SPNameQualifier', $nameId['SPNameQualifier']);
+ }
+ if (array_key_exists('Format', $nameId)) {
+ $xml->setAttribute('Format', $nameId['Format']);
+ }
+
+ $xml->appendChild($node->ownerDocument->createTextNode($nameId['Value']));
+ }
+
+
+ /**
+ * Parse a NameID element.
+ *
+ * @param DOMElement $xml The DOM element we should parse.
+ * @return array The parsed name identifier.
+ */
+ public static function parseNameId(DOMElement $xml) {
+
+ $ret = array('Value' => $xml->textContent);
+
+ foreach (array('NameQualifier', 'SPNameQualifier', 'Format') as $attr) {
+ if ($xml->hasAttribute($attr)) {
+ $ret[$attr] = $xml->getAttribute($attr);
+ }
+ }
+
+ return $ret;
+ }
+
+
+ /**
+ * Insert a Signature-node.
+ *
+ * @param XMLSecurityKey $key The key we should use to sign the message.
+ * @param array $certificates The certificates we should add to the signature node.
+ * @param DOMElement $root The XML node we should sign.
+ * @param DomElement $insertBefore The XML element we should insert the signature element before.
+ */
+ public static function insertSignature(XMLSecurityKey $key, array $certificates, DOMElement $root, DOMNode $insertBefore = NULL) {
+
+ $objXMLSecDSig = new XMLSecurityDSig();
+ $objXMLSecDSig->setCanonicalMethod(XMLSecurityDSig::EXC_C14N);
+
+ $objXMLSecDSig->addReferenceList(
+ array($root),
+ XMLSecurityDSig::SHA1,
+ array('http://www.w3.org/2000/09/xmldsig#enveloped-signature', XMLSecurityDSig::EXC_C14N),
+ array('id_name' => 'ID')
+ );
+
+ $objXMLSecDSig->sign($key);
+
+ foreach ($certificates as $certificate) {
+ $objXMLSecDSig->add509Cert($certificate, TRUE);
+ }
+
+ $objXMLSecDSig->insertSignature($root, $insertBefore);
+
+ }
+}
+
+?> \ No newline at end of file