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.
How do I sign everything?
You need to canonicalize (C14N) the document before you sign it. I cannot comment on using xadesjs but here is some general advice.
For a Xades signature you need to compute a message digest over three parts, all of them C14N-ed.
(1) the full document excluding the
ds:Signature
element - this gives you the digest value for theURI=""
reference.(2) The subset of the
xades:SignedProperties
element, which needs to inherit any namespaces from the full document. This gives the digest value for the SignedProperties reference.(3) The final subset of the
ds:SignedInfo
element, which again needs to include inherited namespaces - this value is used indirectly to compute the signature value.The
<?xml version>
is always excluded. The<?xml-stylesheet>
is included when computing the digest value for the enveloped-signature (with reference URI="") but is excluded when C14N-ing over the two subsets.Computing the C14n transformation is not trivial. I will plug my own canonicalization utility here, SC14N https://www.cryptosys.net/sc14n/.
You can generate the digest values directly as follows:
To see the C14N-ed output, omit the -d option
Note that the
<xml-stylesheet
line is included on a line of its own.