diff --git a/src/c14n-canonicalization.ts b/src/c14n-canonicalization.ts index 91ee3009..76b5bec7 100644 --- a/src/c14n-canonicalization.ts +++ b/src/c14n-canonicalization.ts @@ -167,34 +167,38 @@ export class C14nCanonicalization implements CanonicalizationOrTransformationAlg return { rendered: res.join(""), newDefaultNs }; } - processInner(node, prefixesInScope, defaultNs, defaultNsForPrefix, ancestorNamespaces) { + processInner(node: Node, prefixesInScope, defaultNs, defaultNsForPrefix, ancestorNamespaces) { if (xpath.isComment(node)) { return this.renderComment(node); } - if (node.data) { + if (xpath.isComment(node)) { return utils.encodeSpecialCharactersInText(node.data); } - let i; - let pfxCopy; - const ns = this.renderNs( - node, - prefixesInScope, - defaultNs, - defaultNsForPrefix, - ancestorNamespaces, - ); - const res = ["<", node.tagName, ns.rendered, this.renderAttrs(node), ">"]; - - for (i = 0; i < node.childNodes.length; ++i) { - pfxCopy = prefixesInScope.slice(0); - res.push( - this.processInner(node.childNodes[i], pfxCopy, ns.newDefaultNs, defaultNsForPrefix, []), + if (xpath.isElement(node)) { + let i; + let pfxCopy; + const ns = this.renderNs( + node, + prefixesInScope, + defaultNs, + defaultNsForPrefix, + ancestorNamespaces, ); + const res = ["<", node.tagName, ns.rendered, this.renderAttrs(node), ">"]; + + for (i = 0; i < node.childNodes.length; ++i) { + pfxCopy = prefixesInScope.slice(0); + res.push( + this.processInner(node.childNodes[i], pfxCopy, ns.newDefaultNs, defaultNsForPrefix, []), + ); + } + + res.push(""); + return res.join(""); } - res.push(""); - return res.join(""); + return ""; } // Thanks to deoxxa/xml-c14n for comment renderer @@ -240,8 +244,7 @@ export class C14nCanonicalization implements CanonicalizationOrTransformationAlg /** * Perform canonicalization of the given node * - * @param {Node} node - * @return {String} + * @param node * @api public */ process(node: Node, options: CanonicalizationOrTransformationAlgorithmProcessOptions) { diff --git a/src/signed-xml.ts b/src/signed-xml.ts index 2fd9fd3e..b5d7da13 100644 --- a/src/signed-xml.ts +++ b/src/signed-xml.ts @@ -311,7 +311,7 @@ export class SignedXml { return this.getCanonXml([this.canonicalizationAlgorithm], signedInfo[0], c14nOptions); } - getCanonReferenceXml(doc, ref, node) { + getCanonReferenceXml(doc: Document, ref: Reference, node: Node) { /** * Search for ancestor namespaces before canonicalization. */ @@ -505,7 +505,6 @@ export class SignedXml { const keyInfo = xpath.select1(".//*[local-name(.)='KeyInfo']", signatureNode); - // TODO: should this just be a single return instead of an array that we always take the first entry of? if (xpath.isNodeLike(keyInfo)) { this.keyInfo = keyInfo; } @@ -515,10 +514,10 @@ export class SignedXml { * Load the reference xml node to a model * */ - loadReference(ref) { - let nodes = utils.findChildren(ref, "DigestMethod"); + loadReference(refNode: Node) { + let nodes = utils.findChildren(refNode, "DigestMethod"); if (nodes.length === 0) { - throw new Error(`could not find DigestMethod in reference ${ref.toString()}`); + throw new Error(`could not find DigestMethod in reference ${refNode.toString()}`); } const digestAlgoNode = nodes[0]; @@ -528,9 +527,9 @@ export class SignedXml { } const digestAlgo = attr.value; - nodes = utils.findChildren(ref, "DigestValue"); + nodes = utils.findChildren(refNode, "DigestValue"); if (nodes.length === 0) { - throw new Error(`could not find DigestValue node in reference ${ref.toString()}`); + throw new Error(`could not find DigestValue node in reference ${refNode.toString()}`); } const firstChild = nodes[0].firstChild; if (!firstChild || !("data" in firstChild)) { @@ -540,7 +539,7 @@ export class SignedXml { const transforms: string[] = []; let inclusiveNamespacesPrefixList: string[] = []; - nodes = utils.findChildren(ref, "Transforms"); + nodes = utils.findChildren(refNode, "Transforms"); if (nodes.length !== 0) { const transformsNode = nodes[0]; const transformsAll = utils.findChildren(transformsNode, "Transform"); @@ -590,7 +589,7 @@ export class SignedXml { this.addReference({ transforms, digestAlgorithm: digestAlgo, - uri: utils.findAttr(ref, "URI")?.value, + uri: xpath.isElement(refNode) ? utils.findAttr(refNode, "URI")?.value : undefined, digestValue, inclusiveNamespacesPrefixList, isEmptyUri: false, @@ -908,19 +907,19 @@ export class SignedXml { } getCanonXml( - transforms: CanonicalizationAlgorithmType[], - node, - options: CanonicalizationOrTransformationAlgorithmProcessOptions, + transforms: Reference["transforms"], + node: Node, + options: CanonicalizationOrTransformationAlgorithmProcessOptions = {}, ) { - options = options || {}; options.defaultNsForPrefix = options.defaultNsForPrefix ?? SignedXml.defaultNsForPrefix; options.signatureNode = this.signatureNode; - let canonXml = node.cloneNode(true); // Deep clone + const canonXml = node.cloneNode(true); // Deep clone + let transformedXml: string = canonXml.toString(); transforms.forEach((transformName) => { const transform = this.findCanonicalizationAlgorithm(transformName); - canonXml = transform.process(canonXml, options); + transformedXml = transform.process(canonXml, options).toString(); //TODO: currently transform.process may return either Node or String value (enveloped transformation returns Node, exclusive-canonicalization returns String). //This either needs to be more explicit in the API, or all should return the same. //exclusive-canonicalization returns String since it builds the Xml by hand. If it had used xmldom it would incorrectly minimize empty tags @@ -930,7 +929,7 @@ export class SignedXml { //if only y is the node to sign then a string would be without the definition of the p namespace. probably xmldom toString() should have added it. }); - return canonXml.toString(); + return transformedXml; } /** diff --git a/src/utils.ts b/src/utils.ts index dad31a85..f1226a26 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -76,7 +76,7 @@ export function encodeSpecialCharactersInAttribute(attributeValue) { }); } -export function encodeSpecialCharactersInText(text) { +export function encodeSpecialCharactersInText(text: string): string { return text.replace(/([&<>\r])/g, function (str, item) { // Special character normalization. See: // - https://www.w3.org/TR/xml-c14n#ProcessingModel (Text Nodes) @@ -232,16 +232,20 @@ function isElementSubset(docSubset: Node[]): docSubset is Element[] { * Extract ancestor namespaces in order to import it to root of document subset * which is being canonicalized for non-exclusive c14n. * - * @param {object} doc - Usually a product from `new xmldom.DOMParser().parseFromString()` - * @param {string} docSubsetXpath - xpath query to get document subset being canonicalized - * @param {object} namespaceResolver - xpath namespace resolver - * @returns {Array} i.e. [{prefix: "saml", namespaceURI: "urn:oasis:names:tc:SAML:2.0:assertion"}] + * @param doc - Usually a product from `new xmldom.DOMParser().parseFromString()` + * @param docSubsetXpath - xpath query to get document subset being canonicalized + * @param namespaceResolver - xpath namespace resolver + * @returns i.e. [{prefix: "saml", namespaceURI: "urn:oasis:names:tc:SAML:2.0:assertion"}] */ export function findAncestorNs( - doc: Node, - docSubsetXpath: string, + doc: Document, + docSubsetXpath?: string, namespaceResolver?: XPathNSResolver, -) { +): NamespacePrefix[] { + if (docSubsetXpath == null) { + return []; + } + const docSubset = xpath.selectWithResolver(docSubsetXpath, doc, namespaceResolver); if (!isArrayHasLength(docSubset)) { @@ -289,7 +293,6 @@ export function validateDigestValue(digest, expectedDigest) { return buffer.equals(expectedBuffer); } - // Compatibility with Node < 0.11.13 if (buffer.length !== expectedBuffer.length) { return false; } diff --git a/test/c14nWithComments-unit-tests.spec.ts b/test/c14nWithComments-unit-tests.spec.ts index 4b4f033d..3b196a94 100644 --- a/test/c14nWithComments-unit-tests.spec.ts +++ b/test/c14nWithComments-unit-tests.spec.ts @@ -350,16 +350,19 @@ describe("Exclusive canonicalization with comments", function () { //var doc = new Dom().parseFromString("") const doc = new xmldom.DOMParser().parseFromString(''); const node = xpath.select1("//*[local-name(.)='y']", doc); - const sig = new SignedXml(); - // @ts-expect-error FIXME - const res = sig.getCanonXml( - [ - "http://www.w3.org/2000/09/xmldsig#enveloped-signature", - "http://www.w3.org/2001/10/xml-exc-c14n#", - ], - node, - ); - expect(res).to.equal(''); + if (xpath.isNodeLike(node)) { + const sig = new SignedXml(); + const res = sig.getCanonXml( + [ + "http://www.w3.org/2000/09/xmldsig#enveloped-signature", + "http://www.w3.org/2001/10/xml-exc-c14n#", + ], + node, + ); + expect(res).to.equal(''); + } else { + expect(xpath.isNodeLike(node)).to.be.true; + } }); it("Enveloped-signature canonicalization respects currentnode", function () { @@ -372,9 +375,12 @@ describe("Exclusive canonicalization with comments", function () { const node = xpath.select1("//*[local-name(.)='y']", doc); const sig = new SignedXml(); const transforms = ["http://www.w3.org/2000/09/xmldsig#enveloped-signature"]; - // @ts-expect-error FIXME - const res = sig.getCanonXml(transforms, node); - expect(res).to.equal(""); + if (xpath.isNodeLike(node)) { + const res = sig.getCanonXml(transforms, node); + expect(res).to.equal(""); + } else { + expect(xpath.isNodeLike(node)).to.be.true; + } }); it("The XML canonicalization method processes a node-set by imposing the following additional document order rules on the namespace and attribute nodes of each element: \ diff --git a/test/canonicalization-unit-tests.spec.ts b/test/canonicalization-unit-tests.spec.ts index 85b6a12f..f91193c8 100644 --- a/test/canonicalization-unit-tests.spec.ts +++ b/test/canonicalization-unit-tests.spec.ts @@ -399,16 +399,19 @@ describe("Canonicalization unit tests", function () { //var doc = new Dom().parseFromString("") const doc = new xmldom.DOMParser().parseFromString(''); const node = xpath.select1("//*[local-name(.)='y']", doc); - const sig = new SignedXml(); - // @ts-expect-error FIXME - const res = sig.getCanonXml( - [ - "http://www.w3.org/2000/09/xmldsig#enveloped-signature", - "http://www.w3.org/2001/10/xml-exc-c14n#", - ], - node, - ); - expect(res).to.equal(''); + if (xpath.isNodeLike(node)) { + const sig = new SignedXml(); + const res = sig.getCanonXml( + [ + "http://www.w3.org/2000/09/xmldsig#enveloped-signature", + "http://www.w3.org/2001/10/xml-exc-c14n#", + ], + node, + ); + expect(res).to.equal(''); + } else { + expect(xpath.isNodeLike(node)).to.be.true; + } }); it("Enveloped-signature canonicalization respects currentnode", function () { @@ -419,11 +422,14 @@ describe("Canonicalization unit tests", function () { ''; const doc = new xmldom.DOMParser().parseFromString(xml); const node = xpath.select1("//*[local-name(.)='y']", doc); - const sig = new SignedXml(); - const transforms = ["http://www.w3.org/2000/09/xmldsig#enveloped-signature"]; - // @ts-expect-error FIXME - const res = sig.getCanonXml(transforms, node); - expect(res).to.equal(""); + if (xpath.isNodeLike(node)) { + const sig = new SignedXml(); + const transforms = ["http://www.w3.org/2000/09/xmldsig#enveloped-signature"]; + const res = sig.getCanonXml(transforms, node); + expect(res).to.equal(""); + } else { + expect(xpath.isNodeLike(node)).to.be.true; + } }); it("The XML canonicalization method processes a node-set by imposing the following additional document order rules on the namespace and attribute nodes of each element: \ diff --git a/test/key-info-tests.spec.ts b/test/key-info-tests.spec.ts index e46b2577..c1977064 100644 --- a/test/key-info-tests.spec.ts +++ b/test/key-info-tests.spec.ts @@ -15,7 +15,11 @@ describe("KeyInfo tests", function () { const doc = new xmldom.DOMParser().parseFromString(signedXml); const x509 = xpath.select("//*[local-name(.)='X509Certificate']", doc.documentElement); // @ts-expect-error FIXME - expect(x509.length, "X509Certificate element should exist").to.equal(1); + if (xpath.isArrayOfNodes(x509)) { + expect(x509.length, "X509Certificate element should exist").to.equal(1); + } else { + expect(xpath.isArrayOfNodes(x509)).to.be.true; + } }); it("make sure private hmac key is not leaked due to key confusion", function () { diff --git a/test/signature-unit-tests.spec.ts b/test/signature-unit-tests.spec.ts index 9dcb0d29..f7e558db 100644 --- a/test/signature-unit-tests.spec.ts +++ b/test/signature-unit-tests.spec.ts @@ -1,103 +1,124 @@ import * as xpath from "xpath"; import * as xmldom from "@xmldom/xmldom"; -import { SignedXml } from "../src/index"; +import { SignedXml, createOptionalCallbackFunction } from "../src/index"; import * as fs from "fs"; import * as crypto from "crypto"; import { expect } from "chai"; +import * as utils from "../src/utils"; describe("Signature unit tests", function () { - function verifySignature(xml, mode) { + 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, ); - - const sig = new SignedXml({ idMode: mode }); - sig.publicCert = fs.readFileSync("./test/static/client_public.pem"); - // @ts-expect-error FIXME - sig.loadSignature(node); - try { - const res = sig.checkSignature(xml); - - return res; - } catch (e) { - return false; + if (xpath.isNodeLike(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; + } + } else { + expect(xpath.isNodeLike(node)).to.be.true; } } - function passValidSignature(file, mode) { + 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, toString) { + function passLoadSignature(file: string, toString?: boolean) { const xml = fs.readFileSync(file, "utf8"); const doc = new xmldom.DOMParser().parseFromString(xml); - const node = xpath.select1( + const signature = xpath.select1( "/*//*[local-name(.)='Signature' and namespace-uri(.)='http://www.w3.org/2000/09/xmldsig#']", doc, ); - const sig = new SignedXml(); - // @ts-expect-error FIXME - sig.loadSignature(toString ? node.toString() : node); + if (xpath.isElement(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.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", - ); + expect(sig.signatureAlgorithm, "wrong signature method").to.equal( + "http://www.w3.org/2000/09/xmldsig#rsa-sha1", + ); - // @ts-expect-error FIXME - expect(sig.signatureValue, "wrong signature value").to.equal( - "PI2xGt3XrVcxYZ34Kw7nFdq75c7Mmo7J0q7yeDhBprHuJal/KV9KyKG+Zy3bmQIxNwkPh0KMP5r1YMTKlyifwbWK0JitRCSa0Fa6z6+TgJi193yiR5S1MQ+esoQT0RzyIOBl9/GuJmXx/1rXnqrTxmL7UxtqKuM29/eHwF0QDUI=", - ); + sig.getCertFromKeyInfo = (keyInfo) => { + // @ts-expect-error FIXME + if (xpath.isNodeLike(keyInfo)) { + const keyInfoContents = xpath.select1( + "//*[local-name(.)='KeyInfo']/*[local-name(.)='dummyKey']", + keyInfo, + ); + if (xpath.isNodeLike(keyInfoContents)) { + const firstChild = keyInfoContents.firstChild; + if (xpath.isTextNode(firstChild)) { + expect(firstChild.data, "keyInfo clause not correctly loaded").to.equal("1234"); + } else { + expect(xpath.isTextNode(firstChild), "keyInfo has improper format").to.be.true; + } + } else { + expect(xpath.isNodeLike(keyInfoContents), "KeyInfo contents not found").to.be.true; + } + } else { + // @ts-expect-error FIXME + expect(xpath.isNodeLike(keyInfo), "KeyInfo not found").to.be.true; + } + + return fs.readFileSync("./test/static/client.pem", "latin1"); + }; - const keyInfo = xpath.select1( - "//*[local-name(.)='KeyInfo']/*[local-name(.)='dummyKey']", - // @ts-expect-error FIXME - sig.keyInfo, - ); - // @ts-expect-error FIXME - expect(keyInfo.firstChild.data, "keyInfo clause not correctly loaded").to.equal("1234"); + const checkedSignature = sig.checkSignature(xml); + expect(checkedSignature).to.be.true; - // @ts-expect-error FIXME - expect(sig.references.length).to.equal(3); + // @ts-expect-error FIXME + expect(sig.references.length).to.equal(3); - const digests = [ - "b5GCZ2xpP5T7tbLWBTkOl4CYupQ=", - "K4dI497ZCxzweDIrbndUSmtoezY=", - "sH1gxKve8wlU8LlFVa2l6w3HMJ0=", - ]; + const digests = [ + "b5GCZ2xpP5T7tbLWBTkOl4CYupQ=", + "K4dI497ZCxzweDIrbndUSmtoezY=", + "sH1gxKve8wlU8LlFVa2l6w3HMJ0=", + ]; - // @ts-expect-error FIXME - for (let i = 0; i < sig.references.length; i++) { // @ts-expect-error FIXME - const ref = sig.references[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"); + for (let i = 0; i < sig.references.length; i++) { + // @ts-expect-error FIXME + const ref = sig.references[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"); + } + } else { + expect(xpath.isNodeLike(signature)).to.be.true; } } - function failInvalidSignature(file, mode) { + function failInvalidSignature(file: string, idMode?: "wssecurity") { const xml = fs.readFileSync(file).toString(); - const res = verifySignature(xml, mode); + const res = verifySignature(xml, idMode); expect(res, "expected signature to be invalid, but it was reported valid").to.equal(false); } - function verifyDoesNotDuplicateIdAttributes(mode, prefix) { + function verifyDoesNotDuplicateIdAttributes(prefix: string, idMode?: "wssecurity") { const xml = ``; - const sig = new SignedXml({ idMode: mode }); + const sig = new SignedXml({ idMode }); sig.privateKey = fs.readFileSync("./test/static/client.pem"); sig.addReference({ xpath: "//*[local-name(.)='x']" }); sig.computeSignature(xml); @@ -105,7 +126,11 @@ describe("Signature unit tests", function () { const doc = new xmldom.DOMParser().parseFromString(signedXml); const attrs = xpath.select("//@*", doc); // @ts-expect-error FIXME - expect(attrs.length, "wrong number of attributes").to.equal(2); + if (xpath.isArrayOfNodes(attrs)) { + expect(attrs.length, "wrong number of attributes").to.equal(2); + } else { + expect(xpath.isArrayOfNodes(attrs)).to.be.true; + } } function nodeExists(doc, xpathArg) { @@ -196,7 +221,11 @@ describe("Signature unit tests", function () { const doc = new xmldom.DOMParser().parseFromString(signedXml); const references = xpath.select("//*[local-name(.)='Reference']", doc); // @ts-expect-error FIXME - expect(references.length).to.equal(2); + if (xpath.isArrayOfNodes(references)) { + expect(references.length).to.equal(2); + } else { + expect(xpath.isArrayOfNodes(references)).to.be.true; + } } it("signer adds increasing id attributes to elements", function () { @@ -209,8 +238,8 @@ describe("Signature unit tests", function () { }); it("signer does not duplicate existing id attributes", function () { - verifyDoesNotDuplicateIdAttributes(null, ""); - verifyDoesNotDuplicateIdAttributes("wssecurity", "wsu:"); + verifyDoesNotDuplicateIdAttributes(""); + verifyDoesNotDuplicateIdAttributes("wsu:", "wssecurity"); }); it("signer adds custom attributes to the signature root node", function () { @@ -227,11 +256,15 @@ describe("Signature unit tests", function () { const doc = new xmldom.DOMParser().parseFromString(sig.getSignedXml()); - expect( - // @ts-expect-error FIXME - doc.documentElement.lastChild.localName, - "the signature must be appended to the root node by default", - ).to.equal("Signature"); + const lastChild = doc.documentElement.lastChild; + if (xpath.isElement(lastChild)) { + expect( + lastChild.localName, + "the signature must be appended to the root node by default", + ).to.equal("Signature"); + } else { + expect(xpath.isElement(lastChild)).to.be.true; + } }); it("signer appends signature to a reference node", function () { @@ -251,11 +284,19 @@ describe("Signature unit tests", function () { const doc = new xmldom.DOMParser().parseFromString(sig.getSignedXml()); const referenceNode = xpath.select1("/root/name", doc); - expect( - // @ts-expect-error FIXME - referenceNode.lastChild.localName, - "the signature should be appended to root/name", - ).to.equal("Signature"); + if (xpath.isNodeLike(referenceNode)) { + const lastChild = referenceNode.lastChild; + + if (xpath.isElement(lastChild)) { + expect(lastChild.localName, "the signature should be appended to root/name").to.equal( + "Signature", + ); + } else { + expect(xpath.isElement(lastChild)).to.be.true; + } + } else { + expect(xpath.isNodeLike(referenceNode)).to.be.true; + } }); it("signer prepends signature to a reference node", function () { @@ -274,12 +315,19 @@ describe("Signature unit tests", function () { const doc = new xmldom.DOMParser().parseFromString(sig.getSignedXml()); const referenceNode = xpath.select1("/root/name", doc); - - expect( - // @ts-expect-error FIXME - referenceNode.firstChild.localName, - "the signature should be prepended to root/name", - ).to.equal("Signature"); + if (xpath.isNodeLike(referenceNode)) { + const firstChild = referenceNode.firstChild; + + if (xpath.isElement(firstChild)) { + expect(firstChild.localName, "the signature should be prepended to root/name").to.equal( + "Signature", + ); + } else { + expect(xpath.isElement(firstChild)).to.be.true; + } + } else { + expect(xpath.isNodeLike(referenceNode)).to.be.true; + } }); it("signer inserts signature before a reference node", function () { @@ -298,12 +346,20 @@ describe("Signature unit tests", function () { const doc = new xmldom.DOMParser().parseFromString(sig.getSignedXml()); const referenceNode = xpath.select1("/root/name", doc); - - expect( - // @ts-expect-error FIXME - referenceNode.previousSibling.localName, - "the signature should be inserted before to root/name", - ).to.equal("Signature"); + if (xpath.isNodeLike(referenceNode)) { + const previousSibling = referenceNode.previousSibling; + + if (xpath.isElement(previousSibling)) { + expect( + previousSibling.localName, + "the signature should be inserted before to root/name", + ).to.equal("Signature"); + } else { + expect(xpath.isElement(previousSibling)).to.be.true; + } + } else { + expect(xpath.isNodeLike(referenceNode)).to.be.true; + } }); it("signer inserts signature after a reference node", function () { @@ -323,11 +379,20 @@ describe("Signature unit tests", function () { const doc = new xmldom.DOMParser().parseFromString(sig.getSignedXml()); const referenceNode = xpath.select1("/root/name", doc); - expect( - // @ts-expect-error FIXME - referenceNode.nextSibling.localName, - "the signature should be inserted after to root/name", - ).to.equal("Signature"); + if (xpath.isNodeLike(referenceNode)) { + const nextSibling = referenceNode.nextSibling; + + if (xpath.isElement(nextSibling)) { + expect( + nextSibling.localName, + "the signature should be inserted after to root/name", + ).to.equal("Signature"); + } else { + expect(xpath.isElement(nextSibling)).to.be.true; + } + } else { + expect(xpath.isNodeLike(referenceNode)).to.be.true; + } }); it("signer creates signature with correct structure", function () { @@ -342,6 +407,10 @@ describe("Signature unit tests", function () { } class DummySignatureAlgorithm { + verifySignature = function () { + return true; + }; + getSignature = function () { return "dummy signature"; }; @@ -352,6 +421,7 @@ describe("Signature unit tests", function () { } class DummyTransformation { + includeComments = false; process = function () { return "< x/>"; }; @@ -362,6 +432,7 @@ describe("Signature unit tests", function () { } class DummyCanonicalization { + includeComments = false; process = function () { return "< x/>"; }; @@ -374,12 +445,9 @@ describe("Signature unit tests", function () { const xml = ''; const sig = new SignedXml(); - // @ts-expect-error FIXME sig.CanonicalizationAlgorithms["http://DummyTransformation"] = DummyTransformation; - // @ts-expect-error FIXME sig.CanonicalizationAlgorithms["http://DummyCanonicalization"] = DummyCanonicalization; sig.HashAlgorithms["http://dummyDigest"] = DummyDigest; - // @ts-expect-error FIXME sig.SignatureAlgorithms["http://dummySignatureAlgorithm"] = DummySignatureAlgorithm; sig.signatureAlgorithm = "http://dummySignatureAlgorithm"; @@ -500,6 +568,10 @@ describe("Signature unit tests", function () { } class DummySignatureAlgorithm { + verifySignature = function () { + return true; + }; + getSignature = function () { return "dummy signature"; }; @@ -510,6 +582,7 @@ describe("Signature unit tests", function () { } class DummyTransformation { + includeComments = false; process = function () { return "< x/>"; }; @@ -520,6 +593,7 @@ describe("Signature unit tests", function () { } class DummyCanonicalization { + includeComments = false; process = function () { return "< x/>"; }; @@ -532,12 +606,9 @@ describe("Signature unit tests", function () { const xml = ''; const sig = new SignedXml(); - // @ts-expect-error FIXME sig.CanonicalizationAlgorithms["http://DummyTransformation"] = DummyTransformation; - // @ts-expect-error FIXME sig.CanonicalizationAlgorithms["http://DummyCanonicalization"] = DummyCanonicalization; sig.HashAlgorithms["http://dummyDigest"] = DummyDigest; - // @ts-expect-error FIXME sig.SignatureAlgorithms["http://dummySignatureAlgorithm"] = DummySignatureAlgorithm; sig.signatureAlgorithm = "http://dummySignatureAlgorithm"; @@ -650,8 +721,6 @@ describe("Signature unit tests", function () { ''; const sig = new SignedXml(); sig.privateKey = fs.readFileSync("./test/static/client.pem"); - // @ts-expect-error FIXME - sig.publicCert = null; sig.addReference({ xpath: "//*[local-name(.)='x']" }); sig.addReference({ xpath: "//*[local-name(.)='y']" }); @@ -695,13 +764,19 @@ describe("Signature unit tests", function () { it("signer creates correct signature values using async callback", function () { class DummySignatureAlgorithm { - getSignature = function (signedInfo, privateKey, callback) { - const signer = crypto.createSign("RSA-SHA1"); - signer.update(signedInfo); - const res = signer.sign(privateKey, "base64"); - //Do some asynchronous things here - callback(null, res); + verifySignature = function () { + return true; }; + + getSignature = createOptionalCallbackFunction( + (signedInfo: crypto.BinaryLike, privateKey: crypto.KeyLike) => { + const signer = crypto.createSign("RSA-SHA1"); + signer.update(signedInfo); + const res = signer.sign(privateKey, "base64"); + return res; + }, + ); + getAlgorithmName = function () { return "http://www.w3.org/2000/09/xmldsig#rsa-sha1"; }; @@ -710,12 +785,9 @@ describe("Signature unit tests", function () { const xml = ''; const sig = new SignedXml(); - // @ts-expect-error FIXME sig.SignatureAlgorithms["http://dummySignatureAlgorithmAsync"] = DummySignatureAlgorithm; sig.signatureAlgorithm = "http://dummySignatureAlgorithmAsync"; sig.privateKey = fs.readFileSync("./test/static/client.pem"); - // @ts-expect-error FIXME - sig.publicCert = null; sig.addReference({ xpath: "//*[local-name(.)='x']" }); sig.addReference({ xpath: "//*[local-name(.)='y']" }); @@ -759,7 +831,6 @@ describe("Signature unit tests", function () { }); it("correctly loads signature", function () { - // @ts-expect-error FIXME passLoadSignature("./test/static/valid_signature.xml"); }); @@ -768,17 +839,14 @@ describe("Signature unit tests", function () { }); it("correctly loads signature with root level sig namespace", function () { - // @ts-expect-error FIXME passLoadSignature("./test/static/valid_signature_with_root_level_sig_namespace.xml"); }); it("verifies valid signature", function () { - // @ts-expect-error FIXME passValidSignature("./test/static/valid_signature.xml"); }); it("verifies valid signature with lowercase id attribute", function () { - // @ts-expect-error FIXME passValidSignature("./test/static/valid_signature_with_lowercase_id_attribute.xml"); }); @@ -787,42 +855,34 @@ describe("Signature unit tests", function () { }); it("verifies valid signature with reference keyInfo", function () { - // @ts-expect-error FIXME passValidSignature("./test/static/valid_signature_with_reference_keyInfo.xml"); }); it("verifies valid signature with whitespace in digestvalue", function () { - // @ts-expect-error FIXME passValidSignature("./test/static/valid_signature_with_whitespace_in_digestvalue.xml"); }); it("verifies valid utf8 signature", function () { - // @ts-expect-error FIXME passValidSignature("./test/static/valid_signature_utf8.xml"); }); it("verifies valid signature with unused prefixes", function () { - // @ts-expect-error FIXME passValidSignature("./test/static/valid_signature_with_unused_prefixes.xml"); }); it("fails invalid signature - signature value", function () { - // @ts-expect-error FIXME failInvalidSignature("./test/static/invalid_signature - signature value.xml"); }); it("fails invalid signature - hash", function () { - // @ts-expect-error FIXME failInvalidSignature("./test/static/invalid_signature - hash.xml"); }); it("fails invalid signature - non existing reference", function () { - // @ts-expect-error FIXME failInvalidSignature("./test/static/invalid_signature - non existing reference.xml"); }); it("fails invalid signature - changed content", function () { - // @ts-expect-error FIXME failInvalidSignature("./test/static/invalid_signature - changed content.xml"); }); @@ -855,8 +915,6 @@ describe("Signature unit tests", function () { const xml = ""; const sig = new SignedXml(); sig.privateKey = fs.readFileSync("./test/static/client.pem"); - // @ts-expect-error FIXME - sig.publicCert = null; sig.addReference({ xpath: "//*[local-name(.)='root']", @@ -872,8 +930,11 @@ describe("Signature unit tests", function () { const signedXml = sig.getSignedXml(); const doc = new xmldom.DOMParser().parseFromString(signedXml); const URI = xpath.select1("//*[local-name(.)='Reference']/@URI", doc); - // @ts-expect-error FIXME - expect(URI.value, `uri should be empty but instead was ${URI.value}`).to.equal(""); + if (xpath.isAttribute(URI)) { + expect(URI.value, `uri should be empty but instead was ${URI.value}`).to.equal(""); + } else { + expect(xpath.isAttribute(URI)).to.be.true; + } }); it("signer appends signature to a non-existing reference node", function () { @@ -942,8 +1003,6 @@ describe("Signature unit tests", function () { const xml = ""; const sig = new SignedXml(); sig.privateKey = fs.readFileSync("./test/static/client.pem"); - // @ts-expect-error FIXME - sig.publicCert = null; sig.addReference({ xpath: "//*[local-name(.)='root']", @@ -962,8 +1021,10 @@ describe("Signature unit tests", function () { "//*[local-name(.)='Reference']/*[local-name(.)='Transforms']/*[local-name(.)='Transform']/*[local-name(.)='InclusiveNamespaces']", doc.documentElement, ); - // @ts-expect-error FIXME - expect(inclusiveNamespaces.length, "InclusiveNamespaces element should exist").to.equal(1); + expect( + utils.isArrayHasLength(inclusiveNamespaces) && inclusiveNamespaces.length, + "InclusiveNamespaces element should exist", + ).to.equal(1); // @ts-expect-error FIXME const prefixListAttribute = inclusiveNamespaces[0].getAttribute("PrefixList"); @@ -977,8 +1038,6 @@ describe("Signature unit tests", function () { const xml = ""; const sig = new SignedXml(); sig.privateKey = fs.readFileSync("./test/static/client.pem"); - // @ts-expect-error FIXME - sig.publicCert = null; sig.addReference({ xpath: "//*[local-name(.)='root']", @@ -993,21 +1052,18 @@ describe("Signature unit tests", function () { const signedXml = sig.getSignedXml(); const doc = new xmldom.DOMParser().parseFromString(signedXml); - const inclusiveNamespaces = xpath.select( + const inclusiveNamespaces = xpath.select1( "//*[local-name(.)='Reference']/*[local-name(.)='Transforms']/*[local-name(.)='Transform']/*[local-name(.)='InclusiveNamespaces']", doc.documentElement, ); - // @ts-expect-error FIXME - expect(inclusiveNamespaces.length, "InclusiveNamespaces element should not exist").to.equal(0); + expect(inclusiveNamespaces, "InclusiveNamespaces element should not exist").to.be.undefined; }); it("creates InclusiveNamespaces element inside CanonicalizationMethod when inclusiveNamespacesPrefixList is set on SignedXml options", function () { const xml = ""; const sig = new SignedXml({ inclusiveNamespacesPrefixList: "prefix1 prefix2" }); sig.privateKey = fs.readFileSync("./test/static/client.pem"); - // @ts-expect-error FIXME - sig.publicCert = null; sig.addReference({ xpath: "//*[local-name(.)='root']", @@ -1025,8 +1081,7 @@ describe("Signature unit tests", function () { ); expect( - // @ts-expect-error FIXME - inclusiveNamespaces.length, + utils.isArrayHasLength(inclusiveNamespaces) && inclusiveNamespaces.length, "InclusiveNamespaces element should exist inside CanonicalizationMethod", ).to.equal(1); @@ -1042,8 +1097,6 @@ describe("Signature unit tests", function () { const xml = ""; const sig = new SignedXml(); // Omit inclusiveNamespacesPrefixList property sig.privateKey = fs.readFileSync("./test/static/client.pem"); - // @ts-expect-error FIXME - sig.publicCert = null; sig.addReference({ xpath: "//*[local-name(.)='root']", @@ -1055,16 +1108,15 @@ describe("Signature unit tests", function () { const signedXml = sig.getSignedXml(); const doc = new xmldom.DOMParser().parseFromString(signedXml); - const inclusiveNamespaces = xpath.select( + const inclusiveNamespaces = xpath.select1( "//*[local-name(.)='CanonicalizationMethod']/*[local-name(.)='InclusiveNamespaces']", doc.documentElement, ); expect( - // @ts-expect-error FIXME - inclusiveNamespaces.length, + inclusiveNamespaces, "InclusiveNamespaces element should not exist inside CanonicalizationMethod", - ).to.equal(0); + ).to.be.undefined; }); it("adds attributes to KeyInfo element when attrs are present in keyInfoProvider", function () { @@ -1081,23 +1133,33 @@ describe("Signature unit tests", function () { const signedXml = sig.getSignedXml(); const doc = new xmldom.DOMParser().parseFromString(signedXml); - const keyInfoElement = xpath.select("//*[local-name(.)='KeyInfo']", doc.documentElement); - // @ts-expect-error FIXME - expect(keyInfoElement.length, "KeyInfo element should exist").to.equal(1); + const keyInfoElements = xpath.select("//*[local-name(.)='KeyInfo']", doc.documentElement); // @ts-expect-error FIXME - const algorithmAttribute = keyInfoElement[0].getAttribute("CustomUri"); - expect( - algorithmAttribute, - "KeyInfo element should have the correct CustomUri attribute value", - ).to.equal("http://www.example.com/keyinfo"); - - // @ts-expect-error FIXME - const customAttribute = keyInfoElement[0].getAttribute("CustomAttribute"); - expect( - customAttribute, - "KeyInfo element should have the correct CustomAttribute attribute value", - ).to.equal("custom-value"); + if (xpath.isArrayOfNodes(keyInfoElements)) { + expect(keyInfoElements.length, "KeyInfo element should exist").to.equal(1); + const keyInfoElement = keyInfoElements[0]; + + if (xpath.isElement(keyInfoElement)) { + const algorithmAttribute = keyInfoElement.getAttribute("CustomUri"); + expect( + algorithmAttribute, + "KeyInfo element should have the correct CustomUri attribute value", + ).to.equal("http://www.example.com/keyinfo"); + + const customAttribute = keyInfoElement.getAttribute("CustomAttribute"); + expect( + customAttribute, + "KeyInfo element should have the correct CustomAttribute attribute value", + ).to.equal("custom-value"); + } else { + expect(xpath.isElement(keyInfoElement), "KeyInfo element should be an element node").to.be + .true; + } + } else { + expect(xpath.isArrayOfNodes(keyInfoElements), "KeyInfo should be an array of nodes").to.be + .true; + } }); it("adds all certificates and does not add private keys to KeyInfo element", function () { @@ -1116,25 +1178,30 @@ describe("Signature unit tests", function () { doc.documentElement, ); // @ts-expect-error FIXME - expect(x509certificates.length, "There should be exactly two certificates").to.equal(2); + if (xpath.isArrayOfNodes(x509certificates)) { + expect(x509certificates.length, "There should be exactly two certificates").to.equal(2); - // @ts-expect-error FIXME - const cert1 = x509certificates[0]; - // @ts-expect-error FIXME - const cert2 = x509certificates[1]; - expect(cert1.textContent, "X509Certificate[0] TextContent does not exist").to.exist; - expect(cert2.textContent, "X509Certificate[1] TextContent does not exist").to.exist; + const cert1 = x509certificates[0]; + const cert2 = x509certificates[1]; + expect(cert1.textContent, "X509Certificate[0] TextContent does not exist").to.exist; + expect(cert2.textContent, "X509Certificate[1] TextContent does not exist").to.exist; - const trimmedTextContent1 = cert1.textContent.trim(); - const trimmedTextContent2 = cert2.textContent.trim(); - expect(trimmedTextContent1, "Empty certificate added [0]").to.not.be.empty; - expect(trimmedTextContent2, "Empty certificate added [1]").to.not.be.empty; + const trimmedTextContent1 = cert1.textContent?.trim(); + const trimmedTextContent2 = cert2.textContent?.trim(); + expect(trimmedTextContent1, "Empty certificate added [0]").to.not.be.empty; + expect(trimmedTextContent2, "Empty certificate added [1]").to.not.be.empty; - expect(trimmedTextContent1.substring(0, 5), "Incorrect value for X509Certificate[0]").to.equal( - "MIIDC", - ); - expect(trimmedTextContent2.substring(0, 5), "Incorrect value for X509Certificate[1]").to.equal( - "MIIDZ", - ); + expect( + trimmedTextContent1?.substring(0, 5), + "Incorrect value for X509Certificate[0]", + ).to.equal("MIIDC"); + expect( + trimmedTextContent2?.substring(0, 5), + "Incorrect value for X509Certificate[1]", + ).to.equal("MIIDZ"); + } else { + expect(xpath.isArrayOfNodes(x509certificates), "X509Certificate should be an array of nodes") + .to.be.true; + } }); }); diff --git a/test/static/valid_signature_with_root_level_sig_namespace.xml b/test/static/valid_signature_with_root_level_sig_namespace.xml index 58c698ce..fc3fad07 100644 --- a/test/static/valid_signature_with_root_level_sig_namespace.xml +++ b/test/static/valid_signature_with_root_level_sig_namespace.xml @@ -1 +1 @@ -b5GCZ2xpP5T7tbLWBTkOl4CYupQ=K4dI497ZCxzweDIrbndUSmtoezY=sH1gxKve8wlU8LlFVa2l6w3HMJ0=PI2xGt3XrVcxYZ34Kw7nFdq75c7Mmo7J0q7yeDhBprHuJal/KV9KyKG+Zy3bmQIxNwkPh0KMP5r1YMTKlyifwbWK0JitRCSa0Fa6z6+TgJi193yiR5S1MQ+esoQT0RzyIOBl9/GuJmXx/1rXnqrTxmL7UxtqKuM29/eHwF0QDUI=1234 +b5GCZ2xpP5T7tbLWBTkOl4CYupQ=K4dI497ZCxzweDIrbndUSmtoezY=sH1gxKve8wlU8LlFVa2l6w3HMJ0=rR8+4xHiI8GQJ9Wty2TUbNI7Dd4uc89/BsAygYfeobEjmt4awzg6bQNA0nuQ+VggiPCYdKuKL8cPI7FUhk8osbVKdLPdy+rdJnibsyNpV87R7W5GZlFBEu/NqG6EYOMTHjpD4hq+H8ZeHC5YZDHPknPzJV8+A1UKN/BL2oWMQcg=1234 diff --git a/tsconfig.json b/tsconfig.json index 03136e85..05f5ff27 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,9 +4,9 @@ /* Basic Options */ // "incremental": true, /* Enable incremental compilation */ - "target": "es2018" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */, + "target": "es2020" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */, "module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */, - "lib": ["es2018"] /* Specify library files to be included in the compilation. */, + "lib": ["es2020"] /* Specify library files to be included in the compilation. */, "allowJs": true /* Allow javascript files to be compiled. */, // "checkJs": true, /* Report errors in .js files. */ // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */