Skip to content

Commit a0876df

Browse files
divybotlittledivy
andauthored
fix(ext/node): implement ECDH validation and DH verifyError (#33751)
## Summary Enables `test-crypto-dh-curves` in node_compat suite. ## Test plan - [x] `cargo test --test node_compat -- test-crypto-dh-curves` Co-authored-by: Divy Srivastava <me@littledivy.com>
1 parent 3cb894c commit a0876df

4 files changed

Lines changed: 329 additions & 77 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ Untitled*.ipynb
4747
/.ms-playwright
4848

4949
**/.claude/settings.local.json
50+
**/.claude/scheduled_tasks.lock
5051

5152
# pyenv
5253
/.python-version

ext/node/polyfills/internal/crypto/diffiehellman.ts

Lines changed: 108 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,16 @@
55
// deno-lint-ignore-file prefer-primordials
66

77
import {
8+
op_node_dh_check,
89
op_node_dh_compute_secret,
910
op_node_dh_keys_generate_and_export,
1011
op_node_diffie_hellman,
1112
op_node_ecdh_compute_public_key,
1213
op_node_ecdh_compute_secret,
1314
op_node_ecdh_encode_pubkey,
1415
op_node_ecdh_generate_keys,
16+
op_node_ecdh_validate_private_key,
17+
op_node_ecdh_validate_public_key,
1518
op_node_gen_prime,
1619
} from "ext:core/ops";
1720

@@ -21,6 +24,7 @@ import {
2124
} from "ext:deno_node/internal/util/types.ts";
2225
import {
2326
ERR_CRYPTO_ECDH_INVALID_FORMAT,
27+
ERR_CRYPTO_ECDH_INVALID_PUBLIC_KEY,
2428
ERR_CRYPTO_INCOMPATIBLE_KEY,
2529
ERR_CRYPTO_UNKNOWN_DH_GROUP,
2630
ERR_INVALID_ARG_TYPE,
@@ -177,8 +181,7 @@ export class DiffieHellmanImpl {
177181

178182
this.#checkGenerator();
179183

180-
// TODO(lev): actually implement this value
181-
this.verifyError = 0;
184+
this.verifyError = op_node_dh_check(this.#prime, this.#generator);
182185
}
183186

184187
#checkGenerator(): number {
@@ -1338,10 +1341,30 @@ export function ECDH(curve: string) {
13381341
return new ECDHImpl(curve);
13391342
}
13401343

1344+
function validateEcdhFormat(format: ECDHKeyFormat | string): void {
1345+
if (
1346+
format !== "compressed" &&
1347+
format !== "uncompressed" &&
1348+
format !== "hybrid"
1349+
) {
1350+
throw new ERR_CRYPTO_ECDH_INVALID_FORMAT(String(format));
1351+
}
1352+
}
1353+
1354+
function ecdhEncode(
1355+
buffer: Buffer,
1356+
encoding?: BinaryToTextEncoding | "buffer",
1357+
): Buffer | string {
1358+
if (encoding === undefined || encoding === "buffer") {
1359+
return buffer;
1360+
}
1361+
return buffer.toString(encoding);
1362+
}
1363+
13411364
export class ECDHImpl {
13421365
#curve: EllipticCurve; // the selected curve
1343-
#privbuf: Buffer; // the private key
1344-
#pubbuf: Buffer; // the public key
1366+
#privbuf: Buffer | null = null; // the private key
1367+
#pubbuf: Buffer | null = null; // the public key
13451368

13461369
constructor(curve: string) {
13471370
validateString(curve, "curve");
@@ -1352,8 +1375,6 @@ export class ECDHImpl {
13521375
}
13531376

13541377
this.#curve = c;
1355-
this.#pubbuf = Buffer.alloc(this.#curve.publicKeySize);
1356-
this.#privbuf = Buffer.alloc(this.#curve.privateKeySize);
13571378
}
13581379

13591380
static convertKey(
@@ -1424,19 +1445,41 @@ export class ECDHImpl {
14241445
): string;
14251446
computeSecret(
14261447
otherPublicKey: ArrayBufferView | string,
1427-
_inputEncoding?: BinaryToTextEncoding,
1428-
_outputEncoding?: BinaryToTextEncoding,
1448+
inputEncoding?: BinaryToTextEncoding,
1449+
outputEncoding?: BinaryToTextEncoding,
14291450
): Buffer | string {
1451+
if (this.#privbuf === null) {
1452+
throw new ERR_CRYPTO_ECDH_INVALID_PUBLIC_KEY();
1453+
}
1454+
1455+
const otherBuf = typeof otherPublicKey === "string"
1456+
? Buffer.from(otherPublicKey, inputEncoding)
1457+
: Buffer.from(
1458+
otherPublicKey.buffer,
1459+
otherPublicKey.byteOffset,
1460+
otherPublicKey.byteLength,
1461+
);
1462+
14301463
const secretBuf = Buffer.alloc(this.#curve.sharedSecretSize);
14311464

1432-
op_node_ecdh_compute_secret(
1433-
this.#curve.name,
1434-
this.#privbuf,
1435-
otherPublicKey,
1436-
secretBuf,
1437-
);
1465+
try {
1466+
op_node_ecdh_compute_secret(
1467+
this.#curve.name,
1468+
this.#privbuf,
1469+
this.#pubbuf,
1470+
otherBuf,
1471+
secretBuf,
1472+
);
1473+
} catch (e) {
1474+
// deno-lint-ignore no-explicit-any
1475+
const err = e as any;
1476+
if (err && err.message === "Invalid key pair") {
1477+
throw new Error("Invalid key pair");
1478+
}
1479+
throw new ERR_CRYPTO_ECDH_INVALID_PUBLIC_KEY();
1480+
}
14381481

1439-
return secretBuf;
1482+
return ecdhEncode(secretBuf, outputEncoding ?? "buffer");
14401483
}
14411484

14421485
generateKeys(): Buffer;
@@ -1445,31 +1488,41 @@ export class ECDHImpl {
14451488
encoding?: BinaryToTextEncoding,
14461489
format: ECDHKeyFormat = "uncompressed",
14471490
): Buffer | string {
1448-
this.#pubbuf = Buffer.alloc(
1491+
validateEcdhFormat(format);
1492+
const pubbuf = Buffer.alloc(
14491493
format == "compressed"
14501494
? this.#curve.publicKeySizeCompressed
14511495
: this.#curve.publicKeySize,
14521496
);
1497+
const privbuf = Buffer.alloc(this.#curve.privateKeySize);
14531498
op_node_ecdh_generate_keys(
14541499
this.#curve.name,
1455-
this.#pubbuf,
1456-
this.#privbuf,
1500+
pubbuf,
1501+
privbuf,
14571502
format,
14581503
);
1504+
this.#pubbuf = pubbuf;
1505+
this.#privbuf = privbuf;
14591506

1460-
if (encoding !== undefined) {
1461-
return this.#pubbuf.toString(encoding);
1507+
if (format === "hybrid") {
1508+
const compressedBuf = Buffer.from(op_node_ecdh_encode_pubkey(
1509+
this.#curve.name,
1510+
pubbuf,
1511+
true,
1512+
));
1513+
pubbuf[0] = compressedBuf[0] + 4;
14621514
}
1463-
return this.#pubbuf;
1515+
1516+
return ecdhEncode(pubbuf, encoding ?? "buffer");
14641517
}
14651518

14661519
getPrivateKey(): Buffer;
14671520
getPrivateKey(encoding: BinaryToTextEncoding): string;
14681521
getPrivateKey(encoding?: BinaryToTextEncoding): Buffer | string {
1469-
if (encoding !== undefined) {
1470-
return this.#privbuf.toString(encoding);
1522+
if (this.#privbuf === null) {
1523+
throw new Error("Failed to get ECDH private key");
14711524
}
1472-
return this.#privbuf;
1525+
return ecdhEncode(this.#privbuf, encoding ?? "buffer");
14731526
}
14741527

14751528
getPublicKey(): Buffer;
@@ -1478,6 +1531,10 @@ export class ECDHImpl {
14781531
encoding?: BinaryToTextEncoding,
14791532
format: ECDHKeyFormat = "uncompressed",
14801533
): Buffer | string {
1534+
if (this.#pubbuf === null) {
1535+
throw new Error("Failed to get ECDH public key");
1536+
}
1537+
validateEcdhFormat(format);
14811538
const pubbuf = Buffer.from(op_node_ecdh_encode_pubkey(
14821539
this.#curve.name,
14831540
this.#pubbuf,
@@ -1491,10 +1548,7 @@ export class ECDHImpl {
14911548
));
14921549
pubbuf[0] = compressedBuf[0] + 4;
14931550
}
1494-
if (encoding !== undefined) {
1495-
return pubbuf.toString(encoding);
1496-
}
1497-
return pubbuf;
1551+
return ecdhEncode(pubbuf, encoding ?? "buffer");
14981552
}
14991553

15001554
setPrivateKey(privateKey: ArrayBufferView): void;
@@ -1503,21 +1557,25 @@ export class ECDHImpl {
15031557
privateKey: ArrayBufferView | string,
15041558
encoding?: BinaryToTextEncoding,
15051559
): Buffer | string {
1506-
this.#privbuf = typeof privateKey === "string"
1560+
const privbuf = typeof privateKey === "string"
15071561
? Buffer.from(privateKey, encoding)
1508-
: Buffer.from(privateKey);
1509-
this.#pubbuf = Buffer.alloc(this.#curve.publicKeySize);
1510-
1511-
op_node_ecdh_compute_public_key(
1512-
this.#curve.name,
1513-
this.#privbuf,
1514-
this.#pubbuf,
1515-
);
1562+
: Buffer.from(
1563+
privateKey.buffer,
1564+
privateKey.byteOffset,
1565+
privateKey.byteLength,
1566+
);
15161567

1517-
if (encoding !== undefined) {
1518-
return this.#pubbuf.toString(encoding);
1568+
if (!op_node_ecdh_validate_private_key(this.#curve.name, privbuf)) {
1569+
throw new Error("Private key is not valid for specified curve");
15191570
}
1520-
return this.#pubbuf;
1571+
1572+
const pubbuf = Buffer.alloc(this.#curve.publicKeySize);
1573+
op_node_ecdh_compute_public_key(this.#curve.name, privbuf, pubbuf);
1574+
1575+
this.#privbuf = privbuf;
1576+
this.#pubbuf = pubbuf;
1577+
1578+
return pubbuf;
15211579
}
15221580

15231581
setPublicKey(publicKey: ArrayBufferView): void;
@@ -1526,9 +1584,17 @@ export class ECDHImpl {
15261584
publicKey: ArrayBufferView | string,
15271585
encoding?: BinaryToTextEncoding,
15281586
): void {
1529-
this.#pubbuf = typeof publicKey === "string"
1587+
const pubbuf = typeof publicKey === "string"
15301588
? Buffer.from(publicKey, encoding)
1531-
: Buffer.from(publicKey);
1589+
: Buffer.from(
1590+
publicKey.buffer,
1591+
publicKey.byteOffset,
1592+
publicKey.byteLength,
1593+
);
1594+
if (!op_node_ecdh_validate_public_key(this.#curve.name, pubbuf)) {
1595+
throw new Error("Failed to convert Buffer to EC_POINT");
1596+
}
1597+
this.#pubbuf = pubbuf;
15321598
}
15331599
}
15341600

0 commit comments

Comments
 (0)