From 5200d71457bab9a14c98ee596e3fe13ed6bcae4a Mon Sep 17 00:00:00 2001 From: shunkica Date: Sat, 25 Oct 2025 10:46:20 +0200 Subject: [PATCH 1/2] feat: add XmlDSigVerifier wrapper for SignedXml Types: (old types are re-exported and deprecated - remove deprecated types in next breaking change) - separate CanonicalizationOrTransformationAlgorithm into CanonicalizationAlgorithm and TransformAlgorithm - separate CanonicalizationOrTransformationAlgorithmProcessOptions into CanonicalizationAlgorithmOptions and TransformAlgorithmOptions - separate CanonicalizationOrTransformAlgorithmType into CanonicalizationAlgorithmURI and TransformAlgorithmURI - rename CanonicalizationAlgorithmType into CanonicalizationAlgorithmURI - rename SignatureAlgorithmType into SignatureAlgorithmURI - rename SignatureAlgorithmType into SignatureAlgorithmURI - rename HashAlgorithmType into HashAlgorithmURI - introduce Record maps: CanonicalizationAlgorithmMap, HashAlgorithmMap, SignatureAlgorithmMap, TransformAlgorithmMap - introduce VerificationIdAttributeType, SignatureIdAttributeType, IdAttributeType - introduce KeySelectorFunction type - introduce XmlDsigVerifier specific types: CertificateKeySelector, KeyInfoKeySelector, SharedSecretKeySelector, KeySelector, XmlDSigVerifierOptionsBase, XmlDSigVerifierSecurityOptions, KeyInfoXmlDSigSecurityOptions, KeyInfoXmlDSigVerifierOptions, PublicCertXmlDSigVerifierOptions, SharedSecretXmlDSigVerifierOptions, XmlDSigVerifierOptions, SuccessfulXmlDsigVerificationResult, FailedXmlDsigVerificationResult, XmlDsigVerificationResult Constants: - replace string literal URIs with constants from xmldsig-uris.ts (see XMLDSIG_URIS) SignedXml: - add maxTransforms option - set maximum number of allowed transforms on a reference (throws if limit is exceeded) - add idAttributes option - to override default id attributes - can use use fully qualified idAttributes (see IdAttributeType) - use first idAttribute in idAttributes array when generating new id attribute during signature - if "null" is set as the namespaceURI of an id attribute, it will only look for attributes without a namespace - introduce getDefaultCanonicalizationAlgorithms(), getDefaultHashAlgorithms(), getDefaultAsymmetricSignatureAlgorithms(), getDefaultSymmetricSignatureAlgorithms(), getDefaultTransformAlgorithms(), getDefaultIdAttributes() - add allowedSignatureAlgorithms option - overrides default signature algorithms - add allowedHashAlgorithms option - overrides default hash algorithms - add allowedCanonicalizationAlgorithms option - overrides default canonicalization algorithms - add allowedTransformAlgorithms option - overrides default transform algorithms - split findCanonicalizationAlgorithm into findCanonicalizationAlgorithm and findTransformAlgorithm ( change implementation of findTransformAlgorithm in next breaking change ) XmlDSigVerifier: - introduce the XmlDSigVerifier wrapper for SignedXml, a safer and simpler way to verify XML signatures Docs: - create the XMLDSIG_VERIFIER.md file with detailed instructions on using the XmlDSigVerifier Tests: - introduce the xmldsig-verifier.spec.ts tests for the XmlDSigVerifier Development: - introduce default development node version via .nvmrc Dependencies: - update xpath from 0.0.33 to 0.0.34 - the previous version of xpath had a bug where namespace-uri(.)='' would not find a non-namespaced attribute, instead it would find it using namespace-uri(.)='undefined' (the literal string undefined) --- .nvmrc | 1 + README.md | 25 + XMLDSIG_VERIFIER.md | 157 +++ package-lock.json | 15 +- package.json | 2 +- src/c14n-canonicalization.ts | 19 +- src/enveloped-signature.ts | 18 +- src/exclusive-canonicalization.ts | 20 +- src/hash-algorithms.ts | 7 +- src/index.ts | 2 + src/signature-algorithms.ts | 11 +- src/signed-xml.ts | 318 ++++-- src/types.ts | 410 +++++-- src/utils.ts | 21 +- src/xmldsig-uris.ts | 60 ++ src/xmldsig-verifier.ts | 312 ++++++ test/c14n-non-exclusive-unit-tests.spec.ts | 2 +- test/c14nWithComments-unit-tests.spec.ts | 12 +- test/canonicalization-unit-tests.spec.ts | 17 +- test/document-tests.spec.ts | 6 +- test/hmac-tests.spec.ts | 16 +- test/key-info-tests.spec.ts | 14 +- test/saml-response-tests.spec.ts | 24 +- test/signature-integration-tests.spec.ts | 30 +- test/signature-object-tests.spec.ts | 152 ++- test/signature-unit-tests.spec.ts | 233 ++-- test/static/chain_client.crt.pem | 21 + test/static/chain_client.key.pem | 28 + test/static/chain_root.crt.pem | 21 + test/static/chain_root.crt.srl | 1 + test/static/chain_root.key.pem | 28 + test/static/expired_certificate.crt.pem | 21 + test/static/expired_certificate.key.pem | 28 + test/static/future_certificate.crt.pem | 18 + test/static/future_certificate.csr.pem | 15 + test/static/future_certificate.key.pem | 28 + test/utils-tests.spec.ts | 56 + ...program-repro-misc-validation-and-canon.cs | 50 +- test/wsfed-metadata-tests.spec.ts | 4 +- test/xmldsig-verifier.spec.ts | 999 ++++++++++++++++++ 40 files changed, 2705 insertions(+), 517 deletions(-) create mode 100644 .nvmrc create mode 100644 XMLDSIG_VERIFIER.md create mode 100644 src/xmldsig-uris.ts create mode 100644 src/xmldsig-verifier.ts create mode 100644 test/static/chain_client.crt.pem create mode 100644 test/static/chain_client.key.pem create mode 100644 test/static/chain_root.crt.pem create mode 100644 test/static/chain_root.crt.srl create mode 100644 test/static/chain_root.key.pem create mode 100644 test/static/expired_certificate.crt.pem create mode 100644 test/static/expired_certificate.key.pem create mode 100644 test/static/future_certificate.crt.pem create mode 100644 test/static/future_certificate.csr.pem create mode 100644 test/static/future_certificate.key.pem create mode 100644 test/xmldsig-verifier.spec.ts 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."); + }); + }); +}); From 92eb4178fe35273d661b3f9cf6e358f0cd9fc0a2 Mon Sep 17 00:00:00 2001 From: shunkica Date: Sun, 28 Dec 2025 13:06:53 +0100 Subject: [PATCH 2/2] fix: Support nested enveloped signature location (#525) Update XPath query to find Signature elements at any depth within the document, not just direct children. This fixes an issue where signatures nested within other elements were not properly detected and removed. --- src/enveloped-signature.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/enveloped-signature.ts b/src/enveloped-signature.ts index 389a0627..8dd1226a 100644 --- a/src/enveloped-signature.ts +++ b/src/enveloped-signature.ts @@ -17,7 +17,7 @@ export class EnvelopedSignature implements TransformAlgorithm { process(node: Node, options: TransformAlgorithmOptions): Node { if (null == options.signatureNode) { const signature = xpath.select1( - `./*[local-name(.)='Signature' and namespace-uri(.)='${XMLDSIG_URIS.NAMESPACES.ds}']`, + `.//*[local-name(.)='Signature' and namespace-uri(.)='${XMLDSIG_URIS.NAMESPACES.ds}']`, node, ); if (isDomNode.isNodeLike(signature) && signature.parentNode) {