PeculiarVentures/xmldsigjs

digest correct in some xml files but not others

SmartLayer opened this issue · 12 comments

First, I experimented signing a simple one-liner xml file using xmlsectool, and verified it using xmldsigjs, xmlsectool, xmlsec1, all verifies correctly.

Then, I signed a multi-page XML file with multi-namespace using xmlsectool. xmlsectool and xmlsec1 verifies it correctly but xmldsigjs gives digest mismatch error:

"XMLJS0013: Cryptographic error: Invalid digest for uri ''.
Calculated digest is **** but the xml to validate supplies digest ****"

It's a lot of work to go through the multi-page multi-namespace XML to find out what exactly triggered the miscalculation of digest, but one thing for sure, it's not because of any variation in the <dsig:Signature> since there are no variations like number of sigatures or number of certificates or difference in <dsig:Reference>. There were only differences in the content being signed.


Here is the working case:

$ printf '<?xml version="1.0" encoding="UTF-8"?>\n<text>hello world</text>' > hello-world.xml 
$ /opt/xmlsectool-2.0.0/xmlsectool.sh --sign --keyInfoKeyName 'Shong Wang' --digest SHA-256 --signatureAlgorithm http://www.w3.org/2001/04/xmldsig-more#rsa-sha256 --inFile hello-world.xml --outFile hello-world-signed.xml --keystore shong.wang.p12 --keystoreType PKCS12 --key 1 --keyPassword shong.wang --signaturePosition LAST
INFO  XMLSecTool - Reading XML document from file 'hello-world.xml'
INFO  XMLSecTool - XML document parsed and is well-formed.
INFO  XMLSecTool - XML document successfully signed
INFO  XMLSecTool - XML document written to file /home/weiwu/IdeaProjects/TokenScript/examples/EntryToken/hello-world-signed.xml

The verification works:

$ node test-xmldsigjs.js 
Signature status: true

Where test-xmldsignjs.js is slightly modified from examples to check hello-world-signed.xml

test-xmldsigjs.js.txt


Here is the non-working case, signed with the same key/certificates, the test file is well-formed and validated against corresponding schemas:

long-test.xml.gz

Signing:

$ /opt/xmlsectool-2.0.0/xmlsectool.sh --sign --keyInfoKeyName 'Shong Wang' --digest SHA-256 --signatureAlgorithm http://www.w3.org/2001/04/xmldsig-more#rsa-sha256 --inFile long-test.xml --outFile long-test-signed.xml --keystore shong.wang.p12 --keystoreType PKCS12 --key 1 --keyPassword shong.wang --signaturePosition LAST
INFO  XMLSecTool - Reading XML document from file 'long-test.xml'
INFO  XMLSecTool - XML document parsed and is well-formed.
INFO  XMLSecTool - XML document successfully signed
INFO  XMLSecTool - XML document written to file /home/weiwu/IdeaProjects/TokenScript/examples/EntryToken/long-test-signed.xml

Resulting file:

long-test-signed.xml.gz

Verifying using xmlsectool (SUCCESS):

$ /opt/xmlsectool-2.0.0/xmlsectool.sh --verifySignature --keyInfoKeyName 'Shong Wang' --digest SHA-256 --signatureAlgorithm http://www.w3.org/2001/04/xmldsig-more#rsa-sha256 --inFile long-test-signed.xml --keystore shong.wang.p12 --keystoreType PKCS12 --key 1 --keyPassword shong.wang 
INFO  XMLSecTool - Reading XML document from file 'long-test-signed.xml'
INFO  XMLSecTool - XML document parsed and is well-formed.
INFO  XMLSecTool - XML document signature verified.

Verifying using xmlsec1 (SUCCESS):

$ xmlsec1 --verify long-test-signed.xml 
OK
SignedInfo References (ok/all): 1/1
Manifests References (ok/all): 0/0

Verifying using xmldsigjs.js (file name updated to long-test-signed.xml) (FAILURE)
test-xmldsigjs.js.txt

