1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-12-24 08:09:08 -05:00

feat(node/crypto): Diffie Hellman Support (#18943)

Support crypto.DiffieHellman class in ext/node/crypto
This commit is contained in:
Levente Kurusa 2023-05-15 19:41:53 +02:00 committed by GitHub
parent bfe93c6e81
commit 3356173d00
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 372 additions and 27 deletions

View file

@ -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",

View file

@ -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' }
));

View file

@ -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,

View file

@ -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);

View file

@ -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>,

View file

@ -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);
}
}
}

View file

@ -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)