Skip to content

Commit 26d3414

Browse files
committed
fixup! crypto: add signDigest/verifyDigest and Ed25519ctx support
1 parent ce13128 commit 26d3414

2 files changed

Lines changed: 122 additions & 11 deletions

File tree

lib/internal/crypto/sig.js

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ const { Writable } = require('stream');
4646
const { Buffer } = require('buffer');
4747

4848
const {
49+
isAnyArrayBuffer,
4950
isArrayBufferView,
5051
} = require('internal/util/types');
5152

@@ -152,14 +153,38 @@ Sign.prototype.sign = function sign(options, encoding) {
152153
return ret;
153154
};
154155

156+
function validateDigestOrData(data, prehashed) {
157+
if (prehashed) {
158+
if (!isArrayBufferView(data) && !isAnyArrayBuffer(data)) {
159+
throw new ERR_INVALID_ARG_TYPE(
160+
'digest',
161+
['ArrayBuffer', 'Buffer', 'TypedArray', 'DataView'],
162+
data,
163+
);
164+
}
165+
return data;
166+
}
167+
168+
data = getArrayBufferOrView(data, 'data');
169+
170+
if (!isArrayBufferView(data)) {
171+
throw new ERR_INVALID_ARG_TYPE(
172+
'data',
173+
['Buffer', 'TypedArray', 'DataView'],
174+
data,
175+
);
176+
}
177+
return data;
178+
}
179+
155180
function signOneShotImpl(algorithm, data, key, callback, prehashed) {
156181
if (algorithm != null)
157182
validateString(algorithm, 'algorithm');
158183

159184
if (callback !== undefined)
160185
validateFunction(callback, 'callback');
161186

162-
data = getArrayBufferOrView(data, prehashed ? 'digest' : 'data');
187+
data = validateDigestOrData(data, prehashed);
163188

164189
if (!key)
165190
throw new ERR_CRYPTO_SIGN_KEY_REQUIRED();
@@ -260,16 +285,7 @@ function verifyOneShotImpl(algorithm, data, key, signature, callback, prehashed)
260285
if (callback !== undefined)
261286
validateFunction(callback, 'callback');
262287

263-
const dataName = prehashed ? 'digest' : 'data';
264-
data = getArrayBufferOrView(data, dataName);
265-
266-
if (!isArrayBufferView(data)) {
267-
throw new ERR_INVALID_ARG_TYPE(
268-
dataName,
269-
['Buffer', 'TypedArray', 'DataView'],
270-
data,
271-
);
272-
}
288+
data = validateDigestOrData(data, prehashed);
273289

274290
// Options specific to RSA
275291
const rsaPadding = getPadding(key);

test/parallel/test-crypto-sign-verify-digest.js

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,36 @@ if (hasOpenSSL(3, 2)) {
290290
}));
291291
}
292292

293+
// --- Async (callback) error handling ---
294+
{
295+
// Non-signing key type error is delivered via callback
296+
const x25519Priv = fixtures.readKey('x25519_private.pem', 'ascii');
297+
crypto.signDigest('sha256', Buffer.alloc(32), x25519Priv, common.mustCall((err) => {
298+
assert(err);
299+
assert.match(err.message, /operation not supported for this keytype/);
300+
}));
301+
}
302+
303+
if (hasOpenSSL(3, 2)) {
304+
// Wrong digest length error is delivered via callback
305+
const edPrivKey = fixtures.readKey('ed25519_private.pem', 'ascii');
306+
crypto.signDigest(null, Buffer.alloc(32), edPrivKey, common.mustCall((err) => {
307+
assert(err);
308+
assert.match(err.message, /invalid digest length/);
309+
}));
310+
}
311+
312+
if (hasOpenSSL(3, 5)) {
313+
// PrehashUnsupported error is delivered via callback
314+
const mldsaPrivKey = fixtures.readKey('ml_dsa_44_private.pem', 'ascii');
315+
crypto.signDigest(null, Buffer.alloc(32), mldsaPrivKey, common.mustCall((err) => {
316+
assert(err);
317+
// TODO(@panva): revisit how to make CryptoJob async failures retain
318+
// and decorate OpenSSL errors.
319+
assert.match(err.message, /Deriving bits failed/);
320+
}));
321+
}
322+
293323
// --- Error: unsupported key type for prehashed signing ---
294324
{
295325
// ML-DSA keys are one-shot-only and don't support prehashed signing.
@@ -350,6 +380,60 @@ if (hasOpenSSL(3, 2)) {
350380
}, { code: 'ERR_CRYPTO_INVALID_DIGEST' });
351381
}
352382

