diff --git a/README.md b/README.md index ecfa261..9bc7c89 100644 --- a/README.md +++ b/README.md @@ -273,7 +273,7 @@ To sign xml documents: - `transforms` - an array of [transform algorithms](#canonicalization-and-transformation-algorithms), the referenced element will be transformed for each value in the array - `digestAlgorithm` - one of the supported [hashing algorithms](#hashing-algorithms) - `computeSignature(xml, [options])` - compute the signature of the given xml where: - - `xml` - a string containing a xml document + - `xml` - a string containing an xml document or document object (like an [xmldom](https://github.com/xmldom/xmldom) document) - `options` - an object with the following properties: - `prefix` - adds this value as a prefix for the generated signature tags - `attrs` - a hash of attributes and values `attrName: value` to add to the signature root node @@ -287,7 +287,8 @@ To verify xml documents: - `loadSignature(signatureXml)` - loads the signature where: - `signatureXml` - a string or node object (like an [xmldom](https://github.com/xmldom/xmldom) node) containing the xml representation of the signature -- `checkSignature(xml)` - validates the given xml document and returns `true` if the validation was successful +- `checkSignature(xml)` - validates the given xml document and returns `true` if the validation was successful where: + - `xml` - a string or node object (like an [xmldom](https://github.com/xmldom/xmldom) node) containing the xml representation of the document ## Customizing Algorithms diff --git a/src/signed-xml.ts b/src/signed-xml.ts index bc31cda..4d1c130 100644 --- a/src/signed-xml.ts +++ b/src/signed-xml.ts @@ -245,7 +245,7 @@ export class SignedXml { * @returns `true` if the signature is valid * @throws Error if no key info resolver is provided. */ - checkSignature(xml: string): boolean; + checkSignature(xml: Document | string): boolean; /** * Validates the signature of the provided XML document synchronously using the configured key info provider. * @@ -253,18 +253,20 @@ export class SignedXml { * @param callback Callback function to handle the validation result asynchronously. * @throws Error if the last parameter is provided and is not a function, or if no key info resolver is provided. */ - checkSignature(xml: string, callback: (error: Error | null, isValid?: boolean) => void): void; checkSignature( - xml: string, + xml: Document | string, + callback: (error: Error | null, isValid?: boolean) => void, + ): void; + checkSignature( + xml: Document | string, callback?: (error: Error | null, isValid?: boolean) => void, ): unknown { if (callback != null && typeof callback !== "function") { throw new Error("Last parameter must be a callback function"); } - this.signedXml = xml; - - const doc = new xmldom.DOMParser().parseFromString(xml); + const doc = typeof xml === "string" ? new xmldom.DOMParser().parseFromString(xml) : xml; + this.signedXml = doc.toString(); // Reset the references as only references from our re-parsed signedInfo node can be trusted this.references = []; @@ -347,7 +349,7 @@ export class SignedXml { // Check the signature verification to know whether to reset signature value or not. const sigRes = signer.verifySignature(unverifiedSignedInfoCanon, key, this.signatureValue); - if (sigRes === true) { + if (sigRes) { if (callback) { callback(null, true); } else { @@ -845,11 +847,10 @@ export class SignedXml { * Compute the signature of the given XML (using the already defined settings). * * @param xml The XML to compute the signature for. - * @param callback A callback function to handle the signature computation asynchronously. * @returns void * @throws TypeError If the xml can not be parsed. */ - computeSignature(xml: string): void; + computeSignature(xml: Document | string): void; /** * Compute the signature of the given XML (using the already defined settings). @@ -859,35 +860,35 @@ export class SignedXml { * @returns void * @throws TypeError If the xml can not be parsed. */ - computeSignature(xml: string, callback: ErrorFirstCallback<SignedXml>): void; + computeSignature(xml: Document | string, callback: ErrorFirstCallback<SignedXml>): void; /** * Compute the signature of the given XML (using the already defined settings). * * @param xml The XML to compute the signature for. - * @param opts An object containing options for the signature computation. + * @param options An object containing options for the signature computation. * @returns If no callback is provided, returns `this` (the instance of SignedXml). * @throws TypeError If the xml can not be parsed, or Error if there were invalid options passed. */ - computeSignature(xml: string, options: ComputeSignatureOptions): void; + computeSignature(xml: Document | string, options: ComputeSignatureOptions): void; /** * Compute the signature of the given XML (using the already defined settings). * * @param xml The XML to compute the signature for. - * @param opts An object containing options for the signature computation. + * @param options An object containing options for the signature computation. * @param callback A callback function to handle the signature computation asynchronously. * @returns void * @throws TypeError If the xml can not be parsed, or Error if there were invalid options passed. */ computeSignature( - xml: string, + xml: Document | string, options: ComputeSignatureOptions, callback: ErrorFirstCallback<SignedXml>, ): void; computeSignature( - xml: string, + xml: Document | string, options?: ComputeSignatureOptions | ErrorFirstCallback<SignedXml>, callbackParam?: ErrorFirstCallback<SignedXml>, ): void { @@ -899,8 +900,8 @@ export class SignedXml { callback = callbackParam as ErrorFirstCallback<SignedXml>; options = (options ?? {}) as ComputeSignatureOptions; } + const doc = typeof xml === "string" ? new xmldom.DOMParser().parseFromString(xml) : xml; - const doc = new xmldom.DOMParser().parseFromString(xml); let xmlNsAttr = "xmlns"; const signatureAttrs: string[] = []; let currentPrefix: string; diff --git a/test/signature-unit-tests.spec.ts b/test/signature-unit-tests.spec.ts index baa382d..d5cd6f7 100644 --- a/test/signature-unit-tests.spec.ts +++ b/test/signature-unit-tests.spec.ts @@ -697,6 +697,67 @@ describe("Signature unit tests", function () { expect(expected, "wrong signature format").to.equal(signedXml); }); + it("signer creates correct signature values when receiving a DOM", function () { + const xml = new xmldom.DOMParser().parseFromString( + '<root><x xmlns="ns" Id="_0"></x><y attr="value" Id="_1"></y><z><w Id="_2"></w></z></root>', + ); + const sig = new SignedXml(); + sig.privateKey = fs.readFileSync("./test/static/client.pem"); + + sig.addReference({ + xpath: "//*[local-name(.)='x']", + digestAlgorithm: "http://www.w3.org/2000/09/xmldsig#sha1", + transforms: ["http://www.w3.org/2001/10/xml-exc-c14n#"], + }); + sig.addReference({ + xpath: "//*[local-name(.)='y']", + digestAlgorithm: "http://www.w3.org/2000/09/xmldsig#sha1", + transforms: ["http://www.w3.org/2001/10/xml-exc-c14n#"], + }); + sig.addReference({ + xpath: "//*[local-name(.)='w']", + 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(xml); + const signedXml = sig.getSignedXml(); + const expected = + '<root><x xmlns="ns" Id="_0"/><y attr="value" Id="_1"/><z><w Id="_2"/></z>' + + '<Signature xmlns="http://www.w3.org/2000/09/xmldsig#">' + + "<SignedInfo>" + + '<CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>' + + '<SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>' + + '<Reference URI="#_0">' + + "<Transforms>" + + '<Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/></Transforms>' + + '<DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>' + + "<DigestValue>b5GCZ2xpP5T7tbLWBTkOl4CYupQ=</DigestValue>" + + "</Reference>" + + '<Reference URI="#_1">' + + "<Transforms>" + + '<Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>' + + "</Transforms>" + + '<DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>' + + "<DigestValue>4Pq/sBri+AyOtxtSFsPSOyylyzk=</DigestValue>" + + "</Reference>" + + '<Reference URI="#_2">' + + "<Transforms>" + + '<Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>' + + "</Transforms>" + + '<DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>' + + "<DigestValue>6I7SDu1iV2YOajTlf+iMLIBfLnE=</DigestValue>" + + "</Reference>" + + "</SignedInfo>" + + "<SignatureValue>NejzGB9MDUddKCt3GL2vJhEd5q6NBuhLdQc3W4bJI5q34hk7Hk6zBRoW3OliX+/f7Hpi9y0INYoqMSUfrsAVm3IuPzUETKlI6xiNZo07ULRj1DwxRo6cU66ar1EKUQLRuCZas795FjB8jvUI2lyhcax/00uMJ+Cjf4bwAQ+9gOQ=</SignatureValue>" + + "</Signature>" + + "</root>"; + + expect(expected, "wrong signature format").to.equal(signedXml); + }); + it("signer creates correct signature values using async callback", function () { class DummySignatureAlgorithm { verifySignature = function () { @@ -813,7 +874,7 @@ describe("Signature unit tests", function () { return fs.readFileSync("./test/static/client.pem", "latin1"); }; - const checkedSignature = sig.checkSignature(xml); + const checkedSignature = sig.checkSignature(toString ? doc.toString() : doc); expect(checkedSignature).to.be.true; /* eslint-disable-next-line deprecation/deprecation */ @@ -871,7 +932,6 @@ describe("Signature unit tests", function () { const sig = new SignedXml({ idMode }); sig.publicCert = fs.readFileSync("./test/static/client_public.pem"); sig.loadSignature(node); - return sig; } @@ -895,8 +955,9 @@ describe("Signature unit tests", function () { function throwsValidatingSignature(file: string, idMode?: "wssecurity") { const xml = fs.readFileSync(file).toString(); const sig = loadSignature(xml, idMode); + const doc = new xmldom.DOMParser().parseFromString(xml); expect( - () => sig.checkSignature(xml), + () => sig.checkSignature(doc), "expected an error to be thrown because signatures couldn't be checked for validity", ).to.throw(); expect(sig.getSignedReferences().length).to.equal(0);