diff --git a/LICENSE b/LICENSE index 263715f..5e62dd1 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2010-2016 OneLogin, LLC +Copyright (c) 2010-2019 OneLogin, LLC Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 2bf645d..b1b69b1 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,11 @@ This kind of WP hosting used to cache plugins and protect the wp-login.php view. You will need to contact them in order to disable the cache for this SAML plugin and also allow external HTTP POST to wp-login.php +### Security Improvements on 3.0.0 ### + +Version 3.0.0 includes a security patch that will prevent DDOS by expansion of internally defined entities (XEE) +That version also includes the use of php-saml 3.X so will be compatible with PHP 5.X and 7.X + ### Security Improvements on 2.4.3 ### Version 2.4.3 includes a security patch that contains extra validations that will prevent some kind of elaborated signature wrapping attacks and other security improvements. Previous versions are vulnerable so we highly recommended to upgrade to >= 2.4.3. diff --git a/onelogin-saml-sso/php/_toolkit_loader.php b/onelogin-saml-sso/php/_toolkit_loader.php index 34b1b3e..663b6a6 100755 --- a/onelogin-saml-sso/php/_toolkit_loader.php +++ b/onelogin-saml-sso/php/_toolkit_loader.php @@ -27,16 +27,23 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ********************************************************************************/ -$libDir = dirname(__FILE__) . '/lib/Saml2/'; -$extlibDir = dirname(__FILE__) . '/extlib/'; +// Load xmlseclibs +if (!class_exists("\RobRichards\XMLSecLibs\XMLSecurityKey")) { + $xmlseclibsSrcDir = dirname(__FILE__).'/extlib/xmlseclibs/src'; -// Load first external libs -require_once($extlibDir . 'xmlseclibs/xmlseclibs.php'); + include_once $xmlseclibsSrcDir.'/XMLSecEnc.php'; + include_once $xmlseclibsSrcDir.'/XMLSecurityDSig.php'; + include_once $xmlseclibsSrcDir.'/XMLSecurityKey.php'; + include_once $xmlseclibsSrcDir.'/Utils/XPath.php'; +} + +// Load php-saml +$libDir = dirname(__FILE__).'/lib/Saml2/'; $folderInfo = scandir($libDir); foreach ($folderInfo as $element) { if (is_file($libDir.$element) && (substr($element, -4) === '.php')) { - require_once($libDir.$element); + include_once $libDir.$element; } } diff --git a/onelogin-saml-sso/php/configuration.php b/onelogin-saml-sso/php/configuration.php index 053696f..8adbede 100644 --- a/onelogin-saml-sso/php/configuration.php +++ b/onelogin-saml-sso/php/configuration.php @@ -5,10 +5,8 @@ exit; } +require_once "_toolkit_loader.php"; require_once "compatibility.php"; -require_once (dirname(__FILE__) . "/lib/Saml2/Constants.php"); -require_once (dirname(__FILE__) . "/extlib/xmlseclibs/xmlseclibs.php"); - function onelogin_saml_configuration_render() { $title = __("SSO/SAML Settings", 'onelogin-saml-sso'); diff --git a/onelogin-saml-sso/php/extlib/xmlseclibs/CHANGELOG.txt b/onelogin-saml-sso/php/extlib/xmlseclibs/CHANGELOG.txt index c0b8cfe..a02bc7a 100644 --- a/onelogin-saml-sso/php/extlib/xmlseclibs/CHANGELOG.txt +++ b/onelogin-saml-sso/php/extlib/xmlseclibs/CHANGELOG.txt @@ -1,9 +1,79 @@ xmlseclibs.php -??, ??? ????, 2.0.0 +||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| +15, Nov 2018, 3.0.3 +Bug Fixes: +- Fix casing of class name. (Willem Stuursma-Ruwen) +- Fix Xpath casing. (Tim van Dijen) + +Improvements: +- Make PCRE2 compliant. (Stefan Winter) +- Add PHP 7.3 support. (Stefan Winter) + +27, Sep 2018, 3.0.2 +Security Improvements: +- OpenSSL is now a requirement rather than suggestion. (Slaven Bacelic) +- Filter input to avoid XPath injection. (Jaime Pérez) + +Bug Fixes: +- Fix missing parentheses (Tim van Dijen) + +Improvements: +- Use strict comparison operator to compare digest values. (Jaime Pérez) +- Remove call to file_get_contents that doesn't even work. (Jaime Pérez) +- Document potentially dangerous return value behaviour. (Thijs Kinkhorst) + +31, Aug 2017, 3.0.1 +Bug Fixes: +- Fixed missing () in function call. (Dennis Væversted) + +Improvements: +- Add OneLogin to supported software. +- Add .gitattributes to remove unneeded files. (Filippo Tessarotto) +- Fix bug in example code. (Dan Church) +- Travis: add PHP 7.1, move hhvm to allowed failures. (Thijs Kinkhorst) +- Drop failing extract-win-cert test (Thijs Kinkhorst). (Thijs Kinkhorst) +- Add comments to warn about return values of verify(). (Thijs Kinkhorst) +- Fix tests to properly check return code of verify(). (Thijs Kinkhorst) +- Restore support for PHP >= 5.4. (Jaime Pérez) + +25, May 2017, 3.0.0 +Improvements: +- Remove use of mcrypt (skymeyer) + +08, Sep 2016, 2.0.1 +Bug Fixes: +- Strip whitespace characters when parsing X509Certificate. fixes #84 + (klemen.bratec) +- Certificate 'subject' values can be arrays. fixes #80 (Andreas Stangl) +- HHVM signing node with ID attribute w/out namespace regenerates ID value. + fixes #88 (Milos Tomic) + +Improvements: +- Fix typos and add some PHPDoc Blocks. (gfaust-qb) +- Update lightSAML link. (Milos Tomic) +- Update copyright dates. + +31, Jul 2015, 2.0.0 +Features: +- Namespace support. Classes now in the RobRichards\XMLSecLibs\ namespace. + +Improvements: +- Dropped support for PHP 5.2 + +31, Jul 2015, 1.4.1 +Bug Fixes: +- Allow for large digest values that may have line breaks. fixes #62 + Features: - Support for locating specific signature when multiple exist in document. (griga3k) +Improvements: +- Add optional argument to XMLSecurityDSig to define the prefix to be used, + also allowing for null to use no prefix, for the dsig namespace. fixes #13 +- Code cleanup +- Depreciated XMLSecurityDSig::generate_GUID for XMLSecurityDSig::generateGUID + 23, Jun 2015, 1.4.0 Features: - Support for PSR-0 standard. diff --git a/onelogin-saml-sso/php/extlib/xmlseclibs/LICENSE b/onelogin-saml-sso/php/extlib/xmlseclibs/LICENSE index 2a102b7..54b1e87 100644 --- a/onelogin-saml-sso/php/extlib/xmlseclibs/LICENSE +++ b/onelogin-saml-sso/php/extlib/xmlseclibs/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2007-2013, Robert Richards . +Copyright (c) 2007-2018, Robert Richards . All rights reserved. Redistribution and use in source and binary forms, with or without @@ -28,4 +28,4 @@ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. +POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/onelogin-saml-sso/php/extlib/xmlseclibs/README.md b/onelogin-saml-sso/php/extlib/xmlseclibs/README.md new file mode 100644 index 0000000..e8ebd02 --- /dev/null +++ b/onelogin-saml-sso/php/extlib/xmlseclibs/README.md @@ -0,0 +1,84 @@ +#xmlseclibs + +xmlseclibs is a library written in PHP for working with XML Encryption and Signatures. + +The author of xmlseclibs is Rob Richards. + +# Branches +Both the master and the 2.0 branches are actively maintained. +* master: Removes mcrypt usage requiring 5.4+ (5.6.24+ recommended for security reasons) +* 2.0: Contains namespace support requiring 5.3+ +* 1.4: Contains auto-loader support while also maintaining backwards compatiblity with the older 1.3 version using the xmlseclibs.php file. Supports PHP 5.2+ + +# Requirements + +xmlseclibs requires PHP version 5.4 or greater. **5.6.24+ recommended for security reasons** + + +## How to Install + +Install with [`composer.phar`](http://getcomposer.org). + +```sh +php composer.phar require "robrichards/xmlseclibs" +``` + + +## Use cases + +xmlseclibs is being used in many different software. + +* [SimpleSAMLPHP](https://github.com/simplesamlphp/simplesamlphp) +* [LightSAML](https://github.com/lightsaml/lightsaml) +* [OneLogin](https://github.com/onelogin/php-saml) + +## Basic usage + +The example below shows basic usage of xmlseclibs, with a SHA-256 signature. + +```php +use RobRichards\XMLSecLibs\XMLSecurityDSig; +use RobRichards\XMLSecLibs\XMLSecurityKey; + +// Load the XML to be signed +$doc = new DOMDocument(); +$doc->load('./path/to/file/tobesigned.xml'); + +// Create a new Security object +$objDSig = new XMLSecurityDSig(); +// Use the c14n exclusive canonicalization +$objDSig->setCanonicalMethod(XMLSecurityDSig::EXC_C14N); +// Sign using SHA-256 +$objDSig->addReference( + $doc, + XMLSecurityDSig::SHA256, + array('http://www.w3.org/2000/09/xmldsig#enveloped-signature') +); + +// Create a new (private) Security key +$objKey = new XMLSecurityKey(XMLSecurityKey::RSA_SHA256, array('type'=>'private')); +/* +If key has a passphrase, set it using +$objKey->passphrase = ''; +*/ +// Load the private key +$objKey->loadKey('./path/to/privatekey.pem', TRUE); + +// Sign the XML file +$objDSig->sign($objKey); + +// Add the associated public key to the signature +$objDSig->add509Cert(file_get_contents('./path/to/file/mycert.pem')); + +// Append the signature to the XML +$objDSig->appendSignature($doc->documentElement); +// Save the signed XML +$doc->save('./path/to/signed.xml'); +``` + +## How to Contribute + +* [Open Issues](https://github.com/robrichards/xmlseclibs/issues) +* [Open Pull Requests](https://github.com/robrichards/xmlseclibs/pulls) + +Mailing List: https://groups.google.com/forum/#!forum/xmlseclibs diff --git a/onelogin-saml-sso/php/extlib/xmlseclibs/src/Utils/XPath.php b/onelogin-saml-sso/php/extlib/xmlseclibs/src/Utils/XPath.php new file mode 100644 index 0000000..8cdc48e --- /dev/null +++ b/onelogin-saml-sso/php/extlib/xmlseclibs/src/Utils/XPath.php @@ -0,0 +1,44 @@ +. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * * Neither the name of Robert Richards nor the names of his + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * @author Robert Richards + * @copyright 2007-2018 Robert Richards + * @license http://www.opensource.org/licenses/bsd-license.php BSD License + */ + +class XMLSecEnc +{ + const template = " + + + +"; + + const Element = 'http://www.w3.org/2001/04/xmlenc#Element'; + const Content = 'http://www.w3.org/2001/04/xmlenc#Content'; + const URI = 3; + const XMLENCNS = 'http://www.w3.org/2001/04/xmlenc#'; + + /** @var null|DOMDocument */ + private $encdoc = null; + + /** @var null|DOMNode */ + private $rawNode = null; + + /** @var null|string */ + public $type = null; + + /** @var null|DOMElement */ + public $encKey = null; + + /** @var array */ + private $references = array(); + + public function __construct() + { + $this->_resetTemplate(); + } + + private function _resetTemplate() + { + $this->encdoc = new DOMDocument(); + $this->encdoc->loadXML(self::template); + } + + /** + * @param string $name + * @param DOMNode $node + * @param string $type + * @throws Exception + */ + public function addReference($name, $node, $type) + { + if (! $node instanceOf DOMNode) { + throw new Exception('$node is not of type DOMNode'); + } + $curencdoc = $this->encdoc; + $this->_resetTemplate(); + $encdoc = $this->encdoc; + $this->encdoc = $curencdoc; + $refuri = XMLSecurityDSig::generateGUID(); + $element = $encdoc->documentElement; + $element->setAttribute("Id", $refuri); + $this->references[$name] = array("node" => $node, "type" => $type, "encnode" => $encdoc, "refuri" => $refuri); + } + + /** + * @param DOMNode $node + */ + public function setNode($node) + { + $this->rawNode = $node; + } + + /** + * Encrypt the selected node with the given key. + * + * @param XMLSecurityKey $objKey The encryption key and algorithm. + * @param bool $replace Whether the encrypted node should be replaced in the original tree. Default is true. + * @throws Exception + * + * @return DOMElement The -element. + */ + public function encryptNode($objKey, $replace = true) + { + $data = ''; + if (empty($this->rawNode)) { + throw new Exception('Node to encrypt has not been set'); + } + if (! $objKey instanceof XMLSecurityKey) { + throw new Exception('Invalid Key'); + } + $doc = $this->rawNode->ownerDocument; + $xPath = new DOMXPath($this->encdoc); + $objList = $xPath->query('/xenc:EncryptedData/xenc:CipherData/xenc:CipherValue'); + $cipherValue = $objList->item(0); + if ($cipherValue == null) { + throw new Exception('Error locating CipherValue element within template'); + } + switch ($this->type) { + case (self::Element): + $data = $doc->saveXML($this->rawNode); + $this->encdoc->documentElement->setAttribute('Type', self::Element); + break; + case (self::Content): + $children = $this->rawNode->childNodes; + foreach ($children AS $child) { + $data .= $doc->saveXML($child); + } + $this->encdoc->documentElement->setAttribute('Type', self::Content); + break; + default: + throw new Exception('Type is currently not supported'); + } + + $encMethod = $this->encdoc->documentElement->appendChild($this->encdoc->createElementNS(self::XMLENCNS, 'xenc:EncryptionMethod')); + $encMethod->setAttribute('Algorithm', $objKey->getAlgorithm()); + $cipherValue->parentNode->parentNode->insertBefore($encMethod, $cipherValue->parentNode->parentNode->firstChild); + + $strEncrypt = base64_encode($objKey->encryptData($data)); + $value = $this->encdoc->createTextNode($strEncrypt); + $cipherValue->appendChild($value); + + if ($replace) { + switch ($this->type) { + case (self::Element): + if ($this->rawNode->nodeType == XML_DOCUMENT_NODE) { + return $this->encdoc; + } + $importEnc = $this->rawNode->ownerDocument->importNode($this->encdoc->documentElement, true); + $this->rawNode->parentNode->replaceChild($importEnc, $this->rawNode); + return $importEnc; + case (self::Content): + $importEnc = $this->rawNode->ownerDocument->importNode($this->encdoc->documentElement, true); + while ($this->rawNode->firstChild) { + $this->rawNode->removeChild($this->rawNode->firstChild); + } + $this->rawNode->appendChild($importEnc); + return $importEnc; + } + } else { + return $this->encdoc->documentElement; + } + } + + /** + * @param XMLSecurityKey $objKey + * @throws Exception + */ + public function encryptReferences($objKey) + { + $curRawNode = $this->rawNode; + $curType = $this->type; + foreach ($this->references AS $name => $reference) { + $this->encdoc = $reference["encnode"]; + $this->rawNode = $reference["node"]; + $this->type = $reference["type"]; + try { + $encNode = $this->encryptNode($objKey); + $this->references[$name]["encnode"] = $encNode; + } catch (Exception $e) { + $this->rawNode = $curRawNode; + $this->type = $curType; + throw $e; + } + } + $this->rawNode = $curRawNode; + $this->type = $curType; + } + + /** + * Retrieve the CipherValue text from this encrypted node. + * + * @throws Exception + * @return string|null The Ciphervalue text, or null if no CipherValue is found. + */ + public function getCipherValue() + { + if (empty($this->rawNode)) { + throw new Exception('Node to decrypt has not been set'); + } + + $doc = $this->rawNode->ownerDocument; + $xPath = new DOMXPath($doc); + $xPath->registerNamespace('xmlencr', self::XMLENCNS); + /* Only handles embedded content right now and not a reference */ + $query = "./xmlencr:CipherData/xmlencr:CipherValue"; + $nodeset = $xPath->query($query, $this->rawNode); + $node = $nodeset->item(0); + + if (!$node) { + return null; + } + + return base64_decode($node->nodeValue); + } + + /** + * Decrypt this encrypted node. + * + * The behaviour of this function depends on the value of $replace. + * If $replace is false, we will return the decrypted data as a string. + * If $replace is true, we will insert the decrypted element(s) into the + * document, and return the decrypted element(s). + * + * @param XMLSecurityKey $objKey The decryption key that should be used when decrypting the node. + * @param boolean $replace Whether we should replace the encrypted node in the XML document with the decrypted data. The default is true. + * + * @return string|DOMElement The decrypted data. + */ + public function decryptNode($objKey, $replace=true) + { + if (! $objKey instanceof XMLSecurityKey) { + throw new Exception('Invalid Key'); + } + + $encryptedData = $this->getCipherValue(); + if ($encryptedData) { + $decrypted = $objKey->decryptData($encryptedData); + if ($replace) { + switch ($this->type) { + case (self::Element): + $newdoc = new DOMDocument(); + $newdoc->loadXML($decrypted); + if ($this->rawNode->nodeType == XML_DOCUMENT_NODE) { + return $newdoc; + } + $importEnc = $this->rawNode->ownerDocument->importNode($newdoc->documentElement, true); + $this->rawNode->parentNode->replaceChild($importEnc, $this->rawNode); + return $importEnc; + case (self::Content): + if ($this->rawNode->nodeType == XML_DOCUMENT_NODE) { + $doc = $this->rawNode; + } else { + $doc = $this->rawNode->ownerDocument; + } + $newFrag = $doc->createDocumentFragment(); + $newFrag->appendXML($decrypted); + $parent = $this->rawNode->parentNode; + $parent->replaceChild($newFrag, $this->rawNode); + return $parent; + default: + return $decrypted; + } + } else { + return $decrypted; + } + } else { + throw new Exception("Cannot locate encrypted data"); + } + } + + /** + * Encrypt the XMLSecurityKey + * + * @param XMLSecurityKey $srcKey + * @param XMLSecurityKey $rawKey + * @param bool $append + * @throws Exception + */ + public function encryptKey($srcKey, $rawKey, $append=true) + { + if ((! $srcKey instanceof XMLSecurityKey) || (! $rawKey instanceof XMLSecurityKey)) { + throw new Exception('Invalid Key'); + } + $strEncKey = base64_encode($srcKey->encryptData($rawKey->key)); + $root = $this->encdoc->documentElement; + $encKey = $this->encdoc->createElementNS(self::XMLENCNS, 'xenc:EncryptedKey'); + if ($append) { + $keyInfo = $root->insertBefore($this->encdoc->createElementNS('http://www.w3.org/2000/09/xmldsig#', 'dsig:KeyInfo'), $root->firstChild); + $keyInfo->appendChild($encKey); + } else { + $this->encKey = $encKey; + } + $encMethod = $encKey->appendChild($this->encdoc->createElementNS(self::XMLENCNS, 'xenc:EncryptionMethod')); + $encMethod->setAttribute('Algorithm', $srcKey->getAlgorith()); + if (! empty($srcKey->name)) { + $keyInfo = $encKey->appendChild($this->encdoc->createElementNS('http://www.w3.org/2000/09/xmldsig#', 'dsig:KeyInfo')); + $keyInfo->appendChild($this->encdoc->createElementNS('http://www.w3.org/2000/09/xmldsig#', 'dsig:KeyName', $srcKey->name)); + } + $cipherData = $encKey->appendChild($this->encdoc->createElementNS(self::XMLENCNS, 'xenc:CipherData')); + $cipherData->appendChild($this->encdoc->createElementNS(self::XMLENCNS, 'xenc:CipherValue', $strEncKey)); + if (is_array($this->references) && count($this->references) > 0) { + $refList = $encKey->appendChild($this->encdoc->createElementNS(self::XMLENCNS, 'xenc:ReferenceList')); + foreach ($this->references AS $name => $reference) { + $refuri = $reference["refuri"]; + $dataRef = $refList->appendChild($this->encdoc->createElementNS(self::XMLENCNS, 'xenc:DataReference')); + $dataRef->setAttribute("URI", '#' . $refuri); + } + } + return; + } + + /** + * @param XMLSecurityKey $encKey + * @return DOMElement|string + * @throws Exception + */ + public function decryptKey($encKey) + { + if (! $encKey->isEncrypted) { + throw new Exception("Key is not Encrypted"); + } + if (empty($encKey->key)) { + throw new Exception("Key is missing data to perform the decryption"); + } + return $this->decryptNode($encKey, false); + } + + /** + * @param DOMDocument $element + * @return DOMNode|null + */ + public function locateEncryptedData($element) + { + if ($element instanceof DOMDocument) { + $doc = $element; + } else { + $doc = $element->ownerDocument; + } + if ($doc) { + $xpath = new DOMXPath($doc); + $query = "//*[local-name()='EncryptedData' and namespace-uri()='".self::XMLENCNS."']"; + $nodeset = $xpath->query($query); + return $nodeset->item(0); + } + return null; + } + + /** + * Returns the key from the DOM + * @param null|DOMNode $node + * @return null|XMLSecurityKey + */ + public function locateKey($node=null) + { + if (empty($node)) { + $node = $this->rawNode; + } + if (! $node instanceof DOMNode) { + return null; + } + if ($doc = $node->ownerDocument) { + $xpath = new DOMXPath($doc); + $xpath->registerNamespace('xmlsecenc', self::XMLENCNS); + $query = ".//xmlsecenc:EncryptionMethod"; + $nodeset = $xpath->query($query, $node); + if ($encmeth = $nodeset->item(0)) { + $attrAlgorithm = $encmeth->getAttribute("Algorithm"); + try { + $objKey = new XMLSecurityKey($attrAlgorithm, array('type' => 'private')); + } catch (Exception $e) { + return null; + } + return $objKey; + } + } + return null; + } + + /** + * @param null|XMLSecurityKey $objBaseKey + * @param null|DOMNode $node + * @return null|XMLSecurityKey + * @throws Exception + */ + public static function staticLocateKeyInfo($objBaseKey=null, $node=null) + { + if (empty($node) || (! $node instanceof DOMNode)) { + return null; + } + $doc = $node->ownerDocument; + if (!$doc) { + return null; + } + + $xpath = new DOMXPath($doc); + $xpath->registerNamespace('xmlsecenc', self::XMLENCNS); + $xpath->registerNamespace('xmlsecdsig', XMLSecurityDSig::XMLDSIGNS); + $query = "./xmlsecdsig:KeyInfo"; + $nodeset = $xpath->query($query, $node); + $encmeth = $nodeset->item(0); + if (!$encmeth) { + /* No KeyInfo in EncryptedData / EncryptedKey. */ + return $objBaseKey; + } + + foreach ($encmeth->childNodes AS $child) { + switch ($child->localName) { + case 'KeyName': + if (! empty($objBaseKey)) { + $objBaseKey->name = $child->nodeValue; + } + break; + case 'KeyValue': + foreach ($child->childNodes AS $keyval) { + switch ($keyval->localName) { + case 'DSAKeyValue': + throw new Exception("DSAKeyValue currently not supported"); + case 'RSAKeyValue': + $modulus = null; + $exponent = null; + if ($modulusNode = $keyval->getElementsByTagName('Modulus')->item(0)) { + $modulus = base64_decode($modulusNode->nodeValue); + } + if ($exponentNode = $keyval->getElementsByTagName('Exponent')->item(0)) { + $exponent = base64_decode($exponentNode->nodeValue); + } + if (empty($modulus) || empty($exponent)) { + throw new Exception("Missing Modulus or Exponent"); + } + $publicKey = XMLSecurityKey::convertRSA($modulus, $exponent); + $objBaseKey->loadKey($publicKey); + break; + } + } + break; + case 'RetrievalMethod': + $type = $child->getAttribute('Type'); + if ($type !== 'http://www.w3.org/2001/04/xmlenc#EncryptedKey') { + /* Unsupported key type. */ + break; + } + $uri = $child->getAttribute('URI'); + if ($uri[0] !== '#') { + /* URI not a reference - unsupported. */ + break; + } + $id = substr($uri, 1); + + $query = '//xmlsecenc:EncryptedKey[@Id="'.XPath::filterAttrValue($id, XPath::DOUBLE_QUOTE).'"]'; + $keyElement = $xpath->query($query)->item(0); + if (!$keyElement) { + throw new Exception("Unable to locate EncryptedKey with @Id='$id'."); + } + + return XMLSecurityKey::fromEncryptedKeyElement($keyElement); + case 'EncryptedKey': + return XMLSecurityKey::fromEncryptedKeyElement($child); + case 'X509Data': + if ($x509certNodes = $child->getElementsByTagName('X509Certificate')) { + if ($x509certNodes->length > 0) { + $x509cert = $x509certNodes->item(0)->textContent; + $x509cert = str_replace(array("\r", "\n", " "), "", $x509cert); + $x509cert = "-----BEGIN CERTIFICATE-----\n".chunk_split($x509cert, 64, "\n")."-----END CERTIFICATE-----\n"; + $objBaseKey->loadKey($x509cert, false, true); + } + } + break; + } + } + return $objBaseKey; + } + + /** + * @param null|XMLSecurityKey $objBaseKey + * @param null|DOMNode $node + * @return null|XMLSecurityKey + */ + public function locateKeyInfo($objBaseKey=null, $node=null) + { + if (empty($node)) { + $node = $this->rawNode; + } + return self::staticLocateKeyInfo($objBaseKey, $node); + } +} diff --git a/onelogin-saml-sso/php/extlib/xmlseclibs/src/XMLSecurityDSig.php b/onelogin-saml-sso/php/extlib/xmlseclibs/src/XMLSecurityDSig.php new file mode 100644 index 0000000..9ae4a96 --- /dev/null +++ b/onelogin-saml-sso/php/extlib/xmlseclibs/src/XMLSecurityDSig.php @@ -0,0 +1,1142 @@ +. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * * Neither the name of Robert Richards nor the names of his + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * @author Robert Richards + * @copyright 2007-2018 Robert Richards + * @license http://www.opensource.org/licenses/bsd-license.php BSD License + */ + +class XMLSecurityDSig +{ + const XMLDSIGNS = 'http://www.w3.org/2000/09/xmldsig#'; + const SHA1 = 'http://www.w3.org/2000/09/xmldsig#sha1'; + const SHA256 = 'http://www.w3.org/2001/04/xmlenc#sha256'; + const SHA384 = 'http://www.w3.org/2001/04/xmldsig-more#sha384'; + const SHA512 = 'http://www.w3.org/2001/04/xmlenc#sha512'; + const RIPEMD160 = 'http://www.w3.org/2001/04/xmlenc#ripemd160'; + + const C14N = 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315'; + const C14N_COMMENTS = 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments'; + const EXC_C14N = 'http://www.w3.org/2001/10/xml-exc-c14n#'; + const EXC_C14N_COMMENTS = 'http://www.w3.org/2001/10/xml-exc-c14n#WithComments'; + + const template = ' + + + +'; + + const BASE_TEMPLATE = ' + + + +'; + + /** @var DOMElement|null */ + public $sigNode = null; + + /** @var array */ + public $idKeys = array(); + + /** @var array */ + public $idNS = array(); + + /** @var string|null */ + private $signedInfo = null; + + /** @var DomXPath|null */ + private $xPathCtx = null; + + /** @var string|null */ + private $canonicalMethod = null; + + /** @var string */ + private $prefix = ''; + + /** @var string */ + private $searchpfx = 'secdsig'; + + /** + * This variable contains an associative array of validated nodes. + * @var array|null + */ + private $validatedNodes = null; + + /** + * @param string $prefix + */ + public function __construct($prefix='ds') + { + $template = self::BASE_TEMPLATE; + if (! empty($prefix)) { + $this->prefix = $prefix.':'; + $search = array("ownerDocument; + } + if ($doc) { + $xpath = new DOMXPath($doc); + $xpath->registerNamespace('secdsig', self::XMLDSIGNS); + $query = ".//secdsig:Signature"; + $nodeset = $xpath->query($query, $objDoc); + $this->sigNode = $nodeset->item($pos); + return $this->sigNode; + } + return null; + } + + /** + * @param string $name + * @param null|string $value + * @return DOMElement + */ + public function createNewSignNode($name, $value=null) + { + $doc = $this->sigNode->ownerDocument; + if (! is_null($value)) { + $node = $doc->createElementNS(self::XMLDSIGNS, $this->prefix.$name, $value); + } else { + $node = $doc->createElementNS(self::XMLDSIGNS, $this->prefix.$name); + } + return $node; + } + + /** + * @param string $method + * @throws Exception + */ + public function setCanonicalMethod($method) + { + switch ($method) { + case 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315': + case 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments': + case 'http://www.w3.org/2001/10/xml-exc-c14n#': + case 'http://www.w3.org/2001/10/xml-exc-c14n#WithComments': + $this->canonicalMethod = $method; + break; + default: + throw new Exception('Invalid Canonical Method'); + } + if ($xpath = $this->getXPathObj()) { + $query = './'.$this->searchpfx.':SignedInfo'; + $nodeset = $xpath->query($query, $this->sigNode); + if ($sinfo = $nodeset->item(0)) { + $query = './'.$this->searchpfx.'CanonicalizationMethod'; + $nodeset = $xpath->query($query, $sinfo); + if (! ($canonNode = $nodeset->item(0))) { + $canonNode = $this->createNewSignNode('CanonicalizationMethod'); + $sinfo->insertBefore($canonNode, $sinfo->firstChild); + } + $canonNode->setAttribute('Algorithm', $this->canonicalMethod); + } + } + } + + /** + * @param DOMNode $node + * @param string $canonicalmethod + * @param null|array $arXPath + * @param null|array $prefixList + * @return string + */ + private function canonicalizeData($node, $canonicalmethod, $arXPath=null, $prefixList=null) + { + $exclusive = false; + $withComments = false; + switch ($canonicalmethod) { + case 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315': + $exclusive = false; + $withComments = false; + break; + case 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments': + $withComments = true; + break; + case 'http://www.w3.org/2001/10/xml-exc-c14n#': + $exclusive = true; + break; + case 'http://www.w3.org/2001/10/xml-exc-c14n#WithComments': + $exclusive = true; + $withComments = true; + break; + } + + if (is_null($arXPath) && ($node instanceof DOMNode) && ($node->ownerDocument !== null) && $node->isSameNode($node->ownerDocument->documentElement)) { + /* Check for any PI or comments as they would have been excluded */ + $element = $node; + while ($refnode = $element->previousSibling) { + if ($refnode->nodeType == XML_PI_NODE || (($refnode->nodeType == XML_COMMENT_NODE) && $withComments)) { + break; + } + $element = $refnode; + } + if ($refnode == null) { + $node = $node->ownerDocument; + } + } + + return $node->C14N($exclusive, $withComments, $arXPath, $prefixList); + } + + /** + * @return null|string + */ + public function canonicalizeSignedInfo() + { + + $doc = $this->sigNode->ownerDocument; + $canonicalmethod = null; + if ($doc) { + $xpath = $this->getXPathObj(); + $query = "./secdsig:SignedInfo"; + $nodeset = $xpath->query($query, $this->sigNode); + if ($signInfoNode = $nodeset->item(0)) { + $query = "./secdsig:CanonicalizationMethod"; + $nodeset = $xpath->query($query, $signInfoNode); + if ($canonNode = $nodeset->item(0)) { + $canonicalmethod = $canonNode->getAttribute('Algorithm'); + } + $this->signedInfo = $this->canonicalizeData($signInfoNode, $canonicalmethod); + return $this->signedInfo; + } + } + return null; + } + + /** + * @param string $digestAlgorithm + * @param string $data + * @param bool $encode + * @return string + * @throws Exception + */ + public function calculateDigest($digestAlgorithm, $data, $encode = true) + { + switch ($digestAlgorithm) { + case self::SHA1: + $alg = 'sha1'; + break; + case self::SHA256: + $alg = 'sha256'; + break; + case self::SHA384: + $alg = 'sha384'; + break; + case self::SHA512: + $alg = 'sha512'; + break; + case self::RIPEMD160: + $alg = 'ripemd160'; + break; + default: + throw new Exception("Cannot validate digest: Unsupported Algorithm <$digestAlgorithm>"); + } + + $digest = hash($alg, $data, true); + if ($encode) { + $digest = base64_encode($digest); + } + return $digest; + + } + + /** + * @param $refNode + * @param string $data + * @return bool + */ + public function validateDigest($refNode, $data) + { + $xpath = new DOMXPath($refNode->ownerDocument); + $xpath->registerNamespace('secdsig', self::XMLDSIGNS); + $query = 'string(./secdsig:DigestMethod/@Algorithm)'; + $digestAlgorithm = $xpath->evaluate($query, $refNode); + $digValue = $this->calculateDigest($digestAlgorithm, $data, false); + $query = 'string(./secdsig:DigestValue)'; + $digestValue = $xpath->evaluate($query, $refNode); + return ($digValue === base64_decode($digestValue)); + } + + /** + * @param $refNode + * @param DOMNode $objData + * @param bool $includeCommentNodes + * @return string + */ + public function processTransforms($refNode, $objData, $includeCommentNodes = true) + { + $data = $objData; + $xpath = new DOMXPath($refNode->ownerDocument); + $xpath->registerNamespace('secdsig', self::XMLDSIGNS); + $query = './secdsig:Transforms/secdsig:Transform'; + $nodelist = $xpath->query($query, $refNode); + $canonicalMethod = 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315'; + $arXPath = null; + $prefixList = null; + foreach ($nodelist AS $transform) { + $algorithm = $transform->getAttribute("Algorithm"); + switch ($algorithm) { + case 'http://www.w3.org/2001/10/xml-exc-c14n#': + case 'http://www.w3.org/2001/10/xml-exc-c14n#WithComments': + + if (!$includeCommentNodes) { + /* We remove comment nodes by forcing it to use a canonicalization + * without comments. + */ + $canonicalMethod = 'http://www.w3.org/2001/10/xml-exc-c14n#'; + } else { + $canonicalMethod = $algorithm; + } + + $node = $transform->firstChild; + while ($node) { + if ($node->localName == 'InclusiveNamespaces') { + if ($pfx = $node->getAttribute('PrefixList')) { + $arpfx = array(); + $pfxlist = explode(" ", $pfx); + foreach ($pfxlist AS $pfx) { + $val = trim($pfx); + if (! empty($val)) { + $arpfx[] = $val; + } + } + if (count($arpfx) > 0) { + $prefixList = $arpfx; + } + } + break; + } + $node = $node->nextSibling; + } + break; + case 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315': + case 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments': + if (!$includeCommentNodes) { + /* We remove comment nodes by forcing it to use a canonicalization + * without comments. + */ + $canonicalMethod = 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315'; + } else { + $canonicalMethod = $algorithm; + } + + break; + case 'http://www.w3.org/TR/1999/REC-xpath-19991116': + $node = $transform->firstChild; + while ($node) { + if ($node->localName == 'XPath') { + $arXPath = array(); + $arXPath['query'] = '(.//. | .//@* | .//namespace::*)['.$node->nodeValue.']'; + $arXpath['namespaces'] = array(); + $nslist = $xpath->query('./namespace::*', $node); + foreach ($nslist AS $nsnode) { + if ($nsnode->localName != "xml") { + $arXPath['namespaces'][$nsnode->localName] = $nsnode->nodeValue; + } + } + break; + } + $node = $node->nextSibling; + } + break; + } + } + if ($data instanceof DOMNode) { + $data = $this->canonicalizeData($objData, $canonicalMethod, $arXPath, $prefixList); + } + return $data; + } + + /** + * @param DOMNode $refNode + * @return bool + */ + public function processRefNode($refNode) + { + $dataObject = null; + + /* + * Depending on the URI, we may not want to include comments in the result + * See: http://www.w3.org/TR/xmldsig-core/#sec-ReferenceProcessingModel + */ + $includeCommentNodes = true; + + if ($uri = $refNode->getAttribute("URI")) { + $arUrl = parse_url($uri); + if (empty($arUrl['path'])) { + if ($identifier = $arUrl['fragment']) { + + /* This reference identifies a node with the given id by using + * a URI on the form "#identifier". This should not include comments. + */ + $includeCommentNodes = false; + + $xPath = new DOMXPath($refNode->ownerDocument); + if ($this->idNS && is_array($this->idNS)) { + foreach ($this->idNS as $nspf => $ns) { + $xPath->registerNamespace($nspf, $ns); + } + } + $iDlist = '@Id="'.XPath::filterAttrValue($identifier, XPath::DOUBLE_QUOTE).'"'; + if (is_array($this->idKeys)) { + foreach ($this->idKeys as $idKey) { + $iDlist .= " or @".XPath::filterAttrName($idKey).'="'. + XPath::filterAttrValue($identifier, XPath::DOUBLE_QUOTE).'"'; + } + } + $query = '//*['.$iDlist.']'; + $dataObject = $xPath->query($query)->item(0); + } else { + $dataObject = $refNode->ownerDocument; + } + } + } else { + /* This reference identifies the root node with an empty URI. This should + * not include comments. + */ + $includeCommentNodes = false; + + $dataObject = $refNode->ownerDocument; + } + $data = $this->processTransforms($refNode, $dataObject, $includeCommentNodes); + if (!$this->validateDigest($refNode, $data)) { + return false; + } + + if ($dataObject instanceof DOMNode) { + /* Add this node to the list of validated nodes. */ + if (! empty($identifier)) { + $this->validatedNodes[$identifier] = $dataObject; + } else { + $this->validatedNodes[] = $dataObject; + } + } + + return true; + } + + /** + * @param DOMNode $refNode + * @return null + */ + public function getRefNodeID($refNode) + { + if ($uri = $refNode->getAttribute("URI")) { + $arUrl = parse_url($uri); + if (empty($arUrl['path'])) { + if ($identifier = $arUrl['fragment']) { + return $identifier; + } + } + } + return null; + } + + /** + * @return array + * @throws Exception + */ + public function getRefIDs() + { + $refids = array(); + + $xpath = $this->getXPathObj(); + $query = "./secdsig:SignedInfo/secdsig:Reference"; + $nodeset = $xpath->query($query, $this->sigNode); + if ($nodeset->length == 0) { + throw new Exception("Reference nodes not found"); + } + foreach ($nodeset AS $refNode) { + $refids[] = $this->getRefNodeID($refNode); + } + return $refids; + } + + /** + * @return bool + * @throws Exception + */ + public function validateReference() + { + $docElem = $this->sigNode->ownerDocument->documentElement; + if (! $docElem->isSameNode($this->sigNode)) { + if ($this->sigNode->parentNode != null) { + $this->sigNode->parentNode->removeChild($this->sigNode); + } + } + $xpath = $this->getXPathObj(); + $query = "./secdsig:SignedInfo/secdsig:Reference"; + $nodeset = $xpath->query($query, $this->sigNode); + if ($nodeset->length == 0) { + throw new Exception("Reference nodes not found"); + } + + /* Initialize/reset the list of validated nodes. */ + $this->validatedNodes = array(); + + foreach ($nodeset AS $refNode) { + if (! $this->processRefNode($refNode)) { + /* Clear the list of validated nodes. */ + $this->validatedNodes = null; + throw new Exception("Reference validation failed"); + } + } + return true; + } + + /** + * @param DOMNode $sinfoNode + * @param DOMDocument $node + * @param string $algorithm + * @param null|array $arTransforms + * @param null|array $options + */ + private function addRefInternal($sinfoNode, $node, $algorithm, $arTransforms=null, $options=null) + { + $prefix = null; + $prefix_ns = null; + $id_name = 'Id'; + $overwrite_id = true; + $force_uri = false; + + if (is_array($options)) { + $prefix = empty($options['prefix']) ? null : $options['prefix']; + $prefix_ns = empty($options['prefix_ns']) ? null : $options['prefix_ns']; + $id_name = empty($options['id_name']) ? 'Id' : $options['id_name']; + $overwrite_id = !isset($options['overwrite']) ? true : (bool) $options['overwrite']; + $force_uri = !isset($options['force_uri']) ? false : (bool) $options['force_uri']; + } + + $attname = $id_name; + if (! empty($prefix)) { + $attname = $prefix.':'.$attname; + } + + $refNode = $this->createNewSignNode('Reference'); + $sinfoNode->appendChild($refNode); + + if (! $node instanceof DOMDocument) { + $uri = null; + if (! $overwrite_id) { + $uri = $prefix_ns ? $node->getAttributeNS($prefix_ns, $id_name) : $node->getAttribute($id_name); + } + if (empty($uri)) { + $uri = self::generateGUID(); + $node->setAttributeNS($prefix_ns, $attname, $uri); + } + $refNode->setAttribute("URI", '#'.$uri); + } elseif ($force_uri) { + $refNode->setAttribute("URI", ''); + } + + $transNodes = $this->createNewSignNode('Transforms'); + $refNode->appendChild($transNodes); + + if (is_array($arTransforms)) { + foreach ($arTransforms AS $transform) { + $transNode = $this->createNewSignNode('Transform'); + $transNodes->appendChild($transNode); + if (is_array($transform) && + (! empty($transform['http://www.w3.org/TR/1999/REC-xpath-19991116'])) && + (! empty($transform['http://www.w3.org/TR/1999/REC-xpath-19991116']['query']))) { + $transNode->setAttribute('Algorithm', 'http://www.w3.org/TR/1999/REC-xpath-19991116'); + $XPathNode = $this->createNewSignNode('XPath', $transform['http://www.w3.org/TR/1999/REC-xpath-19991116']['query']); + $transNode->appendChild($XPathNode); + if (! empty($transform['http://www.w3.org/TR/1999/REC-xpath-19991116']['namespaces'])) { + foreach ($transform['http://www.w3.org/TR/1999/REC-xpath-19991116']['namespaces'] AS $prefix => $namespace) { + $XPathNode->setAttributeNS("http://www.w3.org/2000/xmlns/", "xmlns:$prefix", $namespace); + } + } + } else { + $transNode->setAttribute('Algorithm', $transform); + } + } + } elseif (! empty($this->canonicalMethod)) { + $transNode = $this->createNewSignNode('Transform'); + $transNodes->appendChild($transNode); + $transNode->setAttribute('Algorithm', $this->canonicalMethod); + } + + $canonicalData = $this->processTransforms($refNode, $node); + $digValue = $this->calculateDigest($algorithm, $canonicalData); + + $digestMethod = $this->createNewSignNode('DigestMethod'); + $refNode->appendChild($digestMethod); + $digestMethod->setAttribute('Algorithm', $algorithm); + + $digestValue = $this->createNewSignNode('DigestValue', $digValue); + $refNode->appendChild($digestValue); + } + + /** + * @param DOMDocument $node + * @param string $algorithm + * @param null|array $arTransforms + * @param null|array $options + */ + public function addReference($node, $algorithm, $arTransforms=null, $options=null) + { + if ($xpath = $this->getXPathObj()) { + $query = "./secdsig:SignedInfo"; + $nodeset = $xpath->query($query, $this->sigNode); + if ($sInfo = $nodeset->item(0)) { + $this->addRefInternal($sInfo, $node, $algorithm, $arTransforms, $options); + } + } + } + + /** + * @param array $arNodes + * @param string $algorithm + * @param null|array $arTransforms + * @param null|array $options + */ + public function addReferenceList($arNodes, $algorithm, $arTransforms=null, $options=null) + { + if ($xpath = $this->getXPathObj()) { + $query = "./secdsig:SignedInfo"; + $nodeset = $xpath->query($query, $this->sigNode); + if ($sInfo = $nodeset->item(0)) { + foreach ($arNodes AS $node) { + $this->addRefInternal($sInfo, $node, $algorithm, $arTransforms, $options); + } + } + } + } + + /** + * @param DOMElement|string $data + * @param null|string $mimetype + * @param null|string $encoding + * @return DOMElement + */ + public function addObject($data, $mimetype=null, $encoding=null) + { + $objNode = $this->createNewSignNode('Object'); + $this->sigNode->appendChild($objNode); + if (! empty($mimetype)) { + $objNode->setAttribute('MimeType', $mimetype); + } + if (! empty($encoding)) { + $objNode->setAttribute('Encoding', $encoding); + } + + if ($data instanceof DOMElement) { + $newData = $this->sigNode->ownerDocument->importNode($data, true); + } else { + $newData = $this->sigNode->ownerDocument->createTextNode($data); + } + $objNode->appendChild($newData); + + return $objNode; + } + + /** + * @param null|DOMNode $node + * @return null|XMLSecurityKey + */ + public function locateKey($node=null) + { + if (empty($node)) { + $node = $this->sigNode; + } + if (! $node instanceof DOMNode) { + return null; + } + if ($doc = $node->ownerDocument) { + $xpath = new DOMXPath($doc); + $xpath->registerNamespace('secdsig', self::XMLDSIGNS); + $query = "string(./secdsig:SignedInfo/secdsig:SignatureMethod/@Algorithm)"; + $algorithm = $xpath->evaluate($query, $node); + if ($algorithm) { + try { + $objKey = new XMLSecurityKey($algorithm, array('type' => 'public')); + } catch (Exception $e) { + return null; + } + return $objKey; + } + } + return null; + } + + /** + * Returns: + * Bool when verifying HMAC_SHA1; + * Int otherwise, with following meanings: + * 1 on succesful signature verification, + * 0 when signature verification failed, + * -1 if an error occurred during processing. + * + * NOTE: be very careful when checking the int return value, because in + * PHP, -1 will be cast to True when in boolean context. Always check the + * return value in a strictly typed way, e.g. "$obj->verify(...) === 1". + * + * @param XMLSecurityKey $objKey + * @return bool|int + * @throws Exception + */ + public function verify($objKey) + { + $doc = $this->sigNode->ownerDocument; + $xpath = new DOMXPath($doc); + $xpath->registerNamespace('secdsig', self::XMLDSIGNS); + $query = "string(./secdsig:SignatureValue)"; + $sigValue = $xpath->evaluate($query, $this->sigNode); + if (empty($sigValue)) { + throw new Exception("Unable to locate SignatureValue"); + } + return $objKey->verifySignature($this->signedInfo, base64_decode($sigValue)); + } + + /** + * @param XMLSecurityKey $objKey + * @param string $data + * @return mixed|string + */ + public function signData($objKey, $data) + { + return $objKey->signData($data); + } + + /** + * @param XMLSecurityKey $objKey + * @param null|DOMNode $appendToNode + */ + public function sign($objKey, $appendToNode = null) + { + // If we have a parent node append it now so C14N properly works + if ($appendToNode != null) { + $this->resetXPathObj(); + $this->appendSignature($appendToNode); + $this->sigNode = $appendToNode->lastChild; + } + if ($xpath = $this->getXPathObj()) { + $query = "./secdsig:SignedInfo"; + $nodeset = $xpath->query($query, $this->sigNode); + if ($sInfo = $nodeset->item(0)) { + $query = "./secdsig:SignatureMethod"; + $nodeset = $xpath->query($query, $sInfo); + $sMethod = $nodeset->item(0); + $sMethod->setAttribute('Algorithm', $objKey->type); + $data = $this->canonicalizeData($sInfo, $this->canonicalMethod); + $sigValue = base64_encode($this->signData($objKey, $data)); + $sigValueNode = $this->createNewSignNode('SignatureValue', $sigValue); + if ($infoSibling = $sInfo->nextSibling) { + $infoSibling->parentNode->insertBefore($sigValueNode, $infoSibling); + } else { + $this->sigNode->appendChild($sigValueNode); + } + } + } + } + + public function appendCert() + { + + } + + /** + * @param XMLSecurityKey $objKey + * @param null|DOMNode $parent + */ + public function appendKey($objKey, $parent=null) + { + $objKey->serializeKey($parent); + } + + + /** + * This function inserts the signature element. + * + * The signature element will be appended to the element, unless $beforeNode is specified. If $beforeNode + * is specified, the signature element will be inserted as the last element before $beforeNode. + * + * @param DOMNode $node The node the signature element should be inserted into. + * @param DOMNode $beforeNode The node the signature element should be located before. + * + * @return DOMNode The signature element node + */ + public function insertSignature($node, $beforeNode = null) + { + + $document = $node->ownerDocument; + $signatureElement = $document->importNode($this->sigNode, true); + + if ($beforeNode == null) { + return $node->insertBefore($signatureElement); + } else { + return $node->insertBefore($signatureElement, $beforeNode); + } + } + + /** + * @param DOMNode $parentNode + * @param bool $insertBefore + * @return DOMNode + */ + public function appendSignature($parentNode, $insertBefore = false) + { + $beforeNode = $insertBefore ? $parentNode->firstChild : null; + return $this->insertSignature($parentNode, $beforeNode); + } + + /** + * @param string $cert + * @param bool $isPEMFormat + * @return string + */ + public static function get509XCert($cert, $isPEMFormat=true) + { + $certs = self::staticGet509XCerts($cert, $isPEMFormat); + if (! empty($certs)) { + return $certs[0]; + } + return ''; + } + + /** + * @param string $certs + * @param bool $isPEMFormat + * @return array + */ + public static function staticGet509XCerts($certs, $isPEMFormat=true) + { + if ($isPEMFormat) { + $data = ''; + $certlist = array(); + $arCert = explode("\n", $certs); + $inData = false; + foreach ($arCert AS $curData) { + if (! $inData) { + if (strncmp($curData, '-----BEGIN CERTIFICATE', 22) == 0) { + $inData = true; + } + } else { + if (strncmp($curData, '-----END CERTIFICATE', 20) == 0) { + $inData = false; + $certlist[] = $data; + $data = ''; + continue; + } + $data .= trim($curData); + } + } + return $certlist; + } else { + return array($certs); + } + } + + /** + * @param DOMElement $parentRef + * @param string $cert + * @param bool $isPEMFormat + * @param bool $isURL + * @param null|DOMXPath $xpath + * @param null|array $options + * @throws Exception + */ + public static function staticAdd509Cert($parentRef, $cert, $isPEMFormat=true, $isURL=false, $xpath=null, $options=null) + { + if ($isURL) { + $cert = file_get_contents($cert); + } + if (! $parentRef instanceof DOMElement) { + throw new Exception('Invalid parent Node parameter'); + } + $baseDoc = $parentRef->ownerDocument; + + if (empty($xpath)) { + $xpath = new DOMXPath($parentRef->ownerDocument); + $xpath->registerNamespace('secdsig', self::XMLDSIGNS); + } + + $query = "./secdsig:KeyInfo"; + $nodeset = $xpath->query($query, $parentRef); + $keyInfo = $nodeset->item(0); + $dsig_pfx = ''; + if (! $keyInfo) { + $pfx = $parentRef->lookupPrefix(self::XMLDSIGNS); + if (! empty($pfx)) { + $dsig_pfx = $pfx.":"; + } + $inserted = false; + $keyInfo = $baseDoc->createElementNS(self::XMLDSIGNS, $dsig_pfx.'KeyInfo'); + + $query = "./secdsig:Object"; + $nodeset = $xpath->query($query, $parentRef); + if ($sObject = $nodeset->item(0)) { + $sObject->parentNode->insertBefore($keyInfo, $sObject); + $inserted = true; + } + + if (! $inserted) { + $parentRef->appendChild($keyInfo); + } + } else { + $pfx = $keyInfo->lookupPrefix(self::XMLDSIGNS); + if (! empty($pfx)) { + $dsig_pfx = $pfx.":"; + } + } + + // Add all certs if there are more than one + $certs = self::staticGet509XCerts($cert, $isPEMFormat); + + // Attach X509 data node + $x509DataNode = $baseDoc->createElementNS(self::XMLDSIGNS, $dsig_pfx.'X509Data'); + $keyInfo->appendChild($x509DataNode); + + $issuerSerial = false; + $subjectName = false; + if (is_array($options)) { + if (! empty($options['issuerSerial'])) { + $issuerSerial = true; + } + if (! empty($options['subjectName'])) { + $subjectName = true; + } + } + + // Attach all certificate nodes and any additional data + foreach ($certs as $X509Cert) { + if ($issuerSerial || $subjectName) { + if ($certData = openssl_x509_parse("-----BEGIN CERTIFICATE-----\n".chunk_split($X509Cert, 64, "\n")."-----END CERTIFICATE-----\n")) { + if ($subjectName && ! empty($certData['subject'])) { + if (is_array($certData['subject'])) { + $parts = array(); + foreach ($certData['subject'] AS $key => $value) { + if (is_array($value)) { + foreach ($value as $valueElement) { + array_unshift($parts, "$key=$valueElement"); + } + } else { + array_unshift($parts, "$key=$value"); + } + } + $subjectNameValue = implode(',', $parts); + } else { + $subjectNameValue = $certData['issuer']; + } + $x509SubjectNode = $baseDoc->createElementNS(self::XMLDSIGNS, $dsig_pfx.'X509SubjectName', $subjectNameValue); + $x509DataNode->appendChild($x509SubjectNode); + } + if ($issuerSerial && ! empty($certData['issuer']) && ! empty($certData['serialNumber'])) { + if (is_array($certData['issuer'])) { + $parts = array(); + foreach ($certData['issuer'] AS $key => $value) { + array_unshift($parts, "$key=$value"); + } + $issuerName = implode(',', $parts); + } else { + $issuerName = $certData['issuer']; + } + + $x509IssuerNode = $baseDoc->createElementNS(self::XMLDSIGNS, $dsig_pfx.'X509IssuerSerial'); + $x509DataNode->appendChild($x509IssuerNode); + + $x509Node = $baseDoc->createElementNS(self::XMLDSIGNS, $dsig_pfx.'X509IssuerName', $issuerName); + $x509IssuerNode->appendChild($x509Node); + $x509Node = $baseDoc->createElementNS(self::XMLDSIGNS, $dsig_pfx.'X509SerialNumber', $certData['serialNumber']); + $x509IssuerNode->appendChild($x509Node); + } + } + + } + $x509CertNode = $baseDoc->createElementNS(self::XMLDSIGNS, $dsig_pfx.'X509Certificate', $X509Cert); + $x509DataNode->appendChild($x509CertNode); + } + } + + /** + * @param string $cert + * @param bool $isPEMFormat + * @param bool $isURL + * @param null|array $options + */ + public function add509Cert($cert, $isPEMFormat=true, $isURL=false, $options=null) + { + if ($xpath = $this->getXPathObj()) { + self::staticAdd509Cert($this->sigNode, $cert, $isPEMFormat, $isURL, $xpath, $options); + } + } + + /** + * This function appends a node to the KeyInfo. + * + * The KeyInfo element will be created if one does not exist in the document. + * + * @param DOMNode $node The node to append to the KeyInfo. + * + * @return DOMNode The KeyInfo element node + */ + public function appendToKeyInfo($node) + { + $parentRef = $this->sigNode; + $baseDoc = $parentRef->ownerDocument; + + $xpath = $this->getXPathObj(); + if (empty($xpath)) { + $xpath = new DOMXPath($parentRef->ownerDocument); + $xpath->registerNamespace('secdsig', self::XMLDSIGNS); + } + + $query = "./secdsig:KeyInfo"; + $nodeset = $xpath->query($query, $parentRef); + $keyInfo = $nodeset->item(0); + if (! $keyInfo) { + $dsig_pfx = ''; + $pfx = $parentRef->lookupPrefix(self::XMLDSIGNS); + if (! empty($pfx)) { + $dsig_pfx = $pfx.":"; + } + $inserted = false; + $keyInfo = $baseDoc->createElementNS(self::XMLDSIGNS, $dsig_pfx.'KeyInfo'); + + $query = "./secdsig:Object"; + $nodeset = $xpath->query($query, $parentRef); + if ($sObject = $nodeset->item(0)) { + $sObject->parentNode->insertBefore($keyInfo, $sObject); + $inserted = true; + } + + if (! $inserted) { + $parentRef->appendChild($keyInfo); + } + } + + $keyInfo->appendChild($node); + + return $keyInfo; + } + + /** + * This function retrieves an associative array of the validated nodes. + * + * The array will contain the id of the referenced node as the key and the node itself + * as the value. + * + * Returns: + * An associative array of validated nodes or null if no nodes have been validated. + * + * @return array Associative array of validated nodes + */ + public function getValidatedNodes() + { + return $this->validatedNodes; + } +} diff --git a/onelogin-saml-sso/php/extlib/xmlseclibs/src/XMLSecurityKey.php b/onelogin-saml-sso/php/extlib/xmlseclibs/src/XMLSecurityKey.php new file mode 100644 index 0000000..5d855bf --- /dev/null +++ b/onelogin-saml-sso/php/extlib/xmlseclibs/src/XMLSecurityKey.php @@ -0,0 +1,749 @@ +. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * * Neither the name of Robert Richards nor the names of his + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * @author Robert Richards + * @copyright 2007-2018 Robert Richards + * @license http://www.opensource.org/licenses/bsd-license.php BSD License + */ + +class XMLSecurityKey +{ + const TRIPLEDES_CBC = 'http://www.w3.org/2001/04/xmlenc#tripledes-cbc'; + const AES128_CBC = 'http://www.w3.org/2001/04/xmlenc#aes128-cbc'; + const AES192_CBC = 'http://www.w3.org/2001/04/xmlenc#aes192-cbc'; + const AES256_CBC = 'http://www.w3.org/2001/04/xmlenc#aes256-cbc'; + const RSA_1_5 = 'http://www.w3.org/2001/04/xmlenc#rsa-1_5'; + const RSA_OAEP_MGF1P = 'http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p'; + const DSA_SHA1 = 'http://www.w3.org/2000/09/xmldsig#dsa-sha1'; + const RSA_SHA1 = 'http://www.w3.org/2000/09/xmldsig#rsa-sha1'; + const RSA_SHA256 = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256'; + const RSA_SHA384 = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha384'; + const RSA_SHA512 = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha512'; + const HMAC_SHA1 = 'http://www.w3.org/2000/09/xmldsig#hmac-sha1'; + + /** @var array */ + private $cryptParams = array(); + + /** @var int|string */ + public $type = 0; + + /** @var mixed|null */ + public $key = null; + + /** @var string */ + public $passphrase = ""; + + /** @var string|null */ + public $iv = null; + + /** @var string|null */ + public $name = null; + + /** @var mixed|null */ + public $keyChain = null; + + /** @var bool */ + public $isEncrypted = false; + + /** @var XMLSecEnc|null */ + public $encryptedCtx = null; + + /** @var mixed|null */ + public $guid = null; + + /** + * This variable contains the certificate as a string if this key represents an X509-certificate. + * If this key doesn't represent a certificate, this will be null. + * @var string|null + */ + private $x509Certificate = null; + + /** + * This variable contains the certificate thumbprint if we have loaded an X509-certificate. + * @var string|null + */ + private $X509Thumbprint = null; + + /** + * @param string $type + * @param null|array $params + * @throws Exception + */ + public function __construct($type, $params=null) + { + switch ($type) { + case (self::TRIPLEDES_CBC): + $this->cryptParams['library'] = 'openssl'; + $this->cryptParams['cipher'] = 'des-ede3-cbc'; + $this->cryptParams['type'] = 'symmetric'; + $this->cryptParams['method'] = 'http://www.w3.org/2001/04/xmlenc#tripledes-cbc'; + $this->cryptParams['keysize'] = 24; + $this->cryptParams['blocksize'] = 8; + break; + case (self::AES128_CBC): + $this->cryptParams['library'] = 'openssl'; + $this->cryptParams['cipher'] = 'aes-128-cbc'; + $this->cryptParams['type'] = 'symmetric'; + $this->cryptParams['method'] = 'http://www.w3.org/2001/04/xmlenc#aes128-cbc'; + $this->cryptParams['keysize'] = 16; + $this->cryptParams['blocksize'] = 16; + break; + case (self::AES192_CBC): + $this->cryptParams['library'] = 'openssl'; + $this->cryptParams['cipher'] = 'aes-192-cbc'; + $this->cryptParams['type'] = 'symmetric'; + $this->cryptParams['method'] = 'http://www.w3.org/2001/04/xmlenc#aes192-cbc'; + $this->cryptParams['keysize'] = 24; + $this->cryptParams['blocksize'] = 16; + break; + case (self::AES256_CBC): + $this->cryptParams['library'] = 'openssl'; + $this->cryptParams['cipher'] = 'aes-256-cbc'; + $this->cryptParams['type'] = 'symmetric'; + $this->cryptParams['method'] = 'http://www.w3.org/2001/04/xmlenc#aes256-cbc'; + $this->cryptParams['keysize'] = 32; + $this->cryptParams['blocksize'] = 16; + break; + case (self::RSA_1_5): + $this->cryptParams['library'] = 'openssl'; + $this->cryptParams['padding'] = OPENSSL_PKCS1_PADDING; + $this->cryptParams['method'] = 'http://www.w3.org/2001/04/xmlenc#rsa-1_5'; + if (is_array($params) && ! empty($params['type'])) { + if ($params['type'] == 'public' || $params['type'] == 'private') { + $this->cryptParams['type'] = $params['type']; + break; + } + } + throw new Exception('Certificate "type" (private/public) must be passed via parameters'); + case (self::RSA_OAEP_MGF1P): + $this->cryptParams['library'] = 'openssl'; + $this->cryptParams['padding'] = OPENSSL_PKCS1_OAEP_PADDING; + $this->cryptParams['method'] = 'http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p'; + $this->cryptParams['hash'] = null; + if (is_array($params) && ! empty($params['type'])) { + if ($params['type'] == 'public' || $params['type'] == 'private') { + $this->cryptParams['type'] = $params['type']; + break; + } + } + throw new Exception('Certificate "type" (private/public) must be passed via parameters'); + case (self::RSA_SHA1): + $this->cryptParams['library'] = 'openssl'; + $this->cryptParams['method'] = 'http://www.w3.org/2000/09/xmldsig#rsa-sha1'; + $this->cryptParams['padding'] = OPENSSL_PKCS1_PADDING; + if (is_array($params) && ! empty($params['type'])) { + if ($params['type'] == 'public' || $params['type'] == 'private') { + $this->cryptParams['type'] = $params['type']; + break; + } + } + throw new Exception('Certificate "type" (private/public) must be passed via parameters'); + case (self::RSA_SHA256): + $this->cryptParams['library'] = 'openssl'; + $this->cryptParams['method'] = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256'; + $this->cryptParams['padding'] = OPENSSL_PKCS1_PADDING; + $this->cryptParams['digest'] = 'SHA256'; + if (is_array($params) && ! empty($params['type'])) { + if ($params['type'] == 'public' || $params['type'] == 'private') { + $this->cryptParams['type'] = $params['type']; + break; + } + } + throw new Exception('Certificate "type" (private/public) must be passed via parameters'); + case (self::RSA_SHA384): + $this->cryptParams['library'] = 'openssl'; + $this->cryptParams['method'] = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha384'; + $this->cryptParams['padding'] = OPENSSL_PKCS1_PADDING; + $this->cryptParams['digest'] = 'SHA384'; + if (is_array($params) && ! empty($params['type'])) { + if ($params['type'] == 'public' || $params['type'] == 'private') { + $this->cryptParams['type'] = $params['type']; + break; + } + } + throw new Exception('Certificate "type" (private/public) must be passed via parameters'); + case (self::RSA_SHA512): + $this->cryptParams['library'] = 'openssl'; + $this->cryptParams['method'] = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha512'; + $this->cryptParams['padding'] = OPENSSL_PKCS1_PADDING; + $this->cryptParams['digest'] = 'SHA512'; + if (is_array($params) && ! empty($params['type'])) { + if ($params['type'] == 'public' || $params['type'] == 'private') { + $this->cryptParams['type'] = $params['type']; + break; + } + } + throw new Exception('Certificate "type" (private/public) must be passed via parameters'); + case (self::HMAC_SHA1): + $this->cryptParams['library'] = $type; + $this->cryptParams['method'] = 'http://www.w3.org/2000/09/xmldsig#hmac-sha1'; + break; + default: + throw new Exception('Invalid Key Type'); + } + $this->type = $type; + } + + /** + * Retrieve the key size for the symmetric encryption algorithm.. + * + * If the key size is unknown, or this isn't a symmetric encryption algorithm, + * null is returned. + * + * @return int|null The number of bytes in the key. + */ + public function getSymmetricKeySize() + { + if (! isset($this->cryptParams['keysize'])) { + return null; + } + return $this->cryptParams['keysize']; + } + + /** + * Generates a session key using the openssl-extension. + * In case of using DES3-CBC the key is checked for a proper parity bits set. + * @return string + * @throws Exception + */ + public function generateSessionKey() + { + if (!isset($this->cryptParams['keysize'])) { + throw new Exception('Unknown key size for type "' . $this->type . '".'); + } + $keysize = $this->cryptParams['keysize']; + + $key = openssl_random_pseudo_bytes($keysize); + + if ($this->type === self::TRIPLEDES_CBC) { + /* Make sure that the generated key has the proper parity bits set. + * Mcrypt doesn't care about the parity bits, but others may care. + */ + for ($i = 0; $i < strlen($key); $i++) { + $byte = ord($key[$i]) & 0xfe; + $parity = 1; + for ($j = 1; $j < 8; $j++) { + $parity ^= ($byte >> $j) & 1; + } + $byte |= $parity; + $key[$i] = chr($byte); + } + } + + $this->key = $key; + return $key; + } + + /** + * Get the raw thumbprint of a certificate + * + * @param string $cert + * @return null|string + */ + public static function getRawThumbprint($cert) + { + + $arCert = explode("\n", $cert); + $data = ''; + $inData = false; + + foreach ($arCert AS $curData) { + if (! $inData) { + if (strncmp($curData, '-----BEGIN CERTIFICATE', 22) == 0) { + $inData = true; + } + } else { + if (strncmp($curData, '-----END CERTIFICATE', 20) == 0) { + break; + } + $data .= trim($curData); + } + } + + if (! empty($data)) { + return strtolower(sha1(base64_decode($data))); + } + + return null; + } + + /** + * Loads the given key, or - with isFile set true - the key from the keyfile. + * + * @param string $key + * @param bool $isFile + * @param bool $isCert + * @throws Exception + */ + public function loadKey($key, $isFile=false, $isCert = false) + { + if ($isFile) { + $this->key = file_get_contents($key); + } else { + $this->key = $key; + } + if ($isCert) { + $this->key = openssl_x509_read($this->key); + openssl_x509_export($this->key, $str_cert); + $this->x509Certificate = $str_cert; + $this->key = $str_cert; + } else { + $this->x509Certificate = null; + } + if ($this->cryptParams['library'] == 'openssl') { + switch ($this->cryptParams['type']) { + case 'public': + if ($isCert) { + /* Load the thumbprint if this is an X509 certificate. */ + $this->X509Thumbprint = self::getRawThumbprint($this->key); + } + $this->key = openssl_get_publickey($this->key); + if (! $this->key) { + throw new Exception('Unable to extract public key'); + } + break; + + case 'private': + $this->key = openssl_get_privatekey($this->key, $this->passphrase); + break; + + case'symmetric': + if (strlen($this->key) < $this->cryptParams['keysize']) { + throw new Exception('Key must contain at least 25 characters for this cipher'); + } + break; + + default: + throw new Exception('Unknown type'); + } + } + } + + /** + * ISO 10126 Padding + * + * @param string $data + * @param integer $blockSize + * @throws Exception + * @return string + */ + private function padISO10126($data, $blockSize) + { + if ($blockSize > 256) { + throw new Exception('Block size higher than 256 not allowed'); + } + $padChr = $blockSize - (strlen($data) % $blockSize); + $pattern = chr($padChr); + return $data . str_repeat($pattern, $padChr); + } + + /** + * Remove ISO 10126 Padding + * + * @param string $data + * @return string + */ + private function unpadISO10126($data) + { + $padChr = substr($data, -1); + $padLen = ord($padChr); + return substr($data, 0, -$padLen); + } + + /** + * Encrypts the given data (string) using the openssl-extension + * + * @param string $data + * @return string + */ + private function encryptSymmetric($data) + { + $this->iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length($this->cryptParams['cipher'])); + $data = $this->padISO10126($data, $this->cryptParams['blocksize']); + $encrypted = openssl_encrypt($data, $this->cryptParams['cipher'], $this->key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING, $this->iv); + if (false === $encrypted) { + throw new Exception('Failure encrypting Data (openssl symmetric) - ' . openssl_error_string()); + } + return $this->iv . $encrypted; + } + + /** + * Decrypts the given data (string) using the openssl-extension + * + * @param string $data + * @return string + */ + private function decryptSymmetric($data) + { + $iv_length = openssl_cipher_iv_length($this->cryptParams['cipher']); + $this->iv = substr($data, 0, $iv_length); + $data = substr($data, $iv_length); + $decrypted = openssl_decrypt($data, $this->cryptParams['cipher'], $this->key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING, $this->iv); + if (false === $decrypted) { + throw new Exception('Failure decrypting Data (openssl symmetric) - ' . openssl_error_string()); + } + return $this->unpadISO10126($decrypted); + } + + /** + * Encrypts the given public data (string) using the openssl-extension + * + * @param string $data + * @return string + * @throws Exception + */ + private function encryptPublic($data) + { + if (! openssl_public_encrypt($data, $encrypted, $this->key, $this->cryptParams['padding'])) { + throw new Exception('Failure encrypting Data (openssl public) - ' . openssl_error_string()); + } + return $encrypted; + } + + /** + * Decrypts the given public data (string) using the openssl-extension + * + * @param string $data + * @return string + * @throws Exception + */ + private function decryptPublic($data) + { + if (! openssl_public_decrypt($data, $decrypted, $this->key, $this->cryptParams['padding'])) { + throw new Exception('Failure decrypting Data (openssl public) - ' . openssl_error_string()); + } + return $decrypted; + } + + /** + * Encrypts the given private data (string) using the openssl-extension + * + * @param string $data + * @return string + * @throws Exception + */ + private function encryptPrivate($data) + { + if (! openssl_private_encrypt($data, $encrypted, $this->key, $this->cryptParams['padding'])) { + throw new Exception('Failure encrypting Data (openssl private) - ' . openssl_error_string()); + } + return $encrypted; + } + + /** + * Decrypts the given private data (string) using the openssl-extension + * + * @param string $data + * @return string + * @throws Exception + */ + private function decryptPrivate($data) + { + if (! openssl_private_decrypt($data, $decrypted, $this->key, $this->cryptParams['padding'])) { + throw new Exception('Failure decrypting Data (openssl private) - ' . openssl_error_string()); + } + return $decrypted; + } + + /** + * Signs the given data (string) using the openssl-extension + * + * @param string $data + * @return string + * @throws Exception + */ + private function signOpenSSL($data) + { + $algo = OPENSSL_ALGO_SHA1; + if (! empty($this->cryptParams['digest'])) { + $algo = $this->cryptParams['digest']; + } + if (! openssl_sign($data, $signature, $this->key, $algo)) { + throw new Exception('Failure Signing Data: ' . openssl_error_string() . ' - ' . $algo); + } + return $signature; + } + + /** + * Verifies the given data (string) belonging to the given signature using the openssl-extension + * + * Returns: + * 1 on succesful signature verification, + * 0 when signature verification failed, + * -1 if an error occurred during processing. + * + * NOTE: be very careful when checking the return value, because in PHP, + * -1 will be cast to True when in boolean context. So always check the + * return value in a strictly typed way, e.g. "$obj->verify(...) === 1". + * + * @param string $data + * @param string $signature + * @return int + */ + private function verifyOpenSSL($data, $signature) + { + $algo = OPENSSL_ALGO_SHA1; + if (! empty($this->cryptParams['digest'])) { + $algo = $this->cryptParams['digest']; + } + return openssl_verify($data, $signature, $this->key, $algo); + } + + /** + * Encrypts the given data (string) using the regarding php-extension, depending on the library assigned to algorithm in the contructor. + * + * @param string $data + * @return mixed|string + */ + public function encryptData($data) + { + if ($this->cryptParams['library'] === 'openssl') { + switch ($this->cryptParams['type']) { + case 'symmetric': + return $this->encryptSymmetric($data); + case 'public': + return $this->encryptPublic($data); + case 'private': + return $this->encryptPrivate($data); + } + } + } + + /** + * Decrypts the given data (string) using the regarding php-extension, depending on the library assigned to algorithm in the contructor. + * + * @param string $data + * @return mixed|string + */ + public function decryptData($data) + { + if ($this->cryptParams['library'] === 'openssl') { + switch ($this->cryptParams['type']) { + case 'symmetric': + return $this->decryptSymmetric($data); + case 'public': + return $this->decryptPublic($data); + case 'private': + return $this->decryptPrivate($data); + } + } + } + + /** + * Signs the data (string) using the extension assigned to the type in the constructor. + * + * @param string $data + * @return mixed|string + */ + public function signData($data) + { + switch ($this->cryptParams['library']) { + case 'openssl': + return $this->signOpenSSL($data); + case (self::HMAC_SHA1): + return hash_hmac("sha1", $data, $this->key, true); + } + } + + /** + * Verifies the data (string) against the given signature using the extension assigned to the type in the constructor. + * + * Returns in case of openSSL: + * 1 on succesful signature verification, + * 0 when signature verification failed, + * -1 if an error occurred during processing. + * + * NOTE: be very careful when checking the return value, because in PHP, + * -1 will be cast to True when in boolean context. So always check the + * return value in a strictly typed way, e.g. "$obj->verify(...) === 1". + * + * @param string $data + * @param string $signature + * @return bool|int + */ + public function verifySignature($data, $signature) + { + switch ($this->cryptParams['library']) { + case 'openssl': + return $this->verifyOpenSSL($data, $signature); + case (self::HMAC_SHA1): + $expectedSignature = hash_hmac("sha1", $data, $this->key, true); + return strcmp($signature, $expectedSignature) == 0; + } + } + + /** + * @deprecated + * @see getAlgorithm() + * @return mixed + */ + public function getAlgorith() + { + return $this->getAlgorithm(); + } + + /** + * @return mixed + */ + public function getAlgorithm() + { + return $this->cryptParams['method']; + } + + /** + * + * @param int $type + * @param string $string + * @return null|string + */ + public static function makeAsnSegment($type, $string) + { + switch ($type) { + case 0x02: + if (ord($string) > 0x7f) + $string = chr(0).$string; + break; + case 0x03: + $string = chr(0).$string; + break; + } + + $length = strlen($string); + + if ($length < 128) { + $output = sprintf("%c%c%s", $type, $length, $string); + } else if ($length < 0x0100) { + $output = sprintf("%c%c%c%s", $type, 0x81, $length, $string); + } else if ($length < 0x010000) { + $output = sprintf("%c%c%c%c%s", $type, 0x82, $length / 0x0100, $length % 0x0100, $string); + } else { + $output = null; + } + return $output; + } + + /** + * + * Hint: Modulus and Exponent must already be base64 decoded + * @param string $modulus + * @param string $exponent + * @return string + */ + public static function convertRSA($modulus, $exponent) + { + /* make an ASN publicKeyInfo */ + $exponentEncoding = self::makeAsnSegment(0x02, $exponent); + $modulusEncoding = self::makeAsnSegment(0x02, $modulus); + $sequenceEncoding = self::makeAsnSegment(0x30, $modulusEncoding.$exponentEncoding); + $bitstringEncoding = self::makeAsnSegment(0x03, $sequenceEncoding); + $rsaAlgorithmIdentifier = pack("H*", "300D06092A864886F70D0101010500"); + $publicKeyInfo = self::makeAsnSegment(0x30, $rsaAlgorithmIdentifier.$bitstringEncoding); + + /* encode the publicKeyInfo in base64 and add PEM brackets */ + $publicKeyInfoBase64 = base64_encode($publicKeyInfo); + $encoding = "-----BEGIN PUBLIC KEY-----\n"; + $offset = 0; + while ($segment = substr($publicKeyInfoBase64, $offset, 64)) { + $encoding = $encoding.$segment."\n"; + $offset += 64; + } + return $encoding."-----END PUBLIC KEY-----\n"; + } + + /** + * @param mixed $parent + */ + public function serializeKey($parent) + { + + } + + /** + * Retrieve the X509 certificate this key represents. + * + * Will return the X509 certificate in PEM-format if this key represents + * an X509 certificate. + * + * @return string The X509 certificate or null if this key doesn't represent an X509-certificate. + */ + public function getX509Certificate() + { + return $this->x509Certificate; + } + + /** + * Get the thumbprint of this X509 certificate. + * + * Returns: + * The thumbprint as a lowercase 40-character hexadecimal number, or null + * if this isn't a X509 certificate. + * + * @return string Lowercase 40-character hexadecimal number of thumbprint + */ + public function getX509Thumbprint() + { + return $this->X509Thumbprint; + } + + + /** + * Create key from an EncryptedKey-element. + * + * @param DOMElement $element The EncryptedKey-element. + * @throws Exception + * + * @return XMLSecurityKey The new key. + */ + public static function fromEncryptedKeyElement(DOMElement $element) + { + + $objenc = new XMLSecEnc(); + $objenc->setNode($element); + if (! $objKey = $objenc->locateKey()) { + throw new Exception("Unable to locate algorithm for this Encrypted Key"); + } + $objKey->isEncrypted = true; + $objKey->encryptedCtx = $objenc; + XMLSecEnc::staticLocateKeyInfo($objKey, $element); + return $objKey; + } + +} diff --git a/onelogin-saml-sso/php/extlib/xmlseclibs/xmlseclibs.php b/onelogin-saml-sso/php/extlib/xmlseclibs/xmlseclibs.php index bd3caa5..e399860 100644 --- a/onelogin-saml-sso/php/extlib/xmlseclibs/xmlseclibs.php +++ b/onelogin-saml-sso/php/extlib/xmlseclibs/xmlseclibs.php @@ -2,7 +2,7 @@ /** * xmlseclibs.php * - * Copyright (c) 2007-2015, Robert Richards . + * Copyright (c) 2007-2018, Robert Richards . * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -34,1686 +34,14 @@ * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * - * @author Robert Richards - * @copyright 2007-2015 Robert Richards - * @license http://www.opensource.org/licenses/bsd-license.php BSD License - * @version 2.0.0 modified + * @author Robert Richards + * @copyright 2007-2018 Robert Richards + * @license http://www.opensource.org/licenses/bsd-license.php BSD License + * @version 3.0.3-dev */ -class XMLSecurityKey { - const TRIPLEDES_CBC = 'http://www.w3.org/2001/04/xmlenc#tripledes-cbc'; - const AES128_CBC = 'http://www.w3.org/2001/04/xmlenc#aes128-cbc'; - const AES192_CBC = 'http://www.w3.org/2001/04/xmlenc#aes192-cbc'; - const AES256_CBC = 'http://www.w3.org/2001/04/xmlenc#aes256-cbc'; - const RSA_1_5 = 'http://www.w3.org/2001/04/xmlenc#rsa-1_5'; - const RSA_OAEP_MGF1P = 'http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p'; - const DSA_SHA1 = 'http://www.w3.org/2000/09/xmldsig#dsa-sha1'; - const RSA_SHA1 = 'http://www.w3.org/2000/09/xmldsig#rsa-sha1'; - const RSA_SHA256 = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256'; - const RSA_SHA384 = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha384'; - const RSA_SHA512 = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha512'; - const HMAC_SHA1 = 'http://www.w3.org/2000/09/xmldsig#hmac-sha1'; - - private $cryptParams = array(); - public $type = 0; - public $key = null; - public $passphrase = ""; - public $iv = null; - public $name = null; - public $keyChain = null; - public $isEncrypted = false; - public $encryptedCtx = null; - public $guid = null; - - /** - * This variable contains the certificate as a string if this key represents an X509-certificate. - * If this key doesn't represent a certificate, this will be null. - */ - private $x509Certificate = null; - - /* This variable contains the certificate thunbprint if we have loaded an X509-certificate. */ - private $X509Thumbprint = null; - - public function __construct($type, $params=null) { - switch ($type) { - case (XMLSecurityKey::TRIPLEDES_CBC): - $this->cryptParams['library'] = 'mcrypt'; - $this->cryptParams['cipher'] = MCRYPT_TRIPLEDES; - $this->cryptParams['mode'] = MCRYPT_MODE_CBC; - $this->cryptParams['method'] = 'http://www.w3.org/2001/04/xmlenc#tripledes-cbc'; - $this->cryptParams['keysize'] = 24; - break; - case (XMLSecurityKey::AES128_CBC): - $this->cryptParams['library'] = 'mcrypt'; - $this->cryptParams['cipher'] = MCRYPT_RIJNDAEL_128; - $this->cryptParams['mode'] = MCRYPT_MODE_CBC; - $this->cryptParams['method'] = 'http://www.w3.org/2001/04/xmlenc#aes128-cbc'; - $this->cryptParams['keysize'] = 16; - break; - case (XMLSecurityKey::AES192_CBC): - $this->cryptParams['library'] = 'mcrypt'; - $this->cryptParams['cipher'] = MCRYPT_RIJNDAEL_128; - $this->cryptParams['mode'] = MCRYPT_MODE_CBC; - $this->cryptParams['method'] = 'http://www.w3.org/2001/04/xmlenc#aes192-cbc'; - $this->cryptParams['keysize'] = 24; - break; - case (XMLSecurityKey::AES256_CBC): - $this->cryptParams['library'] = 'mcrypt'; - $this->cryptParams['cipher'] = MCRYPT_RIJNDAEL_128; - $this->cryptParams['mode'] = MCRYPT_MODE_CBC; - $this->cryptParams['method'] = 'http://www.w3.org/2001/04/xmlenc#aes256-cbc'; - $this->cryptParams['keysize'] = 32; - break; - case (XMLSecurityKey::RSA_1_5): - $this->cryptParams['library'] = 'openssl'; - $this->cryptParams['padding'] = OPENSSL_PKCS1_PADDING; - $this->cryptParams['method'] = 'http://www.w3.org/2001/04/xmlenc#rsa-1_5'; - if (is_array($params) && ! empty($params['type'])) { - if ($params['type'] == 'public' || $params['type'] == 'private') { - $this->cryptParams['type'] = $params['type']; - break; - } - } - throw new Exception('Certificate "type" (private/public) must be passed via parameters'); - case (XMLSecurityKey::RSA_OAEP_MGF1P): - $this->cryptParams['library'] = 'openssl'; - $this->cryptParams['padding'] = OPENSSL_PKCS1_OAEP_PADDING; - $this->cryptParams['method'] = 'http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p'; - $this->cryptParams['hash'] = null; - if (is_array($params) && ! empty($params['type'])) { - if ($params['type'] == 'public' || $params['type'] == 'private') { - $this->cryptParams['type'] = $params['type']; - break; - } - } - throw new Exception('Certificate "type" (private/public) must be passed via parameters'); - case (XMLSecurityKey::RSA_SHA1): - $this->cryptParams['library'] = 'openssl'; - $this->cryptParams['method'] = 'http://www.w3.org/2000/09/xmldsig#rsa-sha1'; - $this->cryptParams['padding'] = OPENSSL_PKCS1_PADDING; - if (is_array($params) && ! empty($params['type'])) { - if ($params['type'] == 'public' || $params['type'] == 'private') { - $this->cryptParams['type'] = $params['type']; - break; - } - } - throw new Exception('Certificate "type" (private/public) must be passed via parameters'); - case (XMLSecurityKey::RSA_SHA256): - $this->cryptParams['library'] = 'openssl'; - $this->cryptParams['method'] = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256'; - $this->cryptParams['padding'] = OPENSSL_PKCS1_PADDING; - $this->cryptParams['digest'] = 'SHA256'; - if (is_array($params) && ! empty($params['type'])) { - if ($params['type'] == 'public' || $params['type'] == 'private') { - $this->cryptParams['type'] = $params['type']; - break; - } - } - throw new Exception('Certificate "type" (private/public) must be passed via parameters'); - case (XMLSecurityKey::RSA_SHA384): - $this->cryptParams['library'] = 'openssl'; - $this->cryptParams['method'] = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha384'; - $this->cryptParams['padding'] = OPENSSL_PKCS1_PADDING; - $this->cryptParams['digest'] = 'SHA384'; - if (is_array($params) && ! empty($params['type'])) { - if ($params['type'] == 'public' || $params['type'] == 'private') { - $this->cryptParams['type'] = $params['type']; - break; - } - } - throw new Exception('Certificate "type" (private/public) must be passed via parameters'); - case (XMLSecurityKey::RSA_SHA512): - $this->cryptParams['library'] = 'openssl'; - $this->cryptParams['method'] = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha512'; - $this->cryptParams['padding'] = OPENSSL_PKCS1_PADDING; - $this->cryptParams['digest'] = 'SHA512'; - if (is_array($params) && ! empty($params['type'])) { - if ($params['type'] == 'public' || $params['type'] == 'private') { - $this->cryptParams['type'] = $params['type']; - break; - } - } - throw new Exception('Certificate "type" (private/public) must be passed via parameters'); - case (XMLSecurityKey::HMAC_SHA1): - $this->cryptParams['library'] = $type; - $this->cryptParams['method'] = 'http://www.w3.org/2000/09/xmldsig#hmac-sha1'; - break; - default: - throw new Exception('Invalid Key Type'); - } - $this->type = $type; - } - - /** - * Retrieve the key size for the symmetric encryption algorithm.. - * - * If the key size is unknown, or this isn't a symmetric encryption algorithm, - * null is returned. - * - * @return int|null The number of bytes in the key. - */ - public function getSymmetricKeySize() { - if (! isset($this->cryptParams['keysize'])) { - return null; - } - return $this->cryptParams['keysize']; - } - - public function generateSessionKey() { - if (!isset($this->cryptParams['keysize'])) { - throw new Exception('Unknown key size for type "' . $this->type . '".'); - } - $keysize = $this->cryptParams['keysize']; - - if (function_exists('openssl_random_pseudo_bytes')) { - /* We have PHP >= 5.3 - use openssl to generate session key. */ - $key = openssl_random_pseudo_bytes($keysize); - } else { - /* Generating random key using iv generation routines */ - $key = mcrypt_create_iv($keysize, MCRYPT_RAND); - } - - if ($this->type === XMLSecurityKey::TRIPLEDES_CBC) { - /* Make sure that the generated key has the proper parity bits set. - * Mcrypt doesn't care about the parity bits, but others may care. - */ - for ($i = 0; $i < strlen($key); $i++) { - $byte = ord($key[$i]) & 0xfe; - $parity = 1; - for ($j = 1; $j < 8; $j++) { - $parity ^= ($byte >> $j) & 1; - } - $byte |= $parity; - $key[$i] = chr($byte); - } - } - - $this->key = $key; - return $key; - } - - public static function getRawThumbprint($cert) { - - $arCert = explode("\n", $cert); - $data = ''; - $inData = false; - - foreach ($arCert AS $curData) { - if (! $inData) { - if (strncmp($curData, '-----BEGIN CERTIFICATE', 22) == 0) { - $inData = true; - } - } else { - if (strncmp($curData, '-----END CERTIFICATE', 20) == 0) { - break; - } - $data .= trim($curData); - } - } - - if (! empty($data)) { - return strtolower(sha1(base64_decode($data))); - } - - return null; - } - - public function loadKey($key, $isFile=false, $isCert = false) { - if ($isFile) { - $this->key = file_get_contents($key); - } else { - $this->key = $key; - } - if ($isCert) { - $this->key = openssl_x509_read($this->key); - openssl_x509_export($this->key, $str_cert); - $this->x509Certificate = $str_cert; - $this->key = $str_cert; - } else { - $this->x509Certificate = null; - } - if ($this->cryptParams['library'] == 'openssl') { - if ($this->cryptParams['type'] == 'public') { - if ($isCert) { - /* Load the thumbprint if this is an X509 certificate. */ - $this->X509Thumbprint = self::getRawThumbprint($this->key); - } - $this->key = openssl_get_publickey($this->key); - } else { - $this->key = openssl_get_privatekey($this->key, $this->passphrase); - } - } else if (isset($this->cryptParams['cipher']) && $this->cryptParams['cipher'] == MCRYPT_RIJNDAEL_128) { - /* Check key length */ - switch ($this->type) { - case (XMLSecurityKey::AES256_CBC): - if (strlen($this->key) < 25) { - throw new Exception('Key must contain at least 25 characters for this cipher'); - } - break; - case (XMLSecurityKey::AES192_CBC): - if (strlen($this->key) < 17) { - throw new Exception('Key must contain at least 17 characters for this cipher'); - } - break; - } - } - } - - private function encryptMcrypt($data) { - $td = mcrypt_module_open($this->cryptParams['cipher'], '', $this->cryptParams['mode'], ''); - $this->iv = mcrypt_create_iv (mcrypt_enc_get_iv_size($td), MCRYPT_RAND); - mcrypt_generic_init($td, $this->key, $this->iv); - if ($this->cryptParams['mode'] == MCRYPT_MODE_CBC) { - $bs = mcrypt_enc_get_block_size($td); - for ($datalen0=$datalen=strlen($data); (($datalen%$bs)!=($bs-1)); $datalen++) - $data.=chr(mt_rand(1, 127)); - $data.=chr($datalen-$datalen0+1); - } - $encrypted_data = $this->iv.mcrypt_generic($td, $data); - mcrypt_generic_deinit($td); - mcrypt_module_close($td); - return $encrypted_data; - } - - private function decryptMcrypt($data) { - $td = mcrypt_module_open($this->cryptParams['cipher'], '', $this->cryptParams['mode'], ''); - $iv_length = mcrypt_enc_get_iv_size($td); - - $this->iv = substr($data, 0, $iv_length); - $data = substr($data, $iv_length); - - mcrypt_generic_init($td, $this->key, $this->iv); - $decrypted_data = mdecrypt_generic($td, $data); - mcrypt_generic_deinit($td); - mcrypt_module_close($td); - if ($this->cryptParams['mode'] == MCRYPT_MODE_CBC) { - $dataLen = strlen($decrypted_data); - $paddingLength = substr($decrypted_data, $dataLen - 1, 1); - $decrypted_data = substr($decrypted_data, 0, $dataLen - ord($paddingLength)); - } - return $decrypted_data; - } - - private function encryptOpenSSL($data) { - if ($this->cryptParams['type'] == 'public') { - if (! openssl_public_encrypt($data, $encrypted_data, $this->key, $this->cryptParams['padding'])) { - throw new Exception('Failure encrypting Data'); - } - } else { - if (! openssl_private_encrypt($data, $encrypted_data, $this->key, $this->cryptParams['padding'])) { - throw new Exception('Failure encrypting Data'); - } - } - return $encrypted_data; - } - - private function decryptOpenSSL($data) { - if ($this->cryptParams['type'] == 'public') { - if (! openssl_public_decrypt($data, $decrypted, $this->key, $this->cryptParams['padding'])) { - throw new Exception('Failure decrypting Data'); - } - } else { - if (! openssl_private_decrypt($data, $decrypted, $this->key, $this->cryptParams['padding'])) { - throw new Exception('Failure decrypting Data'); - } - } - return $decrypted; - } - - private function signOpenSSL($data) { - $algo = OPENSSL_ALGO_SHA1; - if (! empty($this->cryptParams['digest'])) { - $algo = $this->cryptParams['digest']; - } - if (! openssl_sign ($data, $signature, $this->key, $algo)) { - throw new Exception('Failure Signing Data: ' . openssl_error_string() . ' - ' . $algo); - } - return $signature; - } - - private function verifyOpenSSL($data, $signature) { - $algo = OPENSSL_ALGO_SHA1; - if (! empty($this->cryptParams['digest'])) { - $algo = $this->cryptParams['digest']; - } - return openssl_verify ($data, $signature, $this->key, $algo); - } - - public function encryptData($data) { - switch ($this->cryptParams['library']) { - case 'mcrypt': - return $this->encryptMcrypt($data); - case 'openssl': - return $this->encryptOpenSSL($data); - } - } - - public function decryptData($data) { - switch ($this->cryptParams['library']) { - case 'mcrypt': - return $this->decryptMcrypt($data); - case 'openssl': - return $this->decryptOpenSSL($data); - } - } - - public function signData($data) { - switch ($this->cryptParams['library']) { - case 'openssl': - return $this->signOpenSSL($data); - case (XMLSecurityKey::HMAC_SHA1): - return hash_hmac("sha1", $data, $this->key, true); - } - } - - public function verifySignature($data, $signature) { - switch ($this->cryptParams['library']) { - case 'openssl': - return $this->verifyOpenSSL($data, $signature); - case (XMLSecurityKey::HMAC_SHA1): - $expectedSignature = hash_hmac("sha1", $data, $this->key, true); - return strcmp($signature, $expectedSignature) == 0; - } - } - - public function getAlgorithm() { - return $this->cryptParams['method']; - } - - static function makeAsnSegment($type, $string) { - switch ($type){ - case 0x02: - if (ord($string) > 0x7f) - $string = chr(0).$string; - break; - case 0x03: - $string = chr(0).$string; - break; - } - - $length = strlen($string); - - if ($length < 128){ - $output = sprintf("%c%c%s", $type, $length, $string); - } else if ($length < 0x0100){ - $output = sprintf("%c%c%c%s", $type, 0x81, $length, $string); - } else if ($length < 0x010000) { - $output = sprintf("%c%c%c%c%s", $type, 0x82, $length/0x0100, $length%0x0100, $string); - } else { - $output = null; - } - return($output); - } - - /* Modulus and Exponent must already be base64 decoded */ - static function convertRSA($modulus, $exponent) { - /* make an ASN publicKeyInfo */ - $exponentEncoding = XMLSecurityKey::makeAsnSegment(0x02, $exponent); - $modulusEncoding = XMLSecurityKey::makeAsnSegment(0x02, $modulus); - $sequenceEncoding = XMLSecurityKey:: makeAsnSegment(0x30, $modulusEncoding.$exponentEncoding); - $bitstringEncoding = XMLSecurityKey::makeAsnSegment(0x03, $sequenceEncoding); - $rsaAlgorithmIdentifier = pack("H*", "300D06092A864886F70D0101010500"); - $publicKeyInfo = XMLSecurityKey::makeAsnSegment (0x30, $rsaAlgorithmIdentifier.$bitstringEncoding); - - /* encode the publicKeyInfo in base64 and add PEM brackets */ - $publicKeyInfoBase64 = base64_encode($publicKeyInfo); - $encoding = "-----BEGIN PUBLIC KEY-----\n"; - $offset = 0; - while ($segment=substr($publicKeyInfoBase64, $offset, 64)){ - $encoding = $encoding.$segment."\n"; - $offset += 64; - } - return $encoding."-----END PUBLIC KEY-----\n"; - } - - public function serializeKey($parent) { - - } - - - - /** - * Retrieve the X509 certificate this key represents. - * - * Will return the X509 certificate in PEM-format if this key represents - * an X509 certificate. - * - * @return The X509 certificate or null if this key doesn't represent an X509-certificate. - */ - public function getX509Certificate() { - return $this->x509Certificate; - } - - /* Get the thumbprint of this X509 certificate. - * - * Returns: - * The thumbprint as a lowercase 40-character hexadecimal number, or null - * if this isn't a X509 certificate. - */ - public function getX509Thumbprint() { - return $this->X509Thumbprint; - } - - - /** - * Create key from an EncryptedKey-element. - * - * @param DOMElement $element The EncryptedKey-element. - * @return XMLSecurityKey The new key. - */ - public static function fromEncryptedKeyElement(DOMElement $element) { - - $objenc = new XMLSecEnc(); - $objenc->setNode($element); - if (! $objKey = $objenc->locateKey()) { - throw new Exception("Unable to locate algorithm for this Encrypted Key"); - } - $objKey->isEncrypted = true; - $objKey->encryptedCtx = $objenc; - XMLSecEnc::staticLocateKeyInfo($objKey, $element); - return $objKey; - } - -} - - -class XMLSecurityDSig { - const XMLDSIGNS = 'http://www.w3.org/2000/09/xmldsig#'; - const SHA1 = 'http://www.w3.org/2000/09/xmldsig#sha1'; - const SHA256 = 'http://www.w3.org/2001/04/xmlenc#sha256'; - const SHA384 = 'http://www.w3.org/2001/04/xmldsig-more#sha384'; - const SHA512 = 'http://www.w3.org/2001/04/xmlenc#sha512'; - const RIPEMD160 = 'http://www.w3.org/2001/04/xmlenc#ripemd160'; - - const C14N = 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315'; - const C14N_COMMENTS = 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments'; - const EXC_C14N = 'http://www.w3.org/2001/10/xml-exc-c14n#'; - const EXC_C14N_COMMENTS = 'http://www.w3.org/2001/10/xml-exc-c14n#WithComments'; - - const template = ' - - - -'; - - public $sigNode = null; - public $idKeys = array(); - public $idNS = array(); - private $signedInfo = null; - private $xPathCtx = null; - private $canonicalMethod = null; - private $prefix = 'ds'; - private $searchpfx = 'secdsig'; - - /* This variable contains an associative array of validated nodes. */ - private $validatedNodes = null; - - public function __construct() { - $sigdoc = new DOMDocument(); - $sigdoc->loadXML(XMLSecurityDSig::template); - $this->sigNode = $sigdoc->documentElement; - } - - private function resetXPathObj() { - $this->xPathCtx = null; - } - - private function getXPathObj() { - if (empty($this->xPathCtx) && ! empty($this->sigNode)) { - $xpath = new DOMXPath($this->sigNode->ownerDocument); - $xpath->registerNamespace('secdsig', XMLSecurityDSig::XMLDSIGNS); - $this->xPathCtx = $xpath; - } - return $this->xPathCtx; - } - - static function generateGUID($prefix='pfx') { - $uuid = md5(uniqid(mt_rand(), true)); - $guid = $prefix.substr($uuid,0,8)."-". - substr($uuid,8,4)."-". - substr($uuid,12,4)."-". - substr($uuid,16,4)."-". - substr($uuid,20,12); - return $guid; - } - - public function locateSignature($objDoc, $pos=0) { - if ($objDoc instanceof DOMDocument) { - $doc = $objDoc; - } else { - $doc = $objDoc->ownerDocument; - } - if ($doc) { - $xpath = new DOMXPath($doc); - $xpath->registerNamespace('secdsig', XMLSecurityDSig::XMLDSIGNS); - $query = ".//secdsig:Signature"; - $nodeset = $xpath->query($query, $objDoc); - $this->sigNode = $nodeset->item($pos); - return $this->sigNode; - } - return null; - } - - public function createNewSignNode($name, $value=null) { - $doc = $this->sigNode->ownerDocument; - if (! is_null($value)) { - $node = $doc->createElementNS(XMLSecurityDSig::XMLDSIGNS, $this->prefix.':'.$name, $value); - } else { - $node = $doc->createElementNS(XMLSecurityDSig::XMLDSIGNS, $this->prefix.':'.$name); - } - return $node; - } - - public function setCanonicalMethod($method) { - switch ($method) { - case 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315': - case 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments': - case 'http://www.w3.org/2001/10/xml-exc-c14n#': - case 'http://www.w3.org/2001/10/xml-exc-c14n#WithComments': - $this->canonicalMethod = $method; - break; - default: - throw new Exception('Invalid Canonical Method'); - } - if ($xpath = $this->getXPathObj()) { - $query = './'.$this->searchpfx.':SignedInfo'; - $nodeset = $xpath->query($query, $this->sigNode); - if ($sinfo = $nodeset->item(0)) { - $query = './'.$this->searchpfx.'CanonicalizationMethod'; - $nodeset = $xpath->query($query, $sinfo); - if (! ($canonNode = $nodeset->item(0))) { - $canonNode = $this->createNewSignNode('CanonicalizationMethod'); - $sinfo->insertBefore($canonNode, $sinfo->firstChild); - } - $canonNode->setAttribute('Algorithm', $this->canonicalMethod); - } - } - } - - private function canonicalizeData($node, $canonicalmethod, $arXPath=null, $prefixList=null) { - $exclusive = false; - $withComments = false; - switch ($canonicalmethod) { - case 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315': - $exclusive = false; - $withComments = false; - break; - case 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments': - $withComments = true; - break; - case 'http://www.w3.org/2001/10/xml-exc-c14n#': - $exclusive = true; - break; - case 'http://www.w3.org/2001/10/xml-exc-c14n#WithComments': - $exclusive = true; - $withComments = true; - break; - } - - if (is_null($arXPath) && ($node instanceof DOMNode) && ($node->ownerDocument !== null) && $node->isSameNode($node->ownerDocument->documentElement)) { - /* Check for any PI or comments as they would have been excluded */ - $element = $node; - while ($refnode = $element->previousSibling) { - if ($refnode->nodeType == XML_PI_NODE || (($refnode->nodeType == XML_COMMENT_NODE) && $withComments)) { - break; - } - $element = $refnode; - } - if ($refnode == null) { - $node = $node->ownerDocument; - } - } - - return $node->C14N($exclusive, $withComments, $arXPath, $prefixList); - } - - public function canonicalizeSignedInfo() { - - $doc = $this->sigNode->ownerDocument; - $canonicalmethod = null; - if ($doc) { - $xpath = $this->getXPathObj(); - $query = "./secdsig:SignedInfo"; - $nodeset = $xpath->query($query, $this->sigNode); - if ($signInfoNode = $nodeset->item(0)) { - $query = "./secdsig:CanonicalizationMethod"; - $nodeset = $xpath->query($query, $signInfoNode); - if ($canonNode = $nodeset->item(0)) { - $canonicalmethod = $canonNode->getAttribute('Algorithm'); - } - $this->signedInfo = $this->canonicalizeData($signInfoNode, $canonicalmethod); - return $this->signedInfo; - } - } - return null; - } - - public function calculateDigest ($digestAlgorithm, $data, $encode = true) { - switch ($digestAlgorithm) { - case XMLSecurityDSig::SHA1: - $alg = 'sha1'; - break; - case XMLSecurityDSig::SHA256: - $alg = 'sha256'; - break; - case XMLSecurityDSig::SHA384: - $alg = 'sha384'; - break; - case XMLSecurityDSig::SHA512: - $alg = 'sha512'; - break; - case XMLSecurityDSig::RIPEMD160: - $alg = 'ripemd160'; - break; - default: - throw new Exception("Cannot validate digest: Unsupported Algorithm <$digestAlgorithm>"); - } - - $digest = hash($alg, $data, true); - if ($encode) { - $digest = base64_encode($digest); - } - return $digest; - } - - public function validateDigest($refNode, $data) { - $xpath = new DOMXPath($refNode->ownerDocument); - $xpath->registerNamespace('secdsig', XMLSecurityDSig::XMLDSIGNS); - $query = 'string(./secdsig:DigestMethod/@Algorithm)'; - $digestAlgorithm = $xpath->evaluate($query, $refNode); - $digValue = $this->calculateDigest($digestAlgorithm, $data, false); - $query = 'string(./secdsig:DigestValue)'; - $digestValue = $xpath->evaluate($query, $refNode); - return ($digValue === base64_decode($digestValue)); - } - - public function processTransforms($refNode, $objData, $includeCommentNodes = true) { - $data = $objData; - $xpath = new DOMXPath($refNode->ownerDocument); - $xpath->registerNamespace('secdsig', XMLSecurityDSig::XMLDSIGNS); - $query = './secdsig:Transforms/secdsig:Transform'; - $nodelist = $xpath->query($query, $refNode); - $canonicalMethod = 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315'; - $arXPath = null; - $prefixList = null; - foreach ($nodelist AS $transform) { - $algorithm = $transform->getAttribute("Algorithm"); - switch ($algorithm) { - case 'http://www.w3.org/2001/10/xml-exc-c14n#': - case 'http://www.w3.org/2001/10/xml-exc-c14n#WithComments': - - if(!$includeCommentNodes) { - /* We remove comment nodes by forcing it to use a canonicalization - * without comments. - */ - $canonicalMethod = 'http://www.w3.org/2001/10/xml-exc-c14n#'; - } else { - $canonicalMethod = $algorithm; - } - - $node = $transform->firstChild; - while ($node) { - if ($node->localName == 'InclusiveNamespaces') { - if ($pfx = $node->getAttribute('PrefixList')) { - $arpfx = array(); - $pfxlist = explode(" ", $pfx); - foreach ($pfxlist AS $pfx) { - $val = trim($pfx); - if (! empty($val)) { - $arpfx[] = $val; - } - } - if (count($arpfx) > 0) { - $prefixList = $arpfx; - } - } - break; - } - $node = $node->nextSibling; - } - break; - case 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315': - case 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments': - if(!$includeCommentNodes) { - /* We remove comment nodes by forcing it to use a canonicalization - * without comments. - */ - $canonicalMethod = 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315'; - } else { - $canonicalMethod = $algorithm; - } - - break; - case 'http://www.w3.org/TR/1999/REC-xpath-19991116': - $node = $transform->firstChild; - while ($node) { - if ($node->localName == 'XPath') { - $arXPath = array(); - $arXPath['query'] = '(.//. | .//@* | .//namespace::*)['.$node->nodeValue.']'; - $arXpath['namespaces'] = array(); - $nslist = $xpath->query('./namespace::*', $node); - foreach ($nslist AS $nsnode) { - if ($nsnode->localName != "xml") { - $arXPath['namespaces'][$nsnode->localName] = $nsnode->nodeValue; - } - } - break; - } - $node = $node->nextSibling; - } - break; - } - } - if ($data instanceof DOMNode) { - $data = $this->canonicalizeData($objData, $canonicalMethod, $arXPath, $prefixList); - } - return $data; - } - - public function processRefNode($refNode) { - $dataObject = null; - - /* - * Depending on the URI, we may not want to include comments in the result - * See: http://www.w3.org/TR/xmldsig-core/#sec-ReferenceProcessingModel - */ - $includeCommentNodes = true; - - if ($uri = $refNode->getAttribute("URI")) { - $arUrl = parse_url($uri); - if (empty($arUrl['path'])) { - if ($identifier = $arUrl['fragment']) { - - /* This reference identifies a node with the given id by using - * a URI on the form "#identifier". This should not include comments. - */ - $includeCommentNodes = false; - - $xPath = new DOMXPath($refNode->ownerDocument); - if ($this->idNS && is_array($this->idNS)) { - foreach ($this->idNS AS $nspf=>$ns) { - $xPath->registerNamespace($nspf, $ns); - } - } - $iDlist = '@Id="'.$identifier.'"'; - if (is_array($this->idKeys)) { - foreach ($this->idKeys AS $idKey) { - $iDlist .= " or @$idKey='$identifier'"; - } - } - $query = '//*['.$iDlist.']'; - $dataObject = $xPath->query($query)->item(0); - } else { - $dataObject = $refNode->ownerDocument; - } - } - } else { - /* This reference identifies the root node with an empty URI. This should - * not include comments. - */ - $includeCommentNodes = false; - - $dataObject = $refNode->ownerDocument; - } - $data = $this->processTransforms($refNode, $dataObject, $includeCommentNodes); - if (!$this->validateDigest($refNode, $data)) { - return false; - } - - if ($dataObject instanceof DOMNode) { - /* Add this node to the list of validated nodes. */ - if(! empty($identifier)) { - $this->validatedNodes[$identifier] = $dataObject; - } else { - $this->validatedNodes[] = $dataObject; - } - } - - return true; - } - - public function getRefNodeID($refNode) { - if ($uri = $refNode->getAttribute("URI")) { - $arUrl = parse_url($uri); - if (empty($arUrl['path'])) { - if ($identifier = $arUrl['fragment']) { - return $identifier; - } - } - } - return null; - } - - public function getRefIDs() { - $refids = array(); - - $xpath = $this->getXPathObj(); - $query = "./secdsig:SignedInfo/secdsig:Reference"; - $nodeset = $xpath->query($query, $this->sigNode); - if ($nodeset->length == 0) { - throw new Exception("Reference nodes not found"); - } - foreach ($nodeset AS $refNode) { - $refids[] = $this->getRefNodeID($refNode); - } - return $refids; - } - - public function validateReference() { - $docElem = $this->sigNode->ownerDocument->documentElement; - if (! $docElem->isSameNode($this->sigNode)) { - $this->sigNode->parentNode->removeChild($this->sigNode); - } - $xpath = $this->getXPathObj(); - $query = "./secdsig:SignedInfo/secdsig:Reference"; - $nodeset = $xpath->query($query, $this->sigNode); - if ($nodeset->length == 0) { - throw new Exception("Reference nodes not found"); - } - - /* Initialize/reset the list of validated nodes. */ - $this->validatedNodes = array(); - - foreach ($nodeset AS $refNode) { - if (! $this->processRefNode($refNode)) { - /* Clear the list of validated nodes. */ - $this->validatedNodes = null; - throw new Exception("Reference validation failed"); - } - } - return true; - } - - private function addRefInternal($sinfoNode, $node, $algorithm, $arTransforms=null, $options=null) { - $prefix = null; - $prefix_ns = null; - $id_name = 'Id'; - $overwrite_id = true; - $force_uri = false; - - if (is_array($options)) { - $prefix = empty($options['prefix'])?null:$options['prefix']; - $prefix_ns = empty($options['prefix_ns'])?null:$options['prefix_ns']; - $id_name = empty($options['id_name'])?'Id':$options['id_name']; - $overwrite_id = !isset($options['overwrite'])?true:(bool)$options['overwrite']; - $force_uri = !isset($options['force_uri'])?false:(bool)$options['force_uri']; - } - - $attname = $id_name; - if (! empty($prefix)) { - $attname = $prefix.':'.$attname; - } - - $refNode = $this->createNewSignNode('Reference'); - $sinfoNode->appendChild($refNode); - - if (! $node instanceof DOMDocument) { - $uri = null; - if (! $overwrite_id) { - $uri = $prefix_ns ? $node->getAttributeNS($prefix_ns, $id_name) : $node->getAttribute($id_name); - } - if (empty($uri)) { - $uri = XMLSecurityDSig::generateGUID(); - $node->setAttributeNS($prefix_ns, $attname, $uri); - } - $refNode->setAttribute("URI", '#'.$uri); - } elseif ($force_uri) { - $refNode->setAttribute("URI", ''); - } - - $transNodes = $this->createNewSignNode('Transforms'); - $refNode->appendChild($transNodes); - - if (is_array($arTransforms)) { - foreach ($arTransforms AS $transform) { - $transNode = $this->createNewSignNode('Transform'); - $transNodes->appendChild($transNode); - if (is_array($transform) && - (! empty($transform['http://www.w3.org/TR/1999/REC-xpath-19991116'])) && - (! empty($transform['http://www.w3.org/TR/1999/REC-xpath-19991116']['query']))) { - $transNode->setAttribute('Algorithm', 'http://www.w3.org/TR/1999/REC-xpath-19991116'); - $XPathNode = $this->createNewSignNode('XPath', $transform['http://www.w3.org/TR/1999/REC-xpath-19991116']['query']); - $transNode->appendChild($XPathNode); - if (! empty($transform['http://www.w3.org/TR/1999/REC-xpath-19991116']['namespaces'])) { - foreach ($transform['http://www.w3.org/TR/1999/REC-xpath-19991116']['namespaces'] AS $prefix => $namespace) { - $XPathNode->setAttributeNS("http://www.w3.org/2000/xmlns/", "xmlns:$prefix", $namespace); - } - } - } else { - $transNode->setAttribute('Algorithm', $transform); - } - } - } elseif (! empty($this->canonicalMethod)) { - $transNode = $this->createNewSignNode('Transform'); - $transNodes->appendChild($transNode); - $transNode->setAttribute('Algorithm', $this->canonicalMethod); - } - - $canonicalData = $this->processTransforms($refNode, $node); - $digValue = $this->calculateDigest($algorithm, $canonicalData); - - $digestMethod = $this->createNewSignNode('DigestMethod'); - $refNode->appendChild($digestMethod); - $digestMethod->setAttribute('Algorithm', $algorithm); - - $digestValue = $this->createNewSignNode('DigestValue', $digValue); - $refNode->appendChild($digestValue); - } - - public function addReference($node, $algorithm, $arTransforms=null, $options=null) { - if ($xpath = $this->getXPathObj()) { - $query = "./secdsig:SignedInfo"; - $nodeset = $xpath->query($query, $this->sigNode); - if ($sInfo = $nodeset->item(0)) { - $this->addRefInternal($sInfo, $node, $algorithm, $arTransforms, $options); - } - } - } - - public function addReferenceList($arNodes, $algorithm, $arTransforms=null, $options=null) { - if ($xpath = $this->getXPathObj()) { - $query = "./secdsig:SignedInfo"; - $nodeset = $xpath->query($query, $this->sigNode); - if ($sInfo = $nodeset->item(0)) { - foreach ($arNodes AS $node) { - $this->addRefInternal($sInfo, $node, $algorithm, $arTransforms, $options); - } - } - } - } - - public function addObject($data, $mimetype=null, $encoding=null) { - $objNode = $this->createNewSignNode('Object'); - $this->sigNode->appendChild($objNode); - if (! empty($mimetype)) { - $objNode->setAttribute('MimeType', $mimetype); - } - if (! empty($encoding)) { - $objNode->setAttribute('Encoding', $encoding); - } - - if ($data instanceof DOMElement) { - $newData = $this->sigNode->ownerDocument->importNode($data, true); - } else { - $newData = $this->sigNode->ownerDocument->createTextNode($data); - } - $objNode->appendChild($newData); - - return $objNode; - } - - public function locateKey($node=null) { - if (empty($node)) { - $node = $this->sigNode; - } - if (! $node instanceof DOMNode) { - return null; - } - if ($doc = $node->ownerDocument) { - $xpath = new DOMXPath($doc); - $xpath->registerNamespace('secdsig', XMLSecurityDSig::XMLDSIGNS); - $query = "string(./secdsig:SignedInfo/secdsig:SignatureMethod/@Algorithm)"; - $algorithm = $xpath->evaluate($query, $node); - if ($algorithm) { - try { - $objKey = new XMLSecurityKey($algorithm, array('type'=>'public')); - } catch (Exception $e) { - return null; - } - return $objKey; - } - } - return null; - } - - public function verify($objKey) { - $doc = $this->sigNode->ownerDocument; - $xpath = new DOMXPath($doc); - $xpath->registerNamespace('secdsig', XMLSecurityDSig::XMLDSIGNS); - $query = "string(./secdsig:SignatureValue)"; - $sigValue = $xpath->evaluate($query, $this->sigNode); - if (empty($sigValue)) { - throw new Exception("Unable to locate SignatureValue"); - } - return $objKey->verifySignature($this->signedInfo, base64_decode($sigValue)); - } - - public function signData($objKey, $data) { - return $objKey->signData($data); - } - - public function sign($objKey, $appendToNode = null) { - // If we have a parent node append it now so C14N properly works - if ($appendToNode != null) { - $this->resetXPathObj(); - $this->appendSignature($appendToNode); - $this->sigNode = $appendToNode->lastChild; - } - if ($xpath = $this->getXPathObj()) { - $query = "./secdsig:SignedInfo"; - $nodeset = $xpath->query($query, $this->sigNode); - if ($sInfo = $nodeset->item(0)) { - $query = "./secdsig:SignatureMethod"; - $nodeset = $xpath->query($query, $sInfo); - $sMethod = $nodeset->item(0); - $sMethod->setAttribute('Algorithm', $objKey->type); - $data = $this->canonicalizeData($sInfo, $this->canonicalMethod); - $sigValue = base64_encode($this->signData($objKey, $data)); - $sigValueNode = $this->createNewSignNode('SignatureValue', $sigValue); - if ($infoSibling = $sInfo->nextSibling) { - $infoSibling->parentNode->insertBefore($sigValueNode, $infoSibling); - } else { - $this->sigNode->appendChild($sigValueNode); - } - } - } - } - - public function appendCert() { - - } - - public function appendKey($objKey, $parent=null) { - $objKey->serializeKey($parent); - } - - - /** - * This function inserts the signature element. - * - * The signature element will be appended to the element, unless $beforeNode is specified. If $beforeNode - * is specified, the signature element will be inserted as the last element before $beforeNode. - * - * @param $node The node the signature element should be inserted into. - * @param $beforeNode The node the signature element should be located before. - * - * @return DOMNode The signature element node - */ - public function insertSignature($node, $beforeNode = null) { - - $document = $node->ownerDocument; - $signatureElement = $document->importNode($this->sigNode, true); - - if($beforeNode == null) { - return $node->insertBefore($signatureElement); - } else { - return $node->insertBefore($signatureElement, $beforeNode); - } - } - - public function appendSignature($parentNode, $insertBefore = false) { - $beforeNode = $insertBefore ? $parentNode->firstChild : null; - return $this->insertSignature($parentNode, $beforeNode); - } - - static function get509XCert($cert, $isPEMFormat=true) { - $certs = XMLSecurityDSig::staticGet509XCerts($cert, $isPEMFormat); - if (! empty($certs)) { - return $certs[0]; - } - return ''; - } - - static function staticGet509XCerts($certs, $isPEMFormat=true) { - if ($isPEMFormat) { - $data = ''; - $certlist = array(); - $arCert = explode("\n", $certs); - $inData = false; - foreach ($arCert AS $curData) { - if (! $inData) { - if (strncmp($curData, '-----BEGIN CERTIFICATE', 22) == 0) { - $inData = true; - } - } else { - if (strncmp($curData, '-----END CERTIFICATE', 20) == 0) { - $inData = false; - $certlist[] = $data; - $data = ''; - continue; - } - $data .= trim($curData); - } - } - return $certlist; - } else { - return array($certs); - } - } - - static function staticAdd509Cert($parentRef, $cert, $isPEMFormat=true, $isURL=false, $xpath=null, $options=null) { - if ($isURL) { - $cert = file_get_contents($cert); - } - if (! $parentRef instanceof DOMElement) { - throw new Exception('Invalid parent Node parameter'); - } - - list($parentRef, $keyInfo) = self::auxKeyInfo($parentRef, $xpath); - - // Add all certs if there are more than one - $certs = XMLSecurityDSig::staticGet509XCerts($cert, $isPEMFormat); - - $baseDoc = $parentRef->ownerDocument; - // Attach X509 data node - $x509DataNode = $baseDoc->createElementNS(XMLSecurityDSig::XMLDSIGNS, 'ds:X509Data'); - $keyInfo->appendChild($x509DataNode); - - $issuerSerial = false; - $subjectName = false; - if (is_array($options)) { - if (! empty($options['issuerSerial'])) { - $issuerSerial = true; - } - if (! empty($options['subjectName'])) { - $subjectName = true; - } - } - - // Attach all certificate nodes and any additional data - foreach ($certs as $X509Cert){ - if ($issuerSerial || $subjectName) { - if ($certData = openssl_x509_parse("-----BEGIN CERTIFICATE-----\n".chunk_split($X509Cert, 64, "\n")."-----END CERTIFICATE-----\n")) { - if ($subjectName && ! empty($certData['subject'])) { - if (is_array($certData['subject'])) { - $parts = array(); - foreach ($certData['subject'] AS $key => $value) { - if (is_array($value)) { - foreach ($value as $valueElement) { - array_unshift($parts, "$key=$valueElement"); - } - } else { - array_unshift($parts, "$key=$value"); - } - } - $subjectNameValue = implode(',', $parts); - } else { - $subjectNameValue = $certData['issuer']; - } - $x509SubjectNode = $baseDoc->createElementNS(XMLSecurityDSig::XMLDSIGNS, 'ds:X509SubjectName', $subjectNameValue); - $x509DataNode->appendChild($x509SubjectNode); - } - if ($issuerSerial && ! empty($certData['issuer']) && ! empty($certData['serialNumber'])) { - if (is_array($certData['issuer'])) { - $parts = array(); - foreach ($certData['issuer'] AS $key => $value) { - array_unshift($parts, "$key=$value"); - } - $issuerName = implode(',', $parts); - } else { - $issuerName = $certData['issuer']; - } - - $x509IssuerNode = $baseDoc->createElementNS(XMLSecurityDSig::XMLDSIGNS, 'ds:X509IssuerSerial'); - $x509DataNode->appendChild($x509IssuerNode); - - $x509Node = $baseDoc->createElementNS(XMLSecurityDSig::XMLDSIGNS, 'ds:X509IssuerName', $issuerName); - $x509IssuerNode->appendChild($x509Node); - $x509Node = $baseDoc->createElementNS(XMLSecurityDSig::XMLDSIGNS, 'ds:X509SerialNumber', $certData['serialNumber']); - $x509IssuerNode->appendChild($x509Node); - } - } - - } - $x509CertNode = $baseDoc->createElementNS(XMLSecurityDSig::XMLDSIGNS, 'ds:X509Certificate', $X509Cert); - $x509DataNode->appendChild($x509CertNode); - } - } - - public function add509Cert($cert, $isPEMFormat=true, $isURL=false, $options=null) { - if ($xpath = $this->getXPathObj()) { - self::staticAdd509Cert($this->sigNode, $cert, $isPEMFormat, $isURL, $xpath, $options); - } - } - - /** - * This function appends a node to the KeyInfo. - * - * The KeyInfo element will be created if one does not exist in the document. - * - * @param DOMNode $node The node to append to the KeyInfo. - * - * @return DOMNode The KeyInfo element node - */ - public function appendToKeyInfo($node) { - $parentRef = $this->sigNode; - - $xpath = $this->getXPathObj(); - - list($parentRef, $keyInfo) = self::auxKeyInfo($parentRef, $xpath); - - $keyInfo->appendChild($node); - - return $keyInfo; - } - - static function auxKeyInfo($parentRef, $xpath=null) - { - $baseDoc = $parentRef->ownerDocument; - if (empty($xpath)) { - $xpath = new DOMXPath($parentRef->ownerDocument); - $xpath->registerNamespace('secdsig', XMLSecurityDSig::XMLDSIGNS); - } - - $query = "./secdsig:KeyInfo"; - $nodeset = $xpath->query($query, $parentRef); - $keyInfo = $nodeset->item(0); - if (! $keyInfo) { - $inserted = false; - $keyInfo = $baseDoc->createElementNS(XMLSecurityDSig::XMLDSIGNS, 'ds:KeyInfo'); - - $query = "./secdsig:Object"; - $nodeset = $xpath->query($query, $parentRef); - if ($sObject = $nodeset->item(0)) { - $sObject->parentNode->insertBefore($keyInfo, $sObject); - $inserted = true; - } - - if (! $inserted) { - $parentRef->appendChild($keyInfo); - } - } - return array($parentRef, $keyInfo); - } - - /* This function retrieves an associative array of the validated nodes. - * - * The array will contain the id of the referenced node as the key and the node itself - * as the value. - * - * Returns: - * An associative array of validated nodes or null if no nodes have been validated. - */ - public function getValidatedNodes() { - return $this->validatedNodes; - } -} - - -class XMLSecEnc { - const template = " - - - -"; - - const Element = 'http://www.w3.org/2001/04/xmlenc#Element'; - const Content = 'http://www.w3.org/2001/04/xmlenc#Content'; - const URI = 3; - const XMLENCNS = 'http://www.w3.org/2001/04/xmlenc#'; - - private $encdoc = null; - private $rawNode = null; - public $type = null; - public $encKey = null; - private $references = array(); - - public function __construct() { - $this->_resetTemplate(); - } - - private function _resetTemplate(){ - $this->encdoc = new DOMDocument(); - $this->encdoc->loadXML(XMLSecEnc::template); - } - - public function addReference($name, $node, $type) { - if (! $node instanceOf DOMNode) { - throw new Exception('$node is not of type DOMNode'); - } - $curencdoc = $this->encdoc; - $this->_resetTemplate(); - $encdoc = $this->encdoc; - $this->encdoc = $curencdoc; - $refuri = XMLSecurityDSig::generateGUID(); - $element = $encdoc->documentElement; - $element->setAttribute("Id", $refuri); - $this->references[$name] = array("node" => $node, "type" => $type, "encnode" => $encdoc, "refuri" => $refuri); - } - - public function setNode($node) { - $this->rawNode = $node; - } - - /** - * Encrypt the selected node with the given key. - * - * @param XMLSecurityKey $objKey The encryption key and algorithm. - * @param bool $replace Whether the encrypted node should be replaced in the original tree. Default is true. - * @return DOMElement The -element. - */ - public function encryptNode($objKey, $replace=true) { - $data = ''; - if (empty($this->rawNode)) { - throw new Exception('Node to encrypt has not been set'); - } - if (! $objKey instanceof XMLSecurityKey) { - throw new Exception('Invalid Key'); - } - $doc = $this->rawNode->ownerDocument; - $xPath = new DOMXPath($this->encdoc); - $objList = $xPath->query('/xenc:EncryptedData/xenc:CipherData/xenc:CipherValue'); - $cipherValue = $objList->item(0); - if ($cipherValue == null) { - throw new Exception('Error locating CipherValue element within template'); - } - switch ($this->type) { - case (XMLSecEnc::Element): - $data = $doc->saveXML($this->rawNode); - $this->encdoc->documentElement->setAttribute('Type', XMLSecEnc::Element); - break; - case (XMLSecEnc::Content): - $children = $this->rawNode->childNodes; - foreach ($children AS $child) { - $data .= $doc->saveXML($child); - } - $this->encdoc->documentElement->setAttribute('Type', XMLSecEnc::Content); - break; - default: - throw new Exception('Type is currently not supported'); - } - - $encMethod = $this->encdoc->documentElement->appendChild($this->encdoc->createElementNS(XMLSecEnc::XMLENCNS, 'xenc:EncryptionMethod')); - $encMethod->setAttribute('Algorithm', $objKey->getAlgorithm()); - $cipherValue->parentNode->parentNode->insertBefore($encMethod, $cipherValue->parentNode->parentNode->firstChild); - - $strEncrypt = base64_encode($objKey->encryptData($data)); - $value = $this->encdoc->createTextNode($strEncrypt); - $cipherValue->appendChild($value); - - if ($replace) { - switch ($this->type) { - case (XMLSecEnc::Element): - if ($this->rawNode->nodeType == XML_DOCUMENT_NODE) { - return $this->encdoc; - } - $importEnc = $this->rawNode->ownerDocument->importNode($this->encdoc->documentElement, true); - $this->rawNode->parentNode->replaceChild($importEnc, $this->rawNode); - return $importEnc; - case (XMLSecEnc::Content): - $importEnc = $this->rawNode->ownerDocument->importNode($this->encdoc->documentElement, true); - while($this->rawNode->firstChild) { - $this->rawNode->removeChild($this->rawNode->firstChild); - } - $this->rawNode->appendChild($importEnc); - return $importEnc; - } - } else { - return $this->encdoc->documentElement; - } - } - - public function encryptReferences($objKey) { - $curRawNode = $this->rawNode; - $curType = $this->type; - foreach ($this->references AS $name=>$reference) { - $this->encdoc = $reference["encnode"]; - $this->rawNode = $reference["node"]; - $this->type = $reference["type"]; - try { - $encNode = $this->encryptNode($objKey); - $this->references[$name]["encnode"] = $encNode; - } catch (Exception $e) { - $this->rawNode = $curRawNode; - $this->type = $curType; - throw $e; - } - } - $this->rawNode = $curRawNode; - $this->type = $curType; - } - - /** - * Retrieve the CipherValue text from this encrypted node. - * - * @return string|null The Ciphervalue text, or null if no CipherValue is found. - */ - public function getCipherValue() { - if (empty($this->rawNode)) { - throw new Exception('Node to decrypt has not been set'); - } - - $doc = $this->rawNode->ownerDocument; - $xPath = new DOMXPath($doc); - $xPath->registerNamespace('xmlencr', XMLSecEnc::XMLENCNS); - /* Only handles embedded content right now and not a reference */ - $query = "./xmlencr:CipherData/xmlencr:CipherValue"; - $nodeset = $xPath->query($query, $this->rawNode); - $node = $nodeset->item(0); - - if (!$node) { - return null; - } - - return base64_decode($node->nodeValue); - } - - /** - * Decrypt this encrypted node. - * - * The behaviour of this function depends on the value of $replace. - * If $replace is false, we will return the decrypted data as a string. - * If $replace is true, we will insert the decrypted element(s) into the - * document, and return the decrypted element(s). - * - * @params XMLSecurityKey $objKey The decryption key that should be used when decrypting the node. - * @params boolean $replace Whether we should replace the encrypted node in the XML document with the decrypted data. The default is true. - * @return string|DOMElement The decrypted data. - */ - public function decryptNode($objKey, $replace=true) { - if (! $objKey instanceof XMLSecurityKey) { - throw new Exception('Invalid Key'); - } - - $encryptedData = $this->getCipherValue(); - if ($encryptedData) { - $decrypted = $objKey->decryptData($encryptedData); - if ($replace) { - switch ($this->type) { - case (XMLSecEnc::Element): - $newdoc = new DOMDocument(); - $newdoc->loadXML($decrypted); - if ($this->rawNode->nodeType == XML_DOCUMENT_NODE) { - return $newdoc; - } - $importEnc = $this->rawNode->ownerDocument->importNode($newdoc->documentElement, true); - $this->rawNode->parentNode->replaceChild($importEnc, $this->rawNode); - return $importEnc; - case (XMLSecEnc::Content): - if ($this->rawNode->nodeType == XML_DOCUMENT_NODE) { - $doc = $this->rawNode; - } else { - $doc = $this->rawNode->ownerDocument; - } - $newFrag = $doc->createDocumentFragment(); - $newFrag->appendXML($decrypted); - $parent = $this->rawNode->parentNode; - $parent->replaceChild($newFrag, $this->rawNode); - return $parent; - default: - return $decrypted; - } - } else { - return $decrypted; - } - } else { - throw new Exception("Cannot locate encrypted data"); - } - } - - public function encryptKey($srcKey, $rawKey, $append=true) { - if ((! $srcKey instanceof XMLSecurityKey) || (! $rawKey instanceof XMLSecurityKey)) { - throw new Exception('Invalid Key'); - } - $strEncKey = base64_encode($srcKey->encryptData($rawKey->key)); - $root = $this->encdoc->documentElement; - $encKey = $this->encdoc->createElementNS(XMLSecEnc::XMLENCNS, 'xenc:EncryptedKey'); - if ($append) { - $keyInfo = $root->insertBefore($this->encdoc->createElementNS('http://www.w3.org/2000/09/xmldsig#', 'dsig:KeyInfo'), $root->firstChild); - $keyInfo->appendChild($encKey); - } else { - $this->encKey = $encKey; - } - $encMethod = $encKey->appendChild($this->encdoc->createElementNS(XMLSecEnc::XMLENCNS, 'xenc:EncryptionMethod')); - $encMethod->setAttribute('Algorithm', $srcKey->getAlgorithm()); - if (! empty($srcKey->name)) { - $keyInfo = $encKey->appendChild($this->encdoc->createElementNS('http://www.w3.org/2000/09/xmldsig#', 'dsig:KeyInfo')); - $keyInfo->appendChild($this->encdoc->createElementNS('http://www.w3.org/2000/09/xmldsig#', 'dsig:KeyName', $srcKey->name)); - } - $cipherData = $encKey->appendChild($this->encdoc->createElementNS(XMLSecEnc::XMLENCNS, 'xenc:CipherData')); - $cipherData->appendChild($this->encdoc->createElementNS(XMLSecEnc::XMLENCNS, 'xenc:CipherValue', $strEncKey)); - if (is_array($this->references) && count($this->references) > 0) { - $refList = $encKey->appendChild($this->encdoc->createElementNS(XMLSecEnc::XMLENCNS, 'xenc:ReferenceList')); - foreach ($this->references AS $name=>$reference) { - $refuri = $reference["refuri"]; - $dataRef = $refList->appendChild($this->encdoc->createElementNS(XMLSecEnc::XMLENCNS, 'xenc:DataReference')); - $dataRef->setAttribute("URI", '#' . $refuri); - } - } - return; - } - - public function decryptKey($encKey) { - if (! $encKey->isEncrypted) { - throw new Exception("Key is not Encrypted"); - } - if (empty($encKey->key)) { - throw new Exception("Key is missing data to perform the decryption"); - } - return $this->decryptNode($encKey, false); - } - - public function locateEncryptedData($element) { - if ($element instanceof DOMDocument) { - $doc = $element; - } else { - $doc = $element->ownerDocument; - } - if ($doc) { - $xpath = new DOMXPath($doc); - $query = "//*[local-name()='EncryptedData' and namespace-uri()='".XMLSecEnc::XMLENCNS."']"; - $nodeset = $xpath->query($query); - return $nodeset->item(0); - } - return null; - } - - public function locateKey($node=null) { - if (empty($node)) { - $node = $this->rawNode; - } - if (! $node instanceof DOMNode) { - return null; - } - if ($doc = $node->ownerDocument) { - $xpath = new DOMXPath($doc); - $xpath->registerNamespace('xmlsecenc', XMLSecEnc::XMLENCNS); - $query = ".//xmlsecenc:EncryptionMethod"; - $nodeset = $xpath->query($query, $node); - if ($encmeth = $nodeset->item(0)) { - $attrAlgorithm = $encmeth->getAttribute("Algorithm"); - try { - $objKey = new XMLSecurityKey($attrAlgorithm, array('type'=>'private')); - } catch (Exception $e) { - return null; - } - return $objKey; - } - } - return null; - } - - static function staticLocateKeyInfo($objBaseKey=null, $node=null) { - if (empty($node) || (! $node instanceof DOMNode)) { - return null; - } - $doc = $node->ownerDocument; - if (!$doc) { - return null; - } - - $xpath = new DOMXPath($doc); - $xpath->registerNamespace('xmlsecenc', XMLSecEnc::XMLENCNS); - $xpath->registerNamespace('xmlsecdsig', XMLSecurityDSig::XMLDSIGNS); - $query = "./xmlsecdsig:KeyInfo"; - $nodeset = $xpath->query($query, $node); - $encmeth = $nodeset->item(0); - if (!$encmeth) { - /* No KeyInfo in EncryptedData / EncryptedKey. */ - return $objBaseKey; - } - - foreach ($encmeth->childNodes AS $child) { - switch ($child->localName) { - case 'KeyName': - if (! empty($objBaseKey)) { - $objBaseKey->name = $child->nodeValue; - } - break; - case 'KeyValue': - foreach ($child->childNodes AS $keyval) { - switch ($keyval->localName) { - case 'DSAKeyValue': - throw new Exception("DSAKeyValue currently not supported"); - case 'RSAKeyValue': - $modulus = null; - $exponent = null; - if ($modulusNode = $keyval->getElementsByTagName('Modulus')->item(0)) { - $modulus = base64_decode($modulusNode->nodeValue); - } - if ($exponentNode = $keyval->getElementsByTagName('Exponent')->item(0)) { - $exponent = base64_decode($exponentNode->nodeValue); - } - if (empty($modulus) || empty($exponent)) { - throw new Exception("Missing Modulus or Exponent"); - } - $publicKey = XMLSecurityKey::convertRSA($modulus, $exponent); - $objBaseKey->loadKey($publicKey); - break; - } - } - break; - case 'RetrievalMethod': - $type = $child->getAttribute('Type'); - if ($type !== 'http://www.w3.org/2001/04/xmlenc#EncryptedKey') { - /* Unsupported key type. */ - break; - } - $uri = $child->getAttribute('URI'); - if ($uri[0] !== '#') { - /* URI not a reference - unsupported. */ - break; - } - $id = substr($uri, 1); - - $query = "//xmlsecenc:EncryptedKey[@Id='$id']"; - $keyElement = $xpath->query($query)->item(0); - if (!$keyElement) { - throw new Exception("Unable to locate EncryptedKey with @Id='$id'."); - } - - return XMLSecurityKey::fromEncryptedKeyElement($keyElement); - case 'EncryptedKey': - return XMLSecurityKey::fromEncryptedKeyElement($child); - case 'X509Data': - if ($x509certNodes = $child->getElementsByTagName('X509Certificate')) { - if ($x509certNodes->length > 0) { - $x509cert = $x509certNodes->item(0)->textContent; - $x509cert = str_replace(array("\r", "\n", " "), "", $x509cert); - $x509cert = "-----BEGIN CERTIFICATE-----\n".chunk_split($x509cert, 64, "\n")."-----END CERTIFICATE-----\n"; - $objBaseKey->loadKey($x509cert, false, true); - } - } - break; - } - } - return $objBaseKey; - } - - public function locateKeyInfo($objBaseKey=null, $node=null) { - if (empty($node)) { - $node = $this->rawNode; - } - return XMLSecEnc::staticLocateKeyInfo($objBaseKey, $node); - } -} +$xmlseclibs_srcdir = dirname(__FILE__) . '/src/'; +require $xmlseclibs_srcdir . '/XMLSecurityKey.php'; +require $xmlseclibs_srcdir . '/XMLSecurityDSig.php'; +require $xmlseclibs_srcdir . '/XMLSecEnc.php'; +require $xmlseclibs_srcdir . '/Utils/XPath.php'; diff --git a/onelogin-saml-sso/php/functions.php b/onelogin-saml-sso/php/functions.php index 6335c3c..d0b80c2 100644 --- a/onelogin-saml-sso/php/functions.php +++ b/onelogin-saml-sso/php/functions.php @@ -6,6 +6,9 @@ exit; } +use OneLogin\Saml2\Auth; +use OneLogin\Saml2\Settings; + require_once "compatibility.php"; function saml_checker() { @@ -384,7 +387,7 @@ function saml_metadata() { require_once plugin_dir_path(__FILE__).'_toolkit_loader.php'; require plugin_dir_path(__FILE__).'settings.php'; - $samlSettings = new OneLogin_Saml2_Settings($settings, true); + $samlSettings = new Settings($settings, true); $metadata = $samlSettings->getSPMetadata(); header('Content-Type: text/xml'); @@ -410,8 +413,8 @@ function initialize_saml() { } try { - $auth = new Onelogin_Saml2_Auth($settings); - } catch (Exception $e) { + $auth = new Auth($settings); + } catch (\Exception $e) { echo '
'.__("The Onelogin SSO/SAML plugin is not correctly configured.", 'onelogin-saml-sso').'
'; echo esc_html($e->getMessage()); echo '
'.__("If you are the administrator", 'onelogin-saml-sso').', '.__("access using your wordpress credentials", 'onelogin-saml-sso').' '.__("and fix the problem", 'onelogin-saml-sso'); diff --git a/onelogin-saml-sso/php/lib/Saml2/Auth.php b/onelogin-saml-sso/php/lib/Saml2/Auth.php index 429d965..636c034 100644 --- a/onelogin-saml-sso/php/lib/Saml2/Auth.php +++ b/onelogin-saml-sso/php/lib/Saml2/Auth.php @@ -1,15 +1,33 @@ + * @license MIT https://github.com/onelogin/php-saml/blob/master/LICENSE + * @link https://github.com/onelogin/php-saml + */ + +namespace OneLogin\Saml2; + +use RobRichards\XMLSecLibs\XMLSecurityKey; + +use Exception; /** * Main class of OneLogin's PHP Toolkit - * */ -class OneLogin_Saml2_Auth +class Auth { /** * Settings data. * - * @var OneLogin_Saml2_Settings + * @var Settings */ private $_settings; @@ -41,7 +59,6 @@ class OneLogin_Saml2_Auth */ private $_nameidFormat; - /** * NameID NameQualifier * @@ -49,6 +66,13 @@ class OneLogin_Saml2_Auth */ private $_nameidNameQualifier; + /** + * NameID SP NameQualifier + * + * @var string + */ + private $_nameidSPNameQualifier; + /** * If user is authenticated. * @@ -103,11 +127,18 @@ class OneLogin_Saml2_Auth private $_errors = array(); /** - * Reason of the last error. + * Last error object. + * + * @var Error|null + */ + private $_lastErrorException; + + /** + * Last error. * * @var string|null */ - private $_errorReason; + private $_lastError; /** * Last AuthNRequest ID or LogoutRequest ID generated by this Service Provider @@ -136,19 +167,20 @@ class OneLogin_Saml2_Auth /** * Initializes the SP SAML instance. * - * @param array|object|null $oldSettings Setting data (You can provide a OneLogin_Saml_Settings, the settings object of the Saml folder implementation) + * @param array|null $settings Setting data * - * @throws OneLogin_Saml2_Error + * @throws Exception + * @throws Error */ - public function __construct($oldSettings = null) + public function __construct(array $settings = null) { - $this->_settings = new OneLogin_Saml2_Settings($oldSettings); + $this->_settings = new Settings($settings); } /** * Returns the settings info * - * @return OneLogin_Saml2_Settings The settings data. + * @return Settings The settings data. */ public function getSettings() { @@ -160,14 +192,14 @@ public function getSettings() * * @param bool $value Strict parameter * - * @throws OneLogin_Saml2_Error + * @throws Error */ public function setStrict($value) { if (!is_bool($value)) { - throw new OneLogin_Saml2_Error( + throw new Error( 'Invalid value passed to setStrict()', - OneLogin_Saml2_Error::SETTINGS_INVALID_SYNTAX + Error::SETTINGS_INVALID_SYNTAX ); } @@ -179,16 +211,16 @@ public function setStrict($value) * * @param string|null $requestId The ID of the AuthNRequest sent by this SP to the IdP * - * @throws OneLogin_Saml2_Error - * @throws OneLogin_Saml2_ValidationError + * @throws Error + * @throws ValidationError */ public function processResponse($requestId = null) { $this->_errors = array(); - $this->_errorReason = null; + $this->_lastError = $this->_lastErrorException = null; if (isset($_POST['SAMLResponse'])) { // AuthnResponse -- HTTP_POST Binding - $response = new OneLogin_Saml2_Response($this->_settings, $_POST['SAMLResponse']); + $response = new Response($this->_settings, $_POST['SAMLResponse']); $this->_lastResponse = $response->getXMLDocument(); if ($response->isValid($requestId)) { @@ -197,6 +229,7 @@ public function processResponse($requestId = null) $this->_nameid = $response->getNameId(); $this->_nameidFormat = $response->getNameIdFormat(); $this->_nameidNameQualifier = $response->getNameIdNameQualifier(); + $this->_nameidSPNameQualifier = $response->getNameIdSPNameQualifier(); $this->_authenticated = true; $this->_sessionIndex = $response->getSessionIndex(); $this->_sessionExpiration = $response->getSessionNotOnOrAfter(); @@ -205,13 +238,14 @@ public function processResponse($requestId = null) $this->_lastAssertionNotOnOrAfter = $response->getAssertionNotOnOrAfter(); } else { $this->_errors[] = 'invalid_response'; - $this->_errorReason = $response->getError(); + $this->_lastErrorException = $response->getErrorException(); + $this->_lastError = $response->getError(); } } else { $this->_errors[] = 'invalid_binding'; - throw new OneLogin_Saml2_Error( + throw new Error( 'SAML Response not found, Only supported HTTP_POST Binding', - OneLogin_Saml2_Error::SAML_RESPONSE_NOT_FOUND + Error::SAML_RESPONSE_NOT_FOUND ); } } @@ -227,47 +261,50 @@ public function processResponse($requestId = null) * * @return string|null * - * @throws OneLogin_Saml2_Error + * @throws Error */ public function processSLO($keepLocalSession = false, $requestId = null, $retrieveParametersFromServer = false, $cbDeleteSession = null, $stay = false) { $this->_errors = array(); - $this->_errorReason = null; + $this->_lastError = $this->_lastErrorException = null; if (isset($_GET['SAMLResponse'])) { - $logoutResponse = new OneLogin_Saml2_LogoutResponse($this->_settings, $_GET['SAMLResponse']); + $logoutResponse = new LogoutResponse($this->_settings, $_GET['SAMLResponse']); $this->_lastResponse = $logoutResponse->getXML(); if (!$logoutResponse->isValid($requestId, $retrieveParametersFromServer)) { $this->_errors[] = 'invalid_logout_response'; - $this->_errorReason = $logoutResponse->getError(); - } else if ($logoutResponse->getStatus() !== OneLogin_Saml2_Constants::STATUS_SUCCESS) { + $this->_lastErrorException = $logoutResponse->getErrorException(); + $this->_lastError = $logoutResponse->getError(); + + } else if ($logoutResponse->getStatus() !== Constants::STATUS_SUCCESS) { $this->_errors[] = 'logout_not_success'; } else { $this->_lastMessageId = $logoutResponse->id; if (!$keepLocalSession) { if ($cbDeleteSession === null) { - OneLogin_Saml2_Utils::deleteLocalSession(); + Utils::deleteLocalSession(); } else { call_user_func($cbDeleteSession); } } } } else if (isset($_GET['SAMLRequest'])) { - $logoutRequest = new OneLogin_Saml2_LogoutRequest($this->_settings, $_GET['SAMLRequest']); + $logoutRequest = new LogoutRequest($this->_settings, $_GET['SAMLRequest']); $this->_lastRequest = $logoutRequest->getXML(); if (!$logoutRequest->isValid($retrieveParametersFromServer)) { $this->_errors[] = 'invalid_logout_request'; - $this->_errorReason = $logoutRequest->getError(); + $this->_lastErrorException = $logoutRequest->getErrorException(); + $this->_lastError = $logoutRequest->getError(); } else { if (!$keepLocalSession) { if ($cbDeleteSession === null) { - OneLogin_Saml2_Utils::deleteLocalSession(); + Utils::deleteLocalSession(); } else { call_user_func($cbDeleteSession); } } $inResponseTo = $logoutRequest->id; $this->_lastMessageId = $logoutRequest->id; - $responseBuilder = new OneLogin_Saml2_LogoutResponse($this->_settings); + $responseBuilder = new LogoutResponse($this->_settings); $responseBuilder->build($inResponseTo); $this->_lastResponse = $responseBuilder->getXML(); @@ -289,9 +326,9 @@ public function processSLO($keepLocalSession = false, $requestId = null, $retrie } } else { $this->_errors[] = 'invalid_binding'; - throw new OneLogin_Saml2_Error( + throw new Error( 'SAML LogoutRequest/LogoutResponse not found. Only supported HTTP_REDIRECT Binding', - OneLogin_Saml2_Error::SAML_LOGOUTMESSAGE_NOT_FOUND + Error::SAML_LOGOUTMESSAGE_NOT_FOUND ); } } @@ -300,24 +337,21 @@ public function processSLO($keepLocalSession = false, $requestId = null, $retrie * Redirects the user to the url past by parameter * or to the url that we defined in our SSO Request. * - * @param string $url The target URL to redirect the user. - * @param array $parameters Extra parameters to be passed as part of the url - * @param bool $stay True if we want to stay (returns the url string) False to redirect + * @param string $url The target URL to redirect the user. + * @param array $parameters Extra parameters to be passed as part of the url + * @param bool $stay True if we want to stay (returns the url string) False to redirect * * @return string|null - * - * @throws OneLogin_Saml2_Error */ - public function redirectTo($url = '', $parameters = array(), $stay = false) + public function redirectTo($url = '', array $parameters = array(), $stay = false) { - assert('is_string($url)'); - assert('is_array($parameters)'); + assert(is_string($url)); if (empty($url) && isset($_REQUEST['RelayState'])) { $url = $_REQUEST['RelayState']; } - return OneLogin_Saml2_Utils::redirect($url, $parameters, $stay); + return Utils::redirect($url, $parameters, $stay); } /** @@ -340,6 +374,7 @@ public function getAttributes() return $this->_attributes; } + /** * Returns the set of SAML attributes indexed by FriendlyName * @@ -380,6 +415,16 @@ public function getNameIdNameQualifier() return $this->_nameidNameQualifier; } + /** + * Returns the nameID SP NameQualifier + * + * @return string The nameID SP NameQualifier of the assertion + */ + public function getNameIdSPNameQualifier() + { + return $this->_nameidSPNameQualifier; + } + /** * Returns the SessionIndex * @@ -413,11 +458,22 @@ public function getErrors() /** * Returns the reason for the last error * - * @return string|null Error reason + * @return string|null Error reason */ public function getLastErrorReason() { - return $this->_errorReason; + return $this->_lastError; + } + + + /** + * Returns the last error + * + * @return Exception|null Error + */ + public function getLastErrorException() + { + return $this->_lastErrorException; } /** @@ -429,7 +485,7 @@ public function getLastErrorReason() */ public function getAttribute($name) { - assert('is_string($name)'); + assert(is_string($name)); $value = null; if (isset($this->_attributes[$name])) { @@ -447,8 +503,7 @@ public function getAttribute($name) */ public function getAttributeWithFriendlyName($friendlyName) { - assert('is_string($friendlyName)'); - + assert(is_string($friendlyName)); $value = null; if (isset($this->_attributesWithFriendlyName[$friendlyName])) { return $this->_attributesWithFriendlyName[$friendlyName]; @@ -459,22 +514,20 @@ public function getAttributeWithFriendlyName($friendlyName) /** * Initiates the SSO process. * - * @param string|null $returnTo The target URL the user should be returned to after login. - * @param array $parameters Extra parameters to be added to the GET - * @param bool $forceAuthn When true the AuthNRequest will set the ForceAuthn='true' - * @param bool $isPassive When true the AuthNRequest will set the Ispassive='true' - * @param bool $stay True if we want to stay (returns the url string) False to redirect - * @param bool $setNameIdPolicy When true the AuthNRueqest will set a nameIdPolicy element + * @param string|null $returnTo The target URL the user should be returned to after login. + * @param array $parameters Extra parameters to be added to the GET + * @param bool $forceAuthn When true the AuthNRequest will set the ForceAuthn='true' + * @param bool $isPassive When true the AuthNRequest will set the Ispassive='true' + * @param bool $stay True if we want to stay (returns the url string) False to redirect + * @param bool $setNameIdPolicy When true the AuthNRequest will set a nameIdPolicy element * * @return string|null If $stay is True, it return a string with the SLO URL + LogoutRequest + parameters - * - * @throws OneLogin_Saml2_Error + * + * @throws Error */ - public function login($returnTo = null, $parameters = array(), $forceAuthn = false, $isPassive = false, $stay = false, $setNameIdPolicy = true) + public function login($returnTo = null, array $parameters = array(), $forceAuthn = false, $isPassive = false, $stay = false, $setNameIdPolicy = true) { - assert('is_array($parameters)'); - - $authnRequest = new OneLogin_Saml2_AuthnRequest($this->_settings, $forceAuthn, $isPassive, $setNameIdPolicy); + $authnRequest = new AuthnRequest($this->_settings, $forceAuthn, $isPassive, $setNameIdPolicy); $this->_lastRequest = $authnRequest->getXML(); $this->_lastRequestID = $authnRequest->getId(); @@ -485,7 +538,7 @@ public function login($returnTo = null, $parameters = array(), $forceAuthn = fal if (!empty($returnTo)) { $parameters['RelayState'] = $returnTo; } else { - $parameters['RelayState'] = OneLogin_Saml2_Utils::getSelfRoutedURLNoQuery(); + $parameters['RelayState'] = Utils::getSelfRoutedURLNoQuery(); } $security = $this->_settings->getSecurityData(); @@ -510,17 +563,15 @@ public function login($returnTo = null, $parameters = array(), $forceAuthn = fal * * @return string|null If $stay is True, it return a string with the SLO URL + LogoutRequest + parameters * - * @throws OneLogin_Saml2_Error + * @throws Error */ - public function logout($returnTo = null, $parameters = array(), $nameId = null, $sessionIndex = null, $stay = false, $nameIdFormat = null, $nameIdNameQualifier = null) + public function logout($returnTo = null, array $parameters = array(), $nameId = null, $sessionIndex = null, $stay = false, $nameIdFormat = null, $nameIdNameQualifier = null, $nameIdSPNameQualifier = null) { - assert('is_array($parameters)'); - $sloUrl = $this->getSLOurl(); if (empty($sloUrl)) { - throw new OneLogin_Saml2_Error( + throw new Error( 'The IdP does not support Single Log Out', - OneLogin_Saml2_Error::SAML_SINGLE_LOGOUT_NOT_SUPPORTED + Error::SAML_SINGLE_LOGOUT_NOT_SUPPORTED ); } @@ -531,7 +582,7 @@ public function logout($returnTo = null, $parameters = array(), $nameId = null, $nameIdFormat = $this->_nameidFormat; } - $logoutRequest = new OneLogin_Saml2_LogoutRequest($this->_settings, null, $nameId, $sessionIndex, $nameIdFormat, $nameIdNameQualifier); + $logoutRequest = new LogoutRequest($this->_settings, null, $nameId, $sessionIndex, $nameIdFormat, $nameIdNameQualifier, $nameIdSPNameQualifier); $this->_lastRequest = $logoutRequest->getXML(); $this->_lastRequestID = $logoutRequest->id; @@ -542,7 +593,7 @@ public function logout($returnTo = null, $parameters = array(), $nameId = null, if (!empty($returnTo)) { $parameters['RelayState'] = $returnTo; } else { - $parameters['RelayState'] = OneLogin_Saml2_Utils::getSelfRoutedURLNoQuery(); + $parameters['RelayState'] = Utils::getSelfRoutedURLNoQuery(); } $security = $this->_settings->getSecurityData(); @@ -594,43 +645,18 @@ public function getLastRequestID() /** * Generates the Signature for a SAML Request * - * @param string $samlRequest The SAML Request - * @param string $relayState The RelayState + * @param string $samlRequest The SAML Request + * @param string $relayState The RelayState * @param string $signAlgorithm Signature algorithm method * * @return string A base64 encoded signature * - * @throws OneLogin_Saml2_Error + * @throws Exception + * @throws Error */ - public function buildRequestSignature($samlRequest, $relayState, $signAlgorithm = XMLSecurityKey::RSA_SHA1) + public function buildRequestSignature($samlRequest, $relayState, $signAlgorithm = XMLSecurityKey::RSA_SHA256) { - $key = $this->_settings->getSPkey(); - if (empty($key)) { - throw new OneLogin_Saml2_Error( - "Trying to sign the SAML Request but can't load the SP private key", - OneLogin_Saml2_Error::PRIVATE_KEY_NOT_FOUND - ); - } - - $objKey = new XMLSecurityKey($signAlgorithm, array('type' => 'private')); - $objKey->loadKey($key, false); - - $security = $this->_settings->getSecurityData(); - if ($security['lowercaseUrlencoding']) { - $msg = 'SAMLRequest='.rawurlencode($samlRequest); - if (isset($relayState)) { - $msg .= '&RelayState='.rawurlencode($relayState); - } - $msg .= '&SigAlg=' . rawurlencode($signAlgorithm); - } else { - $msg = 'SAMLRequest='.urlencode($samlRequest); - if (isset($relayState)) { - $msg .= '&RelayState='.urlencode($relayState); - } - $msg .= '&SigAlg=' . urlencode($signAlgorithm); - } - $signature = $objKey->signData($msg); - return base64_encode($signature); + return $this->buildMessageSignature($samlRequest, $relayState, $signAlgorithm, "SAMLRequest"); } /** @@ -642,16 +668,38 @@ public function buildRequestSignature($samlRequest, $relayState, $signAlgorithm * * @return string A base64 encoded signature * - * @throws OneLogin_Saml2_Error + * @throws Exception + * @throws Error */ - public function buildResponseSignature($samlResponse, $relayState, $signAlgorithm = XMLSecurityKey::RSA_SHA1) + public function buildResponseSignature($samlResponse, $relayState, $signAlgorithm = XMLSecurityKey::RSA_SHA256) + { + return $this->buildMessageSignature($samlResponse, $relayState, $signAlgorithm, "SAMLResponse"); + } + + /** + * Generates the Signature for a SAML Message + * + * @param string $samlMessage The SAML Message + * @param string $relayState The RelayState + * @param string $signAlgorithm Signature algorithm method + * @param string $type "SAMLRequest" or "SAMLResponse" + * + * @return string A base64 encoded signature + * + * @throws Exception + * @throws Error + */ + private function buildMessageSignature($samlMessage, $relayState, $signAlgorithm = XMLSecurityKey::RSA_SHA256, $type="SAMLRequest") { $key = $this->_settings->getSPkey(); if (empty($key)) { - throw new OneLogin_Saml2_Error( - "Trying to sign the SAML Response but can't load the SP private key", - OneLogin_Saml2_Error::PRIVATE_KEY_NOT_FOUND - ); + if ($type == "SAMLRequest") { + $errorMsg = "Trying to sign the SAML Request but can't load the SP private key"; + } else { + $errorMsg = "Trying to sign the SAML Response but can't load the SP private key"; + } + + throw new Error($errorMsg, Error::PRIVATE_KEY_NOT_FOUND); } $objKey = new XMLSecurityKey($signAlgorithm, array('type' => 'private')); @@ -659,13 +707,13 @@ public function buildResponseSignature($samlResponse, $relayState, $signAlgorith $security = $this->_settings->getSecurityData(); if ($security['lowercaseUrlencoding']) { - $msg = 'SAMLResponse='.rawurlencode($samlResponse); + $msg = $type.'='.rawurlencode($samlMessage); if (isset($relayState)) { $msg .= '&RelayState='.rawurlencode($relayState); } $msg .= '&SigAlg=' . rawurlencode($signAlgorithm); } else { - $msg = 'SAMLResponse='.urlencode($samlResponse); + $msg = $type.'='.urlencode($samlMessage); if (isset($relayState)) { $msg .= '&RelayState='.urlencode($relayState); } @@ -705,7 +753,7 @@ public function getLastAssertionNotOnOrAfter() * Returns the most recently-constructed/processed * XML SAML request (AuthNRequest, LogoutRequest) * - * @return string The Request XML + * @return string|null The Request XML */ public function getLastRequestXML() { @@ -730,7 +778,7 @@ public function getLastResponseXML() $response = $this->_lastResponse->saveXML(); } } - + return $response; } } diff --git a/onelogin-saml-sso/php/lib/Saml2/AuthnRequest.php b/onelogin-saml-sso/php/lib/Saml2/AuthnRequest.php index 8418ee3..2dd6bd2 100644 --- a/onelogin-saml-sso/php/lib/Saml2/AuthnRequest.php +++ b/onelogin-saml-sso/php/lib/Saml2/AuthnRequest.php @@ -1,26 +1,42 @@ + * @license MIT https://github.com/onelogin/php-saml/blob/master/LICENSE + * @link https://github.com/onelogin/php-saml + */ + +namespace OneLogin\Saml2; /** * SAML 2 Authentication Request - * */ -class OneLogin_Saml2_AuthnRequest +class AuthnRequest { - /** * Object that represents the setting info - * @var OneLogin_Saml2_Settings + * + * @var Settings */ protected $_settings; /** * SAML AuthNRequest string + * * @var string */ private $_authnRequest; /** * SAML AuthNRequest ID. + * * @var string */ private $_id; @@ -28,12 +44,12 @@ class OneLogin_Saml2_AuthnRequest /** * Constructs the AuthnRequest object. * - * @param OneLogin_Saml2_Settings $settings Settings - * @param bool $forceAuthn When true the AuthNReuqest will set the ForceAuthn='true' - * @param bool $isPassive When true the AuthNReuqest will set the Ispassive='true' - * @param bool $setNameIdPolicy When true the AuthNReuqest will set a nameIdPolicy + * @param Settings $settings SAML Toolkit Settings + * @param bool $forceAuthn When true the AuthNReuqest will set the ForceAuthn='true' + * @param bool $isPassive When true the AuthNReuqest will set the Ispassive='true' + * @param bool $setNameIdPolicy When true the AuthNReuqest will set a nameIdPolicy */ - public function __construct(OneLogin_Saml2_Settings $settings, $forceAuthn = false, $isPassive = false, $setNameIdPolicy = true) + public function __construct(\OneLogin\Saml2\Settings $settings, $forceAuthn = false, $isPassive = false, $setNameIdPolicy = true) { $this->_settings = $settings; @@ -41,14 +57,14 @@ public function __construct(OneLogin_Saml2_Settings $settings, $forceAuthn = fal $idpData = $this->_settings->getIdPData(); $security = $this->_settings->getSecurityData(); - $id = OneLogin_Saml2_Utils::generateUniqueID(); - $issueInstant = OneLogin_Saml2_Utils::parseTime2SAML(time()); + $id = Utils::generateUniqueID(); + $issueInstant = Utils::parseTime2SAML(time()); $nameIdPolicyStr = ''; if ($setNameIdPolicy) { $nameIDPolicyFormat = $spData['NameIDFormat']; if (isset($security['wantNameIdEncrypted']) && $security['wantNameIdEncrypted']) { - $nameIDPolicyFormat = OneLogin_Saml2_Constants::NAMEID_ENCRYPTED; + $nameIDPolicyFormat = Constants::NAMEID_ENCRYPTED; } $nameIdPolicyStr = << + * @license MIT https://github.com/onelogin/php-saml/blob/master/LICENSE + * @link https://github.com/onelogin/php-saml + */ + +namespace OneLogin\Saml2; + /** * Constants of OneLogin PHP Toolkit * * Defines all required constants */ -class OneLogin_Saml2_Constants +class Constants { // Value added to the current time in time condition validations const ALLOWED_CLOCK_DRIFT = 180; // 3 min in seconds diff --git a/onelogin-saml-sso/php/lib/Saml2/Error.php b/onelogin-saml-sso/php/lib/Saml2/Error.php index a3d9bd4..211acf4 100644 --- a/onelogin-saml-sso/php/lib/Saml2/Error.php +++ b/onelogin-saml-sso/php/lib/Saml2/Error.php @@ -1,11 +1,28 @@ + * @license MIT https://github.com/onelogin/php-saml/blob/master/LICENSE + * @link https://github.com/onelogin/php-saml + */ + +namespace OneLogin\Saml2; + +use Exception; + /** * Error class of OneLogin PHP Toolkit * * Defines the Error class */ -class OneLogin_Saml2_Error extends Exception +class Error extends Exception { // Errors const SETTINGS_FILE_NOT_FOUND = 0; @@ -33,88 +50,16 @@ class OneLogin_Saml2_Error extends Exception * @param int $code The code error (defined in the error class). * @param array|null $args Arguments used in the message that describes the error. */ - public function __construct($msg, $code = 0, $args = null) - { - assert('is_string($msg)'); - assert('is_int($code)'); - - $message = OneLogin_Saml2_Utils::t($msg, $args); - - parent::__construct($message, $code); - } -} - -/** - * This class implements another custom Exception handler, - * related to exceptions that happens during validation process. - */ -class OneLogin_Saml2_ValidationError extends Exception -{ - # Validation Errors - const UNSUPPORTED_SAML_VERSION = 0; - const MISSING_ID = 1; - const WRONG_NUMBER_OF_ASSERTIONS = 2; - const MISSING_STATUS = 3; - const MISSING_STATUS_CODE = 4; - const STATUS_CODE_IS_NOT_SUCCESS = 5; - const WRONG_SIGNED_ELEMENT = 6; - const ID_NOT_FOUND_IN_SIGNED_ELEMENT = 7; - const DUPLICATED_ID_IN_SIGNED_ELEMENTS = 8; - const INVALID_SIGNED_ELEMENT = 9; - const DUPLICATED_REFERENCE_IN_SIGNED_ELEMENTS = 10; - const UNEXPECTED_SIGNED_ELEMENTS = 11; - const WRONG_NUMBER_OF_SIGNATURES_IN_RESPONSE = 12; - const WRONG_NUMBER_OF_SIGNATURES_IN_ASSERTION = 13; - const INVALID_XML_FORMAT = 14; - const WRONG_INRESPONSETO = 15; - const NO_ENCRYPTED_ASSERTION = 16; - const NO_ENCRYPTED_NAMEID = 17; - const MISSING_CONDITIONS = 18; - const ASSERTION_TOO_EARLY = 19; - const ASSERTION_EXPIRED = 20; - const WRONG_NUMBER_OF_AUTHSTATEMENTS = 21; - const NO_ATTRIBUTESTATEMENT = 22; - const ENCRYPTED_ATTRIBUTES = 23; - const WRONG_DESTINATION = 24; - const EMPTY_DESTINATION = 25; - const WRONG_AUDIENCE = 26; - const ISSUER_MULTIPLE_IN_RESPONSE = 27; - const ISSUER_NOT_FOUND_IN_ASSERTION = 28; - const WRONG_ISSUER = 29; - const SESSION_EXPIRED = 30; - const WRONG_SUBJECTCONFIRMATION = 31; - const NO_SIGNED_MESSAGE = 32; - const NO_SIGNED_ASSERTION = 33; - const NO_SIGNATURE_FOUND = 34; - const KEYINFO_NOT_FOUND_IN_ENCRYPTED_DATA = 35; - const CHILDREN_NODE_NOT_FOUND_IN_KEYINFO = 36; - const UNSUPPORTED_RETRIEVAL_METHOD = 37; - const NO_NAMEID = 38; - const EMPTY_NAMEID = 39; - const SP_NAME_QUALIFIER_NAME_MISMATCH = 40; - const DUPLICATED_ATTRIBUTE_NAME_FOUND = 41; - const INVALID_SIGNATURE = 42; - const WRONG_NUMBER_OF_SIGNATURES = 43; - const RESPONSE_EXPIRED = 44; - const UNEXPECTED_REFERENCE = 45; - const NOT_SUPPORTED = 46; - const KEY_ALGORITHM_ERROR = 47; - const MISSING_ENCRYPTED_ELEMENT = 48; - - - /** - * Constructor - * - * @param string $msg Describes the error. - * @param int $code The code error (defined in the error class). - * @param array|null $args Arguments used in the message that describes the error. - */ - public function __construct($msg, $code = 0, $args = null) + public function __construct($msg, $code = 0, $args = array()) { - assert('is_string($msg)'); - assert('is_int($code)'); + assert(is_string($msg)); + assert(is_int($code)); - $message = OneLogin_Saml2_Utils::t($msg, $args); + if (!isset($args)) { + $args = array(); + } + $params = array_merge(array($msg), $args); + $message = call_user_func_array('sprintf', $params); parent::__construct($message, $code); } diff --git a/onelogin-saml-sso/php/lib/Saml2/IdPMetadataParser.php b/onelogin-saml-sso/php/lib/Saml2/IdPMetadataParser.php index 5ad795c..5bce8a6 100644 --- a/onelogin-saml-sso/php/lib/Saml2/IdPMetadataParser.php +++ b/onelogin-saml-sso/php/lib/Saml2/IdPMetadataParser.php @@ -1,27 +1,43 @@ + * @license MIT https://github.com/onelogin/php-saml/blob/master/LICENSE + * @link https://github.com/onelogin/php-saml */ -class OneLogin_Saml2_IdPMetadataParser +namespace OneLogin\Saml2; + +use DOMDocument; +use Exception; + +/** + * IdP Metadata Parser of OneLogin PHP Toolkit + */ +class IdPMetadataParser { /** * Get IdP Metadata Info from URL * - * @param string $url URL where the IdP metadata is published - * @param string $entityId Entity Id of the desired IdP, if no - * entity Id is provided and the XML - * metadata contains more than one - * IDPSSODescriptor, the first is returned - * @param string $desiredNameIdFormat If available on IdP metadata, use that nameIdFormat - * @param string $desiredSSOBinding Parse specific binding SSO endpoint. - * @param string $desiredSLOBinding Parse specific binding SLO endpoint. + * @param string $url URL where the IdP metadata is published + * @param string $entityId Entity Id of the desired IdP, if no + * entity Id is provided and the XML + * metadata contains more than one + * IDPSSODescriptor, the first is returned + * @param string $desiredNameIdFormat If available on IdP metadata, use that nameIdFormat + * @param string $desiredSSOBinding Parse specific binding SSO endpoint + * @param string $desiredSLOBinding Parse specific binding SLO endpoint * * @return array metadata info in php-saml settings format */ - public static function parseRemoteXML($url, $entityId = null, $desiredNameIdFormat = null, $desiredSSOBinding = OneLogin_Saml2_Constants::BINDING_HTTP_REDIRECT, $desiredSLOBinding = OneLogin_Saml2_Constants::BINDING_HTTP_REDIRECT) + public static function parseRemoteXML($url, $entityId = null, $desiredNameIdFormat = null, $desiredSSOBinding = Constants::BINDING_HTTP_REDIRECT, $desiredSLOBinding = Constants::BINDING_HTTP_REDIRECT) { $metadataInfo = array(); @@ -30,7 +46,6 @@ public static function parseRemoteXML($url, $entityId = null, $desiredNameIdForm curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "GET"); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1); - curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0); curl_setopt($ch, CURLOPT_FAILONERROR, 1); $xml = curl_exec($ch); @@ -47,18 +62,18 @@ public static function parseRemoteXML($url, $entityId = null, $desiredNameIdForm /** * Get IdP Metadata Info from File * - * @param string $filepath File path - * @param string $entityId Entity Id of the desired IdP, if no - * entity Id is provided and the XML - * metadata contains more than one - * IDPSSODescriptor, the first is returned - * @param string $desiredNameIdFormat If available on IdP metadata, use that nameIdFormat - * @param string $desiredSSOBinding Parse specific binding SSO endpoint. - * @param string $desiredSLOBinding Parse specific binding SLO endpoint. + * @param string $filepath File path + * @param string $entityId Entity Id of the desired IdP, if no + * entity Id is provided and the XML + * metadata contains more than one + * IDPSSODescriptor, the first is returned + * @param string $desiredNameIdFormat If available on IdP metadata, use that nameIdFormat + * @param string $desiredSSOBinding Parse specific binding SSO endpoint + * @param string $desiredSLOBinding Parse specific binding SLO endpoint * * @return array metadata info in php-saml settings format */ - public static function parseFileXML($filepath, $entityId = null, $desiredNameIdFormat = null, $desiredSSOBinding = OneLogin_Saml2_Constants::BINDING_HTTP_REDIRECT, $desiredSLOBinding = OneLogin_Saml2_Constants::BINDING_HTTP_REDIRECT) + public static function parseFileXML($filepath, $entityId = null, $desiredNameIdFormat = null, $desiredSSOBinding = Constants::BINDING_HTTP_REDIRECT, $desiredSLOBinding = Constants::BINDING_HTTP_REDIRECT) { $metadataInfo = array(); @@ -75,20 +90,20 @@ public static function parseFileXML($filepath, $entityId = null, $desiredNameIdF /** * Get IdP Metadata Info from URL * - * @param string $xml XML that contains IdP metadata - * @param string $entityId Entity Id of the desired IdP, if no - * entity Id is provided and the XML - * metadata contains more than one - * IDPSSODescriptor, the first is returned - * @param string $desiredNameIdFormat If available on IdP metadata, use that nameIdFormat - * @param string $desiredSSOBinding Parse specific binding SSO endpoint. - * @param string $desiredSLOBinding Parse specific binding SLO endpoint. + * @param string $xml XML that contains IdP metadata + * @param string $entityId Entity Id of the desired IdP, if no + * entity Id is provided and the XML + * metadata contains more than one + * IDPSSODescriptor, the first is returned + * @param string $desiredNameIdFormat If available on IdP metadata, use that nameIdFormat + * @param string $desiredSSOBinding Parse specific binding SSO endpoint + * @param string $desiredSLOBinding Parse specific binding SLO endpoint * * @return array metadata info in php-saml settings format * * @throws Exception */ - public static function parseXML($xml, $entityId = null, $desiredNameIdFormat = null, $desiredSSOBinding = OneLogin_Saml2_Constants::BINDING_HTTP_REDIRECT, $desiredSLOBinding = OneLogin_Saml2_Constants::BINDING_HTTP_REDIRECT) + public static function parseXML($xml, $entityId = null, $desiredNameIdFormat = null, $desiredSSOBinding = Constants::BINDING_HTTP_REDIRECT, $desiredSLOBinding = Constants::BINDING_HTTP_REDIRECT) { $metadataInfo = array(); @@ -96,7 +111,7 @@ public static function parseXML($xml, $entityId = null, $desiredNameIdFormat = n $dom->preserveWhiteSpace = false; $dom->formatOutput = true; try { - $dom = OneLogin_Saml2_Utils::loadXML($dom, $xml); + $dom = Utils::loadXML($dom, $xml); if (!$dom) { throw new Exception('Error parsing metadata'); } @@ -107,7 +122,7 @@ public static function parseXML($xml, $entityId = null, $desiredNameIdFormat = n } $idpDescryptorXPath = '//md:EntityDescriptor' . $customIdPStr . '/md:IDPSSODescriptor'; - $idpDescriptorNodes = OneLogin_Saml2_Utils::query($dom, $idpDescryptorXPath); + $idpDescriptorNodes = Utils::query($dom, $idpDescryptorXPath); if (isset($idpDescriptorNodes) && $idpDescriptorNodes->length > 0) { $metadataInfo['idp'] = array(); @@ -122,9 +137,9 @@ public static function parseXML($xml, $entityId = null, $desiredNameIdFormat = n $metadataInfo['idp']['entityId'] = $entityId; } - $ssoNodes = OneLogin_Saml2_Utils::query($dom, './md:SingleSignOnService[@Binding="'.$desiredSSOBinding.'"]', $idpDescriptor); + $ssoNodes = Utils::query($dom, './md:SingleSignOnService[@Binding="'.$desiredSSOBinding.'"]', $idpDescriptor); if ($ssoNodes->length < 1) { - $ssoNodes = OneLogin_Saml2_Utils::query($dom, './md:SingleSignOnService', $idpDescriptor); + $ssoNodes = Utils::query($dom, './md:SingleSignOnService', $idpDescriptor); } if ($ssoNodes->length > 0) { $metadataInfo['idp']['singleSignOnService'] = array( @@ -133,9 +148,9 @@ public static function parseXML($xml, $entityId = null, $desiredNameIdFormat = n ); } - $sloNodes = OneLogin_Saml2_Utils::query($dom, './md:SingleLogoutService[@Binding="'.$desiredSLOBinding.'"]', $idpDescriptor); + $sloNodes = Utils::query($dom, './md:SingleLogoutService[@Binding="'.$desiredSLOBinding.'"]', $idpDescriptor); if ($sloNodes->length < 1) { - $sloNodes = OneLogin_Saml2_Utils::query($dom, './md:SingleLogoutService', $idpDescriptor); + $sloNodes = Utils::query($dom, './md:SingleLogoutService', $idpDescriptor); } if ($sloNodes->length > 0) { $metadataInfo['idp']['singleLogoutService'] = array( @@ -144,27 +159,29 @@ public static function parseXML($xml, $entityId = null, $desiredNameIdFormat = n ); } - $keyDescriptorCertSigningNodes = OneLogin_Saml2_Utils::query($dom, './md:KeyDescriptor[not(contains(@use, "encryption"))]/ds:KeyInfo/ds:X509Data/ds:X509Certificate', $idpDescriptor); + $keyDescriptorCertSigningNodes = Utils::query($dom, './md:KeyDescriptor[not(contains(@use, "encryption"))]/ds:KeyInfo/ds:X509Data/ds:X509Certificate', $idpDescriptor); - $keyDescriptorCertEncryptionNodes = OneLogin_Saml2_Utils::query($dom, './md:KeyDescriptor[not(contains(@use, "signing"))]/ds:KeyInfo/ds:X509Data/ds:X509Certificate', $idpDescriptor); + $keyDescriptorCertEncryptionNodes = Utils::query($dom, './md:KeyDescriptor[not(contains(@use, "signing"))]/ds:KeyInfo/ds:X509Data/ds:X509Certificate', $idpDescriptor); if (!empty($keyDescriptorCertSigningNodes) || !empty($keyDescriptorCertEncryptionNodes)) { $metadataInfo['idp']['x509certMulti'] = array(); if (!empty($keyDescriptorCertSigningNodes)) { + $idpInfo['x509certMulti']['signing'] = array(); foreach ($keyDescriptorCertSigningNodes as $keyDescriptorCertSigningNode) { - $metadataInfo['idp']['x509certMulti']['signing'][] = OneLogin_Saml2_Utils::formatCert($keyDescriptorCertSigningNode->nodeValue, false); + $metadataInfo['idp']['x509certMulti']['signing'][] = Utils::formatCert($keyDescriptorCertSigningNode->nodeValue, false); } } if (!empty($keyDescriptorCertEncryptionNodes)) { + $idpInfo['x509certMulti']['encryption'] = array(); foreach ($keyDescriptorCertEncryptionNodes as $keyDescriptorCertEncryptionNode) { - $metadataInfo['idp']['x509certMulti']['encryption'][] = OneLogin_Saml2_Utils::formatCert($keyDescriptorCertEncryptionNode->nodeValue, false); + $metadataInfo['idp']['x509certMulti']['encryption'][] = Utils::formatCert($keyDescriptorCertEncryptionNode->nodeValue, false); } } $idpCertdata = $metadataInfo['idp']['x509certMulti']; if ((count($idpCertdata) == 1 and - ((isset($idpCertdata['signing']) and count($idpCertdata['signing']) == 1) or (isset($idpCertdata['encryption']) and count($idpCertdata['encryption']) == 1))) or - ((isset($idpCertdata['signing']) && count($idpCertdata['signing']) == 1) && isset($idpCertdata['encryption']) && count($idpCertdata['encryption']) == 1 && strcmp($idpCertdata['signing'][0], $idpCertdata['encryption'][0]) == 0)) { + ((isset($idpCertdata['signing']) and count($idpCertdata['signing']) == 1) or (isset($idpCertdata['encryption']) and count($idpCertdata['encryption']) == 1))) or + ((isset($idpCertdata['signing']) && count($idpCertdata['signing']) == 1) && isset($idpCertdata['encryption']) && count($idpCertdata['encryption']) == 1 && strcmp($idpCertdata['signing'][0], $idpCertdata['encryption'][0]) == 0)) { if (isset($metadataInfo['idp']['x509certMulti']['signing'][0])) { $metadataInfo['idp']['x509cert'] = $metadataInfo['idp']['x509certMulti']['signing'][0]; } else { @@ -174,7 +191,7 @@ public static function parseXML($xml, $entityId = null, $desiredNameIdFormat = n } } - $nameIdFormatNodes = OneLogin_Saml2_Utils::query($dom, './md:NameIDFormat', $idpDescriptor); + $nameIdFormatNodes = Utils::query($dom, './md:NameIDFormat', $idpDescriptor); if ($nameIdFormatNodes->length > 0) { $metadataInfo['sp']['NameIDFormat'] = $nameIdFormatNodes->item(0)->nodeValue; if (!empty($desiredNameIdFormat)) { @@ -197,8 +214,8 @@ public static function parseXML($xml, $entityId = null, $desiredNameIdFormat = n /** * Inject metadata info into php-saml settings array * - * @param array $settings php-saml settings array - * @param array $metadataInfo array metadata info + * @param array $settings php-saml settings array + * @param array $metadataInfo array metadata info * * @return array settings */ diff --git a/onelogin-saml-sso/php/lib/Saml2/LogoutRequest.php b/onelogin-saml-sso/php/lib/Saml2/LogoutRequest.php index 595c1ca..2e9258c 100644 --- a/onelogin-saml-sso/php/lib/Saml2/LogoutRequest.php +++ b/onelogin-saml-sso/php/lib/Saml2/LogoutRequest.php @@ -1,54 +1,76 @@ + * @license MIT https://github.com/onelogin/php-saml/blob/master/LICENSE + * @link https://github.com/onelogin/php-saml + */ + +namespace OneLogin\Saml2; + +use RobRichards\XMLSecLibs\XMLSecurityKey; + +use DOMDocument; +use Exception; /** * SAML 2 Logout Request - * */ -class OneLogin_Saml2_LogoutRequest +class LogoutRequest { /** - * Contains the ID of the Logout Request - * @var string - */ + * Contains the ID of the Logout Request + * + * @var string + */ public $id; /** * Object that represents the setting info - * @var OneLogin_Saml2_Settings + * + * @var Settings */ protected $_settings; /** * SAML Logout Request + * * @var string */ protected $_logoutRequest; /** - * After execute a validation process, this var contains the cause - * @var string - */ + * After execute a validation process, this var contains the cause + * + * @var Exception + */ private $_error; /** * Constructs the Logout Request object. * - * @param OneLogin_Saml2_Settings $settings Settings - * @param string|null $request A UUEncoded Logout Request. - * @param string|null $nameId The NameID that will be set in the LogoutRequest. - * @param string|null $sessionIndex The SessionIndex (taken from the SAML Response in the SSO process). - * @param string|null $nameIdFormat The NameID Format will be set in the LogoutRequest. - * @param string|null $nameIdNameQualifier The NameID NameQualifier will be set in the LogoutRequest. - * - * @throws OneLogin_Saml2_Error + * @param Settings $settings Settings + * @param string|null $request A UUEncoded Logout Request. + * @param string|null $nameId The NameID that will be set in the LogoutRequest. + * @param string|null $sessionIndex The SessionIndex (taken from the SAML Response in the SSO process). + * @param string|null $nameIdFormat The NameID Format will be set in the LogoutRequest. + * @param string|null $nameIdNameQualifier The NameID NameQualifier will be set in the LogoutRequest. + * @param string|null $nameIdSPNameQualifier The NameID SP NameQualifier will be set in the LogoutRequest. */ - public function __construct(OneLogin_Saml2_Settings $settings, $request = null, $nameId = null, $sessionIndex = null, $nameIdFormat = null, $nameIdNameQualifier = null) + public function __construct(\OneLogin\Saml2\Settings $settings, $request = null, $nameId = null, $sessionIndex = null, $nameIdFormat = null, $nameIdNameQualifier = null, $nameIdSPNameQualifier = null) { $this->_settings = $settings; $baseURL = $this->_settings->getBaseURL(); if (!empty($baseURL)) { - OneLogin_Saml2_Utils::setBaseURL($baseURL); + Utils::setBaseURL($baseURL); } if (!isset($request) || empty($request)) { @@ -56,11 +78,10 @@ public function __construct(OneLogin_Saml2_Settings $settings, $request = null, $idpData = $this->_settings->getIdPData(); $security = $this->_settings->getSecurityData(); - $id = OneLogin_Saml2_Utils::generateUniqueID(); + $id = Utils::generateUniqueID(); $this->id = $id; - $nameIdValue = OneLogin_Saml2_Utils::generateUniqueID(); - $issueInstant = OneLogin_Saml2_Utils::parseTime2SAML(time()); + $issueInstant = Utils::parseTime2SAML(time()); $cert = null; if (isset($security['nameIdEncrypted']) && $security['nameIdEncrypted']) { @@ -74,20 +95,31 @@ public function __construct(OneLogin_Saml2_Settings $settings, $request = null, } if (!empty($nameId)) { - if (empty($nameIdFormat) && - $spData['NameIDFormat'] != OneLogin_Saml2_Constants::NAMEID_UNSPECIFIED) { + if (empty($nameIdFormat) + && $spData['NameIDFormat'] != Constants::NAMEID_UNSPECIFIED) { $nameIdFormat = $spData['NameIDFormat']; } - $spNameQualifier = null; } else { $nameId = $idpData['entityId']; - $nameIdFormat = OneLogin_Saml2_Constants::NAMEID_ENTITY; - $spNameQualifier = $spData['entityId']; + $nameIdFormat = Constants::NAMEID_ENTITY; + } + + /* From saml-core-2.0-os 8.3.6, when the entity Format is used: + "The NameQualifier, SPNameQualifier, and SPProvidedID attributes MUST be omitted. + */ + if (!empty($nameIdFormat) && $nameIdFormat == Constants::NAMEID_ENTITY) { + $nameIdNameQualifier = null; + $nameIdSPNameQualifier = null; + } + + // NameID Format UNSPECIFIED omitted + if (!empty($nameIdFormat) && $nameIdFormat == Constants::NAMEID_UNSPECIFIED) { + $nameIdFormat = null; } - $nameIdObj = OneLogin_Saml2_Utils::generateNameId( + $nameIdObj = Utils::generateNameId( $nameId, - $spNameQualifier, + $nameIdSPNameQualifier, $nameIdFormat, $cert, $nameIdNameQualifier @@ -118,12 +150,11 @@ public function __construct(OneLogin_Saml2_Settings $settings, $request = null, } else { $logoutRequest = $decoded; } - $this->id = self::getID($logoutRequest); + $this->id = static::getID($logoutRequest); } $this->_logoutRequest = $logoutRequest; } - /** * Returns the Logout Request defated, base64encoded, unsigned * @@ -161,14 +192,15 @@ public static function getID($request) $dom = $request; } else { $dom = new DOMDocument(); - $dom = OneLogin_Saml2_Utils::loadXML($dom, $request); + $dom = Utils::loadXML($dom, $request); + } - if (false === $dom) { - throw new OneLogin_Saml2_Error( - "LogoutRequest could not be processed", - OneLogin_Saml2_Error::SAML_LOGOUTREQUEST_INVALID - ); - } + + if (false === $dom) { + throw new Error( + "LogoutRequest could not be processed", + Error::SAML_LOGOUTREQUEST_INVALID + ); } $id = $dom->documentElement->getAttribute('ID'); @@ -179,12 +211,13 @@ public static function getID($request) * Gets the NameID Data of the the Logout Request. * * @param string|DOMDocument $request Logout Request Message - * @param string|null $key The SP key + * @param string|null $key The SP key * * @return array Name ID Data (Value, Format, NameQualifier, SPNameQualifier) * - * @throws OneLogin_Saml2_Error - * @throws OneLogin_Saml2_ValidationError + * @throws Error + * @throws Exception + * @throws ValidationError */ public static function getNameIdData($request, $key = null) { @@ -192,38 +225,38 @@ public static function getNameIdData($request, $key = null) $dom = $request; } else { $dom = new DOMDocument(); - $dom = OneLogin_Saml2_Utils::loadXML($dom, $request); + $dom = Utils::loadXML($dom, $request); } - $encryptedEntries = OneLogin_Saml2_Utils::query($dom, '/samlp:LogoutRequest/saml:EncryptedID'); + $encryptedEntries = Utils::query($dom, '/samlp:LogoutRequest/saml:EncryptedID'); if ($encryptedEntries->length == 1) { $encryptedDataNodes = $encryptedEntries->item(0)->getElementsByTagName('EncryptedData'); $encryptedData = $encryptedDataNodes->item(0); if (empty($key)) { - throw new OneLogin_Saml2_Error( + throw new Error( "Private Key is required in order to decrypt the NameID, check settings", - OneLogin_Saml2_Error::PRIVATE_KEY_NOT_FOUND + Error::PRIVATE_KEY_NOT_FOUND ); } $seckey = new XMLSecurityKey(XMLSecurityKey::RSA_1_5, array('type'=>'private')); $seckey->loadKey($key); - $nameId = OneLogin_Saml2_Utils::decryptElement($encryptedData, $seckey); + $nameId = Utils::decryptElement($encryptedData, $seckey); } else { - $entries = OneLogin_Saml2_Utils::query($dom, '/samlp:LogoutRequest/saml:NameID'); + $entries = Utils::query($dom, '/samlp:LogoutRequest/saml:NameID'); if ($entries->length == 1) { $nameId = $entries->item(0); } } if (!isset($nameId)) { - throw new OneLogin_Saml2_ValidationError( + throw new ValidationError( "NameID not found in the Logout Request", - OneLogin_Saml2_ValidationError::NO_NAMEID + ValidationError::NO_NAMEID ); } @@ -242,12 +275,13 @@ public static function getNameIdData($request, $key = null) * Gets the NameID of the Logout Request. * * @param string|DOMDocument $request Logout Request Message - * @param string|null $key The SP key + * @param string|null $key The SP key * * @return string Name ID Value - * - * @throws OneLogin_Saml2_Error - * @throws OneLogin_Saml2_ValidationError + * + * @throws Error + * @throws Exception + * @throws ValidationError */ public static function getNameId($request, $key = null) { @@ -261,6 +295,7 @@ public static function getNameId($request, $key = null) * @param string|DOMDocument $request Logout Request Message * * @return string|null $issuer The Issuer + * * @throws Exception */ public static function getIssuer($request) @@ -269,11 +304,11 @@ public static function getIssuer($request) $dom = $request; } else { $dom = new DOMDocument(); - $dom = OneLogin_Saml2_Utils::loadXML($dom, $request); + $dom = Utils::loadXML($dom, $request); } $issuer = null; - $issuerNodes = OneLogin_Saml2_Utils::query($dom, '/samlp:LogoutRequest/saml:Issuer'); + $issuerNodes = Utils::query($dom, '/samlp:LogoutRequest/saml:Issuer'); if ($issuerNodes->length == 1) { $issuer = $issuerNodes->item(0)->textContent; } @@ -289,7 +324,7 @@ public static function getIssuer($request) * @param string|DOMDocument $request Logout Request Message * * @return array The SessionIndex value - * + * * @throws Exception */ public static function getSessionIndexes($request) @@ -298,11 +333,11 @@ public static function getSessionIndexes($request) $dom = $request; } else { $dom = new DOMDocument(); - $dom = OneLogin_Saml2_Utils::loadXML($dom, $request); + $dom = Utils::loadXML($dom, $request); } $sessionIndexes = array(); - $sessionIndexNodes = OneLogin_Saml2_Utils::query($dom, '/samlp:LogoutRequest/samlp:SessionIndex'); + $sessionIndexNodes = Utils::query($dom, '/samlp:LogoutRequest/samlp:SessionIndex'); foreach ($sessionIndexNodes as $sessionIndexNode) { $sessionIndexes[] = $sessionIndexNode->textContent; } @@ -312,16 +347,19 @@ public static function getSessionIndexes($request) /** * Checks if the Logout Request recieved is valid. * - * @param bool $retrieveParametersFromServer + * @param bool $retrieveParametersFromServer True if we want to use parameters from $_SERVER to validate the signature * * @return bool If the Logout Request is or not valid + * + * @throws Exception + * @throws ValidationError */ public function isValid($retrieveParametersFromServer = false) { $this->_error = null; try { $dom = new DOMDocument(); - $dom = OneLogin_Saml2_Utils::loadXML($dom, $this->_logoutRequest); + $dom = Utils::loadXML($dom, $this->_logoutRequest); $idpData = $this->_settings->getIdPData(); $idPEntityId = $idpData['entityId']; @@ -330,24 +368,24 @@ public function isValid($retrieveParametersFromServer = false) $security = $this->_settings->getSecurityData(); if ($security['wantXMLValidation']) { - $res = OneLogin_Saml2_Utils::validateXML($dom, 'saml-schema-protocol-2.0.xsd', $this->_settings->isDebugActive()); + $res = Utils::validateXML($dom, 'saml-schema-protocol-2.0.xsd', $this->_settings->isDebugActive()); if (!$res instanceof DOMDocument) { - throw new OneLogin_Saml2_ValidationError( + throw new ValidationError( "Invalid SAML Logout Request. Not match the saml-schema-protocol-2.0.xsd", - OneLogin_Saml2_ValidationError::INVALID_XML_FORMAT + ValidationError::INVALID_XML_FORMAT ); } } - $currentURL = OneLogin_Saml2_Utils::getSelfRoutedURLNoQuery(); + $currentURL = Utils::getSelfRoutedURLNoQuery(); // Check NotOnOrAfter if ($dom->documentElement->hasAttribute('NotOnOrAfter')) { - $na = OneLogin_Saml2_Utils::parseSAML2Time($dom->documentElement->getAttribute('NotOnOrAfter')); + $na = Utils::parseSAML2Time($dom->documentElement->getAttribute('NotOnOrAfter')); if ($na <= time()) { - throw new OneLogin_Saml2_ValidationError( + throw new ValidationError( "Could not validate timestamp: expired. Check system clock.", - OneLogin_Saml2_ValidationError::RESPONSE_EXPIRED + ValidationError::RESPONSE_EXPIRED ); } } @@ -356,9 +394,9 @@ public function isValid($retrieveParametersFromServer = false) if ($dom->documentElement->hasAttribute('Destination')) { $destination = $dom->documentElement->getAttribute('Destination'); if (!empty($destination) && strpos($destination, $currentURL) === false) { - throw new OneLogin_Saml2_ValidationError( + throw new ValidationError( "The LogoutRequest was received at $currentURL instead of $destination", - OneLogin_Saml2_ValidationError::WRONG_DESTINATION + ValidationError::WRONG_DESTINATION ); } } @@ -368,49 +406,63 @@ public function isValid($retrieveParametersFromServer = false) // Check issuer $issuer = static::getIssuer($dom); if (!empty($issuer) && $issuer != $idPEntityId) { - throw new OneLogin_Saml2_ValidationError( + throw new ValidationError( "Invalid issuer in the Logout Request", - OneLogin_Saml2_ValidationError::WRONG_ISSUER + ValidationError::WRONG_ISSUER ); } if ($security['wantMessagesSigned'] && !isset($_GET['Signature'])) { - throw new OneLogin_Saml2_ValidationError( + throw new ValidationError( "The Message of the Logout Request is not signed and the SP require it", - OneLogin_Saml2_ValidationError::NO_SIGNED_MESSAGE + ValidationError::NO_SIGNED_MESSAGE ); } } if (isset($_GET['Signature'])) { - $signatureValid = OneLogin_Saml2_Utils::validateBinarySign("SAMLRequest", $_GET, $idpData, $retrieveParametersFromServer); + $signatureValid = Utils::validateBinarySign("SAMLRequest", $_GET, $idpData, $retrieveParametersFromServer); if (!$signatureValid) { - throw new OneLogin_Saml2_ValidationError( + throw new ValidationError( "Signature validation failed. Logout Request rejected", - OneLogin_Saml2_ValidationError::INVALID_SIGNATURE + ValidationError::INVALID_SIGNATURE ); } } return true; } catch (Exception $e) { - $this->_error = $e->getMessage(); + $this->_error = $e; $debug = $this->_settings->isDebugActive(); if ($debug) { - echo htmlentities($this->_error); + echo htmlentities($this->_error->getMessage()); } return false; } } + /** + * After execute a validation process, if fails this method returns the Exception of the cause + * + * @return Exception Cause + */ + public function getErrorException() + { + return $this->_error; + } + /** * After execute a validation process, if fails this method returns the cause * - * @return string Cause + * @return null|string Error reason */ public function getError() { - return $this->_error; + $errorMsg = null; + if (isset($this->_error)) { + $errorMsg = htmlentities($this->_error->getMessage()); + } + return $errorMsg; } /** diff --git a/onelogin-saml-sso/php/lib/Saml2/LogoutResponse.php b/onelogin-saml-sso/php/lib/Saml2/LogoutResponse.php index 20f582a..2f376bb 100644 --- a/onelogin-saml-sso/php/lib/Saml2/LogoutResponse.php +++ b/onelogin-saml-sso/php/lib/Saml2/LogoutResponse.php @@ -1,57 +1,82 @@ + * @license MIT https://github.com/onelogin/php-saml/blob/master/LICENSE + * @link https://github.com/onelogin/php-saml + */ + +namespace OneLogin\Saml2; + +use DOMDocument; +use DOMNodeList; +use Exception; /** * SAML 2 Logout Response - * */ -class OneLogin_Saml2_LogoutResponse +class LogoutResponse { /** - * Contains the ID of the Logout Response - * @var string - */ + * Contains the ID of the Logout Response + * + * @var string + */ public $id; /** * Object that represents the setting info - * @var OneLogin_Saml2_Settings + * + * @var Settings */ protected $_settings; /** * The decoded, unprocessed XML response provided to the constructor. - * @var string + * + * @var string|null */ protected $_logoutResponse; /** * A DOMDocument class loaded from the SAML LogoutResponse. - * @var DomDocument + * + * @var DOMDocument */ public $document; /** - * After execute a validation process, if it fails, this var contains the cause - * @var string|null - */ + * After execute a validation process, if it fails, this var contains the cause + * + * @var Exception|null + */ private $_error; /** * Constructs a Logout Response object (Initialize params from settings and if provided * load the Logout Response. * - * @param OneLogin_Saml2_Settings $settings Settings. + * @param Settings $settings Settings. * @param string|null $response An UUEncoded SAML Logout response from the IdP. - * - * @throws OneLogin_Saml2_Error + * + * @throws Error + * @throws Exception + * */ - public function __construct(OneLogin_Saml2_Settings $settings, $response = null) + public function __construct(\OneLogin\Saml2\Settings $settings, $response = null) { $this->_settings = $settings; $baseURL = $this->_settings->getBaseURL(); if (!empty($baseURL)) { - OneLogin_Saml2_Utils::setBaseURL($baseURL); + Utils::setBaseURL($baseURL); } if ($response) { @@ -63,12 +88,12 @@ public function __construct(OneLogin_Saml2_Settings $settings, $response = null) $this->_logoutResponse = $decoded; } $this->document = new DOMDocument(); - $this->document = OneLogin_Saml2_Utils::loadXML($this->document, $this->_logoutResponse); + $this->document = Utils::loadXML($this->document, $this->_logoutResponse); if (false === $this->document) { - throw new OneLogin_Saml2_Error( + throw new Error( "LogoutResponse could not be processed", - OneLogin_Saml2_Error::SAML_LOGOUTRESPONSE_INVALID + Error::SAML_LOGOUTRESPONSE_INVALID ); } @@ -111,10 +136,12 @@ public function getStatus() /** * Determines if the SAML LogoutResponse is valid * - * @param string|null $requestId The ID of the LogoutRequest sent by this SP to the IdP - * @param bool $retrieveParametersFromServer + * @param string|null $requestId The ID of the LogoutRequest sent by this SP to the IdP + * @param bool $retrieveParametersFromServer True if we want to use parameters from $_SERVER to validate the signature * * @return bool Returns if the SAML LogoutResponse is or not valid + * + * @throws ValidationError */ public function isValid($requestId = null, $retrieveParametersFromServer = false) { @@ -127,11 +154,11 @@ public function isValid($requestId = null, $retrieveParametersFromServer = false $security = $this->_settings->getSecurityData(); if ($security['wantXMLValidation']) { - $res = OneLogin_Saml2_Utils::validateXML($this->document, 'saml-schema-protocol-2.0.xsd', $this->_settings->isDebugActive()); + $res = Utils::validateXML($this->document, 'saml-schema-protocol-2.0.xsd', $this->_settings->isDebugActive()); if (!$res instanceof DOMDocument) { - throw new OneLogin_Saml2_ValidationError( + throw new ValidationError( "Invalid SAML Logout Response. Not match the saml-schema-protocol-2.0.xsd", - OneLogin_Saml2_ValidationError::INVALID_XML_FORMAT + ValidationError::INVALID_XML_FORMAT ); } } @@ -140,9 +167,9 @@ public function isValid($requestId = null, $retrieveParametersFromServer = false if (isset($requestId) && $this->document->documentElement->hasAttribute('InResponseTo')) { $inResponseTo = $this->document->documentElement->getAttribute('InResponseTo'); if ($requestId != $inResponseTo) { - throw new OneLogin_Saml2_ValidationError( + throw new ValidationError( "The InResponseTo of the Logout Response: $inResponseTo, does not match the ID of the Logout request sent by the SP: $requestId", - OneLogin_Saml2_ValidationError::WRONG_INRESPONSETO + ValidationError::WRONG_INRESPONSETO ); } } @@ -150,48 +177,48 @@ public function isValid($requestId = null, $retrieveParametersFromServer = false // Check issuer $issuer = $this->getIssuer(); if (!empty($issuer) && $issuer != $idPEntityId) { - throw new OneLogin_Saml2_ValidationError( + throw new ValidationError( "Invalid issuer in the Logout Response", - OneLogin_Saml2_ValidationError::WRONG_ISSUER + ValidationError::WRONG_ISSUER ); } - $currentURL = OneLogin_Saml2_Utils::getSelfRoutedURLNoQuery(); + $currentURL = Utils::getSelfRoutedURLNoQuery(); // Check destination if ($this->document->documentElement->hasAttribute('Destination')) { $destination = $this->document->documentElement->getAttribute('Destination'); if (!empty($destination) && strpos($destination, $currentURL) === false) { - throw new OneLogin_Saml2_ValidationError( + throw new ValidationError( "The LogoutResponse was received at $currentURL instead of $destination", - OneLogin_Saml2_ValidationError::WRONG_DESTINATION + ValidationError::WRONG_DESTINATION ); } } if ($security['wantMessagesSigned'] && !isset($_GET['Signature'])) { - throw new OneLogin_Saml2_ValidationError( + throw new ValidationError( "The Message of the Logout Response is not signed and the SP requires it", - OneLogin_Saml2_ValidationError::NO_SIGNED_MESSAGE + ValidationError::NO_SIGNED_MESSAGE ); } } if (isset($_GET['Signature'])) { - $signatureValid = OneLogin_Saml2_Utils::validateBinarySign("SAMLResponse", $_GET, $idpData, $retrieveParametersFromServer); + $signatureValid = Utils::validateBinarySign("SAMLResponse", $_GET, $idpData, $retrieveParametersFromServer); if (!$signatureValid) { - throw new OneLogin_Saml2_ValidationError( + throw new ValidationError( "Signature validation failed. Logout Response rejected", - OneLogin_Saml2_ValidationError::INVALID_SIGNATURE + ValidationError::INVALID_SIGNATURE ); } } return true; } catch (Exception $e) { - $this->_error = $e->getMessage(); + $this->_error = $e; $debug = $this->_settings->isDebugActive(); if ($debug) { - echo htmlentities($this->_error); + echo htmlentities($this->_error->getMessage()); } return false; } @@ -200,13 +227,13 @@ public function isValid($requestId = null, $retrieveParametersFromServer = false /** * Extracts a node from the DOMDocument (Logout Response Menssage) * - * @param string $query Xpath Expresion + * @param string $query Xpath Expression * * @return DOMNodeList The queried node */ private function _query($query) { - return OneLogin_Saml2_Utils::query($this->document, $query); + return Utils::query($this->document, $query); } @@ -221,8 +248,8 @@ public function build($inResponseTo) $spData = $this->_settings->getSPData(); $idpData = $this->_settings->getIdPData(); - $this->id = OneLogin_Saml2_Utils::generateUniqueID(); - $issueInstant = OneLogin_Saml2_Utils::parseTime2SAML(time()); + $this->id = Utils::generateUniqueID(); + $issueInstant = Utils::parseTime2SAML(time()); $spEntityId = htmlspecialchars($spData['entityId'], ENT_QUOTES); $logoutResponse = <<_logoutResponse; + $logoutResponse = $this->_logoutResponse; if (is_null($deflate)) { $deflate = $this->_settings->shouldCompressResponses(); } if ($deflate) { - $subject = gzdeflate($this->_logoutResponse); + $logoutResponse = gzdeflate($this->_logoutResponse); } - return base64_encode($subject); + return base64_encode($logoutResponse); } /** * After execute a validation process, if fails this method returns the cause. * - * @return string Cause + * @return Exception|null Cause */ - public function getError() + public function getErrorException() { return $this->_error; } - /** - * @return string the ID of the Response - */ + /** + * After execute a validation process, if fails this method returns the cause + * + * @return null|string Error reason + */ + public function getError() + { + $errorMsg = null; + if (isset($this->_error)) { + $errorMsg = htmlentities($this->_error->getMessage()); + } + return $errorMsg; + } + + /** + * @return string the ID of the Response + */ public function getId() { return $this->id; @@ -286,7 +327,7 @@ public function getId() * Returns the XML that will be sent as part of the response * or that was received at the SP * - * @return string + * @return string|null */ public function getXML() { diff --git a/onelogin-saml-sso/php/lib/Saml2/Metadata.php b/onelogin-saml-sso/php/lib/Saml2/Metadata.php index 0f05a1a..2efd113 100644 --- a/onelogin-saml-sso/php/lib/Saml2/Metadata.php +++ b/onelogin-saml-sso/php/lib/Saml2/Metadata.php @@ -1,11 +1,30 @@ + * @license MIT https://github.com/onelogin/php-saml/blob/master/LICENSE + * @link https://github.com/onelogin/php-saml */ -class OneLogin_Saml2_Metadata +namespace OneLogin\Saml2; + +use RobRichards\XMLSecLibs\XMLSecurityKey; +use RobRichards\XMLSecLibs\XMLSecurityDSig; + +use DOMDocument; +use Exception; + +/** + * Metadata lib of OneLogin PHP Toolkit + */ +class Metadata { const TIME_VALID = 172800; // 2 days const TIME_CACHED = 604800; // 1 week @@ -16,7 +35,7 @@ class OneLogin_Saml2_Metadata * @param array $sp The SP data * @param bool|string $authnsign authnRequestsSigned attribute * @param bool|string $wsign wantAssertionsSigned attribute - * @param DateTime|null $validUntil Metadata's valid time + * @param int|null $validUntil Metadata's valid time * @param int|null $cacheDuration Duration of the cache in seconds * @param array $contacts Contacts info * @param array $organization Organization ingo @@ -30,7 +49,7 @@ public static function builder($sp, $authnsign = false, $wsign = false, $validUn if (!isset($validUntil)) { $validUntil = time() + self::TIME_VALID; } - $validUntilTime = gmdate('Y-m-d\TH:i:s\Z', $validUntil); + $validUntilTime = Utils::parseTime2SAML($validUntil); if (!isset($cacheDuration)) { $cacheDuration = self::TIME_CACHED; @@ -174,31 +193,31 @@ public static function builder($sp, $authnsign = false, $wsign = false, $validUn /** * Signs the metadata with the key/cert provided * - * @param string $metadata SAML Metadata XML - * @param string $key x509 key - * @param string $cert x509 cert - * @param string $signAlgorithm Signature algorithm method + * @param string $metadata SAML Metadata XML + * @param string $key x509 key + * @param string $cert x509 cert + * @param string $signAlgorithm Signature algorithm method * @param string $digestAlgorithm Digest algorithm method * * @return string Signed Metadata - * + * * @throws Exception */ - public static function signMetadata($metadata, $key, $cert, $signAlgorithm = XMLSecurityKey::RSA_SHA1, $digestAlgorithm = XMLSecurityDSig::SHA1) + public static function signMetadata($metadata, $key, $cert, $signAlgorithm = XMLSecurityKey::RSA_SHA256, $digestAlgorithm = XMLSecurityDSig::SHA256) { - return OneLogin_Saml2_Utils::addSign($metadata, $key, $cert, $signAlgorithm, $digestAlgorithm); + return Utils::addSign($metadata, $key, $cert, $signAlgorithm, $digestAlgorithm); } /** - * Adds the x509 descriptors (sign/encriptation) to the metadata + * Adds the x509 descriptors (sign/encryption) to the metadata * The same cert will be used for sign/encrypt * - * @param string $metadata SAML Metadata XML - * @param string $cert x509 cert - * @param bool $wantsEncrypted Whether to include the KeyDescriptor for encryption + * @param string $metadata SAML Metadata XML + * @param string $cert x509 cert + * @param bool $wantsEncrypted Whether to include the KeyDescriptor for encryption * * @return string Metadata with KeyDescriptors - * + * * @throws Exception */ public static function addX509KeyDescriptors($metadata, $cert, $wantsEncrypted = true) @@ -207,7 +226,7 @@ public static function addX509KeyDescriptors($metadata, $cert, $wantsEncrypted = $xml->preserveWhiteSpace = false; $xml->formatOutput = true; try { - $xml = OneLogin_Saml2_Utils::loadXML($xml, $metadata); + $xml = Utils::loadXML($xml, $metadata); if (!$xml) { throw new Exception('Error parsing metadata'); } @@ -215,16 +234,16 @@ public static function addX509KeyDescriptors($metadata, $cert, $wantsEncrypted = throw new Exception('Error parsing metadata. '.$e->getMessage()); } - $formatedCert = OneLogin_Saml2_Utils::formatCert($cert, false); - $x509Certificate = $xml->createElementNS(OneLogin_Saml2_Constants::NS_DS, 'X509Certificate', $formatedCert); + $formatedCert = Utils::formatCert($cert, false); + $x509Certificate = $xml->createElementNS(Constants::NS_DS, 'X509Certificate', $formatedCert); - $keyData = $xml->createElementNS(OneLogin_Saml2_Constants::NS_DS, 'ds:X509Data'); + $keyData = $xml->createElementNS(Constants::NS_DS, 'ds:X509Data'); $keyData->appendChild($x509Certificate); - $keyInfo = $xml->createElementNS(OneLogin_Saml2_Constants::NS_DS, 'ds:KeyInfo'); + $keyInfo = $xml->createElementNS(Constants::NS_DS, 'ds:KeyInfo'); $keyInfo->appendChild($keyData); - $keyDescriptor = $xml->createElementNS(OneLogin_Saml2_Constants::NS_MD, "md:KeyDescriptor"); + $keyDescriptor = $xml->createElementNS(Constants::NS_MD, "md:KeyDescriptor"); $SPSSODescriptor = $xml->getElementsByTagName('SPSSODescriptor')->item(0); $SPSSODescriptor->insertBefore($keyDescriptor->cloneNode(), $SPSSODescriptor->firstChild); diff --git a/onelogin-saml-sso/php/lib/Saml2/Response.php b/onelogin-saml-sso/php/lib/Saml2/Response.php index cc23a6c..90c3d5c 100644 --- a/onelogin-saml-sso/php/lib/Saml2/Response.php +++ b/onelogin-saml-sso/php/lib/Saml2/Response.php @@ -1,46 +1,72 @@ + * @license MIT https://github.com/onelogin/php-saml/blob/master/LICENSE + * @link https://github.com/onelogin/php-saml */ -class OneLogin_Saml2_Response -{ +namespace OneLogin\Saml2; + +use RobRichards\XMLSecLibs\XMLSecurityKey; +use RobRichards\XMLSecLibs\XMLSecEnc; +use DOMDocument; +use DOMNodeList; +use DOMXPath; +use Exception; + +/** + * SAML 2 Authentication Response + */ +class Response +{ /** * Settings - * @var OneLogin_Saml2_Settings + * + * @var Settings */ protected $_settings; /** * The decoded, unprocessed XML response provided to the constructor. + * * @var string */ public $response; /** * A DOMDocument class loaded from the SAML Response. - * @var DomDocument + * + * @var DOMDocument */ public $document; /** * A DOMDocument class loaded from the SAML Response (Decrypted). - * @var DomDocument + * + * @var DOMDocument */ public $decryptedDocument; /** * The response contains an encrypted assertion. + * * @var bool */ public $encrypted = false; /** * After validation, if it fail this var has the cause of the problem - * @var string + * + * @var Exception|null */ private $_error; @@ -54,29 +80,29 @@ class OneLogin_Saml2_Response /** * Constructs the SAML Response object. * - * @param OneLogin_Saml2_Settings $settings Settings. - * @param string $response A UUEncoded SAML response from the IdP. + * @param Settings $settings Settings. + * @param string $response A UUEncoded SAML response from the IdP. * - * @throws OneLogin_Saml2_Error - * @throws OneLogin_Saml2_ValidationError + * @throws Exception + * @throws ValidationError */ - public function __construct(OneLogin_Saml2_Settings $settings, $response) + public function __construct(\OneLogin\Saml2\Settings $settings, $response) { $this->_settings = $settings; $baseURL = $this->_settings->getBaseURL(); if (!empty($baseURL)) { - OneLogin_Saml2_Utils::setBaseURL($baseURL); + Utils::setBaseURL($baseURL); } $this->response = base64_decode($response); $this->document = new DOMDocument(); - $this->document = OneLogin_Saml2_Utils::loadXML($this->document, $this->response); + $this->document = Utils::loadXML($this->document, $this->response); if (!$this->document) { - throw new OneLogin_Saml2_ValidationError( + throw new ValidationError( "SAML Response could not be processed", - OneLogin_Saml2_ValidationError::INVALID_XML_FORMAT + ValidationError::INVALID_XML_FORMAT ); } @@ -85,7 +111,7 @@ public function __construct(OneLogin_Saml2_Settings $settings, $response) if ($encryptedAssertionNodes->length !== 0) { $this->decryptedDocument = clone $this->document; $this->encrypted = true; - $this->decryptedDocument = $this->_decryptAssertion($this->decryptedDocument); + $this->decryptedDocument = $this->decryptAssertion($this->decryptedDocument); } } @@ -95,6 +121,9 @@ public function __construct(OneLogin_Saml2_Settings $settings, $response) * @param string|null $requestId The ID of the AuthNRequest sent by this SP to the IdP * * @return bool Validate the document + * + * @throws Exception + * @throws ValidationError */ public function isValid($requestId = null) { @@ -102,26 +131,26 @@ public function isValid($requestId = null) try { // Check SAML version if ($this->document->documentElement->getAttribute('Version') != '2.0') { - throw new OneLogin_Saml2_ValidationError( + throw new ValidationError( "Unsupported SAML version", - OneLogin_Saml2_ValidationError::UNSUPPORTED_SAML_VERSION + ValidationError::UNSUPPORTED_SAML_VERSION ); } if (!$this->document->documentElement->hasAttribute('ID')) { - throw new OneLogin_Saml2_ValidationError( + throw new ValidationError( "Missing ID attribute on SAML Response", - OneLogin_Saml2_ValidationError::MISSING_ID + ValidationError::MISSING_ID ); } - $status = $this->checkStatus(); + $this->checkStatus(); $singleAssertion = $this->validateNumAssertions(); if (!$singleAssertion) { - throw new OneLogin_Saml2_ValidationError( + throw new ValidationError( "SAML Response must contain 1 assertion", - OneLogin_Saml2_ValidationError::WRONG_NUMBER_OF_ASSERTIONS + ValidationError::WRONG_NUMBER_OF_ASSERTIONS ); } @@ -132,8 +161,8 @@ public function isValid($requestId = null) $signedElements = $this->processSignedElements(); - $responseTag = '{'.OneLogin_Saml2_Constants::NS_SAMLP.'}Response'; - $assertionTag = '{'.OneLogin_Saml2_Constants::NS_SAML.'}Assertion'; + $responseTag = '{'.Constants::NS_SAMLP.'}Response'; + $assertionTag = '{'.Constants::NS_SAML.'}Assertion'; $hasSignedResponse = in_array($responseTag, $signedElements); $hasSignedAssertion = in_array($assertionTag, $signedElements); @@ -143,63 +172,63 @@ public function isValid($requestId = null) if ($security['wantXMLValidation']) { $errorXmlMsg = "Invalid SAML Response. Not match the saml-schema-protocol-2.0.xsd"; - $res = OneLogin_Saml2_Utils::validateXML($this->document, 'saml-schema-protocol-2.0.xsd', $this->_settings->isDebugActive()); + $res = Utils::validateXML($this->document, 'saml-schema-protocol-2.0.xsd', $this->_settings->isDebugActive()); if (!$res instanceof DOMDocument) { - throw new OneLogin_Saml2_ValidationError( + throw new ValidationError( $errorXmlMsg, - OneLogin_Saml2_ValidationError::INVALID_XML_FORMAT + ValidationError::INVALID_XML_FORMAT ); } - # If encrypted, check also the decrypted document + // If encrypted, check also the decrypted document if ($this->encrypted) { - $res = OneLogin_Saml2_Utils::validateXML($this->decryptedDocument, 'saml-schema-protocol-2.0.xsd', $this->_settings->isDebugActive()); + $res = Utils::validateXML($this->decryptedDocument, 'saml-schema-protocol-2.0.xsd', $this->_settings->isDebugActive()); if (!$res instanceof DOMDocument) { - throw new OneLogin_Saml2_ValidationError( + throw new ValidationError( $errorXmlMsg, - OneLogin_Saml2_ValidationError::INVALID_XML_FORMAT + ValidationError::INVALID_XML_FORMAT ); } } } - $currentURL = OneLogin_Saml2_Utils::getSelfRoutedURLNoQuery(); - + $currentURL = Utils::getSelfRoutedURLNoQuery(); + if ($this->document->documentElement->hasAttribute('InResponseTo')) { $responseInResponseTo = $this->document->documentElement->getAttribute('InResponseTo'); } // Check if the InResponseTo of the Response matchs the ID of the AuthNRequest (requestId) if provided if (isset($requestId) && isset($responseInResponseTo) && $requestId != $responseInResponseTo) { - throw new OneLogin_Saml2_ValidationError( + throw new ValidationError( "The InResponseTo of the Response: $responseInResponseTo, does not match the ID of the AuthNRequest sent by the SP: $requestId", - OneLogin_Saml2_ValidationError::WRONG_INRESPONSETO + ValidationError::WRONG_INRESPONSETO ); } if (!$this->encrypted && $security['wantAssertionsEncrypted']) { - throw new OneLogin_Saml2_ValidationError( + throw new ValidationError( "The assertion of the Response is not encrypted and the SP requires it", - OneLogin_Saml2_ValidationError::NO_ENCRYPTED_ASSERTION + ValidationError::NO_ENCRYPTED_ASSERTION ); } if ($security['wantNameIdEncrypted']) { $encryptedIdNodes = $this->_queryAssertion('/saml:Subject/saml:EncryptedID/xenc:EncryptedData'); if ($encryptedIdNodes->length != 1) { - throw new OneLogin_Saml2_ValidationError( + throw new ValidationError( "The NameID of the Response is not encrypted and the SP requires it", - OneLogin_Saml2_ValidationError::NO_ENCRYPTED_NAMEID + ValidationError::NO_ENCRYPTED_NAMEID ); } } // Validate Conditions element exists if (!$this->checkOneCondition()) { - throw new OneLogin_Saml2_ValidationError( + throw new ValidationError( "The Assertion must include a Conditions element", - OneLogin_Saml2_ValidationError::MISSING_CONDITIONS + ValidationError::MISSING_CONDITIONS ); } @@ -208,18 +237,18 @@ public function isValid($requestId = null) // Validate AuthnStatement element exists and is unique if (!$this->checkOneAuthnStatement()) { - throw new OneLogin_Saml2_ValidationError( + throw new ValidationError( "The Assertion must include an AuthnStatement element", - OneLogin_Saml2_ValidationError::WRONG_NUMBER_OF_AUTHSTATEMENTS + ValidationError::WRONG_NUMBER_OF_AUTHSTATEMENTS ); } // EncryptedAttributes are not supported $encryptedAttributeNodes = $this->_queryAssertion('/saml:AttributeStatement/saml:EncryptedAttribute'); if ($encryptedAttributeNodes->length > 0) { - throw new OneLogin_Saml2_ValidationError( + throw new ValidationError( "There is an EncryptedAttribute in the Response and this SP not support them", - OneLogin_Saml2_ValidationError::ENCRYPTED_ATTRIBUTES + ValidationError::ENCRYPTED_ATTRIBUTES ); } @@ -228,19 +257,19 @@ public function isValid($requestId = null) $destination = trim($this->document->documentElement->getAttribute('Destination')); if (empty($destination)) { if (!$security['relaxDestinationValidation']) { - throw new OneLogin_Saml2_ValidationError( + throw new ValidationError( "The response has an empty Destination value", - OneLogin_Saml2_ValidationError::EMPTY_DESTINATION + ValidationError::EMPTY_DESTINATION ); } } else { if (strpos($destination, $currentURL) !== 0) { - $currentURLNoRouted = OneLogin_Saml2_Utils::getSelfURLNoQuery(); + $currentURLNoRouted = Utils::getSelfURLNoQuery(); if (strpos($destination, $currentURLNoRouted) !== 0) { - throw new OneLogin_Saml2_ValidationError( + throw new ValidationError( "The response was received at $currentURL instead of $destination", - OneLogin_Saml2_ValidationError::WRONG_DESTINATION + ValidationError::WRONG_DESTINATION ); } } @@ -250,13 +279,13 @@ public function isValid($requestId = null) // Check audience $validAudiences = $this->getAudiences(); if (!empty($validAudiences) && !in_array($spEntityId, $validAudiences, true)) { - throw new OneLogin_Saml2_ValidationError( + throw new ValidationError( sprintf( "Invalid audience for this Response (expected '%s', got '%s')", $spEntityId, implode(',', $validAudiences) ), - OneLogin_Saml2_ValidationError::WRONG_AUDIENCE + ValidationError::WRONG_AUDIENCE ); } @@ -265,19 +294,19 @@ public function isValid($requestId = null) foreach ($issuers as $issuer) { $trimmedIssuer = trim($issuer); if (empty($trimmedIssuer) || $trimmedIssuer !== $idPEntityId) { - throw new OneLogin_Saml2_ValidationError( + throw new ValidationError( "Invalid issuer in the Assertion/Response (expected '$idPEntityId', got '$trimmedIssuer')", - OneLogin_Saml2_ValidationError::WRONG_ISSUER + ValidationError::WRONG_ISSUER ); } } // Check the session Expiration $sessionExpiration = $this->getSessionNotOnOrAfter(); - if (!empty($sessionExpiration) && $sessionExpiration + OneLogin_Saml2_Constants::ALLOWED_CLOCK_DRIFT <= time()) { - throw new OneLogin_Saml2_ValidationError( + if (!empty($sessionExpiration) && $sessionExpiration + Constants::ALLOWED_CLOCK_DRIFT <= time()) { + throw new ValidationError( "The attributes have expired, based on the SessionNotOnOrAfter of the AttributeStatement of this Response", - OneLogin_Saml2_ValidationError::SESSION_EXPIRED + ValidationError::SESSION_EXPIRED ); } @@ -285,7 +314,7 @@ public function isValid($requestId = null) $anySubjectConfirmation = false; $subjectConfirmationNodes = $this->_queryAssertion('/saml:Subject/saml:SubjectConfirmation'); foreach ($subjectConfirmationNodes as $scn) { - if ($scn->hasAttribute('Method') && $scn->getAttribute('Method') != OneLogin_Saml2_Constants::CM_BEARER) { + if ($scn->hasAttribute('Method') && $scn->getAttribute('Method') != Constants::CM_BEARER) { continue; } $subjectConfirmationDataNodes = $scn->getElementsByTagName('SubjectConfirmationData'); @@ -306,14 +335,14 @@ public function isValid($requestId = null) } } if ($scnData->hasAttribute('NotOnOrAfter')) { - $noa = OneLogin_Saml2_Utils::parseSAML2Time($scnData->getAttribute('NotOnOrAfter')); - if ($noa + OneLogin_Saml2_Constants::ALLOWED_CLOCK_DRIFT <= time()) { + $noa = Utils::parseSAML2Time($scnData->getAttribute('NotOnOrAfter')); + if ($noa + Constants::ALLOWED_CLOCK_DRIFT <= time()) { continue; } } if ($scnData->hasAttribute('NotBefore')) { - $nb = OneLogin_Saml2_Utils::parseSAML2Time($scnData->getAttribute('NotBefore')); - if ($nb > time() + OneLogin_Saml2_Constants::ALLOWED_CLOCK_DRIFT) { + $nb = Utils::parseSAML2Time($scnData->getAttribute('NotBefore')); + if ($nb > time() + Constants::ALLOWED_CLOCK_DRIFT) { continue; } } @@ -328,42 +357,42 @@ public function isValid($requestId = null) } if (!$anySubjectConfirmation) { - throw new OneLogin_Saml2_ValidationError( + throw new ValidationError( "A valid SubjectConfirmation was not found on this Response", - OneLogin_Saml2_ValidationError::WRONG_SUBJECTCONFIRMATION + ValidationError::WRONG_SUBJECTCONFIRMATION ); } if ($security['wantAssertionsSigned'] && !$hasSignedAssertion) { - throw new OneLogin_Saml2_ValidationError( + throw new ValidationError( "The Assertion of the Response is not signed and the SP requires it", - OneLogin_Saml2_ValidationError::NO_SIGNED_ASSERTION + ValidationError::NO_SIGNED_ASSERTION ); } - + if ($security['wantMessagesSigned'] && !$hasSignedResponse) { - throw new OneLogin_Saml2_ValidationError( + throw new ValidationError( "The Message of the Response is not signed and the SP requires it", - OneLogin_Saml2_ValidationError::NO_SIGNED_MESSAGE + ValidationError::NO_SIGNED_MESSAGE ); } } // Detect case not supported if ($this->encrypted) { - $encryptedIDNodes = OneLogin_Saml2_Utils::query($this->decryptedDocument, '/samlp:Response/saml:Assertion/saml:Subject/saml:EncryptedID'); + $encryptedIDNodes = Utils::query($this->decryptedDocument, '/samlp:Response/saml:Assertion/saml:Subject/saml:EncryptedID'); if ($encryptedIDNodes->length > 0) { - throw new OneLogin_Saml2_ValidationError( + throw new ValidationError( 'Unsigned SAML Response that contains a signed and encrypted Assertion with encrypted nameId is not supported.', - OneLogin_Saml2_ValidationError::NOT_SUPPORTED + ValidationError::NOT_SUPPORTED ); } } if (empty($signedElements) || (!$hasSignedResponse && !$hasSignedAssertion)) { - throw new OneLogin_Saml2_ValidationError( + throw new ValidationError( 'No Signature found. SAML Response rejected', - OneLogin_Saml2_ValidationError::NO_SIGNATURE_FOUND + ValidationError::NO_SIGNATURE_FOUND ); } else { $cert = $idpData['x509cert']; @@ -377,29 +406,29 @@ public function isValid($requestId = null) $multiCerts = $idpData['x509certMulti']['signing']; } - # If find a Signature on the Response, validates it checking the original response - if ($hasSignedResponse && !OneLogin_Saml2_Utils::validateSign($this->document, $cert, $fingerprint, $fingerprintalg, OneLogin_Saml2_Utils::RESPONSE_SIGNATURE_XPATH, $multiCerts)) { - throw new OneLogin_Saml2_ValidationError( + // If find a Signature on the Response, validates it checking the original response + if ($hasSignedResponse && !Utils::validateSign($this->document, $cert, $fingerprint, $fingerprintalg, Utils::RESPONSE_SIGNATURE_XPATH, $multiCerts)) { + throw new ValidationError( "Signature validation failed. SAML Response rejected", - OneLogin_Saml2_ValidationError::INVALID_SIGNATURE + ValidationError::INVALID_SIGNATURE ); } - # If find a Signature on the Assertion (decrypted assertion if was encrypted) + // If find a Signature on the Assertion (decrypted assertion if was encrypted) $documentToCheckAssertion = $this->encrypted ? $this->decryptedDocument : $this->document; - if ($hasSignedAssertion && !OneLogin_Saml2_Utils::validateSign($documentToCheckAssertion, $cert, $fingerprint, $fingerprintalg, OneLogin_Saml2_Utils::ASSERTION_SIGNATURE_XPATH, $multiCerts)) { - throw new OneLogin_Saml2_ValidationError( + if ($hasSignedAssertion && !Utils::validateSign($documentToCheckAssertion, $cert, $fingerprint, $fingerprintalg, Utils::ASSERTION_SIGNATURE_XPATH, $multiCerts)) { + throw new ValidationError( "Signature validation failed. SAML Response rejected", - OneLogin_Saml2_ValidationError::INVALID_SIGNATURE + ValidationError::INVALID_SIGNATURE ); } } return true; } catch (Exception $e) { - $this->_error = $e->getMessage(); + $this->_error = $e; $debug = $this->_settings->isDebugActive(); if ($debug) { - echo htmlentities($this->_error); + echo htmlentities($e->getMessage()); } return false; } @@ -419,13 +448,13 @@ public function getId() /** * @return string|null the ID of the assertion in the Response - * - * @throws InvalidArgumentException + * + * @throws ValidationError */ public function getAssertionId() { if (!$this->validateNumAssertions()) { - throw new InvalidArgumentException("SAML Response must contain 1 Assertion."); + throw new ValidationError("SAML Response must contain 1 Assertion.", ValidationError::WRONG_NUMBER_OF_ASSERTIONS); } $assertionNodes = $this->_queryAssertion(""); $id = null; @@ -447,13 +476,13 @@ public function getAssertionNotOnOrAfter() /** * Checks if the Status is success * - * @throws OneLogin_Saml2_ValidationError If status is not success + * @throws ValidationError If status is not success */ public function checkStatus() { - $status = OneLogin_Saml2_Utils::getStatus($this->document); + $status = Utils::getStatus($this->document); - if (isset($status['code']) && $status['code'] !== OneLogin_Saml2_Constants::STATUS_SUCCESS) { + if (isset($status['code']) && $status['code'] !== Constants::STATUS_SUCCESS) { $explodedCode = explode(':', $status['code']); $printableCode = array_pop($explodedCode); @@ -461,18 +490,18 @@ public function checkStatus() if (!empty($status['msg'])) { $statusExceptionMsg .= ' -> '.$status['msg']; } - throw new OneLogin_Saml2_ValidationError( + throw new ValidationError( $statusExceptionMsg, - OneLogin_Saml2_ValidationError::STATUS_CODE_IS_NOT_SUCCESS + ValidationError::STATUS_CODE_IS_NOT_SUCCESS ); } } - /** - * Checks that the samlp:Response/saml:Assertion/saml:Conditions element exists and is unique. - * - * @return boolean true if the Conditions element exists and is unique - */ + /** + * Checks that the samlp:Response/saml:Assertion/saml:Conditions element exists and is unique. + * + * @return boolean true if the Conditions element exists and is unique + */ public function checkOneCondition() { $entries = $this->_queryAssertion("/saml:Conditions"); @@ -483,11 +512,11 @@ public function checkOneCondition() } } - /** - * Checks that the samlp:Response/saml:Assertion/saml:AuthnStatement element exists and is unique. - * - * @return boolean true if the AuthnStatement element exists and is unique - */ + /** + * Checks that the samlp:Response/saml:Assertion/saml:AuthnStatement element exists and is unique. + * + * @return boolean true if the AuthnStatement element exists and is unique + */ public function checkOneAuthnStatement() { $entries = $this->_queryAssertion("/saml:AuthnStatement"); @@ -523,20 +552,20 @@ public function getAudiences() * * @return array @issuers The issuers of the assertion/response * - * @throws OneLogin_Saml2_ValidationError + * @throws ValidationError */ public function getIssuers() { $issuers = array(); - $responseIssuer = OneLogin_Saml2_Utils::query($this->document, '/samlp:Response/saml:Issuer'); + $responseIssuer = Utils::query($this->document, '/samlp:Response/saml:Issuer'); if ($responseIssuer->length > 0) { if ($responseIssuer->length == 1) { $issuers[] = $responseIssuer->item(0)->textContent; } else { - throw new OneLogin_Saml2_ValidationError( + throw new ValidationError( "Issuer of the Response is multiple.", - OneLogin_Saml2_ValidationError::ISSUER_MULTIPLE_IN_RESPONSE + ValidationError::ISSUER_MULTIPLE_IN_RESPONSE ); } } @@ -545,9 +574,9 @@ public function getIssuers() if ($assertionIssuer->length == 1) { $issuers[] = $assertionIssuer->item(0)->textContent; } else { - throw new OneLogin_Saml2_ValidationError( + throw new ValidationError( "Issuer of the Assertion not found or multiple.", - OneLogin_Saml2_ValidationError::ISSUER_NOT_FOUND_IN_ASSERTION + ValidationError::ISSUER_NOT_FOUND_IN_ASSERTION ); } @@ -559,7 +588,7 @@ public function getIssuers() * * @return array Name ID Data (Value, Format, NameQualifier, SPNameQualifier) * - * @throws OneLogin_Saml2_ValidationError + * @throws ValidationError */ public function getNameIdData() { @@ -572,7 +601,7 @@ public function getNameIdData() $seckey = new XMLSecurityKey(XMLSecurityKey::RSA_1_5, array('type'=>'private')); $seckey->loadKey($key); - $nameId = OneLogin_Saml2_Utils::decryptElement($encryptedData, $seckey); + $nameId = Utils::decryptElement($encryptedData, $seckey); } else { $entries = $this->_queryAssertion('/saml:Subject/saml:NameID'); @@ -586,16 +615,16 @@ public function getNameIdData() if (!isset($nameId)) { $security = $this->_settings->getSecurityData(); if ($security['wantNameId']) { - throw new OneLogin_Saml2_ValidationError( + throw new ValidationError( "NameID not found in the assertion of the Response", - OneLogin_Saml2_ValidationError::NO_NAMEID + ValidationError::NO_NAMEID ); } } else { if ($this->_settings->isStrict() && empty($nameId->nodeValue)) { - throw new OneLogin_Saml2_ValidationError( + throw new ValidationError( "An empty NameID value found", - OneLogin_Saml2_ValidationError::EMPTY_NAMEID + ValidationError::EMPTY_NAMEID ); } $nameIdData['Value'] = $nameId->nodeValue; @@ -606,9 +635,9 @@ public function getNameIdData() $spData = $this->_settings->getSPData(); $spEntityId = $spData['entityId']; if ($spEntityId != $nameId->getAttribute($attr)) { - throw new OneLogin_Saml2_ValidationError( + throw new ValidationError( "The SPNameQualifier value mistmatch the SP entityID value.", - OneLogin_Saml2_ValidationError::SP_NAME_QUALIFIER_NAME_MISMATCH + ValidationError::SP_NAME_QUALIFIER_NAME_MISMATCH ); } } @@ -625,7 +654,7 @@ public function getNameIdData() * * @return string|null Name ID Value * - * @throws OneLogin_Saml2_ValidationError + * @throws ValidationError */ public function getNameId() { @@ -642,7 +671,7 @@ public function getNameId() * * @return string|null Name ID Format * - * @throws OneLogin_Saml2_ValidationError + * @throws ValidationError */ public function getNameIdFormat() { @@ -659,7 +688,7 @@ public function getNameIdFormat() * * @return string|null Name ID NameQualifier * - * @throws OneLogin_Saml2_ValidationError + * @throws ValidationError */ public function getNameIdNameQualifier() { @@ -671,6 +700,23 @@ public function getNameIdNameQualifier() return $nameIdNameQualifier; } + /** + * Gets the NameID SP NameQualifier provided by the SAML response from the IdP. + * + * @return string|null NameID SP NameQualifier + * + * @throws ValidationError + */ + public function getNameIdSPNameQualifier() + { + $nameIdSPNameQualifier = null; + $nameIdData = $this->getNameIdData(); + if (!empty($nameIdData) && isset($nameIdData['SPNameQualifier'])) { + $nameIdSPNameQualifier = $nameIdData['SPNameQualifier']; + } + return $nameIdSPNameQualifier; + } + /** * Gets the SessionNotOnOrAfter from the AuthnStatement. * Could be used to set the local session expiration @@ -684,7 +730,7 @@ public function getSessionNotOnOrAfter() $notOnOrAfter = null; $entries = $this->_queryAssertion('/saml:AuthnStatement[@SessionNotOnOrAfter]'); if ($entries->length !== 0) { - $notOnOrAfter = OneLogin_Saml2_Utils::parseSAML2Time($entries->item(0)->getAttribute('SessionNotOnOrAfter')); + $notOnOrAfter = Utils::parseSAML2Time($entries->item(0)->getAttribute('SessionNotOnOrAfter')); } return $notOnOrAfter; } @@ -697,7 +743,6 @@ public function getSessionNotOnOrAfter() * * @return string|null The SessionIndex value */ - public function getSessionIndex() { $sessionIndex = null; @@ -713,7 +758,7 @@ public function getSessionIndex() * * @return array The attributes of the SAML Assertion * - * @throws OneLogin_Saml2_ValidationError + * @throws ValidationError */ public function getAttributes() { @@ -725,7 +770,7 @@ public function getAttributes() * * @return array The attributes of the SAML Assertion * - * @throws OneLogin_Saml2_ValidationError + * @throws ValidationError */ public function getAttributesWithFriendlyName() { @@ -737,31 +782,25 @@ public function getAttributesWithFriendlyName() * * @return array * - * @throws OneLogin_Saml2_ValidationError + * @throws ValidationError */ - private function _getAttributesByKeyName($keyName="Name") + private function _getAttributesByKeyName($keyName = "Name") { $attributes = array(); - $entries = $this->_queryAssertion('/saml:AttributeStatement/saml:Attribute'); - /** @var $entry DOMNode */ foreach ($entries as $entry) { $attributeKeyNode = $entry->attributes->getNamedItem($keyName); - if ($attributeKeyNode === null) { continue; } - $attributeKeyName = $attributeKeyNode->nodeValue; - if (in_array($attributeKeyName, array_keys($attributes))) { - throw new OneLogin_Saml2_ValidationError( + throw new ValidationError( "Found an Attribute element with duplicated ".$keyName, - OneLogin_Saml2_ValidationError::DUPLICATED_ATTRIBUTE_NAME_FOUND + ValidationError::DUPLICATED_ATTRIBUTE_NAME_FOUND ); } - $attributeValues = array(); foreach ($entry->childNodes as $childNode) { $tagName = ($childNode->prefix ? $childNode->prefix.':' : '') . 'AttributeValue'; @@ -769,7 +808,6 @@ private function _getAttributesByKeyName($keyName="Name") $attributeValues[] = $childNode->nodeValue; } } - $attributes[$attributeKeyName] = $attributeValues; } return $attributes; @@ -802,7 +840,7 @@ public function validateNumAssertions() * * @return array Signed element tags * - * @throws OneLogin_Saml2_ValidationError + * @throws ValidationError */ public function processSignedElements() { @@ -816,31 +854,31 @@ public function processSignedElements() $signNodes = $this->document->getElementsByTagName('Signature'); } foreach ($signNodes as $signNode) { - $responseTag = '{'.OneLogin_Saml2_Constants::NS_SAMLP.'}Response'; - $assertionTag = '{'.OneLogin_Saml2_Constants::NS_SAML.'}Assertion'; + $responseTag = '{'.Constants::NS_SAMLP.'}Response'; + $assertionTag = '{'.Constants::NS_SAML.'}Assertion'; $signedElement = '{'.$signNode->parentNode->namespaceURI.'}'.$signNode->parentNode->localName; if ($signedElement != $responseTag && $signedElement != $assertionTag) { - throw new OneLogin_Saml2_ValidationError( + throw new ValidationError( "Invalid Signature Element $signedElement SAML Response rejected", - OneLogin_Saml2_ValidationError::WRONG_SIGNED_ELEMENT + ValidationError::WRONG_SIGNED_ELEMENT ); } - # Check that reference URI matches the parent ID and no duplicate References or IDs + // Check that reference URI matches the parent ID and no duplicate References or IDs $idValue = $signNode->parentNode->getAttribute('ID'); if (empty($idValue)) { - throw new OneLogin_Saml2_ValidationError( + throw new ValidationError( 'Signed Element must contain an ID. SAML Response rejected', - OneLogin_Saml2_ValidationError::ID_NOT_FOUND_IN_SIGNED_ELEMENT + ValidationError::ID_NOT_FOUND_IN_SIGNED_ELEMENT ); } if (in_array($idValue, $verifiedIds)) { - throw new OneLogin_Saml2_ValidationError( + throw new ValidationError( 'Duplicated ID. SAML Response rejected', - OneLogin_Saml2_ValidationError::DUPLICATED_ID_IN_SIGNED_ELEMENTS + ValidationError::DUPLICATED_ID_IN_SIGNED_ELEMENTS ); } $verifiedIds[] = $idValue; @@ -853,24 +891,24 @@ public function processSignedElements() $sei = substr($sei, 1); if ($sei != $idValue) { - throw new OneLogin_Saml2_ValidationError( + throw new ValidationError( 'Found an invalid Signed Element. SAML Response rejected', - OneLogin_Saml2_ValidationError::INVALID_SIGNED_ELEMENT + ValidationError::INVALID_SIGNED_ELEMENT ); } if (in_array($sei, $verifiedSeis)) { - throw new OneLogin_Saml2_ValidationError( + throw new ValidationError( 'Duplicated Reference URI. SAML Response rejected', - OneLogin_Saml2_ValidationError::DUPLICATED_REFERENCE_IN_SIGNED_ELEMENTS + ValidationError::DUPLICATED_REFERENCE_IN_SIGNED_ELEMENTS ); } $verifiedSeis[] = $sei; } } else { - throw new OneLogin_Saml2_ValidationError( + throw new ValidationError( 'Unexpected number of Reference nodes found for signature. SAML Response rejected.', - OneLogin_Saml2_ValidationError::UNEXPECTED_REFERENCE + ValidationError::UNEXPECTED_REFERENCE ); } $signedElements[] = $signedElement; @@ -878,9 +916,9 @@ public function processSignedElements() // Check SignedElements if (!empty($signedElements) && !$this->validateSignedElements($signedElements)) { - throw new OneLogin_Saml2_ValidationError( + throw new ValidationError( 'Found an unexpected Signature Element. SAML Response rejected', - OneLogin_Saml2_ValidationError::UNEXPECTED_SIGNED_ELEMENTS + ValidationError::UNEXPECTED_SIGNED_ELEMENTS ); } return $signedElements; @@ -892,7 +930,7 @@ public function processSignedElements() * @return bool * * @throws Exception - * @throws OneLogin_Saml2_ValidationError + * @throws ValidationError */ public function validateTimestamps() { @@ -906,16 +944,16 @@ public function validateTimestamps() for ($i = 0; $i < $timestampNodes->length; $i++) { $nbAttribute = $timestampNodes->item($i)->attributes->getNamedItem("NotBefore"); $naAttribute = $timestampNodes->item($i)->attributes->getNamedItem("NotOnOrAfter"); - if ($nbAttribute && OneLogin_Saml2_Utils::parseSAML2Time($nbAttribute->textContent) > time() + OneLogin_Saml2_Constants::ALLOWED_CLOCK_DRIFT) { - throw new OneLogin_Saml2_ValidationError( + if ($nbAttribute && Utils::parseSAML2Time($nbAttribute->textContent) > time() + Constants::ALLOWED_CLOCK_DRIFT) { + throw new ValidationError( 'Could not validate timestamp: not yet valid. Check system clock.', - OneLogin_Saml2_ValidationError::ASSERTION_TOO_EARLY + ValidationError::ASSERTION_TOO_EARLY ); } - if ($naAttribute && OneLogin_Saml2_Utils::parseSAML2Time($naAttribute->textContent) + OneLogin_Saml2_Constants::ALLOWED_CLOCK_DRIFT <= time()) { - throw new OneLogin_Saml2_ValidationError( + if ($naAttribute && Utils::parseSAML2Time($naAttribute->textContent) + Constants::ALLOWED_CLOCK_DRIFT <= time()) { + throw new ValidationError( 'Could not validate timestamp: expired. Check system clock.', - OneLogin_Saml2_ValidationError::ASSERTION_EXPIRED + ValidationError::ASSERTION_EXPIRED ); } } @@ -925,11 +963,11 @@ public function validateTimestamps() /** * Verifies that the document has the expected signed nodes. * - * @param $signedElements + * @param array $signedElements Signed elements * * @return bool * - * @throws OneLogin_Saml2_ValidationError + * @throws ValidationError */ public function validateSignedElements($signedElements) { @@ -937,35 +975,35 @@ public function validateSignedElements($signedElements) return false; } - $responseTag = '{'.OneLogin_Saml2_Constants::NS_SAMLP.'}Response'; - $assertionTag = '{'.OneLogin_Saml2_Constants::NS_SAML.'}Assertion'; + $responseTag = '{'.Constants::NS_SAMLP.'}Response'; + $assertionTag = '{'.Constants::NS_SAML.'}Assertion'; $ocurrence = array_count_values($signedElements); - if ((in_array($responseTag, $signedElements) && $ocurrence[$responseTag] > 1) || - (in_array($assertionTag, $signedElements) && $ocurrence[$assertionTag] > 1) || - !in_array($responseTag, $signedElements) && !in_array($assertionTag, $signedElements) + if ((in_array($responseTag, $signedElements) && $ocurrence[$responseTag] > 1) + || (in_array($assertionTag, $signedElements) && $ocurrence[$assertionTag] > 1) + || !in_array($responseTag, $signedElements) && !in_array($assertionTag, $signedElements) ) { return false; } // Check that the signed elements found here, are the ones that will be verified - // by OneLogin_Saml2_Utils->validateSign() + // by Utils->validateSign() if (in_array($responseTag, $signedElements)) { - $expectedSignatureNodes = OneLogin_Saml2_Utils::query($this->document, OneLogin_Saml2_Utils::RESPONSE_SIGNATURE_XPATH); + $expectedSignatureNodes = Utils::query($this->document, Utils::RESPONSE_SIGNATURE_XPATH); if ($expectedSignatureNodes->length != 1) { - throw new OneLogin_Saml2_ValidationError( + throw new ValidationError( "Unexpected number of Response signatures found. SAML Response rejected.", - OneLogin_Saml2_ValidationError::WRONG_NUMBER_OF_SIGNATURES_IN_RESPONSE + ValidationError::WRONG_NUMBER_OF_SIGNATURES_IN_RESPONSE ); } } if (in_array($assertionTag, $signedElements)) { - $expectedSignatureNodes = $this->_query(OneLogin_Saml2_Utils::ASSERTION_SIGNATURE_XPATH); + $expectedSignatureNodes = $this->_query(Utils::ASSERTION_SIGNATURE_XPATH); if ($expectedSignatureNodes->length != 1) { - throw new OneLogin_Saml2_ValidationError( + throw new ValidationError( "Unexpected number of Assertion signatures found. SAML Response rejected.", - OneLogin_Saml2_ValidationError::WRONG_NUMBER_OF_SIGNATURES_IN_ASSERTION + ValidationError::WRONG_NUMBER_OF_SIGNATURES_IN_ASSERTION ); } } @@ -988,10 +1026,10 @@ protected function _queryAssertion($assertionXpath) $xpath = new DOMXPath($this->document); } - $xpath->registerNamespace('samlp', OneLogin_Saml2_Constants::NS_SAMLP); - $xpath->registerNamespace('saml', OneLogin_Saml2_Constants::NS_SAML); - $xpath->registerNamespace('ds', OneLogin_Saml2_Constants::NS_DS); - $xpath->registerNamespace('xenc', OneLogin_Saml2_Constants::NS_XENC); + $xpath->registerNamespace('samlp', Constants::NS_SAMLP); + $xpath->registerNamespace('saml', Constants::NS_SAML); + $xpath->registerNamespace('ds', Constants::NS_DS); + $xpath->registerNamespace('xenc', Constants::NS_XENC); $assertionNode = '/samlp:Response/saml:Assertion'; $signatureQuery = $assertionNode . '/ds:Signature/ds:SignedInfo/ds:Reference'; @@ -1027,54 +1065,58 @@ protected function _queryAssertion($assertionXpath) /** * Extracts nodes that match the query from the DOMDocument (Response Menssage) * - * @param string $query Xpath Expresion + * @param string $query Xpath Expression * * @return DOMNodeList The queried nodes */ private function _query($query) { if ($this->encrypted) { - return OneLogin_Saml2_Utils::query($this->decryptedDocument, $query); + return Utils::query($this->decryptedDocument, $query); } else { - return OneLogin_Saml2_Utils::query($this->document, $query); + return Utils::query($this->document, $query); } } /** * Decrypts the Assertion (DOMDocument) * - * @param DomNode $dom DomDocument + * @param \DomNode $dom DomDocument * * @return DOMDocument Decrypted Assertion * - * @throws OneLogin_Saml2_Error - * @throws OneLogin_Saml2_ValidationError + * @throws Exception + * @throws ValidationError */ - protected function _decryptAssertion($dom) + protected function decryptAssertion(\DomNode $dom) { $pem = $this->_settings->getSPkey(); + if (empty($pem)) { - throw new OneLogin_Saml2_Error( + throw new Error( "No private key available, check settings", - OneLogin_Saml2_Error::PRIVATE_KEY_NOT_FOUND + Error::PRIVATE_KEY_NOT_FOUND ); } + $objenc = new XMLSecEnc(); $encData = $objenc->locateEncryptedData($dom); if (!$encData) { - throw new OneLogin_Saml2_ValidationError( + throw new ValidationError( "Cannot locate encrypted assertion", - OneLogin_Saml2_ValidationError::MISSING_ENCRYPTED_ELEMENT + ValidationError::MISSING_ENCRYPTED_ELEMENT ); } + $objenc->setNode($encData); $objenc->type = $encData->getAttribute("Type"); if (!$objKey = $objenc->locateKey()) { - throw new OneLogin_Saml2_ValidationError( + throw new ValidationError( "Unknown algorithm", - OneLogin_Saml2_ValidationError::KEY_ALGORITHM_ERROR + ValidationError::KEY_ALGORITHM_ERROR ); } + $key = null; if ($objKeyInfo = $objenc->locateKeyInfo($objKey)) { if ($objKeyInfo->isEncrypted) { @@ -1086,13 +1128,14 @@ protected function _decryptAssertion($dom) $objKeyInfo->loadKey($pem, false, false); } } - + if (empty($objKey->key)) { $objKey->loadKey($key); } + $decryptedXML = $objenc->decryptNode($objKey, false); $decrypted = new DOMDocument(); - $check = OneLogin_Saml2_Utils::loadXML($decrypted, $decryptedXML); + $check = Utils::loadXML($decrypted, $decryptedXML); if ($check === false) { throw new Exception('Error: string from decrypted assertion could not be loaded into a XML document'); } @@ -1117,27 +1160,41 @@ protected function _decryptAssertion($dom) } else { $ns = 'xmlns'; } - $decrypted->setAttributeNS('http://www.w3.org/2000/xmlns/', $ns, OneLogin_Saml2_Constants::NS_SAML); + $decrypted->setAttributeNS('http://www.w3.org/2000/xmlns/', $ns, Constants::NS_SAML); } - OneLogin_Saml2_Utils::treeCopyReplace($encryptedAssertion, $decrypted); + Utils::treeCopyReplace($encryptedAssertion, $decrypted); // Rebuild the DOM will fix issues with namespaces as well $dom = new DOMDocument(); - return OneLogin_Saml2_Utils::loadXML($dom, $container->ownerDocument->saveXML()); + return Utils::loadXML($dom, $container->ownerDocument->saveXML()); } } /** * After execute a validation process, if fails this method returns the cause * - * @return string Cause + * @return Exception|null Cause */ - public function getError() + public function getErrorException() { return $this->_error; } + /** + * After execute a validation process, if fails this method returns the cause + * + * @return null|string Error reason + */ + public function getError() + { + $errorMsg = null; + if (isset($this->_error)) { + $errorMsg = htmlentities($this->_error->getMessage()); + } + return $errorMsg; + } + /** * Returns the SAML Response document (If contains an encrypted assertion, decrypts it) * diff --git a/onelogin-saml-sso/php/lib/Saml2/Settings.php b/onelogin-saml-sso/php/lib/Saml2/Settings.php index 5d0f7ed..a6a41b7 100644 --- a/onelogin-saml-sso/php/lib/Saml2/Settings.php +++ b/onelogin-saml-sso/php/lib/Saml2/Settings.php @@ -1,11 +1,30 @@ + * @license MIT https://github.com/onelogin/php-saml/blob/master/LICENSE + * @link https://github.com/onelogin/php-saml */ -class OneLogin_Saml2_Settings +namespace OneLogin\Saml2; + +use RobRichards\XMLSecLibs\XMLSecurityKey; +use RobRichards\XMLSecLibs\XMLSecurityDSig; + +use DOMDocument; +use Exception; + +/** + * Configuration of the OneLogin PHP Toolkit + */ +class Settings { /** * List of paths. @@ -86,7 +105,7 @@ class OneLogin_Saml2_Settings private $_errors = array(); /** - * Setting errors. + * Valitate SP data only flag * * @var bool */ @@ -97,45 +116,31 @@ class OneLogin_Saml2_Settings * - Sets the paths of the different folders * - Loads settings info from settings file or array/object provided * - * @param array|object|null $settings SAML Toolkit Settings - * @param bool $spValidationOnly + * @param array|null $settings SAML Toolkit Settings + * @param bool $spValidationOnly Validate or not the IdP data * - * @throws OneLogin_Saml2_Error If any settings parameter is invalid - * @throws Exception If OneLogin_Saml2_Settings is incorrectly supplied + * @throws Error If any settings parameter is invalid + * @throws Exception If Settings is incorrectly supplied */ - public function __construct($settings = null, $spValidationOnly = false) + public function __construct(array $settings = null, $spValidationOnly = false) { $this->_spValidationOnly = $spValidationOnly; $this->_loadPaths(); if (!isset($settings)) { if (!$this->_loadSettingsFromFile()) { - throw new OneLogin_Saml2_Error( + throw new Error( 'Invalid file settings: %s', - OneLogin_Saml2_Error::SETTINGS_INVALID, + Error::SETTINGS_INVALID, array(implode(', ', $this->_errors)) ); } $this->_addDefaultValues(); - } else if (is_array($settings)) { - if (!$this->_loadSettingsFromArray($settings)) { - throw new OneLogin_Saml2_Error( - 'Invalid array settings: %s', - OneLogin_Saml2_Error::SETTINGS_INVALID, - array(implode(', ', $this->_errors)) - ); - } - } else if ($settings instanceof OneLogin_Saml2_Settings) { - throw new OneLogin_Saml2_Error( - 'Only instances of OneLogin_Saml_Settings are supported.', - OneLogin_Saml2_Error::UNSUPPORTED_SETTINGS_OBJECT, - array(implode(', ', $this->_errors)) - ); } else { - if (!$this->_loadSettingsFromArray($settings->getValues())) { - throw new OneLogin_Saml2_Error( + if (!$this->_loadSettingsFromArray($settings)) { + throw new Error( 'Invalid array settings: %s', - OneLogin_Saml2_Error::SETTINGS_INVALID, + Error::SETTINGS_INVALID, array(implode(', ', $this->_errors)) ); } @@ -154,18 +159,17 @@ public function __construct($settings = null, $spValidationOnly = false) */ private function _loadPaths() { - $basePath = dirname(dirname(__DIR__)).'/'; - $this->_paths = array ( + $basePath = dirname(dirname(__DIR__)) . '/'; + $this->_paths = array( 'base' => $basePath, 'config' => $basePath, 'cert' => $basePath.'certs/', - 'lib' => $basePath.'lib/', - 'extlib' => $basePath.'extlib/' + 'lib' => $basePath.'src/' ); if (defined('ONELOGIN_CUSTOMPATH')) { $this->_paths['config'] = ONELOGIN_CUSTOMPATH; - $this->_paths['cert'] = ONELOGIN_CUSTOMPATH.'certs/'; + $this->_paths['cert'] = ONELOGIN_CUSTOMPATH . 'certs/'; } } @@ -209,16 +213,6 @@ public function getLibPath() return $this->_paths['lib']; } - /** - * Returns external lib path. - * - * @return string The external library folder path - */ - public function getExtLibPath() - { - return $this->_paths['extlib']; - } - /** * Returns schema path. * @@ -236,7 +230,7 @@ public function getSchemasPath() * * @return bool True if the settings info is valid */ - private function _loadSettingsFromArray($settings) + private function _loadSettingsFromArray(array $settings) { if (isset($settings['sp'])) { $this->_sp = $settings['sp']; @@ -289,7 +283,7 @@ private function _loadSettingsFromArray($settings) * * @return bool True if the settings info is valid * - * @throws OneLogin_Saml2_Error + * @throws Error * * @suppress PhanUndeclaredVariable */ @@ -298,9 +292,9 @@ private function _loadSettingsFromFile() $filename = $this->getConfigPath().'settings.php'; if (!file_exists($filename)) { - throw new OneLogin_Saml2_Error( + throw new Error( 'Settings file not found: %s', - OneLogin_Saml2_Error::SETTINGS_FILE_NOT_FOUND, + Error::SETTINGS_FILE_NOT_FOUND, array($filename) ); } @@ -309,7 +303,6 @@ private function _loadSettingsFromFile() include $filename; // Add advance_settings if exists - $advancedFilename = $this->getConfigPath().'advanced_settings.php'; if (file_exists($advancedFilename)) { @@ -328,10 +321,10 @@ private function _loadSettingsFromFile() private function _addDefaultValues() { if (!isset($this->_sp['assertionConsumerService']['binding'])) { - $this->_sp['assertionConsumerService']['binding'] = OneLogin_Saml2_Constants::BINDING_HTTP_POST; + $this->_sp['assertionConsumerService']['binding'] = Constants::BINDING_HTTP_POST; } if (isset($this->_sp['singleLogoutService']) && !isset($this->_sp['singleLogoutService']['binding'])) { - $this->_sp['singleLogoutService']['binding'] = OneLogin_Saml2_Constants::BINDING_HTTP_REDIRECT; + $this->_sp['singleLogoutService']['binding'] = Constants::BINDING_HTTP_REDIRECT; } if (!isset($this->_compress['requests'])) { @@ -344,7 +337,7 @@ private function _addDefaultValues() // Related to nameID if (!isset($this->_sp['NameIDFormat'])) { - $this->_sp['NameIDFormat'] = OneLogin_Saml2_Constants::NAMEID_UNSPECIFIED; + $this->_sp['NameIDFormat'] = Constants::NAMEID_UNSPECIFIED; } if (!isset($this->_security['nameIdEncrypted'])) { $this->_security['nameIdEncrypted'] = false; @@ -400,12 +393,12 @@ private function _addDefaultValues() // SignatureAlgorithm if (!isset($this->_security['signatureAlgorithm'])) { - $this->_security['signatureAlgorithm'] = XMLSecurityKey::RSA_SHA1; + $this->_security['signatureAlgorithm'] = XMLSecurityKey::RSA_SHA256; } // DigestAlgorithm if (!isset($this->_security['digestAlgorithm'])) { - $this->_security['digestAlgorithm'] = XMLSecurityDSig::SHA1; + $this->_security['digestAlgorithm'] = XMLSecurityDSig::SHA256; } if (!isset($this->_security['lowercaseUrlencoding'])) { @@ -438,11 +431,9 @@ private function _addDefaultValues() * * @return array $errors Errors found on the settings data */ - public function checkSettings($settings) + public function checkSettings(array $settings) { - assert('is_array($settings)'); - - if (!is_array($settings) || empty($settings)) { + if (empty($settings)) { $errors = array('invalid_syntax'); } else { $errors = array(); @@ -496,11 +487,9 @@ public function checkCompressionSettings($settings) * * @return array $errors Errors found on the IdP settings data */ - public function checkIdPSettings($settings) + public function checkIdPSettings(array $settings) { - assert('is_array($settings)'); - - if (!is_array($settings) || empty($settings)) { + if (empty($settings)) { return array('invalid_syntax'); } @@ -561,11 +550,9 @@ public function checkIdPSettings($settings) * * @return array $errors Errors found on the SP settings data */ - public function checkSPSettings($settings) + public function checkSPSettings(array $settings) { - assert('is_array($settings)'); - - if (!is_array($settings) || empty($settings)) { + if (empty($settings)) { return array('invalid_syntax'); } @@ -600,9 +587,12 @@ public function checkSPSettings($settings) $errors[] = 'sp_sls_url_invalid'; } - if (isset($security['signMetadata']) && is_array($security['signMetadata']) && - (!isset($security['signMetadata']['keyFileName']) || !isset($security['signMetadata']['certFileName']))) { - $errors[] = 'sp_signMetadata_invalid'; + if (isset($security['signMetadata']) && is_array($security['signMetadata'])) { + if (!isset($security['signMetadata']['keyFileName']) + || !isset($security['signMetadata']['certFileName']) + ) { + $errors[] = 'sp_signMetadata_invalid'; + } } if (((isset($security['authnRequestsSigned']) && $security['authnRequestsSigned'] == true) @@ -708,6 +698,7 @@ public function getSPcert() * Returns the x509 public of the SP that is * planed to be used soon instead the other * public cert + * * @return string SP public cert New */ public function getSPcertNew() @@ -777,20 +768,20 @@ public function getOrganization() } /** - * Should SAML requests be compressed? - * - * @return bool Yes/No as True/False - */ + * Should SAML requests be compressed? + * + * @return bool Yes/No as True/False + */ public function shouldCompressRequests() { return $this->_compress['requests']; } /** - * Should SAML responses be compressed? - * - * @return bool Yes/No as True/False - */ + * Should SAML responses be compressed? + * + * @return bool Yes/No as True/False + */ public function shouldCompressResponses() { return $this->_compress['responses']; @@ -799,25 +790,25 @@ public function shouldCompressResponses() /** * Gets the SP metadata. The XML representation. * - * @param bool $alwaysPublishEncryptionCert When 'true', the returned metadata - * will always include an 'encryption' KeyDescriptor. Otherwise, the 'encryption' - * KeyDescriptor will only be included if $advancedSettings['security']['wantNameIdEncrypted'] - * or $advancedSettings['security']['wantAssertionsEncrypted'] are enabled. - * @param DateTime|null $validUntil Metadata's valid time + * @param bool $alwaysPublishEncryptionCert When 'true', the returned + * metadata will always include an 'encryption' KeyDescriptor. Otherwise, + * the 'encryption' KeyDescriptor will only be included if + * $advancedSettings['security']['wantNameIdEncrypted'] or + * $advancedSettings['security']['wantAssertionsEncrypted'] are enabled. + * @param int|null $validUntil Metadata's valid time * @param int|null $cacheDuration Duration of the cache in seconds * * @return string SP metadata (xml) - * * @throws Exception - * @throws OneLogin_Saml2_Error + * @throws Error */ public function getSPMetadata($alwaysPublishEncryptionCert = false, $validUntil = null, $cacheDuration = null) { - $metadata = OneLogin_Saml2_Metadata::builder($this->_sp, $this->_security['authnRequestsSigned'], $this->_security['wantAssertionsSigned'], $validUntil, $cacheDuration, $this->getContacts(), $this->getOrganization()); + $metadata = Metadata::builder($this->_sp, $this->_security['authnRequestsSigned'], $this->_security['wantAssertionsSigned'], $validUntil, $cacheDuration, $this->getContacts(), $this->getOrganization()); $certNew = $this->getSPcertNew(); if (!empty($certNew)) { - $metadata = OneLogin_Saml2_Metadata::addX509KeyDescriptors( + $metadata = Metadata::addX509KeyDescriptors( $metadata, $certNew, $alwaysPublishEncryptionCert || $this->_security['wantNameIdEncrypted'] || $this->_security['wantAssertionsEncrypted'] @@ -826,7 +817,7 @@ public function getSPMetadata($alwaysPublishEncryptionCert = false, $validUntil $cert = $this->getSPcert(); if (!empty($cert)) { - $metadata = OneLogin_Saml2_Metadata::addX509KeyDescriptors( + $metadata = Metadata::addX509KeyDescriptors( $metadata, $cert, $alwaysPublishEncryptionCert || $this->_security['wantNameIdEncrypted'] || $this->_security['wantAssertionsEncrypted'] @@ -840,25 +831,25 @@ public function getSPMetadata($alwaysPublishEncryptionCert = false, $validUntil $certMetadata = $cert; if (!$keyMetadata) { - throw new OneLogin_Saml2_Error( + throw new Error( 'SP Private key not found.', - OneLogin_Saml2_Error::PRIVATE_KEY_FILE_NOT_FOUND + Error::PRIVATE_KEY_FILE_NOT_FOUND ); } if (!$certMetadata) { - throw new OneLogin_Saml2_Error( + throw new Error( 'SP Public cert not found.', - OneLogin_Saml2_Error::PUBLIC_CERT_FILE_NOT_FOUND + Error::PUBLIC_CERT_FILE_NOT_FOUND ); } } else { if (!isset($this->_security['signMetadata']['keyFileName']) || !isset($this->_security['signMetadata']['certFileName']) ) { - throw new OneLogin_Saml2_Error( + throw new Error( 'Invalid Setting: signMetadata value of the sp is not valid', - OneLogin_Saml2_Error::SETTINGS_INVALID_SYNTAX + Error::SETTINGS_INVALID_SYNTAX ); } $keyFileName = $this->_security['signMetadata']['keyFileName']; @@ -867,19 +858,18 @@ public function getSPMetadata($alwaysPublishEncryptionCert = false, $validUntil $keyMetadataFile = $this->_paths['cert'].$keyFileName; $certMetadataFile = $this->_paths['cert'].$certFileName; - if (!file_exists($keyMetadataFile)) { - throw new OneLogin_Saml2_Error( + throw new Error( 'SP Private key file not found: %s', - OneLogin_Saml2_Error::PRIVATE_KEY_FILE_NOT_FOUND, + Error::PRIVATE_KEY_FILE_NOT_FOUND, array($keyMetadataFile) ); } if (!file_exists($certMetadataFile)) { - throw new OneLogin_Saml2_Error( + throw new Error( 'SP Public cert file not found: %s', - OneLogin_Saml2_Error::PUBLIC_CERT_FILE_NOT_FOUND, + Error::PUBLIC_CERT_FILE_NOT_FOUND, array($certMetadataFile) ); } @@ -889,7 +879,7 @@ public function getSPMetadata($alwaysPublishEncryptionCert = false, $validUntil $signatureAlgorithm = $this->_security['signatureAlgorithm']; $digestAlgorithm = $this->_security['digestAlgorithm']; - $metadata = OneLogin_Saml2_Metadata::signMetadata($metadata, $keyMetadata, $certMetadata, $signatureAlgorithm, $digestAlgorithm); + $metadata = Metadata::signMetadata($metadata, $keyMetadata, $certMetadata, $signatureAlgorithm, $digestAlgorithm); } return $metadata; } @@ -899,16 +889,16 @@ public function getSPMetadata($alwaysPublishEncryptionCert = false, $validUntil * * @param string $xml Metadata's XML that will be validate * - * @return Array The list of found errors + * @return array The list of found errors * * @throws Exception */ public function validateMetadata($xml) { - assert('is_string($xml)'); + assert(is_string($xml)); $errors = array(); - $res = OneLogin_Saml2_Utils::validateXML($xml, 'saml-schema-metadata-2.0.xsd', $this->_debug); + $res = Utils::validateXML($xml, 'saml-schema-metadata-2.0.xsd', $this->_debug); if (!$res instanceof DOMDocument) { $errors[] = $res; } else { @@ -920,13 +910,13 @@ public function validateMetadata($xml) $validUntil = $cacheDuration = $expireTime = null; if ($element->hasAttribute('validUntil')) { - $validUntil = OneLogin_Saml2_Utils::parseSAML2Time($element->getAttribute('validUntil')); + $validUntil = Utils::parseSAML2Time($element->getAttribute('validUntil')); } if ($element->hasAttribute('cacheDuration')) { $cacheDuration = $element->getAttribute('cacheDuration'); } - $expireTime = OneLogin_Saml2_Utils::getExpireTime($cacheDuration, $validUntil); + $expireTime = Utils::getExpireTime($cacheDuration, $validUntil); if (isset($expireTime) && time() > $expireTime) { $errors[] = 'expired_xml'; } @@ -944,7 +934,7 @@ public function validateMetadata($xml) public function formatIdPCert() { if (isset($this->_idp['x509cert'])) { - $this->_idp['x509cert'] = OneLogin_Saml2_Utils::formatCert($this->_idp['x509cert']); + $this->_idp['x509cert'] = Utils::formatCert($this->_idp['x509cert']); } } @@ -956,12 +946,12 @@ public function formatIdPCertMulti() if (isset($this->_idp['x509certMulti'])) { if (isset($this->_idp['x509certMulti']['signing'])) { foreach ($this->_idp['x509certMulti']['signing'] as $i => $cert) { - $this->_idp['x509certMulti']['signing'][$i] = OneLogin_Saml2_Utils::formatCert($cert); + $this->_idp['x509certMulti']['signing'][$i] = Utils::formatCert($cert); } } if (isset($this->_idp['x509certMulti']['encryption'])) { foreach ($this->_idp['x509certMulti']['encryption'] as $i => $cert) { - $this->_idp['x509certMulti']['encryption'][$i] = OneLogin_Saml2_Utils::formatCert($cert); + $this->_idp['x509certMulti']['encryption'][$i] = Utils::formatCert($cert); } } } @@ -973,7 +963,7 @@ public function formatIdPCertMulti() public function formatSPCert() { if (isset($this->_sp['x509cert'])) { - $this->_sp['x509cert'] = OneLogin_Saml2_Utils::formatCert($this->_sp['x509cert']); + $this->_sp['x509cert'] = Utils::formatCert($this->_sp['x509cert']); } } @@ -983,7 +973,7 @@ public function formatSPCert() public function formatSPCertNew() { if (isset($this->_sp['x509certNew'])) { - $this->_sp['x509certNew'] = OneLogin_Saml2_Utils::formatCert($this->_sp['x509certNew']); + $this->_sp['x509certNew'] = Utils::formatCert($this->_sp['x509certNew']); } } @@ -993,7 +983,7 @@ public function formatSPCertNew() public function formatSPKey() { if (isset($this->_sp['privateKey'])) { - $this->_sp['privateKey'] = OneLogin_Saml2_Utils::formatPrivateKey($this->_sp['privateKey']); + $this->_sp['privateKey'] = Utils::formatPrivateKey($this->_sp['privateKey']); } } @@ -1046,7 +1036,7 @@ public function isDebugActive() /** * Set a baseurl value. * - * @param $baseurl + * @param string $baseurl Base URL. */ public function setBaseURL($baseurl) { diff --git a/onelogin-saml-sso/php/lib/Saml2/Utils.php b/onelogin-saml-sso/php/lib/Saml2/Utils.php index da83ae2..73d1695 100644 --- a/onelogin-saml-sso/php/lib/Saml2/Utils.php +++ b/onelogin-saml-sso/php/lib/Saml2/Utils.php @@ -1,12 +1,37 @@ + * @license MIT https://github.com/onelogin/php-saml/blob/master/LICENSE + * @link https://github.com/onelogin/php-saml + */ + +namespace OneLogin\Saml2; + +use RobRichards\XMLSecLibs\XMLSecurityKey; +use RobRichards\XMLSecLibs\XMLSecurityDSig; +use RobRichards\XMLSecLibs\XMLSecEnc; + +use DOMDocument; +use DOMElement; +use DOMNodeList; +use DomNode; +use DOMXPath; +use Exception; /** * Utils of OneLogin PHP Toolkit * * Defines several often used methods */ - -class OneLogin_Saml2_Utils +class Utils { const RESPONSE_SIGNATURE_XPATH = "/samlp:Response/ds:Signature"; const ASSERTION_SIGNATURE_XPATH = "/samlp:Response/saml:Assertion/ds:Signature"; @@ -16,7 +41,6 @@ class OneLogin_Saml2_Utils */ private static $_proxyVars = false; - /** * @var string|null */ @@ -27,6 +51,11 @@ class OneLogin_Saml2_Utils */ private static $_protocol; + /** + * @var string + */ + private static $_protocolRegex = '@^https?://@i'; + /** * @var int|null */ @@ -37,37 +66,6 @@ class OneLogin_Saml2_Utils */ private static $_baseurlpath; - /** - * @var string - */ - private static $_protocolRegex = '@^https?://@i'; - - /** - * Translates any string. Accepts args - * - * @param string $msg Message to be translated - * @param array|null $args Arguments - * - * @return string $translatedMsg Translated text - */ - public static function t($msg, $args = array()) - { - assert('is_string($msg)'); - if (extension_loaded('gettext')) { - bindtextdomain("phptoolkit", dirname(dirname(__DIR__)).'/locale'); - textdomain('phptoolkit'); - - $translatedMsg = gettext($msg); - } else { - $translatedMsg = $msg; - } - if (!empty($args)) { - $params = array_merge(array($translatedMsg), $args); - $translatedMsg = call_user_func_array('sprintf', $params); - } - return $translatedMsg; - } - /** * This function load an XML string in a save way. * Prevent XEE/XXE Attacks @@ -75,23 +73,29 @@ public static function t($msg, $args = array()) * @param DOMDocument $dom The document where load the xml. * @param string $xml The XML string to be loaded. * - * @return DOMDocument|false $dom The result of load the XML at the DomDocument + * @return DOMDocument|false $dom The result of load the XML at the DOMDocument * * @throws Exception */ - public static function loadXML($dom, $xml) + public static function loadXML(DOMDocument $dom, $xml) { - assert('$dom instanceof DOMDocument'); - assert('is_string($xml)'); - - if (strpos($xml, 'loadXML($xml); + libxml_disable_entity_loader($oldEntityLoader); + foreach ($dom->childNodes as $child) { + if ($child->nodeType === XML_DOCUMENT_TYPE_NODE) { + throw new Exception( + 'Detected use of DOCTYPE/ENTITY in XML, disabled to prevent XXE/XEE attacks' + ); + } + } + if (!$res) { return false; } else { @@ -102,11 +106,11 @@ public static function loadXML($dom, $xml) /** * This function attempts to validate an XML string against the specified schema. * - * It will parse the string into a DOM document and validate this document against the schema. + * It will parse the string into a DOMDocument and validate this document against the schema. * - * @param string|DOMDocument $xml The XML string or document which should be validated. - * @param string $schema The schema filename which should be used. - * @param bool $debug To disable/enable the debug mode + * @param string|DOMDocument $xml The XML string or document which should be validated. + * @param string $schema The schema filename which should be used. + * @param bool $debug To disable/enable the debug mode * * @return string|DOMDocument $dom string that explains the problem or the DOMDocument * @@ -114,8 +118,8 @@ public static function loadXML($dom, $xml) */ public static function validateXML($xml, $schema, $debug = false) { - assert('is_string($xml) || $xml instanceof DOMDocument'); - assert('is_string($schema)'); + assert(is_string($xml) || $xml instanceof DOMDocument); + assert(is_string($schema)); libxml_clear_errors(); libxml_use_internal_errors(true); @@ -130,7 +134,7 @@ public static function validateXML($xml, $schema, $debug = false) } } - $schemaFile = __DIR__.'/schemas/' . $schema; + $schemaFile = __DIR__ . '/schemas/' . $schema; $oldEntityLoader = libxml_disable_entity_loader(false); $res = $dom->schemaValidate($schemaFile); libxml_disable_entity_loader($oldEntityLoader); @@ -140,14 +144,12 @@ public static function validateXML($xml, $schema, $debug = false) if ($debug) { foreach ($xmlErrors as $error) { - echo htmlentities($error->message."\n"); + echo htmlentities($error->message)."\n"; } } - return 'invalid_xml'; } - return $dom; } @@ -196,12 +198,11 @@ public static function treeCopyReplace(DomNode $targetNode, DomNode $sourceNode, /** * Returns a x509 cert (adding header & footer if required). * - * @param string $cert A x509 unformated cert - * @param bool $heads True if we want to include head and footer + * @param string $cert A x509 unformated cert + * @param bool $heads True if we want to include head and footer * * @return string $x509 Formatted cert */ - public static function formatCert($cert, $heads = true) { $x509cert = str_replace(array("\x0D", "\r", "\n"), "", $cert); @@ -221,25 +222,24 @@ public static function formatCert($cert, $heads = true) /** * Returns a private key (adding header & footer if required). * - * @param string $key A private key - * @param bool $heads True if we want to include head and footer + * @param string $key A private key + * @param bool $heads True if we want to include head and footer * * @return string $rsaKey Formatted private key */ - public static function formatPrivateKey($key, $heads = true) { $key = str_replace(array("\x0D", "\r", "\n"), "", $key); if (!empty($key)) { if (strpos($key, '-----BEGIN PRIVATE KEY-----') !== false) { - $key = OneLogin_Saml2_Utils::getStringBetween($key, '-----BEGIN PRIVATE KEY-----', '-----END PRIVATE KEY-----'); + $key = Utils::getStringBetween($key, '-----BEGIN PRIVATE KEY-----', '-----END PRIVATE KEY-----'); $key = str_replace(' ', '', $key); if ($heads) { $key = "-----BEGIN PRIVATE KEY-----\n".chunk_split($key, 64, "\n")."-----END PRIVATE KEY-----\n"; } } else if (strpos($key, '-----BEGIN RSA PRIVATE KEY-----') !== false) { - $key = OneLogin_Saml2_Utils::getStringBetween($key, '-----BEGIN RSA PRIVATE KEY-----', '-----END RSA PRIVATE KEY-----'); + $key = Utils::getStringBetween($key, '-----BEGIN RSA PRIVATE KEY-----', '-----END RSA PRIVATE KEY-----'); $key = str_replace(' ', '', $key); if ($heads) { @@ -259,9 +259,9 @@ public static function formatPrivateKey($key, $heads = true) /** * Extracts a substring between 2 marks * - * @param string $str The target string - * @param string $start The initial mark - * @param string $end The end mark + * @param string $str The target string + * @param string $start The initial mark + * @param string $end The end mark * * @return string A substring or an empty string if is not able to find the marks * or if there is no string between the marks @@ -283,18 +283,17 @@ public static function getStringBetween($str, $start, $end) /** * Executes a redirection to the provided url (or return the target url). * - * @param string $url The target url - * @param array $parameters Extra parameters to be passed as part of the url - * @param bool $stay True if we want to stay (returns the url string) False to redirect + * @param string $url The target url + * @param array $parameters Extra parameters to be passed as part of the url + * @param bool $stay True if we want to stay (returns the url string) False to redirect * * @return string|null $url * - * @throws OneLogin_Saml2_Error + * @throws Error */ - public static function redirect($url, $parameters = array(), $stay = false) + public static function redirect($url, array $parameters = array(), $stay = false) { - assert('is_string($url)'); - assert('is_array($parameters)'); + assert(is_string($url)); if (substr($url, 0, 1) === '/') { $url = self::getSelfURLhost() . $url; @@ -307,9 +306,9 @@ public static function redirect($url, $parameters = array(), $stay = false) $wrongProtocol = !preg_match(self::$_protocolRegex, $url); $url = filter_var($url, FILTER_VALIDATE_URL); if ($wrongProtocol || empty($url)) { - throw new OneLogin_Saml2_Error( + throw new Error( 'Redirect to invalid URL: ' . $url, - OneLogin_Saml2_Error::REDIRECT_INVALID_URL + Error::REDIRECT_INVALID_URL ); } @@ -351,8 +350,8 @@ public static function redirect($url, $parameters = array(), $stay = false) exit(); } - /** - * @var $protocolRegex string + /** + * @param $protocolRegex string */ public static function setProtocolRegex($protocolRegex) { @@ -362,12 +361,15 @@ public static function setProtocolRegex($protocolRegex) } /** - * @param $baseurl string The base url to be used when constructing URLs + * Set the Base URL value. + * + * @param string $baseurl The base url to be used when constructing URLs */ public static function setBaseURL($baseurl) { if (!empty($baseurl)) { $baseurlpath = '/'; + $matches = array(); if (preg_match('#^https?://([^/]*)/?(.*)#i', $baseurl, $matches)) { if (strpos($baseurl, 'https://') === false) { self::setSelfProtocol('http'); @@ -402,7 +404,7 @@ public static function setBaseURL($baseurl) } /** - * @param $proxyVars bool Whether to use `X-Forwarded-*` headers to determine port/domain/protocol + * @param bool $proxyVars Whether to use `X-Forwarded-*` headers to determine port/domain/protocol */ public static function setProxyVars($proxyVars) { @@ -410,7 +412,7 @@ public static function setProxyVars($proxyVars) } /** - * return bool + * @return bool */ public static function getProxyVars() { @@ -421,7 +423,7 @@ public static function getProxyVars() * Returns the protocol + the current host + the port (if different than * common ports). * - * @return string $url + * @return string The URL */ public static function getSelfURLhost() { @@ -445,7 +447,7 @@ public static function getSelfURLhost() } /** - * @param $host string The host to use when constructing URLs + * @param string $host The host to use when constructing URLs */ public static function setSelfHost($host) { @@ -453,7 +455,7 @@ public static function setSelfHost($host) } /** - * @param $baseurlpath string The baseurl path to use when constructing URLs + * @param string $baseurlpath The baseurl path to use when constructing URLs */ public static function setBaseURLPath($baseurlpath) { @@ -498,7 +500,7 @@ protected static function getRawHost() } /** - * @param $port int The port number to use when constructing URLs + * @param int $port The port number to use when constructing URLs */ public static function setSelfPort($port) { @@ -506,7 +508,7 @@ public static function setSelfPort($port) } /** - * @param $protocol string The protocol to identify as using, usually http or https + * @param string $protocol The protocol to identify as using, usually http or https */ public static function setSelfProtocol($protocol) { @@ -648,6 +650,7 @@ public static function getSelfURL() $requestURI = ''; if (!empty($_SERVER['REQUEST_URI'])) { $requestURI = $_SERVER['REQUEST_URI']; + $matches = array(); if ($requestURI[0] !== '/' && preg_match('#^https?://[^/]*(/.*)#i', $requestURI, $matches)) { $requestURI = $matches[1]; } @@ -664,7 +667,7 @@ public static function getSelfURL() /** * Returns the part of the URL with the BaseURLPath. * - * @param $info + * @param string $info Contains path info * * @return string */ @@ -720,7 +723,7 @@ public static function generateUniqueID() */ public static function parseTime2SAML($time) { - $date = new DateTime("@$time", new DateTimeZone('UTC')); + $date = new \DateTime("@$time", new \DateTimeZone('UTC')); $timestamp = $date->format("Y-m-d\TH:i:s\Z"); return $timestamp; } @@ -753,12 +756,12 @@ public static function parseSAML2Time($time) * matches in the regex. int cast will ignore leading zeroes * in the string. */ - $year = (int)$matches[1]; - $month = (int)$matches[2]; - $day = (int)$matches[3]; - $hour = (int)$matches[4]; - $minute = (int)$matches[5]; - $second = (int)$matches[6]; + $year = (int) $matches[1]; + $month = (int) $matches[2]; + $day = (int) $matches[3]; + $hour = (int) $matches[4]; + $minute = (int) $matches[5]; + $second = (int) $matches[6]; /* We use gmmktime because the timestamp will always be given * in UTC. @@ -783,12 +786,13 @@ public static function parseSAML2Time($time) */ public static function parseDuration($duration, $timestamp = null) { - assert('is_string($duration)'); - assert('is_null($timestamp) || is_int($timestamp)'); + assert(is_string($duration)); + assert(is_null($timestamp) || is_int($timestamp)); + + $matches = array(); /* Parse the duration. We use a very strict pattern. */ $durationRegEx = '#^(-?)P(?:(?:(?:(\\d+)Y)?(?:(\\d+)M)?(?:(\\d+)D)?(?:T(?:(\\d+)H)?(?:(\\d+)M)?(?:(\\d+)S)?)?)|(?:(\\d+)W))$#D'; - $matches = array(); if (!preg_match($durationRegEx, $duration, $matches)) { throw new Exception('Invalid ISO 8601 duration: ' . $duration); } @@ -860,8 +864,8 @@ public static function parseDuration($duration, $timestamp = null) /** * Compares 2 dates and returns the earliest. * - * @param string|null $cacheDuration The duration, as a string. - * @param string|int|null $validUntil The valid until date, as a string or as a timestamp + * @param string|null $cacheDuration The duration, as a string. + * @param string|int|null $validUntil The valid until date, as a string or as a timestamp * * @return int|null $expireTime The expiration time. * @@ -893,22 +897,22 @@ public static function getExpireTime($cacheDuration = null, $validUntil = null) /** * Extracts nodes from the DOMDocument. * - * @param DOMDocument $dom The DOMDocument - * @param string $query Xpath Expresion - * @param DomElement|null $context Context Node (DomElement) + * @param DOMDocument $dom The DOMDocument + * @param string $query \Xpath Expression + * @param DOMElement|null $context Context Node (DOMElement) * * @return DOMNodeList The queried nodes */ - public static function query($dom, $query, $context = null) + public static function query(DOMDocument $dom, $query, DOMElement $context = null) { $xpath = new DOMXPath($dom); - $xpath->registerNamespace('samlp', OneLogin_Saml2_Constants::NS_SAMLP); - $xpath->registerNamespace('saml', OneLogin_Saml2_Constants::NS_SAML); - $xpath->registerNamespace('ds', OneLogin_Saml2_Constants::NS_DS); - $xpath->registerNamespace('xenc', OneLogin_Saml2_Constants::NS_XENC); - $xpath->registerNamespace('xsi', OneLogin_Saml2_Constants::NS_XSI); - $xpath->registerNamespace('xs', OneLogin_Saml2_Constants::NS_XS); - $xpath->registerNamespace('md', OneLogin_Saml2_Constants::NS_MD); + $xpath->registerNamespace('samlp', Constants::NS_SAMLP); + $xpath->registerNamespace('saml', Constants::NS_SAML); + $xpath->registerNamespace('ds', Constants::NS_DS); + $xpath->registerNamespace('xenc', Constants::NS_XENC); + $xpath->registerNamespace('xsi', Constants::NS_XSI); + $xpath->registerNamespace('xs', Constants::NS_XS); + $xpath->registerNamespace('md', Constants::NS_MD); if (isset($context)) { $res = $xpath->query($query, $context); @@ -938,7 +942,7 @@ public static function isSessionStarted() public static function deleteLocalSession() { - if (OneLogin_Saml2_Utils::isSessionStarted()) { + if (Utils::isSessionStarted()) { session_destroy(); } @@ -948,14 +952,14 @@ public static function deleteLocalSession() /** * Calculates the fingerprint of a x509cert. * - * @param string $x509cert x509 cert - * @param string $alg + * @param string $x509cert x509 cert formatted + * @param string $alg Algorithm to be used in order to calculate the fingerprint * * @return null|string Formatted fingerprint */ public static function calculateX509Fingerprint($x509cert, $alg = 'sha1') { - assert('is_string($x509cert)'); + assert(is_string($x509cert)); $arCert = explode("\n", $x509cert); $data = ''; @@ -1014,11 +1018,11 @@ public static function formatFingerPrint($fingerprint) /** * Generates a nameID. * - * @param string $value fingerprint - * @param string $spnq SP Name Qualifier + * @param string $value fingerprint + * @param string $spnq SP Name Qualifier * @param string|null $format SP Format - * @param string|null $cert IdP Public cert to encrypt the nameID - * @param string|null $nq IdP Name Qualifier + * @param string|null $cert IdP Public cert to encrypt the nameID + * @param string|null $nq IdP Name Qualifier * * @return string $nameIDElement DOMElement | XMLSec nameID * @@ -1079,25 +1083,25 @@ public static function generateNameId($value, $spnq, $format = null, $cert = nul * * @return array $status The Status, an array with the code and a message. * - * @throws OneLogin_Saml2_ValidationError + * @throws ValidationError */ - public static function getStatus($dom) + public static function getStatus(DOMDocument $dom) { $status = array(); $statusEntry = self::query($dom, '/samlp:Response/samlp:Status'); if ($statusEntry->length != 1) { - throw new OneLogin_Saml2_ValidationError( + throw new ValidationError( "Missing Status on response", - OneLogin_Saml2_ValidationError::MISSING_STATUS + ValidationError::MISSING_STATUS ); } $codeEntry = self::query($dom, '/samlp:Response/samlp:Status/samlp:StatusCode', $statusEntry->item(0)); if ($codeEntry->length != 1) { - throw new OneLogin_Saml2_ValidationError( + throw new ValidationError( "Missing Status Code on response", - OneLogin_Saml2_ValidationError::MISSING_STATUS_CODE + ValidationError::MISSING_STATUS_CODE ); } $code = $codeEntry->item(0)->getAttribute('Value'); @@ -1127,7 +1131,7 @@ public static function getStatus($dom) * * @return DOMElement The decrypted element. * - * @throws OneLogin_Saml2_ValidationError + * @throws ValidationError */ public static function decryptElement(DOMElement $encryptedData, XMLSecurityKey $inputKey, $formatOutput = true) { @@ -1139,17 +1143,17 @@ public static function decryptElement(DOMElement $encryptedData, XMLSecurityKey $symmetricKey = $enc->locateKey($encryptedData); if (!$symmetricKey) { - throw new OneLogin_Saml2_ValidationError( + throw new ValidationError( 'Could not locate key algorithm in encrypted data.', - OneLogin_Saml2_ValidationError::KEY_ALGORITHM_ERROR + ValidationError::KEY_ALGORITHM_ERROR ); } $symmetricKeyInfo = $enc->locateKeyInfo($symmetricKey); if (!$symmetricKeyInfo) { - throw new OneLogin_Saml2_ValidationError( + throw new ValidationError( "Could not locate for the encrypted key.", - OneLogin_Saml2_ValidationError::KEYINFO_NOT_FOUND_IN_ENCRYPTED_DATA + ValidationError::KEYINFO_NOT_FOUND_IN_ENCRYPTED_DATA ); } @@ -1162,12 +1166,12 @@ public static function decryptElement(DOMElement $encryptedData, XMLSecurityKey } if ($inputKeyAlgo !== $symKeyInfoAlgo) { - throw new OneLogin_Saml2_ValidationError( + throw new ValidationError( '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), - OneLogin_Saml2_ValidationError::KEY_ALGORITHM_ERROR + ValidationError::KEY_ALGORITHM_ERROR ); } @@ -1176,9 +1180,9 @@ public static function decryptElement(DOMElement $encryptedData, XMLSecurityKey $keySize = $symmetricKey->getSymmetricKeySize(); if ($keySize === null) { // To protect against "key oracle" attacks - throw new OneLogin_Saml2_ValidationError( + throw new ValidationError( 'Unknown key size for encryption algorithm: ' . var_export($symmetricKey->type, true), - OneLogin_Saml2_ValidationError::KEY_ALGORITHM_ERROR + ValidationError::KEY_ALGORITHM_ERROR ); } @@ -1200,11 +1204,11 @@ public static function decryptElement(DOMElement $encryptedData, XMLSecurityKey } else { $symKeyAlgo = $symmetricKey->getAlgorithm(); if ($inputKeyAlgo !== $symKeyAlgo) { - throw new OneLogin_Saml2_ValidationError( + throw new ValidationError( 'Algorithm mismatch between input key and key in message. ' . 'Key was: ' . var_export($inputKeyAlgo, true) . '; message was: ' . var_export($symKeyAlgo, true), - OneLogin_Saml2_ValidationError::KEY_ALGORITHM_ERROR + ValidationError::KEY_ALGORITHM_ERROR ); } $symmetricKey = $inputKey; @@ -1220,29 +1224,29 @@ public static function decryptElement(DOMElement $encryptedData, XMLSecurityKey } $newDoc = self::loadXML($newDoc, $xml); if (!$newDoc) { - throw new OneLogin_Saml2_ValidationError( + throw new ValidationError( 'Failed to parse decrypted XML.', - OneLogin_Saml2_ValidationError::INVALID_XML_FORMAT + ValidationError::INVALID_XML_FORMAT ); } $decryptedElement = $newDoc->firstChild->firstChild; if ($decryptedElement === null) { - throw new OneLogin_Saml2_ValidationError( + throw new ValidationError( 'Missing encrypted element.', - OneLogin_Saml2_ValidationError::MISSING_ENCRYPTED_ELEMENT + ValidationError::MISSING_ENCRYPTED_ELEMENT ); } return $decryptedElement; } - /** + /** * Converts a XMLSecurityKey to the correct algorithm. * - * @param XMLSecurityKey $key The key. - * @param string $algorithm The desired algorithm. - * @param string $type Public or private key, defaults to public. + * @param XMLSecurityKey $key The key. + * @param string $algorithm The desired algorithm. + * @param string $type Public or private key, defaults to public. * * @return XMLSecurityKey The new key. * @@ -1250,14 +1254,15 @@ public static function decryptElement(DOMElement $encryptedData, XMLSecurityKey */ public static function castKey(XMLSecurityKey $key, $algorithm, $type = 'public') { - assert('is_string($algorithm)'); - assert('$type === "public" || $type === "private"'); + 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; } - if (!OneLogin_Saml2_Utils::isSupportedSigningAlgorithm($algorithm)) { + if (!Utils::isSupportedSigningAlgorithm($algorithm)) { throw new Exception('Unsupported signing algorithm.'); } @@ -1295,17 +1300,17 @@ public static function isSupportedSigningAlgorithm($algorithm) /** * Adds signature key and senders certificate to an element (Message or Assertion). * - * @param string|DomDocument $xml The element we should sign - * @param string $key The private key - * @param string $cert The public - * @param string $signAlgorithm Signature algorithm method + * @param string|DOMDocument $xml The element we should sign + * @param string $key The private key + * @param string $cert The public + * @param string $signAlgorithm Signature algorithm method * @param string $digestAlgorithm Digest algorithm method * * @return string * * @throws Exception */ - public static function addSign($xml, $key, $cert, $signAlgorithm = XMLSecurityKey::RSA_SHA1, $digestAlgorithm = XMLSecurityDSig::SHA1) + public static function addSign($xml, $key, $cert, $signAlgorithm = XMLSecurityKey::RSA_SHA256, $digestAlgorithm = XMLSecurityDSig::SHA256) { if ($xml instanceof DOMDocument) { $dom = $xml; @@ -1361,12 +1366,12 @@ public static function addSign($xml, $key, $cert, $signAlgorithm = XMLSecurityKe /** * Validates a signature (Message or Assertion). * - * @param string|DomNode $xml The element we should validate - * @param string|null $cert The pubic cert - * @param string|null $fingerprint The fingerprint of the public cert - * @param string|null $fingerprintalg The algorithm used to get the fingerprint - * @param string|null $xpath The xpath of the signed element - * @param array|null $multiCerts Multiple public certs + * @param string|\DomNode $xml The element we should validate + * @param string|null $cert The pubic cert + * @param string|null $fingerprint The fingerprint of the public cert + * @param string|null $fingerprintalg The algorithm used to get the fingerprint + * @param string|null $xpath The xpath of the signed element + * @param array|null $multiCerts Multiple public certs * * @return bool * @@ -1387,7 +1392,7 @@ public static function validateSign($xml, $cert = null, $fingerprint = null, $fi $objXMLSecDSig->idKeys = array('ID'); if ($xpath) { - $nodeset = OneLogin_Saml2_Utils::query($dom, $xpath); + $nodeset = Utils::query($dom, $xpath); $objDSig = $nodeset->item(0); $objXMLSecDSig->sigNode = $objDSig; } else { @@ -1403,7 +1408,7 @@ public static function validateSign($xml, $cert = null, $fingerprint = null, $fi throw new Exception('We have no idea about the key'); } - if (!OneLogin_Saml2_Utils::isSupportedSigningAlgorithm($objKey->type)) { + if (!Utils::isSupportedSigningAlgorithm($objKey->type)) { throw new Exception('Unsupported signing algorithm.'); } @@ -1440,8 +1445,8 @@ public static function validateSign($xml, $cert = null, $fingerprint = null, $fi } else { if (!empty($fingerprint)) { $domCert = $objKey->getX509Certificate(); - $domCertFingerprint = OneLogin_Saml2_Utils::calculateX509Fingerprint($domCert, $fingerprintalg); - if (OneLogin_Saml2_Utils::formatFingerPrint($fingerprint) == $domCertFingerprint) { + $domCertFingerprint = Utils::calculateX509Fingerprint($domCert, $fingerprintalg); + if (Utils::formatFingerPrint($fingerprint) == $domCertFingerprint) { $objKey->loadKey($domCert, false, true); if ($objXMLSecDSig->verify($objKey) === 1) { $valid = true; @@ -1475,11 +1480,11 @@ public static function validateBinarySign($messageType, $getData, $idpData, $ret } if ($retrieveParametersFromServer) { - $signedQuery = $messageType.'='.OneLogin_Saml2_Utils::extractOriginalQueryParam($messageType); + $signedQuery = $messageType.'='.Utils::extractOriginalQueryParam($messageType); if (isset($getData['RelayState'])) { - $signedQuery .= '&RelayState='.OneLogin_Saml2_Utils::extractOriginalQueryParam('RelayState'); + $signedQuery .= '&RelayState='.Utils::extractOriginalQueryParam('RelayState'); } - $signedQuery .= '&SigAlg='.OneLogin_Saml2_Utils::extractOriginalQueryParam('SigAlg'); + $signedQuery .= '&SigAlg='.Utils::extractOriginalQueryParam('SigAlg'); } else { $signedQuery = $messageType.'='.urlencode($getData[$messageType]); if (isset($getData['RelayState'])) { @@ -1495,9 +1500,9 @@ public static function validateBinarySign($messageType, $getData, $idpData, $ret } $existsMultiX509Sign = isset($idpData['x509certMulti']) && isset($idpData['x509certMulti']['signing']) && !empty($idpData['x509certMulti']['signing']); if ((!isset($idpData['x509cert']) || empty($idpData['x509cert'])) && !$existsMultiX509Sign) { - throw new OneLogin_Saml2_Error( + throw new Error( "In order to validate the sign on the ".$strMessageType.", the x509cert of the IdP is required", - OneLogin_Saml2_Error::CERT_NOT_FOUND + Error::CERT_NOT_FOUND ); } @@ -1514,11 +1519,11 @@ public static function validateBinarySign($messageType, $getData, $idpData, $ret if ($signAlg != XMLSecurityKey::RSA_SHA1) { try { - $objKey = OneLogin_Saml2_Utils::castKey($objKey, $signAlg, 'public'); + $objKey = Utils::castKey($objKey, $signAlg, 'public'); } catch (Exception $e) { - $ex = new OneLogin_Saml2_ValidationError( + $ex = new ValidationError( "Invalid signAlg in the recieved ".$strMessageType, - OneLogin_Saml2_ValidationError::INVALID_SIGNATURE + ValidationError::INVALID_SIGNATURE ); if (count($multiCerts) == 1) { throw $ex; diff --git a/onelogin-saml-sso/php/lib/Saml2/ValidationError.php b/onelogin-saml-sso/php/lib/Saml2/ValidationError.php new file mode 100644 index 0000000..889f531 --- /dev/null +++ b/onelogin-saml-sso/php/lib/Saml2/ValidationError.php @@ -0,0 +1,100 @@ + + * @license MIT https://github.com/onelogin/php-saml/blob/master/LICENSE + * @link https://github.com/onelogin/php-saml + */ + +namespace OneLogin\Saml2; + +use Exception; + +/** + * ValidationError class of OneLogin PHP Toolkit + * + * This class implements another custom Exception handler, + * related to exceptions that happens during validation process. + */ +class ValidationError extends Exception +{ + // Validation Errors + const UNSUPPORTED_SAML_VERSION = 0; + const MISSING_ID = 1; + const WRONG_NUMBER_OF_ASSERTIONS = 2; + const MISSING_STATUS = 3; + const MISSING_STATUS_CODE = 4; + const STATUS_CODE_IS_NOT_SUCCESS = 5; + const WRONG_SIGNED_ELEMENT = 6; + const ID_NOT_FOUND_IN_SIGNED_ELEMENT = 7; + const DUPLICATED_ID_IN_SIGNED_ELEMENTS = 8; + const INVALID_SIGNED_ELEMENT = 9; + const DUPLICATED_REFERENCE_IN_SIGNED_ELEMENTS = 10; + const UNEXPECTED_SIGNED_ELEMENTS = 11; + const WRONG_NUMBER_OF_SIGNATURES_IN_RESPONSE = 12; + const WRONG_NUMBER_OF_SIGNATURES_IN_ASSERTION = 13; + const INVALID_XML_FORMAT = 14; + const WRONG_INRESPONSETO = 15; + const NO_ENCRYPTED_ASSERTION = 16; + const NO_ENCRYPTED_NAMEID = 17; + const MISSING_CONDITIONS = 18; + const ASSERTION_TOO_EARLY = 19; + const ASSERTION_EXPIRED = 20; + const WRONG_NUMBER_OF_AUTHSTATEMENTS = 21; + const NO_ATTRIBUTESTATEMENT = 22; + const ENCRYPTED_ATTRIBUTES = 23; + const WRONG_DESTINATION = 24; + const EMPTY_DESTINATION = 25; + const WRONG_AUDIENCE = 26; + const ISSUER_MULTIPLE_IN_RESPONSE = 27; + const ISSUER_NOT_FOUND_IN_ASSERTION = 28; + const WRONG_ISSUER = 29; + const SESSION_EXPIRED = 30; + const WRONG_SUBJECTCONFIRMATION = 31; + const NO_SIGNED_MESSAGE = 32; + const NO_SIGNED_ASSERTION = 33; + const NO_SIGNATURE_FOUND = 34; + const KEYINFO_NOT_FOUND_IN_ENCRYPTED_DATA = 35; + const CHILDREN_NODE_NOT_FOUND_IN_KEYINFO = 36; + const UNSUPPORTED_RETRIEVAL_METHOD = 37; + const NO_NAMEID = 38; + const EMPTY_NAMEID = 39; + const SP_NAME_QUALIFIER_NAME_MISMATCH = 40; + const DUPLICATED_ATTRIBUTE_NAME_FOUND = 41; + const INVALID_SIGNATURE = 42; + const WRONG_NUMBER_OF_SIGNATURES = 43; + const RESPONSE_EXPIRED = 44; + const UNEXPECTED_REFERENCE = 45; + const NOT_SUPPORTED = 46; + const KEY_ALGORITHM_ERROR = 47; + const MISSING_ENCRYPTED_ELEMENT = 48; + + + /** + * Constructor + * + * @param string $msg Describes the error. + * @param int $code The code error (defined in the error class). + * @param array|null $args Arguments used in the message that describes the error. + */ + public function __construct($msg, $code = 0, $args = array()) + { + assert(is_string($msg)); + assert(is_int($code)); + + if (!isset($args)) { + $args = array(); + } + $params = array_merge(array($msg), $args); + $message = call_user_func_array('sprintf', $params); + + parent::__construct($message, $code); + } +} diff --git a/onelogin-saml-sso/php/lib/Saml2/version.json b/onelogin-saml-sso/php/lib/Saml2/version.json index b00006b..743c4c4 100644 --- a/onelogin-saml-sso/php/lib/Saml2/version.json +++ b/onelogin-saml-sso/php/lib/Saml2/version.json @@ -1,6 +1,7 @@ { "php-saml": { - "version": "2.14.0", - "released": "07/06/2018" + "version": "3.1.0", + "released": "28/01/2019" } } + diff --git a/onelogin-saml-sso/php/settings.php b/onelogin-saml-sso/php/settings.php index cfccef4..3ce7717 100644 --- a/onelogin-saml-sso/php/settings.php +++ b/onelogin-saml-sso/php/settings.php @@ -6,26 +6,27 @@ exit; } -require_once (dirname(__FILE__) . "/lib/Saml2/Constants.php"); +require_once "_toolkit_loader.php"; +use OneLogin\Saml2\Constants; $posible_nameidformat_values = array( - 'unspecified' => OneLogin_Saml2_Constants::NAMEID_UNSPECIFIED, - 'emailAddress' => OneLogin_Saml2_Constants::NAMEID_EMAIL_ADDRESS, - 'transient' => OneLogin_Saml2_Constants::NAMEID_TRANSIENT, - 'persistent' => OneLogin_Saml2_Constants::NAMEID_PERSISTENT, - 'entity' => OneLogin_Saml2_Constants::NAMEID_ENTITY, - 'encrypted' => OneLogin_Saml2_Constants::NAMEID_ENCRYPTED, - 'kerberos' => OneLogin_Saml2_Constants::NAMEID_KERBEROS, - 'x509subjecname' => OneLogin_Saml2_Constants::NAMEID_X509_SUBJECT_NAME, - 'windowsdomainqualifiedname' => OneLogin_Saml2_Constants::NAMEID_WINDOWS_DOMAIN_QUALIFIED_NAME + 'unspecified' => Constants::NAMEID_UNSPECIFIED, + 'emailAddress' => Constants::NAMEID_EMAIL_ADDRESS, + 'transient' => Constants::NAMEID_TRANSIENT, + 'persistent' => Constants::NAMEID_PERSISTENT, + 'entity' => Constants::NAMEID_ENTITY, + 'encrypted' => Constants::NAMEID_ENCRYPTED, + 'kerberos' => Constants::NAMEID_KERBEROS, + 'x509subjecname' => Constants::NAMEID_X509_SUBJECT_NAME, + 'windowsdomainqualifiedname' => Constants::NAMEID_WINDOWS_DOMAIN_QUALIFIED_NAME ); $posible_requestedauthncontext_values = array( - 'unspecified' => OneLogin_Saml2_Constants::AC_UNSPECIFIED, - 'password' => OneLogin_Saml2_Constants::AC_PASSWORD, + 'unspecified' => Constants::AC_UNSPECIFIED, + 'password' => Constants::AC_PASSWORD, 'passwordprotectedtransport' => "urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport", - 'x509' => OneLogin_Saml2_Constants::AC_X509, - 'smartcard' => OneLogin_Saml2_Constants::AC_SMARTCARD, - 'kerberos' => OneLogin_Saml2_Constants::AC_KERBEROS, + 'x509' => Constants::AC_X509, + 'smartcard' => Constants::AC_SMARTCARD, + 'kerberos' => Constants::AC_KERBEROS, ); diff --git a/onelogin-saml-sso/php/validate.php b/onelogin-saml-sso/php/validate.php index ff29544..5f1c599 100755 --- a/onelogin-saml-sso/php/validate.php +++ b/onelogin-saml-sso/php/validate.php @@ -1,4 +1,5 @@ @@ -31,9 +35,9 @@ $spCert = $settings['sp']['privateKey']; try { - $samlSettings = new OneLogin_Saml2_Settings($settings); + $samlSettings = new Settings($settings); echo '
'.__("SAML settings are", 'onelogin-saml-sso').' ok.
'; -} catch (Exception $e) { +} catch (\Exception $e) { echo '
'.__("Found errors while validating SAML settings info.", 'onelogin-saml-sso'); echo esc_html($e->getMessage()); echo '
'; diff --git a/onelogin-saml-sso/readme.txt b/onelogin-saml-sso/readme.txt index 815421c..cf0e96a 100644 --- a/onelogin-saml-sso/readme.txt +++ b/onelogin-saml-sso/readme.txt @@ -2,7 +2,7 @@ Contributors: onelogin Tags: sso, saml, single sign on, password, active directory, ldap, identity, onelogin, yubico, yubikey, vip access, otp Requires at least: 2.1.2 -Tested up to: 4.9.6 +Tested up to: 5.0.3 Stable tag: trunk This plugin provides single sign-on via SAML and gives users one-click access to their WordPress accounts from identity providers like OneLogin. @@ -22,6 +22,11 @@ To mitigate that bug, place the script at the root of wordpress and execute it ( == Changelog == += 3.0.0 = +* Update php-saml to 3.1.0 to make the plugin compatible with PHP7.3 +* Overriding user_register will prevent admins to register users, so deactivating that override +* Stop using $blog_id at the is_user_member_of_blog call + = 2.8.0 = * Update php-saml to 2.14.0 * Remove the use of screen_icon method