Newer
Older
<?php
/**
* Common code for building SAML 2 messages based on the
* available metadata.
*
* @package simpleSAMLphp
* @version $Id$
*/
class sspmod_saml2_Message {
/**
* Retrieve the destination we should send the message to.
*
* This will return a debug endpoint if we have debug enabled. If debug
* is disabled, NULL is returned, in which case the default destination
* will be used.
*
* @return string|NULL The destination the message should be delivered to.
*/
public static function getDebugDestination() {
$globalConfig = SimpleSAML_Configuration::getInstance();
if (!$globalConfig->getBoolean('debug', FALSE)) {
return NULL;
}
return SimpleSAML_Module::getModuleURL('saml2/debug.php');
}
/**
Olav Morken
committed
* Add signature key and and senders 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.
public static function addSign(SimpleSAML_Configuration $srcMetadata, SimpleSAML_Configuration $dstMetadata, SAML2_SignedElement $element) {
$keyArray = SimpleSAML_Utilities::loadPrivateKey($srcMetadata, TRUE);
$certArray = SimpleSAML_Utilities::loadPublicKey($srcMetadata, FALSE);
$privateKey = new XMLSecurityKey(XMLSecurityKey::RSA_SHA1, 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) {
$signingEnabled = $dstMetadata->getBoolean('redirect.sign', NULL);
if ($signingEnabled === NULL) {
$signingEnabled = $srcMetadata->getBoolean('redirect.sign', FALSE);
}
if (!$signingEnabled) {
return;
}
self::addSign($srcMetadata, $dstMetadata, $message);
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
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
/**
* 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) {
$certificates = $element->getCertificates();
SimpleSAML_Logger::debug('Found ' . count($certificates) . ' certificates in ' . get_class($element));
/* Find the certificate that should verify signatures by this entity. */
$certArray = SimpleSAML_Utilities::loadPublicKey($srcMetadata, FALSE);
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
if ($certArray !== NULL) {
if (array_key_exists('PEM', $certArray)) {
$pemCert = $certArray['PEM'];
} else {
/*
* 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;
}
$certFingerprints = $certArray['certFingerprint'];
if (count($certFingerprints) === 0) {
/* For some reason, we have a certFingerprint entry without any fingerprints. */
throw new SimpleSAML_Error_Exception('certFingerprint array was empty.');
}
$pemCert = self::findCertificate($certFingerprints, $certificates);
}
} else {
/* Attempt CA validation. */
$caFile = $srcMetadata->getString('caFile', NULL);
if ($caFile === NULL) {
throw new SimpleSAML_Error_Exception(
'Missing certificate in metadata for ' .
var_export($srcMetadata->getString('entityid'), TRUE));
}
$caFile = SimpleSAML_Utilities::resolveCert($caFile);
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
if (count($certificates) === 0) {
/* We need the full certificate in order to check it against the CA file. */
SimpleSAML_Logger::debug('No certificate in message when validating with CA.');
return FALSE;
}
/* We assume that it is the first certificate that was used to sign the message. */
$pemCert = "-----BEGIN CERTIFICATE-----\n" .
chunk_split($certificates[0], 64) .
"-----END CERTIFICATE-----\n";
SimpleSAML_Utilities::validateCA($pemCert, $caFile);
}
/* Extract the public key from the certificate for validation. */
$key = new XMLSecurityKey(XMLSecurityKey::RSA_SHA1, array('type'=>'public'));
$key->loadKey($pemCert);
/*
* Make sure that we have a valid signature on either the response
* or the assertion.
*/
return $element->validate($key);
}
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
/**
* 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
) {
$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 key from metadata.
*
* @param SimpleSAML_Configuration $srcMetadata The metadata of the sender (IdP).
* @param SimpleSAML_Configuration $dstMetadata The metadata of the recipient (SP).
* @return XMLSecurityKey The decryption key.
*/
private static function getDecryptionKey(SimpleSAML_Configuration $srcMetadata,
SimpleSAML_Configuration $dstMetadata) {
$sharedKey = $srcMetadata->getString('sharedkey', NULL);
if ($sharedKey !== NULL) {
$key = new XMLSecurityKey(XMLSecurityKey::AES128_CBC);
$key->loadKey($sharedKey);
} else {
/* Find the private key we should use to decrypt messages to this SP. */
$keyArray = SimpleSAML_Utilities::loadPrivateKey($dstMetadata, TRUE);
if (!array_key_exists('PEM', $keyArray)) {
throw new Exception('Unable to locate key we should use to decrypt the message.');
}
/* Extract the public key from the certificate for encryption. */
$key = new XMLSecurityKey(XMLSecurityKey::RSA_1_5, array('type'=>'private'));
if (array_key_exists('password', $keyArray)) {
$key->passphrase = $keyArray['password'];
}
$key->loadKey($keyArray['PEM']);
}
return $key;
}
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
/**
* Encrypt an assertion.
*
* This function takes in a SAML2_Assertion and encrypts it if encryption of
* assertions are enabled in the metadata.
*
* @param SimpleSAML_Configuration $srcMetadata The metadata of the sender (IdP).
* @param SimpleSAML_Configuration $dstMetadata The metadata of the recipient (SP).
* @param SAML2_Assertion $assertion The assertion we are encrypting.
* @return SAML2_Assertion|SAML2_EncryptedAssertion The assertion.
*/
public static function encryptAssertion(SimpleSAML_Configuration $srcMetadata,
SimpleSAML_Configuration $dstMetadata, SAML2_Assertion $assertion) {
$encryptAssertion = $dstMetadata->getBoolean('assertion.encryption', NULL);
if ($encryptAssertion === NULL) {
$encryptAssertion = $srcMetadata->getBoolean('assertion.encryption', FALSE);
}
if (!$encryptAssertion) {
/* We are _not_ encrypting this assertion, and are therefore done. */
return $assertion;
}
$sharedKey = $dstMetadata->getString('sharedkey', NULL);
if ($sharedKey !== NULL) {
$key = new XMLSecurityKey(XMLSecurityKey::AES128_CBC);
$key->loadKey($sharedKey);
} else {
/* Find the certificate that we should use to encrypt messages to this SP. */
$certArray = SimpleSAML_Utilities::loadPublicKey($dstMetadata, TRUE);
if (!array_key_exists('PEM', $certArray)) {
throw new Exception('Unable to locate key we should use to encrypt the assertionst ' .
'to the SP: ' . var_export($dstMetadata->getString('entityid'), TRUE) . '.');
}
$pemCert = $certArray['PEM'];
/* Extract the public key from the certificate for encryption. */
$key = new XMLSecurityKey(XMLSecurityKey::RSA_1_5, array('type'=>'public'));
$key->loadKey($pemCert);
}
$ea = new SAML2_EncryptedAssertion();
$ea->setAssertion($assertion, $key);
return $ea;
}
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
/**
* 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 {
$key = self::getDecryptionKey($srcMetadata, $dstMetadata);
} catch (Exception $e) {
throw new SimpleSAML_Error_Exception('Error decrypting assertion: ' . $e->getMessage());
}
return $assertion->getAssertion($key);
}
/**
* Retrieve the status code of a response as a sspmod_saml2_error.
*
* @param SAML2_StatusResponse $response The response.
* @return sspmod_saml2_Error The error.
*/
public static function getResponseError(SAML2_StatusResponse $response) {
$status = $response->getStatus();
return new sspmod_saml2_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,
$dst = $idpMetadata->getDefaultEndpoint('SingleSignOnService', array(SAML2_Const::BINDING_HTTP_REDIRECT));
$dst = $dst['Location'];
$ar->setIssuer($spMetadata->getString('entityid'));
$ar->setForceAuthn($spMetadata->getBoolean('ForceAuthn', FALSE));
$ar->setIsPassive($spMetadata->getBoolean('IsPassive', FALSE));
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) {
$dst = $dstMetadata->getDefaultEndpoint('SingleLogoutService', array(SAML2_Const::BINDING_HTTP_REDIRECT));
$dst = $dst['Location'];
$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) {
$dst = $dstMetadata->getDefaultEndpoint('SingleLogoutService', array(SAML2_Const::BINDING_HTTP_REDIRECT));
if (isset($dst['ResponseLocation'])) {
$dst = $dst['ResponseLocation'];
} else {
$dst = $dst['Location'];
}
$lr = new SAML2_LogoutResponse();
$lr->setIssuer($srcMetadata->getString('entityid'));
$lr->setDestination($dst);
Olav Morken
committed
self::addRedirectSign($srcMetadata, $dstMetadata, $lr);
return $lr;
}
/**
* Calculate the NameID value that should be used.
*
* @param SimpleSAML_Configuration $srcMetadata The metadata of the sender (IdP).
* @param SimpleSAML_Configuration $dstMetadata The metadata of the recipient (SP).
* @param array $attributes The attributes of the user
* @return string The NameID value.
*/
private static function generateNameIdValue(SimpleSAML_Configuration $srcMetadata,
SimpleSAML_Configuration $dstMetadata, array $attributes) {
$attribute = $dstMetadata->getString('simplesaml.nameidattribute', NULL);
if ($attribute === NULL) {
$attribute = $srcMetadata->getString('simplesaml.nameidattribute', NULL);
if ($attribute === NULL) {
/* generate a stable id */
return SimpleSAML_Utilities::generateUserIdentifier($srcMetadata->getString( 'entityid' ),
$dstMetadata->getString( 'entityid' ),
$attributes );
}
}
if (!array_key_exists($attribute, $attributes)) {
SimpleSAML_Logger::error('Unable to add NameID: Missing ' . var_export($attribute, TRUE) .
' in the attributes of the user.');
return SimpleSAML_Utilities::generateID();
}
return $attributes[$attribute][0];
}
/**
* Helper function for encoding attributes.
*
* @param SimpleSAML_Configuration $srcMetadata The metadata of the sender (IdP).
* @param SimpleSAML_Configuration $dstMetadata The metadata of the recipient (SP).
* @param array $attributes The attributes of the user
* @return array The encoded attributes.
*/
private static function encodeAttributes(SimpleSAML_Configuration $srcMetadata,
SimpleSAML_Configuration $dstMetadata, array $attributes) {
Olav Morken
committed
$base64Attributes = $dstMetadata->getBoolean('base64attributes', NULL);
if ($base64Attributes === NULL) {
$base64Attributes = $srcMetadata->getBoolean('base64attributes', FALSE);
}
if ($base64Attributes) {
$defaultEncoding = 'base64';
} else {
$defaultEncoding = 'string';
}
Olav Morken
committed
$srcEncodings = $srcMetadata->getArray('attributeencodings', array());
$dstEncodings = $dstMetadata->getArray('attributeencodings', array());
/*
* Merge the two encoding arrays. Encodings specified in the target metadata
* takes precedence over the source metadata.
*/
$encodings = array_merge($srcEncodings, $dstEncodings);
$ret = array();
foreach ($attributes as $name => $values) {
$ret[$name] = array();
if (array_key_exists($name, $encodings)) {
$encoding = $encodings[$name];
} else {
$encoding = $defaultEncoding;
}
foreach ($values as $value) {
switch ($encoding) {
case 'string':
$value = (string)$value;
break;
case 'base64':
$value = base64_encode((string)$value);
if (is_string($value)) {
$doc = new DOMDocument();
$doc->loadXML('<root>' . $value . '</root>');
$value = $doc->firstChild->childNodes;
}
assert('$value instanceof DOMNodeList');
break;
default:
throw new SimpleSAML_Error_Exception('Invalid encoding for attribute ' .
var_export($name, TRUE) . ': ' . var_export($encoding, TRUE));
}
$ret[$name][] = $value;
}
}
return $ret;
}
/**
* Build an assertion based on information in the metadata.
*
* @param SimpleSAML_Configuration $srcMetadata The metadata of the sender (IdP).
* @param SimpleSAML_Configuration $dstMetadata The metadata of the recipient (SP).
* @param array $attributes The attributes of the user
* @return SAML2_Assertion The assertion.
*/
public static function buildAssertion(SimpleSAML_Configuration $srcMetadata,
SimpleSAML_Configuration $dstMetadata, array $attributes, $consumerURL) {
$signAssertion = $dstMetadata->getBoolean('saml20.sign.assertion', NULL);
if ($signAssertion === NULL) {
$signAssertion = $srcMetadata->getBoolean('saml20.sign.assertion', TRUE);
}
$config = SimpleSAML_Configuration::getInstance();
$a = new SAML2_Assertion();
if ($signAssertion) {
self::addSign($srcMetadata, $dstMetadata, $a);
}
$a->setIssuer($srcMetadata->getString('entityid'));
$a->setDestination($consumerURL);
$a->setValidAudiences(array($dstMetadata->getString('entityid')));
$a->setNotBefore(time() - 30);
$assertionLifetime = $dstMetadata->getInteger('assertion.lifetime', NULL);
if ($assertionLifetime === NULL) {
$assertionLifetime = $srcMetadata->getInteger('assertion.lifetime', 300);
}
$a->setNotOnOrAfter(time() + $assertionLifetime);
$a->setAuthnContext(SAML2_Const::AC_PASSWORD);
$session = SimpleSAML_Session::getInstance();
$a->setAuthnInstant($session->getAuthnInstant());
$sessionLifetime = $config->getInteger('session.duration', 8*60*60);
$a->setSessionNotOnOrAfter(time() + $sessionLifetime);
$a->setSessionIndex(SimpleSAML_Utilities::generateID());
/* Add attributes. */
if ($dstMetadata->getBoolean('simplesaml.attributes', TRUE)) {
$attributeNameFormat = $dstMetadata->getString('AttributeNameFormat', NULL);
if ($attributeNameFormat === NULL) {
$attributeNameFormat = $srcMetadata->getString('AttributeNameFormat',
'urn:oasis:names:tc:SAML:2.0:attrname-format:basic');
}
$a->setAttributeNameFormat($attributeNameFormat);
$attributes = self::encodeAttributes($srcMetadata, $dstMetadata, $attributes);
$a->setAttributes($attributes);
}
/* Generate the NameID for the assertion. */
$nameIdFormat = $dstMetadata->getString('NameIDFormat', 'urn:oasis:names:tc:SAML:2.0:nameid-format:transient');
$spNameQualifier = $dstMetadata->getString('SPNameQualifier', NULL);
if ($spNameQualifier === NULL) {
$spNameQualifier = $dstMetadata->getString('entityid');
}
if ($nameIdFormat === SAML2_Const::NAMEID_TRANSIENT) {
/* generate a random id */
$nameIdValue = SimpleSAML_Utilities::generateID();
} else {
/* this code will end up generating either a fixed assigned id (via nameid.attribute)
or random id if not assigned/configured */
$nameIdValue = self::generateNameIdValue($srcMetadata, $dstMetadata, $attributes);
}
$a->setNameId(array(
'Format' => $nameIdFormat,
'Value' => $nameIdValue,
'SPNameQualifier' => $spNameQualifier,
));
return $a;
}
/**
* Build a authentication response based on information in the metadata.
*
* @param SimpleSAML_Configuration $srcMetadata The metadata of the sender (IdP).
* @param SimpleSAML_Configuration $dstMetadata The metadata of the recipient (SP).
*/
public static function buildResponse(SimpleSAML_Configuration $srcMetadata, SimpleSAML_Configuration $dstMetadata, $consumerURL) {
$signResponse = $dstMetadata->getBoolean('saml20.sign.response', NULL);
if ($signResponse === NULL) {
$signResponse = $srcMetadata->getBoolean('saml20.sign.response', TRUE);
}
$r = new SAML2_Response();
$r->setIssuer($srcMetadata->getString('entityid'));
$r->setDestination($consumerURL);
if ($signResponse) {
self::addSign($srcMetadata, $dstMetadata, $r);
}
return $r;
}
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
/**
* 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;
}