Skip to content

feat!: decouple public key and data types from node:crypto - #570

Open
cjbarth wants to merge 1 commit into
node-saml:masterfrom
cjbarth:feat/runtime-neutral-key-types
Open

feat!: decouple public key and data types from node:crypto#570
cjbarth wants to merge 1 commit into
node-saml:masterfrom
cjbarth:feat/runtime-neutral-key-types

Conversation

@cjbarth

@cjbarth cjbarth commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

Closes #545

Every public signature that touched key material named node:crypto directly, which hard-coded Node into the type surface: a caller could not pass a CryptoKey — the only key representation Web Crypto produces — so the types blocked a Web Crypto backend before any implementation existed.

BinaryLike and KeyLike are now defined in src/types.ts and used for SignedXmlOptions.privateKey/publicCert, GetKeyInfoContentArgs.publicCert and the SignatureAlgorithm interface.

How the bundled algorithms narrow, without blanket casts

The issue offers three options; this takes the first. SignatureAlgorithm takes the accepted key type as a parameter, so each implementation states what it can really use:

Algorithm Declares
RsaSha1, RsaSha256, RsaSha512, HmacSha1 SignatureAlgorithm<crypto.KeyLike | Uint8Array>
RsaSha256Mgf1 SignatureAlgorithm<string | Buffer> — it needs a key it can put in a SignPrivateKeyInput

Nothing casts back out. Verified that the compiler now rejects what it should:

TS2345: Argument of type 'CryptoKey' is not assignable to parameter of type
'Uint8Array<ArrayBufferLike> | KeyLike'.

The two arms the issue flagged as broken are now genuinely supported rather than narrowed away:

  • Uint8Array is viewed as a Buffer (Buffer.from(key.buffer, key.byteOffset, key.byteLength), no copy) instead of reaching OpenSSL as ERR_OSSL_UNSUPPORTED.
  • ArrayBuffer data is viewed the same way, so BinaryLike is true for these algorithms. Test asserts an ArrayBuffer signs to the same bytes as the equivalent string.

The residual hole, and why it fails closed

The signature algorithm is looked up by a URI read from the document under inspection, so a JavaScript caller can still pair a CryptoKey with a Node algorithm without the compiler seeing it. Confirmed on master that Node does not fail in that case — it signs:

master: signed with a CryptoKey, no error
(node:425927) [DEP0203] DeprecationWarning: Passing a CryptoKey to node:crypto functions is deprecated.

The signature verifies and the tests pass, so the algorithm appears to support a key it never really did, and it breaks whenever Node removes the shim. On this branch:

RsaSha256 needs a key that node:crypto accepts: a string, a Buffer, a Uint8Array, or a KeyObject

That check is the one piece of defensive code here; there is a comment saying why it exists despite the parameter type.

Notes

  • CryptoKey is spelled crypto.webcrypto.CryptoKey so it resolves under lib: ["es2020"] on the @types/node@16 floor. A DOM or global CryptoKey is structurally identical and assignable.
  • getKeyInfoContent still silently emits an empty <X509Data/> for key material it cannot read as PEM. That predates this change (a KeyObject does it today) and is left alone.

Breaking

Source-compatible for callers. A break for anyone who implements SignatureAlgorithm: declare the key type, e.g. implements SignatureAlgorithm<crypto.KeyLike>. README Upgrading and a new "Declaring the key material your algorithm accepts" section cover it.

Verification

npm run build && npm test && npm run lint clean; 244 passing (241 + 3). New tests in test/key-material-tests.spec.ts were confirmed to fail on master for the right reasons — two as compile errors, the CryptoKey one because master signs successfully.

🤖 Generated with Claude Code

Every public signature that touched key material named `node:crypto`
directly, which hard-coded Node into the type surface: a caller could not
pass a `CryptoKey`, the only key representation Web Crypto produces, so the
types blocked a Web Crypto backend before any implementation existed.

Introduce `BinaryLike` and `KeyLike` in `src/types.ts` and use them for
`SignedXmlOptions.privateKey`/`publicCert`, `GetKeyInfoContentArgs.publicCert`
and the `SignatureAlgorithm` interface.

`SignatureAlgorithm` takes the accepted key type as a parameter rather than
declaring the whole union, so each implementation states what it can really
use and nothing casts back out. The bundled algorithms declare
`crypto.KeyLike | Uint8Array` (`string | Buffer` for MGF1, which needs a key
it can put in a `SignPrivateKeyInput`), and a `Uint8Array` is now viewed as a
`Buffer` instead of reaching OpenSSL as `ERR_OSSL_UNSUPPORTED`. An
`ArrayBuffer` of data is likewise viewed rather than cast, so the widened data
type is true for them.

The algorithm is looked up by a URI read from the document, so a JavaScript
caller can still pair a `CryptoKey` with a Node algorithm without the compiler
seeing it. Node answers that by accepting the key through its DEP0203 shim: it
signs, the tests pass, and the signature is attributed to a key the algorithm
never supported. Reject it explicitly instead.

BREAKING CHANGE: implementers of `SignatureAlgorithm` should declare the key
type they accept, e.g. `implements SignatureAlgorithm<crypto.KeyLike>`. The
bundled algorithms now throw on key material `node:crypto` cannot use rather
than silently accepting a `CryptoKey`.

Closes node-saml#545

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Sep 9, 2026

Copy link
Copy Markdown

Warning

Review limit reached

Next included review available in 20 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used all 2 included reviews currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Advanced

Run ID: 6a931e3d-f508-49af-bdc0-71f8a01987fa

📥 Commits

Reviewing files that changed from the base of the PR and between 0409418 and 2ce236f.

📒 Files selected for processing (5)
  • README.md
  • src/signature-algorithms.ts
  • src/signed-xml.ts
  • src/types.ts
  • test/key-material-tests.spec.ts

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

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

@codecov

codecov Bot commented Sep 9, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 76.61%. Comparing base (f5c4d22) to head (2ce236f).
⚠️ Report is 4 commits behind head on master.

Additional details and impacted files
@@            Coverage Diff             @@
##           master     #570      +/-   ##
==========================================
+ Coverage   75.95%   76.61%   +0.66%     
==========================================
  Files           9        9              
  Lines        1048     1065      +17     
  Branches      273      278       +5     
==========================================
+ Hits          796      816      +20     
+ Misses        144      142       -2     
+ Partials      108      107       -1     

☔ View full report in Codecov by Harness.
📢 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.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Decouple public key and data types from node:crypto (BinaryLike / KeyLike)

1 participant