Skip to content

Commit 8fd99f2

Browse files
committed
feat: add XmlDSigSigner and XmlDSigValidator wrappers
1 parent 73db72d commit 8fd99f2

18 files changed

+2436
-38
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,4 @@ npm-debug.log
1212
.nyc_output/
1313
coverage/
1414
.idea
15+
.aider*

.nvmrc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
v22.16.0

src/algorithms.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/**
2+
* Supported canonicalization algorithms
3+
*/
4+
const CANONICALIZATION_ALGORITHMS = {
5+
C14N: "http://www.w3.org/TR/2001/REC-xml-c14n-20010315",
6+
C14N_WITH_COMMENTS: "http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments",
7+
EXCLUSIVE_C14N: "http://www.w3.org/2001/10/xml-exc-c14n#",
8+
EXCLUSIVE_C14N_WITH_COMMENTS: "http://www.w3.org/2001/10/xml-exc-c14n#WithComments",
9+
} as const;
10+
11+
/**
12+
* Supported transform algorithms (includes canonicalization + enveloped signature)
13+
*/
14+
const TRANSFORM_ALGORITHMS = {
15+
...CANONICALIZATION_ALGORITHMS,
16+
ENVELOPED_SIGNATURE: "http://www.w3.org/2000/09/xmldsig#enveloped-signature",
17+
} as const;
18+
19+
/**
20+
* Supported hash/digest algorithms
21+
*/
22+
const HASH_ALGORITHMS = {
23+
SHA1: "http://www.w3.org/2000/09/xmldsig#sha1",
24+
SHA256: "http://www.w3.org/2001/04/xmlenc#sha256",
25+
SHA512: "http://www.w3.org/2001/04/xmlenc#sha512",
26+
} as const;
27+
28+
/**
29+
* Supported signature algorithms
30+
*/
31+
const SIGNATURE_ALGORITHMS = {
32+
RSA_SHA1: "http://www.w3.org/2000/09/xmldsig#rsa-sha1",
33+
RSA_SHA256: "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256",
34+
RSA_SHA256_MGF1: "http://www.w3.org/2007/05/xmldsig-more#sha256-rsa-MGF1",
35+
RSA_SHA512: "http://www.w3.org/2001/04/xmldsig-more#rsa-sha512",
36+
HMAC_SHA1: "http://www.w3.org/2000/09/xmldsig#hmac-sha1",
37+
} as const;
38+
39+
export const Algorithms = {
40+
canonicalization: CANONICALIZATION_ALGORITHMS,
41+
transform: TRANSFORM_ALGORITHMS,
42+
hash: HASH_ALGORITHMS,
43+
signature: SIGNATURE_ALGORITHMS,
44+
};

src/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,8 @@ export {
44
ExclusiveCanonicalizationWithComments,
55
} from "./exclusive-canonicalization";
66
export { SignedXml } from "./signed-xml";
7+
export { XmlDSigSigner } from "./xmldsig-signer";
8+
export { XmlDSigValidator } from "./xmldsig-validator";
9+
export { Algorithms } from "./algorithms";
710
export * from "./types";
811
export * from "./utils";

src/signed-xml.ts

