nodejs xml-crypto package giving a digest value and signature value mismatch error

256 Views Asked by At

I am using version "xml-crypto": "^4.0.1"

node version v16.14.0

Error displayed as

invalid signature: for uri calculated digest is 2U1suBt1sOA2olbnbMK1gC/3FHk= but the xml to validate supplies digest OGXcEIgUP1W+Hv9ghexl8gdMtrI=

Generated XML

<samlp:Response xmlns:samlp="urn:oasis:names:tc:SAML:1.0:protocol">
    <saml:Assertion MajorVersion="1" MinorVersion="1"
        AssertionID="_1" Issuer="mydomain.com"
        IssueInstant="2023-08-13T03:01:27.265Z" xmlns:saml="urn:oasis:names:tc:SAML:1.0:assertion"
        Id="_0">
        <saml:Conditions NotBefore="2023-08-13T03:01:27.266Z"
            NotOnOrAfter="2023-08-14T03:01:27.263Z" />
        <saml:AuthenticationStatement AuthenticationMethod="urn:oasis:names:tc:SAML:1.0:am:password"
            AuthenticationInstant="2023-08-13T03:01:27.268Z">
            <saml:Subject>
                <saml:NameIdentifier Format="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
                    NameQualifier="urn:mydomain.com">123356</saml:NameIdentifier>
                <saml:SubjectConfirmation>
                    <saml:ConfirmationMethod>urn:oasis:names:tc:SAML:1.0:cm:bearer</saml:ConfirmationMethod>
                </saml:SubjectConfirmation>
            </saml:Subject>
        </saml:AuthenticationStatement>
        <saml:AttributeStatement>
            <saml:Subject>
                <saml:NameIdentifier Format="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
                    NameQualifier="urn:mydomain.com">123356</saml:NameIdentifier>
                <saml:SubjectConfirmation>
                    <saml:ConfirmationMethod>urn:oasis:names:tc:SAML:1.0:cm:bearer</saml:ConfirmationMethod>
                </saml:SubjectConfirmation>
            </saml:Subject>
            <saml:Attribute AttributeName="version" AttributeNamespace="mydomain.com">
                <saml:AttributeValue>1</saml:AttributeValue>
            </saml:Attribute>
        </saml:AttributeStatement>
    </saml:Assertion>
    <samlp:Status>
        <samlp:StatusCode Value="samlp:Success" />
    </samlp:Status>
    <Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
        <SignedInfo>
            <CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315" />
            <SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1" />
            <Reference URI="#_0">
                <Transforms>
                    <Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature" />
                </Transforms>
                <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" />
                <DigestValue>OGXcEIgUP1W+Hv9ghexl8gdMtrI=</DigestValue>
            </Reference>
        </SignedInfo>
        <SignatureValue>
            HKJ/iE4XpJlwQW9H0zP8yK6SZO/HazYl/YpE09GTwDFcsgPRots+nosEeVtVU0wLkYW6wNgwf79LEoTiEComatyuRhVWg3ZtZpsdSkrkfR74M2B9aECoPS8k5tqArQbJl0KO...[redacted]...tsRlIJX9nMtdg==</SignatureValue>
    </Signature>
</samlp:Response>

I used the following function to strip the spaces from the XML

function removeXMLSpaces(string){
  let xmlString = string;
  xmlString = xmlString.replace(/>\s*/g, ">");
  xmlString = xmlString.replace(/\s*</g, "<");
  xmlString = xmlString.replace(/\r\n?/g, "\n");
  xmlString = xmlString.replace(/(\r\n\t|\n|\r\t)/gm, "");
  return xmlString;
}

Here is my SignXML function used

function signXml(xml, options) {
  console.log({ options });

  const sig = new SignedXml(options);

  // sig.keyInfoProvider = new KeyProvider();

  sig.signatureAlgorithm = "http://www.w3.org/2000/09/xmldsig#rsa-sha1";

  sig.canonicalizationAlgorithm = "http://www.w3.org/TR/2001/REC-xml-c14n-20010315";

  sig.addReference({
    xpath: "//*[local-name(.)='Assertion']",
    transforms: ["http://www.w3.org/2000/09/xmldsig#enveloped-signature"]
  });

  sig.computeSignature(xml);

  fs.writeFileSync(process.cwd() + "/xml/signed.xml", sig.getSignedXml());

  let currentSignedXML = sig.getSignedXml();
  currentSignedXML = currentSignedXML.replace("Id=\"_0\"","").replace("URI=\"#_0\"","URI=\"\"");

  return currentSignedXML;
}
1

There are 1 best solutions below

0
On

So...i found the issue. I am not sure if this requires an update to validation.

For it to work, I had to add the transform for C14n. On reading the documentation, I believe that this was a default value but on inspecting the code in the package, I saw the value was missing in the validationXML which caused the mismatch.

Also note that I changes the xpath to "Response" and took @srd90 suggestion for appending the signature.

function signXml(xml, options) {
  const sig = new SignedXml(options);

  sig.signatureAlgorithm = "http://www.w3.org/2000/09/xmldsig#rsa-sha1";

  sig.canonicalizationAlgorithm =
    "http://www.w3.org/TR/2001/REC-xml-c14n-20010315";

  sig.addReference({
    xpath: "//*[local-name(.)='Response']",
    transforms: ["http://www.w3.org/2000/09/xmldsig#enveloped-signature", "http://www.w3.org/TR/2001/REC-xml-c14n-20010315"],
  });

  sig.computeSignature(xml, {
    location: {
      reference: "//*[local-name(.)='Status']",
      action: "after",
    },
  });

  let currentSignedXML = sig.getSignedXml();
  
  return currentSignedXML;
}

See github issue: https://github.com/node-saml/xml-crypto/issues/376