$ node test-xmldsigjs.js 
XmlError {
  prefix: 'XMLJS',
  code: 13,
  name: 'XmlError',
  message: "XMLJS0013: Cryptographic error: Invalid digest for uri ''. Calculated digest is RsL4D/BpAQQ5k5MBbyhv+Ez7TJbBAXVwecUyit8w7pU= but the xml to validate supplies digest IaBPprnL25MstZmfJOg9R8iV+Ktu/qba6n3WxmhU1TQ=",
  stack: "Error: XMLJS0013: Cryptographic error: Invalid digest for uri ''. Calculated digest is RsL4D/BpAQQ5k5MBbyhv+Ez7TJbBAXVwecUyit8w7pU= but the xml to validate supplies digest IaBPprnL25MstZmfJOg9R8iV+Ktu/qba6n3WxmhU1TQ=\n" +
    '    at new XmlError (/home/weiwu/IdeaProjects/tokenscript/node_modules/xml-core/dist/index.js:216:22)\n' +
    '    at SignedXml.ValidateReferences (/home/weiwu/IdeaProjects/tokenscript/node_modules/xmldsigjs/build/index.js:2708:23)\n' +
    '    at async SignedXml.Verify (/home/weiwu/IdeaProjects/tokenscript/node_modules/xmldsigjs/build/index.js:2403:21)'
}

My first thought is that there is some digest calculating issue when the file content has mixed namespaces. What is the underlying library that calculates the digest for xmldsigjs?

C14N is hardest part of XMLDSIG, there are lots of different nuances that could result in this.

The way we would debug is use another implementation, for example the .NET one, changing but by bit until we figured out how to get the same hash.

We will find time to look at this but can not commit to a timeline.

@rmhrisk Thanks. Since I use Java mostly, I can dump the result of Java C14N. It's native support for XMLDSig for quite some years and sing the same tune as xmlsec C library. If you can show me a few lines of how to dump the canonicalised XML from JavaScript, I can do a byte-to-byte comparison with the Java's dump and find the culprit there. How do you think?

I had a look at the files and experimented a bit I found that the issue is in the referenced files; token.en.shtml and enter.en.shtml. In these files are segments of CDATA. Whenever anything (even if it is just a single whitespace) is written in such a segment the verification fails. Thus it seems that the different libraries handle CDATA differently.
Since everything in CDATA is user-decided I guess a quick fix is simply to avoid using CDATA. In fact, since even a non-special character messes up the verification it seems that XMLDSIGjs might not support CDATA at all? But I am not sure about this.

I do recall there being some historic issue with CDATA; it’s entirely possible we don’t C14N it right.

C14N CDATA

case XmlCore.XmlNodeType.CDATA:
case XmlCore.XmlNodeType.SignificantWhitespace:
case XmlCore.XmlNodeType.Text:
// CDATA sections are processed as text nodes
this.WriteTextNode(node);
break;

xmldsigjs/test/canon.ts

Lines 275 to 280 in 5dccd54

it("#32 CDATA sections are replaced with their character content", () => {
const xml = "<root><child><inner><![CDATA[foo & bar in the <x>123</x>]]></inner></child></root>";
const xpath = '//*[local-name(.)="child"]';
C14N(xml, xpath, "<child><inner>foo &amp; bar in the &lt;x&gt;123&lt;/x&gt;</inner></child>");
ExcC14N(xml, xpath, "<child><inner>foo &amp; bar in the &lt;x&gt;123&lt;/x&gt;</inner></child>");
});

@colourful-land it looks like this particular document works fine in browser but not in node; that seems to be a function of xmldom's handling of CDATA.

@colourful-land it looks like this particular document works fine in browser but not in node; that seems to be a function of xmldom's handling of CDATA.

Later this month I set out to find the exact cause of this problem by stripping off all CDATA, then I will close this issue and start another with a specific test-case.

SGTM!

SGTM!

OKay, I stripped all CDATA and still, the code fails on 1/2 of the files I sent to test. On the other hand, all these signed XML files verify correctly in Java and TCL.

https://github.com/AlphaWallet/TokenScript/tree/xmldsig-verification-examples/xmldsig

So this problem is not limited to or caused by CDATA.

The above link provided how to reproduce the test, including a xmldsigverify.js which verifies all files given as command-line parameters; then a suite of test-files which you can get with git clone.

Ah, spent more than 5 hours to narrow down this problem to a simple 3-line XML test case that can demonstrate the problem. Let's close this issue and move the discussion to the new issue where the test case is presented. #47