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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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