From 453ca4a80716007ca0997ef2550aeb2eb78929fa Mon Sep 17 00:00:00 2001 From: Tim van Dijen Date: Mon, 11 Jan 2021 17:55:53 +0100 Subject: [PATCH 01/78] Rewrite signed elements structure --- src/XML/AbstractSignedXMLElement.php | 41 ++++++++++ src/XML/SignableElementInterface.php | 49 ++++++++++++ src/XML/SignableElementTrait.php | 113 +++++++++++++++++++++++++++ src/XML/SignedElementInterface.php | 33 ++++++++ src/XML/SignedElementTrait.php | 113 +++++++++++++++++++++++++++ 5 files changed, 349 insertions(+) create mode 100644 src/XML/AbstractSignedXMLElement.php create mode 100644 src/XML/SignableElementInterface.php create mode 100644 src/XML/SignableElementTrait.php create mode 100644 src/XML/SignedElementInterface.php create mode 100644 src/XML/SignedElementTrait.php diff --git a/src/XML/AbstractSignedXMLElement.php b/src/XML/AbstractSignedXMLElement.php new file mode 100644 index 00000000..f21afdf7 --- /dev/null +++ b/src/XML/AbstractSignedXMLElement.php @@ -0,0 +1,41 @@ +certificates; + } + + + /** + * Set the certificates that should be included in the element. + * The certificates should be strings with the PEM encoded data. + * + * @param string[] $certificates An array of certificates. + */ + public function setCertificates(array $certificates): void + { + Assert::allStringNotEmpty($certificates); + + $this->certificates = $certificates; + } + + + /** + * Get the private key we should use to sign the message. + * + * If the key is null, the message will be sent unsigned. + * + * @return \SimpleSAML\XMLSecurity\XMLSecurityKey|null + */ + public function getSigningKey(): ?XMLSecurityKey + { + return $this->signingKey; + } + + + /** + * Set the private key we should use to sign the message. + * + * If the key is null, the message will be sent unsigned. + * + * @param \SimpleSAML\XMLSecurity\XMLSecurityKey|null $signingKey + */ + public function setSigningKey(XMLSecurityKey $signingKey = null): void + { + $this->signingKey = $signingKey; + } + + + /** + * Sign the given XML element. + * + * @param \DOMElement $root The element we should sign. + * @return \DOMElement The signed element. + * @throws \Exception If an error occurs while trying to sign. + protected function signElement(DOMElement $root, DOMNode $insertBefore = null): DOMElement + { + if ($this->signingKey instanceof XMLSecurityKey) { + if ($insertBefore !== null) { + XMLSecurityUtils::insertSignature($this->signingKey, $this->certificates, $root, $insertBefore); + + $doc = clone $root->ownerDocument; + $this->signature = Signature::fromXML(XMLUtils::xpQuery($doc->documentElement, './ds:Signature')[0]); + } else { + $this->signature = new Signature($this->signingKey->getAlgorithm(), $this->certificates, $this->signingKey); + $this->signature->toXML($root); + } + } + return $root; + } + */ +} diff --git a/src/XML/SignedElementInterface.php b/src/XML/SignedElementInterface.php new file mode 100644 index 00000000..94d2626a --- /dev/null +++ b/src/XML/SignedElementInterface.php @@ -0,0 +1,33 @@ +signature; + } + + + /** + * Initialize a signed element from XML. + * + * @param \SimpleSAML\XMLSecurity\XML\ds\Signature|null $signature The ds:Signature object + */ + protected function setSignature(?Signature $signature): void + { + if ($signature) { + $this->signature = $signature; + } + } + + + /** + * Validate this element against a public key. + * + * true is returned on success, false is returned if we don't have any + * signature we can validate. An exception is thrown if the signature + * validation fails. + * + * @param \SimpleSAML\XMLSecurity\XMLSecurityKey $key The key we should check against. + * @return bool True on success, false when we don't have a signature. + * @throws \Exception + */ + public function validate(XMLSecurityKey $key): bool + { + if ($this->signature === null) { + return false; + } + + $signer = $this->signature->getSigner(); + Assert::eq( + $key->getAlgorithm(), + $this->signature->getAlgorithm(), + 'Algorithm provided in key does not match algorithm used in signature.' + ); + + // check the signature + if ($signer->verify($key) === 1) { + return true; + } + + throw new Exception("Unable to validate Signature"); + } + + + /** + * Retrieve certificates that sign this element. + * + * @return array Array with certificates. + * @throws \Exception if an error occurs while trying to extract the public key from a certificate. + */ + public function getValidatingCertificates(): array + { + if ($this->signature === null) { + return []; + } + $ret = []; + foreach ($this->signature->getCertificates() as $cert) { + // extract the public key from the certificate for validation. + $key = new XMLSecurityKey($this->signature->getAlgorithm(), ['type' => 'public']); + $key->loadKey($cert); + + try { + // check the signature. + if ($this->validate($key)) { + $ret[] = $cert; + } + } catch (Exception $e) { + // this certificate does not sign this element. + } + } + + return $ret; + } +} From 5fb279dc8acb49dbd6ff0e8b2d8daf9f511c0ec9 Mon Sep 17 00:00:00 2001 From: Tim van Dijen Date: Mon, 11 Jan 2021 19:01:34 +0100 Subject: [PATCH 02/78] Add toUnsignedXML to the SignableElementTrait --- src/XML/SignableElementInterface.php | 38 ++-------- src/XML/SignableElementTrait.php | 102 +++++---------------------- 2 files changed, 23 insertions(+), 117 deletions(-) diff --git a/src/XML/SignableElementInterface.php b/src/XML/SignableElementInterface.php index c89177e8..3f5d24f4 100644 --- a/src/XML/SignableElementInterface.php +++ b/src/XML/SignableElementInterface.php @@ -2,6 +2,7 @@ namespace SimpleSAML\SAML2\XML; +use SimpleSAML\XMLSecurity\SignedElementInterface; use SimpleSAML\XMLSecurity\XMLSecurityKey; /** @@ -12,38 +13,11 @@ interface SignableElementInterface { /** - * Retrieve the certificates that are included in the message. + * Sign the 'Element' and return a 'SignedElement' * - * @return string[] An array of certificates + * @param \SimpleSAML\XMLSecurity\XMLSecurityKey $signingKey The private key we should use to sign the message + * @param string[] $certificates The certificates should be strings with the PEM encoded data + * @return \SimpleSAML\XMLSecurity\SignedElementInterface */ - public function getCertificates(): array; - - - /** - * Set the certificates that should be included in the element. - * The certificates should be strings with the PEM encoded data. - * - * @param string[] $certificates An array of certificates. - */ - public function setCertificates(array $certificates): void; - - - /** - * Get the private key we should use to sign the message. - * - * If the key is null, the message will be sent unsigned. - * - * @return \SimpleSAML\XMLSecurity\XMLSecurityKey|null - */ - public function getSigningKey(): ?XMLSecurityKey; - - - /** - * Set the private key we should use to sign the message. - * - * If the key is null, the message will be sent unsigned. - * - * @param \SimpleSAML\XMLSecurity\XMLSecurityKey|null $signingKey - */ - public function setSigningKey(XMLSecurityKey $signingKey = null): void; + public function sign(XMLSecurityKey $signingKey, array $certificates): SignedElementInterface; } diff --git a/src/XML/SignableElementTrait.php b/src/XML/SignableElementTrait.php index 84934326..d5d9a8f8 100644 --- a/src/XML/SignableElementTrait.php +++ b/src/XML/SignableElementTrait.php @@ -4,11 +4,10 @@ namespace SimpleSAML\XMLSecurity\XML; -//use DOMElement; -//use DOMNode; -//use Exception; +use DOMElement; +use DOMNode; use SimpleSAML\Assert\Assert; -//use SimpleSAML\XMLSecurity\Utils\Security as XMLSecurityUtils; +use SimpleSAML\XMLSecurity\Utils\Security as XMLSecurityUtils; use SimpleSAML\XMLSecurity\XML\ds\Signature; use SimpleSAML\XMLSecurity\XMLSecurityKey; use SimpleSAML\XML\Utils as XMLUtils; @@ -20,94 +19,27 @@ */ trait SignableElementTrait { - /** - * List of certificates that should be included in the message. - * - * @var string[] - */ - protected array $certificates = []; - - /** - * The private key we should use to sign an unsigned message. - * - * The private key can be null, in which case we can only validate an already signed message. - * - * @var \SimpleSAML\XMLSecurity\XMLSecurityKey|null - */ - protected ?XMLSecurityKey $signingKey = null; - - - /** - * Retrieve the certificates that are included in the message. - * - * @return string[] An array of certificates - */ - public function getCertificates(): array - { - return $this->certificates; - } - - - /** - * Set the certificates that should be included in the element. - * The certificates should be strings with the PEM encoded data. - * - * @param string[] $certificates An array of certificates. - */ - public function setCertificates(array $certificates): void - { - Assert::allStringNotEmpty($certificates); - - $this->certificates = $certificates; - } - - - /** - * Get the private key we should use to sign the message. - * - * If the key is null, the message will be sent unsigned. - * - * @return \SimpleSAML\XMLSecurity\XMLSecurityKey|null - */ - public function getSigningKey(): ?XMLSecurityKey - { - return $this->signingKey; - } - - - /** - * Set the private key we should use to sign the message. - * - * If the key is null, the message will be sent unsigned. - * - * @param \SimpleSAML\XMLSecurity\XMLSecurityKey|null $signingKey - */ - public function setSigningKey(XMLSecurityKey $signingKey = null): void - { - $this->signingKey = $signingKey; - } - - /** * Sign the given XML element. * - * @param \DOMElement $root The element we should sign. + * @param \SimpleSAML\XMLSecurity\XMLSecurityKey $signKey The private key used for signing. + * @param array $certificates Any public key to be added to the ds:Signature + * @param \DOMNode|null $insertBefore A specific node in the DOM structure where the ds:Signature should be put in front. * @return \DOMElement The signed element. * @throws \Exception If an error occurs while trying to sign. - protected function signElement(DOMElement $root, DOMNode $insertBefore = null): DOMElement + */ + private function toSignedXML(XMLSecurityKey $signKey, array $certificates, DOMNode $insertBefore = null): DOMElement { - if ($this->signingKey instanceof XMLSecurityKey) { - if ($insertBefore !== null) { - XMLSecurityUtils::insertSignature($this->signingKey, $this->certificates, $root, $insertBefore); - - $doc = clone $root->ownerDocument; - $this->signature = Signature::fromXML(XMLUtils::xpQuery($doc->documentElement, './ds:Signature')[0]); - } else { - $this->signature = new Signature($this->signingKey->getAlgorithm(), $this->certificates, $this->signingKey); - $this->signature->toXML($root); - } + $root = $this->toXML(); + + if ($insertBefore !== null) { + XMLSecurityUtils::insertSignature($this->signingKey, $this->certificates, $root, $insertBefore); + $doc = clone $root->ownerDocument; + } else { + $signature = new Signature($this->signingKey->getAlgorithm(), $this->certificates, $this->signingKey); + $signature->toXML($root); } + return $root; } - */ } From b877b16f3b84550e02da69954635f00abd722bc4 Mon Sep 17 00:00:00 2001 From: Tim van Dijen Date: Mon, 11 Jan 2021 19:04:40 +0100 Subject: [PATCH 03/78] Any signed element should have a signature set --- src/XML/SignedElementTrait.php | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/src/XML/SignedElementTrait.php b/src/XML/SignedElementTrait.php index 46880fdf..22b2cc71 100644 --- a/src/XML/SignedElementTrait.php +++ b/src/XML/SignedElementTrait.php @@ -27,7 +27,7 @@ trait SignableElementTrait /** * Get the signature element of this object. * - * @return \SimpleSAML\XMLSecurity\XML\ds\Signature|null + * @return \SimpleSAML\XMLSecurity\XML\ds\Signature */ public function getSignature(): ?Signature { @@ -38,13 +38,11 @@ public function getSignature(): ?Signature /** * Initialize a signed element from XML. * - * @param \SimpleSAML\XMLSecurity\XML\ds\Signature|null $signature The ds:Signature object + * @param \SimpleSAML\XMLSecurity\XML\ds\Signature $signature The ds:Signature object */ - protected function setSignature(?Signature $signature): void + protected function setSignature(Signature $signature): void { - if ($signature) { - $this->signature = $signature; - } + $this->signature = $signature; } @@ -89,9 +87,6 @@ public function validate(XMLSecurityKey $key): bool */ public function getValidatingCertificates(): array { - if ($this->signature === null) { - return []; - } $ret = []; foreach ($this->signature->getCertificates() as $cert) { // extract the public key from the certificate for validation. From c7c4ab8ba6a98e1524078366ef254e8e703eacf8 Mon Sep 17 00:00:00 2001 From: Tim van Dijen Date: Mon, 11 Jan 2021 20:25:10 +0100 Subject: [PATCH 04/78] Further finalize abstract --- src/XML/AbstractSignedXMLElement.php | 97 ++++++++++++++++++++++++---- src/XML/SignableElementInterface.php | 7 +- src/XML/SignableElementTrait.php | 8 +-- src/XML/SignedElementInterface.php | 4 +- src/XML/SignedElementTrait.php | 2 +- 5 files changed, 96 insertions(+), 22 deletions(-) diff --git a/src/XML/AbstractSignedXMLElement.php b/src/XML/AbstractSignedXMLElement.php index f21afdf7..5fb9b434 100644 --- a/src/XML/AbstractSignedXMLElement.php +++ b/src/XML/AbstractSignedXMLElement.php @@ -2,13 +2,10 @@ declare(strict_types=1); -namespace SimpleSAML\XML; +namespace SimpleSAML\XMLSecurity\XML; use DOMElement; -use SimpleSAML\XML\DOMDocumentFactory; -use SimpleSAML\XML\Exception\MissingAttributeException; -use Serializable; -use SimpleSAML\Assert\Assert; +use SimpleSAML\XMLSecurity\XML\ds\Signature; /** * Abstract class to be implemented by all signed classes @@ -19,12 +16,6 @@ abstract class AbstractSignedXMLElement implements SignedElementInterface { use SignedElementTrait; - /** - * Create a document structure for this element - * - * @param \DOMElement|null $parent The element we should append to. - * @return \DOMElement - */ /** * The signed DOM structure. * @@ -35,7 +26,87 @@ abstract class AbstractSignedXMLElement implements SignedElementInterface /** * The unsigned elelement. * - * @var \SimpleSAML\XML\AbstractXMLElement + * @var \SimpleSAML\XMLSecurity\XML\SignableElementInterface + */ + protected SignableElementInterface $element; + + + /** + * Create/parse an alg:SigningMethod element. + * + * @param \DOMElement $xml + * @param \SimpleSAML\XMLSecurity\XML\SignableElementInterface $elt + * @param \SimpleSAML\XMLSecurity\XML\ds\Signature $signature + */ + private function __construct(DOMElement $xml, SignableElementInterface $elt, Signature $signature) + { + $this->setStructure($xml); + $this->setElement($elt); + $this->setSignature($signature); + } + + + /** + * Collect the value of the unsigned element + * + * @return \SimpleSAML\XMLSecurity\XML\SignableElementInterface + */ + public function getElement(): SignableElementInterface + { + return $this->element; + } + + + /** + * Set the value of the elment-property + * + * @param \SimpleSAML\XMLSecurity\XML\SignableElementInterface $elt + */ + private function setElement(SignableElementInterface $elt): void + { + $this->element = $elt; + } + + + /** + * Collect the value of the structure-property + * + * @return \DOMElement + */ + public function getStructure(): DOMElement + { + return $this->structure; + } + + + /** + * Set the value of the structure-property + * + * @param \DOMElement $structure + */ + private function setStructure(DOMElement $structure): void + { + $this->structure = $structure; + } + + + /** + * Create XML from this class + * + * @param \DOMElement|null $parent + * @return \DOMElement + */ + public function toXML(DOMElement $parent = null): DOMElement + { + return $this->structure; + } + + + /** + * Create a class from XML + * + * @param \DOMElement $xml + * @return self */ - protected AbstractXMLElement $elt; + abstract public static function fromXML(DOMElement $xml): object; } diff --git a/src/XML/SignableElementInterface.php b/src/XML/SignableElementInterface.php index 3f5d24f4..e82302df 100644 --- a/src/XML/SignableElementInterface.php +++ b/src/XML/SignableElementInterface.php @@ -1,8 +1,9 @@ toXML(); if ($insertBefore !== null) { - XMLSecurityUtils::insertSignature($this->signingKey, $this->certificates, $root, $insertBefore); + XMLSecurityUtils::insertSignature($signingKey, $certificates, $root, $insertBefore); $doc = clone $root->ownerDocument; } else { - $signature = new Signature($this->signingKey->getAlgorithm(), $this->certificates, $this->signingKey); + $signature = new Signature($signingKey->getAlgorithm(), $certificates, $signingKey); $signature->toXML($root); } diff --git a/src/XML/SignedElementInterface.php b/src/XML/SignedElementInterface.php index 94d2626a..427b3d1b 100644 --- a/src/XML/SignedElementInterface.php +++ b/src/XML/SignedElementInterface.php @@ -1,6 +1,8 @@ Date: Mon, 11 Jan 2021 21:36:53 +0100 Subject: [PATCH 05/78] Add testable CustomSignable and CustomSigned --- bin/generate_CustomSignable.php | 17 ++++ src/XML/AbstractSignedXMLElement.php | 13 ++- src/XML/SignableElementTrait.php | 3 +- src/XML/SignedElementTrait.php | 2 +- tests/XML/CustomSignable.php | 137 +++++++++++++++++++++++++++ tests/XML/CustomSigned.php | 47 +++++++++ 6 files changed, 215 insertions(+), 4 deletions(-) create mode 100644 bin/generate_CustomSignable.php create mode 100644 tests/XML/CustomSignable.php create mode 100644 tests/XML/CustomSigned.php diff --git a/bin/generate_CustomSignable.php b/bin/generate_CustomSignable.php new file mode 100644 index 00000000..422ba6c2 --- /dev/null +++ b/bin/generate_CustomSignable.php @@ -0,0 +1,17 @@ +Chunk')->documentElement); +$signable = new CustomSignable($chunk); + +$privateKey = PEMCertificatesMock::getPrivateKey(XMLSecurityKey::RSA_SHA256, PEMCertificatesMock::SELFSIGNED_PRIVATE_KEY); +$x = $signable->sign($privateKey); +echo $x; +//var_dump($x); diff --git a/src/XML/AbstractSignedXMLElement.php b/src/XML/AbstractSignedXMLElement.php index 5fb9b434..ef54dc5a 100644 --- a/src/XML/AbstractSignedXMLElement.php +++ b/src/XML/AbstractSignedXMLElement.php @@ -38,7 +38,7 @@ abstract class AbstractSignedXMLElement implements SignedElementInterface * @param \SimpleSAML\XMLSecurity\XML\SignableElementInterface $elt * @param \SimpleSAML\XMLSecurity\XML\ds\Signature $signature */ - private function __construct(DOMElement $xml, SignableElementInterface $elt, Signature $signature) + protected function __construct(DOMElement $xml, SignableElementInterface $elt, Signature $signature) { $this->setStructure($xml); $this->setElement($elt); @@ -46,6 +46,17 @@ private function __construct(DOMElement $xml, SignableElementInterface $elt, Sig } + /** + * Output the class as an XML-formatted string + * + * @return string + */ + public function __toString(): string + { + return $this->structure->ownerDocument->saveXML(); + } + + /** * Collect the value of the unsigned element * diff --git a/src/XML/SignableElementTrait.php b/src/XML/SignableElementTrait.php index 43353890..86dca1f0 100644 --- a/src/XML/SignableElementTrait.php +++ b/src/XML/SignableElementTrait.php @@ -7,10 +7,10 @@ use DOMElement; use DOMNode; use SimpleSAML\Assert\Assert; +use SimpleSAML\XML\Utils as XMLUtils; use SimpleSAML\XMLSecurity\Utils\Security as XMLSecurityUtils; use SimpleSAML\XMLSecurity\XML\ds\Signature; use SimpleSAML\XMLSecurity\XMLSecurityKey; -use SimpleSAML\XML\Utils as XMLUtils; /** * Helper trait for processing signed elements. @@ -34,7 +34,6 @@ private function toSignedXML(XMLSecurityKey $signingKey, array $certificates, DO if ($insertBefore !== null) { XMLSecurityUtils::insertSignature($signingKey, $certificates, $root, $insertBefore); - $doc = clone $root->ownerDocument; } else { $signature = new Signature($signingKey->getAlgorithm(), $certificates, $signingKey); $signature->toXML($root); diff --git a/src/XML/SignedElementTrait.php b/src/XML/SignedElementTrait.php index ee94ac14..36574e96 100644 --- a/src/XML/SignedElementTrait.php +++ b/src/XML/SignedElementTrait.php @@ -63,7 +63,6 @@ public function validate(XMLSecurityKey $key): bool return false; } - $signer = $this->signature->getSigner(); Assert::eq( $key->getAlgorithm(), $this->signature->getAlgorithm(), @@ -71,6 +70,7 @@ public function validate(XMLSecurityKey $key): bool ); // check the signature + $signer = $this->signature->getSigner(); if ($signer->verify($key) === 1) { return true; } diff --git a/tests/XML/CustomSignable.php b/tests/XML/CustomSignable.php new file mode 100644 index 00000000..e0fa5dc9 --- /dev/null +++ b/tests/XML/CustomSignable.php @@ -0,0 +1,137 @@ +setElement($elt); + } + + + /** + * Get the namespace for the element. + * + * @return string + */ + public static function getNamespaceURI(): string + { + return static::NS; + } + + + /** + * Get the namespace-prefix for the element. + * + * @return string + */ + public static function getNamespacePrefix(): string + { + return static::NS_PREFIX; + } + + + /** + * Collect the value of the $element property + * + * @return \SimpleSAML\XML\XML\Chunk + */ + public function getElement(): Chunk + { + return $this->element; + } + + + /** + * Set the value of the elment-property + * + * @param \SimpleSAML\XML\Chunk $elt + */ + private function setElement(Chunk $elt): void + { + $this->element = $elt; + } + + + /** + * Sign the 'Element' and return a 'SignedElement' + * + * @param \SimpleSAML\XMLSecurity\XMLSecurityKey $signingKey The private key we should use to sign the message + * @param string[] $certificates The certificates should be strings with the PEM encoded data + * @return \SimpleSAML\XMLSecurity\XML\SignedElementInterface + */ + public function sign(XMLSecurityKey $signingKey, array $certificates = []): SignedElementInterface + { + return CustomSigned::fromXML($this->toSignedXML($signingKey, $certificates)); + } + + + /** + * Convert XML into a CustomSignable + * + * @param \DOMElement $xml The XML element we should load + * @return \SimpleSAML\XMLSecurity\Test\XML\CustomSignable + * + * @throws \SimpleSAML\XML\Exception\InvalidDOMElementException if the qualified name of the supplied element is wrong + */ + public static function fromXML(DOMElement $xml): object + { + Assert::same($xml->localName, 'CustomSignable', InvalidDOMElementException::class); + Assert::same($xml->namespaceURI, static::NS, InvalidDOMElementException::class); + + Assert::minCount($xml->childNodes, 1, MissingElementException::class); + Assert::maxCount($xml->childNodes, 2, TooManyElementsException::class); + $element = new Chunk($xml->childNodes[0]); + + return new self($element); + } + + + /** + * Convert this CustomSignable to XML. + * + * @param \DOMElement|null $element The element we are converting to XML. + * @return \DOMElement The XML element after adding the data corresponding to this CustomSignable. + */ + public function toXML(DOMElement $parent = null): DOMElement + { + /** @psalm-var \DOMDocument $e->ownerDocument */ + $e = $this->instantiateParentElement($parent); + + $e->appendChild($e->ownerDocument->importNode($this->element->getXML(), true)); + return $e; + } +} diff --git a/tests/XML/CustomSigned.php b/tests/XML/CustomSigned.php new file mode 100644 index 00000000..5cfa81ce --- /dev/null +++ b/tests/XML/CustomSigned.php @@ -0,0 +1,47 @@ +localName, 'CustomSignable', InvalidDOMElementException::class); + Assert::same($xml->namespaceURI, CustomSignable::NS, InvalidDOMElementException::class); + + $signature = Signature::getChildrenOfClass($xml); + Assert::minCount($signature, 1, MissingElementException::class); + Assert::minCount($signature, 1, TooManyElementsException::class); + + return new self( + $xml, + CustomSignable::fromXML($xml), + array_pop($signature) + ); + } +} From bb29c978cea030d0c4f15902916b95a2b79f1d8a Mon Sep 17 00:00:00 2001 From: Tim van Dijen Date: Sat, 16 Jan 2021 17:01:41 +0100 Subject: [PATCH 06/78] Test signable --- bin/generate_CustomSignable.php | 1 - tests/XML/CustomSignable.php | 4 +- tests/XML/CustomSignableTest.php | 82 +++++++++++++++++++ tests/XML/CustomSigned.php | 7 ++ tests/resources/xml/custom_CustomSignable.xml | 3 + tests/resources/xml/custom_CustomSigned.xml | 5 ++ 6 files changed, 99 insertions(+), 3 deletions(-) create mode 100644 tests/XML/CustomSignableTest.php create mode 100644 tests/resources/xml/custom_CustomSignable.xml create mode 100644 tests/resources/xml/custom_CustomSigned.xml diff --git a/bin/generate_CustomSignable.php b/bin/generate_CustomSignable.php index 422ba6c2..3144a927 100644 --- a/bin/generate_CustomSignable.php +++ b/bin/generate_CustomSignable.php @@ -14,4 +14,3 @@ $privateKey = PEMCertificatesMock::getPrivateKey(XMLSecurityKey::RSA_SHA256, PEMCertificatesMock::SELFSIGNED_PRIVATE_KEY); $x = $signable->sign($privateKey); echo $x; -//var_dump($x); diff --git a/tests/XML/CustomSignable.php b/tests/XML/CustomSignable.php index e0fa5dc9..2ec4aaa4 100644 --- a/tests/XML/CustomSignable.php +++ b/tests/XML/CustomSignable.php @@ -24,10 +24,10 @@ final class CustomSignable extends AbstractXMLElement implements SignableElement use SignableElementTrait; /** @var string */ - public const NS = 'urn:simplesamlphp:test'; + public const NS = 'urn:oasis:names:tc:SAML:2.0:assertion'; /** @var string */ - public const NS_PREFIX = 'ssp'; + public const NS_PREFIX = 'saml'; /** @var \SimpleSAML\XML\Chunk $element */ protected $element; diff --git a/tests/XML/CustomSignableTest.php b/tests/XML/CustomSignableTest.php new file mode 100644 index 00000000..59866a15 --- /dev/null +++ b/tests/XML/CustomSignableTest.php @@ -0,0 +1,82 @@ +document = DOMDocumentFactory::fromFile( + dirname(dirname(__FILE__)) . '/resources/xml/custom_CustomSignable.xml' + ); + } + + + /** + */ + public function testMarshalling(): void + { + $document = DOMDocumentFactory::fromString( + 'Chunk' + ); + + $customSignable = new CustomSignable(new Chunk($document->documentElement)); + $this->assertFalse($customSignable->isEmptyElement()); + $this->assertEquals( + $this->document->saveXML($this->document->documentElement), + strval($customSignable) + ); + } + + + /** + */ + public function testUnmarshalling(): void + { + $customSignable = CustomSignable::fromXML($this->document->documentElement); + + $customSignableElement = $customSignable->getElement(); + $customSignableElement = $customSignableElement->getXML(); + + $this->assertEquals('some', $customSignableElement->tagName); + $this->assertEquals( + 'Chunk', + $customSignableElement->textContent + ); + } + + + /** + * Test serialization / unserialization + */ + public function testSerialization(): void + { + $this->assertEquals( + $this->document->saveXML($this->document->documentElement), + strval(unserialize(serialize(CustomSignable::fromXML($this->document->documentElement)))) + ); + } +} + diff --git a/tests/XML/CustomSigned.php b/tests/XML/CustomSigned.php index 5cfa81ce..ebde998e 100644 --- a/tests/XML/CustomSigned.php +++ b/tests/XML/CustomSigned.php @@ -34,10 +34,17 @@ public static function fromXML(DOMElement $xml): object Assert::same($xml->localName, 'CustomSignable', InvalidDOMElementException::class); Assert::same($xml->namespaceURI, CustomSignable::NS, InvalidDOMElementException::class); +var_dump($xml->ownerDocument->saveXML()); +// $signatureElement = XMLUtils::xpQuery($xml, './ds:Signature'); +// Assert::minCount($signatureElement, 1, MissingElementException::class); +// Assert::minCount($signatureElement, 1, TooManyElementsException::class); $signature = Signature::getChildrenOfClass($xml); Assert::minCount($signature, 1, MissingElementException::class); Assert::minCount($signature, 1, TooManyElementsException::class); +// $clone = clone $signatureElement[0]; +// $signature = Signature::fromXML($clone); + return new self( $xml, CustomSignable::fromXML($xml), diff --git a/tests/resources/xml/custom_CustomSignable.xml b/tests/resources/xml/custom_CustomSignable.xml new file mode 100644 index 00000000..0ab64266 --- /dev/null +++ b/tests/resources/xml/custom_CustomSignable.xml @@ -0,0 +1,3 @@ + + Chunk + diff --git a/tests/resources/xml/custom_CustomSigned.xml b/tests/resources/xml/custom_CustomSigned.xml new file mode 100644 index 00000000..499f8083 --- /dev/null +++ b/tests/resources/xml/custom_CustomSigned.xml @@ -0,0 +1,5 @@ + + + + UFc2JQ3NbwaTn4RolCpUGudOo1fiLf+qAVznEK3tipQ=zNL052dCUhVupXy6vUNK831roWBwwRpIVroBaQbA3Bteff7dk94y9r9a6l1/muwcFy6CNVT1EiJ6v8G3a92imw9q4dfzi4TvXkyzm/s31OqmYZspDgVtRhypx2FF7zqNyU68NVZHFRlkApKoZY6dWFFm6ipnryrjUgtrWx8zDtk= +Chunk From 747a70eb45f5b31595c75af97f544fd6a596d9d3 Mon Sep 17 00:00:00 2001 From: Tim van Dijen Date: Sat, 16 Jan 2021 20:29:57 +0100 Subject: [PATCH 07/78] Remove getter/setter for element --- src/XML/AbstractSignedXMLElement.php | 24 +------- tests/XML/CustomSigned.php | 20 +++---- tests/XML/CustomSignedTest.php | 83 ++++++++++++++++++++++++++++ 3 files changed, 94 insertions(+), 33 deletions(-) create mode 100644 tests/XML/CustomSignedTest.php diff --git a/src/XML/AbstractSignedXMLElement.php b/src/XML/AbstractSignedXMLElement.php index ef54dc5a..6cd98984 100644 --- a/src/XML/AbstractSignedXMLElement.php +++ b/src/XML/AbstractSignedXMLElement.php @@ -40,8 +40,8 @@ abstract class AbstractSignedXMLElement implements SignedElementInterface */ protected function __construct(DOMElement $xml, SignableElementInterface $elt, Signature $signature) { + $this->element = $elt; $this->setStructure($xml); - $this->setElement($elt); $this->setSignature($signature); } @@ -57,28 +57,6 @@ public function __toString(): string } - /** - * Collect the value of the unsigned element - * - * @return \SimpleSAML\XMLSecurity\XML\SignableElementInterface - */ - public function getElement(): SignableElementInterface - { - return $this->element; - } - - - /** - * Set the value of the elment-property - * - * @param \SimpleSAML\XMLSecurity\XML\SignableElementInterface $elt - */ - private function setElement(SignableElementInterface $elt): void - { - $this->element = $elt; - } - - /** * Collect the value of the structure-property * diff --git a/tests/XML/CustomSigned.php b/tests/XML/CustomSigned.php index ebde998e..a5388ecd 100644 --- a/tests/XML/CustomSigned.php +++ b/tests/XML/CustomSigned.php @@ -34,21 +34,21 @@ public static function fromXML(DOMElement $xml): object Assert::same($xml->localName, 'CustomSignable', InvalidDOMElementException::class); Assert::same($xml->namespaceURI, CustomSignable::NS, InvalidDOMElementException::class); -var_dump($xml->ownerDocument->saveXML()); -// $signatureElement = XMLUtils::xpQuery($xml, './ds:Signature'); -// Assert::minCount($signatureElement, 1, MissingElementException::class); -// Assert::minCount($signatureElement, 1, TooManyElementsException::class); - $signature = Signature::getChildrenOfClass($xml); - Assert::minCount($signature, 1, MissingElementException::class); - Assert::minCount($signature, 1, TooManyElementsException::class); +//var_dump($xml->ownerDocument->saveXML()); + $signatureElement = XMLUtils::xpQuery($xml, './ds:Signature'); + Assert::minCount($signatureElement, 1, MissingElementException::class); + Assert::minCount($signatureElement, 1, TooManyElementsException::class); +// $signature = Signature::getChildrenOfClass($xml); +// Assert::minCount($signature, 1, MissingElementException::class); +// Assert::minCount($signature, 1, TooManyElementsException::class); -// $clone = clone $signatureElement[0]; -// $signature = Signature::fromXML($clone); + $clone = clone $signatureElement[0]; + $signature = Signature::fromXML($clone); return new self( $xml, CustomSignable::fromXML($xml), - array_pop($signature) + $signature ); } } diff --git a/tests/XML/CustomSignedTest.php b/tests/XML/CustomSignedTest.php new file mode 100644 index 00000000..3060e97a --- /dev/null +++ b/tests/XML/CustomSignedTest.php @@ -0,0 +1,83 @@ +document = DOMDocumentFactory::fromFile( + dirname(dirname(__FILE__)) . '/resources/xml/custom_CustomSigned.xml' + ); + } + + + /** + public function testMarshalling(): void + { + $document = DOMDocumentFactory::fromString( + 'Chunk' + ); + + $customSignable = new CustomSignable(new Chunk($document->documentElement)); + $this->assertFalse($customSignable->isEmptyElement()); + $this->assertEquals( + $this->document->saveXML($this->document->documentElement), + strval($customSignable) + ); + } + */ + + + /** + public function testUnmarshalling(): void + { + $customSignable = CustomSignable::fromXML($this->document->documentElement); + + $customSignableElement = $customSignable->getElement(); + $customSignableElement = $customSignableElement->getXML(); + + $this->assertEquals('some', $customSignableElement->tagName); + $this->assertEquals( + 'Chunk', + $customSignableElement->textContent + ); + } + */ + + + /** + * Test serialization / unserialization + */ + public function testSerialization(): void + { + $this->assertEquals( + $this->document->saveXML($this->document->documentElement), + strval(unserialize(serialize(CustomSigned::fromXML($this->document->documentElement)))) + ); + } +} + From 5ad95cd518612908d22c564b161230194d419768 Mon Sep 17 00:00:00 2001 From: Tim van Dijen Date: Sat, 16 Jan 2021 20:42:22 +0100 Subject: [PATCH 08/78] Remove unnecessary SignableElementTrait --- src/XML/SignableElementTrait.php | 44 -------------------------------- tests/XML/CustomSignable.php | 10 +++++--- tests/XML/CustomSignableTest.php | 1 - 3 files changed, 6 insertions(+), 49 deletions(-) delete mode 100644 src/XML/SignableElementTrait.php diff --git a/src/XML/SignableElementTrait.php b/src/XML/SignableElementTrait.php deleted file mode 100644 index 86dca1f0..00000000 --- a/src/XML/SignableElementTrait.php +++ /dev/null @@ -1,44 +0,0 @@ -toXML(); - - if ($insertBefore !== null) { - XMLSecurityUtils::insertSignature($signingKey, $certificates, $root, $insertBefore); - } else { - $signature = new Signature($signingKey->getAlgorithm(), $certificates, $signingKey); - $signature->toXML($root); - } - - return $root; - } -} diff --git a/tests/XML/CustomSignable.php b/tests/XML/CustomSignable.php index 2ec4aaa4..458138e8 100644 --- a/tests/XML/CustomSignable.php +++ b/tests/XML/CustomSignable.php @@ -12,7 +12,6 @@ use SimpleSAML\XML\Exception\MissingElementException; use SimpleSAML\XML\Exception\TooManyElementsException; use SimpleSAML\XMLSecurity\XML\SignableElementInterface; -use SimpleSAML\XMLSecurity\XML\SignableElementTrait; use SimpleSAML\XMLSecurity\XML\SignedElementInterface; use SimpleSAML\XMLSecurity\XMLSecurityKey; @@ -21,8 +20,6 @@ */ final class CustomSignable extends AbstractXMLElement implements SignableElementInterface { - use SignableElementTrait; - /** @var string */ public const NS = 'urn:oasis:names:tc:SAML:2.0:assertion'; @@ -95,7 +92,12 @@ private function setElement(Chunk $elt): void */ public function sign(XMLSecurityKey $signingKey, array $certificates = []): SignedElementInterface { - return CustomSigned::fromXML($this->toSignedXML($signingKey, $certificates)); + $unsigned = $this->toXML(); + + $signature = new Signature($signingKey->getAlgorithm(), $certificates, $signingKey); + $signature->toXML($unsigned); + + return $unsigned; } diff --git a/tests/XML/CustomSignableTest.php b/tests/XML/CustomSignableTest.php index 59866a15..114e4152 100644 --- a/tests/XML/CustomSignableTest.php +++ b/tests/XML/CustomSignableTest.php @@ -13,7 +13,6 @@ /** * Class \SimpleSAML\XMLSecurity\XML\CustomSignableTest * - * @covers \SimpleSAML\XMLSecurity\XML\SignableElementTrait * @covers \SimpleSAML\XMLSecurity\Test\XML\CustomSignable * * @package simplesamlphp/xml-security From de5a171083c8f7922e8f3c621fd96d0c5a1f8f54 Mon Sep 17 00:00:00 2001 From: Tim van Dijen Date: Sat, 16 Jan 2021 21:02:58 +0100 Subject: [PATCH 09/78] Fix --- src/XML/AbstractSignedXMLElement.php | 5 ++--- tests/XML/CustomSigned.php | 22 ++++------------------ 2 files changed, 6 insertions(+), 21 deletions(-) diff --git a/src/XML/AbstractSignedXMLElement.php b/src/XML/AbstractSignedXMLElement.php index 6cd98984..f7e39b6d 100644 --- a/src/XML/AbstractSignedXMLElement.php +++ b/src/XML/AbstractSignedXMLElement.php @@ -35,12 +35,11 @@ abstract class AbstractSignedXMLElement implements SignedElementInterface * Create/parse an alg:SigningMethod element. * * @param \DOMElement $xml - * @param \SimpleSAML\XMLSecurity\XML\SignableElementInterface $elt * @param \SimpleSAML\XMLSecurity\XML\ds\Signature $signature */ - protected function __construct(DOMElement $xml, SignableElementInterface $elt, Signature $signature) + protected function __construct(DOMElement $xml, Signature $signature) { - $this->element = $elt; +// $this->element = $elt; $this->setStructure($xml); $this->setSignature($signature); } diff --git a/tests/XML/CustomSigned.php b/tests/XML/CustomSigned.php index a5388ecd..8ffe326f 100644 --- a/tests/XML/CustomSigned.php +++ b/tests/XML/CustomSigned.php @@ -31,24 +31,10 @@ final class CustomSigned extends AbstractSignedXMLElement */ public static function fromXML(DOMElement $xml): object { - Assert::same($xml->localName, 'CustomSignable', InvalidDOMElementException::class); - Assert::same($xml->namespaceURI, CustomSignable::NS, InvalidDOMElementException::class); + $signature = Signature::getChildrenOfClass($xml); + Assert::minCount($signature, 1, MissingElementException::class); + Assert::maxCount($signature, 1, TooManyElementsException::class); -//var_dump($xml->ownerDocument->saveXML()); - $signatureElement = XMLUtils::xpQuery($xml, './ds:Signature'); - Assert::minCount($signatureElement, 1, MissingElementException::class); - Assert::minCount($signatureElement, 1, TooManyElementsException::class); -// $signature = Signature::getChildrenOfClass($xml); -// Assert::minCount($signature, 1, MissingElementException::class); -// Assert::minCount($signature, 1, TooManyElementsException::class); - - $clone = clone $signatureElement[0]; - $signature = Signature::fromXML($clone); - - return new self( - $xml, - CustomSignable::fromXML($xml), - $signature - ); + return new self($xml, array_pop($signature)); } } From 72fe857757c0aec928f07b3925c7eb4a641127d2 Mon Sep 17 00:00:00 2001 From: Tim van Dijen Date: Sat, 16 Jan 2021 21:28:44 +0100 Subject: [PATCH 10/78] Fix --- src/XML/SignedElementInterface.php | 4 ++-- src/XML/SignedElementTrait.php | 9 +++++---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/XML/SignedElementInterface.php b/src/XML/SignedElementInterface.php index 427b3d1b..d94f983b 100644 --- a/src/XML/SignedElementInterface.php +++ b/src/XML/SignedElementInterface.php @@ -29,7 +29,7 @@ public function getValidatingCertificates(): array; * but cannot be verified, an exception will be thrown. * * @param \SimpleSAML\XMLSecurity\XMLSecurityKey $key The key we should check against. - * @return bool True if successful, false if we don't have a signature that can be verified. + * @return \SimpleSAML\XMLSecurity\XML\SignedElementInterface The signed element if we can verify the signature. */ - public function validate(XMLSecurityKey $key): bool; + public function validate(XMLSecurityKey $key): SignedElementInterface; } diff --git a/src/XML/SignedElementTrait.php b/src/XML/SignedElementTrait.php index 36574e96..3b6796c2 100644 --- a/src/XML/SignedElementTrait.php +++ b/src/XML/SignedElementTrait.php @@ -7,6 +7,7 @@ use Exception; use SimpleSAML\Assert\Assert; use SimpleSAML\XMLSecurity\XML\ds\Signature; +use SimpleSAML\XMLSecurity\XML\SignedElementInterface; use SimpleSAML\XMLSecurity\XMLSecurityKey; /** @@ -54,13 +55,13 @@ protected function setSignature(Signature $signature): void * validation fails. * * @param \SimpleSAML\XMLSecurity\XMLSecurityKey $key The key we should check against. - * @return bool True on success, false when we don't have a signature. + * @return \SimpleSAML\XMLSecurity\XML\SignedElementInterface The Signed element if it was validated. * @throws \Exception */ - public function validate(XMLSecurityKey $key): bool + public function validate(XMLSecurityKey $key): SignedElementInterface { if ($this->signature === null) { - return false; + throw new Exception("Unsigned element"); } Assert::eq( @@ -72,7 +73,7 @@ public function validate(XMLSecurityKey $key): bool // check the signature $signer = $this->signature->getSigner(); if ($signer->verify($key) === 1) { - return true; + return /* This should return the signed element */; } throw new Exception("Unable to validate Signature"); From df18f2c7fd0f2f496b0f9ef1846388602d9909da Mon Sep 17 00:00:00 2001 From: Tim van Dijen Date: Mon, 29 Mar 2021 21:10:14 +0200 Subject: [PATCH 11/78] Make SignedElement*::fromXML generic --- src/XML/AbstractSignedXMLElement.php | 13 +++++++++++-- tests/XML/CustomSigned.php | 25 ------------------------- 2 files changed, 11 insertions(+), 27 deletions(-) diff --git a/src/XML/AbstractSignedXMLElement.php b/src/XML/AbstractSignedXMLElement.php index f7e39b6d..afbdbb7f 100644 --- a/src/XML/AbstractSignedXMLElement.php +++ b/src/XML/AbstractSignedXMLElement.php @@ -5,6 +5,9 @@ namespace SimpleSAML\XMLSecurity\XML; use DOMElement; +use SimpleSAML\Assert\Assert; +use SimpleSAML\XML\Exception\MissingElementException; +use SimpleSAML\XML\Exception\TooManyElementsException; use SimpleSAML\XMLSecurity\XML\ds\Signature; /** @@ -39,7 +42,6 @@ abstract class AbstractSignedXMLElement implements SignedElementInterface */ protected function __construct(DOMElement $xml, Signature $signature) { -// $this->element = $elt; $this->setStructure($xml); $this->setSignature($signature); } @@ -96,5 +98,12 @@ public function toXML(DOMElement $parent = null): DOMElement * @param \DOMElement $xml * @return self */ - abstract public static function fromXML(DOMElement $xml): object; + public static function fromXML(DOMElement $xml): object + { + $signature = Signature::getChildrenOfClass($xml); + Assert::minCount($signature, 1, MissingElementException::class); + Assert::maxCount($signature, 1, TooManyElementsException::class); + + return new self($xml, array_pop($signature)); + } } diff --git a/tests/XML/CustomSigned.php b/tests/XML/CustomSigned.php index 8ffe326f..703428b8 100644 --- a/tests/XML/CustomSigned.php +++ b/tests/XML/CustomSigned.php @@ -4,15 +4,7 @@ namespace SimpleSAML\XMLSecurity\Test\XML; -use DOMElement; -use SimpleSAML\Assert\Assert; -use SimpleSAML\XML\Chunk; -use SimpleSAML\XML\Exception\MissingElementException; -use SimpleSAML\XML\Exception\TooManyElementsException; -use SimpleSAML\XML\Utils as XMLUtils; -use SimpleSAML\XMLSecurity\XML\ds\Signature; use SimpleSAML\XMLSecurity\XML\AbstractSignedXMLElement; -use SimpleSAML\XMLSecurity\XML\SignedElementInterface; use SimpleSAML\XMLSecurity\XML\SignedElementTrait; /** @@ -20,21 +12,4 @@ */ final class CustomSigned extends AbstractSignedXMLElement { - use SignedElementTrait; - - - /** - * Create a class from XML - * - * @param \DOMElement $xml - * @return self - */ - public static function fromXML(DOMElement $xml): object - { - $signature = Signature::getChildrenOfClass($xml); - Assert::minCount($signature, 1, MissingElementException::class); - Assert::maxCount($signature, 1, TooManyElementsException::class); - - return new self($xml, array_pop($signature)); - } } From e0f111f7fa6935e3862e178086088486b4cc9dda Mon Sep 17 00:00:00 2001 From: Tim van Dijen Date: Mon, 29 Mar 2021 21:14:39 +0200 Subject: [PATCH 12/78] Change parent --- tests/XML/CustomSignable.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/XML/CustomSignable.php b/tests/XML/CustomSignable.php index 458138e8..bf766384 100644 --- a/tests/XML/CustomSignable.php +++ b/tests/XML/CustomSignable.php @@ -18,7 +18,7 @@ /** * @package simplesamlphp\saml2 */ -final class CustomSignable extends AbstractXMLElement implements SignableElementInterface +final class CustomSignable extends AbstractSerializableXMLElement implements SignableElementInterface { /** @var string */ public const NS = 'urn:oasis:names:tc:SAML:2.0:assertion'; From a0a04d05a513d5f3e9688377fe5970fecfcd6384 Mon Sep 17 00:00:00 2001 From: Tim van Dijen Date: Mon, 29 Mar 2021 21:19:37 +0200 Subject: [PATCH 13/78] Use SerializableXMLTestTrait --- tests/XML/CustomSignableTest.php | 24 +++++++----------------- tests/XML/CustomSignedTest.php | 18 +----------------- 2 files changed, 8 insertions(+), 34 deletions(-) diff --git a/tests/XML/CustomSignableTest.php b/tests/XML/CustomSignableTest.php index 114e4152..ae9fb200 100644 --- a/tests/XML/CustomSignableTest.php +++ b/tests/XML/CustomSignableTest.php @@ -6,6 +6,7 @@ use DOMDocument; use PHPUnit\Framework\TestCase; +use SimpleSAML\Test\XML\SerializableXMLTestTrait; use SimpleSAML\XML\DOMDocumentFactory; use SimpleSAML\XML\Chunk; use SimpleSAML\XMLSecurity\Test\XML\CustomSignable; @@ -19,15 +20,16 @@ */ final class SignableElementTest extends TestCase { - /** @var \DOMDocument */ - private DOMDocument $document; + use SerializableXMLTestTrait; /** */ public function setUp(): void { - $this->document = DOMDocumentFactory::fromFile( + $this->testedClass = CustomSignable::class; + + $this->xmlRepresentation = DOMDocumentFactory::fromFile( dirname(dirname(__FILE__)) . '/resources/xml/custom_CustomSignable.xml' ); } @@ -44,7 +46,7 @@ public function testMarshalling(): void $customSignable = new CustomSignable(new Chunk($document->documentElement)); $this->assertFalse($customSignable->isEmptyElement()); $this->assertEquals( - $this->document->saveXML($this->document->documentElement), + $this->xmlRepresentation->saveXML($this->xmlRepresentation->documentElement), strval($customSignable) ); } @@ -54,7 +56,7 @@ public function testMarshalling(): void */ public function testUnmarshalling(): void { - $customSignable = CustomSignable::fromXML($this->document->documentElement); + $customSignable = CustomSignable::fromXML($this->xmlRepresentation->documentElement); $customSignableElement = $customSignable->getElement(); $customSignableElement = $customSignableElement->getXML(); @@ -64,18 +66,6 @@ public function testUnmarshalling(): void 'Chunk', $customSignableElement->textContent ); - } - - - /** - * Test serialization / unserialization - */ - public function testSerialization(): void - { - $this->assertEquals( - $this->document->saveXML($this->document->documentElement), - strval(unserialize(serialize(CustomSignable::fromXML($this->document->documentElement)))) - ); } } diff --git a/tests/XML/CustomSignedTest.php b/tests/XML/CustomSignedTest.php index 3060e97a..af543a21 100644 --- a/tests/XML/CustomSignedTest.php +++ b/tests/XML/CustomSignedTest.php @@ -21,15 +21,11 @@ */ final class SignedElementTest extends TestCase { - /** @var \DOMDocument */ - private DOMDocument $document; - - /** */ public function setUp(): void { - $this->document = DOMDocumentFactory::fromFile( + $this->xmlRepresentation = DOMDocumentFactory::fromFile( dirname(dirname(__FILE__)) . '/resources/xml/custom_CustomSigned.xml' ); } @@ -67,17 +63,5 @@ public function testUnmarshalling(): void ); } */ - - - /** - * Test serialization / unserialization - */ - public function testSerialization(): void - { - $this->assertEquals( - $this->document->saveXML($this->document->documentElement), - strval(unserialize(serialize(CustomSigned::fromXML($this->document->documentElement)))) - ); - } } From f23834c5b115d1d61c59f40dbffa92176ff0d4dd Mon Sep 17 00:00:00 2001 From: Tim van Dijen Date: Tue, 30 Mar 2021 09:46:11 +0200 Subject: [PATCH 14/78] Return unsigned element --- src/XML/SignedElementTrait.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/XML/SignedElementTrait.php b/src/XML/SignedElementTrait.php index 3b6796c2..b0195b1f 100644 --- a/src/XML/SignedElementTrait.php +++ b/src/XML/SignedElementTrait.php @@ -73,7 +73,7 @@ public function validate(XMLSecurityKey $key): SignedElementInterface // check the signature $signer = $this->signature->getSigner(); if ($signer->verify($key) === 1) { - return /* This should return the signed element */; + return $this->getElement(); } throw new Exception("Unable to validate Signature"); From 6155eb3781408c9e46c270c93b09e7a2eeaaa42f Mon Sep 17 00:00:00 2001 From: Tim van Dijen Date: Tue, 30 Mar 2021 09:47:11 +0200 Subject: [PATCH 15/78] Remove getStructure and use toXML instead --- src/XML/AbstractSignedXMLElement.php | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/src/XML/AbstractSignedXMLElement.php b/src/XML/AbstractSignedXMLElement.php index afbdbb7f..45896c1e 100644 --- a/src/XML/AbstractSignedXMLElement.php +++ b/src/XML/AbstractSignedXMLElement.php @@ -58,17 +58,6 @@ public function __toString(): string } - /** - * Collect the value of the structure-property - * - * @return \DOMElement - */ - public function getStructure(): DOMElement - { - return $this->structure; - } - - /** * Set the value of the structure-property * From b24899e71e9efefb9076882c64141a5607d67bad Mon Sep 17 00:00:00 2001 From: Tim van Dijen Date: Tue, 30 Mar 2021 09:50:50 +0200 Subject: [PATCH 16/78] Fix class-name --- tests/XML/CustomSignable.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/XML/CustomSignable.php b/tests/XML/CustomSignable.php index bf766384..458138e8 100644 --- a/tests/XML/CustomSignable.php +++ b/tests/XML/CustomSignable.php @@ -18,7 +18,7 @@ /** * @package simplesamlphp\saml2 */ -final class CustomSignable extends AbstractSerializableXMLElement implements SignableElementInterface +final class CustomSignable extends AbstractXMLElement implements SignableElementInterface { /** @var string */ public const NS = 'urn:oasis:names:tc:SAML:2.0:assertion'; From 3b8db4f2cacdb2ac9215567bc26e026a7bd953b8 Mon Sep 17 00:00:00 2001 From: Tim van Dijen Date: Tue, 30 Mar 2021 10:28:54 +0200 Subject: [PATCH 17/78] Fix CustomSignedTest --- tests/XML/CustomSignable.php | 5 +++++ tests/XML/CustomSignedTest.php | 8 ++++---- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/tests/XML/CustomSignable.php b/tests/XML/CustomSignable.php index 458138e8..96981473 100644 --- a/tests/XML/CustomSignable.php +++ b/tests/XML/CustomSignable.php @@ -14,6 +14,7 @@ use SimpleSAML\XMLSecurity\XML\SignableElementInterface; use SimpleSAML\XMLSecurity\XML\SignedElementInterface; use SimpleSAML\XMLSecurity\XMLSecurityKey; +use SimpleSAML\XMLSecurity\XML\ds\Signature; /** * @package simplesamlphp\saml2 @@ -116,6 +117,10 @@ public static function fromXML(DOMElement $xml): object Assert::minCount($xml->childNodes, 1, MissingElementException::class); Assert::maxCount($xml->childNodes, 2, TooManyElementsException::class); + + // Remove the signature + Signature::getChildrenOfClass($xml); + $element = new Chunk($xml->childNodes[0]); return new self($element); diff --git a/tests/XML/CustomSignedTest.php b/tests/XML/CustomSignedTest.php index af543a21..d9a7084d 100644 --- a/tests/XML/CustomSignedTest.php +++ b/tests/XML/CustomSignedTest.php @@ -32,6 +32,7 @@ public function setUp(): void /** + */ public function testMarshalling(): void { $document = DOMDocumentFactory::fromString( @@ -41,17 +42,17 @@ public function testMarshalling(): void $customSignable = new CustomSignable(new Chunk($document->documentElement)); $this->assertFalse($customSignable->isEmptyElement()); $this->assertEquals( - $this->document->saveXML($this->document->documentElement), + $this->xmlRepresentation->saveXML($this->xmlRepresentation->documentElement), strval($customSignable) ); } - */ /** + */ public function testUnmarshalling(): void { - $customSignable = CustomSignable::fromXML($this->document->documentElement); + $customSignable = CustomSignable::fromXML($this->xmlRepresentation->documentElement); $customSignableElement = $customSignable->getElement(); $customSignableElement = $customSignableElement->getXML(); @@ -62,6 +63,5 @@ public function testUnmarshalling(): void $customSignableElement->textContent ); } - */ } From 222e5666ead3bc7a8c9ecbea0c639b9a1cb65d21 Mon Sep 17 00:00:00 2001 From: Tim van Dijen Date: Tue, 30 Mar 2021 12:28:04 +0200 Subject: [PATCH 18/78] Fix return value --- tests/XML/CustomSignable.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/XML/CustomSignable.php b/tests/XML/CustomSignable.php index 96981473..8089a701 100644 --- a/tests/XML/CustomSignable.php +++ b/tests/XML/CustomSignable.php @@ -94,11 +94,10 @@ private function setElement(Chunk $elt): void public function sign(XMLSecurityKey $signingKey, array $certificates = []): SignedElementInterface { $unsigned = $this->toXML(); - $signature = new Signature($signingKey->getAlgorithm(), $certificates, $signingKey); - $signature->toXML($unsigned); + $signed = new CustomSigned($unsigned, $signature); - return $unsigned; + return $signed; } From f43c3b85e1862afdcd018978937b12cb0a22c9b6 Mon Sep 17 00:00:00 2001 From: Tim van Dijen Date: Tue, 30 Mar 2021 12:40:21 +0200 Subject: [PATCH 19/78] Mess up --- src/XML/AbstractSignedXMLElement.php | 2 +- tests/XML/CustomSignable.php | 3 ++- tests/XML/CustomSignedTest.php | 8 +++++++- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/XML/AbstractSignedXMLElement.php b/src/XML/AbstractSignedXMLElement.php index 45896c1e..e1f9468f 100644 --- a/src/XML/AbstractSignedXMLElement.php +++ b/src/XML/AbstractSignedXMLElement.php @@ -40,7 +40,7 @@ abstract class AbstractSignedXMLElement implements SignedElementInterface * @param \DOMElement $xml * @param \SimpleSAML\XMLSecurity\XML\ds\Signature $signature */ - protected function __construct(DOMElement $xml, Signature $signature) + public function __construct(DOMElement $xml, Signature $signature) { $this->setStructure($xml); $this->setSignature($signature); diff --git a/tests/XML/CustomSignable.php b/tests/XML/CustomSignable.php index 8089a701..b87fd782 100644 --- a/tests/XML/CustomSignable.php +++ b/tests/XML/CustomSignable.php @@ -95,7 +95,8 @@ public function sign(XMLSecurityKey $signingKey, array $certificates = []): Sign { $unsigned = $this->toXML(); $signature = new Signature($signingKey->getAlgorithm(), $certificates, $signingKey); - $signed = new CustomSigned($unsigned, $signature); + $this->setSignature($signature); + $signed = new CustomSigned($this->toXML(), $signature); return $signed; } diff --git a/tests/XML/CustomSignedTest.php b/tests/XML/CustomSignedTest.php index d9a7084d..2502c719 100644 --- a/tests/XML/CustomSignedTest.php +++ b/tests/XML/CustomSignedTest.php @@ -9,6 +9,8 @@ use SimpleSAML\XML\DOMDocumentFactory; use SimpleSAML\XML\Chunk; use SimpleSAML\XMLSecurity\Test\XML\CustomSigned; +use SimpleSAML\XMLSecurity\TestUtils\PEMCertificatesMock; +use SimpleSAML\XMLSecurity\XMLSecurityKey; /** * Class \SimpleSAML\XMLSecurity\XML\CustomSignedTest @@ -41,9 +43,13 @@ public function testMarshalling(): void $customSignable = new CustomSignable(new Chunk($document->documentElement)); $this->assertFalse($customSignable->isEmptyElement()); + + $privateKey = PEMCertificatesMock::getPrivateKey(XMLSecurityKey::RSA_SHA256, PEMCertificatesMock::SELFSIGNED_PRIVATE_KEY); + $customSigned = $customSignable->sign($privateKey); + $this->assertEquals( $this->xmlRepresentation->saveXML($this->xmlRepresentation->documentElement), - strval($customSignable) + strval($customSigned) ); } From a49b61e9b27d9d2694926066a00d143412e10ee0 Mon Sep 17 00:00:00 2001 From: Tim van Dijen Date: Tue, 30 Mar 2021 13:16:25 +0200 Subject: [PATCH 20/78] Fix --- tests/XML/CustomSignable.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/XML/CustomSignable.php b/tests/XML/CustomSignable.php index b87fd782..2ecb16d9 100644 --- a/tests/XML/CustomSignable.php +++ b/tests/XML/CustomSignable.php @@ -95,8 +95,8 @@ public function sign(XMLSecurityKey $signingKey, array $certificates = []): Sign { $unsigned = $this->toXML(); $signature = new Signature($signingKey->getAlgorithm(), $certificates, $signingKey); - $this->setSignature($signature); - $signed = new CustomSigned($this->toXML(), $signature); + $signedXml = $signature->toXML($this->toXML()); + $signed = new CustomSigned($signedXml, $signature); return $signed; } From aada76d7888a9d374bc095ed8735556517ccf360 Mon Sep 17 00:00:00 2001 From: Tim van Dijen Date: Tue, 30 Mar 2021 13:41:47 +0200 Subject: [PATCH 21/78] Leave the Chunk in the trunk --- tests/XML/CustomSignable.php | 19 +++++++++---------- tests/XML/CustomSignableTest.php | 5 ++--- tests/XML/CustomSignedTest.php | 4 +--- 3 files changed, 12 insertions(+), 16 deletions(-) diff --git a/tests/XML/CustomSignable.php b/tests/XML/CustomSignable.php index 2ecb16d9..804b75bc 100644 --- a/tests/XML/CustomSignable.php +++ b/tests/XML/CustomSignable.php @@ -7,7 +7,6 @@ use DOMElement; use SimpleSAML\Assert\Assert; use SimpleSAML\XML\AbstractXMLElement; -use SimpleSAML\XML\Chunk; use SimpleSAML\XML\Exception\InvalidDOMElementException; use SimpleSAML\XML\Exception\MissingElementException; use SimpleSAML\XML\Exception\TooManyElementsException; @@ -27,15 +26,15 @@ final class CustomSignable extends AbstractXMLElement implements SignableElement /** @var string */ public const NS_PREFIX = 'saml'; - /** @var \SimpleSAML\XML\Chunk $element */ + /** @var \DOMElement $element */ protected $element; /** * Constructor * - * @param \SimpleSAML\XML\Chunk $elt + * @param \DOMElement $elt */ - public function __construct(Chunk $elt) { + public function __construct(DOMElement $elt) { $this->setElement($elt); } @@ -65,9 +64,9 @@ public static function getNamespacePrefix(): string /** * Collect the value of the $element property * - * @return \SimpleSAML\XML\XML\Chunk + * @return \DOMElement */ - public function getElement(): Chunk + public function getElement(): DOMElement { return $this->element; } @@ -76,9 +75,9 @@ public function getElement(): Chunk /** * Set the value of the elment-property * - * @param \SimpleSAML\XML\Chunk $elt + * @param \DOMElement $elt */ - private function setElement(Chunk $elt): void + private function setElement(DOMElement $elt): void { $this->element = $elt; } @@ -121,7 +120,7 @@ public static function fromXML(DOMElement $xml): object // Remove the signature Signature::getChildrenOfClass($xml); - $element = new Chunk($xml->childNodes[0]); + $element = $xml->childNodes[0]; return new self($element); } @@ -138,7 +137,7 @@ public function toXML(DOMElement $parent = null): DOMElement /** @psalm-var \DOMDocument $e->ownerDocument */ $e = $this->instantiateParentElement($parent); - $e->appendChild($e->ownerDocument->importNode($this->element->getXML(), true)); + $e->appendChild($e->ownerDocument->importNode($this->element, true)); return $e; } } diff --git a/tests/XML/CustomSignableTest.php b/tests/XML/CustomSignableTest.php index ae9fb200..9037ceb7 100644 --- a/tests/XML/CustomSignableTest.php +++ b/tests/XML/CustomSignableTest.php @@ -8,7 +8,6 @@ use PHPUnit\Framework\TestCase; use SimpleSAML\Test\XML\SerializableXMLTestTrait; use SimpleSAML\XML\DOMDocumentFactory; -use SimpleSAML\XML\Chunk; use SimpleSAML\XMLSecurity\Test\XML\CustomSignable; /** @@ -43,7 +42,7 @@ public function testMarshalling(): void 'Chunk' ); - $customSignable = new CustomSignable(new Chunk($document->documentElement)); + $customSignable = new CustomSignable($document->documentElement); $this->assertFalse($customSignable->isEmptyElement()); $this->assertEquals( $this->xmlRepresentation->saveXML($this->xmlRepresentation->documentElement), @@ -59,7 +58,7 @@ public function testUnmarshalling(): void $customSignable = CustomSignable::fromXML($this->xmlRepresentation->documentElement); $customSignableElement = $customSignable->getElement(); - $customSignableElement = $customSignableElement->getXML(); + $customSignableElement = $customSignableElement->ownerDocument->saveXML(); $this->assertEquals('some', $customSignableElement->tagName); $this->assertEquals( diff --git a/tests/XML/CustomSignedTest.php b/tests/XML/CustomSignedTest.php index 2502c719..140fa1df 100644 --- a/tests/XML/CustomSignedTest.php +++ b/tests/XML/CustomSignedTest.php @@ -7,7 +7,6 @@ use DOMDocument; use PHPUnit\Framework\TestCase; use SimpleSAML\XML\DOMDocumentFactory; -use SimpleSAML\XML\Chunk; use SimpleSAML\XMLSecurity\Test\XML\CustomSigned; use SimpleSAML\XMLSecurity\TestUtils\PEMCertificatesMock; use SimpleSAML\XMLSecurity\XMLSecurityKey; @@ -41,7 +40,7 @@ public function testMarshalling(): void 'Chunk' ); - $customSignable = new CustomSignable(new Chunk($document->documentElement)); + $customSignable = new CustomSignable($document->documentElement); $this->assertFalse($customSignable->isEmptyElement()); $privateKey = PEMCertificatesMock::getPrivateKey(XMLSecurityKey::RSA_SHA256, PEMCertificatesMock::SELFSIGNED_PRIVATE_KEY); @@ -61,7 +60,6 @@ public function testUnmarshalling(): void $customSignable = CustomSignable::fromXML($this->xmlRepresentation->documentElement); $customSignableElement = $customSignable->getElement(); - $customSignableElement = $customSignableElement->getXML(); $this->assertEquals('some', $customSignableElement->tagName); $this->assertEquals( From aceb71573f983348c6fb4fb3eeb7afb828c792ca Mon Sep 17 00:00:00 2001 From: Tim van Dijen Date: Tue, 30 Mar 2021 13:49:27 +0200 Subject: [PATCH 22/78] Make Signed classes serializable --- src/XML/AbstractSignedXMLElement.php | 3 ++- tests/XML/CustomSigned.php | 27 +++++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/src/XML/AbstractSignedXMLElement.php b/src/XML/AbstractSignedXMLElement.php index e1f9468f..a4335a40 100644 --- a/src/XML/AbstractSignedXMLElement.php +++ b/src/XML/AbstractSignedXMLElement.php @@ -6,6 +6,7 @@ use DOMElement; use SimpleSAML\Assert\Assert; +use SimpleSAML\XML\AbstractXMLElement; use SimpleSAML\XML\Exception\MissingElementException; use SimpleSAML\XML\Exception\TooManyElementsException; use SimpleSAML\XMLSecurity\XML\ds\Signature; @@ -15,7 +16,7 @@ * * @package simplesamlphp/xml-security */ -abstract class AbstractSignedXMLElement implements SignedElementInterface +abstract class AbstractSignedXMLElement extends AbstractXMLElement implements SignedElementInterface { use SignedElementTrait; diff --git a/tests/XML/CustomSigned.php b/tests/XML/CustomSigned.php index 703428b8..94a247ec 100644 --- a/tests/XML/CustomSigned.php +++ b/tests/XML/CustomSigned.php @@ -12,4 +12,31 @@ */ final class CustomSigned extends AbstractSignedXMLElement { + /** @var string */ + public const NS = 'urn:ssp:custom'; + + /** @var string */ + public const NS_PREFIX = 'custom'; + + + /** + * Get the namespace for the element. + * + * @return string + */ + public static function getNamespaceURI(): string + { + return static::NS; + } + + + /** + * Get the namespace-prefix for the element. + * + * @return string + */ + public static function getNamespacePrefix(): string + { + return static::NS_PREFIX; + } } From 35ca698105c929e059fbdb4447722ba277d7bc14 Mon Sep 17 00:00:00 2001 From: Tim van Dijen Date: Wed, 31 Mar 2021 13:10:13 +0200 Subject: [PATCH 23/78] s/self/static --- src/XML/AbstractSignedXMLElement.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/XML/AbstractSignedXMLElement.php b/src/XML/AbstractSignedXMLElement.php index a4335a40..9974b8ec 100644 --- a/src/XML/AbstractSignedXMLElement.php +++ b/src/XML/AbstractSignedXMLElement.php @@ -94,6 +94,6 @@ public static function fromXML(DOMElement $xml): object Assert::minCount($signature, 1, MissingElementException::class); Assert::maxCount($signature, 1, TooManyElementsException::class); - return new self($xml, array_pop($signature)); + return new static($xml, array_pop($signature)); } } From 0e2f5238cd0b924068134849d58c5c40abe49dd6 Mon Sep 17 00:00:00 2001 From: Tim van Dijen Date: Wed, 31 Mar 2021 16:46:15 +0200 Subject: [PATCH 24/78] Improve tests --- bin/generate_CustomSignable.php | 3 +-- src/XML/AbstractSignedXMLElement.php | 4 +++- tests/XML/CustomSignable.php | 4 ++-- tests/XML/CustomSignableTest.php | 9 ++------- tests/XML/CustomSigned.php | 2 +- tests/XML/CustomSignedTest.php | 20 ++++++++++--------- tests/resources/xml/custom_CustomSignable.xml | 6 +++--- tests/resources/xml/custom_CustomSigned.xml | 6 +++--- 8 files changed, 26 insertions(+), 28 deletions(-) diff --git a/bin/generate_CustomSignable.php b/bin/generate_CustomSignable.php index 3144a927..9230e863 100644 --- a/bin/generate_CustomSignable.php +++ b/bin/generate_CustomSignable.php @@ -2,13 +2,12 @@ require_once('../vendor/autoload.php'); -use SimpleSAML\XML\Chunk; use SimpleSAML\XML\DOMDocumentFactory; use SimpleSAML\XMLSecurity\Test\XML\CustomSignable; use SimpleSAML\XMLSecurity\TestUtils\PEMCertificatesMock; use SimpleSAML\XMLSecurity\XMLSecurityKey; -$chunk = new Chunk(DOMDocumentFactory::fromString('Chunk')->documentElement); +$chunk = DOMDocumentFactory::fromString('Chunk')->documentElement; $signable = new CustomSignable($chunk); $privateKey = PEMCertificatesMock::getPrivateKey(XMLSecurityKey::RSA_SHA256, PEMCertificatesMock::SELFSIGNED_PRIVATE_KEY); diff --git a/src/XML/AbstractSignedXMLElement.php b/src/XML/AbstractSignedXMLElement.php index 9974b8ec..2e30d1c7 100644 --- a/src/XML/AbstractSignedXMLElement.php +++ b/src/XML/AbstractSignedXMLElement.php @@ -90,10 +90,12 @@ public function toXML(DOMElement $parent = null): DOMElement */ public static function fromXML(DOMElement $xml): object { + $original = $xml->ownerDocument->cloneNode(true); + $signature = Signature::getChildrenOfClass($xml); Assert::minCount($signature, 1, MissingElementException::class); Assert::maxCount($signature, 1, TooManyElementsException::class); - return new static($xml, array_pop($signature)); + return new static($original->documentElement, array_pop($signature)); } } diff --git a/tests/XML/CustomSignable.php b/tests/XML/CustomSignable.php index 804b75bc..6a0cb508 100644 --- a/tests/XML/CustomSignable.php +++ b/tests/XML/CustomSignable.php @@ -21,10 +21,10 @@ final class CustomSignable extends AbstractXMLElement implements SignableElementInterface { /** @var string */ - public const NS = 'urn:oasis:names:tc:SAML:2.0:assertion'; + public const NS = 'urn:ssp:custom'; /** @var string */ - public const NS_PREFIX = 'saml'; + public const NS_PREFIX = 'ssp'; /** @var \DOMElement $element */ protected $element; diff --git a/tests/XML/CustomSignableTest.php b/tests/XML/CustomSignableTest.php index 9037ceb7..6bb6cf93 100644 --- a/tests/XML/CustomSignableTest.php +++ b/tests/XML/CustomSignableTest.php @@ -39,7 +39,7 @@ public function setUp(): void public function testMarshalling(): void { $document = DOMDocumentFactory::fromString( - 'Chunk' + 'Chunk' ); $customSignable = new CustomSignable($document->documentElement); @@ -58,13 +58,8 @@ public function testUnmarshalling(): void $customSignable = CustomSignable::fromXML($this->xmlRepresentation->documentElement); $customSignableElement = $customSignable->getElement(); - $customSignableElement = $customSignableElement->ownerDocument->saveXML(); - $this->assertEquals('some', $customSignableElement->tagName); - $this->assertEquals( - 'Chunk', - $customSignableElement->textContent - ); + $this->assertEqualXmlStructure($this->xmlRepresentation->documentElement, $customSignableElement); } } diff --git a/tests/XML/CustomSigned.php b/tests/XML/CustomSigned.php index 94a247ec..758a8bd0 100644 --- a/tests/XML/CustomSigned.php +++ b/tests/XML/CustomSigned.php @@ -16,7 +16,7 @@ final class CustomSigned extends AbstractSignedXMLElement public const NS = 'urn:ssp:custom'; /** @var string */ - public const NS_PREFIX = 'custom'; + public const NS_PREFIX = 'ssp'; /** diff --git a/tests/XML/CustomSignedTest.php b/tests/XML/CustomSignedTest.php index 140fa1df..e4b949a0 100644 --- a/tests/XML/CustomSignedTest.php +++ b/tests/XML/CustomSignedTest.php @@ -6,6 +6,7 @@ use DOMDocument; use PHPUnit\Framework\TestCase; +use SimpleSAML\Test\XML\SerializableXMLTestTrait; use SimpleSAML\XML\DOMDocumentFactory; use SimpleSAML\XMLSecurity\Test\XML\CustomSigned; use SimpleSAML\XMLSecurity\TestUtils\PEMCertificatesMock; @@ -22,10 +23,15 @@ */ final class SignedElementTest extends TestCase { + use SerializableXMLTestTrait; + + /** */ public function setUp(): void { + $this->testedClass = CustomSigned::class; + $this->xmlRepresentation = DOMDocumentFactory::fromFile( dirname(dirname(__FILE__)) . '/resources/xml/custom_CustomSigned.xml' ); @@ -37,7 +43,7 @@ public function setUp(): void public function testMarshalling(): void { $document = DOMDocumentFactory::fromString( - 'Chunk' + 'Chunk' ); $customSignable = new CustomSignable($document->documentElement); @@ -46,9 +52,9 @@ public function testMarshalling(): void $privateKey = PEMCertificatesMock::getPrivateKey(XMLSecurityKey::RSA_SHA256, PEMCertificatesMock::SELFSIGNED_PRIVATE_KEY); $customSigned = $customSignable->sign($privateKey); - $this->assertEquals( - $this->xmlRepresentation->saveXML($this->xmlRepresentation->documentElement), - strval($customSigned) + $this->assertEqualXMLStructure( + $this->xmlRepresentation->documentElement, + $customSigned->toXML()->ownerDocument->documentElement ); } @@ -61,11 +67,7 @@ public function testUnmarshalling(): void $customSignableElement = $customSignable->getElement(); - $this->assertEquals('some', $customSignableElement->tagName); - $this->assertEquals( - 'Chunk', - $customSignableElement->textContent - ); + $this->assertEqualXMLStructure($this->xmlRepresentation->documentElement, $customSignableElement->ownerDocument->documentElement); } } diff --git a/tests/resources/xml/custom_CustomSignable.xml b/tests/resources/xml/custom_CustomSignable.xml index 0ab64266..41c67a1d 100644 --- a/tests/resources/xml/custom_CustomSignable.xml +++ b/tests/resources/xml/custom_CustomSignable.xml @@ -1,3 +1,3 @@ - - Chunk - + + Chunk + diff --git a/tests/resources/xml/custom_CustomSigned.xml b/tests/resources/xml/custom_CustomSigned.xml index 499f8083..adcfe1f7 100644 --- a/tests/resources/xml/custom_CustomSigned.xml +++ b/tests/resources/xml/custom_CustomSigned.xml @@ -1,5 +1,5 @@ - + - UFc2JQ3NbwaTn4RolCpUGudOo1fiLf+qAVznEK3tipQ=zNL052dCUhVupXy6vUNK831roWBwwRpIVroBaQbA3Bteff7dk94y9r9a6l1/muwcFy6CNVT1EiJ6v8G3a92imw9q4dfzi4TvXkyzm/s31OqmYZspDgVtRhypx2FF7zqNyU68NVZHFRlkApKoZY6dWFFm6ipnryrjUgtrWx8zDtk= -Chunk + j+vUDatE3NAWXXBx01uUb58YMuFofMcn1VgFR7968iQ=LQplDy+GzAknBdoWf2LCG/UHl1Mjo4270Yt85hXZpji1ZHsytMWfTTkKrX1wJFD8YYp8VLy4zT0V12vFT8G7KVODMFzwS2RkQe54+rSIjSWvQ571tijEZ+9q7cy/tTXBj4lOXneM401qnKd/HIpmQOZF3gS5D1VPfF27QhViVIQ= +Chunk From d2ea2880279bfa8de95bf6074f3a88196d5b2dfa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaime=20P=C3=A9rez=20Crespo?= Date: Mon, 19 Jul 2021 09:00:38 +0200 Subject: [PATCH 25/78] Make sure to ignore extra whitespaces in InclusiveNamespaces@PrefixList --- src/XML/ec/InclusiveNamespaces.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/XML/ec/InclusiveNamespaces.php b/src/XML/ec/InclusiveNamespaces.php index dbe6bd23..29ca9c1a 100644 --- a/src/XML/ec/InclusiveNamespaces.php +++ b/src/XML/ec/InclusiveNamespaces.php @@ -65,7 +65,7 @@ public static function fromXML(DOMElement $xml): object { $prefixes = self::getAttribute($xml, 'PrefixList', ''); - return new self(explode(' ', $prefixes)); + return new self(array_filter(explode(' ', $prefixes), function ($v, $k) { return !empty($v); })); } /** From b920d5c22d8947a2078fca11eba58df40fe01da9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaime=20P=C3=A9rez=20Crespo?= Date: Mon, 19 Jul 2021 09:01:03 +0200 Subject: [PATCH 26/78] Minor fixes to ds:XPath implementation --- src/XML/ds/XPath.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/XML/ds/XPath.php b/src/XML/ds/XPath.php index 61f90ba2..e81a4d1c 100644 --- a/src/XML/ds/XPath.php +++ b/src/XML/ds/XPath.php @@ -110,10 +110,10 @@ public static function fromXML(DOMElement $xml): object $namespaces = []; $xpath = new \DOMXPath($xml->ownerDocument); /** @var \DOMNode $ns */ - foreach ($xpath->query('namespace::*', $xml) as $ns) { + foreach ($xpath->query('./namespace::*', $xml) as $ns) { if ($xml->getAttributeNode($ns->nodeName)) { - $namespaces[str_replace('xmlns:', '', $ns->nodeName)] = - $xml->getAttribute($ns->nodeName); + // only add namespaces when they are defined explicitly in an attribute + $namespaces[$ns->localName] = $xml->getAttribute($ns->nodeName); } } From 4052d1b3709751b000352737bf02c4aeb4ef6986 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaime=20Pe=CC=81rez?= Date: Mon, 19 Jul 2021 19:38:03 +0200 Subject: [PATCH 27/78] Remove closure from InclusiveNamespaces --- src/XML/ec/InclusiveNamespaces.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/XML/ec/InclusiveNamespaces.php b/src/XML/ec/InclusiveNamespaces.php index 29ca9c1a..5f030de9 100644 --- a/src/XML/ec/InclusiveNamespaces.php +++ b/src/XML/ec/InclusiveNamespaces.php @@ -65,7 +65,7 @@ public static function fromXML(DOMElement $xml): object { $prefixes = self::getAttribute($xml, 'PrefixList', ''); - return new self(array_filter(explode(' ', $prefixes), function ($v, $k) { return !empty($v); })); + return new self(array_filter(explode(' ', $prefixes))); } /** From e85ed90e9ba04c573152de8902595c5f98e6eae9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaime=20Pe=CC=81rez?= Date: Tue, 20 Jul 2021 19:44:35 +0200 Subject: [PATCH 28/78] Nitpicks Add strict_types declarations and modify package names. --- src/Alg/Signature/AbstractSigner.php | 4 +++- src/Alg/Signature/HMAC.php | 4 +++- src/Alg/Signature/RSA.php | 4 +++- src/Alg/Signature/SignatureAlgorithmFactory.php | 4 +++- src/Constants.php | 4 +++- src/Key/AbstractKey.php | 4 +++- src/Key/AsymmetricKey.php | 4 +++- src/Key/PrivateKey.php | 6 +++--- src/Key/PublicKey.php | 6 +++--- src/Key/SymmetricKey.php | 5 +++-- src/Key/X509Certificate.php | 5 ++++- tests/Alg/SignatureAlgorithmFactoryTest.php | 5 +++-- 12 files changed, 37 insertions(+), 18 deletions(-) diff --git a/src/Alg/Signature/AbstractSigner.php b/src/Alg/Signature/AbstractSigner.php index a5d62c70..d9eccf37 100644 --- a/src/Alg/Signature/AbstractSigner.php +++ b/src/Alg/Signature/AbstractSigner.php @@ -1,5 +1,7 @@ certificate = $certificate; parent::__construct(openssl_pkey_get_public($this->certificate)); diff --git a/tests/Alg/SignatureAlgorithmFactoryTest.php b/tests/Alg/SignatureAlgorithmFactoryTest.php index 2789cb09..da7d3d90 100644 --- a/tests/Alg/SignatureAlgorithmFactoryTest.php +++ b/tests/Alg/SignatureAlgorithmFactoryTest.php @@ -1,5 +1,7 @@ Date: Tue, 20 Jul 2021 19:48:24 +0200 Subject: [PATCH 29/78] Make SignatureAlgorithmFactory reusable for custom algs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit rewrites the way the signature algorithms work and are created via the factory. It offloads the responsibility to check what algorithms they support to the signer implementations themselves, and makes them return their supported algs. It also introduces a mechanism in the factory so that you can register new signer implementations, and use them automatically in your code. Signed-off-by: Jaime Pérez Update tests for SignatureAlgorithmFactory --- src/Alg/Signature/AbstractSigner.php | 34 ++++- src/Alg/Signature/HMAC.php | 26 +++- src/Alg/Signature/RSA.php | 26 +++- .../Signature/SignatureAlgorithmFactory.php | 132 ++++++++++-------- src/Alg/SignatureAlgorithm.php | 17 +++ src/Constants.php | 18 +++ tests/Alg/SignatureAlgorithmFactoryTest.php | 4 +- 7 files changed, 183 insertions(+), 74 deletions(-) diff --git a/src/Alg/Signature/AbstractSigner.php b/src/Alg/Signature/AbstractSigner.php index d9eccf37..0a876f77 100644 --- a/src/Alg/Signature/AbstractSigner.php +++ b/src/Alg/Signature/AbstractSigner.php @@ -4,8 +4,10 @@ namespace SimpleSAML\XMLSecurity\Alg\Signature; +use SimpleSAML\Assert\Assert; use SimpleSAML\XMLSecurity\Alg\SignatureAlgorithm; use SimpleSAML\XMLSecurity\Backend\SignatureBackend; +use SimpleSAML\XMLSecurity\Exception\RuntimeException; use SimpleSAML\XMLSecurity\Key\AbstractKey; /** @@ -16,7 +18,7 @@ abstract class AbstractSigner implements SignatureAlgorithm { /** @var \SimpleSAML\XMLSecurity\Key\AbstractKey */ - protected AbstractKey $key; + private AbstractKey $key; /** @var \SimpleSAML\XMLSecurity\Backend\SignatureBackend */ protected SignatureBackend $backend; @@ -27,22 +29,46 @@ abstract class AbstractSigner implements SignatureAlgorithm /** @var string */ protected string $digest; + /** @var string */ + protected string $algId; + /** * Build a signature algorithm. * + * Extend this class to implement your own signers. + * + * WARNING: remember to adjust the type of the key to the one that works with your algorithm! + * * @param \SimpleSAML\XMLSecurity\Key\AbstractKey $key The signing key. + * @param string $algId The identifier of this algorithm. * @param string $digest The identifier of the digest algorithm to use. */ - public function __construct(AbstractKey $key, string $digest) + public function __construct(AbstractKey $key, string $algId, string $digest) { + Assert::oneOf( + $algId, + static::getSupportedAlgorithms(), + 'Unsupported algorithm for ' . static::class, + RuntimeException::class + ); $this->key = $key; + $this->algId = $algId; $this->digest = $digest; $this->backend = new $this->default_backend(); $this->backend->setDigestAlg($digest); } + /** + * @return string + */ + public function getAlgorithmId(): string + { + return $this->algId; + } + + /** * @return string */ @@ -69,7 +95,7 @@ public function setBackend(SignatureBackend $backend): void * * @return string The (binary) signature corresponding to the given plaintext. */ - public function sign(string $plaintext): string + final public function sign(string $plaintext): string { return $this->backend->sign($this->key, $plaintext); } @@ -83,7 +109,7 @@ public function sign(string $plaintext): string * * @return boolean True if the signature can be verified, false otherwise. */ - public function verify(string $plaintext, string $signature): bool + final public function verify(string $plaintext, string $signature): bool { return $this->backend->verify($this->key, $plaintext, $signature); } diff --git a/src/Alg/Signature/HMAC.php b/src/Alg/Signature/HMAC.php index c0640bdb..1433030a 100644 --- a/src/Alg/Signature/HMAC.php +++ b/src/Alg/Signature/HMAC.php @@ -6,7 +6,7 @@ use SimpleSAML\XMLSecurity\Alg\SignatureAlgorithm; use SimpleSAML\XMLSecurity\Backend\HMAC as HMAC_Backend; -use SimpleSAML\XMLSecurity\Constants; +use SimpleSAML\XMLSecurity\Constants as C; use SimpleSAML\XMLSecurity\Key\SymmetricKey; /** @@ -14,7 +14,7 @@ * * @package simplesamlphp/xml-security */ -class HMAC extends AbstractSigner implements SignatureAlgorithm +final class HMAC extends AbstractSigner implements SignatureAlgorithm { /** @var string */ protected string $default_backend = HMAC_Backend::class; @@ -24,10 +24,26 @@ class HMAC extends AbstractSigner implements SignatureAlgorithm * HMAC constructor. * * @param \SimpleSAML\XMLSecurity\Key\SymmetricKey $key The symmetric key to use. - * @param string $digest The identifier of the digest algorithm to use. + * @param string $algId The identifier of this algorithm. */ - public function __construct(SymmetricKey $key, string $digest = Constants::DIGEST_SHA1) + public function __construct(SymmetricKey $key, string $algId = C::SIG_HMAC_SHA256) { - parent::__construct($key, $digest); + parent::__construct($key, $algId, C::$HMAC_DIGESTS[$algId]); + } + + + /** + * @inheritDoc + */ + public static function getSupportedAlgorithms(): array + { + return [ + C::SIG_HMAC_SHA1, + C::SIG_HMAC_SHA224, + C::SIG_HMAC_SHA256, + C::SIG_HMAC_SHA384, + C::SIG_HMAC_SHA512, + C::SIG_HMAC_RIPEMD160, + ]; } } diff --git a/src/Alg/Signature/RSA.php b/src/Alg/Signature/RSA.php index 88d357e1..84f75f33 100644 --- a/src/Alg/Signature/RSA.php +++ b/src/Alg/Signature/RSA.php @@ -6,7 +6,7 @@ use SimpleSAML\XMLSecurity\Alg\SignatureAlgorithm; use SimpleSAML\XMLSecurity\Backend\OpenSSL; -use SimpleSAML\XMLSecurity\Constants; +use SimpleSAML\XMLSecurity\Constants as C; use SimpleSAML\XMLSecurity\Key\AsymmetricKey; /** @@ -14,7 +14,7 @@ * * @package simplesamlphp/xml-security */ -class RSA extends AbstractSigner implements SignatureAlgorithm +final class RSA extends AbstractSigner implements SignatureAlgorithm { /** @var string */ protected string $default_backend = OpenSSL::class; @@ -24,10 +24,26 @@ class RSA extends AbstractSigner implements SignatureAlgorithm * RSA constructor. * * @param \SimpleSAML\XMLSecurity\Key\AsymmetricKey $key The asymmetric key (either public or private) to use. - * @param string $digest The identifier of the digest algorithm to use. + * @param string $algId The identifier of this algorithm. */ - public function __construct(AsymmetricKey $key, string $digest = Constants::DIGEST_SHA1) + public function __construct(AsymmetricKey $key, string $algId = C::SIG_RSA_SHA256) { - parent::__construct($key, $digest); + parent::__construct($key, $algId, C::$RSA_DIGESTS[$algId]); + } + + + /** + * @inheritDoc + */ + public static function getSupportedAlgorithms(): array + { + return [ + C::SIG_RSA_RIPEMD160, + C::SIG_RSA_SHA1, + C::SIG_RSA_SHA224, + C::SIG_RSA_SHA256, + C::SIG_RSA_SHA384, + C::SIG_RSA_SHA512, + ]; } } diff --git a/src/Alg/Signature/SignatureAlgorithmFactory.php b/src/Alg/Signature/SignatureAlgorithmFactory.php index cdcec5be..40002bf8 100644 --- a/src/Alg/Signature/SignatureAlgorithmFactory.php +++ b/src/Alg/Signature/SignatureAlgorithmFactory.php @@ -4,13 +4,11 @@ namespace SimpleSAML\XMLSecurity\Alg\Signature; +use SimpleSAML\Assert\Assert; use SimpleSAML\XMLSecurity\Alg\SignatureAlgorithm; use SimpleSAML\XMLSecurity\Constants; use SimpleSAML\XMLSecurity\Exception\InvalidArgumentException; -use SimpleSAML\XMLSecurity\Exception\RuntimeException; use SimpleSAML\XMLSecurity\Key\AbstractKey; -use SimpleSAML\XMLSecurity\Key\AsymmetricKey; -use SimpleSAML\XMLSecurity\Key\SymmetricKey; use function in_array; @@ -19,8 +17,18 @@ * * @package simplesamlphp/xml-security */ -class SignatureAlgorithmFactory +final class SignatureAlgorithmFactory { + /** + * An array holding the known classes extending \SimpleSAML\XMLSecurity\Alg\Signature\AbstractSigner. + * + * @var string[] + */ + private static array $algorithms = [ + RSA::class, + HMAC::class + ]; + /** * An array of blacklisted algorithms. * @@ -28,11 +36,25 @@ class SignatureAlgorithmFactory * * @var string[] */ - protected array $blacklist = [ + private array $blacklist = [ Constants::SIG_RSA_SHA1, Constants::SIG_HMAC_SHA1, ]; + /** + * A cache of signers indexed by algorithm ID. + * + * @var string[] + */ + private static array $cache = []; + + /** + * Whether the factory has been initialized or not. + * + * @var bool + */ + private static bool $initialized = false; + /** * Build a factory that creates signature algorithms. @@ -44,6 +66,14 @@ public function __construct(array $blacklist = null) if ($blacklist !== null) { $this->blacklist = $blacklist; } + + // initialize the cache for supported algorithms per known signer + if (!self::$initialized) { + foreach (self::$algorithms as $algorithm) { + self::updateCache($algorithm); + } + self::$initialized = true; + } } @@ -55,8 +85,8 @@ public function __construct(array $blacklist = null) * * @return \SimpleSAML\XMLSecurity\Alg\SignatureAlgorithm An object implementing the given algorithm. * - * @throws InvalidArgumentException If an error occurs, e.g. the given algorithm is blacklisted, unknown or the - * given key is not suitable for it. + * @throws \SimpleSAML\XMLSecurity\Exception\InvalidArgumentException If an error occurs, e.g. the given algorithm + * is blacklisted, unknown or the given key is not suitable for it. */ public function getAlgorithm(string $algId, AbstractKey $key): SignatureAlgorithm { @@ -64,59 +94,45 @@ public function getAlgorithm(string $algId, AbstractKey $key): SignatureAlgorith throw new InvalidArgumentException('Blacklisted signature algorithm'); } - // determine digest - switch ($algId) { - case Constants::SIG_RSA_SHA1: - case Constants::SIG_HMAC_SHA1: - $digest = Constants::DIGEST_SHA1; - break; - case Constants::SIG_RSA_SHA224: - case Constants::SIG_HMAC_SHA224: - $digest = Constants::DIGEST_SHA224; - break; - case Constants::SIG_RSA_SHA256: - case Constants::SIG_HMAC_SHA256: - $digest = Constants::DIGEST_SHA256; - break; - case Constants::SIG_RSA_SHA384: - case Constants::SIG_HMAC_SHA384: - $digest = Constants::DIGEST_SHA384; - break; - case Constants::SIG_RSA_SHA512: - case Constants::SIG_HMAC_SHA512: - $digest = Constants::DIGEST_SHA512; - break; - case Constants::SIG_RSA_RIPEMD160: - case Constants::SIG_HMAC_RIPEMD160: - $digest = Constants::DIGEST_RIPEMD160; - break; - default: - throw new RuntimeException('Unsupported signature algorithm'); + if (!array_key_exists($algId, self::$cache)) { + throw new InvalidArgumentException('Unknown algorithm identifier.'); } - // create instance - switch ($algId) { - case Constants::SIG_RSA_SHA1: - case Constants::SIG_RSA_SHA224: - case Constants::SIG_RSA_SHA256: - case Constants::SIG_RSA_SHA384: - case Constants::SIG_RSA_SHA512: - case Constants::SIG_RSA_RIPEMD160: - if ($key instanceof AsymmetricKey) { - return new RSA($key, $digest); - } - break; - case Constants::SIG_HMAC_SHA1: - case Constants::SIG_HMAC_SHA224: - case Constants::SIG_HMAC_SHA256: - case Constants::SIG_HMAC_SHA384: - case Constants::SIG_HMAC_SHA512: - case Constants::SIG_HMAC_RIPEMD160: - if ($key instanceof SymmetricKey) { - return new HMAC($key, $digest); - } - break; + return new self::$cache[$algId]($key, $algId); + } + + + /** + * Register a signature algorithm for its use. + * + * @note Algorithms must extend \SimpleSAML\XMLSecurity\Alg\Signature\AbstractSigner. + * + * @param string $className + */ + public static function registerAlgorithm(string $className): void + { + Assert::subclassOf( + $className, + AbstractSigner::class, + 'Cannot register algorithm "' . $className . '", must implement ' . "\SimpleSAML\XMLSecurity\Alg\SignatureInterface.", + InvalidArgumentException::class + ); + + self::$algorithms[] = $className; + self::updateCache($className); + } + + + /** + * Update the cache with a new signer implementation. + * + * @param string $signer + */ + private static function updateCache(string $signer): void + { + /** @var \SimpleSAML\XMLSecurity\Alg\Signature\AbstractSigner $signer */ + foreach ($signer::getSupportedAlgorithms() as $algId) { + self::$cache[$algId] = $signer; } - throw new RuntimeException('Invalid type of key for algorithm'); } } diff --git a/src/Alg/SignatureAlgorithm.php b/src/Alg/SignatureAlgorithm.php index 17be1241..d642c22f 100644 --- a/src/Alg/SignatureAlgorithm.php +++ b/src/Alg/SignatureAlgorithm.php @@ -11,6 +11,14 @@ */ interface SignatureAlgorithm { + /** + * Get an array with all the identifiers for algorithms supported. + * + * @return string[] + */ + public static function getSupportedAlgorithms(): array; + + /** * Get the digest used by this signature algorithm. * @@ -19,6 +27,15 @@ interface SignatureAlgorithm public function getDigest(): string; + /** + * Get the identifier of this signature algorithm. + * + * @return string The identifier of this signature algorithm. + */ + public function getAlgorithmId(): string; + + + /** * Set the backend to use for actual computations by this algorithm. * diff --git a/src/Constants.php b/src/Constants.php index c99e99b1..66ab4f09 100644 --- a/src/Constants.php +++ b/src/Constants.php @@ -107,6 +107,24 @@ class Constants extends \SimpleSAML\XML\Constants public const SIG_HMAC_SHA512 = 'http://www.w3.org/2001/04/xmldsig-more#hmac-sha512'; public const SIG_HMAC_RIPEMD160 = 'http://www.w3.org/2001/04/xmldsig-more#hmac-ripemd160'; + public static $RSA_DIGESTS = [ + self::SIG_RSA_SHA1 => self::DIGEST_SHA1, + self::SIG_RSA_SHA224 => self::DIGEST_SHA224, + self::SIG_RSA_SHA256 => self::DIGEST_SHA256, + self::SIG_RSA_SHA384 => self::DIGEST_SHA384, + self::SIG_RSA_SHA512 => self::DIGEST_SHA512, + self::SIG_RSA_RIPEMD160 => self::DIGEST_RIPEMD160, + ]; + + public static $HMAC_DIGESTS = [ + self::SIG_HMAC_SHA1 => self::DIGEST_SHA1, + self::SIG_HMAC_SHA224 => self::DIGEST_SHA224, + self::SIG_HMAC_SHA256 => self::DIGEST_SHA256, + self::SIG_HMAC_SHA384 => self::DIGEST_SHA384, + self::SIG_HMAC_SHA512 => self::DIGEST_SHA512, + self::SIG_HMAC_RIPEMD160 => self::DIGEST_RIPEMD160, + ]; + /** * XML & XPath namespaces and identifiers */ diff --git a/tests/Alg/SignatureAlgorithmFactoryTest.php b/tests/Alg/SignatureAlgorithmFactoryTest.php index da7d3d90..e251200f 100644 --- a/tests/Alg/SignatureAlgorithmFactoryTest.php +++ b/tests/Alg/SignatureAlgorithmFactoryTest.php @@ -75,8 +75,8 @@ public function testGetDigestAlgorithm(): void public function testGetUnknownAlgorithm(): void { $factory = new SignatureAlgorithmFactory([]); - $this->expectException(RuntimeException::class); - $factory->getAlgorithm('Unknown alg', $this->skey); + $this->expectException(InvalidArgumentException::class); + $factory->getAlgorithm('Unknown algorithm identifier', $this->skey); } From 0a8912338901c9b0e1393cd44fd9c1c0818eb93f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaime=20Pe=CC=81rez?= Date: Tue, 20 Jul 2021 19:51:36 +0200 Subject: [PATCH 30/78] Move the hash() function to our Security utility MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jaime Pérez --- src/Utils/Security.php | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/src/Utils/Security.php b/src/Utils/Security.php index 73474f56..ea69406d 100644 --- a/src/Utils/Security.php +++ b/src/Utils/Security.php @@ -1,5 +1,7 @@ Date: Tue, 20 Jul 2021 19:52:19 +0200 Subject: [PATCH 31/78] Add new XML utility class MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit For now, move there the canonicalization and transform processing mechanisms. Signed-off-by: Jaime Pérez --- src/Utils/XML.php | 144 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 144 insertions(+) create mode 100644 src/Utils/XML.php diff --git a/src/Utils/XML.php b/src/Utils/XML.php new file mode 100644 index 00000000..e96952b4 --- /dev/null +++ b/src/Utils/XML.php @@ -0,0 +1,144 @@ +ownerDocument !== null) + && $element->isSameNode($element->ownerDocument->documentElement) + ) { + // check for any PI or comments as they would have been excluded + $current = $element; + while ($refNode = $current->previousSibling) { + if ( + (($refNode->nodeType === XML_COMMENT_NODE) && $withComments) + || $refNode->nodeType === XML_PI_NODE + ) { + break; + } + $current = $refNode; + } + if ($refNode == null) { + $element = $element->ownerDocument; + } + } + + return $element->C14N($exclusive, $withComments, $xpaths, $prefixes); + } + + + /** + * Process all transforms specified by a given Reference element. + * + * @param \SimpleSAML\XMLSecurity\XML\ds\Transforms $transforms The transforms to apply. + * @param \DOMElement $data The data referenced. + * @param bool $includeCommentNodes Whether to allow canonicalization with comments or not. + * + * @return string The canonicalized data after applying all transforms specified by $ref. + * + * @see http://www.w3.org/TR/xmldsig-core/#sec-ReferenceProcessingModel + */ + public static function processTransforms( + Transforms $transforms, + DOMElement $data, + bool $includeCommentNodes = false + ): string { + $canonicalMethod = C::C14N_EXCLUSIVE_WITHOUT_COMMENTS; + $arXPath = null; + $prefixList = null; + foreach ($transforms as $transform) { + /** @var Transform $transform */ + $algorithm = $transform->getAlgorithm(); + switch ($algorithm) { + case C::C14N_EXCLUSIVE_WITHOUT_COMMENTS: + case C::C14N_EXCLUSIVE_WITH_COMMENTS: + if (!$includeCommentNodes) { + // remove comment nodes by forcing it to use a canonicalization without comments + $canonicalMethod = C::C14N_EXCLUSIVE_WITHOUT_COMMENTS; + } else { + $canonicalMethod = $algorithm; + } + + $inclusiveNamespaces = $transform->getInclusiveNamespaces(); + if ($inclusiveNamespaces !== null) { + $prefixes = $inclusiveNamespaces->getPrefixes(); + if (count($prefixes > 0)) { + $prefixList = $prefixes; + } + } + break; + case C::C14N_INCLUSIVE_WITHOUT_COMMENTS: + case C::C14N_INCLUSIVE_WITH_COMMENTS: + if (!$includeCommentNodes) { + // remove comment nodes by forcing it to use a canonicalization without comments + $canonicalMethod = C::C14N_INCLUSIVE_WITHOUT_COMMENTS; + } else { + $canonicalMethod = $algorithm; + } + + break; + case C::XPATH_URI: + $xpath = $transform->getXPath(); + if ($xpath !== null) { + $arXPath = []; + $arXPath['query'] = '(.//. | .//@* | .//namespace::*)[' . $xpath->getExpression() . ']'; + $arXpath['namespaces'] = $xpath->getNamespaces(); + // TODO: review if $nsnode->localName is equivalent to the keys in getNamespaces() +// $nslist = $xp->query('./namespace::*', $node); +// foreach ($nslist as $nsnode) { +// if ($nsnode->localName != "xml") { +// $arXPath['namespaces'][$nsnode->localName] = $nsnode->nodeValue; +// } +// } + } + break; + } + } + + return self::canonicalizeData($data, $canonicalMethod, $arXPath, $prefixList); + } +} \ No newline at end of file From 6935a684a82d240a9df097df4b800a26a60515ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaime=20Pe=CC=81rez?= Date: Tue, 20 Jul 2021 20:06:12 +0200 Subject: [PATCH 32/78] First attempt to implement signing in XML objects MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This introduces a SignedElementTrait that gives us most of what we need. The SignableElementInterface::sign() method is now changed to receive a SignatureAlgorithm, the canonicalization algorithm and an optional KeyInfo element to add to the resulting signature. It also introduces a new method getId() that each element must implement to return their own ID used by the signature reference. The new trait implements the sign() method of the interface, and also provides a doSign() method that objects can use to effectively sign them. Calling it will generate a Signature object and keep it in a property. This object can then be used in toXML() in the object and inserted wherever we want, using the insertBefore() convenience method (also provided by the trait). The CustomSignable class demonstrates how to use the functionality. CustomSigned and its corresponding test disappears (as it made no sense in this context, since the actual XML elements are called CustomSignable). Signed-off-by: Jaime Pérez --- src/XML/SignableElementInterface.php | 31 ++++- src/XML/SignableElementTrait.php | 127 ++++++++++++++++++ src/XML/ds/X509Data.php | 1 - tests/XML/CustomSignable.php | 58 ++++---- tests/XML/CustomSignableTest.php | 65 --------- tests/XML/CustomSigned.php | 42 ------ tests/XML/CustomSignedTest.php | 73 ---------- tests/XML/SignableElementTest.php | 94 +++++++++++++ tests/XML/SignedElementTest.php | 68 ++++++++++ tests/resources/xml/custom_CustomSignable.xml | 4 +- tests/resources/xml/custom_CustomSigned.xml | 20 ++- 11 files changed, 363 insertions(+), 220 deletions(-) create mode 100644 src/XML/SignableElementTrait.php delete mode 100644 tests/XML/CustomSignableTest.php delete mode 100644 tests/XML/CustomSigned.php delete mode 100644 tests/XML/CustomSignedTest.php create mode 100644 tests/XML/SignableElementTest.php create mode 100644 tests/XML/SignedElementTest.php diff --git a/src/XML/SignableElementInterface.php b/src/XML/SignableElementInterface.php index e82302df..e6ae4ece 100644 --- a/src/XML/SignableElementInterface.php +++ b/src/XML/SignableElementInterface.php @@ -4,21 +4,38 @@ namespace SimpleSAML\XMLSecurity\XML; -use SimpleSAML\XMLSecurity\XMLSecurityKey; +use SimpleSAML\XMLSecurity\Alg\SignatureAlgorithm; +use SimpleSAML\XMLSecurity\XML\ds\KeyInfo; /** - * An interface describing signable elements. + * An interface describing objects that can be signed. * * @package simplesamlphp/xml-security */ interface SignableElementInterface { /** - * Sign the 'Element' and return a 'SignedElement' + * Get the ID of this element. * - * @param \SimpleSAML\XMLSecurity\XMLSecurityKey $signingKey The private key we should use to sign the message - * @param string[] $certificates The certificates should be strings with the PEM encoded data - * @return \SimpleSAML\XMLSecurity\XML\SignedElementInterface + * When this method returns null, the signature created for this object will reference the entire document. + * + * @return string|null The ID of this element, or null if we don't have one. + */ + public function getId(): ?string; + + + /** + * Sign the current element. + * + * @note The signature will not be applied until toSignedXML() is called. + * + * @param \SimpleSAML\XMLSecurity\Alg\SignatureAlgorithm $signer The actual signer implementation to use. + * @param string|null $canonicalizationAlg The identifier of the canonicalization algorithm to use. + * @param \SimpleSAML\XMLSecurity\XML\ds\KeyInfo|null $keyInfo A KeyInfo object to add to the signature. */ - public function sign(XMLSecurityKey $signingKey, array $certificates): SignedElementInterface; + public function sign( + SignatureAlgorithm $signer, + string $canonicalizationAlg, + ?KeyInfo $keyInfo = null + ): void; } diff --git a/src/XML/SignableElementTrait.php b/src/XML/SignableElementTrait.php new file mode 100644 index 00000000..aa038f22 --- /dev/null +++ b/src/XML/SignableElementTrait.php @@ -0,0 +1,127 @@ +signer = $signer; + $this->keyInfo = $keyInfo; + Assert::oneOf( + $canonicalizationAlg, + [ + Constants::C14N_INCLUSIVE_WITH_COMMENTS, + Constants::C14N_EXCLUSIVE_WITHOUT_COMMENTS, + Constants::C14N_EXCLUSIVE_WITH_COMMENTS, + Constants::C14N_EXCLUSIVE_WITHOUT_COMMENTS + ], + 'Unsupported canonicalization algorithm' + ); + $this->c14nAlg = $canonicalizationAlg; + } + + + /** + * @param \DOMElement $xml + * @throws \Exception + */ + private function doSign(DOMElement $xml): void + { + if ($this->signer === null) { + throw new RuntimeException('Cannot call toSignedXML() without calling sign() first.'); + } + $algorithm = $this->signer->getAlgorithmId(); + $digest = $this->signer->getDigest(); + + $transforms = new Transforms([ + new Transform(Constants::XMLDSIG_ENVELOPED), + new Transform($this->c14nAlg) + ]); + + $refId = $this->getId(); + $reference = new Reference( + new DigestMethod($digest), + new DigestValue(Security::hash($digest, XML::processTransforms($transforms, $xml))), + $transforms, + null, + null, + ($refId !== null) ? '#' . $refId : null + ); + + $signedInfo = new SignedInfo( + new CanonicalizationMethod($this->c14nAlg), + new SignatureMethod($algorithm), + [$reference] + ); + + $signingData = XML::canonicalizeData($signedInfo->toXML(), $this->c14nAlg); + $signedData = base64_encode($this->signer->sign($signingData)); + + $this->signature = new Signature($signedInfo, new SignatureValue($signedData), $this->keyInfo); + } + + + /** + * @param DOMElement $root + * @param DOMNode $node + * @param DOMElement $signature + * @return DOMElement + */ + private function insertBefore(DOMElement $root, DOMNode $node, DOMElement $signature): DOMElement + { + $root->removeChild($signature); + return $root->insertBefore($signature, $node); + } +} diff --git a/src/XML/ds/X509Data.php b/src/XML/ds/X509Data.php index 758b2c11..851e61af 100644 --- a/src/XML/ds/X509Data.php +++ b/src/XML/ds/X509Data.php @@ -8,7 +8,6 @@ use SimpleSAML\Assert\Assert; use SimpleSAML\XML\Exception\InvalidDOMElementException; use SimpleSAML\XML\Chunk; -use SimpleSAML\XMLSecurity\XML\ds\X509Certificate; /** * Class representing a ds:X509Data element. diff --git a/tests/XML/CustomSignable.php b/tests/XML/CustomSignable.php index 6a0cb508..2810d634 100644 --- a/tests/XML/CustomSignable.php +++ b/tests/XML/CustomSignable.php @@ -10,16 +10,17 @@ use SimpleSAML\XML\Exception\InvalidDOMElementException; use SimpleSAML\XML\Exception\MissingElementException; use SimpleSAML\XML\Exception\TooManyElementsException; -use SimpleSAML\XMLSecurity\XML\SignableElementInterface; -use SimpleSAML\XMLSecurity\XML\SignedElementInterface; -use SimpleSAML\XMLSecurity\XMLSecurityKey; use SimpleSAML\XMLSecurity\XML\ds\Signature; +use SimpleSAML\XMLSecurity\XML\SignableElementInterface; +use SimpleSAML\XMLSecurity\XML\SignableElementTrait; /** * @package simplesamlphp\saml2 */ -final class CustomSignable extends AbstractXMLElement implements SignableElementInterface +class CustomSignable extends AbstractXMLElement implements SignableElementInterface { + use SignableElementTrait; + /** @var string */ public const NS = 'urn:ssp:custom'; @@ -27,7 +28,14 @@ final class CustomSignable extends AbstractXMLElement implements SignableElement public const NS_PREFIX = 'ssp'; /** @var \DOMElement $element */ - protected $element; + protected \DOMElement $element; + + /** @var bool */ + protected bool $formatOutput = false; + + /** @var \SimpleSAML\XMLSecurity\XML\ds\Signature|null */ + protected ?Signature $signature = null; + /** * Constructor @@ -84,20 +92,11 @@ private function setElement(DOMElement $elt): void /** - * Sign the 'Element' and return a 'SignedElement' - * - * @param \SimpleSAML\XMLSecurity\XMLSecurityKey $signingKey The private key we should use to sign the message - * @param string[] $certificates The certificates should be strings with the PEM encoded data - * @return \SimpleSAML\XMLSecurity\XML\SignedElementInterface + * @return string|null */ - public function sign(XMLSecurityKey $signingKey, array $certificates = []): SignedElementInterface + public function getId(): ?string { - $unsigned = $this->toXML(); - $signature = new Signature($signingKey->getAlgorithm(), $certificates, $signingKey); - $signedXml = $signature->toXML($this->toXML()); - $signed = new CustomSigned($signedXml, $signature); - - return $signed; + return null; } @@ -117,19 +116,21 @@ public static function fromXML(DOMElement $xml): object Assert::minCount($xml->childNodes, 1, MissingElementException::class); Assert::maxCount($xml->childNodes, 2, TooManyElementsException::class); - // Remove the signature - Signature::getChildrenOfClass($xml); - - $element = $xml->childNodes[0]; + $signature = Signature::getChildrenOfClass($xml); + Assert::maxCount($signature, 1, TooManyElementsException::class); - return new self($element); + $customSignable = new self($xml->childNodes[(empty($signature) ? 0 : 1)]); + if (!empty($signature)) { + $customSignable->signature = $signature[0]; + } + return $customSignable; } /** * Convert this CustomSignable to XML. * - * @param \DOMElement|null $element The element we are converting to XML. + * @param \DOMElement|null $parent The parent element to append this CustomSignable to. * @return \DOMElement The XML element after adding the data corresponding to this CustomSignable. */ public function toXML(DOMElement $parent = null): DOMElement @@ -137,7 +138,16 @@ public function toXML(DOMElement $parent = null): DOMElement /** @psalm-var \DOMDocument $e->ownerDocument */ $e = $this->instantiateParentElement($parent); - $e->appendChild($e->ownerDocument->importNode($this->element, true)); + $node = $e->appendChild($e->ownerDocument->importNode($this->element, true)); + + if ($this->signer !== null) { + $this->doSign($e); + } + + if ($this->signature !== null) { + $this->insertBefore($e, $node, $this->signature->toXML($e)); + } + return $e; } } diff --git a/tests/XML/CustomSignableTest.php b/tests/XML/CustomSignableTest.php deleted file mode 100644 index 6bb6cf93..00000000 --- a/tests/XML/CustomSignableTest.php +++ /dev/null @@ -1,65 +0,0 @@ -testedClass = CustomSignable::class; - - $this->xmlRepresentation = DOMDocumentFactory::fromFile( - dirname(dirname(__FILE__)) . '/resources/xml/custom_CustomSignable.xml' - ); - } - - - /** - */ - public function testMarshalling(): void - { - $document = DOMDocumentFactory::fromString( - 'Chunk' - ); - - $customSignable = new CustomSignable($document->documentElement); - $this->assertFalse($customSignable->isEmptyElement()); - $this->assertEquals( - $this->xmlRepresentation->saveXML($this->xmlRepresentation->documentElement), - strval($customSignable) - ); - } - - - /** - */ - public function testUnmarshalling(): void - { - $customSignable = CustomSignable::fromXML($this->xmlRepresentation->documentElement); - - $customSignableElement = $customSignable->getElement(); - - $this->assertEqualXmlStructure($this->xmlRepresentation->documentElement, $customSignableElement); - } -} - diff --git a/tests/XML/CustomSigned.php b/tests/XML/CustomSigned.php deleted file mode 100644 index 758a8bd0..00000000 --- a/tests/XML/CustomSigned.php +++ /dev/null @@ -1,42 +0,0 @@ -testedClass = CustomSigned::class; - - $this->xmlRepresentation = DOMDocumentFactory::fromFile( - dirname(dirname(__FILE__)) . '/resources/xml/custom_CustomSigned.xml' - ); - } - - - /** - */ - public function testMarshalling(): void - { - $document = DOMDocumentFactory::fromString( - 'Chunk' - ); - - $customSignable = new CustomSignable($document->documentElement); - $this->assertFalse($customSignable->isEmptyElement()); - - $privateKey = PEMCertificatesMock::getPrivateKey(XMLSecurityKey::RSA_SHA256, PEMCertificatesMock::SELFSIGNED_PRIVATE_KEY); - $customSigned = $customSignable->sign($privateKey); - - $this->assertEqualXMLStructure( - $this->xmlRepresentation->documentElement, - $customSigned->toXML()->ownerDocument->documentElement - ); - } - - - /** - */ - public function testUnmarshalling(): void - { - $customSignable = CustomSignable::fromXML($this->xmlRepresentation->documentElement); - - $customSignableElement = $customSignable->getElement(); - - $this->assertEqualXMLStructure($this->xmlRepresentation->documentElement, $customSignableElement->ownerDocument->documentElement); - } -} - diff --git a/tests/XML/SignableElementTest.php b/tests/XML/SignableElementTest.php new file mode 100644 index 00000000..7c463661 --- /dev/null +++ b/tests/XML/SignableElementTest.php @@ -0,0 +1,94 @@ +testedClass = CustomSignable::class; + + $this->xmlRepresentation = DOMDocumentFactory::fromFile( + dirname(dirname(__FILE__)) . '/resources/xml/custom_CustomSignable.xml' + ); + + $this->signed = DOMDocumentFactory::fromFile( + dirname(dirname(__FILE__)) . '/resources/xml/custom_CustomSigned.xml' + ); + + $certificate = file_get_contents( + dirname(dirname(__FILE__)) . '/resources/certificates/rsa-pem/selfsigned.simplesamlphp.org.crt' + ); + $certificateLines = explode("\n", trim($certificate)); + array_pop($certificateLines); + array_shift($certificateLines); + $this->certificate = join("\n", $certificateLines); + + $this->key = PrivateKey::fromFile( + dirname(dirname(__FILE__)) . '/resources/certificates/rsa-pem/selfsigned.simplesamlphp.org_nopasswd.key' + ); + } + + + /** + * Test that signing produces the expected output. + */ + public function testSigning(): void + { + $document = DOMDocumentFactory::fromString( + 'Chunk' + ); + $customSignable = new CustomSignable($document->documentElement); + $this->assertFalse($customSignable->isEmptyElement()); + + $factory = new SignatureAlgorithmFactory(); + $signer = $factory->getAlgorithm(Constants::SIG_RSA_SHA256, $this->key); + + $keyInfo = new KeyInfo([ + new X509Data([ + new X509Certificate($this->certificate) + ]) + ]); + + $customSignable->sign($signer, Constants::C14N_EXCLUSIVE_WITHOUT_COMMENTS, $keyInfo); + + $this->assertEquals( + $this->signed->saveXML($this->signed->documentElement), + strval($customSignable) + ); + } +} + diff --git a/tests/XML/SignedElementTest.php b/tests/XML/SignedElementTest.php new file mode 100644 index 00000000..5865ba44 --- /dev/null +++ b/tests/XML/SignedElementTest.php @@ -0,0 +1,68 @@ +testedClass = CustomSignable::class; + + $this->xmlRepresentation = DOMDocumentFactory::fromFile( + dirname(dirname(__FILE__)) . '/resources/xml/custom_CustomSigned.xml' + ); + + $certificate = file_get_contents( + dirname(dirname(__FILE__)) . '/resources/certificates/rsa-pem/selfsigned.simplesamlphp.org.crt' + ); + $certificateLines = explode("\n", trim($certificate)); + array_pop($certificateLines); + array_shift($certificateLines); + $this->certificate = join("\n", $certificateLines); + + $this->key = PrivateKey::fromFile( + dirname(dirname(__FILE__)) . '/resources/certificates/rsa-pem/selfsigned.simplesamlphp.org_nopasswd.key' + ); + } + + + /** + */ + public function testUnmarshalling(): void + { + $customSigned = CustomSignable::fromXML($this->xmlRepresentation->documentElement); + + $this->assertEquals( + $this->xmlRepresentation->saveXML($this->xmlRepresentation->documentElement), + strval($customSigned) + ); + } +} + diff --git a/tests/resources/xml/custom_CustomSignable.xml b/tests/resources/xml/custom_CustomSignable.xml index 41c67a1d..ac407dd5 100644 --- a/tests/resources/xml/custom_CustomSignable.xml +++ b/tests/resources/xml/custom_CustomSignable.xml @@ -1,3 +1 @@ - - Chunk - +Chunk diff --git a/tests/resources/xml/custom_CustomSigned.xml b/tests/resources/xml/custom_CustomSigned.xml index adcfe1f7..43af1322 100644 --- a/tests/resources/xml/custom_CustomSigned.xml +++ b/tests/resources/xml/custom_CustomSigned.xml @@ -1,5 +1,15 @@ - - - - j+vUDatE3NAWXXBx01uUb58YMuFofMcn1VgFR7968iQ=LQplDy+GzAknBdoWf2LCG/UHl1Mjo4270Yt85hXZpji1ZHsytMWfTTkKrX1wJFD8YYp8VLy4zT0V12vFT8G7KVODMFzwS2RkQe54+rSIjSWvQ571tijEZ+9q7cy/tTXBj4lOXneM401qnKd/HIpmQOZF3gS5D1VPfF27QhViVIQ= -Chunk +5F9s1oK6M4QD7UH4GDU9a2Otz876kwhRbvh+oGVTCoM=V5dmpyjjOLJKFNc8uBJFKDA89OsyL+0oT5EJhVyD5jYO/aO8juNE5+btW0sLbo46DbvmXxLmyZADsT4wqR8pRH6FiBwvYK0wS4FRociNh710HrEcTghR0sCasW/VQh0F6jcRqbi8PJOLpTrtBs7LZH31marO5I0Eed9RPihLSws=MIICxDCCAi2gAwIBAgIUCJ8EYI/BrvQnHt9QFGfdqgaxDmowDQYJKoZIhvcNAQEL +BQAwczElMCMGA1UEAwwcc2VsZnNpZ25lZC5zaW1wbGVzYW1scGhwLm9yZzEZMBcG +A1UECgwQU2ltcGxlU0FNTHBocCBIUTERMA8GA1UEBwwISG9ub2x1bHUxDzANBgNV +BAgMBkhhd2FpaTELMAkGA1UEBhMCVVMwIBcNMjAwNjIzMjEwMTEyWhgPMjEyMDA1 +MzAyMTAxMTJaMHMxJTAjBgNVBAMMHHNlbGZzaWduZWQuc2ltcGxlc2FtbHBocC5v +cmcxGTAXBgNVBAoMEFNpbXBsZVNBTUxwaHAgSFExETAPBgNVBAcMCEhvbm9sdWx1 +MQ8wDQYDVQQIDAZIYXdhaWkxCzAJBgNVBAYTAlVTMIGfMA0GCSqGSIb3DQEBAQUA +A4GNADCBiQKBgQDZ3ztmRqrJm6zSJ2jNUjbxdlX3WLadthHvDkdfQDWuFIUjMkCf +O8+OWYdFP4cQWKBVnWyO+/LXFl9p9fwcSJmhmkgz5blUw0cjv8jccHw+yfVnGxGC +K2Up/Si4HE/WStgjTKr3ivAqyc0Kc/97EoIXmb96SEEb4Lat9xt6mFSbWQIDAQAB +o1MwUTAdBgNVHQ4EFgQUHhB/tFwoDOT8V1nLi5XY4CJgTTQwHwYDVR0jBBgwFoAU +HhB/tFwoDOT8V1nLi5XY4CJgTTQwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0B +AQsFAAOBgQBVqkGuiwyYMx0FH3j/qirZ2nKrjUU+p0ganzEA7kVZ97RivW5SFLHk +RjcfqDXVyqOzmr4iR8rfxI2rUjYQI1SsKacPhitiArMk2IQ+uvxZ5eNtU2yFzW4a +rQ+a2ttlZI/GF9nLvgUNFIUwaDc+PQD2rOk+ACklbAS3jkf+L3DpQg==Chunk \ No newline at end of file From 950411474f0019c117cf3b5fea0e64ca99b99d00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaime=20Pe=CC=81rez?= Date: Tue, 20 Jul 2021 20:43:21 +0200 Subject: [PATCH 33/78] Use the local XML-related constants MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Downgrade xml-common to 0.7.0, use the local constants. Signed-off-by: Jaime Pérez --- composer.json | 2 +- src/Constants.php | 6 +++--- src/Signature.php | 6 +++--- src/Utils/XPath.php | 4 ++-- src/XML/ds/KeyInfo.php | 2 +- src/XML/ds/Signature.php | 2 +- src/XML/xenc/AbstractEncryptionMethod.php | 6 +++--- src/XML/xenc/AbstractXencElement.php | 2 +- tests/XML/xenc/EncryptionMethodTest.php | 6 ++---- 9 files changed, 17 insertions(+), 19 deletions(-) diff --git a/composer.json b/composer.json index 6f06bb21..3852824e 100644 --- a/composer.json +++ b/composer.json @@ -40,7 +40,7 @@ "robrichards/xmlseclibs": "^3.1.1", "simplesamlphp/assert": "~0.2.6", - "simplesamlphp/xml-common": "^0.7.1" + "simplesamlphp/xml-common": "^0.7.0" }, "require-dev": { "simplesamlphp/simplesamlphp-test-framework": "^1.0.5" diff --git a/src/Constants.php b/src/Constants.php index 66ab4f09..58d5a7ba 100644 --- a/src/Constants.php +++ b/src/Constants.php @@ -128,11 +128,11 @@ class Constants extends \SimpleSAML\XML\Constants /** * XML & XPath namespaces and identifiers */ - public const XMLDSIGNS = 'http://www.w3.org/2000/09/xmldsig#'; - public const XMLDSIG11NS = 'http://www.w3.org/2009/xmldsig11#'; + public const NS_XDSIG = 'http://www.w3.org/2000/09/xmldsig#'; + public const NS_XDSIG11 = 'http://www.w3.org/2009/xmldsig11#'; public const XMLDSIG_ENVELOPED = 'http://www.w3.org/2000/09/xmldsig#enveloped-signature'; - public const XMLENCNS = 'http://www.w3.org/2001/04/xmlenc#'; + public const NS_XENC = 'http://www.w3.org/2001/04/xmlenc#'; public const XMLENC_ELEMENT = 'http://www.w3.org/2001/04/xmlenc#Element'; public const XMLENC_CONTENT = 'http://www.w3.org/2001/04/xmlenc#Content'; diff --git a/src/Signature.php b/src/Signature.php index b9e77172..849e5215 100644 --- a/src/Signature.php +++ b/src/Signature.php @@ -361,7 +361,7 @@ public function addX509Certificates( if ($digest !== false) { // add certificate digest $fingerprint = base64_encode(hex2bin($cert->getRawThumbprint($digest))); - $x509DigestNode = $this->createElement('X509Digest', $fingerprint, C::XMLDSIG11NS, 'dsig11'); + $x509DigestNode = $this->createElement('X509Digest', $fingerprint, C::NS_XDSIG11, 'dsig11'); $x509DigestNode->setAttribute('Algorithm', $digest); $certDataNode->appendChild($x509DigestNode); } @@ -714,7 +714,7 @@ public function setPrefix($prefix): void */ public function setSignatureElement(DOMElement $element): void { - if ($element->localName !== 'Signature' || $element->namespaceURI !== C::XMLDSIGNS) { + if ($element->localName !== 'Signature' || $element->namespaceURI !== C::NS_XDSIG) { throw new RuntimeException('Node is not an XML signature'); } $this->sigNode = $element; @@ -917,7 +917,7 @@ protected function canonicalizeData( protected function createElement( string $name, string $content = null, - string $ns = C::XMLDSIGNS, + string $ns = C::NS_XDSIG, string $nsPrefix = null ): DOMElement { if ($this->sigNode === null) { diff --git a/src/Utils/XPath.php b/src/Utils/XPath.php index a23c1b75..41cf7d27 100644 --- a/src/Utils/XPath.php +++ b/src/Utils/XPath.php @@ -26,8 +26,8 @@ class XPath extends \RobRichards\XMLSecLibs\Utils\XPath public static function getXPath(DOMDocument $doc) { $xp = new DOMXPath($doc); - $xp->registerNamespace('ds', C::XMLDSIGNS); - $xp->registerNamespace('xenc', C::XMLENCNS); + $xp->registerNamespace('ds', C::NS_XDSIG); + $xp->registerNamespace('xenc', C::NS_XENC); return $xp; } diff --git a/src/XML/ds/KeyInfo.php b/src/XML/ds/KeyInfo.php index bf73ea2e..e8803917 100644 --- a/src/XML/ds/KeyInfo.php +++ b/src/XML/ds/KeyInfo.php @@ -146,7 +146,7 @@ public static function fromXML(DOMElement $xml): object $info[] = new Chunk($n); break; } - } elseif ($n->namespaceURI === Constants::XMLENCNS) { + } elseif ($n->namespaceURI === Constants::NS_XENC) { switch ($n->localName) { case 'EncryptedData': $info[] = EncryptedData::fromXML($n); diff --git a/src/XML/ds/Signature.php b/src/XML/ds/Signature.php index b7005b1c..296928df 100644 --- a/src/XML/ds/Signature.php +++ b/src/XML/ds/Signature.php @@ -7,8 +7,8 @@ use DOMElement; use SimpleSAML\Assert\Assert; use SimpleSAML\XML\Chunk; -use SimpleSAML\XML\Constants; use SimpleSAML\XML\Exception\InvalidDOMElementException; +use SimpleSAML\XMLSecurity\Constants; use function array_pop; diff --git a/src/XML/xenc/AbstractEncryptionMethod.php b/src/XML/xenc/AbstractEncryptionMethod.php index fbe2e3ac..c6c7c545 100644 --- a/src/XML/xenc/AbstractEncryptionMethod.php +++ b/src/XML/xenc/AbstractEncryptionMethod.php @@ -84,7 +84,7 @@ public static function fromXML(DOMElement $xml): object foreach ($xml->childNodes as $node) { if (!$node instanceof DOMElement) { continue; - } elseif ($node->namespaceURI === Constants::XMLENCNS) { + } elseif ($node->namespaceURI === Constants::NS_XENC) { if ($node->localName === 'KeySize') { Assert::null( $keySize, @@ -232,12 +232,12 @@ public function toXML(DOMElement $parent = null): DOMElement $e->setAttribute('Algorithm', $this->algorithm); if ($this->keySize !== null) { - $keySize = $e->ownerDocument->createElementNS(Constants::XMLENCNS, 'xenc:KeySize', strval($this->keySize)); + $keySize = $e->ownerDocument->createElementNS(Constants::NS_XENC, 'xenc:KeySize', strval($this->keySize)); $e->appendChild($keySize); } if ($this->oaepParams !== null) { - $oaepParams = $e->ownerDocument->createElementNS(Constants::XMLENCNS, 'xenc:OAEPParams', $this->oaepParams); + $oaepParams = $e->ownerDocument->createElementNS(Constants::NS_XENC, 'xenc:OAEPParams', $this->oaepParams); $e->appendChild($oaepParams); } diff --git a/src/XML/xenc/AbstractXencElement.php b/src/XML/xenc/AbstractXencElement.php index f1fdf149..fd5aa38f 100644 --- a/src/XML/xenc/AbstractXencElement.php +++ b/src/XML/xenc/AbstractXencElement.php @@ -15,7 +15,7 @@ abstract class AbstractXencElement extends AbstractXMLElement { /** @var string */ - public const NS = Constants::XMLENCNS; + public const NS = Constants::NS_XENC; /** @var string */ public const NS_PREFIX = 'xenc'; diff --git a/tests/XML/xenc/EncryptionMethodTest.php b/tests/XML/xenc/EncryptionMethodTest.php index 051f50c5..61d33753 100644 --- a/tests/XML/xenc/EncryptionMethodTest.php +++ b/tests/XML/xenc/EncryptionMethodTest.php @@ -4,10 +4,8 @@ namespace SimpleSAML\XMLSecurity\Test\XML\xenc; -use DOMDocument; use PHPUnit\Framework\TestCase; use SimpleSAML\Test\XML\SerializableXMLTestTrait; -use SimpleSAML\Assert\AssertionFailedException; use SimpleSAML\XML\Chunk; use SimpleSAML\XML\DOMDocumentFactory; use SimpleSAML\XML\Exception\MissingAttributeException; @@ -70,7 +68,7 @@ public function testMarshallingWithoutOptionalParameters(): void { $em = new EncryptionMethod('http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p'); $document = DOMDocumentFactory::fromString( - '' ); @@ -145,7 +143,7 @@ public function testUnmarshallingWithoutAlgorithm(): void */ public function testUnmarshallingWithoutOptionalParameters(): void { - $xencns = Constants::XMLENCNS; + $xencns = Constants::NS_XENC; $document = DOMDocumentFactory::fromString(<< XML From 40b63dab9e8f24f554da18eff25bca35788667f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaime=20Pe=CC=81rez?= Date: Wed, 21 Jul 2021 16:40:34 +0200 Subject: [PATCH 34/78] Add CanonicalizableInterface MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This interface provides a method to canonicalise elements implementing it. This allows us to be sure that the actual input for the C14N algorithm is the same as the original, if the object was created with fromXML(). Signed-off-by: Jaime Pérez --- src/XML/CanonicalizableInterface.php | 36 ++++++++++++ src/XML/CanonicalizableTrait.php | 58 +++++++++++++++++++ src/XML/SignableElementTrait.php | 2 +- src/XML/ds/SignedInfo.php | 33 ++++++++--- tests/XML/ds/SignedInfoTest.php | 50 ++++++++++++++++ .../xml/ds_SignedInfoWithComments.xml | 14 +++++ 6 files changed, 184 insertions(+), 9 deletions(-) create mode 100644 src/XML/CanonicalizableInterface.php create mode 100644 src/XML/CanonicalizableTrait.php create mode 100644 tests/resources/xml/ds_SignedInfoWithComments.xml diff --git a/src/XML/CanonicalizableInterface.php b/src/XML/CanonicalizableInterface.php new file mode 100644 index 00000000..97f83d5d --- /dev/null +++ b/src/XML/CanonicalizableInterface.php @@ -0,0 +1,36 @@ +getOriginalXML(), $method, $xpaths, $prefixes); + } + + + /** + * Serialize this canonicalisable element. + * + * @return string The serialized chunk. + */ + public function serialize(): string + { + $xml = $this->getOriginalXML(); + return $xml->ownerDocument->saveXML($xml); + } +} \ No newline at end of file diff --git a/src/XML/SignableElementTrait.php b/src/XML/SignableElementTrait.php index aa038f22..db97dbfc 100644 --- a/src/XML/SignableElementTrait.php +++ b/src/XML/SignableElementTrait.php @@ -106,7 +106,7 @@ private function doSign(DOMElement $xml): void [$reference] ); - $signingData = XML::canonicalizeData($signedInfo->toXML(), $this->c14nAlg); + $signingData = $signedInfo->canonicalize($this->c14nAlg); $signedData = base64_encode($this->signer->sign($signingData)); $this->signature = new Signature($signedInfo, new SignatureValue($signedData), $this->keyInfo); diff --git a/src/XML/ds/SignedInfo.php b/src/XML/ds/SignedInfo.php index 8a8b2d25..0d4947ad 100644 --- a/src/XML/ds/SignedInfo.php +++ b/src/XML/ds/SignedInfo.php @@ -7,7 +7,8 @@ use DOMElement; use SimpleSAML\Assert\Assert; use SimpleSAML\XML\Exception\InvalidDOMElementException; -use SimpleSAML\XML\Chunk; +use SimpleSAML\XMLSecurity\XML\CanonicalizableInterface; +use SimpleSAML\XMLSecurity\XML\CanonicalizableTrait; use function array_pop; @@ -16,8 +17,10 @@ * * @package simplesamlphp/xml-security */ -final class SignedInfo extends AbstractDsElement +final class SignedInfo extends AbstractDsElement implements CanonicalizableInterface { + use CanonicalizableTrait; + /** @var string|null */ protected ?string $Id; @@ -36,6 +39,11 @@ final class SignedInfo extends AbstractDsElement */ protected array $references; + /** + * @var DOMElement + */ + protected ?DOMElement $xml = null; + /** * Initialize a SignedIfno. @@ -148,6 +156,18 @@ private function setId(?string $Id): void } + /** + * @inheritDoc + */ + protected function getOriginalXML(): DOMElement + { + if ($this->xml !== null) { + return $this->xml; + } + return $this->toXML(); + } + + /** * Convert XML into a SignedInfo instance * @@ -173,12 +193,9 @@ public static function fromXML(DOMElement $xml): object $references = Reference::getChildrenOfClass($xml); Assert::minCount($references, 1, 'A ds:SignedInfo element must contain at least one ds:Reference'); - return new self( - array_pop($canonicalizationMethod), - array_pop($signatureMethod), - $references, - $Id - ); + $signedInfo = new self(array_pop($canonicalizationMethod), array_pop($signatureMethod), $references, $Id); + $signedInfo->xml = $xml; + return $signedInfo; } diff --git a/tests/XML/ds/SignedInfoTest.php b/tests/XML/ds/SignedInfoTest.php index 980dd26a..035687cd 100644 --- a/tests/XML/ds/SignedInfoTest.php +++ b/tests/XML/ds/SignedInfoTest.php @@ -89,4 +89,54 @@ public function testUnmarshalling(): void strval($signedInfo) ); } + + + /** + * + */ + public function canonicalization(\DOMElement $xml, SignedInfo $signedInfo): void + { + $this->assertEquals( + $xml->C14N(true, false), + $signedInfo->canonicalize(Constants::C14N_EXCLUSIVE_WITHOUT_COMMENTS) + ); + $this->assertEquals( + $xml->C14N(false, false), + $signedInfo->canonicalize(Constants::C14N_INCLUSIVE_WITHOUT_COMMENTS) + ); + $this->assertEquals( + $xml->C14N(true, true), + $signedInfo->canonicalize(Constants::C14N_EXCLUSIVE_WITH_COMMENTS) + ); + $this->assertEquals( + $xml->C14N(false, true), + $signedInfo->canonicalize(Constants::C14N_INCLUSIVE_WITH_COMMENTS) + ); + } + + + /** + * Test that canonicalization works fine. + */ + public function testCanonicalizaation(): void + { + $xml = DOMDocumentFactory::fromFile( + dirname(dirname(dirname(__FILE__))) . '/resources/xml/ds_SignedInfoWithComments.xml' + )->documentElement; + $signedInfo = SignedInfo::fromXML($xml); + $this->canonicalization($xml, $signedInfo); + } + + + /** + * Test that canonicalization works fine even after serializing and unserializing + */ + public function testCanonicalizationAfterSerialization(): void + { + $xml = DOMDocumentFactory::fromFile( + dirname(dirname(dirname(__FILE__))) . '/resources/xml/ds_SignedInfoWithComments.xml' + )->documentElement; + $signedInfo = unserialize(serialize(SignedInfo::fromXML($xml))); + $this->canonicalization($xml, $signedInfo); + } } diff --git a/tests/resources/xml/ds_SignedInfoWithComments.xml b/tests/resources/xml/ds_SignedInfoWithComments.xml new file mode 100644 index 00000000..43ca0a0f --- /dev/null +++ b/tests/resources/xml/ds_SignedInfoWithComments.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + /CTj03d1DB5e2t7CTo9BEzCf5S9NRzwnBgZRlm32REI= + + From 129de508b77f48373d99ac3a4f0510111d559365 Mon Sep 17 00:00:00 2001 From: Tim van Dijen Date: Wed, 21 Jul 2021 20:33:40 +0200 Subject: [PATCH 35/78] Use array constants --- src/Alg/Signature/HMAC.php | 9 +-------- src/Alg/Signature/RSA.php | 9 +-------- 2 files changed, 2 insertions(+), 16 deletions(-) diff --git a/src/Alg/Signature/HMAC.php b/src/Alg/Signature/HMAC.php index 1433030a..039a2ceb 100644 --- a/src/Alg/Signature/HMAC.php +++ b/src/Alg/Signature/HMAC.php @@ -37,13 +37,6 @@ public function __construct(SymmetricKey $key, string $algId = C::SIG_HMAC_SHA25 */ public static function getSupportedAlgorithms(): array { - return [ - C::SIG_HMAC_SHA1, - C::SIG_HMAC_SHA224, - C::SIG_HMAC_SHA256, - C::SIG_HMAC_SHA384, - C::SIG_HMAC_SHA512, - C::SIG_HMAC_RIPEMD160, - ]; + return C::HMAC_DIGESTS; } } diff --git a/src/Alg/Signature/RSA.php b/src/Alg/Signature/RSA.php index 84f75f33..e407eeb6 100644 --- a/src/Alg/Signature/RSA.php +++ b/src/Alg/Signature/RSA.php @@ -37,13 +37,6 @@ public function __construct(AsymmetricKey $key, string $algId = C::SIG_RSA_SHA25 */ public static function getSupportedAlgorithms(): array { - return [ - C::SIG_RSA_RIPEMD160, - C::SIG_RSA_SHA1, - C::SIG_RSA_SHA224, - C::SIG_RSA_SHA256, - C::SIG_RSA_SHA384, - C::SIG_RSA_SHA512, - ]; + return C::RSA_DIGESTS; } } From cbda6238a5799d7ff0c2a76bd8db0dc9ce94cd60 Mon Sep 17 00:00:00 2001 From: Tim van Dijen Date: Wed, 21 Jul 2021 20:40:47 +0200 Subject: [PATCH 36/78] Fix typos --- src/XML/CanonicalizableInterface.php | 6 +++--- src/XML/ds/SignedInfo.php | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/XML/CanonicalizableInterface.php b/src/XML/CanonicalizableInterface.php index 97f83d5d..28f13fd3 100644 --- a/src/XML/CanonicalizableInterface.php +++ b/src/XML/CanonicalizableInterface.php @@ -7,10 +7,10 @@ use SimpleSAML\XML\XMLElementInterface; /** - * An interface for objects that can be canonicalised. + * An interface for objects that can be canonicalized. * * Objects implementing this interface should retain the original XML structure that was used to create them with - * fromXML(), in order to guarantee that the original canonicalisation of the object is the same as the one produced + * fromXML(), in order to guarantee that the original canonicalization of the object is the same as the one produced * by this interface. * * If the original DOM object is retained, please remember to implement the \Serializable interface so that the @@ -33,4 +33,4 @@ interface CanonicalizableInterface extends XMLElementInterface, \Serializable * @return string */ public function canonicalize(string $method, ?array $xpaths = null, ?array $prefixes = null): string; -} \ No newline at end of file +} diff --git a/src/XML/ds/SignedInfo.php b/src/XML/ds/SignedInfo.php index 0d4947ad..7be343a9 100644 --- a/src/XML/ds/SignedInfo.php +++ b/src/XML/ds/SignedInfo.php @@ -46,7 +46,7 @@ final class SignedInfo extends AbstractDsElement implements CanonicalizableInter /** - * Initialize a SignedIfno. + * Initialize a SignedInfo. * * @param \SimpleSAML\XMLSecurity\XML\ds\CanonicalizationMethod $canonicalizationMethod * @param \SimpleSAML\XMLSecurity\XML\ds\SignatureMethod $signatureMethod From db79a571706e4469e178d3a2995bcf3b0e16774a Mon Sep 17 00:00:00 2001 From: Tim van Dijen Date: Wed, 21 Jul 2021 20:49:18 +0200 Subject: [PATCH 37/78] Fix booboo --- src/Alg/Signature/HMAC.php | 2 +- src/Alg/Signature/RSA.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Alg/Signature/HMAC.php b/src/Alg/Signature/HMAC.php index 039a2ceb..f37e640f 100644 --- a/src/Alg/Signature/HMAC.php +++ b/src/Alg/Signature/HMAC.php @@ -37,6 +37,6 @@ public function __construct(SymmetricKey $key, string $algId = C::SIG_HMAC_SHA25 */ public static function getSupportedAlgorithms(): array { - return C::HMAC_DIGESTS; + return C::$HMAC_DIGESTS; } } diff --git a/src/Alg/Signature/RSA.php b/src/Alg/Signature/RSA.php index e407eeb6..bdd60046 100644 --- a/src/Alg/Signature/RSA.php +++ b/src/Alg/Signature/RSA.php @@ -37,6 +37,6 @@ public function __construct(AsymmetricKey $key, string $algId = C::SIG_RSA_SHA25 */ public static function getSupportedAlgorithms(): array { - return C::RSA_DIGESTS; + return C::$RSA_DIGESTS; } } From 73b05c8e6c6b6de6a9c3ec2c000d1cecdc2dac5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaime=20Pe=CC=81rez?= Date: Wed, 21 Jul 2021 21:52:35 +0200 Subject: [PATCH 38/78] Rename Canonicalize* interface and trait MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jaime Pérez --- ...eInterface.php => CanonicalizableElementInterface.php} | 2 +- ...calizableTrait.php => CanonicalizableElementTrait.php} | 4 ++-- src/XML/SignedElementInterface.php | 2 +- src/XML/ds/SignedInfo.php | 8 ++++---- 4 files changed, 8 insertions(+), 8 deletions(-) rename src/XML/{CanonicalizableInterface.php => CanonicalizableElementInterface.php} (94%) rename src/XML/{CanonicalizableTrait.php => CanonicalizableElementTrait.php} (94%) diff --git a/src/XML/CanonicalizableInterface.php b/src/XML/CanonicalizableElementInterface.php similarity index 94% rename from src/XML/CanonicalizableInterface.php rename to src/XML/CanonicalizableElementInterface.php index 28f13fd3..6ca5f5e5 100644 --- a/src/XML/CanonicalizableInterface.php +++ b/src/XML/CanonicalizableElementInterface.php @@ -18,7 +18,7 @@ * * @package simplesamlphp/xml-security */ -interface CanonicalizableInterface extends XMLElementInterface, \Serializable +interface CanonicalizableElementInterface extends XMLElementInterface, \Serializable { /** * Get the canonical (string) representation of this object. diff --git a/src/XML/CanonicalizableTrait.php b/src/XML/CanonicalizableElementTrait.php similarity index 94% rename from src/XML/CanonicalizableTrait.php rename to src/XML/CanonicalizableElementTrait.php index 8928a1f7..e141060b 100644 --- a/src/XML/CanonicalizableTrait.php +++ b/src/XML/CanonicalizableElementTrait.php @@ -8,11 +8,11 @@ use SimpleSAML\XMLSecurity\Utils\XML; /** - * A trait implementing the CanonicalizableInterface. + * A trait implementing the CanonicalizableElementInterface. * * @package simplesamlphp/xml-security */ -trait CanonicalizableTrait +trait CanonicalizableElementTrait { /** * This trait uses the php DOM extension. As such, it requires you to keep track (or produce) the DOMElement diff --git a/src/XML/SignedElementInterface.php b/src/XML/SignedElementInterface.php index d94f983b..b62690ff 100644 --- a/src/XML/SignedElementInterface.php +++ b/src/XML/SignedElementInterface.php @@ -11,7 +11,7 @@ * * @package simplesamlphp/xml-security */ -interface SignedElementInterface +interface SignedElementInterface extends CanonicalizableElementInterface { /** * Retrieve certificates that sign this element. diff --git a/src/XML/ds/SignedInfo.php b/src/XML/ds/SignedInfo.php index 7be343a9..2df6ab31 100644 --- a/src/XML/ds/SignedInfo.php +++ b/src/XML/ds/SignedInfo.php @@ -7,8 +7,8 @@ use DOMElement; use SimpleSAML\Assert\Assert; use SimpleSAML\XML\Exception\InvalidDOMElementException; -use SimpleSAML\XMLSecurity\XML\CanonicalizableInterface; -use SimpleSAML\XMLSecurity\XML\CanonicalizableTrait; +use SimpleSAML\XMLSecurity\XML\CanonicalizableElementInterface; +use SimpleSAML\XMLSecurity\XML\CanonicalizableElementTrait; use function array_pop; @@ -17,9 +17,9 @@ * * @package simplesamlphp/xml-security */ -final class SignedInfo extends AbstractDsElement implements CanonicalizableInterface +final class SignedInfo extends AbstractDsElement implements CanonicalizableElementInterface { - use CanonicalizableTrait; + use CanonicalizableElementTrait; /** @var string|null */ protected ?string $Id; From f57790fc51803c87744d4cb31d1962f3132db60f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaime=20Pe=CC=81rez?= Date: Wed, 21 Jul 2021 22:00:04 +0200 Subject: [PATCH 39/78] Bugfix MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit What we need to return in the getSupportedAlgorithms() method is the list of signature algorithms, not their corresponding digests. Signed-off-by: Jaime Pérez --- src/Alg/Signature/HMAC.php | 2 +- src/Alg/Signature/RSA.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Alg/Signature/HMAC.php b/src/Alg/Signature/HMAC.php index f37e640f..a497d86d 100644 --- a/src/Alg/Signature/HMAC.php +++ b/src/Alg/Signature/HMAC.php @@ -37,6 +37,6 @@ public function __construct(SymmetricKey $key, string $algId = C::SIG_HMAC_SHA25 */ public static function getSupportedAlgorithms(): array { - return C::$HMAC_DIGESTS; + return array_keys(C::$HMAC_DIGESTS); } } diff --git a/src/Alg/Signature/RSA.php b/src/Alg/Signature/RSA.php index bdd60046..093708a1 100644 --- a/src/Alg/Signature/RSA.php +++ b/src/Alg/Signature/RSA.php @@ -37,6 +37,6 @@ public function __construct(AsymmetricKey $key, string $algId = C::SIG_RSA_SHA25 */ public static function getSupportedAlgorithms(): array { - return C::$RSA_DIGESTS; + return array_keys(C::$RSA_DIGESTS); } } From c3381c731237a4132be3b8854bdb4cf21a88726a Mon Sep 17 00:00:00 2001 From: Tim van Dijen Date: Wed, 21 Jul 2021 20:51:49 +0200 Subject: [PATCH 40/78] Bump xml-common --- composer.json | 2 +- src/Utils/XPath.php | 10 +++++----- tests/XML/ds/SignatureTest.php | 7 ++++--- tests/XML/ds/X509IssuerSerialTest.php | 8 +++++--- tests/XML/xenc/EncryptedKeyTest.php | 14 ++++++++++---- 5 files changed, 25 insertions(+), 16 deletions(-) diff --git a/composer.json b/composer.json index 3852824e..e541ee18 100644 --- a/composer.json +++ b/composer.json @@ -40,7 +40,7 @@ "robrichards/xmlseclibs": "^3.1.1", "simplesamlphp/assert": "~0.2.6", - "simplesamlphp/xml-common": "^0.7.0" + "simplesamlphp/xml-common": "^0.8.0" }, "require-dev": { "simplesamlphp/simplesamlphp-test-framework": "^1.0.5" diff --git a/src/Utils/XPath.php b/src/Utils/XPath.php index 41cf7d27..5b8b9020 100644 --- a/src/Utils/XPath.php +++ b/src/Utils/XPath.php @@ -13,19 +13,19 @@ * * @package SimpleSAML\XMLSecurity\Utils */ -class XPath extends \RobRichards\XMLSecLibs\Utils\XPath +class XPath extends \SimpleSAML\XML\Utils\XPath { /** * Get a DOMXPath object that can be used to search for XMLDSIG elements. * - * @param \DOMDocument $doc The document to associate to the DOMXPath object. + * @param \DOMNode $doc The document to associate to the DOMXPath object. * * @return \DOMXPath A DOMXPath object ready to use in the given document, with the XMLDSIG namespace already * registered. */ - public static function getXPath(DOMDocument $doc) + public static function getXPath(DOMNode $doc): DOMXPath { - $xp = new DOMXPath($doc); + $xp = parent::getXPath($doc); $xp->registerNamespace('ds', C::NS_XDSIG); $xp->registerNamespace('xenc', C::NS_XENC); return $xp; @@ -42,7 +42,7 @@ public static function getXPath(DOMDocument $doc) * * @throws RuntimeException If no DOM document is available. */ - public static function findElement(DOMNode $ref, $name) + public static function findElement(DOMNode $ref, string $name): DOMDocument { $doc = $ref instanceof DOMDocument ? $ref : $ref->ownerDocument; if ($doc === null) { diff --git a/tests/XML/ds/SignatureTest.php b/tests/XML/ds/SignatureTest.php index d89c1ca8..a737f7f3 100644 --- a/tests/XML/ds/SignatureTest.php +++ b/tests/XML/ds/SignatureTest.php @@ -8,7 +8,7 @@ use SimpleSAML\Test\XML\SerializableXMLTestTrait; use SimpleSAML\XML\Chunk; use SimpleSAML\XML\DOMDocumentFactory; -use SimpleSAML\XML\Utils as XMLUtils; +use SimpleSAML\XMLSecurity\Utils\XPath; use SimpleSAML\XMLSecurity\XML\ds\KeyInfo; use SimpleSAML\XMLSecurity\XML\ds\Signature; use SimpleSAML\XMLSecurity\XML\ds\SignatureValue; @@ -112,12 +112,13 @@ public function testMarshallingElementOrdering(): void ); $signatureElement = $signature->toXML(); + $xpCache = XPath::getXPath($signatureElement); - $signedInfo = XMLUtils::xpQuery($signatureElement, './ds:SignedInfo'); + $signedInfo = XPath::xpQuery($signatureElement, './ds:SignedInfo', $xpCache); $this->assertCount(1, $signedInfo); /** @psalm-var \DOMElement[] $signatureElements */ - $signatureElements = XMLUtils::xpQuery($signatureElement, './ds:SignedInfo/following-sibling::*'); + $signatureElements = XPath::xpQuery($signatureElement, './ds:SignedInfo/following-sibling::*', $xpCache); // Test ordering of Signature contents $this->assertCount(3, $signatureElements); diff --git a/tests/XML/ds/X509IssuerSerialTest.php b/tests/XML/ds/X509IssuerSerialTest.php index 288ab806..6d9071e6 100644 --- a/tests/XML/ds/X509IssuerSerialTest.php +++ b/tests/XML/ds/X509IssuerSerialTest.php @@ -7,11 +7,11 @@ use DOMDocument; use PHPUnit\Framework\TestCase; use SimpleSAML\XML\DOMDocumentFactory; -use SimpleSAML\XML\Utils as XMLUtils; use SimpleSAML\XMLSecurity\Constants; use SimpleSAML\XMLSecurity\Key; use SimpleSAML\XMLSecurity\Utils\Certificate as CertificateUtils; use SimpleSAML\XMLSecurity\TestUtils\PEMCertificatesMock; +use SimpleSAML\XMLSecurity\Utils\XPath; use SimpleSAML\XMLSecurity\XML\ds\X509IssuerSerial; use SimpleSAML\XMLSecurity\XML\ds\X509IssuerName; use SimpleSAML\XMLSecurity\XML\ds\X509SerialNumber; @@ -80,11 +80,13 @@ public function testMarshallingElementOrdering(): void $X509IssuerSerial = new X509IssuerSerial($this->issuer, $this->serial); $X509IssuerSerialElement = $X509IssuerSerial->toXML(); - $issuerName = XMLUtils::xpQuery($X509IssuerSerialElement, './ds:X509IssuerName'); + $xpCache = XPath::getXPath($X509IssuerSerialElement); + + $issuerName = XPath::xpQuery($X509IssuerSerialElement, './ds:X509IssuerName', $xpCache); $this->assertCount(1, $issuerName); /** @psalm-var \DOMElement[] $X509IssuerSerialElements */ - $X509IssuerSerialElements = XMLUtils::xpQuery($X509IssuerSerialElement, './ds:X509IssuerName/following-sibling::*'); + $X509IssuerSerialElements = XPath::xpQuery($X509IssuerSerialElement, './ds:X509IssuerName/following-sibling::*', $xpCache); // Test ordering of X509IssuerSerial contents $this->assertCount(1, $X509IssuerSerialElements); diff --git a/tests/XML/xenc/EncryptedKeyTest.php b/tests/XML/xenc/EncryptedKeyTest.php index f166ad9f..c6a2291f 100644 --- a/tests/XML/xenc/EncryptedKeyTest.php +++ b/tests/XML/xenc/EncryptedKeyTest.php @@ -9,7 +9,7 @@ use SimpleSAML\Test\XML\SerializableXMLTestTrait; use SimpleSAML\XML\Chunk; use SimpleSAML\XML\DOMDocumentFactory; -use SimpleSAML\XML\Utils as XMLUtils; +use SimpleSAML\XMLSecurity\Utils\XPath; use SimpleSAML\XMLSecurity\XML\ds\KeyInfo; use SimpleSAML\XMLSecurity\XML\xenc\CipherData; use SimpleSAML\XMLSecurity\XML\xenc\DataReference; @@ -119,14 +119,20 @@ public function testMarshallingElementOrdering(): void // Marshall it to a \DOMElement $encryptedKeyElement = $encryptedKey->toXML(); + $xpCache = XPath::getXPath($encryptedKeyElement); // Test for a ReferenceList - $encryptedKeyElements = XMLUtils::xpQuery($encryptedKeyElement, './xenc:ReferenceList'); + $encryptedKeyElements = XPath::xpQuery( + $encryptedKeyElement, + './xenc:ReferenceList', + $xpCache + ); $this->assertCount(1, $encryptedKeyElements); // Test ordering of EncryptedKey contents - $encryptedKeyElements = XMLUtils::xpQuery( + $encryptedKeyElements = XPath::xpQuery( $encryptedKeyElement, - './xenc:ReferenceList/following-sibling::*' + './xenc:ReferenceList/following-sibling::*', + $xpCache ); $this->assertCount(1, $encryptedKeyElements); $this->assertEquals('xenc:CarriedKeyName', $encryptedKeyElements[0]->tagName); From e1532e4da3e4d5d722e64075cd029edc5a3af02c Mon Sep 17 00:00:00 2001 From: Tim van Dijen Date: Wed, 21 Jul 2021 21:19:48 +0200 Subject: [PATCH 41/78] Unrelated; cleanup --- tests/XML/ds/DigestValueTest.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/XML/ds/DigestValueTest.php b/tests/XML/ds/DigestValueTest.php index 55594473..c06296a0 100644 --- a/tests/XML/ds/DigestValueTest.php +++ b/tests/XML/ds/DigestValueTest.php @@ -11,7 +11,6 @@ use SimpleSAML\XML\DOMDocumentFactory; use SimpleSAML\XMLSecurity\Test\XML\XMLDumper; use SimpleSAML\XMLSecurity\XML\ds\DigestValue; -use SimpleSAML\XMLSecurity\XMLSecurityDSig; use function dirname; use function strval; @@ -58,7 +57,7 @@ public function testMarshalling(): void public function testMarshallingNotBase64(): void { $this->expectException(AssertionFailedException::class); - $digestValue = new DigestValue('/CTj3d1DB5e2t7CTo9BEzCf5S9NRzwnBgZRlm32REI='); + new DigestValue('/CTj3d1DB5e2t7CTo9BEzCf5S9NRzwnBgZRlm32REI='); } From cf47709aa498a9f6f23e73b51273987a52d0b494 Mon Sep 17 00:00:00 2001 From: Tim van Dijen Date: Wed, 21 Jul 2021 21:42:41 +0200 Subject: [PATCH 42/78] Add CipherValue XML-class --- src/XML/xenc/CipherValue.php | 26 +++++++++ tests/XML/xenc/CipherValueTest.php | 69 ++++++++++++++++++++++++ tests/resources/xml/xenc_CipherValue.xml | 4 +- 3 files changed, 98 insertions(+), 1 deletion(-) create mode 100644 src/XML/xenc/CipherValue.php create mode 100644 tests/XML/xenc/CipherValueTest.php diff --git a/src/XML/xenc/CipherValue.php b/src/XML/xenc/CipherValue.php new file mode 100644 index 00000000..4593d61c --- /dev/null +++ b/src/XML/xenc/CipherValue.php @@ -0,0 +1,26 @@ +setContent($content); + } +} diff --git a/tests/XML/xenc/CipherValueTest.php b/tests/XML/xenc/CipherValueTest.php new file mode 100644 index 00000000..03387951 --- /dev/null +++ b/tests/XML/xenc/CipherValueTest.php @@ -0,0 +1,69 @@ +testedClass = CipherValue::class; + + $this->xmlRepresentation = DOMDocumentFactory::fromFile( + dirname(dirname(dirname(dirname(__FILE__)))) . '/tests/resources/xml/xenc_CipherValue.xml' + ); + } + + + /** + */ + public function testMarshalling(): void + { + $cipherValue = new CipherValue('/CTj03d1DB5e2t7CTo9BEzCf5S9NRzwnBgZRlm32REI='); + + $this->assertEquals( + XMLDumper::dumpDOMDocumentXMLWithBase64Content($this->xmlRepresentation), + strval($cipherValue) + ); + } + + + /** + */ + public function testMarshallingNotBase64(): void + { + $this->expectException(AssertionFailedException::class); + new CipherValue('/CTj3d1DB5e2t7CTo9BEzCf5S9NRzwnBgZRlm32REI='); + } + + + /** + */ + public function testUnmarshalling(): void + { + $cipherValue = CipherValue::fromXML($this->xmlRepresentation->documentElement); + + $this->assertEquals('/CTj03d1DB5e2t7CTo9BEzCf5S9NRzwnBgZRlm32REI=', $cipherValue->getContent()); + } +} diff --git a/tests/resources/xml/xenc_CipherValue.xml b/tests/resources/xml/xenc_CipherValue.xml index 4b2f5b24..5c4feb4c 100644 --- a/tests/resources/xml/xenc_CipherValue.xml +++ b/tests/resources/xml/xenc_CipherValue.xml @@ -1 +1,3 @@ -c29tZSB0ZXh0 + +/CTj03d1DB5e2t7CTo9BEzCf5S9NRzwnBgZRlm32REI= + From cc999180a3f9af4490699cc08d5fe6717d44fa08 Mon Sep 17 00:00:00 2001 From: Tim van Dijen Date: Wed, 21 Jul 2021 21:44:21 +0200 Subject: [PATCH 43/78] Implement CipherValue XML element --- src/XML/xenc/CipherData.php | 35 ++++++++++++---------- tests/XML/xenc/CipherDataTest.php | 5 ++-- tests/XML/xenc/EncryptedDataTest.php | 7 +++-- tests/XML/xenc/EncryptedKeyTest.php | 13 ++++---- tests/XML/xenc/EncryptionMethodTest.php | 8 +++-- tests/resources/xml/xenc_EncryptedData.xml | 4 +-- tests/resources/xml/xenc_EncryptedKey.xml | 4 +-- 7 files changed, 42 insertions(+), 34 deletions(-) diff --git a/src/XML/xenc/CipherData.php b/src/XML/xenc/CipherData.php index 66c4d7ff..646f9e76 100644 --- a/src/XML/xenc/CipherData.php +++ b/src/XML/xenc/CipherData.php @@ -7,7 +7,7 @@ use DOMElement; use SimpleSAML\Assert\Assert; use SimpleSAML\XML\Exception\InvalidDOMElementException; -use SimpleSAML\XML\Utils as XMLUtils; +use SimpleSAML\XMLSecurity\Utils\XPath; use function array_pop; @@ -18,8 +18,8 @@ */ class CipherData extends AbstractXencElement { - /** @var string|null */ - protected ?string $cipherValue = null; + /** @var \SimpleSAML\XMLSecurity\xenc\CipherValue|null */ + protected ?CipherValue $cipherValue = null; /** @var \SimpleSAML\XMLSecurity\XML\xenc\CipherReference|null */ protected ?CipherReference $cipherReference = null; @@ -28,10 +28,10 @@ class CipherData extends AbstractXencElement /** * CipherData constructor. * - * @param string|null $cipherValue + * @param \SimpleSAML\XMLSecurity\XML\xenc\CipherValue|null $cipherValue * @param \SimpleSAML\XMLSecurity\XML\xenc\CipherReference|null $cipherReference */ - public function __construct(?string $cipherValue, ?CipherReference $cipherReference = null) + public function __construct(?CipherValue $cipherValue, ?CipherReference $cipherReference = null) { Assert::oneOf( null, @@ -39,28 +39,32 @@ public function __construct(?string $cipherValue, ?CipherReference $cipherRefere 'Can only have one of CipherValue/CipherReference' ); + Assert::false( + is_null($cipherValue) && is_null($cipherReference), + 'You need either a CipherValue or a CipherReference' + ); + $this->setCipherValue($cipherValue); $this->setCipherReference($cipherReference); } /** - * Get the string value of the element inside this CipherData object. + * Get the value of the $cipherValue property. * - * @return string|null + * @return \SimpleSAML\XMLSecurity\XML\xenc\CipherValue|null */ - public function getCipherValue(): ?string + public function getCipherValue(): ?CipherValue { return $this->cipherValue; } /** - * @param string|null $cipherValue + * @param \SimpleSAML\XMLSecurity\XML\xenc\CipherValue|null $cipherValue */ - protected function setCipherValue(?string $cipherValue): void + protected function setCipherValue(?CipherValue $cipherValue): void { - Assert::nullOrRegex($cipherValue, '/[a-zA-Z0-9_\-=\+\/]/', 'Invalid data in .'); $this->cipherValue = $cipherValue; } @@ -96,15 +100,14 @@ public static function fromXML(DOMElement $xml): object Assert::same($xml->localName, 'CipherData', InvalidDOMElementException::class); Assert::same($xml->namespaceURI, CipherData::NS, InvalidDOMElementException::class); - $cv = XMLUtils::xpQuery($xml, './xenc:CipherValue'); - Assert::notEmpty($cv, 'Missing CipherValue element in '); - Assert::count($cv, 1, 'More than one CipherValue element in textContent, + empty($cv) ? null : array_pop($cv), empty($cr) ? null : array_pop($cr) ); } @@ -119,7 +122,7 @@ public function toXML(DOMElement $parent = null): DOMElement $e = $this->instantiateParentElement($parent); if ($this->cipherValue !== null) { - XMLUtils::addString($e, $this::NS, 'CipherValue', $this->cipherValue); + $this->cipherValue->toXML($e); } if ($this->cipherReference !== null) { diff --git a/tests/XML/xenc/CipherDataTest.php b/tests/XML/xenc/CipherDataTest.php index c14e4307..7bacc9f8 100644 --- a/tests/XML/xenc/CipherDataTest.php +++ b/tests/XML/xenc/CipherDataTest.php @@ -10,6 +10,7 @@ use SimpleSAML\XML\Chunk; use SimpleSAML\XML\DOMDocumentFactory; use SimpleSAML\XMLSecurity\XML\xenc\CipherData; +use SimpleSAML\XMLSecurity\XML\xenc\CipherValue; use SimpleSAML\XMLSecurity\XMLSecurityDsig; use function dirname; @@ -46,7 +47,7 @@ public function setup(): void */ public function testMarshalling(): void { - $cipherData = new CipherData('c29tZSB0ZXh0'); + $cipherData = new CipherData(new CipherValue('c29tZSB0ZXh0')); $this->assertEquals( $this->xmlRepresentation->saveXML($this->xmlRepresentation->documentElement), @@ -64,6 +65,6 @@ public function testUnmarshalling(): void { $cipherData = CipherData::fromXML($this->xmlRepresentation->documentElement); - $this->assertEquals('c29tZSB0ZXh0', $cipherData->getCipherValue()); + $this->assertEquals('c29tZSB0ZXh0', $cipherData->getCipherValue()->getContent()); } } diff --git a/tests/XML/xenc/EncryptedDataTest.php b/tests/XML/xenc/EncryptedDataTest.php index c96a3e0f..63fcd8d6 100644 --- a/tests/XML/xenc/EncryptedDataTest.php +++ b/tests/XML/xenc/EncryptedDataTest.php @@ -11,6 +11,7 @@ use SimpleSAML\XML\DOMDocumentFactory; use SimpleSAML\XMLSecurity\XML\ds\KeyInfo; use SimpleSAML\XMLSecurity\XML\xenc\CipherData; +use SimpleSAML\XMLSecurity\XML\xenc\CipherValue; use SimpleSAML\XMLSecurity\XML\xenc\EncryptedData; use SimpleSAML\XMLSecurity\XML\xenc\EncryptedKey; use SimpleSAML\XMLSecurity\XML\xenc\EncryptionMethod; @@ -52,7 +53,7 @@ public function setup(): void public function testMarshalling(): void { $encryptedData = new EncryptedData( - new CipherData('iaDc7...'), + new CipherData(new CipherValue('/CTj03d1DB5e2t7CTo9BEzCf5S9NRzwnBgZRlm32REI=')), 'MyID', 'http://www.w3.org/2001/04/xmlenc#Element', 'text/plain', @@ -61,7 +62,7 @@ public function testMarshalling(): void new KeyInfo( [ new EncryptedKey( - new CipherData('nxf0b...'), + new CipherData(new CipherValue('/CTj03d1DB5e2t7CTo9BEzCf5S9NRzwnBgZRlm32REI=')), null, null, null, @@ -91,7 +92,7 @@ public function testUnmarshalling(): void $encryptedData = EncryptedData::fromXML($this->xmlRepresentation->documentElement); $cipherData = $encryptedData->getCipherData(); - $this->assertEquals('iaDc7...', $cipherData->getCipherValue()); + $this->assertEquals('/CTj03d1DB5e2t7CTo9BEzCf5S9NRzwnBgZRlm32REI=', $cipherData->getCipherValue()->getContent()); $encryptionMethod = $encryptedData->getEncryptionMethod(); $this->assertEquals('http://www.w3.org/2001/04/xmlenc#aes128-cbc', $encryptionMethod->getAlgorithm()); diff --git a/tests/XML/xenc/EncryptedKeyTest.php b/tests/XML/xenc/EncryptedKeyTest.php index c6a2291f..4cce4657 100644 --- a/tests/XML/xenc/EncryptedKeyTest.php +++ b/tests/XML/xenc/EncryptedKeyTest.php @@ -12,6 +12,7 @@ use SimpleSAML\XMLSecurity\Utils\XPath; use SimpleSAML\XMLSecurity\XML\ds\KeyInfo; use SimpleSAML\XMLSecurity\XML\xenc\CipherData; +use SimpleSAML\XMLSecurity\XML\xenc\CipherValue; use SimpleSAML\XMLSecurity\XML\xenc\DataReference; use SimpleSAML\XMLSecurity\XML\xenc\EncryptedKey; use SimpleSAML\XMLSecurity\XML\xenc\EncryptionMethod; @@ -50,11 +51,11 @@ public function setup(): void /** - */ +- */ public function testMarshalling(): void { $encryptedKey = new EncryptedKey( - new CipherData('PzA5X...'), + new CipherData(new CipherValue('/CTj03d1DB5e2t7CTo9BEzCf5S9NRzwnBgZRlm32REI=')), 'Encrypted_KEY_ID', 'http://www.w3.org/2001/04/xmlenc#Element', 'text/plain', @@ -65,7 +66,7 @@ public function testMarshalling(): void new KeyInfo( [ new EncryptedKey( - new CipherData('nxf0b...'), + new CipherData(new CipherValue('/CTj03d1DB5e2t7CTo9BEzCf5S9NRzwnBgZRlm32REI=')), null, null, null, @@ -91,7 +92,7 @@ public function testMarshalling(): void public function testMarshallingElementOrdering(): void { $encryptedKey = new EncryptedKey( - new CipherData('PzA5X...'), + new CipherData(new CipherValue('/CTj03d1DB5e2t7CTo9BEzCf5S9NRzwnBgZRlm32REI=')), 'Encrypted_KEY_ID', 'http://www.w3.org/2001/04/xmlenc#Element', 'text/plain', @@ -102,7 +103,7 @@ public function testMarshallingElementOrdering(): void new KeyInfo( [ new EncryptedKey( - new CipherData('nxf0b...'), + new CipherData(new CipherValue('/CTj03d1DB5e2t7CTo9BEzCf5S9NRzwnBgZRlm32REI=')), null, null, null, @@ -149,7 +150,7 @@ public function testUnmarshalling(): void $encryptedKey = EncryptedKey::fromXML($this->xmlRepresentation->documentElement); $cipherData = $encryptedKey->getCipherData(); - $this->assertEquals('PzA5X...', $cipherData->getCipherValue()); + $this->assertEquals('/CTj03d1DB5e2t7CTo9BEzCf5S9NRzwnBgZRlm32REI=', $cipherData->getCipherValue()->getContent()); $encryptionMethod = $encryptedKey->getEncryptionMethod(); $this->assertEquals('http://www.w3.org/2001/04/xmlenc#rsa-1_5', $encryptionMethod->getAlgorithm()); diff --git a/tests/XML/xenc/EncryptionMethodTest.php b/tests/XML/xenc/EncryptionMethodTest.php index 61d33753..e55076d9 100644 --- a/tests/XML/xenc/EncryptionMethodTest.php +++ b/tests/XML/xenc/EncryptionMethodTest.php @@ -9,7 +9,7 @@ use SimpleSAML\XML\Chunk; use SimpleSAML\XML\DOMDocumentFactory; use SimpleSAML\XML\Exception\MissingAttributeException; -use SimpleSAML\XML\Utils as XMLUtils; +use SimpleSAML\XMLSecurity\Utils\XPath; use SimpleSAML\XMLSecurity\XML\xenc\EncryptionMethod; use SimpleSAML\XMLSecurity\Constants; @@ -93,13 +93,15 @@ public function testMarshallingElementOrdering(): void // Marshall it to a \DOMElement $emElement = $em->toXML(); + $xpCache = XPath::getXPath($emElement); + // Test for a KeySize - $keySizeElements = XMLUtils::xpQuery($emElement, './xenc:KeySize'); + $keySizeElements = XPath::xpQuery($emElement, './xenc:KeySize', $xpCache); $this->assertCount(1, $keySizeElements); $this->assertEquals('10', $keySizeElements[0]->textContent); // Test ordering of EncryptionMethod contents - $emElements = XMLUtils::xpQuery($emElement, './xenc:KeySize/following-sibling::*'); + $emElements = XPath::xpQuery($emElement, './xenc:KeySize/following-sibling::*', $xpCache); $this->assertCount(2, $emElements); $this->assertEquals('xenc:OAEPParams', $emElements[0]->tagName); diff --git a/tests/resources/xml/xenc_EncryptedData.xml b/tests/resources/xml/xenc_EncryptedData.xml index 5aeddf59..e6ac54e2 100644 --- a/tests/resources/xml/xenc_EncryptedData.xml +++ b/tests/resources/xml/xenc_EncryptedData.xml @@ -4,12 +4,12 @@ - nxf0b... + /CTj03d1DB5e2t7CTo9BEzCf5S9NRzwnBgZRlm32REI= - iaDc7... + /CTj03d1DB5e2t7CTo9BEzCf5S9NRzwnBgZRlm32REI= diff --git a/tests/resources/xml/xenc_EncryptedKey.xml b/tests/resources/xml/xenc_EncryptedKey.xml index 59101674..893a469b 100644 --- a/tests/resources/xml/xenc_EncryptedKey.xml +++ b/tests/resources/xml/xenc_EncryptedKey.xml @@ -4,12 +4,12 @@ - nxf0b... + /CTj03d1DB5e2t7CTo9BEzCf5S9NRzwnBgZRlm32REI= - PzA5X... + /CTj03d1DB5e2t7CTo9BEzCf5S9NRzwnBgZRlm32REI= From a0c08973898981c64eb765e91a04572433dd6e5f Mon Sep 17 00:00:00 2001 From: Tim van Dijen Date: Wed, 21 Jul 2021 21:52:25 +0200 Subject: [PATCH 44/78] Add CarriedKeyName XML-class --- src/XML/xenc/CarriedKeyName.php | 26 ++++++++++ tests/XML/xenc/CarriedKeyNameTest.php | 57 +++++++++++++++++++++ tests/resources/xml/xenc_CarriedKeyName.xml | 1 + 3 files changed, 84 insertions(+) create mode 100644 src/XML/xenc/CarriedKeyName.php create mode 100644 tests/XML/xenc/CarriedKeyNameTest.php create mode 100644 tests/resources/xml/xenc_CarriedKeyName.xml diff --git a/src/XML/xenc/CarriedKeyName.php b/src/XML/xenc/CarriedKeyName.php new file mode 100644 index 00000000..e1f971d5 --- /dev/null +++ b/src/XML/xenc/CarriedKeyName.php @@ -0,0 +1,26 @@ +setContent($content); + } +} diff --git a/tests/XML/xenc/CarriedKeyNameTest.php b/tests/XML/xenc/CarriedKeyNameTest.php new file mode 100644 index 00000000..4350334f --- /dev/null +++ b/tests/XML/xenc/CarriedKeyNameTest.php @@ -0,0 +1,57 @@ +testedClass = CarriedKeyName::class; + + $this->xmlRepresentation = DOMDocumentFactory::fromFile( + dirname(dirname(dirname(dirname(__FILE__)))) . '/tests/resources/xml/xenc_CarriedKeyName.xml' + ); + } + + + /** + */ + public function testMarshalling(): void + { + $keyName = new CarriedKeyName('Some label'); + + $this->assertEquals( + $this->xmlRepresentation->saveXML($this->xmlRepresentation->documentElement), + strval($keyName) + ); + } + + + /** + */ + public function testUnmarshalling(): void + { + $keyName = CarriedKeyName::fromXML($this->xmlRepresentation->documentElement); + + $this->assertEquals('Some label', $keyName->getContent()); + } +} diff --git a/tests/resources/xml/xenc_CarriedKeyName.xml b/tests/resources/xml/xenc_CarriedKeyName.xml new file mode 100644 index 00000000..83e3ebcb --- /dev/null +++ b/tests/resources/xml/xenc_CarriedKeyName.xml @@ -0,0 +1 @@ +Some label From 15d792421ced4f86c53cb638e04f8e4de365131d Mon Sep 17 00:00:00 2001 From: Tim van Dijen Date: Wed, 21 Jul 2021 22:02:01 +0200 Subject: [PATCH 45/78] Implement CarriedKeyName XML-class --- src/XML/xenc/EncryptedKey.php | 33 ++++++++++++++--------------- tests/XML/xenc/EncryptedKeyTest.php | 7 +++--- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/XML/xenc/EncryptedKey.php b/src/XML/xenc/EncryptedKey.php index 4cc8a491..94244528 100644 --- a/src/XML/xenc/EncryptedKey.php +++ b/src/XML/xenc/EncryptedKey.php @@ -19,8 +19,8 @@ */ class EncryptedKey extends AbstractEncryptedType { - /** @var string|null */ - protected ?string $carriedKeyName; + /** @var \SimpleSAML\XMLSecurity\XML\xenc\CarriedKeyName|null */ + protected ?CarriedKeyName $carriedKeyName; /** @var string|null */ protected ?string $recipient; @@ -38,7 +38,8 @@ class EncryptedKey extends AbstractEncryptedType * @param string|null $mimeType The MimeType attribute of this object. Optional. * @param string|null $encoding The Encoding attribute of this object. Optional. * @param string|null $recipient The Recipient attribute of this object. Optional. - * @param string|null $carriedKeyName The value of the CarriedKeyName element of this EncryptedData. + * @param \SimpleSAML\XMLSecurity\XML\xenc\CarriedKeyName|null $carriedKeyName + * The value of the CarriedKeyName element of this EncryptedData. * @param \SimpleSAML\XMLSecurity\XML\xenc\EncryptionMethod|null $encryptionMethod * The EncryptionMethod object of this EncryptedData. Optional. * @param \SimpleSAML\XMLSecurity\XML\ds\KeyInfo|null $keyInfo The KeyInfo object of this EncryptedData. Optional. @@ -52,7 +53,7 @@ public function __construct( ?string $mimeType = null, ?string $encoding = null, ?string $recipient = null, - ?string $carriedKeyName = null, + ?CarriedKeyName $carriedKeyName = null, ?EncryptionMethod $encryptionMethod = null, ?KeyInfo $keyInfo = null, ?ReferenceList $referenceList = null @@ -65,20 +66,20 @@ public function __construct( /** - * Get the value of the CarriedKeyName element. + * Get the value of the CarriedKeyName property. * - * @return string|null + * @return \SimpleSAML\XMLSecurity\XML\xenc\CarriedKeyName|null */ - public function getCarriedKeyName(): ?string + public function getCarriedKeyName(): ?CarriedKeyName { return $this->carriedKeyName; } /** - * @param string|null $carriedKeyName + * @param \SimpleSAML\XMLSecurity\XML\xenc\CarriedKeyName|null $carriedKeyName */ - protected function setCarriedKeyName(?string $carriedKeyName): void + protected function setCarriedKeyName(?CarriedKeyName $carriedKeyName): void { $this->carriedKeyName = $carriedKeyName; } @@ -151,7 +152,7 @@ public static function fromXML(DOMElement $xml): object $referenceLists = ReferenceList::getChildrenOfClass($xml); Assert::maxCount($keyInfo, 1, 'Only one ReferenceList element allowed in .'); - $carriedKeyNames = XMLUtils::xpQuery($xml, './xenc:CarriedKeyName'); + $carriedKeyNames = CarriedKeyName::getChildrenOfClass($xml); Assert::maxCount($carriedKeyNames, 1, 'Only one CarriedKeyName element allowed in .'); return new self( @@ -161,10 +162,10 @@ public static function fromXML(DOMElement $xml): object self::getAttribute($xml, 'MimeType', null), self::getAttribute($xml, 'Encoding', null), self::getAttribute($xml, 'Recipient', null), - count($carriedKeyNames) === 1 ? $carriedKeyNames[0]->textContent : null, - count($encryptionMethod) === 1 ? $encryptionMethod[0] : null, - count($keyInfo) === 1 ? $keyInfo[0] : null, - count($referenceLists) === 1 ? $referenceLists[0] : null + array_pop($carriedKeyNames), + array_pop($encryptionMethod), + array_pop($keyInfo), + array_pop($referenceLists) ); } @@ -182,9 +183,7 @@ public function toXML(DOMElement $parent = null): DOMElement } if ($this->carriedKeyName !== null) { - $ckn = $e->ownerDocument->createElementNS(self::NS, self::NS_PREFIX . ':CarriedKeyName'); - $ckn->textContent = $this->carriedKeyName; - $e->appendChild($ckn); + $this->carriedKeyName->toXML($e); } if ($this->recipient !== null) { diff --git a/tests/XML/xenc/EncryptedKeyTest.php b/tests/XML/xenc/EncryptedKeyTest.php index 4cce4657..b7eba3aa 100644 --- a/tests/XML/xenc/EncryptedKeyTest.php +++ b/tests/XML/xenc/EncryptedKeyTest.php @@ -11,6 +11,7 @@ use SimpleSAML\XML\DOMDocumentFactory; use SimpleSAML\XMLSecurity\Utils\XPath; use SimpleSAML\XMLSecurity\XML\ds\KeyInfo; +use SimpleSAML\XMLSecurity\XML\xenc\CarriedKeyName; use SimpleSAML\XMLSecurity\XML\xenc\CipherData; use SimpleSAML\XMLSecurity\XML\xenc\CipherValue; use SimpleSAML\XMLSecurity\XML\xenc\DataReference; @@ -61,7 +62,7 @@ public function testMarshalling(): void 'text/plain', 'someEncoding', 'some_ENTITY_ID', - 'Name of the key', + new CarriedKeyName('Name of the key'), new EncryptionMethod('http://www.w3.org/2001/04/xmlenc#rsa-1_5'), new KeyInfo( [ @@ -98,7 +99,7 @@ public function testMarshallingElementOrdering(): void 'text/plain', 'someEncoding', 'some_ENTITY_ID', - 'Name of the key', + new CarriedKeyName('Name of the key'), new EncryptionMethod('http://www.w3.org/2001/04/xmlenc#rsa-1_5'), new KeyInfo( [ @@ -173,7 +174,7 @@ public function testUnmarshalling(): void $this->assertEquals('text/plain', $encryptedKey->getMimeType()); $this->assertEquals('Encrypted_KEY_ID', $encryptedKey->getID()); $this->assertEquals('some_ENTITY_ID', $encryptedKey->getRecipient()); - $this->assertEquals('Name of the key', $encryptedKey->getCarriedKeyName()); + $this->assertEquals('Name of the key', $encryptedKey->getCarriedKeyName()->getContent()); $this->assertEquals( $this->xmlRepresentation->saveXML($this->xmlRepresentation->documentElement), From db733059c78053479978c4f480078090593f4d95 Mon Sep 17 00:00:00 2001 From: Tim van Dijen Date: Wed, 21 Jul 2021 22:19:50 +0200 Subject: [PATCH 46/78] Add missing @covers Raise coverage --- tests/XML/ec/InclusiveNamespacesTest.php | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/XML/ec/InclusiveNamespacesTest.php b/tests/XML/ec/InclusiveNamespacesTest.php index 642e2eb7..746a747f 100644 --- a/tests/XML/ec/InclusiveNamespacesTest.php +++ b/tests/XML/ec/InclusiveNamespacesTest.php @@ -16,6 +16,7 @@ * Class \SimpleSAML\XMLSecurity\Test\XML\ec\InclusiveNamespacesTest * * @covers \SimpleSAML\XMLSecurity\XML\ec\InclusiveNamespaces + * @covers \SimpleSAML\XMLSecurity\XML\ec\AbstractEcElement * * @package simplesamlphp/xml-security */ From 4fa1e79aaacae6758098d76947e2ff358ec84f4c Mon Sep 17 00:00:00 2001 From: Tim van Dijen Date: Wed, 21 Jul 2021 22:36:10 +0200 Subject: [PATCH 47/78] Remove overhead --- src/XML/ec/AbstractEcElement.php | 24 +----------------------- 1 file changed, 1 insertion(+), 23 deletions(-) diff --git a/src/XML/ec/AbstractEcElement.php b/src/XML/ec/AbstractEcElement.php index 5e1fa352..80d803b7 100644 --- a/src/XML/ec/AbstractEcElement.php +++ b/src/XML/ec/AbstractEcElement.php @@ -20,26 +20,4 @@ abstract class AbstractEcElement extends AbstractXMLElement /** @var string */ public const NS_PREFIX = 'ec'; - - - /** - * Get the namespace for the element. - * - * @return string - */ - public static function getNamespaceURI(): string - { - return static::NS; - } - - - /** - * Get the namespace prefix for the element. - * - * @return string - */ - public static function getNamespacePrefix(): string - { - return static::NS_PREFIX; - } -} \ No newline at end of file +} From 753866ed5040c0a6f15b9b847cec7e0af8f7c271 Mon Sep 17 00:00:00 2001 From: Tim van Dijen Date: Wed, 21 Jul 2021 22:50:30 +0200 Subject: [PATCH 48/78] Start cleaning up old code with hooks to xmlseclibs --- src/Utils/Security.php | 425 ------------------------------- src/XML/ds/AbstractDsElement.php | 4 +- src/XMLSecurityDSig.php | 49 ---- 3 files changed, 2 insertions(+), 476 deletions(-) delete mode 100644 src/XMLSecurityDSig.php diff --git a/src/Utils/Security.php b/src/Utils/Security.php index ea69406d..eb0c96eb 100644 --- a/src/Utils/Security.php +++ b/src/Utils/Security.php @@ -4,19 +4,8 @@ namespace SimpleSAML\XMLSecurity\Utils; -use DOMDocument; -use DOMElement; -use DOMNode; -use Exception; -use RuntimeException; -use SimpleSAML\Assert\Assert; -use SimpleSAML\XML\DOMDocumentFactory; -use SimpleSAML\XML\Utils as XMLUtils; use SimpleSAML\XMLSecurity\Constants; use SimpleSAML\XMLSecurity\Exception\InvalidArgumentException; -use SimpleSAML\XMLSecurity\XMLSecEnc; -use SimpleSAML\XMLSecurity\XMLSecurityDSig; -use SimpleSAML\XMLSecurity\XMLSecurityKey; use function count; use function hash_equals; @@ -80,418 +69,4 @@ public static function hash(string $alg, string $data, bool $encode = true): str } return $digest; } - - - - /** - * Check the Signature in a XML element. - * - * This function expects the XML element to contain a Signature element - * which contains a reference to the XML-element. This is common for both - * messages and assertions. - * - * Note that this function only validates the element itself. It does not - * check this against any local keys. - * - * If no Signature-element is located, this function will return false. All - * other validation errors result in an exception. On successful validation - * an array will be returned. This array contains the information required to - * check the signature against a public key. - * - * @param \DOMElement $root The element which should be validated. - * @throws \Exception - * @return array|false An array with information about the Signature element. - */ - public static function validateElement(DOMElement $root) - { - /* Create an XML security object. */ - $objXMLSecDSig = new XMLSecurityDSig(); - - /* Both SAML messages and SAML assertions use the 'ID' attribute. */ - $objXMLSecDSig->idKeys[] = 'ID'; - - /* Locate the XMLDSig Signature element to be used. */ - /** @var \DOMElement[] $signatureElement */ - $signatureElement = XMLUtils::xpQuery($root, './ds:Signature'); - if (empty($signatureElement)) { - /* We don't have a signature element ot validate. */ - - return false; - } elseif (count($signatureElement) > 1) { - throw new Exception('XMLSec: more than one signature element in root.'); - } - $signatureElement = $signatureElement[0]; - $objXMLSecDSig->sigNode = $signatureElement; - - /* Canonicalize the XMLDSig SignedInfo element in the message. */ - $objXMLSecDSig->canonicalizeSignedInfo(); - - /* Validate referenced xml nodes. */ - if (!$objXMLSecDSig->validateReference()) { - throw new Exception('XMLsec: digest validation failed'); - } - - /* Check that $root is one of the signed nodes. */ - $rootSigned = false; - /** @var \DOMNode $signedNode */ - foreach ($objXMLSecDSig->getValidatedNodes() as $signedNode) { - if ($signedNode->isSameNode($root)) { - $rootSigned = true; - break; - } elseif ($root->parentNode instanceof DOMDocument && $signedNode->isSameNode($root->ownerDocument)) { - /* $root is the root element of a signed document. */ - $rootSigned = true; - break; - } - } - if (!$rootSigned) { - throw new Exception('XMLSec: The root element is not signed.'); - } - - /* Now we extract all available X509 certificates in the signature element. */ - $certificates = []; - foreach (XMLUtils::xpQuery($signatureElement, './ds:KeyInfo/ds:X509Data/ds:X509Certificate') as $certNode) { - $certData = trim($certNode->textContent); - $certData = str_replace(["\r", "\n", "\t", ' '], '', $certData); - $certificates[] = $certData; - } - - $ret = [ - 'Signature' => $objXMLSecDSig, - 'Certificates' => $certificates, - ]; - - return $ret; - } - - - /** - * Helper function to convert a XMLSecurityKey to the correct algorithm. - * - * @param \SimpleSAML\XMLSecurity\XMLSecurityKey $key The key. - * @param string $algorithm The desired algorithm. - * @param string $type Public or private key, defaults to public. - * @return \SimpleSAML\XMLSecurity\XMLSecurityKey The new key. - * - * @throws \SimpleSAML\Assert\AssertionFailedException if assertions are false - */ - public static function castKey(XMLSecurityKey $key, string $algorithm, string $type = null): XMLSecurityKey - { - $type = $type ?: 'public'; - Assert::oneOf($type, ["private", "public"]); - - // do nothing if algorithm is already the type of the key - if ($key->type === $algorithm) { - return $key; - } - - if ( - !in_array( - $algorithm, - [ - XMLSecurityKey::RSA_1_5, - XMLSecurityKey::RSA_SHA1, - XMLSecurityKey::RSA_SHA256, - XMLSecurityKey::RSA_SHA384, - XMLSecurityKey::RSA_SHA512 - ], - true - ) - ) { - throw new Exception('Unsupported signing algorithm.'); - } - - /** @psalm-suppress PossiblyNullArgument */ - $keyInfo = openssl_pkey_get_details($key->key); - if ($keyInfo === false) { - throw new Exception('Unable to get key details from XMLSecurityKey.'); - } - if (!isset($keyInfo['key'])) { - throw new Exception('Missing key in public key details.'); - } - - $newKey = new XMLSecurityKey($algorithm, ['type' => $type]); - $newKey->loadKey($keyInfo['key']); - - return $newKey; - } - - - /** - * Check a signature against a key. - * - * An exception is thrown if we are unable to validate the signature. - * - * @param array $info The information returned by the validateElement() function. - * @param \SimpleSAML\XMLSecurity\XMLSecurityKey $key The publickey that should validate the Signature object. - * @throws \Exception - * - * @throws \SimpleSAML\Assert\AssertionFailedException if assertions are false - */ - public static function validateSignature(array $info, XMLSecurityKey $key): void - { - Assert::keyExists($info, "Signature"); - - /** @var XMLSecurityDSig $objXMLSecDSig */ - $objXMLSecDSig = $info['Signature']; - - /** - * @var \DOMElement[] $sigMethod - * @var \DOMElement $objXMLSecDSig->sigNode - */ - $sigMethod = XMLUtils::xpQuery($objXMLSecDSig->sigNode, './ds:SignedInfo/ds:SignatureMethod'); - if (empty($sigMethod)) { - throw new Exception('Missing SignatureMethod element.'); - } - $sigMethod = $sigMethod[0]; - if (!$sigMethod->hasAttribute('Algorithm')) { - throw new Exception('Missing Algorithm-attribute on SignatureMethod element.'); - } - $algo = $sigMethod->getAttribute('Algorithm'); - - if ($key->type === XMLSecurityKey::RSA_SHA256 && $algo !== $key->type) { - $key = self::castKey($key, $algo); - } - - /* Check the signature. */ - if ($objXMLSecDSig->verify($key) !== 1) { - throw new Exception("Unable to validate Signature; " . openssl_error_string()); - } - } - - - /** - * Insert a Signature node. - * - * @param \SimpleSAML\XMLSecurity\XMLSecurityKey $key The key we should use to sign the message. - * @param array $certificates The certificates we should add to the signature node. - * @param \DOMElement $root The XML node we should sign. - * @param \DOMNode $insertBefore The XML element we should insert the signature element before. - */ - public static function insertSignature( - XMLSecurityKey $key, - array $certificates, - DOMElement $root, - DOMNode $insertBefore = null - ): void { - $objXMLSecDSig = new XMLSecurityDSig(); - $objXMLSecDSig->setCanonicalMethod(XMLSecurityDSig::EXC_C14N); - - switch ($key->type) { - case XMLSecurityKey::RSA_SHA256: - $type = XMLSecurityDSig::SHA256; - break; - case XMLSecurityKey::RSA_SHA384: - $type = XMLSecurityDSig::SHA384; - break; - case XMLSecurityKey::RSA_SHA512: - $type = XMLSecurityDSig::SHA512; - break; - default: - $type = XMLSecurityDSig::SHA1; - } - - $objXMLSecDSig->addReferenceList( - [$root], - $type, - ['http://www.w3.org/2000/09/xmldsig#enveloped-signature', XMLSecurityDSig::EXC_C14N], - ['id_name' => 'ID', 'overwrite' => false] - ); - - $objXMLSecDSig->sign($key); - - foreach ($certificates as $certificate) { - $objXMLSecDSig->add509Cert($certificate, true); - } - - $objXMLSecDSig->insertSignature($root, $insertBefore); - } - - - /** - * Decrypt an encrypted element. - * - * This is an internal helper function. - * - * @param \DOMElement $encryptedData The encrypted data. - * @param \SimpleSAML\XMLSecurity\XMLSecurityKey $inputKey The decryption key. - * @param array &$blacklist Blacklisted decryption algorithms. - * @throws \Exception - * @return \DOMElement The decrypted element. - */ - private static function doDecryptElement( - DOMElement $encryptedData, - XMLSecurityKey $inputKey, - array &$blacklist - ): DOMElement { - $enc = new XMLSecEnc(); - - $enc->setNode($encryptedData); - $enc->type = $encryptedData->getAttribute("Type"); - - $symmetricKey = $enc->locateKey($encryptedData); - if (!$symmetricKey) { - throw new Exception('Could not locate key algorithm in encrypted data.'); - } - - $symmetricKeyInfo = $enc->locateKeyInfo($symmetricKey); - if (!$symmetricKeyInfo) { - throw new Exception('Could not locate for the encrypted key.'); - } - - $inputKeyAlgo = $inputKey->getAlgorithm(); - if ($symmetricKeyInfo->isEncrypted) { - $symKeyInfoAlgo = $symmetricKeyInfo->getAlgorithm(); - - if (in_array($symKeyInfoAlgo, $blacklist, true)) { - throw new Exception('Algorithm disabled: ' . var_export($symKeyInfoAlgo, true)); - } - - if ($symKeyInfoAlgo === XMLSecurityKey::RSA_OAEP_MGF1P && $inputKeyAlgo === XMLSecurityKey::RSA_1_5) { - /* - * The RSA key formats are equal, so loading an RSA_1_5 key - * into an RSA_OAEP_MGF1P key can be done without problems. - * We therefore pretend that the input key is an - * RSA_OAEP_MGF1P key. - */ - $inputKeyAlgo = XMLSecurityKey::RSA_OAEP_MGF1P; - } - - /* Make sure that the input key format is the same as the one used to encrypt the key. */ - if ($inputKeyAlgo !== $symKeyInfoAlgo) { - throw new Exception( - 'Algorithm mismatch between input key and key used to encrypt ' . - ' the symmetric key for the message. Key was: ' . - var_export($inputKeyAlgo, true) . '; message was: ' . - var_export($symKeyInfoAlgo, true) - ); - } - - /** @var XMLSecEnc $encKey */ - $encKey = $symmetricKeyInfo->encryptedCtx; - $symmetricKeyInfo->key = $inputKey->key; - - $keySize = $symmetricKey->getSymmetricKeySize(); - if ($keySize === null) { - /* To protect against "key oracle" attacks, we need to be able to create a - * symmetric key, and for that we need to know the key size. - */ - throw new Exception( - 'Unknown key size for encryption algorithm: ' . var_export($symmetricKey->type, true) - ); - } - - try { - /** - * @var string $key - * @psalm-suppress UndefinedClass - */ - $key = $encKey->decryptKey($symmetricKeyInfo); - if (strlen($key) !== $keySize) { - throw new Exception( - 'Unexpected key size (' . strval(strlen($key) * 8) . 'bits) for encryption algorithm: ' . - var_export($symmetricKey->type, true) - ); - } - } catch (Exception $e) { - /* We failed to decrypt this key. Log it, and substitute a "random" key. */ -// Utils::getContainer()->getLogger()->error('Failed to decrypt symmetric key: ' . $e->getMessage()); - /* Create a replacement key, so that it looks like we fail in the same way as if the key was correctly - * padded. */ - - /* We base the symmetric key on the encrypted key and private key, so that we always behave the - * same way for a given input key. - */ - $encryptedKey = $encKey->getCipherValue(); - if ($encryptedKey === null) { - throw new Exception('No CipherValue available in the encrypted element.'); - } - - /** @psalm-suppress PossiblyNullArgument */ - $pkey = openssl_pkey_get_details($symmetricKeyInfo->key); - $pkey = sha1(serialize($pkey), true); - $key = sha1($encryptedKey . $pkey, true); - - /* Make sure that the key has the correct length. */ - if (strlen($key) > $keySize) { - $key = substr($key, 0, $keySize); - } elseif (strlen($key) < $keySize) { - $key = str_pad($key, $keySize); - } - } - $symmetricKey->loadkey($key); - } else { - $symKeyAlgo = $symmetricKey->getAlgorithm(); - /* Make sure that the input key has the correct format. */ - if ($inputKeyAlgo !== $symKeyAlgo) { - throw new Exception( - 'Algorithm mismatch between input key and key in message. ' . - 'Key was: ' . var_export($inputKeyAlgo, true) . '; message was: ' . - var_export($symKeyAlgo, true) - ); - } - $symmetricKey = $inputKey; - } - - $algorithm = $symmetricKey->getAlgorithm(); - if (in_array($algorithm, $blacklist, true)) { - throw new Exception('Algorithm disabled: ' . var_export($algorithm, true)); - } - - /** - * @var string $decrypted - * @psalm-suppress UndefinedClass - */ - $decrypted = $enc->decryptNode($symmetricKey, false); - - /* - * This is a workaround for the case where only a subset of the XML - * tree was serialized for encryption. In that case, we may miss the - * namespaces needed to parse the XML. - */ - $xml = '' . - $decrypted . ''; - - try { - $newDoc = DOMDocumentFactory::fromString($xml); - } catch (RuntimeException $e) { - throw new Exception('Failed to parse decrypted XML. Maybe the wrong sharedkey was used?', 0, $e); - } - - /** @psalm-suppress PossiblyNullPropertyFetch */ - $decryptedElement = $newDoc->firstChild->firstChild; - if (!($decryptedElement instanceof DOMElement)) { - throw new Exception('Missing decrypted element or it was not actually a DOMElement.'); - } - - return $decryptedElement; - } - - - /** - * Decrypt an encrypted element. - * - * @param \DOMElement $encryptedData The encrypted data. - * @param \SimpleSAML\XMLSecurity\XMLSecurityKey $inputKey The decryption key. - * @param array $blacklist Blacklisted decryption algorithms. - * @throws \Exception - * @return \DOMElement The decrypted element. - */ - public static function decryptElement( - DOMElement $encryptedData, - XMLSecurityKey $inputKey, - array $blacklist = [] - ): DOMElement { - try { - return self::doDecryptElement($encryptedData, $inputKey, $blacklist); - } catch (Exception $e) { - /* - * Something went wrong during decryption, but for security - * reasons we cannot tell the user what failed. - */ -// Utils::getContainer()->getLogger()->error('Decryption failed: ' . $e->getMessage()); - throw new Exception('Failed to decrypt XML element.', 0, $e); - } - } } diff --git a/src/XML/ds/AbstractDsElement.php b/src/XML/ds/AbstractDsElement.php index 4a740195..d3c8e605 100644 --- a/src/XML/ds/AbstractDsElement.php +++ b/src/XML/ds/AbstractDsElement.php @@ -5,7 +5,7 @@ namespace SimpleSAML\XMLSecurity\XML\ds; use SimpleSAML\XML\AbstractXMLElement; -use SimpleSAML\XMLSecurity\XMLSecurityDSig; +use SimpleSAML\XMLSecurity\Constants; /** * Abstract class to be implemented by all the classes in this namespace @@ -15,7 +15,7 @@ abstract class AbstractDsElement extends AbstractXMLElement { /** @var string */ - public const NS = XMLSecurityDSig::XMLDSIGNS; + public const NS = Constants::NS_XDSIG; /** @var string */ public const NS_PREFIX = 'ds'; diff --git a/src/XMLSecurityDSig.php b/src/XMLSecurityDSig.php deleted file mode 100644 index 6646c77c..00000000 --- a/src/XMLSecurityDSig.php +++ /dev/null @@ -1,49 +0,0 @@ -. - * 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. - * - * @copyright 2007-2017 Robert Richards - * @license http://www.opensource.org/licenses/bsd-license.php BSD License - */ - -class XMLSecurityDSig extends \RobRichards\XMLSecLibs\XMLSecurityDSig -{ -} From c184d0767af9d63f0e7c721587adcadf2f3a9f52 Mon Sep 17 00:00:00 2001 From: Tim van Dijen Date: Thu, 22 Jul 2021 09:39:07 +0200 Subject: [PATCH 49/78] Default to empty string; Scrutinizer warns us to do additional type check now, but the default value is never returned anyway --- src/Backend/OpenSSL.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Backend/OpenSSL.php b/src/Backend/OpenSSL.php index ee9a184d..20e9b31c 100644 --- a/src/Backend/OpenSSL.php +++ b/src/Backend/OpenSSL.php @@ -80,7 +80,7 @@ public function encrypt(AbstractKey $key, string $plaintext): string $fn = 'openssl_private_encrypt'; } - $ciphertext = null; + $ciphertext = ''; if (!$fn($plaintext, $ciphertext, $key->get(), $this->padding)) { throw new RuntimeException('Cannot encrypt data: ' . openssl_error_string()); } @@ -125,7 +125,7 @@ public function decrypt(AbstractKey $key, string $ciphertext): string $fn = 'openssl_private_decrypt'; } - $plaintext = null; + $plaintext = ''; if (!$fn($ciphertext, $plaintext, $key->get(), $this->padding)) { throw new RuntimeException('Cannot decrypt data: ' . openssl_error_string()); } From 801ed71db9ff82233e75c0f5024718c10c960377 Mon Sep 17 00:00:00 2001 From: Tim van Dijen Date: Thu, 22 Jul 2021 10:16:13 +0200 Subject: [PATCH 50/78] Add constants for ASN tags and sizes to improve readability --- src/Key/PublicKey.php | 45 ++++++++++++++++++++++++++++++------------- 1 file changed, 32 insertions(+), 13 deletions(-) diff --git a/src/Key/PublicKey.php b/src/Key/PublicKey.php index 94348ccb..f99b8b66 100644 --- a/src/Key/PublicKey.php +++ b/src/Key/PublicKey.php @@ -19,6 +19,25 @@ */ class PublicKey extends AsymmetricKey { + /** @var int */ + public const ASN1_TYPE_INTEGER = 0x02; // 2 + + /** @var int */ + public const ASN1_TYPE_BIT_STRING = 0x03; // 3 + + /** @var int */ + public const ASN1_TYPE_SEQUENCE = 0x30; // 16 + + /** @var int */ + public const ASN1_SIZE_128 = 0x80; // 128 + + /** @var int */ + public const ASN1_SIZE_256 = 0x0100; // 256 + + /** @var int */ + public const ASN1_SIZE_65535 = 0x010000; // 65535 + + /** * Create a new public key from the PEM-encoded key material. * @@ -56,24 +75,24 @@ public static function fromFile(string $file): PublicKey protected static function makeASN1Segment(int $type, string $string): ?string { switch ($type) { - case 0x02: - if (ord($string) > 0x7f) { + case self::ASN1_TYPE_INTEGER: + if (ord($string) > self::ASN1_SIZE_128 - 1) { $string = chr(0) . $string; } break; - case 0x03: + case self::ASN1_TYPE_BIT_STRING: $string = chr(0) . $string; break; } $length = strlen($string); - if ($length < 128) { + if ($length < self::ASN1_SIZE_128) { $output = sprintf("%c%c%s", $type, $length, $string); - } elseif ($length < 0x0100) { - $output = sprintf("%c%c%c%s", $type, 0x81, $length, $string); - } elseif ($length < 0x010000) { - $output = sprintf("%c%c%c%c%s", $type, 0x82, $length / 0x0100, $length % 0x0100, $string); + } elseif ($length < self::ASN1_SIZE_256) { + $output = sprintf("%c%c%c%s", $type, self::ASN1_SIZE_128 + 1, $length, $string); + } elseif ($length < self::ASN1_SIZE_65535) { + $output = sprintf("%c%c%c%c%s", $type, self::ASN1_SIZE_128 +2, $length / 0x0100, $length % 0x0100, $string); } else { $output = null; } @@ -96,14 +115,14 @@ public static function fromDetails(string $modulus, string $exponent): PublicKey chunk_split( base64_encode( self::makeASN1Segment( - 0x30, + self::ASN1_TYPE_SEQUENCE, pack("H*", "300D06092A864886F70D0101010500") . // RSA alg id self::makeASN1Segment( // bitstring - 0x03, + self::ASN1_TYPE_BIT_STRING, self::makeASN1Segment( // sequence - 0x30, - self::makeASN1Segment(0x02, $modulus) . - self::makeASN1Segment(0x02, $exponent) + self::ASN1_TYPE_SEQUENCE, + self::makeASN1Segment(self::ASN1_TYPE_INTEGER, $modulus) + . self::makeASN1Segment(self::ASN1_TYPE_INTEGER, $exponent) ) ) ) From 9f9cf6371a494d2fa12f0320288f40c2390888cc Mon Sep 17 00:00:00 2001 From: Tim van Dijen Date: Thu, 22 Jul 2021 10:19:55 +0200 Subject: [PATCH 51/78] Use assertion instead of returning null for string out-of-bounds --- src/Key/PublicKey.php | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Key/PublicKey.php b/src/Key/PublicKey.php index f99b8b66..73cf60d4 100644 --- a/src/Key/PublicKey.php +++ b/src/Key/PublicKey.php @@ -4,6 +4,8 @@ namespace SimpleSAML\XMLSecurity\Key; +use Webmozart\Assert\Assert; + use function base64_encode; use function chr; use function chunk_split; @@ -86,16 +88,16 @@ protected static function makeASN1Segment(int $type, string $string): ?string } $length = strlen($string); + Assert::lessThan($length, self::ASN1_SIZE_65535); if ($length < self::ASN1_SIZE_128) { $output = sprintf("%c%c%s", $type, $length, $string); } elseif ($length < self::ASN1_SIZE_256) { $output = sprintf("%c%c%c%s", $type, self::ASN1_SIZE_128 + 1, $length, $string); - } elseif ($length < self::ASN1_SIZE_65535) { + } else { // ($length < self::ASN1_SIZE_65535) $output = sprintf("%c%c%c%c%s", $type, self::ASN1_SIZE_128 +2, $length / 0x0100, $length % 0x0100, $string); - } else { - $output = null; } + return $output; } From 135bf213850f8c1d561f1d1defe584f62e97bd03 Mon Sep 17 00:00:00 2001 From: Tim van Dijen Date: Thu, 22 Jul 2021 11:29:11 +0200 Subject: [PATCH 52/78] Silence Scrutinizer; the syntax is just fine, but Scrutinizer doesn't like the alternative syntax --- src/Signature.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Signature.php b/src/Signature.php index 849e5215..38e1660e 100644 --- a/src/Signature.php +++ b/src/Signature.php @@ -390,7 +390,7 @@ public function addX509Certificates( $pem_lines = explode("\n", trim($cert->getCertificate())); array_shift($pem_lines); array_pop($pem_lines); - $pem = join($pem_lines); + $pem = /** @scrutinizer ignore-call */ join($pem_lines); $x509CertNode = $this->createElement('X509Certificate', $pem); $certDataNode->appendChild($x509CertNode); } From 3dd2911b3951cd4981567499014c1713d57a7e76 Mon Sep 17 00:00:00 2001 From: Tim van Dijen Date: Thu, 22 Jul 2021 11:31:32 +0200 Subject: [PATCH 53/78] Fix implicit array to bool cast --- src/Signature.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Signature.php b/src/Signature.php index 38e1660e..1d72ba8a 100644 --- a/src/Signature.php +++ b/src/Signature.php @@ -1052,7 +1052,7 @@ protected function processReference(DOMElement $ref): bool $includeCommentNodes = false; $xp = XP::getXPath($ref->ownerDocument); - if ($this->idNS && is_array($this->idNS)) { + if (!empty($this->idNS) && is_array($this->idNS)) { foreach ($this->idNS as $nspf => $ns) { $xp->registerNamespace($nspf, $ns); } From e23ada20840ac52aa4f74d46ada96072b2b71043 Mon Sep 17 00:00:00 2001 From: Tim van Dijen Date: Thu, 22 Jul 2021 11:33:13 +0200 Subject: [PATCH 54/78] Remove array test; the properties are always an array --- src/Signature.php | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/Signature.php b/src/Signature.php index 1d72ba8a..ca04f8cf 100644 --- a/src/Signature.php +++ b/src/Signature.php @@ -1052,16 +1052,12 @@ protected function processReference(DOMElement $ref): bool $includeCommentNodes = false; $xp = XP::getXPath($ref->ownerDocument); - if (!empty($this->idNS) && is_array($this->idNS)) { - foreach ($this->idNS as $nspf => $ns) { - $xp->registerNamespace($nspf, $ns); - } + foreach ($this->idNS as $nspf => $ns) { + $xp->registerNamespace($nspf, $ns); } $iDlist = '@Id="' . $identifier . '"'; - if (is_array($this->idKeys)) { - foreach ($this->idKeys as $idKey) { - $iDlist .= " or @$idKey='$identifier'"; - } + foreach ($this->idKeys as $idKey) { + $iDlist .= " or @$idKey='$identifier'"; } $query = '//*[' . $iDlist . ']'; $dataObject = $xp->query($query)->item(0); From fa7d6279041ed96b6d95d0090b2e2d2ebc89024f Mon Sep 17 00:00:00 2001 From: Tim van Dijen Date: Thu, 22 Jul 2021 11:35:32 +0200 Subject: [PATCH 55/78] Fix phpdoc --- src/TestUtils/PEMCertificatesMock.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/TestUtils/PEMCertificatesMock.php b/src/TestUtils/PEMCertificatesMock.php index 8e49cff2..0aa6ffe0 100644 --- a/src/TestUtils/PEMCertificatesMock.php +++ b/src/TestUtils/PEMCertificatesMock.php @@ -77,7 +77,7 @@ public static function loadPlainCertificateFile(string $file, $sig_alg = self::A /** * @param string $hash_alg - * @param string The file to use + * @param string $file The file to use * @param string $sig_alg One of rsa|dsa * @return \SimpleSAML\XMLSecurity\XMLSecurityKey */ @@ -94,7 +94,7 @@ public static function getPublicKey( /** * @param string $hash_alg - * @param string The file to use + * @param string $file The file to use * @param string $sig_alg One of rsa|dsa * @return \SimpleSAML\XMLSecurity\XMLSecurityKey */ From 9349424def844f678e106d0ad4d21d6b4efdb3d0 Mon Sep 17 00:00:00 2001 From: Tim van Dijen Date: Thu, 22 Jul 2021 11:37:25 +0200 Subject: [PATCH 56/78] Fix parentheses --- src/Utils/XML.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Utils/XML.php b/src/Utils/XML.php index e96952b4..afe8c7e3 100644 --- a/src/Utils/XML.php +++ b/src/Utils/XML.php @@ -106,7 +106,7 @@ public static function processTransforms( $inclusiveNamespaces = $transform->getInclusiveNamespaces(); if ($inclusiveNamespaces !== null) { $prefixes = $inclusiveNamespaces->getPrefixes(); - if (count($prefixes > 0)) { + if (count($prefixes) > 0) { $prefixList = $prefixes; } } @@ -141,4 +141,4 @@ public static function processTransforms( return self::canonicalizeData($data, $canonicalMethod, $arXPath, $prefixList); } -} \ No newline at end of file +} From 8d45f6080187514aa96cd19e118a87623f90362a Mon Sep 17 00:00:00 2001 From: Tim van Dijen Date: Thu, 22 Jul 2021 11:42:25 +0200 Subject: [PATCH 57/78] Fix incorrect return type --- src/Utils/XPath.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Utils/XPath.php b/src/Utils/XPath.php index 5b8b9020..6457ba6a 100644 --- a/src/Utils/XPath.php +++ b/src/Utils/XPath.php @@ -42,7 +42,7 @@ public static function getXPath(DOMNode $doc): DOMXPath * * @throws RuntimeException If no DOM document is available. */ - public static function findElement(DOMNode $ref, string $name): DOMDocument + public static function findElement(DOMNode $ref, string $name) { $doc = $ref instanceof DOMDocument ? $ref : $ref->ownerDocument; if ($doc === null) { From 40658bab57a0e02ba93148b73b721caef1f5be75 Mon Sep 17 00:00:00 2001 From: Tim van Dijen Date: Thu, 22 Jul 2021 12:05:52 +0200 Subject: [PATCH 58/78] Fix phpdoc --- src/XML/xenc/CipherData.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/XML/xenc/CipherData.php b/src/XML/xenc/CipherData.php index 646f9e76..93e12d89 100644 --- a/src/XML/xenc/CipherData.php +++ b/src/XML/xenc/CipherData.php @@ -18,7 +18,7 @@ */ class CipherData extends AbstractXencElement { - /** @var \SimpleSAML\XMLSecurity\xenc\CipherValue|null */ + /** @var \SimpleSAML\XMLSecurity\XML\xenc\CipherValue|null */ protected ?CipherValue $cipherValue = null; /** @var \SimpleSAML\XMLSecurity\XML\xenc\CipherReference|null */ From c9a295152d362e591ad7900b9ccba82b7f97cd54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaime=20Pe=CC=81rez?= Date: Fri, 23 Jul 2021 09:29:50 +0200 Subject: [PATCH 59/78] Add a method to SignatureAlgorithm to get the key MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jaime Pérez --- src/Alg/Signature/AbstractSigner.php | 9 +++++++++ src/Alg/SignatureAlgorithm.php | 8 ++++++++ src/Backend/HMAC.php | 2 +- 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/src/Alg/Signature/AbstractSigner.php b/src/Alg/Signature/AbstractSigner.php index 0a876f77..31aa7119 100644 --- a/src/Alg/Signature/AbstractSigner.php +++ b/src/Alg/Signature/AbstractSigner.php @@ -78,6 +78,15 @@ public function getDigest(): string } + /** + * @return AbstractKey + */ + public function getKey(): AbstractKey + { + return $this->key; + } + + /** * @param \SimpleSAML\XMLSecurity\Backend\SignatureBackend $backend */ diff --git a/src/Alg/SignatureAlgorithm.php b/src/Alg/SignatureAlgorithm.php index d642c22f..10deffcb 100644 --- a/src/Alg/SignatureAlgorithm.php +++ b/src/Alg/SignatureAlgorithm.php @@ -3,6 +3,7 @@ namespace SimpleSAML\XMLSecurity\Alg; use SimpleSAML\XMLSecurity\Backend\SignatureBackend; +use SimpleSAML\XMLSecurity\Key\AbstractKey; /** * An interface representing algorithms that can be used for digital signatures. @@ -35,6 +36,13 @@ public function getDigest(): string; public function getAlgorithmId(): string; + /** + * Get the key to use with this signature algorithm. + * + * @return AbstractKey + */ + public function getKey(): AbstractKey; + /** * Set the backend to use for actual computations by this algorithm. diff --git a/src/Backend/HMAC.php b/src/Backend/HMAC.php index 2c580234..55703d26 100644 --- a/src/Backend/HMAC.php +++ b/src/Backend/HMAC.php @@ -62,7 +62,7 @@ public function sign(AbstractKey $key, string $plaintext): string /** * Verify a signature with this cipher and a given key. * - * @param \SimpleSAML\XMLSecurity\Key\AbstractKey $key The key to use to. + * @param \SimpleSAML\XMLSecurity\Key\AbstractKey $key The key to use to verify the signature. * @param string $plaintext The original signed text. * @param string $signature The (binary) signature to verify. * From 35b1ab9a42b6f9410e60c14a18df2ce832d245a8 Mon Sep 17 00:00:00 2001 From: Tim van Dijen Date: Fri, 23 Jul 2021 18:52:33 +0200 Subject: [PATCH 60/78] Fix DOMXPath use --- src/XML/ds/XPath.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/XML/ds/XPath.php b/src/XML/ds/XPath.php index e81a4d1c..88a9de94 100644 --- a/src/XML/ds/XPath.php +++ b/src/XML/ds/XPath.php @@ -6,6 +6,7 @@ use DOMElement; use SimpleSAML\XML\Exception\InvalidDOMElementException; +use SimpleSAML\XMLSecurity\Utils\XPath as XPathUtils; use Webmozart\Assert\Assert; use function str_replace; @@ -108,9 +109,9 @@ public static function fromXML(DOMElement $xml): object Assert::same($xml->namespaceURI, self::NS, InvalidDOMElementException::class); $namespaces = []; - $xpath = new \DOMXPath($xml->ownerDocument); + $xpath = XPathUtils::getXPath($xml->ownerDocument); /** @var \DOMNode $ns */ - foreach ($xpath->query('./namespace::*', $xml) as $ns) { + foreach (XPathUtils::xpQuery($xml, './namespace::*', $xpath) as $ns) { if ($xml->getAttributeNode($ns->nodeName)) { // only add namespaces when they are defined explicitly in an attribute $namespaces[$ns->localName] = $xml->getAttribute($ns->nodeName); From bbfbf6091a53ba0d67e266a5fc56ec6527f60eb2 Mon Sep 17 00:00:00 2001 From: Tim van Dijen Date: Fri, 23 Jul 2021 21:32:36 +0200 Subject: [PATCH 61/78] Add use-statements for builtins --- src/Utils/XML.php | 3 +++ src/XML/AbstractSignedXMLElement.php | 2 ++ tests/XML/SignableElementTest.php | 9 +++++++++ tests/XML/SignedElementTest.php | 9 +++++++++ tests/XML/xenc/CarriedKeyNameTest.php | 3 +++ tests/XML/xenc/CipherValueTest.php | 3 +++ 6 files changed, 29 insertions(+) diff --git a/src/Utils/XML.php b/src/Utils/XML.php index afe8c7e3..cf3caefa 100644 --- a/src/Utils/XML.php +++ b/src/Utils/XML.php @@ -9,6 +9,9 @@ use SimpleSAML\XMLSecurity\XML\ds\Transform; use SimpleSAML\XMLSecurity\XML\ds\Transforms; +use function count; +use function is_null; + /** * Class with utility methods for XML manipulation. * diff --git a/src/XML/AbstractSignedXMLElement.php b/src/XML/AbstractSignedXMLElement.php index 2e30d1c7..c46bc1f2 100644 --- a/src/XML/AbstractSignedXMLElement.php +++ b/src/XML/AbstractSignedXMLElement.php @@ -11,6 +11,8 @@ use SimpleSAML\XML\Exception\TooManyElementsException; use SimpleSAML\XMLSecurity\XML\ds\Signature; +use function array_pop; + /** * Abstract class to be implemented by all signed classes * diff --git a/tests/XML/SignableElementTest.php b/tests/XML/SignableElementTest.php index 7c463661..154f8fe5 100644 --- a/tests/XML/SignableElementTest.php +++ b/tests/XML/SignableElementTest.php @@ -14,6 +14,15 @@ use SimpleSAML\XMLSecurity\XML\ds\X509Certificate; use SimpleSAML\XMLSecurity\XML\ds\X509Data; +use function array_pop; +use function array_shift; +use function dirname; +use function explode; +use function file_get_contents; +use function join; +use function strval; +use function trim; + /** * Class \SimpleSAML\XMLSecurity\Test\XML\SignableElementTest * diff --git a/tests/XML/SignedElementTest.php b/tests/XML/SignedElementTest.php index 5865ba44..ecd1ce3f 100644 --- a/tests/XML/SignedElementTest.php +++ b/tests/XML/SignedElementTest.php @@ -9,6 +9,15 @@ use SimpleSAML\XML\DOMDocumentFactory; use SimpleSAML\XMLSecurity\Key\PrivateKey; +use function array_pop; +use function array_shift; +use function dirname; +use function explode; +use function file_get_contents; +use function join; +use function strval; +use function trim; + /** * Class \SimpleSAML\XMLSecurity\Test\XML\SignedElementTest * diff --git a/tests/XML/xenc/CarriedKeyNameTest.php b/tests/XML/xenc/CarriedKeyNameTest.php index 4350334f..09e71958 100644 --- a/tests/XML/xenc/CarriedKeyNameTest.php +++ b/tests/XML/xenc/CarriedKeyNameTest.php @@ -9,6 +9,9 @@ use SimpleSAML\XML\DOMDocumentFactory; use SimpleSAML\XMLSecurity\XML\xenc\CarriedKeyName; +use function dirname; +use function strval; + /** * Class \SimpleSAML\XMLSecurity\Test\XML\xenc\CarriedKeyNameTest * diff --git a/tests/XML/xenc/CipherValueTest.php b/tests/XML/xenc/CipherValueTest.php index 03387951..481320d7 100644 --- a/tests/XML/xenc/CipherValueTest.php +++ b/tests/XML/xenc/CipherValueTest.php @@ -12,6 +12,9 @@ use SimpleSAML\XMLSecurity\Test\XML\XMLDumper; use SimpleSAML\XMLSecurity\XML\xenc\CipherValue; +use function dirname; +use function strval; + /** * Class \SimpleSAML\XMLSecurity\Test\XML\xenc\CipherValueTest * From 05b928bbf1fa1c0ae68f521ff388844c1fc58ccd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaime=20Pe=CC=81rez?= Date: Fri, 23 Jul 2021 12:38:11 +0200 Subject: [PATCH 62/78] Add constants for X509 certificate header/footer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jaime Pérez --- src/Key/X509Certificate.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Key/X509Certificate.php b/src/Key/X509Certificate.php index 2600b00d..0728d6d5 100644 --- a/src/Key/X509Certificate.php +++ b/src/Key/X509Certificate.php @@ -31,6 +31,9 @@ */ class X509Certificate extends PublicKey { + public const PEM_HEADER = '-----BEGIN CERTIFICATE-----'; + public const PEM_FOOTER = '-----END CERTIFICATE-----'; + /** @var string */ protected string $certificate; From 7215435db2188fb205ed616b94a2bcc69baced3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaime=20Pe=CC=81rez?= Date: Fri, 23 Jul 2021 12:53:34 +0200 Subject: [PATCH 63/78] Use our own Assert in SignableElementTrait MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jaime Pérez --- src/XML/SignableElementTrait.php | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/XML/SignableElementTrait.php b/src/XML/SignableElementTrait.php index db97dbfc..c07937b0 100644 --- a/src/XML/SignableElementTrait.php +++ b/src/XML/SignableElementTrait.php @@ -6,8 +6,10 @@ use DOMElement; use DOMNode; +use SimpleSAML\Assert\Assert; use SimpleSAML\XMLSecurity\Alg\SignatureAlgorithm; use SimpleSAML\XMLSecurity\Constants; +use SimpleSAML\XMLSecurity\Exception\InvalidArgumentException; use SimpleSAML\XMLSecurity\Exception\RuntimeException; use SimpleSAML\XMLSecurity\Utils\Security; use SimpleSAML\XMLSecurity\Utils\XML; @@ -22,7 +24,6 @@ use SimpleSAML\XMLSecurity\XML\ds\SignedInfo; use SimpleSAML\XMLSecurity\XML\ds\Transform; use SimpleSAML\XMLSecurity\XML\ds\Transforms; -use Webmozart\Assert\Assert; /** * Trait SignableElementTrait @@ -37,7 +38,7 @@ trait SignableElementTrait /** @var string */ private string $c14nAlg = Constants::C14N_EXCLUSIVE_WITHOUT_COMMENTS; - /** @var KeyInfo|null */ + /** @var \SimpleSAML\XMLSecurity\XML\ds\KeyInfo|null */ private ?KeyInfo $keyInfo = null; /** @var \SimpleSAML\XMLSecurity\Alg\SignatureAlgorithm|null */ @@ -67,7 +68,8 @@ public function sign( Constants::C14N_EXCLUSIVE_WITH_COMMENTS, Constants::C14N_EXCLUSIVE_WITHOUT_COMMENTS ], - 'Unsupported canonicalization algorithm' + 'Unsupported canonicalization algorithm', + InvalidArgumentException::class ); $this->c14nAlg = $canonicalizationAlg; } @@ -79,9 +81,12 @@ public function sign( */ private function doSign(DOMElement $xml): void { - if ($this->signer === null) { - throw new RuntimeException('Cannot call toSignedXML() without calling sign() first.'); - } + Assert::notNull( + $this->signer, + 'Cannot call toSignedXML() without calling sign() first.', + RuntimeException::class + ); + $algorithm = $this->signer->getAlgorithmId(); $digest = $this->signer->getDigest(); From 6bbe2ef410a965cc87bb73fb4ea160fa237c508e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaime=20Pe=CC=81rez?= Date: Fri, 23 Jul 2021 13:08:39 +0200 Subject: [PATCH 64/78] Phpdoc fixes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jaime Pérez --- src/XML/SignableElementInterface.php | 2 +- src/XML/SignableElementTrait.php | 13 ++++++++++++- src/XML/SignedElementInterface.php | 2 +- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/XML/SignableElementInterface.php b/src/XML/SignableElementInterface.php index e6ae4ece..4c8ee883 100644 --- a/src/XML/SignableElementInterface.php +++ b/src/XML/SignableElementInterface.php @@ -30,7 +30,7 @@ public function getId(): ?string; * @note The signature will not be applied until toSignedXML() is called. * * @param \SimpleSAML\XMLSecurity\Alg\SignatureAlgorithm $signer The actual signer implementation to use. - * @param string|null $canonicalizationAlg The identifier of the canonicalization algorithm to use. + * @param string $canonicalizationAlg The identifier of the canonicalization algorithm to use. * @param \SimpleSAML\XMLSecurity\XML\ds\KeyInfo|null $keyInfo A KeyInfo object to add to the signature. */ public function sign( diff --git a/src/XML/SignableElementTrait.php b/src/XML/SignableElementTrait.php index c07937b0..24c636be 100644 --- a/src/XML/SignableElementTrait.php +++ b/src/XML/SignableElementTrait.php @@ -46,12 +46,23 @@ trait SignableElementTrait /** - * @return string|null + * Get the ID of this element. + * + * When this method returns null, the signature created for this object will reference the entire document. + * + * @return string|null The ID of this element, or null if we don't have one. */ abstract public function getId(): ?string; /** + * Sign the current element. + * + * @note The signature will not be applied until toSignedXML() is called. + * + * @param \SimpleSAML\XMLSecurity\Alg\SignatureAlgorithm $signer The actual signer implementation to use. + * @param string $canonicalizationAlg The identifier of the canonicalization algorithm to use. + * @param \SimpleSAML\XMLSecurity\XML\ds\KeyInfo|null $keyInfo A KeyInfo object to add to the signature. */ public function sign( SignatureAlgorithm $signer, diff --git a/src/XML/SignedElementInterface.php b/src/XML/SignedElementInterface.php index b62690ff..6a6024f8 100644 --- a/src/XML/SignedElementInterface.php +++ b/src/XML/SignedElementInterface.php @@ -23,7 +23,7 @@ public function getValidatingCertificates(): array; /** - * Validate this element against a public key. + * Verify the signature in this object. * * If no signature is present, false is returned. If a signature is present, * but cannot be verified, an exception will be thrown. From b582541efc7c9463915c287407c3db986662303b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaime=20Pe=CC=81rez?= Date: Fri, 23 Jul 2021 13:10:41 +0200 Subject: [PATCH 65/78] Make SignableElementInterface extend CanonicalizableElementInterface MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jaime Pérez --- src/XML/SignableElementInterface.php | 2 +- src/XML/SignableElementTrait.php | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/XML/SignableElementInterface.php b/src/XML/SignableElementInterface.php index 4c8ee883..b159bb7a 100644 --- a/src/XML/SignableElementInterface.php +++ b/src/XML/SignableElementInterface.php @@ -12,7 +12,7 @@ * * @package simplesamlphp/xml-security */ -interface SignableElementInterface +interface SignableElementInterface extends CanonicalizableElementInterface { /** * Get the ID of this element. diff --git a/src/XML/SignableElementTrait.php b/src/XML/SignableElementTrait.php index 24c636be..f2a32e94 100644 --- a/src/XML/SignableElementTrait.php +++ b/src/XML/SignableElementTrait.php @@ -32,6 +32,8 @@ */ trait SignableElementTrait { + use CanonicalizableElementTrait; + /** @var \SimpleSAML\XMLSecurity\XML\ds\Signature|null */ protected ?Signature $signature = null; From 49ee59cd85428bc5d9d8f1aabccace91557a80d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaime=20Pe=CC=81rez?= Date: Sat, 24 Jul 2021 18:05:48 +0200 Subject: [PATCH 66/78] Minor fixes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jaime Pérez --- src/Utils/XML.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Utils/XML.php b/src/Utils/XML.php index cf3caefa..13dac0a2 100644 --- a/src/Utils/XML.php +++ b/src/Utils/XML.php @@ -52,6 +52,7 @@ public static function canonicalizeData( if ( is_null($xpaths) && ($element->ownerDocument !== null) + && ($element->ownerDocument->documentElement !== null) && $element->isSameNode($element->ownerDocument->documentElement) ) { // check for any PI or comments as they would have been excluded @@ -65,7 +66,7 @@ public static function canonicalizeData( } $current = $refNode; } - if ($refNode == null) { + if ($refNode === null) { $element = $element->ownerDocument; } } From eeb15ef58c7342f65f1b0169b6aa2dcd88e9687f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaime=20Pe=CC=81rez?= Date: Sat, 24 Jul 2021 18:06:27 +0200 Subject: [PATCH 67/78] Add a method to ds:Reference to determine if it's an XPointer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jaime Pérez --- src/XML/ds/Reference.php | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/XML/ds/Reference.php b/src/XML/ds/Reference.php index 3977f73c..a9897d76 100644 --- a/src/XML/ds/Reference.php +++ b/src/XML/ds/Reference.php @@ -171,6 +171,17 @@ private function setURI(?string $URI): void } + /** + * Determine whether this is an xpointer reference. + * + * @return bool + */ + public function isXPointer(): bool + { + return !empty($this->URI) && preg_match('/^#xpointer\(.+\)$/', $this->URI); + } + + /** * Convert XML into a Reference element * From 20dd0c8109886e9285009143463e8744afef5572 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaime=20Pe=CC=81rez?= Date: Sat, 24 Jul 2021 18:07:24 +0200 Subject: [PATCH 68/78] Add more signed documents MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit These new documents can be used in tests to verify different features. Signed-off-by: Jaime Pérez --- .../resources/xml/custom_CustomSignedElement.xml | 15 +++++++++++++++ .../resources/xml/custom_CustomSignedTampered.xml | 15 +++++++++++++++ .../xml/custom_CustomSignedWithComments.xml | 15 +++++++++++++++ .../xml/custom_customSignedWithCommentsAndId.xml | 15 +++++++++++++++ 4 files changed, 60 insertions(+) create mode 100644 tests/resources/xml/custom_CustomSignedElement.xml create mode 100644 tests/resources/xml/custom_CustomSignedTampered.xml create mode 100644 tests/resources/xml/custom_CustomSignedWithComments.xml create mode 100644 tests/resources/xml/custom_customSignedWithCommentsAndId.xml diff --git a/tests/resources/xml/custom_CustomSignedElement.xml b/tests/resources/xml/custom_CustomSignedElement.xml new file mode 100644 index 00000000..be6b0667 --- /dev/null +++ b/tests/resources/xml/custom_CustomSignedElement.xml @@ -0,0 +1,15 @@ +0QMzfXh0cPsgEyn3hNdzMHkFVglAjlv9DwYe9YhlQKU=zuBh9JdjIsnhM13tbGSyI8oGujRWutc8A020Ah32xxk6TXKFO2ltVFWktJib0IBt5gR3zXECtvDaRlePYo4S58eFQTYVLsnvCINBvh5tdTWZ4Rhje3wWofeax3qdprFqxhbEqX/5zwjqap1q+VURC+msJMTUiYd+X8sERb5JtRI=MIICxDCCAi2gAwIBAgIUCJ8EYI/BrvQnHt9QFGfdqgaxDmowDQYJKoZIhvcNAQEL +BQAwczElMCMGA1UEAwwcc2VsZnNpZ25lZC5zaW1wbGVzYW1scGhwLm9yZzEZMBcG +A1UECgwQU2ltcGxlU0FNTHBocCBIUTERMA8GA1UEBwwISG9ub2x1bHUxDzANBgNV +BAgMBkhhd2FpaTELMAkGA1UEBhMCVVMwIBcNMjAwNjIzMjEwMTEyWhgPMjEyMDA1 +MzAyMTAxMTJaMHMxJTAjBgNVBAMMHHNlbGZzaWduZWQuc2ltcGxlc2FtbHBocC5v +cmcxGTAXBgNVBAoMEFNpbXBsZVNBTUxwaHAgSFExETAPBgNVBAcMCEhvbm9sdWx1 +MQ8wDQYDVQQIDAZIYXdhaWkxCzAJBgNVBAYTAlVTMIGfMA0GCSqGSIb3DQEBAQUA +A4GNADCBiQKBgQDZ3ztmRqrJm6zSJ2jNUjbxdlX3WLadthHvDkdfQDWuFIUjMkCf +O8+OWYdFP4cQWKBVnWyO+/LXFl9p9fwcSJmhmkgz5blUw0cjv8jccHw+yfVnGxGC +K2Up/Si4HE/WStgjTKr3ivAqyc0Kc/97EoIXmb96SEEb4Lat9xt6mFSbWQIDAQAB +o1MwUTAdBgNVHQ4EFgQUHhB/tFwoDOT8V1nLi5XY4CJgTTQwHwYDVR0jBBgwFoAU +HhB/tFwoDOT8V1nLi5XY4CJgTTQwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0B +AQsFAAOBgQBVqkGuiwyYMx0FH3j/qirZ2nKrjUU+p0ganzEA7kVZ97RivW5SFLHk +RjcfqDXVyqOzmr4iR8rfxI2rUjYQI1SsKacPhitiArMk2IQ+uvxZ5eNtU2yFzW4a +rQ+a2ttlZI/GF9nLvgUNFIUwaDc+PQD2rOk+ACklbAS3jkf+L3DpQg==Chunk \ No newline at end of file diff --git a/tests/resources/xml/custom_CustomSignedTampered.xml b/tests/resources/xml/custom_CustomSignedTampered.xml new file mode 100644 index 00000000..8de612ea --- /dev/null +++ b/tests/resources/xml/custom_CustomSignedTampered.xml @@ -0,0 +1,15 @@ +5F9s1oK6M4QD7UH4GDU9a2Otz876kwhRbvh+oGVTCoM=V5dmpyjjOLJKFNc8uBJFKDA89OsyL+0oT5EJhVyD5jYO/aO8juNE5+btW0sLbo46DbvmXxLmyZADsT4wqR8pRH6FiBwvYK0wS4FRociNh710HrEcTghR0sCasW/VQh0F6jcRqbi8PJOLpTrtBs7LZH31marO5I0Eed9RPihLSws=MIICxDCCAi2gAwIBAgIUCJ8EYI/BrvQnHt9QFGfdqgaxDmowDQYJKoZIhvcNAQEL +BQAwczElMCMGA1UEAwwcc2VsZnNpZ25lZC5zaW1wbGVzYW1scGhwLm9yZzEZMBcG +A1UECgwQU2ltcGxlU0FNTHBocCBIUTERMA8GA1UEBwwISG9ub2x1bHUxDzANBgNV +BAgMBkhhd2FpaTELMAkGA1UEBhMCVVMwIBcNMjAwNjIzMjEwMTEyWhgPMjEyMDA1 +MzAyMTAxMTJaMHMxJTAjBgNVBAMMHHNlbGZzaWduZWQuc2ltcGxlc2FtbHBocC5v +cmcxGTAXBgNVBAoMEFNpbXBsZVNBTUxwaHAgSFExETAPBgNVBAcMCEhvbm9sdWx1 +MQ8wDQYDVQQIDAZIYXdhaWkxCzAJBgNVBAYTAlVTMIGfMA0GCSqGSIb3DQEBAQUA +A4GNADCBiQKBgQDZ3ztmRqrJm6zSJ2jNUjbxdlX3WLadthHvDkdfQDWuFIUjMkCf +O8+OWYdFP4cQWKBVnWyO+/LXFl9p9fwcSJmhmkgz5blUw0cjv8jccHw+yfVnGxGC +K2Up/Si4HE/WStgjTKr3ivAqyc0Kc/97EoIXmb96SEEb4Lat9xt6mFSbWQIDAQAB +o1MwUTAdBgNVHQ4EFgQUHhB/tFwoDOT8V1nLi5XY4CJgTTQwHwYDVR0jBBgwFoAU +HhB/tFwoDOT8V1nLi5XY4CJgTTQwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0B +AQsFAAOBgQBVqkGuiwyYMx0FH3j/qirZ2nKrjUU+p0ganzEA7kVZ97RivW5SFLHk +RjcfqDXVyqOzmr4iR8rfxI2rUjYQI1SsKacPhitiArMk2IQ+uvxZ5eNtU2yFzW4a +rQ+a2ttlZI/GF9nLvgUNFIUwaDc+PQD2rOk+ACklbAS3jkf+L3DpQg==knuhC \ No newline at end of file diff --git a/tests/resources/xml/custom_CustomSignedWithComments.xml b/tests/resources/xml/custom_CustomSignedWithComments.xml new file mode 100644 index 00000000..d1342f73 --- /dev/null +++ b/tests/resources/xml/custom_CustomSignedWithComments.xml @@ -0,0 +1,15 @@ +Aoq6LrKSoNxBpBqyzwcyJOrTX9lL3lc5vJVREcNpQvo=rkF/RWNL0B2Q+esER2eLGFUJeCQSYGDRo3VQ868wwzIVdeswYqj/cHz8il4bdd1AmG7Ubnmgy4O37c3QvZW++DOvEG426pJ/ueRGfxZI2vLk/dkGK5LllU16rW93bEdmyPudelAcOpjoRy/3v4o8hrB1Xj6i+/efcschCwFdj3U=MIICxDCCAi2gAwIBAgIUCJ8EYI/BrvQnHt9QFGfdqgaxDmowDQYJKoZIhvcNAQEL +BQAwczElMCMGA1UEAwwcc2VsZnNpZ25lZC5zaW1wbGVzYW1scGhwLm9yZzEZMBcG +A1UECgwQU2ltcGxlU0FNTHBocCBIUTERMA8GA1UEBwwISG9ub2x1bHUxDzANBgNV +BAgMBkhhd2FpaTELMAkGA1UEBhMCVVMwIBcNMjAwNjIzMjEwMTEyWhgPMjEyMDA1 +MzAyMTAxMTJaMHMxJTAjBgNVBAMMHHNlbGZzaWduZWQuc2ltcGxlc2FtbHBocC5v +cmcxGTAXBgNVBAoMEFNpbXBsZVNBTUxwaHAgSFExETAPBgNVBAcMCEhvbm9sdWx1 +MQ8wDQYDVQQIDAZIYXdhaWkxCzAJBgNVBAYTAlVTMIGfMA0GCSqGSIb3DQEBAQUA +A4GNADCBiQKBgQDZ3ztmRqrJm6zSJ2jNUjbxdlX3WLadthHvDkdfQDWuFIUjMkCf +O8+OWYdFP4cQWKBVnWyO+/LXFl9p9fwcSJmhmkgz5blUw0cjv8jccHw+yfVnGxGC +K2Up/Si4HE/WStgjTKr3ivAqyc0Kc/97EoIXmb96SEEb4Lat9xt6mFSbWQIDAQAB +o1MwUTAdBgNVHQ4EFgQUHhB/tFwoDOT8V1nLi5XY4CJgTTQwHwYDVR0jBBgwFoAU +HhB/tFwoDOT8V1nLi5XY4CJgTTQwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0B +AQsFAAOBgQBVqkGuiwyYMx0FH3j/qirZ2nKrjUU+p0ganzEA7kVZ97RivW5SFLHk +RjcfqDXVyqOzmr4iR8rfxI2rUjYQI1SsKacPhitiArMk2IQ+uvxZ5eNtU2yFzW4a +rQ+a2ttlZI/GF9nLvgUNFIUwaDc+PQD2rOk+ACklbAS3jkf+L3DpQg==Chunk \ No newline at end of file diff --git a/tests/resources/xml/custom_customSignedWithCommentsAndId.xml b/tests/resources/xml/custom_customSignedWithCommentsAndId.xml new file mode 100644 index 00000000..33258e0e --- /dev/null +++ b/tests/resources/xml/custom_customSignedWithCommentsAndId.xml @@ -0,0 +1,15 @@ +NV/zxP9pMcTkzKYAb5UBwoLX1UTaWD085lwBFImZF2w=d/gPQffy48OzCNvPFVggbFJM3WjJdsKTJBfHYWrvMY2VdwkAEVEmDTzy21z3zXPMUA8ynrV7PkWvDf9TKxBsTcDaXb8aqWQyfrBkz+hwYcWEyMZmUNwhsaUfZK3OGfjCss7jBN7xVOzOi+KPdK+show6B+MfTMLtR+yoB2EtZ9s=MIICxDCCAi2gAwIBAgIUCJ8EYI/BrvQnHt9QFGfdqgaxDmowDQYJKoZIhvcNAQEL +BQAwczElMCMGA1UEAwwcc2VsZnNpZ25lZC5zaW1wbGVzYW1scGhwLm9yZzEZMBcG +A1UECgwQU2ltcGxlU0FNTHBocCBIUTERMA8GA1UEBwwISG9ub2x1bHUxDzANBgNV +BAgMBkhhd2FpaTELMAkGA1UEBhMCVVMwIBcNMjAwNjIzMjEwMTEyWhgPMjEyMDA1 +MzAyMTAxMTJaMHMxJTAjBgNVBAMMHHNlbGZzaWduZWQuc2ltcGxlc2FtbHBocC5v +cmcxGTAXBgNVBAoMEFNpbXBsZVNBTUxwaHAgSFExETAPBgNVBAcMCEhvbm9sdWx1 +MQ8wDQYDVQQIDAZIYXdhaWkxCzAJBgNVBAYTAlVTMIGfMA0GCSqGSIb3DQEBAQUA +A4GNADCBiQKBgQDZ3ztmRqrJm6zSJ2jNUjbxdlX3WLadthHvDkdfQDWuFIUjMkCf +O8+OWYdFP4cQWKBVnWyO+/LXFl9p9fwcSJmhmkgz5blUw0cjv8jccHw+yfVnGxGC +K2Up/Si4HE/WStgjTKr3ivAqyc0Kc/97EoIXmb96SEEb4Lat9xt6mFSbWQIDAQAB +o1MwUTAdBgNVHQ4EFgQUHhB/tFwoDOT8V1nLi5XY4CJgTTQwHwYDVR0jBBgwFoAU +HhB/tFwoDOT8V1nLi5XY4CJgTTQwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0B +AQsFAAOBgQBVqkGuiwyYMx0FH3j/qirZ2nKrjUU+p0ganzEA7kVZ97RivW5SFLHk +RjcfqDXVyqOzmr4iR8rfxI2rUjYQI1SsKacPhitiArMk2IQ+uvxZ5eNtU2yFzW4a +rQ+a2ttlZI/GF9nLvgUNFIUwaDc+PQD2rOk+ACklbAS3jkf+L3DpQg==Chunk \ No newline at end of file From df435c270521bc7fe8fa0e8cb239832ecabe855f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaime=20Pe=CC=81rez?= Date: Sat, 24 Jul 2021 18:10:21 +0200 Subject: [PATCH 69/78] Signable elements should call toXML() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We got rid of the initial extra toSignedXML() method. If we want to sign an element, we should just call sign() on it, then get the result with toXML(). This means it will be toXML() the method that actually does the signing, while sign() will simply prepare everything for it and signal that the element should be signed when calling toXML(). Signed-off-by: Jaime Pérez --- src/XML/SignableElementInterface.php | 2 +- src/XML/SignableElementTrait.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/XML/SignableElementInterface.php b/src/XML/SignableElementInterface.php index b159bb7a..31b88e5f 100644 --- a/src/XML/SignableElementInterface.php +++ b/src/XML/SignableElementInterface.php @@ -27,7 +27,7 @@ public function getId(): ?string; /** * Sign the current element. * - * @note The signature will not be applied until toSignedXML() is called. + * @note The signature will not be applied until toXML() is called. * * @param \SimpleSAML\XMLSecurity\Alg\SignatureAlgorithm $signer The actual signer implementation to use. * @param string $canonicalizationAlg The identifier of the canonicalization algorithm to use. diff --git a/src/XML/SignableElementTrait.php b/src/XML/SignableElementTrait.php index f2a32e94..cad7ee23 100644 --- a/src/XML/SignableElementTrait.php +++ b/src/XML/SignableElementTrait.php @@ -60,7 +60,7 @@ abstract public function getId(): ?string; /** * Sign the current element. * - * @note The signature will not be applied until toSignedXML() is called. + * The signature will not be applied until toXML() is called. * * @param \SimpleSAML\XMLSecurity\Alg\SignatureAlgorithm $signer The actual signer implementation to use. * @param string $canonicalizationAlg The identifier of the canonicalization algorithm to use. From 5fa57da77626f88300734641aa3e789eb1415bdc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaime=20Pe=CC=81rez?= Date: Sat, 24 Jul 2021 18:12:05 +0200 Subject: [PATCH 70/78] Use short name for Constants class MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jaime Pérez --- src/XML/SignableElementTrait.php | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/XML/SignableElementTrait.php b/src/XML/SignableElementTrait.php index cad7ee23..fb38dfd1 100644 --- a/src/XML/SignableElementTrait.php +++ b/src/XML/SignableElementTrait.php @@ -8,7 +8,7 @@ use DOMNode; use SimpleSAML\Assert\Assert; use SimpleSAML\XMLSecurity\Alg\SignatureAlgorithm; -use SimpleSAML\XMLSecurity\Constants; +use SimpleSAML\XMLSecurity\Constants as C; use SimpleSAML\XMLSecurity\Exception\InvalidArgumentException; use SimpleSAML\XMLSecurity\Exception\RuntimeException; use SimpleSAML\XMLSecurity\Utils\Security; @@ -38,7 +38,7 @@ trait SignableElementTrait protected ?Signature $signature = null; /** @var string */ - private string $c14nAlg = Constants::C14N_EXCLUSIVE_WITHOUT_COMMENTS; + private string $c14nAlg = C::C14N_EXCLUSIVE_WITHOUT_COMMENTS; /** @var \SimpleSAML\XMLSecurity\XML\ds\KeyInfo|null */ private ?KeyInfo $keyInfo = null; @@ -68,7 +68,7 @@ abstract public function getId(): ?string; */ public function sign( SignatureAlgorithm $signer, - string $canonicalizationAlg = Constants::C14N_EXCLUSIVE_WITHOUT_COMMENTS, + string $canonicalizationAlg = C::C14N_EXCLUSIVE_WITHOUT_COMMENTS, ?KeyInfo $keyInfo = null ): void { $this->signer = $signer; @@ -76,10 +76,10 @@ public function sign( Assert::oneOf( $canonicalizationAlg, [ - Constants::C14N_INCLUSIVE_WITH_COMMENTS, - Constants::C14N_EXCLUSIVE_WITHOUT_COMMENTS, - Constants::C14N_EXCLUSIVE_WITH_COMMENTS, - Constants::C14N_EXCLUSIVE_WITHOUT_COMMENTS + C::C14N_INCLUSIVE_WITH_COMMENTS, + C::C14N_EXCLUSIVE_WITHOUT_COMMENTS, + C::C14N_EXCLUSIVE_WITH_COMMENTS, + C::C14N_EXCLUSIVE_WITHOUT_COMMENTS ], 'Unsupported canonicalization algorithm', InvalidArgumentException::class @@ -104,7 +104,7 @@ private function doSign(DOMElement $xml): void $digest = $this->signer->getDigest(); $transforms = new Transforms([ - new Transform(Constants::XMLDSIG_ENVELOPED), + new Transform(C::XMLDSIG_ENVELOPED), new Transform($this->c14nAlg) ]); From 4137de0feef16d0dd2417a9c4a2feb884d91ba5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaime=20Pe=CC=81rez?= Date: Sat, 24 Jul 2021 18:12:20 +0200 Subject: [PATCH 71/78] Fix wrong package name MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jaime Pérez --- tests/XML/CustomSignable.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/XML/CustomSignable.php b/tests/XML/CustomSignable.php index 2810d634..7c0b38d2 100644 --- a/tests/XML/CustomSignable.php +++ b/tests/XML/CustomSignable.php @@ -15,7 +15,7 @@ use SimpleSAML\XMLSecurity\XML\SignableElementTrait; /** - * @package simplesamlphp\saml2 + * @package simplesamlphp/xml-security */ class CustomSignable extends AbstractXMLElement implements SignableElementInterface { From 4073c66f21ef58c694b48a67d832a8057202e738 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaime=20Pe=CC=81rez?= Date: Sat, 24 Jul 2021 18:13:22 +0200 Subject: [PATCH 72/78] No longer override c14n alg MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jaime Pérez --- src/Utils/XML.php | 28 ++++------------------------ 1 file changed, 4 insertions(+), 24 deletions(-) diff --git a/src/Utils/XML.php b/src/Utils/XML.php index 13dac0a2..e3306050 100644 --- a/src/Utils/XML.php +++ b/src/Utils/XML.php @@ -6,7 +6,6 @@ use DOMElement; use SimpleSAML\XMLSecurity\Constants as C; -use SimpleSAML\XMLSecurity\XML\ds\Transform; use SimpleSAML\XMLSecurity\XML\ds\Transforms; use function count; @@ -88,25 +87,16 @@ public static function canonicalizeData( */ public static function processTransforms( Transforms $transforms, - DOMElement $data, - bool $includeCommentNodes = false + DOMElement $data ): string { $canonicalMethod = C::C14N_EXCLUSIVE_WITHOUT_COMMENTS; $arXPath = null; $prefixList = null; - foreach ($transforms as $transform) { - /** @var Transform $transform */ - $algorithm = $transform->getAlgorithm(); - switch ($algorithm) { + foreach ($transforms->getTransform() as $transform) { + $canonicalMethod = $transform->getAlgorithm(); + switch ($canonicalMethod) { case C::C14N_EXCLUSIVE_WITHOUT_COMMENTS: case C::C14N_EXCLUSIVE_WITH_COMMENTS: - if (!$includeCommentNodes) { - // remove comment nodes by forcing it to use a canonicalization without comments - $canonicalMethod = C::C14N_EXCLUSIVE_WITHOUT_COMMENTS; - } else { - $canonicalMethod = $algorithm; - } - $inclusiveNamespaces = $transform->getInclusiveNamespaces(); if ($inclusiveNamespaces !== null) { $prefixes = $inclusiveNamespaces->getPrefixes(); @@ -114,16 +104,6 @@ public static function processTransforms( $prefixList = $prefixes; } } - break; - case C::C14N_INCLUSIVE_WITHOUT_COMMENTS: - case C::C14N_INCLUSIVE_WITH_COMMENTS: - if (!$includeCommentNodes) { - // remove comment nodes by forcing it to use a canonicalization without comments - $canonicalMethod = C::C14N_INCLUSIVE_WITHOUT_COMMENTS; - } else { - $canonicalMethod = $algorithm; - } - break; case C::XPATH_URI: $xpath = $transform->getXPath(); From 20bcbd0c095ab45b66b5f7bb56e8ae74645fb962 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaime=20Pe=CC=81rez?= Date: Sat, 24 Jul 2021 18:15:22 +0200 Subject: [PATCH 73/78] Implement signature verification, refactor signing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit adds support for verifying signatures (and renames the API method to verify(), which is the correct term we should us in this context, instead of validate()). It also refactors the way we sign, and adds support for canonicalization algorithms with comments, which were in practice not supported before. Signed-off-by: Jaime Pérez --- src/XML/SignableElementTrait.php | 93 ++++++++--- src/XML/SignedElementInterface.php | 50 +++++- src/XML/SignedElementTrait.php | 254 ++++++++++++++++++++++++----- tests/XML/CustomSignable.php | 67 ++++---- tests/XML/SignableElementTest.php | 169 ++++++++++++++++++- tests/XML/SignedElementTest.php | 180 +++++++++++++++++--- 6 files changed, 685 insertions(+), 128 deletions(-) diff --git a/src/XML/SignableElementTrait.php b/src/XML/SignableElementTrait.php index fb38dfd1..7a1ad82d 100644 --- a/src/XML/SignableElementTrait.php +++ b/src/XML/SignableElementTrait.php @@ -5,8 +5,8 @@ namespace SimpleSAML\XMLSecurity\XML; use DOMElement; -use DOMNode; use SimpleSAML\Assert\Assert; +use SimpleSAML\XML\DOMDocumentFactory; use SimpleSAML\XMLSecurity\Alg\SignatureAlgorithm; use SimpleSAML\XMLSecurity\Constants as C; use SimpleSAML\XMLSecurity\Exception\InvalidArgumentException; @@ -89,10 +89,69 @@ public function sign( /** - * @param \DOMElement $xml - * @throws \Exception + * Get a ds:Reference pointing to this object. + * + * @param string $digestAlg The digest algorithm to use. + * @param \SimpleSAML\XMLSecurity\XML\ds\Transforms $transforms The transforms to apply to the object. */ - private function doSign(DOMElement $xml): void + private function getReference( + string $digestAlg, + Transforms $transforms, + DOMElement $xml, + string $canonicalDocument + ): Reference { + $id = $this->getId(); + $uri = null; + if (empty($id)) { // document reference + Assert::notNull( + $xml->ownerDocument->documentElement, + 'Cannot create a document reference without a root element in the document.', + RuntimeException::class + ); + Assert::true( + $xml->isSameNode($xml->ownerDocument->documentElement), + 'Cannot create a document reference when signing an object that is not the root of the document. ' . + 'Please give your object an identifier.', + RuntimeException::class + ); + if (in_array($this->c14nAlg, [C::C14N_INCLUSIVE_WITH_COMMENTS, C::C14N_EXCLUSIVE_WITH_COMMENTS])) { + $uri = '#xpointer(/)'; + } + } elseif (in_array($this->c14nAlg, [C::C14N_INCLUSIVE_WITH_COMMENTS, C::C14N_EXCLUSIVE_WITH_COMMENTS])) { + // regular reference, but must retain comments + $uri = '#xpointer(id(' . $id . '))'; + } else { // regular reference, can ignore comments + $uri = '#' . $id; + } + + return new Reference( + new DigestMethod($digestAlg), + new DigestValue(Security::hash($digestAlg, $canonicalDocument)), + $transforms, + null, + null, + $uri + ); + } + + + /** + * Do the actual signing of the document. + * + * Note that this method does not insert the signature in the returned \DOMElement. The signature will be available + * in $this->signature as a \SimpleSAML\XMLSecurity\XML\ds\Signature object, which can then be converted to XML + * calling toXML() on it, passing the \DOMElement value returned here as a parameter. The resulting \DOMElement + * can then be inserted in the position desired. + * + * E.g.: + * $xml = // our XML to sign + * $signedXML = $this->doSign($xml); + * $signedXML->appendChild($this->signature->toXML($signedXML)); + * + * @param \DOMElement $xml The element to sign. + * @return \DOMElement The signed element, without the signature attached to it just yet. + */ + protected function doSign(DOMElement $xml): DOMElement { Assert::notNull( $this->signer, @@ -108,38 +167,18 @@ private function doSign(DOMElement $xml): void new Transform($this->c14nAlg) ]); - $refId = $this->getId(); - $reference = new Reference( - new DigestMethod($digest), - new DigestValue(Security::hash($digest, XML::processTransforms($transforms, $xml))), - $transforms, - null, - null, - ($refId !== null) ? '#' . $refId : null - ); + $canonicalDocument = XML::processTransforms($transforms, $xml); $signedInfo = new SignedInfo( new CanonicalizationMethod($this->c14nAlg), new SignatureMethod($algorithm), - [$reference] + [$this->getReference($digest, $transforms, $xml, $canonicalDocument)] ); $signingData = $signedInfo->canonicalize($this->c14nAlg); $signedData = base64_encode($this->signer->sign($signingData)); $this->signature = new Signature($signedInfo, new SignatureValue($signedData), $this->keyInfo); - } - - - /** - * @param DOMElement $root - * @param DOMNode $node - * @param DOMElement $signature - * @return DOMElement - */ - private function insertBefore(DOMElement $root, DOMNode $node, DOMElement $signature): DOMElement - { - $root->removeChild($signature); - return $root->insertBefore($signature, $node); + return DOMDocumentFactory::fromString($canonicalDocument)->documentElement; } } diff --git a/src/XML/SignedElementInterface.php b/src/XML/SignedElementInterface.php index 6a6024f8..26b16891 100644 --- a/src/XML/SignedElementInterface.php +++ b/src/XML/SignedElementInterface.php @@ -4,7 +4,9 @@ namespace SimpleSAML\XMLSecurity\XML; -use SimpleSAML\XMLSecurity\XMLSecurityKey; +use SimpleSAML\XMLSecurity\Alg\SignatureAlgorithm; +use SimpleSAML\XMLSecurity\Key\AbstractKey; +use SimpleSAML\XMLSecurity\XML\ds\Signature; /** * An interface describing signed elements. @@ -14,12 +16,40 @@ interface SignedElementInterface extends CanonicalizableElementInterface { /** - * Retrieve certificates that sign this element. + * Get the ID of this element. * - * @return array Array with certificates. + * When this method returns null, the signature created for this object will reference the entire document. + * + * @return string|null The ID of this element, or null if we don't have one. + */ + public function getId(): ?string; + + + /** + * Retrieve the signature in this object, if any. + * + * @return Signature|null + */ + public function getSignature(): ?Signature; + + + /** + * Retrieve the key used to validate the signature in this object. + * + * Warning: + * + * @return \SimpleSAML\XMLSecurity\Key\AbstractKey The key that validated the signature in this object. * @throws \Exception if an error occurs while trying to extract the public key from a certificate. */ - public function getValidatingCertificates(): array; + public function getValidatingKey(): ?AbstractKey; + + + /** + * Whether this object is signed or not. + * + * @return bool + */ + public function isSigned(): bool; /** @@ -28,8 +58,14 @@ public function getValidatingCertificates(): array; * If no signature is present, false is returned. If a signature is present, * but cannot be verified, an exception will be thrown. * - * @param \SimpleSAML\XMLSecurity\XMLSecurityKey $key The key we should check against. - * @return \SimpleSAML\XMLSecurity\XML\SignedElementInterface The signed element if we can verify the signature. + * @param \SimpleSAML\XMLSecurity\Alg\SignatureAlgorithm|null $verifier The verifier to use to verify the signature. + * If null, attempt to verify it with the KeyInfo information in the signature. + * @return \SimpleSAML\XMLSecurity\XML\SignedElementInterface The object processed again from its canonicalised + * representation verified by the signature. + * @throws \SimpleSAML\XMLSecurity\Exception\NoSignatureFound if the object is not signed. + * @throws \SimpleSAML\XMLSecurity\Exception\InvalidArgumentException if no key is passed and there is no KeyInfo + * in the signature. + * @throws \SimpleSAML\XMLSecurity\Exception\RuntimeException if the signature fails to validate. */ - public function validate(XMLSecurityKey $key): SignedElementInterface; + public function verify(SignatureAlgorithm $verifier = null): SignedElementInterface; } diff --git a/src/XML/SignedElementTrait.php b/src/XML/SignedElementTrait.php index b0195b1f..cf828484 100644 --- a/src/XML/SignedElementTrait.php +++ b/src/XML/SignedElementTrait.php @@ -4,11 +4,22 @@ namespace SimpleSAML\XMLSecurity\XML; -use Exception; use SimpleSAML\Assert\Assert; +use SimpleSAML\XML\DOMDocumentFactory; +use SimpleSAML\XMLSecurity\Alg\Signature\SignatureAlgorithmFactory; +use SimpleSAML\XMLSecurity\Alg\SignatureAlgorithm; +use SimpleSAML\XMLSecurity\Constants; +use SimpleSAML\XMLSecurity\Exception\InvalidArgumentException; +use SimpleSAML\XMLSecurity\Exception\NoSignatureFound; +use SimpleSAML\XMLSecurity\Exception\RuntimeException; +use SimpleSAML\XMLSecurity\Key\AbstractKey; +use SimpleSAML\XMLSecurity\Utils\Security; +use SimpleSAML\XMLSecurity\Utils\XML; +use SimpleSAML\XMLSecurity\Utils\XPath; +use SimpleSAML\XMLSecurity\XML\ds\Reference; use SimpleSAML\XMLSecurity\XML\ds\Signature; -use SimpleSAML\XMLSecurity\XML\SignedElementInterface; -use SimpleSAML\XMLSecurity\XMLSecurityKey; +use SimpleSAML\XMLSecurity\XML\ds\X509Certificate; +use SimpleSAML\XMLSecurity\XML\ds\X509Data; /** * Helper trait for processing signed elements. @@ -17,12 +28,21 @@ */ trait SignedElementTrait { + use CanonicalizableElementTrait; + /** * The signature of this element. * - * @var \SimpleSAML\XMLSecurity\XML\ds\Signature $signature + * @var \SimpleSAML\XMLSecurity\XML\ds\Signature|null $signature + */ + protected ?Signature $signature = null; + + /** + * The key that successfully validates the signature in this object. + * + * @var \SimpleSAML\XMLSecurity\Key\AbstractKey|null */ - protected Signature $signature; + private ?AbstractKey $validatingKey = null; /** @@ -47,6 +67,92 @@ protected function setSignature(Signature $signature): void } + /** + * Make sure the given Reference points to the original XML given. + */ + private function validateReferenceUri(Reference $reference, \DOMElement $xml): void + { + if (in_array( + $this->signature->getSignedInfo()->getCanonicalizationMethod()->getAlgorithm(), + [ + Constants::C14N_INCLUSIVE_WITH_COMMENTS, + Constants::C14N_EXCLUSIVE_WITH_COMMENTS, + ] + ) && !$reference->isXPointer() + ) { // canonicalization with comments used, but reference wasn't an xpointer! + throw new RuntimeException('Invalid reference for canonicalization algorithm.'); + } + + $id = $this->getId(); + $uri = $reference->getURI(); + + if (empty($uri) || $uri === '#xpointer(/)') { // same-document reference + Assert::true( + $xml->isSameNode($xml->ownerDocument->documentElement), + 'Cannot use document reference when element is not the root of the document.', + RuntimeException::class + + ); + } else { // short-name or scheme-based xpointer + Assert::notEmpty( + $id, + 'Reference points to an element, but given element does not have an ID.', + RuntimeException::class + ); + Assert::oneOf( + $uri, + [ + '#' . $id, + '#xpointer(id(' . $id . '))' + ], + 'Reference does not point to given element.', + RuntimeException::class + ); + } + } + + + /** + * @return \SimpleSAML\XMLSecurity\XML\SignedElementInterface + */ + private function validateReference(): SignedElementInterface + { + /** @var \SimpleSAML\XMLSecurity\XML\ds\Signature $this->signature */ + $signedInfo = $this->signature->getSignedInfo(); + $references = $signedInfo->getReferences(); + Assert::count( + $references, + 1, + 'Exactly one reference expected in signature.', + RuntimeException::class + ); + $reference = array_pop($references); + + $xml = $this->getOriginalXML(); + $this->validateReferenceUri($reference, $xml); + + $xp = XPath::getXPath($xml->ownerDocument); + $sigNode = $xp->query('child::ds:Signature', $xml); + Assert::count( + $sigNode, + 1, + 'None or more than one signature found in object.', + RuntimeException::class + ); + $xml->removeChild($sigNode->item(0)); + + $data = XML::processTransforms($reference->getTransforms(), $xml); + $digest = Security::hash($reference->getDigestMethod()->getAlgorithm(), $data, false); + + if (Security::compareStrings($digest, base64_decode($reference->getDigestValue()->getRawContent())) !== true) { + throw new RuntimeException('Failed to validate signature.'); + } + + $verifiedXml = DOMDocumentFactory::fromString($data); + return static::fromXML($verifiedXml->documentElement); + } + + /** * Validate this element against a public key. * @@ -54,56 +160,126 @@ protected function setSignature(Signature $signature): void * signature we can validate. An exception is thrown if the signature * validation fails. * - * @param \SimpleSAML\XMLSecurity\XMLSecurityKey $key The key we should check against. + * @param \SimpleSAML\XMLSecurity\Alg\SignatureAlgorithm|null $verifier The verifier to use to verify the signature. + * If null, attempt to verify it with the KeyInfo information in the signature. + * * @return \SimpleSAML\XMLSecurity\XML\SignedElementInterface The Signed element if it was validated. - * @throws \Exception */ - public function validate(XMLSecurityKey $key): SignedElementInterface + private function verifyInternal(SignatureAlgorithm $verifier): SignedElementInterface { - if ($this->signature === null) { - throw new Exception("Unsigned element"); + /** @var \SimpleSAML\XMLSecurity\XML\ds\Signature $this->signature */ + $signedInfo = $this->signature->getSignedInfo(); + $c14nAlg = $signedInfo->getCanonicalizationMethod()->getAlgorithm(); + $c14nSignedInfo = $signedInfo->canonicalize($c14nAlg); + /** @var SignedElementInterface $ref */ + $ref = $this->validateReference(); + + if ($verifier->verify( + $c14nSignedInfo, // the canonicalized ds:SignedInfo element (plaintext) + base64_decode($this->signature->getSignatureValue()->getRawContent()) // the actual signature + )) { + /* + * validateReference() returns an object of the same class using this trait. This means the validatingKey + * property is available, and we can set it on the newly created object because we are in the same class, + * even thought the property itself is private. + */ + /** @psalm-suppress NoInterfaceProperties */ + $ref->validatingKey = $verifier->getKey(); + return $ref; } + throw new RuntimeException('Failed to validate signature.'); + } - Assert::eq( - $key->getAlgorithm(), - $this->signature->getAlgorithm(), - 'Algorithm provided in key does not match algorithm used in signature.' - ); - // check the signature - $signer = $this->signature->getSigner(); - if ($signer->verify($key) === 1) { - return $this->getElement(); - } + /** + * Retrieve certificates that sign this element. + * + * @return \SimpleSAML\XMLSecurity\Key\AbstractKey|null The key that successfully validated this signature. + */ + public function getValidatingKey(): ?AbstractKey + { + return $this->validatingKey; + } - throw new Exception("Unable to validate Signature"); + + /** + * Whether this object is signed or not. + * + * @return bool + */ + public function isSigned(): bool + { + return $this->signature !== null; } /** - * Retrieve certificates that sign this element. + * Verify the signature in this object. * - * @return array Array with certificates. - * @throws \Exception if an error occurs while trying to extract the public key from a certificate. + * If no signature is present, false is returned. If a signature is present, + * but cannot be verified, an exception will be thrown. + * + * @param \SimpleSAML\XMLSecurity\Alg\SignatureAlgorithm|null $verifier The verifier to use to verify the signature. + * If null, attempt to verify it with the KeyInfo information in the signature. + * @return \SimpleSAML\XMLSecurity\XML\SignedElementInterface The object processed again from its canonicalised + * representation verified by the signature. + * @throws \SimpleSAML\XMLSecurity\Exception\NoSignatureFound if the object is not signed. + * @throws \SimpleSAML\XMLSecurity\Exception\InvalidArgumentException if no key is passed and there is no KeyInfo + * in the signature. + * @throws \SimpleSAML\XMLSecurity\Exception\RuntimeException if the signature fails to validate. */ - public function getValidatingCertificates(): array + public function verify(SignatureAlgorithm $verifier = null): SignedElementInterface { - $ret = []; - foreach ($this->signature->getCertificates() as $cert) { - // extract the public key from the certificate for validation. - $key = new XMLSecurityKey($this->signature->getAlgorithm(), ['type' => 'public']); - $key->loadKey($cert); - - try { - // check the signature. - if ($this->validate($key)) { - $ret[] = $cert; + if (!$this->isSigned()) { + throw new NoSignatureFound(); + } + + $keyInfo = $this->signature->getKeyInfo(); + $algId = $this->signature->getSignedInfo()->getSignatureMethod()->getAlgorithm(); + if ($verifier === null && $keyInfo === null) { + throw new InvalidArgumentException('No key or KeyInfo available for signature verification.'); + } + + if ($verifier !== null) { + // verify using given key + // TODO: make this part of the condition, so that we support using this verifier to decrypt an encrypted key + Assert::eq( + $verifier->getAlgorithmId(), + $algId, + 'Algorithm provided in key does not match algorithm used in signature.' + ); + + return $this->verifyInternal($verifier); + } + + $factory = new SignatureAlgorithmFactory(); + foreach ($keyInfo->getInfo() as $info) { + if (!$info instanceof X509Data) { + continue; + } + + /** @var \SimpleSAML\XMLSecurity\XML\ds\X509Data $info */ + foreach ($info->getData() as $data) { + if (!$data instanceof X509Certificate) { + // not supported + continue; + } + + // build a valid PEM for the certificate + $cert = \SimpleSAML\XMLSecurity\Key\X509Certificate::PEM_HEADER . "\n" . + $data->getRawContent() . "\n" . + \SimpleSAML\XMLSecurity\Key\X509Certificate::PEM_FOOTER; + + $key = new \SimpleSAML\XMLSecurity\Key\X509Certificate($cert); + $verifier = $factory->getAlgorithm($algId, $key); + + try { + return $this->verifyInternal($verifier); + } catch (RuntimeException $e) { + // failed to validate with this certificate, try with other, if any } - } catch (Exception $e) { - // this certificate does not sign this element. } } - - return $ret; + throw new RuntimeException('Failed to validate signature.'); } } diff --git a/tests/XML/CustomSignable.php b/tests/XML/CustomSignable.php index 7c0b38d2..a9a6dacd 100644 --- a/tests/XML/CustomSignable.php +++ b/tests/XML/CustomSignable.php @@ -8,18 +8,20 @@ use SimpleSAML\Assert\Assert; use SimpleSAML\XML\AbstractXMLElement; use SimpleSAML\XML\Exception\InvalidDOMElementException; -use SimpleSAML\XML\Exception\MissingElementException; use SimpleSAML\XML\Exception\TooManyElementsException; use SimpleSAML\XMLSecurity\XML\ds\Signature; use SimpleSAML\XMLSecurity\XML\SignableElementInterface; use SimpleSAML\XMLSecurity\XML\SignableElementTrait; +use SimpleSAML\XMLSecurity\XML\SignedElementInterface; +use SimpleSAML\XMLSecurity\XML\SignedElementTrait; /** * @package simplesamlphp/xml-security */ -class CustomSignable extends AbstractXMLElement implements SignableElementInterface +class CustomSignable extends AbstractXMLElement implements SignableElementInterface, SignedElementInterface { use SignableElementTrait; + use SignedElementTrait; /** @var string */ public const NS = 'urn:ssp:custom'; @@ -27,8 +29,11 @@ class CustomSignable extends AbstractXMLElement implements SignableElementInterf /** @var string */ public const NS_PREFIX = 'ssp'; - /** @var \DOMElement $element */ - protected \DOMElement $element; + /** @var string|null */ + public ?string $id = null; + + /** @var \DOMElement $xml */ + protected \DOMElement $xml; /** @var bool */ protected bool $formatOutput = false; @@ -40,10 +45,11 @@ class CustomSignable extends AbstractXMLElement implements SignableElementInterf /** * Constructor * - * @param \DOMElement $elt + * @param \DOMElement $xml */ - public function __construct(DOMElement $elt) { - $this->setElement($elt); + private function __construct(DOMElement $xml, ?string $id) { + $this->setXML($xml); + $this->id = $id; } @@ -70,24 +76,24 @@ public static function getNamespacePrefix(): string /** - * Collect the value of the $element property + * Get the XML element. * * @return \DOMElement */ - public function getElement(): DOMElement + public function getXML(): DOMElement { - return $this->element; + return $this->xml; } /** - * Set the value of the elment-property + * Set the XML element. * - * @param \DOMElement $elt + * @param \DOMElement $xml */ - private function setElement(DOMElement $elt): void + private function setXML(DOMElement $xml): void { - $this->element = $elt; + $this->xml = $xml; } @@ -96,7 +102,16 @@ private function setElement(DOMElement $elt): void */ public function getId(): ?string { - return null; + return $this->id; + } + + + /** + * @inheritDoc + */ + protected function getOriginalXML(): DOMElement + { + return $this->xml; } @@ -113,13 +128,11 @@ public static function fromXML(DOMElement $xml): object Assert::same($xml->localName, 'CustomSignable', InvalidDOMElementException::class); Assert::same($xml->namespaceURI, static::NS, InvalidDOMElementException::class); - Assert::minCount($xml->childNodes, 1, MissingElementException::class); - Assert::maxCount($xml->childNodes, 2, TooManyElementsException::class); - + $id = self::getAttribute($xml, 'id', null); $signature = Signature::getChildrenOfClass($xml); Assert::maxCount($signature, 1, TooManyElementsException::class); - $customSignable = new self($xml->childNodes[(empty($signature) ? 0 : 1)]); + $customSignable = new self($xml, $id); if (!empty($signature)) { $customSignable->signature = $signature[0]; } @@ -132,22 +145,16 @@ public static function fromXML(DOMElement $xml): object * * @param \DOMElement|null $parent The parent element to append this CustomSignable to. * @return \DOMElement The XML element after adding the data corresponding to this CustomSignable. + * @throws \Exception */ public function toXML(DOMElement $parent = null): DOMElement { - /** @psalm-var \DOMDocument $e->ownerDocument */ - $e = $this->instantiateParentElement($parent); - - $node = $e->appendChild($e->ownerDocument->importNode($this->element, true)); - if ($this->signer !== null) { - $this->doSign($e); - } - - if ($this->signature !== null) { - $this->insertBefore($e, $node, $this->signature->toXML($e)); + $signedXML = $this->doSign($this->xml); + $signedXML->insertBefore($this->signature->toXML($signedXML), $signedXML->firstChild); + return $signedXML; } - return $e; + return $this->xml; } } diff --git a/tests/XML/SignableElementTest.php b/tests/XML/SignableElementTest.php index 154f8fe5..d62cb11a 100644 --- a/tests/XML/SignableElementTest.php +++ b/tests/XML/SignableElementTest.php @@ -9,6 +9,7 @@ use SimpleSAML\XML\DOMDocumentFactory; use SimpleSAML\XMLSecurity\Alg\Signature\SignatureAlgorithmFactory; use SimpleSAML\XMLSecurity\Constants; +use SimpleSAML\XMLSecurity\Exception\RuntimeException; use SimpleSAML\XMLSecurity\Key\PrivateKey; use SimpleSAML\XMLSecurity\XML\ds\KeyInfo; use SimpleSAML\XMLSecurity\XML\ds\X509Certificate; @@ -74,13 +75,12 @@ public function setUp(): void /** * Test that signing produces the expected output. + * + * In this test we try to sign an entire document, since the element is the root of it, and doesn't have an ID. */ - public function testSigning(): void + public function testSigningDocument(): void { - $document = DOMDocumentFactory::fromString( - 'Chunk' - ); - $customSignable = new CustomSignable($document->documentElement); + $customSignable = CustomSignable::fromXML($this->xmlRepresentation->documentElement); $this->assertFalse($customSignable->isEmptyElement()); $factory = new SignatureAlgorithmFactory(); @@ -99,5 +99,164 @@ public function testSigning(): void strval($customSignable) ); } + + + /** + * Test that signing an element works. + * + * This test implies signing an element. Since the element itself has an ID, we use that to create our reference. + */ + public function testSigningElement(): void + { + $xml = DOMDocumentFactory::fromString( + 'Chunk' . + '' + ); + $customSignable = CustomSignable::fromXML($xml->documentElement); + $this->assertFalse($customSignable->isEmptyElement()); + + $factory = new SignatureAlgorithmFactory(); + $signer = $factory->getAlgorithm(Constants::SIG_RSA_SHA256, $this->key); + + $keyInfo = new KeyInfo([ + new X509Data([ + new X509Certificate($this->certificate) + ]) + ]); + + $customSignable->sign($signer, Constants::C14N_EXCLUSIVE_WITHOUT_COMMENTS, $keyInfo); + $signed = DOMDocumentFactory::fromFile( + dirname(dirname(__FILE__)) . '/resources/xml/custom_CustomSignedElement.xml' + ); + + $this->assertEquals( + $signed->saveXML($signed->documentElement), + strval($customSignable) + ); + } + + + /** + * Test that signing a document with comments works. + * + * This tests attempts to sign a document with comments, and verifies that the resulting reference is an xpointer + * pointing to the root of the document. + */ + public function testSigningDocumentWithComments(): void + { + $xml = DOMDocumentFactory::fromString( + 'Chunk' . + '' + ); + $customSignable = CustomSignable::fromXML($xml->documentElement); + $this->assertFalse($customSignable->isEmptyElement()); + + $factory = new SignatureAlgorithmFactory(); + $signer = $factory->getAlgorithm(Constants::SIG_RSA_SHA256, $this->key); + + $keyInfo = new KeyInfo([ + new X509Data([ + new X509Certificate($this->certificate) + ]) + ]); + + $customSignable->sign($signer, Constants::C14N_EXCLUSIVE_WITH_COMMENTS, $keyInfo); + $signed = DOMDocumentFactory::fromFile( + dirname(dirname(__FILE__)) . '/resources/xml/custom_CustomSignedWithComments.xml' + ); + + $this->assertEquals( + $signed->saveXML($signed->documentElement), + strval($customSignable) + ); + } + + + /** + * Test that signing an element with an ID including comments works. + * + * This test attempts to sign an element with an ID, using exclusive canonicalization with comments. The resulting + * reference should be an xpointer specifying the ID of the element. + */ + public function testSigningElementWithIdAndComments(): void + { + $xml = DOMDocumentFactory::fromString( + 'Chunk' . + '' + ); + $customSignable = CustomSignable::fromXML($xml->documentElement); + $this->assertFalse($customSignable->isEmptyElement()); + + $factory = new SignatureAlgorithmFactory(); + $signer = $factory->getAlgorithm(Constants::SIG_RSA_SHA256, $this->key); + + $keyInfo = new KeyInfo([ + new X509Data([ + new X509Certificate($this->certificate) + ]) + ]); + + $customSignable->sign($signer, Constants::C14N_EXCLUSIVE_WITH_COMMENTS, $keyInfo); + $signed = DOMDocumentFactory::fromFile( + dirname(dirname(__FILE__)) . '/resources/xml/custom_CustomSignedWithCommentsAndId.xml' + ); + + $this->assertEquals( + $signed->saveXML($signed->documentElement), + strval($customSignable) + ); + } + + + /** + * Test that signing an object with a document reference fails if there's no document root. + * + * This test attempts to sign a document with an element without an ID that's not marked as its root. This should + * fail since we cannot use a self-document reference (because the element is not the root), and we don't have an + * ID for the element, so we have no way to refer to it. + */ + public function testSigningDocumentWithoutRoot(): void + { + $doc = DOMDocumentFactory::create(); + $node = $doc->importNode($this->xmlRepresentation->documentElement, true); + $customSignable = CustomSignable::fromXML($node); + $factory = new SignatureAlgorithmFactory(); + $signer = $factory->getAlgorithm(Constants::SIG_RSA_SHA256, $this->key); + $customSignable->sign($signer); + + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('Cannot create a document reference without a root element in the document.'); + $customSignable->toXML(); + } + + + /** + * Test that signing an object with a document reference fails if the object is not the document's root. + * + * This test attempts to sign an element without an ID, forcing us to use a self-document reference. However, the + * document contains another element before the one we try to sign, and that other element is marked as the root + * of the document. We cannot therefore create the self-document reference because the element we try to sign is + * not the root, and we should fail accordingly. + */ + public function testSigningWithDifferentRoot(): void + { + $doc = DOMDocumentFactory::fromString('bar'); + $node = $doc->importNode($this->xmlRepresentation->documentElement, true); + $doc->appendChild($node); + $customSignable = CustomSignable::fromXML($node); + $factory = new SignatureAlgorithmFactory(); + $signer = $factory->getAlgorithm(Constants::SIG_RSA_SHA256, $this->key); + $customSignable->sign($signer); + + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage( + 'Cannot create a document reference when signing an object that is not the root of the document. Please ' . + 'give your object an identifier.' + ); + $customSignable->toXML($doc->documentElement); + } + + + } diff --git a/tests/XML/SignedElementTest.php b/tests/XML/SignedElementTest.php index ecd1ce3f..627190dd 100644 --- a/tests/XML/SignedElementTest.php +++ b/tests/XML/SignedElementTest.php @@ -5,9 +5,12 @@ namespace SimpleSAML\XMLSecurity\Test\XML; use PHPUnit\Framework\TestCase; -use SimpleSAML\Test\XML\SerializableXMLTestTrait; use SimpleSAML\XML\DOMDocumentFactory; -use SimpleSAML\XMLSecurity\Key\PrivateKey; +use SimpleSAML\XMLSecurity\Alg\Signature\SignatureAlgorithmFactory; +use SimpleSAML\XMLSecurity\Constants; +use SimpleSAML\XMLSecurity\Exception\RuntimeException; +use SimpleSAML\XMLSecurity\Key\X509Certificate; +use SimpleSAML\XMLSecurity\XML\ds\Signature; use function array_pop; use function array_shift; @@ -29,49 +32,186 @@ */ final class SignedElementTest extends TestCase { - use SerializableXMLTestTrait; - /** @var string */ private string $certificate; - /** @var PrivateKey */ - private PrivateKey $key; + /** @var \DOMElement */ + private \DOMElement $signedDocumentWithComments; + + /** @var \DOMElement */ + private \DOMElement $signedDocument; + + /** @var \DOMElement */ + private \DOMElement $tamperedDocument; /** */ public function setUp(): void { - $this->testedClass = CustomSignable::class; + $this->signedDocumentWithComments = DOMDocumentFactory::fromFile( + dirname(dirname(__FILE__)) . '/resources/xml/custom_CustomSignedWithComments.xml' + )->documentElement; - $this->xmlRepresentation = DOMDocumentFactory::fromFile( + $this->signedDocument = DOMDocumentFactory::fromFile( dirname(dirname(__FILE__)) . '/resources/xml/custom_CustomSigned.xml' - ); + )->documentElement; - $certificate = file_get_contents( - dirname(dirname(__FILE__)) . '/resources/certificates/rsa-pem/selfsigned.simplesamlphp.org.crt' - ); - $certificateLines = explode("\n", trim($certificate)); - array_pop($certificateLines); - array_shift($certificateLines); - $this->certificate = join("\n", $certificateLines); + $this->tamperedDocument = DOMDocumentFactory::fromFile( + dirname(dirname(__FILE__)) . '/resources/xml/custom_CustomSignedTampered.xml' + )->documentElement; - $this->key = PrivateKey::fromFile( - dirname(dirname(__FILE__)) . '/resources/certificates/rsa-pem/selfsigned.simplesamlphp.org_nopasswd.key' + $this->certificate = file_get_contents( + dirname(dirname(__FILE__)) . '/resources/certificates/rsa-pem/selfsigned.simplesamlphp.org.crt' ); } /** + * Test creating a signed object from its XML representation. */ public function testUnmarshalling(): void { - $customSigned = CustomSignable::fromXML($this->xmlRepresentation->documentElement); + $customSigned = CustomSignable::fromXML($this->signedDocument); $this->assertEquals( - $this->xmlRepresentation->saveXML($this->xmlRepresentation->documentElement), + $this->signedDocument->ownerDocument->saveXML($this->signedDocument), strval($customSigned) ); } + + + /** + * Test the verification of a signature with a given key. + */ + public function testSuccessfulVerifyingWithGivenKey(): void + { + $customSigned = CustomSignable::fromXML($this->signedDocument); + + $this->assertTrue($customSigned->isSigned()); + $signature = $customSigned->getSignature(); + $this->assertInstanceOf(Signature::class, $signature); + $sigAlg = $signature->getSignedInfo()->getSignatureMethod()->getAlgorithm(); + $this->assertEquals(Constants::SIG_RSA_SHA256, $sigAlg); + $factory = new SignatureAlgorithmFactory(); + $certificate = new X509Certificate($this->certificate); + $verifier = $factory->getAlgorithm($sigAlg, $certificate); + + $verified = $customSigned->verify($verifier); + $this->assertInstanceOf(CustomSignable::class, $verified); + $this->assertFalse($verified->isSigned()); + $this->assertEquals( + 'Chunk', + strval($verified) + ); + $this->assertEquals($certificate, $verified->getValidatingKey()); + } + + + /** + * Test the verification of a signature without passing a key, just what's in KeyInfo + */ + public function testSuccessfulVerifyingWithoutKey(): void + { + $customSigned = CustomSignable::fromXML($this->signedDocument); + + $this->assertTrue($customSigned->isSigned()); + $signature = $customSigned->getSignature(); + $this->assertInstanceOf(Signature::class, $signature); + $sigAlg = $signature->getSignedInfo()->getSignatureMethod()->getAlgorithm(); + $this->assertEquals(Constants::SIG_RSA_SHA256, $sigAlg); + $certificate = new X509Certificate($this->certificate); + + $verified = $customSigned->verify(); + $this->assertInstanceOf(CustomSignable::class, $verified); + $this->assertFalse($verified->isSigned()); + $this->assertEquals( + 'Chunk', + strval($verified) + ); + $validatingKey = $verified->getValidatingKey(); + $this->assertInstanceOf(X509Certificate::class, $validatingKey); + /** @var \SimpleSAML\XMLSecurity\Key\X509Certificate $validatingKey */ + $this->assertEquals($certificate->getCertificate(), $validatingKey->getCertificate()); + } + + + /** + * Test that verifying a tampered signature, without giving a key for verification, fails as expected. + */ + public function testVerifyingTamperedSignatureWithoutKeyFails(): void + { + $customSigned = CustomSignable::fromXML($this->tamperedDocument); + + $this->assertTrue($customSigned->isSigned()); + $signature = $customSigned->getSignature(); + $this->assertInstanceOf(Signature::class, $signature); + $sigAlg = $signature->getSignedInfo()->getSignatureMethod()->getAlgorithm(); + $this->assertEquals(Constants::SIG_RSA_SHA256, $sigAlg); + + $this->expectException(RuntimeException::class); + $this->expectDeprecationMessage('Failed to validate signature.'); + $customSigned->verify(); + } + + + /** + * Test that verifying a tampered signature with a given key fails as expected. + */ + public function testVerifyingTamperedSignatureWithKeyFails(): void + { + $customSigned = CustomSignable::fromXML($this->tamperedDocument); + + + $this->assertTrue($customSigned->isSigned()); + $signature = $customSigned->getSignature(); + $this->assertInstanceOf(Signature::class, $signature); + $sigAlg = $signature->getSignedInfo()->getSignatureMethod()->getAlgorithm(); + $this->assertEquals(Constants::SIG_RSA_SHA256, $sigAlg); + $factory = new SignatureAlgorithmFactory(); + $certificate = new X509Certificate($this->certificate); + $verifier = $factory->getAlgorithm($sigAlg, $certificate); + + $this->expectException(RuntimeException::class); + $this->expectDeprecationMessage('Failed to validate signature.'); + $customSigned->verify($verifier); + } + + + + /** + * Test the verification of a signature with a given key, for an element that has comments in it. + * + * In this case, canonicalization must remove the comments, and the object resulting the verification must NOT + * have them. + */ + public function testSuccessfulVerifyingDocumentWithComments(): void + { + $customSigned = CustomSignable::fromXML($this->signedDocumentWithComments); + + $this->assertTrue($customSigned->isSigned()); + $signature = $customSigned->getSignature(); + $this->assertInstanceOf(Signature::class, $signature); + $sigAlg = $signature->getSignedInfo()->getSignatureMethod()->getAlgorithm(); + $this->assertEquals(Constants::SIG_RSA_SHA256, $sigAlg); + $factory = new SignatureAlgorithmFactory(); + $certificate = new X509Certificate($this->certificate); + $verifier = $factory->getAlgorithm($sigAlg, $certificate); + + // verify first that our dumb object normally retains comments + $this->assertEquals( + $this->signedDocumentWithComments->ownerDocument->saveXML($this->signedDocumentWithComments), + strval($customSigned) + ); + + $verified = $customSigned->verify($verifier); + $this->assertInstanceOf(CustomSignable::class, $verified); + $this->assertFalse($verified->isSigned()); + $this->assertEquals( + 'Chunk', + strval($verified) + ); + $this->assertEquals($certificate, $verified->getValidatingKey()); + } } From ed8ca901ae9a66c35f564ca44a20fe668b742b8f Mon Sep 17 00:00:00 2001 From: Tim van Dijen Date: Sat, 24 Jul 2021 18:55:42 +0200 Subject: [PATCH 74/78] Fix nits --- src/XML/SignableElementTrait.php | 2 ++ src/XML/SignedElementTrait.php | 20 ++++++++++++-------- src/XML/ds/Reference.php | 1 + tests/XML/CustomSignable.php | 2 +- tests/XML/SignedElementTest.php | 7 ++++--- 5 files changed, 20 insertions(+), 12 deletions(-) diff --git a/src/XML/SignableElementTrait.php b/src/XML/SignableElementTrait.php index 7a1ad82d..0cd13599 100644 --- a/src/XML/SignableElementTrait.php +++ b/src/XML/SignableElementTrait.php @@ -25,6 +25,8 @@ use SimpleSAML\XMLSecurity\XML\ds\Transform; use SimpleSAML\XMLSecurity\XML\ds\Transforms; +use function in_array; + /** * Trait SignableElementTrait * diff --git a/src/XML/SignedElementTrait.php b/src/XML/SignedElementTrait.php index cf828484..b47f4ea9 100644 --- a/src/XML/SignedElementTrait.php +++ b/src/XML/SignedElementTrait.php @@ -12,7 +12,7 @@ use SimpleSAML\XMLSecurity\Exception\InvalidArgumentException; use SimpleSAML\XMLSecurity\Exception\NoSignatureFound; use SimpleSAML\XMLSecurity\Exception\RuntimeException; -use SimpleSAML\XMLSecurity\Key\AbstractKey; +use SimpleSAML\XMLSecurity\Key; use SimpleSAML\XMLSecurity\Utils\Security; use SimpleSAML\XMLSecurity\Utils\XML; use SimpleSAML\XMLSecurity\Utils\XPath; @@ -21,6 +21,10 @@ use SimpleSAML\XMLSecurity\XML\ds\X509Certificate; use SimpleSAML\XMLSecurity\XML\ds\X509Data; +use function array_pop; +use function base64_decode; +use function in_array; + /** * Helper trait for processing signed elements. * @@ -42,7 +46,7 @@ trait SignedElementTrait * * @var \SimpleSAML\XMLSecurity\Key\AbstractKey|null */ - private ?AbstractKey $validatingKey = null; + private ?Key\AbstractKey $validatingKey = null; /** @@ -132,14 +136,14 @@ private function validateReference(): SignedElementInterface $this->validateReferenceUri($reference, $xml); $xp = XPath::getXPath($xml->ownerDocument); - $sigNode = $xp->query('child::ds:Signature', $xml); + $sigNode = XPath::xpQuery($xml, 'child::ds:Signature', $xp); Assert::count( $sigNode, 1, 'None or more than one signature found in object.', RuntimeException::class ); - $xml->removeChild($sigNode->item(0)); + $xml->removeChild($sigNode[0]); $data = XML::processTransforms($reference->getTransforms(), $xml); $digest = Security::hash($reference->getDigestMethod()->getAlgorithm(), $data, false); @@ -196,7 +200,7 @@ private function verifyInternal(SignatureAlgorithm $verifier): SignedElementInte * * @return \SimpleSAML\XMLSecurity\Key\AbstractKey|null The key that successfully validated this signature. */ - public function getValidatingKey(): ?AbstractKey + public function getValidatingKey(): ?Key\AbstractKey { return $this->validatingKey; } @@ -266,11 +270,11 @@ public function verify(SignatureAlgorithm $verifier = null): SignedElementInterf } // build a valid PEM for the certificate - $cert = \SimpleSAML\XMLSecurity\Key\X509Certificate::PEM_HEADER . "\n" . + $cert = Key\X509Certificate::PEM_HEADER . "\n" . $data->getRawContent() . "\n" . - \SimpleSAML\XMLSecurity\Key\X509Certificate::PEM_FOOTER; + Key\X509Certificate::PEM_FOOTER; - $key = new \SimpleSAML\XMLSecurity\Key\X509Certificate($cert); + $key = new Key\X509Certificate($cert); $verifier = $factory->getAlgorithm($algId, $key); try { diff --git a/src/XML/ds/Reference.php b/src/XML/ds/Reference.php index a9897d76..cfc47b97 100644 --- a/src/XML/ds/Reference.php +++ b/src/XML/ds/Reference.php @@ -9,6 +9,7 @@ use SimpleSAML\XML\Exception\InvalidDOMElementException; use function array_pop; +use function preg_match; /** * Class representing a ds:Reference element. diff --git a/tests/XML/CustomSignable.php b/tests/XML/CustomSignable.php index a9a6dacd..b5152ee7 100644 --- a/tests/XML/CustomSignable.php +++ b/tests/XML/CustomSignable.php @@ -33,7 +33,7 @@ class CustomSignable extends AbstractXMLElement implements SignableElementInterf public ?string $id = null; /** @var \DOMElement $xml */ - protected \DOMElement $xml; + protected DOMElement $xml; /** @var bool */ protected bool $formatOutput = false; diff --git a/tests/XML/SignedElementTest.php b/tests/XML/SignedElementTest.php index 627190dd..152c0063 100644 --- a/tests/XML/SignedElementTest.php +++ b/tests/XML/SignedElementTest.php @@ -4,6 +4,7 @@ namespace SimpleSAML\XMLSecurity\Test\XML; +use DOMElement; use PHPUnit\Framework\TestCase; use SimpleSAML\XML\DOMDocumentFactory; use SimpleSAML\XMLSecurity\Alg\Signature\SignatureAlgorithmFactory; @@ -36,13 +37,13 @@ final class SignedElementTest extends TestCase private string $certificate; /** @var \DOMElement */ - private \DOMElement $signedDocumentWithComments; + private DOMElement $signedDocumentWithComments; /** @var \DOMElement */ - private \DOMElement $signedDocument; + private DOMElement $signedDocument; /** @var \DOMElement */ - private \DOMElement $tamperedDocument; + private DOMElement $tamperedDocument; /** From 60939ab2067d01746e227d411db6c9702c97b127 Mon Sep 17 00:00:00 2001 From: Tim van Dijen Date: Sat, 24 Jul 2021 19:00:54 +0200 Subject: [PATCH 75/78] Fix nits --- tests/XML/SignableElementTest.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/XML/SignableElementTest.php b/tests/XML/SignableElementTest.php index d62cb11a..799a366b 100644 --- a/tests/XML/SignableElementTest.php +++ b/tests/XML/SignableElementTest.php @@ -4,6 +4,7 @@ namespace SimpleSAML\XMLSecurity\Test\XML; +use DOMDocument; use PHPUnit\Framework\TestCase; use SimpleSAML\Test\XML\SerializableXMLTestTrait; use SimpleSAML\XML\DOMDocumentFactory; @@ -42,7 +43,7 @@ final class SignableElementTest extends TestCase private PrivateKey $key; /** @var \DOMDocument */ - private \DOMDocument $signed; + private DOMDocument $signed; /** From 0f1f17c23c6758ab646ef350c9c340d0984333fd Mon Sep 17 00:00:00 2001 From: Tim van Dijen Date: Sat, 24 Jul 2021 19:01:53 +0200 Subject: [PATCH 76/78] Fix file name --- ...CommentsAndId.xml => custom_CustomSignedWithCommentsAndId.xml} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tests/resources/xml/{custom_customSignedWithCommentsAndId.xml => custom_CustomSignedWithCommentsAndId.xml} (100%) diff --git a/tests/resources/xml/custom_customSignedWithCommentsAndId.xml b/tests/resources/xml/custom_CustomSignedWithCommentsAndId.xml similarity index 100% rename from tests/resources/xml/custom_customSignedWithCommentsAndId.xml rename to tests/resources/xml/custom_CustomSignedWithCommentsAndId.xml From 8123f41d916f5e636705b33a222b31528b7123c4 Mon Sep 17 00:00:00 2001 From: Tim van Dijen Date: Sat, 24 Jul 2021 19:15:11 +0200 Subject: [PATCH 77/78] Fix phpcs issues --- .../Signature/SignatureAlgorithmFactory.php | 3 +- src/Key/PublicKey.php | 9 +- src/XML/CanonicalizableElementTrait.php | 2 +- src/XML/SignedElementTrait.php | 17 +- src/XML/ds/DigestMethod.php | 3 +- src/XML/ds/SignedInfo.php | 6 +- src/XML/ec/AbstractEcElement.php | 3 +- tests/XML/CustomSignable.php | 6 +- tests/XML/SignableElementTest.php | 4 - tests/XML/SignedElementTest.php | 148 +++++++++--------- tests/XML/ds/SignatureValueTest.php | 3 +- tests/XML/ds/X509IssuerSerialTest.php | 6 +- tests/XML/ds/XPathTest.php | 1 - tests/XML/xenc/EncryptedDataTest.php | 5 +- tests/XML/xenc/EncryptedKeyTest.php | 5 +- 15 files changed, 121 insertions(+), 100 deletions(-) diff --git a/src/Alg/Signature/SignatureAlgorithmFactory.php b/src/Alg/Signature/SignatureAlgorithmFactory.php index 40002bf8..a23574b5 100644 --- a/src/Alg/Signature/SignatureAlgorithmFactory.php +++ b/src/Alg/Signature/SignatureAlgorithmFactory.php @@ -114,7 +114,8 @@ public static function registerAlgorithm(string $className): void Assert::subclassOf( $className, AbstractSigner::class, - 'Cannot register algorithm "' . $className . '", must implement ' . "\SimpleSAML\XMLSecurity\Alg\SignatureInterface.", + 'Cannot register algorithm "' . $className . '", must implement ' + . "\SimpleSAML\XMLSecurity\Alg\SignatureInterface.", InvalidArgumentException::class ); diff --git a/src/Key/PublicKey.php b/src/Key/PublicKey.php index 73cf60d4..232b60f2 100644 --- a/src/Key/PublicKey.php +++ b/src/Key/PublicKey.php @@ -95,7 +95,14 @@ protected static function makeASN1Segment(int $type, string $string): ?string } elseif ($length < self::ASN1_SIZE_256) { $output = sprintf("%c%c%c%s", $type, self::ASN1_SIZE_128 + 1, $length, $string); } else { // ($length < self::ASN1_SIZE_65535) - $output = sprintf("%c%c%c%c%s", $type, self::ASN1_SIZE_128 +2, $length / 0x0100, $length % 0x0100, $string); + $output = sprintf( + "%c%c%c%c%s", + $type, + self::ASN1_SIZE_128 + 2, + $length / 0x0100, + $length % 0x0100, + $string + ); } return $output; diff --git a/src/XML/CanonicalizableElementTrait.php b/src/XML/CanonicalizableElementTrait.php index e141060b..3eea3bc4 100644 --- a/src/XML/CanonicalizableElementTrait.php +++ b/src/XML/CanonicalizableElementTrait.php @@ -55,4 +55,4 @@ public function serialize(): string $xml = $this->getOriginalXML(); return $xml->ownerDocument->saveXML($xml); } -} \ No newline at end of file +} diff --git a/src/XML/SignedElementTrait.php b/src/XML/SignedElementTrait.php index b47f4ea9..49d05fb8 100644 --- a/src/XML/SignedElementTrait.php +++ b/src/XML/SignedElementTrait.php @@ -76,13 +76,15 @@ protected function setSignature(Signature $signature): void */ private function validateReferenceUri(Reference $reference, \DOMElement $xml): void { - if (in_array( + if ( + in_array( $this->signature->getSignedInfo()->getCanonicalizationMethod()->getAlgorithm(), [ Constants::C14N_INCLUSIVE_WITH_COMMENTS, Constants::C14N_EXCLUSIVE_WITH_COMMENTS, ] - ) && !$reference->isXPointer() + ) + && !$reference->isXPointer() ) { // canonicalization with comments used, but reference wasn't an xpointer! throw new RuntimeException('Invalid reference for canonicalization algorithm.'); } @@ -95,7 +97,6 @@ private function validateReferenceUri(Reference $reference, \DOMElement $xml): v $xml->isSameNode($xml->ownerDocument->documentElement), 'Cannot use document reference when element is not the root of the document.', RuntimeException::class - ); } else { // short-name or scheme-based xpointer Assert::notEmpty( @@ -178,10 +179,12 @@ private function verifyInternal(SignatureAlgorithm $verifier): SignedElementInte /** @var SignedElementInterface $ref */ $ref = $this->validateReference(); - if ($verifier->verify( - $c14nSignedInfo, // the canonicalized ds:SignedInfo element (plaintext) - base64_decode($this->signature->getSignatureValue()->getRawContent()) // the actual signature - )) { + if ( + $verifier->verify( + $c14nSignedInfo, // the canonicalized ds:SignedInfo element (plaintext) + base64_decode($this->signature->getSignatureValue()->getRawContent()) // the actual signature + ) + ) { /* * validateReference() returns an object of the same class using this trait. This means the validatingKey * property is available, and we can set it on the newly created object because we are in the same class, diff --git a/src/XML/ds/DigestMethod.php b/src/XML/ds/DigestMethod.php index 216677d0..926fb1e1 100644 --- a/src/XML/ds/DigestMethod.php +++ b/src/XML/ds/DigestMethod.php @@ -95,7 +95,8 @@ public function getElements(): array * Set the value of the elements-property * * @param \SimpleSAML\XML\Chunk[] $elements - * @throws \SimpleSAML\Assert\AssertionFailedException if the supplied array contains anything other than Chunk objects + * @throws \SimpleSAML\Assert\AssertionFailedException + * if the supplied array contains anything other than Chunk objects */ private function setElements(array $elements): void { diff --git a/src/XML/ds/SignedInfo.php b/src/XML/ds/SignedInfo.php index 2df6ab31..eb4359b7 100644 --- a/src/XML/ds/SignedInfo.php +++ b/src/XML/ds/SignedInfo.php @@ -185,7 +185,11 @@ public static function fromXML(DOMElement $xml): object $Id = self::getAttribute($xml, 'Id', null); $canonicalizationMethod = CanonicalizationMethod::getChildrenOfClass($xml); - Assert::count($canonicalizationMethod, 1, 'A ds:SignedInfo element must contain exactly one ds:CanonicalizationMethod'); + Assert::count( + $canonicalizationMethod, + 1, + 'A ds:SignedInfo element must contain exactly one ds:CanonicalizationMethod' + ); $signatureMethod = SignatureMethod::getChildrenOfClass($xml); Assert::count($signatureMethod, 1, 'A ds:SignedInfo element must contain exactly one ds:SignatureMethod'); diff --git a/src/XML/ec/AbstractEcElement.php b/src/XML/ec/AbstractEcElement.php index 80d803b7..15fcf7d2 100644 --- a/src/XML/ec/AbstractEcElement.php +++ b/src/XML/ec/AbstractEcElement.php @@ -7,10 +7,9 @@ use SimpleSAML\XML\AbstractXMLElement; use SimpleSAML\XMLSecurity\Constants; - /** * Abstract class to be implemented by all the classes in this namespace - + * * @package simplesamlphp/xml-security */ abstract class AbstractEcElement extends AbstractXMLElement diff --git a/tests/XML/CustomSignable.php b/tests/XML/CustomSignable.php index b5152ee7..5210d6c8 100644 --- a/tests/XML/CustomSignable.php +++ b/tests/XML/CustomSignable.php @@ -47,7 +47,8 @@ class CustomSignable extends AbstractXMLElement implements SignableElementInterf * * @param \DOMElement $xml */ - private function __construct(DOMElement $xml, ?string $id) { + private function __construct(DOMElement $xml, ?string $id) + { $this->setXML($xml); $this->id = $id; } @@ -121,7 +122,8 @@ protected function getOriginalXML(): DOMElement * @param \DOMElement $xml The XML element we should load * @return \SimpleSAML\XMLSecurity\Test\XML\CustomSignable * - * @throws \SimpleSAML\XML\Exception\InvalidDOMElementException if the qualified name of the supplied element is wrong + * @throws \SimpleSAML\XML\Exception\InvalidDOMElementException + * if the qualified name of the supplied element is wrong */ public static function fromXML(DOMElement $xml): object { diff --git a/tests/XML/SignableElementTest.php b/tests/XML/SignableElementTest.php index 799a366b..def98d67 100644 --- a/tests/XML/SignableElementTest.php +++ b/tests/XML/SignableElementTest.php @@ -256,8 +256,4 @@ public function testSigningWithDifferentRoot(): void ); $customSignable->toXML($doc->documentElement); } - - - } - diff --git a/tests/XML/SignedElementTest.php b/tests/XML/SignedElementTest.php index 152c0063..c0d2d2bd 100644 --- a/tests/XML/SignedElementTest.php +++ b/tests/XML/SignedElementTest.php @@ -79,104 +79,103 @@ public function testUnmarshalling(): void $this->signedDocument->ownerDocument->saveXML($this->signedDocument), strval($customSigned) ); - } + } /** * Test the verification of a signature with a given key. */ - public function testSuccessfulVerifyingWithGivenKey(): void - { - $customSigned = CustomSignable::fromXML($this->signedDocument); - - $this->assertTrue($customSigned->isSigned()); - $signature = $customSigned->getSignature(); - $this->assertInstanceOf(Signature::class, $signature); - $sigAlg = $signature->getSignedInfo()->getSignatureMethod()->getAlgorithm(); - $this->assertEquals(Constants::SIG_RSA_SHA256, $sigAlg); - $factory = new SignatureAlgorithmFactory(); - $certificate = new X509Certificate($this->certificate); - $verifier = $factory->getAlgorithm($sigAlg, $certificate); - - $verified = $customSigned->verify($verifier); - $this->assertInstanceOf(CustomSignable::class, $verified); - $this->assertFalse($verified->isSigned()); - $this->assertEquals( - 'Chunk', - strval($verified) - ); - $this->assertEquals($certificate, $verified->getValidatingKey()); - } + public function testSuccessfulVerifyingWithGivenKey(): void + { + $customSigned = CustomSignable::fromXML($this->signedDocument); + + $this->assertTrue($customSigned->isSigned()); + $signature = $customSigned->getSignature(); + $this->assertInstanceOf(Signature::class, $signature); + $sigAlg = $signature->getSignedInfo()->getSignatureMethod()->getAlgorithm(); + $this->assertEquals(Constants::SIG_RSA_SHA256, $sigAlg); + $factory = new SignatureAlgorithmFactory(); + $certificate = new X509Certificate($this->certificate); + $verifier = $factory->getAlgorithm($sigAlg, $certificate); + + $verified = $customSigned->verify($verifier); + $this->assertInstanceOf(CustomSignable::class, $verified); + $this->assertFalse($verified->isSigned()); + $this->assertEquals( + 'Chunk', + strval($verified) + ); + $this->assertEquals($certificate, $verified->getValidatingKey()); + } /** * Test the verification of a signature without passing a key, just what's in KeyInfo */ - public function testSuccessfulVerifyingWithoutKey(): void - { - $customSigned = CustomSignable::fromXML($this->signedDocument); - - $this->assertTrue($customSigned->isSigned()); - $signature = $customSigned->getSignature(); - $this->assertInstanceOf(Signature::class, $signature); - $sigAlg = $signature->getSignedInfo()->getSignatureMethod()->getAlgorithm(); - $this->assertEquals(Constants::SIG_RSA_SHA256, $sigAlg); - $certificate = new X509Certificate($this->certificate); - - $verified = $customSigned->verify(); - $this->assertInstanceOf(CustomSignable::class, $verified); - $this->assertFalse($verified->isSigned()); - $this->assertEquals( - 'Chunk', - strval($verified) - ); - $validatingKey = $verified->getValidatingKey(); - $this->assertInstanceOf(X509Certificate::class, $validatingKey); - /** @var \SimpleSAML\XMLSecurity\Key\X509Certificate $validatingKey */ - $this->assertEquals($certificate->getCertificate(), $validatingKey->getCertificate()); - } + public function testSuccessfulVerifyingWithoutKey(): void + { + $customSigned = CustomSignable::fromXML($this->signedDocument); + + $this->assertTrue($customSigned->isSigned()); + $signature = $customSigned->getSignature(); + $this->assertInstanceOf(Signature::class, $signature); + $sigAlg = $signature->getSignedInfo()->getSignatureMethod()->getAlgorithm(); + $this->assertEquals(Constants::SIG_RSA_SHA256, $sigAlg); + $certificate = new X509Certificate($this->certificate); + + $verified = $customSigned->verify(); + $this->assertInstanceOf(CustomSignable::class, $verified); + $this->assertFalse($verified->isSigned()); + $this->assertEquals( + 'Chunk', + strval($verified) + ); + $validatingKey = $verified->getValidatingKey(); + $this->assertInstanceOf(X509Certificate::class, $validatingKey); + /** @var \SimpleSAML\XMLSecurity\Key\X509Certificate $validatingKey */ + $this->assertEquals($certificate->getCertificate(), $validatingKey->getCertificate()); + } /** * Test that verifying a tampered signature, without giving a key for verification, fails as expected. */ - public function testVerifyingTamperedSignatureWithoutKeyFails(): void - { - $customSigned = CustomSignable::fromXML($this->tamperedDocument); + public function testVerifyingTamperedSignatureWithoutKeyFails(): void + { + $customSigned = CustomSignable::fromXML($this->tamperedDocument); - $this->assertTrue($customSigned->isSigned()); - $signature = $customSigned->getSignature(); - $this->assertInstanceOf(Signature::class, $signature); - $sigAlg = $signature->getSignedInfo()->getSignatureMethod()->getAlgorithm(); - $this->assertEquals(Constants::SIG_RSA_SHA256, $sigAlg); + $this->assertTrue($customSigned->isSigned()); + $signature = $customSigned->getSignature(); + $this->assertInstanceOf(Signature::class, $signature); + $sigAlg = $signature->getSignedInfo()->getSignatureMethod()->getAlgorithm(); + $this->assertEquals(Constants::SIG_RSA_SHA256, $sigAlg); - $this->expectException(RuntimeException::class); - $this->expectDeprecationMessage('Failed to validate signature.'); - $customSigned->verify(); - } + $this->expectException(RuntimeException::class); + $this->expectDeprecationMessage('Failed to validate signature.'); + $customSigned->verify(); + } /** * Test that verifying a tampered signature with a given key fails as expected. */ - public function testVerifyingTamperedSignatureWithKeyFails(): void - { - $customSigned = CustomSignable::fromXML($this->tamperedDocument); - + public function testVerifyingTamperedSignatureWithKeyFails(): void + { + $customSigned = CustomSignable::fromXML($this->tamperedDocument); - $this->assertTrue($customSigned->isSigned()); - $signature = $customSigned->getSignature(); - $this->assertInstanceOf(Signature::class, $signature); - $sigAlg = $signature->getSignedInfo()->getSignatureMethod()->getAlgorithm(); - $this->assertEquals(Constants::SIG_RSA_SHA256, $sigAlg); - $factory = new SignatureAlgorithmFactory(); - $certificate = new X509Certificate($this->certificate); - $verifier = $factory->getAlgorithm($sigAlg, $certificate); + $this->assertTrue($customSigned->isSigned()); + $signature = $customSigned->getSignature(); + $this->assertInstanceOf(Signature::class, $signature); + $sigAlg = $signature->getSignedInfo()->getSignatureMethod()->getAlgorithm(); + $this->assertEquals(Constants::SIG_RSA_SHA256, $sigAlg); + $factory = new SignatureAlgorithmFactory(); + $certificate = new X509Certificate($this->certificate); + $verifier = $factory->getAlgorithm($sigAlg, $certificate); - $this->expectException(RuntimeException::class); - $this->expectDeprecationMessage('Failed to validate signature.'); - $customSigned->verify($verifier); - } + $this->expectException(RuntimeException::class); + $this->expectDeprecationMessage('Failed to validate signature.'); + $customSigned->verify($verifier); + } @@ -215,4 +214,3 @@ public function testSuccessfulVerifyingDocumentWithComments(): void $this->assertEquals($certificate, $verified->getValidatingKey()); } } - diff --git a/tests/XML/ds/SignatureValueTest.php b/tests/XML/ds/SignatureValueTest.php index f7780706..066577b4 100644 --- a/tests/XML/ds/SignatureValueTest.php +++ b/tests/XML/ds/SignatureValueTest.php @@ -51,7 +51,8 @@ public function testMarshalling(): void 'j14G9v6AnsOiEJYgkTg864DG3e/KLqoGpuybPGSGblVTn7ST6M/BsvP7YiVZjLqJEuEvWmf2mW4DPb+pbArzzDcsLWEtNveMrw+F' . 'kWehDUQV9oe20iepo+W46wmj7zB/eWL+Z8MrGvlycoTndJU6CVwHTLsB+dq2FDa7JV4pAPjMY32JZTbiwKhzqw3nEi/eVrujJE4Y' . 'RrlW28D+rXhITfoUAGGvsqPzcwGzp02lnMe2SmXADY1u9lbVjOhUrJpgvWfn9YuiCR+wjvaGMwIwzfJxChLJZOBV+1ad1CyNTiu6' . - 'qAblxZ4F8cWlMWJ7f0KkWvtw66HOf2VNR6Qan2Ra7Q==')) + 'qAblxZ4F8cWlMWJ7f0KkWvtw66HOf2VNR6Qan2Ra7Q==' + )) ); } diff --git a/tests/XML/ds/X509IssuerSerialTest.php b/tests/XML/ds/X509IssuerSerialTest.php index 6d9071e6..a9bde9cf 100644 --- a/tests/XML/ds/X509IssuerSerialTest.php +++ b/tests/XML/ds/X509IssuerSerialTest.php @@ -86,7 +86,11 @@ public function testMarshallingElementOrdering(): void $this->assertCount(1, $issuerName); /** @psalm-var \DOMElement[] $X509IssuerSerialElements */ - $X509IssuerSerialElements = XPath::xpQuery($X509IssuerSerialElement, './ds:X509IssuerName/following-sibling::*', $xpCache); + $X509IssuerSerialElements = XPath::xpQuery( + $X509IssuerSerialElement, + './ds:X509IssuerName/following-sibling::*', + $xpCache + ); // Test ordering of X509IssuerSerial contents $this->assertCount(1, $X509IssuerSerialElements); diff --git a/tests/XML/ds/XPathTest.php b/tests/XML/ds/XPathTest.php index ce276f61..321cb349 100644 --- a/tests/XML/ds/XPathTest.php +++ b/tests/XML/ds/XPathTest.php @@ -75,5 +75,4 @@ public function testUnmarshalling(): void strval($xpath) ); } - } diff --git a/tests/XML/xenc/EncryptedDataTest.php b/tests/XML/xenc/EncryptedDataTest.php index 63fcd8d6..3688f12a 100644 --- a/tests/XML/xenc/EncryptedDataTest.php +++ b/tests/XML/xenc/EncryptedDataTest.php @@ -92,7 +92,10 @@ public function testUnmarshalling(): void $encryptedData = EncryptedData::fromXML($this->xmlRepresentation->documentElement); $cipherData = $encryptedData->getCipherData(); - $this->assertEquals('/CTj03d1DB5e2t7CTo9BEzCf5S9NRzwnBgZRlm32REI=', $cipherData->getCipherValue()->getContent()); + $this->assertEquals( + '/CTj03d1DB5e2t7CTo9BEzCf5S9NRzwnBgZRlm32REI=', + $cipherData->getCipherValue()->getContent() + ); $encryptionMethod = $encryptedData->getEncryptionMethod(); $this->assertEquals('http://www.w3.org/2001/04/xmlenc#aes128-cbc', $encryptionMethod->getAlgorithm()); diff --git a/tests/XML/xenc/EncryptedKeyTest.php b/tests/XML/xenc/EncryptedKeyTest.php index b7eba3aa..d3c8248c 100644 --- a/tests/XML/xenc/EncryptedKeyTest.php +++ b/tests/XML/xenc/EncryptedKeyTest.php @@ -151,7 +151,10 @@ public function testUnmarshalling(): void $encryptedKey = EncryptedKey::fromXML($this->xmlRepresentation->documentElement); $cipherData = $encryptedKey->getCipherData(); - $this->assertEquals('/CTj03d1DB5e2t7CTo9BEzCf5S9NRzwnBgZRlm32REI=', $cipherData->getCipherValue()->getContent()); + $this->assertEquals( + '/CTj03d1DB5e2t7CTo9BEzCf5S9NRzwnBgZRlm32REI=', + $cipherData->getCipherValue()->getContent() + ); $encryptionMethod = $encryptedKey->getEncryptionMethod(); $this->assertEquals('http://www.w3.org/2001/04/xmlenc#rsa-1_5', $encryptionMethod->getAlgorithm()); From 32ac52483199309d17b0c6ba571192c2097e7a68 Mon Sep 17 00:00:00 2001 From: Tim van Dijen Date: Sat, 24 Jul 2021 19:16:53 +0200 Subject: [PATCH 78/78] Fix psalm issue --- src/Utils/XPath.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Utils/XPath.php b/src/Utils/XPath.php index 6457ba6a..3f06895a 100644 --- a/src/Utils/XPath.php +++ b/src/Utils/XPath.php @@ -18,14 +18,14 @@ class XPath extends \SimpleSAML\XML\Utils\XPath /** * Get a DOMXPath object that can be used to search for XMLDSIG elements. * - * @param \DOMNode $doc The document to associate to the DOMXPath object. + * @param \DOMNode $node The document to associate to the DOMXPath object. * * @return \DOMXPath A DOMXPath object ready to use in the given document, with the XMLDSIG namespace already * registered. */ - public static function getXPath(DOMNode $doc): DOMXPath + public static function getXPath(DOMNode $node): DOMXPath { - $xp = parent::getXPath($doc); + $xp = parent::getXPath($node); $xp->registerNamespace('ds', C::NS_XDSIG); $xp->registerNamespace('xenc', C::NS_XENC); return $xp;