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\Constants::CM_BEARER, \SAML2\Constants::CM_HOK, \SAML2\Constants::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\Constants::CM_BEARER && $hok) {
$lastError = 'Bearer SubjectConfirmation received, but Holder-of-Key SubjectConfirmation needed';
continue;
}
if ($sc->Method === \SAML2\Constants::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\Constants::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;
}