diff --git a/docs/simplesamlphp-changelog.txt b/docs/simplesamlphp-changelog.txt index b3e6f07873069bc6412c38ebc458cb80fee0a1b4..faeae606bd15d7a0ad5fc6b784ba4f6bb90a0363 100644 --- a/docs/simplesamlphp-changelog.txt +++ b/docs/simplesamlphp-changelog.txt @@ -34,6 +34,8 @@ See the upgrade notes for specific information about upgrading. * Removed www/auth/login-auto.php. * Removed www/auth/login-feide.php. * Removed optional parameters from `SimpleSAML_XHTML_Template::getLanguage()`. + * Removed functions from `SAML2_Assertion`: `get/setDestination`, `get/setInResponseTo`. + Replaced with `setSubjectConfirmation`. ### SAML 2 IdP diff --git a/lib/SAML2/Assertion.php b/lib/SAML2/Assertion.php index 768795d57fcf64aa6219d9d4f45df510d37571c2..76c1b96388b9eb727bcec24876acd4df9a2414fb 100644 --- a/lib/SAML2/Assertion.php +++ b/lib/SAML2/Assertion.php @@ -78,24 +78,6 @@ class SAML2_Assertion implements SAML2_SignedElement { 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. * @@ -203,6 +185,14 @@ class SAML2_Assertion implements SAML2_SignedElement { private $requiredEncAttributes; + /** + * The SubjectConfirmation elements of the Subject in the assertion. + * + * @var array Array of SAML2_XML_saml_SubjectConfirmation elements. + */ + private $SubjectConfirmation; + + /** * Constructor for SAML 2 assertions. * @@ -218,6 +208,7 @@ class SAML2_Assertion implements SAML2_SignedElement { $this->nameFormat = SAML2_Const::NAMEFORMAT_UNSPECIFIED; $this->certificates = array(); $this->AuthenticatingAuthority = array(); + $this->SubjectConfirmation = array(); if ($xml === NULL) { return; @@ -283,38 +274,10 @@ class SAML2_Assertion implements SAML2_SignedElement { $subjectConfirmation = SAML2_Utils::xpQuery($subject, './saml_assertion: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]; - $subjectConfirmation = new SAML2_XML_saml_SubjectConfirmation($subjectConfirmation); - if ($subjectConfirmation->Method !== SAML2_Const::CM_BEARER) { - throw new Exception('Unsupported subject confirmation method: ' . var_export($method, TRUE)); - } - - $confirmationData = $subjectConfirmation->SubjectConfirmationData; - if ($confirmationData === NULL) { - return; - } - - if ($confirmationData->NotBefore !== NULL) { - $notBefore = $confirmationData->NotBefore; - if ($this->notBefore === NULL || $this->notBefore < $notBefore) { - $this->notBefore = $notBefore; - } - } - if ($confirmationData->NotOnOrAfter !== NULL) { - $notOnOrAfter = $confirmationData->NotOnOrAfter; - if ($this->notOnOrAfter === NULL || $this->notOnOrAfter > $notOnOrAfter) { - $this->notOnOrAfter = $notOnOrAfter; - } - } - if ($confirmationData->InResponseTo !== NULL) { - $this->inResponseTo = $confirmationData->InResponseTo; - } - if ($confirmationData->Recipient !== NULL) { - $this->destination = $confirmationData->Recipient; + foreach ($subjectConfirmation as $sc) { + $this->SubjectConfirmation[] = new SAML2_XML_saml_SubjectConfirmation($sc); } } @@ -763,32 +726,6 @@ class SAML2_Assertion implements SAML2_SignedElement { } - /** - * 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; - } - - /** * Set $EncryptedAttributes if attributes will send encrypted * @@ -799,33 +736,6 @@ class SAML2_Assertion implements SAML2_SignedElement { } - /** - * 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. * @@ -1028,6 +938,27 @@ class SAML2_Assertion implements SAML2_SignedElement { } + /** + * Retrieve the SubjectConfirmation elements we have in our Subject element. + * + * @return array Array of SAML2_XML_saml_SubjectConfirmation elements. + */ + public function getSubjectConfirmation() { + return $this->SubjectConfirmation; + } + + + /** + * Set the SubjectConfirmation elements that should be included in the assertion. + * + * @param array $SubjectConfirmation Array of SAML2_XML_saml_SubjectConfirmation elements. + */ + public function setSubjectConfirmation(array $SubjectConfirmation) { + + $this->SubjectConfirmation = $SubjectConfirmation; + } + + /** * Retrieve the private key we should use to sign the assertion. * @@ -1159,22 +1090,9 @@ class SAML2_Assertion implements SAML2_SignedElement { SAML2_Utils::addNameId($subject, $this->nameId); - $sc = new SAML2_XML_saml_SubjectConfirmation(); - $sc->Method = SAML2_Const::CM_BEARER; - $sc->SubjectConfirmationData = new SAML2_XML_saml_SubjectConfirmationData(); - $sc->SubjectConfirmationData->Recipient = $this->destination; - - if ($this->notOnOrAfter !== NULL) { - $sc->SubjectConfirmationData->NotOnOrAfter = $this->notOnOrAfter; - } - if ($this->destination !== NULL) { - $sc->SubjectConfirmationData->Recipient = $this->destination; + foreach ($this->SubjectConfirmation as $sc) { + $sc->toXML($subject); } - if ($this->inResponseTo !== NULL) { - $sc->SubjectConfirmationData->InResponseTo = $this->inResponseTo; - } - - $sc->toXML($subject); } diff --git a/modules/exampleattributeserver/www/attributeserver.php b/modules/exampleattributeserver/www/attributeserver.php index abce3e17ea8d72da2d331ce0a2046c2cd3429b82..19cbee44ee295e3dba9133965d2d8af7fe988c41 100644 --- a/modules/exampleattributeserver/www/attributeserver.php +++ b/modules/exampleattributeserver/www/attributeserver.php @@ -63,15 +63,22 @@ if (count($returnAttributes) === 0) { /* $returnAttributes contains the attributes we should return. Send them. */ $assertion = new SAML2_Assertion(); -$assertion->setDestination($endpoint); $assertion->setIssuer($idpEntityId); $assertion->setNameId($query->getNameId()); $assertion->setNotBefore(time()); $assertion->setNotOnOrAfter(time() + 5*60); -$assertion->setInResponseTo($query->getId()); $assertion->setValidAudiences(array($spEntityId)); $assertion->setAttributes($returnAttributes); $assertion->setAttributeNameFormat($attributeNameFormat); + +$sc = new SAML2_XML_saml_SubjectConfirmation(); +$sc->Method = SAML2_Const::CM_BEARER; +$sc->SubjectConfirmationData = new SAML2_XML_saml_SubjectConfirmationData(); +$sc->SubjectConfirmationData->NotOnOrAfter = time() + 5*60; +$sc->SubjectConfirmationData->Recipient = $endpoint; +$sc->SubjectConfirmationData->InResponseTo = $query->getId(); +$assertion->setSubjectConfirmation(array($sc)); + sspmod_saml_Message::addSign($idpMetadata, $spMetadata, $assertion); $response = new SAML2_Response(); diff --git a/modules/saml/lib/IdP/SAML2.php b/modules/saml/lib/IdP/SAML2.php index a4a7266008e3463c60786227dac63eb470092f4c..68b285fb0cb178604978c4fdedbe373787ce8418 100644 --- a/modules/saml/lib/IdP/SAML2.php +++ b/modules/saml/lib/IdP/SAML2.php @@ -37,7 +37,6 @@ class sspmod_saml_IdP_SAML2 { $idpMetadata = $idp->getConfig(); $assertion = self::buildAssertion($idpMetadata, $spMetadata, $state); - $assertion->setInResponseTo($requestId); if (isset($state['saml:AuthenticatingAuthority'])) { $assertion->setAuthenticatingAuthority($state['saml:AuthenticatingAuthority']); @@ -554,7 +553,6 @@ class sspmod_saml_IdP_SAML2 { } $a->setIssuer($idpMetadata->getString('entityid')); - $a->setDestination($state['saml:ConsumerURL']); $a->setValidAudiences(array($spMetadata->getString('entityid'))); $a->setNotBefore(time() - 30); @@ -576,6 +574,14 @@ class sspmod_saml_IdP_SAML2 { $a->setSessionIndex(SimpleSAML_Utilities::generateID()); + $sc = new SAML2_XML_saml_SubjectConfirmation(); + $sc->Method = SAML2_Const::CM_BEARER; + $sc->SubjectConfirmationData = new SAML2_XML_saml_SubjectConfirmationData(); + $sc->SubjectConfirmationData->NotOnOrAfter = time() + $assertionLifetime; + $sc->SubjectConfirmationData->Recipient = $state['saml:ConsumerURL']; + $sc->SubjectConfirmationData->InResponseTo = $state['saml:RequestId']; + $a->setSubjectConfirmation(array($sc)); + /* Add attributes. */ if ($spMetadata->getBoolean('simplesaml.attributes', TRUE)) { diff --git a/modules/saml/lib/Message.php b/modules/saml/lib/Message.php index 8178b6c4d147547f2363c54be509e2ae2a59a029..88f9d2455647588a0be14c9804946b57ac882a86 100644 --- a/modules/saml/lib/Message.php +++ b/modules/saml/lib/Message.php @@ -487,16 +487,6 @@ class sspmod_saml_Message { /* At least one valid signature found. */ - /* Make sure that some fields in the assertion matches the same fields in the message. */ - - $asrtInResponseTo = $assertion->getInResponseTo(); - $msgInResponseTo = $response->getInResponseTo(); - if ($asrtInResponseTo !== NULL && $msgInResponseTo !== NULL) { - if ($asrtInResponseTo !== $msgInResponseTo) { - throw new SimpleSAML_Error_Exception('InResponseTo in assertion did not match InResponseTo in message.'); - } - } - /* Validate Response-element destination. */ $currentURL = SimpleSAML_Utilities::selfURLNoQuery(); @@ -524,12 +514,6 @@ class sspmod_saml_Message { throw new SimpleSAML_Error_Exception('Received an assertion with a session that has expired. Check clock synchronization on IdP and SP.'); } - $destination = $assertion->getDestination(); - if ($destination !== $currentURL) { - throw new Exception('Recipient in assertion doesn\'t match the current URL. Recipient is "' . - $destination . '", current URL is "' . $currentURL . '".'); - } - $validAudiences = $assertion->getValidAudiences(); if ($validAudiences !== NULL) { $spEntityId = $spMetadata->getString('entityid'); @@ -539,6 +523,39 @@ class sspmod_saml_Message { } } + $found = FALSE; + $lastError = 'No SubjectConfirmation element in Subject.'; + foreach ($assertion->getSubjectConfirmation() as $sc) { + if ($sc->Method !== SAML2_Const::CM_BEARER) { + $lastError = 'Invalid Method on SubjectConfirmation: ' . var_export($sc->Method, TRUE); + continue; + } + $scd = $sc->SubjectConfirmationData; + if ($scd->NotBefore && $scd->NotBefore > time() + 60) { + $lastError = 'NotBefore in SubjectConfirmationData is in the future: ' . $scd->NotBefore; + continue; + } + if ($scd->NotOnOrAfter && $scd->NotOnOrAfter <= time() - 60) { + $lastError = 'NotOnOrAfter in SubjectConfirmationData is in the past: ' . $scd->NotOnOrAfter; + continue; + } + if ($scd->Recipient !== NULL && $scd->Recipient !== $currentURL) { + $lastError = 'Recipient in SubjectConfirmationData does not match the current URL. Recipient is ' . + var_export($scd->Recipient, TRUE) . ', current URL is ' . var_export($currentURL, TRUE) . '.'; + continue; + } + if ($scd->InResponseTo !== NULL && $response->getInResponseTo() !== NULL && $scd->InResponseTo !== $response->getInResponseTo()) { + $lastError = 'InResponseTo in SubjectConfirmationData does not match the Response. Response has ' . + var_export($response->getInResponseTo(), TRUE) . ', SubjectConfirmationData has ' . var_export($scd->InResponseTo, TRUE) . '.'; + continue; + } + $found = TRUE; + break; + } + if (!$found) { + throw new SimpleSAML_Error_Exception('Error validating SubjectConfirmation in Assertion: ' . $lastError); + } + /* As far as we can tell, the assertion is valid. */