23 \
SAML2\SignedElement $element
25 $dstPrivateKey = $dstMetadata->
getString(
'signature.privatekey', null);
27 if ($dstPrivateKey !== null) {
37 $algo = $srcMetadata->
getString(
'signature.algorithm', XMLSecurityKey::RSA_SHA256);
41 if (array_key_exists(
'password', $keyArray)) {
42 $privateKey->passphrase = $keyArray[
'password'];
44 $privateKey->loadKey($keyArray[
'PEM'],
false);
46 $element->setSignatureKey($privateKey);
48 if ($certArray === null) {
53 if (!array_key_exists(
'PEM', $certArray)) {
58 $element->setCertificates(array($certArray[
'PEM']));
75 $signingEnabled = null;
76 if ($message instanceof \
SAML2\LogoutRequest || $message instanceof \
SAML2\LogoutResponse) {
77 $signingEnabled = $srcMetadata->
getBoolean(
'sign.logout', null);
78 if ($signingEnabled === null) {
79 $signingEnabled = $dstMetadata->
getBoolean(
'sign.logout', null);
81 } elseif ($message instanceof \
SAML2\AuthnRequest) {
82 $signingEnabled = $srcMetadata->
getBoolean(
'sign.authnrequest', null);
83 if ($signingEnabled === null) {
84 $signingEnabled = $dstMetadata->
getBoolean(
'sign.authnrequest', null);
88 if ($signingEnabled === null) {
89 $signingEnabled = $dstMetadata->
getBoolean(
'redirect.sign', null);
90 if ($signingEnabled === null) {
91 $signingEnabled = $srcMetadata->
getBoolean(
'redirect.sign',
false);
94 if (!$signingEnabled) {
98 self::addSign($srcMetadata, $dstMetadata, $message);
116 $candidates = array();
118 foreach ($certificates as $cert) {
119 $fp = strtolower(sha1(base64_decode($cert)));
120 if (!in_array($fp, $certFingerprints,
true)) {
126 $pem =
"-----BEGIN CERTIFICATE-----\n".
127 chunk_split($cert, 64).
128 "-----END CERTIFICATE-----\n";
132 $candidates =
"'".implode(
"', '", $candidates).
"'";
133 $fps =
"'".implode(
"', '", $certFingerprints).
"'";
135 'fingerprint. Candidates: '.$candidates.
'; certFingerprint: '.$fps.
'.');
156 switch ($key[
'type']) {
157 case 'X509Certificate':
158 $pemKeys[] =
"-----BEGIN CERTIFICATE-----\n".
159 chunk_split($key[
'X509Certificate'], 64).
160 "-----END CERTIFICATE-----\n";
166 } elseif ($srcMetadata->
hasValue(
'certFingerprint')) {
168 "Validating certificates by fingerprint is deprecated. Please use ".
169 "certData or certificate options in your remote metadata configuration." 173 foreach ($certFingerprint as &$fp) {
174 $fp = strtolower(str_replace(
':',
'', $fp));
188 $pemCert = self::findCertificate($certFingerprint,
$certificates);
189 $pemKeys = array($pemCert);
192 'Missing certificate in metadata for '.
193 var_export($srcMetadata->
getString(
'entityid'),
true)
199 $lastException = null;
200 foreach ($pemKeys as
$i => $pem) {
219 if ($lastException !== null) {
220 throw $lastException;
242 if ($message instanceof \
SAML2\LogoutRequest || $message instanceof \
SAML2\LogoutResponse) {
243 $enabled = $srcMetadata->
getBoolean(
'validate.logout', null);
244 if ($enabled === null) {
245 $enabled = $dstMetadata->
getBoolean(
'validate.logout', null);
247 } elseif ($message instanceof \
SAML2\AuthnRequest) {
248 $enabled = $srcMetadata->
getBoolean(
'validate.authnrequest', null);
249 if ($enabled === null) {
250 $enabled = $dstMetadata->
getBoolean(
'validate.authnrequest', null);
254 if ($enabled === null) {
255 $enabled = $srcMetadata->
getBoolean(
'redirect.validate', null);
256 if ($enabled === null) {
257 $enabled = $dstMetadata->
getBoolean(
'redirect.validate',
false);
265 if (!self::checkSign($srcMetadata, $message)) {
267 'Validation of received messages enabled, but no signature found on message.' 285 $sharedKey = $srcMetadata->
getString(
'sharedkey', null);
286 if ($sharedKey !== null) {
288 $key->loadKey($sharedKey);
296 if ($keyArray !== null) {
297 assert(isset($keyArray[
'PEM']));
300 if (array_key_exists(
'password', $keyArray)) {
301 $key->passphrase = $keyArray[
'password'];
303 $key->loadKey($keyArray[
'PEM']);
309 assert(isset($keyArray[
'PEM']));
312 if (array_key_exists(
'password', $keyArray)) {
313 $key->passphrase = $keyArray[
'password'];
315 $key->loadKey($keyArray[
'PEM']);
336 $blacklist = $srcMetadata->
getArray(
'encryption.blacklisted-algorithms', null);
337 if ($blacklist === null) {
338 $blacklist = $dstMetadata->
getArray(
'encryption.blacklisted-algorithms', array(XMLSecurityKey::RSA_1_5));
362 assert($assertion instanceof \
SAML2\Assertion || $assertion instanceof \
SAML2\EncryptedAssertion);
364 if ($assertion instanceof \
SAML2\Assertion) {
365 $encryptAssertion = $srcMetadata->
getBoolean(
'assertion.encryption', null);
366 if ($encryptAssertion === null) {
367 $encryptAssertion = $dstMetadata->
getBoolean(
'assertion.encryption',
false);
369 if ($encryptAssertion) {
371 throw new Exception(
'Received unencrypted assertion, but encryption was enabled.');
378 $keys = self::getDecryptionKeys($srcMetadata, $dstMetadata);
383 $blacklist = self::getBlacklistedAlgorithms($srcMetadata, $dstMetadata);
385 $lastException = null;
388 $ret = $assertion->getAssertion(
$key, $blacklist);
396 throw $lastException;
414 \
SAML2\Assertion &$assertion
416 if (!$assertion->hasEncryptedAttributes()) {
421 $keys = self::getDecryptionKeys($srcMetadata, $dstMetadata);
426 $blacklist = self::getBlacklistedAlgorithms($srcMetadata, $dstMetadata);
431 $assertion->decryptAttributes(
$key, $blacklist);
454 $status = $response->getStatus();
455 return new sspmod_saml_Error($status[
'Code'], $status[
'SubCode'], $status[
'Message']);
470 $ar = new \SAML2\AuthnRequest();
473 $nameIdPolicy = array();
474 if ($idpMetadata->
hasValue(
'NameIDPolicy')) {
475 $nameIdPolicy = $idpMetadata->
getValue(
'NameIDPolicy');
476 } elseif ($spMetadata->
hasValue(
'NameIDPolicy')) {
477 $nameIdPolicy = $spMetadata->
getValue(
'NameIDPolicy');
480 if (!is_array($nameIdPolicy)) {
482 $nameIdPolicy = array(
'Format' => $nameIdPolicy);
487 'Format' => $nameIdPolicy_cf->getString(
'Format', \
SAML2\Constants::NAMEID_TRANSIENT),
488 'AllowCreate' => $nameIdPolicy_cf->getBoolean(
'AllowCreate',
true),
490 $spNameQualifier = $nameIdPolicy_cf->getString(
'SPNameQualifier',
false);
491 if ($spNameQualifier !==
false) {
492 $policy[
'SPNameQualifier'] = $spNameQualifier;
494 $ar->setNameIdPolicy($policy);
496 $ar->setForceAuthn($spMetadata->
getBoolean(
'ForceAuthn',
false));
497 $ar->setIsPassive($spMetadata->
getBoolean(
'IsPassive',
false));
500 \
SAML2\Constants::BINDING_HTTP_POST,
501 \
SAML2\Constants::BINDING_HOK_SSO,
502 \
SAML2\Constants::BINDING_HTTP_ARTIFACT,
503 \
SAML2\Constants::BINDING_HTTP_REDIRECT,
504 ), \
SAML2\Constants::BINDING_HTTP_POST);
507 $ar->setProtocolBinding($protbind);
508 $ar->setIssuer($spMetadata->
getString(
'entityid'));
509 $ar->setAssertionConsumerServiceIndex($spMetadata->
getInteger(
'AssertionConsumerServiceIndex', null));
510 $ar->setAttributeConsumingServiceIndex($spMetadata->
getInteger(
'AttributeConsumingServiceIndex', null));
512 if ($spMetadata->
hasValue(
'AuthnContextClassRef')) {
515 \
SAML2\Constants::COMPARISON_EXACT,
516 \
SAML2\Constants::COMPARISON_MINIMUM,
517 \
SAML2\Constants::COMPARISON_MAXIMUM,
518 \
SAML2\Constants::COMPARISON_BETTER,
519 ), \
SAML2\Constants::COMPARISON_EXACT);
520 $ar->setRequestedAuthnContext(array(
'AuthnContextClassRef' => $accr,
'Comparison' => $comp));
523 self::addRedirectSign($spMetadata, $idpMetadata, $ar);
540 $lr = new \SAML2\LogoutRequest();
543 self::addRedirectSign($srcMetadata, $dstMetadata,
$lr);
560 $lr = new \SAML2\LogoutResponse();
563 self::addRedirectSign($srcMetadata, $dstMetadata,
$lr);
588 if (!$response->isSuccess()) {
589 throw self::getResponseError($response);
594 $msgDestination = $response->getDestination();
595 if ($msgDestination !== null && $msgDestination !== $currentURL) {
596 throw new Exception(
'Destination in response doesn\'t match the current URL. Destination is "'.
597 $msgDestination.
'", current URL is "'.$currentURL.
'".');
600 $responseSigned = self::checkSign($idpMetadata, $response);
606 $assertion = $response->getAssertions();
607 if (empty($assertion)) {
612 foreach ($assertion as $a) {
613 $ret[] = self::processAssertion($spMetadata, $idpMetadata, $response, $a, $responseSigned);
643 assert($assertion instanceof \
SAML2\Assertion || $assertion instanceof \
SAML2\EncryptedAssertion);
644 assert(is_bool($responseSigned));
646 $assertion = self::decryptAssertion($idpMetadata, $spMetadata, $assertion);
647 self::decryptAttributes($idpMetadata, $spMetadata, $assertion);
649 if (!self::checkSign($idpMetadata, $assertion)) {
650 if (!$responseSigned) {
658 $notBefore = $assertion->getNotBefore();
659 if ($notBefore !== null && $notBefore > time() + 60) {
661 'Received an assertion that is valid in the future. Check clock synchronization on IdP and SP.' 664 $notOnOrAfter = $assertion->getNotOnOrAfter();
665 if ($notOnOrAfter !== null && $notOnOrAfter <= time() - 60) {
667 'Received an assertion that has expired. Check clock synchronization on IdP and SP.' 670 $sessionNotOnOrAfter = $assertion->getSessionNotOnOrAfter();
671 if ($sessionNotOnOrAfter !== null && $sessionNotOnOrAfter <= time() - 60) {
673 'Received an assertion with a session that has expired. Check clock synchronization on IdP and SP.' 676 $validAudiences = $assertion->getValidAudiences();
677 if ($validAudiences !== null) {
679 if (!in_array(
$spEntityId, $validAudiences,
true)) {
680 $candidates =
'['.implode(
'], [', $validAudiences).
']';
682 '] is not a valid audience for the assertion. Candidates were: '.$candidates);
687 $lastError =
'No SubjectConfirmation element in Subject.';
688 $validSCMethods = array(\
SAML2\Constants::CM_BEARER, \
SAML2\Constants::CM_HOK, \
SAML2\Constants::CM_VOUCHES);
689 foreach ($assertion->getSubjectConfirmation() as
$sc) {
690 if (!in_array(
$sc->Method, $validSCMethods,
true)) {
691 $lastError =
'Invalid Method on SubjectConfirmation: '.var_export(
$sc->Method,
true);
696 $hok = $idpMetadata->
getBoolean(
'saml20.hok.assertion', null);
698 $hok = $spMetadata->
getBoolean(
'saml20.hok.assertion',
false);
700 if (
$sc->Method === \
SAML2\Constants::CM_BEARER && $hok) {
701 $lastError =
'Bearer SubjectConfirmation received, but Holder-of-Key SubjectConfirmation needed';
704 if (
$sc->Method === \
SAML2\Constants::CM_HOK && !$hok) {
705 $lastError =
'Holder-of-Key SubjectConfirmation received, '.
706 'but the Holder-of-Key profile is not enabled.';
710 $scd =
$sc->SubjectConfirmationData;
711 if (
$sc->Method === \
SAML2\Constants::CM_HOK) {
713 if (\
SimpleSAML\Utils\HTTP::isHTTPS() ===
false) {
714 $lastError =
'No HTTPS connection, but required for Holder-of-Key SSO';
717 if (isset(
$_SERVER[
'SSL_CLIENT_CERT']) && empty(
$_SERVER[
'SSL_CLIENT_CERT'])) {
718 $lastError =
'No client certificate provided during TLS Handshake with SP';
722 $clientCert =
$_SERVER[
'SSL_CLIENT_CERT'];
723 $pattern =
'/^-----BEGIN CERTIFICATE-----([^-]*)^-----END CERTIFICATE-----/m';
724 if (!preg_match($pattern, $clientCert, $matches)) {
725 $lastError =
'Error while looking for client certificate during TLS handshake with SP, the client '.
726 'certificate does not have the expected structure';
730 $clientCert = str_replace(array(
"\r",
"\n",
" "),
'', $matches[1]);
733 foreach ($scd->info as $thing) {
734 if ($thing instanceof \
SAML2\XML\ds\KeyInfo) {
738 if (count($keyInfo) != 1) {
739 $lastError =
'Error validating Holder-of-Key assertion: Only one <ds:KeyInfo> element in '.
740 '<SubjectConfirmationData> allowed';
745 foreach ($keyInfo[0]->
info as $thing) {
746 if ($thing instanceof \
SAML2\XML\ds\X509Data) {
747 $x509data[] = $thing;
750 if (count($x509data) != 1) {
751 $lastError =
'Error validating Holder-of-Key assertion: Only one <ds:X509Data> element in '.
752 '<ds:KeyInfo> within <SubjectConfirmationData> allowed';
757 foreach ($x509data[0]->
data as $thing) {
758 if ($thing instanceof \
SAML2\XML\ds\X509Certificate) {
759 $x509cert[] = $thing;
762 if (count($x509cert) != 1) {
763 $lastError =
'Error validating Holder-of-Key assertion: Only one <ds:X509Certificate> element in '.
764 '<ds:X509Data> within <SubjectConfirmationData> allowed';
768 $HoKCertificate = $x509cert[0]->certificate;
769 if ($HoKCertificate !== $clientCert) {
770 $lastError =
'Provided client certificate does not match the certificate bound to the '.
771 'Holder-of-Key assertion';
778 $lastError =
'No SubjectConfirmationData provided';
782 if ($scd->NotBefore && $scd->NotBefore > time() + 60) {
783 $lastError =
'NotBefore in SubjectConfirmationData is in the future: '.$scd->NotBefore;
786 if ($scd->NotOnOrAfter && $scd->NotOnOrAfter <= time() - 60) {
787 $lastError =
'NotOnOrAfter in SubjectConfirmationData is in the past: '.$scd->NotOnOrAfter;
790 if ($scd->Recipient !== null && $scd->Recipient !== $currentURL) {
791 $lastError =
'Recipient in SubjectConfirmationData does not match the current URL. Recipient is '.
792 var_export($scd->Recipient,
true).
', current URL is '.var_export($currentURL,
true).
'.';
795 if ($scd->InResponseTo !== null && $response->getInResponseTo() !== null &&
796 $scd->InResponseTo !== $response->getInResponseTo()
798 $lastError =
'InResponseTo in SubjectConfirmationData does not match the Response. Response has '.
799 var_export($response->getInResponseTo(),
true).
800 ', SubjectConfirmationData has '.var_export($scd->InResponseTo,
true).
'.';
811 if ($idpMetadata->
getBoolean(
'base64attributes',
false)) {
813 $newAttributes = array();
815 $newAttributes[
$name] = array();
817 foreach (explode(
'_', $value) as $v) {
818 $newAttributes[
$name][] = base64_decode($v);
822 $assertion->setAttributes($newAttributes);
826 if ($assertion->isNameIdEncrypted()) {
828 $keys = self::getDecryptionKeys($idpMetadata, $spMetadata);
833 $blacklist = self::getBlacklistedAlgorithms($idpMetadata, $spMetadata);
835 $lastException = null;
838 $assertion->decryptNameId(
$key, $blacklist);
840 $lastException = null;
847 if ($lastException !== null) {
848 throw $lastException;
868 $sharedKey = $metadata->
getString(
'sharedkey', null);
869 if ($sharedKey !== null) {
871 $key->loadKey($sharedKey);
877 switch ($key[
'type']) {
878 case 'X509Certificate':
879 $pemKey =
"-----BEGIN CERTIFICATE-----\n".
880 chunk_split($key[
'X509Certificate'], 64).
881 "-----END CERTIFICATE-----\n";
882 $key =
new XMLSecurityKey(XMLSecurityKey::RSA_OAEP_MGF1P, array(
'type' =>
'public'));
883 $key->loadKey($pemKey);
889 var_export($metadata->
getString(
'entityid'),
true));
static getBlacklistedAlgorithms(SimpleSAML_Configuration $srcMetadata, SimpleSAML_Configuration $dstMetadata)
Retrieve blacklisted algorithms.
static validateMessage(SimpleSAML_Configuration $srcMetadata, SimpleSAML_Configuration $dstMetadata, \SAML2\Message $message)
Check signature on a SAML2 message if enabled.
if((!isset($_SERVER['DOCUMENT_ROOT'])) OR(empty($_SERVER['DOCUMENT_ROOT']))) $_SERVER['DOCUMENT_ROOT']
getArray($name, $default=self::REQUIRED_OPTION)
This function retrieves an array configuration option.
hasValue($name)
Check whether a key in the configuration exists or not.
getValue($name, $default=null)
Retrieve a configuration option set in config.php.
static buildLogoutRequest(SimpleSAML_Configuration $srcMetadata, SimpleSAML_Configuration $dstMetadata)
Build a logout request based on information in the metadata.
static findCertificate(array $certFingerprints, array $certificates)
Find the certificate used to sign a message or assertion.
$metadata['__DYNAMIC:1__']
static buildAuthnRequest(SimpleSAML_Configuration $spMetadata, SimpleSAML_Configuration $idpMetadata)
Build an authentication request based on information in the metadata.
static addRedirectSign(SimpleSAML_Configuration $srcMetadata, SimpleSAML_Configuration $dstMetadata, \SAML2\Message $message)
Add signature key and and senders certificate to message.
getValueValidate($name, $allowedValues, $default=self::REQUIRED_OPTION)
Retrieve a configuration option with one of the given values.
static loadPrivateKey(\SimpleSAML_Configuration $metadata, $required=false, $prefix='', $full_path=false)
Load a private key from metadata.
Attribute-related utility methods.
catch(Exception $e) $message
static decryptAssertion(SimpleSAML_Configuration $srcMetadata, SimpleSAML_Configuration $dstMetadata, $assertion)
Decrypt an assertion.
foreach($_POST as $key=> $value) $res
static getSelfURLNoQuery()
Retrieve the current URL using the base URL in the configuration, without the query parameters...
static getDecryptionKeys(SimpleSAML_Configuration $srcMetadata, SimpleSAML_Configuration $dstMetadata)
Retrieve the decryption keys from metadata.
static loadPublicKey(\SimpleSAML_Configuration $metadata, $required=false, $prefix='')
Get public key or certificate from metadata.
static addSign(SimpleSAML_Configuration $srcMetadata, SimpleSAML_Configuration $dstMetadata, \SAML2\SignedElement $element)
Add signature key and sender certificate to an element (Message or Assertion).
getBoolean($name, $default=self::REQUIRED_OPTION)
This function retrieves a boolean configuration option.
if(array_key_exists('yes', $_REQUEST)) $attributes
getArrayizeString($name, $default=self::REQUIRED_OPTION)
This function retrieves a configuration option with a string or an array of strings.
static checkSign(SimpleSAML_Configuration $srcMetadata, \SAML2\SignedElement $element)
Check the signature on a SAML2 message or assertion.
getInteger($name, $default=self::REQUIRED_OPTION)
This function retrieves an integer configuration option.
static getEncryptionKey(SimpleSAML_Configuration $metadata)
Retrieve the encryption key for the given entity.
static decryptAttributes(SimpleSAML_Configuration $srcMetadata, SimpleSAML_Configuration $dstMetadata, \SAML2\Assertion &$assertion)
Decrypt any encrypted attributes in an assertion.
static getResponseError(\SAML2\StatusResponse $response)
Retrieve the status code of a response as a sspmod_saml_Error.
getPublicKeys($use=null, $required=false, $prefix='')
Get public key from metadata.
getString($name, $default=self::REQUIRED_OPTION)
This function retrieves a string configuration option.
static processAssertion(SimpleSAML_Configuration $spMetadata, SimpleSAML_Configuration $idpMetadata, \SAML2\Response $response, $assertion, $responseSigned)
Process an assertion in a response.
static processResponse(SimpleSAML_Configuration $spMetadata, SimpleSAML_Configuration $idpMetadata, \SAML2\Response $response)
Process a response message.
static loadFromArray($config, $location='[ARRAY]', $instance=null)
Loads a configuration from the given array.
static buildLogoutResponse(SimpleSAML_Configuration $srcMetadata, SimpleSAML_Configuration $dstMetadata)
Build a logout response based on information in the metadata.