node.jsxpathxml-crypto

Nodejs Xpath parse error using xml-crypto


I am having trouble signing an xml using xml-crypto library. Here is my code:

  const fs = require("fs");
  const xmlCrypto = require("xml-crypto");
  const xpath = require("xpath");
  const dom = require("xmldom").DOMParser;

  var xml = "<book><title>Harry Potter</title></book>";
  var xmlDocument = new dom().parseFromString(xml, "text/xml");

  var node = xpath.select("//title", xmlDocument)[0];

  console.log(node.localName + ": " + node.firstChild.data);
  console.log("Node: " + node.toString());

  const privateKey = fs.readFileSync(
    "./certifications/universal-pk.pem",
    "utf-8"
  );

  console.log("privateKey", privateKey);

  const reference = {
    uri: "",
    transforms: ["canonicalize", "c14n"],
    digestAlgorithm: "http://www.w3.org/2001/04/xmlenc#sha256",
  };

  const sig = new xmlCrypto.SignedXml();
  sig.addReference(reference);
  sig.signingKey = privateKey;
  sig.computeSignature(node);

And the console log:

title: Harry Potter
Node: <title>Harry Potter</title>
privateKey -----BEGIN PRIVATE KEY-----
.......
-----END PRIVATE KEY-----

[xmldom error]  invalid doc source
@#[line:0,col:undefined]
\api\node_modules\xml-crypto\node_modules\xpath\xpath.js:1278
                    throw new Error("XPath parse error");
                          ^

Error: XPath parse error

I also tried the sample code from https://www.npmjs.com/package/xml-crypto

However, I also received the exact same error message.


Solution

  • The problem is that you're not passing correct input to computeSignature method.

    The docs say:

    computeSignature(xml, [options]) - compute the signature of the given xml where:

    xml - a string containing a xml document

    https://www.npmjs.com/package/xml-crypto#signing-xml-documents

    So, it takes an XML string, but node variable which you are sending here

    sig.computeSignature(node);
    

    is actually an xmldom object, hence the error.

    So, send an XML string instead:

    sig.computeSignature(node.toString());
    

    Also, it seems you're using some older version, so when changed in accordance with the current docs, your code should look like this:

    (note that you don't really need to use DOMParser, you can pass xml string and modify xpath selector in reference)

    var sig = new xmlCrypto.SignedXml({ privateKey });
    sig.addReference({
      xpath: "//*[local-name(.)='title']",
      digestAlgorithm: "http://www.w3.org/2000/09/xmldsig#sha1",
      transforms: ["http://www.w3.org/2001/10/xml-exc-c14n#"],
    });
    sig.canonicalizationAlgorithm = "http://www.w3.org/2001/10/xml-exc-c14n#";
    sig.signatureAlgorithm = "http://www.w3.org/2000/09/xmldsig#rsa-sha1";
    sig.computeSignature(node.toString()); // or just xml and modify xpath..