Files
scriptc/tests/corpus/6702-ecdh-errors.js
T
Chris Tate 2582e46e76 Support crypto.createECDH and ECDH.convertKey
crypto.createECDH returns a native ECDH handle backed by mbedTLS for
prime192v1, secp224r1, prime256v1, secp384r1, secp521r1, secp192k1,
secp224k1, secp256k1, brainpoolP256r1, brainpoolP384r1 and
brainpoolP512r1. generateKeys, computeSecret, getPublicKey,
getPrivateKey, setPrivateKey, setPublicKey and ECDH.convertKey follow
Node 24: compressed, uncompressed and hybrid points, input and output
encodings, the private key range, key pair validation, and Node's error
codes and messages.

Curve names follow OpenSSL's short-name lookup: names of other curves
from crypto.getCurves() are refused at compile time when literal and
throw a coded SC2020 error when computed, other OpenSSL short names
throw ERR_CRYPTO_OPERATION_FAILED, and unknown names throw
ERR_CRYPTO_INVALID_CURVE. Calls whose arguments no @types/node overload
accepts, and computeSecret(view, encoding), are refused at compile time.

The runtime unit scr_ecdh.c links, with the mbedTLS archive, only into
programs that use ECDH. wasm32-wasi builds refuse it.
2026-10-20 19:17:05 -05:00

99 lines
5.8 KiB
JavaScript

