Skip to content

Conversation

shunkica
Copy link
Contributor

@shunkica shunkica commented Jun 20, 2025

Added support for custom <Object> elements within the <Signature> element via a new

getObjectContent?(): Array<{ content: string; attributes?: ObjectAttributes; }> | null method.

This is specified in xmldsig-core1/#sec-Object

Modified the signature creation process to sign and reference these internal objects.

These changes are also a step towards XAdES support.

Summary by CodeRabbit

  • New Features

    • Support embedding custom ds:Object elements in signatures via constructor options; references can include optional Id and Type; computeSignature accepts prefix, attrs, location, and existingPrefixes for flexible signature output.
  • Bug Fixes

    • Two-phase handling for unresolved references: unresolved entries are deferred during initial assembly and finalized post-insertion to ensure correct Reference nodes and digests.
  • Documentation

    • README updated with new API shapes and a usage example for embedding and referencing Object nodes.
  • Tests

    • New unit and integration tests covering Object embedding, reference attributes, signature validity, and XAdES scenarios.

@shunkica
Copy link
Contributor Author

@cjbarth I am happy with this PR. Let me know if you need any changes for this to be merged.

@shunkica
Copy link
Contributor Author

I have added the option to specify custom Id and Type attributes on references via the addReference method, as specified in xmldsig-core1/#sec-Reference

This is required to allow XAdES implementations because the Reference must contain Type="http://uri.etsi.org/01903#SignedProperties"

@srd90 srd90 mentioned this pull request Jul 2, 2025
@cjbarth
Copy link
Contributor

cjbarth commented Jul 8, 2025

@imedacf, does this PR address your issue? Would you like to review this code?

@shunkica , are you using these features in a production system already, perhaps with your own fork?

@shunkica
Copy link
Contributor Author

shunkica commented Jul 8, 2025

@imedacf, does this PR address your issue? Would you like to review this code?

@shunkica , are you using these features in a production system already, perhaps with your own fork?

Not using them as of yet but I plan on it in the coming months as things develop here with some legislature changes.

@shunkica
Copy link
Contributor Author

@cjbarth I have addressed your review comments and I am keen to get this merged.

Additionally, in an effort to simplify using this functionality, I have removed the need for the isSignatureReference flag by tracking which references were processed in the first pass.

Copy link

coderabbitai bot commented Aug 15, 2025

Walkthrough

Adds ds:Object embedding via a new objects SignedXml option, updates addReference to accept an options object with optional id/type, implements deferred two‑pass reference processing to finalize unresolved references, and exposes new computeSignature options for prefix/placement/attributes.

Changes

Cohort / File(s) Summary
Documentation
README.md
Documents objects constructor option; updates addReference to accept an options object (adds id/type); documents new computeSignature options (prefix, attrs, location, existingPrefixes); adds examples for embedding and referencing ds:Object nodes.
Core Types
src/types.ts
Adds ObjectAttributes interface; extends SignedXmlOptions with objects?: Array<{ content: string; attributes?: ObjectAttributes }>; extends Reference with optional id?: string and type?: string.
Signature Engine
src/signed-xml.ts
Adds public objects property and getObjects(prefix) rendering; constructor accepts and wires objects; addReference now takes an options object and records id/type and wasProcessed; implements two‑pass handling via processSignatureReferences to finalize unresolved references and compute digests; uses DOM APIs to render Reference and Object nodes and integrates them into the final Signature.
DOM Utils
src/utils.ts
Adds exported helper isDescendantOf(node, parent) to detect DOM ancestry for reference validation.
Object & XAdES Tests
test/signature-object-tests.spec.ts
Adds extensive tests for ds:Object embedding and XAdES QualifyingProperties: object attributes (Id, MimeType, Encoding), base64 handling, referencing Objects by Id, InclusiveNamespaces, and end-to-end signature verification across digest/canonicalization scenarios.
Unit Tests (Reference attrs & missing xpath)
test/signature-unit-tests.spec.ts
Adds unit tests verifying Reference Id and Type attributes emission and that computeSignature throws when an XPath matches no nodes.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor Caller
  participant SX as SignedXml
  participant DOM as DOM
  participant C14N as Canonicalizer
  participant DG as Digest

  Caller->>SX: new SignedXml({ objects, ... })
  Caller->>SX: addReference({ xpath, transforms, digestAlgorithm, id?, type? })
  Caller->>SX: computeSignature(xml, options)

  SX->>DOM: Build Signature skeleton (SignedInfo, SignatureValue, KeyInfo)
  SX->>DOM: Insert ds:Object elements (getObjects)
  SX->>DOM: Create initial Reference placeholders (mark wasProcessed=false if unresolved)

  SX->>DOM: processSignatureReferences()
  loop For each unprocessed Reference
    SX->>DOM: Locate target node (by xpath/URI)
    SX->>C14N: Apply transforms / canonicalize
    C14N-->>SX: Canonical form
    SX->>DG: Compute digest
    DG-->>SX: DigestValue
    SX->>DOM: Append/complete Reference with DigestMethod + DigestValue
  end

  SX->>C14N: Canonicalize SignedInfo
  SX->>DG: Compute SignatureValue
  SX-->>Caller: Return Signature XML
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

I nibbled bytes and signed the leaves,
Tucked Objects snug in XML sleeves.
Two‑pass hops to find each part,
Ids and Types stitched from the heart.
A carrot grin — the signature weaves. 🥕✨

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🔭 Outside diff range comments (1)
src/signed-xml.ts (1)

1134-1184: CreateReferences: validate Id/Type to avoid malformed XML in attributes

ref.id and ref.type are appended as raw attribute values. If they contain quotes or angle brackets, the signature becomes invalid XML.

Apply this diff to validate before concatenation:

-        if (ref.id) {
+        if (ref.id) {
+          if (/[<>&"]/.test(ref.id)) {
+            throw new Error('Invalid character in Reference Id attribute');
+          }
           referenceAttrs += ` Id="${ref.id}"`;
         }
 
-        if (ref.type) {
+        if (ref.type) {
+          if (/[<>&"]/.test(ref.type)) {
+            throw new Error('Invalid character in Reference Type attribute');
+          }
           referenceAttrs += ` Type="${ref.type}"`;
         }

Also, consider switching this method to DOM construction (like processSignatureReferences) for consistency and to avoid manual string concatenation issues.

🧹 Nitpick comments (8)
src/types.ts (1)

46-60: New ObjectAttributes type looks good; consider clarifying allowed attribute names

The interface aligns with xmldsig spec (Id, MimeType, Encoding) and allows custom attributes. No functional issues spotted.

To reduce ambiguity, add a brief note in the JSDoc that attribute names are emitted verbatim and consumers must ensure XML-safe values (no quotes, angle brackets, or ampersands), or that the library will validate/escape them (see suggested changes in src/signed-xml.ts).

test/signature-unit-tests.spec.ts (1)

1282-1319: Good coverage for Reference Id/Type attributes

This test ensures Id and Type are present when provided. Consider adding a negative test asserting they’re omitted when not set, and one that guards against invalid characters (quotes/angle brackets) if you add validation as suggested.

README.md (2)

264-265: Docs: objects option clearly documented

The new objects option is well explained. To set expectations, consider adding a note that attribute values must be valid XML attribute values (no quotes, <, >, &), or that the library will throw/escape.


540-575: Great new section on adding custom Objects; add a short safety note

Example is clear and mirrors API. Add one sentence reminding users that content is inserted as-is; provide guidance to wrap non-XML content appropriately (e.g., base64 with Encoding/MimeType) or ensure it’s valid XML.

test/signature-object-tests.spec.ts (2)

94-101: Rename test to reflect current API (objects), not getObjectContent

The description mentions getObjectContent but the code uses the objects option. Rename for clarity.

Apply this diff:

-  it("should handle empty or null getObjectContent", function () {
+  it("should handle missing or empty objects option", function () {

223-313: SHA256 path is sound; consider direct use of registered algorithm instead of importing class

You validate both Reference DigestMethod and SignatureMethod URIs and end-to-end signature. As a minor cleanup, since the algorithms are already registered within SignedXml, you could avoid importing Sha256 entirely by hashing using Node crypto directly for the XAdES case (or import from src, per above fix).

src/signed-xml.ts (2)

994-996: Second-pass reference processing is a nice simplification

Deferring unresolved references and appending them later removes the need for a special flag and supports signature-internal references. Be sure to update wasProcessed in this pass (see below).


1324-1436: Finalizing unprocessed references: mark processed and keep model consistent

After appending Reference nodes, mark each ref as processed and (optionally) set ref.digestValue to the computed value to keep in-memory state aligned with the XML.

Apply this diff at the end of the inner loop and after processing each ref:

-        // Append the reference element to SignedInfo
+        // Append the reference element to SignedInfo
         signedInfoNode.appendChild(referenceElem);
+        // Update in-memory model
+        ref.digestValue = digestValueElem.textContent;
+        ref.wasProcessed = true;
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 9b91edf and 55dc24d.

📒 Files selected for processing (5)
  • README.md (2 hunks)
  • src/signed-xml.ts (14 hunks)
  • src/types.ts (3 hunks)
  • test/signature-object-tests.spec.ts (1 hunks)
  • test/signature-unit-tests.spec.ts (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (2)
test/signature-object-tests.spec.ts (2)
src/signed-xml.ts (1)
  • SignedXml (30-1463)
src/hash-algorithms.ts (1)
  • Sha256 (17-28)
src/signed-xml.ts (1)
src/types.ts (2)
  • ObjectAttributes (50-59)
  • Reference (124-160)
🔇 Additional comments (9)
src/types.ts (2)

76-76: Option for objects on SignedXmlOptions is a solid API surface

Introducing objects?: Array<{ content: string; attributes?: ObjectAttributes }> is clear and future-proof. It also keeps the signing data close to the signer config, which is nice.


146-151: Expose Reference Id/Type (Id/Type attributes) — OK; ensure downstream escapes/validation

Extending Reference with id?: string and type?: string is correct and matches xmldsig-core1. Please ensure these values are validated/escaped where rendered (see src/signed-xml.ts comments) to avoid producing malformed XML when values contain quotes or angle brackets.

README.md (1)

272-278: Docs: addReference now includes id/type — good

This clarifies the API expansion and XAdES use case for Type. Looks consistent with the implementation.

test/signature-object-tests.spec.ts (2)

155-167: Base64 content under Encoding is correctly exercised

Good scenario covering Object Encoding/MimeType/Id and end-to-end validity.


669-745: Excellent XAdES flow coverage

The test builds QualifyingProperties in an Object, references SignedProperties with Type, and uses SHA-256 RSA-SHA256; also verifies with public cert. This should help prevent regressions.

src/signed-xml.ts (4)

60-61: Public objects property: OK

Exposing objects?: Array<{ content: string; attributes?: ObjectAttributes }> on the class is consistent with options and tests. No issues here.


75-75: Track wasProcessed on Reference — OK, but ensure second pass updates it

The new flag is useful to avoid double-processing. In the second-pass processSignatureReferences, remember to mark processed references as well (see suggestion later in that method).


979-981: Including Object elements in the Signature: good placement

this.getObjects(prefix) is inserted after KeyInfo and before closing Signature. This matches spec flexibility for ds:Object placement.


1189-1216: Transform pipeline: OK; note on enveloped-signature in second pass

Because getCanonReferenceXml sets options.signatureNode = this.signatureNode, and this.signatureNode is only initialized after this method (in computeSignature), references that include the enveloped-signature transform in the second pass would not remove the signature element. Current tests don’t hit that path, but it’s a corner case.

If you plan to support enveloped-signature in second pass, set this.signatureNode = signatureDoc.documentElement just before calling processSignatureReferences, then restore it after — or refactor getCanonReferenceXml to accept an explicit signature node via options.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (8)
test/signature-object-tests.spec.ts (8)

55-61: Select ds:Object by namespace and scope it under ds:Signature

Current XPath matches any Object in any namespace. Tighten it to ensure we only validate ds:Object elements within ds:Signature.

Apply this change (and consider the same pattern in other tests that select Object elements):

-    const objectNodes = xpath.select("//*[local-name(.)='Object']", doc);
+    const objectNodes = xpath.select(
+      "//*[local-name(.)='Signature' and namespace-uri(.)='http://www.w3.org/2000/09/xmldsig#']/*[local-name(.)='Object' and namespace-uri(.)='http://www.w3.org/2000/09/xmldsig#']",
+      doc,
+    );

69-75: Avoid brittle exact text match for Object content

Direct equality can fail if the serializer inserts whitespace. Trimming makes the check resilient.

-    expect(secondObject.textContent).to.equal("Plain text content");
+    expect(secondObject.textContent?.trim()).to.equal("Plain text content");

94-94: Rename test to reflect what it validates (objects option, not getObjectContent)

This test doesn’t exercise getObjectContent; it verifies undefined/empty objects option. Renaming improves clarity. If you want, I can add a separate test that exercises getObjectContent().

-  it("should handle empty or null getObjectContent", function () {
+  it("should handle undefined or empty objects option", function () {

121-123: Prefer isDomNode assertions and namespace-scoped XPath for consistency

Keep type guards consistent and ensure the selection targets ds:Object under ds:Signature.

-    const objectNodesWithNull = xpath.select("//*[local-name(.)='Object']", docWithNull);
-    expect(objectNodesWithNull).to.be.an("array").that.is.empty;
+    const objectNodesWithNull = xpath.select(
+      "//*[local-name(.)='Signature' and namespace-uri(.)='http://www.w3.org/2000/09/xmldsig#']/*[local-name(.)='Object' and namespace-uri(.)='http://www.w3.org/2000/09/xmldsig#']",
+      docWithNull,
+    );
+    isDomNode.assertIsArrayOfNodes(objectNodesWithNull);
+    expect(objectNodesWithNull.length).to.equal(0);

147-149: Repeat consistent type guards and namespace-scoped XPath

Mirror the previous change for the “empty objects” branch.

-    const objectNodesWithEmpty = xpath.select("//*[local-name(.)='Object']", docWithEmpty);
-    expect(objectNodesWithEmpty).to.be.an("array").that.is.empty;
+    const objectNodesWithEmpty = xpath.select(
+      "//*[local-name(.)='Signature' and namespace-uri(.)='http://www.w3.org/2000/09/xmldsig#']/*[local-name(.)='Object' and namespace-uri(.)='http://www.w3.org/2000/09/xmldsig#']",
+      docWithEmpty,
+    );
+    isDomNode.assertIsArrayOfNodes(objectNodesWithEmpty);
+    expect(objectNodesWithEmpty.length).to.equal(0);

589-667: This test largely duplicates the previous “add a reference to an Object element” case

Both tests set up one Object, two references (doc + object), and verify presence and validity. Consider consolidating to reduce suite run time and maintenance.


731-745: Add assertions for XAdES: SignedProperties Reference Type/URI and QualifyingProperties Target

Strengthen the XAdES test by asserting the SignedProperties Reference has the correct Type and URI and that QualifyingProperties.Target points to the Signature Id. Also assert there are exactly two SignedInfo/Reference elements.

     const signedXml = sig.getSignedXml();
     const signedDoc = new xmldom.DOMParser().parseFromString(signedXml);
     const signatureNode = xpath.select1(
       "//*[local-name(.)='Signature' and namespace-uri(.)='http://www.w3.org/2000/09/xmldsig#']",
       signedDoc,
     );
     isDomNode.assertIsNodeLike(signatureNode);
 
+    // Validate references and XAdES wiring
+    const referenceNodes = xpath.select(
+      "//*[local-name(.)='SignedInfo']/*[local-name(.)='Reference']",
+      signedDoc,
+    );
+    isDomNode.assertIsArrayOfNodes(referenceNodes);
+    expect(referenceNodes.length).to.equal(2);
+
+    const signedPropsRef = xpath.select(
+      `//*[local-name(.)='SignedInfo']/*[local-name(.)='Reference' and @Type='http://uri.etsi.org/01903#SignedProperties' and @URI='#${signedPropertiesId}']`,
+      signedDoc,
+    );
+    isDomNode.assertIsArrayOfNodes(signedPropsRef);
+    expect(signedPropsRef.length).to.equal(1);
+
+    const qualifyingProps = xpath.select(
+      \`//*[local-name(.)='Object']/*[local-name(.)='QualifyingProperties' and @Target='#\${signatureId}']\`,
+      signedDoc,
+    );
+    isDomNode.assertIsArrayOfNodes(qualifyingProps);
+    expect(qualifyingProps.length).to.equal(1);
+
     const verifier = new SignedXml({
       publicCert: publicCert,
     });

9-667: Minor: prefer modern algorithms where practical

You already have SHA-256 and SHA-512 coverage—great. Consider defaulting to SHA-256 for non-legacy tests to discourage SHA-1 usage in new code paths.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 55dc24d and e151d31.

📒 Files selected for processing (1)
  • test/signature-object-tests.spec.ts (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
test/signature-object-tests.spec.ts (2)
src/signed-xml.ts (1)
  • SignedXml (30-1463)
src/hash-algorithms.ts (1)
  • Sha256 (17-28)
🔇 Additional comments (2)
test/signature-object-tests.spec.ts (2)

1-8: Sha256 import path corrected (src) — good catch

Using ../src/hash-algorithms aligns with the rest of the suite and avoids CI path issues. Looks good.


223-297: Good coverage for SHA-256 flow (two references, digests, and signature method)

This test exercises both document and Object references with SHA-256 and validates digest and signature algorithms. Solid.

Copy link

codecov bot commented Aug 15, 2025

Codecov Report

❌ Patch coverage is 79.45205% with 15 lines in your changes missing coverage. Please review.
✅ Project coverage is 75.73%. Comparing base (627d83f) to head (9fd58a5).
⚠️ Report is 6 commits behind head on master.

Files with missing lines Patch % Lines
src/signed-xml.ts 79.45% 7 Missing and 8 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master     #506      +/-   ##
==========================================
+ Coverage   73.05%   75.73%   +2.67%     
==========================================
  Files           9        9              
  Lines         902     1026     +124     
  Branches      239      272      +33     
==========================================
+ Hits          659      777     +118     
+ Misses        143      136       -7     
- Partials      100      113      +13     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
test/signature-object-tests.spec.ts (1)

7-7: Sha256 import path corrected to src — resolved

Using "../src/hash-algorithms" matches the repo’s pattern and avoids CI/ts-node path issues previously flagged.

🧹 Nitpick comments (6)
test/signature-object-tests.spec.ts (6)

13-25: Surface validation errors in helper for faster debugging

When a signature check fails, the tests will only report “expected true” without context. Emitting validationErrors here greatly speeds up failure triage while keeping the boolean contract.

   verifier.loadSignature(signatureNode);
-  return verifier.checkSignature(signedXml);
+  const ok = verifier.checkSignature(signedXml);
+  if (!ok && Array.isArray((verifier as any).validationErrors) && (verifier as any).validationErrors.length) {
+    // Keep returning a boolean so existing assertions remain unchanged,
+    // but surface details to aid debugging if a test fails.
+    // eslint-disable-next-line no-console
+    console.error("Signature validation errors:", (verifier as any).validationErrors);
+  }
+  return ok;

86-133: Prefer DOM-aware assertions for consistency and type-safety

Elsewhere you assert the XPath result type with assertIsArrayOfNodes. Mirror that here for consistency and clearer failures.

-    const objectNodesWithNull = xpath.select("//*[local-name(.)='Object']", docWithNull);
-    expect(objectNodesWithNull).to.be.an("array").that.is.empty;
+    const objectNodesWithNull = xpath.select("//*[local-name(.)='Object']", docWithNull);
+    isDomNode.assertIsArrayOfNodes(objectNodesWithNull);
+    expect(objectNodesWithNull.length).to.equal(0);
@@
-    const objectNodesWithEmpty = xpath.select("//*[local-name(.)='Object']", docWithEmpty);
-    expect(objectNodesWithEmpty).to.be.an("array").that.is.empty;
+    const objectNodesWithEmpty = xpath.select("//*[local-name(.)='Object']", docWithEmpty);
+    isDomNode.assertIsArrayOfNodes(objectNodesWithEmpty);
+    expect(objectNodesWithEmpty.length).to.equal(0);

180-246: Reduce duplication via a parameterized test for SHA256/SHA512 variants

Both tests are identical aside from algorithms. Consider a small matrix to DRY and make it easier to extend with more variants later.

Example:

[
  {
    name: "SHA256",
    signatureAlg: "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256",
    digestAlg: "http://www.w3.org/2001/04/xmlenc#sha256",
  },
  {
    name: "SHA512",
    signatureAlg: "http://www.w3.org/2001/04/xmldsig-more#rsa-sha512",
    digestAlg: "http://www.w3.org/2001/04/xmlenc#sha512",
  },
].forEach(({ name, signatureAlg, digestAlg }) => {
  it(`should sign Object with ${name} digest algorithm and RSA-${name} signature`, () => {
    // construct SignedXml with signatureAlg, add references with digestAlg, then assert as you do now
  });
});

Also applies to: 247-312


443-494: Test appears redundant with the previous “add a reference to an Object element”

This test exercises essentially the same flow; consider merging to keep the suite tight and reduce runtime.


496-531: Strengthen InclusiveNamespaces assertion using XPath (avoid brittle string match)

Prefer asserting the actual element and attribute via XPath and DOM. Also assert the object reference via XPath.

     sig.computeSignature(xml);
     const signedXml = sig.getSignedXml();
-
-    // Verify that the Object element is present
-    expect(signedXml).to.include('<InclusiveNamespaces PrefixList="ns1 ns2"');
-    // Verify that the Reference URI is correct
-    expect(signedXml).to.include('URI="#object1"');
-
-    // Verify that the signature is valid
-    expect(checkSignature(signedXml)).to.be.true;
+    const doc = new xmldom.DOMParser().parseFromString(signedXml);
+
+    // Verify InclusiveNamespaces element and PrefixList value
+    const incNs = xpath.select1(
+      "//*[local-name(.)='InclusiveNamespaces' and namespace-uri(.)='http://www.w3.org/2001/10/xml-exc-c14n#']",
+      doc,
+    );
+    isDomNode.assertIsElementNode(incNs);
+    expect(incNs.getAttribute("PrefixList")).to.equal("ns1 ns2");
+
+    // Verify that the Reference URI points to the Object
+    const objectRef = xpath.select1("//*[local-name(.)='Reference' and @URI='#object1']", doc);
+    isDomNode.assertIsElementNode(objectRef);
+
+    // Verify that the signature is valid
+    expect(checkSignature(signedXml, doc)).to.be.true;

534-598: Add structural XAdES assertions (Type on Reference, Target on QualifyingProperties, SignedProperties Id)

The signature validity check is great, but it would be stronger to assert that:

  • The SignedProperties Reference carries the XAdES Type.
  • QualifyingProperties targets the Signature’s Id.
  • SignedProperties has the expected Id.
-    const signedXml = sig.getSignedXml();
-
-    // Verify that the signature is valid
-    expect(checkSignature(signedXml)).to.be.true;
+    const signedXml = sig.getSignedXml();
+    const doc = new xmldom.DOMParser().parseFromString(signedXml);
+
+    // QualifyingProperties should target the signature Id
+    const qp = xpath.select1(
+      "//*[local-name(.)='QualifyingProperties' and namespace-uri(.)='http://uri.etsi.org/01903/v1.3.2#']",
+      doc,
+    );
+    isDomNode.assertIsElementNode(qp);
+    expect(qp.getAttribute("Target")).to.equal(`#${signatureId}`);
+
+    // SignedProperties should have expected Id
+    const sp = xpath.select1("//*[local-name(.)='SignedProperties']", doc);
+    isDomNode.assertIsElementNode(sp);
+    expect(sp.getAttribute("Id")).to.equal(signedPropertiesId);
+
+    // Reference to SignedProperties should carry the XAdES Type and correct URI
+    const spRef = xpath.select1(
+      "//*[local-name(.)='SignedInfo']/*[local-name(.)='Reference' and @Type='http://uri.etsi.org/01903#SignedProperties' and @URI='#" +
+        signedPropertiesId +
+        "']",
+      doc,
+    );
+    isDomNode.assertIsElementNode(spRef);
+
+    // Verify that the signature is valid
+    expect(checkSignature(signedXml, doc)).to.be.true;

Also consider adding a small test asserting that a custom Reference id set via addReference({ id: 'ref-1', ... }) ends up on ds:Reference as Id="ref-1", since this is part of the new API surface.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 9fd58a5 and eafbfc3.

📒 Files selected for processing (2)
  • test/signature-object-tests.spec.ts (1 hunks)
  • test/signature-unit-tests.spec.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • test/signature-unit-tests.spec.ts
🔇 Additional comments (3)
test/signature-object-tests.spec.ts (3)

27-85: LGTM: solid coverage for basic ds:Object insertion and verification

Good end-to-end check for attributes, content, and successful verification.


134-179: LGTM: base64 Encoding attribute path is exercised well

Good check for Id, MimeType, Encoding, and base64 content; verification passes.


382-441: LGTM: verifies reference to Object is included in SignedInfo

Clear assertion that the Reference with @URI='#object1' lives under SignedInfo.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
test/signature-object-tests.spec.ts (1)

7-7: Fixed import path for Sha256 — thanks for addressing earlier feedback

Import now correctly targets ../src/hash-algorithms, aligning with the rest of the test suite and avoiding CI path issues.

🧹 Nitpick comments (5)
test/signature-object-tests.spec.ts (5)

13-25: Improve signature verification diagnostics in tests

When verification fails, the current helper only returns false and hides the underlying validation errors. Emitting the library’s validation errors makes triage much faster.

Apply this diff to surface errors on failure:

-  verifier.loadSignature(signatureNode);
-  return verifier.checkSignature(signedXml);
+  verifier.loadSignature(signatureNode);
+  const ok = verifier.checkSignature(signedXml);
+  if (!ok) {
+    const errors =
+      typeof (verifier as any).getValidationErrors === "function"
+        ? (verifier as any).getValidationErrors()
+        : (verifier as any).validationErrors || [];
+    throw new Error(`Signature verification failed: ${JSON.stringify(errors)}`);
+  }
+  return true;

59-84: Assert that ds:Object entries are referenced by SignedInfo (exercise auto object reference creation)

Per PR goals, Objects should be created and referenced automatically. This test validates Object presence and overall signature validity, but not that SignedInfo contains references to the ds:Object Ids. Adding assertions here tightens coverage on the new behavior.

Apply this diff to assert the object references exist in SignedInfo:

     const doc = new xmldom.DOMParser().parseFromString(signedXml);
 
     // Should have two Object elements
     const objectNodes = xpath.select("//*[local-name(.)='Object']", doc);
     isDomNode.assertIsArrayOfNodes(objectNodes);
     expect(objectNodes.length).to.equal(2);
 
+    // Objects should be referenced from SignedInfo
+    const refObj1 = xpath.select(
+      "//*[local-name(.)='SignedInfo']/*[local-name(.)='Reference' and @URI='#object1']",
+      doc,
+    );
+    const refObj2 = xpath.select(
+      "//*[local-name(.)='SignedInfo']/*[local-name(.)='Reference' and @URI='#object2']",
+      doc,
+    );
+    isDomNode.assertIsArrayOfNodes(refObj1);
+    isDomNode.assertIsArrayOfNodes(refObj2);
+    expect(refObj1.length).to.equal(1);
+    expect(refObj2.length).to.equal(1);
+
     // Verify the first Object element

327-386: Reduce duplication with the next test

This test and the one below both add a reference to the Object by Id and assert similar invariants. Consider merging them or parameterizing differences to cut duplication and speed up the suite.


388-439: Duplicate of previous scenario

This is functionally identical to “should add a reference to an Object element”. Recommend consolidating to a single parametrized test to avoid drift.


488-552: Strengthen XAdES assertions: check Reference@Type, SignedProperties@Id, and QualifyingProperties@Target

The test verifies overall signature validity but misses key XAdES wiring checks. Adding these assertions better validates the new addReference type/id support and the XAdES structure.

Apply this diff to add targeted assertions:

   const signedXml = sig.getSignedXml();
 
-  // Verify that the signature is valid
-  expect(checkSignature(signedXml)).to.be.true;
+  const signedDoc = new xmldom.DOMParser().parseFromString(signedXml);
+
+  // ds:Signature has the expected Id
+  const elSig = xpath.select1("//*[local-name(.)='Signature']", signedDoc);
+  isDomNode.assertIsElementNode(elSig);
+  expect(elSig.getAttribute("Id")).to.equal(signatureId);
+
+  // xades:QualifyingProperties targets the signature Id
+  const elQP = xpath.select1("//*[local-name(.)='QualifyingProperties']", signedDoc);
+  isDomNode.assertIsElementNode(elQP);
+  expect(elQP.getAttribute("Target")).to.equal(`#${signatureId}`);
+
+  // xades:SignedProperties has the expected Id
+  const elSP = xpath.select1("//*[local-name(.)='SignedProperties']", signedDoc);
+  isDomNode.assertIsElementNode(elSP);
+  expect(elSP.getAttribute("Id")).to.equal(signedPropertiesId);
+
+  // Reference for SignedProperties exists with correct @Type and @URI
+  const elSPRef = xpath.select1(
+    "//*[local-name(.)='SignedInfo']/*[local-name(.)='Reference' and @Type='http://uri.etsi.org/01903#SignedProperties']",
+    signedDoc,
+  );
+  isDomNode.assertIsElementNode(elSPRef);
+  expect(elSPRef.getAttribute("URI")).to.equal(`#${signedPropertiesId}`);
+
+  // DigestMethod for SignedProperties is SHA-256
+  const elSPDigestMethod = xpath.select1(
+    "//*[local-name(.)='SignedInfo']/*[local-name(.)='Reference' and @URI=$uri]/*[local-name(.)='DigestMethod']",
+    signedDoc,
+  ) as Element | null;
+  // Fallback if variables are not supported by the XPath engine:
+  const elSPDigestMethodFallback = elSPDigestMethod
+    ? elSPDigestMethod
+    : (xpath.select1(
+        `//*[local-name(.)='SignedInfo']/*[local-name(.)='Reference' and @URI='#${signedPropertiesId}']/*[local-name(.)='DigestMethod']`,
+        signedDoc,
+      ) as Element);
+  isDomNode.assertIsElementNode(elSPDigestMethodFallback);
+  expect(elSPDigestMethodFallback.getAttribute("Algorithm")).to.equal(
+    "http://www.w3.org/2001/04/xmlenc#sha256",
+  );
+
+  // Verify signature validity last for clearer failure messages
+  expect(checkSignature(signedXml, signedDoc)).to.be.true;
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between eafbfc3 and e761453.

📒 Files selected for processing (1)
  • test/signature-object-tests.spec.ts (1 hunks)
🔇 Additional comments (5)
test/signature-object-tests.spec.ts (5)

86-134: LGTM: Correct handling of undefined and empty objects

Good coverage to ensure no ds:Object nodes are emitted when objects is undefined or empty. Signature validity checks are appropriate.


136-180: LGTM: Encoded Object attributes and content verified

Solid assertions on Id, MimeType, Encoding, and content, plus end-to-end verification.


193-256: LGTM: Algorithm matrix strengthens confidence

Iterating over SHA256/SHA512 for both digest and signature is thorough. Verifying DigestMethod and SignatureMethod URIs is spot-on.


258-325: LGTM: Canonicalization coverage

Asserting both transforms and CanonicalizationMethod for C14N ensures correct wiring of canonicalization options.


441-485: LGTM: InclusiveNamespaces coverage is precise

Good assertion on InclusiveNamespaces PrefixList and that the detected URI uses the Object’s Id.

@shunkica
Copy link
Contributor Author

shunkica commented Aug 16, 2025

I have identified a problem in a specific situation where there is a Reference to an Object which has InclusiveNamespaces PrefixList which contains namespaces not present in the signature, but the xml document itself, and am working on a fix.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🔭 Outside diff range comments (1)
src/signed-xml.ts (1)

1158-1184: Restrict InclusiveNamespaces to Exclusive C14n Transforms Only

Only Exclusive Canonicalization supports the <InclusiveNamespaces> element. Emitting it for other transforms (e.g. enveloped-signature) produces invalid XML.

– File: src/signed-xml.ts
– Around lines 1160–1170, update the for (const trans of ref.transforms…) loop:

         const transform = this.findCanonicalizationAlgorithm(trans);
         res += `<${prefix}Transform Algorithm="${transform.getAlgorithmName()}"`;
-        if (utils.isArrayHasLength(ref.inclusiveNamespacesPrefixList)) {
+        const algoName = transform.getAlgorithmName();
+        const isExclusiveC14n =
+          algoName === "http://www.w3.org/2001/10/xml-exc-c14n#" ||
+          algoName === "http://www.w3.org/2001/10/xml-exc-c14n#WithComments";
+        if (isExclusiveC14n && utils.isArrayHasLength(ref.inclusiveNamespacesPrefixList)) {
           res += ">";
           res += `<InclusiveNamespaces PrefixList="${ref.inclusiveNamespacesPrefixList.join(
             " ",
           )}" xmlns="${transform.getAlgorithmName()}"/>`;
           res += `</${prefix}Transform>`;
         } else {
           res += " />";
         }

If you merge this change, review and update any tests that assume <InclusiveNamespaces> appears on the first transform regardless of its algorithm.

♻️ Duplicate comments (1)
src/signed-xml.ts (1)

168-171: Restore default getCertFromKeyInfo to preserve embedded KeyInfo behavior

Defaulting to noop breaks verification when callers rely on embedded KeyInfo without supplying publicCert/privateKey. Restore the previous default.

-    this.getCertFromKeyInfo = getCertFromKeyInfo ?? SignedXml.noop;
+    this.getCertFromKeyInfo = getCertFromKeyInfo ?? this.getCertFromKeyInfo;
🧹 Nitpick comments (5)
test/signature-object-tests.spec.ts (3)

122-154: Strengthen namespace assertion for ds:Object

The test currently only asserts existence. Add an assertion on namespaceURI to ensure the ds:Object is actually emitted in the ds namespace when no default namespace is present.

-    const objectNode = select1Ns("/root/ds:Signature/ds:Object[@Id='object1']", doc);
+    const objectNode = select1Ns("/root/ds:Signature/ds:Object[@Id='object1']", doc);
     isDomNode.assertIsElementNode(objectNode);
+    expect(objectNode.namespaceURI).to.equal("http://www.w3.org/2000/09/xmldsig#");

206-206: Fix typo in test name (“Rerefence” → “Reference”)

Minor typo in test description.

-  it("should handle Rerefence to Object", function () {
+  it("should handle Reference to Object", function () {

209-212: Avoid redundant property shorthand in object literal

Nit: Use property shorthand for privateKey.

-    const sig = new SignedXml({
-      privateKey: privateKey,
+    const sig = new SignedXml({
+      privateKey,
src/signed-xml.ts (2)

1328-1435: Mark references as processed after deferred generation

You set wasProcessed in createReferences but not after the deferred path. This can mislead consumers that inspect internal state.

       // Append the reference element to SignedInfo
       signedInfoNode.appendChild(referenceElem);
     }
+    // Mark as processed once all nodes are handled
+    ref.wasProcessed = true;
   }

60-61: API ergonomics: allow Node content for Object to avoid brittle string concatenation

Right now content is stringly-typed and concatenated. Accepting a Node (or a union Node | string with a contentIsXml flag) would prevent well-formedness issues and reduce escaping concerns. It also allows callers to build Object content with DOM safely.

If you want, I can sketch a backward-compatible change for the types and serializer so callers can pass either a string (escaped as text) or a Node (inserted as-is).

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between ece496b and 230d4c5.

📒 Files selected for processing (2)
  • src/signed-xml.ts (14 hunks)
  • test/signature-object-tests.spec.ts (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (2)
test/signature-object-tests.spec.ts (2)
src/signed-xml.ts (2)
  • checkSignature (261-385)
  • SignedXml (30-1463)
src/hash-algorithms.ts (1)
  • Sha256 (17-28)
src/signed-xml.ts (1)
src/types.ts (2)
  • ObjectAttributes (50-59)
  • Reference (124-160)
🔇 Additional comments (1)
test/signature-object-tests.spec.ts (1)

7-7: Import path fixed to src — LGTM

Thanks for aligning the Sha256 import to ../src/hash-algorithms. This resolves the prior CI/ts-node path issue.

@srd90
Copy link

srd90 commented Aug 17, 2025

Out of curiosity: if/when this PR ( #506 ) is merged what should be done to another PR:

I see that this PR ( #506 ) seems to enables similar functionality ( b5aa833#diff-bf627b29a0628573db9f32289a7b987716b7c1ad162446b74be13cee6af9bbfbR704 )

@shunkica
Copy link
Contributor Author

shunkica commented Aug 17, 2025

Out of curiosity: if/when this PR ( #506 ) is merged what should be done to another PR:

I think this PR would make that redundant. It provides much more versatility.
This will not only enable user to generate XAdES, it also allows the user to sign any ds:Object and the ds:KeyInfo.

Furthermore, with some minor additions it can allow the library to generate Enveloping and Detached signatures as well.
I already have a local commit which enables creating Enveloping signatures by calling computeSignature(null) when a reference to ds:Object exists.
Detached signatures would be a little more work but certainly doable.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
src/signed-xml.ts (1)

169-171: Bug: default getCertFromKeyInfo changed to noop (breaks verification of embedded certs)

This reintroduces the previously flagged behavior change. It diverges from the prior default and will break scenarios that rely on extracting certs from KeyInfo when publicCert isn’t provided.

Apply this diff to preserve the intended default:

-    this.getCertFromKeyInfo = getCertFromKeyInfo ?? SignedXml.noop;
+    this.getCertFromKeyInfo = getCertFromKeyInfo ?? this.getCertFromKeyInfo;
🧹 Nitpick comments (3)
src/signed-xml.ts (3)

29-29: Avoid duplicate imports from the same module

You already import the module as utils; prefer utils.isDescendantOf(...) instead of adding a named import.

Apply this diff to remove the extra import:

-import { isDescendantOf } from "./utils";
+// use utils.isDescendantOf to avoid duplicate imports

1363-1367: Use utils.isDescendantOf to match the module-level import

This keeps imports consistent and avoids an extra named import from the same file.

-          node === signedInfoNode ||
-          isDescendantOf(node, signedInfoNode)
+          node === signedInfoNode ||
+          utils.isDescendantOf(node, signedInfoNode)

1326-1441: Create all new DOM nodes from a single ownerDocument and mark references as processed

You’re mixing signatureElem.ownerDocument.createElementNS (Reference) with doc.createElementNS (Transforms, DigestMethod, DigestValue). If ownerDocument !== doc in some environments, appending may throw WRONG_DOCUMENT_ERR. After insertion, they’re typically the same in xmldom, but relying on that coupling is brittle. Also, ref.wasProcessed isn’t set here, which leaves stale state.

Proposed refactor:

  • Use a single ownerDoc (prefer signatureElem.ownerDocument || doc) to create all elements.
  • Set ref.wasProcessed = true after successfully appending Reference(s) for the given ref.
   private processSignatureReferences(doc: Document, signatureElem: Element, prefix?: string) {
@@
-    const signatureNamespace = "http://www.w3.org/2000/09/xmldsig#";
+    const signatureNamespace = "http://www.w3.org/2000/09/xmldsig#";
+    const ownerDoc = signatureElem.ownerDocument || doc;
@@
-      // Process the reference
+      // Process the reference
       for (const node of nodes) {
@@
-        const referenceElem = signatureElem.ownerDocument.createElementNS(
-          signatureNamespace,
-          `${prefix}Reference`,
-        );
+        const referenceElem = ownerDoc.createElementNS(signatureNamespace, `${prefix}Reference`);
@@
-        const transformsElem = doc.createElementNS(signatureNamespace, `${prefix}Transforms`);
+        const transformsElem = ownerDoc.createElementNS(signatureNamespace, `${prefix}Transforms`);
@@
-          const transformElem = doc.createElementNS(signatureNamespace, `${prefix}Transform`);
+          const transformElem = ownerDoc.createElementNS(signatureNamespace, `${prefix}Transform`);
           transformElem.setAttribute("Algorithm", transform.getAlgorithmName());
@@
-            const inclusiveNamespacesElem = doc.createElementNS(
+            const inclusiveNamespacesElem = ownerDoc.createElementNS(
               transform.getAlgorithmName(),
               "InclusiveNamespaces",
             );
@@
-        const digestMethodElem = doc.createElementNS(signatureNamespace, `${prefix}DigestMethod`);
+        const digestMethodElem = ownerDoc.createElementNS(signatureNamespace, `${prefix}DigestMethod`);
         digestMethodElem.setAttribute("Algorithm", digestAlgorithm.getAlgorithmName());
 
-        const digestValueElem = doc.createElementNS(signatureNamespace, `${prefix}DigestValue`);
+        const digestValueElem = ownerDoc.createElementNS(signatureNamespace, `${prefix}DigestValue`);
         digestValueElem.textContent = digestAlgorithm.getHash(canonXml);
@@
         signedInfoNode.appendChild(referenceElem);
       }
+      // Mark this reference as processed
+      ref.wasProcessed = true;
     }
   }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 230d4c5 and 04a1de4.

📒 Files selected for processing (3)
  • src/signed-xml.ts (17 hunks)
  • src/utils.ts (1 hunks)
  • test/signature-object-tests.spec.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • test/signature-object-tests.spec.ts
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-08-16T14:07:57.604Z
Learnt from: shunkica
PR: node-saml/xml-crypto#506
File: src/signed-xml.ts:1088-1119
Timestamp: 2025-08-16T14:07:57.604Z
Learning: The xml-crypto library does not validate user input and places the responsibility for providing properly encoded values on the users. This is a consistent design philosophy throughout the library.

Applied to files:

  • src/signed-xml.ts
🧬 Code Graph Analysis (1)
src/signed-xml.ts (2)
src/types.ts (2)
  • ObjectAttributes (50-59)
  • Reference (124-160)
src/utils.ts (1)
  • isDescendantOf (318-333)
🔇 Additional comments (7)
src/utils.ts (1)

317-333: Utility is correct and fits the intended “descendant-only” check

Implementation is straightforward, null-safe, and intentionally excludes the “self” case, which aligns with its usage guarding against signing SignedInfo or its descendants.

src/signed-xml.ts (6)

61-61: Nice addition: objects property for ds:Object embedding

Adding objects?: Array<{ content: string; attributes?: ObjectAttributes }> is a clean way to support ds:Object without complicating the existing API with more callbacks.


76-76: Tracking wasProcessed per reference is a good simplification

This replaces an extra flag with a per-reference state and enables the two-pass flow without mutation games.


976-982: Correct placement of ds:Object before SignatureValue insertion

Appending Objects as part of the initial Signature string and inserting SignatureValue afterward ensures the final order matches the schema: SignedInfo, SignatureValue, KeyInfo?, Object*. Good.


1136-1139: Gracefully deferring unresolved references is the right call

Continuing when a reference target isn’t found in the first pass avoids premature failure and lets post-processing finalize Ds:Object and similar cases.


1152-1159: Support for Reference Id and Type attributes looks good

This aligns with xmldsig-core1 and enables XAdES scenarios (e.g., SignedProperties). The attributes are correctly optional and serialized when present.


1090-1120: Object rendering path is clear and minimal

Concise implementation that keeps with library philosophy (no input validation) while enabling flexible ds:Object creation with arbitrary attributes. Tests should ensure typical XAdES Object contents serialize as expected.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (2)
src/signed-xml.ts (2)

168-170: Defaulting getCertFromKeyInfo to noop likely breaks embedded KeyInfo resolution

This was previously flagged and acknowledged as out of scope for this PR. It remains a potential breaking change for users relying on embedded certs without passing publicCert.

If you’d like, I can open a follow-up issue to restore the previous default or add a deprecation path.


1089-1119: Object rendering: raw attribute/content passthrough matches library philosophy

Direct concatenation of attributes and content is consistent with xml-crypto’s stance of not validating/escaping user input. Given that, this implementation is fine.

If you ever choose to add an opt-in escape path later, I can sketch a minimal escapeXmlAttr helper and a contentIsXml toggle without changing the default behavior.

🧹 Nitpick comments (2)
src/signed-xml.ts (2)

60-60: New public objects property: API addition looks good

Clear, typed entry-point for ds:Object embedding. Consider adding a short JSDoc describing expected content semantics (XML vs plain text) in a follow-up.


1355-1361: Namespace resolver: add ds fallback to improve ergonomics for Object XPaths

Second-pass selection fails if callers reference ds:Object but didn’t supply existingPrefixes.ds. Since ds is a well-known default for this library, fall back to SignedXml.defaultNsForPrefix here to make Object XPaths “just work” without extra config.

Apply this diff:

-      const nodes = xpath.selectWithResolver(ref.xpath ?? "", doc, this.namespaceResolver);
+      // Prefer caller-provided prefixes but fall back to built-in ds mapping
+      const resolver: XPathNSResolver = {
+        lookupNamespaceURI: (p) =>
+          p
+            ? this.namespaceResolver.lookupNamespaceURI(p) ||
+              (SignedXml.defaultNsForPrefix as Record<string, string>)[p] ||
+              null
+            : null,
+      };
+      const nodes = xpath.selectWithResolver(ref.xpath ?? "", doc, resolver);
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 04a1de4 and 38c552b.

📒 Files selected for processing (1)
  • src/signed-xml.ts (16 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-08-16T14:07:57.604Z
Learnt from: shunkica
PR: node-saml/xml-crypto#506
File: src/signed-xml.ts:1088-1119
Timestamp: 2025-08-16T14:07:57.604Z
Learning: The xml-crypto library does not validate user input and places the responsibility for providing properly encoded values on the users. This is a consistent design philosophy throughout the library.

Applied to files:

  • src/signed-xml.ts
🧬 Code Graph Analysis (1)
src/signed-xml.ts (1)
src/types.ts (2)
  • ObjectAttributes (50-59)
  • Reference (124-160)
🔇 Additional comments (6)
src/signed-xml.ts (6)

11-11: Import of ObjectAttributes is appropriate

Type import matches new Object attributes usage. No issues.


75-75: Tracking reference processing state via wasProcessed is a good simplification

This removes the need for the previous isSignatureReference flag and enables a clean two-pass flow.


148-149: Wiring the objects option through the constructor

Correctly threads the new option. LGTM.


979-980: Placement of ds:Object elements is sound

Objects are rendered before the SignatureValue is inserted and ultimately end up after SignatureValue (and after KeyInfo when present), matching schema ordering expectations.


1056-1067: SignatureValue insertion after SignedInfo is correct

Inserting SignatureValue immediately after SignedInfo preserves the required element order: SignedInfo, SignatureValue, KeyInfo?, Object*.


1132-1138: Two-pass Reference handling: good approach; minor state consistency note

  • Skipping unresolved references in the first pass (Line 1136) and handling them later is the right trade-off.
  • Including Id and Type attributes is correctly implemented.
  • Marking ref.wasProcessed = true happens in this pass (Line 1184).

No changes requested here.

Also applies to: 1151-1159, 1184-1184

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (3)
src/signed-xml.ts (3)

11-11: Object elements wiring and serializer: solid integration; consider a brief API doc note

Nice addition and clean plumbing of ds:Object through options ➜ instance ➜ serializer ➜ placement within Signature. Emitting Objects before closing Signature and inserting SignatureValue later yields the correct final order: SignedInfo, SignatureValue, KeyInfo, Object*.

Given this library’s philosophy of trusting inputs, concatenating attribute values and content verbatim is consistent. A small DX improvement would be to document that expectation right where users see the property.

You can add a terse TSDoc to the objects property to clarify content/attribute handling:

-  objects?: Array<{ content: string; attributes?: ObjectAttributes }>;
+  /**
+   * Optional ds:Object nodes to embed under Signature.
+   * Note: `content` must be a well-formed XML string. Attributes are inserted verbatim.
+   * This library does not escape or validate user-provided values.
+   */
+  objects?: Array<{ content: string; attributes?: ObjectAttributes }>;

Also applies to: 60-60, 148-149, 170-170, 979-980, 1089-1119


1355-1361: Make XPath against ds:Object work out-of-the-box by mapping the ds prefix

If callers write XPaths like //ds:Object for intra-signature references, they currently need to pass a namespace map in existingPrefixes. Since this PR introduces signature-internal selection, it’s ergonomic to map “ds” (and the configured options.prefix) by default.

Apply this diff in computeSignature to augment the resolver:

-    this.namespaceResolver = {
-      lookupNamespaceURI: function (prefix) {
-        return prefix ? existingPrefixes[prefix] : null;
-      },
-    };
+    this.namespaceResolver = {
+      lookupNamespaceURI: (p) => {
+        if (!p) return null;
+        // User-provided mappings win
+        if (existingPrefixes[p]) return existingPrefixes[p];
+        // Convenience: map 'ds' and the configured signature prefix to the ds namespace
+        if (p === "ds" || (prefix && p === prefix)) {
+          return "http://www.w3.org/2000/09/xmldsig#";
+        }
+        return null;
+      },
+    };

This keeps existing behavior intact while making common ds-prefixed XPaths work without extra setup.


1056-1057: Micro‑DRY: factor SignatureValue insertion to a helper

The two branches duplicate the same insertion and serialization steps.

You can factor into a small helper to reduce duplication:

-          this.signatureValue = signature || "";
-          signatureElem.insertBefore(this.createSignature(prefix), signedInfoNode.nextSibling);
-          this.signatureXml = signatureElem.toString();
-          this.signedXml = doc.toString();
+          this.signatureValue = signature || "";
+          this.insertSignatureValue(signatureElem, signedInfoNode, prefix, doc);

and

-      this.calculateSignatureValue(doc);
-      signatureElem.insertBefore(this.createSignature(prefix), signedInfoNode.nextSibling);
-      this.signatureXml = signatureElem.toString();
-      this.signedXml = doc.toString();
+      this.calculateSignatureValue(doc);
+      this.insertSignatureValue(signatureElem, signedInfoNode, prefix, doc);

with:

// place near createSignature()
private insertSignatureValue(signatureElem: Element, signedInfoNode: Node, prefix: string | undefined, doc: Document) {
  signatureElem.insertBefore(this.createSignature(prefix), signedInfoNode.nextSibling);
  this.signatureXml = signatureElem.toString();
  this.signedXml = doc.toString();
}

Also applies to: 1065-1066

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 38c552b and aa9b086.

📒 Files selected for processing (1)
  • src/signed-xml.ts (16 hunks)
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-08-16T14:07:57.604Z
Learnt from: shunkica
PR: node-saml/xml-crypto#506
File: src/signed-xml.ts:1088-1119
Timestamp: 2025-08-16T14:07:57.604Z
Learning: The xml-crypto library does not validate user input and places the responsibility for providing properly encoded values on the users. This is a consistent design philosophy throughout the library.

Applied to files:

  • src/signed-xml.ts
📚 Learning: 2025-08-17T10:50:18.014Z
Learnt from: shunkica
PR: node-saml/xml-crypto#506
File: src/signed-xml.ts:1447-1451
Timestamp: 2025-08-17T10:50:18.014Z
Learning: In the xml-crypto codebase, ref.digestValue is only used during signature validation when loading references from existing signatures, not during signature creation in methods like createReferences or processSignatureReferences.

Applied to files:

  • src/signed-xml.ts
🧬 Code Graph Analysis (1)
src/signed-xml.ts (1)
src/types.ts (2)
  • ObjectAttributes (50-59)
  • Reference (124-160)
🔇 Additional comments (3)
src/signed-xml.ts (3)

1136-1138: Two‑pass reference handling and Id/Type support look correct

  • Deferring unresolved refs in createReferences and finalizing them in processSignatureReferences is clean and keeps the first pass simple.
  • Adding Id and Type to Reference elements aligns with xmldsig/XAdES needs.
  • Marking ref.wasProcessed = true prevents duplication on subsequent passes.

Also applies to: 1141-1149, 1151-1158, 1159-1185


1365-1374: Good guard: prevent self-signing of Signature/SignedInfo

Blocking references to the Signature element, SignedInfo, or descendants of SignedInfo avoids foot-guns and aligns with signature processing model expectations.


1402-1423: InclusiveNamespaces emission matches initial path (consistent behavior)

Emitting InclusiveNamespaces whenever a prefix list is provided mirrors createReferences. This consistency avoids subtle discrepancies between first- and second-pass generation. The known corner case around PrefixList containing namespaces absent from the signature scope is acknowledged in PR comments; fine to address separately.

If you have a failing repro for the “PrefixList contains namespaces present in the doc but not in the signature” case, consider adding it as a pending test to track the follow-up.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants