PeculiarVentures/xadesjs

XAdES validation fails with Reference URI=“” for document with stylesheet processing instruction

realtomaszkula opened this issue · 3 comments

The document I am signing looks like this.

<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet href="CDA_PL_IG_1.3.1.xsl" type="text/xsl"?>
<ClinicalDocument></ClinicalDocument>

I am using xadesjs to sign this XML with the following code:

const crypto = new Crypto();
xadesjs.Application.setEngine('NodeJS', crypto);

export async function sign(xml: string, { publicKey, privateKey }: any) {
	const hash = 'SHA-1';
	const alg = {
		name: 'RSASSA-PKCS1-v1_5',
		hash
	};
	const keyDer = pem2der(privateKey.toString());
	const key = await crypto.subtle.importKey('pkcs8', keyDer, alg, true, [ 'sign' ]);
	const parsed = xadesjs.Parse(xml.trim());
	const xadesXml = new xadesjs.SignedXml();
	const signature = await xadesXml.Sign(alg, key, parsed, {
		signingCertificate: preparePem(publicKey.toString()),
		references: [ { uri: '', hash, transforms: [ 'enveloped' ] } ],
		x509: [ preparePem(publicKey.toString()) ]
	});

	parsed.documentElement.appendChild(signature.GetXml()!);
	return parsed.toString();
}

function preparePem(pem: string) {
	return pem.replace(/-----(BEGIN|END)[\w\d\s]+-----/g, '').replace(/[\r\n]/g, '');
}

function pem2der(pem: string) {
	pem = preparePem(pem);
	return new Uint8Array(Buffer.from(pem, 'base64')).buffer;
}

The generated signature is valid only if I remove the xml declaration and stylesheet instruction. So only signing this returns a correctly signed document:

<ClinicalDocument></ClinicalDocument>

Signing this

<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet href="CDA_PL_IG_1.3.1.xsl" type="text/xsl"?>
<ClinicalDocument></ClinicalDocument>

errors with message saying that not the entire document is signed.

I assume the problem is with the URI="" reference. It signs only the <ClinicalDocument> and leaves the <?xml version> and <?xml-stylesheet> with no signature.

I am not sure if this is an issue with the library or the incorrect usage on my part. I've also started related bounty on stackoverflow.

How do I sign everything?

I've traced this issue to the problem with canonicalization of processing instruction in the xmldsigj library. Currently there is a commented out test case dealing with this.

it("2.1 PIs, Comments, and Outside of Document Element", () => {
      const xml = `<?xml version="1.0"?>
            <?xml-stylesheet   href="doc.xsl"
               type="text/xsl"   ?>
            <!DOCTYPE doc SYSTEM "doc.dtd">
            <doc>Hello, world!<!-- Comment 1 --></doc>
            <?pi-without-data     ?>
            <!-- Comment 2 -->
            <!-- Comment 3 -->`;
      const xpath = "//*";
      C14N(xml, xpath, `<?xml-stylesheet href="doc.xsl"
            type="text/xsl"   ?>
         <doc>Hello, world!</doc>
         <?pi-without-data?>`);
    });

Currently the test case is failing by returning only the <doc>Hello, world!</doc> element without the <?xml-stylesheet href="doc.xsl" type="text/xsl" ?>. That would explain why the signature for element with processing instruction ends up invalid.

Am I on the right track? @microshine @alexey-pelykh @rmhrisk

@zetsnotdead Please try a new version of xmldsigjs. I published next version.

npm i xmldsigjs@next

Also, I found that xmldom has got some problems with PI elements parsing. xmldom/xmldom#42

I am still seeing the issue.

Testing with:

  • xmldsigjs@2.0.29-next.0
  • xadesjs@2.0.17

I can share the failing xml document privately if you'd like.