Skip to content

[ENHANCEMENT]: Make signature-emission template methods protected so subclasses can customize output without type suppressions #539

Description

@msheby

Is your feature request related to a problem? Please describe...

Summary

SignedXml has a natural template-method shape — computeSignature calls createSignedInfo, which calls createReferences, which calls getCanonReferenceXml, which calls the (public) getCanonXml; separately computeSignature calls getKeyInfo and createSignature. In v6 every one of those intermediate methods is declared private, which makes the class impossible to subclass cleanly in strict TypeScript: any override triggers TS4114 (must have 'override' modifier) and adding override triggers TS2415 (private in base class). The only way to keep the subclass compiling is @ts-expect-error or an intersection-type cast — both of which sidestep type safety rather than express the design.

The proposed change is a one-line-per-method visibility change from private to protected on the methods that participate in the template. No behavior change; consumers who don't subclass see no difference. Consumers who do subclass can now express legitimate customizations in typed code.

Motivating cases

  • Pretty-printing / spec-mandated formatting of SignedInfo and its children. Post-processing the emitted signature to reformat <SignedInfo> isn't safe — non-exclusive C14N preserves inter-element whitespace as text nodes, so any post-hoc reformat changes the canonical form and invalidates the already-computed SignatureValue. The only correct place to intervene is inside the same chain that produces the pre-canonicalization string, which today means overriding createSignedInfo (private) or createReferences (private).

  • Reference-emission customization. Some SMPTE standards require that Reference elements not contain a <Transforms> child at all. The digest still has to be computed under C14N; only the emission changes. Doing this cleanly needs a createReferences override, which is currently blocked.

  • Custom getKeyInfo output (e.g. emitting a full X.509 certificate chain rather than a single cert) — the getKeyInfoContent callback covers the content, but a subclass that wants to alter attribute ordering, whitespace, or attach Id attributes to the outer <KeyInfo> element needs the wrapping method.

  • Repro-friendly algorithm lookups in tests. The findSignatureAlgorithm / findCanonicalizationAlgorithm / findHashAlgorithm helpers are the natural stub points for deterministic tests of subclass logic; keeping them private forces test subclasses into the same visibility gymnastics.

Describe teh solution you'd like...

Concrete ask

Change visibility from private to protected on the following methods in src/signed-xml.ts (line numbers from v6.1.2):

Method Line Role
getCanonSignedInfoXml 383 Called by calculateSignatureValue; entry point for SignedInfo C14N
getCanonReferenceXml 425 Per-reference C14N; entry point for reference digest computation
calculateSignatureValue 441 Signature computation; useful for algorithm-injection subclasses
findSignatureAlgorithm 454 Algorithm lookup
findCanonicalizationAlgorithm 466 Algorithm lookup
findHashAlgorithm 477 Algorithm lookup
loadReference 699 Per-reference load during verify; parallel to createReferences
getKeyInfo 1055 Emits the <KeyInfo> wrapper
createReferences 1077 Emits <Reference> elements
ensureHasId 1163 Id-attribute placement
createSignedInfo 1208 Emits <SignedInfo>
createSignature 1243 Emits the wrapping <Signature> element

The state fields those methods touch (this.signatureNode, this.references, this.signatureValue, etc.) can stay private — subclasses that need them can be added incrementally.

Compatibility

private → protected is a source-compatible widening. Existing consumers that don't subclass are unaffected. Existing subclasses (if any) that were already relying on TypeScript workarounds can drop those workarounds. The compiled JavaScript is byte-identical.

Related

Describe the alternatives you've considered...

Forking the type declarations -- ugh.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions