-
Notifications
You must be signed in to change notification settings - Fork 177
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[BUG]: Sign signature on xml that included tag <![CDATA[.....]]> #474
Comments
Have you tried it by signing something other than the root? |
Could you be more specific. Is the issue that signature validation fails at some other system which uses some other xml-crypto library for verification or is your issue perhaps some stacktrace during signing of xml? Perhaps something like "Unable to canoni... node type..."? Quote from 6.0.0's codebase that contains handlers for different DOM node element types for one of the C14N implementations:
Note: I didn't bother to spend time to run example code due lack of e.g. xml-crypto version information, package.json and due example code not being immedialy runnable. Note2: IF it turns out that C14N implementations of this project for example skip content of CDATA and because output of C14N is used to calculate checksum then there could be even more serious problem if signature is also verified with library which does the same skipping because then e.g. |
No, because I want to ensure that the content of my XML remains unchanged after signing complete. |
I believe the issue lies with the XML signing function, specifically with the canonicalization algorithm that might be skipping parts of CDATA. The signing function completes successfully, but when I try to validate the signature, it still fails. I've tried every canonicalization algorithm available. The verification function works perfectly fine. I also tested verification using different frameworks, such as the Go library goxmldsig and the Chilkat Online Tools website. However, any XML containing CDATA tags always fails verification if it was signed using xml-crypto. I know this because I have an XML file with a CDATA tag signed by an unknown method, and when I verify it with xml-crypto , golang and website, it passes. My package.json |
Just out of curiosity checking (and sharing) this quick test about tl;dr; I.e. see how this algorithm works in Line 81 in 0ed7ab2
NOTE: 0ed7ab2 == First lets see what C14N spec says. https://www.w3.org/TR/2001/REC-xml-c14n-20010315 has example of input document with Here is copy paste of input document from aforementioned spec:
and here is copy paste of canonical form from that specification:
We are especially interested how this line is canonicalized by various tools:
Lets see how https://tools.chilkat.io/XmlCanonicalize canonicalize aforementioned input document from C14N spec. Using C14N type C14N without comments. Ouput of chilkat is: <doc>
<text>First line
Second line</text>
<value>2</value>
<compute>value>"0" && value<"10" ?"valid":"error"</compute>
<compute expr="value>"0" && value<"10" ?"valid":"error"">valid</compute>
<norm attr=" ' 
	 ' "></norm>
<normNames attr=" A 
	 B "></normNames>
<normId id=" ' 
	 ' "></normId>
</doc> diff between canonicalized version from spec and chilkats output - <normNames attr="A 
	 B"></normNames>
- <normId id="' 
	 '"></normId>
+ <normNames attr=" A 
	 B "></normNames>
+ <normId id=" ' 
	 ' "></normId> dunno why Lets see how xmllint --c14n - <<EOF
<doc>
<text>First line
 Second line</text>
<value>2</value>
<compute><![CDATA[value>"0" && value<"10" ?"valid":"error"]]></compute>
<compute expr='value>"0" && value<"10" ?"valid":"error"'>valid</compute>
<norm attr=' '   
	 ' '/>
<normNames attr=' A   
	 B '/>
<normId id=' '   
	 ' '/>
</doc>
EOF Result is <doc>
<text>First line
Second line</text>
<value>2</value>
<compute>value>"0" && value<"10" ?"valid":"error"</compute>
<compute expr="value>"0" && value<"10" ?"valid":"error"">valid</compute>
<norm attr=" ' 
	 ' "></norm>
<normNames attr=" A 
	 B "></normNames>
<normId id=" ' 
	 ' "></normId>
</doc> diff between canonizalized version from spec and xmllint's - <normNames attr="A 
	 B"></normNames>
- <normId id="' 
	 '"></normId>
+ <normNames attr=" A 
	 B "></normNames>
+ <normId id=" ' 
	 ' "></normId> again...dunno why there is whitespace difference between what spec says and what - in this case - xmllint says but at least Finally lets see how xml-crypto 6.0.0 canonicalized input document from spec. /*
git clone https://github.com/node-saml/xml-crypto.git
cd xml-crypto
git checkout v6.0.0
npm install
npm run build
put content of this file to e.g. dummy_c14n_test.js
*/
const C14nCanonicalization = require('./lib/c14n-canonicalization').C14nCanonicalization
const DOMParser = require("@xmldom/xmldom").DOMParser
const inputDocumentFromSpec = `
<doc>
<text>First line
 Second line</text>
<value>2</value>
<compute><![CDATA[value>"0" && value<"10" ?"valid":"error"]]></compute>
<compute expr='value>"0" && value<"10" ?"valid":"error"'>valid</compute>
<norm attr=' '   
	 ' '/>
<normNames attr=' A   
	 B '/>
<normId id=' '   
	 ' '/>
</doc>
`
const documentElement= (new DOMParser()).parseFromString(inputDocumentFromSpec).documentElement
console.log( (new C14nCanonicalization()).process(documentElement, {}).toString() )
/*
end execute:
node dummy_c14n_test.js
*/ Output is: <doc>
<text>First line
Second line</text>
<value>2</value>
<compute>value>"0" && value<"10" ?"valid":"error"</compute>
<compute expr="value>"0" && value<"10" ?"valid":"error"">valid</compute>
<norm attr=" ' 
	 ' "></norm>
<normNames attr=" A 
	 B "></normNames>
<normId id=" ' 
	 ' "></normId>
</doc> and diff between canonicalized form from spec is similar with other two tools. And whats important |
Now that I'm reading (but not trying to test / run) your example code again it seems that your code which uses xml-crypto Read more about this "non-symmetric behavior" from these two comments:
So try to add |
You say at one of the commets that:
but at the code snippet at issue description you use various parameter names e.g. for Word of advice for the future: consider trying to replicate issue with same information that you provide for others. Rest of this comment assumes that you are actually using Here example is code snippet provided at issue description wrapped with extra stuff to enable easier replication/debugging: /*
Related to https://github.com/node-saml/xml-crypto/issues/474
package.json
{
"name": "foo",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"xml-crypto": "^6.0.0"
}
}
*/
// Wrapping example code from issue with stuff to make it runnable/debuggable
//
const SignedXml = require("xml-crypto").SignedXml;
// https://raw.githubusercontent.com/node-saml/xml-crypto/v2.1.5/test/static/client.pem
const pemPrivateKey = `
-----BEGIN PRIVATE KEY-----
MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAL4vpoH3H3byehjj
7RAGxefGRATiq4mXtzc9Q91W7uT0DTaFEbjzVch9aGsNjmLs4QHsoZbuoUmi0st4
x5z9SQpTAKC/dW8muzacT3E7dJJYh03MAO6RiH4LG34VRTq1SQN6qDt2rCK85eG4
5NHI4jceptZNu6Zot1zyO5/PYuFpAgMBAAECgYAhspeyF3M/xB7WIixy1oBiXMLY
isESFAumgfhwU2LotkVRD6rgNl1QtMe3kCNWa9pCWQcYkxeI0IzA+JmFu2shVvoR
oL7eV4VCe1Af33z24E46+cY5grxNhHt/LyCnZKcitvCcrzXExUc5n6KngX0mMKgk
W7skZDwsnKzhyUV8wQJBAN2bQMeASQVOqdfqBdFgC/NPnKY2cuDi6h659QN1l+kg
X3ywdZ7KKftJo1G9l45SN9YpkyEd9zEO6PMFaufJvZUCQQDbtAWxk0i8BT3UTNWC
T/9bUQROPcGZagwwnRFByX7gpmfkf1ImIvbWVXSpX68/IjbjSkTw1nj/Yj1NwFZ0
nxeFAkEAzPhRpXVBlPgaXkvlz7AfvY+wW4hXHyyi0YK8XdPBi25XA5SPZiylQfjt
Z6iN6qSfYqYXoPT/c0/QJR+orvVJNQJBANhRPNXljVTK2GDCseoXd/ZiI5ohxg+W
UaA/1fDvQsRQM7TQA4NXI7BO/YmSk4rW1jIeOxjiIspY4MFAIh+7UL0CQFL6zTg6
wfeMlEZzvgqwCGoLuvTnqtvyg45z7pfcrg2cHdgCXIy9kErcjwGiu6BOevEA1qTW
Rk+bv0tknWvcz/s=
-----END PRIVATE KEY-----
`;
// https://raw.githubusercontent.com/node-saml/xml-crypto/v2.1.5/test/static/client_public.pem
const pemCertificate = `
-----BEGIN CERTIFICATE-----
MIIBxDCCAW6gAwIBAgIQxUSXFzWJYYtOZnmmuOMKkjANBgkqhkiG9w0BAQQFADAW
MRQwEgYDVQQDEwtSb290IEFnZW5jeTAeFw0wMzA3MDgxODQ3NTlaFw0zOTEyMzEy
MzU5NTlaMB8xHTAbBgNVBAMTFFdTRTJRdWlja1N0YXJ0Q2xpZW50MIGfMA0GCSqG
SIb3DQEBAQUAA4GNADCBiQKBgQC+L6aB9x928noY4+0QBsXnxkQE4quJl7c3PUPd
Vu7k9A02hRG481XIfWhrDY5i7OEB7KGW7qFJotLLeMec/UkKUwCgv3VvJrs2nE9x
O3SSWIdNzADukYh+Cxt+FUU6tUkDeqg7dqwivOXhuOTRyOI3HqbWTbumaLdc8juf
z2LhaQIDAQABo0swSTBHBgNVHQEEQDA+gBAS5AktBh0dTwCNYSHcFmRjoRgwFjEU
MBIGA1UEAxMLUm9vdCBBZ2VuY3mCEAY3bACqAGSKEc+41KpcNfQwDQYJKoZIhvcN
AQEEBQADQQAfIbnMPVYkNNfX1tG1F+qfLhHwJdfDUZuPyRPucWF5qkh6sSdWVBY5
sT/txBnVJGziyO8DPYdu2fPMER8ajJfl
-----END CERTIFICATE-----
`;
const xmlRootName = "HeaderDocument";
// example document from issue report
const xmlContent = `
<?xml version="1.0" encoding="UTF-8"?>
<HeaderDocument>
<Payload>
<![CDATA[<test>data<test>]]>
</Payload>
</HeaderDocument>
`;
// -------------------------------------------------
// Start example code from issue report:
const sig = new SignedXml({ privateKey: pemPrivateKey,publicCert: pemCertificate });
sig.addReference(
{
xpath: `//*[local-name(.)='${xmlRootName}']`,
digestAlgorithm: "http://www.w3.org/2001/04/xmlenc#sha256",
isEmptyUri: true,
transforms: ["http://www.w3.org/2000/09/xmldsig#enveloped-signature"],
}
);
sig.signatureAlgorithm = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256';
sig.canonicalizationAlgorithm = 'http://www.w3.org/2001/10/xml-exc-c14n#';
sig.computeSignature(xmlContent);
const xmlSignedContent = sig.getSignedXml();
const sig2 = new SignedXml({publicCert: pemCertificate});
const signature = sig.getSignatureXml();
sig2.loadSignature(signature);
const verify = sig2.checkSignature(xmlSignedContent);
// End example code from issue report
// -------------------------------------------------
console.log("-------------------------");
console.log(xmlSignedContent);
console.log("-------------------------");
console.log("Check signature = " + verify); Output <?xml version="1.0" encoding="UTF-8"?>
<HeaderDocument>
<Payload>
<![CDATA[<test>data<test>]]>
</Payload>
<Signature xmlns="http://www.w3.org/2000/09/xmldsig#"><SignedInfo><CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/><SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/><Reference URI=""><Transforms><Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/></Transforms><DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/><DigestValue>fpYnSQR900fNoPES7LrTO9DC3h0A3YI/YRW1Qyy88V8=</DigestValue></Reference></SignedInfo><SignatureValue>KScOwudyPkYZ2gAlDC3nbFm75gFHHO39pckTVGxCf3jRG9RmFCGdPf2IyEQfCUXUzMxWhgK/9IQ0okSShQ+wKVNernCSduTIzWkOhlT0rwMwFMtTYZsm+V6Ogyy9YDUtOlaOtM9sVgsJMPuizGaPsLgQsjmnb2GLWtG8OzY+gTE=</SignatureValue><KeyInfo><X509Data><X509Certificate>MIIBxDCCAW6gAwIBAgIQxUSXFzWJYYtOZnmmuOMKkjANBgkqhkiG9w0BAQQFADAWMRQwEgYDVQQDEwtSb290IEFnZW5jeTAeFw0wMzA3MDgxODQ3NTlaFw0zOTEyMzEyMzU5NTlaMB8xHTAbBgNVBAMTFFdTRTJRdWlja1N0YXJ0Q2xpZW50MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC+L6aB9x928noY4+0QBsXnxkQE4quJl7c3PUPdVu7k9A02hRG481XIfWhrDY5i7OEB7KGW7qFJotLLeMec/UkKUwCgv3VvJrs2nE9xO3SSWIdNzADukYh+Cxt+FUU6tUkDeqg7dqwivOXhuOTRyOI3HqbWTbumaLdc8jufz2LhaQIDAQABo0swSTBHBgNVHQEEQDA+gBAS5AktBh0dTwCNYSHcFmRjoRgwFjEUMBIGA1UEAxMLUm9vdCBBZ2VuY3mCEAY3bACqAGSKEc+41KpcNfQwDQYJKoZIhvcNAQEEBQADQQAfIbnMPVYkNNfX1tG1F+qfLhHwJdfDUZuPyRPucWF5qkh6sSdWVBY5sT/txBnVJGziyO8DPYdu2fPMER8ajJfl</X509Certificate></X509Data></KeyInfo></Signature></HeaderDocument> and script reports that signature is not valid (as expected) When signed XML is pasted to https://tools.chilkat.io/xmlDsigVerify.cshtml it says that: "digest is invalid because the computed digest differs from the digest in the XML". Same script as above but with excplicitly added C14N transformation in addition to enveloped transformation. I.e. diff to aforementioned script is: - transforms: ["http://www.w3.org/2000/09/xmldsig#enveloped-signature"],
+//
+// added http://www.w3.org/TR/2001/REC-xml-c14n-20010315 due reason
+// described at https://github.com/node-saml/xml-crypto/issues/474#issuecomment-2311181715
+//
+ transforms: [
+ "http://www.w3.org/2000/09/xmldsig#enveloped-signature",
+ "http://www.w3.org/TR/2001/REC-xml-c14n-20010315",
+ ], /*
Related to https://github.com/node-saml/xml-crypto/issues/474
package.json
{
"name": "foo",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"xml-crypto": "^6.0.0"
}
}
*/
// Wrapping example code from issue with stuff to make it runnable/debuggable
//
const SignedXml = require("xml-crypto").SignedXml;
// https://raw.githubusercontent.com/node-saml/xml-crypto/v2.1.5/test/static/client.pem
const pemPrivateKey = `
-----BEGIN PRIVATE KEY-----
MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAL4vpoH3H3byehjj
7RAGxefGRATiq4mXtzc9Q91W7uT0DTaFEbjzVch9aGsNjmLs4QHsoZbuoUmi0st4
x5z9SQpTAKC/dW8muzacT3E7dJJYh03MAO6RiH4LG34VRTq1SQN6qDt2rCK85eG4
5NHI4jceptZNu6Zot1zyO5/PYuFpAgMBAAECgYAhspeyF3M/xB7WIixy1oBiXMLY
isESFAumgfhwU2LotkVRD6rgNl1QtMe3kCNWa9pCWQcYkxeI0IzA+JmFu2shVvoR
oL7eV4VCe1Af33z24E46+cY5grxNhHt/LyCnZKcitvCcrzXExUc5n6KngX0mMKgk
W7skZDwsnKzhyUV8wQJBAN2bQMeASQVOqdfqBdFgC/NPnKY2cuDi6h659QN1l+kg
X3ywdZ7KKftJo1G9l45SN9YpkyEd9zEO6PMFaufJvZUCQQDbtAWxk0i8BT3UTNWC
T/9bUQROPcGZagwwnRFByX7gpmfkf1ImIvbWVXSpX68/IjbjSkTw1nj/Yj1NwFZ0
nxeFAkEAzPhRpXVBlPgaXkvlz7AfvY+wW4hXHyyi0YK8XdPBi25XA5SPZiylQfjt
Z6iN6qSfYqYXoPT/c0/QJR+orvVJNQJBANhRPNXljVTK2GDCseoXd/ZiI5ohxg+W
UaA/1fDvQsRQM7TQA4NXI7BO/YmSk4rW1jIeOxjiIspY4MFAIh+7UL0CQFL6zTg6
wfeMlEZzvgqwCGoLuvTnqtvyg45z7pfcrg2cHdgCXIy9kErcjwGiu6BOevEA1qTW
Rk+bv0tknWvcz/s=
-----END PRIVATE KEY-----
`;
// https://raw.githubusercontent.com/node-saml/xml-crypto/v2.1.5/test/static/client_public.pem
const pemCertificate = `
-----BEGIN CERTIFICATE-----
MIIBxDCCAW6gAwIBAgIQxUSXFzWJYYtOZnmmuOMKkjANBgkqhkiG9w0BAQQFADAW
MRQwEgYDVQQDEwtSb290IEFnZW5jeTAeFw0wMzA3MDgxODQ3NTlaFw0zOTEyMzEy
MzU5NTlaMB8xHTAbBgNVBAMTFFdTRTJRdWlja1N0YXJ0Q2xpZW50MIGfMA0GCSqG
SIb3DQEBAQUAA4GNADCBiQKBgQC+L6aB9x928noY4+0QBsXnxkQE4quJl7c3PUPd
Vu7k9A02hRG481XIfWhrDY5i7OEB7KGW7qFJotLLeMec/UkKUwCgv3VvJrs2nE9x
O3SSWIdNzADukYh+Cxt+FUU6tUkDeqg7dqwivOXhuOTRyOI3HqbWTbumaLdc8juf
z2LhaQIDAQABo0swSTBHBgNVHQEEQDA+gBAS5AktBh0dTwCNYSHcFmRjoRgwFjEU
MBIGA1UEAxMLUm9vdCBBZ2VuY3mCEAY3bACqAGSKEc+41KpcNfQwDQYJKoZIhvcN
AQEEBQADQQAfIbnMPVYkNNfX1tG1F+qfLhHwJdfDUZuPyRPucWF5qkh6sSdWVBY5
sT/txBnVJGziyO8DPYdu2fPMER8ajJfl
-----END CERTIFICATE-----
`;
const xmlRootName = "HeaderDocument";
// example document from issue report
const xmlContent = `
<?xml version="1.0" encoding="UTF-8"?>
<HeaderDocument>
<Payload>
<![CDATA[<test>data<test>]]>
</Payload>
</HeaderDocument>
`;
// -------------------------------------------------
// Start example code from issue report:
const sig = new SignedXml({ privateKey: pemPrivateKey,publicCert: pemCertificate });
sig.addReference(
{
xpath: `//*[local-name(.)='${xmlRootName}']`,
digestAlgorithm: "http://www.w3.org/2001/04/xmlenc#sha256",
isEmptyUri: true,
//
// added http://www.w3.org/TR/2001/REC-xml-c14n-20010315 due reason
// described at https://github.com/node-saml/xml-crypto/issues/474#issuecomment-2311181715
//
transforms: [
"http://www.w3.org/2000/09/xmldsig#enveloped-signature",
"http://www.w3.org/TR/2001/REC-xml-c14n-20010315",
],
}
);
sig.signatureAlgorithm = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256';
sig.canonicalizationAlgorithm = 'http://www.w3.org/2001/10/xml-exc-c14n#';
sig.computeSignature(xmlContent);
const xmlSignedContent = sig.getSignedXml();
const sig2 = new SignedXml({publicCert: pemCertificate});
const signature = sig.getSignatureXml();
sig2.loadSignature(signature);
const verify = sig2.checkSignature(xmlSignedContent);
// End example code from issue report
// -------------------------------------------------
console.log("-------------------------");
console.log(xmlSignedContent);
console.log("-------------------------");
console.log("Check signature = " + verify); Output <?xml version="1.0" encoding="UTF-8"?>
<HeaderDocument>
<Payload>
<![CDATA[<test>data<test>]]>
</Payload>
<Signature xmlns="http://www.w3.org/2000/09/xmldsig#"><SignedInfo><CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/><SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/><Reference URI=""><Transforms><Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/><Transform Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/></Transforms><DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/><DigestValue>pppiemIj4Se2Xa6nB3eChKIyCL3CWC3qk/L/xF1vAQ4=</DigestValue></Reference></SignedInfo><SignatureValue>UiJxY52MZf+k4GUG3Dlb+sgKcPmP4KP7KpIDDrU5SzFeGzNjvFV26fRyoRRd/oAJTXn1EHbn0r/wDg9k+/muC6HE02mX+p2xUIBzJ8mgDA73hRINOSDBLl4GpAI/eZlJrb77SnNZWLoDnvJVJQEabxcuzjWMUZIhcw2OcnuHirE=</SignatureValue><KeyInfo><X509Data><X509Certificate>MIIBxDCCAW6gAwIBAgIQxUSXFzWJYYtOZnmmuOMKkjANBgkqhkiG9w0BAQQFADAWMRQwEgYDVQQDEwtSb290IEFnZW5jeTAeFw0wMzA3MDgxODQ3NTlaFw0zOTEyMzEyMzU5NTlaMB8xHTAbBgNVBAMTFFdTRTJRdWlja1N0YXJ0Q2xpZW50MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC+L6aB9x928noY4+0QBsXnxkQE4quJl7c3PUPdVu7k9A02hRG481XIfWhrDY5i7OEB7 KGW7qFJotLLeMec/UkKUwCgv3VvJrs2nE9xO3SSWIdNzADukYh+Cxt+FUU6tUkDeqg7dqwivOXhuOTRyOI3HqbWTbumaLdc8jufz2LhaQIDAQABo0swSTBHBgNVHQEEQDA+gBAS5AktBh0dTwCNYSHcFmRjoRgwFjEUMBIGA1UEAxMLUm9vdCBBZ2VuY3mCEAY3bACqAGSKEc+41KpcNfQwDQYJKoZIhvcNAQEEBQADQQAfIbnMPVYkNNfX1tG1F+qfLhHwJdfDUZuPyRPucWF5qkh6sSdWVBY5sT/txBnVJGziyO8DPYdu2fPMER8ajJfl</X509Certificate></X509Data></KeyInfo></Signature></HeaderDocument> and script reports that signature is valid. When signed XML is pasted to https://tools.chilkat.io/xmlDsigVerify.cshtml it says that "digest is valid".
IMHO based on evidence above root cause is not CDATA but issue described at these comments |
This works! Thanks a lot for all the information. |
Is your feature request related to a problem? Please describe...
I'm facing the issue of signing a xml signature.
but when I remove CDATA or content inside CDATA and try to verify it's work fine.
This is my code
Describe teh solution you'd like...
I have change a canonicalizationAlgorithm to every single one, but it isn't work.
Describe the alternatives you've considered...
I guess it would be a problem when on signing.
The text was updated successfully, but these errors were encountered: