Skip to content

Accept DOM node when computing/verifying signatures #492

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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

Expand Down
33 changes: 17 additions & 16 deletions src/signed-xml.ts
Original file line number Diff line number Diff line change
Expand Up @@ -245,26 +245,28 @@ 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.
*
* @param xml The XML document containing the signature to be validated.
* @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 = [];
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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).
Expand All @@ -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 {
Expand All @@ -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;
Expand Down
67 changes: 64 additions & 3 deletions test/signature-unit-tests.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 () {
Expand Down Expand Up @@ -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 */
Expand Down Expand Up @@ -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;
}

Expand All @@ -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);
Expand Down