// createECDH and ECDH errors from JavaScript: curve argument types,
// OpenSSL names that are not curves, unknown names, key argument types,
// private keys outside [1, n), points that are not on the curve or not in
// a known form, format and encoding errors, keys used before they exist,
// and members ECDH does not have.
import crypto from "node:crypto";
// Values the declared overloads reject reach the calls through an
// untyped function, as they would from other untyped code.
const loose = (value) => value;
function attempt(label, run) {
try {
const result = run();
console.log(label, "ok", Buffer.isBuffer(result) ? result.toString("hex") : JSON.stringify(result));
} catch (e) {
console.log(label, e.name, e.code, e.message);
}
}
attempt("curve number", () => crypto.createECDH(loose(5)));
attempt("curve missing", () => crypto.createECDH(loose(undefined)));
attempt("curve object", () => crypto.createECDH(loose({})));
attempt("curve unknown", () => crypto.createECDH("bogus"));
attempt("curve case", () => crypto.createECDH("PRIME256V1"));
attempt("curve jwk name", () => crypto.createECDH("P-256"));
attempt("curve x25519", () => crypto.createECDH("x25519"));
attempt("short name X25519", () => crypto.createECDH("X25519"));
attempt("short name RSA", () => crypto.createECDH("RSA"));
attempt("short name SHA256", () => crypto.createECDH("SHA256"));
attempt("lower sha256", () => crypto.createECDH("sha256"));
const fresh = crypto.createECDH("prime256v1");
attempt("public before keys", () => fresh.getPublicKey());
attempt("private before keys", () => fresh.getPrivateKey());
attempt("secret before keys, invalid point", () => fresh.computeSecret(Buffer.alloc(65, 4)));
const peer = crypto.createECDH("prime256v1");
peer.setPrivateKey(Buffer.from([2]));
attempt("secret before keys, valid point", () => fresh.computeSecret(peer.getPublicKey()));
const ecdh = crypto.createECDH("prime256v1");
attempt("private zero", () => ecdh.setPrivateKey(Buffer.alloc(32)));
attempt("private empty", () => ecdh.setPrivateKey(Buffer.alloc(0)));
attempt(
"private order",
() => ecdh.setPrivateKey("ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551", "hex"),
);
attempt("private number", () => ecdh.setPrivateKey(loose(5)));
attempt("private null", () => ecdh.setPrivateKey(loose(null)));
attempt("private encoding", () => ecdh.setPrivateKey("abc", loose("bogus")));
attempt("private one", () => ecdh.setPrivateKey(Buffer.from([1])) === ecdh);
attempt("format bogus", () => ecdh.getPublicKey("hex", loose("bogus")));
attempt("format number", () => ecdh.getPublicKey(null, loose(5)));
attempt("format object", () => ecdh.getPublicKey("hex", loose({})));
attempt("encoding bogus", () => ecdh.getPublicKey(loose("bogus")));
attempt("encoding number", () => ecdh.getPublicKey(loose(7)));
attempt("encoding true", () => ecdh.getPublicKey(loose(true)));
attempt("encoding object", () => ecdh.getPublicKey(loose({})));
attempt("encoding empty", () => ecdh.getPublicKey(loose("")).length);
attempt("encoding buffer", () => ecdh.getPublicKey(loose("buffer")).length);
attempt("encoding BUFFER", () => ecdh.getPublicKey(loose("BUFFER")));
attempt("secret number", () => ecdh.computeSecret(loose(5)));
attempt("secret empty", () => ecdh.computeSecret(Buffer.alloc(0)));
attempt("secret wrong length", () => ecdh.computeSecret(Buffer.alloc(34, 2)));
attempt("secret prefix 05", () => ecdh.computeSecret(Buffer.concat([Buffer.from([5]), peer.getPublicKey().subarray(1)])));
attempt("secret x >= p", () => ecdh.computeSecret(Buffer.concat([Buffer.from([4]), Buffer.alloc(64, 0xff)])));
attempt("secret off curve", () => ecdh.computeSecret(Buffer.concat([Buffer.from([2]), Buffer.alloc(31), Buffer.from([1])])));
attempt("secret infinity", () => ecdh.computeSecret(Buffer.from([0])));
attempt("secret odd hex", () => ecdh.computeSecret("abc", "hex"));
attempt("secret input encoding", () => ecdh.computeSecret("abc", loose("bogus")));
attempt("secret input buffer", () => ecdh.computeSecret("abc", loose("buffer")));
attempt("secret input BUFFER", () => ecdh.computeSecret("abc", loose("BUFFER")));
attempt("secret output encoding", () => ecdh.computeSecret(peer.getPublicKey(), loose(null), loose("bogus")));
attempt("secret other curve", () => ecdh.computeSecret(crypto.createECDH("secp384r1").generateKeys()));
const hybrid = peer.getPublicKey(null, "hybrid");
hybrid[0] ^= 1;
attempt("secret hybrid parity", () => ecdh.computeSecret(hybrid));
attempt("convert curve number", () => crypto.ECDH.convertKey(peer.getPublicKey(), loose(5)));
attempt("convert key number", () => crypto.ECDH.convertKey(loose(5), "prime256v1"));
attempt("convert unknown curve", () => crypto.ECDH.convertKey(peer.getPublicKey(), "bogus"));
attempt("convert short name", () => crypto.ECDH.convertKey(peer.getPublicKey(), "X25519"));
attempt("convert bad point", () => crypto.ECDH.convertKey(Buffer.alloc(65, 4), "prime256v1"));
attempt("convert other curve", () => crypto.ECDH.convertKey(peer.getPublicKey(), "secp384r1"));
attempt("convert bad format", () => crypto.ECDH.convertKey(peer.getPublicKey(), "prime256v1", loose(null), loose(null), loose("zip")));
attempt("convert empty, bad curve", () => crypto.ECDH.convertKey("", "bogus", undefined, loose("bogus")));
attempt("convert empty, bad format", () => crypto.ECDH.convertKey("", "prime256v1", undefined, undefined, loose("zip")));
attempt("convert infinity extra byte", () => crypto.ECDH.convertKey(Buffer.from([0, 0]), "prime256v1"));
attempt("convert prefix 01", () => crypto.ECDH.convertKey(Buffer.from([1]), "prime256v1"));
// generateKeys creates the keys before it reads the format.
const later = crypto.createECDH("secp256k1");
attempt("generate bad format", () => later.generateKeys("hex", loose("bad")));
attempt("keys after bad format", () => later.getPrivateKey().length > 0);
attempt("missing method", () => later.foo());