Newer
Older
<?php
/**
* Common code for building SAML 2 messages based on the
* available metadata.
*
* @package simpleSAMLphp
*/
class sspmod_saml_Message {
Jaime Pérez Crespo
committed
* Add signature key and sender certificate to an element (Message or Assertion).
Olav Morken
committed
* @param SimpleSAML_Configuration $srcMetadata The metadata of the sender.
* @param SimpleSAML_Configuration $dstMetadata The metadata of the recipient.
* @param SAML2_Message $element The element we should add the data to.
Jaime Pérez Crespo
committed
public static function addSign(SimpleSAML_Configuration $srcMetadata, SimpleSAML_Configuration $dstMetadata, SAML2_SignedElement $element) {
$dstPrivateKey = $dstMetadata->getString('signature.privatekey', NULL);
$keyArray = SimpleSAML\Utils\Crypto::loadPrivateKey($dstMetadata, TRUE, 'signature.');
$certArray = SimpleSAML\Utils\Crypto::loadPublicKey($dstMetadata, FALSE, 'signature.');
$keyArray = SimpleSAML\Utils\Crypto::loadPrivateKey($srcMetadata, TRUE);
$certArray = SimpleSAML\Utils\Crypto::loadPublicKey($srcMetadata, FALSE);
Jaime Pérez Crespo
committed
$algo = $dstMetadata->getString('signature.algorithm', NULL);
if ($algo === NULL) {
/*
* In the NIST Special Publication 800-131A, SHA-1 became deprecated for generating
* new digital signatures in 2011, and will be explicitly disallowed starting the 1st
* of January, 2014. We'll keep this as a default for the next release and mark it
* as deprecated, as part of the transition to SHA-256.
*
* See http://csrc.nist.gov/publications/nistpubs/800-131A/sp800-131A.pdf for more info.
*
* TODO: change default to XMLSecurityKey::RSA_SHA256.
*/
$algo = $srcMetadata->getString('signature.algorithm', XMLSecurityKey::RSA_SHA1);
Jaime Pérez Crespo
committed
}
Jaime Pérez Crespo
committed
$privateKey = new XMLSecurityKey($algo, array('type' => 'private'));
if (array_key_exists('password', $keyArray)) {
$privateKey->passphrase = $keyArray['password'];
}
$privateKey->loadKey($keyArray['PEM'], FALSE);
Olav Morken
committed
$element->setSignatureKey($privateKey);
if ($certArray === NULL) {
/* We don't have a certificate to add. */
return;
}
if (!array_key_exists('PEM', $certArray)) {
/* We have a public key with only a fingerprint. */
return;
}
Olav Morken
committed
$element->setCertificates(array($certArray['PEM']));
}
/**
* Add signature key and and senders certificate to message.
*
* @param SimpleSAML_Configuration $srcMetadata The metadata of the sender.
* @param SimpleSAML_Configuration $dstMetadata The metadata of the recipient.
* @param SAML2_Message $message The message we should add the data to.
*/
private static function addRedirectSign(SimpleSAML_Configuration $srcMetadata, SimpleSAML_Configuration $dstMetadata, SAML2_message $message) {
if ($message instanceof SAML2_LogoutRequest || $message instanceof SAML2_LogoutResponse) {
$signingEnabled = $srcMetadata->getBoolean('sign.logout', NULL);
if ($signingEnabled === NULL) {
$signingEnabled = $dstMetadata->getBoolean('sign.logout', NULL);
}
} elseif ($message instanceof SAML2_AuthnRequest) {
$signingEnabled = $srcMetadata->getBoolean('sign.authnrequest', NULL);
if ($signingEnabled === NULL) {
$signingEnabled = $dstMetadata->getBoolean('sign.authnrequest', NULL);
}
}
Olav Morken
committed
if ($signingEnabled === NULL) {
$signingEnabled = $dstMetadata->getBoolean('redirect.sign', NULL);
if ($signingEnabled === NULL) {
$signingEnabled = $srcMetadata->getBoolean('redirect.sign', FALSE);
}
Olav Morken
committed
}
if (!$signingEnabled) {
return;
}
self::addSign($srcMetadata, $dstMetadata, $message);
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
/**
* Find the certificate used to sign a message or assertion.
*
* An exception is thrown if we are unable to locate the certificate.
*
* @param array $certFingerprints The fingerprints we are looking for.
* @param array $certificates Array of certificates.
* @return string Certificate, in PEM-format.
*/
private static function findCertificate(array $certFingerprints, array $certificates) {
$candidates = array();
foreach ($certificates as $cert) {
$fp = strtolower(sha1(base64_decode($cert)));
if (!in_array($fp, $certFingerprints, TRUE)) {
$candidates[] = $fp;
continue;
}
/* We have found a matching fingerprint. */
$pem = "-----BEGIN CERTIFICATE-----\n" .
chunk_split($cert, 64) .
"-----END CERTIFICATE-----\n";
return $pem;
}
$candidates = "'" . implode("', '", $candidates) . "'";
$fps = "'" . implode("', '", $certFingerprints) . "'";
throw new SimpleSAML_Error_Exception('Unable to find a certificate matching the configured ' .
'fingerprint. Candidates: ' . $candidates . '; certFingerprint: ' . $fps . '.');
}
/**
* Check the signature on a SAML2 message or assertion.
*
* @param SimpleSAML_Configuration $srcMetadata The metadata of the sender.
* @param SAML2_SignedElement $element Either a SAML2_Response or a SAML2_Assertion.
*/
public static function checkSign(SimpleSAML_Configuration $srcMetadata, SAML2_SignedElement $element) {
/* Find the public key that should verify signatures by this entity. */
$keys = $srcMetadata->getPublicKeys('signing');
if ($keys !== NULL) {
$pemKeys = array();
foreach ($keys as $key) {
switch ($key['type']) {
case 'X509Certificate':
$pemKeys[] = "-----BEGIN CERTIFICATE-----\n" .
chunk_split($key['X509Certificate'], 64) .
"-----END CERTIFICATE-----\n";
break;
default:
SimpleSAML_Logger::debug('Skipping unknown key type: ' . $key['type']);
} elseif ($srcMetadata->hasValue('certFingerprint')) {
$certFingerprint = $srcMetadata->getArrayizeString('certFingerprint');
foreach ($certFingerprint as &$fp) {
$fp = strtolower(str_replace(':', '', $fp));
}
$certificates = $element->getCertificates();
/*
* We don't have the full certificate stored. Try to find it
* in the message or the assertion instead.
*/
if (count($certificates) === 0) {
/* We need the full certificate in order to match it against the fingerprint. */
SimpleSAML_Logger::debug('No certificate in message when validating against fingerprint.');
return FALSE;
} else {
SimpleSAML_Logger::debug('Found ' . count($certificates) . ' certificates in ' . get_class($element));
$pemCert = self::findCertificate($certFingerprint, $certificates);
$pemKeys = array($pemCert);
throw new SimpleSAML_Error_Exception(
'Missing certificate in metadata for ' .
var_export($srcMetadata->getString('entityid'), TRUE));
SimpleSAML_Logger::debug('Has ' . count($pemKeys) . ' candidate keys for validation.');
$lastException = NULL;
foreach ($pemKeys as $i => $pem) {
$key = new XMLSecurityKey(XMLSecurityKey::RSA_SHA1, array('type'=>'public'));
$key->loadKey($pem);
try {
/*
* Make sure that we have a valid signature on either the response
* or the assertion.
*/
$res = $element->validate($key);
if ($res) {
SimpleSAML_Logger::debug('Validation with key #' . $i . ' succeeded.');
return TRUE;
}
SimpleSAML_Logger::debug('Validation with key #' . $i . ' failed without exception.');
} catch (Exception $e) {
SimpleSAML_Logger::debug('Validation with key #' . $i . ' failed with exception: ' . $e->getMessage());
$lastException = $e;
}
}
/* We were unable to validate the signature with any of our keys. */
if ($lastException !== NULL) {
throw $lastException;
} else {
return FALSE;
}
/**
* Check signature on a SAML2 message if enabled.
*
* @param SimpleSAML_Configuration $srcMetadata The metadata of the sender.
* @param SimpleSAML_Configuration $dstMetadata The metadata of the recipient.
* @param SAML2_Message $message The message we should check the signature on.
*/
public static function validateMessage(
SimpleSAML_Configuration $srcMetadata,
SimpleSAML_Configuration $dstMetadata,
SAML2_Message $message
) {
if ($message instanceof SAML2_LogoutRequest || $message instanceof SAML2_LogoutResponse) {
$enabled = $srcMetadata->getBoolean('validate.logout', NULL);
if ($enabled === NULL) {
$enabled = $dstMetadata->getBoolean('validate.logout', NULL);
}
} elseif ($message instanceof SAML2_AuthnRequest) {
$enabled = $srcMetadata->getBoolean('validate.authnrequest', NULL);
if ($enabled === NULL) {
$enabled = $dstMetadata->getBoolean('validate.authnrequest', NULL);
}
}
if ($enabled === NULL) {
$enabled = $srcMetadata->getBoolean('redirect.validate', NULL);
if ($enabled === NULL) {
$enabled = $dstMetadata->getBoolean('redirect.validate', FALSE);
}
}
if (!$enabled) {
return;
}
if (!self::checkSign($srcMetadata, $message)) {
throw new SimpleSAML_Error_Exception('Validation of received messages enabled, but no signature found on message.');
}
}
/**
* Retrieve the decryption keys from metadata.
*
* @param SimpleSAML_Configuration $srcMetadata The metadata of the sender (IdP).
* @param SimpleSAML_Configuration $dstMetadata The metadata of the recipient (SP).
* @return array Array of decryption keys.
*/
public static function getDecryptionKeys(SimpleSAML_Configuration $srcMetadata,
SimpleSAML_Configuration $dstMetadata) {
$sharedKey = $srcMetadata->getString('sharedkey', NULL);
if ($sharedKey !== NULL) {
$key = new XMLSecurityKey(XMLSecurityKey::AES128_CBC);
$key->loadKey($sharedKey);
return array($key);
}
$keys = array();
/* Load the new private key if it exists. */
$keyArray = SimpleSAML\Utils\Crypto::loadPrivateKey($dstMetadata, FALSE, 'new_');
if ($keyArray !== NULL) {
assert('isset($keyArray["PEM"])');
$key = new XMLSecurityKey(XMLSecurityKey::RSA_1_5, array('type'=>'private'));
if (array_key_exists('password', $keyArray)) {
$key->passphrase = $keyArray['password'];
}
$key->loadKey($keyArray['PEM']);
}
$keyArray = SimpleSAML\Utils\Crypto::loadPrivateKey($dstMetadata, TRUE);
assert('isset($keyArray["PEM"])');
$key = new XMLSecurityKey(XMLSecurityKey::RSA_1_5, array('type'=>'private'));
if (array_key_exists('password', $keyArray)) {
$key->passphrase = $keyArray['password'];
}
$key->loadKey($keyArray['PEM']);
$keys[] = $key;
return $keys;
}
/**
* Retrieve blacklisted algorithms.
*
* Remote configuration overrides local configuration.
*
* @param SimpleSAML_Configuration $srcMetadata The metadata of the sender.
* @param SimpleSAML_Configuration $dstMetadata The metadata of the recipient.
* @return array Array of blacklisted algorithms.
*/
public static function getBlacklistedAlgorithms(SimpleSAML_Configuration $srcMetadata,
SimpleSAML_Configuration $dstMetadata) {
$blacklist = $srcMetadata->getArray('encryption.blacklisted-algorithms', NULL);
if ($blacklist === NULL) {
$blacklist = $dstMetadata->getArray('encryption.blacklisted-algorithms', array(XMLSecurityKey::RSA_1_5));
}
return $blacklist;
}
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
/**
* Decrypt an assertion.
*
* This function takes in a SAML2_Assertion and decrypts it if it is encrypted.
* If it is unencrypted, and encryption is enabled in the metadata, an exception
* will be throws.
*
* @param SimpleSAML_Configuration $srcMetadata The metadata of the sender (IdP).
* @param SimpleSAML_Configuration $dstMetadata The metadata of the recipient (SP).
* @param SAML2_Assertion|SAML2_EncryptedAssertion $assertion The assertion we are decrypting.
* @return SAML2_Assertion The assertion.
*/
private static function decryptAssertion(SimpleSAML_Configuration $srcMetadata,
SimpleSAML_Configuration $dstMetadata, $assertion) {
assert('$assertion instanceof SAML2_Assertion || $assertion instanceof SAML2_EncryptedAssertion');
if ($assertion instanceof SAML2_Assertion) {
$encryptAssertion = $srcMetadata->getBoolean('assertion.encryption', NULL);
if ($encryptAssertion === NULL) {
$encryptAssertion = $dstMetadata->getBoolean('assertion.encryption', FALSE);
}
if ($encryptAssertion) {
/* The assertion was unencrypted, but we have encryption enabled. */
throw new Exception('Received unencrypted assertion, but encryption was enabled.');
}
return $assertion;
}
try {
$keys = self::getDecryptionKeys($srcMetadata, $dstMetadata);
} catch (Exception $e) {
throw new SimpleSAML_Error_Exception('Error decrypting assertion: ' . $e->getMessage());
$blacklist = self::getBlacklistedAlgorithms($srcMetadata, $dstMetadata);
$lastException = NULL;
foreach ($keys as $i => $key) {
try {
$ret = $assertion->getAssertion($key, $blacklist);
SimpleSAML_Logger::debug('Decryption with key #' . $i . ' succeeded.');
return $ret;
} catch (Exception $e) {
SimpleSAML_Logger::debug('Decryption with key #' . $i . ' failed with exception: ' . $e->getMessage());
$lastException = $e;
}
}
throw $lastException;
* Retrieve the status code of a response as a sspmod_saml_Error.
*
* @param SAML2_StatusResponse $response The response.
* @return sspmod_saml_Error The error.
*/
public static function getResponseError(SAML2_StatusResponse $response) {
$status = $response->getStatus();
return new sspmod_saml_Error($status['Code'], $status['SubCode'], $status['Message']);
/**
* Build an authentication request based on information in the metadata.
*
* @param SimpleSAML_Configuration $spMetadata The metadata of the service provider.
* @param SimpleSAML_Configuration $idpMetadata The metadata of the identity provider.
*/
public static function buildAuthnRequest(SimpleSAML_Configuration $spMetadata, SimpleSAML_Configuration $idpMetadata) {
$ar = new SAML2_AuthnRequest();
if ($spMetadata->hasValue('NameIDPolicy')) {
$nameIdPolicy = $spMetadata->getString('NameIDPolicy', NULL);
} else {
$nameIdPolicy = $spMetadata->getString('NameIDFormat', SAML2_Const::NAMEID_TRANSIENT);
}
if ($nameIdPolicy !== NULL) {
$ar->setNameIdPolicy(array(
'Format' => $nameIdPolicy,
'AllowCreate' => TRUE,
$ar->setForceAuthn($spMetadata->getBoolean('ForceAuthn', FALSE));
$ar->setIsPassive($spMetadata->getBoolean('IsPassive', FALSE));
$protbind = $spMetadata->getValueValidate('ProtocolBinding', array(
SAML2_Const::BINDING_HTTP_POST,
SAML2_Const::BINDING_HTTP_ARTIFACT,
SAML2_Const::BINDING_HTTP_REDIRECT,
), SAML2_Const::BINDING_HTTP_POST);
/* Shoaib - setting the appropriate binding based on parameter in sp-metadata defaults to HTTP_POST */
$ar->setProtocolBinding($protbind);
$ar->setIssuer($spMetadata->getString('entityid'));
Olav Morken
committed
$ar->setAssertionConsumerServiceIndex($spMetadata->getInteger('AssertionConsumerServiceIndex', NULL));
$ar->setAttributeConsumingServiceIndex($spMetadata->getInteger('AttributeConsumingServiceIndex', NULL));
if ($spMetadata->hasValue('AuthnContextClassRef')) {
$accr = $spMetadata->getArrayizeString('AuthnContextClassRef');
$ar->setRequestedAuthnContext(array('AuthnContextClassRef' => $accr));
}
Olav Morken
committed
self::addRedirectSign($spMetadata, $idpMetadata, $ar);
return $ar;
}
/**
* Build a logout request based on information in the metadata.
*
* @param SimpleSAML_Configuration $srcMetadata The metadata of the sender.
* @param SimpleSAML_Configuration $dstpMetadata The metadata of the recipient.
*/
public static function buildLogoutRequest(SimpleSAML_Configuration $srcMetadata, SimpleSAML_Configuration $dstMetadata) {
$lr = new SAML2_LogoutRequest();
$lr->setIssuer($srcMetadata->getString('entityid'));
Olav Morken
committed
self::addRedirectSign($srcMetadata, $dstMetadata, $lr);
/**
* Build a logout response based on information in the metadata.
*
* @param SimpleSAML_Configuration $srcMetadata The metadata of the sender.
* @param SimpleSAML_Configuration $dstpMetadata The metadata of the recipient.
*/
public static function buildLogoutResponse(SimpleSAML_Configuration $srcMetadata, SimpleSAML_Configuration $dstMetadata) {
$lr = new SAML2_LogoutResponse();
$lr->setIssuer($srcMetadata->getString('entityid'));
Olav Morken
committed
self::addRedirectSign($srcMetadata, $dstMetadata, $lr);
return $lr;
}
/**
* Process a response message.
*
* If the response is an error response, we will throw a sspmod_saml_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 array Array with SAML2_Assertion objects, containing valid assertions from the response.
*/
public static function processResponse(
SimpleSAML_Configuration $spMetadata, SimpleSAML_Configuration $idpMetadata,
SAML2_Response $response
) {
if (!$response->isSuccess()) {
throw self::getResponseError($response);
}
/* Validate Response-element destination. */
$currentURL = \SimpleSAML\Utils\HTTP::getSelfURLNoQuery();
$msgDestination = $response->getDestination();
if ($msgDestination !== NULL && $msgDestination !== $currentURL) {
throw new Exception('Destination in response doesn\'t match the current URL. Destination is "' .
$msgDestination . '", current URL is "' . $currentURL . '".');
}
$responseSigned = self::checkSign($idpMetadata, $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.');
}
$ret = array();
foreach ($assertion as $a) {
$ret[] = self::processAssertion($spMetadata, $idpMetadata, $response, $a, $responseSigned);
}
return $ret;
}
/**
* 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 */
Jaime Perez Crespo
committed
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';
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
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;
}
/**
* Retrieve the encryption key for the given entity.
*
* @param SimpleSAML_Configuration $metadata The metadata of the entity.
* @return XMLSecurityKey The encryption key.
*/
public static function getEncryptionKey(SimpleSAML_Configuration $metadata) {
$sharedKey = $metadata->getString('sharedkey', NULL);
if ($sharedKey !== NULL) {
$key = new XMLSecurityKey(XMLSecurityKey::AES128_CBC);
$key->loadKey($sharedKey);
return $key;
}
$keys = $metadata->getPublicKeys('encryption', TRUE);
foreach ($keys as $key) {
switch ($key['type']) {
case 'X509Certificate':
$pemKey = "-----BEGIN CERTIFICATE-----\n" .
chunk_split($key['X509Certificate'], 64) .
"-----END CERTIFICATE-----\n";
$key = new XMLSecurityKey(XMLSecurityKey::RSA_OAEP_MGF1P, array('type'=>'public'));
$key->loadKey($pemKey);
return $key;
}
}
throw new SimpleSAML_Error_Exception('No supported encryption key in ' . var_export($metadata->getString('entityid'), TRUE));
}