PeculiarVentures / xmldsigjs

XMLDSIGjs provides an implementation of XMLDSIG in Typescript/Javascript based on WebCrypto
MIT License
44 stars 29 forks source link

Namespace prefix on root creates invalid signatures #59

Open QAnders opened 3 years ago

QAnders commented 3 years ago

Working through my issues trying to solve the Peppol (Peppol.eu) standard signature I realized that the signature becomes invalid if the root element has a namespace prefix...

This snippet generates a valid signature:

  let xml2 = xmldsigjs.Parse(`<?xml version="1.0" encoding="UTF-8"?>
<SignedServiceMetadata xmlns="http://www.w3.org/2005/08/addressing" xmlns:ns2="http://busdox.org/serviceMetadata/publishing/1.0/">
  <ns2:xml>
    <test>hello</test>
  </ns2:xml>
</SignedServiceMetadata>
`);

But adding the prefix to the root generates a invalid signature:

  let xml2 = xmldsigjs.Parse(`<?xml version="1.0" encoding="UTF-8"?>
<ns2:SignedServiceMetadata xmlns="http://www.w3.org/2005/08/addressing" xmlns:ns2="http://busdox.org/serviceMetadata/publishing/1.0/">
  <xml>
    <test>hello</test>
  </xml>
</ns2:SignedServiceMetadata>
`);
QAnders commented 3 years ago

I'll share my code as well which works now and signature is validated using xmlsec1. I send in the certificates in PEM (CER) format (base64 with the BEGIN/END headers/footers).

const { XMLSerializer } = require('xmldom-alpha');
const xmldsigjs = require('xmldsigjs');
const { Crypto } = require('@peculiar/webcrypto');

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

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

function pem2der(pem) {
  pem = preparePem(pem);
  // convert base64 to ArrayBuffer
  return new Uint8Array(Buffer.from(pem, 'base64')).buffer;
}

async function signXML(xmlString, publicKeyPem, privateKeyPem) {
  // const fs = require('fs');

  const hash = 'SHA-1';
  const alg = {
      name: 'RSASSA-PKCS1-v1_5',
      hash,
      publicExponent: new Uint8Array([1,0,1]),
      modulusLength: 2048,
  };

  const keyDer = pem2der(privateKeyPem);
  const key = await crypto.subtle.importKey('pkcs8', keyDer, alg, false, [
    'sign'
  ]);

  let xml = xmldsigjs.Parse(xmlString);

  let digSigXml = new xmldsigjs.SignedXml();

  const X509Data = new xmldsigjs.KeyInfoX509Data();
  X509Data.AddSubjectName(process.env.SMP_QVALIA_CN);
  digSigXml.XmlSignature.KeyInfo.Add(X509Data);

  const signature = await digSigXml.Sign(
      // Signing document
      alg, // algorithm
      key, // key
      xml, // document
      {
          // options
          references: [
              {
                  hash,
                  transforms: ['enveloped']
              },
          ],
          x509: [preparePem(publicKeyPem)],
          signingCertificate: preparePem(publicKeyPem)
      }
  );

  // append signature
  xml.documentElement.appendChild(signature.GetXml());

  // serialize XML
  const oSerializer = new XMLSerializer();
  const sXML = oSerializer.serializeToString(xml);
  // fs.writeFileSync("/home/anders/CodeStuff/signed3.xml", sXML.toString());
  return sXML.toString();
}
QAnders commented 3 years ago

So this is not a bug?

rmhrisk commented 3 years ago

Your post made it sound like you solved your problem?

QAnders commented 3 years ago

Ah, no, sorry if I'm being a pain... I just wanted to add my code in full incase I am missing something...

I meant that it is producing valid signatures IF I don't have any namespace prefix on the root element.

QAnders commented 2 years ago

Is this one dea, or something someone is looking at?

rmhrisk commented 2 years ago

As I understand it the signature validates with xmlsec1 but the other tool your using doesn’t like it.

this would require us to 1) have an example of a file it does like, 2) have access to the tool

ivanbreet commented 1 year ago

We are also experiencing this issue. We validated using multiple tools, and having a namespace prefix in the root element causes validation issues.

Use an online validator like: https://tools.chilkat.io/xmlDsigVerify.cshtml

Steps to re-create The below example succeeds when using no root namespace prefixes

<InitSessionSignedRequest
        xmlns="http://example.com/online/types/2021/10/01/0001"
        xmlns:ns2="http://example.com/types/2021/10/01/0001"
        xmlns:ns3="http://example.com/online/auth/request/2021/10/01/0001">
</InitSessionSignedRequest>
CleanShot 2022-11-07 at 17 03 03@2x

The below example fails when using a root prefix:

<ns3:InitSessionSignedRequest
        xmlns="http://example.com/online/types/2021/10/01/0001"
        xmlns:ns2="http://example.com/types/2021/10/01/0001"
        xmlns:ns3="http://example.com/online/auth/request/2021/10/01/0001">
</ns3:InitSessionSignedRequest>
CleanShot 2022-11-07 at 17 02 04@2x

We tried multiple permutations and transforms, and the issue appears related to how the XML (and prefix) root is being handled in this library.