Skip to content

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

Closed
cjbarth wants to merge 1 commit into
masterfrom
feat/runtime-neutral-key-types
Closed

feat!: decouple public key and data types from node:crypto#562
cjbarth wants to merge 1 commit into
masterfrom
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 #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 59 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: d1bea397-dc85-4f68-9ba1-834bcba1a9c6

📥 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.

@cjbarth cjbarth added this to the v7.0 milestone Sep 9, 2026
@cjbarth cjbarth closed this Sep 9, 2026
@cjbarth
cjbarth deleted the feat/runtime-neutral-key-types branch September 9, 2026 23:50
@cjbarth

cjbarth commented Sep 9, 2026

Copy link
Copy Markdown
Contributor Author

Closed in favour of #570 — the same commits, opened from cjbarth/xml-crypto instead of a branch pushed directly to this repo by mistake. The branch here has been deleted.

🤖 Generated with Claude Code

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