23 \
SAML2\SignedElement $element
25 $dstPrivateKey = $dstMetadata->
getString(
'signature.privatekey',
null);
27 if ($dstPrivateKey !==
null) {
47 $algo = $srcMetadata->
getString(
'signature.algorithm', XMLSecurityKey::RSA_SHA1);
51 if (array_key_exists(
'password', $keyArray)) {
52 $privateKey->passphrase = $keyArray[
'password'];
54 $privateKey->loadKey($keyArray[
'PEM'],
false);
56 $element->setSignatureKey($privateKey);
58 if ($certArray ===
null) {
63 if (!array_key_exists(
'PEM', $certArray)) {
68 $element->setCertificates(array($certArray[
'PEM']));
85 $signingEnabled =
null;
87 $signingEnabled = $srcMetadata->
getBoolean(
'sign.logout',
null);
88 if ($signingEnabled ===
null) {
89 $signingEnabled = $dstMetadata->
getBoolean(
'sign.logout',
null);
92 $signingEnabled = $srcMetadata->
getBoolean(
'sign.authnrequest',
null);
93 if ($signingEnabled ===
null) {
94 $signingEnabled = $dstMetadata->
getBoolean(
'sign.authnrequest',
null);
98 if ($signingEnabled ===
null) {
99 $signingEnabled = $dstMetadata->
getBoolean(
'redirect.sign',
null);
100 if ($signingEnabled ===
null) {
101 $signingEnabled = $srcMetadata->
getBoolean(
'redirect.sign',
false);
104 if (!$signingEnabled) {
126 $candidates = array();
129 $fp = strtolower(sha1(base64_decode($cert)));
130 if (!in_array($fp, $certFingerprints,
true)) {
136 $pem =
"-----BEGIN CERTIFICATE-----\n".
137 chunk_split($cert, 64).
138 "-----END CERTIFICATE-----\n";
142 $candidates =
"'".implode(
"', '", $candidates).
"'";
143 $fps =
"'".implode(
"', '", $certFingerprints).
"'";
145 'fingerprint. Candidates: '.$candidates.
'; certFingerprint: '.$fps.
'.');
163 if (
$keys !==
null) {
166 switch (
$key[
'type']) {
167 case 'X509Certificate':
168 $pemKeys[] =
"-----BEGIN CERTIFICATE-----\n".
169 chunk_split(
$key[
'X509Certificate'], 64).
170 "-----END CERTIFICATE-----\n";
176 } elseif ($srcMetadata->
hasValue(
'certFingerprint')) {
178 "Validating certificates by fingerprint is deprecated. Please use ".
179 "certData or certificate options in your remote metadata configuration."
183 foreach ($certFingerprint as &$fp) {
184 $fp = strtolower(str_replace(
':',
'', $fp));
199 $pemKeys = array($pemCert);
202 'Missing certificate in metadata for '.
203 var_export($srcMetadata->
getString(
'entityid'),
true)
209 $lastException =
null;
210 foreach ($pemKeys as
$i => $pem) {
222 }
catch (Exception $e) {
229 if ($lastException !==
null) {
230 throw $lastException;
253 $enabled = $srcMetadata->
getBoolean(
'validate.logout',
null);
254 if ($enabled ===
null) {
255 $enabled = $dstMetadata->
getBoolean(
'validate.logout',
null);
258 $enabled = $srcMetadata->
getBoolean(
'validate.authnrequest',
null);
259 if ($enabled ===
null) {
260 $enabled = $dstMetadata->
getBoolean(
'validate.authnrequest',
null);
264 if ($enabled ===
null) {
265 $enabled = $srcMetadata->
getBoolean(
'redirect.validate',
null);
266 if ($enabled ===
null) {
267 $enabled = $dstMetadata->
getBoolean(
'redirect.validate',
false);
275 if (!self::checkSign($srcMetadata,
$message)) {
277 'Validation of received messages enabled, but no signature found on message.'
295 $sharedKey = $srcMetadata->
getString(
'sharedkey',
null);
296 if ($sharedKey !==
null) {
298 $key->loadKey($sharedKey);
306 if ($keyArray !==
null) {
307 assert(
'isset($keyArray["PEM"])');
310 if (array_key_exists(
'password', $keyArray)) {
311 $key->passphrase = $keyArray[
'password'];
313 $key->loadKey($keyArray[
'PEM']);
319 assert(
'isset($keyArray["PEM"])');
322 if (array_key_exists(
'password', $keyArray)) {
323 $key->passphrase = $keyArray[
'password'];
325 $key->loadKey($keyArray[
'PEM']);
346 $blacklist = $srcMetadata->
getArray(
'encryption.blacklisted-algorithms',
null);
347 if ($blacklist ===
null) {
348 $blacklist = $dstMetadata->
getArray(
'encryption.blacklisted-algorithms', array(XMLSecurityKey::RSA_1_5));
372 assert(
'$assertion instanceof \SAML2\Assertion || $assertion instanceof \SAML2\EncryptedAssertion');
374 if ($assertion instanceof \
SAML2\Assertion) {
375 $encryptAssertion = $srcMetadata->
getBoolean(
'assertion.encryption',
null);
376 if ($encryptAssertion ===
null) {
377 $encryptAssertion = $dstMetadata->
getBoolean(
'assertion.encryption',
false);
379 if ($encryptAssertion) {
381 throw new Exception(
'Received unencrypted assertion, but encryption was enabled.');
389 }
catch (Exception $e) {
395 $lastException =
null;
398 $ret = $assertion->getAssertion(
$key, $blacklist);
401 }
catch (Exception $e) {
406 throw $lastException;
420 return new sspmod_saml_Error($status[
'Code'], $status[
'SubCode'], $status[
'Message']);
435 $ar = new \SAML2\AuthnRequest();
438 $nameIdPolicy = array();
442 $nameIdPolicy =
$spMetadata->getValue(
'NameIDPolicy');
445 if (!is_array($nameIdPolicy)) {
447 $nameIdPolicy = array(
'Format' => $nameIdPolicy);
452 'Format' => $nameIdPolicy_cf->getString(
'Format', \
SAML2\Constants::NAMEID_TRANSIENT),
453 'AllowCreate' => $nameIdPolicy_cf->getBoolean(
'AllowCreate',
true),
455 $spNameQualifier = $nameIdPolicy_cf->getString(
'SPNameQualifier',
false);
456 if ($spNameQualifier !==
false) {
457 $policy[
'SPNameQualifier'] = $spNameQualifier;
459 $ar->setNameIdPolicy($policy);
461 $ar->setForceAuthn(
$spMetadata->getBoolean(
'ForceAuthn',
false));
462 $ar->setIsPassive(
$spMetadata->getBoolean(
'IsPassive',
false));
464 $protbind =
$spMetadata->getValueValidate(
'ProtocolBinding', array(
465 \
SAML2\Constants::BINDING_HTTP_POST,
466 \
SAML2\Constants::BINDING_HOK_SSO,
467 \
SAML2\Constants::BINDING_HTTP_ARTIFACT,
468 \
SAML2\Constants::BINDING_HTTP_REDIRECT,
469 ), \
SAML2\Constants::BINDING_HTTP_POST);
472 $ar->setProtocolBinding($protbind);
473 $ar->setIssuer(
$spMetadata->getString(
'entityid'));
474 $ar->setAssertionConsumerServiceIndex(
$spMetadata->getInteger(
'AssertionConsumerServiceIndex',
null));
475 $ar->setAttributeConsumingServiceIndex(
$spMetadata->getInteger(
'AttributeConsumingServiceIndex',
null));
477 if (
$spMetadata->hasValue(
'AuthnContextClassRef')) {
478 $accr =
$spMetadata->getArrayizeString(
'AuthnContextClassRef');
479 $comp =
$spMetadata->getValueValidate(
'AuthnContextComparison', array(
480 \
SAML2\Constants::COMPARISON_EXACT,
481 \
SAML2\Constants::COMPARISON_MINIMUM,
482 \
SAML2\Constants::COMPARISON_MAXIMUM,
483 \
SAML2\Constants::COMPARISON_BETTER,
484 ), \
SAML2\Constants::COMPARISON_EXACT);
485 $ar->setRequestedAuthnContext(array(
'AuthnContextClassRef' => $accr,
'Comparison' => $comp));
505 $lr = new \SAML2\LogoutRequest();
525 $lr = new \SAML2\LogoutResponse();
559 $msgDestination =
$response->getDestination();
560 if ($msgDestination !==
null && $msgDestination !== $currentURL) {
561 throw new Exception(
'Destination in response doesn\'t match the current URL. Destination is "'.
562 $msgDestination.
'", current URL is "'.$currentURL.
'".');
572 if (empty($assertion)) {
577 foreach ($assertion as $a) {
608 assert(
'$assertion instanceof \SAML2\Assertion || $assertion instanceof \SAML2\EncryptedAssertion');
609 assert(
'is_bool($responseSigned)');
614 if (!$responseSigned) {
622 $notBefore = $assertion->getNotBefore();
623 if ($notBefore !==
null && $notBefore > time() + 60) {
625 'Received an assertion that is valid in the future. Check clock synchronization on IdP and SP.'
628 $notOnOrAfter = $assertion->getNotOnOrAfter();
629 if ($notOnOrAfter !==
null && $notOnOrAfter <= time() - 60) {
631 'Received an assertion that has expired. Check clock synchronization on IdP and SP.'
634 $sessionNotOnOrAfter = $assertion->getSessionNotOnOrAfter();
635 if ($sessionNotOnOrAfter !==
null && $sessionNotOnOrAfter <= time() - 60) {
637 'Received an assertion with a session that has expired. Check clock synchronization on IdP and SP.'
640 $validAudiences = $assertion->getValidAudiences();
641 if ($validAudiences !==
null) {
643 if (!in_array(
$spEntityId, $validAudiences,
true)) {
644 $candidates =
'['.implode(
'], [', $validAudiences).
']';
646 '] is not a valid audience for the assertion. Candidates were: '.$candidates);
651 $lastError =
'No SubjectConfirmation element in Subject.';
652 $validSCMethods = array(\
SAML2\Constants::CM_BEARER, \
SAML2\Constants::CM_HOK, \
SAML2\Constants::CM_VOUCHES);
653 foreach ($assertion->getSubjectConfirmation() as
$sc) {
654 if (!in_array(
$sc->Method, $validSCMethods,
true)) {
655 $lastError =
'Invalid Method on SubjectConfirmation: '.var_export(
$sc->Method,
true);
660 $hok =
$idpMetadata->getBoolean(
'saml20.hok.assertion',
null);
662 $hok =
$spMetadata->getBoolean(
'saml20.hok.assertion',
false);
664 if (
$sc->Method === \
SAML2\Constants::CM_BEARER && $hok) {
665 $lastError =
'Bearer SubjectConfirmation received, but Holder-of-Key SubjectConfirmation needed';
668 if (
$sc->Method === \
SAML2\Constants::CM_HOK && !$hok) {
669 $lastError =
'Holder-of-Key SubjectConfirmation received, '.
670 'but the Holder-of-Key profile is not enabled.';
674 $scd =
$sc->SubjectConfirmationData;
675 if (
$sc->Method === \
SAML2\Constants::CM_HOK) {
677 if (\
SimpleSAML\Utils\HTTP::isHTTPS() ===
false) {
678 $lastError =
'No HTTPS connection, but required for Holder-of-Key SSO';
681 if (isset(
$_SERVER[
'SSL_CLIENT_CERT']) && empty(
$_SERVER[
'SSL_CLIENT_CERT'])) {
682 $lastError =
'No client certificate provided during TLS Handshake with SP';
686 $clientCert =
$_SERVER[
'SSL_CLIENT_CERT'];
687 $pattern =
'/^-----BEGIN CERTIFICATE-----([^-]*)^-----END CERTIFICATE-----/m';
688 if (!preg_match($pattern, $clientCert, $matches)) {
689 $lastError =
'Error while looking for client certificate during TLS handshake with SP, the client '.
690 'certificate does not have the expected structure';
694 $clientCert = str_replace(array(
"\r",
"\n",
" "),
'', $matches[1]);
697 foreach ($scd->info as $thing) {
698 if ($thing instanceof \
SAML2\XML\ds\KeyInfo) {
702 if (count($keyInfo) != 1) {
703 $lastError =
'Error validating Holder-of-Key assertion: Only one <ds:KeyInfo> element in '.
704 '<SubjectConfirmationData> allowed';
709 foreach ($keyInfo[0]->info as $thing) {
710 if ($thing instanceof \
SAML2\XML\ds\X509Data) {
711 $x509data[] = $thing;
714 if (count($x509data) != 1) {
715 $lastError =
'Error validating Holder-of-Key assertion: Only one <ds:X509Data> element in '.
716 '<ds:KeyInfo> within <SubjectConfirmationData> allowed';
721 foreach ($x509data[0]->
data as $thing) {
722 if ($thing instanceof \
SAML2\XML\ds\X509Certificate) {
723 $x509cert[] = $thing;
726 if (count($x509cert) != 1) {
727 $lastError =
'Error validating Holder-of-Key assertion: Only one <ds:X509Certificate> element in '.
728 '<ds:X509Data> within <SubjectConfirmationData> allowed';
732 $HoKCertificate = $x509cert[0]->certificate;
733 if ($HoKCertificate !== $clientCert) {
734 $lastError =
'Provided client certificate does not match the certificate bound to the '.
735 'Holder-of-Key assertion';
742 $lastError =
'No SubjectConfirmationData provided';
746 if ($scd->NotBefore && $scd->NotBefore > time() + 60) {
747 $lastError =
'NotBefore in SubjectConfirmationData is in the future: '.$scd->NotBefore;
750 if ($scd->NotOnOrAfter && $scd->NotOnOrAfter <= time() - 60) {
751 $lastError =
'NotOnOrAfter in SubjectConfirmationData is in the past: '.$scd->NotOnOrAfter;
754 if ($scd->Recipient !==
null && $scd->Recipient !== $currentURL) {
755 $lastError =
'Recipient in SubjectConfirmationData does not match the current URL. Recipient is '.
756 var_export($scd->Recipient,
true).
', current URL is '.var_export($currentURL,
true).
'.';
759 if ($scd->InResponseTo !==
null &&
$response->getInResponseTo() !==
null &&
760 $scd->InResponseTo !==
$response->getInResponseTo()
762 $lastError =
'InResponseTo in SubjectConfirmationData does not match the Response. Response has '.
763 var_export(
$response->getInResponseTo(),
true).
764 ', SubjectConfirmationData has '.var_export($scd->InResponseTo,
true).
'.';
775 if (
$idpMetadata->getBoolean(
'base64attributes',
false)) {
777 $newAttributes = array();
779 $newAttributes[
$name] = array();
780 foreach ($values as $value) {
781 foreach (explode(
'_', $value) as $v) {
782 $newAttributes[
$name][] = base64_decode($v);
786 $assertion->setAttributes($newAttributes);
790 if ($assertion->isNameIdEncrypted()) {
793 }
catch (Exception $e) {
799 $lastException =
null;
802 $assertion->decryptNameId(
$key, $blacklist);
804 $lastException =
null;
806 }
catch (Exception $e) {
811 if ($lastException !==
null) {
812 throw $lastException;
832 $sharedKey =
$metadata->getString(
'sharedkey',
null);
833 if ($sharedKey !==
null) {
835 $key->loadKey($sharedKey);
841 switch (
$key[
'type']) {
842 case 'X509Certificate':
843 $pemKey =
"-----BEGIN CERTIFICATE-----\n".
844 chunk_split(
$key[
'X509Certificate'], 64).
845 "-----END CERTIFICATE-----\n";
847 $key->loadKey($pemKey);
853 var_export(
$metadata->getString(
'entityid'),
true));
$metadata['__DYNAMIC:1__']
An exception for terminatinating execution or to throw for unit testing.
static loadPublicKey(\SimpleSAML_Configuration $metadata, $required=false, $prefix='')
Get public key or certificate from metadata.
static loadPrivateKey(\SimpleSAML_Configuration $metadata, $required=false, $prefix='', $full_path=false)
Load a private key from metadata.
static getSelfURLNoQuery()
Retrieve the current URL using the base URL in the configuration, without the query parameters.
getString($name, $default=self::REQUIRED_OPTION)
This function retrieves a string configuration option.
getBoolean($name, $default=self::REQUIRED_OPTION)
This function retrieves a boolean configuration option.
static loadFromArray($config, $location='[ARRAY]', $instance=null)
Loads a configuration from the given array.
getArrayizeString($name, $default=self::REQUIRED_OPTION)
This function retrieves a configuration option with a string or an array of strings.
getArray($name, $default=self::REQUIRED_OPTION)
This function retrieves an array configuration option.
getPublicKeys($use=null, $required=false, $prefix='')
Get public key from metadata.
hasValue($name)
Check whether a key in the configuration exists or not.
static getResponseError(\SAML2\StatusResponse $response)
Retrieve the status code of a response as a sspmod_saml_Error.
static addSign(SimpleSAML_Configuration $srcMetadata, SimpleSAML_Configuration $dstMetadata, \SAML2\SignedElement $element)
Add signature key and sender certificate to an element (Message or Assertion).
static buildLogoutRequest(SimpleSAML_Configuration $srcMetadata, SimpleSAML_Configuration $dstMetadata)
Build a logout request based on information in the metadata.
static buildAuthnRequest(SimpleSAML_Configuration $spMetadata, SimpleSAML_Configuration $idpMetadata)
Build an authentication request based on information in the metadata.
static checkSign(SimpleSAML_Configuration $srcMetadata, \SAML2\SignedElement $element)
Check the signature on a SAML2 message or assertion.
static getDecryptionKeys(SimpleSAML_Configuration $srcMetadata, SimpleSAML_Configuration $dstMetadata)
Retrieve the decryption keys from metadata.
static decryptAssertion(SimpleSAML_Configuration $srcMetadata, SimpleSAML_Configuration $dstMetadata, $assertion)
Decrypt an assertion.
static buildLogoutResponse(SimpleSAML_Configuration $srcMetadata, SimpleSAML_Configuration $dstMetadata)
Build a logout response based on information in the metadata.
static processResponse(SimpleSAML_Configuration $spMetadata, SimpleSAML_Configuration $idpMetadata, \SAML2\Response $response)
Process a response message.
static getBlacklistedAlgorithms(SimpleSAML_Configuration $srcMetadata, SimpleSAML_Configuration $dstMetadata)
Retrieve blacklisted algorithms.
static getEncryptionKey(SimpleSAML_Configuration $metadata)
Retrieve the encryption key for the given entity.
static validateMessage(SimpleSAML_Configuration $srcMetadata, SimpleSAML_Configuration $dstMetadata, \SAML2\Message $message)
Check signature on a SAML2 message if enabled.
static processAssertion(SimpleSAML_Configuration $spMetadata, SimpleSAML_Configuration $idpMetadata, \SAML2\Response $response, $assertion, $responseSigned)
Process an assertion in a response.
static findCertificate(array $certFingerprints, array $certificates)
Find the certificate used to sign a message or assertion.
static addRedirectSign(SimpleSAML_Configuration $srcMetadata, SimpleSAML_Configuration $dstMetadata, \SAML2\Message $message)
Add signature key and and senders certificate to message.
catch(Exception $e) $message
Attribute-related utility methods.
foreach($_POST as $key=> $value) $res
if((!isset($_SERVER['DOCUMENT_ROOT'])) OR(empty($_SERVER['DOCUMENT_ROOT']))) $_SERVER['DOCUMENT_ROOT']