383+
// --- Error: string inputs are rejected (digest must be binary) ---
384+
{
385+
const privKey = fixtures.readKey('rsa_private_2048.pem', 'ascii');
386+
const pubKey = fixtures.readKey('rsa_public_2048.pem', 'ascii');
387+
388+
// 64-byte string (same length as a SHA-512 digest)
389+
const strDigest64 = 'a'.repeat(64);
390+
assert.throws(() => {
391+
crypto.signDigest('sha256', strDigest64, privKey);
392+
}, { code: 'ERR_INVALID_ARG_TYPE' });
393+
394+
assert.throws(() => {
395+
crypto.verifyDigest('sha256', strDigest64, pubKey, Buffer.alloc(256));
396+
}, { code: 'ERR_INVALID_ARG_TYPE' });
397+
398+
// 128-byte string
399+
const strDigest128 = 'b'.repeat(128);
400+
assert.throws(() => {
401+
crypto.signDigest('sha256', strDigest128, privKey);
402+
}, { code: 'ERR_INVALID_ARG_TYPE' });
403+
404+
assert.throws(() => {
405+
crypto.verifyDigest('sha256', strDigest128, pubKey, Buffer.alloc(256));
406+
}, { code: 'ERR_INVALID_ARG_TYPE' });
407+
}
408+
409+
// --- ECDSA: hash larger than curve order (cross-verify) ---
410+
// Using SHA-512 (64 bytes) with P-256 (32-byte order) and P-384 (48-byte order).
411+
// The digest is larger than the curve's order; ECDSA truncates it internally.
412+
{
413+
const curves = [
414+
{ priv: 'ec_p256_private.pem', pub: 'ec_p256_public.pem' },
415+
{ priv: 'ec_p384_private.pem', pub: 'ec_p384_public.pem' },
416+
];
417+
418+
for (const { priv, pub } of curves) {
419+
const privKey = fixtures.readKey(priv, 'ascii');
420+
const pubKey = fixtures.readKey(pub, 'ascii');
421+
422+
const digest = crypto.createHash('sha512').update(data).digest();
423+
424+
// signDigest + verifyDigest
425+
const sig = crypto.signDigest('sha512', digest, privKey);
426+
assert.strictEqual(crypto.verifyDigest('sha512', digest, pubKey, sig), true);
427+
428+
// Cross-verify: sign with crypto.signDigest, verify with crypto.verify
429+
assert.strictEqual(crypto.verify('sha512', data, pubKey, sig), true);
430+
431+
// Cross-verify: sign with crypto.sign, verify with crypto.verifyDigest
432+
const sig2 = crypto.sign('sha512', data, privKey);
433+
assert.strictEqual(crypto.verifyDigest('sha512', digest, pubKey, sig2), true);
434+
}
435+
}
436+
353437
// --- Error: wrong digest length for Ed25519ph/Ed448ph ---
354438
if (hasOpenSSL(3, 2)) {
355439
// Ed25519ph requires exactly 64-byte SHA-512 digest
@@ -367,4 +451,15 @@ if (hasOpenSSL(3, 2)) {
367451
crypto.signDigest(null, Buffer.alloc(32), privKey);
368452
}, { code: 'ERR_OSSL_INVALID_DIGEST_LENGTH' });
369453
}
454+
455+
// Ed448ph rejects a valid 128-byte SHAKE256 digest (must be exactly 64 bytes)
456+
{
457+
const privKey = fixtures.readKey('ed448_private.pem', 'ascii');
458+
const shake256_128 = crypto.createHash('shake256', { outputLength: 128 }).update(data).digest();
459+
assert.strictEqual(shake256_128.length, 128);
460+
461+
assert.throws(() => {
462+
crypto.signDigest(null, shake256_128, privKey);
463+
}, { code: 'ERR_OSSL_INVALID_DIGEST_LENGTH' });
464+
}
370465
}

0 commit comments

Comments
 (0)