Skip to content

Restore Node.js sign/verify key-option, digest and temporary-prefix semantics #7613

Open

Description

@lexoliu

node:crypto / node:fs — four Node.js compatibility gaps in sign/verify and mkdtemp

Four Node.js compatibility defects were reproduced at upstream commit
535b64a124df7fb3108b904a75287470c7a4ab62, using Node as the independent
control. The default-digest, wrapped-key and temporary-prefix defects block
an unmodified tuf-js 6.0.0 / @sigstore/tuf 5.0.0 client. Native regressions
and separate Node-generated signature tests also exposed incorrect RSA
padding and salt-length handling. The RSA tests are separate from the TUF
client test. Related work: #6924 (Node compatibility survey) and #4269
(the earlier mkdtemp implementation).

1. sign/verify with an omitted or null digest fails for RSA/EC keys

verify(null, data, ecPublicKey, sig);   // Node: verifies as sha256
// workerd: Error: Failed to initialize verification context

Node derives the digest from the key type when algorithm is omitted or
null (SHA-256 for RSA/EC; Ed25519/Ed448 are digest-free). workerd passed the
absent digest straight into mdctx.signInit/verifyInit, which fail.

2. options.key does not accept a KeyObject or CryptoKey

sign('sha256', data, { key: privateKeyObject });   // Node: works
// workerd: "options.key" property must be of type string or ... Received
// an instance of PrivateKeyObject

Node unwraps a KeyObject/CryptoKey supplied inside the options object —
this is the form tuf-js uses, so its verifyDelegate path reported
"root was signed by 0/3 keys". workerd's key parser only handled raw
string/Buffer material inside options.key.

3. One-shot RSA sign/verify ignores padding and saltLength

const sig = sign('sha256', data, { key, padding: RSA_PKCS1_PSS_PADDING, saltLength: 32 });
verify('sha256', data, { key: pub, padding: RSA_PKCS1_PSS_PADDING, saltLength: 32 }, sig);
// Node: true; verify with default padding: false
// workerd: the signature was PKCS1 (padding ignored); every padding option verified true

CryptoImpl::signOneShot/verifyOneShot accepted rsaPadding/
pssSaltLength but never applied them to the operation context, so one-shot
RSA always used the default padding. Node cross-check (Node-made signatures
verified in workerd): a real PSS signature was rejected under correct PSS
options while a PKCS1 signature was accepted under PSS options.

4. fs.mkdtemp('/tmp/') throws EINVAL when the prefix names an existing directory

fs.mkdtempSync('/tmp/');   // Node: '/tmp/<random>'
// workerd: EINVAL inside mkdtempSync

The mkdir path returned early when the prefix itself already exists instead
of creating <prefix><generated>.

Scope

The proposed fix covers the four defects and their maintained regressions
in one linear branch. Preserve explicit digest behavior, key type and key
creation restrictions, signature options, and filesystem write restrictions.
Completion requires the full affected native targets, independent Node
signature comparisons, and the unchanged TUF metadata-client test to pass.
No changes outside src/node//src/workerd/api/node/ —
crypto_keys.ts, crypto_sign.ts, crypto.c++, filesystem.c++ plus
regression tests.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions