public function validate(SAML2_Response $response, SAML2_Response_Validation_Result $result) { $destination = $response->getDestination(); if (!$this->expectedDestination->equals(new SAML2_Configuration_Destination($destination))) { $result->addError(sprintf('Destination in response "%s" does not match the expected destination "%s"', $destination, $this->expectedDestination)); } }
/** * @param SAML2_Response $response * @param SimpleSAML_Configuration $idpConfig */ private function addSigns(SAML2_Response $response, SimpleSAML_Configuration $idpConfig) { $assertions = $response->getAssertions(); $className = EngineBlock_ApplicationSingleton::getInstance()->getDiContainer()->getMessageUtilClassName(); // Special case the 'normal' message verification class name so we have IDE support. if ($className === 'sspmod_saml_Message') { sspmod_saml_Message::addSign($idpConfig, SimpleSAML_Configuration::loadFromArray(array()), $assertions[0]); return; } $className::addSign($idpConfig, SimpleSAML_Configuration::loadFromArray(array()), $assertions[0]); }
public function setUp() { $this->request = new EngineBlock_Saml2_AuthnRequestAnnotationDecorator(new SAML2_AuthnRequest()); $assertion = new SAML2_Assertion(); $assertion->setAttributes(array()); $response = new SAML2_Response(); $response->setAssertions(array($assertion)); $response = new EngineBlock_Saml2_ResponseAnnotationDecorator($response); $response->setIntendedNameId('urn:collab:person:example.edu:mock1'); $this->response = $response; $this->serviceProvider = new ServiceProvider('http://sp.example.edu'); $this->collabPersonId = 'urn:collab:person:example.edu:mock1'; $this->resolver = new EngineBlock_Test_Saml2_NameIdResolverMock(); }
/** * @test * @group signature */ public function signed_message_with_valid_signature_is_validated_correctly() { $pattern = SAML2_Utilities_Certificate::CERTIFICATE_PATTERN; preg_match($pattern, SAML2_CertificatesMock::PUBLIC_KEY_PEM, $matches); $config = new SAML2_Configuration_IdentityProvider(array('certificateData' => $matches[1])); $validator = new SAML2_Signature_PublicKeyValidator(new SAML2_SimpleTestLogger(), new SAML2_Certificate_KeyLoader()); $doc = SAML2_DOMDocumentFactory::fromFile(__DIR__ . '/response.xml'); $response = new SAML2_Response($doc->firstChild); $response->setSignatureKey(SAML2_CertificatesMock::getPrivateKey()); $response->setCertificates(array(SAML2_CertificatesMock::PUBLIC_KEY_PEM)); // convert to signed response $response = new SAML2_Response($response->toSignedXML()); $this->assertTrue($validator->canValidate($response, $config), 'Cannot validate the element'); $this->assertTrue($validator->hasValidSignature($response, $config), 'The signature is not valid'); }
public function testMarshalling() { $response = new SAML2_Response(); $response->setStatus(array('Code' => 'OurStatusCode', 'SubCode' => 'OurSubStatusCode', 'Message' => 'OurMessageText')); $responseElement = $response->toUnsignedXML(); $statusElements = SAML2_Utils::xpQuery($responseElement, './saml_protocol:Status'); $this->assertCount(1, $statusElements); $statusCodeElements = SAML2_Utils::xpQuery($statusElements[0], './saml_protocol:StatusCode'); $this->assertCount(1, $statusCodeElements); $this->assertEquals('OurStatusCode', $statusCodeElements[0]->getAttribute("Value")); $nestedStatusCodeElements = SAML2_Utils::xpQuery($statusCodeElements[0], './saml_protocol:StatusCode'); $this->assertCount(1, $nestedStatusCodeElements); $this->assertEquals('OurSubStatusCode', $nestedStatusCodeElements[0]->getAttribute("Value")); $statusMessageElements = SAML2_Utils::xpQuery($statusElements[0], './saml_protocol:StatusMessage'); $this->assertCount(1, $statusMessageElements); $this->assertEquals('OurMessageText', $statusMessageElements[0]->textContent); }
/** * @test * @group signature */ public function signed_message_with_valid_signature_is_validated_correctly() { $pattern = SAML2_Utilities_Certificate::CERTIFICATE_PATTERN; preg_match($pattern, SAML2_CertificatesMock::PUBLIC_KEY_PEM, $matches); $certdata = SAML2_Certificate_X509::createFromCertificateData($matches[1]); $fingerprint = $certdata->getFingerprint(); $fingerprint_retry = $certdata->getFingerprint(); $this->assertTrue($fingerprint->equals($fingerprint_retry), 'Cached fingerprint does not match original'); $config = new SAML2_Configuration_IdentityProvider(array('certificateFingerprints' => array($fingerprint->getRaw()))); $validator = new SAML2_Signature_FingerprintValidator(new SAML2_SimpleTestLogger(), new SAML2_Certificate_FingerprintLoader()); $doc = SAML2_DOMDocumentFactory::fromFile(__DIR__ . '/response.xml'); $response = new SAML2_Response($doc->firstChild); $response->setSignatureKey(SAML2_CertificatesMock::getPrivateKey()); $response->setCertificates(array(SAML2_CertificatesMock::PUBLIC_KEY_PEM)); // convert to signed response $response = new SAML2_Response($response->toSignedXML()); $this->assertTrue($validator->canValidate($response, $config), 'Cannot validate the element'); $this->assertTrue($validator->hasValidSignature($response, $config), 'The signature is not valid'); }
/** * @param string $samlMessageXml * @param string $class * @return SAML_Message */ public function deserialize($samlMessageXml, $class) { $elementName = $this->getElementForClass($class); $document = new DOMDocument(); $document->loadXML($samlMessageXml); $messageDomElement = $document->getElementsByTagNameNs('urn:oasis:names:tc:SAML:2.0:protocol', $elementName)->item(0); if ($class === 'SAML2_AuthnRequest') { return SAML2_AuthnRequest::fromXML($messageDomElement); } else { if ($class === 'SAML2_Response') { return SAML2_Response::fromXML($messageDomElement); } } throw new EngineBlock_Exception('Unknown message type for deserialization?'); }
/** * @return EngineBlock_Corto_Module_Bindings */ private function mockBindingsModule() { $spRequest = new SAML2_AuthnRequest(); $spRequest->setId('SPREQUEST'); $spRequest->setIssuer('testSp'); $spRequest = new EngineBlock_Saml2_AuthnRequestAnnotationDecorator($spRequest); $ebRequest = new SAML2_AuthnRequest(); $ebRequest->setId('EBREQUEST'); $ebRequest = new EngineBlock_Saml2_AuthnRequestAnnotationDecorator($ebRequest); $dummyLog = new Psr\Log\NullLogger(); $authnRequestRepository = new EngineBlock_Saml2_AuthnRequestSessionRepository($dummyLog); $authnRequestRepository->store($spRequest); $authnRequestRepository->store($ebRequest); $authnRequestRepository->link($ebRequest, $spRequest); $assertion = new SAML2_Assertion(); $assertion->setAttributes(array('urn:org:openconext:corto:internal:sp-entity-id' => array('testSp'), 'urn:mace:dir:attribute-def:cn' => array(null))); $responseFixture = new SAML2_Response(); $responseFixture->setInResponseTo('EBREQUEST'); $responseFixture->setAssertions(array($assertion)); $responseFixture = new EngineBlock_Saml2_ResponseAnnotationDecorator($responseFixture); $responseFixture->setOriginalIssuer('testIdP'); // Mock bindings module /** @var EngineBlock_Corto_Module_Bindings $bindingsModuleMock */ $bindingsModuleMock = Phake::mock('EngineBlock_Corto_Module_Bindings'); Phake::when($bindingsModuleMock)->receiveResponse()->thenReturn($responseFixture); return $bindingsModuleMock; }
} /* Filter which attribute values we should return. */ $returnAttributes[$name] = array_intersect($values, $attributes[$name]); } } /* $returnAttributes contains the attributes we should return. Send them. */ $assertion = new SAML2_Assertion(); $assertion->setIssuer($idpEntityId); $assertion->setNameId($query->getNameId()); $assertion->setNotBefore(time()); $assertion->setNotOnOrAfter(time() + 5 * 60); $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(); $response->setRelayState($query->getRelayState()); $response->setDestination($endpoint); $response->setIssuer($idpEntityId); $response->setInResponseTo($query->getId()); $response->setAssertions(array($assertion)); sspmod_saml_Message::addSign($idpMetadata, $spMetadata, $response); $binding = new SAML2_HTTPPost(); $binding->send($response);
/** * Process a response message. * * If the response is an error response, we will throw a sspmod_saml2_Error * exception with the error. * * @param SimpleSAML_Configuration $spMetadata The metadata of the service provider. * @param SimpleSAML_Configuration $idpMetadata The metadata of the identity provider. * @param SAML2_Response $response The response. * @return SAML2_Assertion The assertion in the response, if it is valid. */ public static function processResponse(SimpleSAML_Configuration $spMetadata, SimpleSAML_Configuration $idpMetadata, SAML2_Response $response) { if (!$response->isSuccess()) { throw self::getResponseError($response); } /* * When we get this far, the response itself is valid. * We only need to check signatures and conditions of the response. */ $assertion = $response->getAssertions(); if (empty($assertion)) { throw new SimpleSAML_Error_Exception('No assertions found in response from IdP.'); } elseif (count($assertion) > 1) { throw new SimpleSAML_Error_Exception('More than one assertion found in response from IdP.'); } $assertion = $assertion[0]; $assertion = self::decryptAssertion($idpMetadata, $spMetadata, $assertion); if (!self::checkSign($idpMetadata, $assertion)) { if (!self::checkSign($idpMetadata, $response)) { throw new SimpleSAML_Error_Exception('Neither the assertion nor the response was signed.'); } } /* 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.'); } } $asrtDestination = $assertion->getDestination(); $msgDestination = $response->getDestination(); if ($asrtDestination !== NULL && $msgDestination !== NULL) { if ($asrtDestination !== $msgDestination) { throw new SimpleSAML_Error_Exception('Destination in assertion did not match Destination in message.'); } } /* Check various properties of the assertion. */ $notBefore = $assertion->getNotBefore(); if ($notBefore > time() + 60) { throw new SimpleSAML_Error_Exception('Received an assertion that is valid in the future. Check clock synchronization on IdP and SP.'); } $notOnOrAfter = $assertion->getNotOnOrAfter(); if ($notOnOrAfter <= time() - 60) { throw new SimpleSAML_Error_Exception('Received an assertion that has expired. Check clock synchronization on IdP and SP.'); } $sessionNotOnOrAfter = $assertion->getSessionNotOnOrAfter(); if ($sessionNotOnOrAfter !== NULL && $sessionNotOnOrAfter <= time() - 60) { throw new SimpleSAML_Error_Exception('Received an assertion with a session that has expired. Check clock synchronization on IdP and SP.'); } $destination = $assertion->getDestination(); $currentURL = SimpleSAML_Utilities::selfURLNoQuery(); 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'); if (!in_array($spEntityId, $validAudiences, TRUE)) { $candidates = '[' . implode('], [', $validAudiences) . ']'; throw new SimpleSAML_Error_Exception('This SP [' . $spEntityId . '] is not a valid audience for the assertion. Candidates were: ' . $candidates); } } /* As far as we can tell, the assertion is valid. */ /* Maybe we need to base64 decode the attributes in the assertion? */ if ($idpMetadata->getBoolean('base64attributes', FALSE)) { $attributes = $assertion->getAttributes(); $newAttributes = array(); foreach ($attributes as $name => $values) { $newAttributes[$name] = array(); foreach ($values as $value) { foreach (explode('_', $value) as $v) { $newAttributes[$name][] = base64_decode($v); } } } $assertion->setAttributes($newAttributes); } /* Decrypt the NameID element if it is encrypted. */ if ($assertion->isNameIdEncrypted()) { try { $key = self::getDecryptionKey($idpMetadata, $spMetadata); } catch (Exception $e) { throw new SimpleSAML_Error_Exception('Error decrypting NameID: ' . $e->getMessage()); } $assertion->decryptNameId($key); } return $assertion; }
/** * Determines if a Response carries an encrypted assertion. * * @param SAML2_Response $sspResponse * @return bool */ private function hasEncryptedAssertion(SAML2_Response $sspResponse) { $hasEncryptedAssertion = false; foreach ($sspResponse->getAssertions() as $assertion) { if ($assertion instanceof SAML2_EncryptedAssertion) { $hasEncryptedAssertion = true; break; } } return $hasEncryptedAssertion; }
private function buildResponse($returnAttributes) { /* SubjectConfirmation */ $sc = new SAML2_XML_saml_SubjectConfirmation(); $sc->Method = SAML2_Const::CM_BEARER; $sc->SubjectConfirmationData = new SAML2_XML_saml_SubjectConfirmationData(); $sc->SubjectConfirmationData->NotBefore = time(); $sc->SubjectConfirmationData->NotOnOrAfter = time() + $this->config->getInteger('validFor'); $sc->SubjectConfirmationData->InResponseTo = $this->query->getId(); $assertion = new SAML2_Assertion(); $assertion->setSubjectConfirmation(array($sc)); $assertion->setIssuer($this->aaEntityId); $assertion->setNameId($this->query->getNameId()); $assertion->setNotBefore(time()); $assertion->setNotOnOrAfter(time() + $this->config->getInteger('validFor')); $assertion->setValidAudiences(array($this->spEntityId)); $assertion->setAttributes($returnAttributes); $assertion->setAttributeNameFormat($this->attributeNameFormat); if ($this->signAssertion) { sspmod_saml_Message::addSign($this->aaMetadata, $this->spMetadata, $assertion); } /* The Response */ $response = new SAML2_Response(); $response->setRelayState($this->query->getRelayState()); $response->setIssuer($this->aaEntityId); $response->setInResponseTo($this->query->getId()); $response->setAssertions(array($assertion)); if ($this->signResponse) { sspmod_saml_Message::addSign($this->aaMetadata, $this->spMetadata, $response); } return $response; }
private static function combine_response(array $values, \SAML2_Response $response = NULL) { if (!isset($response)) { $response = new \SAML2_Response(); } $presented_assertions = $response->getAssertions(); $assertion = empty($presented_assertions) ? new \SAML2_Assertion() : $presented_assertions[0]; if (self::original_spid_isset($values)) { $response->setInResponseTo($values['InResponseTo']); } if (array_key_exists('ResponseID', $values)) { $response->setId($values['ResponseID']); } if (array_key_exists('AssertionID', $values)) { $assertion->setId($values['AssertionID']); } if (array_key_exists('Issuer', $values)) { $response->setIssuer($values['Issuer']); $assertion->setIssuer($values['Issuer']); } if (array_key_exists('NameID', $values)) { $assertion->setNameId(self::build_name_id($values['NameID'])); } $not_on_or_after_time = time(); if (array_key_exists('AllowedTimeDelta', $values)) { $not_on_or_after_time += $values['AllowedTimeDelta']; $assertion->setNotBefore(time() - $values['AllowedTimeDelta']); $assertion->setNotOnOrAfter($not_on_or_after_time); } else { $not_on_or_after_time += DEFAULT_RESPONSE_TIME_DELTA; } if (array_key_exists('Audience', $values)) { $assertion->setValidAudiences(array($values['Audience'])); } if (array_key_exists('Attributes', $values)) { $assertion->setAttributes($values['Attributes']); } $assertion->setAuthnInstant(time()); if (array_key_exists('AuthnContextClassRef', $values)) { $assertion->setAuthnContextClassRef($values['AuthnContextClassRef']); } if (array_key_exists('SessionIndex', $values)) { $assertion->setSessionIndex($values['SessionIndex']); } if (self::original_spid_isset($values) || array_key_exists('Destination', $values)) { $original_confirmations = $assertion->getSubjectConfirmation(); $confirmation = NULL; if (empty($original_confirmations)) { $confirmation = new \SAML2_XML_saml_SubjectConfirmation(); $confirmation->Method = SAML_CONFIGURATION_METHOD; } else { $confirmation = $original_confirmations[0]; } $original_data = $confirmation->SubjectConfirmationData; $data = NULL; if (empty($original_data)) { $data = new \SAML2_XML_saml_SubjectConfirmationData(); $data->NotOnOrAfter = $not_on_or_after_time; } else { $data = $original_data; if (empty($data->NotOnOrAfter)) { $data->NotOnOrAfter = $not_on_or_after_time; } } if (array_key_exists('Destination', $values)) { $data->Recipient = $values['Destination']; } if (self::original_spid_isset($values)) { $data->InResponseTo = $values['InResponseTo']; } $confirmation->SubjectConfirmationData = $data; $assertion->setSubjectConfirmation(array($confirmation)); } if (array_key_exists('Destination', $values)) { $response->setDestination($values['Destination']); } if (self::need_sign($values)) { if (array_key_exists('SHA256KeyFile', $values)) { $ekey = new \XMLSecurityKey(\XMLSecurityKey::RSA_SHA256, array('type' => 'private')); $ekey->loadKey($values['SHA256KeyFile'], true); if (self::need_sign_attributes($values)) { $assertion->setSignatureKey($ekey); } if (self::need_sign_message($values)) { $response->setSignatureKey($ekey); } } if (array_key_exists('SHA256CertFile', $values)) { $certifictaes = array(file_get_contents($values['SHA256CertFile'])); if (self::need_sign_attributes($values)) { $assertion->setCertificates($certifictaes); } if (self::need_sign_message($values)) { $response->setCertificates($certifictaes); } } } $response->setAssertions(array($assertion)); return $response; }
/** * @return SAML2_Response */ private function getSignedResponseWithSignedAssertion() { $doc = new DOMDocument(); $doc->load(__DIR__ . '/response.xml'); $response = new SAML2_Response($doc->firstChild); $response->setSignatureKey(SAML2_CertificatesMock::getPrivateKey()); $response->setCertificates(array(SAML2_CertificatesMock::PUBLIC_KEY_PEM)); $assertions = $response->getAssertions(); $assertion = $assertions[0]; $assertion->setSignatureKey(SAML2_CertificatesMock::getPrivateKey()); $assertion->setCertificates(array(SAML2_CertificatesMock::PUBLIC_KEY_PEM)); return new SAML2_Response($response->toSignedXML()); }
/** * @return SAML2_Assertion[]|SAML2_EncryptedAssertion[] */ public function getAssertions() { return $this->sspMessage->getAssertions(); }
/** * Process an assertion in a response. * * Will throw an exception if it is invalid. * * @param SimpleSAML_Configuration $spMetadata The metadata of the service provider. * @param SimpleSAML_Configuration $idpMetadata The metadata of the identity provider. * @param SAML2_Response $response The response containing the assertion. * @param SAML2_Assertion|SAML2_EncryptedAssertion $assertion The assertion. * @param bool $responseSigned Whether the response is signed. * @return SAML2_Assertion The assertion, if it is valid. */ private static function processAssertion(SimpleSAML_Configuration $spMetadata, SimpleSAML_Configuration $idpMetadata, SAML2_Response $response, $assertion, $responseSigned) { assert('$assertion instanceof SAML2_Assertion || $assertion instanceof SAML2_EncryptedAssertion'); assert('is_bool($responseSigned)'); $assertion = self::decryptAssertion($idpMetadata, $spMetadata, $assertion); if (!self::checkSign($idpMetadata, $assertion)) { if (!$responseSigned) { throw new SimpleSAML_Error_Exception('Neither the assertion nor the response was signed.'); } } /* At least one valid signature found. */ $currentURL = \SimpleSAML\Utils\HTTP::getSelfURLNoQuery(); /* Check various properties of the assertion. */ $notBefore = $assertion->getNotBefore(); if ($notBefore !== NULL && $notBefore > time() + 60) { throw new SimpleSAML_Error_Exception('Received an assertion that is valid in the future. Check clock synchronization on IdP and SP.'); } $notOnOrAfter = $assertion->getNotOnOrAfter(); if ($notOnOrAfter !== NULL && $notOnOrAfter <= time() - 60) { throw new SimpleSAML_Error_Exception('Received an assertion that has expired. Check clock synchronization on IdP and SP.'); } $sessionNotOnOrAfter = $assertion->getSessionNotOnOrAfter(); if ($sessionNotOnOrAfter !== NULL && $sessionNotOnOrAfter <= time() - 60) { throw new SimpleSAML_Error_Exception('Received an assertion with a session that has expired. Check clock synchronization on IdP and SP.'); } $validAudiences = $assertion->getValidAudiences(); if ($validAudiences !== NULL) { $spEntityId = $spMetadata->getString('entityid'); if (!in_array($spEntityId, $validAudiences, TRUE)) { $candidates = '[' . implode('], [', $validAudiences) . ']'; throw new SimpleSAML_Error_Exception('This SP [' . $spEntityId . '] is not a valid audience for the assertion. Candidates were: ' . $candidates); } } $found = FALSE; $lastError = 'No SubjectConfirmation element in Subject.'; $validSCMethods = array(SAML2_Const::CM_BEARER, SAML2_Const::CM_HOK, SAML2_Const::CM_VOUCHES); foreach ($assertion->getSubjectConfirmation() as $sc) { if (!in_array($sc->Method, $validSCMethods)) { $lastError = 'Invalid Method on SubjectConfirmation: ' . var_export($sc->Method, TRUE); continue; } /* Is SSO with HoK enabled? IdP remote metadata overwrites SP metadata configuration. */ $hok = $idpMetadata->getBoolean('saml20.hok.assertion', NULL); if ($hok === NULL) { $hok = $spMetadata->getBoolean('saml20.hok.assertion', FALSE); } if ($sc->Method === SAML2_Const::CM_BEARER && $hok) { $lastError = 'Bearer SubjectConfirmation received, but Holder-of-Key SubjectConfirmation needed'; continue; } if ($sc->Method === SAML2_Const::CM_HOK && !$hok) { $lastError = 'Holder-of-Key SubjectConfirmation received, but the Holder-of-Key profile is not enabled.'; continue; } $scd = $sc->SubjectConfirmationData; if ($sc->Method === SAML2_Const::CM_HOK) { /* Check HoK Assertion */ if (\SimpleSAML\Utils\HTTP::isHTTPS() === FALSE) { $lastError = 'No HTTPS connection, but required for Holder-of-Key SSO'; continue; } if (isset($_SERVER['SSL_CLIENT_CERT']) && empty($_SERVER['SSL_CLIENT_CERT'])) { $lastError = 'No client certificate provided during TLS Handshake with SP'; continue; } /* Extract certificate data (if this is a certificate). */ $clientCert = $_SERVER['SSL_CLIENT_CERT']; $pattern = '/^-----BEGIN CERTIFICATE-----([^-]*)^-----END CERTIFICATE-----/m'; if (!preg_match($pattern, $clientCert, $matches)) { $lastError = 'Error while looking for client certificate during TLS handshake with SP, the client certificate does not ' . 'have the expected structure'; continue; } /* We have a valid client certificate from the browser. */ $clientCert = str_replace(array("\r", "\n", " "), '', $matches[1]); foreach ($scd->info as $thing) { if ($thing instanceof SAML2_XML_ds_KeyInfo) { $keyInfo[] = $thing; } } if (count($keyInfo) != 1) { $lastError = 'Error validating Holder-of-Key assertion: Only one <ds:KeyInfo> element in <SubjectConfirmationData> allowed'; continue; } foreach ($keyInfo[0]->info as $thing) { if ($thing instanceof SAML2_XML_ds_X509Data) { $x509data[] = $thing; } } if (count($x509data) != 1) { $lastError = 'Error validating Holder-of-Key assertion: Only one <ds:X509Data> element in <ds:KeyInfo> within <SubjectConfirmationData> allowed'; continue; } foreach ($x509data[0]->data as $thing) { if ($thing instanceof SAML2_XML_ds_X509Certificate) { $x509cert[] = $thing; } } if (count($x509cert) != 1) { $lastError = 'Error validating Holder-of-Key assertion: Only one <ds:X509Certificate> element in <ds:X509Data> within <SubjectConfirmationData> allowed'; continue; } $HoKCertificate = $x509cert[0]->certificate; if ($HoKCertificate !== $clientCert) { $lastError = 'Provided client certificate does not match the certificate bound to the Holder-of-Key assertion'; continue; } } 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. */ /* Maybe we need to base64 decode the attributes in the assertion? */ if ($idpMetadata->getBoolean('base64attributes', FALSE)) { $attributes = $assertion->getAttributes(); $newAttributes = array(); foreach ($attributes as $name => $values) { $newAttributes[$name] = array(); foreach ($values as $value) { foreach (explode('_', $value) as $v) { $newAttributes[$name][] = base64_decode($v); } } } $assertion->setAttributes($newAttributes); } /* Decrypt the NameID element if it is encrypted. */ if ($assertion->isNameIdEncrypted()) { try { $keys = self::getDecryptionKeys($idpMetadata, $spMetadata); } catch (Exception $e) { throw new SimpleSAML_Error_Exception('Error decrypting NameID: ' . $e->getMessage()); } $blacklist = self::getBlacklistedAlgorithms($idpMetadata, $spMetadata); $lastException = NULL; foreach ($keys as $i => $key) { try { $assertion->decryptNameId($key, $blacklist); SimpleSAML_Logger::debug('Decryption with key #' . $i . ' succeeded.'); $lastException = NULL; break; } catch (Exception $e) { SimpleSAML_Logger::debug('Decryption with key #' . $i . ' failed with exception: ' . $e->getMessage()); $lastException = $e; } } if ($lastException !== NULL) { throw $lastException; } } return $assertion; }
/** * @param SAML2_Response $response * * @return SAML2_Assertion[] */ private function processAssertions(SAML2_Response $response) { $assertions = $response->getAssertions(); if (empty($assertions)) { throw new SAML2_Response_Exception_NoAssertionsFoundException('No assertions found in response from IdP.'); } if (!$this->responseIsSigned) { foreach ($assertions as $assertion) { if (!$assertion->getWasSignedAtConstruction()) { throw new SAML2_Response_Exception_UnsignedResponseException('Both the response and the assertion it containes are not signed.'); } } } return $this->assertionProcessor->processAssertions($assertions); }
private function mockGlobals() { $_POST['ID'] = 'test'; $_POST['consent'] = 'yes'; $assertion = new SAML2_Assertion(); $assertion->setAttributes(array('urn:mace:dir:attribute-def:mail' => '*****@*****.**')); $spRequest = new SAML2_AuthnRequest(); $spRequest->setId('SPREQUEST'); $spRequest->setIssuer('https://sp.example.edu'); $spRequest = new EngineBlock_Saml2_AuthnRequestAnnotationDecorator($spRequest); $ebRequest = new SAML2_AuthnRequest(); $ebRequest->setId('EBREQUEST'); $ebRequest = new EngineBlock_Saml2_AuthnRequestAnnotationDecorator($ebRequest); $dummySessionLog = new Psr\Log\NullLogger(); $authnRequestRepository = new EngineBlock_Saml2_AuthnRequestSessionRepository($dummySessionLog); $authnRequestRepository->store($spRequest); $authnRequestRepository->store($ebRequest); $authnRequestRepository->link($ebRequest, $spRequest); $sspResponse = new SAML2_Response(); $sspResponse->setInResponseTo('EBREQUEST'); $sspResponse->setAssertions(array($assertion)); $_SESSION['consent']['test']['response'] = new EngineBlock_Saml2_ResponseAnnotationDecorator($sspResponse); }
protected function _createBaseResponse(EngineBlock_Saml2_AuthnRequestAnnotationDecorator $request) { if ($request->getVoContext() && $request->isVoContextExplicit()) { $this->setVirtualOrganisationContext($request->getVoContext()); } if ($keyId = $request->getKeyId()) { $this->setKeyId($keyId); } $requestWasUnsolicited = $request->isUnsolicited(); $response = new SAML2_Response(); /** @var SAML2_AuthnRequest $request */ $response->setRelayState($request->getRelayState()); $response->setId($this->getNewId(IdFrame::ID_USAGE_SAML2_RESPONSE)); $response->setIssueInstant(time()); if (!$requestWasUnsolicited) { $response->setInResponseTo($request->getId()); } $response->setDestination($request->getIssuer()); $response->setIssuer($this->getUrl('idpMetadataService', $request->getIssuer(), $request)); $acs = $this->getRequestAssertionConsumer($request); $response->setDestination($acs->location); $response->setStatus(array('Code' => SAML2_Const::STATUS_SUCCESS)); $response = new EngineBlock_Saml2_ResponseAnnotationDecorator($response); $response->setDeliverByBinding($acs->binding); return $response; }
public function validate(SAML2_Response $response, SAML2_Response_Validation_Result $result) { if (!$response->isSuccess()) { $result->addError($this->buildMessage($response->getStatus())); } }
/** * Process an assertion in a response. * * Will throw an exception if it is invalid. * * @param SimpleSAML_Configuration $spMetadata The metadata of the service provider. * @param SimpleSAML_Configuration $idpMetadata The metadata of the identity provider. * @param SAML2_Response $response The response containing the assertion. * @param SAML2_Assertion|SAML2_EncryptedAssertion $assertion The assertion. * @param bool $responseSigned Whether the response is signed. * @return SAML2_Assertion The assertion, if it is valid. */ private static function processAssertion(SimpleSAML_Configuration $spMetadata, SimpleSAML_Configuration $idpMetadata, SAML2_Response $response, $assertion, $responseSigned) { assert('$assertion instanceof SAML2_Assertion || $assertion instanceof SAML2_EncryptedAssertion'); assert('is_bool($responseSigned)'); $assertion = self::decryptAssertion($idpMetadata, $spMetadata, $assertion); if (!self::checkSign($idpMetadata, $assertion)) { if (!$responseSigned) { throw new SimpleSAML_Error_Exception('Neither the assertion nor the response was signed.'); } } /* At least one valid signature found. */ $currentURL = SimpleSAML_Utilities::selfURLNoQuery(); /* Check various properties of the assertion. */ $notBefore = $assertion->getNotBefore(); if ($notBefore > time() + 60) { throw new SimpleSAML_Error_Exception('Received an assertion that is valid in the future. Check clock synchronization on IdP and SP.'); } $notOnOrAfter = $assertion->getNotOnOrAfter(); if ($notOnOrAfter <= time() - 60) { throw new SimpleSAML_Error_Exception('Received an assertion that has expired. Check clock synchronization on IdP and SP.'); } $sessionNotOnOrAfter = $assertion->getSessionNotOnOrAfter(); if ($sessionNotOnOrAfter !== NULL && $sessionNotOnOrAfter <= time() - 60) { throw new SimpleSAML_Error_Exception('Received an assertion with a session that has expired. Check clock synchronization on IdP and SP.'); } $validAudiences = $assertion->getValidAudiences(); if ($validAudiences !== NULL) { $spEntityId = $spMetadata->getString('entityid'); if (!in_array($spEntityId, $validAudiences, TRUE)) { $candidates = '[' . implode('], [', $validAudiences) . ']'; throw new SimpleSAML_Error_Exception('This SP [' . $spEntityId . '] is not a valid audience for the assertion. Candidates were: ' . $candidates); } } $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. */ /* Maybe we need to base64 decode the attributes in the assertion? */ if ($idpMetadata->getBoolean('base64attributes', FALSE)) { $attributes = $assertion->getAttributes(); $newAttributes = array(); foreach ($attributes as $name => $values) { $newAttributes[$name] = array(); foreach ($values as $value) { foreach (explode('_', $value) as $v) { $newAttributes[$name][] = base64_decode($v); } } } $assertion->setAttributes($newAttributes); } /* Decrypt the NameID element if it is encrypted. */ if ($assertion->isNameIdEncrypted()) { try { $keys = self::getDecryptionKeys($idpMetadata, $spMetadata); } catch (Exception $e) { throw new SimpleSAML_Error_Exception('Error decrypting NameID: ' . $e->getMessage()); } $lastException = NULL; foreach ($keys as $i => $key) { try { $assertion->decryptNameId($key); SimpleSAML_Logger::debug('Decryption with key #' . $i . ' succeeded.'); $lastException = NULL; break; } catch (Exception $e) { SimpleSAML_Logger::debug('Decryption with key #' . $i . ' failed with exception: ' . $e->getMessage()); $lastException = $e; } } if ($lastException !== NULL) { throw $lastException; } } return $assertion; }
/** * Build a authentication response based on information in the metadata. * * @param SimpleSAML_Configuration $idpMetadata The metadata of the IdP. * @param SimpleSAML_Configuration $spMetadata The metadata of the SP. * @param string $consumerURL The Destination URL of the response. */ private static function buildResponse(SimpleSAML_Configuration $idpMetadata, SimpleSAML_Configuration $spMetadata, $consumerURL) { $signResponse = $spMetadata->getBoolean('saml20.sign.response', NULL); if ($signResponse === NULL) { $signResponse = $idpMetadata->getBoolean('saml20.sign.response', TRUE); } $r = new SAML2_Response(); $r->setIssuer($idpMetadata->getString('entityid')); $r->setDestination($consumerURL); if ($signResponse) { sspmod_saml_Message::addSign($idpMetadata, $spMetadata, $r); } return $r; }
public function testLoop() { $fixtureResponseDom = new DOMDocument(); $fixtureResponseDom->loadXML(<<<XML <samlp:Response xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="s2a0da3504aff978b0f8c80f6a62c713c4a2f64c5b" InResponseTo="_bec424fa5103428909a30ff1e31168327f79474984" Version="2.0" IssueInstant="2007-12-10T11:39:48Z" Destination="http://moodle.bridge.feide.no/simplesaml/saml2/sp/AssertionConsumerService.php"> <saml:Issuer>max.feide.no</saml:Issuer> <samlp:Extensions> <myns:AttributeList xmlns:myns="urn:mynamespace"> <myns:Attribute name="UserName" value=""/> </myns:AttributeList> </samlp:Extensions> <samlp:Status> <samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"/> </samlp:Status> <saml:Assertion xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xs="http://www.w3.org/2001/XMLSchema" Version="2.0" ID="s2b7afe8e21a0910d027dfbc94ec4b862e1fbbd9ab" IssueInstant="2007-12-10T11:39:48Z"> <saml:Issuer>max.feide.no</saml:Issuer> <saml:Subject> <saml:NameID NameQualifier="max.feide.no" SPNameQualifier="urn:mace:feide.no:services:no.feide.moodle" Format="urn:oasis:names:tc:SAML:2.0:nameid-format:persistent">UB/WJAaKAPrSHbqlbcKWu7JktcKY</saml:NameID> <saml:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer"> <saml:SubjectConfirmationData NotOnOrAfter="2007-12-10T19:39:48Z" InResponseTo="_bec424fa5103428909a30ff1e31168327f79474984" Recipient="http://moodle.bridge.feide.no/simplesaml/saml2/sp/AssertionConsumerService.php"/> </saml:SubjectConfirmation> </saml:Subject> <saml:Conditions NotBefore="2007-12-10T11:29:48Z" NotOnOrAfter="2007-12-10T19:39:48Z"> <saml:AudienceRestriction> <saml:Audience>urn:mace:feide.no:services:no.feide.moodle</saml:Audience> </saml:AudienceRestriction> </saml:Conditions> <saml:AuthnStatement AuthnInstant="2007-12-10T11:39:48Z" SessionIndex="s259fad9cad0cf7d2b3b68f42b17d0cfa6668e0201"> <saml:AuthnContext> <saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:Password</saml:AuthnContextClassRef> </saml:AuthnContext> </saml:AuthnStatement> <saml:AttributeStatement> <saml:Attribute Name="givenName"> <saml:AttributeValue xsi:type="xs:string">RkVJREUgVGVzdCBVc2VyIChnaXZlbk5hbWUpIMO4w6bDpcOYw4bDhQ==</saml:AttributeValue> </saml:Attribute> <saml:Attribute Name="eduPersonPrincipalName"> <saml:AttributeValue xsi:type="xs:string">dGVzdEBmZWlkZS5ubw==</saml:AttributeValue> </saml:Attribute> <saml:Attribute Name="o"> <saml:AttributeValue xsi:type="xs:string">VU5JTkVUVA==</saml:AttributeValue> </saml:Attribute> <saml:Attribute Name="ou"> <saml:AttributeValue xsi:type="xs:string">VU5JTkVUVA==</saml:AttributeValue> </saml:Attribute> <saml:Attribute Name="eduPersonOrgDN"> <saml:AttributeValue xsi:type="xs:string">ZGM9dW5pbmV0dCxkYz1ubw==</saml:AttributeValue> </saml:Attribute> <saml:Attribute Name="eduPersonPrimaryAffiliation"> <saml:AttributeValue xsi:type="xs:string">c3R1ZGVudA==</saml:AttributeValue> </saml:Attribute> <saml:Attribute Name="mail"> <saml:AttributeValue xsi:type="xs:string">bW9yaWEtc3VwcG9ydEB1bmluZXR0Lm5v</saml:AttributeValue> </saml:Attribute> <saml:Attribute Name="preferredLanguage"> <saml:AttributeValue xsi:type="xs:string">bm8=</saml:AttributeValue> </saml:Attribute> <saml:Attribute Name="eduPersonOrgUnitDN"> <saml:AttributeValue xsi:type="xs:string">b3U9dW5pbmV0dCxvdT1vcmdhbml6YXRpb24sZGM9dW5pbmV0dCxkYz1ubw==</saml:AttributeValue> </saml:Attribute> <saml:Attribute Name="sn"> <saml:AttributeValue xsi:type="xs:string">RkVJREUgVGVzdCBVc2VyIChzbikgw7jDpsOlw5jDhsOF</saml:AttributeValue> </saml:Attribute> <saml:Attribute Name="cn"> <saml:AttributeValue xsi:type="xs:string">RkVJREUgVGVzdCBVc2VyIChjbikgw7jDpsOlw5jDhsOF</saml:AttributeValue> </saml:Attribute> <saml:Attribute Name="eduPersonAffiliation"> <saml:AttributeValue xsi:type="xs:string">ZW1wbG95ZWU=_c3RhZmY=_c3R1ZGVudA==</saml:AttributeValue> </saml:Attribute> </saml:AttributeStatement> </saml:Assertion> </samlp:Response> XML ); $request = new SAML2_Response($fixtureResponseDom->firstChild); $requestXml = $requestDocument = $request->toUnsignedXML()->ownerDocument->C14N(); $fixtureXml = $fixtureResponseDom->C14N(); $this->assertXmlStringEqualsXmlString($fixtureXml, $requestXml, 'Response after Unmarshalling and re-marshalling remains the same'); }