summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cli/tests/node_compat/config.jsonc2
-rw-r--r--cli/tests/node_compat/test/parallel/test-crypto-dh.js214
-rw-r--r--ext/node/lib.rs2
-rw-r--r--ext/node/ops/crypto/dh.rs2
-rw-r--r--ext/node/ops/crypto/mod.rs25
-rw-r--r--ext/node/polyfills/internal/crypto/diffiehellman.ts151
-rw-r--r--tools/node_compat/TODO.md3
7 files changed, 372 insertions, 27 deletions
diff --git a/cli/tests/node_compat/config.jsonc b/cli/tests/node_compat/config.jsonc
index 2146daf92..8631efcad 100644
--- a/cli/tests/node_compat/config.jsonc
+++ b/cli/tests/node_compat/config.jsonc
@@ -45,6 +45,7 @@
"test-child-process-stdout-flush-exit.js",
"test-child-process-stdout-flush.js",
"test-console-instance.js",
+ "test-crypto-dh.js",
"test-crypto-hkdf.js",
"test-crypto-hmac.js",
"test-crypto-prime.js",
@@ -239,6 +240,7 @@
"test-console-sync-write-error.js",
"test-console-table.js",
"test-console-tty-colors.js",
+ "test-crypto-dh.js",
"test-crypto-hkdf.js",
"test-crypto-hmac.js",
"test-crypto-prime.js",
diff --git a/cli/tests/node_compat/test/parallel/test-crypto-dh.js b/cli/tests/node_compat/test/parallel/test-crypto-dh.js
new file mode 100644
index 000000000..b436207ac
--- /dev/null
+++ b/cli/tests/node_compat/test/parallel/test-crypto-dh.js
@@ -0,0 +1,214 @@
+// deno-fmt-ignore-file
+// deno-lint-ignore-file
+
+// Copyright Joyent and Node contributors. All rights reserved. MIT license.
+// Taken from Node 18.12.1
+// This file is automatically generated by "node/_tools/setup.ts". Do not modify this file manually
+
+'use strict';
+const common = require('../common');
+if (!common.hasCrypto)
+ common.skip('missing crypto');
+
+const assert = require('assert');
+const crypto = require('crypto');
+
+const size = common.hasFipsCrypto || common.hasOpenSSL3 ? 1024 : 256;
+const dh1 = crypto.createDiffieHellman(size);
+const p1 = dh1.getPrime('buffer');
+const dh2 = crypto.createDiffieHellman(p1, 'buffer');
+const key1 = dh1.generateKeys();
+const key2 = dh2.generateKeys('hex');
+const secret1 = dh1.computeSecret(key2, 'hex', 'base64');
+const secret2 = dh2.computeSecret(key1, 'latin1', 'buffer');
+
+// Test Diffie-Hellman with two parties sharing a secret,
+// using various encodings as we go along
+assert.strictEqual(secret2.toString('base64'), secret1);
+assert.strictEqual(dh1.verifyError, 0);
+assert.strictEqual(dh2.verifyError, 0);
+
+// https://github.com/nodejs/node/issues/32738
+// XXX(bnoordhuis) validateInt32() throwing ERR_OUT_OF_RANGE and RangeError
+// instead of ERR_INVALID_ARG_TYPE and TypeError is questionable, IMO.
+assert.throws(() => crypto.createDiffieHellman(13.37), {
+ code: 'ERR_OUT_OF_RANGE',
+ name: 'RangeError',
+ message: 'The value of "sizeOrKey" is out of range. ' +
+ 'It must be an integer. Received 13.37',
+});
+
+assert.throws(() => crypto.createDiffieHellman('abcdef', 13.37), {
+ code: 'ERR_OUT_OF_RANGE',
+ name: 'RangeError',
+ message: 'The value of "generator" is out of range. ' +
+ 'It must be an integer. Received 13.37',
+});
+
+for (const bits of [-1, 0, 1]) {
+ if (common.hasOpenSSL3) {
+ assert.throws(() => crypto.createDiffieHellman(bits), {
+ code: 'ERR_OSSL_DH_MODULUS_TOO_SMALL',
+ name: 'Error',
+ message: /modulus too small/,
+ });
+ } else {
+ assert.throws(() => crypto.createDiffieHellman(bits), {
+ code: 'ERR_OSSL_BN_BITS_TOO_SMALL',
+ name: 'Error',
+ message: /bits too small/,
+ });
+ }
+}
+
+// Through a fluke of history, g=0 defaults to DH_GENERATOR (2).
+{
+ const g = 0;
+ crypto.createDiffieHellman('abcdef', g);
+ crypto.createDiffieHellman('abcdef', 'hex', g);
+}
+
+for (const g of [-1, 1]) {
+ const ex = {
+ code: 'ERR_OSSL_DH_BAD_GENERATOR',
+ name: 'Error',
+ message: /bad generator/,
+ };
+ assert.throws(() => crypto.createDiffieHellman('abcdef', g), ex);
+ assert.throws(() => crypto.createDiffieHellman('abcdef', 'hex', g), ex);
+}
+
+crypto.createDiffieHellman('abcdef', Buffer.from([2])); // OK
+
+for (const g of [Buffer.from([]),
+ Buffer.from([0]),
+ Buffer.from([1])]) {
+ const ex = {
+ code: 'ERR_OSSL_DH_BAD_GENERATOR',
+ name: 'Error',
+ message: /bad generator/,
+ };
+ assert.throws(() => crypto.createDiffieHellman('abcdef', g), ex);
+ assert.throws(() => crypto.createDiffieHellman('abcdef', 'hex', g), ex);
+}
+
+[
+ [0x1, 0x2],
+ () => { },
+ /abc/,
+ {},
+].forEach((input) => {
+ assert.throws(
+ () => crypto.createDiffieHellman(input),
+ {
+ code: 'ERR_INVALID_ARG_TYPE',
+ name: 'TypeError',
+ }
+ );
+});
+
+// Create "another dh1" using generated keys from dh1,
+// and compute secret again
+const dh3 = crypto.createDiffieHellman(p1, 'buffer');
+const privkey1 = dh1.getPrivateKey();
+dh3.setPublicKey(key1);
+dh3.setPrivateKey(privkey1);
+
+assert.deepStrictEqual(dh1.getPrime(), dh3.getPrime());
+assert.deepStrictEqual(dh1.getGenerator(), dh3.getGenerator());
+assert.deepStrictEqual(dh1.getPublicKey(), dh3.getPublicKey());
+assert.deepStrictEqual(dh1.getPrivateKey(), dh3.getPrivateKey());
+assert.strictEqual(dh3.verifyError, 0);
+
+const secret3 = dh3.computeSecret(key2, 'hex', 'base64');
+
+assert.strictEqual(secret1, secret3);
+
+// computeSecret works without a public key set at all.
+const dh4 = crypto.createDiffieHellman(p1, 'buffer');
+dh4.setPrivateKey(privkey1);
+
+assert.deepStrictEqual(dh1.getPrime(), dh4.getPrime());
+assert.deepStrictEqual(dh1.getGenerator(), dh4.getGenerator());
+assert.deepStrictEqual(dh1.getPrivateKey(), dh4.getPrivateKey());
+assert.strictEqual(dh4.verifyError, 0);
+
+const secret4 = dh4.computeSecret(key2, 'hex', 'base64');
+
+assert.strictEqual(secret1, secret4);
+
+
+if (false) {
+ let wrongBlockLength;
+ if (common.hasOpenSSL3) {
+ wrongBlockLength = {
+ message: 'error:1C80006B:Provider routines::wrong final block length',
+ code: 'ERR_OSSL_WRONG_FINAL_BLOCK_LENGTH',
+ library: 'Provider routines',
+ reason: 'wrong final block length'
+ };
+ } else {
+ wrongBlockLength = {
+ message: 'error:0606506D:digital envelope' +
+ ' routines:EVP_DecryptFinal_ex:wrong final block length',
+ code: 'ERR_OSSL_EVP_WRONG_FINAL_BLOCK_LENGTH',
+ library: 'digital envelope routines',
+ reason: 'wrong final block length'
+ };
+ }
+
+ // Run this one twice to make sure that the dh3 clears its error properly
+ {
+ const c = crypto.createDecipheriv('aes-128-ecb', crypto.randomBytes(16), '');
+ assert.throws(() => {
+ c.final('utf8');
+ }, wrongBlockLength);
+ }
+
+ {
+ const c = crypto.createDecipheriv('aes-128-ecb', crypto.randomBytes(16), '');
+ assert.throws(() => {
+ c.final('utf8');
+ }, wrongBlockLength);
+ }
+
+ assert.throws(() => {
+ dh3.computeSecret('');
+ }, { message: common.hasOpenSSL3 ?
+ 'error:02800080:Diffie-Hellman routines::invalid secret' :
+ 'Supplied key is too small' });
+
+ // Invalid test: curve argument is undefined
+ assert.throws(
+ () => crypto.createECDH(),
+ {
+ code: 'ERR_INVALID_ARG_TYPE',
+ name: 'TypeError',
+ message: 'The "curve" argument must be of type string. ' +
+ 'Received undefined'
+ });
+
+ assert.throws(
+ function() {
+ crypto.getDiffieHellman('unknown-group');
+ },
+ {
+ name: 'Error',
+ code: 'ERR_CRYPTO_UNKNOWN_DH_GROUP',
+ message: 'Unknown DH group'
+ },
+ 'crypto.getDiffieHellman(\'unknown-group\') ' +
+ 'failed to throw the expected error.'
+ );
+}
+
+assert.throws(
+ () => crypto.createDiffieHellman('', true),
+ {
+ code: 'ERR_INVALID_ARG_TYPE'
+ }
+);
+[true, Symbol(), {}, () => {}, []].forEach((generator) => assert.throws(
+ () => crypto.createDiffieHellman('', 'base64', generator),
+ { code: 'ERR_INVALID_ARG_TYPE' }
+));
diff --git a/ext/node/lib.rs b/ext/node/lib.rs
index e01954109..aed325c93 100644
--- a/ext/node/lib.rs
+++ b/ext/node/lib.rs
@@ -170,6 +170,8 @@ deno_core::extension!(deno_node,
ops::crypto::op_node_dh_generate_group,
ops::crypto::op_node_dh_generate_group_async,
ops::crypto::op_node_dh_generate,
+ ops::crypto::op_node_dh_generate2,
+ ops::crypto::op_node_dh_compute_secret,
ops::crypto::op_node_dh_generate_async,
ops::crypto::op_node_verify,
ops::crypto::op_node_random_int,
diff --git a/ext/node/ops/crypto/dh.rs b/ext/node/ops/crypto/dh.rs
index 4da9a01bf..8b756d9a2 100644
--- a/ext/node/ops/crypto/dh.rs
+++ b/ext/node/ops/crypto/dh.rs
@@ -63,7 +63,7 @@ impl DiffieHellman {
}
pub fn new(prime: Prime, generator: usize) -> Self {
- let private_key = PrivateKey::new(32);
+ let private_key = PrivateKey::new(prime.bits());
let generator = BigUint::from_usize(generator).unwrap();
let public_key = private_key.compute_public_key(&generator, &prime);
diff --git a/ext/node/ops/crypto/mod.rs b/ext/node/ops/crypto/mod.rs
index 0f8feb2a9..05f2d34f7 100644
--- a/ext/node/ops/crypto/mod.rs
+++ b/ext/node/ops/crypto/mod.rs
@@ -11,6 +11,7 @@ use deno_core::StringOrBuffer;
use deno_core::ZeroCopyBuf;
use hkdf::Hkdf;
use num_bigint::BigInt;
+use num_bigint_dig::BigUint;
use num_traits::FromPrimitive;
use rand::distributions::Distribution;
use rand::distributions::Uniform;
@@ -788,6 +789,30 @@ pub fn op_node_dh_generate(
dh_generate(prime, prime_len, generator)
}
+// TODO(lev): This duplication should be avoided.
+#[op]
+pub fn op_node_dh_generate2(
+ prime: ZeroCopyBuf,
+ prime_len: usize,
+ generator: usize,
+) -> Result<(ZeroCopyBuf, ZeroCopyBuf), AnyError> {
+ dh_generate(Some(prime).as_deref(), prime_len, generator)
+}
+
+#[op]
+pub fn op_node_dh_compute_secret(
+ prime: ZeroCopyBuf,
+ private_key: ZeroCopyBuf,
+ their_public_key: ZeroCopyBuf,
+) -> Result<ZeroCopyBuf, AnyError> {
+ let pubkey: BigUint = BigUint::from_bytes_be(their_public_key.as_ref());
+ let privkey: BigUint = BigUint::from_bytes_be(private_key.as_ref());
+ let primei: BigUint = BigUint::from_bytes_be(prime.as_ref());
+ let shared_secret: BigUint = pubkey.modpow(&privkey, &primei);
+
+ Ok(shared_secret.to_bytes_be().into())
+}
+
#[op]
pub async fn op_node_dh_generate_async(
prime: Option<ZeroCopyBuf>,
diff --git a/ext/node/polyfills/internal/crypto/diffiehellman.ts b/ext/node/polyfills/internal/crypto/diffiehellman.ts
index 62a802126..2531c07c7 100644
--- a/ext/node/polyfills/internal/crypto/diffiehellman.ts
+++ b/ext/node/polyfills/internal/crypto/diffiehellman.ts
@@ -6,7 +6,10 @@ import {
isAnyArrayBuffer,
isArrayBufferView,
} from "ext:deno_node/internal/util/types.ts";
-import { ERR_INVALID_ARG_TYPE } from "ext:deno_node/internal/errors.ts";
+import {
+ ERR_INVALID_ARG_TYPE,
+ NodeError,
+} from "ext:deno_node/internal/errors.ts";
import {
validateInt32,
validateString,
@@ -32,9 +35,14 @@ const DH_GENERATOR = 2;
export class DiffieHellman {
verifyError!: number;
+ #prime: Buffer;
+ #primeLength: number;
+ #generator: Buffer;
+ #privateKey: Buffer;
+ #publicKey: Buffer;
constructor(
- sizeOrKey: unknown,
+ sizeOrKey: number | string | ArrayBufferView,
keyEncoding?: unknown,
generator?: unknown,
genEncoding?: unknown,
@@ -71,24 +79,68 @@ export class DiffieHellman {
genEncoding = genEncoding || encoding;
if (typeof sizeOrKey !== "number") {
- sizeOrKey = toBuf(sizeOrKey as string, keyEncoding as string);
+ this.#prime = toBuf(sizeOrKey as string, keyEncoding as string);
+ } else {
+ // The supplied parameter is our primeLength, generate a suitable prime.
+ this.#primeLength = sizeOrKey as number;
+ if (this.#primeLength < 2) {
+ throw new NodeError("ERR_OSSL_BN_BITS_TOO_SMALL", "bits too small");
+ }
+
+ this.#prime = Buffer.from(
+ ops.op_node_gen_prime(this.#primeLength).buffer,
+ );
}
if (!generator) {
- generator = DH_GENERATOR;
+ // While the commonly used cyclic group generators for DH are 2 and 5, we
+ // need this a buffer, because, well.. Node.
+ this.#generator = Buffer.alloc(4);
+ this.#generator.writeUint32BE(DH_GENERATOR);
} else if (typeof generator === "number") {
validateInt32(generator, "generator");
+ this.#generator = Buffer.alloc(4);
+ if (generator <= 0 || generator >= 0x7fffffff) {
+ throw new NodeError("ERR_OSSL_DH_BAD_GENERATOR", "bad generator");
+ }
+ this.#generator.writeUint32BE(generator);
} else if (typeof generator === "string") {
generator = toBuf(generator, genEncoding as string);
+ this.#generator = generator;
} else if (!isArrayBufferView(generator) && !isAnyArrayBuffer(generator)) {
throw new ERR_INVALID_ARG_TYPE(
"generator",
["number", "string", "ArrayBuffer", "Buffer", "TypedArray", "DataView"],
generator,
);
+ } else {
+ this.#generator = Buffer.from(generator);
}
- notImplemented("crypto.DiffieHellman");
+ this.#checkGenerator();
+
+ // TODO(lev): actually implement this value
+ this.verifyError = 0;
+ }
+
+ #checkGenerator(): number {
+ let generator: number;
+
+ if (this.#generator.length == 0) {
+ throw new NodeError("ERR_OSSL_DH_BAD_GENERATOR", "bad generator");
+ } else if (this.#generator.length == 1) {
+ generator = this.#generator.readUint8();
+ } else if (this.#generator.length == 2) {
+ generator = this.#generator.readUint16BE();
+ } else {
+ generator = this.#generator.readUint32BE();
+ }
+
+ if (generator != 2 && generator != 5) {
+ throw new NodeError("ERR_OSSL_DH_BAD_GENERATOR", "bad generator");
+ }
+
+ return generator;
}
computeSecret(otherPublicKey: ArrayBufferView): Buffer;
@@ -106,59 +158,110 @@ export class DiffieHellman {
outputEncoding: BinaryToTextEncoding,
): string;
computeSecret(
- _otherPublicKey: ArrayBufferView | string,
- _inputEncoding?: BinaryToTextEncoding,
- _outputEncoding?: BinaryToTextEncoding,
+ otherPublicKey: ArrayBufferView | string,
+ inputEncoding?: BinaryToTextEncoding,
+ outputEncoding?: BinaryToTextEncoding,
): Buffer | string {
- notImplemented("crypto.DiffieHellman.prototype.computeSecret");
+ let buf;
+ if (inputEncoding != undefined && inputEncoding != "buffer") {
+ buf = Buffer.from(otherPublicKey.buffer, inputEncoding);
+ } else {
+ buf = Buffer.from(otherPublicKey.buffer);
+ }
+
+ const sharedSecret = ops.op_node_dh_compute_secret(
+ this.#prime,
+ this.#privateKey,
+ buf,
+ );
+
+ if (outputEncoding == undefined || outputEncoding == "buffer") {
+ return Buffer.from(sharedSecret.buffer);
+ }
+
+ return Buffer.from(sharedSecret.buffer).toString(outputEncoding);
}
generateKeys(): Buffer;
generateKeys(encoding: BinaryToTextEncoding): string;
generateKeys(_encoding?: BinaryToTextEncoding): Buffer | string {
- notImplemented("crypto.DiffieHellman.prototype.generateKeys");
+ const generator = this.#checkGenerator();
+ const [privateKey, publicKey] = ops.op_node_dh_generate2(
+ this.#prime,
+ this.#primeLength,
+ generator,
+ );
+
+ this.#privateKey = Buffer.from(privateKey.buffer);
+ this.#publicKey = Buffer.from(publicKey.buffer);
+
+ return this.#publicKey;
}
getGenerator(): Buffer;
getGenerator(encoding: BinaryToTextEncoding): string;
- getGenerator(_encoding?: BinaryToTextEncoding): Buffer | string {
- notImplemented("crypto.DiffieHellman.prototype.getGenerator");
+ getGenerator(encoding?: BinaryToTextEncoding): Buffer | string {
+ if (encoding !== undefined && encoding != "buffer") {
+ return this.#generator.toString(encoding);
+ }
+
+ return this.#generator;
}
getPrime(): Buffer;
getPrime(encoding: BinaryToTextEncoding): string;
- getPrime(_encoding?: BinaryToTextEncoding): Buffer | string {
- notImplemented("crypto.DiffieHellman.prototype.getPrime");
+ getPrime(encoding?: BinaryToTextEncoding): Buffer | string {
+ if (encoding !== undefined && encoding != "buffer") {
+ return this.#prime.toString(encoding);
+ }
+
+ return this.#prime;
}
getPrivateKey(): Buffer;
getPrivateKey(encoding: BinaryToTextEncoding): string;
- getPrivateKey(_encoding?: BinaryToTextEncoding): Buffer | string {
- notImplemented("crypto.DiffieHellman.prototype.getPrivateKey");
+ getPrivateKey(encoding?: BinaryToTextEncoding): Buffer | string {
+ if (encoding !== undefined && encoding != "buffer") {
+ return this.#privateKey.toString(encoding);
+ }
+
+ return this.#privateKey;
}
getPublicKey(): Buffer;
getPublicKey(encoding: BinaryToTextEncoding): string;
- getPublicKey(_encoding?: BinaryToTextEncoding): Buffer | string {
- notImplemented("crypto.DiffieHellman.prototype.getPublicKey");
+ getPublicKey(encoding?: BinaryToTextEncoding): Buffer | string {
+ if (encoding !== undefined && encoding != "buffer") {
+ return this.#publicKey.toString(encoding);
+ }
+
+ return this.#publicKey;
}
setPrivateKey(privateKey: ArrayBufferView): void;
setPrivateKey(privateKey: string, encoding: BufferEncoding): void;
setPrivateKey(
- _privateKey: ArrayBufferView | string,
- _encoding?: BufferEncoding,
+ privateKey: ArrayBufferView | string,
+ encoding?: BufferEncoding,
) {
- notImplemented("crypto.DiffieHellman.prototype.setPrivateKey");
+ if (encoding == undefined || encoding == "buffer") {
+ this.#privateKey = Buffer.from(privateKey);
+ } else {
+ this.#privateKey = Buffer.from(privateKey, encoding);
+ }
}
setPublicKey(publicKey: ArrayBufferView): void;
setPublicKey(publicKey: string, encoding: BufferEncoding): void;
setPublicKey(
- _publicKey: ArrayBufferView | string,
- _encoding?: BufferEncoding,
+ publicKey: ArrayBufferView | string,
+ encoding?: BufferEncoding,
) {
- notImplemented("crypto.DiffieHellman.prototype.setPublicKey");
+ if (encoding == undefined || encoding == "buffer") {
+ this.#publicKey = Buffer.from(publicKey);
+ } else {
+ this.#publicKey = Buffer.from(publicKey, encoding);
+ }
}
}
diff --git a/tools/node_compat/TODO.md b/tools/node_compat/TODO.md
index 2ea6983f9..a94dbc090 100644
--- a/tools/node_compat/TODO.md
+++ b/tools/node_compat/TODO.md
@@ -3,7 +3,7 @@
NOTE: This file should not be manually edited. Please edit 'cli/tests/node_compat/config.json' and run 'tools/node_compat/setup.ts' instead.
-Total: 2935
+Total: 2934
- [abort/test-abort-backtrace.js](https://github.com/nodejs/node/tree/v18.12.1/test/abort/test-abort-backtrace.js)
- [abort/test-abort-fatal-error.js](https://github.com/nodejs/node/tree/v18.12.1/test/abort/test-abort-fatal-error.js)
@@ -476,7 +476,6 @@ Total: 2935
- [parallel/test-crypto-dh-padding.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-crypto-dh-padding.js)
- [parallel/test-crypto-dh-shared.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-crypto-dh-shared.js)
- [parallel/test-crypto-dh-stateless.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-crypto-dh-stateless.js)
-- [parallel/test-crypto-dh.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-crypto-dh.js)
- [parallel/test-crypto-domain.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-crypto-domain.js)
- [parallel/test-crypto-domains.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-crypto-domains.js)
- [parallel/test-crypto-ecb.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-crypto-ecb.js)