I’m working on a SAMLResponse generator in JavaScript without using server-side solutions.
The response is generated, but the signing process seems to fail, as I get the following error:
XMLJS0013: Cryptographic error: Invalid digest for URI '#_n1rwnf4r2'. Calculated digest is * but the xml to validate supplies digest *
I’m using the following libraries to handle the signature and digest:
jsrsasign.js
forge.js
Here is the part of my code responsible for signing the response:
const canonicalizedAssertion = canonicalize(assertion);
const digestValue = generateDigestValue(canonicalizedAssertion);
let signedInfo = `
<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
<CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
<SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
<Reference URI="#_${assertionID}">
<Transforms>
<Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
<Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
</Transforms>
<DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
<DigestValue>${digestValue}</DigestValue>
</Reference>
</SignedInfo>`;
const canonicalizedSignedInfo = canonicalize(signedInfo);
const signatureValue = generateSignature(canonicalizedSignedInfo);
That will added to the signature:
let signedAssertion = assertion.replace('</saml2:Assertion>', `
<Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
${signedInfo}
<SignatureValue>${signatureValue}</SignatureValue>
<KeyInfo>
<X509Data>
<X509Certificate>${certificateStr.replace(/-----BEGIN CERTIFICATE-----/g, "")
.replace(/-----END CERTIFICATE-----/g, "")
.replace(/n/g, "")}</X509Certificate>
</X509Data>
</KeyInfo>
</Signature>
</saml2:Assertion>`);
Here’s how I canonicalize the XML and generate the digest:
function canonicalize(xmlString) {
return xmlString.replace(/ss+/g, ' ').trim();
}
function generateDigestValue(content) {
const sha256 = forge.md.sha256.create();
sha256.update(content, 'utf8');
return forge.util.encode64(sha256.digest().bytes());
}
Does anyone have experience with this issue? How can I ensure the digest matches correctly? Any help would be appreciated!