diff --git a/lib/SimpleSAML/XML/SAML20/AuthnResponse.php b/lib/SimpleSAML/XML/SAML20/AuthnResponse.php index 3a60eb4189d429317f36fa1b89045df25f93c89f..851d8aa69211371e5d02c16ca6ab68f8ab3bd60e 100644 --- a/lib/SimpleSAML/XML/SAML20/AuthnResponse.php +++ b/lib/SimpleSAML/XML/SAML20/AuthnResponse.php @@ -5,6 +5,7 @@ require_once('SimpleSAML/Session.php'); require_once('SimpleSAML/Utilities.php'); require_once('SimpleSAML/Metadata/MetaDataStorageHandler.php'); require_once('SimpleSAML/XML/AuthnResponse.php'); +require_once('SimpleSAML/XML/Validator.php'); require_once('xmlseclibs.php'); @@ -28,7 +29,12 @@ class SimpleSAML_XML_SAML20_AuthnResponse extends SimpleSAML_XML_AuthnResponse { */ const SAML2_ASSERT_NS = 'urn:oasis:names:tc:SAML:2.0:assertion'; const SAML2_PROTOCOL_NS = 'urn:oasis:names:tc:SAML:2.0:protocol'; - + + + /** + * This variable contains an XML validator for this message. + */ + private $validator = null; function __construct(SimpleSAML_Configuration $configuration, SimpleSAML_Metadata_MetaDataStorageHandler $metadatastore) { @@ -40,102 +46,54 @@ class SimpleSAML_XML_SAML20_AuthnResponse extends SimpleSAML_XML_AuthnResponse { public function validate() { $dom = $this->getDOM(); - - /* Create an XML security object, and register ID as the id attribute for sig references. */ - $objXMLSecDSig = new XMLSecurityDSig(); - $objXMLSecDSig->idKeys[] = 'ID'; - - /* Locate the signature element to be used. */ - $objDSig = $objXMLSecDSig->locateSignature($dom); - - /* If no signature element was found, throw an error */ - if (!$objDSig) { - throw new Exception("Could not locate XML Signature element in Authentication Response"); - } - - - /* Get information about canoncalization in to the xmlsec library. Read from the siginfo part. */ - $objXMLSecDSig->canonicalizeSignedInfo(); - - $refids = $objXMLSecDSig->getRefIDs(); - - - - /* Validate refrences */ - $retVal = $objXMLSecDSig->validateReference(); - if (! $retVal) { - throw new Exception("XMLsec: digest validation failed"); - } + /* Validate the signature. */ + $this->validator = new SimpleSAML_XML_Validator($dom, 'ID'); - $key = NULL; - $objKey = $objXMLSecDSig->locateKey(); - - if ($objKey) { - if ($objKeyInfo = XMLSecEnc::staticLocateKeyInfo($objKey, $objDSig)) { - /* Handle any additional key processing such as encrypted keys here */ - } - } - - if (empty($objKey)) { - throw new Exception("Error loading key to handle Signature"); - } + // Get the issuer of the response. + $issuer = $this->getIssuer(); - /* Check certificate fingerprint. */ - if ( ! $this->validateCertFingerprint($objKey) ) { - throw new Exception("Fingerprint Validation Failed"); - } + /* Get the metadata of the issuer. */ + $md = $this->metadata->getMetaData($issuer, 'saml20-idp-remote'); + + /* Get fingerprint for the certificate of the issuer. */ + $issuerFingerprint = $md['certFingerprint']; + + /* Validate the fingerprint. */ + $this->validator->validateFingerprint($issuerFingerprint); - if (! $objXMLSecDSig->verify($objKey)) { - throw new Exception("Unable to validate Signature"); - } - - foreach ($refids AS $key => $value) { - $refids[$key] = str_replace('#', '', $value); - } - - $this->validIDs = $refids; return true; } - - - function validateCertFingerprint($objKey) { - /* Get the fingerprint. */ - $fingerprint = $objKey->getX509Fingerprint(); - if($fingerprint === NULL) { - throw new Exception('Key used to sign the message wasn\'t an X509 certificate.'); - } + /** + * This function runs an xPath query on this authentication response. + * + * @param $query The query which should be run. + * @param $node The node which this query is relative to. If this node is NULL (the default) + * then the query will be relative to the root of the response. + * @return Whatever DOMXPath::query returns. + */ + private function doXPathQuery($query, $node = NULL) { + assert('is_string($query)'); - // Get the issuer of the assertion. - $issuer = $this->getIssuer(); - $md = $this->metadata->getMetaData($issuer, 'saml20-idp-remote'); - - /* - * Get fingerprint from saml20-idp-remote metadata... - * - * Accept fingerprints with or without colons, case insensitive - */ - $issuerFingerprint = strtolower( str_replace(":", "", $md['certFingerprint']) ); - + $dom = $this->getDOM(); + assert('$dom instanceof DOMDocument'); - - if (empty($issuerFingerprint)) { - throw new Exception("Certificate finger print for entity ID [" . $issuer . "] in metadata was empty."); - } - if (empty($fingerprint)) { - throw new Exception("Certificate finger print in message was empty."); + if($node === NULL) { + $node = $dom->documentElement; } - if ($fingerprint != $issuerFingerprint) { - throw new Exception("Expecting fingerprint $issuerFingerprint but got fingerprint $fingerprint ."); - } - - return ($fingerprint == $issuerFingerprint); + assert('$node instanceof DOMNode'); + + $xPath = new DOMXpath($dom); + $xPath->registerNamespace("saml", self::SAML2_ASSERT_NS); + $xPath->registerNamespace("samlp", self::SAML2_PROTOCOL_NS); + + return $xPath->query($query, $node); } - - + + public function createSession() { SimpleSAML_Session::init(true, 'saml2'); @@ -170,105 +128,58 @@ class SimpleSAML_XML_SAML20_AuthnResponse extends SimpleSAML_XML_AuthnResponse { public function getAttributes() { - $md = $this->metadata->getMetadata($this->getIssuer(), 'saml20-idp-remote'); - + $base64 = isset($md['base64attributes']) ? $md['base64attributes'] : false; - - /* - echo 'Validids<pre>'; - print_r($this->validIDs); - echo '</pre>'; - */ $attributes = array(); $token = $this->getDOM(); - - - //echo '<pre>' . $this->getXML() . '</pre>'; - - - if ($token instanceof DOMDocument) { - - /* - echo "<PRE>token:"; - echo htmlentities($token->saveXML()); - echo ":</PRE>"; - */ - - $xPath = new DOMXpath($token); - $xPath->registerNamespace("mysaml", self::SAML2_ASSERT_NS); - $xPath->registerNamespace("mysamlp", self::SAML2_PROTOCOL_NS); - $query = "/mysamlp:Response/mysaml:Assertion/mysaml:Conditions"; - $nodelist = $xPath->query($query); - - if ($node = $nodelist->item(0)) { - - $start = $node->getAttribute("NotBefore"); - $end = $node->getAttribute("NotOnOrAfter"); - + + if($this->validator === NULL) { + throw new Exception('Called getAttributes on a SAML2 AuthnResponse which hasn\'t been validated.'); + } + + + if ( !($token instanceof DOMDocument)) { + throw new Exception('Called getAttributes on a SAML2 AuthnResponse which doesn\'t contain a message.'); + } + + $assertions = $this->doXPathQuery('/samlp:Response/saml:Assertion'); + foreach($assertions as $assertion) { + + if(!$this->validator->isNodeValidated($assertion)) { + throw new Exception('A SAML2 AuthnResponse contained an Assertion which isn\'t verified by the signature.'); + } + + foreach($this->doXPathQuery('saml:Conditions', $assertion) as $condition) { + + $start = $condition->getAttribute("NotBefore"); + $end = $condition->getAttribute("NotOnOrAfter"); + if (! SimpleSAML_Utilities::checkDateConditions($start, $end)) { throw new Exception("Date check failed (between $start and $end). Check if the clocks on the SP and IdP are synchronized. Alternatively you can get this message, when you move back in history or refresh an old page."); } } - - $valididqueries = array(); - foreach ($this->validIDs AS $vid) { - $valididqueries[] = "@ID='" . $vid . "'"; - } - $valididquery = join(' or ', $valididqueries); - - - foreach ( - array( - "/mysamlp:Response[" . $valididquery . "]/mysaml:Assertion/mysaml:AttributeStatement/mysaml:Attribute", - "/mysamlp:Response/mysaml:Assertion[" . $valididquery . "]/mysaml:AttributeStatement/mysaml:Attribute") AS $query) { - - //echo 'performing query : ' . $query; - -// $query = "/mysamlp:Response[" . $valididquery . "]/mysaml:Assertion/mysaml:AttributeStatement/mysaml:Attribute"; - $nodelist = $xPath->query($query); - - - -// if (is_array($nodelist)) { - - - foreach ($nodelist AS $node) { - - if ($name = $node->getAttribute("Name")) { -// echo "Name "; - $value = array(); - foreach ($node->childNodes AS $child) { - if ($child->localName == "AttributeValue") { - $newvalue = $child->textContent; - if ($base64) { - $values = explode('_', $newvalue); - foreach($values AS $v) { - $value[] = base64_decode($v); - } - } else { - - $value[] = $newvalue; - } - } - } - $attributes[$name] = $value; - } + + foreach($this->doXPathQuery('saml:AttributeStatement/saml:Attribute/saml:AttributeValue', $assertion) as $attribute) { + + $name = $attribute->parentNode->getAttribute('Name'); + $value = $attribute->textContent; + + if(!array_key_exists($name, $attributes)) { + $attributes[$name] = array(); + } + + if ($base64) { + foreach(explode('_', $value) AS $v) { + $attributes[$name][] = base64_decode($v); } - -// } - + } else { + $attributes[$name][] = $value; + } } - - - } -/* - echo '<p>Attributes<pre>'; - print_r($attributes); - echo '</pre>'; -*/ + return $attributes; }