From 92605050493b0c703fe15dd8ecb495f657f1bada Mon Sep 17 00:00:00 2001 From: Divy Srivastava Date: Wed, 19 Apr 2023 22:27:34 +0530 Subject: [PATCH] fix(ext/node): implement asymmetric keygen (#18651) Towards #18455 This commit implements the keypair generation for asymmetric keys for the `generateKeyPair` API. See how key material is managed in this implementation: https://www.notion.so/denolandinc/node-crypto-design-99fc33f568d24e47a5e4b36002c5325d?pvs=4 Private and public key encoding depend on `KeyObject#export` which is not implemented. I've also skipped ED448 and X448 since we need a crate for that in WebCrypto too. --- .github/workflows/ci.generate.ts | 2 +- .github/workflows/ci.yml | 4 +- Cargo.lock | 107 +++++-- cli/tests/unit_node/crypto_key.ts | 161 ++++++++++- ext/node/Cargo.toml | 5 + ext/node/crypto/dh.rs | 288 +++++++++++++++++++ ext/node/crypto/mod.rs | 272 ++++++++++++++++++ ext/node/crypto/primes.rs | 25 ++ ext/node/lib.rs | 14 + ext/node/polyfills/internal/crypto/keygen.ts | 275 +++++++++++++++++- 10 files changed, 1120 insertions(+), 33 deletions(-) create mode 100644 ext/node/crypto/dh.rs diff --git a/.github/workflows/ci.generate.ts b/.github/workflows/ci.generate.ts index 5bf55f62b4828e..241b38ef990ac2 100755 --- a/.github/workflows/ci.generate.ts +++ b/.github/workflows/ci.generate.ts @@ -17,7 +17,7 @@ const Runners = (() => { })(); // bump the number at the start when you want to purge the cache const prCacheKeyPrefix = - "19-cargo-target-${{ matrix.os }}-${{ matrix.profile }}-${{ matrix.job }}-"; + "20-cargo-target-${{ matrix.os }}-${{ matrix.profile }}-${{ matrix.job }}-"; const installPkgsCommand = "sudo apt-get install --no-install-recommends debootstrap clang-15 lld-15"; diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b0dab622aab8bd..c7144164b60846 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -302,7 +302,7 @@ jobs: !./target/*/*.zip !./target/*/*.tar.gz key: never_saved - restore-keys: '19-cargo-target-${{ matrix.os }}-${{ matrix.profile }}-${{ matrix.job }}-' + restore-keys: '20-cargo-target-${{ matrix.os }}-${{ matrix.profile }}-${{ matrix.job }}-' - name: Apply and update mtime cache if: '!(github.event_name == ''pull_request'' && matrix.skip_pr) && (!startsWith(github.ref, ''refs/tags/''))' uses: ./.github/mtime_cache @@ -578,7 +578,7 @@ jobs: !./target/*/gn_out !./target/*/*.zip !./target/*/*.tar.gz - key: '19-cargo-target-${{ matrix.os }}-${{ matrix.profile }}-${{ matrix.job }}-${{ github.sha }}' + key: '20-cargo-target-${{ matrix.os }}-${{ matrix.profile }}-${{ matrix.job }}-${{ github.sha }}' publish-canary: name: publish canary runs-on: ubuntu-22.04 diff --git a/Cargo.lock b/Cargo.lock index fdb9ecd24f052d..57ba8a941c05d4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -927,8 +927,8 @@ dependencies = [ "serde_bytes", "sha1", "sha2", - "signature", - "spki", + "signature 1.6.4", + "spki 0.6.0", "tokio", "uuid", "x25519-dalek", @@ -1152,6 +1152,7 @@ dependencies = [ "data-encoding", "deno_core", "digest 0.10.6", + "dsa", "ecb", "hex", "hkdf", @@ -1162,6 +1163,7 @@ dependencies = [ "md-5", "md4", "num-bigint", + "num-bigint-dig", "num-integer", "num-traits", "once_cell", @@ -1169,6 +1171,7 @@ dependencies = [ "pbkdf2", "rand", "regex", + "ring", "ripemd", "rsa", "scrypt", @@ -1176,9 +1179,10 @@ dependencies = [ "sha-1 0.10.0", "sha2", "sha3", - "signature", + "signature 1.6.4", "tokio", "typenum", + "x25519-dalek", "x509-parser", ] @@ -1386,6 +1390,16 @@ dependencies = [ "zeroize", ] +[[package]] +name = "der" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82b10af9f9f9f2134a42d3f8aa74658660f2e0234b0eb81bd171df8aa32779ed" +dependencies = [ + "const-oid", + "zeroize", +] + [[package]] name = "der-parser" version = "8.2.0" @@ -1565,6 +1579,22 @@ dependencies = [ "text_lines", ] +[[package]] +name = "dsa" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5638f6d17447bc0ffc46354949ee366847e83450e2a07895862942085cc9761" +dependencies = [ + "digest 0.10.6", + "num-bigint-dig", + "num-traits", + "pkcs8 0.10.2", + "rfc6979 0.4.0", + "sha2", + "signature 2.1.0", + "zeroize", +] + [[package]] name = "dyn-clone" version = "1.0.11" @@ -1612,10 +1642,10 @@ version = "0.14.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "413301934810f597c1d19ca71c8710e99a3f1ba28a0d2ebc01551a2daeea3c5c" dependencies = [ - "der", + "der 0.6.1", "elliptic-curve", - "rfc6979", - "signature", + "rfc6979 0.3.1", + "signature 1.6.4", ] [[package]] @@ -1632,14 +1662,14 @@ checksum = "e7bb888ab5300a19b8e5bceef25ac745ad065f3c9f7efc6de1b91958110891d3" dependencies = [ "base16ct", "crypto-bigint", - "der", + "der 0.6.1", "digest 0.10.6", "ff", "generic-array 0.14.6", "group", "hkdf", "pem-rfc7468", - "pkcs8", + "pkcs8 0.9.0", "rand_core 0.6.4", "sec1", "subtle", @@ -2981,6 +3011,7 @@ dependencies = [ "num-iter", "num-traits", "rand", + "serde", "smallvec", "zeroize", ] @@ -3312,9 +3343,9 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eff33bdbdfc54cc98a2eca766ebdec3e1b8fb7387523d5c9c9a2891da856f719" dependencies = [ - "der", - "pkcs8", - "spki", + "der 0.6.1", + "pkcs8 0.9.0", + "spki 0.6.0", "zeroize", ] @@ -3324,8 +3355,18 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9eca2c590a5f85da82668fa685c09ce2888b9430e83299debf1f34b65fd4a4ba" dependencies = [ - "der", - "spki", + "der 0.6.1", + "spki 0.6.0", +] + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der 0.7.3", + "spki 0.7.1", ] [[package]] @@ -3654,6 +3695,16 @@ dependencies = [ "zeroize", ] +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac", + "subtle", +] + [[package]] name = "ring" version = "0.16.20" @@ -3691,9 +3742,9 @@ dependencies = [ "num-iter", "num-traits", "pkcs1", - "pkcs8", + "pkcs8 0.9.0", "rand_core 0.6.4", - "signature", + "signature 1.6.4", "smallvec", "subtle", "zeroize", @@ -3906,9 +3957,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3be24c1842290c45df0a7bf069e0c268a747ad05a192f2fd7dcfdbc1cba40928" dependencies = [ "base16ct", - "der", + "der 0.6.1", "generic-array 0.14.6", - "pkcs8", + "pkcs8 0.9.0", "subtle", "zeroize", ] @@ -4128,6 +4179,16 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "signature" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e1788eed21689f9cf370582dfc467ef36ed9c707f073528ddafa8d83e3b8500" +dependencies = [ + "digest 0.10.6", + "rand_core 0.6.4", +] + [[package]] name = "simdutf8" version = "0.1.4" @@ -4204,7 +4265,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67cf02bbac7a337dc36e4f5a693db6c21e7863f45070f7064577eb4367a3212b" dependencies = [ "base64ct", - "der", + "der 0.6.1", +] + +[[package]] +name = "spki" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37a5be806ab6f127c3da44b7378837ebf01dadca8510a0e572460216b228bd0e" +dependencies = [ + "base64ct", + "der 0.7.3", ] [[package]] diff --git a/cli/tests/unit_node/crypto_key.ts b/cli/tests/unit_node/crypto_key.ts index d1a33db9e5f387..49d81003f01c79 100644 --- a/cli/tests/unit_node/crypto_key.ts +++ b/cli/tests/unit_node/crypto_key.ts @@ -1,9 +1,39 @@ +// deno-lint-ignore-file no-explicit-any + // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. -import { createSecretKey, randomBytes } from "node:crypto"; +import { + createSecretKey, + generateKeyPair, + generateKeyPairSync, + KeyObject, + randomBytes, +} from "node:crypto"; +import { promisify } from "node:util"; import { Buffer } from "node:buffer"; -import { assertEquals } from "../../../test_util/std/testing/asserts.ts"; +import { + assertEquals, + assertThrows, +} from "../../../test_util/std/testing/asserts.ts"; import { createHmac } from "node:crypto"; +const generateKeyPairAsync = promisify( + ( + type: any, + options: any, + callback: ( + err: Error | null, + key: { publicKey: KeyObject; privateKey: KeyObject }, + ) => void, + ) => + generateKeyPair( + type, + options, + (err: Error | null, publicKey: KeyObject, privateKey: KeyObject) => { + callback(err, { publicKey, privateKey }); + }, + ), +); + Deno.test({ name: "create secret key", fn() { @@ -45,3 +75,130 @@ Deno.test({ ); }, }); + +for (const type of ["rsa", "rsa-pss", "dsa"]) { + for (const modulusLength of [2048, 3072]) { + Deno.test({ + name: `generate ${type} key`, + fn() { + const { publicKey, privateKey } = generateKeyPairSync(type as any, { + modulusLength, + }); + + assertEquals(publicKey.type, "public"); + assertEquals(privateKey.type, "private"); + }, + }); + + Deno.test({ + name: `generate ${type} key async`, + async fn() { + const x = await generateKeyPairAsync(type as any, { + modulusLength, + }); + const { publicKey, privateKey } = x; + assertEquals(publicKey.type, "public"); + assertEquals(privateKey.type, "private"); + }, + }); + } +} + +for (const namedCurve of ["P-384", "P-256"]) { + Deno.test({ + name: `generate ec key ${namedCurve}`, + fn() { + const { publicKey, privateKey } = generateKeyPairSync("ec", { + namedCurve, + }); + + assertEquals(publicKey.type, "public"); + assertEquals(privateKey.type, "private"); + }, + }); + + Deno.test({ + name: `generate ec key ${namedCurve} async`, + async fn() { + const { publicKey, privateKey } = await generateKeyPairAsync("ec", { + namedCurve, + }); + + assertEquals(publicKey.type, "public"); + assertEquals(privateKey.type, "private"); + }, + }); + + Deno.test({ + name: `generate ec key ${namedCurve} paramEncoding=explicit fails`, + fn() { + assertThrows(() => { + // @ts-ignore: @types/node is broken? + generateKeyPairSync("ec", { + namedCurve, + paramEncoding: "explicit", + }); + }); + }, + }); +} + +for ( + const groupName of ["modp5", "modp14", "modp15", "modp16", "modp17", "modp18"] +) { + Deno.test({ + name: `generate dh key ${groupName}`, + fn() { + // @ts-ignore: @types/node is broken? + const { publicKey, privateKey } = generateKeyPairSync("dh", { + group: groupName, + }); + + assertEquals(publicKey.type, "public"); + assertEquals(privateKey.type, "private"); + }, + }); + + Deno.test({ + name: `generate dh key ${groupName} async`, + async fn() { + // @ts-ignore: @types/node is broken? + const { publicKey, privateKey } = await generateKeyPairAsync("dh", { + group: groupName, + }); + + assertEquals(publicKey.type, "public"); + assertEquals(privateKey.type, "private"); + }, + }); +} + +for (const primeLength of [1024, 2048, 4096]) { + Deno.test({ + name: `generate dh key ${primeLength}`, + fn() { + // @ts-ignore: @types/node is broken? + const { publicKey, privateKey } = generateKeyPairSync("dh", { + primeLength, + generator: 2, + }); + + assertEquals(publicKey.type, "public"); + assertEquals(privateKey.type, "private"); + }, + }); + + Deno.test({ + name: `generate dh key ${primeLength} async`, + async fn() { + // @ts-ignore: @types/node is broken? + const { publicKey, privateKey } = await generateKeyPairAsync("dh", { + primeLength, + generator: 2, + }); + + assertEquals(publicKey.type, "public"); + assertEquals(privateKey.type, "private"); + }, + }); +} diff --git a/ext/node/Cargo.toml b/ext/node/Cargo.toml index 5827b585aa7d5e..0d647e4f02811c 100644 --- a/ext/node/Cargo.toml +++ b/ext/node/Cargo.toml @@ -19,6 +19,7 @@ cbc.workspace = true data-encoding = "2.3.3" deno_core.workspace = true digest = { version = "0.10.5", features = ["core-api", "std"] } +dsa = "0.6.1" ecb.workspace = true hex.workspace = true hkdf.workspace = true @@ -29,6 +30,7 @@ libz-sys = { version = "1.1.8", features = ["static"] } md-5 = "0.10.5" md4 = "0.10.2" num-bigint.workspace = true +num-bigint-dig = "0.8.2" num-integer = "0.1.45" num-traits = "0.2.14" once_cell.workspace = true @@ -36,6 +38,7 @@ path-clean = "=0.1.0" pbkdf2 = "0.12.1" rand.workspace = true regex.workspace = true +ring.workspace = true ripemd = "0.1.3" rsa.workspace = true scrypt = "0.11.0" @@ -46,4 +49,6 @@ sha3 = "0.10.5" signature.workspace = true tokio.workspace = true typenum = "1.15.0" +# https://github.com/dalek-cryptography/x25519-dalek/pull/89 +x25519-dalek = "2.0.0-pre.1" x509-parser = "0.15.0" diff --git a/ext/node/crypto/dh.rs b/ext/node/crypto/dh.rs new file mode 100644 index 00000000000000..4da9a01bf8c2ad --- /dev/null +++ b/ext/node/crypto/dh.rs @@ -0,0 +1,288 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +use super::primes::Prime; +use num_bigint_dig::BigUint; +use num_bigint_dig::RandBigInt; +use num_traits::FromPrimitive; + +pub struct PublicKey(BigUint); + +impl PublicKey { + pub fn into_vec(self) -> Vec { + self.0.to_bytes_be() + } +} + +pub struct PrivateKey(BigUint); + +impl PrivateKey { + pub fn new(exponent_size: usize) -> Self { + let mut rng = rand::thread_rng(); + let exponent = rng.gen_biguint(exponent_size); + Self(exponent) + } + + /// Diffie-Hellman modular exponentiation. + /// s = g^x mod p + pub fn compute_public_key( + &self, + generator: &BigUint, + modulus: &BigUint, + ) -> PublicKey { + let public_key = generator.modpow(&self.0, modulus); + PublicKey(public_key) + } + + pub fn into_vec(self) -> Vec { + self.0.to_bytes_be() + } +} + +/// Classic DH +pub struct DiffieHellman { + pub private_key: PrivateKey, + pub public_key: PublicKey, +} + +impl DiffieHellman { + pub fn group() -> Self + where + G: DiffieHellmanGroup, + { + let private_key = PrivateKey::new(G::EXPONENT_SIZE / 8); + + let generator = BigUint::from_usize(G::GENERATOR).unwrap(); + let modulus = BigUint::from_slice(G::MODULUS); + + let public_key = private_key.compute_public_key(&generator, &modulus); + + Self { + private_key, + public_key, + } + } + + pub fn new(prime: Prime, generator: usize) -> Self { + let private_key = PrivateKey::new(32); + + let generator = BigUint::from_usize(generator).unwrap(); + let public_key = private_key.compute_public_key(&generator, &prime); + + Self { + private_key, + public_key, + } + } +} + +/// Well-known modp groups +// +/// More Modular Exponential (MODP) Diffie-Hellman groups for Internet Key Exchange (IKE) +/// https://www.rfc-editor.org/rfc/rfc3526 +/// +/// Insecure groups `modp1` and `modp2` from https://www.rfc-editor.org/rfc/rfc2409.txt +/// are deprecated in Node.js. We don't support them. +pub trait DiffieHellmanGroup { + const GENERATOR: usize; + const MODULUS: &'static [u32]; + /// Size of the exponent in bits + const EXPONENT_SIZE: usize; +} + +/// 1536-bit MODP Group +/// https://www.rfc-editor.org/rfc/rfc3526#section-2 +pub struct Modp1536; +impl DiffieHellmanGroup for Modp1536 { + const GENERATOR: usize = 2; + const EXPONENT_SIZE: usize = 192; + + const MODULUS: &'static [u32] = &[ + 0xFFFFFFFF, 0xFFFFFFFF, 0xC90FDAA2, 0x2168C234, 0xC4C6628B, 0x80DC1CD1, + 0x29024E08, 0x8A67CC74, 0x020BBEA6, 0x3B139B22, 0x514A0879, 0x8E3404DD, + 0xEF9519B3, 0xCD3A431B, 0x302B0A6D, 0xF25F1437, 0x4FE1356D, 0x6D51C245, + 0xE485B576, 0x625E7EC6, 0xF44C42E9, 0xA637ED6B, 0x0BFF5CB6, 0xF406B7ED, + 0xEE386BFB, 0x5A899FA5, 0xAE9F2411, 0x7C4B1FE6, 0x49286651, 0xECE45B3D, + 0xC2007CB8, 0xA163BF05, 0x98DA4836, 0x1C55D39A, 0x69163FA8, 0xFD24CF5F, + 0x83655D23, 0xDCA3AD96, 0x1C62F356, 0x208552BB, 0x9ED52907, 0x7096966D, + 0x670C354E, 0x4ABC9804, 0xF1746C08, 0xCA237327, 0xFFFFFFFF, 0xFFFFFFFF, + ]; +} + +/// 2048-bit MODP Group +/// https://www.rfc-editor.org/rfc/rfc3526#section-3 +pub struct Modp2048; +impl DiffieHellmanGroup for Modp2048 { + const GENERATOR: usize = 2; + const EXPONENT_SIZE: usize = 256; + + const MODULUS: &'static [u32] = &[ + 0xFFFFFFFF, 0xFFFFFFFF, 0xC90FDAA2, 0x2168C234, 0xC4C6628B, 0x80DC1CD1, + 0x29024E08, 0x8A67CC74, 0x020BBEA6, 0x3B139B22, 0x514A0879, 0x8E3404DD, + 0xEF9519B3, 0xCD3A431B, 0x302B0A6D, 0xF25F1437, 0x4FE1356D, 0x6D51C245, + 0xE485B576, 0x625E7EC6, 0xF44C42E9, 0xA637ED6B, 0x0BFF5CB6, 0xF406B7ED, + 0xEE386BFB, 0x5A899FA5, 0xAE9F2411, 0x7C4B1FE6, 0x49286651, 0xECE45B3D, + 0xC2007CB8, 0xA163BF05, 0x98DA4836, 0x1C55D39A, 0x69163FA8, 0xFD24CF5F, + 0x83655D23, 0xDCA3AD96, 0x1C62F356, 0x208552BB, 0x9ED52907, 0x7096966D, + 0x670C354E, 0x4ABC9804, 0xF1746C08, 0xCA18217C, 0x32905E46, 0x2E36CE3B, + 0xE39E772C, 0x180E8603, 0x9B2783A2, 0xEC07A28F, 0xB5C55DF0, 0x6F4C52C9, + 0xDE2BCBF6, 0x95581718, 0x3995497C, 0xEA956AE5, 0x15D22618, 0x98FA0510, + 0x15728E5A, 0x8AACAA68, 0xFFFFFFFF, 0xFFFFFFFF, + ]; +} + +/// 3072-bit MODP Group +/// https://www.rfc-editor.org/rfc/rfc3526#section-4 +pub struct Modp3072; +impl DiffieHellmanGroup for Modp3072 { + const GENERATOR: usize = 2; + const EXPONENT_SIZE: usize = 384; + + const MODULUS: &'static [u32] = &[ + 0xFFFFFFFF, 0xFFFFFFFF, 0xC90FDAA2, 0x2168C234, 0xC4C6628B, 0x80DC1CD1, + 0x29024E08, 0x8A67CC74, 0x020BBEA6, 0x3B139B22, 0x514A0879, 0x8E3404DD, + 0xEF9519B3, 0xCD3A431B, 0x302B0A6D, 0xF25F1437, 0x4FE1356D, 0x6D51C245, + 0xE485B576, 0x625E7EC6, 0xF44C42E9, 0xA637ED6B, 0x0BFF5CB6, 0xF406B7ED, + 0xEE386BFB, 0x5A899FA5, 0xAE9F2411, 0x7C4B1FE6, 0x49286651, 0xECE45B3D, + 0xC2007CB8, 0xA163BF05, 0x98DA4836, 0x1C55D39A, 0x69163FA8, 0xFD24CF5F, + 0x83655D23, 0xDCA3AD96, 0x1C62F356, 0x208552BB, 0x9ED52907, 0x7096966D, + 0x670C354E, 0x4ABC9804, 0xF1746C08, 0xCA18217C, 0x32905E46, 0x2E36CE3B, + 0xE39E772C, 0x180E8603, 0x9B2783A2, 0xEC07A28F, 0xB5C55DF0, 0x6F4C52C9, + 0xDE2BCBF6, 0x95581718, 0x3995497C, 0xEA956AE5, 0x15D22618, 0x98FA0510, + 0x15728E5A, 0x8AAAC42D, 0xAD33170D, 0x04507A33, 0xA85521AB, 0xDF1CBA64, + 0xECFB8504, 0x58DBEF0A, 0x8AEA7157, 0x5D060C7D, 0xB3970F85, 0xA6E1E4C7, + 0xABF5AE8C, 0xDB0933D7, 0x1E8C94E0, 0x4A25619D, 0xCEE3D226, 0x1AD2EE6B, + 0xF12FFA06, 0xD98A0864, 0xD8760273, 0x3EC86A64, 0x521F2B18, 0x177B200C, + 0xBBE11757, 0x7A615D6C, 0x770988C0, 0xBAD946E2, 0x08E24FA0, 0x74E5AB31, + 0x43DB5BFC, 0xE0FD108E, 0x4B82D120, 0xA93AD2CA, 0xFFFFFFFF, 0xFFFFFFFF, + ]; +} + +/// 4096-bit MODP Group +/// https://www.rfc-editor.org/rfc/rfc3526#section-5 +pub struct Modp4096; +impl DiffieHellmanGroup for Modp4096 { + const GENERATOR: usize = 2; + const EXPONENT_SIZE: usize = 512; + + const MODULUS: &'static [u32] = &[ + 0xFFFFFFFF, 0xFFFFFFFF, 0xC90FDAA2, 0x2168C234, 0xC4C6628B, 0x80DC1CD1, + 0x29024E08, 0x8A67CC74, 0x020BBEA6, 0x3B139B22, 0x514A0879, 0x8E3404DD, + 0xEF9519B3, 0xCD3A431B, 0x302B0A6D, 0xF25F1437, 0x4FE1356D, 0x6D51C245, + 0xE485B576, 0x625E7EC6, 0xF44C42E9, 0xA637ED6B, 0x0BFF5CB6, 0xF406B7ED, + 0xEE386BFB, 0x5A899FA5, 0xAE9F2411, 0x7C4B1FE6, 0x49286651, 0xECE45B3D, + 0xC2007CB8, 0xA163BF05, 0x98DA4836, 0x1C55D39A, 0x69163FA8, 0xFD24CF5F, + 0x83655D23, 0xDCA3AD96, 0x1C62F356, 0x208552BB, 0x9ED52907, 0x7096966D, + 0x670C354E, 0x4ABC9804, 0xF1746C08, 0xCA18217C, 0x32905E46, 0x2E36CE3B, + 0xE39E772C, 0x180E8603, 0x9B2783A2, 0xEC07A28F, 0xB5C55DF0, 0x6F4C52C9, + 0xDE2BCBF6, 0x95581718, 0x3995497C, 0xEA956AE5, 0x15D22618, 0x98FA0510, + 0x15728E5A, 0x8AAAC42D, 0xAD33170D, 0x04507A33, 0xA85521AB, 0xDF1CBA64, + 0xECFB8504, 0x58DBEF0A, 0x8AEA7157, 0x5D060C7D, 0xB3970F85, 0xA6E1E4C7, + 0xABF5AE8C, 0xDB0933D7, 0x1E8C94E0, 0x4A25619D, 0xCEE3D226, 0x1AD2EE6B, + 0xF12FFA06, 0xD98A0864, 0xD8760273, 0x3EC86A64, 0x521F2B18, 0x177B200C, + 0xBBE11757, 0x7A615D6C, 0x770988C0, 0xBAD946E2, 0x08E24FA0, 0x74E5AB31, + 0x43DB5BFC, 0xE0FD108E, 0x4B82D120, 0xA9210801, 0x1A723C12, 0xA787E6D7, + 0x88719A10, 0xBDBA5B26, 0x99C32718, 0x6AF4E23C, 0x1A946834, 0xB6150BDA, + 0x2583E9CA, 0x2AD44CE8, 0xDBBBC2DB, 0x04DE8EF9, 0x2E8EFC14, 0x1FBECAA6, + 0x287C5947, 0x4E6BC05D, 0x99B2964F, 0xA090C3A2, 0x233BA186, 0x515BE7ED, + 0x1F612970, 0xCEE2D7AF, 0xB81BDD76, 0x2170481C, 0xD0069127, 0xD5B05AA9, + 0x93B4EA98, 0x8D8FDDC1, 0x86FFB7DC, 0x90A6C08F, 0x4DF435C9, 0x34063199, + 0xFFFFFFFF, 0xFFFFFFFF, + ]; +} + +/// 6144-bit MODP Group +/// https://www.rfc-editor.org/rfc/rfc3526#section-6 +pub struct Modp6144; +impl DiffieHellmanGroup for Modp6144 { + const GENERATOR: usize = 2; + const EXPONENT_SIZE: usize = 768; + + const MODULUS: &'static [u32] = &[ + 0xFFFFFFFF, 0xFFFFFFFF, 0xC90FDAA2, 0x2168C234, 0xC4C6628B, 0x80DC1CD1, + 0x29024E08, 0x8A67CC74, 0x020BBEA6, 0x3B139B22, 0x514A0879, 0x8E3404DD, + 0xEF9519B3, 0xCD3A431B, 0x302B0A6D, 0xF25F1437, 0x4FE1356D, 0x6D51C245, + 0xE485B576, 0x625E7EC6, 0xF44C42E9, 0xA637ED6B, 0x0BFF5CB6, 0xF406B7ED, + 0xEE386BFB, 0x5A899FA5, 0xAE9F2411, 0x7C4B1FE6, 0x49286651, 0xECE45B3D, + 0xC2007CB8, 0xA163BF05, 0x98DA4836, 0x1C55D39A, 0x69163FA8, 0xFD24CF5F, + 0x83655D23, 0xDCA3AD96, 0x1C62F356, 0x208552BB, 0x9ED52907, 0x7096966D, + 0x670C354E, 0x4ABC9804, 0xF1746C08, 0xCA18217C, 0x32905E46, 0x2E36CE3B, + 0xE39E772C, 0x180E8603, 0x9B2783A2, 0xEC07A28F, 0xB5C55DF0, 0x6F4C52C9, + 0xDE2BCBF6, 0x95581718, 0x3995497C, 0xEA956AE5, 0x15D22618, 0x98FA0510, + 0x15728E5A, 0x8AAAC42D, 0xAD33170D, 0x04507A33, 0xA85521AB, 0xDF1CBA64, + 0xECFB8504, 0x58DBEF0A, 0x8AEA7157, 0x5D060C7D, 0xB3970F85, 0xA6E1E4C7, + 0xABF5AE8C, 0xDB0933D7, 0x1E8C94E0, 0x4A25619D, 0xCEE3D226, 0x1AD2EE6B, + 0xF12FFA06, 0xD98A0864, 0xD8760273, 0x3EC86A64, 0x521F2B18, 0x177B200C, + 0xBBE11757, 0x7A615D6C, 0x770988C0, 0xBAD946E2, 0x08E24FA0, 0x74E5AB31, + 0x43DB5BFC, 0xE0FD108E, 0x4B82D120, 0xA9210801, 0x1A723C12, 0xA787E6D7, + 0x88719A10, 0xBDBA5B26, 0x99C32718, 0x6AF4E23C, 0x1A946834, 0xB6150BDA, + 0x2583E9CA, 0x2AD44CE8, 0xDBBBC2DB, 0x04DE8EF9, 0x2E8EFC14, 0x1FBECAA6, + 0x287C5947, 0x4E6BC05D, 0x99B2964F, 0xA090C3A2, 0x233BA186, 0x515BE7ED, + 0x1F612970, 0xCEE2D7AF, 0xB81BDD76, 0x2170481C, 0xD0069127, 0xD5B05AA9, + 0x93B4EA98, 0x8D8FDDC1, 0x86FFB7DC, 0x90A6C08F, 0x4DF435C9, 0x34028492, + 0x36C3FAB4, 0xD27C7026, 0xC1D4DCB2, 0x602646DE, 0xC9751E76, 0x3DBA37BD, + 0xF8FF9406, 0xAD9E530E, 0xE5DB382F, 0x413001AE, 0xB06A53ED, 0x9027D831, + 0x179727B0, 0x865A8918, 0xDA3EDBEB, 0xCF9B14ED, 0x44CE6CBA, 0xCED4BB1B, + 0xDB7F1447, 0xE6CC254B, 0x33205151, 0x2BD7AF42, 0x6FB8F401, 0x378CD2BF, + 0x5983CA01, 0xC64B92EC, 0xF032EA15, 0xD1721D03, 0xF482D7CE, 0x6E74FEF6, + 0xD55E702F, 0x46980C82, 0xB5A84031, 0x900B1C9E, 0x59E7C97F, 0xBEC7E8F3, + 0x23A97A7E, 0x36CC88BE, 0x0F1D45B7, 0xFF585AC5, 0x4BD407B2, 0x2B4154AA, + 0xCC8F6D7E, 0xBF48E1D8, 0x14CC5ED2, 0x0F8037E0, 0xA79715EE, 0xF29BE328, + 0x06A1D58B, 0xB7C5DA76, 0xF550AA3D, 0x8A1FBFF0, 0xEB19CCB1, 0xA313D55C, + 0xDA56C9EC, 0x2EF29632, 0x387FE8D7, 0x6E3C0468, 0x043E8F66, 0x3F4860EE, + 0x12BF2D5B, 0x0B7474D6, 0xE694F91E, 0x6DCC4024, 0xFFFFFFFF, 0xFFFFFFFF, + ]; +} + +/// 8192-bit MODP Group +/// https://www.rfc-editor.org/rfc/rfc3526#section-7 +pub struct Modp8192; +impl DiffieHellmanGroup for Modp8192 { + const GENERATOR: usize = 2; + const EXPONENT_SIZE: usize = 1024; + + const MODULUS: &'static [u32] = &[ + 0xFFFFFFFF, 0xFFFFFFFF, 0xC90FDAA2, 0x2168C234, 0xC4C6628B, 0x80DC1CD1, + 0x29024E08, 0x8A67CC74, 0x020BBEA6, 0x3B139B22, 0x514A0879, 0x8E3404DD, + 0xEF9519B3, 0xCD3A431B, 0x302B0A6D, 0xF25F1437, 0x4FE1356D, 0x6D51C245, + 0xE485B576, 0x625E7EC6, 0xF44C42E9, 0xA637ED6B, 0x0BFF5CB6, 0xF406B7ED, + 0xEE386BFB, 0x5A899FA5, 0xAE9F2411, 0x7C4B1FE6, 0x49286651, 0xECE45B3D, + 0xC2007CB8, 0xA163BF05, 0x98DA4836, 0x1C55D39A, 0x69163FA8, 0xFD24CF5F, + 0x83655D23, 0xDCA3AD96, 0x1C62F356, 0x208552BB, 0x9ED52907, 0x7096966D, + 0x670C354E, 0x4ABC9804, 0xF1746C08, 0xCA18217C, 0x32905E46, 0x2E36CE3B, + 0xE39E772C, 0x180E8603, 0x9B2783A2, 0xEC07A28F, 0xB5C55DF0, 0x6F4C52C9, + 0xDE2BCBF6, 0x95581718, 0x3995497C, 0xEA956AE5, 0x15D22618, 0x98FA0510, + 0x15728E5A, 0x8AAAC42D, 0xAD33170D, 0x04507A33, 0xA85521AB, 0xDF1CBA64, + 0xECFB8504, 0x58DBEF0A, 0x8AEA7157, 0x5D060C7D, 0xB3970F85, 0xA6E1E4C7, + 0xABF5AE8C, 0xDB0933D7, 0x1E8C94E0, 0x4A25619D, 0xCEE3D226, 0x1AD2EE6B, + 0xF12FFA06, 0xD98A0864, 0xD8760273, 0x3EC86A64, 0x521F2B18, 0x177B200C, + 0xBBE11757, 0x7A615D6C, 0x770988C0, 0xBAD946E2, 0x08E24FA0, 0x74E5AB31, + 0x43DB5BFC, 0xE0FD108E, 0x4B82D120, 0xA9210801, 0x1A723C12, 0xA787E6D7, + 0x88719A10, 0xBDBA5B26, 0x99C32718, 0x6AF4E23C, 0x1A946834, 0xB6150BDA, + 0x2583E9CA, 0x2AD44CE8, 0xDBBBC2DB, 0x04DE8EF9, 0x2E8EFC14, 0x1FBECAA6, + 0x287C5947, 0x4E6BC05D, 0x99B2964F, 0xA090C3A2, 0x233BA186, 0x515BE7ED, + 0x1F612970, 0xCEE2D7AF, 0xB81BDD76, 0x2170481C, 0xD0069127, 0xD5B05AA9, + 0x93B4EA98, 0x8D8FDDC1, 0x86FFB7DC, 0x90A6C08F, 0x4DF435C9, 0x34028492, + 0x36C3FAB4, 0xD27C7026, 0xC1D4DCB2, 0x602646DE, 0xC9751E76, 0x3DBA37BD, + 0xF8FF9406, 0xAD9E530E, 0xE5DB382F, 0x413001AE, 0xB06A53ED, 0x9027D831, + 0x179727B0, 0x865A8918, 0xDA3EDBEB, 0xCF9B14ED, 0x44CE6CBA, 0xCED4BB1B, + 0xDB7F1447, 0xE6CC254B, 0x33205151, 0x2BD7AF42, 0x6FB8F401, 0x378CD2BF, + 0x5983CA01, 0xC64B92EC, 0xF032EA15, 0xD1721D03, 0xF482D7CE, 0x6E74FEF6, + 0xD55E702F, 0x46980C82, 0xB5A84031, 0x900B1C9E, 0x59E7C97F, 0xBEC7E8F3, + 0x23A97A7E, 0x36CC88BE, 0x0F1D45B7, 0xFF585AC5, 0x4BD407B2, 0x2B4154AA, + 0xCC8F6D7E, 0xBF48E1D8, 0x14CC5ED2, 0x0F8037E0, 0xA79715EE, 0xF29BE328, + 0x06A1D58B, 0xB7C5DA76, 0xF550AA3D, 0x8A1FBFF0, 0xEB19CCB1, 0xA313D55C, + 0xDA56C9EC, 0x2EF29632, 0x387FE8D7, 0x6E3C0468, 0x043E8F66, 0x3F4860EE, + 0x12BF2D5B, 0x0B7474D6, 0xE694F91E, 0x6DBE1159, 0x74A3926F, 0x12FEE5E4, + 0x38777CB6, 0xA932DF8C, 0xD8BEC4D0, 0x73B931BA, 0x3BC832B6, 0x8D9DD300, + 0x741FA7BF, 0x8AFC47ED, 0x2576F693, 0x6BA42466, 0x3AAB639C, 0x5AE4F568, + 0x3423B474, 0x2BF1C978, 0x238F16CB, 0xE39D652D, 0xE3FDB8BE, 0xFC848AD9, + 0x22222E04, 0xA4037C07, 0x13EB57A8, 0x1A23F0C7, 0x3473FC64, 0x6CEA306B, + 0x4BCBC886, 0x2F8385DD, 0xFA9D4B7F, 0xA2C087E8, 0x79683303, 0xED5BDD3A, + 0x062B3CF5, 0xB3A278A6, 0x6D2A13F8, 0x3F44F82D, 0xDF310EE0, 0x74AB6A36, + 0x4597E899, 0xA0255DC1, 0x64F31CC5, 0x0846851D, 0xF9AB4819, 0x5DED7EA1, + 0xB1D510BD, 0x7EE74D73, 0xFAF36BC3, 0x1ECFA268, 0x359046F4, 0xEB879F92, + 0x4009438B, 0x481C6CD7, 0x889A002E, 0xD5EE382B, 0xC9190DA6, 0xFC026E47, + 0x9558E447, 0x5677E9AA, 0x9E3050E2, 0x765694DF, 0xC81F56E8, 0x80B96E71, + 0x60C980DD, 0x98EDD3DF, 0xFFFFFFFF, 0xFFFFFFFF, + ]; +} diff --git a/ext/node/crypto/mod.rs b/ext/node/crypto/mod.rs index 26392da4c001e1..d224b40f7218bd 100644 --- a/ext/node/crypto/mod.rs +++ b/ext/node/crypto/mod.rs @@ -10,8 +10,10 @@ use deno_core::StringOrBuffer; use deno_core::ZeroCopyBuf; use hkdf::Hkdf; use num_bigint::BigInt; +use num_traits::FromPrimitive; use rand::distributions::Distribution; use rand::distributions::Uniform; +use rand::thread_rng; use rand::Rng; use std::future::Future; use std::rc::Rc; @@ -24,6 +26,7 @@ use rsa::RsaPrivateKey; use rsa::RsaPublicKey; mod cipher; +mod dh; mod digest; mod primes; pub mod x509; @@ -534,6 +537,275 @@ pub async fn op_node_hkdf_async( .await? } +use rsa::pkcs1::EncodeRsaPrivateKey; +use rsa::pkcs1::EncodeRsaPublicKey; + +use self::primes::Prime; + +fn generate_rsa( + modulus_length: usize, + public_exponent: usize, +) -> Result<(ZeroCopyBuf, ZeroCopyBuf), AnyError> { + let mut rng = rand::thread_rng(); + let private_key = RsaPrivateKey::new_with_exp( + &mut rng, + modulus_length, + &rsa::BigUint::from_usize(public_exponent).unwrap(), + )?; + let public_key = private_key.to_public_key(); + let private_key_der = private_key.to_pkcs1_der()?.as_bytes().to_vec(); + let public_key_der = public_key.to_pkcs1_der()?.to_vec(); + + Ok((private_key_der.into(), public_key_der.into())) +} + +#[op] +pub fn op_node_generate_rsa( + modulus_length: usize, + public_exponent: usize, +) -> Result<(ZeroCopyBuf, ZeroCopyBuf), AnyError> { + generate_rsa(modulus_length, public_exponent) +} + +#[op] +pub async fn op_node_generate_rsa_async( + modulus_length: usize, + public_exponent: usize, +) -> Result<(ZeroCopyBuf, ZeroCopyBuf), AnyError> { + tokio::task::spawn_blocking(move || { + generate_rsa(modulus_length, public_exponent) + }) + .await? +} + +fn dsa_generate( + modulus_length: usize, + divisor_length: usize, +) -> Result<(ZeroCopyBuf, ZeroCopyBuf), AnyError> { + let mut rng = rand::thread_rng(); + use dsa::pkcs8::EncodePrivateKey; + use dsa::pkcs8::EncodePublicKey; + use dsa::Components; + use dsa::KeySize; + use dsa::SigningKey; + + let key_size = match (modulus_length, divisor_length) { + #[allow(deprecated)] + (1024, 160) => KeySize::DSA_1024_160, + (2048, 224) => KeySize::DSA_2048_224, + (2048, 256) => KeySize::DSA_2048_256, + (3072, 256) => KeySize::DSA_3072_256, + _ => return Err(type_error("Invalid modulus_length or divisor_length")), + }; + let components = Components::generate(&mut rng, key_size); + let signing_key = SigningKey::generate(&mut rng, components); + let verifying_key = signing_key.verifying_key(); + + Ok(( + signing_key + .to_pkcs8_der() + .map_err(|_| type_error("Not valid pkcs8"))? + .as_bytes() + .to_vec() + .into(), + verifying_key + .to_public_key_der() + .map_err(|_| type_error("Not valid spki"))? + .to_vec() + .into(), + )) +} + +#[op] +pub fn op_node_dsa_generate( + modulus_length: usize, + divisor_length: usize, +) -> Result<(ZeroCopyBuf, ZeroCopyBuf), AnyError> { + dsa_generate(modulus_length, divisor_length) +} + +#[op] +pub async fn op_node_dsa_generate_async( + modulus_length: usize, + divisor_length: usize, +) -> Result<(ZeroCopyBuf, ZeroCopyBuf), AnyError> { + tokio::task::spawn_blocking(move || { + dsa_generate(modulus_length, divisor_length) + }) + .await? +} + +fn ec_generate( + named_curve: &str, +) -> Result<(ZeroCopyBuf, ZeroCopyBuf), AnyError> { + use ring::signature::EcdsaKeyPair; + use ring::signature::KeyPair; + + let curve = match named_curve { + "P-256" => &ring::signature::ECDSA_P256_SHA256_FIXED_SIGNING, + "P-384" => &ring::signature::ECDSA_P384_SHA384_FIXED_SIGNING, + _ => return Err(type_error("Unsupported named curve")), + }; + + let rng = ring::rand::SystemRandom::new(); + + let pkcs8 = EcdsaKeyPair::generate_pkcs8(curve, &rng) + .map_err(|_| type_error("Failed to generate EC key"))?; + + let public_key = EcdsaKeyPair::from_pkcs8(curve, pkcs8.as_ref()) + .map_err(|_| type_error("Failed to generate EC key"))? + .public_key() + .as_ref() + .to_vec(); + Ok((pkcs8.as_ref().to_vec().into(), public_key.into())) +} + +#[op] +pub fn op_node_ec_generate( + named_curve: &str, +) -> Result<(ZeroCopyBuf, ZeroCopyBuf), AnyError> { + ec_generate(named_curve) +} + +#[op] +pub async fn op_node_ec_generate_async( + named_curve: String, +) -> Result<(ZeroCopyBuf, ZeroCopyBuf), AnyError> { + tokio::task::spawn_blocking(move || ec_generate(&named_curve)).await? +} + +fn ed25519_generate() -> Result<(ZeroCopyBuf, ZeroCopyBuf), AnyError> { + use ring::signature::Ed25519KeyPair; + use ring::signature::KeyPair; + + let mut rng = thread_rng(); + let mut seed = vec![0u8; 32]; + rng.fill(seed.as_mut_slice()); + + let pair = Ed25519KeyPair::from_seed_unchecked(&seed) + .map_err(|_| type_error("Failed to generate Ed25519 key"))?; + + let public_key = pair.public_key().as_ref().to_vec(); + Ok((seed.into(), public_key.into())) +} + +#[op] +pub fn op_node_ed25519_generate() -> Result<(ZeroCopyBuf, ZeroCopyBuf), AnyError> +{ + ed25519_generate() +} + +#[op] +pub async fn op_node_ed25519_generate_async( +) -> Result<(ZeroCopyBuf, ZeroCopyBuf), AnyError> { + tokio::task::spawn_blocking(ed25519_generate).await? +} + +fn x25519_generate() -> Result<(ZeroCopyBuf, ZeroCopyBuf), AnyError> { + // u-coordinate of the base point. + const X25519_BASEPOINT_BYTES: [u8; 32] = [ + 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, + ]; + + let mut pkey = [0; 32]; + + let mut rng = thread_rng(); + rng.fill(pkey.as_mut_slice()); + + let pkey_copy = pkey.to_vec(); + // https://www.rfc-editor.org/rfc/rfc7748#section-6.1 + // pubkey = x25519(a, 9) which is constant-time Montgomery ladder. + // https://eprint.iacr.org/2014/140.pdf page 4 + // https://eprint.iacr.org/2017/212.pdf algorithm 8 + // pubkey is in LE order. + let pubkey = x25519_dalek::x25519(pkey, X25519_BASEPOINT_BYTES); + + Ok((pkey_copy.into(), pubkey.to_vec().into())) +} + +#[op] +pub fn op_node_x25519_generate() -> Result<(ZeroCopyBuf, ZeroCopyBuf), AnyError> +{ + x25519_generate() +} + +#[op] +pub async fn op_node_x25519_generate_async( +) -> Result<(ZeroCopyBuf, ZeroCopyBuf), AnyError> { + tokio::task::spawn_blocking(x25519_generate).await? +} + +fn dh_generate_group( + group_name: &str, +) -> Result<(ZeroCopyBuf, ZeroCopyBuf), AnyError> { + let dh = match group_name { + "modp5" => dh::DiffieHellman::group::(), + "modp14" => dh::DiffieHellman::group::(), + "modp15" => dh::DiffieHellman::group::(), + "modp16" => dh::DiffieHellman::group::(), + "modp17" => dh::DiffieHellman::group::(), + "modp18" => dh::DiffieHellman::group::(), + _ => return Err(type_error("Unsupported group name")), + }; + + Ok(( + dh.private_key.into_vec().into(), + dh.public_key.into_vec().into(), + )) +} + +#[op] +pub fn op_node_dh_generate_group( + group_name: &str, +) -> Result<(ZeroCopyBuf, ZeroCopyBuf), AnyError> { + dh_generate_group(group_name) +} + +#[op] +pub async fn op_node_dh_generate_group_async( + group_name: String, +) -> Result<(ZeroCopyBuf, ZeroCopyBuf), AnyError> { + tokio::task::spawn_blocking(move || dh_generate_group(&group_name)).await? +} + +fn dh_generate( + prime: Option<&[u8]>, + prime_len: usize, + generator: usize, +) -> Result<(ZeroCopyBuf, ZeroCopyBuf), AnyError> { + let prime = prime + .map(|p| p.into()) + .unwrap_or_else(|| Prime::generate(prime_len)); + let dh = dh::DiffieHellman::new(prime, generator); + + Ok(( + dh.private_key.into_vec().into(), + dh.public_key.into_vec().into(), + )) +} + +#[op] +pub fn op_node_dh_generate( + prime: Option<&[u8]>, + prime_len: usize, + generator: usize, +) -> Result<(ZeroCopyBuf, ZeroCopyBuf), AnyError> { + dh_generate(prime, prime_len, generator) +} + +#[op] +pub async fn op_node_dh_generate_async( + prime: Option, + prime_len: usize, + generator: usize, +) -> Result<(ZeroCopyBuf, ZeroCopyBuf), AnyError> { + tokio::task::spawn_blocking(move || { + dh_generate(prime.as_deref(), prime_len, generator) + }) + .await? +} + #[op] pub fn op_node_random_int(min: i32, max: i32) -> Result { let mut rng = rand::thread_rng(); diff --git a/ext/node/crypto/primes.rs b/ext/node/crypto/primes.rs index f438d87259410d..d03398f024b482 100644 --- a/ext/node/crypto/primes.rs +++ b/ext/node/crypto/primes.rs @@ -1,10 +1,35 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. use num_bigint::BigInt; +use num_bigint_dig::RandPrime; use num_integer::Integer; use num_traits::One; use num_traits::Zero; use rand::Rng; +use std::ops::Deref; + +pub struct Prime(num_bigint_dig::BigUint); + +impl Prime { + pub fn generate(n: usize) -> Self { + let mut rng = rand::thread_rng(); + Self(rng.gen_prime(n)) + } +} + +impl From<&[u8]> for Prime { + fn from(value: &[u8]) -> Self { + Self(num_bigint_dig::BigUint::from_bytes_be(value)) + } +} + +impl Deref for Prime { + type Target = num_bigint_dig::BigUint; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} struct Witness { pow: BigInt, diff --git a/ext/node/lib.rs b/ext/node/lib.rs index d363c444af4d20..64a2e083ab29b6 100644 --- a/ext/node/lib.rs +++ b/ext/node/lib.rs @@ -194,6 +194,20 @@ deno_core::extension!(deno_node, crypto::op_node_generate_secret, crypto::op_node_generate_secret_async, crypto::op_node_sign, + crypto::op_node_generate_rsa, + crypto::op_node_generate_rsa_async, + crypto::op_node_dsa_generate, + crypto::op_node_dsa_generate_async, + crypto::op_node_ec_generate, + crypto::op_node_ec_generate_async, + crypto::op_node_ed25519_generate, + crypto::op_node_ed25519_generate_async, + crypto::op_node_x25519_generate, + crypto::op_node_x25519_generate_async, + crypto::op_node_dh_generate_group, + crypto::op_node_dh_generate_group_async, + crypto::op_node_dh_generate, + crypto::op_node_dh_generate_async, crypto::op_node_verify, crypto::op_node_random_int, crypto::op_node_scrypt_sync, diff --git a/ext/node/polyfills/internal/crypto/keygen.ts b/ext/node/polyfills/internal/crypto/keygen.ts index b490cedd79f1fd..cdc616db6d796f 100644 --- a/ext/node/polyfills/internal/crypto/keygen.ts +++ b/ext/node/polyfills/internal/crypto/keygen.ts @@ -1,3 +1,5 @@ +// deno-lint-ignore-file no-explicit-any + // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. // Copyright Joyent, Inc. and Node.js contributors. All rights reserved. MIT license. @@ -8,13 +10,20 @@ import { 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 { + ERR_INCOMPATIBLE_OPTION_PAIR, + ERR_INVALID_ARG_VALUE, + ERR_MISSING_OPTION, +} from "ext:deno_node/internal/errors.ts"; +import { + validateBuffer, validateFunction, + validateInt32, validateInteger, validateObject, validateOneOf, validateString, + validateUint32, } 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"; @@ -529,17 +538,34 @@ export function generateKeyPair( ) => void, ): void; export function generateKeyPair( - _type: KeyType, - _options: unknown, - _callback: ( + type: KeyType, + options: unknown, + callback: ( err: Error | null, - // deno-lint-ignore no-explicit-any publicKey: any, - // deno-lint-ignore no-explicit-any privateKey: any, ) => void, ) { - notImplemented("crypto.generateKeyPair"); + createJob(kAsync, type, options).then(([privateKey, publicKey]) => { + privateKey = new KeyObject("private", setOwnedKey(privateKey)); + publicKey = new KeyObject("public", setOwnedKey(publicKey)); + + if (typeof options === "object" && options !== null) { + const { publicKeyEncoding, privateKeyEncoding } = options as any; + + if (publicKeyEncoding) { + publicKey = publicKey.export(publicKeyEncoding); + } + + if (privateKeyEncoding) { + privateKey = privateKey.export(privateKeyEncoding); + } + } + + callback(null, publicKey, privateKey); + }).catch((err) => { + callback(err, null, null); + }); } export interface KeyPairKeyObjectResult { @@ -716,12 +742,241 @@ export function generateKeyPairSync( options?: X448KeyPairKeyObjectOptions, ): KeyPairKeyObjectResult; export function generateKeyPairSync( - _type: KeyType, - _options: unknown, + type: KeyType, + options: unknown, ): | KeyPairKeyObjectResult | KeyPairSyncResult { - notImplemented("crypto.generateKeyPairSync"); + let [privateKey, publicKey] = createJob(kSync, type, options); + + privateKey = new KeyObject("private", setOwnedKey(privateKey)); + publicKey = new KeyObject("public", setOwnedKey(publicKey)); + + if (typeof options === "object" && options !== null) { + const { publicKeyEncoding, privateKeyEncoding } = options as any; + + if (publicKeyEncoding) { + publicKey = publicKey.export(publicKeyEncoding); + } + + if (privateKeyEncoding) { + privateKey = privateKey.export(privateKeyEncoding); + } + } + + return { publicKey, privateKey }; +} + +const kSync = 0; +const kAsync = 1; + +function createJob(mode, type, options) { + validateString(type, "type"); + + if (options !== undefined) { + validateObject(options, "options"); + } + + switch (type) { + case "rsa": + case "rsa-pss": { + validateObject(options, "options"); + const { modulusLength } = options; + validateUint32(modulusLength, "options.modulusLength"); + + let { publicExponent } = options; + if (publicExponent == null) { + publicExponent = 0x10001; + } else { + validateUint32(publicExponent, "options.publicExponent"); + } + + if (type === "rsa") { + if (mode === kSync) { + return ops.op_node_generate_rsa( + modulusLength, + publicExponent, + ); + } else { + return core.opAsync( + "op_node_generate_rsa_async", + modulusLength, + publicExponent, + ); + } + } + + const { + hash, + mgf1Hash, + hashAlgorithm, + mgf1HashAlgorithm, + saltLength, + } = options; + + if (saltLength !== undefined) { + validateInt32(saltLength, "options.saltLength", 0); + } + if (hashAlgorithm !== undefined) { + validateString(hashAlgorithm, "options.hashAlgorithm"); + } + if (mgf1HashAlgorithm !== undefined) { + validateString(mgf1HashAlgorithm, "options.mgf1HashAlgorithm"); + } + if (hash !== undefined) { + process.emitWarning( + '"options.hash" is deprecated, ' + + 'use "options.hashAlgorithm" instead.', + "DeprecationWarning", + "DEP0154", + ); + validateString(hash, "options.hash"); + if (hashAlgorithm && hash !== hashAlgorithm) { + throw new ERR_INVALID_ARG_VALUE("options.hash", hash); + } + } + if (mgf1Hash !== undefined) { + process.emitWarning( + '"options.mgf1Hash" is deprecated, ' + + 'use "options.mgf1HashAlgorithm" instead.', + "DeprecationWarning", + "DEP0154", + ); + validateString(mgf1Hash, "options.mgf1Hash"); + if (mgf1HashAlgorithm && mgf1Hash !== mgf1HashAlgorithm) { + throw new ERR_INVALID_ARG_VALUE("options.mgf1Hash", mgf1Hash); + } + } + + if (mode === kSync) { + return ops.op_node_generate_rsa( + modulusLength, + publicExponent, + ); + } else { + return core.opAsync( + "op_node_generate_rsa_async", + modulusLength, + publicExponent, + ); + } + } + case "dsa": { + validateObject(options, "options"); + const { modulusLength } = options; + validateUint32(modulusLength, "options.modulusLength"); + + let { divisorLength } = options; + if (divisorLength == null) { + divisorLength = 256; + } else { + validateInt32(divisorLength, "options.divisorLength", 0); + } + + if (mode === kSync) { + return ops.op_node_dsa_generate(modulusLength, divisorLength); + } + return core.opAsync( + "op_node_dsa_generate_async", + modulusLength, + divisorLength, + ); + } + case "ec": { + validateObject(options, "options"); + const { namedCurve } = options; + validateString(namedCurve, "options.namedCurve"); + const { paramEncoding } = options; + if (paramEncoding == null || paramEncoding === "named") { + // pass. + } else if (paramEncoding === "explicit") { + // TODO(@littledivy): Explicit param encoding is very rarely used, and not supported by the ring crate. + throw new TypeError("Explicit encoding is not supported"); + } else { + throw new ERR_INVALID_ARG_VALUE("options.paramEncoding", paramEncoding); + } + + if (mode === kSync) { + return ops.op_node_ec_generate(namedCurve); + } else { + return core.opAsync("op_node_ec_generate_async", namedCurve); + } + } + case "ed25519": { + if (mode === kSync) { + return ops.op_node_ed25519_generate(); + } + return core.opAsync("op_node_ed25519_generate_async"); + } + case "x25519": { + if (mode === kSync) { + return ops.op_node_x25519_generate(); + } + return core.opAsync("op_node_x25519_generate_async"); + } + case "ed448": + case "x448": { + notImplemented(type); + break; + } + case "dh": { + validateObject(options, "options"); + const { group, primeLength, prime, generator } = options; + if (group != null) { + if (prime != null) { + throw new ERR_INCOMPATIBLE_OPTION_PAIR("group", "prime"); + } + if (primeLength != null) { + throw new ERR_INCOMPATIBLE_OPTION_PAIR("group", "primeLength"); + } + if (generator != null) { + throw new ERR_INCOMPATIBLE_OPTION_PAIR("group", "generator"); + } + + validateString(group, "options.group"); + + if (mode === kSync) { + return ops.op_node_dh_generate_group(group); + } else { + return core.opAsync("op_node_dh_generate_group_async", group); + } + } + + if (prime != null) { + if (primeLength != null) { + throw new ERR_INCOMPATIBLE_OPTION_PAIR("prime", "primeLength"); + } + + validateBuffer(prime, "options.prime"); + } else if (primeLength != null) { + validateInt32(primeLength, "options.primeLength", 0); + } else { + throw new ERR_MISSING_OPTION( + "At least one of the group, prime, or primeLength options", + ); + } + + if (generator != null) { + validateInt32(generator, "options.generator", 0); + } + + const g = generator == null ? 2 : generator; + + if (mode === kSync) { + return ops.op_node_dh_generate(prime, primeLength ?? 0, g); + } else { + return core.opAsync( + "op_node_dh_generate_async", + prime, + primeLength ?? 0, + g, + ); + } + } + default: + // Fall through + } + throw new ERR_INVALID_ARG_VALUE("type", type, "must be a supported key type"); } export default {