Lines changed: 65 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import type {
88
GetKeyInfoContentArgs,
99
HashAlgorithm,
1010
HashAlgorithmType,
11+
IdAttributeType,
1112
ObjectAttributes,
1213
Reference,
1314
SignatureAlgorithm,
@@ -29,7 +30,7 @@ import * as utils from "./utils";
2930

3031
export class SignedXml {
3132
idMode?: "wssecurity";
32-
idAttributes: string[];
33+
idAttributes: IdAttributeType[];
3334
/**
3435
* A {@link Buffer} or pem encoded {@link String} containing your private key
3536
*/
@@ -53,6 +54,7 @@ export class SignedXml {
5354
throw new Error("Not implemented");
5455
},
5556
};
57+
private maxTransforms: number | null;
5658
implicitTransforms: ReadonlyArray<CanonicalizationOrTransformAlgorithmType> = [];
5759
keyInfoAttributes: { [attrName: string]: string } = {};
5860
getKeyInfoContent = SignedXml.getKeyInfoContent;
@@ -137,11 +139,13 @@ export class SignedXml {
137139
const {
138140
idMode,
139141
idAttribute,
142+
idAttributes,
140143
privateKey,
141144
publicCert,
142145
signatureAlgorithm,
143146
canonicalizationAlgorithm,
144147
inclusiveNamespacesPrefixList,
148+
maxTransforms,
145149
implicitTransforms,
146150
keyInfoAttributes,
147151
getKeyInfoContent,
@@ -151,7 +155,7 @@ export class SignedXml {
151155

152156
// Options
153157
this.idMode = idMode;
154-
this.idAttributes = ["Id", "ID", "id"];
158+
this.idAttributes = idAttributes ?? ["Id", "ID", "id"];
155159
if (idAttribute) {
156160
this.idAttributes.unshift(idAttribute);
157161
}
@@ -164,6 +168,7 @@ export class SignedXml {
164168
} else if (utils.isArrayHasLength(inclusiveNamespacesPrefixList)) {
165169
this.inclusiveNamespacesPrefixList = inclusiveNamespacesPrefixList;
166170
}
171+
this.maxTransforms = maxTransforms ?? null;
167172
this.implicitTransforms = implicitTransforms ?? this.implicitTransforms;
168173
this.keyInfoAttributes = keyInfoAttributes ?? this.keyInfoAttributes;
169174
this.getKeyInfoContent = getKeyInfoContent ?? this.getKeyInfoContent;
@@ -502,11 +507,18 @@ export class SignedXml {
502507
for (const ref of this.getReferences()) {
503508
const uri = ref.uri?.[0] === "#" ? ref.uri.substring(1) : ref.uri;
504509

505-
for (const attr of this.idAttributes) {
506-
const elemId = elem.getAttribute(attr);
507-
if (uri === elemId) {
508-
ref.xpath = `//*[@*[local-name(.)='${attr}']='${uri}']`;
509-
break; // found the correct element, no need to check further
510+
for (const idAttr of this.idAttributes) {
511+
if (typeof idAttr === "string") {
512+
if (uri === elem.getAttribute(idAttr)) {
513+
ref.xpath = `//*[@*[local-name(.)='${idAttr}']='${uri}']`;
514+
break; // found the correct element, no need to check further
515+
}
516+
} else {
517+
const attr = utils.findAttr(elem, idAttr.localName, idAttr.namespaceUri);
518+
if (attr && uri === attr.value) {
519+
ref.xpath = `//*[@*[local-name(.)='${idAttr.localName}' and namespace-uri(.)='${idAttr.namespaceUri}']='${uri}']`;
520+
break; // found the correct element, no need to check further
521+
}
510522
}
511523
}
512524

@@ -533,8 +545,19 @@ export class SignedXml {
533545
throw new Error("Cannot validate a uri with quotes inside it");
534546
} else {
535547
let num_elements_for_id = 0;
536-
for (const attr of this.idAttributes) {
537-
const tmp_elemXpath = `//*[@*[local-name(.)='${attr}']='${uri}']`;
548+
for (const idAttr of this.idAttributes) {
549+
let tmp_elemXpath: string;
550+
551+
if (typeof idAttr === "string") {
552+
tmp_elemXpath = `//*[@*[local-name(.)='${idAttr}']='${uri}']`;
553+
} else {
554+
if (idAttr.namespaceUri) {
555+
tmp_elemXpath = `//*[@*[local-name(.)='${idAttr.localName}' and namespace-uri(.)='${idAttr.namespaceUri}']='${uri}']`;
556+
} else {
557+
tmp_elemXpath = `//*[@*[local-name(.)='${idAttr.localName}']='${uri}']`;
558+
}
559+
}
560+
538561
const tmp_elem = xpath.select(tmp_elemXpath, doc);
539562
if (utils.isArrayHasLength(tmp_elem)) {
540563
num_elements_for_id += tmp_elem.length;
@@ -781,6 +804,14 @@ export class SignedXml {
781804
? refNode.getAttribute("URI") || undefined
782805
: undefined;
783806

807+
if (this.maxTransforms) {
808+
if (transforms.length > this.maxTransforms) {
809+
throw new Error(
810+
`Number of transforms (${transforms.length}) exceeds the maximum allowed (${this.maxTransforms})`,
811+
);
812+
}
813+
}
814+
784815
this.addReference({
785816
transforms,
786817
digestAlgorithm: digestAlgo,
@@ -1305,7 +1336,11 @@ export class SignedXml {
13051336
);
13061337
} else {
13071338
this.idAttributes.some((idAttribute) => {
1308-
attr = utils.findAttr(node, idAttribute);
1339+
if (typeof idAttribute === "string") {
1340+
attr = utils.findAttr(node, idAttribute);
1341+
} else {
1342+
attr = utils.findAttr(node, idAttribute.localName, idAttribute.namespaceUri);
1343+
}
13091344
return !!attr; // This will break the loop as soon as a truthy attr is found.
13101345
});
13111346
}
@@ -1329,7 +1364,26 @@ export class SignedXml {
13291364
id,
13301365
);
13311366
} else {
1332-
node.setAttribute("Id", id);
1367+
// Use the first idAttribute to set the new ID
1368+
const firstIdAttr = this.idAttributes[0];
1369+
if (typeof firstIdAttr === "string") {
1370+
node.setAttribute(firstIdAttr, id);
1371+
} else {
1372+
if (firstIdAttr.prefix && firstIdAttr.namespaceUri) {
1373+
node.setAttributeNS(
1374+
"http://www.w3.org/2000/xmlns/",
1375+
`xmlns:${firstIdAttr.prefix}`,
1376+
firstIdAttr.namespaceUri,
1377+
);
1378+
node.setAttributeNS(
1379+
firstIdAttr.namespaceUri,
1380+
`${firstIdAttr.prefix}:${firstIdAttr.localName}`,
1381+
id,
1382+
);
1383+
} else {
1384+
node.setAttribute(firstIdAttr.localName, id);
1385+
}
1386+
}
13331387
}
13341388

13351389
return id;

src/types.ts

Lines changed: 15 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -10,30 +10,15 @@ import * as crypto from "crypto";
1010

1111
export type ErrorFirstCallback<T> = (err: Error | null, result?: T) => void;
1212

13-
export type CanonicalizationAlgorithmType =
14-
| "http://www.w3.org/TR/2001/REC-xml-c14n-20010315"
15-
| "http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments"
16-
| "http://www.w3.org/2001/10/xml-exc-c14n#"
17-
| "http://www.w3.org/2001/10/xml-exc-c14n#WithComments"
18-
| string;
19-
20-
export type CanonicalizationOrTransformAlgorithmType =
21-
| CanonicalizationAlgorithmType
22-
| "http://www.w3.org/2000/09/xmldsig#enveloped-signature";
23-
24-
export type HashAlgorithmType =
25-
| "http://www.w3.org/2000/09/xmldsig#sha1"
26-
| "http://www.w3.org/2001/04/xmlenc#sha256"
27-
| "http://www.w3.org/2001/04/xmlenc#sha512"
28-
| string;
29-
30-
export type SignatureAlgorithmType =
31-
| "http://www.w3.org/2000/09/xmldsig#rsa-sha1"
32-
| "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"
33-
| "http://www.w3.org/2007/05/xmldsig-more#sha256-rsa-MGF1"
34-
| "http://www.w3.org/2001/04/xmldsig-more#rsa-sha512"
35-
| "http://www.w3.org/2000/09/xmldsig#hmac-sha1"
36-
| string;
13+
export type IdAttributeType = string | { prefix: string; localName: string; namespaceUri: string };
14+
15+
export type CanonicalizationAlgorithmType = string;
16+
17+
export type CanonicalizationOrTransformAlgorithmType = string;
18+
19+
export type HashAlgorithmType = string;
20+
21+
export type SignatureAlgorithmType = string;
3722

3823
/**
3924
* @param cert the certificate as a string or array of strings (@see https://www.w3.org/TR/2008/REC-xmldsig-core-20080610/#sec-X509Data)
@@ -59,21 +44,25 @@ export interface ObjectAttributes {
5944
[key: string]: string | undefined;
6045
}
6146

47+
export type KeySelectorFunction = (keyInfo?: Node | null) => string | null;
48+
6249
/**
6350
* Options for the SignedXml constructor.
6451
*/
6552
export interface SignedXmlOptions {
6653
idMode?: "wssecurity";
67-
idAttribute?: string;
54+
idAttribute?: IdAttributeType;
55+
idAttributes?: IdAttributeType[];
6856
privateKey?: crypto.KeyLike;
6957
publicCert?: crypto.KeyLike;
7058
signatureAlgorithm?: SignatureAlgorithmType;
7159
canonicalizationAlgorithm?: CanonicalizationAlgorithmType;
7260
inclusiveNamespacesPrefixList?: string | string[];
61+
maxTransforms?: number | null;
7362
implicitTransforms?: ReadonlyArray<CanonicalizationOrTransformAlgorithmType>;
7463
keyInfoAttributes?: Record<string, string>;
7564
getKeyInfoContent?(args?: GetKeyInfoContentArgs): string | null;
76-
getCertFromKeyInfo?(keyInfo?: Node | null): string | null;
65+
getCertFromKeyInfo?: KeySelectorFunction;
7766
objects?: Array<{ content: string; attributes?: ObjectAttributes }>;
7867
}
7968

0 commit comments

Comments
 (0)