Skip to content

Commit 014b0fb

Browse files
committed
feat: add id and type attributes to Reference elements in XML signature
1 parent 95a4f47 commit 014b0fb

File tree

5 files changed

+80
-7
lines changed

5 files changed

+80
-7
lines changed

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -269,11 +269,13 @@ A `SignedXml` object provides the following methods:
269269

270270
To sign xml documents:
271271

272-
- `addReference(xpath, transforms, digestAlgorithm)` - adds a reference to a xml element where:
272+
- `addReference({ xpath, transforms, digestAlgorithm, isSignatureReference, id, type })` - adds a reference to a xml element where:
273273
- `xpath` - a string containing a XPath expression referencing a xml element
274274
- `transforms` - an array of [transform algorithms](#canonicalization-and-transformation-algorithms), the referenced element will be transformed for each value in the array
275275
- `digestAlgorithm` - one of the supported [hashing algorithms](#hashing-algorithms)
276276
- `isSignatureReference` - boolean - default `false` - indicates whether the target of this reference is located inside the `<Signature>` element (e.g. an `<Object>`)
277+
- `id` - an optional `Id` attribute to add to the reference element
278+
- `type` - the optional `Type` attribute to add to the reference element (represented as a URI)
277279
- `computeSignature(xml, [options])` - compute the signature of the given xml where:
278280
- `xml` - a string containing a xml document
279281
- `options` - an object with the following properties:

src/signed-xml.ts

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -810,6 +810,8 @@ export class SignedXml {
810810
* @param inclusiveNamespacesPrefixList The prefix list for inclusive namespace canonicalization.
811811
* @param isEmptyUri Indicates whether the URI is empty. Defaults to `false`.
812812
* @param isSignatureReference Indicates whether this reference points to an element in the signature itself (like an Object element).
813+
* @param id An optional `Id` attribute for the reference.
814+
* @param type An optional `Type` attribute for the reference.
813815
*/
814816
addReference({
815817
xpath,
@@ -820,6 +822,8 @@ export class SignedXml {
820822
inclusiveNamespacesPrefixList = [],
821823
isEmptyUri = false,
822824
isSignatureReference = false,
825+
id = undefined,
826+
type = undefined,
823827
}: Partial<Reference> & Pick<Reference, "xpath">): void {
824828
if (digestAlgorithm == null) {
825829
throw new Error("digestAlgorithm is required");
@@ -838,6 +842,8 @@ export class SignedXml {
838842
inclusiveNamespacesPrefixList,
839843
isEmptyUri,
840844
isSignatureReference,
845+
id,
846+
type,
841847
getValidatedNode: () => {
842848
throw new Error(
843849
"Reference has not been validated yet; Did you call `sig.checkSignature()`?",
@@ -1148,13 +1154,25 @@ export class SignedXml {
11481154
}
11491155

11501156
for (const node of nodes) {
1157+
let referenceAttrs = "";
1158+
11511159
if (ref.isEmptyUri) {
1152-
res += `<${prefix}Reference URI="">`;
1160+
referenceAttrs = 'URI=""';
11531161
} else {
11541162
const id = this.ensureHasId(node);
11551163
ref.uri = id;
1156-
res += `<${prefix}Reference URI="#${id}">`;
1164+
referenceAttrs = `URI="#${id}"`;
1165+
}
1166+
1167+
if (ref.id) {
1168+
referenceAttrs += ` Id="${ref.id}"`;
1169+
}
1170+
1171+
if (ref.type) {
1172+
referenceAttrs += ` Type="${ref.type}"`;
11571173
}
1174+
1175+
res += `<${prefix}Reference ${referenceAttrs}>`;
11581176
res += `<${prefix}Transforms>`;
11591177
for (const trans of ref.transforms || []) {
11601178
const transform = this.findCanonicalizationAlgorithm(trans);
@@ -1368,6 +1386,14 @@ export class SignedXml {
13681386
referenceElem.setAttribute("URI", `#${id}`);
13691387
}
13701388

1389+
if (ref.id) {
1390+
referenceElem.setAttribute("Id", ref.id);
1391+
}
1392+
1393+
if (ref.type) {
1394+
referenceElem.setAttribute("Type", ref.type);
1395+
}
1396+
13711397
const transformsElem = signatureDoc.createElementNS(
13721398
signatureNamespace,
13731399
`${prefix}Transforms`,

src/types.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,12 @@ export interface Reference {
146146
// Optional. Indicates if this reference points to an element within the Signature
147147
isSignatureReference?: boolean;
148148

149+
// Optional. The `Id` attribute of the reference node.
150+
id?: string;
151+
152+
// Optional. The `Type` attribute of the reference node.
153+
type?: string;
154+
149155
// Optional. The type of the reference node.
150156
ancestorNamespaces?: NamespacePrefix[];
151157

test/signature-object-tests.spec.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -683,15 +683,14 @@ describe("Object support in XML signatures", function () {
683683

684684
describe("XAdES Object support in XML signatures", function () {
685685
it("should be able to add and sign XAdES objects", function () {
686-
const rootId = "root_0";
687686
const signatureId = "signature_0";
688687
const signedPropertiesId = "signedProperties_0";
689688

690689
const privateKey = fs.readFileSync("./test/static/client.pem");
691690
const publicCert = fs.readFileSync("./test/static/client_public.pem");
692691
const publicCertDer = fs.readFileSync("./test/static/client_public.der");
693692
const publicCertDigest = new Sha256().getHash(publicCertDer);
694-
const xml = `<root Id="${rootId}"><content>text</content></root>`;
693+
const xml = `<root><content>text</content></root>`;
695694

696695
const sig = new SignedXml({
697696
publicCert: publicCert,
@@ -701,7 +700,7 @@ describe("XAdES Object support in XML signatures", function () {
701700
getObjectContent: () => [
702701
{
703702
content:
704-
`<xades:QualifyingProperties xmlns:xades="http://uri.etsi.org/01903/v1.3.2#" Target="#${rootId}">` +
703+
`<xades:QualifyingProperties xmlns:xades="http://uri.etsi.org/01903/v1.3.2#" Target="#${signatureId}">` +
705704
`<xades:SignedProperties Id="${signedPropertiesId}">` +
706705
`<xades:SignedSignatureProperties>` +
707706
`<xades:SigningTime>2025-06-21T12:00:00Z</xades:SigningTime>` +
@@ -717,7 +716,8 @@ describe("XAdES Object support in XML signatures", function () {
717716
});
718717

719718
sig.addReference({
720-
xpath: `//*[@Id='${rootId}']`,
719+
xpath: `/*`,
720+
isEmptyUri: true,
721721
digestAlgorithm: "http://www.w3.org/2001/04/xmlenc#sha256",
722722
transforms: [
723723
"http://www.w3.org/2000/09/xmldsig#enveloped-signature",
@@ -727,6 +727,7 @@ describe("XAdES Object support in XML signatures", function () {
727727

728728
sig.addReference({
729729
xpath: `//*[@Id='${signedPropertiesId}']`,
730+
type: "http://uri.etsi.org/01903#SignedProperties",
730731
digestAlgorithm: "http://www.w3.org/2001/04/xmlenc#sha256",
731732
transforms: ["http://www.w3.org/2001/10/xml-exc-c14n#"],
732733
isSignatureReference: true,

test/signature-unit-tests.spec.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1279,4 +1279,42 @@ describe("Signature unit tests", function () {
12791279
"MIIDZ",
12801280
);
12811281
});
1282+
1283+
it("adds id and type attributes to Reference elements when provided", function () {
1284+
const xml = "<root><x /></root>";
1285+
const sig = new SignedXml();
1286+
sig.privateKey = fs.readFileSync("./test/static/client.pem");
1287+
1288+
sig.addReference({
1289+
xpath: "//*[local-name(.)='x']",
1290+
digestAlgorithm: "http://www.w3.org/2000/09/xmldsig#sha1",
1291+
transforms: ["http://www.w3.org/2001/10/xml-exc-c14n#"],
1292+
id: "ref-1",
1293+
type: "http://www.w3.org/2000/09/xmldsig#Object",
1294+
});
1295+
1296+
sig.canonicalizationAlgorithm = "http://www.w3.org/2001/10/xml-exc-c14n#";
1297+
sig.signatureAlgorithm = "http://www.w3.org/2000/09/xmldsig#rsa-sha1";
1298+
sig.computeSignature(xml);
1299+
const signedXml = sig.getSignedXml();
1300+
1301+
const doc = new xmldom.DOMParser().parseFromString(signedXml);
1302+
const referenceElements = xpath.select("//*[local-name(.)='Reference']", doc);
1303+
isDomNode.assertIsArrayOfNodes(referenceElements);
1304+
expect(referenceElements.length, "Reference element should exist").to.equal(1);
1305+
1306+
const referenceElement = referenceElements[0];
1307+
isDomNode.assertIsElementNode(referenceElement);
1308+
1309+
const idAttribute = referenceElement.getAttribute("Id");
1310+
expect(idAttribute, "Reference element should have the correct Id attribute value").to.equal(
1311+
"ref-1",
1312+
);
1313+
1314+
const typeAttribute = referenceElement.getAttribute("Type");
1315+
expect(
1316+
typeAttribute,
1317+
"Reference element should have the correct Type attribute value",
1318+
).to.equal("http://www.w3.org/2000/09/xmldsig#Object");
1319+
});
12821320
});

0 commit comments

Comments
 (0)