Example #1
0
 public function validate(Response $response, Result $result)
 {
     $destination = $response->getDestination();
     if (!$this->expectedDestination->equals(new Destination($destination))) {
         $result->addError(sprintf('Destination in response "%s" does not match the expected destination "%s"', $destination, $this->expectedDestination));
     }
 }
Example #2
0
 /**
  * @test
  * @group signature
  */
 public function signed_message_with_valid_signature_is_validated_correctly()
 {
     $pattern = Certificate::CERTIFICATE_PATTERN;
     preg_match($pattern, CertificatesMock::PUBLIC_KEY_PEM, $matches);
     $config = new IdentityProvider(array('certificateData' => $matches[1]));
     $validator = new PublicKeyValidator(new SimpleTestLogger(), new KeyLoader());
     $doc = DOMDocumentFactory::fromFile(__DIR__ . '/response.xml');
     $response = new Response($doc->firstChild);
     $response->setSignatureKey(CertificatesMock::getPrivateKey());
     $response->setCertificates(array(CertificatesMock::PUBLIC_KEY_PEM));
     // convert to signed response
     $response = new Response($response->toSignedXML());
     $this->assertTrue($validator->canValidate($response, $config), 'Cannot validate the element');
     $this->assertTrue($validator->hasValidSignature($response, $config), 'The signature is not valid');
 }
 public static function handleLoginRequest(IPerson $Person)
 {
     try {
         $binding = Binding::getCurrentBinding();
     } catch (Exception $e) {
         return static::throwUnauthorizedError('Cannot obtain SAML2 binding');
     }
     $request = $binding->receive();
     // build response
     $response = new Response();
     $response->setInResponseTo($request->getId());
     $response->setRelayState($request->getRelayState());
     $response->setDestination($request->getAssertionConsumerServiceURL());
     // build assertion
     $assertion = new Assertion();
     $assertion->setIssuer(static::$issuer);
     $assertion->setSessionIndex(ContainerSingleton::getInstance()->generateId());
     $assertion->setNotBefore(time() - 30);
     $assertion->setNotOnOrAfter(time() + 300);
     $assertion->setAuthnContext(SAML2_Constants::AC_PASSWORD);
     // build subject confirmation
     $sc = new SubjectConfirmation();
     $sc->Method = SAML2_Constants::CM_BEARER;
     $sc->SubjectConfirmationData = new SubjectConfirmationData();
     $sc->SubjectConfirmationData->NotOnOrAfter = $assertion->getNotOnOrAfter();
     $sc->SubjectConfirmationData->Recipient = $request->getAssertionConsumerServiceURL();
     $sc->SubjectConfirmationData->InResponseTo = $request->getId();
     $assertion->setSubjectConfirmation([$sc]);
     // set NameID
     $assertion->setNameId(['Format' => 'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress', 'Value' => $Person->Username . '@' . static::$issuer]);
     // set additional attributes
     $assertion->setAttributes(['User.Email' => [$Person->Email], 'User.Username' => [$Person->Username]]);
     // attach assertion to response
     $response->setAssertions([$assertion]);
     // create signature
     $privateKey = new XMLSecurityKey(XMLSecurityKey::RSA_SHA1, ['type' => 'private']);
     $privateKey->loadKey(static::$privateKey);
     $response->setSignatureKey($privateKey);
     $response->setCertificates([static::$certificate]);
     // prepare response
     $responseXML = $response->toSignedXML();
     $responseString = $responseXML->ownerDocument->saveXML($responseXML);
     // dump response and quit
     #        header('Content-Type: text/xml');
     #        die($responseString);
     // send response
     $responseBinding = new HTTPPost();
     $responseBinding->send($response);
 }
 /**
  * @test
  * @group signature
  */
 public function signed_message_with_valid_signature_is_validated_correctly()
 {
     $pattern = Certificate::CERTIFICATE_PATTERN;
     preg_match($pattern, CertificatesMock::PUBLIC_KEY_PEM, $matches);
     $certdata = X509::createFromCertificateData($matches[1]);
     $fingerprint = $certdata->getFingerprint();
     $fingerprint_retry = $certdata->getFingerprint();
     $this->assertTrue($fingerprint->equals($fingerprint_retry), 'Cached fingerprint does not match original');
     $config = new IdentityProvider(array('certificateFingerprints' => array($fingerprint->getRaw())));
     $validator = new FingerprintValidator(new SimpleTestLogger(), new FingerprintLoader());
     $doc = DOMDocumentFactory::fromFile(__DIR__ . '/response.xml');
     $response = new Response($doc->firstChild);
     $response->setSignatureKey(CertificatesMock::getPrivateKey());
     $response->setCertificates(array(CertificatesMock::PUBLIC_KEY_PEM));
     // convert to signed response
     $response = new Response($response->toSignedXML());
     $this->assertTrue($validator->canValidate($response, $config), 'Cannot validate the element');
     $this->assertTrue($validator->hasValidSignature($response, $config), 'The signature is not valid');
 }
Example #5
0
 /**
  * Construct an authnresponse and send it.
  * Also test setting a relaystate and destination for the response.
  */
 public function testSendAuthnResponse()
 {
     $response = new Response();
     $response->setIssuer('testIssuer');
     $response->setRelayState('http://example.org');
     $response->setDestination('http://example.org/login?success=yes');
     $response->setSignatureKey(CertificatesMock::getPrivateKey());
     $hr = new HTTPPost();
     $hr->send($response);
 }
Example #6
0
 /**
  * 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\Constants::CM_BEARER, \SAML2\Constants::CM_HOK, \SAML2\Constants::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\Constants::CM_BEARER && $hok) {
             $lastError = 'Bearer SubjectConfirmation received, but Holder-of-Key SubjectConfirmation needed';
             continue;
         }
         if ($sc->Method === \SAML2\Constants::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\Constants::CM_HOK) {
             /* Check HoK Assertion */
             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';
                 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;
 }
Example #7
0
 /**
  * Construct an authnresponse and send it.
  * Also test setting a relaystate and destination for the response.
  */
 public function testSendAuthnResponse()
 {
     $response = new Response();
     $response->setIssuer('testIssuer');
     $response->setRelayState('http://example.org');
     $response->setDestination('http://example.org/login?success=yes');
     $hr = new HTTPRedirect();
     $hr->send($response);
 }
Example #8
0
 /**
  * @param \SAML2\Response $response
  *
  * @return \SAML2\Assertion[]
  */
 private function processAssertions(Response $response)
 {
     $assertions = $response->getAssertions();
     if (empty($assertions)) {
         throw new NoAssertionsFoundException('No assertions found in response from IdP.');
     }
     if (!$this->responseIsSigned) {
         foreach ($assertions as $assertion) {
             if (!$assertion->getWasSignedAtConstruction()) {
                 throw new UnsignedResponseException('Both the response and the assertion it containes are not signed.');
             }
         }
     }
     return $this->assertionProcessor->processAssertions($assertions);
 }
 /**
  * @return \SAML2\Response
  */
 private function getSignedResponseWithSignedAssertion()
 {
     $doc = new \DOMDocument();
     $doc->load(__DIR__ . '/response.xml');
     $response = new Response($doc->firstChild);
     $response->setSignatureKey(CertificatesMock::getPrivateKey());
     $response->setCertificates(array(CertificatesMock::PUBLIC_KEY_PEM));
     $assertions = $response->getAssertions();
     $assertion = $assertions[0];
     $assertion->setSignatureKey(CertificatesMock::getPrivateKey());
     $assertion->setCertificates(array(CertificatesMock::PUBLIC_KEY_PEM));
     return new Response($response->toSignedXML());
 }
Example #10
0
 /**
  * @group Message
  */
 public function testConvertIssuerToXML()
 {
     // first, try with common Issuer objects (Format=entity)
     $response = new Response();
     $issuer = new XML\saml\Issuer();
     $issuer->value = 'https://gateway.stepup.org/saml20/sp/metadata';
     $response->setIssuer($issuer);
     $xml = $response->toUnsignedXML();
     $xml_issuer = Utils::xpQuery($xml, './saml_assertion:Issuer');
     $xml_issuer = $xml_issuer[0];
     $this->assertFalse($xml_issuer->hasAttributes());
     $this->assertEquals($issuer->value, $xml_issuer->textContent);
     // now, try an Issuer with another format and attributes
     $issuer->Format = Constants::NAMEID_UNSPECIFIED;
     $issuer->NameQualifier = 'SomeNameQualifier';
     $issuer->SPNameQualifier = 'SomeSPNameQualifier';
     $issuer->SPProvidedID = 'SomeSPProvidedID';
     $response->setIssuer($issuer);
     $xml = $response->toUnsignedXML();
     $xml_issuer = Utils::xpQuery($xml, './saml_assertion:Issuer');
     $xml_issuer = $xml_issuer[0];
     $this->assertTrue($xml_issuer->hasAttributes());
     $this->assertEquals($issuer->value, $xml_issuer->textContent);
     $this->assertEquals($issuer->NameQualifier, $xml_issuer->getAttribute('NameQualifier'));
     $this->assertEquals($issuer->SPNameQualifier, $xml_issuer->getAttribute('SPNameQualifier'));
     $this->assertEquals($issuer->SPProvidedID, $xml_issuer->getAttribute('SPProvidedID'));
     // finally, make sure we can skip the Issuer by setting it to null
     $response->setIssuer(null);
     $xml = $response->toUnsignedXML();
     $this->assertEmpty(Utils::xpQuery($xml, './saml_assertion:Issuer'));
 }
Example #11
0
 public function validate(Response $response, Result $result)
 {
     if (!$response->isSuccess()) {
         $result->addError($this->buildMessage($response->getStatus()));
     }
 }