From 93743da314967fdf96eacd31b2b6692b389d7eec Mon Sep 17 00:00:00 2001 From: Chris Barth Date: Tue, 13 Oct 2026 14:00:22 -0400 Subject: [PATCH 1/3] chore: deprecate signing and verifying a document that has a DOCTYPE xmldom does not process a document type declaration, so an entity or a default attribute it declares is missing from the canonical form, which XML 1.0 and Canonical XML require to include both. 7.0 will refuse such a document. Print a DeprecationWarning from computeSignature() and checkSignature() until then. Co-Authored-By: Claude Opus 5.5 --- README.md | 9 ++++++++ src/signed-xml.ts | 12 ++++++++++ test/document-type-declaration.spec.ts | 31 ++++++++++++++++++++++++++ 3 files changed, 52 insertions(+) create mode 100644 test/document-type-declaration.spec.ts diff --git a/README.md b/README.md index f5309ed9..94b3fa3d 100644 --- a/README.md +++ b/README.md @@ -90,6 +90,15 @@ The `enveloped-signature` transform removes only the `Signature` element being v - `getCanonXml()` finds the loaded signature in the node's document the same way, and removes nothing when the signature is not there. +### Documents with a DOCTYPE + +`computeSignature()` and `checkSignature()` do not process a document type declaration. An entity +it declares is not expanded and a default attribute it declares is not added, though +[Canonical XML](https://www.w3.org/TR/xml-c14n#Terminology) requires both, so the canonical form is +not the one an implementation that processes the declaration computes. Signing or verifying a +document that has a DOCTYPE is deprecated and will throw in 7.0. Until then it prints a +`DeprecationWarning`. + ### Malformed certificates A certificate whose encapsulated data is not base64 is rejected rather than decoded as far as it diff --git a/src/signed-xml.ts b/src/signed-xml.ts index 841e23c1..3a76e4c8 100644 --- a/src/signed-xml.ts +++ b/src/signed-xml.ts @@ -84,6 +84,12 @@ const warnOriginalXmlWithIds = deprecate( "XML_CRYPTO_GET_ORIGINAL_XML_WITH_IDS", ); +const warnDocumentTypeDeclaration = deprecate( + () => {}, + "Signing or verifying a document that has a document type declaration (``) is deprecated and will throw in version 7.0. The declaration is not processed, so an entity it declares is not expanded and a default attribute it declares is not added to what is signed or verified.", + "XML_CRYPTO_DOCUMENT_TYPE_DECLARATION", +); + const warnValidateElementAgainstReferences = deprecate( () => {}, "`validateElementAgainstReferences()` is deprecated and will be removed in a future version. After `checkSignature()` succeeds, use the XML that `getSignedReferences()` returns instead of nodes from the original document.", @@ -335,6 +341,9 @@ export class SignedXml { this.signedXml = xml; const doc = new xmldom.DOMParser().parseFromString(xml); + if (doc.doctype != null) { + warnDocumentTypeDeclaration(); + } // Reset the references as only references from our re-parsed signedInfo node can be trusted this.references = []; @@ -995,6 +1004,9 @@ export class SignedXml { } const doc = new xmldom.DOMParser().parseFromString(xml); + if (doc.doctype != null) { + warnDocumentTypeDeclaration(); + } let xmlNsAttr = "xmlns"; const signatureAttrs: string[] = []; let currentPrefix: string; diff --git a/test/document-type-declaration.spec.ts b/test/document-type-declaration.spec.ts new file mode 100644 index 00000000..dea80068 --- /dev/null +++ b/test/document-type-declaration.spec.ts @@ -0,0 +1,31 @@ +import { SignedXml } from "../src/index"; +import * as fs from "fs"; +import { expect } from "chai"; + +describe("A document with a DOCTYPE, which 7.0 refuses", function () { + it("is still signed and verified", function () { + 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", + }); + sig.addReference({ + xpath: "/*", + digestAlgorithm: "http://www.w3.org/2001/04/xmlenc#sha256", + transforms: [ + "http://www.w3.org/2000/09/xmldsig#enveloped-signature", + "http://www.w3.org/2001/10/xml-exc-c14n#", + ], + }); + sig.computeSignature("content"); + const signedXml = sig.getSignedXml(); + + const verifier = new SignedXml({ + publicCert: fs.readFileSync("./test/static/client_public.pem"), + }); + verifier.loadSignature(sig.getSignatureXml()); + + expect(signedXml).to.include(""); + expect(verifier.checkSignature(signedXml)).to.be.true; + }); +}); From a0f1886f3cef81284351245e159068b89b4714b7 Mon Sep 17 00:00:00 2001 From: Chris Barth Date: Tue, 13 Oct 2026 14:30:38 -0400 Subject: [PATCH 2/3] test: sign and verify a document whose DOCTYPE declares a default attribute Co-Authored-By: Claude Opus 5.5 --- test/document-type-declaration.spec.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/test/document-type-declaration.spec.ts b/test/document-type-declaration.spec.ts index dea80068..a2e70719 100644 --- a/test/document-type-declaration.spec.ts +++ b/test/document-type-declaration.spec.ts @@ -3,7 +3,7 @@ import * as fs from "fs"; import { expect } from "chai"; describe("A document with a DOCTYPE, which 7.0 refuses", function () { - it("is still signed and verified", function () { + it("is still signed and verified, without the default attribute it declares", function () { const sig = new SignedXml({ privateKey: fs.readFileSync("./test/static/client.pem"), canonicalizationAlgorithm: "http://www.w3.org/2001/10/xml-exc-c14n#", @@ -17,7 +17,9 @@ describe("A document with a DOCTYPE, which 7.0 refuses", function () { "http://www.w3.org/2001/10/xml-exc-c14n#", ], }); - sig.computeSignature("content"); + sig.computeSignature( + ']>content', + ); const signedXml = sig.getSignedXml(); const verifier = new SignedXml({ @@ -26,6 +28,7 @@ describe("A document with a DOCTYPE, which 7.0 refuses", function () { verifier.loadSignature(sig.getSignatureXml()); expect(signedXml).to.include(""); + expect(signedXml).to.not.include("role"); expect(verifier.checkSignature(signedXml)).to.be.true; }); }); From 89373d9566543de08227f88b32e3d809ef3499d9 Mon Sep 17 00:00:00 2001 From: Chris Barth Date: Tue, 13 Oct 2026 19:11:01 -0400 Subject: [PATCH 3/3] chore: drop the test of signing and verifying a document with a DOCTYPE Co-Authored-By: Claude Opus 5.5 --- test/document-type-declaration.spec.ts | 34 -------------------------- 1 file changed, 34 deletions(-) delete mode 100644 test/document-type-declaration.spec.ts diff --git a/test/document-type-declaration.spec.ts b/test/document-type-declaration.spec.ts deleted file mode 100644 index a2e70719..00000000 --- a/test/document-type-declaration.spec.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { SignedXml } from "../src/index"; -import * as fs from "fs"; -import { expect } from "chai"; - -describe("A document with a DOCTYPE, which 7.0 refuses", function () { - it("is still signed and verified, without the default attribute it declares", function () { - 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", - }); - sig.addReference({ - xpath: "/*", - digestAlgorithm: "http://www.w3.org/2001/04/xmlenc#sha256", - transforms: [ - "http://www.w3.org/2000/09/xmldsig#enveloped-signature", - "http://www.w3.org/2001/10/xml-exc-c14n#", - ], - }); - sig.computeSignature( - ']>content', - ); - const signedXml = sig.getSignedXml(); - - const verifier = new SignedXml({ - publicCert: fs.readFileSync("./test/static/client_public.pem"), - }); - verifier.loadSignature(sig.getSignatureXml()); - - expect(signedXml).to.include(""); - expect(signedXml).to.not.include("role"); - expect(verifier.checkSignature(signedXml)).to.be.true; - }); -});