From f7dbece1851793c5496465b0cbe4d23fd34bdd87 Mon Sep 17 00:00:00 2001 From: shunkica Date: Mon, 27 Oct 2025 09:54:15 +0100 Subject: [PATCH] fix: id attribute detection and generation during signing (resolves #520) - Run ensureHasId(node) in addAllReferences to: 1. have consistent Id matching logic as the initial pass 2. add Id attributes to elements which are present inside the Signature itself (KeyInfo, Object) - Added tests for autogenerated ids within the Signature and prefixed Ids --- src/signed-xml.ts | 22 +---- test/signature-object-tests.spec.ts | 124 ++++++++++++++++++++++++++++ test/signature-unit-tests.spec.ts | 31 +++++++ 3 files changed, 156 insertions(+), 21 deletions(-) diff --git a/src/signed-xml.ts b/src/signed-xml.ts index 50a370f1..663d3d0e 100644 --- a/src/signed-xml.ts +++ b/src/signed-xml.ts @@ -1135,27 +1135,7 @@ export class SignedXml { if (ref.isEmptyUri) { targetUri = ""; } else { - let id: string | null = null; - if (this.idMode === "wssecurity") { - const attr = utils.findAttr( - node, - "Id", - "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd", - ); - if (attr) { - id = attr.value; - } - } else { - for (const attr of this.idAttributes) { - id = node.getAttribute(attr); - if (id) { - break; - } - } - } - if (!id) { - throw new Error(`No ID attribute found on node for reference: ${ref.xpath}`); - } + const id = this.ensureHasId(node); ref.uri = id; targetUri = `#${id}`; } diff --git a/test/signature-object-tests.spec.ts b/test/signature-object-tests.spec.ts index 59b70118..75126c23 100644 --- a/test/signature-object-tests.spec.ts +++ b/test/signature-object-tests.spec.ts @@ -366,6 +366,130 @@ describe("Valid signatures with ds:Object elements", function () { const { valid, errorMessage } = checkSignature(signedXml, doc); expect(valid, errorMessage).to.be.true; }); + + it("should create valid signature and generate Id attribute for ds:Object when not provided", function () { + const xml = ""; + const sig = new SignedXml({ + privateKey, + canonicalizationAlgorithm: "http://www.w3.org/2001/10/xml-exc-c14n#", + signatureAlgorithm: "http://www.w3.org/2000/09/xmldsig#rsa-sha1", + objects: [ + { + content: "Test data in Object element", + }, + ], + }); + + sig.addReference({ + xpath: "//*[local-name(.)='Data']", + digestAlgorithm: "http://www.w3.org/2000/09/xmldsig#sha1", + transforms: [ + "http://www.w3.org/2000/09/xmldsig#enveloped-signature", + "http://www.w3.org/2001/10/xml-exc-c14n#", + ], + }); + + sig.computeSignature(xml, { prefix: "ds" }); + const signedXml = sig.getSignedXml(); + const doc = new xmldom.DOMParser().parseFromString(signedXml); + + // Find the ds:Object/Data element and get the value of its Id attribute (ensuring it was generated) + const dataEl = select1Ns("/root/ds:Signature/ds:Object/Data[@Id]", doc); + isDomNode.assertIsElementNode(dataEl); + const idValue = dataEl.getAttribute("Id"); + expect(idValue).to.be.a("string").that.is.not.empty; + + // Verify that there is a Reference pointing to the generated Id + const uri = `#${idValue}`; + const refEl = select1Ns(`/root/ds:Signature/ds:SignedInfo/ds:Reference[@URI='${uri}']`, doc); + isDomNode.assertIsElementNode(refEl); + + // Verify that the signature is valid + const { valid, errorMessage } = checkSignature(signedXml, doc); + expect(valid, errorMessage).to.be.true; + }); +}); + +describe("Should successfuly sign references to ds:KeyInfo elements", function () { + it("should create valid signatures with references to ds:KeyInfo when the Id attribute is provided", function () { + const xml = ""; + const sig = new SignedXml({ + privateKey, + canonicalizationAlgorithm: "http://www.w3.org/2001/10/xml-exc-c14n#", + signatureAlgorithm: "http://www.w3.org/2000/09/xmldsig#rsa-sha1", + keyInfoAttributes: { + Id: "key-info-1", + }, + getKeyInfoContent: () => "", + }); + + sig.addReference({ + xpath: "//*[local-name(.)='KeyInfo']", + digestAlgorithm: "http://www.w3.org/2000/09/xmldsig#sha1", + transforms: [ + "http://www.w3.org/2000/09/xmldsig#enveloped-signature", + "http://www.w3.org/2001/10/xml-exc-c14n#", + ], + }); + + sig.computeSignature(xml); + const signedXml = sig.getSignedXml(); + + const doc = new xmldom.DOMParser().parseFromString(signedXml); + + // Verify that there is a Reference to KeyInfo + const referenceEl = select1Ns( + "/root/ds:Signature/ds:SignedInfo/ds:Reference[@URI='#key-info-1']", + doc, + ); + isDomNode.assertIsElementNode(referenceEl); + + // Verify that the signature is valid + const { valid, errorMessage } = checkSignature(signedXml, doc); + expect(valid, errorMessage).to.be.true; + }); + + it("should create valid signatures with references to ds:KeyInfo when the Id attribute is autogenerated", function () { + const xml = ""; + const sig = new SignedXml({ + privateKey, + canonicalizationAlgorithm: "http://www.w3.org/2001/10/xml-exc-c14n#", + signatureAlgorithm: "http://www.w3.org/2000/09/xmldsig#rsa-sha1", + getKeyInfoContent: () => "", + }); + + sig.addReference({ + xpath: "//*[local-name(.)='KeyInfo']", + digestAlgorithm: "http://www.w3.org/2000/09/xmldsig#sha1", + transforms: [ + "http://www.w3.org/2000/09/xmldsig#enveloped-signature", + "http://www.w3.org/2001/10/xml-exc-c14n#", + ], + }); + + sig.computeSignature(xml); + const signedXml = sig.getSignedXml(); + + const doc = new xmldom.DOMParser().parseFromString(signedXml); + + // Find the KeyInfo element and get the value of its Id attribute (ensuring it was generated) + const keyInfoEl = select1Ns("/root/ds:Signature/ds:KeyInfo[@Id]", doc); + isDomNode.assertIsElementNode(keyInfoEl); + const idValue = keyInfoEl.getAttribute("Id"); + expect(idValue).to.be.a("string").that.is.not.empty; + + // Find a Reference with URI=`#${idValue}` + const uri = `#${idValue}`; + const referenceEl = select1Ns( + `/root/ds:Signature/ds:SignedInfo/ds:Reference[@URI='${uri}']`, + doc, + ); + isDomNode.assertIsElementNode(referenceEl); + + // Verify that the signature is valid + const { valid, errorMessage } = checkSignature(signedXml, doc); + expect(valid, errorMessage).to.be.true; + }); }); describe("XAdES Object support in XML signatures", function () { diff --git a/test/signature-unit-tests.spec.ts b/test/signature-unit-tests.spec.ts index deeffa9a..c0dcf136 100644 --- a/test/signature-unit-tests.spec.ts +++ b/test/signature-unit-tests.spec.ts @@ -1403,4 +1403,35 @@ describe("Signature unit tests", function () { /the following xpath cannot be signed because it was not found/, ); }); + + it("should sign references when the Id attribute is prefixed", () => { + const xml = ''; + const sig = new SignedXml({ + privateKey: fs.readFileSync("./test/static/client.pem"), + canonicalizationAlgorithm: "http://www.w3.org/2001/10/xml-exc-c14n#", + signatureAlgorithm: "http://www.w3.org/2000/09/xmldsig#rsa-sha1", + }); + + 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.computeSignature(xml); + const signedXml = sig.getSignedXml(); + + const doc = new xmldom.DOMParser().parseFromString(signedXml); + const referenceElements = xpath.select("//*[local-name(.)='Reference']", doc); + isDomNode.assertIsArrayOfNodes(referenceElements); + expect(referenceElements.length, "Reference element should exist").to.equal(1); + + const referenceElement = referenceElements[0]; + isDomNode.assertIsElementNode(referenceElement); + + const uriAttribute = referenceElement.getAttribute("URI"); + expect(uriAttribute, "Reference element should have the correct URI attribute value").to.equal( + "#unique-id", + ); + }); });