diff --git a/.cs.php b/.cs.php index 2bba5e9..a113324 100644 --- a/.cs.php +++ b/.cs.php @@ -19,7 +19,7 @@ 'array_syntax' => ['syntax' => 'short'], 'cast_spaces' => ['space' => 'none'], 'concat_space' => ['spacing' => 'one'], - 'compact_nullable_typehint' => true, + 'compact_nullable_type_declaration' => true, 'declare_equal_normalize' => ['space' => 'single'], 'general_phpdoc_annotation_remove' => [ 'annotations' => [ diff --git a/src/Algorithm.php b/src/Algorithm.php index 54151c8..f8a3058 100644 --- a/src/Algorithm.php +++ b/src/Algorithm.php @@ -56,7 +56,7 @@ final class Algorithm * @param string $signatureMethodAlgorithm * @param string|null $digestMethodAlgorithm */ - public function __construct(string $signatureMethodAlgorithm, string $digestMethodAlgorithm = null) + public function __construct(string $signatureMethodAlgorithm, ?string $digestMethodAlgorithm = null) { $this->setSignatureMethodAlgorithm($signatureMethodAlgorithm); $this->setDigestMethodAlgorithm($digestMethodAlgorithm ?? $signatureMethodAlgorithm); diff --git a/src/X509Reader.php b/src/X509Reader.php index 125b4e7..a9b1c67 100644 --- a/src/X509Reader.php +++ b/src/X509Reader.php @@ -74,6 +74,6 @@ public function toRawBase64(OpenSSLCertificate $certificate): string preg_match(self::PEM_REGEX_PATTERN, $exportedCertificate, $matches); - return str_replace(["\r\n", "\n"], '', trim($matches[1])); + return str_replace(["\r\n", "\n"], '', trim($matches[1] ?? '')); } } diff --git a/src/XmlSignatureVerifier.php b/src/XmlSignatureVerifier.php index d9ea208..af43fe6 100644 --- a/src/XmlSignatureVerifier.php +++ b/src/XmlSignatureVerifier.php @@ -19,16 +19,23 @@ final class XmlSignatureVerifier private bool $preserveWhiteSpace; + private bool $exclusive; + /** * The constructor. * * @param CryptoVerifierInterface $cryptoVerifier * @param bool $preserveWhiteSpace To remove redundant white spaces + * @param bool $exclusive Exclusive canonicalization. @see https://www.php.net/manual/en/domnode.c14n.php */ - public function __construct(CryptoVerifierInterface $cryptoVerifier, bool $preserveWhiteSpace = true) - { + public function __construct( + CryptoVerifierInterface $cryptoVerifier, + bool $preserveWhiteSpace = true, + bool $exclusive = true, + ) { $this->cryptoVerifier = $cryptoVerifier; $this->preserveWhiteSpace = $preserveWhiteSpace; + $this->exclusive = $exclusive; $this->xmlReader = new XmlReader(); } @@ -82,13 +89,13 @@ public function verifyDocument(DOMDocument $xml): bool $signatureValueElement = $this->xmlReader->queryDomNode($xpath, '//xmlns:SignatureValue', $signedInfoNode); $signatureValueElement->nodeValue = ''; - $canonicalData = $signedInfoNode->C14N(true, false); + $canonicalData = $signedInfoNode->C14N($this->exclusive, false); $xml2 = new DOMDocument(); $xml2->preserveWhiteSpace = true; $xml2->formatOutput = true; $xml2->loadXML($canonicalData); - $canonicalData = $xml2->C14N(true, false); + $canonicalData = $xml2->C14N($this->exclusive, false); $isValidSignature = $this->cryptoVerifier->verify($canonicalData, $signatureValue, $signatureAlgorithm); @@ -124,8 +131,8 @@ private function checkDigest(DOMDocument $xml, DOMXPath $xpath, string $algorith $signatureNode->parentNode->removeChild($signatureNode); } - // Canonicalize the content, exclusive and without comments - $canonicalData = $xml->C14N(true, false); + // Canonicalize the content without comments + $canonicalData = $xml->C14N($this->exclusive, false); $digestValue2 = $this->cryptoVerifier->computeDigest($canonicalData, $algorithm); diff --git a/src/XmlSigner.php b/src/XmlSigner.php index 1b28466..21da694 100644 --- a/src/XmlSigner.php +++ b/src/XmlSigner.php @@ -20,9 +20,18 @@ final class XmlSigner private CryptoSignerInterface $cryptoSigner; - public function __construct(CryptoSignerInterface $cryptoSigner) - { + private bool $preserveWhiteSpace; + + private bool $exclusive; + + public function __construct( + CryptoSignerInterface $cryptoSigner, + bool $preserveWhiteSpace = true, + bool $exclusive = true, + ) { $this->xmlReader = new XmlReader(); + $this->preserveWhiteSpace = $preserveWhiteSpace; + $this->exclusive = $exclusive; $this->cryptoSigner = $cryptoSigner; } @@ -42,7 +51,7 @@ public function signXml(string $data): string $xml = new DOMDocument(); // Whitespaces must be preserved - $xml->preserveWhiteSpace = true; + $xml->preserveWhiteSpace = $this->preserveWhiteSpace; $xml->formatOutput = false; $xml->loadXML($data); @@ -63,7 +72,7 @@ public function signXml(string $data): string * * @return string The signed XML as string */ - public function signDocument(DOMDocument $document, DOMElement $element = null): string + public function signDocument(DOMDocument $document, ?DOMElement $element = null): string { $element = $element ?? $document->documentElement; @@ -71,7 +80,7 @@ public function signDocument(DOMDocument $document, DOMElement $element = null): throw new XmlSignerException('Invalid XML document element'); } - $canonicalData = $element->C14N(true, false); + $canonicalData = $element->C14N($this->exclusive, false); // Calculate and encode digest value $digestValue = $this->cryptoSigner->computeDigest($canonicalData); @@ -176,11 +185,11 @@ private function appendSignature(DOMDocument $xml, string $digestValue): void } // http://www.soapclient.com/XMLCanon.html - $c14nSignedInfo = $signedInfoElement->C14N(true, false); + $c14nSignedInfo = $signedInfoElement->C14N($this->exclusive, false); $signatureValue = $this->cryptoSigner->computeSignature($c14nSignedInfo); - $xpath = new DOMXpath($xml); + $xpath = new DOMXPath($xml); $signatureValueElement = $this->xmlReader->queryDomNode($xpath, '//SignatureValue', $signatureElement); $signatureValueElement->nodeValue = base64_encode($signatureValue); } diff --git a/tests/XmlSignatureTest.php b/tests/XmlSignatureTest.php index 53d6246..f237f31 100644 --- a/tests/XmlSignatureTest.php +++ b/tests/XmlSignatureTest.php @@ -48,38 +48,43 @@ public function testSignAndVerify(string $privateKeyFile, string $publicKeyFile, foreach ($files as $filename) { foreach ($algos as $algo) { - $privateKeyStore = new PrivateKeyStore(); + $this->testFileAndAlgo($filename, $algo, $privateKeyFile, $publicKeyFile, $password); + } + } + } - if (pathinfo($privateKeyFile, PATHINFO_EXTENSION) === 'p12') { - $privateKeyStore->loadFromPkcs12(file_get_contents($privateKeyFile), $password); - } else { - $privateKeyStore->loadFromPem(file_get_contents($privateKeyFile), $password); - } + private function testFileAndAlgo(string $filename, string $algo, string $privateKeyFile, string $publicKeyFile, string $password) + { + $privateKeyStore = new PrivateKeyStore(); - $algorithm = new Algorithm($algo, $algo); - $cryptoSigner = new CryptoSigner($privateKeyStore, $algorithm); + if (pathinfo($privateKeyFile, PATHINFO_EXTENSION) === 'p12') { + $privateKeyStore->loadFromPkcs12(file_get_contents($privateKeyFile), $password); + } else { + $privateKeyStore->loadFromPem(file_get_contents($privateKeyFile), $password); + } - $xmlSigner = new XmlSigner($cryptoSigner); - $xmlSigner->setReferenceUri(''); + $algorithm = new Algorithm($algo, $algo); + $cryptoSigner = new CryptoSigner($privateKeyStore, $algorithm); - $signedXml = $xmlSigner->signXml(file_get_contents($filename)); + $xmlSigner = new XmlSigner($cryptoSigner); + $xmlSigner->setReferenceUri(''); - // verify - $publicKeyStore = new PublicKeyStore(); - if (pathinfo($publicKeyFile, PATHINFO_EXTENSION) === 'p12') { - $publicKeyStore->loadFromPkcs12(file_get_contents($publicKeyFile), $password); - } else { - $publicKeyStore->loadFromPem(file_get_contents($publicKeyFile)); - } + $signedXml = $xmlSigner->signXml(file_get_contents($filename)); - $cryptoVerifier = new CryptoVerifier($publicKeyStore); - $xmlSignatureVerifier = new XmlSignatureVerifier($cryptoVerifier); + // verify + $publicKeyStore = new PublicKeyStore(); + if (pathinfo($publicKeyFile, PATHINFO_EXTENSION) === 'p12') { + $publicKeyStore->loadFromPkcs12(file_get_contents($publicKeyFile), $password); + } else { + $publicKeyStore->loadFromPem(file_get_contents($publicKeyFile)); + } - $isValid = $xmlSignatureVerifier->verifyXml($signedXml); + $cryptoVerifier = new CryptoVerifier($publicKeyStore); + $xmlSignatureVerifier = new XmlSignatureVerifier($cryptoVerifier); - $this->assertTrue($isValid); - } - } + $isValid = $xmlSignatureVerifier->verifyXml($signedXml); + + $this->assertTrue($isValid); } /**