diff --git a/README.md b/README.md index 135ce6bc..3ee926e9 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,7 @@ by default the following algorithms are used: _Canonicalization/Transformation Algorithm:_ Exclusive Canonicalization -_Hashing Algorithm:_ SHA1 digest +_Hashing/Digest Algorithm:_ SHA1 digest _Signature Algorithm:_ RSA-SHA1 @@ -244,7 +244,7 @@ The `SignedXml` constructor provides an abstraction for sign and verify xml docu - `privateKey` - string or Buffer - default `null` - the private key to use for signing - `publicCert` - string or Buffer - default `null` - the public certificate to use for verifying - `signatureAlgorithm` - string - default `http://www.w3.org/2000/09/xmldsig#rsa-sha1` - the signature algorithm to use -- `canonicalizationAlgorithm` - string - default `http://www.w3.org/TR/2001/REC-xml-c14n-20010315` - the canonicalization algorithm to use +- `canonicalizationAlgorithm` - string - default `undefined` - the canonicalization algorithm to use - `inclusiveNamespacesPrefixList` - string - default `null` - a list of namespace prefixes to include during canonicalization - `implicitTransforms` - string[] - default `[]` - a list of implicit transforms to use during verification - `keyInfoAttributes` - object - default `{}` - a hash of attributes and values `attrName: value` to add to the KeyInfo node @@ -313,7 +313,7 @@ function MyDigest() { } ``` -A custom signing algorithm. The default is RSA-SHA1. +A custom signing algorithm. ```javascript function MySignatureAlgorithm() { @@ -328,7 +328,7 @@ function MySignatureAlgorithm() { } ``` -Custom transformation algorithm. The default is exclusive canonicalization. +Custom transformation algorithm. ```javascript function MyTransformation() { diff --git a/src/c14n-canonicalization.ts b/src/c14n-canonicalization.ts index 9c9d9df8..dd9d7788 100644 --- a/src/c14n-canonicalization.ts +++ b/src/c14n-canonicalization.ts @@ -8,7 +8,11 @@ import * as utils from "./utils"; import * as isDomNode from "@xmldom/is-dom-node"; export class C14nCanonicalization implements CanonicalizationOrTransformationAlgorithm { - includeComments = false; + protected includeComments = false; + + constructor() { + this.includeComments = false; + } attrCompare(a, b) { if (!a.namespaceURI && b.namespaceURI) { diff --git a/src/enveloped-signature.ts b/src/enveloped-signature.ts index 88ea1342..5c74c362 100644 --- a/src/enveloped-signature.ts +++ b/src/enveloped-signature.ts @@ -8,7 +8,12 @@ import type { } from "./types"; export class EnvelopedSignature implements CanonicalizationOrTransformationAlgorithm { - includeComments = false; + protected includeComments = false; + + constructor() { + this.includeComments = false; + } + process(node: Node, options: CanonicalizationOrTransformationAlgorithmProcessOptions): Node { if (null == options.signatureNode) { const signature = xpath.select1( diff --git a/src/exclusive-canonicalization.ts b/src/exclusive-canonicalization.ts index 88efb5af..ea88aa2c 100644 --- a/src/exclusive-canonicalization.ts +++ b/src/exclusive-canonicalization.ts @@ -18,7 +18,11 @@ function isPrefixInScope(prefixesInScope, prefix, namespaceURI) { } export class ExclusiveCanonicalization implements CanonicalizationOrTransformationAlgorithm { - includeComments = false; + protected includeComments = false; + + constructor() { + this.includeComments = false; + } attrCompare(a, b) { if (!a.namespaceURI && b.namespaceURI) { diff --git a/src/signed-xml.ts b/src/signed-xml.ts index 151118ee..7e583b1d 100644 --- a/src/signed-xml.ts +++ b/src/signed-xml.ts @@ -41,8 +41,7 @@ export class SignedXml { /** * Rules used to convert an XML document into its canonical form. */ - canonicalizationAlgorithm: CanonicalizationAlgorithmType = - "http://www.w3.org/2001/10/xml-exc-c14n#"; + canonicalizationAlgorithm?: CanonicalizationAlgorithmType = undefined; /** * It specifies a list of namespace prefixes that should be considered "inclusive" during the canonicalization process. */ @@ -140,7 +139,7 @@ export class SignedXml { this.privateKey = privateKey; this.publicCert = publicCert; this.signatureAlgorithm = signatureAlgorithm ?? this.signatureAlgorithm; - this.canonicalizationAlgorithm = canonicalizationAlgorithm ?? this.canonicalizationAlgorithm; + this.canonicalizationAlgorithm = canonicalizationAlgorithm; if (typeof inclusiveNamespacesPrefixList === "string") { this.inclusiveNamespacesPrefixList = inclusiveNamespacesPrefixList.split(" "); } else if (utils.isArrayHasLength(inclusiveNamespacesPrefixList)) { @@ -286,6 +285,9 @@ export class SignedXml { if (this.signatureNode == null) { throw new Error("No signature found."); } + if (typeof this.canonicalizationAlgorithm !== "string") { + throw new Error("Missing canonicalizationAlgorithm when trying to get signed info for XML"); + } const signedInfo = utils.findChildren(this.signatureNode, "SignedInfo"); if (signedInfo.length === 0) { @@ -312,6 +314,7 @@ export class SignedXml { const c14nOptions = { ancestorNamespaces: ancestorNamespaces, }; + return this.getCanonXml([this.canonicalizationAlgorithm], signedInfo[0], c14nOptions); } @@ -354,12 +357,14 @@ export class SignedXml { } private findCanonicalizationAlgorithm(name: CanonicalizationOrTransformAlgorithmType) { - const algo = this.CanonicalizationAlgorithms[name]; - if (algo) { - return new algo(); - } else { - throw new Error(`canonicalization algorithm '${name}' is not supported`); + if (name != null) { + const algo = this.CanonicalizationAlgorithms[name]; + if (algo) { + return new algo(); + } } + + throw new Error(`canonicalization algorithm '${name}' is not supported`); } private findHashAlgorithm(name: HashAlgorithmType) { @@ -1024,6 +1029,11 @@ export class SignedXml { * */ private createSignedInfo(doc, prefix) { + if (typeof this.canonicalizationAlgorithm !== "string") { + throw new Error( + "Missing canonicalizationAlgorithm when trying to create signed info for XML", + ); + } const transform = this.findCanonicalizationAlgorithm(this.canonicalizationAlgorithm); const algo = this.findSignatureAlgorithm(this.signatureAlgorithm); let currentPrefix; diff --git a/src/types.ts b/src/types.ts index 97aae1ba..090c944e 100644 --- a/src/types.ts +++ b/src/types.ts @@ -143,8 +143,6 @@ export interface CanonicalizationOrTransformationAlgorithm { ): Node | string; getAlgorithmName(): CanonicalizationOrTransformAlgorithmType; - - includeComments: boolean; } /** Implement this to create a new HashAlgorithm */ diff --git a/test/hmac-tests.spec.ts b/test/hmac-tests.spec.ts index 42804df6..8760791d 100644 --- a/test/hmac-tests.spec.ts +++ b/test/hmac-tests.spec.ts @@ -48,6 +48,7 @@ describe("HMAC tests", function () { sig.privateKey = fs.readFileSync("./test/static/hmac.key"); sig.signatureAlgorithm = "http://www.w3.org/2000/09/xmldsig#hmac-sha1"; sig.addReference({ xpath: "//*[local-name(.)='book']" }); + sig.canonicalizationAlgorithm = "http://www.w3.org/2001/10/xml-exc-c14n#"; sig.computeSignature(xml); const doc = new xmldom.DOMParser().parseFromString(sig.getSignedXml()); diff --git a/test/key-info-tests.spec.ts b/test/key-info-tests.spec.ts index 8504ead8..83d32eab 100644 --- a/test/key-info-tests.spec.ts +++ b/test/key-info-tests.spec.ts @@ -11,6 +11,7 @@ describe("KeyInfo tests", function () { const sig = new SignedXml(); sig.privateKey = fs.readFileSync("./test/static/client.pem"); sig.publicCert = fs.readFileSync("./test/static/client_public.pem"); + sig.canonicalizationAlgorithm = "http://www.w3.org/2001/10/xml-exc-c14n#"; sig.computeSignature(xml); const signedXml = sig.getSignedXml(); const doc = new xmldom.DOMParser().parseFromString(signedXml); @@ -28,6 +29,7 @@ describe("KeyInfo tests", function () { sig.signatureAlgorithm = "http://www.w3.org/2000/09/xmldsig#hmac-sha1"; sig.enableHMAC(); sig.addReference({ xpath: "//*[local-name(.)='book']" }); + sig.canonicalizationAlgorithm = "http://www.w3.org/2001/10/xml-exc-c14n#"; sig.computeSignature(xml); const doc = new xmldom.DOMParser().parseFromString(sig.getSignedXml()); diff --git a/test/signature-integration-tests.spec.ts b/test/signature-integration-tests.spec.ts index c3c99a6e..b178b198 100644 --- a/test/signature-integration-tests.spec.ts +++ b/test/signature-integration-tests.spec.ts @@ -6,7 +6,7 @@ import { expect } from "chai"; import * as isDomNode from "@xmldom/is-dom-node"; describe("Signature integration tests", function () { - function verifySignature(xml, expected, xpath) { + function verifySignature(xml, expected, xpath, canonicalizationAlgorithm) { const sig = new SignedXml(); sig.privateKey = fs.readFileSync("./test/static/client.pem"); @@ -14,6 +14,7 @@ describe("Signature integration tests", function () { sig.addReference({ xpath: n }); }); + sig.canonicalizationAlgorithm = canonicalizationAlgorithm; sig.computeSignature(xml); const signed = sig.getSignedXml(); @@ -24,11 +25,12 @@ describe("Signature integration tests", function () { it("verify signature", function () { const xml = ''; - verifySignature(xml, "./test/static/integration/expectedVerify.xml", [ - "//*[local-name(.)='x']", - "//*[local-name(.)='y']", - "//*[local-name(.)='w']", - ]); + verifySignature( + xml, + "./test/static/integration/expectedVerify.xml", + ["//*[local-name(.)='x']", "//*[local-name(.)='y']", "//*[local-name(.)='w']"], + "http://www.w3.org/2001/10/xml-exc-c14n#", + ); }); it("verify signature of complex element", function () { @@ -43,9 +45,12 @@ describe("Signature integration tests", function () { "" + ""; - verifySignature(xml, "./test/static/integration/expectedVerifyComplex.xml", [ - "//*[local-name(.)='book']", - ]); + verifySignature( + xml, + "./test/static/integration/expectedVerifyComplex.xml", + ["//*[local-name(.)='book']"], + "http://www.w3.org/2001/10/xml-exc-c14n#", + ); }); it("empty URI reference should consider the whole document", function () { @@ -168,6 +173,7 @@ describe("Signature integration tests", function () { const sig = new SignedXml(); sig.addReference({ xpath: "//*[local-name(.)='book']" }); sig.privateKey = fs.readFileSync("./test/static/client.pem"); + sig.canonicalizationAlgorithm = "http://www.w3.org/2001/10/xml-exc-c14n#"; sig.computeSignature(xml); const signed = sig.getSignedXml(); diff --git a/test/signature-unit-tests.spec.ts b/test/signature-unit-tests.spec.ts index b4d0d8f1..0ad62c07 100644 --- a/test/signature-unit-tests.spec.ts +++ b/test/signature-unit-tests.spec.ts @@ -7,146 +7,97 @@ import { expect } from "chai"; import * as isDomNode from "@xmldom/is-dom-node"; describe("Signature unit tests", function () { - function verifySignature(xml: string, idMode?: "wssecurity") { - const doc = new xmldom.DOMParser().parseFromString(xml); - const node = xpath.select1( - "//*[local-name(.)='Signature' and namespace-uri(.)='http://www.w3.org/2000/09/xmldsig#']", - doc, - ); - isDomNode.assertIsNodeLike(node); - const sig = new SignedXml({ idMode }); - sig.publicCert = fs.readFileSync("./test/static/client_public.pem"); - sig.loadSignature(node); - try { - const res = sig.checkSignature(xml); - - return res; - } catch (e) { - return false; + describe("verify adds ID", function () { + function nodeExists(doc, xpathArg) { + if (!doc && !xpathArg) { + return; + } + const node = xpath.select(xpathArg, doc); + isDomNode.assertIsArrayOfNodes(node); + expect(node.length, `xpath ${xpathArg} not found`).to.equal(1); } - } - - function passValidSignature(file: string, mode?: "wssecurity") { - const xml = fs.readFileSync(file, "utf8"); - const res = verifySignature(xml, mode); - expect(res, "expected signature to be valid, but it was reported invalid").to.equal(true); - } - - function passLoadSignature(file: string, toString?: boolean) { - const xml = fs.readFileSync(file, "utf8"); - const doc = new xmldom.DOMParser().parseFromString(xml); - const signature = xpath.select1( - "/*//*[local-name(.)='Signature' and namespace-uri(.)='http://www.w3.org/2000/09/xmldsig#']", - doc, - ); - isDomNode.assertIsElementNode(signature); - const sig = new SignedXml(); - sig.loadSignature(toString ? signature.toString() : signature); - expect(sig.canonicalizationAlgorithm, "wrong canonicalization method").to.equal( - "http://www.w3.org/2001/10/xml-exc-c14n#", - ); + function verifyAddsId(mode, nsMode) { + const xml = ''; + const sig = new SignedXml({ idMode: mode }); + sig.privateKey = fs.readFileSync("./test/static/client.pem"); - expect(sig.signatureAlgorithm, "wrong signature method").to.equal( - "http://www.w3.org/2000/09/xmldsig#rsa-sha1", - ); + sig.addReference({ xpath: "//*[local-name(.)='x']" }); + sig.addReference({ xpath: "//*[local-name(.)='y']" }); + sig.addReference({ xpath: "//*[local-name(.)='w']" }); - sig.getCertFromKeyInfo = (keyInfo) => { - isDomNode.assertIsNodeLike(keyInfo); - const keyInfoContents = xpath.select1( - "//*[local-name(.)='KeyInfo']/*[local-name(.)='dummyKey']", - keyInfo, - ); - isDomNode.assertIsNodeLike(keyInfoContents); - const firstChild = keyInfoContents.firstChild; - isDomNode.assertIsTextNode(firstChild); - expect(firstChild.data, "keyInfo clause not correctly loaded").to.equal("1234"); + sig.canonicalizationAlgorithm = "http://www.w3.org/2001/10/xml-exc-c14n#"; + sig.computeSignature(xml); + const signedXml = sig.getOriginalXmlWithIds(); + const doc = new xmldom.DOMParser().parseFromString(signedXml); - return fs.readFileSync("./test/static/client.pem", "latin1"); - }; + const op = nsMode === "equal" ? "=" : "!="; - const checkedSignature = sig.checkSignature(xml); - expect(checkedSignature).to.be.true; - - expect(sig.getReferences().length).to.equal(3); - - const digests = [ - "b5GCZ2xpP5T7tbLWBTkOl4CYupQ=", - "K4dI497ZCxzweDIrbndUSmtoezY=", - "sH1gxKve8wlU8LlFVa2l6w3HMJ0=", - ]; - - const firstGrandchild = doc.firstChild?.firstChild; - isDomNode.assertIsElementNode(firstGrandchild); - const matchedReference = sig.validateElementAgainstReferences(firstGrandchild, doc); - expect(matchedReference).to.not.be.false; - - for (let i = 0; i < sig.getReferences().length; i++) { - const ref = sig.getReferences()[i]; - const expectedUri = `#_${i}`; - expect( - ref.uri, - `wrong uri for index ${i}. expected: ${expectedUri} actual: ${ref.uri}`, - ).to.equal(expectedUri); - expect(ref.transforms.length).to.equal(1); - expect(ref.transforms[0]).to.equal("http://www.w3.org/2001/10/xml-exc-c14n#"); - expect(ref.digestValue).to.equal(digests[i]); - expect(ref.digestAlgorithm).to.equal("http://www.w3.org/2000/09/xmldsig#sha1"); + const xpathArg = `//*[local-name(.)='{elem}' and '_{id}' = @*[local-name(.)='Id' and namespace-uri(.)${op}'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd']]`; + + //verify each of the signed nodes now has an "Id" attribute with the right value + nodeExists(doc, xpathArg.replace("{id}", "0").replace("{elem}", "x")); + nodeExists(doc, xpathArg.replace("{id}", "1").replace("{elem}", "y")); + nodeExists(doc, xpathArg.replace("{id}", "2").replace("{elem}", "w")); } - } - function failInvalidSignature(file: string, idMode?: "wssecurity") { - const xml = fs.readFileSync(file).toString(); - const res = verifySignature(xml, idMode); - expect(res, "expected signature to be invalid, but it was reported valid").to.equal(false); - } + it("signer adds increasing different id attributes to elements", function () { + verifyAddsId(null, "different"); + }); - function verifyDoesNotDuplicateIdAttributes(prefix: string, idMode?: "wssecurity") { - const xml = ``; - const sig = new SignedXml({ idMode }); - sig.privateKey = fs.readFileSync("./test/static/client.pem"); - sig.addReference({ xpath: "//*[local-name(.)='x']" }); - sig.computeSignature(xml); - const signedXml = sig.getOriginalXmlWithIds(); - const doc = new xmldom.DOMParser().parseFromString(signedXml); - const attrs = xpath.select("//@*", doc); - isDomNode.assertIsArrayOfNodes(attrs); - expect(attrs.length, "wrong number of attributes").to.equal(2); - } - - function nodeExists(doc, xpathArg) { - if (!doc && !xpathArg) { - return; - } - const node = xpath.select(xpathArg, doc); - isDomNode.assertIsArrayOfNodes(node); - expect(node.length, `xpath ${xpathArg} not found`).to.equal(1); - } + it("signer adds increasing equal id attributes to elements", function () { + verifyAddsId("wssecurity", "equal"); + }); + }); + + it("signer adds references with namespaces", function () { + const xml = + 'xml-cryptogithub'; + const sig = new SignedXml({ idMode: "wssecurity" }); - function verifyAddsId(mode, nsMode) { - const xml = ''; - const sig = new SignedXml({ idMode: mode }); sig.privateKey = fs.readFileSync("./test/static/client.pem"); - sig.addReference({ xpath: "//*[local-name(.)='x']" }); - sig.addReference({ xpath: "//*[local-name(.)='y']" }); - sig.addReference({ xpath: "//*[local-name(.)='w']" }); + sig.addReference({ xpath: "//*[@wsu:Id]" }); - sig.computeSignature(xml); - const signedXml = sig.getOriginalXmlWithIds(); + sig.canonicalizationAlgorithm = "http://www.w3.org/2001/10/xml-exc-c14n#"; + sig.computeSignature(xml, { + existingPrefixes: { + wsu: "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd", + }, + }); + + const signedXml = sig.getSignatureXml(); const doc = new xmldom.DOMParser().parseFromString(signedXml); + const references = xpath.select("//*[local-name(.)='Reference']", doc); + isDomNode.assertIsArrayOfNodes(references); + expect(references.length).to.equal(2); + }); - const op = nsMode === "equal" ? "=" : "!="; + describe("signer does not duplicate id attributes", function () { + function verifyDoesNotDuplicateIdAttributes(prefix: string, idMode?: "wssecurity") { + const xml = ``; + const sig = new SignedXml({ idMode }); + sig.privateKey = fs.readFileSync("./test/static/client.pem"); + sig.addReference({ xpath: "//*[local-name(.)='x']" }); + sig.canonicalizationAlgorithm = "http://www.w3.org/2001/10/xml-exc-c14n#"; + sig.computeSignature(xml); + const signedXml = sig.getOriginalXmlWithIds(); + const doc = new xmldom.DOMParser().parseFromString(signedXml); + const attrs = xpath.select("//@*", doc); + isDomNode.assertIsArrayOfNodes(attrs); + expect(attrs.length, "wrong number of attributes").to.equal(2); + } - const xpathArg = `//*[local-name(.)='{elem}' and '_{id}' = @*[local-name(.)='Id' and namespace-uri(.)${op}'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd']]`; + it("signer does not implicitly duplicate existing id attributes", function () { + verifyDoesNotDuplicateIdAttributes(""); + }); - //verify each of the signed nodes now has an "Id" attribute with the right value - nodeExists(doc, xpathArg.replace("{id}", "0").replace("{elem}", "x")); - nodeExists(doc, xpathArg.replace("{id}", "1").replace("{elem}", "y")); - nodeExists(doc, xpathArg.replace("{id}", "2").replace("{elem}", "w")); - } + it("signer does not explicitly duplicate existing id attributes", function () { + verifyDoesNotDuplicateIdAttributes("wsu:", "wssecurity"); + }); + }); - function verifyAddsAttrs() { + it("signer adds custom attributes to the signature root node", function () { const xml = 'xml-cryptogithub'; const sig = new SignedXml(); const attrs = { @@ -159,6 +110,7 @@ describe("Signature unit tests", function () { sig.addReference({ xpath: "//*[local-name(.)='name']" }); + sig.canonicalizationAlgorithm = "http://www.w3.org/2001/10/xml-exc-c14n#"; sig.computeSignature(xml, { attrs: attrs, }); @@ -181,46 +133,6 @@ describe("Signature unit tests", function () { signatureNode.getAttribute("xmlns"), 'xmlns attribute is not equal to the expected value: "http://www.w3.org/2000/09/xmldsig#"', ).to.equal("http://www.w3.org/2000/09/xmldsig#"); - } - - function verifyReferenceNS() { - const xml = - 'xml-cryptogithub'; - const sig = new SignedXml({ idMode: "wssecurity" }); - - sig.privateKey = fs.readFileSync("./test/static/client.pem"); - - sig.addReference({ xpath: "//*[@wsu:Id]" }); - - sig.computeSignature(xml, { - existingPrefixes: { - wsu: "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd", - }, - }); - - const signedXml = sig.getSignatureXml(); - const doc = new xmldom.DOMParser().parseFromString(signedXml); - const references = xpath.select("//*[local-name(.)='Reference']", doc); - isDomNode.assertIsArrayOfNodes(references); - expect(references.length).to.equal(2); - } - - it("signer adds increasing id attributes to elements", function () { - verifyAddsId("wssecurity", "equal"); - verifyAddsId(null, "different"); - }); - - it("signer adds references with namespaces", function () { - verifyReferenceNS(); - }); - - it("signer does not duplicate existing id attributes", function () { - verifyDoesNotDuplicateIdAttributes(""); - verifyDoesNotDuplicateIdAttributes("wsu:", "wssecurity"); - }); - - it("signer adds custom attributes to the signature root node", function () { - verifyAddsAttrs(); }); it("signer appends signature to the root node by default", function () { @@ -229,6 +141,7 @@ describe("Signature unit tests", function () { sig.privateKey = fs.readFileSync("./test/static/client.pem"); sig.addReference({ xpath: "//*[local-name(.)='name']" }); + sig.canonicalizationAlgorithm = "http://www.w3.org/2001/10/xml-exc-c14n#"; sig.computeSignature(xml); const doc = new xmldom.DOMParser().parseFromString(sig.getSignedXml()); @@ -248,6 +161,7 @@ describe("Signature unit tests", function () { sig.privateKey = fs.readFileSync("./test/static/client.pem"); sig.addReference({ xpath: "//*[local-name(.)='repository']" }); + sig.canonicalizationAlgorithm = "http://www.w3.org/2001/10/xml-exc-c14n#"; sig.computeSignature(xml, { location: { reference: "/root/name", @@ -274,6 +188,7 @@ describe("Signature unit tests", function () { sig.privateKey = fs.readFileSync("./test/static/client.pem"); sig.addReference({ xpath: "//*[local-name(.)='repository']" }); + sig.canonicalizationAlgorithm = "http://www.w3.org/2001/10/xml-exc-c14n#"; sig.computeSignature(xml, { location: { reference: "/root/name", @@ -299,6 +214,7 @@ describe("Signature unit tests", function () { sig.privateKey = fs.readFileSync("./test/static/client.pem"); sig.addReference({ xpath: "//*[local-name(.)='repository']" }); + sig.canonicalizationAlgorithm = "http://www.w3.org/2001/10/xml-exc-c14n#"; sig.computeSignature(xml, { location: { reference: "/root/name", @@ -325,6 +241,7 @@ describe("Signature unit tests", function () { sig.privateKey = fs.readFileSync("./test/static/client.pem"); sig.addReference({ xpath: "//*[local-name(.)='repository']" }); + sig.canonicalizationAlgorithm = "http://www.w3.org/2001/10/xml-exc-c14n#"; sig.computeSignature(xml, { location: { reference: "/root/name", @@ -677,6 +594,7 @@ describe("Signature unit tests", function () { sig.addReference({ xpath: "//*[local-name(.)='y']" }); sig.addReference({ xpath: "//*[local-name(.)='w']" }); + sig.canonicalizationAlgorithm = "http://www.w3.org/2001/10/xml-exc-c14n#"; sig.computeSignature(xml); const signedXml = sig.getSignedXml(); const expected = @@ -744,6 +662,7 @@ describe("Signature unit tests", function () { sig.addReference({ xpath: "//*[local-name(.)='y']" }); sig.addReference({ xpath: "//*[local-name(.)='w']" }); + sig.canonicalizationAlgorithm = "http://www.w3.org/2001/10/xml-exc-c14n#"; sig.computeSignature(xml, function () { const signedXml = sig.getSignedXml(); const expected = @@ -781,93 +700,193 @@ describe("Signature unit tests", function () { }); }); - it("correctly loads signature", function () { - passLoadSignature("./test/static/valid_signature.xml"); - }); + describe("verify existing signature", function () { + describe("pass loading signatures", function () { + function passLoadSignature(file: string, toString?: boolean) { + const xml = fs.readFileSync(file, "utf8"); + const doc = new xmldom.DOMParser().parseFromString(xml); + const signature = xpath.select1( + "/*//*[local-name(.)='Signature' and namespace-uri(.)='http://www.w3.org/2000/09/xmldsig#']", + doc, + ); + isDomNode.assertIsElementNode(signature); + const sig = new SignedXml(); + sig.loadSignature(toString ? signature.toString() : signature); + + expect(sig.canonicalizationAlgorithm, "wrong canonicalization method").to.equal( + "http://www.w3.org/2001/10/xml-exc-c14n#", + ); + + expect(sig.signatureAlgorithm, "wrong signature method").to.equal( + "http://www.w3.org/2000/09/xmldsig#rsa-sha1", + ); + + sig.getCertFromKeyInfo = (keyInfo) => { + isDomNode.assertIsNodeLike(keyInfo); + const keyInfoContents = xpath.select1( + "//*[local-name(.)='KeyInfo']/*[local-name(.)='dummyKey']", + keyInfo, + ); + isDomNode.assertIsNodeLike(keyInfoContents); + const firstChild = keyInfoContents.firstChild; + isDomNode.assertIsTextNode(firstChild); + expect(firstChild.data, "keyInfo clause not correctly loaded").to.equal("1234"); + + return fs.readFileSync("./test/static/client.pem", "latin1"); + }; + + const checkedSignature = sig.checkSignature(xml); + expect(checkedSignature).to.be.true; + + expect(sig.getReferences().length).to.equal(3); + + const digests = [ + "b5GCZ2xpP5T7tbLWBTkOl4CYupQ=", + "K4dI497ZCxzweDIrbndUSmtoezY=", + "sH1gxKve8wlU8LlFVa2l6w3HMJ0=", + ]; + + const firstGrandchild = doc.firstChild?.firstChild; + isDomNode.assertIsElementNode(firstGrandchild); + const matchedReference = sig.validateElementAgainstReferences(firstGrandchild, doc); + expect(matchedReference).to.not.be.false; + + for (let i = 0; i < sig.getReferences().length; i++) { + const ref = sig.getReferences()[i]; + const expectedUri = `#_${i}`; + expect( + ref.uri, + `wrong uri for index ${i}. expected: ${expectedUri} actual: ${ref.uri}`, + ).to.equal(expectedUri); + expect(ref.transforms.length).to.equal(1); + expect(ref.transforms[0]).to.equal("http://www.w3.org/2001/10/xml-exc-c14n#"); + expect(ref.digestValue).to.equal(digests[i]); + expect(ref.digestAlgorithm).to.equal("http://www.w3.org/2000/09/xmldsig#sha1"); + } + } + + it("correctly loads signature", function () { + passLoadSignature("./test/static/valid_signature.xml"); + }); - it("correctly loads signature with validation", function () { - passLoadSignature("./test/static/valid_signature.xml", true); - }); + it("correctly loads signature with validation", function () { + passLoadSignature("./test/static/valid_signature.xml", true); + }); - it("correctly loads signature with root level sig namespace", function () { - passLoadSignature("./test/static/valid_signature_with_root_level_sig_namespace.xml"); - }); + it("correctly loads signature with root level sig namespace", function () { + passLoadSignature("./test/static/valid_signature_with_root_level_sig_namespace.xml"); + }); + }); - it("verifies valid signature", function () { - passValidSignature("./test/static/valid_signature.xml"); - }); + describe("pass verify signature", function () { + function verifySignature(xml: string, idMode?: "wssecurity") { + const doc = new xmldom.DOMParser().parseFromString(xml); + const node = xpath.select1( + "//*[local-name(.)='Signature' and namespace-uri(.)='http://www.w3.org/2000/09/xmldsig#']", + doc, + ); + isDomNode.assertIsNodeLike(node); + const sig = new SignedXml({ idMode }); + sig.publicCert = fs.readFileSync("./test/static/client_public.pem"); + sig.loadSignature(node); + try { + const res = sig.checkSignature(xml); - it("verifies valid signature with lowercase id attribute", function () { - passValidSignature("./test/static/valid_signature_with_lowercase_id_attribute.xml"); - }); + return res; + } catch (e) { + return false; + } + } + + function passValidSignature(file: string, mode?: "wssecurity") { + const xml = fs.readFileSync(file, "utf8"); + const res = verifySignature(xml, mode); + expect(res, "expected signature to be valid, but it was reported invalid").to.equal(true); + } + + function failInvalidSignature(file: string, idMode?: "wssecurity") { + const xml = fs.readFileSync(file).toString(); + const res = verifySignature(xml, idMode); + expect(res, "expected signature to be invalid, but it was reported valid").to.equal(false); + } + + it("verifies valid signature", function () { + passValidSignature("./test/static/valid_signature.xml"); + }); - it("verifies valid signature with wsu", function () { - passValidSignature("./test/static/valid_signature wsu.xml", "wssecurity"); - }); + it("verifies valid signature with lowercase id attribute", function () { + passValidSignature("./test/static/valid_signature_with_lowercase_id_attribute.xml"); + }); - it("verifies valid signature with reference keyInfo", function () { - passValidSignature("./test/static/valid_signature_with_reference_keyInfo.xml"); - }); + it("verifies valid signature with wsu", function () { + passValidSignature("./test/static/valid_signature wsu.xml", "wssecurity"); + }); - it("verifies valid signature with whitespace in digestvalue", function () { - passValidSignature("./test/static/valid_signature_with_whitespace_in_digestvalue.xml"); - }); + it("verifies valid signature with reference keyInfo", function () { + passValidSignature("./test/static/valid_signature_with_reference_keyInfo.xml"); + }); - it("verifies valid utf8 signature", function () { - passValidSignature("./test/static/valid_signature_utf8.xml"); - }); + it("verifies valid signature with whitespace in digestvalue", function () { + passValidSignature("./test/static/valid_signature_with_whitespace_in_digestvalue.xml"); + }); - it("verifies valid signature with unused prefixes", function () { - passValidSignature("./test/static/valid_signature_with_unused_prefixes.xml"); - }); + it("verifies valid utf8 signature", function () { + passValidSignature("./test/static/valid_signature_utf8.xml"); + }); - it("verifies valid signature without transforms element", function () { - passValidSignature("./test/static/valid_signature_without_transforms_element.xml"); - }); + it("verifies valid signature with unused prefixes", function () { + passValidSignature("./test/static/valid_signature_with_unused_prefixes.xml"); + }); - it("fails invalid signature - signature value", function () { - failInvalidSignature("./test/static/invalid_signature - signature value.xml"); - }); + it("verifies valid signature without transforms element", function () { + passValidSignature("./test/static/valid_signature_without_transforms_element.xml"); + }); - it("fails invalid signature - hash", function () { - failInvalidSignature("./test/static/invalid_signature - hash.xml"); - }); + it("fails invalid signature - signature value", function () { + failInvalidSignature("./test/static/invalid_signature - signature value.xml"); + }); - it("fails invalid signature - non existing reference", function () { - failInvalidSignature("./test/static/invalid_signature - non existing reference.xml"); - }); + it("fails invalid signature - hash", function () { + failInvalidSignature("./test/static/invalid_signature - hash.xml"); + }); - it("fails invalid signature - changed content", function () { - failInvalidSignature("./test/static/invalid_signature - changed content.xml"); - }); + it("fails invalid signature - non existing reference", function () { + failInvalidSignature("./test/static/invalid_signature - non existing reference.xml"); + }); - it("fails invalid signature - wsu - invalid signature value", function () { - failInvalidSignature( - "./test/static/invalid_signature - wsu - invalid signature value.xml", - "wssecurity", - ); - }); + it("fails invalid signature - changed content", function () { + failInvalidSignature("./test/static/invalid_signature - changed content.xml"); + }); - it("fails invalid signature - wsu - hash", function () { - failInvalidSignature("./test/static/invalid_signature - wsu - hash.xml", "wssecurity"); - }); + it("fails invalid signature - wsu - invalid signature value", function () { + failInvalidSignature( + "./test/static/invalid_signature - wsu - invalid signature value.xml", + "wssecurity", + ); + }); - it("fails invalid signature - wsu - non existing reference", function () { - failInvalidSignature( - "./test/static/invalid_signature - wsu - non existing reference.xml", - "wssecurity", - ); - }); + it("fails invalid signature - wsu - hash", function () { + failInvalidSignature("./test/static/invalid_signature - wsu - hash.xml", "wssecurity"); + }); - it("fails invalid signature - wsu - changed content", function () { - failInvalidSignature( - "./test/static/invalid_signature - wsu - changed content.xml", - "wssecurity", - ); - }); + it("fails invalid signature - wsu - non existing reference", function () { + failInvalidSignature( + "./test/static/invalid_signature - wsu - non existing reference.xml", + "wssecurity", + ); + }); - it("fails invalid signature without transforms element", function () { - failInvalidSignature("./test/static/invalid_signature_without_transforms_element.xml"); + it("fails invalid signature - wsu - changed content", function () { + failInvalidSignature( + "./test/static/invalid_signature - wsu - changed content.xml", + "wssecurity", + ); + }); + + it("fails invalid signature without transforms element", function () { + failInvalidSignature("./test/static/invalid_signature_without_transforms_element.xml"); + }); + }); }); it("allow empty reference uri when signing", function () { @@ -885,6 +904,7 @@ describe("Signature unit tests", function () { isEmptyUri: true, }); + sig.canonicalizationAlgorithm = "http://www.w3.org/2001/10/xml-exc-c14n#"; sig.computeSignature(xml); const signedXml = sig.getSignedXml(); const doc = new xmldom.DOMParser().parseFromString(signedXml); @@ -938,6 +958,7 @@ describe("Signature unit tests", function () { const assertionId = "_81d5fba5c807be9e9cf60c58566349b1"; sig.getKeyInfoContent = getKeyInfoContentWithAssertionId.bind(this, { assertionId }); sig.privateKey = fs.readFileSync("./test/static/client.pem"); + sig.canonicalizationAlgorithm = "http://www.w3.org/2001/10/xml-exc-c14n#"; sig.computeSignature(xml, { prefix: "ds", location: { @@ -969,6 +990,7 @@ describe("Signature unit tests", function () { inclusiveNamespacesPrefixList: ["prefix1", "prefix2"], }); + sig.canonicalizationAlgorithm = "http://www.w3.org/2001/10/xml-exc-c14n#"; sig.computeSignature(xml); const signedXml = sig.getSignedXml(); @@ -1004,6 +1026,7 @@ describe("Signature unit tests", function () { inclusiveNamespacesPrefixList: [], }); + sig.canonicalizationAlgorithm = "http://www.w3.org/2001/10/xml-exc-c14n#"; sig.computeSignature(xml); const signedXml = sig.getSignedXml(); @@ -1027,6 +1050,7 @@ describe("Signature unit tests", function () { digestAlgorithm: "http://www.w3.org/2000/09/xmldsig#sha1", }); + sig.canonicalizationAlgorithm = "http://www.w3.org/2001/10/xml-exc-c14n#"; sig.computeSignature(xml); const signedXml = sig.getSignedXml(); @@ -1064,6 +1088,7 @@ describe("Signature unit tests", function () { digestAlgorithm: "http://www.w3.org/2000/09/xmldsig#sha1", }); + sig.canonicalizationAlgorithm = "http://www.w3.org/2001/10/xml-exc-c14n#"; sig.computeSignature(xml); const signedXml = sig.getSignedXml(); @@ -1089,6 +1114,7 @@ describe("Signature unit tests", function () { }; sig.getKeyInfoContent = () => ""; + sig.canonicalizationAlgorithm = "http://www.w3.org/2001/10/xml-exc-c14n#"; sig.computeSignature(xml); const signedXml = sig.getSignedXml(); @@ -1119,6 +1145,7 @@ describe("Signature unit tests", function () { const pemBuffer = fs.readFileSync("./test/static/client_bundle.pem"); sig.privateKey = pemBuffer; sig.publicCert = pemBuffer; + sig.canonicalizationAlgorithm = "http://www.w3.org/2001/10/xml-exc-c14n#"; sig.computeSignature(xml); const signedXml = sig.getSignedXml();