diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 00000000..aebd91c5 --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +v22.16.0 diff --git a/README.md b/README.md index dfe164f2..5d6f77cb 100644 --- a/README.md +++ b/README.md @@ -209,6 +209,31 @@ Note: The xml-crypto api requires you to supply it separately the xml signature ("<Signature>...</Signature>", in loadSignature) and the signed xml (in checkSignature). The signed xml may or may not contain the signature in it, but you are still required to supply the signature separately. +### Secure Verification with XmlDSigVerifier (Recommended) + +For a more secure and streamlined verification experience, use the `XmlDSigVerifier` class. It provides built-in checks for certificate expiration, truststore validation, and easier configuration. + +```javascript +const { XmlDSigVerifier } = require("xml-crypto"); +const fs = require("fs"); + +const xml = fs.readFileSync("signed.xml", "utf-8"); +const publicCert = fs.readFileSync("client_public.pem", "utf-8"); + +const result = XmlDSigVerifier.verifySignature(xml, { + keySelector: { publicCert: publicCert }, +}); + +if (result.success) { + console.log("Valid signature!"); + console.log("Signed content:", result.signedReferences); +} else { + console.error("Invalid signature:", result.error); +} +``` + +For detailed usage instructions, see [XMLDSIG_VERIFIER.md](./XMLDSIG_VERIFIER.md). + ### Caring for Implicit transform If you fail to verify signed XML, then one possible cause is that there are some hidden implicit transforms(#). diff --git a/XMLDSIG_VERIFIER.md b/XMLDSIG_VERIFIER.md new file mode 100644 index 00000000..14fb938d --- /dev/null +++ b/XMLDSIG_VERIFIER.md @@ -0,0 +1,157 @@ +# XmlDSigVerifier Usage Guide + +`XmlDSigVerifier` provides a focused, secure, and easy-to-use API for verifying XML signatures. It is designed to replace direct usage of `SignedXml` for verification scenarios, offering built-in security checks and a simplified configuration. + +## Features + +- **Type-Safe Configuration:** Explicit options for different key retrieval strategies (Public Certificate, KeyInfo, Shared Secret). +- **Enhanced Security:** Built-in checks for certificate expiration, truststore validation, and limits on transform complexity. +- **Flexible Error Handling:** Choose between throwing errors or returning a result object. + +## Installation + +Ensure you have `xml-crypto` installed: + +```bash +npm install xml-crypto +``` + +## Quick Start + +### 1. Verifying with a Public Certificate + +If you already have the public certificate or key and want to verify a document signed with the corresponding private key: + +```typescript +import { XmlDSigVerifier } from "xml-crypto"; +import * as fs from "fs"; + +const xml = fs.readFileSync("signed_document.xml", "utf-8"); +const publicCert = fs.readFileSync("public_key.pem", "utf-8"); + +const result = XmlDSigVerifier.verifySignature(xml, { + keySelector: { publicCert: publicCert }, +}); + +if (result.success) { + console.log("Signature valid!"); + // Access the signed content securely + console.log("Signed references:", result.signedReferences); +} else { + console.error("Verification failed:", result.error); +} +``` + +### 2. Verifying using KeyInfo (with Truststore) + +When the XML document contains the certificate in a `` element, you can verify it while ensuring the certificate is trusted and valid. + +```typescript +import { XmlDSigVerifier, SignedXml } from "xml-crypto"; +import * as fs from "fs"; + +const xml = fs.readFileSync("signed_with_keyinfo.xml", "utf-8"); +const trustedRootCert = fs.readFileSync("root_ca.pem", "utf-8"); + +const result = XmlDSigVerifier.verifySignature(xml, { + keySelector: { + // Extract the certificate from KeyInfo + getCertFromKeyInfo: (keyInfo) => SignedXml.getCertFromKeyInfo(keyInfo), + }, + security: { + // Ensure the certificate is trusted by your root CA + truststore: [trustedRootCert], + // Automatically check if the certificate is expired + checkCertExpiration: true, + }, +}); + +if (result.success) { + console.log("Signature is valid and trusted."); +} else { + console.log("Verification failed:", result.error); +} +``` + +## Advanced Usage + +### Reusing the Verifier Instance + +For better performance when verifying multiple documents with the same configuration, create an instance of `XmlDSigVerifier`. + +```typescript +const verifier = new XmlDSigVerifier({ + keySelector: { publicCert: myPublicCert }, + // Global security options + security: { maxTransforms: 2 }, +}); + +const result1 = verifier.verifySignature(xml1); +const result2 = verifier.verifySignature(xml2); +``` + +### Verification Options + +The `verifySignature` method accepts an options object with the following structure: + +```typescript +interface XmlDSigVerifierOptions { + // STRATEGY: Choose one of the following key selectors + keySelector: + | { publicCert: string | Buffer } // Direct public key/cert + | { getCertFromKeyInfo: (node) => string | null } // Extract from XML + | { sharedSecretKey: string | Buffer }; // HMAC + + // CONFIGURATION + idAttributes?: string[]; // e.g., ['Id', 'ID'] + throwOnError?: boolean; // Default: false (returns result object) + + // SECURITY + security?: { + maxTransforms?: number; // Limit transforms (DoS protection) + checkCertExpiration?: boolean; // Check NotBefore/NotAfter (KeyInfo only) + truststore?: (string | Buffer)[]; // List of trusted CAs (KeyInfo only) + + // Algorithm allow-lists + signatureAlgorithms?: Record; + hashAlgorithms?: Record; + // ... + }; +} +``` + +### Error Handling + +By default, `verifySignature` returns a result object. If you prefer to handle exceptions: + +```typescript +try { + const result = XmlDSigVerifier.verifySignature(xml, { + keySelector: { publicCert }, + throwOnError: true, // Will throw Error on failure + }); + // If code reaches here, signature is valid +} catch (e) { + console.error("Signature invalid:", e.message); +} +``` + +### Handling Multiple Signatures + +If a document contains multiple signatures, you must specify which one to verify by passing the signature node. + +```typescript +import { DOMParser } from "@xmldom/xmldom"; + +const doc = new DOMParser().parseFromString(xml, "application/xml"); +const signatures = doc.getElementsByTagNameNS("http://www.w3.org/2000/09/xmldsig#", "Signature"); + +// Verify the second signature +const result = XmlDSigVerifier.verifySignature( + xml, + { + keySelector: { publicCert }, + }, + signatures[1], +); +``` diff --git a/package-lock.json b/package-lock.json index dc9511ea..b96d2f40 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,7 @@ "dependencies": { "@xmldom/is-dom-node": "^1.0.1", "@xmldom/xmldom": "^0.8.10", - "xpath": "^0.0.33" + "xpath": "^0.0.34" }, "devDependencies": { "@cjbarth/github-release-notes": "^4.2.0", @@ -11882,9 +11882,10 @@ } }, "node_modules/xpath": { - "version": "0.0.33", - "resolved": "https://registry.npmjs.org/xpath/-/xpath-0.0.33.tgz", - "integrity": "sha512-NNXnzrkDrAzalLhIUc01jO2mOzXGXh1JwPgkihcLLzw98c0WgYDmmjSh1Kl3wzaxSVWMuA+fe0WTWOBDWCBmNA==", + "version": "0.0.34", + "resolved": "https://registry.npmjs.org/xpath/-/xpath-0.0.34.tgz", + "integrity": "sha512-FxF6+rkr1rNSQrhUNYrAFJpRXNzlDoMxeXN5qI84939ylEv3qqPFKa85Oxr6tDaJKqwW6KKyo2v26TSv3k6LeA==", + "license": "MIT", "engines": { "node": ">=0.6.0" } @@ -20474,9 +20475,9 @@ "dev": true }, "xpath": { - "version": "0.0.33", - "resolved": "https://registry.npmjs.org/xpath/-/xpath-0.0.33.tgz", - "integrity": "sha512-NNXnzrkDrAzalLhIUc01jO2mOzXGXh1JwPgkihcLLzw98c0WgYDmmjSh1Kl3wzaxSVWMuA+fe0WTWOBDWCBmNA==" + "version": "0.0.34", + "resolved": "https://registry.npmjs.org/xpath/-/xpath-0.0.34.tgz", + "integrity": "sha512-FxF6+rkr1rNSQrhUNYrAFJpRXNzlDoMxeXN5qI84939ylEv3qqPFKa85Oxr6tDaJKqwW6KKyo2v26TSv3k6LeA==" }, "y18n": { "version": "5.0.8", diff --git a/package.json b/package.json index 8bd4caa0..1383f143 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,7 @@ "dependencies": { "@xmldom/is-dom-node": "^1.0.1", "@xmldom/xmldom": "^0.8.10", - "xpath": "^0.0.33" + "xpath": "^0.0.34" }, "devDependencies": { "@cjbarth/github-release-notes": "^4.2.0", diff --git a/src/c14n-canonicalization.ts b/src/c14n-canonicalization.ts index dd9d7788..a037042d 100644 --- a/src/c14n-canonicalization.ts +++ b/src/c14n-canonicalization.ts @@ -1,13 +1,15 @@ import type { - CanonicalizationOrTransformationAlgorithm, - CanonicalizationOrTransformationAlgorithmProcessOptions, + CanonicalizationAlgorithmURI, + CanonicalizationAlgorithm, + TransformAlgorithmOptions, NamespacePrefix, RenderedNamespace, } from "./types"; import * as utils from "./utils"; import * as isDomNode from "@xmldom/is-dom-node"; +import { XMLDSIG_URIS } from "./xmldsig-uris"; -export class C14nCanonicalization implements CanonicalizationOrTransformationAlgorithm { +export class C14nCanonicalization implements CanonicalizationAlgorithm { protected includeComments = false; constructor() { @@ -252,9 +254,10 @@ export class C14nCanonicalization implements CanonicalizationOrTransformationAlg * Perform canonicalization of the given node * * @param node + * @param options * @api public */ - process(node: Node, options: CanonicalizationOrTransformationAlgorithmProcessOptions): string { + process(node: Node, options: TransformAlgorithmOptions): string { options = options || {}; const defaultNs = options.defaultNs || ""; const defaultNsForPrefix = options.defaultNsForPrefix || {}; @@ -275,8 +278,8 @@ export class C14nCanonicalization implements CanonicalizationOrTransformationAlg return res; } - getAlgorithmName() { - return "http://www.w3.org/TR/2001/REC-xml-c14n-20010315"; + getAlgorithmName(): CanonicalizationAlgorithmURI { + return XMLDSIG_URIS.CANONICALIZATION_ALGORITHMS.C14N; } } @@ -289,7 +292,7 @@ export class C14nCanonicalizationWithComments extends C14nCanonicalization { this.includeComments = true; } - getAlgorithmName() { - return "http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments"; + getAlgorithmName(): CanonicalizationAlgorithmURI { + return XMLDSIG_URIS.CANONICALIZATION_ALGORITHMS.C14N_WITH_COMMENTS; } } diff --git a/src/enveloped-signature.ts b/src/enveloped-signature.ts index 5c74c362..389a0627 100644 --- a/src/enveloped-signature.ts +++ b/src/enveloped-signature.ts @@ -1,23 +1,23 @@ import * as xpath from "xpath"; import * as isDomNode from "@xmldom/is-dom-node"; - +import { XMLDSIG_URIS } from "./xmldsig-uris"; import type { - CanonicalizationOrTransformationAlgorithm, - CanonicalizationOrTransformationAlgorithmProcessOptions, + TransformAlgorithmOptions, CanonicalizationOrTransformAlgorithmType, + TransformAlgorithm, } from "./types"; -export class EnvelopedSignature implements CanonicalizationOrTransformationAlgorithm { +export class EnvelopedSignature implements TransformAlgorithm { protected includeComments = false; constructor() { this.includeComments = false; } - process(node: Node, options: CanonicalizationOrTransformationAlgorithmProcessOptions): Node { + process(node: Node, options: TransformAlgorithmOptions): Node { if (null == options.signatureNode) { const signature = xpath.select1( - "./*[local-name(.)='Signature' and namespace-uri(.)='http://www.w3.org/2000/09/xmldsig#']", + `./*[local-name(.)='Signature' and namespace-uri(.)='${XMLDSIG_URIS.NAMESPACES.ds}']`, node, ); if (isDomNode.isNodeLike(signature) && signature.parentNode) { @@ -34,7 +34,7 @@ export class EnvelopedSignature implements CanonicalizationOrTransformationAlgor const expectedSignatureValueData = expectedSignatureValue.data; const signatures = xpath.select( - ".//*[local-name(.)='Signature' and namespace-uri(.)='http://www.w3.org/2000/09/xmldsig#']", + `.//*[local-name(.)='Signature' and namespace-uri(.)='${XMLDSIG_URIS.NAMESPACES.ds}']`, node, ); for (const nodeSignature of Array.isArray(signatures) ? signatures : []) { @@ -55,7 +55,9 @@ export class EnvelopedSignature implements CanonicalizationOrTransformationAlgor return node; } + // eslint-disable-next-line deprecation/deprecation getAlgorithmName(): CanonicalizationOrTransformAlgorithmType { - return "http://www.w3.org/2000/09/xmldsig#enveloped-signature"; + // TODO: replace with TransformAlgorithmURI in next breaking change + return XMLDSIG_URIS.TRANSFORM_ALGORITHMS.ENVELOPED_SIGNATURE; } } diff --git a/src/exclusive-canonicalization.ts b/src/exclusive-canonicalization.ts index ea88aa2c..12ee4565 100644 --- a/src/exclusive-canonicalization.ts +++ b/src/exclusive-canonicalization.ts @@ -1,10 +1,12 @@ import type { - CanonicalizationOrTransformationAlgorithm, - CanonicalizationOrTransformationAlgorithmProcessOptions, + CanonicalizationAlgorithmURI, + CanonicalizationAlgorithm, + TransformAlgorithmOptions, NamespacePrefix, } from "./types"; import * as utils from "./utils"; import * as isDomNode from "@xmldom/is-dom-node"; +import { XMLDSIG_URIS } from "./xmldsig-uris"; function isPrefixInScope(prefixesInScope, prefix, namespaceURI) { let ret = false; @@ -17,7 +19,7 @@ function isPrefixInScope(prefixesInScope, prefix, namespaceURI) { return ret; } -export class ExclusiveCanonicalization implements CanonicalizationOrTransformationAlgorithm { +export class ExclusiveCanonicalization implements CanonicalizationAlgorithm { protected includeComments = false; constructor() { @@ -265,7 +267,7 @@ export class ExclusiveCanonicalization implements CanonicalizationOrTransformati * * @api public */ - process(elem: Element, options: CanonicalizationOrTransformationAlgorithmProcessOptions): string { + process(elem: Element, options: TransformAlgorithmOptions): string { options = options || {}; let inclusiveNamespacesPrefixList = options.inclusiveNamespacesPrefixList || []; const defaultNs = options.defaultNs || ""; @@ -299,7 +301,7 @@ export class ExclusiveCanonicalization implements CanonicalizationOrTransformati ancestorNamespaces.forEach(function (ancestorNamespace) { if (prefix === ancestorNamespace.prefix) { elem.setAttributeNS( - "http://www.w3.org/2000/xmlns/", + XMLDSIG_URIS.NAMESPACES.xmlns, `xmlns:${prefix}`, ancestorNamespace.namespaceURI, ); @@ -319,8 +321,8 @@ export class ExclusiveCanonicalization implements CanonicalizationOrTransformati return res; } - getAlgorithmName() { - return "http://www.w3.org/2001/10/xml-exc-c14n#"; + getAlgorithmName(): CanonicalizationAlgorithmURI { + return XMLDSIG_URIS.CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N; } } @@ -330,7 +332,7 @@ export class ExclusiveCanonicalizationWithComments extends ExclusiveCanonicaliza this.includeComments = true; } - getAlgorithmName() { - return "http://www.w3.org/2001/10/xml-exc-c14n#WithComments"; + getAlgorithmName(): CanonicalizationAlgorithmURI { + return XMLDSIG_URIS.CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N_WITH_COMMENTS; } } diff --git a/src/hash-algorithms.ts b/src/hash-algorithms.ts index eeeb8b27..e75838e1 100644 --- a/src/hash-algorithms.ts +++ b/src/hash-algorithms.ts @@ -1,5 +1,6 @@ import * as crypto from "crypto"; import type { HashAlgorithm } from "./types"; +import { XMLDSIG_URIS } from "./xmldsig-uris"; export class Sha1 implements HashAlgorithm { getHash = function (xml) { @@ -10,7 +11,7 @@ export class Sha1 implements HashAlgorithm { }; getAlgorithmName = function () { - return "http://www.w3.org/2000/09/xmldsig#sha1"; + return XMLDSIG_URIS.HASH_ALGORITHMS.SHA1; }; } @@ -23,7 +24,7 @@ export class Sha256 implements HashAlgorithm { }; getAlgorithmName = function () { - return "http://www.w3.org/2001/04/xmlenc#sha256"; + return XMLDSIG_URIS.HASH_ALGORITHMS.SHA256; }; } @@ -36,6 +37,6 @@ export class Sha512 implements HashAlgorithm { }; getAlgorithmName = function () { - return "http://www.w3.org/2001/04/xmlenc#sha512"; + return XMLDSIG_URIS.HASH_ALGORITHMS.SHA512; }; } diff --git a/src/index.ts b/src/index.ts index 3c82b7a8..a370bfef 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,5 +4,7 @@ export { ExclusiveCanonicalizationWithComments, } from "./exclusive-canonicalization"; export { SignedXml } from "./signed-xml"; +export { XmlDSigVerifier } from "./xmldsig-verifier"; +export { XMLDSIG_URIS } from "./xmldsig-uris"; export * from "./types"; export * from "./utils"; diff --git a/src/signature-algorithms.ts b/src/signature-algorithms.ts index 52e09280..e8512cf8 100644 --- a/src/signature-algorithms.ts +++ b/src/signature-algorithms.ts @@ -1,5 +1,6 @@ import * as crypto from "crypto"; import { type SignatureAlgorithm, createOptionalCallbackFunction } from "./types"; +import { XMLDSIG_URIS } from "./xmldsig-uris"; export class RsaSha1 implements SignatureAlgorithm { getSignature = createOptionalCallbackFunction( @@ -23,7 +24,7 @@ export class RsaSha1 implements SignatureAlgorithm { ); getAlgorithmName = () => { - return "http://www.w3.org/2000/09/xmldsig#rsa-sha1"; + return XMLDSIG_URIS.SIGNATURE_ALGORITHMS.RSA_SHA1; }; } @@ -49,7 +50,7 @@ export class RsaSha256 implements SignatureAlgorithm { ); getAlgorithmName = () => { - return "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"; + return XMLDSIG_URIS.SIGNATURE_ALGORITHMS.RSA_SHA256; }; } @@ -96,7 +97,7 @@ export class RsaSha256Mgf1 implements SignatureAlgorithm { ); getAlgorithmName = () => { - return "http://www.w3.org/2007/05/xmldsig-more#sha256-rsa-MGF1"; + return XMLDSIG_URIS.SIGNATURE_ALGORITHMS.RSA_SHA256_MGF1; }; } @@ -122,7 +123,7 @@ export class RsaSha512 implements SignatureAlgorithm { ); getAlgorithmName = () => { - return "http://www.w3.org/2001/04/xmldsig-more#rsa-sha512"; + return XMLDSIG_URIS.SIGNATURE_ALGORITHMS.RSA_SHA512; }; } @@ -158,6 +159,6 @@ export class HmacSha1 implements SignatureAlgorithm { ); getAlgorithmName = () => { - return "http://www.w3.org/2000/09/xmldsig#hmac-sha1"; + return XMLDSIG_URIS.SIGNATURE_ALGORITHMS.HMAC_SHA1; }; } diff --git a/src/signed-xml.ts b/src/signed-xml.ts index 663d3d0e..4bcb2c56 100644 --- a/src/signed-xml.ts +++ b/src/signed-xml.ts @@ -1,18 +1,21 @@ import type { - CanonicalizationAlgorithmType, - CanonicalizationOrTransformAlgorithmType, - CanonicalizationOrTransformationAlgorithm, - CanonicalizationOrTransformationAlgorithmProcessOptions, + CanonicalizationAlgorithmURI, + TransformAlgorithmOptions, ComputeSignatureOptions, ErrorFirstCallback, GetKeyInfoContentArgs, - HashAlgorithm, - HashAlgorithmType, + HashAlgorithmURI, + IdAttributeType, ObjectAttributes, Reference, - SignatureAlgorithm, - SignatureAlgorithmType, + SignatureAlgorithmURI, SignedXmlOptions, + HashAlgorithmMap, + SignatureAlgorithmMap, + CanonicalizationAlgorithmMap, + TransformAlgorithmMap, + VerificationIdAttributeType, + CanonicalizationOrTransformAlgorithmType, } from "./types"; import * as isDomNode from "@xmldom/is-dom-node"; @@ -26,10 +29,30 @@ import * as execC14n from "./exclusive-canonicalization"; import * as hashAlgorithms from "./hash-algorithms"; import * as signatureAlgorithms from "./signature-algorithms"; import * as utils from "./utils"; +import { XMLDSIG_URIS } from "./xmldsig-uris"; +const { + CANONICALIZATION_ALGORITHMS, + HASH_ALGORITHMS, + SIGNATURE_ALGORITHMS, + TRANSFORM_ALGORITHMS, + NAMESPACES, +} = XMLDSIG_URIS; export class SignedXml { + /** + * Specifies the mode to use when searching for ID attributes. + * Planned for deprecation. Use `idAttributes` instead with value [{ prefix: "wsu", localName: "Id", namespaceUri: "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" }] + */ idMode?: "wssecurity"; - idAttributes: string[]; + /** + * Specifies the Id attributes which will be used to resolve reference URIs. + * When signing, if no Id attribute is found on the element to be signed the first one from this list will be added. + * If idAttribute is also specified, it will be added to the start of this list. + * + * @default {@link SignedXml.getDefaultIdAttributes()} + * @example [{localName: "Id", namespaceUri: "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" }] + */ + idAttributes: IdAttributeType[]; /** * A {@link Buffer} or pem encoded {@link String} containing your private key */ @@ -37,15 +60,16 @@ export class SignedXml { publicCert?: crypto.KeyLike; /** * One of the supported signature algorithms. - * @see {@link SignatureAlgorithmType} + * @see {@link SignatureAlgorithmURI} */ - signatureAlgorithm?: SignatureAlgorithmType = undefined; + signatureAlgorithm?: SignatureAlgorithmURI = undefined; /** * Rules used to convert an XML document into its canonical form. */ - canonicalizationAlgorithm?: CanonicalizationAlgorithmType = undefined; + canonicalizationAlgorithm?: CanonicalizationAlgorithmURI = undefined; /** * It specifies a list of namespace prefixes that should be considered "inclusive" during the canonicalization process. + * Only applicable when using exclusive canonicalization. */ inclusiveNamespacesPrefixList: string[] = []; namespaceResolver: XPathNSResolver = { @@ -53,7 +77,10 @@ export class SignedXml { throw new Error("Not implemented"); }, }; - implicitTransforms: ReadonlyArray = []; + + maxTransforms: number | null; + // eslint-disable-next-line deprecation/deprecation + implicitTransforms: ReadonlyArray = []; // TODO: replace with TransformAlgorithmURI in next breaking change keyInfoAttributes: { [attrName: string]: string } = {}; getKeyInfoContent = SignedXml.getKeyInfoContent; getCertFromKeyInfo = SignedXml.getCertFromKeyInfo; @@ -83,52 +110,69 @@ export class SignedXml { private signedReferences: string[] = []; /** - * 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} + * To add a new canonicalization algorithm create a new class that implements the {@link CanonicalizationAlgorithm} interface, and register it here. More info: {@link https://github.com/node-saml/xml-crypto#customizing-algorithms|Customizing Algorithms} + * @internal Use {@link allowedCanonicalizationAlgorithms} instead */ - CanonicalizationAlgorithms: Record< - CanonicalizationOrTransformAlgorithmType, - new () => CanonicalizationOrTransformationAlgorithm - > = { - "http://www.w3.org/TR/2001/REC-xml-c14n-20010315": c14n.C14nCanonicalization, - "http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments": - c14n.C14nCanonicalizationWithComments, - "http://www.w3.org/2001/10/xml-exc-c14n#": execC14n.ExclusiveCanonicalization, - "http://www.w3.org/2001/10/xml-exc-c14n#WithComments": - execC14n.ExclusiveCanonicalizationWithComments, - "http://www.w3.org/2000/09/xmldsig#enveloped-signature": envelopedSignatures.EnvelopedSignature, - }; - - // TODO: In v7.x we may consider deprecating sha1 + CanonicalizationAlgorithms: CanonicalizationAlgorithmMap; /** * 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} + * @internal Use {@link allowedHashAlgorithms} instead */ - HashAlgorithms: Record HashAlgorithm> = { - "http://www.w3.org/2000/09/xmldsig#sha1": hashAlgorithms.Sha1, - "http://www.w3.org/2001/04/xmlenc#sha256": hashAlgorithms.Sha256, - "http://www.w3.org/2001/04/xmlenc#sha512": hashAlgorithms.Sha512, - }; - - // TODO: In v7.x we may consider deprecating sha1 + HashAlgorithms: HashAlgorithmMap; /** * 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} + * @internal Use {@link allowedSignatureAlgorithms} instead */ - SignatureAlgorithms: Record SignatureAlgorithm> = { - "http://www.w3.org/2000/09/xmldsig#rsa-sha1": signatureAlgorithms.RsaSha1, - "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256": signatureAlgorithms.RsaSha256, - "http://www.w3.org/2007/05/xmldsig-more#sha256-rsa-MGF1": signatureAlgorithms.RsaSha256Mgf1, - "http://www.w3.org/2001/04/xmldsig-more#rsa-sha512": signatureAlgorithms.RsaSha512, - // Disabled by default due to key confusion concerns. - // 'http://www.w3.org/2000/09/xmldsig#hmac-sha1': SignatureAlgorithms.HmacSha1 - }; + SignatureAlgorithms: SignatureAlgorithmMap; + + /** + * To add a new transformation algorithm create a new class that implements the {@link TransformAlgorithm} interface, and register it here. + * @internal Use {@link allowedTransformAlgorithms} instead + */ + TransformAlgorithms: TransformAlgorithmMap | undefined; static defaultNsForPrefix = { - ds: "http://www.w3.org/2000/09/xmldsig#", + ds: NAMESPACES.ds, }; static noop = () => null; + static readonly getDefaultCanonicalizationAlgorithms = (): CanonicalizationAlgorithmMap => ({ + [CANONICALIZATION_ALGORITHMS.C14N]: c14n.C14nCanonicalization, + [CANONICALIZATION_ALGORITHMS.C14N_WITH_COMMENTS]: c14n.C14nCanonicalizationWithComments, + [CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N]: execC14n.ExclusiveCanonicalization, + [CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N_WITH_COMMENTS]: + execC14n.ExclusiveCanonicalizationWithComments, + // TODO: separate TransformAlgorithms from CanonicalizationAlgorithms + [TRANSFORM_ALGORITHMS.ENVELOPED_SIGNATURE]: envelopedSignatures.EnvelopedSignature, + }); + + static readonly getDefaultHashAlgorithms = (): HashAlgorithmMap => ({ + // TODO: In v7.x we may consider removing sha1 from defaults + [HASH_ALGORITHMS.SHA1]: hashAlgorithms.Sha1, + [HASH_ALGORITHMS.SHA256]: hashAlgorithms.Sha256, + [HASH_ALGORITHMS.SHA512]: hashAlgorithms.Sha512, + }); + + static readonly getDefaultAsymmetricSignatureAlgorithms = (): SignatureAlgorithmMap => ({ + // TODO: In v7.x we may consider removing rsa-sha1 from defaults + [SIGNATURE_ALGORITHMS.RSA_SHA1]: signatureAlgorithms.RsaSha1, + [SIGNATURE_ALGORITHMS.RSA_SHA256]: signatureAlgorithms.RsaSha256, + [SIGNATURE_ALGORITHMS.RSA_SHA256_MGF1]: signatureAlgorithms.RsaSha256Mgf1, + [SIGNATURE_ALGORITHMS.RSA_SHA512]: signatureAlgorithms.RsaSha512, + }); + + static readonly getDefaultSymmetricSignatureAlgorithms = (): SignatureAlgorithmMap => ({ + [SIGNATURE_ALGORITHMS.HMAC_SHA1]: signatureAlgorithms.HmacSha1, + }); + + static readonly getDefaultTransformAlgorithms = (): TransformAlgorithmMap => + SignedXml.getDefaultCanonicalizationAlgorithms(); + + static readonly getDefaultIdAttributes = (): VerificationIdAttributeType[] => ["Id", "ID", "id"]; + /** * The SignedXml constructor provides an abstraction for sign and verify xml documents. The object is constructed using * @param options {@link SignedXmlOptions} @@ -137,21 +181,27 @@ export class SignedXml { const { idMode, idAttribute, + idAttributes, privateKey, publicCert, signatureAlgorithm, canonicalizationAlgorithm, inclusiveNamespacesPrefixList, + maxTransforms, implicitTransforms, keyInfoAttributes, getKeyInfoContent, getCertFromKeyInfo, objects, + allowedSignatureAlgorithms, + allowedHashAlgorithms, + allowedCanonicalizationAlgorithms, + allowedTransformAlgorithms, } = options; // Options this.idMode = idMode; - this.idAttributes = ["Id", "ID", "id"]; + this.idAttributes = idAttributes ?? SignedXml.getDefaultIdAttributes(); if (idAttribute) { this.idAttributes.unshift(idAttribute); } @@ -164,14 +214,18 @@ export class SignedXml { } else if (utils.isArrayHasLength(inclusiveNamespacesPrefixList)) { this.inclusiveNamespacesPrefixList = inclusiveNamespacesPrefixList; } + this.maxTransforms = maxTransforms ?? null; this.implicitTransforms = implicitTransforms ?? this.implicitTransforms; this.keyInfoAttributes = keyInfoAttributes ?? this.keyInfoAttributes; this.getKeyInfoContent = getKeyInfoContent ?? this.getKeyInfoContent; this.getCertFromKeyInfo = getCertFromKeyInfo ?? SignedXml.noop; this.objects = objects; - this.CanonicalizationAlgorithms; - this.HashAlgorithms; - this.SignatureAlgorithms; + this.CanonicalizationAlgorithms = + allowedCanonicalizationAlgorithms ?? SignedXml.getDefaultCanonicalizationAlgorithms(); + this.HashAlgorithms = allowedHashAlgorithms ?? SignedXml.getDefaultHashAlgorithms(); + this.SignatureAlgorithms = + allowedSignatureAlgorithms ?? SignedXml.getDefaultAsymmetricSignatureAlgorithms(); + this.TransformAlgorithms = allowedTransformAlgorithms; // TODO: use default transform algorithms (breaking change) } /** @@ -180,9 +234,8 @@ export class SignedXml { * This enables HMAC and disables other signing algorithms. */ enableHMAC(): void { - this.SignatureAlgorithms = { - "http://www.w3.org/2000/09/xmldsig#hmac-sha1": signatureAlgorithms.HmacSha1, - }; + // eslint-disable-next-line deprecation/deprecation + this.SignatureAlgorithms = SignedXml.getDefaultSymmetricSignatureAlgorithms(); this.getKeyInfoContent = SignedXml.noop; } @@ -404,9 +457,8 @@ export class SignedXml { } if ( - this.canonicalizationAlgorithm === "http://www.w3.org/TR/2001/REC-xml-c14n-20010315" || - this.canonicalizationAlgorithm === - "http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments" + this.canonicalizationAlgorithm === CANONICALIZATION_ALGORITHMS.C14N || + this.canonicalizationAlgorithm === CANONICALIZATION_ALGORITHMS.C14N_WITH_COMMENTS ) { if (!doc || typeof doc !== "object") { throw new Error( @@ -456,7 +508,7 @@ export class SignedXml { } } - private findSignatureAlgorithm(name?: SignatureAlgorithmType) { + private findSignatureAlgorithm(name?: SignatureAlgorithmURI) { if (name == null) { throw new Error("signatureAlgorithm is required"); } @@ -468,7 +520,7 @@ export class SignedXml { } } - private findCanonicalizationAlgorithm(name: CanonicalizationOrTransformAlgorithmType) { + private findCanonicalizationAlgorithm(name: CanonicalizationAlgorithmURI) { if (name != null) { const algo = this.CanonicalizationAlgorithms[name]; if (algo) { @@ -479,7 +531,7 @@ export class SignedXml { throw new Error(`canonicalization algorithm '${name}' is not supported`); } - private findHashAlgorithm(name: HashAlgorithmType) { + private findHashAlgorithm(name: HashAlgorithmURI) { const algo = this.HashAlgorithms[name]; if (algo) { return new algo(); @@ -488,6 +540,21 @@ export class SignedXml { } } + // eslint-disable-next-line deprecation/deprecation + private findTransformAlgorithm(name: CanonicalizationOrTransformAlgorithmType) { + // TODO: replace with TransformAlgorithmURI in next breaking change + // TODO: remove this fallback (breaking change) + if (this.TransformAlgorithms == null) { + return this.findCanonicalizationAlgorithm(name); + } + const algo = this.TransformAlgorithms[name]; + if (algo) { + return new algo(); + } else { + throw new Error(`transform algorithm '${name}' is not supported`); + } + } + validateElementAgainstReferences(elemOrXpath: Element | string, doc: Document): Reference { let elem: Element; if (typeof elemOrXpath === "string") { @@ -502,11 +569,28 @@ export class SignedXml { for (const ref of this.getReferences()) { const uri = ref.uri?.[0] === "#" ? ref.uri.substring(1) : ref.uri; - for (const attr of this.idAttributes) { - const elemId = elem.getAttribute(attr); - if (uri === elemId) { - ref.xpath = `//*[@*[local-name(.)='${attr}']='${uri}']`; - break; // found the correct element, no need to check further + for (const idAttr of this.idAttributes) { + if (typeof idAttr === "string") { + if (uri === elem.getAttribute(idAttr)) { + // We look for attributes in any namespace or no namespace + ref.xpath = `//*[@*[local-name(.)='${idAttr}']='${uri}']`; + break; // found the correct element, no need to check further + } + } else { + const attr = utils.findAttr(elem, idAttr.localName, idAttr.namespaceUri); + if (attr && uri === attr.value) { + if (typeof idAttr.namespaceUri === "string") { + // When namespaceUri is set, we look for attributes in that specific namespace + ref.xpath = `//*[@*[local-name(.)='${idAttr.localName}' and namespace-uri(.)='${idAttr.namespaceUri}']='${uri}']`; + } else if (idAttr.namespaceUri === null) { + // When namespaceUri is explicitly set to null, we look only for attributes without a namespace + ref.xpath = `//*[@*[local-name(.)='${idAttr.localName}' and namespace-uri(.)='']='${uri}']`; + } else { + // When namespaceUri is undefined, we look for attributes regardless of namespace + ref.xpath = `//*[@*[local-name(.)='${idAttr.localName}']='${uri}']`; + } + break; // found the correct element, no need to check further + } } } @@ -533,8 +617,21 @@ export class SignedXml { throw new Error("Cannot validate a uri with quotes inside it"); } else { let num_elements_for_id = 0; - for (const attr of this.idAttributes) { - const tmp_elemXpath = `//*[@*[local-name(.)='${attr}']='${uri}']`; + for (const idAttr of this.idAttributes) { + let tmp_elemXpath: string; + + if (typeof idAttr === "string") { + tmp_elemXpath = `//*[@*[local-name(.)='${idAttr}']='${uri}']`; + } else { + if (typeof idAttr.namespaceUri === "string") { + tmp_elemXpath = `//*[@*[local-name(.)='${idAttr.localName}' and namespace-uri(.)='${idAttr.namespaceUri}']='${uri}']`; + } else if (idAttr.namespaceUri === null) { + tmp_elemXpath = `//*[@*[local-name(.)='${idAttr.localName}' and namespace-uri(.)='']='${uri}']`; + } else { + tmp_elemXpath = `//*[@*[local-name(.)='${idAttr.localName}']='${uri}']`; + } + } + const tmp_elem = xpath.select(tmp_elemXpath, doc); if (utils.isArrayHasLength(tmp_elem)) { num_elements_for_id += tmp_elem.length; @@ -594,7 +691,7 @@ export class SignedXml { findSignatures(doc: Node): Node[] { const nodes = xpath.select( - "//*[local-name(.)='Signature' and namespace-uri(.)='http://www.w3.org/2000/09/xmldsig#']", + `//*[local-name(.)='Signature' and namespace-uri(.)='${NAMESPACES.ds}']`, doc, ); @@ -624,7 +721,13 @@ export class SignedXml { } if (isDomNode.isAttributeNode(node)) { - this.canonicalizationAlgorithm = node.value as CanonicalizationAlgorithmType; + this.canonicalizationAlgorithm = node.value as CanonicalizationAlgorithmURI; + + if (!this.findCanonicalizationAlgorithm(this.canonicalizationAlgorithm)) { + throw new Error( + `unsupported canonicalization algorithm: ${this.canonicalizationAlgorithm}`, + ); + } } const signatureAlgorithm = xpath.select1( @@ -633,7 +736,7 @@ export class SignedXml { ); if (isDomNode.isAttributeNode(signatureAlgorithm)) { - this.signatureAlgorithm = signatureAlgorithm.value as SignatureAlgorithmType; + this.signatureAlgorithm = signatureAlgorithm.value as SignatureAlgorithmURI; } const signedInfoNodes = utils.findChildren(this.signatureNode, "SignedInfo"); @@ -652,12 +755,10 @@ export class SignedXml { let canonicalizationAlgorithmForSignedInfo = this.canonicalizationAlgorithm; if ( !canonicalizationAlgorithmForSignedInfo || - canonicalizationAlgorithmForSignedInfo === - "http://www.w3.org/TR/2001/REC-xml-c14n-20010315" || - canonicalizationAlgorithmForSignedInfo === - "http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments" + canonicalizationAlgorithmForSignedInfo === CANONICALIZATION_ALGORITHMS.C14N || + canonicalizationAlgorithmForSignedInfo === CANONICALIZATION_ALGORITHMS.C14N_WITH_COMMENTS ) { - canonicalizationAlgorithmForSignedInfo = "http://www.w3.org/2001/10/xml-exc-c14n#"; + canonicalizationAlgorithmForSignedInfo = CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N; } const temporaryCanonSignedInfo = this.getCanonXml( @@ -773,14 +874,22 @@ export class SignedXml { */ if ( transforms.length === 0 || - transforms[transforms.length - 1] === "http://www.w3.org/2000/09/xmldsig#enveloped-signature" + transforms[transforms.length - 1] === TRANSFORM_ALGORITHMS.ENVELOPED_SIGNATURE ) { - transforms.push("http://www.w3.org/TR/2001/REC-xml-c14n-20010315"); + transforms.push(CANONICALIZATION_ALGORITHMS.C14N); } const refUri = isDomNode.isElementNode(refNode) ? refNode.getAttribute("URI") || undefined : undefined; + if (this.maxTransforms !== null) { + if (transforms.length > this.maxTransforms) { + throw new Error( + `Number of transforms (${transforms.length}) exceeds the maximum allowed (${this.maxTransforms})`, + ); + } + } + this.addReference({ transforms, digestAlgorithm: digestAlgo, @@ -1095,7 +1204,7 @@ export class SignedXml { } const currentPrefix = prefix ? `${prefix}:` : ""; - const signatureNamespace = "http://www.w3.org/2000/09/xmldsig#"; + const signatureNamespace = XMLDSIG_URIS.NAMESPACES.ds; // Find the SignedInfo element to append to const signedInfoNode = xpath.select1(`./*[local-name(.)='SignedInfo']`, signatureElem); @@ -1161,7 +1270,7 @@ export class SignedXml { ); for (const trans of ref.transforms || []) { - const transform = this.findCanonicalizationAlgorithm(trans); + const transform = this.findTransformAlgorithm(trans); const transformElem = signatureDoc.createElementNS( signatureNamespace, `${currentPrefix}Transform`, @@ -1264,7 +1373,7 @@ export class SignedXml { getCanonXml( transforms: Reference["transforms"], node: Node, - options: CanonicalizationOrTransformationAlgorithmProcessOptions = {}, + options: TransformAlgorithmOptions = {}, ) { options.defaultNsForPrefix = options.defaultNsForPrefix ?? SignedXml.defaultNsForPrefix; options.signatureNode = this.signatureNode; @@ -1275,7 +1384,7 @@ export class SignedXml { transforms.forEach((transformName) => { if (isDomNode.isNodeLike(transformedXml)) { // If, after processing, `transformedNode` is a string, we can't do anymore transforms on it - const transform = this.findCanonicalizationAlgorithm(transformName); + const transform = this.findTransformAlgorithm(transformName); transformedXml = transform.process(transformedXml, options); } //TODO: currently transform.process may return either Node or String value (enveloped transformation returns Node, exclusive-canonicalization returns String). @@ -1298,14 +1407,14 @@ export class SignedXml { let attr; if (this.idMode === "wssecurity") { - attr = utils.findAttr( - node, - "Id", - "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd", - ); + attr = utils.findAttr(node, "Id", XMLDSIG_URIS.NAMESPACES.wsu); } else { this.idAttributes.some((idAttribute) => { - attr = utils.findAttr(node, idAttribute); + if (typeof idAttribute === "string") { + attr = utils.findAttr(node, idAttribute); + } else { + attr = utils.findAttr(node, idAttribute.localName, idAttribute.namespaceUri); + } return !!attr; // This will break the loop as soon as a truthy attr is found. }); } @@ -1318,18 +1427,29 @@ export class SignedXml { const id = `_${this.id++}`; if (this.idMode === "wssecurity") { - 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", - ); - node.setAttributeNS( - "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd", - "wsu:Id", - id, - ); + node.setAttributeNS(NAMESPACES.xmlns, "xmlns:wsu", NAMESPACES.wsu); + node.setAttributeNS(NAMESPACES.wsu, "wsu:Id", id); } else { - node.setAttribute("Id", id); + // Use the first idAttribute to set the new ID + const firstIdAttr = this.idAttributes[0]; + if (typeof firstIdAttr === "string") { + node.setAttribute(firstIdAttr, id); + } else { + if ("prefix" in firstIdAttr && firstIdAttr.prefix) { + node.setAttributeNS( + NAMESPACES.xmlns, + `xmlns:${firstIdAttr.prefix}`, + firstIdAttr.namespaceUri, + ); + node.setAttributeNS( + firstIdAttr.namespaceUri, + `${firstIdAttr.prefix}:${firstIdAttr.localName}`, + id, + ); + } else { + node.setAttribute(firstIdAttr.localName, id); + } + } } return id; @@ -1345,17 +1465,17 @@ export class SignedXml { "Missing canonicalizationAlgorithm when trying to create signed info for XML", ); } - const transform = this.findCanonicalizationAlgorithm(this.canonicalizationAlgorithm); + const canonicalization = this.findCanonicalizationAlgorithm(this.canonicalizationAlgorithm); const algo = this.findSignatureAlgorithm(this.signatureAlgorithm); const currentPrefix = prefix ? `${prefix}:` : ""; let res = `<${currentPrefix}SignedInfo>`; - res += `<${currentPrefix}CanonicalizationMethod Algorithm="${transform.getAlgorithmName()}"`; + res += `<${currentPrefix}CanonicalizationMethod Algorithm="${canonicalization.getAlgorithmName()}"`; if (utils.isArrayHasLength(this.inclusiveNamespacesPrefixList)) { res += ">"; res += ``; + )}" xmlns="${canonicalization.getAlgorithmName()}"/>`; res += ``; } else { res += " />"; @@ -1384,7 +1504,7 @@ export class SignedXml { const signatureValueXml = `<${prefix}SignatureValue>${this.signatureValue}`; //the canonicalization requires to get a valid xml node. //we need to wrap the info in a dummy signature since it contains the default namespace. - const dummySignatureWrapper = `<${prefix}Signature ${xmlNsAttr}="http://www.w3.org/2000/09/xmldsig#">${signatureValueXml}`; + const dummySignatureWrapper = `<${prefix}Signature ${xmlNsAttr}="${NAMESPACES.ds}">${signatureValueXml}`; const doc = new xmldom.DOMParser().parseFromString(dummySignatureWrapper); diff --git a/src/types.ts b/src/types.ts index 89c0b304..432de774 100644 --- a/src/types.ts +++ b/src/types.ts @@ -7,33 +7,22 @@ /// import * as crypto from "crypto"; +import { XMLDSIG_URIS } from "./xmldsig-uris"; +import { KeyLike, X509Certificate } from "node:crypto"; +const { SIGNATURE_ALGORITHMS, HASH_ALGORITHMS, TRANSFORM_ALGORITHMS, CANONICALIZATION_ALGORITHMS } = + XMLDSIG_URIS; export type ErrorFirstCallback = (err: Error | null, result?: T) => void; -export type CanonicalizationAlgorithmType = - | "http://www.w3.org/TR/2001/REC-xml-c14n-20010315" - | "http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments" - | "http://www.w3.org/2001/10/xml-exc-c14n#" - | "http://www.w3.org/2001/10/xml-exc-c14n#WithComments" - | string; - -export type CanonicalizationOrTransformAlgorithmType = - | CanonicalizationAlgorithmType - | "http://www.w3.org/2000/09/xmldsig#enveloped-signature"; +export type SignatureIdAttributeType = + | string + | { prefix?: undefined; localName: string; namespaceUri?: null } + | { prefix: string; localName: string; namespaceUri: string }; -export type HashAlgorithmType = - | "http://www.w3.org/2000/09/xmldsig#sha1" - | "http://www.w3.org/2001/04/xmlenc#sha256" - | "http://www.w3.org/2001/04/xmlenc#sha512" - | string; - -export type SignatureAlgorithmType = - | "http://www.w3.org/2000/09/xmldsig#rsa-sha1" - | "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256" - | "http://www.w3.org/2007/05/xmldsig-more#sha256-rsa-MGF1" - | "http://www.w3.org/2001/04/xmldsig-more#rsa-sha512" - | "http://www.w3.org/2000/09/xmldsig#hmac-sha1" - | string; +export type VerificationIdAttributeType = + | string + | { localName: string; namespaceUri?: string | null }; +export type IdAttributeType = SignatureIdAttributeType | VerificationIdAttributeType; /** * @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,27 +48,132 @@ export interface ObjectAttributes { [key: string]: string | undefined; } +export type KeySelectorFunction = (keyInfo?: Node | null) => string | null; + +export interface NamespacePrefix { + prefix: string; + namespaceURI: string; +} + +export interface TransformAlgorithmOptions { + defaultNs?: string; + defaultNsForPrefix?: Record; + ancestorNamespaces?: NamespacePrefix[]; + signatureNode?: Node | null; + inclusiveNamespacesPrefixList?: string[]; +} + +export type SignatureAlgorithmURI = + | (typeof SIGNATURE_ALGORITHMS)[keyof typeof SIGNATURE_ALGORITHMS] + | string; + +/** Extend this to create a new SignatureAlgorithm */ +export interface SignatureAlgorithm { + /** + * Sign the given string using the given key + */ + getSignature(signedInfo: crypto.BinaryLike, privateKey: crypto.KeyLike): string; + getSignature( + signedInfo: crypto.BinaryLike, + privateKey: crypto.KeyLike, + callback?: ErrorFirstCallback, + ): void; + /** + * Verify the given signature of the given string using key + * + * @param key a public cert, public key, or private key can be passed here + */ + verifySignature(material: string, key: crypto.KeyLike, signatureValue: string): boolean; + verifySignature( + material: string, + key: crypto.KeyLike, + signatureValue: string, + callback?: ErrorFirstCallback, + ): void; + + getAlgorithmName(): SignatureAlgorithmURI; +} +export type SignatureAlgorithmMap = Record SignatureAlgorithm>; + +export type HashAlgorithmURI = (typeof HASH_ALGORITHMS)[keyof typeof HASH_ALGORITHMS] | string; +/** Implement this to create a new HashAlgorithm */ +export interface HashAlgorithm { + getAlgorithmName(): HashAlgorithmURI; + + getHash(xml: string): string; +} +export type HashAlgorithmMap = Record HashAlgorithm>; + +export type TransformAlgorithmURI = + | (typeof TRANSFORM_ALGORITHMS)[keyof typeof TRANSFORM_ALGORITHMS] + | string; +/** Implement this to create a new TransformAlgorithm */ +export interface TransformAlgorithm { + getAlgorithmName(): TransformAlgorithmURI; + + process(node: Node, options: TransformAlgorithmOptions): string | Node; +} +export type CanonicalizationAlgorithmURI = + | (typeof CANONICALIZATION_ALGORITHMS)[keyof typeof CANONICALIZATION_ALGORITHMS] + | string; +/** Implement this to create a new CanonicalizationAlgorithm */ +export interface CanonicalizationAlgorithm extends TransformAlgorithm { + getAlgorithmName(): CanonicalizationAlgorithmURI; + + // TODO: after canonicalization algorithms algorithms are separated from transform algorithms, + // set process to return string only + process(node: Node, options: TransformAlgorithmOptions): string | Node; +} +export type CanonicalizationAlgorithmMap = Record< + CanonicalizationAlgorithmURI, + new () => CanonicalizationAlgorithm +>; +/** + * @deprecated Use CanonicalizationAlgorithm or TransformAlgorithm instead. + */ +// eslint-disable-next-line deprecation/deprecation +export type CanonicalizationOrTransformationAlgorithm = + | CanonicalizationAlgorithm + | TransformAlgorithm; +export type TransformAlgorithmMap = Record< + TransformAlgorithmURI, + // eslint-disable-next-line deprecation/deprecation + new () => CanonicalizationOrTransformationAlgorithm +>; // TODO: replace with TransformAlgorithm in next breaking change +/** + * @deprecated Use CanonicalizationAlgorithmURI or TransformAlgorithmURI instead. + */ +export type CanonicalizationOrTransformAlgorithmType = + | CanonicalizationAlgorithmURI + | TransformAlgorithmURI; + +/** + * @deprecated Use CanonicalizationAlgorithmURI instead. + */ +export type CanonicalizationAlgorithmType = CanonicalizationAlgorithmURI; /** * Options for the SignedXml constructor. */ export interface SignedXmlOptions { idMode?: "wssecurity"; - idAttribute?: string; + idAttribute?: SignatureIdAttributeType; + idAttributes?: VerificationIdAttributeType[]; privateKey?: crypto.KeyLike; publicCert?: crypto.KeyLike; - signatureAlgorithm?: SignatureAlgorithmType; - canonicalizationAlgorithm?: CanonicalizationAlgorithmType; + signatureAlgorithm?: SignatureAlgorithmURI; + canonicalizationAlgorithm?: CanonicalizationAlgorithmURI; inclusiveNamespacesPrefixList?: string | string[]; - implicitTransforms?: ReadonlyArray; + maxTransforms?: number | null; + // eslint-disable-next-line deprecation/deprecation + implicitTransforms?: ReadonlyArray; // TODO: replace with TransformAlgorithmURI in next breaking change keyInfoAttributes?: Record; getKeyInfoContent?(args?: GetKeyInfoContentArgs): string | null; - getCertFromKeyInfo?(keyInfo?: Node | null): string | null; + getCertFromKeyInfo?: KeySelectorFunction; objects?: Array<{ content: string; attributes?: ObjectAttributes }>; -} - -export interface NamespacePrefix { - prefix: string; - namespaceURI: string; + allowedSignatureAlgorithms?: SignatureAlgorithmMap; + allowedHashAlgorithms?: HashAlgorithmMap; + allowedCanonicalizationAlgorithms?: CanonicalizationAlgorithmMap; + allowedTransformAlgorithms?: TransformAlgorithmMap; } export interface RenderedNamespace { @@ -87,14 +181,6 @@ export interface RenderedNamespace { newDefaultNs: string; } -export interface CanonicalizationOrTransformationAlgorithmProcessOptions { - defaultNs?: string; - defaultNsForPrefix?: Record; - ancestorNamespaces?: NamespacePrefix[]; - signatureNode?: Node | null; - inclusiveNamespacesPrefixList?: string[]; -} - export interface ComputeSignatureOptionsLocation { reference?: string; action?: "append" | "prepend" | "before" | "after"; @@ -127,10 +213,11 @@ export interface Reference { xpath?: string; // An array of transforms to be applied to the data before signing. - transforms: ReadonlyArray; + // eslint-disable-next-line deprecation/deprecation + transforms: ReadonlyArray; // TODO: replace with TransformAlgorithmURI in next breaking change // The algorithm used to calculate the digest value of the data. - digestAlgorithm: HashAlgorithmType; + digestAlgorithm: HashAlgorithmURI; // The URI that identifies the data to be signed. uri: string; @@ -160,57 +247,6 @@ export interface Reference { signedReference?: string; } -/** Implement this to create a new CanonicalizationOrTransformationAlgorithm */ -export interface CanonicalizationOrTransformationAlgorithm { - process( - node: Node, - options: CanonicalizationOrTransformationAlgorithmProcessOptions, - ): Node | string; - - getAlgorithmName(): CanonicalizationOrTransformAlgorithmType; -} - -/** Implement this to create a new HashAlgorithm */ -export interface HashAlgorithm { - getAlgorithmName(): HashAlgorithmType; - - getHash(xml: string): string; -} - -/** Extend this to create a new SignatureAlgorithm */ -export interface SignatureAlgorithm { - /** - * Sign the given string using the given key - */ - getSignature(signedInfo: crypto.BinaryLike, privateKey: crypto.KeyLike): string; - getSignature( - signedInfo: crypto.BinaryLike, - privateKey: crypto.KeyLike, - callback?: ErrorFirstCallback, - ): void; - /** - * Verify the given signature of the given string using key - * - * @param key a public cert, public key, or private key can be passed here - */ - verifySignature(material: string, key: crypto.KeyLike, signatureValue: string): boolean; - verifySignature( - material: string, - key: crypto.KeyLike, - signatureValue: string, - callback?: ErrorFirstCallback, - ): void; - - getAlgorithmName(): SignatureAlgorithmType; -} - -/** Implement this to create a new TransformAlgorithm */ -export interface TransformAlgorithm { - getAlgorithmName(): CanonicalizationOrTransformAlgorithmType; - - process(node: Node): string; -} - /** * ### Sign * #### Properties @@ -268,3 +304,189 @@ export function createOptionalCallbackFunction( (...args: [...A, ErrorFirstCallback]): void; }; } + +/*** XmlDSigVerifier types ***/ + +export type CertificateKeySelector = { + /** Public certificate or key to use for verification */ + publicCert: KeyLike; +}; + +export type KeyInfoKeySelector = { + /** Function to extract the public key from KeyInfo element */ + getCertFromKeyInfo: (keyInfo?: Node | null) => string | null; +}; + +export type SharedSecretKeySelector = { + /** Shared secret key to use for HMAC verification */ + sharedSecretKey: KeyLike; +}; + +export type KeySelector = CertificateKeySelector | KeyInfoKeySelector | SharedSecretKeySelector; + +/** + * Common configuration options for XML-DSig verification (Base). + */ +export interface XmlDSigVerifierOptionsBase { + /** + * Names of XML attributes to treat as element identifiers. + * @default {@link SignedXml.getDefaultIdAttributes()} + */ + idAttributes?: VerificationIdAttributeType[]; + + /** + * Transforms to apply implicitly during canonicalization. + */ + // eslint-disable-next-line deprecation/deprecation + implicitTransforms?: ReadonlyArray; // TODO: replace with TransformAlgorithmURI in next breaking change + + /** + * Whether to throw an exception on verification failure. + * @default false + */ + throwOnError?: boolean; +} + +export interface XmlDSigVerifierSecurityOptions { + /** + * Maximum number of transforms allowed per Reference element. + * Limits complexity to prevent denial-of-service attacks. + * @default {@link SignedXml.DEFAULT_MAX_TRANSFORMS} + */ + maxTransforms?: number; + + /** + * Signature algorithms allowed during verification. + * + * @default {@link SignedXml.getDefaultAsymmetricSignatureAlgorithms()} {@link SignedXml.getDefaultSymmetricSignatureAlgorithms()} + */ + signatureAlgorithms?: SignatureAlgorithmMap; + + /** + * Hash algorithms allowed during verification. + * + * @default {@link SignedXml.getDefaultHashAlgorithms()} + */ + hashAlgorithms?: HashAlgorithmMap; + + /** + * Transform algorithms allowed during verification. (This must include canonicalization algorithms) + * + * @default all algorithms in {@link SignedXml.getDefaultTransformAlgorithms()} + */ + transformAlgorithms?: TransformAlgorithmMap; + + /** + * Canonicalization algorithms allowed during verification. + * + * @default all algorithms in {@link SignedXml.getDefaultCanonicalizationAlgorithms()} + */ + canonicalizationAlgorithms?: CanonicalizationAlgorithmMap; +} + +export interface KeyInfoXmlDSigSecurityOptions extends XmlDSigVerifierSecurityOptions { + /** + * Check certificate expiration dates during verification. + * If true, signatures with expired certificates will be considered invalid. + * This only applies when using KeyInfoKeySelector + * @default true + */ + checkCertExpiration?: boolean; + + /** + * Optional truststore of trusted certificates + * When provided, the certificate used to sign the XML must chain to one of these trusted certificates. + * These must be PEM or DER encoded X509 certificates + */ + truststore?: Array; +} + +/** + * Configuration options for verification using KeyInfo. + * Allows advanced security options for cert validation. + */ +export type KeyInfoXmlDSigVerifierOptions = XmlDSigVerifierOptionsBase & { + /** + * Function to extract the public key from KeyInfo element. + */ + keySelector: KeyInfoKeySelector; + + /** + * Security options for KeyInfo verification. + */ + security?: KeyInfoXmlDSigSecurityOptions; +}; + +/** + * Configuration options for verification using a provided public certificate. + * Certificate validation options (e.g., expiration) are not applicable here. + */ +export type PublicCertXmlDSigVerifierOptions = XmlDSigVerifierOptionsBase & { + /** + * Public certificate or key to use for verification. + */ + keySelector: CertificateKeySelector; + + /** + * Basic security options for verification. + */ + security?: XmlDSigVerifierSecurityOptions; +}; + +/** + * Configuration options for verification using a shared secret (HMAC). + */ +export type SharedSecretXmlDSigVerifierOptions = XmlDSigVerifierOptionsBase & { + /** + * Shared secret key to use for HMAC verification. + */ + keySelector: SharedSecretKeySelector; + + /** + * Basic security options for verification. + */ + security?: XmlDSigVerifierSecurityOptions; +}; + +export type XmlDSigVerifierOptions = + | KeyInfoXmlDSigVerifierOptions + | PublicCertXmlDSigVerifierOptions + | SharedSecretXmlDSigVerifierOptions; + +/** + * Verification result containing the outcome and signed content. + */ +export type SuccessfulXmlDsigVerificationResult = { + /** Whether the signature was successfully verified */ + success: true; + error?: undefined; + /** The canonicalized XML content that passed verification */ + signedReferences: string[]; +}; + +export type FailedXmlDsigVerificationResult = { + /** Whether the signature was successfully verified */ + success: false; + /** Error message if verification failed */ + error: string; + signedReferences?: undefined; +}; + +export type XmlDsigVerificationResult = + | SuccessfulXmlDsigVerificationResult + | FailedXmlDsigVerificationResult; + +/** + * @deprecated Use TransformAlgorithmOptions instead. + */ +export type CanonicalizationOrTransformationAlgorithmProcessOptions = TransformAlgorithmOptions; + +/** + * @deprecated Use SignatureAlgorithmURI instead. + */ +export type SignatureAlgorithmType = SignatureAlgorithmURI; + +/** + * @deprecated Use HashAlgorithmURI instead. + */ +export type HashAlgorithmType = HashAlgorithmURI; diff --git a/src/utils.ts b/src/utils.ts index 466b252e..308f2508 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -6,18 +6,33 @@ export function isArrayHasLength(array: unknown): array is unknown[] { return Array.isArray(array) && array.length > 0; } -function attrEqualsExplicitly(attr: Attr, localName: string, namespace?: string) { +function attrEqualsExplicitly(attr: Attr, localName: string, namespace?: string | null) { + if (namespace === null) { + return attr.localName === localName && !attr.namespaceURI; + } return attr.localName === localName && (attr.namespaceURI === namespace || namespace == null); } -function attrEqualsImplicitly(attr: Attr, localName: string, namespace?: string, node?: Element) { +function attrEqualsImplicitly( + attr: Attr, + localName: string, + namespace?: string | null, + node?: Element, +) { + if (namespace === null) { + return attr.localName === localName && !attr.namespaceURI; + } return ( attr.localName === localName && ((!attr.namespaceURI && node?.namespaceURI === namespace) || namespace == null) ); } -export function findAttr(element: Element, localName: string, namespace?: string) { +export function findAttr( + element: Element, + localName: string, + namespace?: string | null | undefined, +) { for (let i = 0; i < element.attributes.length; i++) { const attr = element.attributes[i]; diff --git a/src/xmldsig-uris.ts b/src/xmldsig-uris.ts new file mode 100644 index 00000000..0c498716 --- /dev/null +++ b/src/xmldsig-uris.ts @@ -0,0 +1,60 @@ +/** + * Supported canonicalization algorithms + */ +const CANONICALIZATION_ALGORITHMS = { + C14N: "http://www.w3.org/TR/2001/REC-xml-c14n-20010315", + C14N_WITH_COMMENTS: "http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments", + EXCLUSIVE_C14N: "http://www.w3.org/2001/10/xml-exc-c14n#", + EXCLUSIVE_C14N_WITH_COMMENTS: "http://www.w3.org/2001/10/xml-exc-c14n#WithComments", +} as const; + +/** + * Supported transform algorithms (includes canonicalization + enveloped signature) + */ +const TRANSFORM_ALGORITHMS = { + ...CANONICALIZATION_ALGORITHMS, + ENVELOPED_SIGNATURE: "http://www.w3.org/2000/09/xmldsig#enveloped-signature", +} as const; + +/** + * Supported digest algorithms + */ +const HASH_ALGORITHMS = { + SHA1: "http://www.w3.org/2000/09/xmldsig#sha1", + SHA256: "http://www.w3.org/2001/04/xmlenc#sha256", + SHA512: "http://www.w3.org/2001/04/xmlenc#sha512", +} as const; + +/** + * Supported signature algorithms + */ +const SIGNATURE_ALGORITHMS = { + RSA_SHA1: "http://www.w3.org/2000/09/xmldsig#rsa-sha1", + RSA_SHA256: "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256", + RSA_SHA256_MGF1: "http://www.w3.org/2007/05/xmldsig-more#sha256-rsa-MGF1", + RSA_SHA512: "http://www.w3.org/2001/04/xmldsig-more#rsa-sha512", + HMAC_SHA1: "http://www.w3.org/2000/09/xmldsig#hmac-sha1", +} as const; + +/** + * Common XML namespaces + */ +const NAMESPACES = { + xml: "http://www.w3.org/XML/1998/namespace", + xmlns: "http://www.w3.org/2000/xmlns/", + ds: "http://www.w3.org/2000/09/xmldsig#", + wsu: "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd", + wsse: "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd", + xades: "http://uri.etsi.org/01903/v1.3.2#", +} as const; + +/** + * XML-DSig URI constants organized by category + */ +export const XMLDSIG_URIS = { + CANONICALIZATION_ALGORITHMS, + TRANSFORM_ALGORITHMS, + HASH_ALGORITHMS, + SIGNATURE_ALGORITHMS, + NAMESPACES, +} as const; diff --git a/src/xmldsig-verifier.ts b/src/xmldsig-verifier.ts new file mode 100644 index 00000000..67db21cb --- /dev/null +++ b/src/xmldsig-verifier.ts @@ -0,0 +1,312 @@ +import { KeyLike, X509Certificate } from "node:crypto"; +import { DOMParser } from "@xmldom/xmldom"; +import { SignedXml } from "./signed-xml"; +import { + KeySelectorFunction, + SignedXmlOptions, + VerificationIdAttributeType, + XmlDSigVerifierOptions, + XmlDsigVerificationResult, + TransformAlgorithmURI, + KeyInfoXmlDSigSecurityOptions, + KeyInfoKeySelector, + SharedSecretKeySelector, + CertificateKeySelector, + XmlDSigVerifierSecurityOptions, + KeyInfoXmlDSigVerifierOptions, + SharedSecretXmlDSigVerifierOptions, + PublicCertXmlDSigVerifierOptions, +} from "./types"; +import { isArrayHasLength } from "./utils"; + +type ResolvedXmlDSigVerifierOptionsBase = { + idAttributes: VerificationIdAttributeType[]; + implicitTransforms?: ReadonlyArray; + throwOnError: boolean; +}; + +type ResolvedKeyInfoOptions = ResolvedXmlDSigVerifierOptionsBase & { + optionsType: "keyinfo"; + keySelector: KeyInfoKeySelector; + security: Required; +}; + +type ResolvedCertificateOptions = ResolvedXmlDSigVerifierOptionsBase & { + optionsType: "certificate"; + keySelector: CertificateKeySelector; + security: Required; +}; + +type ResolvedSharedSecretOptions = ResolvedXmlDSigVerifierOptionsBase & { + optionsType: "sharedsecret"; + keySelector: SharedSecretKeySelector; + security: Required; +}; + +type ResolvedXmlDsigVerifierOptions = + | ResolvedKeyInfoOptions + | ResolvedCertificateOptions + | ResolvedSharedSecretOptions; + +const isResolvedKeyInfoOptions = ( + options: ResolvedXmlDsigVerifierOptions, +): options is ResolvedKeyInfoOptions => options.optionsType === "keyinfo"; + +const isResolvedPublicCertOptions = ( + options: ResolvedXmlDsigVerifierOptions, +): options is ResolvedCertificateOptions => options.optionsType === "certificate"; + +const isResolvedSharedSecretOptions = ( + options: ResolvedXmlDsigVerifierOptions, +): options is ResolvedSharedSecretOptions => options.optionsType === "sharedsecret"; + +const isKeyInfoSelector = ( + options: XmlDSigVerifierOptions, +): options is KeyInfoXmlDSigVerifierOptions => "getCertFromKeyInfo" in options.keySelector; + +const isSharedSecretSelector = ( + options: XmlDSigVerifierOptions, +): options is SharedSecretXmlDSigVerifierOptions => "sharedSecretKey" in options.keySelector; + +const isPublicCertSelector = ( + options: XmlDSigVerifierOptions, +): options is PublicCertXmlDSigVerifierOptions => "publicCert" in options.keySelector; + +/** + * A focused API for XML signature verification with enhanced security. + */ +export class XmlDSigVerifier { + private readonly signedXml: SignedXml; + private readonly options: ResolvedXmlDsigVerifierOptions; + + public static readonly DEFAULT_MAX_TRANSFORMS = 4; + public static readonly DEFAULT_CHECK_CERT_EXPIRATION = true; + public static readonly DEFAULT_THROW_ON_ERROR = false; + + /** + * Creates a new XmlDSigVerifier instance. The instance can be reused for multiple verifications. + * + * @param options Configuration options for verification + */ + constructor(options: XmlDSigVerifierOptions) { + this.options = XmlDSigVerifier.resolveOptions(options); + + this.signedXml = XmlDSigVerifier.createSignedXml(this.options); + } + + /** + * Verifies an XML signature. Static convenience method for one-off verifications. + * + * @param xml The signed XML document to validate + * @param options Configuration options for verification + * @param signatureNode Optional specific Signature node to validate + */ + public static verifySignature( + xml: string, + options: XmlDSigVerifierOptions, + signatureNode?: Node, + ): XmlDsigVerificationResult { + try { + return new XmlDSigVerifier(options).verifySignature(xml, signatureNode); + } catch (error) { + return XmlDSigVerifier.handleError( + error, + options.throwOnError ?? XmlDSigVerifier.DEFAULT_THROW_ON_ERROR, + ); + } + } + + /** + * Validates an XML signature using the pre-configured options. + * + * @param xml The signed XML document to validate + * @param signatureNode Optional specific Signature node to validate + * @returns Verification result with signed references if successful + */ + public verifySignature(xml: string, signatureNode?: Node): XmlDsigVerificationResult { + try { + // Load the signature node + if (signatureNode) { + // Use the provided signature node + this.signedXml.loadSignature(signatureNode); + } else { + // Auto-detect signature if exactly one signature is found in the document + const doc = new DOMParser().parseFromString(xml, "application/xml"); + const signatureNodes = this.signedXml.findSignatures(doc); + + if (signatureNodes.length === 0) { + return XmlDSigVerifier.handleError( + "No Signature element found in the provided XML document.", + this.options.throwOnError, + ); + } else if (signatureNodes.length > 1) { + return XmlDSigVerifier.handleError( + "Multiple Signature elements found in the provided XML document. Please provide the specific signatureNode parameter to validate.", + this.options.throwOnError, + ); + } + + // Load the single found signature + this.signedXml.loadSignature(signatureNodes[0]); + } + + // Perform cryptographic verification + const isValid = this.signedXml.checkSignature(xml); + + if (!isValid) { + throw new Error("Signature verification failed"); + } + + return { + success: isValid, + signedReferences: this.signedXml.getSignedReferences(), + }; + } catch (error) { + return XmlDSigVerifier.handleError(error, this.options.throwOnError); + } + } + + private static resolveOptions(options: XmlDSigVerifierOptions): ResolvedXmlDsigVerifierOptions { + const defaults = { + idAttributes: SignedXml.getDefaultIdAttributes(), + maxTransforms: XmlDSigVerifier.DEFAULT_MAX_TRANSFORMS, + checkCertExpiration: XmlDSigVerifier.DEFAULT_CHECK_CERT_EXPIRATION, + truststore: [], + signatureAlgorithms: isSharedSecretSelector(options) + ? SignedXml.getDefaultSymmetricSignatureAlgorithms() + : SignedXml.getDefaultAsymmetricSignatureAlgorithms(), + hashAlgorithms: SignedXml.getDefaultHashAlgorithms(), + transformAlgorithms: SignedXml.getDefaultTransformAlgorithms(), + canonicalizationAlgorithms: SignedXml.getDefaultCanonicalizationAlgorithms(), + }; + + const baseOptions = { + idAttributes: options.idAttributes ?? defaults.idAttributes, + implicitTransforms: options.implicitTransforms, + throwOnError: options.throwOnError ?? XmlDSigVerifier.DEFAULT_THROW_ON_ERROR, + }; + + const baseSecurity = { + maxTransforms: options.security?.maxTransforms ?? defaults.maxTransforms, + signatureAlgorithms: options.security?.signatureAlgorithms ?? defaults.signatureAlgorithms, + hashAlgorithms: options.security?.hashAlgorithms ?? defaults.hashAlgorithms, + transformAlgorithms: options.security?.transformAlgorithms ?? defaults.transformAlgorithms, + canonicalizationAlgorithms: + options.security?.canonicalizationAlgorithms ?? defaults.canonicalizationAlgorithms, + }; + + if (isKeyInfoSelector(options)) { + return { + optionsType: "keyinfo", + ...baseOptions, + keySelector: options.keySelector, + security: { + ...baseSecurity, + checkCertExpiration: + options.security?.checkCertExpiration ?? defaults.checkCertExpiration, + truststore: options.security?.truststore ?? defaults.truststore, + }, + }; + } else if (isSharedSecretSelector(options)) { + return { + optionsType: "sharedsecret", + ...baseOptions, + keySelector: options.keySelector, + security: baseSecurity, + }; + } else if (isPublicCertSelector(options)) { + return { + optionsType: "certificate", + ...baseOptions, + keySelector: options.keySelector, + security: baseSecurity, + }; + } else { + throw new Error("XmlDSigVerifier requires a valid keySelector option."); + } + } + + private static createSignedXml(options: ResolvedXmlDsigVerifierOptions): SignedXml { + const signedXmlOptions: SignedXmlOptions = { + publicCert: undefined as KeyLike | undefined, + getCertFromKeyInfo: undefined as KeySelectorFunction | undefined, + idAttributes: options.idAttributes, + maxTransforms: options.security.maxTransforms, + implicitTransforms: options.implicitTransforms, + allowedSignatureAlgorithms: options.security.signatureAlgorithms, + allowedHashAlgorithms: options.security.hashAlgorithms, + allowedTransformAlgorithms: options.security.transformAlgorithms, + allowedCanonicalizationAlgorithms: options.security.canonicalizationAlgorithms, + }; + + if (isResolvedKeyInfoOptions(options)) { + const keySelector = options.keySelector; + + if (typeof keySelector.getCertFromKeyInfo !== "function") { + throw new Error("XmlDSigVerifier requires a valid getCertFromKeyInfo function."); + } + + const getCertFromKeyInfo = keySelector.getCertFromKeyInfo; + const truststore = options.security.truststore.map((cert) => { + if (typeof cert === "string" || Buffer.isBuffer(cert)) { + const x509 = new X509Certificate(cert); + return x509.publicKey; + } + return cert.publicKey; + }); + const checkCertExpiration = options.security.checkCertExpiration; + + signedXmlOptions.getCertFromKeyInfo = (keyInfo?: Node | null): string | null => { + const certPem = getCertFromKeyInfo(keyInfo); + if (!certPem) { + return null; + } + + if (checkCertExpiration || isArrayHasLength(truststore)) { + const x509 = new X509Certificate(certPem); + if (checkCertExpiration) { + const now = new Date(); + if (x509.validTo && new Date(x509.validTo) < now) { + throw new Error("The certificate used to sign the XML has expired."); + } + if (x509.validFrom && new Date(x509.validFrom) > now) { + throw new Error("The certificate used to sign the XML is not yet valid."); + } + } + if (isArrayHasLength(truststore)) { + const isTrusted = truststore.some((trustedCert) => { + if (trustedCert.equals?.(x509.publicKey) || x509.verify(trustedCert)) { + return true; + } + return false; + }); + if (!isTrusted) { + throw new Error("The certificate used to sign the XML is not trusted."); + } + } + } + return certPem; + }; + } else if (isResolvedPublicCertOptions(options)) { + signedXmlOptions.publicCert = options.keySelector.publicCert; + } else if (isResolvedSharedSecretOptions(options)) { + signedXmlOptions.privateKey = options.keySelector.sharedSecretKey; + } + + return new SignedXml(signedXmlOptions); + } + + private static handleError(error: unknown, throwOnError: boolean): XmlDsigVerificationResult { + if (throwOnError) { + throw error; + } + + const errorMessage = + error instanceof Error ? error.message : `Verification error occurred: ${String(error)}`; + + return { + success: false, + error: errorMessage, + }; + } +} diff --git a/test/c14n-non-exclusive-unit-tests.spec.ts b/test/c14n-non-exclusive-unit-tests.spec.ts index ee7f2ba4..73505052 100644 --- a/test/c14n-non-exclusive-unit-tests.spec.ts +++ b/test/c14n-non-exclusive-unit-tests.spec.ts @@ -1,6 +1,6 @@ import { expect } from "chai"; -import { C14nCanonicalization } from "../src/c14n-canonicalization"; +import { C14nCanonicalization } from "../src"; import * as xmldom from "@xmldom/xmldom"; import * as xpath from "xpath"; import * as utils from "../src/utils"; diff --git a/test/c14nWithComments-unit-tests.spec.ts b/test/c14nWithComments-unit-tests.spec.ts index fbf36895..2ef98f34 100644 --- a/test/c14nWithComments-unit-tests.spec.ts +++ b/test/c14nWithComments-unit-tests.spec.ts @@ -3,7 +3,7 @@ import { expect } from "chai"; import { ExclusiveCanonicalizationWithComments as c14nWithComments } from "../src/exclusive-canonicalization"; import * as xmldom from "@xmldom/xmldom"; import * as xpath from "xpath"; -import { SignedXml } from "../src/index"; +import { SignedXml, XMLDSIG_URIS } from "../src"; import * as isDomNode from "@xmldom/is-dom-node"; const compare = function (xml, xpathArg, expected, inclusiveNamespacesPrefixList?: string[]) { @@ -216,7 +216,7 @@ describe("Exclusive canonicalization with comments", function () { ); }); - /* + /* TODO: Uncomment this when this issue is fixed it("Exclusive canonicalization removal of whitespace between PITarget and its data", function () { compare( @@ -242,7 +242,7 @@ describe("Exclusive canonicalization with comments", function () { ); }); - /* + /* TODO: Uncomment this when this issue is fixed it("The XML declaration and document type declaration (DTD) are removed, stylesheet retained", function () { compare( @@ -356,8 +356,8 @@ describe("Exclusive canonicalization with comments", function () { 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#", + XMLDSIG_URIS.TRANSFORM_ALGORITHMS.ENVELOPED_SIGNATURE, + XMLDSIG_URIS.CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N, ], node, ); @@ -373,7 +373,7 @@ describe("Exclusive canonicalization with comments", 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"]; + const transforms = [XMLDSIG_URIS.TRANSFORM_ALGORITHMS.ENVELOPED_SIGNATURE]; isDomNode.assertIsNodeLike(node); const res = sig.getCanonXml(transforms, node); expect(res).to.equal(""); diff --git a/test/canonicalization-unit-tests.spec.ts b/test/canonicalization-unit-tests.spec.ts index 7a39f168..97d4d71e 100644 --- a/test/canonicalization-unit-tests.spec.ts +++ b/test/canonicalization-unit-tests.spec.ts @@ -1,9 +1,8 @@ import { expect } from "chai"; -import { ExclusiveCanonicalization } from "../src/exclusive-canonicalization"; import * as xmldom from "@xmldom/xmldom"; import * as xpath from "xpath"; -import { SignedXml } from "../src/index"; +import { SignedXml, ExclusiveCanonicalization, XMLDSIG_URIS } from "../src"; import * as isDomNode from "@xmldom/is-dom-node"; const compare = function ( @@ -58,7 +57,7 @@ describe("Canonicalization unit tests", function () { "//*[local-name(.)='SignedInfo']", '', undefined, - { ds: "http://www.w3.org/2000/09/xmldsig#" }, + { ds: XMLDSIG_URIS.NAMESPACES.ds }, ); }); @@ -407,8 +406,8 @@ describe("Canonicalization unit tests", function () { 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#", + XMLDSIG_URIS.TRANSFORM_ALGORITHMS.ENVELOPED_SIGNATURE, + XMLDSIG_URIS.CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N, ], node, ); @@ -426,12 +425,12 @@ describe("Canonicalization unit tests", function () { const sig = new SignedXml(); const res1 = sig.getCanonXml( [ - "http://www.w3.org/2001/10/xml-exc-c14n#", - "http://www.w3.org/2000/09/xmldsig#enveloped-signature", + XMLDSIG_URIS.CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N, + XMLDSIG_URIS.TRANSFORM_ALGORITHMS.ENVELOPED_SIGNATURE, ], node1, ); - const res2 = sig.getCanonXml(["http://www.w3.org/2001/10/xml-exc-c14n#"], node2); + const res2 = sig.getCanonXml([XMLDSIG_URIS.CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N], node2); expect(res1) .to.equal(res2) .to.equal( @@ -450,7 +449,7 @@ describe("Canonicalization unit tests", function () { isDomNode.assertIsNodeLike(node); const sig = new SignedXml(); - const transforms = ["http://www.w3.org/2000/09/xmldsig#enveloped-signature"]; + const transforms = [XMLDSIG_URIS.TRANSFORM_ALGORITHMS.ENVELOPED_SIGNATURE]; const res = sig.getCanonXml(transforms, node); expect(res).to.equal(""); }); diff --git a/test/document-tests.spec.ts b/test/document-tests.spec.ts index b8311994..84392ec5 100644 --- a/test/document-tests.spec.ts +++ b/test/document-tests.spec.ts @@ -1,4 +1,4 @@ -import { SignedXml } from "../src/index"; +import { SignedXml, XMLDSIG_URIS } from "../src"; import * as xpath from "xpath"; import * as xmldom from "@xmldom/xmldom"; import * as fs from "fs"; @@ -10,7 +10,7 @@ describe("Document tests", function () { const xml = fs.readFileSync("./test/static/valid_saml.xml", "utf-8"); const doc = new xmldom.DOMParser().parseFromString(xml); const node = xpath.select1( - "/*/*[local-name(.)='Signature' and namespace-uri(.)='http://www.w3.org/2000/09/xmldsig#']", + `/*/*[local-name(.)='Signature' and namespace-uri(.)='${XMLDSIG_URIS.NAMESPACES.ds}']`, doc, ); @@ -28,7 +28,7 @@ describe("Document tests", function () { const xml = fs.readFileSync("./test/static/valid_saml.xml", "utf-8"); const doc = new xmldom.DOMParser().parseFromString(xml); const node = xpath.select1( - "/*/*[local-name(.)='Signature' and namespace-uri(.)='http://www.w3.org/2000/09/xmldsig#']", + `/*/*[local-name(.)='Signature' and namespace-uri(.)='${XMLDSIG_URIS.NAMESPACES.ds}']`, doc, ); diff --git a/test/hmac-tests.spec.ts b/test/hmac-tests.spec.ts index 6573ca6b..9ebaa2dd 100644 --- a/test/hmac-tests.spec.ts +++ b/test/hmac-tests.spec.ts @@ -1,4 +1,4 @@ -import { SignedXml } from "../src/index"; +import { SignedXml, XMLDSIG_URIS } from "../src"; import * as xpath from "xpath"; import * as xmldom from "@xmldom/xmldom"; import * as fs from "fs"; @@ -10,7 +10,7 @@ describe("HMAC tests", function () { const xml = fs.readFileSync("./test/static/hmac_signature.xml", "utf-8"); const doc = new xmldom.DOMParser().parseFromString(xml); const signature = xpath.select1( - "/*/*[local-name(.)='Signature' and namespace-uri(.)='http://www.w3.org/2000/09/xmldsig#']", + `/*/*[local-name(.)='Signature' and namespace-uri(.)='${XMLDSIG_URIS.NAMESPACES.ds}']`, doc, ); isDomNode.assertIsNodeLike(signature); @@ -29,7 +29,7 @@ describe("HMAC tests", function () { const xml = fs.readFileSync("./test/static/hmac_signature.xml", "utf-8"); const doc = new xmldom.DOMParser().parseFromString(xml); const signature = xpath.select1( - "/*/*[local-name(.)='Signature' and namespace-uri(.)='http://www.w3.org/2000/09/xmldsig#']", + `/*/*[local-name(.)='Signature' and namespace-uri(.)='${XMLDSIG_URIS.NAMESPACES.ds}']`, doc, ); isDomNode.assertIsNodeLike(signature); @@ -48,18 +48,18 @@ describe("HMAC tests", function () { const sig = new SignedXml(); sig.enableHMAC(); sig.privateKey = fs.readFileSync("./test/static/hmac.key"); - sig.signatureAlgorithm = "http://www.w3.org/2000/09/xmldsig#hmac-sha1"; + sig.signatureAlgorithm = XMLDSIG_URIS.SIGNATURE_ALGORITHMS.HMAC_SHA1; sig.addReference({ xpath: "//*[local-name(.)='book']", - digestAlgorithm: "http://www.w3.org/2000/09/xmldsig#sha1", - transforms: ["http://www.w3.org/2001/10/xml-exc-c14n#"], + digestAlgorithm: XMLDSIG_URIS.HASH_ALGORITHMS.SHA1, + transforms: [XMLDSIG_URIS.CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N], }); - sig.canonicalizationAlgorithm = "http://www.w3.org/2001/10/xml-exc-c14n#"; + sig.canonicalizationAlgorithm = XMLDSIG_URIS.CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N; sig.computeSignature(xml); const doc = new xmldom.DOMParser().parseFromString(sig.getSignedXml()); const signature = xpath.select1( - "/*/*[local-name(.)='Signature' and namespace-uri(.)='http://www.w3.org/2000/09/xmldsig#']", + `/*/*[local-name(.)='Signature' and namespace-uri(.)='${XMLDSIG_URIS.NAMESPACES.ds}']`, doc, ); isDomNode.assertIsNodeLike(signature); diff --git a/test/key-info-tests.spec.ts b/test/key-info-tests.spec.ts index 19c8f4a7..4ba648a1 100644 --- a/test/key-info-tests.spec.ts +++ b/test/key-info-tests.spec.ts @@ -1,7 +1,7 @@ import * as xmldom from "@xmldom/xmldom"; import * as fs from "fs"; import * as xpath from "xpath"; -import { SignedXml } from "../src/index"; +import { SignedXml, XMLDSIG_URIS } from "../src"; import { expect } from "chai"; import * as isDomNode from "@xmldom/is-dom-node"; @@ -11,8 +11,8 @@ describe("KeyInfo tests", function () { const sig = new SignedXml(); sig.privateKey = fs.readFileSync("./test/static/client.pem"); sig.publicCert = fs.readFileSync("./test/static/client_public.pem"); - sig.canonicalizationAlgorithm = "http://www.w3.org/2001/10/xml-exc-c14n#"; - sig.signatureAlgorithm = "http://www.w3.org/2000/09/xmldsig#rsa-sha1"; + sig.canonicalizationAlgorithm = XMLDSIG_URIS.CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N; + sig.signatureAlgorithm = XMLDSIG_URIS.SIGNATURE_ALGORITHMS.RSA_SHA1; sig.computeSignature(xml); const signedXml = sig.getSignedXml(); const doc = new xmldom.DOMParser().parseFromString(signedXml); @@ -27,14 +27,14 @@ describe("KeyInfo tests", function () { const sig = new SignedXml(); sig.privateKey = fs.readFileSync("./test/static/hmac.key"); sig.publicCert = fs.readFileSync("./test/static/hmac.key"); - sig.signatureAlgorithm = "http://www.w3.org/2000/09/xmldsig#hmac-sha1"; + sig.signatureAlgorithm = XMLDSIG_URIS.SIGNATURE_ALGORITHMS.HMAC_SHA1; sig.enableHMAC(); sig.addReference({ xpath: "//*[local-name(.)='book']", - digestAlgorithm: "http://www.w3.org/2000/09/xmldsig#sha1", - transforms: ["http://www.w3.org/2001/10/xml-exc-c14n#"], + digestAlgorithm: XMLDSIG_URIS.HASH_ALGORITHMS.SHA1, + transforms: [XMLDSIG_URIS.CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N], }); - sig.canonicalizationAlgorithm = "http://www.w3.org/2001/10/xml-exc-c14n#"; + sig.canonicalizationAlgorithm = XMLDSIG_URIS.CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N; sig.computeSignature(xml); const doc = new xmldom.DOMParser().parseFromString(sig.getSignedXml()); diff --git a/test/saml-response-tests.spec.ts b/test/saml-response-tests.spec.ts index eb349098..1677a703 100644 --- a/test/saml-response-tests.spec.ts +++ b/test/saml-response-tests.spec.ts @@ -1,4 +1,4 @@ -import { SignedXml } from "../src/index"; +import { SignedXml, XMLDSIG_URIS } from "../src"; import * as xpath from "xpath"; import * as xmldom from "@xmldom/xmldom"; import * as fs from "fs"; @@ -10,7 +10,7 @@ describe("SAML response tests", function () { const xml = fs.readFileSync("./test/static/valid_saml.xml", "utf-8"); const doc = new xmldom.DOMParser().parseFromString(xml); const signature = xpath.select1( - "/*/*[local-name(.)='Signature' and namespace-uri(.)='http://www.w3.org/2000/09/xmldsig#']", + `/*/*[local-name(.)='Signature' and namespace-uri(.)='${XMLDSIG_URIS.NAMESPACES.ds}']`, doc, ); isDomNode.assertIsNodeLike(signature); @@ -27,7 +27,7 @@ describe("SAML response tests", function () { const xml = fs.readFileSync("./test/static/valid_saml_sha256_rsa_mgf1.xml", "utf-8"); const doc = new xmldom.DOMParser().parseFromString(xml); const signature = xpath.select1( - "/*/*[local-name(.)='Signature' and namespace-uri(.)='http://www.w3.org/2000/09/xmldsig#']", + `/*/*[local-name(.)='Signature' and namespace-uri(.)='${XMLDSIG_URIS.NAMESPACES.ds}']`, doc, ); isDomNode.assertIsNodeLike(signature); @@ -43,7 +43,7 @@ describe("SAML response tests", function () { const xml = fs.readFileSync("./test/static/invalid_saml_sha256_rsa_mgf1.xml", "utf-8"); const doc = new xmldom.DOMParser().parseFromString(xml); const signature = xpath.select1( - "/*/*[local-name(.)='Signature' and namespace-uri(.)='http://www.w3.org/2000/09/xmldsig#']", + `/*/*[local-name(.)='Signature' and namespace-uri(.)='${XMLDSIG_URIS.NAMESPACES.ds}']`, doc, ); isDomNode.assertIsNodeLike(signature); @@ -61,7 +61,7 @@ describe("SAML response tests", function () { const assertion = xpath.select1("//*[local-name(.)='Assertion']", doc); isDomNode.assertIsNodeLike(assertion); const signature = xpath.select1( - "//*[local-name(.)='Signature' and namespace-uri(.)='http://www.w3.org/2000/09/xmldsig#']", + `//*[local-name(.)='Signature' and namespace-uri(.)='${XMLDSIG_URIS.NAMESPACES.ds}']`, assertion, ); isDomNode.assertIsNodeLike(signature); @@ -83,7 +83,7 @@ describe("SAML response tests", function () { const xml = fs.readFileSync("./test/static/saml_external_ns.xml", "utf-8"); const doc = new xmldom.DOMParser().parseFromString(xml); const signature = xpath.select1( - "//*//*[local-name(.)='Signature' and namespace-uri(.)='http://www.w3.org/2000/09/xmldsig#']", + `//*//*[local-name(.)='Signature' and namespace-uri(.)='${XMLDSIG_URIS.NAMESPACES.ds}']`, doc, ); isDomNode.assertIsNodeLike(signature); @@ -101,7 +101,7 @@ describe("SAML response tests", function () { const assertion = xpath.select1("//*[local-name(.)='Assertion']", doc); isDomNode.assertIsNodeLike(assertion); const signature = xpath.select1( - "//*[local-name(.)='Signature' and namespace-uri(.)='http://www.w3.org/2000/09/xmldsig#']", + `//*[local-name(.)='Signature' and namespace-uri(.)='${XMLDSIG_URIS.NAMESPACES.ds}']`, assertion, ); isDomNode.assertIsNodeLike(signature); @@ -117,7 +117,7 @@ describe("SAML response tests", function () { const xml = fs.readFileSync("./test/static/valid_saml_withcomments.xml", "utf-8"); const doc = new xmldom.DOMParser().parseFromString(xml); const signature = xpath.select1( - "/*/*[local-name(.)='Signature' and namespace-uri(.)='http://www.w3.org/2000/09/xmldsig#']", + `/*/*[local-name(.)='Signature' and namespace-uri(.)='${XMLDSIG_URIS.NAMESPACES.ds}']`, doc, ); isDomNode.assertIsNodeLike(signature); @@ -133,7 +133,7 @@ describe("SAML response tests", function () { const xml = fs.readFileSync("./test/static/invalid_saml_no_signed_info.xml", "utf-8"); const doc = new xmldom.DOMParser().parseFromString(xml); const node = xpath.select1( - "/*/*[local-name(.)='Signature' and namespace-uri(.)='http://www.w3.org/2000/09/xmldsig#']", + `/*/*[local-name(.)='Signature' and namespace-uri(.)='${XMLDSIG_URIS.NAMESPACES.ds}']`, doc, ); @@ -151,7 +151,7 @@ describe("SAML response tests", function () { const assertion = xpath.select1("//*[local-name(.)='Assertion']", doc); isDomNode.assertIsNodeLike(assertion); const signature = xpath.select1( - "//*[local-name(.)='Signature' and namespace-uri(.)='http://www.w3.org/2000/09/xmldsig#']", + `//*[local-name(.)='Signature' and namespace-uri(.)='${XMLDSIG_URIS.NAMESPACES.ds}']`, assertion, ); isDomNode.assertIsNodeLike(signature); @@ -172,7 +172,7 @@ describe("SAML response tests", function () { const assertion = xpath.select1("//*[local-name(.)='Assertion'][1]", doc); isDomNode.assertIsNodeLike(assertion); const signature = xpath.select1( - "//*[local-name(.)='Signature' and namespace-uri(.)='http://www.w3.org/2000/09/xmldsig#']", + `//*[local-name(.)='Signature' and namespace-uri(.)='${XMLDSIG_URIS.NAMESPACES.ds}']`, assertion, ); isDomNode.assertIsNodeLike(signature); @@ -191,7 +191,7 @@ describe("SAML response tests", function () { const assertion = xpath.select1("//*[local-name(.)='Assertion']", doc); isDomNode.assertIsNodeLike(assertion); const signature = xpath.select1( - "//*[local-name(.)='Signature' and namespace-uri(.)='http://www.w3.org/2000/09/xmldsig#']", + `//*[local-name(.)='Signature' and namespace-uri(.)='${XMLDSIG_URIS.NAMESPACES.ds}']`, assertion, ); isDomNode.assertIsNodeLike(signature); diff --git a/test/signature-integration-tests.spec.ts b/test/signature-integration-tests.spec.ts index 02da0949..12962337 100644 --- a/test/signature-integration-tests.spec.ts +++ b/test/signature-integration-tests.spec.ts @@ -1,6 +1,6 @@ import * as xpath from "xpath"; import * as xmldom from "@xmldom/xmldom"; -import { SignedXml } from "../src/index"; +import { SignedXml, XMLDSIG_URIS } from "../src"; import * as fs from "fs"; import { expect } from "chai"; import * as isDomNode from "@xmldom/is-dom-node"; @@ -10,16 +10,16 @@ describe("Signature integration tests", function () { const sig = new SignedXml(); sig.privateKey = fs.readFileSync("./test/static/client.pem"); - xpath.map(function (n) { + xpath.forEach(function (n) { sig.addReference({ xpath: n, - digestAlgorithm: "http://www.w3.org/2000/09/xmldsig#sha1", - transforms: ["http://www.w3.org/2001/10/xml-exc-c14n#"], + digestAlgorithm: XMLDSIG_URIS.HASH_ALGORITHMS.SHA1, + transforms: [XMLDSIG_URIS.CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N], }); }); sig.canonicalizationAlgorithm = canonicalizationAlgorithm; - sig.signatureAlgorithm = "http://www.w3.org/2000/09/xmldsig#rsa-sha1"; + sig.signatureAlgorithm = XMLDSIG_URIS.SIGNATURE_ALGORITHMS.RSA_SHA1; sig.computeSignature(xml); const signed = sig.getSignedXml(); @@ -34,7 +34,7 @@ describe("Signature integration tests", function () { xml, "./test/static/integration/expectedVerify.xml", ["//*[local-name(.)='x']", "//*[local-name(.)='y']", "//*[local-name(.)='w']"], - "http://www.w3.org/2001/10/xml-exc-c14n#", + XMLDSIG_URIS.CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N, ); }); @@ -54,7 +54,7 @@ describe("Signature integration tests", function () { xml, "./test/static/integration/expectedVerifyComplex.xml", ["//*[local-name(.)='book']"], - "http://www.w3.org/2001/10/xml-exc-c14n#", + XMLDSIG_URIS.CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N, ); }); @@ -101,7 +101,7 @@ describe("Signature integration tests", function () { const childXml = doc.firstChild?.toString(); const signature = xpath.select1( - "//*//*[local-name(.)='Signature' and namespace-uri(.)='http://www.w3.org/2000/09/xmldsig#']", + `//*//*[local-name(.)='Signature' and namespace-uri(.)='${XMLDSIG_URIS.NAMESPACES.ds}']`, doc, ); isDomNode.assertIsNodeLike(signature); @@ -120,7 +120,7 @@ describe("Signature integration tests", function () { const childXml = doc.firstChild?.toString(); const signature = xpath.select1( - "//*//*[local-name(.)='Signature' and namespace-uri(.)='http://www.w3.org/2000/09/xmldsig#']", + `//*//*[local-name(.)='Signature' and namespace-uri(.)='${XMLDSIG_URIS.NAMESPACES.ds}']`, doc, ); isDomNode.assertIsNodeLike(signature); @@ -142,7 +142,7 @@ describe("Signature integration tests", function () { const childXml = doc.firstChild?.toString(); const signature = xpath.select1( - "//*//*[local-name(.)='Signature' and namespace-uri(.)='http://www.w3.org/2000/09/xmldsig#']", + `//*//*[local-name(.)='Signature' and namespace-uri(.)='${XMLDSIG_URIS.NAMESPACES.ds}']`, doc, ); isDomNode.assertIsNodeLike(signature); @@ -164,7 +164,7 @@ describe("Signature integration tests", function () { const childXml = doc.firstChild?.toString(); const signature = xpath.select1( - "//*//*[local-name(.)='Signature' and namespace-uri(.)='http://www.w3.org/2000/09/xmldsig#']", + `//*//*[local-name(.)='Signature' and namespace-uri(.)='${XMLDSIG_URIS.NAMESPACES.ds}']`, doc, ); isDomNode.assertIsNodeLike(signature); @@ -183,12 +183,12 @@ describe("Signature integration tests", function () { const sig = new SignedXml(); sig.addReference({ xpath: "//*[local-name(.)='book']", - digestAlgorithm: "http://www.w3.org/2000/09/xmldsig#sha1", - transforms: ["http://www.w3.org/2001/10/xml-exc-c14n#"], + digestAlgorithm: XMLDSIG_URIS.HASH_ALGORITHMS.SHA1, + transforms: [XMLDSIG_URIS.CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N], }); sig.privateKey = fs.readFileSync("./test/static/client.pem"); - sig.canonicalizationAlgorithm = "http://www.w3.org/2001/10/xml-exc-c14n#"; - sig.signatureAlgorithm = "http://www.w3.org/2000/09/xmldsig#rsa-sha1"; + sig.canonicalizationAlgorithm = XMLDSIG_URIS.CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N; + sig.signatureAlgorithm = XMLDSIG_URIS.SIGNATURE_ALGORITHMS.RSA_SHA1; sig.computeSignature(xml); const signed = sig.getSignedXml(); diff --git a/test/signature-object-tests.spec.ts b/test/signature-object-tests.spec.ts index 75126c23..fdd45e7f 100644 --- a/test/signature-object-tests.spec.ts +++ b/test/signature-object-tests.spec.ts @@ -3,7 +3,7 @@ import { expect, assert } from "chai"; import * as xpath from "xpath"; import * as xmldom from "@xmldom/xmldom"; import * as isDomNode from "@xmldom/is-dom-node"; -import { SignedXml } from "../src"; +import { SignedXml, XMLDSIG_URIS } from "../src"; import { Sha256 } from "../src/hash-algorithms"; const privateKey = fs.readFileSync("./test/static/client.pem", "utf-8"); @@ -11,13 +11,13 @@ const publicCert = fs.readFileSync("./test/static/client_public.pem", "utf-8"); const publicCertDer = fs.readFileSync("./test/static/client_public.der"); const selectNs = (expression: string, node: Node, ns?: Record) => xpath.useNamespaces({ - ds: "http://www.w3.org/2000/09/xmldsig#", + ds: XMLDSIG_URIS.NAMESPACES.ds, xades: "http://uri.etsi.org/01903/v1.3.2#", ...ns, })(expression, node, false); const select1Ns = (expression: string, node: Node, ns?: Record) => xpath.useNamespaces({ - ds: "http://www.w3.org/2000/09/xmldsig#", + ds: XMLDSIG_URIS.NAMESPACES.ds, xades: "http://uri.etsi.org/01903/v1.3.2#", ...ns, })(expression, node, true); @@ -44,8 +44,8 @@ describe("ds:Object support in XML signatures", function () { 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", + canonicalizationAlgorithm: XMLDSIG_URIS.CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N, + signatureAlgorithm: XMLDSIG_URIS.SIGNATURE_ALGORITHMS.RSA_SHA1, objects: [ { content: "Test data in Object element", @@ -75,8 +75,8 @@ describe("ds:Object support in XML signatures", function () { 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#"], + digestAlgorithm: XMLDSIG_URIS.HASH_ALGORITHMS.SHA1, + transforms: [XMLDSIG_URIS.CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N], }); sig.computeSignature(xml); @@ -124,8 +124,8 @@ describe("ds:Object support in XML signatures", function () { 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", + canonicalizationAlgorithm: XMLDSIG_URIS.CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N, + signatureAlgorithm: XMLDSIG_URIS.SIGNATURE_ALGORITHMS.RSA_SHA1, objects: [ { content: "Test data", @@ -139,8 +139,8 @@ describe("ds:Object support in XML signatures", function () { sig.addReference({ xpath: "/*", - digestAlgorithm: "http://www.w3.org/2000/09/xmldsig#sha1", - transforms: ["http://www.w3.org/2001/10/xml-exc-c14n#"], + digestAlgorithm: XMLDSIG_URIS.HASH_ALGORITHMS.SHA1, + transforms: [XMLDSIG_URIS.CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N], }); // When we add a prefix to the signature, there is no default namespace @@ -159,15 +159,15 @@ describe("ds:Object support in XML signatures", function () { // Test with undefined objects const sigWithNull = new SignedXml({ privateKey, - canonicalizationAlgorithm: "http://www.w3.org/2001/10/xml-exc-c14n#", - signatureAlgorithm: "http://www.w3.org/2000/09/xmldsig#rsa-sha1", + canonicalizationAlgorithm: XMLDSIG_URIS.CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N, + signatureAlgorithm: XMLDSIG_URIS.SIGNATURE_ALGORITHMS.RSA_SHA1, objects: undefined, }); sigWithNull.addReference({ xpath: "/*", - digestAlgorithm: "http://www.w3.org/2000/09/xmldsig#sha1", - transforms: ["http://www.w3.org/2001/10/xml-exc-c14n#"], + digestAlgorithm: XMLDSIG_URIS.HASH_ALGORITHMS.SHA1, + transforms: [XMLDSIG_URIS.CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N], }); sigWithNull.computeSignature(xml); @@ -182,15 +182,15 @@ describe("ds:Object support in XML signatures", function () { // Test with empty array objects const sigWithEmpty = new SignedXml({ privateKey, - canonicalizationAlgorithm: "http://www.w3.org/2001/10/xml-exc-c14n#", - signatureAlgorithm: "http://www.w3.org/2000/09/xmldsig#rsa-sha1", + canonicalizationAlgorithm: XMLDSIG_URIS.CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N, + signatureAlgorithm: XMLDSIG_URIS.SIGNATURE_ALGORITHMS.RSA_SHA1, objects: [], }); sigWithEmpty.addReference({ xpath: "/*", - digestAlgorithm: "http://www.w3.org/2000/09/xmldsig#sha1", - transforms: ["http://www.w3.org/2001/10/xml-exc-c14n#"], + digestAlgorithm: XMLDSIG_URIS.HASH_ALGORITHMS.SHA1, + transforms: [XMLDSIG_URIS.CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N], }); sigWithEmpty.computeSignature(xml); @@ -208,8 +208,8 @@ describe("ds:Object support in XML signatures", function () { const sig = new SignedXml({ privateKey: privateKey, - signatureAlgorithm: "http://www.w3.org/2000/09/xmldsig#rsa-sha1", - canonicalizationAlgorithm: "http://www.w3.org/2001/10/xml-exc-c14n#", + signatureAlgorithm: XMLDSIG_URIS.SIGNATURE_ALGORITHMS.RSA_SHA1, + canonicalizationAlgorithm: XMLDSIG_URIS.CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N, objects: [ { content: @@ -224,8 +224,8 @@ describe("ds:Object support in XML signatures", function () { sig.addReference({ xpath: "//*[local-name(.)='Object' and @Id='object1']", inclusiveNamespacesPrefixList: ["ns1", "ns2"], - digestAlgorithm: "http://www.w3.org/2000/09/xmldsig#sha1", - transforms: ["http://www.w3.org/2001/10/xml-exc-c14n#"], + digestAlgorithm: XMLDSIG_URIS.HASH_ALGORITHMS.SHA1, + transforms: [XMLDSIG_URIS.CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N], }); sig.computeSignature(xml); @@ -246,12 +246,12 @@ describe("ds:Object support in XML signatures", function () { const transformEl = select1Ns("ds:Transforms/ds:Transform", referenceEl); isDomNode.assertIsElementNode(transformEl); expect(transformEl.getAttribute("Algorithm")).to.equal( - "http://www.w3.org/2001/10/xml-exc-c14n#", + XMLDSIG_URIS.CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N, ); // Verify that the InclusiveNamespacesPrefixList is set correctly const inclusiveNamespacesEl = select1Ns("ec:InclusiveNamespaces", transformEl, { - ec: "http://www.w3.org/2001/10/xml-exc-c14n#", + ec: XMLDSIG_URIS.CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N, }); isDomNode.assertIsElementNode(inclusiveNamespacesEl); expect(inclusiveNamespacesEl.getAttribute("PrefixList")).to.equal("ns1 ns2"); @@ -259,9 +259,7 @@ describe("ds:Object support in XML signatures", function () { // Verify that the Reference contains the correct DigestMethod const digestMethodEl = select1Ns("ds:DigestMethod", referenceEl); isDomNode.assertIsElementNode(digestMethodEl); - expect(digestMethodEl.getAttribute("Algorithm")).to.equal( - "http://www.w3.org/2000/09/xmldsig#sha1", - ); + expect(digestMethodEl.getAttribute("Algorithm")).to.equal(XMLDSIG_URIS.HASH_ALGORITHMS.SHA1); // Verify that the Reference contains a non-empty DigestValue const digestValueEl = select1Ns("ds:DigestValue", referenceEl); @@ -276,8 +274,8 @@ describe("Valid signatures with ds:Object elements", function () { 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", + canonicalizationAlgorithm: XMLDSIG_URIS.CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N, + signatureAlgorithm: XMLDSIG_URIS.SIGNATURE_ALGORITHMS.RSA_SHA1, objects: [ { content: "Test data in Object element", @@ -291,10 +289,10 @@ describe("Valid signatures with ds:Object elements", function () { sig.addReference({ xpath: "/*", - digestAlgorithm: "http://www.w3.org/2000/09/xmldsig#sha1", + digestAlgorithm: XMLDSIG_URIS.HASH_ALGORITHMS.SHA1, transforms: [ - "http://www.w3.org/2000/09/xmldsig#enveloped-signature", - "http://www.w3.org/2001/10/xml-exc-c14n#", + XMLDSIG_URIS.TRANSFORM_ALGORITHMS.ENVELOPED_SIGNATURE, + XMLDSIG_URIS.CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N, ], }); @@ -312,8 +310,8 @@ describe("Valid signatures with ds:Object elements", function () { 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", + canonicalizationAlgorithm: XMLDSIG_URIS.CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N, + signatureAlgorithm: XMLDSIG_URIS.SIGNATURE_ALGORITHMS.RSA_SHA1, inclusiveNamespacesPrefixList: ["ns1", "ns2"], objects: [ { @@ -329,19 +327,19 @@ describe("Valid signatures with ds:Object elements", function () { sig.addReference({ xpath: "/*", - digestAlgorithm: "http://www.w3.org/2000/09/xmldsig#sha1", + digestAlgorithm: XMLDSIG_URIS.HASH_ALGORITHMS.SHA1, transforms: [ - "http://www.w3.org/2000/09/xmldsig#enveloped-signature", - "http://www.w3.org/2001/10/xml-exc-c14n#", + XMLDSIG_URIS.TRANSFORM_ALGORITHMS.ENVELOPED_SIGNATURE, + XMLDSIG_URIS.CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N, ], }); sig.addReference({ xpath: "//*[local-name(.)='Object' and @Id='object1']", - digestAlgorithm: "http://www.w3.org/2000/09/xmldsig#sha1", + digestAlgorithm: XMLDSIG_URIS.HASH_ALGORITHMS.SHA1, transforms: [ - "http://www.w3.org/2000/09/xmldsig#enveloped-signature", - "http://www.w3.org/2001/10/xml-exc-c14n#", + XMLDSIG_URIS.TRANSFORM_ALGORITHMS.ENVELOPED_SIGNATURE, + XMLDSIG_URIS.CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N, ], inclusiveNamespacesPrefixList: ["ns1", "ns3"], }); @@ -371,8 +369,8 @@ describe("Valid signatures with ds:Object elements", 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", + canonicalizationAlgorithm: XMLDSIG_URIS.CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N, + signatureAlgorithm: XMLDSIG_URIS.SIGNATURE_ALGORITHMS.RSA_SHA1, objects: [ { content: "Test data in Object element", @@ -382,10 +380,10 @@ describe("Valid signatures with ds:Object elements", function () { sig.addReference({ xpath: "//*[local-name(.)='Data']", - digestAlgorithm: "http://www.w3.org/2000/09/xmldsig#sha1", + digestAlgorithm: XMLDSIG_URIS.HASH_ALGORITHMS.SHA1, transforms: [ - "http://www.w3.org/2000/09/xmldsig#enveloped-signature", - "http://www.w3.org/2001/10/xml-exc-c14n#", + XMLDSIG_URIS.TRANSFORM_ALGORITHMS.ENVELOPED_SIGNATURE, + XMLDSIG_URIS.CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N, ], }); @@ -415,8 +413,8 @@ describe("Should successfuly sign references to ds:KeyInfo elements", 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", + canonicalizationAlgorithm: XMLDSIG_URIS.CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N, + signatureAlgorithm: XMLDSIG_URIS.SIGNATURE_ALGORITHMS.RSA_SHA1, keyInfoAttributes: { Id: "key-info-1", }, @@ -425,10 +423,10 @@ describe("Should successfuly sign references to ds:KeyInfo elements", function ( sig.addReference({ xpath: "//*[local-name(.)='KeyInfo']", - digestAlgorithm: "http://www.w3.org/2000/09/xmldsig#sha1", + digestAlgorithm: XMLDSIG_URIS.HASH_ALGORITHMS.SHA1, transforms: [ - "http://www.w3.org/2000/09/xmldsig#enveloped-signature", - "http://www.w3.org/2001/10/xml-exc-c14n#", + XMLDSIG_URIS.TRANSFORM_ALGORITHMS.ENVELOPED_SIGNATURE, + XMLDSIG_URIS.CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N, ], }); @@ -453,17 +451,17 @@ describe("Should successfuly sign references to ds:KeyInfo elements", 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", + canonicalizationAlgorithm: XMLDSIG_URIS.CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N, + signatureAlgorithm: XMLDSIG_URIS.SIGNATURE_ALGORITHMS.RSA_SHA1, getKeyInfoContent: () => "", }); sig.addReference({ xpath: "//*[local-name(.)='KeyInfo']", - digestAlgorithm: "http://www.w3.org/2000/09/xmldsig#sha1", + digestAlgorithm: XMLDSIG_URIS.HASH_ALGORITHMS.SHA1, transforms: [ - "http://www.w3.org/2000/09/xmldsig#enveloped-signature", - "http://www.w3.org/2001/10/xml-exc-c14n#", + XMLDSIG_URIS.TRANSFORM_ALGORITHMS.ENVELOPED_SIGNATURE, + XMLDSIG_URIS.CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N, ], }); @@ -503,8 +501,8 @@ describe("XAdES Object support in XML signatures", function () { const sig = new SignedXml({ publicCert, privateKey, - canonicalizationAlgorithm: "http://www.w3.org/2001/10/xml-exc-c14n#", - signatureAlgorithm: "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256", + canonicalizationAlgorithm: XMLDSIG_URIS.CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N, + signatureAlgorithm: XMLDSIG_URIS.SIGNATURE_ALGORITHMS.RSA_SHA256, objects: [ { content: @@ -526,18 +524,18 @@ describe("XAdES Object support in XML signatures", function () { sig.addReference({ xpath: `/*`, isEmptyUri: true, - digestAlgorithm: "http://www.w3.org/2001/04/xmlenc#sha256", + digestAlgorithm: XMLDSIG_URIS.HASH_ALGORITHMS.SHA256, transforms: [ - "http://www.w3.org/2000/09/xmldsig#enveloped-signature", - "http://www.w3.org/2001/10/xml-exc-c14n#", + XMLDSIG_URIS.TRANSFORM_ALGORITHMS.ENVELOPED_SIGNATURE, + XMLDSIG_URIS.CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N, ], }); sig.addReference({ xpath: `//*[@Id='${signedPropertiesId}']`, type: "http://uri.etsi.org/01903#SignedProperties", - digestAlgorithm: "http://www.w3.org/2001/04/xmlenc#sha256", - transforms: ["http://www.w3.org/2001/10/xml-exc-c14n#"], + digestAlgorithm: XMLDSIG_URIS.HASH_ALGORITHMS.SHA256, + transforms: [XMLDSIG_URIS.CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N], }); sig.computeSignature(xml, { @@ -581,14 +579,14 @@ describe("Signature self-reference prevention", function () { 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", + canonicalizationAlgorithm: XMLDSIG_URIS.CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N, + signatureAlgorithm: XMLDSIG_URIS.SIGNATURE_ALGORITHMS.RSA_SHA1, }); sig.addReference({ xpath: ".//*[local-name(.)='Signature']", - digestAlgorithm: "http://www.w3.org/2000/09/xmldsig#sha1", - transforms: ["http://www.w3.org/2001/10/xml-exc-c14n#"], + digestAlgorithm: XMLDSIG_URIS.HASH_ALGORITHMS.SHA1, + transforms: [XMLDSIG_URIS.CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N], }); expect(() => { @@ -601,14 +599,14 @@ describe("Signature self-reference prevention", function () { 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", + canonicalizationAlgorithm: XMLDSIG_URIS.CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N, + signatureAlgorithm: XMLDSIG_URIS.SIGNATURE_ALGORITHMS.RSA_SHA1, }); sig.addReference({ xpath: ".//*[local-name(.)='SignedInfo']", - digestAlgorithm: "http://www.w3.org/2000/09/xmldsig#sha1", - transforms: ["http://www.w3.org/2001/10/xml-exc-c14n#"], + digestAlgorithm: XMLDSIG_URIS.HASH_ALGORITHMS.SHA1, + transforms: [XMLDSIG_URIS.CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N], }); expect(() => { @@ -621,20 +619,20 @@ describe("Signature self-reference prevention", function () { 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", + canonicalizationAlgorithm: XMLDSIG_URIS.CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N, + signatureAlgorithm: XMLDSIG_URIS.SIGNATURE_ALGORITHMS.RSA_SHA1, }); sig.addReference({ xpath: "/*", - digestAlgorithm: "http://www.w3.org/2000/09/xmldsig#sha1", - transforms: ["http://www.w3.org/2001/10/xml-exc-c14n#"], + digestAlgorithm: XMLDSIG_URIS.HASH_ALGORITHMS.SHA1, + transforms: [XMLDSIG_URIS.CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N], }); sig.addReference({ xpath: ".//*[local-name(.)='Reference']/*", - digestAlgorithm: "http://www.w3.org/2000/09/xmldsig#sha1", - transforms: ["http://www.w3.org/2001/10/xml-exc-c14n#"], + digestAlgorithm: XMLDSIG_URIS.HASH_ALGORITHMS.SHA1, + transforms: [XMLDSIG_URIS.CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N], }); expect(() => { diff --git a/test/signature-unit-tests.spec.ts b/test/signature-unit-tests.spec.ts index c0dcf136..5f21b2fb 100644 --- a/test/signature-unit-tests.spec.ts +++ b/test/signature-unit-tests.spec.ts @@ -1,16 +1,19 @@ import * as xpath from "xpath"; import * as xmldom from "@xmldom/xmldom"; -import { SignedXml, createOptionalCallbackFunction } from "../src/index"; +import { SignedXml, createOptionalCallbackFunction, XMLDSIG_URIS } from "../src"; import * as fs from "fs"; import * as crypto from "crypto"; import { expect } from "chai"; import * as isDomNode from "@xmldom/is-dom-node"; +const { SIGNATURE_ALGORITHMS, HASH_ALGORITHMS, CANONICALIZATION_ALGORITHMS, NAMESPACES } = + XMLDSIG_URIS; + const signatureAlgorithms = [ - "http://www.w3.org/2000/09/xmldsig#rsa-sha1", - "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256", - "http://www.w3.org/2007/05/xmldsig-more#sha256-rsa-MGF1", - "http://www.w3.org/2001/04/xmldsig-more#rsa-sha512", + SIGNATURE_ALGORITHMS.RSA_SHA1, + SIGNATURE_ALGORITHMS.RSA_SHA256, + SIGNATURE_ALGORITHMS.RSA_SHA256_MGF1, + SIGNATURE_ALGORITHMS.RSA_SHA512, ]; describe("Signature unit tests", function () { @@ -23,11 +26,11 @@ describe("Signature unit tests", function () { 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#"], + digestAlgorithm: HASH_ALGORITHMS.SHA1, + transforms: [CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N], }); - sig.canonicalizationAlgorithm = "http://www.w3.org/2001/10/xml-exc-c14n#"; + sig.canonicalizationAlgorithm = CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N; sig.signatureAlgorithm = signatureAlgorithm; sig.computeSignature(xml); return sig.getSignedXml(); @@ -36,7 +39,7 @@ describe("Signature unit tests", function () { function loadSignature(xml: string): SignedXml { const doc = new xmldom.DOMParser().parseFromString(xml); const node = xpath.select1( - "//*[local-name(.)='Signature' and namespace-uri(.)='http://www.w3.org/2000/09/xmldsig#']", + `//*[local-name(.)='Signature' and namespace-uri(.)='${XMLDSIG_URIS.NAMESPACES.ds}']`, doc, ); isDomNode.assertIsNodeLike(node); @@ -92,22 +95,22 @@ describe("Signature unit tests", function () { 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#"], + digestAlgorithm: HASH_ALGORITHMS.SHA1, + transforms: [CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N], }); sig.addReference({ xpath: "//*[local-name(.)='y']", - digestAlgorithm: "http://www.w3.org/2000/09/xmldsig#sha1", - transforms: ["http://www.w3.org/2001/10/xml-exc-c14n#"], + digestAlgorithm: HASH_ALGORITHMS.SHA1, + transforms: [CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N], }); sig.addReference({ xpath: "//*[local-name(.)='w']", - digestAlgorithm: "http://www.w3.org/2000/09/xmldsig#sha1", - transforms: ["http://www.w3.org/2001/10/xml-exc-c14n#"], + digestAlgorithm: HASH_ALGORITHMS.SHA1, + transforms: [CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N], }); - sig.canonicalizationAlgorithm = "http://www.w3.org/2001/10/xml-exc-c14n#"; - sig.signatureAlgorithm = "http://www.w3.org/2000/09/xmldsig#rsa-sha1"; + sig.canonicalizationAlgorithm = CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N; + sig.signatureAlgorithm = SIGNATURE_ALGORITHMS.RSA_SHA1; sig.computeSignature(xml); const signedXml = sig.getOriginalXmlWithIds(); const doc = new xmldom.DOMParser().parseFromString(signedXml); @@ -140,15 +143,15 @@ describe("Signature unit tests", function () { sig.addReference({ xpath: "//*[@wsu:Id]", - digestAlgorithm: "http://www.w3.org/2000/09/xmldsig#sha1", - transforms: ["http://www.w3.org/2001/10/xml-exc-c14n#"], + digestAlgorithm: HASH_ALGORITHMS.SHA1, + transforms: [CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N], }); - sig.canonicalizationAlgorithm = "http://www.w3.org/2001/10/xml-exc-c14n#"; - sig.signatureAlgorithm = "http://www.w3.org/2000/09/xmldsig#rsa-sha1"; + sig.canonicalizationAlgorithm = CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N; + sig.signatureAlgorithm = SIGNATURE_ALGORITHMS.RSA_SHA1; sig.computeSignature(xml, { existingPrefixes: { - wsu: "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd", + wsu: NAMESPACES.wsu, }, }); @@ -166,11 +169,11 @@ describe("Signature unit tests", function () { sig.privateKey = fs.readFileSync("./test/static/client.pem"); 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#"], + digestAlgorithm: HASH_ALGORITHMS.SHA1, + transforms: [CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N], }); - sig.canonicalizationAlgorithm = "http://www.w3.org/2001/10/xml-exc-c14n#"; - sig.signatureAlgorithm = "http://www.w3.org/2000/09/xmldsig#rsa-sha1"; + sig.canonicalizationAlgorithm = CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N; + sig.signatureAlgorithm = SIGNATURE_ALGORITHMS.RSA_SHA1; sig.computeSignature(xml); const signedXml = sig.getOriginalXmlWithIds(); const doc = new xmldom.DOMParser().parseFromString(signedXml); @@ -201,12 +204,12 @@ describe("Signature unit tests", function () { sig.addReference({ xpath: "//*[local-name(.)='name']", - digestAlgorithm: "http://www.w3.org/2000/09/xmldsig#sha1", - transforms: ["http://www.w3.org/2001/10/xml-exc-c14n#"], + digestAlgorithm: HASH_ALGORITHMS.SHA1, + transforms: [CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N], }); - sig.canonicalizationAlgorithm = "http://www.w3.org/2001/10/xml-exc-c14n#"; - sig.signatureAlgorithm = "http://www.w3.org/2000/09/xmldsig#rsa-sha1"; + sig.canonicalizationAlgorithm = CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N; + sig.signatureAlgorithm = SIGNATURE_ALGORITHMS.RSA_SHA1; sig.computeSignature(xml, { attrs: attrs, }); @@ -228,7 +231,7 @@ describe("Signature unit tests", function () { expect( signatureNode.getAttribute("xmlns"), 'xmlns attribute is not equal to the expected value: "http://www.w3.org/2000/09/xmldsig#"', - ).to.equal("http://www.w3.org/2000/09/xmldsig#"); + ).to.equal(XMLDSIG_URIS.NAMESPACES.ds); }); it("signer appends signature to the root node by default", function () { @@ -238,11 +241,11 @@ describe("Signature unit tests", function () { sig.privateKey = fs.readFileSync("./test/static/client.pem"); sig.addReference({ xpath: "//*[local-name(.)='name']", - digestAlgorithm: "http://www.w3.org/2000/09/xmldsig#sha1", - transforms: ["http://www.w3.org/2001/10/xml-exc-c14n#"], + digestAlgorithm: HASH_ALGORITHMS.SHA1, + transforms: [CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N], }); - sig.canonicalizationAlgorithm = "http://www.w3.org/2001/10/xml-exc-c14n#"; - sig.signatureAlgorithm = "http://www.w3.org/2000/09/xmldsig#rsa-sha1"; + sig.canonicalizationAlgorithm = CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N; + sig.signatureAlgorithm = SIGNATURE_ALGORITHMS.RSA_SHA1; sig.computeSignature(xml); const doc = new xmldom.DOMParser().parseFromString(sig.getSignedXml()); @@ -262,12 +265,12 @@ describe("Signature unit tests", function () { sig.privateKey = fs.readFileSync("./test/static/client.pem"); sig.addReference({ xpath: "//*[local-name(.)='repository']", - digestAlgorithm: "http://www.w3.org/2000/09/xmldsig#sha1", - transforms: ["http://www.w3.org/2001/10/xml-exc-c14n#"], + digestAlgorithm: HASH_ALGORITHMS.SHA1, + transforms: [CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N], }); - sig.canonicalizationAlgorithm = "http://www.w3.org/2001/10/xml-exc-c14n#"; - sig.signatureAlgorithm = "http://www.w3.org/2000/09/xmldsig#rsa-sha1"; + sig.canonicalizationAlgorithm = CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N; + sig.signatureAlgorithm = SIGNATURE_ALGORITHMS.RSA_SHA1; sig.computeSignature(xml, { location: { reference: "/root/name", @@ -294,12 +297,12 @@ describe("Signature unit tests", function () { sig.privateKey = fs.readFileSync("./test/static/client.pem"); sig.addReference({ xpath: "//*[local-name(.)='repository']", - digestAlgorithm: "http://www.w3.org/2000/09/xmldsig#sha1", - transforms: ["http://www.w3.org/2001/10/xml-exc-c14n#"], + digestAlgorithm: HASH_ALGORITHMS.SHA1, + transforms: [CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N], }); - sig.canonicalizationAlgorithm = "http://www.w3.org/2001/10/xml-exc-c14n#"; - sig.signatureAlgorithm = "http://www.w3.org/2000/09/xmldsig#rsa-sha1"; + sig.canonicalizationAlgorithm = CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N; + sig.signatureAlgorithm = SIGNATURE_ALGORITHMS.RSA_SHA1; sig.computeSignature(xml, { location: { reference: "/root/name", @@ -325,12 +328,12 @@ describe("Signature unit tests", function () { sig.privateKey = fs.readFileSync("./test/static/client.pem"); sig.addReference({ xpath: "//*[local-name(.)='repository']", - digestAlgorithm: "http://www.w3.org/2000/09/xmldsig#sha1", - transforms: ["http://www.w3.org/2001/10/xml-exc-c14n#"], + digestAlgorithm: HASH_ALGORITHMS.SHA1, + transforms: [CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N], }); - sig.canonicalizationAlgorithm = "http://www.w3.org/2001/10/xml-exc-c14n#"; - sig.signatureAlgorithm = "http://www.w3.org/2000/09/xmldsig#rsa-sha1"; + sig.canonicalizationAlgorithm = CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N; + sig.signatureAlgorithm = SIGNATURE_ALGORITHMS.RSA_SHA1; sig.computeSignature(xml, { location: { reference: "/root/name", @@ -357,12 +360,12 @@ describe("Signature unit tests", function () { sig.privateKey = fs.readFileSync("./test/static/client.pem"); sig.addReference({ xpath: "//*[local-name(.)='repository']", - digestAlgorithm: "http://www.w3.org/2000/09/xmldsig#sha1", - transforms: ["http://www.w3.org/2001/10/xml-exc-c14n#"], + digestAlgorithm: HASH_ALGORITHMS.SHA1, + transforms: [CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N], }); - sig.canonicalizationAlgorithm = "http://www.w3.org/2001/10/xml-exc-c14n#"; - sig.signatureAlgorithm = "http://www.w3.org/2000/09/xmldsig#rsa-sha1"; + sig.canonicalizationAlgorithm = CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N; + sig.signatureAlgorithm = SIGNATURE_ALGORITHMS.RSA_SHA1; sig.computeSignature(xml, { location: { reference: "/root/name", @@ -713,22 +716,22 @@ describe("Signature unit tests", function () { 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#"], + digestAlgorithm: HASH_ALGORITHMS.SHA1, + transforms: [CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N], }); sig.addReference({ xpath: "//*[local-name(.)='y']", - digestAlgorithm: "http://www.w3.org/2000/09/xmldsig#sha1", - transforms: ["http://www.w3.org/2001/10/xml-exc-c14n#"], + digestAlgorithm: HASH_ALGORITHMS.SHA1, + transforms: [CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N], }); sig.addReference({ xpath: "//*[local-name(.)='w']", - digestAlgorithm: "http://www.w3.org/2000/09/xmldsig#sha1", - transforms: ["http://www.w3.org/2001/10/xml-exc-c14n#"], + digestAlgorithm: HASH_ALGORITHMS.SHA1, + transforms: [CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N], }); - sig.canonicalizationAlgorithm = "http://www.w3.org/2001/10/xml-exc-c14n#"; - sig.signatureAlgorithm = "http://www.w3.org/2000/09/xmldsig#rsa-sha1"; + sig.canonicalizationAlgorithm = CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N; + sig.signatureAlgorithm = SIGNATURE_ALGORITHMS.RSA_SHA1; sig.computeSignature(xml); const signedXml = sig.getSignedXml(); const expected = @@ -781,7 +784,7 @@ describe("Signature unit tests", function () { ); getAlgorithmName = function () { - return "http://www.w3.org/2000/09/xmldsig#rsa-sha1"; + return SIGNATURE_ALGORITHMS.RSA_SHA1; }; } @@ -794,21 +797,21 @@ describe("Signature unit tests", function () { 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#"], + digestAlgorithm: HASH_ALGORITHMS.SHA1, + transforms: [CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N], }); sig.addReference({ xpath: "//*[local-name(.)='y']", - digestAlgorithm: "http://www.w3.org/2000/09/xmldsig#sha1", - transforms: ["http://www.w3.org/2001/10/xml-exc-c14n#"], + digestAlgorithm: HASH_ALGORITHMS.SHA1, + transforms: [CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N], }); sig.addReference({ xpath: "//*[local-name(.)='w']", - digestAlgorithm: "http://www.w3.org/2000/09/xmldsig#sha1", - transforms: ["http://www.w3.org/2001/10/xml-exc-c14n#"], + digestAlgorithm: HASH_ALGORITHMS.SHA1, + transforms: [CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N], }); - sig.canonicalizationAlgorithm = "http://www.w3.org/2001/10/xml-exc-c14n#"; + sig.canonicalizationAlgorithm = CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N; sig.computeSignature(xml, function () { const signedXml = sig.getSignedXml(); const expected = @@ -852,7 +855,7 @@ describe("Signature unit tests", function () { const xml = fs.readFileSync(file, "utf8"); const doc = new xmldom.DOMParser().parseFromString(xml); const signature = xpath.select1( - "/*//*[local-name(.)='Signature' and namespace-uri(.)='http://www.w3.org/2000/09/xmldsig#']", + `/*//*[local-name(.)='Signature' and namespace-uri(.)='${XMLDSIG_URIS.NAMESPACES.ds}']`, doc, ); isDomNode.assertIsElementNode(signature); @@ -860,11 +863,11 @@ describe("Signature unit tests", function () { sig.loadSignature(toString ? signature.toString() : signature); expect(sig.canonicalizationAlgorithm, "wrong canonicalization method").to.equal( - "http://www.w3.org/2001/10/xml-exc-c14n#", + CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N, ); expect(sig.signatureAlgorithm, "wrong signature method").to.equal( - "http://www.w3.org/2000/09/xmldsig#rsa-sha1", + SIGNATURE_ALGORITHMS.RSA_SHA1, ); sig.getCertFromKeyInfo = (keyInfo) => { @@ -909,9 +912,9 @@ describe("Signature unit tests", function () { `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.transforms[0]).to.equal(CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N); expect(ref.digestValue).to.equal(digests[i]); - expect(ref.digestAlgorithm).to.equal("http://www.w3.org/2000/09/xmldsig#sha1"); + expect(ref.digestAlgorithm).to.equal(HASH_ALGORITHMS.SHA1); } } @@ -932,7 +935,7 @@ describe("Signature unit tests", function () { function loadSignature(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#']", + `//*[local-name(.)='Signature' and namespace-uri(.)='${XMLDSIG_URIS.NAMESPACES.ds}']`, doc, ); isDomNode.assertIsNodeLike(node); @@ -1056,16 +1059,16 @@ describe("Signature unit tests", function () { sig.addReference({ xpath: "//*[local-name(.)='root']", - transforms: ["http://www.w3.org/2000/09/xmldsig#enveloped-signature"], - digestAlgorithm: "http://www.w3.org/2000/09/xmldsig#sha1", + transforms: [XMLDSIG_URIS.TRANSFORM_ALGORITHMS.ENVELOPED_SIGNATURE], + digestAlgorithm: HASH_ALGORITHMS.SHA1, uri: "", digestValue: "", inclusiveNamespacesPrefixList: [], isEmptyUri: true, }); - sig.canonicalizationAlgorithm = "http://www.w3.org/2001/10/xml-exc-c14n#"; - sig.signatureAlgorithm = "http://www.w3.org/2000/09/xmldsig#rsa-sha1"; + sig.canonicalizationAlgorithm = CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N; + sig.signatureAlgorithm = SIGNATURE_ALGORITHMS.RSA_SHA1; sig.computeSignature(xml); const signedXml = sig.getSignedXml(); const doc = new xmldom.DOMParser().parseFromString(signedXml); @@ -1081,8 +1084,8 @@ describe("Signature unit tests", function () { sig.privateKey = fs.readFileSync("./test/static/client.pem"); sig.addReference({ xpath: "//*[local-name(.)='repository']", - digestAlgorithm: "http://www.w3.org/2000/09/xmldsig#sha1", - transforms: ["http://www.w3.org/2001/10/xml-exc-c14n#"], + digestAlgorithm: HASH_ALGORITHMS.SHA1, + transforms: [CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N], }); try { @@ -1123,8 +1126,8 @@ describe("Signature unit tests", function () { const assertionId = "_81d5fba5c807be9e9cf60c58566349b1"; sig.getKeyInfoContent = getKeyInfoContentWithAssertionId.bind(this, { assertionId }); sig.privateKey = fs.readFileSync("./test/static/client.pem"); - sig.canonicalizationAlgorithm = "http://www.w3.org/2001/10/xml-exc-c14n#"; - sig.signatureAlgorithm = "http://www.w3.org/2000/09/xmldsig#rsa-sha1"; + sig.canonicalizationAlgorithm = CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N; + sig.signatureAlgorithm = SIGNATURE_ALGORITHMS.RSA_SHA1; sig.computeSignature(xml, { prefix: "ds", location: { @@ -1132,8 +1135,8 @@ describe("Signature unit tests", function () { action: "after", }, existingPrefixes: { - wsse: "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd", - wsu: "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd", + wsse: NAMESPACES.wsse, + wsu: NAMESPACES.wsu, }, }); const result = sig.getSignedXml(); @@ -1149,15 +1152,15 @@ describe("Signature unit tests", function () { sig.addReference({ xpath: "//*[local-name(.)='root']", - transforms: ["http://www.w3.org/2000/09/xmldsig#enveloped-signature"], - digestAlgorithm: "http://www.w3.org/2000/09/xmldsig#sha1", + transforms: [XMLDSIG_URIS.TRANSFORM_ALGORITHMS.ENVELOPED_SIGNATURE], + digestAlgorithm: HASH_ALGORITHMS.SHA1, uri: "", digestValue: "", inclusiveNamespacesPrefixList: ["prefix1", "prefix2"], }); - sig.canonicalizationAlgorithm = "http://www.w3.org/2001/10/xml-exc-c14n#"; - sig.signatureAlgorithm = "http://www.w3.org/2000/09/xmldsig#rsa-sha1"; + sig.canonicalizationAlgorithm = CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N; + sig.signatureAlgorithm = SIGNATURE_ALGORITHMS.RSA_SHA1; sig.computeSignature(xml); const signedXml = sig.getSignedXml(); @@ -1186,15 +1189,15 @@ describe("Signature unit tests", function () { sig.addReference({ xpath: "//*[local-name(.)='root']", - transforms: ["http://www.w3.org/2000/09/xmldsig#enveloped-signature"], - digestAlgorithm: "http://www.w3.org/2000/09/xmldsig#sha1", + transforms: [XMLDSIG_URIS.TRANSFORM_ALGORITHMS.ENVELOPED_SIGNATURE], + digestAlgorithm: HASH_ALGORITHMS.SHA1, uri: "", digestValue: "", inclusiveNamespacesPrefixList: [], }); - sig.canonicalizationAlgorithm = "http://www.w3.org/2001/10/xml-exc-c14n#"; - sig.signatureAlgorithm = "http://www.w3.org/2000/09/xmldsig#rsa-sha1"; + sig.canonicalizationAlgorithm = CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N; + sig.signatureAlgorithm = SIGNATURE_ALGORITHMS.RSA_SHA1; sig.computeSignature(xml); const signedXml = sig.getSignedXml(); @@ -1214,12 +1217,12 @@ describe("Signature unit tests", function () { sig.addReference({ xpath: "//*[local-name(.)='root']", - transforms: ["http://www.w3.org/2000/09/xmldsig#enveloped-signature"], - digestAlgorithm: "http://www.w3.org/2000/09/xmldsig#sha1", + transforms: [XMLDSIG_URIS.TRANSFORM_ALGORITHMS.ENVELOPED_SIGNATURE], + digestAlgorithm: HASH_ALGORITHMS.SHA1, }); - sig.canonicalizationAlgorithm = "http://www.w3.org/2001/10/xml-exc-c14n#"; - sig.signatureAlgorithm = "http://www.w3.org/2000/09/xmldsig#rsa-sha1"; + sig.canonicalizationAlgorithm = CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N; + sig.signatureAlgorithm = SIGNATURE_ALGORITHMS.RSA_SHA1; sig.computeSignature(xml); const signedXml = sig.getSignedXml(); @@ -1253,12 +1256,12 @@ describe("Signature unit tests", function () { sig.addReference({ xpath: "//*[local-name(.)='root']", - transforms: ["http://www.w3.org/2000/09/xmldsig#enveloped-signature"], - digestAlgorithm: "http://www.w3.org/2000/09/xmldsig#sha1", + transforms: [XMLDSIG_URIS.TRANSFORM_ALGORITHMS.ENVELOPED_SIGNATURE], + digestAlgorithm: HASH_ALGORITHMS.SHA1, }); - sig.canonicalizationAlgorithm = "http://www.w3.org/2001/10/xml-exc-c14n#"; - sig.signatureAlgorithm = "http://www.w3.org/2000/09/xmldsig#rsa-sha1"; + sig.canonicalizationAlgorithm = CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N; + sig.signatureAlgorithm = SIGNATURE_ALGORITHMS.RSA_SHA1; sig.computeSignature(xml); const signedXml = sig.getSignedXml(); @@ -1284,8 +1287,8 @@ describe("Signature unit tests", function () { }; sig.getKeyInfoContent = () => ""; - sig.canonicalizationAlgorithm = "http://www.w3.org/2001/10/xml-exc-c14n#"; - sig.signatureAlgorithm = "http://www.w3.org/2000/09/xmldsig#rsa-sha1"; + sig.canonicalizationAlgorithm = CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N; + sig.signatureAlgorithm = SIGNATURE_ALGORITHMS.RSA_SHA1; sig.computeSignature(xml); const signedXml = sig.getSignedXml(); @@ -1316,8 +1319,8 @@ describe("Signature unit tests", function () { const pemBuffer = fs.readFileSync("./test/static/client_bundle.pem"); sig.privateKey = pemBuffer; sig.publicCert = pemBuffer; - sig.canonicalizationAlgorithm = "http://www.w3.org/2001/10/xml-exc-c14n#"; - sig.signatureAlgorithm = "http://www.w3.org/2000/09/xmldsig#rsa-sha1"; + sig.canonicalizationAlgorithm = CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N; + sig.signatureAlgorithm = SIGNATURE_ALGORITHMS.RSA_SHA1; sig.computeSignature(xml); const signedXml = sig.getSignedXml(); @@ -1355,14 +1358,14 @@ describe("Signature unit tests", function () { 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#"], + digestAlgorithm: HASH_ALGORITHMS.SHA1, + transforms: [CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N], id: "ref-1", type: "http://www.w3.org/2000/09/xmldsig#Object", }); - sig.canonicalizationAlgorithm = "http://www.w3.org/2001/10/xml-exc-c14n#"; - sig.signatureAlgorithm = "http://www.w3.org/2000/09/xmldsig#rsa-sha1"; + sig.canonicalizationAlgorithm = CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N; + sig.signatureAlgorithm = SIGNATURE_ALGORITHMS.RSA_SHA1; sig.computeSignature(xml); const signedXml = sig.getSignedXml(); @@ -1389,14 +1392,14 @@ describe("Signature unit tests", function () { it("should throw if xpath matches no nodes", () => { 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/2001/04/xmldsig-more#rsa-sha256", + canonicalizationAlgorithm: CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N, + signatureAlgorithm: XMLDSIG_URIS.SIGNATURE_ALGORITHMS.RSA_SHA256, }); sig.addReference({ xpath: "//definitelyNotThere", - digestAlgorithm: "http://www.w3.org/2000/09/xmldsig#sha1", - transforms: ["http://www.w3.org/2001/10/xml-exc-c14n#"], + digestAlgorithm: HASH_ALGORITHMS.SHA1, + transforms: [CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N], }); expect(() => sig.computeSignature("")).to.throw( @@ -1408,14 +1411,14 @@ describe("Signature unit tests", function () { 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", + canonicalizationAlgorithm: CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N, + signatureAlgorithm: SIGNATURE_ALGORITHMS.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#"], + digestAlgorithm: HASH_ALGORITHMS.SHA1, + transforms: [CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N], }); sig.computeSignature(xml); diff --git a/test/static/chain_client.crt.pem b/test/static/chain_client.crt.pem new file mode 100644 index 00000000..f20a5799 --- /dev/null +++ b/test/static/chain_client.crt.pem @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDfDCCAmSgAwIBAgIUb+8ZAYLznP4nyAYw6Wr+8fojmsowDQYJKoZIhvcNAQEL +BQAwVDELMAkGA1UEBhMCVVMxDjAMBgNVBAgMBVN0YXRlMQ0wCwYDVQQHDARDaXR5 +MRUwEwYDVQQKDAxPcmdhbml6YXRpb24xDzANBgNVBAMMBlJvb3RDQTAeFw0yNTEw +MjcyMTQ3MjVaFw0zNTEwMjUyMTQ3MjVaMFgxCzAJBgNVBAYTAlVTMQ4wDAYDVQQI +DAVTdGF0ZTENMAsGA1UEBwwEQ2l0eTEVMBMGA1UECgwMT3JnYW5pemF0aW9uMRMw +EQYDVQQDDApTaWduZWRDZXJ0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC +AQEArRiG7bSPk4pNlCzlGBXG8Wmcpt25oL5GXaSgOkLozCqNGT3Ozoujs6GnIaJQ +mvR6RUBlrjjPqw1R1UQZip1NKViEOYzgwy7TmD5P6Aj1Ds+WWke7j1xCtl1sw7l1 +Bwo0BQ5iRW9hhujOgoOEK3XH7OxcwTax4zWM1UNstJmVIP/OdJU+QoMWxm2eAvK4 +P464XJ3FokUmw26srDiCO2xxLaQH3ygPx4gc0BNMpSBravTXGpPudcSgS9dbPPEP +rkpCMMc5sXvqeMnCHznfG+9AEJTS6mQA4cfhcAImnpwdz9O9S8aw//8IHD6vejJW +/xeqQW5mSGlz42njNORrn8rtpQIDAQABo0IwQDAdBgNVHQ4EFgQUoeCxmSUsDnfI +28ViPssKhEoR9eowHwYDVR0jBBgwFoAUmasR+rPpj9JuLcRl3Fn1FLpEIIUwDQYJ +KoZIhvcNAQELBQADggEBADAWzRh6y98k41B6Hdt//yG7VqDQJVJyMqQ/UXcPt3aC +QbBZSopAR7n1kb4UlhBuCHhnj54V4JdotW2PS9kBNN3s/0OOZqk6069DDyZhKNH4 +KK2KVcieNtuwRSVU9mrVsTDvJHFnmwKkL2YS+DRqtQtzHLLJjOYf2yGz978ZgP38 +HFz1xCYQjitBFVVlzpnBm4HPfKj9279oO3cpByM+GIDNSEND+TkVZ3g+/1bTXFeH +h4NIFIqoyQktH7p6isJpASGwJlSrlxe/WrX85pmtHZFqVwfbMKZUx7LBgpkjrZwq +JA79GZ7IijhhU8WdQASHWrQnucKgXCxTWJUJa029HkE= +-----END CERTIFICATE----- diff --git a/test/static/chain_client.key.pem b/test/static/chain_client.key.pem new file mode 100644 index 00000000..2b655b07 --- /dev/null +++ b/test/static/chain_client.key.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCtGIbttI+Tik2U +LOUYFcbxaZym3bmgvkZdpKA6QujMKo0ZPc7Oi6OzoacholCa9HpFQGWuOM+rDVHV +RBmKnU0pWIQ5jODDLtOYPk/oCPUOz5ZaR7uPXEK2XWzDuXUHCjQFDmJFb2GG6M6C +g4Qrdcfs7FzBNrHjNYzVQ2y0mZUg/850lT5CgxbGbZ4C8rg/jrhcncWiRSbDbqys +OII7bHEtpAffKA/HiBzQE0ylIGtq9Ncak+51xKBL11s88Q+uSkIwxzmxe+p4ycIf +Od8b70AQlNLqZADhx+FwAiaenB3P071LxrD//wgcPq96Mlb/F6pBbmZIaXPjaeM0 +5Gufyu2lAgMBAAECggEAUxcwgf/IYh8kQWpRqL+fabh0ScequWZNOdtyTLVcsdEF +PWYllZGDihGhvGwBvHh6Dy8sADdmPKqeqzzO8/KxnRTQGB4vsJIUYYMb8XsHQ85T +UtAXUWiM36S2NrgaXMBBm2G9u64NR2kO5KjEM+aMi4ckuV0LhFFq4t7EWmdVJmrJ +dqnL1m1nCb2pfBoO3+DDO/auHbGGRVYHu9S/vB8qeCsSudbCERPzDFly4IUUqkGS +9+zrQzZOjyG6isrrV07qRxS1nEBh4ZB5Nq1xgl1ptjnQn4IJBd5571smCr1+2FWG +QGj6KlB6qJgYi42GDkfsaK85O0Hal8dFi4NjjN2wiwKBgQDv+KAnQSgGZONz31hu +/OlLtpwNRL/2XnoCk9tGkGCPaZT7pqo0KSmqBQjWHqKrdNaRsPQClTGSZJWjz58U +Zhs7FPuG/ibIsJgJOW5RDFJ9n4gwAHMbwr8AMvp164mNVn/8xZwG9B3ZEp64fRDE +YldCytWknEWqQ6a37o8kOI6OmwKBgQC4qF6UY6qUTuY1oY3BM+dyWM61vv9iotar +UzbfY/ufXqU/5OhxY8E5SZUO4g3Zrl5RtEdOoM8Py7mymvznSlzJulRcX+6SESbV +A+5O9fGCqtQ+v1P1xGRE3t73myXrJ6s/znZBphMuMNIkqsmqxA48vq9tmsgsYhPb +egO7qTAYvwKBgHE5dAdRfNsXeyJO/WDQwBrTPGoeSByskxDoRovSz1ybSoo6JxCZ +Y2kvGu48YjBX3m27ekZFsrAJ+XjjG4H6c1q7Gbql7BLBD9s6V8yx7bIMNavAao9s +ocYsR3Sf/7TKXXUcn/O/9t1XJcCScfjXFakUHx2eBljBtsYOL0e9z7WFAoGBAIo0 +KaVx+sdJTe8x3MCPMlhYs00/iDCwo25St6z2Ter3kUKC9p13BbT0p4UeFzOm15zb +CsuEe7Tcyz0r1sDc3Rl2RZFlk07rW17utDuQw5MCfBwCYrp8pHcPP12eVwDrDbaR +tdxoic52Z7FdydXvKqC4LuAfilX9idMoPQcFF6RNAoGAAKt5ehZpu6O/vIVLX2Oa +7/6ktelkghs4IjzYklEUY1Qjb4xbDOwHVDlsgczhgQf7a/pnLnk4OiLbSUt9fa8s +2uFDcQmNBxvPELc0CxnY3jeey8cmINFyejlsR94nxBDbLOvGisigkyfG/2sl8Tpq +IlK7fz+aDRZrmeJO1YW4aQY= +-----END PRIVATE KEY----- diff --git a/test/static/chain_root.crt.pem b/test/static/chain_root.crt.pem new file mode 100644 index 00000000..d6e2ee8b --- /dev/null +++ b/test/static/chain_root.crt.pem @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDiTCCAnGgAwIBAgIUR9LnAGXI1oyKIYuthL2GhMVSYlQwDQYJKoZIhvcNAQEL +BQAwVDELMAkGA1UEBhMCVVMxDjAMBgNVBAgMBVN0YXRlMQ0wCwYDVQQHDARDaXR5 +MRUwEwYDVQQKDAxPcmdhbml6YXRpb24xDzANBgNVBAMMBlJvb3RDQTAeFw0yNTEw +MjcyMTQ1MjhaFw0zNTEwMjUyMTQ1MjhaMFQxCzAJBgNVBAYTAlVTMQ4wDAYDVQQI +DAVTdGF0ZTENMAsGA1UEBwwEQ2l0eTEVMBMGA1UECgwMT3JnYW5pemF0aW9uMQ8w +DQYDVQQDDAZSb290Q0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCY +eFugeXX+MELgCdh4qAHsqodLDx3yCHsZQnmtA0qDxiVrnXT0zYVXUuqTjv1zBtce +scqqOmjsxnlTv5FIDkrIlgz8rbvP6VQDUaAz4j3t62l3C3N67z4QEfW7052rDauZ +JzonBVMiUAAlY769a+cOstPzjW3Pe3hvKbP5pTHlyn9L4wCCsfjXgP5n+BcVs8wC +kvjRG9fAOVjE0OCzaowVtCqdPkZ/iQ2A5Q+Fz/FtIjcrEowANnbaAyEkHogu2LuN +94qH9FFx7ZPMo3uVT+0bulFDw5ms/yaYAz9wOmZe+EGrIVS069TXnExIEGjB7VzF +zAJDXXWCuMD9icpJFs/bAgMBAAGjUzBRMB0GA1UdDgQWBBSZqxH6s+mP0m4txGXc +WfUUukQghTAfBgNVHSMEGDAWgBSZqxH6s+mP0m4txGXcWfUUukQghTAPBgNVHRMB +Af8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQBjOKuGCGvKYi7izcRFBWqHFZJA +Qd9WesrWSpNBAd1sflGBJt7pMrI08Z3Zk04JL0oyTuYJndFvoc1putnq7YVtFxhQ +hrqzea3+Wb6MxeAsmGgiwNrGtl3BGaTdvz/t2eVE84oNtscypJKJQz9ApZmnEQ3t +6ZbTTDDCzx3UiiTvKodOSPuMDyaJUeTreXEr2dD8hseH39NCgqxKhyUJXzidwhbq +7wj3Ga9HpQnLepJVVADNYpkRG7q1nuByiZj91b99o7eMpkvidPCrGXk30EJ7t28n +MwCEBUj3dG+d/7iogXuptsL79Hk3dTmCgTQ1d/HVDbeh2500nO2/8X3kBUY0 +-----END CERTIFICATE----- diff --git a/test/static/chain_root.crt.srl b/test/static/chain_root.crt.srl new file mode 100644 index 00000000..8d11a28e --- /dev/null +++ b/test/static/chain_root.crt.srl @@ -0,0 +1 @@ +6FEF190182F39CFE27C80630E96AFEF1FA239ACA diff --git a/test/static/chain_root.key.pem b/test/static/chain_root.key.pem new file mode 100644 index 00000000..71c7686b --- /dev/null +++ b/test/static/chain_root.key.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCYeFugeXX+MELg +Cdh4qAHsqodLDx3yCHsZQnmtA0qDxiVrnXT0zYVXUuqTjv1zBtcescqqOmjsxnlT +v5FIDkrIlgz8rbvP6VQDUaAz4j3t62l3C3N67z4QEfW7052rDauZJzonBVMiUAAl +Y769a+cOstPzjW3Pe3hvKbP5pTHlyn9L4wCCsfjXgP5n+BcVs8wCkvjRG9fAOVjE +0OCzaowVtCqdPkZ/iQ2A5Q+Fz/FtIjcrEowANnbaAyEkHogu2LuN94qH9FFx7ZPM +o3uVT+0bulFDw5ms/yaYAz9wOmZe+EGrIVS069TXnExIEGjB7VzFzAJDXXWCuMD9 +icpJFs/bAgMBAAECggEABK3nnKcy+ILL//WNZTaSyIv7QachyM8l0q1Nmq0NLmTN +uohx5x7KRtcj8ooNkT2BK09S5E038dGti4/HilSV9aNVRrP2SE0654HWeYEqBrvr +grHL8NaYKwTCFpT0s86O7lEGjIgHTc2daN2veZTEL0P3E/R1OExBHU2ZrmxkJE85 +3xranr0ZnxOXfT1b5tzO95sO8MmCr46O12imNMPFLLwHwqTZZr0nFNnlJ6OVOnH7 +CssCsycfYmk/IGhr0b2xPf/r+V3cFHfUi/lY6ZC/gWyo5BRIKjDJ+VJzbS1QLzhe +UorFuzxZ8KrdVuHl8dfUETZG4L82M2wAiX/1iYNqEQKBgQDGG/Sr5h1GdQnoRi0G +5m24V/Z8/wrkYVzxjfRlmLcxNKTA9H0bQaFaS6TGHHPMBnsw1LRc21x7sSIqExWo +66uJ8Nr83PxrJeetnroUaIMRfB8f2jTqTQdAF8fQ4NGEPHpLlmnAg4mX0wQQb0ZO +CS2fotYbh2iVspKOVOUhBRiRDwKBgQDFBkLLM9LZDg1fgimdAvmleaXq6YNQZxph +MapMFfOuT0zIcEAEhEMb6l8sHKTsi9VrYhIi0c2xpuOnhNS+Z2LHwcoUgRKHY+DG +NwEiPlK0JSSdKnKRXJ9g2BXU8I54K8FkcWgJivo6rp8bID9ZoVvLQmWj/qOieAvm +GbA9JVM8dQKBgAcNZbdc2LvyXKjtHps5RryiPP8UITIiGSnsMMARIKxawGayDWYT +/wd02+fFiYXA0U/aspT/photIxc2WLYLta6SaWlJAJ9b2RSAKwWg9tF/hqgen3Wb +yl9IuW9BIZRAhuX788XLqPFDrMhc/ba3cu1U4aRXPKzfj4ILmaCESuyXAoGARKDF +q1pF221VoysHq7VZmBYjgQwNvXfsbGaMVyxeUR02NatD4U7gwVyGAiuIFw0uLdVf +U9mYuITVT4ipQhlpAwOxjCrZdWeI6AJI1tC2piE5+7TJa3DD40vhbubL+XfkSURn +ZMuQFdi1exFkf6gA/XAHT3RnMzR1kJTqGqJht/ECgYAOcJUXE8uQnwFMajPNu/Cj +F2VVcouOtopKhFNqqGYigYWeJT8JDvuJb46xsLf1Blxvgns5thttGkir9UaG1tbH +9DFVOKllX6x2yqyY2fPuNhT4/YadFdkrGtZPqLhmL+Ws4g83j8qi7rcguDMmbf+c +zt4tPipnP8L9OeCzTSaPMA== +-----END PRIVATE KEY----- diff --git a/test/static/expired_certificate.crt.pem b/test/static/expired_certificate.crt.pem new file mode 100644 index 00000000..a3baa560 --- /dev/null +++ b/test/static/expired_certificate.crt.pem @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDizCCAnOgAwIBAgIUai2c+XPx6ig5Gp++qU4GckIHLIQwDQYJKoZIhvcNAQEL +BQAwVTELMAkGA1UEBhMCVVMxDjAMBgNVBAgMBVN0YXRlMQ0wCwYDVQQHDARDaXR5 +MRUwEwYDVQQKDAxPcmdhbml6YXRpb24xEDAOBgNVBAMMB0V4cGlyZWQwHhcNMTkx +MjMxMjMwMDAwWhcNMjAwMTAxMjMwMDAwWjBVMQswCQYDVQQGEwJVUzEOMAwGA1UE +CAwFU3RhdGUxDTALBgNVBAcMBENpdHkxFTATBgNVBAoMDE9yZ2FuaXphdGlvbjEQ +MA4GA1UEAwwHRXhwaXJlZDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB +APpx3e8bSksaJfQqWSjpn09xA/F+acobLdidspYSdLOBGkV9ou1vgzrYrURfFcVj +s/U2s6CzR14kqzE2ZGAi5xdUMxLyx+jLtx851FTtTQEZm4AeZIeC77JNcu998yk0 ++gD7LrAcWWQ1j3f7gj0+ciWy5Yjeck9PyPSLpqpNY0EFy0oBwVHaZd9qnrYrwesJ +mPetQ+lexY11g1t72Oxyh6eimY5Uz4SavhACACYQ9jao5nwZttJ7d/tJa6xHbo1w +pmPze3hhVMMoVWWA+Wb9jxkjhzqZF7i0wu3wzTk3AQPmfU3QMPNpwJaH278WT9B1 +1hONCyEPA12VDn04yXldJMcCAwEAAaNTMFEwHQYDVR0OBBYEFDcsmaoj3UiR3R54 +kDBgTy6KnVREMB8GA1UdIwQYMBaAFDcsmaoj3UiR3R54kDBgTy6KnVREMA8GA1Ud +EwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBABwyWeEUI+AUjsB3MBXOPpwj +Xhq3j0On00nz4w+QujvyACXVahPuQ8kxPqk9emnS8b0/+YBCh9yIDta1XiJ/46kl +ajiXzhlLFLUHEU9n5BJKEV6DYr47stYO/lZ+Q9dVtNFxtzVywI9WHY4JqWQ7W1D9 +T8VnKbLFu+IabNJLIQaKdOwWx9F3ybQtRufT+aHK04lC2zQco+yFCiq0Je0aa9Pl +dfLV/UNvYrNr56xwiRhfqFi+MHNz04jWpFal6q1NuI1bahyWbPhTzfXR+XrUZoS2 +3+xL61FvoWd57BWz8+Pid8bPoB/cYFU33mqWeH/LI1pSfhdOqP9X67bsH3MFZwQ= +-----END CERTIFICATE----- diff --git a/test/static/expired_certificate.key.pem b/test/static/expired_certificate.key.pem new file mode 100644 index 00000000..449468dc --- /dev/null +++ b/test/static/expired_certificate.key.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQD6cd3vG0pLGiX0 +Klko6Z9PcQPxfmnKGy3YnbKWEnSzgRpFfaLtb4M62K1EXxXFY7P1NrOgs0deJKsx +NmRgIucXVDMS8sfoy7cfOdRU7U0BGZuAHmSHgu+yTXLvffMpNPoA+y6wHFlkNY93 ++4I9PnIlsuWI3nJPT8j0i6aqTWNBBctKAcFR2mXfap62K8HrCZj3rUPpXsWNdYNb +e9jscoenopmOVM+Emr4QAgAmEPY2qOZ8GbbSe3f7SWusR26NcKZj83t4YVTDKFVl +gPlm/Y8ZI4c6mRe4tMLt8M05NwED5n1N0DDzacCWh9u/Fk/QddYTjQshDwNdlQ59 +OMl5XSTHAgMBAAECggEAc9PR1tICTDWts/0Z+0gBPBaCwl+6wZRMYdCdVbb3bkWZ +RuZSQgm+4apwiByJzx7Lje9cqEgCC9Jdsob7aVL7GdkBPhQ2zL3a1YBDaXvOj2Gu +f1SPHfU6snYLYCQaH8a2kVmaQCz8UtJKpi0WEQkedb0FV4W5zGCUCjXEQSNFcj43 +iy8V8pIznTJoaTBtXjhI0uWGixTtlUgKXw3sTwkD+nJUXEe5fafJCBW0cFkswCG/ +O48oDsW9uG0cp9u6+zhTS9Y+cTjZtjyKPuAlmdEvA2vgP+pxkjt5E3ZdKD5wTUaM +w08O7vTM/TwgMjERZDt1OeItwKLVT/WIao+nXlKv8QKBgQD/xZrMjP4FKscK/bfl +Shk0Hg++3rxOcEL8OBymZt98ovKc1g0CxBVO8a+NPPIkUvZycq9hxrAT74Gzc2U3 +ilpTCw0Bnmzj6Hrwa+g0G92ptPWsMrRg1wyDY1XElHYJ6TmxV56J0XkI6iUx4BZS +lccLC3WGwwj/alw/1drtzP9x+wKBgQD6qwvHotsTU/LFRLwJgWvZfz1S5o66zIpr +pTxnrFSij+A5aHbp8mSSthMZWSKg0t5LLjykbDdlEzSl0YjQkfvETbAUvVzK3PSk +LF8DNA9rUuQXKyQzBBMUbMMQFn3mIUoc16M7v9D19E14AL1exSyPOOGeDaVXoH57 +6lfg/piqpQKBgHvbPPMA86Gc7XYtFvg5waqzQ/yx744sXsO0iGssNd0tKz83iGVm +fssTzmcetENSyXTyhGtcw7djq/MyVjlnDgZYu5ulFCXpVl9GYdOaCuU7dBxHEYIz +oSOe3tGq8t4pyn5OZ79laK8gc5KLaUPks9ZtXiQ8HgdRggqHjNTLCIgxAoGBAMOM +aDYnT+x2Eu/dvStVMYONBZQElNgY9OshDkx6XdQrlWpzmkDLfbYOIDwoEyGPHydb +PKewXE6XevzYx3ieSeBMEs87IoaHdLoWe1CObnD1S0bfuu+pgBDxAAMu6Kx8z8pM +VuUnsKYPHdg+C31BKI/aeffJAXGonMOif0fglcyZAoGBAKn0KUfzF85kAWXhbqAD +BaoNHB2R+FBTIbK62XT7ipVr7/ESO+BPFF3/no8fj7UcnLRfwJNqp7WgUzDjfhas +NS4Go8hlBdpRAZIeNZ2MuNRjyWOi57+UqJMiXNkfRLW0sxs1dpXap/C49Srb40Cp +FPFcndwuNUAbFlORtw3A97du +-----END PRIVATE KEY----- diff --git a/test/static/future_certificate.crt.pem b/test/static/future_certificate.crt.pem new file mode 100644 index 00000000..1a5822f0 --- /dev/null +++ b/test/static/future_certificate.crt.pem @@ -0,0 +1,18 @@ +-----BEGIN CERTIFICATE----- +MIIC7TCCAdWgAwIBAgIUUYG5GJ7mlsFhulnT9/RkKyA8jNUwDQYJKoZIhvcNAQEL +BQAwHTEbMBkGA1UEAwwSRnV0dXJlIENlcnRpZmljYXRlMCIYDzk5OTkwMTAxMjMw +MDAwWhgPOTk5OTAxMDIyMzAwMDBaMB0xGzAZBgNVBAMMEkZ1dHVyZSBDZXJ0aWZp +Y2F0ZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKfCW4az2KHliibZ ++CxT5bgBOFAuGYFJBqN89lpGiZR1wIUSY4970S1yT1Mqspac16WpSuMPn5z6P+YL +9IMSWGhbM+AFKPoWgwpn8tdOT9zhSNO6cK27wP4p4CSt877TgKQwDsjZS/qpk+gZ +FNL2z5ZyppOoY9Fl1BwoqDF6LBykU+EjqWtSE5BZ47fcC+6YWOlu64ujwcy7c7y3 +qMAV1695grEYZeG0kIwzWtHGi5hGJsYLSREpy49c3M6OpzCRxTKdes1VJ70WavT3 ++gZE6bxDq/S5P1Jtcf9QAS1xM9kzIj8uQQQtfQqZbpF9x8iKNSbg3aMLz/KFj69d +xtThaekCAwEAAaMhMB8wHQYDVR0OBBYEFJWppJVYPR6ti8+6hYSJwSncPipSMA0G +CSqGSIb3DQEBCwUAA4IBAQBVC6INvRLzPVSK5F0YH07izKj7Ky8oumQ/an+G0/Yt +GJ52oRDTloHa012ad2weTLObIVlKGz10zRWZPB0IWcBOsMq7A2HE84pHjZpDiDXg +xgwTMOSlwBWqjK9r3fzK3jzz/zu8dRV0egvmu1bqK5hrquMuIWJIeUkvuNU8nS5g +lzk9M3dPo+fqFYujzxUtVrWc/MRg7gnsEUFWkZKdRmkJsdSWGxl2yc+jPo5WAXJA +kRrNigclD620Vnxji0JGd1EVrGHxoiwmG4Vq1yUrXv+7fcYB3mhK4nhkOLhjNqx3 +i8l/o/cqTCe8tXXuPeH+ymnKEAJsOGW0cH3kEComNQO9 +-----END CERTIFICATE----- diff --git a/test/static/future_certificate.csr.pem b/test/static/future_certificate.csr.pem new file mode 100644 index 00000000..9979127b --- /dev/null +++ b/test/static/future_certificate.csr.pem @@ -0,0 +1,15 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIICYjCCAUoCAQAwHTEbMBkGA1UEAwwSRnV0dXJlIENlcnRpZmljYXRlMIIBIjAN +BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAp8JbhrPYoeWKJtn4LFPluAE4UC4Z +gUkGo3z2WkaJlHXAhRJjj3vRLXJPUyqylpzXpalK4w+fnPo/5gv0gxJYaFsz4AUo ++haDCmfy105P3OFI07pwrbvA/ingJK3zvtOApDAOyNlL+qmT6BkU0vbPlnKmk6hj +0WXUHCioMXosHKRT4SOpa1ITkFnjt9wL7phY6W7ri6PBzLtzvLeowBXXr3mCsRhl +4bSQjDNa0caLmEYmxgtJESnLj1zczo6nMJHFMp16zVUnvRZq9Pf6BkTpvEOr9Lk/ +Um1x/1ABLXEz2TMiPy5BBC19CplukX3HyIo1JuDdowvP8oWPr13G1OFp6QIDAQAB +oAAwDQYJKoZIhvcNAQELBQADggEBABHJ+oTa5shXN7qNxfJVXznUMiujXlEqw6te +GMz14U/bZSllI42X3ipwdsn7GHHUPWN5STtJz534o4HdcSyKSzhO4Q9hcoKFE5xg +z1NeQSrBcX1UNgEL4+0xzSuVYJsNlz5qDEMFB/dOGtf+dSV7W+ljr3RPr9AeuDT4 +nkakHi6zbK5plEX295HY6GJxCCP7YZOJe3Iw3807L3rS+iQ+uwP/xiCSg25gzz/u +xNPWYRm99onM/4w+DlS8KHcjVyPJlurtsPxSdvFUxmOOusQYzG65xxZqazuBqVtC +qMDFVlYjazKNMU426JDmV41SR9yMa8XCIRYQmJuRN/4IH4g/Dfg= +-----END CERTIFICATE REQUEST----- diff --git a/test/static/future_certificate.key.pem b/test/static/future_certificate.key.pem new file mode 100644 index 00000000..386c5076 --- /dev/null +++ b/test/static/future_certificate.key.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCnwluGs9ih5Yom +2fgsU+W4AThQLhmBSQajfPZaRomUdcCFEmOPe9Etck9TKrKWnNelqUrjD5+c+j/m +C/SDElhoWzPgBSj6FoMKZ/LXTk/c4UjTunCtu8D+KeAkrfO+04CkMA7I2Uv6qZPo +GRTS9s+WcqaTqGPRZdQcKKgxeiwcpFPhI6lrUhOQWeO33AvumFjpbuuLo8HMu3O8 +t6jAFdeveYKxGGXhtJCMM1rRxouYRibGC0kRKcuPXNzOjqcwkcUynXrNVSe9Fmr0 +9/oGROm8Q6v0uT9SbXH/UAEtcTPZMyI/LkEELX0KmW6RfcfIijUm4N2jC8/yhY+v +XcbU4WnpAgMBAAECggEAFc/OaKgvjXUzzjNe8hyCbLcz5DDqPgYJp+4SddBgCP56 +ZpLqgPhfTSJkr/KIP87qtu5Y/0bDwPxEnJuHUhdriT36c7EYD9Qne43iZB4ZgiWE +e4rtJZmY0TMOopY/b9s+CZr6ASFHoLK1uWKxc3CFsxD7GY22VL6BopuiqrQw0hRR +rDOEGXwlJjlIdwz4istCaEsNAmaOPCQ6r+KdIw7cMdUKuG+lsxfTpC2QJtbiknI0 +WcNkWZ9zBxbTX+GpTWTLIM7rz2g96wuD+0ThCzteyWVffBBtIlCS3GOg+rdhWTYV +v2IbJZGG7qT5lOKlkvIUSMAiMd0fwCoIaPBybaQS0QKBgQDWSOKnMhQ10QuxqkL5 +Pr7dD+x6zkz8kaZCuYWXGevL3UYuBsBh50FeznKjqIAQRpz1v1Mgat9q4pI+DtOg +hqxreTsgAJfFSysaL4BuHUM606dqDEzeGz7Vavpnok1PgEzSAOBEDCo/xOHye2zE +evBy7nhMLhYwCcRNX/QhRnb8pQKBgQDIatKXDwX3ZK52yE4BuC1y+GFiL13m1BR5 +voHdFTwf7expYYmc95wud2KMqvEOT76tvMpNNkO/oESNbDVyjEUsXtykk5h7QJMq +1T50YgJQR2SAHVzPsWhR3NB/EJzBNW92gS+JysbtWgdkt3PunY58SMy0Xg6Kz1/u +XqYkoykg9QKBgQCgdZWbo6l0nyRFlvxtzal4ufrX/vGxU5OPdYLuog9q6jgqMQ4Q +ge32g1te58d16JqSfwFNThoc3Kqr48he9VnZZL98eFUt/Nq60gU275yvSVyc0bch +vn8vqtr1jZicxrM/sj49Vmqws8qKHBhXjMPPHHliekRNFpMzaX3TCQQCrQKBgGOT +v8JSMpKysYRPDYMJMXu4MRqJkkxH/0xl/TwNeuwaWKYbUjZtSGpF4u8lV9PWh1Tn +QlSOq6agSK9DnmKlkxDyqQoUU2SZtwVHIlrM/31Hm4WUETMYYE6cOfOIG3pbxF/K +3AXIfIIdgyLli3J5UfwqZ5sOSIdrdayH1mDJuHupAoGAcZJzimLVBHW9szrKFji1 +BkSUBaHvJExkXewJWaRjmkkV1GvWzbLU1vVM6/QxFy9kkrZk3dlYd60JW40Ap68n +ZvlfnArNw324nigu3Twc4b7ENYKUsimq3zprETeLw4BPojHPDTrQk+A/nhJZXXOo +QUJcxSlNR3iOhb7+BBwrI74= +-----END PRIVATE KEY----- diff --git a/test/utils-tests.spec.ts b/test/utils-tests.spec.ts index b5fe41aa..aa66598d 100644 --- a/test/utils-tests.spec.ts +++ b/test/utils-tests.spec.ts @@ -80,4 +80,60 @@ describe("Utils tests", function () { expect(() => utils.pemToDer("not a pem")).to.throw(); }); }); + + describe("findAttr", function () { + it("should find attribute with no namespace when null is passed as namespace", function () { + const xml = + ''; + const doc = new xmldom.DOMParser().parseFromString(xml); + const rootElement = doc.documentElement; + + const attr = utils.findAttr(rootElement, "testAttr", null); + + expect(attr).to.not.be.null; + expect(attr?.value).to.equal("value"); + expect(attr?.namespaceURI).to.be.undefined; + }); + + it("should not find namespaced attribute when null is passed as namespace", function () { + const xml = ''; + const doc = new xmldom.DOMParser().parseFromString(xml); + const rootElement = doc.documentElement; + + const attr = utils.findAttr(rootElement, "testAttr", null); + + expect(attr).to.be.null; + }); + + it("should find namespaced attribute when matching namespace is provided", function () { + const xml = ''; + const doc = new xmldom.DOMParser().parseFromString(xml); + const rootElement = doc.documentElement; + + const attr = utils.findAttr(rootElement, "testAttr", "http://example.com"); + + expect(attr).to.not.be.null; + expect(attr?.value).to.equal("nsValue"); + expect(attr?.namespaceURI).to.equal("http://example.com"); + }); + + it("should distinguish between namespaced and non-namespaced attributes with same localName", function () { + const xml = + ''; + const doc = new xmldom.DOMParser().parseFromString(xml); + const rootElement = doc.documentElement; + + // Find the non-namespaced attribute + const noNsAttr = utils.findAttr(rootElement, "testAttr", null); + expect(noNsAttr).to.not.be.null; + expect(noNsAttr?.value).to.equal("noNsValue"); + expect(noNsAttr?.namespaceURI).to.be.undefined; + + // Find the namespaced attribute + const nsAttr = utils.findAttr(rootElement, "testAttr", "http://example.com"); + expect(nsAttr).to.not.be.null; + expect(nsAttr?.value).to.equal("nsValue"); + expect(nsAttr?.namespaceURI).to.equal("http://example.com"); + }); + }); }); diff --git a/test/validators/XmlCryptoUtilities/XmlCryptoUtilities/program-repro-misc-validation-and-canon.cs b/test/validators/XmlCryptoUtilities/XmlCryptoUtilities/program-repro-misc-validation-and-canon.cs index f3eca86e..2a832156 100644 --- a/test/validators/XmlCryptoUtilities/XmlCryptoUtilities/program-repro-misc-validation-and-canon.cs +++ b/test/validators/XmlCryptoUtilities/XmlCryptoUtilities/program-repro-misc-validation-and-canon.cs @@ -1,6 +1,6 @@ // // This example signs an XML file using an -// envelope signature. It then verifies the +// envelope signature. It then verifies the // signed XML. // using System; @@ -66,7 +66,7 @@ static bool ValidateXml(XmlDocument receipt, X509Certificate2 certificate) } sxml.LoadXml((XmlElement)dsig); - + // Check the signature bool isValid = sxml.CheckSignature(certificate, true); @@ -91,13 +91,13 @@ static bool ValidateXml(XmlDocument receipt, X509Certificate2 certificate) var resolver = new XmlSecureResolver(new XmlUrlResolver(), securityUrl); //TransformToOctetStream(Stream input, XmlResolver resolver, string baseUri) MethodInfo trans = _ref.TransformChain.GetType().GetMethods(BindingFlags.NonPublic | BindingFlags.Instance)[2]; - + var stream = trans.Invoke(_ref.TransformChain, new object[] {receipt, resolver, securityUrl}); var canontype = sig.GetType().Assembly.GetType("System.Security.Cryptography.Xml.CanonicalXml"); var foo = Activator.CreateInstance(canontype, BindingFlags.NonPublic | BindingFlags.Instance, null, new object[] {receipt, resolver}, null); - + @@ -124,21 +124,21 @@ public static void Main(String[] args) //calculate caninicalized xml - + var t = new XmlDsigEnvelopedSignatureTransform(false); XmlDocument doc = new XmlDocument(); //doc.PreserveWhitespace = true; doc.Load(@"c:\temp\x.xml"); t.LoadInput(doc); - - FieldInfo field = t.GetType().GetField("_signaturePosition", + + FieldInfo field = t.GetType().GetField("_signaturePosition", BindingFlags.NonPublic | BindingFlags.Instance); - field.SetValue(t, 1); - + field.SetValue(t, 1); + var res = (XmlDocument)t.GetOutput(); var s = res.OuterXml; @@ -147,31 +147,31 @@ public static void Main(String[] args) var mem = (MemoryStream)c14.GetOutput(); var sha = new SHA256Managed(); - + var byte1 = c14.GetDigestedOutput(new SHA256Managed()); - var digest1 = Convert.ToBase64String(byte1); + var digest1 = Convert.ToBase64String(byte1); var byte2 = sha.ComputeHash(mem.ToArray()); var digest2 = Convert.ToBase64String(byte2); - - var s1 = System.Text.Encoding.UTF8.GetString(mem.ToArray()); + + var s1 = System.Text.Encoding.UTF8.GetString(mem.ToArray()); var byte3 = sha.ComputeHash(System.Text.Encoding.UTF8.GetBytes(s1)); var digest3 = Convert.ToBase64String(byte3); //return; - - - //validate signature - + + + //validate signature + CryptoConfig.AddAlgorithm(typeof(RSAPKCS1SHA256SignatureDescription), "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"); - XmlDocument xmlDoc = new XmlDocument(); + XmlDocument xmlDoc = new XmlDocument(); xmlDoc.Load(@"c:\temp\x.xml"); XmlNode node = xmlDoc.DocumentElement; X509Certificate2 cert = new X509Certificate2(File.ReadAllBytes(@"c:\temp\x.cer")); - bool isValid = ValidateXml(xmlDoc, cert); + bool isValid = ValidateXml(xmlDoc, cert); //return; - + //calc hash var sha1 = new SHA256Managed(); @@ -179,8 +179,8 @@ public static void Main(String[] args) var b64 = Convert.ToBase64String(b1); } - // Sign an XML file and save the signature in a new file. This method does not - // save the public key within the XML file. This file cannot be verified unless + // Sign an XML file and save the signature in a new file. This method does not + // save the public key within the XML file. This file cannot be verified unless // the verifying code has the key with which it was signed. public static void SignXmlFile(string FileName, string SignedFileName, RSA Key) { @@ -193,7 +193,7 @@ public static void SignXmlFile(string FileName, string SignedFileName, RSA Key) // Create a SignedXml object. SignedXml signedXml = new SignedXml(doc); - // Add the key to the SignedXml document. + // Add the key to the SignedXml document. signedXml.SigningKey = Key; // Create a reference to be signed. @@ -229,14 +229,14 @@ public static void SignXmlFile(string FileName, string SignedFileName, RSA Key) xmltw.Close(); } - // Verify the signature of an XML file against an asymetric + // Verify the signature of an XML file against an asymetric // algorithm and return the result. public static Boolean VerifyXmlFile(String Name, RSA Key) { // Create a new XML document. XmlDocument xmlDocument = new XmlDocument(); - // Load the passed XML file into the document. + // Load the passed XML file into the document. xmlDocument.Load(Name); // Create a new SignedXml object and pass it diff --git a/test/wsfed-metadata-tests.spec.ts b/test/wsfed-metadata-tests.spec.ts index 7ddea8e8..574bfc5d 100644 --- a/test/wsfed-metadata-tests.spec.ts +++ b/test/wsfed-metadata-tests.spec.ts @@ -1,4 +1,4 @@ -import { SignedXml } from "../src/index"; +import { SignedXml, XMLDSIG_URIS } from "../src"; import * as xpath from "xpath"; import * as xmldom from "@xmldom/xmldom"; import * as fs from "fs"; @@ -10,7 +10,7 @@ describe("WS-Fed Metadata tests", function () { const xml = fs.readFileSync("./test/static/wsfederation_metadata.xml", "utf-8"); const doc = new xmldom.DOMParser().parseFromString(xml); const signature = xpath.select1( - "/*/*[local-name(.)='Signature' and namespace-uri(.)='http://www.w3.org/2000/09/xmldsig#']", + `/*/*[local-name(.)='Signature' and namespace-uri(.)='${XMLDSIG_URIS.NAMESPACES.ds}']`, doc, ); isDomNode.assertIsNodeLike(signature); diff --git a/test/xmldsig-verifier.spec.ts b/test/xmldsig-verifier.spec.ts new file mode 100644 index 00000000..4e025a0d --- /dev/null +++ b/test/xmldsig-verifier.spec.ts @@ -0,0 +1,999 @@ +import * as fs from "fs"; +import { expect } from "chai"; +import { XmlDSigVerifier, SignedXml, ExclusiveCanonicalization } from "../src"; +import { RsaSha1 } from "../src/signature-algorithms"; +import { Sha1 } from "../src/hash-algorithms"; +import { EnvelopedSignature } from "../src/enveloped-signature"; +import { XMLDSIG_URIS, XmlDsigVerificationResult } from "../src/"; + +import { X509Certificate } from "node:crypto"; + +// Parse the XML and get both signature nodes +import { DOMParser } from "@xmldom/xmldom"; + +const { CANONICALIZATION_ALGORITHMS, HASH_ALGORITHMS, SIGNATURE_ALGORITHMS, TRANSFORM_ALGORITHMS } = + XMLDSIG_URIS; + +// Default test certificate files +const privateKey = fs.readFileSync("./test/static/client.pem", "utf-8"); +const publicCert = fs.readFileSync("./test/static/client_public.pem", "utf-8"); + +// Chain certificate files for truststore testing +const chainPrivateKey = fs.readFileSync("./test/static/chain_client.key.pem", "utf-8"); +const chainPublicCert = fs.readFileSync("./test/static/chain_client.crt.pem", "utf-8"); +const rootCert = fs.readFileSync("./test/static/chain_root.crt.pem", "utf-8"); + +// Expired certificate for testing certificate expiration validation +const expiredKey = fs.readFileSync("./test/static/expired_certificate.key.pem", "utf-8"); +const expiredCert = fs.readFileSync("./test/static/expired_certificate.crt.pem", "utf-8"); + +// Future certificate for testing certificate validity period validation +const futureKey = fs.readFileSync("./test/static/future_certificate.key.pem", "utf-8"); +const futureCert = fs.readFileSync("./test/static/future_certificate.crt.pem", "utf-8"); + +// Helper function to create a signed XML document +function createSignedXml( + xml: string, + options: { prefix?: string; attrs?: Record } = {}, +): string { + const sig = new SignedXml({ + privateKey, + canonicalizationAlgorithm: CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N, + signatureAlgorithm: SIGNATURE_ALGORITHMS.RSA_SHA1, + }); + + sig.addReference({ + xpath: "//*[local-name(.)='test']", + digestAlgorithm: HASH_ALGORITHMS.SHA1, + transforms: [CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N], + }); + + sig.computeSignature(xml, options); + return sig.getSignedXml(); +} + +// Helper function to create a signed XML document for truststore testing +function createChainSignedXml(xml: string): string { + const sig = new SignedXml({ + privateKey: chainPrivateKey, + canonicalizationAlgorithm: CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N, + signatureAlgorithm: SIGNATURE_ALGORITHMS.RSA_SHA1, + getKeyInfoContent: () => SignedXml.getKeyInfoContent({ publicCert: chainPublicCert }), + }); + + sig.addReference({ + xpath: "//*[local-name(.)='test']", + digestAlgorithm: HASH_ALGORITHMS.SHA1, + transforms: [CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N], + }); + + sig.computeSignature(xml); + return sig.getSignedXml(); +} + +function createExpiredSignedXml(xml: string): string { + const sig = new SignedXml({ + privateKey: expiredKey, + canonicalizationAlgorithm: CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N, + signatureAlgorithm: SIGNATURE_ALGORITHMS.RSA_SHA1, + getKeyInfoContent: () => SignedXml.getKeyInfoContent({ publicCert: expiredCert }), + }); + + sig.addReference({ + xpath: "//*[local-name(.)='test']", + digestAlgorithm: HASH_ALGORITHMS.SHA1, + transforms: [CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N], + }); + + sig.computeSignature(xml); + return sig.getSignedXml(); +} + +function createFutureSignedXml(xml: string): string { + const sig = new SignedXml({ + privateKey: futureKey, + canonicalizationAlgorithm: CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N, + signatureAlgorithm: SIGNATURE_ALGORITHMS.RSA_SHA1, + getKeyInfoContent: () => SignedXml.getKeyInfoContent({ publicCert: futureCert }), + }); + + sig.addReference({ + xpath: "//*[local-name(.)='test']", + digestAlgorithm: HASH_ALGORITHMS.SHA1, + transforms: [CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N], + }); + + sig.computeSignature(xml); + return sig.getSignedXml(); +} + +function expectValidResult(result: XmlDsigVerificationResult, references: number = 1) { + expect(result.success).to.be.true; + expect(result.error).to.be.undefined; + expect(result.signedReferences).to.be.an("array"); + expect(result.signedReferences).to.have.length(references); +} + +function expectInvalidResult(result: XmlDsigVerificationResult, errorMessage?: string) { + expect(result.success).to.be.false; + expect(result.signedReferences).to.be.undefined; + expect(result.error).to.be.a("string"); + if (errorMessage && result.error) { + expect(result.error.toLowerCase()).to.contain(errorMessage.toLowerCase()); + } +} + +describe("XmlDSigVerifier", function () { + const xml = "content"; + + describe("constructor", function () { + it("should create verifier with public certificate", function () { + const verifier = new XmlDSigVerifier({ + keySelector: { publicCert }, + }); + expect(verifier).to.be.instanceOf(XmlDSigVerifier); + }); + + it("should create verifier with getCertFromKeyInfo function", function () { + const verifier = new XmlDSigVerifier({ + keySelector: { + getCertFromKeyInfo: () => publicCert, + }, + }); + expect(verifier).to.be.instanceOf(XmlDSigVerifier); + }); + + it("should throw when trying to create a verifier without publicCert or getCertFromKeyInfo", function () { + expect(() => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + new XmlDSigVerifier({ keySelector: {} as any }); + }).to.throw("XmlDSigVerifier requires a valid keySelector option"); + }); + + it("should create verifier with all options set", function () { + const verifier = new XmlDSigVerifier({ + keySelector: { publicCert }, + idAttributes: ["customId"], + implicitTransforms: [CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N], + throwOnError: true, + security: { + maxTransforms: 5, + checkCertExpiration: true, + truststore: [rootCert], + signatureAlgorithms: SignedXml.getDefaultAsymmetricSignatureAlgorithms(), + hashAlgorithms: SignedXml.getDefaultHashAlgorithms(), + transformAlgorithms: SignedXml.getDefaultTransformAlgorithms(), + }, + }); + expect(verifier).to.be.instanceOf(XmlDSigVerifier); + }); + + it("should throw when getCertFromKeyInfo is undefined", function () { + expect(() => { + new XmlDSigVerifier({ + keySelector: { + getCertFromKeyInfo: undefined as never, + }, + }); + }).to.throw("XmlDSigVerifier requires a valid getCertFromKeyInfo function."); + }); + + it("should throw when getCertFromKeyInfo is set to publicCert string directly", function () { + expect(() => { + new XmlDSigVerifier({ + keySelector: { + getCertFromKeyInfo: publicCert as never, + }, + }); + }).to.throw("XmlDSigVerifier requires a valid getCertFromKeyInfo function."); + }); + }); + + describe("publicCert selector", function () { + it("should validate a valid signed XML document", function () { + const signedXml = createSignedXml(xml); + + const verifier = new XmlDSigVerifier({ + keySelector: { publicCert }, + }); + expectValidResult(verifier.verifySignature(signedXml)); + }); + + it("should validate when publicCert is a buffer", function () { + const signedXml = createSignedXml(xml); + + const verifier = new XmlDSigVerifier({ + keySelector: { publicCert: Buffer.from(publicCert) }, + }); + expectValidResult(verifier.verifySignature(signedXml)); + }); + + it("should fail validation when document is signed with different key", function () { + const signedXml = createChainSignedXml(xml); + + const verifier = new XmlDSigVerifier({ + keySelector: { publicCert }, + throwOnError: false, + }); + + expectInvalidResult(verifier.verifySignature(signedXml), "invalid signature"); + }); + }); + + describe("getCertFromKeyInfo selector", function () { + it("should validate a valid signed XML document", function () { + const signedXml = createSignedXml(xml); + + const verifier = new XmlDSigVerifier({ + keySelector: { + getCertFromKeyInfo: () => publicCert, + }, + }); + expectValidResult(verifier.verifySignature(signedXml)); + }); + + it("should fail validation when document is signed with different key", function () { + const signedXml = createChainSignedXml(xml); + + const verifier = new XmlDSigVerifier({ + keySelector: { + getCertFromKeyInfo: () => publicCert, + }, + throwOnError: false, + }); + + expectInvalidResult(verifier.verifySignature(signedXml), "invalid signature"); + }); + + it("should fail validation when getCertFromKeyInfo returns null", function () { + const signedXml = createSignedXml(xml); + + const verifier = new XmlDSigVerifier({ + keySelector: { + getCertFromKeyInfo: () => null, + }, + throwOnError: false, + }); + + expectInvalidResult(verifier.verifySignature(signedXml), "keyinfo"); + }); + + it("should fail validation when getCertFromKeyInfo returns empty string", function () { + const signedXml = createSignedXml(xml); + + const verifier = new XmlDSigVerifier({ + keySelector: { + getCertFromKeyInfo: () => "", + }, + throwOnError: false, + }); + + expectInvalidResult(verifier.verifySignature(signedXml), "keyinfo"); + }); + }); + + describe("idAttributes option", function () { + const xmlWithCustomId = 'content'; + const xmlWithPrefixedId = `content`; + + it("should validate a valid signed XML document with custom Id", function () { + const sig = new SignedXml({ + privateKey, + canonicalizationAlgorithm: CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N, + signatureAlgorithm: SIGNATURE_ALGORITHMS.RSA_SHA1, + idAttributes: ["customId"], + }); + sig.addReference({ + xpath: "//*[@customId='test1']", + digestAlgorithm: HASH_ALGORITHMS.SHA1, + transforms: [CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N], + }); + sig.computeSignature(xmlWithCustomId); + const signedXml = sig.getSignedXml(); + + const verifier = new XmlDSigVerifier({ + keySelector: { publicCert }, + idAttributes: ["customId"], + }); + expectValidResult(verifier.verifySignature(signedXml)); + }); + + it("should validate a valid signed XML document with prefixed Id", function () { + const sig = new SignedXml({ + privateKey, + canonicalizationAlgorithm: CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N, + signatureAlgorithm: SIGNATURE_ALGORITHMS.RSA_SHA1, + idAttributes: ["customId"], + }); + sig.addReference({ + xpath: "//*[@*[namespace-uri() = 'uri:foo' and local-name() = 'customId']]", + digestAlgorithm: HASH_ALGORITHMS.SHA1, + transforms: [CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N], + }); + sig.computeSignature(xmlWithPrefixedId); + const signedXml = sig.getSignedXml(); + + const verifier = new XmlDSigVerifier({ + keySelector: { publicCert }, + idAttributes: ["customId"], + }); + expectValidResult(verifier.verifySignature(signedXml)); + }); + + it("should work with explicitly namespaced Id attributes", function () { + const sig = new SignedXml({ + privateKey, + canonicalizationAlgorithm: CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N, + signatureAlgorithm: SIGNATURE_ALGORITHMS.RSA_SHA1, + idAttributes: ["customId"], + }); + sig.addReference({ + xpath: "//*[@*[namespace-uri() = 'uri:foo' and local-name() = 'customId']]", + digestAlgorithm: HASH_ALGORITHMS.SHA1, + transforms: [CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N], + }); + sig.computeSignature(xmlWithPrefixedId); + const signedXml = sig.getSignedXml(); + + const verifier = new XmlDSigVerifier({ + keySelector: { publicCert }, + idAttributes: [{ localName: "customId", namespaceUri: "uri:foo" }], + }); + expectValidResult(verifier.verifySignature(signedXml)); + }); + + it("should fail validation when Id attribute is not in the correct namespace", function () { + const sig = new SignedXml({ + privateKey, + canonicalizationAlgorithm: CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N, + signatureAlgorithm: SIGNATURE_ALGORITHMS.RSA_SHA1, + idAttributes: ["customId"], + }); + sig.addReference({ + xpath: "//*[@*[namespace-uri() = 'uri:foo' and local-name() = 'customId']]", + digestAlgorithm: HASH_ALGORITHMS.SHA1, + transforms: [CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N], + }); + sig.computeSignature(xmlWithPrefixedId); + const signedXml = sig.getSignedXml(); + + const verifier = new XmlDSigVerifier({ + keySelector: { publicCert }, + idAttributes: [{ localName: "customId", namespaceUri: "uri:bar" }], + throwOnError: false, + }); + expectInvalidResult(verifier.verifySignature(signedXml), "fail"); + }); + + it("should fail validation when Id attribute is not namespaced but namespaceUri is provided", function () { + const sig = new SignedXml({ + privateKey, + canonicalizationAlgorithm: CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N, + signatureAlgorithm: SIGNATURE_ALGORITHMS.RSA_SHA1, + idAttributes: ["customId"], + }); + sig.addReference({ + xpath: "//*[@customId='test1']", + digestAlgorithm: HASH_ALGORITHMS.SHA1, + transforms: [CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N], + }); + sig.computeSignature(xmlWithCustomId); + const signedXml = sig.getSignedXml(); + + const verifier = new XmlDSigVerifier({ + keySelector: { publicCert }, + idAttributes: [{ localName: "customId", namespaceUri: "uri:foo" }], + throwOnError: false, + }); + expectInvalidResult(verifier.verifySignature(signedXml), "fail"); + }); + + describe("idAttributes property handling", function () { + const xmlIdAttrNoNs = 'content'; + const xmlIdAttrWithNs = + 'content'; + const xmlIdAttrOtherNs = + 'content'; + + it("should validate when idAttributes is a string (matches non-namespaced ID attribute)", function () { + // Create signature for no-namespace XML + const sig = new SignedXml({ + privateKey, + idAttributes: ["customId"], + canonicalizationAlgorithm: CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N, + signatureAlgorithm: SIGNATURE_ALGORITHMS.RSA_SHA1, + }); + sig.addReference({ + xpath: "//*[local-name(.)='test']", + digestAlgorithm: HASH_ALGORITHMS.SHA1, + transforms: [CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N], + }); + sig.computeSignature(xmlIdAttrNoNs); + const signedXml = sig.getSignedXml(); + + const verifier = new XmlDSigVerifier({ + keySelector: { publicCert }, + idAttributes: ["customId"], + }); + expectValidResult(verifier.verifySignature(signedXml)); + }); + + it("should validate namespaced attribute when idAttributes is a string (matches namespaced ID attribute)", function () { + const sig = new SignedXml({ + privateKey, + idAttributes: ["customId"], + canonicalizationAlgorithm: CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N, + signatureAlgorithm: SIGNATURE_ALGORITHMS.RSA_SHA1, + }); + sig.addReference({ + xpath: "//*[local-name(.)='test']", + digestAlgorithm: HASH_ALGORITHMS.SHA1, + transforms: [CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N], + }); + sig.computeSignature(xmlIdAttrWithNs); + const signedXml = sig.getSignedXml(); + + const verifier = new XmlDSigVerifier({ + keySelector: { publicCert }, + idAttributes: ["customId"], + }); + expectValidResult(verifier.verifySignature(signedXml)); + }); + + it("should validate when `idAttributes` `namespaceUri` is `undefined` (matches namespaced ID attribute)", function () { + const sig = new SignedXml({ + privateKey, + idAttributes: [{ localName: "customId", namespaceUri: undefined }], + canonicalizationAlgorithm: CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N, + signatureAlgorithm: SIGNATURE_ALGORITHMS.RSA_SHA1, + }); + sig.addReference({ + xpath: "//*[local-name(.)='test']", + digestAlgorithm: HASH_ALGORITHMS.SHA1, + transforms: [CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N], + }); + sig.computeSignature(xmlIdAttrWithNs); + const signedXml = sig.getSignedXml(); + + const verifier = new XmlDSigVerifier({ + keySelector: { publicCert }, + idAttributes: [{ localName: "customId", namespaceUri: undefined }], + }); + expectValidResult(verifier.verifySignature(signedXml)); + }); + + it("should validate when `idAttributes` `namespaceUri` is `null` (matches non-namespaced ID attribute)", function () { + const sig = new SignedXml({ + privateKey, + idAttributes: [{ localName: "customId", namespaceUri: null }], + canonicalizationAlgorithm: CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N, + signatureAlgorithm: SIGNATURE_ALGORITHMS.RSA_SHA1, + }); + sig.addReference({ + xpath: "//*[local-name(.)='test']", + digestAlgorithm: HASH_ALGORITHMS.SHA1, + transforms: [CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N], + }); + sig.computeSignature(xmlIdAttrNoNs); + const signedXml = sig.getSignedXml(); + + const verifier = new XmlDSigVerifier({ + keySelector: { publicCert }, + idAttributes: [{ localName: "customId", namespaceUri: null }], + }); + expectValidResult(verifier.verifySignature(signedXml)); + }); + + it("should fail validation when `idAttributes` `namespaceUri` is `null` but ID attribute is namespaced (should be excluded)", function () { + const sig = new SignedXml({ + privateKey, + idAttributes: ["customId"], // Sign it loosely so it signs + canonicalizationAlgorithm: CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N, + signatureAlgorithm: SIGNATURE_ALGORITHMS.RSA_SHA1, + }); + sig.addReference({ + xpath: "//*[local-name(.)='test']", + digestAlgorithm: HASH_ALGORITHMS.SHA1, + transforms: [CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N], + }); + sig.computeSignature(xmlIdAttrWithNs); + const signedXml = sig.getSignedXml(); + + // Verifier expects NO namespace, but XML has one + const verifier = new XmlDSigVerifier({ + keySelector: { publicCert }, + idAttributes: [{ localName: "customId", namespaceUri: null }], + throwOnError: false, + }); + + // Should fail because it can't find the reference target with the strict criteria + expectInvalidResult(verifier.verifySignature(signedXml), "verification failed"); + }); + + it("should validate when `idAttributes` `namespaceUri` is a string and matches the ID attribute's namespace", function () { + const sig = new SignedXml({ + privateKey, + idAttributes: [{ localName: "customId", namespaceUri: "urn:test" }], + canonicalizationAlgorithm: CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N, + signatureAlgorithm: SIGNATURE_ALGORITHMS.RSA_SHA1, + }); + sig.addReference({ + xpath: "//*[local-name(.)='test']", + digestAlgorithm: HASH_ALGORITHMS.SHA1, + transforms: [CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N], + }); + sig.computeSignature(xmlIdAttrWithNs); + const signedXml = sig.getSignedXml(); + + const verifier = new XmlDSigVerifier({ + keySelector: { publicCert }, + idAttributes: [{ localName: "customId", namespaceUri: "urn:test" }], + }); + expectValidResult(verifier.verifySignature(signedXml)); + }); + + it("should fail validation when `idAttributes` `namespaceUri` is a string but ID attribute has a different namespace (should be excluded)", function () { + const sig = new SignedXml({ + privateKey, + idAttributes: ["customId"], // Sign loosely + canonicalizationAlgorithm: CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N, + signatureAlgorithm: SIGNATURE_ALGORITHMS.RSA_SHA1, + }); + sig.addReference({ + xpath: "//*[local-name(.)='test']", + digestAlgorithm: HASH_ALGORITHMS.SHA1, + transforms: [CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N], + }); + sig.computeSignature(xmlIdAttrOtherNs); + const signedXml = sig.getSignedXml(); + + const verifier = new XmlDSigVerifier({ + keySelector: { publicCert }, + idAttributes: [{ localName: "customId", namespaceUri: "urn:test" }], + throwOnError: false, + }); + + expectInvalidResult(verifier.verifySignature(signedXml), "verification failed"); + }); + }); + }); + + describe("throwOnError option", function () { + it("should throw validation errors when throwOnError is true", function () { + const signedXml = createSignedXml(xml); + const tamperedXml = signedXml.replace("content", "tampered"); + + const verifier = new XmlDSigVerifier({ + keySelector: { publicCert }, + throwOnError: true, + }); + + expect(() => verifier.verifySignature(tamperedXml)).to.throw("verification failed"); + }); + + it("should return error details when throwOnError is false", function () { + const signedXml = createSignedXml(xml); + const tamperedXml = signedXml.replace("content", "tampered"); + + const verifier = new XmlDSigVerifier({ + keySelector: { publicCert }, + throwOnError: false, + }); + + const result = verifier.verifySignature(tamperedXml); + expectInvalidResult(result, "verification failed"); + }); + }); + + describe("security options", function () { + describe("maxTransforms", function () { + it("should validate when number of transforms is within maxTransforms", function () { + const signedXml = createSignedXml(xml); + const verifier = new XmlDSigVerifier({ + keySelector: { publicCert }, + security: { maxTransforms: 1 }, + }); + expectValidResult(verifier.verifySignature(signedXml)); + }); + + it("should fail validation when number of transforms exceeds maxTransforms", function () { + const sig = new SignedXml({ + privateKey, + canonicalizationAlgorithm: CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N, + signatureAlgorithm: SIGNATURE_ALGORITHMS.RSA_SHA1, + }); + sig.addReference({ + xpath: "//*[local-name(.)='test']", + digestAlgorithm: HASH_ALGORITHMS.SHA1, + transforms: [ + CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N, + TRANSFORM_ALGORITHMS.ENVELOPED_SIGNATURE, + ], + }); + sig.computeSignature(xml); + const signedXml = sig.getSignedXml(); + const verifier = new XmlDSigVerifier({ + keySelector: { publicCert }, + security: { maxTransforms: 1 }, + }); + expectInvalidResult(verifier.verifySignature(signedXml), "exceeds the maximum allowed"); + }); + }); + + describe("checkCertExpiration", function () { + it("should validate when certificate is not expired and checkCertExpiration is true", function () { + const signedXml = createSignedXml(xml); + // @ts-expect-error -- ignore for test purposes + const verifier = new XmlDSigVerifier({ + keySelector: { publicCert }, + security: { checkCertExpiration: true }, + }); + expectValidResult(verifier.verifySignature(signedXml)); + }); + + it("should validate when certificate is expired and checkCertExpiration is false", function () { + const signedXml = createExpiredSignedXml(xml); + // @ts-expect-error -- ignore for test purposes + const verifier = new XmlDSigVerifier({ + keySelector: { publicCert: expiredCert }, + security: { checkCertExpiration: false }, + }); + expectValidResult(verifier.verifySignature(signedXml)); + }); + + it("should fail validation when certificate is expired and checkCertExpiration is true", function () { + const signedXml = createExpiredSignedXml(xml); + const verifier = new XmlDSigVerifier({ + keySelector: { getCertFromKeyInfo: () => expiredCert }, + security: { checkCertExpiration: true }, + }); + expectInvalidResult(verifier.verifySignature(signedXml), "expired"); + }); + + it("should fail validation when certificate is not yet valid and checkCertExpiration is true", function () { + const signedXml = createFutureSignedXml(xml); + const verifier = new XmlDSigVerifier({ + keySelector: { getCertFromKeyInfo: () => futureCert }, + security: { checkCertExpiration: true }, + }); + expectInvalidResult(verifier.verifySignature(signedXml), "not yet valid"); + }); + }); + + describe("truststore", function () { + it("should validate when certificate is exactly in truststore", function () { + const signedXml = createSignedXml(xml); + const verifier = new XmlDSigVerifier({ + keySelector: { getCertFromKeyInfo: () => publicCert }, + security: { truststore: [publicCert] }, + }); + + expectValidResult(verifier.verifySignature(signedXml)); + }); + + it("should validate when certificate is trusted", function () { + const signedXml = createChainSignedXml(xml); + const verifier = new XmlDSigVerifier({ + keySelector: { getCertFromKeyInfo: () => chainPublicCert }, + security: { truststore: [rootCert] }, + }); + + expectValidResult(verifier.verifySignature(signedXml)); + }); + + it("should validate when X509Certificate is directly passed into truststore", function () { + const signedXml = createChainSignedXml(xml); + const rootX509 = new X509Certificate(rootCert); + + const verifier = new XmlDSigVerifier({ + keySelector: { getCertFromKeyInfo: () => chainPublicCert }, + security: { truststore: [rootX509] }, + }); + + expectValidResult(verifier.verifySignature(signedXml)); + }); + + it("should fail validation when certificate is not trusted", function () { + const signedXml = createSignedXml(xml); + const verifier = new XmlDSigVerifier({ + keySelector: { getCertFromKeyInfo: () => publicCert }, + security: { truststore: [rootCert] }, + }); + expectInvalidResult(verifier.verifySignature(signedXml), "not trusted"); + }); + + it("should validate truststore even when checkCertExpiration is false", function () { + const signedXml = createChainSignedXml(xml); + const verifier = new XmlDSigVerifier({ + keySelector: { getCertFromKeyInfo: () => chainPublicCert }, + security: { + checkCertExpiration: false, + truststore: [rootCert], + }, + }); + expectValidResult(verifier.verifySignature(signedXml)); + }); + + it("should validate when checkCertExpiration is false and no truststore is provided", function () { + const signedXml = createSignedXml(xml); + const verifier = new XmlDSigVerifier({ + keySelector: { getCertFromKeyInfo: () => publicCert }, + security: { + checkCertExpiration: false, + truststore: [], + }, + }); + expectValidResult(verifier.verifySignature(signedXml)); + }); + }); + + describe("signatureAlgorithms", function () { + it("should validate when signature algorithm is allowed", function () { + const signedXml = createSignedXml(xml); + const verifier = new XmlDSigVerifier({ + keySelector: { publicCert }, + security: { signatureAlgorithms: SignedXml.getDefaultAsymmetricSignatureAlgorithms() }, + }); + expectValidResult(verifier.verifySignature(signedXml)); + }); + + it("should fail validation when signature algorithm is not allowed", function () { + const signedXml = createSignedXml(xml); + const verifier = new XmlDSigVerifier({ + keySelector: { publicCert }, + security: { signatureAlgorithms: { foo: RsaSha1 } }, + }); + expectInvalidResult(verifier.verifySignature(signedXml), "signature algorithm"); + }); + }); + + describe("hashAlgorithms", function () { + it("should validate when hash algorithm is allowed", function () { + const signedXml = createSignedXml(xml); + const verifier = new XmlDSigVerifier({ + keySelector: { publicCert }, + security: { hashAlgorithms: SignedXml.getDefaultHashAlgorithms() }, + }); + expectValidResult(verifier.verifySignature(signedXml)); + }); + + it("should fail validation when hash algorithm is not allowed", function () { + const signedXml = createSignedXml(xml); + const verifier = new XmlDSigVerifier({ + keySelector: { publicCert }, + security: { hashAlgorithms: { foo: Sha1 } }, + }); + expectInvalidResult(verifier.verifySignature(signedXml), "hash algorithm"); + }); + }); + + describe("transformAlgorithms", function () { + it("should validate when transform algorithms are allowed", function () { + const signedXml = createSignedXml(xml); + const verifier = new XmlDSigVerifier({ + keySelector: { publicCert }, + security: { transformAlgorithms: SignedXml.getDefaultTransformAlgorithms() }, + }); + expectValidResult(verifier.verifySignature(signedXml)); + }); + + it("should fail validation when a transform algorithm is not allowed", function () { + const signedXml = createSignedXml(xml); + const verifier = new XmlDSigVerifier({ + keySelector: { publicCert }, + security: { transformAlgorithms: { foo: EnvelopedSignature } }, + }); + expectInvalidResult(verifier.verifySignature(signedXml), "transform algorithm"); + }); + }); + + describe("canonicalizationAlgorithms", function () { + it("should validate when canonicalization algorithms are allowed", function () { + const signedXml = createSignedXml(xml); + const verifier = new XmlDSigVerifier({ + keySelector: { publicCert }, + security: { + canonicalizationAlgorithms: SignedXml.getDefaultCanonicalizationAlgorithms(), + }, + }); + expectValidResult(verifier.verifySignature(signedXml)); + }); + + it("should fail validation when a canonicalization algorithm is not allowed", function () { + const signedXml = createSignedXml(xml); + const verifier = new XmlDSigVerifier({ + keySelector: { publicCert }, + security: { canonicalizationAlgorithms: { foo: ExclusiveCanonicalization } }, + }); + expectInvalidResult(verifier.verifySignature(signedXml), "canonicalization algorithm"); + }); + }); + }); + + describe("signatureNode parameter", function () { + it("should fail when XML has no signatures", function () { + const unsignedXml = "content"; + + const verifier = new XmlDSigVerifier({ + keySelector: { publicCert }, + throwOnError: false, + }); + + const result = verifier.verifySignature(unsignedXml); + expectInvalidResult(result, "No Signature element found"); + }); + + it("should validate when signatureNode is provided directly", function () { + const signedXml = createSignedXml(xml); + const doc = new DOMParser().parseFromString(signedXml, "application/xml"); + const signatureNode = doc.getElementsByTagNameNS(XMLDSIG_URIS.NAMESPACES.ds, "Signature")[0]; + + const verifier = new XmlDSigVerifier({ + keySelector: { publicCert }, + }); + + const result = verifier.verifySignature(signedXml, signatureNode); + expectValidResult(result); + }); + + it("should fail when XML has multiple signatures but no signatureNode is specified", function () { + // Create XML with two different test elements + const xmlWithTwoElements = + "content1content2"; + + // Create first signature for first test element + const sig1 = new SignedXml({ + privateKey, + canonicalizationAlgorithm: CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N, + signatureAlgorithm: SIGNATURE_ALGORITHMS.RSA_SHA1, + }); + sig1.addReference({ + xpath: "//*[@id='1']", + digestAlgorithm: HASH_ALGORITHMS.SHA1, + transforms: [CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N], + }); + sig1.computeSignature(xmlWithTwoElements, { + location: { reference: "/root", action: "append" }, + }); + const xmlWithFirstSig = sig1.getSignedXml(); + + // Create second signature for second test element + const sig2 = new SignedXml({ + privateKey: chainPrivateKey, + canonicalizationAlgorithm: CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N, + signatureAlgorithm: SIGNATURE_ALGORITHMS.RSA_SHA1, + }); + sig2.addReference({ + xpath: "//*[@id='2']", + digestAlgorithm: HASH_ALGORITHMS.SHA1, + transforms: [CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N], + }); + sig2.computeSignature(xmlWithFirstSig, { + location: { reference: "/root", action: "append" }, + }); + const xmlWithTwoSigs = sig2.getSignedXml(); + + const verifier = new XmlDSigVerifier({ + keySelector: { publicCert }, + throwOnError: false, + }); + + const result = verifier.verifySignature(xmlWithTwoSigs); + expectInvalidResult(result, "Multiple Signature elements found"); + }); + + it("should validate specific signature when XML has multiple signatures", function () { + // Create XML with two different test elements + const xmlWithTwoElements = + "content1content2"; + + // Create first signature for first test element + const sig1 = new SignedXml({ + privateKey, + canonicalizationAlgorithm: CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N, + signatureAlgorithm: SIGNATURE_ALGORITHMS.RSA_SHA1, + }); + sig1.addReference({ + xpath: "//*[@id='1']", + digestAlgorithm: HASH_ALGORITHMS.SHA1, + transforms: [CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N], + }); + sig1.computeSignature(xmlWithTwoElements, { + location: { reference: "/root", action: "append" }, + }); + const xmlWithFirstSig = sig1.getSignedXml(); + + // Create second signature for second test element + const sig2 = new SignedXml({ + privateKey: chainPrivateKey, // Use different key for second signature + canonicalizationAlgorithm: CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N, + signatureAlgorithm: SIGNATURE_ALGORITHMS.RSA_SHA1, + }); + sig2.addReference({ + xpath: "//*[@id='2']", + digestAlgorithm: HASH_ALGORITHMS.SHA1, + transforms: [CANONICALIZATION_ALGORITHMS.EXCLUSIVE_C14N], + }); + sig2.computeSignature(xmlWithFirstSig, { + location: { reference: "/root", action: "append" }, + }); + const xmlWithTwoSigs = sig2.getSignedXml(); + const doc = new DOMParser().parseFromString(xmlWithTwoSigs, "application/xml"); + const signatureNodes = doc.getElementsByTagNameNS(XMLDSIG_URIS.NAMESPACES.ds, "Signature"); + + expect(signatureNodes.length).to.equal(2); + + // Verify first signature with first key + const verifier1 = new XmlDSigVerifier({ + keySelector: { publicCert }, + }); + const result1 = verifier1.verifySignature(xmlWithTwoSigs, signatureNodes[0]); + expectValidResult(result1); + + // Verify second signature with second key + const verifier2 = new XmlDSigVerifier({ + keySelector: { publicCert: chainPublicCert }, + }); + const result2 = verifier2.verifySignature(xmlWithTwoSigs, signatureNodes[1]); + expectValidResult(result2); + }); + }); + + describe("static verifySignature method", function () { + it("should return success result when throwOnError is false and no error occurs", function () { + const signedXml = createSignedXml(xml); + + const result = XmlDSigVerifier.verifySignature(signedXml, { + keySelector: { publicCert }, + throwOnError: false, + }); + + expectValidResult(result); + }); + + it("should return error result when throwOnError is false and error occurs", function () { + const signedXml = createSignedXml(xml); + const tamperedXml = signedXml.replace("content", "tampered"); + + const result = XmlDSigVerifier.verifySignature(tamperedXml, { + keySelector: { publicCert }, + throwOnError: false, + }); + + expectInvalidResult(result, "verification failed"); + }); + + it("should return success result when throwOnError is true and no error occurs", function () { + const signedXml = createSignedXml(xml); + + const result = XmlDSigVerifier.verifySignature(signedXml, { + keySelector: { publicCert }, + throwOnError: true, + }); + + expectValidResult(result); + }); + + it("should throw error when throwOnError is true and error occurs", function () { + const signedXml = createSignedXml(xml); + const tamperedXml = signedXml.replace("content", "tampered"); + + expect(() => { + XmlDSigVerifier.verifySignature(tamperedXml, { + keySelector: { publicCert }, + throwOnError: true, + }); + }).to.throw("verification failed"); + }); + + it("should use default throwOnError (false) when not explicitly provided", function () { + const result = XmlDSigVerifier.verifySignature("content", { + keySelector: { + getCertFromKeyInfo: null as never, + }, + }); + + expectInvalidResult(result, "XmlDSigVerifier requires a valid getCertFromKeyInfo function."); + }); + }); +});