summaryrefslogtreecommitdiffstats
path: root/lib/SAML2/Utils.php
diff options
context:
space:
mode:
Diffstat (limited to 'lib/SAML2/Utils.php')
-rw-r--r--lib/SAML2/Utils.php641
1 files changed, 0 insertions, 641 deletions
diff --git a/lib/SAML2/Utils.php b/lib/SAML2/Utils.php
deleted file mode 100644
index 5120639..0000000
--- a/lib/SAML2/Utils.php
+++ /dev/null
@@ -1,641 +0,0 @@
-<?php
-
-/**
- * Helper functions for the SAML2 library.
- *
- * @package simpleSAMLphp
- * @version $Id$
- */
-class SAML2_Utils {
-
- /**
- * Check the Signature in a XML element.
- *
- * This function expects the XML element to contain a Signature-element
- * which contains a reference to the XML-element. This is common for both
- * messages and assertions.
- *
- * Note that this function only validates the element itself. It does not
- * check this against any local keys.
- *
- * If no Signature-element is located, this function will return FALSE. All
- * other validation errors result in an exception. On successful validation
- * an array will be returned. This array contains the information required to
- * check the signature against a public key.
- *
- * @param DOMElement $root The element which should be validated.
- * @return array|FALSE An array with information about the Signature-element.
- */
- public static function validateElement(DOMElement $root) {
-
- /* Create an XML security object. */
- $objXMLSecDSig = new XMLSecurityDSig();
-
- /* Both SAML messages and SAML assertions use the 'ID' attribute. */
- $objXMLSecDSig->idKeys[] = 'ID';
-
- /* Locate the XMLDSig Signature element to be used. */
- $signatureElement = self::xpQuery($root, './ds:Signature');
- if (count($signatureElement) === 0) {
- /* We don't have a signature element ot validate. */
- return FALSE;
- } elseif (count($signatureElement) > 1) {
- throw new Exception('XMLSec: more than one signature element in root.');
- }
- $signatureElement = $signatureElement[0];
- $objXMLSecDSig->sigNode = $signatureElement;
-
- /* Canonicalize the XMLDSig SignedInfo element in the message. */
- $objXMLSecDSig->canonicalizeSignedInfo();
-
- /* Validate referenced xml nodes. */
- if (!$objXMLSecDSig->validateReference()) {
- throw new Exception('XMLsec: digest validation failed');
- }
-
- /* Check that $root is one of the signed nodes. */
- $rootSigned = FALSE;
- foreach ($objXMLSecDSig->getValidatedNodes() as $signedNode) {
- if ($signedNode->isSameNode($root)) {
- $rootSigned = TRUE;
- break;
- } elseif ($root->parentNode instanceof DOMDocument && $signedNode->isSameNode($root->ownerDocument)) {
- /* $root is the root element of a signed document. */
- $rootSigned = TRUE;
- break;
- }
- }
- if (!$rootSigned) {
- throw new Exception('XMLSec: The root element is not signed.');
- }
-
- /* Now we extract all available X509 certificates in the signature element. */
- $certificates = array();
- foreach (self::xpQuery($signatureElement, './ds:KeyInfo/ds:X509Data/ds:X509Certificate') as $certNode) {
- $certData = trim($certNode->textContent);
- $certData = str_replace(array("\r", "\n", "\t", ' '), '', $certData);
- $certificates[] = $certData;
- }
-
- $ret = array(
- 'Signature' => $objXMLSecDSig,
- 'Certificates' => $certificates,
- );
-
- return $ret;
- }
-
-
- /**
- * Helper function to convert a XMLSecurityKey to the correct algorithm.
- *
- * @param XMLSecurityKey $key The key.
- * @param string $algorithm The desired algorithm.
- * @param string $types Public or private key, defaults to public.
- * @return XMLSecurityKey The new key.
- */
- public static function castKey(XMLSecurityKey $key, $algorithm, $type = 'public') {
- assert('is_string($algorithm)');
- assert('$type === "public" || $type === "private"');
-
- // do nothing if algorithm is already the type of the key
- if ($key->type === $algorithm) {
- return $key;
- }
-
- $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.');
- }
-
- $newKey = new XMLSecurityKey($algorithm, array('type'=>$type));
- $newKey->loadKey($keyInfo['key']);
- return $newKey;
- }
-
-
- /**
- * Check a signature against a key.
- *
- * An exception is thrown if we are unable to validate the signature.
- *
- * @param array $info The information returned by the validateElement()-function.
- * @param XMLSecurityKey $key The publickey that should validate the Signature object.
- */
- public static function validateSignature(array $info, XMLSecurityKey $key) {
- assert('array_key_exists("Signature", $info)');
-
- $objXMLSecDSig = $info['Signature'];
-
- $sigMethod = self::xpQuery($objXMLSecDSig->sigNode, './ds:SignedInfo/ds:SignatureMethod');
- if (empty($sigMethod)) {
- throw new Exception('Missing SignatureMethod element.');
- }
- $sigMethod = $sigMethod[0];
- if (!$sigMethod->hasAttribute('Algorithm')) {
- throw new Exception('Missing Algorithm-attribute on SignatureMethod element.');
- }
- $algo = $sigMethod->getAttribute('Algorithm');
-
- if ($key->type === XMLSecurityKey::RSA_SHA1 && $algo !== $key->type) {
- $key = self::castKey($key, $algo);
- }
-
- /* Check the signature. */
- if (! $objXMLSecDSig->verify($key)) {
- throw new Exception("Unable to validate Signature");
- }
- }
-
-
- /**
- * Do an XPath query on an XML node.
- *
- * @param DOMNode $node The XML node.
- * @param string $query The query.
- * @return array Array with matching DOM nodes.
- */
- public static function xpQuery(DOMNode $node, $query) {
- assert('is_string($query)');
- static $xpCache = NULL;
-
- if ($node instanceof DOMDocument) {
- $doc = $node;
- } else {
- $doc = $node->ownerDocument;
- }
-
- if ($xpCache === NULL || !$xpCache->document->isSameNode($doc)) {
- $xpCache = new DOMXPath($doc);
- $xpCache->registerNamespace('soap-env', SAML2_Const::NS_SOAP);
- $xpCache->registerNamespace('saml_protocol', SAML2_Const::NS_SAMLP);
- $xpCache->registerNamespace('saml_assertion', SAML2_Const::NS_SAML);
- $xpCache->registerNamespace('saml_metadata', SAML2_Const::NS_MD);
- $xpCache->registerNamespace('ds', XMLSecurityDSig::XMLDSIGNS);
- $xpCache->registerNamespace('xenc', XMLSecEnc::XMLENCNS);
- }
-
- $results = $xpCache->query($query, $node);
- $ret = array();
- for ($i = 0; $i < $results->length; $i++) {
- $ret[$i] = $results->item($i);
- }
-
- return $ret;
- }
-
-
- /**
- * Make an exact copy the specific DOMElement.
- *
- * @param DOMElement $element The element we should copy.
- * @param DOMElement|NULL $parent The target parent element.
- * @return DOMElement The copied element.
- */
- public static function copyElement(DOMElement $element, DOMElement $parent = NULL) {
-
- if ($parent === NULL) {
- $document = new DOMDocument();
- } else {
- $document = $parent->ownerDocument;
- }
-
- $namespaces = array();
- for ($e = $element; $e !== NULL; $e = $e->parentNode) {
- foreach (SAML2_Utils::xpQuery($e, './namespace::*') as $ns) {
- $prefix = $ns->localName;
- if ($prefix === 'xml' || $prefix === 'xmlns') {
- continue;
- }
- $uri = $ns->nodeValue;
- if (!isset($namespaces[$prefix])) {
- $namespaces[$prefix] = $uri;
- }
- }
- }
-
- $newElement = $document->importNode($element, TRUE);
- if ($parent !== NULL) {
- /* We need to append the child to the parent before we add the namespaces. */
- $parent->appendChild($newElement);
- }
-
- foreach ($namespaces as $prefix => $uri) {
- $newElement->setAttributeNS($uri, $prefix . ':__ns_workaround__', 'tmp');
- $newElement->removeAttributeNS($uri, '__ns_workaround__');
- }
-
- return $newElement;
- }
-
-
- /**
- * Parse a boolean attribute.
- *
- * @param DOMElement $node The element we should fetch the attribute from.
- * @param string $attributeName The name of the attribute.
- * @param mixed $default The value that should be returned if the attribute doesn't exist.
- * @return bool|mixed The value of the attribute, or $default if the attribute doesn't exist.
- */
- public static function parseBoolean(DOMElement $node, $attributeName, $default = NULL) {
- assert('is_string($attributeName)');
-
- if (!$node->hasAttribute($attributeName)) {
- return $default;
- }
- $value = $node->getAttribute($attributeName);
- switch (strtolower($value)) {
- case '0':
- case 'false':
- return FALSE;
- case '1':
- case 'true':
- return TRUE;
- default:
- throw new Exception('Invalid value of boolean attribute ' . var_export($attributeName, TRUE) . ': ' . var_export($value, TRUE));
- }
- }
-
-
- /**
- * Create a NameID element.
- *
- * The NameId array can have the following elements: 'Value', 'Format',
- * 'NameQualifier, 'SPNameQualifier'
- *
- * Only the 'Value'-element is required.
- *
- * @param DOMElement $node The DOM node we should append the NameId to.
- * @param array $nameId The name identifier.
- */
- public static function addNameId(DOMElement $node, array $nameId) {
- assert('array_key_exists("Value", $nameId)');
-
- $xml = SAML2_Utils::addString($node, SAML2_Const::NS_SAML, 'saml:NameID', $nameId['Value']);
-
- if (array_key_exists('NameQualifier', $nameId) && $nameId['NameQualifier'] !== NULL) {
- $xml->setAttribute('NameQualifier', $nameId['NameQualifier']);
- }
- if (array_key_exists('SPNameQualifier', $nameId) && $nameId['SPNameQualifier'] !== NULL) {
- $xml->setAttribute('SPNameQualifier', $nameId['SPNameQualifier']);
- }
- if (array_key_exists('Format', $nameId) && $nameId['Format'] !== NULL) {
- $xml->setAttribute('Format', $nameId['Format']);
- }
- }
-
-
- /**
- * Parse a NameID element.
- *
- * @param DOMElement $xml The DOM element we should parse.
- * @return array The parsed name identifier.
- */
- public static function parseNameId(DOMElement $xml) {
-
- $ret = array('Value' => trim($xml->textContent));
-
- foreach (array('NameQualifier', 'SPNameQualifier', 'Format') as $attr) {
- if ($xml->hasAttribute($attr)) {
- $ret[$attr] = $xml->getAttribute($attr);
- }
- }
-
- return $ret;
- }
-
-
- /**
- * Insert a Signature-node.
- *
- * @param XMLSecurityKey $key The key we should use to sign the message.
- * @param array $certificates The certificates we should add to the signature node.
- * @param DOMElement $root The XML node we should sign.
- * @param DomElement $insertBefore The XML element we should insert the signature element before.
- */
- public static function insertSignature(XMLSecurityKey $key, array $certificates, DOMElement $root, DOMNode $insertBefore = NULL) {
-
- $objXMLSecDSig = new XMLSecurityDSig();
- $objXMLSecDSig->setCanonicalMethod(XMLSecurityDSig::EXC_C14N);
-
- switch ($key->type) {
- case XMLSecurityKey::RSA_SHA256:
- $type = XMLSecurityDSig::SHA256;
- break;
- case XMLSecurityKey::RSA_SHA384:
- $type = XMLSecurityDSig::SHA384;
- break;
- case XMLSecurityKey::RSA_SHA512:
- $type = XMLSecurityDSig::SHA512;
- break;
- default:
- $type = XMLSecurityDSig::SHA1;
- }
-
- $objXMLSecDSig->addReferenceList(
- array($root),
- $type,
- array('http://www.w3.org/2000/09/xmldsig#enveloped-signature', XMLSecurityDSig::EXC_C14N),
- array('id_name' => 'ID', 'overwrite' => FALSE)
- );
-
- $objXMLSecDSig->sign($key);
-
- foreach ($certificates as $certificate) {
- $objXMLSecDSig->add509Cert($certificate, TRUE);
- }
-
- $objXMLSecDSig->insertSignature($root, $insertBefore);
-
- }
-
-
- /**
- * Decrypt an encrypted element.
- *
- * This is an internal helper function.
- *
- * @param DOMElement $encryptedData The encrypted data.
- * @param XMLSecurityKey $inputKey The decryption key.
- * @param array &$blacklist Blacklisted decryption algorithms.
- * @return DOMElement The decrypted element.
- */
- private static function _decryptElement(DOMElement $encryptedData, XMLSecurityKey $inputKey, array &$blacklist) {
-
- $enc = new XMLSecEnc();
-
- $enc->setNode($encryptedData);
- $enc->type = $encryptedData->getAttribute("Type");
-
- $symmetricKey = $enc->locateKey($encryptedData);
- if (!$symmetricKey) {
- throw new Exception('Could not locate key algorithm in encrypted data.');
- }
-
- $symmetricKeyInfo = $enc->locateKeyInfo($symmetricKey);
- if (!$symmetricKeyInfo) {
- throw new Exception('Could not locate <dsig:KeyInfo> for the encrypted key.');
- }
-
- $inputKeyAlgo = $inputKey->getAlgorith();
- if ($symmetricKeyInfo->isEncrypted) {
- $symKeyInfoAlgo = $symmetricKeyInfo->getAlgorith();
-
- if (in_array($symKeyInfoAlgo, $blacklist, TRUE)) {
- throw new Exception('Algorithm disabled: ' . var_export($symKeyInfoAlgo, TRUE));
- }
-
- if ($symKeyInfoAlgo === XMLSecurityKey::RSA_OAEP_MGF1P && $inputKeyAlgo === XMLSecurityKey::RSA_1_5) {
- /*
- * The RSA key formats are equal, so loading an RSA_1_5 key
- * into an RSA_OAEP_MGF1P key can be done without problems.
- * We therefore pretend that the input key is an
- * RSA_OAEP_MGF1P key.
- */
- $inputKeyAlgo = XMLSecurityKey::RSA_OAEP_MGF1P;
- }
-
- /* Make sure that the input key format is the same as the one used to encrypt the key. */
- if ($inputKeyAlgo !== $symKeyInfoAlgo) {
- throw new Exception('Algorithm mismatch between input key and key used to encrypt ' .
- ' the symmetric key for the message. Key was: ' .
- var_export($inputKeyAlgo, TRUE) . '; message was: ' .
- var_export($symKeyInfoAlgo, TRUE));
- }
-
- $encKey = $symmetricKeyInfo->encryptedCtx;
- $symmetricKeyInfo->key = $inputKey->key;
-
- $keySize = $symmetricKey->getSymmetricKeySize();
- if ($keySize === NULL) {
- /* To protect against "key oracle" attacks, we need to be able to create a
- * symmetric key, and for that we need to know the key size.
- */
- throw new Exception('Unknown key size for encryption algorithm: ' . var_export($symmetricKey->type, TRUE));
- }
-
- try {
- $key = $encKey->decryptKey($symmetricKeyInfo);
- if (strlen($key) != $keySize) {
- throw new Exception('Unexpected key size (' . strlen($key) * 8 . 'bits) for encryption algorithm: ' .
- var_export($symmetricKey->type, TRUE));
- }
- } catch (Exception $e) {
- /* We failed to decrypt this key. Log it, and substitute a "random" key. */
- SimpleSAML_Logger::error('Failed to decrypt symmetric key: ' . $e->getMessage());
- /* Create a replacement key, so that it looks like we fail in the same way as if the key was correctly padded. */
-
- /* We base the symmetric key on the encrypted key and private key, so that we always behave the
- * same way for a given input key.
- */
- $encryptedKey = $encKey->getCipherValue();
- $pkey = openssl_pkey_get_details($symmetricKeyInfo->key);
- $pkey = sha1(serialize($pkey), TRUE);
- $key = sha1($encryptedKey . $pkey, TRUE);
-
- /* Make sure that the key has the correct length. */
- if (strlen($key) > $keySize) {
- $key = substr($key, 0, $keySize);
- } elseif (strlen($key) < $keySize) {
- $key = str_pad($key, $keySize);
- }
- }
- $symmetricKey->loadkey($key);
-
- } else {
- $symKeyAlgo = $symmetricKey->getAlgorith();
- /* Make sure that the input key has the correct format. */
- if ($inputKeyAlgo !== $symKeyAlgo) {
- throw new Exception('Algorithm mismatch between input key and key in message. ' .
- 'Key was: ' . var_export($inputKeyAlgo, TRUE) . '; message was: ' .
- var_export($symKeyAlgo, TRUE));
- }
- $symmetricKey = $inputKey;
- }
-
- $algorithm = $symmetricKey->getAlgorith();
- if (in_array($algorithm, $blacklist, TRUE)) {
- throw new Exception('Algorithm disabled: ' . var_export($algorithm, TRUE));
- }
-
- $decrypted = $enc->decryptNode($symmetricKey, FALSE);
-
- /*
- * This is a workaround for the case where only a subset of the XML
- * tree was serialized for encryption. In that case, we may miss the
- * namespaces needed to parse the XML.
- */
- $xml = '<root xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">'.$decrypted.'</root>';
- $newDoc = new DOMDocument();
- if (!@$newDoc->loadXML($xml)) {
- throw new Exception('Failed to parse decrypted XML. Maybe the wrong sharedkey was used?');
- }
- $decryptedElement = $newDoc->firstChild->firstChild;
- if ($decryptedElement === NULL) {
- throw new Exception('Missing encrypted element.');
- }
-
- if (!($decryptedElement instanceof DOMElement)) {
- throw new Exception('Decrypted element was not actually a DOMElement.');
- }
-
- return $decryptedElement;
- }
-
-
- /**
- * 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.
- */
- public static function decryptElement(DOMElement $encryptedData, XMLSecurityKey $inputKey, array $blacklist = array()) {
-
- try {
- return self::_decryptElement($encryptedData, $inputKey, $blacklist);
- } catch (Exception $e) {
- /*
- * Something went wrong during decryption, but for security
- * reasons we cannot tell the user what failed.
- */
- SimpleSAML_Logger::error('Decryption failed: ' . $e->getMessage());
- throw new Exception('Failed to decrypt XML element.');
- }
- }
-
-
- /**
- * Extract localized strings from a set of nodes.
- *
- * @param DOMElement $parent The element that contains the localized strings.
- * @param string $namespaceURI The namespace URI the localized strings should have.
- * @param string $localName The localName of the localized strings.
- * @return array Localized strings.
- */
- public static function extractLocalizedStrings(DOMElement $parent, $namespaceURI, $localName) {
- assert('is_string($namespaceURI)');
- assert('is_string($localName)');
-
- $ret = array();
- for ($node = $parent->firstChild; $node !== NULL; $node = $node->nextSibling) {
- if ($node->namespaceURI !== $namespaceURI || $node->localName !== $localName) {
- continue;
- }
-
- if ($node->hasAttribute('xml:lang')) {
- $language = $node->getAttribute('xml:lang');
- } else {
- $language = 'en';
- }
- $ret[$language] = trim($node->textContent);
- }
-
- return $ret;
- }
-
-
- /**
- * Extract strings from a set of nodes.
- *
- * @param DOMElement $parent The element that contains the localized strings.
- * @param string $namespaceURI The namespace URI the string elements should have.
- * @param string $localName The localName of the string elements.
- * @return array The string values of the various nodes.
- */
- public static function extractStrings(DOMElement $parent, $namespaceURI, $localName) {
- assert('is_string($namespaceURI)');
- assert('is_string($localName)');
-
- $ret = array();
- for ($node = $parent->firstChild; $node !== NULL; $node = $node->nextSibling) {
- if ($node->namespaceURI !== $namespaceURI || $node->localName !== $localName) {
- continue;
- }
- $ret[] = trim($node->textContent);
- }
-
- return $ret;
- }
-
-
- /**
- * Append string element.
- *
- * @param DOMElement $parent The parent element we should append the new nodes to.
- * @param string $namespace The namespace of the created element.
- * @param string $name The name of the created element.
- * @param string $value The value of the element.
- * @return DOMElement The generated element.
- */
- public static function addString(DOMElement $parent, $namespace, $name, $value) {
- assert('is_string($namespace)');
- assert('is_string($name)');
- assert('is_string($value)');
-
- $doc = $parent->ownerDocument;
-
- $n = $doc->createElementNS($namespace, $name);
- $n->appendChild($doc->createTextNode($value));
- $parent->appendChild($n);
-
- return $n;
- }
-
-
- /**
- * Append string elements.
- *
- * @param DOMElement $parent The parent element we should append the new nodes to.
- * @param string $namespace The namespace of the created elements
- * @param string $name The name of the created elements
- * @param bool $localized Whether the strings are localized, and should include the xml:lang attribute.
- * @param array $values The values we should create the elements from.
- */
- public static function addStrings(DOMElement $parent, $namespace, $name, $localized, array $values) {
- assert('is_string($namespace)');
- assert('is_string($name)');
- assert('is_bool($localized)');
-
- $doc = $parent->ownerDocument;
-
- foreach ($values as $index => $value) {
- $n = $doc->createElementNS($namespace, $name);
- $n->appendChild($doc->createTextNode($value));
- if ($localized) {
- $n->setAttribute('xml:lang', $index);
- }
- $parent->appendChild($n);
- }
- }
-
-
- /**
- * Create a KeyDescriptor with the given certificate.
- *
- * @param string $x509Data The certificate, as a base64-encoded DER data.
- * @return SAML2_XML_md_KeyDescriptor The keydescriptor.
- */
- public static function createKeyDescriptor($x509Data) {
- assert('is_string($x509Data)');
-
- $x509Certificate = new SAML2_XML_ds_X509Certificate();
- $x509Certificate->certificate = $x509Data;
-
- $x509Data = new SAML2_XML_ds_X509Data();
- $x509Data->data[] = $x509Certificate;
-
- $keyInfo = new SAML2_XML_ds_KeyInfo();
- $keyInfo->info[] = $x509Data;
-
- $keyDescriptor = new SAML2_XML_md_KeyDescriptor();
- $keyDescriptor->KeyInfo = $keyInfo;
-
- return $keyDescriptor;
- }
-
-}