From 3b62a58818f83e32fc2644f44e75a5c8465b2003 Mon Sep 17 00:00:00 2001 From: Divy Srivastava Date: Thu, 6 Apr 2023 18:39:25 +0530 Subject: [PATCH] fix(ext/node): add symmetric keygen (#18609) Towards #18455 --- cli/tests/node_compat/config.json | 1 + .../parallel/test-crypto-secret-keygen.js | 137 ++++++++++++++++++ ext/node/crypto/mod.rs | 17 +++ ext/node/lib.rs | 2 + ext/node/polyfills/internal/crypto/keygen.ts | 79 ++++++++-- ext/node/polyfills/internal/crypto/keys.ts | 6 +- ext/node/polyfills/internal/crypto/util.ts | 5 +- 7 files changed, 231 insertions(+), 16 deletions(-) create mode 100644 cli/tests/node_compat/test/parallel/test-crypto-secret-keygen.js diff --git a/cli/tests/node_compat/config.json b/cli/tests/node_compat/config.json index cdfbf4677276dd..e314c19581fd65 100644 --- a/cli/tests/node_compat/config.json +++ b/cli/tests/node_compat/config.json @@ -230,6 +230,7 @@ "test-console-tty-colors.js", "test-crypto-hmac.js", "test-crypto-prime.js", + "test-crypto-secret-keygen.js", "test-dgram-close-during-bind.js", "test-dgram-close-signal.js", "test-diagnostics-channel-has-subscribers.js", diff --git a/cli/tests/node_compat/test/parallel/test-crypto-secret-keygen.js b/cli/tests/node_compat/test/parallel/test-crypto-secret-keygen.js new file mode 100644 index 00000000000000..09888221558243 --- /dev/null +++ b/cli/tests/node_compat/test/parallel/test-crypto-secret-keygen.js @@ -0,0 +1,137 @@ +'use strict'; +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); + +const assert = require('assert'); + +const { + generateKey, + generateKeySync +} = require('crypto'); + +[1, true, [], {}, Infinity, null, undefined].forEach((i) => { + assert.throws(() => generateKey(i, 1, common.mustNotCall()), { + code: 'ERR_INVALID_ARG_TYPE', + message: /The "type" argument must be / + }); + assert.throws(() => generateKeySync(i, 1), { + code: 'ERR_INVALID_ARG_TYPE', + message: /The "type" argument must be / + }); +}); + +['', true, [], null, undefined].forEach((i) => { + assert.throws(() => generateKey('aes', i, common.mustNotCall()), { + code: 'ERR_INVALID_ARG_TYPE', + message: /The "options" argument must be / + }); + assert.throws(() => generateKeySync('aes', i), { + code: 'ERR_INVALID_ARG_TYPE', + message: /The "options" argument must be / + }); +}); + +['', true, {}, [], null, undefined].forEach((length) => { + assert.throws(() => generateKey('hmac', { length }, common.mustNotCall()), { + code: 'ERR_INVALID_ARG_TYPE', + message: /The "options\.length" property must be / + }); + assert.throws(() => generateKeySync('hmac', { length }), { + code: 'ERR_INVALID_ARG_TYPE', + message: /The "options\.length" property must be / + }); +}); + +assert.throws(() => generateKey('aes', { length: 256 }), { + code: 'ERR_INVALID_ARG_TYPE' +}); + +assert.throws(() => generateKey('hmac', { length: -1 }, common.mustNotCall()), { + code: 'ERR_OUT_OF_RANGE' +}); + +assert.throws(() => generateKey('hmac', { length: 4 }, common.mustNotCall()), { + code: 'ERR_OUT_OF_RANGE' +}); + +assert.throws(() => generateKey('hmac', { length: 7 }, common.mustNotCall()), { + code: 'ERR_OUT_OF_RANGE' +}); + +assert.throws( + () => generateKey('hmac', { length: 2 ** 31 }, common.mustNotCall()), { + code: 'ERR_OUT_OF_RANGE' + }); + +assert.throws(() => generateKeySync('hmac', { length: -1 }), { + code: 'ERR_OUT_OF_RANGE' +}); + +assert.throws(() => generateKeySync('hmac', { length: 4 }), { + code: 'ERR_OUT_OF_RANGE' +}); + +assert.throws(() => generateKeySync('hmac', { length: 7 }), { + code: 'ERR_OUT_OF_RANGE' +}); + +assert.throws( + () => generateKeySync('hmac', { length: 2 ** 31 }), { + code: 'ERR_OUT_OF_RANGE' + }); + +assert.throws(() => generateKeySync('aes', { length: 123 }), { + code: 'ERR_INVALID_ARG_VALUE', + message: /The property 'options\.length' must be one of: 128, 192, 256/ +}); + +{ + const key = generateKeySync('aes', { length: 128 }); + assert(key); + const keybuf = key.export(); + assert.strictEqual(keybuf.byteLength, 128 / 8); + + generateKey('aes', { length: 128 }, common.mustSucceed((key) => { + assert(key); + const keybuf = key.export(); + assert.strictEqual(keybuf.byteLength, 128 / 8); + })); +} + +{ + const key = generateKeySync('aes', { length: 256 }); + assert(key); + const keybuf = key.export(); + assert.strictEqual(keybuf.byteLength, 256 / 8); + + generateKey('aes', { length: 256 }, common.mustSucceed((key) => { + assert(key); + const keybuf = key.export(); + assert.strictEqual(keybuf.byteLength, 256 / 8); + })); +} + +{ + const key = generateKeySync('hmac', { length: 123 }); + assert(key); + const keybuf = key.export(); + assert.strictEqual(keybuf.byteLength, Math.floor(123 / 8)); + + generateKey('hmac', { length: 123 }, common.mustSucceed((key) => { + assert(key); + const keybuf = key.export(); + assert.strictEqual(keybuf.byteLength, Math.floor(123 / 8)); + })); +} + +assert.throws( + () => generateKey('unknown', { length: 123 }, common.mustNotCall()), { + code: 'ERR_INVALID_ARG_VALUE', + message: /The argument 'type' must be a supported key type/ + }); + +assert.throws(() => generateKeySync('unknown', { length: 123 }), { + code: 'ERR_INVALID_ARG_VALUE', + message: /The argument 'type' must be a supported key type/ +}); \ No newline at end of file diff --git a/ext/node/crypto/mod.rs b/ext/node/crypto/mod.rs index 3529a3aa48adc8..499e99fea8eec4 100644 --- a/ext/node/crypto/mod.rs +++ b/ext/node/crypto/mod.rs @@ -8,6 +8,7 @@ use deno_core::ResourceId; use deno_core::StringOrBuffer; use deno_core::ZeroCopyBuf; use num_bigint::BigInt; +use rand::Rng; use std::future::Future; use std::rc::Rc; @@ -402,3 +403,19 @@ pub async fn op_node_pbkdf2_async( }) .await? } + +#[op] +pub fn op_node_generate_secret(buf: &mut [u8]) { + rand::thread_rng().fill(buf); +} + +#[op] +pub async fn op_node_generate_secret_async(len: i32) -> ZeroCopyBuf { + tokio::task::spawn_blocking(move || { + let mut buf = vec![0u8; len as usize]; + rand::thread_rng().fill(&mut buf[..]); + buf.into() + }) + .await + .unwrap() +} diff --git a/ext/node/lib.rs b/ext/node/lib.rs index 04fd07cab6052a..ec3e7ab25aa017 100644 --- a/ext/node/lib.rs +++ b/ext/node/lib.rs @@ -170,6 +170,8 @@ deno_core::extension!(deno_node, crypto::op_node_check_prime_bytes_async, crypto::op_node_pbkdf2, crypto::op_node_pbkdf2_async, + crypto::op_node_generate_secret, + crypto::op_node_generate_secret_async, crypto::op_node_sign, winerror::op_node_sys_to_uv_error, v8::op_v8_cached_data_version_tag, diff --git a/ext/node/polyfills/internal/crypto/keygen.ts b/ext/node/polyfills/internal/crypto/keygen.ts index dadc9c1981f42c..b490cedd79f1fd 100644 --- a/ext/node/polyfills/internal/crypto/keygen.ts +++ b/ext/node/polyfills/internal/crypto/keygen.ts @@ -2,18 +2,80 @@ // Copyright Joyent, Inc. and Node.js contributors. All rights reserved. MIT license. import { KeyObject } from "ext:deno_node/internal/crypto/keys.ts"; +import { kAesKeyLengths } from "ext:deno_node/internal/crypto/util.ts"; +import { + SecretKeyObject, + setOwnedKey, +} from "ext:deno_node/internal/crypto/keys.ts"; import { notImplemented } from "ext:deno_node/_utils.ts"; +import { ERR_INVALID_ARG_VALUE } from "ext:deno_node/internal/errors.ts"; +import { + validateFunction, + validateInteger, + validateObject, + validateOneOf, + validateString, +} from "ext:deno_node/internal/validators.mjs"; import { Buffer } from "ext:deno_node/buffer.ts"; import { KeyFormat, KeyType } from "ext:deno_node/internal/crypto/types.ts"; +const { core } = globalThis.__bootstrap; +const { ops } = core; + +function validateGenerateKey( + type: "hmac" | "aes", + options: { length: number }, +) { + validateString(type, "type"); + validateObject(options, "options"); + const { length } = options; + switch (type) { + case "hmac": + validateInteger(length, "options.length", 8, 2 ** 31 - 1); + break; + case "aes": + validateOneOf(length, "options.length", kAesKeyLengths); + break; + default: + throw new ERR_INVALID_ARG_VALUE( + "type", + type, + "must be a supported key type", + ); + } +} + +export function generateKeySync( + type: "hmac" | "aes", + options: { + length: number; + }, +): KeyObject { + validateGenerateKey(type, options); + const { length } = options; + + const key = new Uint8Array(Math.floor(length / 8)); + ops.op_node_generate_secret(key); + + return new SecretKeyObject(setOwnedKey(key)); +} + export function generateKey( - _type: "hmac" | "aes", - _options: { + type: "hmac" | "aes", + options: { length: number; }, - _callback: (err: Error | null, key: KeyObject) => void, + callback: (err: Error | null, key: KeyObject) => void, ) { - notImplemented("crypto.generateKey"); + validateGenerateKey(type, options); + validateFunction(callback, "callback"); + const { length } = options; + + core.opAsync("op_node_generate_secret_async", Math.floor(length / 8)).then( + (key) => { + callback(null, new SecretKeyObject(setOwnedKey(key))); + }, + ); } export interface BasePrivateKeyEncodingOptions { @@ -662,15 +724,6 @@ export function generateKeyPairSync( notImplemented("crypto.generateKeyPairSync"); } -export function generateKeySync( - _type: "hmac" | "aes", - _options: { - length: number; - }, -): KeyObject { - notImplemented("crypto.generateKeySync"); -} - export default { generateKey, generateKeySync, diff --git a/ext/node/polyfills/internal/crypto/keys.ts b/ext/node/polyfills/internal/crypto/keys.ts index eb7990b0690471..ef2aba737c4b35 100644 --- a/ext/node/polyfills/internal/crypto/keys.ts +++ b/ext/node/polyfills/internal/crypto/keys.ts @@ -279,7 +279,7 @@ export function prepareSecretKey( return getArrayBufferOrView(key, "key", encoding); } -class SecretKeyObject extends KeyObject { +export class SecretKeyObject extends KeyObject { constructor(handle: unknown) { super("secret", handle); } @@ -313,7 +313,7 @@ class SecretKeyObject extends KeyObject { } } -function setOwnedKey(key: Uint8Array): unknown { +export function setOwnedKey(key: Uint8Array): unknown { const handle = {}; KEY_STORE.set(handle, key); return handle; @@ -345,4 +345,6 @@ export default { isCryptoKey, KeyObject, prepareSecretKey, + setOwnedKey, + SecretKeyObject, }; diff --git a/ext/node/polyfills/internal/crypto/util.ts b/ext/node/polyfills/internal/crypto/util.ts index 317c1d50333362..ccb7726316319b 100644 --- a/ext/node/polyfills/internal/crypto/util.ts +++ b/ext/node/polyfills/internal/crypto/util.ts @@ -133,7 +133,9 @@ export function setEngine(_engine: string, _flags: typeof constants) { notImplemented("crypto.setEngine"); } -export { kHandle, kKeyObject }; +const kAesKeyLengths = [128, 192, 256]; + +export { kAesKeyLengths, kHandle, kKeyObject }; export default { getDefaultEncoding, @@ -147,4 +149,5 @@ export default { toBuf, kHandle, kKeyObject, + kAesKeyLengths, };