diff --git a/README.md b/README.md index ecfa2612..9bc7c895 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 bc31cda7..4d1c1308 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): void; + computeSignature(xml: Document | string, callback: ErrorFirstCallback): 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, ): void; computeSignature( - xml: string, + xml: Document | string, options?: ComputeSignatureOptions | ErrorFirstCallback, callbackParam?: ErrorFirstCallback, ): void { @@ -899,8 +900,8 @@ export class SignedXml { callback = callbackParam as ErrorFirstCallback; 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 baa382db..d5cd6f7f 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( + '', + ); + 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 = + '' + + '' + + "" + + '' + + '' + + '' + + "" + + '' + + '' + + "b5GCZ2xpP5T7tbLWBTkOl4CYupQ=" + + "" + + '' + + "" + + '' + + "" + + '' + + "4Pq/sBri+AyOtxtSFsPSOyylyzk=" + + "" + + '' + + "" + + '' + + "" + + '' + + "6I7SDu1iV2YOajTlf+iMLIBfLnE=" + + "" + + "" + + "NejzGB9MDUddKCt3GL2vJhEd5q6NBuhLdQc3W4bJI5q34hk7Hk6zBRoW3OliX+/f7Hpi9y0INYoqMSUfrsAVm3IuPzUETKlI6xiNZo07ULRj1DwxRo6cU66ar1EKUQLRuCZas795FjB8jvUI2lyhcax/00uMJ+Cjf4bwAQ+9gOQ=" + + "" + + ""; + + 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);