xml-crypto-next 7.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1040 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SignedXml = void 0;
4
+ const isDomNode = require("@xmldom/is-dom-node");
5
+ const xmldom = require("@xmldom/xmldom");
6
+ const util_1 = require("util");
7
+ const xpath = require("xpath");
8
+ const c14n = require("./c14n-canonicalization");
9
+ const envelopedSignatures = require("./enveloped-signature");
10
+ const execC14n = require("./exclusive-canonicalization");
11
+ const hashAlgorithms = require("./hash-algorithms");
12
+ const signatureAlgorithms = require("./signature-algorithms");
13
+ const utils = require("./utils");
14
+ class SignedXml {
15
+ /**
16
+ * The SignedXml constructor provides an abstraction for sign and verify xml documents. The object is constructed using
17
+ * @param options {@link SignedXmlOptions}
18
+ */
19
+ constructor(options = {}) {
20
+ /**
21
+ * One of the supported signature algorithms.
22
+ * @see {@link SignatureAlgorithmType}
23
+ */
24
+ this.signatureAlgorithm = undefined;
25
+ /**
26
+ * Rules used to convert an XML document into its canonical form.
27
+ */
28
+ this.canonicalizationAlgorithm = undefined;
29
+ /**
30
+ * It specifies a list of namespace prefixes that should be considered "inclusive" during the canonicalization process.
31
+ */
32
+ this.inclusiveNamespacesPrefixList = [];
33
+ this.namespaceResolver = {
34
+ lookupNamespaceURI: function ( /* prefix */) {
35
+ throw new Error("Not implemented");
36
+ },
37
+ };
38
+ this.implicitTransforms = [];
39
+ this.keyInfoAttributes = {};
40
+ this.getKeyInfoContent = SignedXml.getKeyInfoContent;
41
+ this.getCertFromKeyInfo = SignedXml.getCertFromKeyInfo;
42
+ // Internal state
43
+ this.id = 0;
44
+ this.signedXml = "";
45
+ this.signatureXml = "";
46
+ this.signatureNode = null;
47
+ this.signatureValue = "";
48
+ this.originalXmlWithIds = "";
49
+ this.keyInfo = null;
50
+ /**
51
+ * Contains the references that were signed.
52
+ * @see {@link Reference}
53
+ */
54
+ this.references = [];
55
+ /**
56
+ * Contains the canonicalized XML of the references that were validly signed.
57
+ *
58
+ * This populates with the canonical XML of the reference only after
59
+ * verifying the signature is cryptographically authentic.
60
+ */
61
+ this.signedReferences = [];
62
+ /**
63
+ * To add a new transformation algorithm create a new class that implements the {@link TransformationAlgorithm} interface, and register it here. More info: {@link https://github.com/node-saml/xml-crypto#customizing-algorithms|Customizing Algorithms}
64
+ */
65
+ this.CanonicalizationAlgorithms = {
66
+ "http://www.w3.org/TR/2001/REC-xml-c14n-20010315": c14n.C14nCanonicalization,
67
+ "http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments": c14n.C14nCanonicalizationWithComments,
68
+ "http://www.w3.org/2001/10/xml-exc-c14n#": execC14n.ExclusiveCanonicalization,
69
+ "http://www.w3.org/2001/10/xml-exc-c14n#WithComments": execC14n.ExclusiveCanonicalizationWithComments,
70
+ "http://www.w3.org/2000/09/xmldsig#enveloped-signature": envelopedSignatures.EnvelopedSignature,
71
+ };
72
+ // TODO: In v7.x we may consider deprecating sha1
73
+ /**
74
+ * To add a new hash algorithm create a new class that implements the {@link HashAlgorithm} interface, and register it here. More info: {@link https://github.com/node-saml/xml-crypto#customizing-algorithms|Customizing Algorithms}
75
+ */
76
+ this.HashAlgorithms = {
77
+ "http://www.w3.org/2000/09/xmldsig#sha1": hashAlgorithms.Sha1,
78
+ "http://www.w3.org/2001/04/xmlenc#sha256": hashAlgorithms.Sha256,
79
+ "http://www.w3.org/2001/04/xmlenc#sha512": hashAlgorithms.Sha512,
80
+ };
81
+ // TODO: In v7.x we may consider deprecating sha1
82
+ /**
83
+ * To add a new signature algorithm create a new class that implements the {@link SignatureAlgorithm} interface, and register it here. More info: {@link https://github.com/node-saml/xml-crypto#customizing-algorithms|Customizing Algorithms}
84
+ */
85
+ this.SignatureAlgorithms = {
86
+ "http://www.w3.org/2000/09/xmldsig#rsa-sha1": signatureAlgorithms.RsaSha1,
87
+ "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256": signatureAlgorithms.RsaSha256,
88
+ "http://www.w3.org/2007/05/xmldsig-more#sha256-rsa-MGF1": signatureAlgorithms.RsaSha256Mgf1,
89
+ "http://www.w3.org/2007/05/xmldsig-more#sha384-rsa-MGF1": signatureAlgorithms.RsaSha384Mgf1,
90
+ "http://www.w3.org/2001/04/xmldsig-more#rsa-sha384": signatureAlgorithms.RsaSha384,
91
+ "http://www.w3.org/2007/05/xmldsig-more#sha512-rsa-MGF1": signatureAlgorithms.RsaSha512Mgf1,
92
+ "http://www.w3.org/2001/04/xmldsig-more#rsa-sha512": signatureAlgorithms.RsaSha512,
93
+ "http://www.w3.org/2007/05/xmldsig-more#eddsa-ed25519": signatureAlgorithms.Ed25519,
94
+ // Disabled by default due to key confusion concerns.
95
+ // 'http://www.w3.org/2000/09/xmldsig#hmac-sha1': SignatureAlgorithms.HmacSha1
96
+ };
97
+ const { idMode, idAttribute, privateKey, publicCert, signatureAlgorithm, canonicalizationAlgorithm, inclusiveNamespacesPrefixList, implicitTransforms, keyInfoAttributes, getKeyInfoContent, getCertFromKeyInfo, objects, } = options;
98
+ // Options
99
+ this.idMode = idMode;
100
+ this.idAttributes = ["Id", "ID", "id"];
101
+ if (idAttribute) {
102
+ this.idAttributes.unshift(idAttribute);
103
+ }
104
+ this.privateKey = privateKey;
105
+ this.publicCert = publicCert;
106
+ this.signatureAlgorithm = signatureAlgorithm ?? this.signatureAlgorithm;
107
+ this.canonicalizationAlgorithm = canonicalizationAlgorithm;
108
+ if (typeof inclusiveNamespacesPrefixList === "string") {
109
+ this.inclusiveNamespacesPrefixList = inclusiveNamespacesPrefixList.split(" ");
110
+ }
111
+ else if (utils.isArrayHasLength(inclusiveNamespacesPrefixList)) {
112
+ this.inclusiveNamespacesPrefixList = inclusiveNamespacesPrefixList;
113
+ }
114
+ this.implicitTransforms = implicitTransforms ?? this.implicitTransforms;
115
+ this.keyInfoAttributes = keyInfoAttributes ?? this.keyInfoAttributes;
116
+ this.getKeyInfoContent = getKeyInfoContent ?? this.getKeyInfoContent;
117
+ this.getCertFromKeyInfo = getCertFromKeyInfo ?? SignedXml.noop;
118
+ this.objects = objects;
119
+ this.CanonicalizationAlgorithms;
120
+ this.HashAlgorithms;
121
+ this.SignatureAlgorithms;
122
+ }
123
+ /**
124
+ * Due to key-confusion issues, it's risky to have both hmac
125
+ * and digital signature algorithms enabled at the same time.
126
+ * This enables HMAC and disables other signing algorithms.
127
+ */
128
+ enableHMAC() {
129
+ this.SignatureAlgorithms = {
130
+ "http://www.w3.org/2000/09/xmldsig#hmac-sha1": signatureAlgorithms.HmacSha1,
131
+ };
132
+ this.getKeyInfoContent = SignedXml.noop;
133
+ }
134
+ /**
135
+ * Builds the contents of a KeyInfo element as an XML string.
136
+ *
137
+ * For example, if the value of the prefix argument is 'foo', then
138
+ * the resultant XML string will be "<foo:X509Data></foo:X509Data>"
139
+ *
140
+ * @return an XML string representation of the contents of a KeyInfo element, or `null` if no `KeyInfo` element should be included
141
+ */
142
+ static getKeyInfoContent({ publicCert, prefix }) {
143
+ if (publicCert == null) {
144
+ return null;
145
+ }
146
+ prefix = prefix ? `${prefix}:` : "";
147
+ let x509Certs = "";
148
+ if (Buffer.isBuffer(publicCert)) {
149
+ publicCert = publicCert.toString("latin1");
150
+ }
151
+ let publicCertMatches = [];
152
+ if (typeof publicCert === "string") {
153
+ publicCertMatches = publicCert.match(utils.EXTRACT_X509_CERTS) || [];
154
+ }
155
+ if (publicCertMatches.length > 0) {
156
+ x509Certs = publicCertMatches
157
+ .map((c) => `<${prefix}X509Certificate>${utils
158
+ .pemToDer(c)
159
+ .toString("base64")}</${prefix}X509Certificate>`)
160
+ .join("");
161
+ }
162
+ return `<${prefix}X509Data>${x509Certs}</${prefix}X509Data>`;
163
+ }
164
+ /**
165
+ * Returns the value of the signing certificate based on the contents of the
166
+ * specified KeyInfo.
167
+ *
168
+ * @param keyInfo KeyInfo element (@see https://www.w3.org/TR/2008/REC-xmldsig-core-20080610/#sec-X509Data)
169
+ * @return the signing certificate as a string in PEM format
170
+ */
171
+ static getCertFromKeyInfo(keyInfo) {
172
+ if (keyInfo != null) {
173
+ const cert = xpath.select1(".//*[local-name(.)='X509Certificate']", keyInfo);
174
+ if (isDomNode.isNodeLike(cert)) {
175
+ return utils.derToPem(cert.textContent ?? "", "CERTIFICATE");
176
+ }
177
+ }
178
+ return null;
179
+ }
180
+ checkSignature(xml, callback) {
181
+ if (callback != null && typeof callback !== "function") {
182
+ throw new Error("Last parameter must be a callback function");
183
+ }
184
+ this.signedXml = xml;
185
+ const doc = new xmldom.DOMParser().parseFromString(xml);
186
+ // Reset the references as only references from our re-parsed signedInfo node can be trusted
187
+ this.references = [];
188
+ const unverifiedSignedInfoCanon = this.getCanonSignedInfoXml(doc);
189
+ if (!unverifiedSignedInfoCanon) {
190
+ if (callback) {
191
+ callback(new Error("Canonical signed info cannot be empty"), false);
192
+ return;
193
+ }
194
+ throw new Error("Canonical signed info cannot be empty");
195
+ }
196
+ // unsigned, verify later to keep with consistent callback behavior
197
+ const parsedUnverifiedSignedInfo = new xmldom.DOMParser().parseFromString(unverifiedSignedInfoCanon, "text/xml");
198
+ const unverifiedSignedInfoDoc = parsedUnverifiedSignedInfo.documentElement;
199
+ if (!unverifiedSignedInfoDoc) {
200
+ if (callback) {
201
+ callback(new Error("Could not parse unverifiedSignedInfoCanon into a document"), false);
202
+ return;
203
+ }
204
+ throw new Error("Could not parse unverifiedSignedInfoCanon into a document");
205
+ }
206
+ const references = utils.findChildren(unverifiedSignedInfoDoc, "Reference");
207
+ if (!utils.isArrayHasLength(references)) {
208
+ if (callback) {
209
+ callback(new Error("could not find any Reference elements"), false);
210
+ return;
211
+ }
212
+ throw new Error("could not find any Reference elements");
213
+ }
214
+ // TODO: In a future release we'd like to load the Signature and its References at the same time,
215
+ // however, in the `.loadSignature()` method we don't have the entire document,
216
+ // which we need to to keep the inclusive namespaces
217
+ for (const reference of references) {
218
+ this.loadReference(reference);
219
+ }
220
+ /* eslint-disable-next-line deprecation/deprecation */
221
+ if (!this.getReferences().every((ref) => this.validateReference(ref, doc))) {
222
+ /* Trustworthiness can only be determined if SignedInfo's (which holds References' DigestValue(s)
223
+ which were validated at this stage) signature is valid. Execution does not proceed to validate
224
+ signature phase thus each References' DigestValue must be considered to be untrusted (attacker
225
+ might have injected any data with new new references and/or recalculated new DigestValue for
226
+ altered Reference(s)). Returning any content via `signedReferences` would give false sense of
227
+ trustworthiness if/when SignedInfo's (which holds references' DigestValues) signature is not
228
+ valid(ated). Put simply: if one fails, they are all not trustworthy.
229
+ */
230
+ this.signedReferences = [];
231
+ this.references.forEach((ref) => {
232
+ ref.signedReference = undefined;
233
+ });
234
+ // TODO: add this breaking change here later on for even more security: `this.references = [];`
235
+ if (callback) {
236
+ callback(new Error("Could not validate all references"), false);
237
+ return;
238
+ }
239
+ // We return false because some references validated, but not all
240
+ // We should actually be throwing an error here, but that would be a breaking change
241
+ // See https://www.w3.org/TR/xmldsig-core/#sec-CoreValidation
242
+ return false;
243
+ }
244
+ // (Stage B authentication step, show that the `signedInfoCanon` is signed)
245
+ // First find the key & signature algorithm, these should match
246
+ // Stage B: Take the signature algorithm and key and verify the `SignatureValue` against the canonicalized `SignedInfo`
247
+ const signer = this.findSignatureAlgorithm(this.signatureAlgorithm);
248
+ const key = this.getCertFromKeyInfo(this.keyInfo) || this.publicCert || this.privateKey;
249
+ if (key == null) {
250
+ throw new Error("KeyInfo or publicCert or privateKey is required to validate signature");
251
+ }
252
+ // Check the signature verification to know whether to reset signature value or not.
253
+ const sigRes = signer.verifySignature(unverifiedSignedInfoCanon, key, this.signatureValue);
254
+ if (sigRes === true) {
255
+ if (callback) {
256
+ callback(null, true);
257
+ }
258
+ else {
259
+ return true;
260
+ }
261
+ }
262
+ else {
263
+ // Ideally, we would start by verifying the `signedInfoCanon` first,
264
+ // but that may cause some breaking changes, so we'll handle that in v7.x.
265
+ // If we were validating `signedInfoCanon` first, we wouldn't have to reset this array.
266
+ this.signedReferences = [];
267
+ this.references.forEach((ref) => {
268
+ ref.signedReference = undefined;
269
+ });
270
+ // TODO: add this breaking change here later on for even more security: `this.references = [];`
271
+ if (callback) {
272
+ callback(new Error(`invalid signature: the signature value ${this.signatureValue} is incorrect`));
273
+ return; // return early
274
+ }
275
+ else {
276
+ throw new Error(`invalid signature: the signature value ${this.signatureValue} is incorrect`);
277
+ }
278
+ }
279
+ }
280
+ getCanonSignedInfoXml(doc) {
281
+ if (this.signatureNode == null) {
282
+ throw new Error("No signature found.");
283
+ }
284
+ if (typeof this.canonicalizationAlgorithm !== "string") {
285
+ throw new Error("Missing canonicalizationAlgorithm when trying to get signed info for XML");
286
+ }
287
+ const signedInfo = utils.findChildren(this.signatureNode, "SignedInfo");
288
+ if (signedInfo.length === 0) {
289
+ throw new Error("could not find SignedInfo element in the message");
290
+ }
291
+ if (signedInfo.length > 1) {
292
+ throw new Error("could not get canonicalized signed info for a signature that contains multiple SignedInfo nodes");
293
+ }
294
+ if (this.canonicalizationAlgorithm === "http://www.w3.org/TR/2001/REC-xml-c14n-20010315" ||
295
+ this.canonicalizationAlgorithm ===
296
+ "http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments") {
297
+ if (!doc || typeof doc !== "object") {
298
+ throw new Error("When canonicalization method is non-exclusive, whole xml dom must be provided as an argument");
299
+ }
300
+ }
301
+ /**
302
+ * Search for ancestor namespaces before canonicalization.
303
+ */
304
+ const ancestorNamespaces = utils.findAncestorNs(doc, "//*[local-name()='SignedInfo']");
305
+ const c14nOptions = {
306
+ ancestorNamespaces: ancestorNamespaces,
307
+ };
308
+ return this.getCanonXml([this.canonicalizationAlgorithm], signedInfo[0], c14nOptions);
309
+ }
310
+ getCanonReferenceXml(doc, ref, node) {
311
+ /**
312
+ * Search for ancestor namespaces before canonicalization.
313
+ */
314
+ if (Array.isArray(ref.transforms)) {
315
+ ref.ancestorNamespaces = utils.findAncestorNs(doc, ref.xpath, this.namespaceResolver);
316
+ }
317
+ const c14nOptions = {
318
+ inclusiveNamespacesPrefixList: ref.inclusiveNamespacesPrefixList,
319
+ ancestorNamespaces: ref.ancestorNamespaces,
320
+ };
321
+ return this.getCanonXml(ref.transforms, node, c14nOptions);
322
+ }
323
+ calculateSignatureValue(doc, callback) {
324
+ const signedInfoCanon = this.getCanonSignedInfoXml(doc);
325
+ const signer = this.findSignatureAlgorithm(this.signatureAlgorithm);
326
+ if (this.privateKey == null) {
327
+ throw new Error("Private key is required to compute signature");
328
+ }
329
+ if (typeof callback === "function") {
330
+ signer.getSignature(signedInfoCanon, this.privateKey, callback);
331
+ }
332
+ else {
333
+ this.signatureValue = signer.getSignature(signedInfoCanon, this.privateKey);
334
+ }
335
+ }
336
+ findSignatureAlgorithm(name) {
337
+ if (name == null) {
338
+ throw new Error("signatureAlgorithm is required");
339
+ }
340
+ const algo = this.SignatureAlgorithms[name];
341
+ if (algo) {
342
+ return new algo();
343
+ }
344
+ else {
345
+ throw new Error(`signature algorithm '${name}' is not supported`);
346
+ }
347
+ }
348
+ findCanonicalizationAlgorithm(name) {
349
+ if (name != null) {
350
+ const algo = this.CanonicalizationAlgorithms[name];
351
+ if (algo) {
352
+ return new algo();
353
+ }
354
+ }
355
+ throw new Error(`canonicalization algorithm '${name}' is not supported`);
356
+ }
357
+ findHashAlgorithm(name) {
358
+ const algo = this.HashAlgorithms[name];
359
+ if (algo) {
360
+ return new algo();
361
+ }
362
+ else {
363
+ throw new Error(`hash algorithm '${name}' is not supported`);
364
+ }
365
+ }
366
+ validateElementAgainstReferences(elemOrXpath, doc) {
367
+ let elem;
368
+ if (typeof elemOrXpath === "string") {
369
+ const firstElem = xpath.select1(elemOrXpath, doc);
370
+ isDomNode.assertIsElementNode(firstElem);
371
+ elem = firstElem;
372
+ }
373
+ else {
374
+ elem = elemOrXpath;
375
+ }
376
+ /* eslint-disable-next-line deprecation/deprecation */
377
+ for (const ref of this.getReferences()) {
378
+ const uri = ref.uri?.[0] === "#" ? ref.uri.substring(1) : ref.uri;
379
+ for (const attr of this.idAttributes) {
380
+ const elemId = elem.getAttribute(attr);
381
+ if (uri === elemId) {
382
+ ref.xpath = `//*[@*[local-name(.)='${attr}']='${uri}']`;
383
+ break; // found the correct element, no need to check further
384
+ }
385
+ }
386
+ const canonXml = this.getCanonReferenceXml(doc, ref, elem);
387
+ const hash = this.findHashAlgorithm(ref.digestAlgorithm);
388
+ const digest = hash.getHash(canonXml);
389
+ if (utils.validateDigestValue(digest, ref.digestValue)) {
390
+ return ref;
391
+ }
392
+ }
393
+ throw new Error("No references passed validation");
394
+ }
395
+ validateReference(ref, doc) {
396
+ const uri = ref.uri?.[0] === "#" ? ref.uri.substring(1) : ref.uri;
397
+ let elem = null;
398
+ if (uri === "") {
399
+ elem = xpath.select1("//*", doc);
400
+ }
401
+ else if (uri?.indexOf("'") !== -1) {
402
+ // xpath injection
403
+ throw new Error("Cannot validate a uri with quotes inside it");
404
+ }
405
+ else {
406
+ let num_elements_for_id = 0;
407
+ for (const attr of this.idAttributes) {
408
+ const tmp_elemXpath = `//*[@*[local-name(.)='${attr}']='${uri}']`;
409
+ const tmp_elem = xpath.select(tmp_elemXpath, doc);
410
+ if (utils.isArrayHasLength(tmp_elem)) {
411
+ num_elements_for_id += tmp_elem.length;
412
+ if (num_elements_for_id > 1) {
413
+ throw new Error("Cannot validate a document which contains multiple elements with the " +
414
+ "same value for the ID / Id / Id attributes, in order to prevent " +
415
+ "signature wrapping attack.");
416
+ }
417
+ elem = tmp_elem[0];
418
+ ref.xpath = tmp_elemXpath;
419
+ }
420
+ }
421
+ }
422
+ ref.getValidatedNode = (0, util_1.deprecate)((xpathSelector) => {
423
+ xpathSelector = xpathSelector || ref.xpath;
424
+ if (typeof xpathSelector !== "string" || ref.validationError != null) {
425
+ return null;
426
+ }
427
+ const selectedValue = xpath.select1(xpathSelector, doc);
428
+ return isDomNode.isNodeLike(selectedValue) ? selectedValue : null;
429
+ }, "`ref.getValidatedNode()` is deprecated and insecure. Use `ref.signedReference` or `this.getSignedReferences()` instead.");
430
+ if (!isDomNode.isNodeLike(elem)) {
431
+ const validationError = new Error(`invalid signature: the signature references an element with uri ${ref.uri} but could not find such element in the xml`);
432
+ ref.validationError = validationError;
433
+ return false;
434
+ }
435
+ const canonXml = this.getCanonReferenceXml(doc, ref, elem);
436
+ const hash = this.findHashAlgorithm(ref.digestAlgorithm);
437
+ const digest = hash.getHash(canonXml);
438
+ if (!utils.validateDigestValue(digest, ref.digestValue)) {
439
+ const validationError = new Error(`invalid signature: for uri ${ref.uri} calculated digest is ${digest} but the xml to validate supplies digest ${ref.digestValue}`);
440
+ ref.validationError = validationError;
441
+ return false;
442
+ }
443
+ // This step can only be done after we have verified the `signedInfo`.
444
+ // We verified that they have same hash,
445
+ // thus the `canonXml` and _only_ the `canonXml` can be trusted.
446
+ // Append this to `signedReferences`.
447
+ this.signedReferences.push(canonXml);
448
+ ref.signedReference = canonXml;
449
+ return true;
450
+ }
451
+ findSignatures(doc) {
452
+ const nodes = xpath.select("//*[local-name(.)='Signature' and namespace-uri(.)='http://www.w3.org/2000/09/xmldsig#']", doc);
453
+ return isDomNode.isArrayOfNodes(nodes) ? nodes : [];
454
+ }
455
+ /**
456
+ * Loads the signature information from the provided XML node or string.
457
+ *
458
+ * @param signatureNode The XML node or string representing the signature.
459
+ */
460
+ loadSignature(signatureNode) {
461
+ if (typeof signatureNode === "string") {
462
+ this.signatureNode = signatureNode = new xmldom.DOMParser().parseFromString(signatureNode);
463
+ }
464
+ else {
465
+ this.signatureNode = signatureNode;
466
+ }
467
+ this.signatureXml = signatureNode.toString();
468
+ const node = xpath.select1(".//*[local-name(.)='CanonicalizationMethod']/@Algorithm", signatureNode);
469
+ if (!isDomNode.isNodeLike(node)) {
470
+ throw new Error("could not find CanonicalizationMethod/@Algorithm element");
471
+ }
472
+ if (isDomNode.isAttributeNode(node)) {
473
+ this.canonicalizationAlgorithm = node.value;
474
+ }
475
+ const signatureAlgorithm = xpath.select1(".//*[local-name(.)='SignatureMethod']/@Algorithm", signatureNode);
476
+ if (isDomNode.isAttributeNode(signatureAlgorithm)) {
477
+ this.signatureAlgorithm = signatureAlgorithm.value;
478
+ }
479
+ const signedInfoNodes = utils.findChildren(this.signatureNode, "SignedInfo");
480
+ if (!utils.isArrayHasLength(signedInfoNodes)) {
481
+ throw new Error("no signed info node found");
482
+ }
483
+ if (signedInfoNodes.length > 1) {
484
+ throw new Error("could not load signature that contains multiple SignedInfo nodes");
485
+ }
486
+ // Try to operate on the c14n version of `signedInfo`. This forces the initial `getReferences()`
487
+ // API call to always return references that are loaded under the canonical `SignedInfo`
488
+ // in the case that the client access the `.references` **before** signature verification.
489
+ // Ensure canonicalization algorithm is exclusive, otherwise we'd need the entire document
490
+ let canonicalizationAlgorithmForSignedInfo = this.canonicalizationAlgorithm;
491
+ if (!canonicalizationAlgorithmForSignedInfo ||
492
+ canonicalizationAlgorithmForSignedInfo ===
493
+ "http://www.w3.org/TR/2001/REC-xml-c14n-20010315" ||
494
+ canonicalizationAlgorithmForSignedInfo ===
495
+ "http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments") {
496
+ canonicalizationAlgorithmForSignedInfo = "http://www.w3.org/2001/10/xml-exc-c14n#";
497
+ }
498
+ const temporaryCanonSignedInfo = this.getCanonXml([canonicalizationAlgorithmForSignedInfo], signedInfoNodes[0]);
499
+ const temporaryCanonSignedInfoXml = new xmldom.DOMParser().parseFromString(temporaryCanonSignedInfo, "text/xml");
500
+ const signedInfoDoc = temporaryCanonSignedInfoXml.documentElement;
501
+ this.references = [];
502
+ const references = utils.findChildren(signedInfoDoc, "Reference");
503
+ if (!utils.isArrayHasLength(references)) {
504
+ throw new Error("could not find any Reference elements");
505
+ }
506
+ for (const reference of references) {
507
+ this.loadReference(reference);
508
+ }
509
+ const signatureValue = xpath.select1(".//*[local-name(.)='SignatureValue']/text()", signatureNode);
510
+ if (isDomNode.isTextNode(signatureValue)) {
511
+ this.signatureValue = signatureValue.data.replace(/\r?\n/g, "");
512
+ }
513
+ const keyInfo = xpath.select1(".//*[local-name(.)='KeyInfo']", signatureNode);
514
+ if (isDomNode.isNodeLike(keyInfo)) {
515
+ this.keyInfo = keyInfo;
516
+ }
517
+ }
518
+ /**
519
+ * Load the reference xml node to a model
520
+ *
521
+ */
522
+ loadReference(refNode) {
523
+ let nodes = utils.findChildren(refNode, "DigestMethod");
524
+ if (nodes.length === 0) {
525
+ throw new Error(`could not find DigestMethod in reference ${refNode.toString()}`);
526
+ }
527
+ const digestAlgoNode = nodes[0];
528
+ const attr = utils.findAttr(digestAlgoNode, "Algorithm");
529
+ if (!attr) {
530
+ throw new Error(`could not find Algorithm attribute in node ${digestAlgoNode.toString()}`);
531
+ }
532
+ const digestAlgo = attr.value;
533
+ nodes = utils.findChildren(refNode, "DigestValue");
534
+ if (nodes.length === 0) {
535
+ throw new Error(`could not find DigestValue node in reference ${refNode.toString()}`);
536
+ }
537
+ if (nodes.length > 1) {
538
+ throw new Error(`could not load reference for a node that contains multiple DigestValue nodes: ${refNode.toString()}`);
539
+ }
540
+ const digestValue = nodes[0].textContent;
541
+ if (!digestValue) {
542
+ throw new Error(`could not find the value of DigestValue in ${refNode.toString()}`);
543
+ }
544
+ const transforms = [];
545
+ let inclusiveNamespacesPrefixList = [];
546
+ nodes = utils.findChildren(refNode, "Transforms");
547
+ if (nodes.length !== 0) {
548
+ const transformsNode = nodes[0];
549
+ const transformsAll = utils.findChildren(transformsNode, "Transform");
550
+ for (const transform of transformsAll) {
551
+ const transformAttr = utils.findAttr(transform, "Algorithm");
552
+ if (transformAttr) {
553
+ transforms.push(transformAttr.value);
554
+ }
555
+ }
556
+ // This is a little strange, we are looking for children of the last child of `transformsNode`
557
+ const inclusiveNamespaces = utils.findChildren(transformsAll[transformsAll.length - 1], "InclusiveNamespaces");
558
+ if (utils.isArrayHasLength(inclusiveNamespaces)) {
559
+ // Should really only be one prefix list, but maybe there's some circumstances where more than one to let's handle it
560
+ inclusiveNamespacesPrefixList = inclusiveNamespaces
561
+ .flatMap((namespace) => (namespace.getAttribute("PrefixList") ?? "").split(" "))
562
+ .filter((value) => value.length > 0);
563
+ }
564
+ }
565
+ if (utils.isArrayHasLength(this.implicitTransforms)) {
566
+ this.implicitTransforms.forEach(function (t) {
567
+ transforms.push(t);
568
+ });
569
+ }
570
+ /**
571
+ * DigestMethods take an octet stream rather than a node set. If the output of the last transform is a node set, we
572
+ * need to canonicalize the node set to an octet stream using non-exclusive canonicalization. If there are no
573
+ * transforms, we need to canonicalize because URI dereferencing for a same-document reference will return a node-set.
574
+ * @see:
575
+ * https://www.w3.org/TR/xmldsig-core1/#sec-DigestMethod
576
+ * https://www.w3.org/TR/xmldsig-core1/#sec-ReferenceProcessingModel
577
+ * https://www.w3.org/TR/xmldsig-core1/#sec-Same-Document
578
+ */
579
+ if (transforms.length === 0 ||
580
+ transforms[transforms.length - 1] === "http://www.w3.org/2000/09/xmldsig#enveloped-signature") {
581
+ transforms.push("http://www.w3.org/TR/2001/REC-xml-c14n-20010315");
582
+ }
583
+ const refUri = isDomNode.isElementNode(refNode)
584
+ ? refNode.getAttribute("URI") || undefined
585
+ : undefined;
586
+ this.addReference({
587
+ transforms,
588
+ digestAlgorithm: digestAlgo,
589
+ uri: refUri,
590
+ digestValue,
591
+ inclusiveNamespacesPrefixList,
592
+ isEmptyUri: false,
593
+ });
594
+ }
595
+ /**
596
+ * Adds a reference to the signature.
597
+ *
598
+ * @param xpath The XPath expression to select the XML nodes to be referenced.
599
+ * @param transforms An array of transform algorithms to be applied to the selected nodes.
600
+ * @param digestAlgorithm The digest algorithm to use for computing the digest value.
601
+ * @param uri The URI identifier for the reference. If empty, an empty URI will be used.
602
+ * @param digestValue The expected digest value for the reference.
603
+ * @param inclusiveNamespacesPrefixList The prefix list for inclusive namespace canonicalization.
604
+ * @param isEmptyUri Indicates whether the URI is empty. Defaults to `false`.
605
+ * @param id An optional `Id` attribute for the reference.
606
+ * @param type An optional `Type` attribute for the reference.
607
+ */
608
+ addReference({ xpath, transforms, digestAlgorithm, uri = "", digestValue, inclusiveNamespacesPrefixList = [], isEmptyUri = false, id = undefined, type = undefined, }) {
609
+ if (digestAlgorithm == null) {
610
+ throw new Error("digestAlgorithm is required");
611
+ }
612
+ if (!utils.isArrayHasLength(transforms)) {
613
+ throw new Error("transforms must contain at least one transform algorithm");
614
+ }
615
+ this.references.push({
616
+ xpath,
617
+ transforms,
618
+ digestAlgorithm,
619
+ uri,
620
+ digestValue,
621
+ inclusiveNamespacesPrefixList,
622
+ isEmptyUri,
623
+ id,
624
+ type,
625
+ getValidatedNode: () => {
626
+ throw new Error("Reference has not been validated yet; Did you call `sig.checkSignature()`?");
627
+ },
628
+ });
629
+ }
630
+ /**
631
+ * Returns the list of references.
632
+ */
633
+ getReferences() {
634
+ // TODO: Refactor once `getValidatedNode` is removed
635
+ /* Once we completely remove the deprecated `getValidatedNode()` method,
636
+ we can change this to return a clone to prevent accidental mutations,
637
+ e.g.:
638
+ return [...this.references];
639
+ */
640
+ return this.references;
641
+ }
642
+ getSignedReferences() {
643
+ return [...this.signedReferences];
644
+ }
645
+ computeSignature(xml, options, callbackParam) {
646
+ let callback;
647
+ if (typeof options === "function" && callbackParam == null) {
648
+ callback = options;
649
+ options = {};
650
+ }
651
+ else {
652
+ callback = callbackParam;
653
+ options = (options ?? {});
654
+ }
655
+ const doc = new xmldom.DOMParser().parseFromString(xml);
656
+ let xmlNsAttr = "xmlns";
657
+ const signatureAttrs = [];
658
+ let currentPrefix;
659
+ const validActions = ["append", "prepend", "before", "after"];
660
+ const prefix = options.prefix;
661
+ const attrs = options.attrs || {};
662
+ const location = options.location || {};
663
+ const existingPrefixes = options.existingPrefixes || {};
664
+ this.namespaceResolver = {
665
+ lookupNamespaceURI: function (prefix) {
666
+ return prefix ? existingPrefixes[prefix] : null;
667
+ },
668
+ };
669
+ // defaults to the root node
670
+ location.reference = location.reference || "/*";
671
+ // defaults to append action
672
+ location.action = location.action || "append";
673
+ if (validActions.indexOf(location.action) === -1) {
674
+ const err = new Error(`location.action option has an invalid action: ${location.action}, must be any of the following values: ${validActions.join(", ")}`);
675
+ if (!callback) {
676
+ throw err;
677
+ }
678
+ else {
679
+ callback(err);
680
+ return;
681
+ }
682
+ }
683
+ // Add IDs for all non-self references upfront
684
+ for (const ref of this.getReferences()) {
685
+ if (ref.isEmptyUri) {
686
+ continue;
687
+ } // No specific nodes to ID for empty URI
688
+ const nodes = xpath.selectWithResolver(ref.xpath ?? "", doc, this.namespaceResolver);
689
+ for (const node of nodes) {
690
+ isDomNode.assertIsElementNode(node);
691
+ this.ensureHasId(node);
692
+ }
693
+ }
694
+ // Capture original with IDs (no sig yet)
695
+ this.originalXmlWithIds = doc.toString();
696
+ // automatic insertion of `:`
697
+ if (prefix) {
698
+ xmlNsAttr += `:${prefix}`;
699
+ currentPrefix = `${prefix}:`;
700
+ }
701
+ else {
702
+ currentPrefix = "";
703
+ }
704
+ Object.keys(attrs).forEach(function (name) {
705
+ if (name !== "xmlns" && name !== xmlNsAttr) {
706
+ signatureAttrs.push(`${name}="${attrs[name]}"`);
707
+ }
708
+ });
709
+ // add the xml namespace attribute
710
+ signatureAttrs.push(`${xmlNsAttr}="http://www.w3.org/2000/09/xmldsig#"`);
711
+ let signatureXml = `<${currentPrefix}Signature ${signatureAttrs.join(" ")}>`;
712
+ signatureXml += this.createSignedInfo(doc, prefix);
713
+ signatureXml += this.getKeyInfo(prefix);
714
+ signatureXml += this.getObjects(prefix);
715
+ signatureXml += `</${currentPrefix}Signature>`;
716
+ let existingPrefixesString = "";
717
+ Object.keys(existingPrefixes).forEach(function (key) {
718
+ existingPrefixesString += `xmlns:${key}="${existingPrefixes[key]}" `;
719
+ });
720
+ // A trick to remove the namespaces that already exist in the xml
721
+ // This only works if the prefix and namespace match with those in the xml
722
+ const dummySignatureWrapper = `<Dummy ${existingPrefixesString}>${signatureXml}</Dummy>`;
723
+ const nodeXml = new xmldom.DOMParser().parseFromString(dummySignatureWrapper);
724
+ // Because we are using a dummy wrapper hack described above, we know there will be a `firstChild`
725
+ // and that it will be an `Element` node.
726
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
727
+ const signatureElem = nodeXml.documentElement.firstChild;
728
+ const referenceNode = xpath.select1(location.reference, doc);
729
+ if (!isDomNode.isNodeLike(referenceNode)) {
730
+ const err2 = new Error(`the following xpath cannot be used because it was not found: ${location.reference}`);
731
+ if (!callback) {
732
+ throw err2;
733
+ }
734
+ else {
735
+ callback(err2);
736
+ return;
737
+ }
738
+ }
739
+ if (location.action === "append") {
740
+ referenceNode.appendChild(signatureElem);
741
+ }
742
+ else if (location.action === "prepend") {
743
+ referenceNode.insertBefore(signatureElem, referenceNode.firstChild);
744
+ }
745
+ else if (location.action === "before") {
746
+ if (referenceNode.parentNode == null) {
747
+ throw new Error("`location.reference` refers to the root node (by default), so we can't insert `before`");
748
+ }
749
+ referenceNode.parentNode.insertBefore(signatureElem, referenceNode);
750
+ }
751
+ else if (location.action === "after") {
752
+ if (referenceNode.parentNode == null) {
753
+ throw new Error("`location.reference` refers to the root node (by default), so we can't insert `after`");
754
+ }
755
+ referenceNode.parentNode.insertBefore(signatureElem, referenceNode.nextSibling);
756
+ }
757
+ // Now add all references (including any to the signature itself)
758
+ this.addAllReferences(doc, signatureElem, prefix);
759
+ this.signatureNode = signatureElem;
760
+ const signedInfoNodes = utils.findChildren(this.signatureNode, "SignedInfo");
761
+ if (signedInfoNodes.length === 0) {
762
+ const err3 = new Error("could not find SignedInfo element in the message");
763
+ if (!callback) {
764
+ throw err3;
765
+ }
766
+ else {
767
+ callback(err3);
768
+ return;
769
+ }
770
+ }
771
+ const signedInfoNode = signedInfoNodes[0];
772
+ if (typeof callback === "function") {
773
+ // Asynchronous flow
774
+ this.calculateSignatureValue(doc, (err, signature) => {
775
+ if (err) {
776
+ callback(err);
777
+ }
778
+ else {
779
+ this.signatureValue = signature || "";
780
+ signatureElem.insertBefore(this.createSignature(prefix), signedInfoNode.nextSibling);
781
+ this.signatureXml = signatureElem.toString();
782
+ this.signedXml = doc.toString();
783
+ callback(null, this);
784
+ }
785
+ });
786
+ }
787
+ else {
788
+ // Synchronous flow
789
+ this.calculateSignatureValue(doc);
790
+ signatureElem.insertBefore(this.createSignature(prefix), signedInfoNode.nextSibling);
791
+ this.signatureXml = signatureElem.toString();
792
+ this.signedXml = doc.toString();
793
+ }
794
+ }
795
+ /**
796
+ * Adds all references to the SignedInfo after the signature placeholder is inserted.
797
+ */
798
+ addAllReferences(doc, signatureElem, prefix) {
799
+ if (!utils.isArrayHasLength(this.references)) {
800
+ return;
801
+ }
802
+ const currentPrefix = prefix ? `${prefix}:` : "";
803
+ const signatureNamespace = "http://www.w3.org/2000/09/xmldsig#";
804
+ // Find the SignedInfo element to append to
805
+ const signedInfoNode = xpath.select1(`./*[local-name(.)='SignedInfo']`, signatureElem);
806
+ isDomNode.assertIsElementNode(signedInfoNode); // Type-safe assertion
807
+ // Signature document is technically the same document as the one we are signing,
808
+ // but we will extract it here for clarity (and also make it support detached signatures in the future)
809
+ const signatureDoc = signatureElem.ownerDocument;
810
+ // Process each reference
811
+ for (const ref of this.getReferences()) {
812
+ const nodes = xpath.selectWithResolver(ref.xpath ?? "", doc, this.namespaceResolver);
813
+ if (!utils.isArrayHasLength(nodes)) {
814
+ throw new Error(`the following xpath cannot be signed because it was not found: ${ref.xpath}`);
815
+ }
816
+ // Process the reference
817
+ for (const node of nodes) {
818
+ isDomNode.assertIsElementNode(node);
819
+ // Must not be a reference to Signature, SignedInfo, or a child of SignedInfo
820
+ if (node === signatureElem ||
821
+ node === signedInfoNode ||
822
+ utils.isDescendantOf(node, signedInfoNode)) {
823
+ throw new Error(`Cannot sign a reference to the Signature or SignedInfo element itself: ${ref.xpath}`);
824
+ }
825
+ // Compute the target URI (ID already ensured earlier, extract it)
826
+ let targetUri;
827
+ if (ref.isEmptyUri) {
828
+ targetUri = "";
829
+ }
830
+ else {
831
+ const id = this.ensureHasId(node);
832
+ ref.uri = id;
833
+ targetUri = `#${id}`;
834
+ }
835
+ // Create the reference element directly using DOM methods to avoid namespace issues
836
+ const referenceElem = signatureDoc.createElementNS(signatureNamespace, `${currentPrefix}Reference`);
837
+ referenceElem.setAttribute("URI", targetUri);
838
+ if (ref.id) {
839
+ referenceElem.setAttribute("Id", ref.id);
840
+ }
841
+ if (ref.type) {
842
+ referenceElem.setAttribute("Type", ref.type);
843
+ }
844
+ const transformsElem = signatureDoc.createElementNS(signatureNamespace, `${currentPrefix}Transforms`);
845
+ for (const trans of ref.transforms || []) {
846
+ const transform = this.findCanonicalizationAlgorithm(trans);
847
+ const transformElem = signatureDoc.createElementNS(signatureNamespace, `${currentPrefix}Transform`);
848
+ transformElem.setAttribute("Algorithm", transform.getAlgorithmName());
849
+ if (utils.isArrayHasLength(ref.inclusiveNamespacesPrefixList)) {
850
+ const inclusiveNamespacesElem = signatureDoc.createElementNS(transform.getAlgorithmName(), "InclusiveNamespaces");
851
+ inclusiveNamespacesElem.setAttribute("PrefixList", ref.inclusiveNamespacesPrefixList.join(" "));
852
+ transformElem.appendChild(inclusiveNamespacesElem);
853
+ }
854
+ transformsElem.appendChild(transformElem);
855
+ }
856
+ // Get the canonicalized XML
857
+ const canonXml = this.getCanonReferenceXml(doc, ref, node);
858
+ // Get the digest algorithm and compute the digest value
859
+ const digestAlgorithm = this.findHashAlgorithm(ref.digestAlgorithm);
860
+ const digestMethodElem = signatureDoc.createElementNS(signatureNamespace, `${currentPrefix}DigestMethod`);
861
+ digestMethodElem.setAttribute("Algorithm", digestAlgorithm.getAlgorithmName());
862
+ const digestValueElem = signatureDoc.createElementNS(signatureNamespace, `${currentPrefix}DigestValue`);
863
+ digestValueElem.textContent = digestAlgorithm.getHash(canonXml);
864
+ referenceElem.appendChild(transformsElem);
865
+ referenceElem.appendChild(digestMethodElem);
866
+ referenceElem.appendChild(digestValueElem);
867
+ // Append the reference element to SignedInfo
868
+ signedInfoNode.appendChild(referenceElem);
869
+ }
870
+ }
871
+ }
872
+ getKeyInfo(prefix) {
873
+ const currentPrefix = prefix ? `${prefix}:` : "";
874
+ let keyInfoAttrs = "";
875
+ if (this.keyInfoAttributes) {
876
+ Object.keys(this.keyInfoAttributes).forEach((name) => {
877
+ keyInfoAttrs += ` ${name}="${this.keyInfoAttributes[name]}"`;
878
+ });
879
+ }
880
+ const keyInfoContent = this.getKeyInfoContent({ publicCert: this.publicCert, prefix });
881
+ if (keyInfoAttrs || keyInfoContent) {
882
+ return `<${currentPrefix}KeyInfo${keyInfoAttrs}>${keyInfoContent}</${currentPrefix}KeyInfo>`;
883
+ }
884
+ return "";
885
+ }
886
+ /**
887
+ * Creates XML for Object elements to be included in the signature
888
+ *
889
+ * @param prefix Optional namespace prefix
890
+ * @returns XML string with Object elements or empty string if none
891
+ */
892
+ getObjects(prefix) {
893
+ const currentPrefix = prefix ? `${prefix}:` : "";
894
+ if (!this.objects || this.objects.length === 0) {
895
+ return "";
896
+ }
897
+ let result = "";
898
+ for (const obj of this.objects) {
899
+ let objectAttrs = "";
900
+ if (obj.attributes) {
901
+ Object.keys(obj.attributes).forEach((name) => {
902
+ const value = obj.attributes?.[name];
903
+ if (value !== undefined) {
904
+ objectAttrs += ` ${name}="${value}"`;
905
+ }
906
+ });
907
+ }
908
+ result += `<${currentPrefix}Object${objectAttrs}>${obj.content}</${currentPrefix}Object>`;
909
+ }
910
+ return result;
911
+ }
912
+ getCanonXml(transforms, node, options = {}) {
913
+ options.defaultNsForPrefix = options.defaultNsForPrefix ?? SignedXml.defaultNsForPrefix;
914
+ options.signatureNode = this.signatureNode;
915
+ const canonXml = node.cloneNode(true); // Deep clone
916
+ let transformedXml = canonXml;
917
+ transforms.forEach((transformName) => {
918
+ if (isDomNode.isNodeLike(transformedXml)) {
919
+ // If, after processing, `transformedNode` is a string, we can't do anymore transforms on it
920
+ const transform = this.findCanonicalizationAlgorithm(transformName);
921
+ transformedXml = transform.process(transformedXml, options);
922
+ }
923
+ //TODO: currently transform.process may return either Node or String value (enveloped transformation returns Node, exclusive-canonicalization returns String).
924
+ //This either needs to be more explicit in the API, or all should return the same.
925
+ //exclusive-canonicalization returns String since it builds the Xml by hand. If it had used xmldom it would incorrectly minimize empty tags
926
+ //to <x/> instead of <x></x> and also incorrectly handle some delicate line break issues.
927
+ //enveloped transformation returns Node since if it would return String consider this case:
928
+ //<x xmlns:p='ns'><p:y/></x>
929
+ //if only y is the node to sign then a string would be <p:y/> without the definition of the p namespace. probably xmldom toString() should have added it.
930
+ });
931
+ return transformedXml.toString();
932
+ }
933
+ /**
934
+ * Ensure an element has Id attribute. If not create it with unique value.
935
+ * Work with both normal and wssecurity Id flavour
936
+ */
937
+ ensureHasId(node) {
938
+ let attr;
939
+ if (this.idMode === "wssecurity") {
940
+ attr = utils.findAttr(node, "Id", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd");
941
+ }
942
+ else {
943
+ this.idAttributes.some((idAttribute) => {
944
+ attr = utils.findAttr(node, idAttribute);
945
+ return !!attr; // This will break the loop as soon as a truthy attr is found.
946
+ });
947
+ }
948
+ if (attr) {
949
+ return attr.value;
950
+ }
951
+ //add the attribute
952
+ const id = `_${this.id++}`;
953
+ if (this.idMode === "wssecurity") {
954
+ node.setAttributeNS("http://www.w3.org/2000/xmlns/", "xmlns:wsu", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd");
955
+ node.setAttributeNS("http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd", "wsu:Id", id);
956
+ }
957
+ else {
958
+ node.setAttribute("Id", id);
959
+ }
960
+ return id;
961
+ }
962
+ /**
963
+ * Create the SignedInfo element
964
+ *
965
+ */
966
+ createSignedInfo(doc, prefix) {
967
+ if (typeof this.canonicalizationAlgorithm !== "string") {
968
+ throw new Error("Missing canonicalizationAlgorithm when trying to create signed info for XML");
969
+ }
970
+ const transform = this.findCanonicalizationAlgorithm(this.canonicalizationAlgorithm);
971
+ const algo = this.findSignatureAlgorithm(this.signatureAlgorithm);
972
+ const currentPrefix = prefix ? `${prefix}:` : "";
973
+ let res = `<${currentPrefix}SignedInfo>`;
974
+ res += `<${currentPrefix}CanonicalizationMethod Algorithm="${transform.getAlgorithmName()}"`;
975
+ if (utils.isArrayHasLength(this.inclusiveNamespacesPrefixList)) {
976
+ res += ">";
977
+ res += `<InclusiveNamespaces PrefixList="${this.inclusiveNamespacesPrefixList.join(" ")}" xmlns="${transform.getAlgorithmName()}"/>`;
978
+ res += `</${currentPrefix}CanonicalizationMethod>`;
979
+ }
980
+ else {
981
+ res += " />";
982
+ }
983
+ res += `<${currentPrefix}SignatureMethod Algorithm="${algo.getAlgorithmName()}" />`;
984
+ // No references here - added later
985
+ res += `</${currentPrefix}SignedInfo>`;
986
+ return res;
987
+ }
988
+ /**
989
+ * Create the Signature element
990
+ *
991
+ */
992
+ createSignature(prefix) {
993
+ let xmlNsAttr = "xmlns";
994
+ if (prefix) {
995
+ xmlNsAttr += `:${prefix}`;
996
+ prefix += ":";
997
+ }
998
+ else {
999
+ prefix = "";
1000
+ }
1001
+ const signatureValueXml = `<${prefix}SignatureValue>${this.signatureValue}</${prefix}SignatureValue>`;
1002
+ //the canonicalization requires to get a valid xml node.
1003
+ //we need to wrap the info in a dummy signature since it contains the default namespace.
1004
+ const dummySignatureWrapper = `<${prefix}Signature ${xmlNsAttr}="http://www.w3.org/2000/09/xmldsig#">${signatureValueXml}</${prefix}Signature>`;
1005
+ const doc = new xmldom.DOMParser().parseFromString(dummySignatureWrapper);
1006
+ // Because we are using a dummy wrapper hack described above, we know there will be a `firstChild`
1007
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1008
+ return doc.documentElement.firstChild;
1009
+ }
1010
+ /**
1011
+ * Returns just the signature part, must be called only after {@link computeSignature}
1012
+ *
1013
+ * @returns The signature XML.
1014
+ */
1015
+ getSignatureXml() {
1016
+ return this.signatureXml;
1017
+ }
1018
+ /**
1019
+ * Returns the original xml with Id attributes added on relevant elements (required for validation), must be called only after {@link computeSignature}
1020
+ *
1021
+ * @returns The original XML with IDs.
1022
+ */
1023
+ getOriginalXmlWithIds() {
1024
+ return this.originalXmlWithIds;
1025
+ }
1026
+ /**
1027
+ * Returns the original xml document with the signature in it, must be called only after {@link computeSignature}
1028
+ *
1029
+ * @returns The signed XML.
1030
+ */
1031
+ getSignedXml() {
1032
+ return this.signedXml;
1033
+ }
1034
+ }
1035
+ exports.SignedXml = SignedXml;
1036
+ SignedXml.defaultNsForPrefix = {
1037
+ ds: "http://www.w3.org/2000/09/xmldsig#",
1038
+ };
1039
+ SignedXml.noop = () => null;
1040
+ //# sourceMappingURL=signed-xml.js.map