Skip to content

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

Description

@cjbarth

Summary

Introduce runtime-neutral BinaryLike and KeyLike types in src/types.ts, and use them across the public API in place of crypto.BinaryLike / crypto.KeyLike.

export type BinaryLike = crypto.BinaryLike | ArrayBuffer;
export type KeyLike = crypto.KeyLike | CryptoKey | Uint8Array;

Why

Today every public signature that touches key material or data to be signed is typed against node:crypto directly — SignedXmlOptions.privateKey, SignedXmlOptions.publicCert, GetKeyInfoContentArgs.publicCert, and both methods on the SignatureAlgorithm interface.

That hard-codes Node into the type surface. A caller cannot pass a CryptoKey, which is the only key representation the Web Crypto API produces, so the public types themselves block any Web Crypto backend before a line of implementation is written. This abstraction is a prerequisite for #477, and it is the piece of #513 that stands on its own regardless of what shape the rest of that work eventually takes.

Why 7.0

Altering types exported from src/types.ts is breaking for consumers, so this belongs in a major even though it is additive in practice. Widening a parameter type is source-compatible for callers, but it is not compatible for anyone who implements SignatureAlgorithm — third-party HSM- and KMS-backed implementations exist, and their method signatures have to widen to match.

Design note

In #513 these were introduced as blanket unions, which forced casts back out at every use site:

signer.update(signedInfo as crypto.BinaryLike);
const res = signer.sign(privateKey as crypto.KeyLike, "base64");

The cast is load-bearing: without it TypeScript correctly rejects the call (TS2345: Type 'CryptoKey' is not assignable to parameter of type 'KeyLike | SignKeyObjectInput | SignPrivateKeyInput'). So the widened type promises support that RsaSha1/RsaSha256/RsaSha512/HmacSha1 do not have, and the cast deletes the diagnostic that would have said so.

What happens at runtime differs by arm, and the CryptoKey case is the concerning one:

  • Uint8Array fails with ERR_OSSL_UNSUPPORTED: error:1E08010C:DECODER routines::unsupported — an opaque OpenSSL error a long way from the cause.
  • CryptoKey does not fail at all. Node accepts it and emits DEP0203 DeprecationWarning: Passing a CryptoKey to node:crypto functions is deprecated. It appears to work, tests pass, and it breaks whenever Node removes the shim — and it can never work off-Node, where node:crypto does not exist. A Node-backed algorithm would look like it supports Web Crypto keys while doing nothing of the kind.

Options that avoid the cast rather than suppressing it:

  1. Make the interface generic in its key type, so each implementation declares what it accepts.
  2. Keep the union, but narrow through a helper that validates and throws a clear error instead of an as.
  3. Separate interfaces per backend, so a Node algorithm never claims to take a CryptoKey at all.

Whichever way it goes, the goal is that each algorithm states what it actually accepts, rather than casting out of a union that promises more than it can take.

Scope

In scope:

  • Define BinaryLike / KeyLike in src/types.ts
  • Apply them to SignedXmlOptions, GetKeyInfoContentArgs, and the SignatureAlgorithm interface
  • Decide how the bundled Node-backed algorithms narrow to concrete types without blanket casts
  • Document the change for implementers of SignatureAlgorithm

Out of scope — these get their own issues:

  • Any Web Crypto algorithm implementation
  • Async / callback API changes
  • HashAlgorithm changes

References

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

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions