/** * Initialize a message. * * This constructor takes an optional parameter with a \DOMElement. If this * parameter is given, the message will be initialized with data from that * XML element. * * If no XML element is given, the message is initialized with suitable * default values. * * @param string $tagName The tag name of the root element. * @param \DOMElement|null $xml The input message. * @throws \Exception */ protected function __construct($tagName, \DOMElement $xml = null) { assert('is_string($tagName)'); $this->tagName = $tagName; $this->id = Utils::getContainer()->generateId(); $this->issueInstant = Temporal::getTime(); $this->certificates = array(); $this->validators = array(); if ($xml === null) { return; } if (!$xml->hasAttribute('ID')) { throw new \Exception('Missing ID attribute on SAML message.'); } $this->id = $xml->getAttribute('ID'); if ($xml->getAttribute('Version') !== '2.0') { /* Currently a very strict check. */ throw new \Exception('Unsupported version: ' . $xml->getAttribute('Version')); } $this->issueInstant = Utils::xsDateTimeToTimestamp($xml->getAttribute('IssueInstant')); if ($xml->hasAttribute('Destination')) { $this->destination = $xml->getAttribute('Destination'); } if ($xml->hasAttribute('Consent')) { $this->consent = $xml->getAttribute('Consent'); } $issuer = Utils::xpQuery($xml, './saml_assertion:Issuer'); if (!empty($issuer)) { $this->issuer = trim($issuer[0]->textContent); } /* Validate the signature element of the message. */ try { $sig = Utils::validateElement($xml); if ($sig !== false) { $this->messageContainedSignatureUponConstruction = true; $this->certificates = $sig['Certificates']; $this->validators[] = array('Function' => array('\\SAML2\\Utils', 'validateSignature'), 'Data' => $sig); } } catch (\Exception $e) { /* Ignore signature validation errors. */ } $this->extensions = Extensions::getList($xml); }
/** * Decrypt the NameId of the subject in the assertion. * * @param XMLSecurityKey $key The decryption key. * @param array $blacklist Blacklisted decryption algorithms. */ public function decryptNameId(XMLSecurityKey $key, array $blacklist = array()) { if ($this->encryptedNameId === null) { /* No NameID to decrypt. */ return; } $nameId = Utils::decryptElement($this->encryptedNameId, $key, $blacklist); Utils::getContainer()->debugMessage($nameId, 'decrypt'); $this->nameId = Utils::parseNameId($nameId); $this->encryptedNameId = null; }
/** * Initialize a message. * * This constructor takes an optional parameter with a \DOMElement. If this * parameter is given, the message will be initialized with data from that * XML element. * * If no XML element is given, the message is initialized with suitable * default values. * * @param string $tagName The tag name of the root element * @param \DOMElement|null $xml The input message * * @throws \Exception */ protected function __construct($tagName, \DOMElement $xml = null) { assert('is_string($tagName)'); $this->tagName = $tagName; $this->id = Utils::getContainer()->generateId(); $this->issueInstant = Temporal::getTime(); $this->certificates = array(); $this->validators = array(); if ($xml === null) { return; } if (!$xml->hasAttribute('ID')) { throw new \Exception('Missing ID attribute on SAML message.'); } $this->id = $xml->getAttribute('ID'); if ($xml->getAttribute('Version') !== '2.0') { /* Currently a very strict check. */ throw new \Exception('Unsupported version: ' . $xml->getAttribute('Version')); } $this->issueInstant = Utils::xsDateTimeToTimestamp($xml->getAttribute('IssueInstant')); if ($xml->hasAttribute('Destination')) { $this->destination = $xml->getAttribute('Destination'); } if ($xml->hasAttribute('Consent')) { $this->consent = $xml->getAttribute('Consent'); } $issuer = Utils::xpQuery($xml, './saml_assertion:Issuer'); if (!empty($issuer)) { $this->issuer = new XML\saml\Issuer($issuer[0]); if ($this->issuer->Format === Constants::NAMEID_ENTITY) { $this->issuer = $this->issuer->value; } } $this->validateSignature($xml); $this->extensions = Extensions::getList($xml); }
/** * Retrieve the assertion. * * @param XMLSecurityKey $inputKey The key we should use to decrypt the assertion. * @param array $blacklist Blacklisted decryption algorithms. * @return \SAML2\Assertion The decrypted assertion. */ public function getAssertion(XMLSecurityKey $inputKey, array $blacklist = array()) { $assertionXML = Utils::decryptElement($this->encryptedData, $inputKey, $blacklist); Utils::getContainer()->debugMessage($assertionXML, 'decrypt'); return new Assertion($assertionXML); }
/** * Decrypt an encrypted element. * * @param \DOMElement $encryptedData The encrypted data. * @param XMLSecurityKey $inputKey The decryption key. * @param array $blacklist Blacklisted decryption algorithms. * @return \DOMElement The decrypted element. * @throws \Exception */ public static function decryptElement(\DOMElement $encryptedData, XMLSecurityKey $inputKey, array $blacklist = array()) { try { return self::doDecryptElement($encryptedData, $inputKey, $blacklist); } catch (\Exception $e) { /* * Something went wrong during decryption, but for security * reasons we cannot tell the user what failed. */ Utils::getContainer()->getLogger()->error('Decryption failed: ' . $e->getMessage()); throw new \Exception('Failed to decrypt XML element.', 0, $e); } }
/** * Validate a SOAP message against the certificate on the SSL connection. * * @param string $data The public key that was used on the connection. * @param XMLSecurityKey $key The key we should validate the certificate against. * @throws \Exception */ public static function validateSSL($data, XMLSecurityKey $key) { assert('is_string($data)'); $keyInfo = openssl_pkey_get_details($key->key); if ($keyInfo === false) { throw new \Exception('Unable to get key details from XMLSecurityKey.'); } if (!isset($keyInfo['key'])) { throw new \Exception('Missing key in public key details.'); } if ($keyInfo['key'] !== $data) { Utils::getContainer()->getLogger()->debug('Key on SSL connection did not match key we validated against.'); return; } Utils::getContainer()->getLogger()->debug('Message validated based on SSL certificate.'); }
/** * Receive a SAML 2 message sent using the HTTP-Redirect binding. * * Throws an exception if it is unable receive the message. * * @return \SAML2\Message The received message. * @throws \Exception * * NPath is currently too high but solving that just moves code around. * @SuppressWarnings(PHPMD.NPathComplexity) */ public function receive() { $data = self::parseQuery(); if (array_key_exists('SAMLRequest', $data)) { $message = $data['SAMLRequest']; } elseif (array_key_exists('SAMLResponse', $data)) { $message = $data['SAMLResponse']; } else { throw new \Exception('Missing SAMLRequest or SAMLResponse parameter.'); } if (isset($data['SAMLEncoding']) && $data['SAMLEncoding'] !== self::DEFLATE) { throw new \Exception('Unknown SAMLEncoding: ' . var_export($data['SAMLEncoding'], true)); } $message = base64_decode($message); if ($message === false) { throw new \Exception('Error while base64 decoding SAML message.'); } $message = gzinflate($message); if ($message === false) { throw new \Exception('Error while inflating SAML message.'); } Utils::getContainer()->debugMessage($message, 'in'); $document = DOMDocumentFactory::fromString($message); $xml = $document->firstChild; $message = Message::fromXML($xml); if (array_key_exists('RelayState', $data)) { $message->setRelayState($data['RelayState']); } if (!array_key_exists('Signature', $data)) { return $message; } if (!array_key_exists('SigAlg', $data)) { throw new \Exception('Missing signature algorithm.'); } $signData = array('Signature' => $data['Signature'], 'SigAlg' => $data['SigAlg'], 'Query' => $data['SignedQuery']); $message->addValidator(array(get_class($this), 'validateSignature'), $signData); return $message; }
/** * Receive a SAML 2 message sent using the HTTP-Artifact binding. * * Throws an exception if it is unable receive the message. * * @return \SAML2\Message The received message. * @throws \Exception */ public function receive() { if (array_key_exists('SAMLart', $_REQUEST)) { $artifact = base64_decode($_REQUEST['SAMLart']); $endpointIndex = bin2hex(substr($artifact, 2, 2)); $sourceId = bin2hex(substr($artifact, 4, 20)); } else { throw new \Exception('Missing SAMLArt parameter.'); } $metadataHandler = SimpleSAML_Metadata_MetaDataStorageHandler::getMetadataHandler(); $idpMetadata = $metadataHandler->getMetaDataConfigForSha1($sourceId, 'saml20-idp-remote'); if ($idpMetadata === null) { throw new \Exception('No metadata found for remote provider with SHA1 ID: ' . var_export($sourceId, true)); } $endpoint = null; foreach ($idpMetadata->getEndpoints('ArtifactResolutionService') as $ep) { if ($ep['index'] === hexdec($endpointIndex)) { $endpoint = $ep; break; } } if ($endpoint === null) { throw new \Exception('No ArtifactResolutionService with the correct index.'); } Utils::getContainer()->getLogger()->debug("ArtifactResolutionService endpoint being used is := " . $endpoint['Location']); //Construct the ArtifactResolve Request $ar = new ArtifactResolve(); /* Set the request attributes */ $ar->setIssuer($this->spMetadata->getString('entityid')); $ar->setArtifact($_REQUEST['SAMLart']); $ar->setDestination($endpoint['Location']); /* Sign the request */ sspmod_saml_Message::addSign($this->spMetadata, $idpMetadata, $ar); // Shoaib - moved from the SOAPClient. $soap = new SOAPClient(); // Send message through SoapClient /** @var \SAML2\ArtifactResponse $artifactResponse */ $artifactResponse = $soap->send($ar, $this->spMetadata); if (!$artifactResponse->isSuccess()) { throw new \Exception('Received error from ArtifactResolutionService.'); } $xml = $artifactResponse->getAny(); if ($xml === null) { /* Empty ArtifactResponse - possibly because of Artifact replay? */ return null; } $samlResponse = Message::fromXML($xml); $samlResponse->addValidator(array(get_class($this), 'validateSignature'), $artifactResponse); if (isset($_REQUEST['RelayState'])) { $samlResponse->setRelayState($_REQUEST['RelayState']); } return $samlResponse; }