diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2444c6f..3113d63 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -26,7 +26,7 @@ jobs: extensions: mbstring, intl, mysql, zlib, dom, openssl, soap, json, simplexml, libxml - name: checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Composer Install run: | diff --git a/src/Signer.php b/src/Signer.php index d6b4e44..a94066c 100755 --- a/src/Signer.php +++ b/src/Signer.php @@ -32,7 +32,7 @@ class Signer { - const CANONICAL = [true,false,null,null]; + const CANONICAL = [true, false, null, null]; /** * Make Signature tag @@ -74,7 +74,7 @@ public static function sign( if (empty($node) || empty($root)) { throw SignerException::tagNotFound($tagname); } - if (!self::existsSignature($content)) { + if (!self::existsSignature($content, $rootname)) { $dom = self::createSignature( $certificate, $dom, @@ -174,6 +174,7 @@ private static function createSignature( $x509DataNode->appendChild($x509CertificateNode); return $dom; } + /** * Remove old signature from document to replace it * @param string $content @@ -196,6 +197,7 @@ public static function removeSignature($content) } return $dom->saveXML(); } + /** * Verify if xml signature is valid * @param string $content @@ -218,9 +220,10 @@ public static function isSigned($content, $tagname = '', $canonical = self::CANO /** * Check if Signature tag already exists * @param string $content + * @param string|null $rootname * @return boolean */ - public static function existsSignature($content) + public static function existsSignature($content, $rootname = null) { if (!Validator::isXML($content)) { throw SignerException::isNotXml(); @@ -229,8 +232,23 @@ public static function existsSignature($content) $dom->formatOutput = false; $dom->preserveWhiteSpace = false; $dom->loadXML($content); - $signature = $dom->getElementsByTagName('Signature')->item(0); - return !empty($signature); + + if (empty($rootname)) { + return !empty($dom->getElementsByTagName('Signature')->item(0)); + } + + $root = $dom->documentElement->getElementsByTagName($rootname)->item(0); + if ($dom->documentElement->tagName == $rootname) { + $root = $dom->documentElement; + } + + foreach ($root->childNodes as $child) { + if ($child->nodeName == 'Signature') { + return true; + } + } + + return false; } /** @@ -256,7 +274,7 @@ public static function signatureCheck($xml, $canonical = self::CANONICAL) $publicKey = PublicKey::createFromContent($certificateContent); $signInfoNode = self::canonize($signature->getElementsByTagName('SignedInfo')->item(0), $canonical); $signatureValue = $signature->getElementsByTagName('SignatureValue')->item(0)->nodeValue; - $decodedSignature = base64_decode(str_replace(array("\r", "\n"), '', $signatureValue)); + $decodedSignature = base64_decode(str_replace(["\r", "\n"], '', $signatureValue)); if (!$publicKey->verify($signInfoNode, $decodedSignature, $algorithm)) { throw SignerException::signatureComparisonFailed(); } @@ -285,7 +303,9 @@ public static function digestCheck($xml, $tagname = '', $canonical = self::CANON $tagname = $root->nodeName; } else { $xpath = new \DOMXPath($dom); + /** @var \DOMNodeList $entries */ $entries = $xpath->query('//@Id'); + /** @var \DOMAttr $entry */ foreach ($entries as $entry) { $tagname = $entry->ownerElement->nodeName; break; diff --git a/tests/SignerTest.php b/tests/SignerTest.php index 655bc4b..524531b 100755 --- a/tests/SignerTest.php +++ b/tests/SignerTest.php @@ -23,6 +23,16 @@ public function testSign() $xmlsign = Signer::sign($certificate, $content, 'infNFe', 'Id'); $actual = Signer::isSigned($xmlsign); $this->assertTrue($actual); + + $xmlsign = Signer::sign($certificate, 'Teste Assinar mesmo documento 2x', 'b', 'Id', OPENSSL_ALGO_SHA1, Signer::CANONICAL, 'b'); + $this->assertTrue(Signer::existsSignature($xmlsign, 'b')); + + $xmlsign2x = Signer::sign($certificate, $xmlsign, 'b', 'Id', OPENSSL_ALGO_SHA1, Signer::CANONICAL, 'a'); + $this->assertTrue(Signer::existsSignature($xmlsign2x, 'a')); + + $dom = new \DOMDocument('1.0', 'utf-8'); + $dom->loadXML($xmlsign2x); + $this->assertEquals(2, $dom->getElementsByTagName('Signature')->count()); } /** @@ -79,6 +89,30 @@ public function testIsSignedFailDigest() Signer::isSigned($xml); } + public function testExistsSignatureRootnode(): void + { + $content = ''; + $this->assertTrue(Signer::existsSignature($content)); + + $content = ''; + $this->assertTrue(Signer::existsSignature($content)); + + $content = ''; + $this->assertFalse(Signer::existsSignature($content, 'a')); + + $content = ''; + $this->assertTrue(Signer::existsSignature($content, 'a')); + + $content = ''; + $this->assertFalse(Signer::existsSignature($content, 'c')); + + $content = ''; + $this->assertTrue(Signer::existsSignature($content, 'c')); + + $content = ''; + $this->assertTrue(Signer::existsSignature($content, 'b')); + } + /** * @covers Signer::existsSignature * @covers Signer::digestCheck