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