ILIAS  release_5-4 Revision v5.4.26-12-gabc799a52e6
Message.php
Go to the documentation of this file.
1 <?php
2 
4 
11 {
12 
20  public static function addSign(
21  SimpleSAML_Configuration $srcMetadata,
22  SimpleSAML_Configuration $dstMetadata,
23  \SAML2\SignedElement $element
24  ) {
25  $dstPrivateKey = $dstMetadata->getString('signature.privatekey', null);
26 
27  if ($dstPrivateKey !== null) {
28  $keyArray = SimpleSAML\Utils\Crypto::loadPrivateKey($dstMetadata, true, 'signature.');
29  $certArray = SimpleSAML\Utils\Crypto::loadPublicKey($dstMetadata, false, 'signature.');
30  } else {
31  $keyArray = SimpleSAML\Utils\Crypto::loadPrivateKey($srcMetadata, true);
32  $certArray = SimpleSAML\Utils\Crypto::loadPublicKey($srcMetadata, false);
33  }
34 
35  $algo = $dstMetadata->getString('signature.algorithm', null);
36  if ($algo === null) {
37  $algo = $srcMetadata->getString('signature.algorithm', XMLSecurityKey::RSA_SHA256);
38  }
39 
40  $privateKey = new XMLSecurityKey($algo, array('type' => 'private'));
41  if (array_key_exists('password', $keyArray)) {
42  $privateKey->passphrase = $keyArray['password'];
43  }
44  $privateKey->loadKey($keyArray['PEM'], false);
45 
46  $element->setSignatureKey($privateKey);
47 
48  if ($certArray === null) {
49  // we don't have a certificate to add
50  return;
51  }
52 
53  if (!array_key_exists('PEM', $certArray)) {
54  // we have a public key with only a fingerprint
55  return;
56  }
57 
58  $element->setCertificates(array($certArray['PEM']));
59  }
60 
61 
69  private static function addRedirectSign(
70  SimpleSAML_Configuration $srcMetadata,
71  SimpleSAML_Configuration $dstMetadata,
72  \SAML2\Message $message
73  ) {
74 
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);
80  }
81  } elseif ($message instanceof \SAML2\AuthnRequest) {
82  $signingEnabled = $srcMetadata->getBoolean('sign.authnrequest', null);
83  if ($signingEnabled === null) {
84  $signingEnabled = $dstMetadata->getBoolean('sign.authnrequest', null);
85  }
86  }
87 
88  if ($signingEnabled === null) {
89  $signingEnabled = $dstMetadata->getBoolean('redirect.sign', null);
90  if ($signingEnabled === null) {
91  $signingEnabled = $srcMetadata->getBoolean('redirect.sign', false);
92  }
93  }
94  if (!$signingEnabled) {
95  return;
96  }
97 
98  self::addSign($srcMetadata, $dstMetadata, $message);
99  }
100 
101 
114  private static function findCertificate(array $certFingerprints, array $certificates)
115  {
116  $candidates = array();
117 
118  foreach ($certificates as $cert) {
119  $fp = strtolower(sha1(base64_decode($cert)));
120  if (!in_array($fp, $certFingerprints, true)) {
121  $candidates[] = $fp;
122  continue;
123  }
124 
125  /* We have found a matching fingerprint. */
126  $pem = "-----BEGIN CERTIFICATE-----\n".
127  chunk_split($cert, 64).
128  "-----END CERTIFICATE-----\n";
129  return $pem;
130  }
131 
132  $candidates = "'".implode("', '", $candidates)."'";
133  $fps = "'".implode("', '", $certFingerprints)."'";
134  throw new SimpleSAML_Error_Exception('Unable to find a certificate matching the configured '.
135  'fingerprint. Candidates: '.$candidates.'; certFingerprint: '.$fps.'.');
136  }
137 
138 
149  public static function checkSign(SimpleSAML_Configuration $srcMetadata, \SAML2\SignedElement $element)
150  {
151  // find the public key that should verify signatures by this entity
152  $keys = $srcMetadata->getPublicKeys('signing');
153  if (!empty($keys)) {
154  $pemKeys = array();
155  foreach ($keys as $key) {
156  switch ($key['type']) {
157  case 'X509Certificate':
158  $pemKeys[] = "-----BEGIN CERTIFICATE-----\n".
159  chunk_split($key['X509Certificate'], 64).
160  "-----END CERTIFICATE-----\n";
161  break;
162  default:
163  SimpleSAML\Logger::debug('Skipping unknown key type: '.$key['type']);
164  }
165  }
166  } elseif ($srcMetadata->hasValue('certFingerprint')) {
168  "Validating certificates by fingerprint is deprecated. Please use ".
169  "certData or certificate options in your remote metadata configuration."
170  );
171 
172  $certFingerprint = $srcMetadata->getArrayizeString('certFingerprint');
173  foreach ($certFingerprint as &$fp) {
174  $fp = strtolower(str_replace(':', '', $fp));
175  }
176 
177  $certificates = $element->getCertificates();
178 
179  // we don't have the full certificate stored. Try to find it in the message or the assertion instead
180  if (count($certificates) === 0) {
181  /* We need the full certificate in order to match it against the fingerprint. */
182  SimpleSAML\Logger::debug('No certificate in message when validating against fingerprint.');
183  return false;
184  } else {
185  SimpleSAML\Logger::debug('Found '.count($certificates).' certificates in '.get_class($element));
186  }
187 
188  $pemCert = self::findCertificate($certFingerprint, $certificates);
189  $pemKeys = array($pemCert);
190  } else {
191  throw new SimpleSAML_Error_Exception(
192  'Missing certificate in metadata for '.
193  var_export($srcMetadata->getString('entityid'), true)
194  );
195  }
196 
197  SimpleSAML\Logger::debug('Has '.count($pemKeys).' candidate keys for validation.');
198 
199  $lastException = null;
200  foreach ($pemKeys as $i => $pem) {
201  $key = new XMLSecurityKey(XMLSecurityKey::RSA_SHA256, array('type' => 'public'));
202  $key->loadKey($pem);
203 
204  try {
205  // make sure that we have a valid signature on either the response or the assertion
206  $res = $element->validate($key);
207  if ($res) {
208  SimpleSAML\Logger::debug('Validation with key #'.$i.' succeeded.');
209  return true;
210  }
211  SimpleSAML\Logger::debug('Validation with key #'.$i.' failed without exception.');
212  } catch (Exception $e) {
213  SimpleSAML\Logger::debug('Validation with key #'.$i.' failed with exception: '.$e->getMessage());
214  $lastException = $e;
215  }
216  }
217 
218  // we were unable to validate the signature with any of our keys
219  if ($lastException !== null) {
220  throw $lastException;
221  } else {
222  return false;
223  }
224  }
225 
226 
236  public static function validateMessage(
237  SimpleSAML_Configuration $srcMetadata,
238  SimpleSAML_Configuration $dstMetadata,
239  \SAML2\Message $message
240  ) {
241  $enabled = null;
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);
246  }
247  } elseif ($message instanceof \SAML2\AuthnRequest) {
248  $enabled = $srcMetadata->getBoolean('validate.authnrequest', null);
249  if ($enabled === null) {
250  $enabled = $dstMetadata->getBoolean('validate.authnrequest', null);
251  }
252  }
253 
254  if ($enabled === null) {
255  $enabled = $srcMetadata->getBoolean('redirect.validate', null);
256  if ($enabled === null) {
257  $enabled = $dstMetadata->getBoolean('redirect.validate', false);
258  }
259  }
260 
261  if (!$enabled) {
262  return;
263  }
264 
265  if (!self::checkSign($srcMetadata, $message)) {
266  throw new SimpleSAML_Error_Exception(
267  'Validation of received messages enabled, but no signature found on message.'
268  );
269  }
270  }
271 
272 
281  public static function getDecryptionKeys(
282  SimpleSAML_Configuration $srcMetadata,
283  SimpleSAML_Configuration $dstMetadata
284  ) {
285  $sharedKey = $srcMetadata->getString('sharedkey', null);
286  if ($sharedKey !== null) {
287  $key = new XMLSecurityKey(XMLSecurityKey::AES128_CBC);
288  $key->loadKey($sharedKey);
289  return array($key);
290  }
291 
292  $keys = array();
293 
294  // load the new private key if it exists
295  $keyArray = SimpleSAML\Utils\Crypto::loadPrivateKey($dstMetadata, false, 'new_');
296  if ($keyArray !== null) {
297  assert(isset($keyArray['PEM']));
298 
299  $key = new XMLSecurityKey(XMLSecurityKey::RSA_1_5, array('type' => 'private'));
300  if (array_key_exists('password', $keyArray)) {
301  $key->passphrase = $keyArray['password'];
302  }
303  $key->loadKey($keyArray['PEM']);
304  $keys[] = $key;
305  }
306 
307  // find the existing private key
308  $keyArray = SimpleSAML\Utils\Crypto::loadPrivateKey($dstMetadata, true);
309  assert(isset($keyArray['PEM']));
310 
311  $key = new XMLSecurityKey(XMLSecurityKey::RSA_1_5, array('type' => 'private'));
312  if (array_key_exists('password', $keyArray)) {
313  $key->passphrase = $keyArray['password'];
314  }
315  $key->loadKey($keyArray['PEM']);
316  $keys[] = $key;
317 
318  return $keys;
319  }
320 
321 
332  public static function getBlacklistedAlgorithms(
333  SimpleSAML_Configuration $srcMetadata,
334  SimpleSAML_Configuration $dstMetadata
335  ) {
336  $blacklist = $srcMetadata->getArray('encryption.blacklisted-algorithms', null);
337  if ($blacklist === null) {
338  $blacklist = $dstMetadata->getArray('encryption.blacklisted-algorithms', array(XMLSecurityKey::RSA_1_5));
339  }
340  return $blacklist;
341  }
342 
343 
357  private static function decryptAssertion(
358  SimpleSAML_Configuration $srcMetadata,
359  SimpleSAML_Configuration $dstMetadata,
360  $assertion
361  ) {
362  assert($assertion instanceof \SAML2\Assertion || $assertion instanceof \SAML2\EncryptedAssertion);
363 
364  if ($assertion instanceof \SAML2\Assertion) {
365  $encryptAssertion = $srcMetadata->getBoolean('assertion.encryption', null);
366  if ($encryptAssertion === null) {
367  $encryptAssertion = $dstMetadata->getBoolean('assertion.encryption', false);
368  }
369  if ($encryptAssertion) {
370  /* The assertion was unencrypted, but we have encryption enabled. */
371  throw new Exception('Received unencrypted assertion, but encryption was enabled.');
372  }
373 
374  return $assertion;
375  }
376 
377  try {
378  $keys = self::getDecryptionKeys($srcMetadata, $dstMetadata);
379  } catch (Exception $e) {
380  throw new SimpleSAML_Error_Exception('Error decrypting assertion: '.$e->getMessage());
381  }
382 
383  $blacklist = self::getBlacklistedAlgorithms($srcMetadata, $dstMetadata);
384 
385  $lastException = null;
386  foreach ($keys as $i => $key) {
387  try {
388  $ret = $assertion->getAssertion($key, $blacklist);
389  SimpleSAML\Logger::debug('Decryption with key #'.$i.' succeeded.');
390  return $ret;
391  } catch (Exception $e) {
392  SimpleSAML\Logger::debug('Decryption with key #'.$i.' failed with exception: '.$e->getMessage());
393  $lastException = $e;
394  }
395  }
396  throw $lastException;
397  }
398 
399 
411  private static function decryptAttributes(
412  SimpleSAML_Configuration $srcMetadata,
413  SimpleSAML_Configuration $dstMetadata,
414  \SAML2\Assertion &$assertion
415  ) {
416  if (!$assertion->hasEncryptedAttributes()) {
417  return;
418  }
419 
420  try {
421  $keys = self::getDecryptionKeys($srcMetadata, $dstMetadata);
422  } catch (Exception $e) {
423  throw new SimpleSAML_Error_Exception('Error decrypting attributes: '.$e->getMessage());
424  }
425 
426  $blacklist = self::getBlacklistedAlgorithms($srcMetadata, $dstMetadata);
427 
428  $error = true;
429  foreach ($keys as $i => $key) {
430  try {
431  $assertion->decryptAttributes($key, $blacklist);
432  SimpleSAML\Logger::debug('Attribute decryption with key #'.$i.' succeeded.');
433  $error = false;
434  break;
435  } catch (Exception $e) {
436  SimpleSAML\Logger::debug('Attribute decryption failed with exception: '.$e->getMessage());
437  }
438  }
439  if ($error) {
440  throw new SimpleSAML_Error_Exception('Could not decrypt the attributes');
441  }
442  }
443 
444 
452  public static function getResponseError(\SAML2\StatusResponse $response)
453  {
454  $status = $response->getStatus();
455  return new sspmod_saml_Error($status['Code'], $status['SubCode'], $status['Message']);
456  }
457 
458 
466  public static function buildAuthnRequest(
469  ) {
470  $ar = new \SAML2\AuthnRequest();
471 
472  // get the NameIDPolicy to apply. IdP metadata has precedence.
473  $nameIdPolicy = array();
474  if ($idpMetadata->hasValue('NameIDPolicy')) {
475  $nameIdPolicy = $idpMetadata->getValue('NameIDPolicy');
476  } elseif ($spMetadata->hasValue('NameIDPolicy')) {
477  $nameIdPolicy = $spMetadata->getValue('NameIDPolicy');
478  }
479 
480  if (!is_array($nameIdPolicy)) {
481  // handle old configurations where 'NameIDPolicy' was used to specify just the format
482  $nameIdPolicy = array('Format' => $nameIdPolicy);
483  }
484 
485  $nameIdPolicy_cf = SimpleSAML_Configuration::loadFromArray($nameIdPolicy);
486  $policy = array(
487  'Format' => $nameIdPolicy_cf->getString('Format', \SAML2\Constants::NAMEID_TRANSIENT),
488  'AllowCreate' => $nameIdPolicy_cf->getBoolean('AllowCreate', true),
489  );
490  $spNameQualifier = $nameIdPolicy_cf->getString('SPNameQualifier', false);
491  if ($spNameQualifier !== false) {
492  $policy['SPNameQualifier'] = $spNameQualifier;
493  }
494  $ar->setNameIdPolicy($policy);
495 
496  $ar->setForceAuthn($spMetadata->getBoolean('ForceAuthn', false));
497  $ar->setIsPassive($spMetadata->getBoolean('IsPassive', false));
498 
499  $protbind = $spMetadata->getValueValidate('ProtocolBinding', array(
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);
505 
506  // Shoaib: setting the appropriate binding based on parameter in sp-metadata defaults to 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));
511 
512  if ($spMetadata->hasValue('AuthnContextClassRef')) {
513  $accr = $spMetadata->getArrayizeString('AuthnContextClassRef');
514  $comp = $spMetadata->getValueValidate('AuthnContextComparison', array(
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));
521  }
522 
523  self::addRedirectSign($spMetadata, $idpMetadata, $ar);
524 
525  return $ar;
526  }
527 
528 
536  public static function buildLogoutRequest(
537  SimpleSAML_Configuration $srcMetadata,
538  SimpleSAML_Configuration $dstMetadata
539  ) {
540  $lr = new \SAML2\LogoutRequest();
541  $lr->setIssuer($srcMetadata->getString('entityid'));
542 
543  self::addRedirectSign($srcMetadata, $dstMetadata, $lr);
544 
545  return $lr;
546  }
547 
548 
556  public static function buildLogoutResponse(
557  SimpleSAML_Configuration $srcMetadata,
558  SimpleSAML_Configuration $dstMetadata
559  ) {
560  $lr = new \SAML2\LogoutResponse();
561  $lr->setIssuer($srcMetadata->getString('entityid'));
562 
563  self::addRedirectSign($srcMetadata, $dstMetadata, $lr);
564 
565  return $lr;
566  }
567 
568 
583  public static function processResponse(
586  \SAML2\Response $response
587  ) {
588  if (!$response->isSuccess()) {
589  throw self::getResponseError($response);
590  }
591 
592  // validate Response-element destination
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.'".');
598  }
599 
600  $responseSigned = self::checkSign($idpMetadata, $response);
601 
602  /*
603  * When we get this far, the response itself is valid.
604  * We only need to check signatures and conditions of the response.
605  */
606  $assertion = $response->getAssertions();
607  if (empty($assertion)) {
608  throw new SimpleSAML_Error_Exception('No assertions found in response from IdP.');
609  }
610 
611  $ret = array();
612  foreach ($assertion as $a) {
613  $ret[] = self::processAssertion($spMetadata, $idpMetadata, $response, $a, $responseSigned);
614  }
615 
616  return $ret;
617  }
618 
619 
636  private static function processAssertion(
639  \SAML2\Response $response,
640  $assertion,
641  $responseSigned
642  ) {
643  assert($assertion instanceof \SAML2\Assertion || $assertion instanceof \SAML2\EncryptedAssertion);
644  assert(is_bool($responseSigned));
645 
646  $assertion = self::decryptAssertion($idpMetadata, $spMetadata, $assertion);
647  self::decryptAttributes($idpMetadata, $spMetadata, $assertion);
648 
649  if (!self::checkSign($idpMetadata, $assertion)) {
650  if (!$responseSigned) {
651  throw new SimpleSAML_Error_Exception('Neither the assertion nor the response was signed.');
652  }
653  } // at least one valid signature found
654 
656 
657  // check various properties of the assertion
658  $notBefore = $assertion->getNotBefore();
659  if ($notBefore !== null && $notBefore > time() + 60) {
660  throw new SimpleSAML_Error_Exception(
661  'Received an assertion that is valid in the future. Check clock synchronization on IdP and SP.'
662  );
663  }
664  $notOnOrAfter = $assertion->getNotOnOrAfter();
665  if ($notOnOrAfter !== null && $notOnOrAfter <= time() - 60) {
666  throw new SimpleSAML_Error_Exception(
667  'Received an assertion that has expired. Check clock synchronization on IdP and SP.'
668  );
669  }
670  $sessionNotOnOrAfter = $assertion->getSessionNotOnOrAfter();
671  if ($sessionNotOnOrAfter !== null && $sessionNotOnOrAfter <= time() - 60) {
672  throw new SimpleSAML_Error_Exception(
673  'Received an assertion with a session that has expired. Check clock synchronization on IdP and SP.'
674  );
675  }
676  $validAudiences = $assertion->getValidAudiences();
677  if ($validAudiences !== null) {
678  $spEntityId = $spMetadata->getString('entityid');
679  if (!in_array($spEntityId, $validAudiences, true)) {
680  $candidates = '['.implode('], [', $validAudiences).']';
681  throw new SimpleSAML_Error_Exception('This SP ['.$spEntityId.
682  '] is not a valid audience for the assertion. Candidates were: '.$candidates);
683  }
684  }
685 
686  $found = false;
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);
692  continue;
693  }
694 
695  // is SSO with HoK enabled? IdP remote metadata overwrites SP metadata configuration
696  $hok = $idpMetadata->getBoolean('saml20.hok.assertion', null);
697  if ($hok === null) {
698  $hok = $spMetadata->getBoolean('saml20.hok.assertion', false);
699  }
700  if ($sc->Method === \SAML2\Constants::CM_BEARER && $hok) {
701  $lastError = 'Bearer SubjectConfirmation received, but Holder-of-Key SubjectConfirmation needed';
702  continue;
703  }
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.';
707  continue;
708  }
709 
710  $scd = $sc->SubjectConfirmationData;
711  if ($sc->Method === \SAML2\Constants::CM_HOK) {
712  // check HoK Assertion
713  if (\SimpleSAML\Utils\HTTP::isHTTPS() === false) {
714  $lastError = 'No HTTPS connection, but required for Holder-of-Key SSO';
715  continue;
716  }
717  if (isset($_SERVER['SSL_CLIENT_CERT']) && empty($_SERVER['SSL_CLIENT_CERT'])) {
718  $lastError = 'No client certificate provided during TLS Handshake with SP';
719  continue;
720  }
721  // extract certificate data (if this is a certificate)
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';
727  continue;
728  }
729  // we have a valid client certificate from the browser
730  $clientCert = str_replace(array("\r", "\n", " "), '', $matches[1]);
731 
732  $keyInfo = array();
733  foreach ($scd->info as $thing) {
734  if ($thing instanceof \SAML2\XML\ds\KeyInfo) {
735  $keyInfo[] = $thing;
736  }
737  }
738  if (count($keyInfo) != 1) {
739  $lastError = 'Error validating Holder-of-Key assertion: Only one <ds:KeyInfo> element in '.
740  '<SubjectConfirmationData> allowed';
741  continue;
742  }
743 
744  $x509data = array();
745  foreach ($keyInfo[0]->info as $thing) {
746  if ($thing instanceof \SAML2\XML\ds\X509Data) {
747  $x509data[] = $thing;
748  }
749  }
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';
753  continue;
754  }
755 
756  $x509cert = array();
757  foreach ($x509data[0]->data as $thing) {
758  if ($thing instanceof \SAML2\XML\ds\X509Certificate) {
759  $x509cert[] = $thing;
760  }
761  }
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';
765  continue;
766  }
767 
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';
772  continue;
773  }
774  }
775 
776  // if no SubjectConfirmationData then don't do anything.
777  if ($scd === null) {
778  $lastError = 'No SubjectConfirmationData provided';
779  continue;
780  }
781 
782  if ($scd->NotBefore && $scd->NotBefore > time() + 60) {
783  $lastError = 'NotBefore in SubjectConfirmationData is in the future: '.$scd->NotBefore;
784  continue;
785  }
786  if ($scd->NotOnOrAfter && $scd->NotOnOrAfter <= time() - 60) {
787  $lastError = 'NotOnOrAfter in SubjectConfirmationData is in the past: '.$scd->NotOnOrAfter;
788  continue;
789  }
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).'.';
793  continue;
794  }
795  if ($scd->InResponseTo !== null && $response->getInResponseTo() !== null &&
796  $scd->InResponseTo !== $response->getInResponseTo()
797  ) {
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).'.';
801  continue;
802  }
803  $found = true;
804  break;
805  }
806  if (!$found) {
807  throw new SimpleSAML_Error_Exception('Error validating SubjectConfirmation in Assertion: '.$lastError);
808  } // as far as we can tell, the assertion is valid
809 
810  // maybe we need to base64 decode the attributes in the assertion?
811  if ($idpMetadata->getBoolean('base64attributes', false)) {
812  $attributes = $assertion->getAttributes();
813  $newAttributes = array();
814  foreach ($attributes as $name => $values) {
815  $newAttributes[$name] = array();
816  foreach ($values as $value) {
817  foreach (explode('_', $value) as $v) {
818  $newAttributes[$name][] = base64_decode($v);
819  }
820  }
821  }
822  $assertion->setAttributes($newAttributes);
823  }
824 
825  // decrypt the NameID element if it is encrypted
826  if ($assertion->isNameIdEncrypted()) {
827  try {
828  $keys = self::getDecryptionKeys($idpMetadata, $spMetadata);
829  } catch (Exception $e) {
830  throw new SimpleSAML_Error_Exception('Error decrypting NameID: '.$e->getMessage());
831  }
832 
833  $blacklist = self::getBlacklistedAlgorithms($idpMetadata, $spMetadata);
834 
835  $lastException = null;
836  foreach ($keys as $i => $key) {
837  try {
838  $assertion->decryptNameId($key, $blacklist);
839  SimpleSAML\Logger::debug('Decryption with key #'.$i.' succeeded.');
840  $lastException = null;
841  break;
842  } catch (Exception $e) {
843  SimpleSAML\Logger::debug('Decryption with key #'.$i.' failed with exception: '.$e->getMessage());
844  $lastException = $e;
845  }
846  }
847  if ($lastException !== null) {
848  throw $lastException;
849  }
850  }
851 
852  return $assertion;
853  }
854 
855 
866  {
867 
868  $sharedKey = $metadata->getString('sharedkey', null);
869  if ($sharedKey !== null) {
870  $key = new XMLSecurityKey(XMLSecurityKey::AES128_CBC);
871  $key->loadKey($sharedKey);
872  return $key;
873  }
874 
875  $keys = $metadata->getPublicKeys('encryption', true);
876  foreach ($keys as $key) {
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);
884  return $key;
885  }
886  }
887 
888  throw new SimpleSAML_Error_Exception('No supported encryption key in '.
889  var_export($metadata->getString('entityid'), true));
890  }
891 }
static getBlacklistedAlgorithms(SimpleSAML_Configuration $srcMetadata, SimpleSAML_Configuration $dstMetadata)
Retrieve blacklisted algorithms.
Definition: Message.php:332
static validateMessage(SimpleSAML_Configuration $srcMetadata, SimpleSAML_Configuration $dstMetadata, \SAML2\Message $message)
Check signature on a SAML2 message if enabled.
Definition: Message.php:236
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.
static debug($string)
Definition: Logger.php:211
$spEntityId
hasValue($name)
Check whether a key in the configuration exists or not.
$certificates
Definition: metarefresh.php:39
getValue($name, $default=null)
Retrieve a configuration option set in config.php.
$keys
static buildLogoutRequest(SimpleSAML_Configuration $srcMetadata, SimpleSAML_Configuration $dstMetadata)
Build a logout request based on information in the metadata.
Definition: Message.php:536
static findCertificate(array $certFingerprints, array $certificates)
Find the certificate used to sign a message or assertion.
Definition: Message.php:114
$spMetadata
$metadata['__DYNAMIC:1__']
static buildAuthnRequest(SimpleSAML_Configuration $spMetadata, SimpleSAML_Configuration $idpMetadata)
Build an authentication request based on information in the metadata.
Definition: Message.php:466
static addRedirectSign(SimpleSAML_Configuration $srcMetadata, SimpleSAML_Configuration $dstMetadata, \SAML2\Message $message)
Add signature key and and senders certificate to message.
Definition: Message.php:69
$algo
Definition: pwgen.php:34
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.
Definition: Crypto.php:195
static notice($string)
Definition: Logger.php:188
Attribute-related utility methods.
catch(Exception $e) $message
static decryptAssertion(SimpleSAML_Configuration $srcMetadata, SimpleSAML_Configuration $dstMetadata, $assertion)
Decrypt an assertion.
Definition: Message.php:357
foreach($_POST as $key=> $value) $res
static getSelfURLNoQuery()
Retrieve the current URL using the base URL in the configuration, without the query parameters...
Definition: HTTP.php:843
$values
static getDecryptionKeys(SimpleSAML_Configuration $srcMetadata, SimpleSAML_Configuration $dstMetadata)
Retrieve the decryption keys from metadata.
Definition: Message.php:281
static loadPublicKey(\SimpleSAML_Configuration $metadata, $required=false, $prefix='')
Get public key or certificate from metadata.
Definition: Crypto.php:265
static addSign(SimpleSAML_Configuration $srcMetadata, SimpleSAML_Configuration $dstMetadata, \SAML2\SignedElement $element)
Add signature key and sender certificate to an element (Message or Assertion).
Definition: Message.php:20
getBoolean($name, $default=self::REQUIRED_OPTION)
This function retrieves a boolean configuration option.
if(array_key_exists('yes', $_REQUEST)) $attributes
Definition: getconsent.php:85
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.
Definition: Message.php:149
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.
Definition: Message.php:865
static decryptAttributes(SimpleSAML_Configuration $srcMetadata, SimpleSAML_Configuration $dstMetadata, \SAML2\Assertion &$assertion)
Decrypt any encrypted attributes in an assertion.
Definition: Message.php:411
static getResponseError(\SAML2\StatusResponse $response)
Retrieve the status code of a response as a sspmod_saml_Error.
Definition: Message.php:452
getPublicKeys($use=null, $required=false, $prefix='')
Get public key from metadata.
$lr
getString($name, $default=self::REQUIRED_OPTION)
This function retrieves a string configuration option.
$this data['403_header']
static processAssertion(SimpleSAML_Configuration $spMetadata, SimpleSAML_Configuration $idpMetadata, \SAML2\Response $response, $assertion, $responseSigned)
Process an assertion in a response.
Definition: Message.php:636
$ret
Definition: parser.php:6
$i
Definition: disco.tpl.php:19
$idpMetadata
info()
Definition: info.php:2
$response
$key
Definition: croninfo.php:18
static processResponse(SimpleSAML_Configuration $spMetadata, SimpleSAML_Configuration $idpMetadata, \SAML2\Response $response)
Process a response message.
Definition: Message.php:583
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.
Definition: Message.php:556