From 93d479252b5a18e6e782c74b808240bd3ef036bd Mon Sep 17 00:00:00 2001 From: Luca Casonato Date: Thu, 8 Aug 2024 11:35:29 +0200 Subject: [PATCH] fix(ext/node): add crypto.diffieHellman (#24938) Co-authored-by: Divy Srivastava Closes #21806 --- Cargo.lock | 13 ++ ext/node/Cargo.toml | 1 + ext/node/lib.rs | 61 +++---- ext/node/ops/crypto/keys.rs | 156 ++++++++++++++---- ext/node/ops/crypto/mod.rs | 86 ++++++++++ ext/node/ops/crypto/pkcs3.rs | 20 +++ ext/node/ops/crypto/primes.rs | 1 + .../internal/crypto/diffiehellman.ts | 15 +- ext/node/polyfills/internal/crypto/keys.ts | 2 +- 9 files changed, 288 insertions(+), 67 deletions(-) create mode 100644 ext/node/ops/crypto/pkcs3.rs diff --git a/Cargo.lock b/Cargo.lock index 761ce1df4e..386329a194 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1776,6 +1776,7 @@ dependencies = [ "deno_package_json", "deno_permissions", "deno_whoami", + "der", "digest", "dsa", "ecb", @@ -2204,6 +2205,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" dependencies = [ "const-oid", + "der_derive", "pem-rfc7468", "zeroize", ] @@ -2222,6 +2224,17 @@ dependencies = [ "rusticata-macros", ] +[[package]] +name = "der_derive" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8034092389675178f570469e6c3b0465d3d30b4505c294a6550db47f3c17ad18" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.72", +] + [[package]] name = "deranged" version = "0.3.11" diff --git a/ext/node/Cargo.toml b/ext/node/Cargo.toml index 70d8f70cf7..a0191b0255 100644 --- a/ext/node/Cargo.toml +++ b/ext/node/Cargo.toml @@ -36,6 +36,7 @@ deno_net.workspace = true deno_package_json.workspace = true deno_permissions.workspace = true deno_whoami = "0.1.0" +der = { version = "0.7.9", features = ["derive"] } digest = { version = "0.10.5", features = ["core-api", "std"] } dsa = "0.6.1" ecb.workspace = true diff --git a/ext/node/lib.rs b/ext/node/lib.rs index 3de6ddce6c..51b22cefb2 100644 --- a/ext/node/lib.rs +++ b/ext/node/lib.rs @@ -219,46 +219,47 @@ deno_core::extension!(deno_node, ops::buffer::op_is_ascii, ops::buffer::op_is_utf8, - ops::crypto::op_node_create_decipheriv, + ops::crypto::op_node_check_prime_async, + ops::crypto::op_node_check_prime_bytes_async, + ops::crypto::op_node_check_prime_bytes, + ops::crypto::op_node_check_prime, ops::crypto::op_node_cipheriv_encrypt, ops::crypto::op_node_cipheriv_final, ops::crypto::op_node_cipheriv_set_aad, - ops::crypto::op_node_decipheriv_set_aad, ops::crypto::op_node_create_cipheriv, + ops::crypto::op_node_create_decipheriv, ops::crypto::op_node_create_hash, - ops::crypto::op_node_get_hashes, ops::crypto::op_node_decipheriv_decrypt, ops::crypto::op_node_decipheriv_final, - ops::crypto::op_node_hash_update, - ops::crypto::op_node_hash_update_str, - ops::crypto::op_node_hash_digest, - ops::crypto::op_node_hash_digest_hex, - ops::crypto::op_node_hash_clone, - ops::crypto::op_node_private_encrypt, - ops::crypto::op_node_private_decrypt, - ops::crypto::op_node_public_encrypt, - ops::crypto::op_node_check_prime, - ops::crypto::op_node_check_prime_async, - ops::crypto::op_node_check_prime_bytes, - ops::crypto::op_node_check_prime_bytes_async, - ops::crypto::op_node_gen_prime, - ops::crypto::op_node_gen_prime_async, - ops::crypto::op_node_pbkdf2, - ops::crypto::op_node_pbkdf2_async, - ops::crypto::op_node_hkdf, - ops::crypto::op_node_hkdf_async, - ops::crypto::op_node_fill_random, - ops::crypto::op_node_fill_random_async, - ops::crypto::op_node_sign, + ops::crypto::op_node_decipheriv_set_aad, ops::crypto::op_node_dh_compute_secret, - ops::crypto::op_node_verify, - ops::crypto::op_node_random_int, - ops::crypto::op_node_scrypt_sync, - ops::crypto::op_node_scrypt_async, - ops::crypto::op_node_ecdh_generate_keys, - ops::crypto::op_node_ecdh_compute_secret, + ops::crypto::op_node_diffie_hellman, ops::crypto::op_node_ecdh_compute_public_key, + ops::crypto::op_node_ecdh_compute_secret, ops::crypto::op_node_ecdh_encode_pubkey, + ops::crypto::op_node_ecdh_generate_keys, + ops::crypto::op_node_fill_random_async, + ops::crypto::op_node_fill_random, + ops::crypto::op_node_gen_prime_async, + ops::crypto::op_node_gen_prime, + ops::crypto::op_node_get_hashes, + ops::crypto::op_node_hash_clone, + ops::crypto::op_node_hash_digest_hex, + ops::crypto::op_node_hash_digest, + ops::crypto::op_node_hash_update_str, + ops::crypto::op_node_hash_update, + ops::crypto::op_node_hkdf_async, + ops::crypto::op_node_hkdf, + ops::crypto::op_node_pbkdf2_async, + ops::crypto::op_node_pbkdf2, + ops::crypto::op_node_private_decrypt, + ops::crypto::op_node_private_encrypt, + ops::crypto::op_node_public_encrypt, + ops::crypto::op_node_random_int, + ops::crypto::op_node_scrypt_async, + ops::crypto::op_node_scrypt_sync, + ops::crypto::op_node_sign, + ops::crypto::op_node_verify, ops::crypto::keys::op_node_create_private_key, ops::crypto::keys::op_node_create_public_key, ops::crypto::keys::op_node_create_secret_key, diff --git a/ext/node/ops/crypto/keys.rs b/ext/node/ops/crypto/keys.rs index 86280c11ae..5f634b35fd 100644 --- a/ext/node/ops/crypto/keys.rs +++ b/ext/node/ops/crypto/keys.rs @@ -30,6 +30,7 @@ use rsa::pkcs1::EncodeRsaPublicKey; use rsa::traits::PublicKeyParts; use rsa::RsaPrivateKey; use rsa::RsaPublicKey; +use sec1::der::Tag; use sec1::der::Writer as _; use sec1::pem::PemLabel as _; use sec1::DecodeEcPrivateKey as _; @@ -46,7 +47,10 @@ use spki::EncodePublicKey as _; use spki::SubjectPublicKeyInfoRef; use super::dh; +use super::dh::DiffieHellmanGroup; use super::digest::match_fixed_digest_with_oid; +use super::pkcs3; +use super::pkcs3::DhParameter; use super::primes::Prime; #[derive(Clone)] @@ -66,8 +70,7 @@ pub enum AsymmetricPrivateKey { Ec(EcPrivateKey), X25519(x25519_dalek::StaticSecret), Ed25519(ed25519_dalek::SigningKey), - #[allow(unused)] - Dh(dh::PrivateKey), + Dh(DhPrivateKey), } #[derive(Clone)] @@ -125,17 +128,21 @@ pub enum EcPrivateKey { P384(p384::SecretKey), } +#[derive(Clone)] +pub struct DhPrivateKey { + pub key: dh::PrivateKey, + pub params: DhParameter, +} + #[derive(Clone)] pub enum AsymmetricPublicKey { Rsa(rsa::RsaPublicKey), RsaPss(RsaPssPublicKey), Dsa(dsa::VerifyingKey), Ec(EcPublicKey), - #[allow(unused)] X25519(x25519_dalek::PublicKey), Ed25519(ed25519_dalek::VerifyingKey), - #[allow(unused)] - Dh(dh::PublicKey), + Dh(DhPublicKey), } #[derive(Clone)] @@ -151,6 +158,12 @@ pub enum EcPublicKey { P384(p384::PublicKey), } +#[derive(Clone)] +pub struct DhPublicKey { + pub key: dh::PublicKey, + pub params: DhParameter, +} + impl KeyObjectHandle { /// Returns the private key if the handle is an asymmetric private key. pub fn as_private_key(&self) -> Option<&AsymmetricPrivateKey> { @@ -492,9 +505,18 @@ impl KeyObjectHandle { bytes.copy_from_slice(string_ref.as_bytes()); AsymmetricPrivateKey::Ed25519(ed25519_dalek::SigningKey::from(bytes)) } - DH_KEY_AGREEMENT_OID => AsymmetricPrivateKey::Dh( - dh::PrivateKey::from_bytes(pk_info.private_key), - ), + DH_KEY_AGREEMENT_OID => { + let params = pk_info + .algorithm + .parameters + .ok_or_else(|| type_error("missing dh parameters"))?; + let params = pkcs3::DhParameter::from_der(¶ms.to_der().unwrap()) + .map_err(|_| type_error("malformed dh parameters"))?; + AsymmetricPrivateKey::Dh(DhPrivateKey { + key: dh::PrivateKey::from_bytes(pk_info.private_key), + params, + }) + } _ => return Err(type_error("unsupported private key oid")), }; @@ -634,11 +656,20 @@ impl KeyObjectHandle { AsymmetricPublicKey::Ed25519(verifying_key) } DH_KEY_AGREEMENT_OID => { + let params = spki + .algorithm + .parameters + .ok_or_else(|| type_error("missing dh parameters"))?; + let params = pkcs3::DhParameter::from_der(¶ms.to_der().unwrap()) + .map_err(|_| type_error("malformed dh parameters"))?; let Some(subject_public_key) = spki.subject_public_key.as_bytes() else { return Err(type_error("malformed or missing public key in dh spki")); }; - AsymmetricPublicKey::Dh(dh::PublicKey::from_bytes(subject_public_key)) + AsymmetricPublicKey::Dh(DhPublicKey { + key: dh::PublicKey::from_bytes(subject_public_key), + params, + }) } _ => return Err(type_error("unsupported public key oid")), }; @@ -788,16 +819,18 @@ impl AsymmetricPublicKey { .into_boxed_slice() } AsymmetricPublicKey::Dh(key) => { - let public_key_bytes = key.clone().into_vec(); - let spki = - SubjectPublicKeyInfoRef { - algorithm: rsa::pkcs8::AlgorithmIdentifierRef { - oid: DH_KEY_AGREEMENT_OID, - parameters: None, - }, - subject_public_key: BitStringRef::from_bytes(&public_key_bytes) - .map_err(|_| type_error("invalid DH public key"))?, - }; + let public_key_bytes = key.key.clone().into_vec(); + let params = key.params.to_der().unwrap(); + let spki = SubjectPublicKeyInfoRef { + algorithm: rsa::pkcs8::AlgorithmIdentifierRef { + oid: DH_KEY_AGREEMENT_OID, + parameters: Some(AnyRef::new(Tag::Sequence, ¶ms).unwrap()), + }, + subject_public_key: BitStringRef::from_bytes(&public_key_bytes) + .map_err(|_| { + type_error("invalid DH public key") + })?, + }; spki .to_der() .map_err(|_| type_error("invalid DH public key"))? @@ -917,11 +950,12 @@ impl AsymmetricPrivateKey { .into_boxed_slice() } AsymmetricPrivateKey::Dh(key) => { - let private_key = key.clone().into_vec(); + let private_key = key.key.clone().into_vec(); + let params = key.params.to_der().unwrap(); let private_key = PrivateKeyInfo { algorithm: rsa::pkcs8::AlgorithmIdentifierRef { oid: DH_KEY_AGREEMENT_OID, - parameters: None, + parameters: Some(AnyRef::new(Tag::Sequence, ¶ms).unwrap()), }, private_key: &private_key, public_key: None, @@ -1514,21 +1548,66 @@ pub async fn op_node_generate_ed25519_key_async() -> KeyObjectHandlePair { spawn_blocking(ed25519_generate).await.unwrap() } +fn u32_slice_to_u8_slice(slice: &[u32]) -> &[u8] { + // SAFETY: just reinterpreting the slice as u8 + unsafe { + std::slice::from_raw_parts( + slice.as_ptr() as *const u8, + std::mem::size_of_val(slice), + ) + } +} + fn dh_group_generate( group_name: &str, ) -> Result { - 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::(), + let (dh, prime, generator) = match group_name { + "modp5" => ( + dh::DiffieHellman::group::(), + dh::Modp1536::MODULUS, + dh::Modp1536::GENERATOR, + ), + "modp14" => ( + dh::DiffieHellman::group::(), + dh::Modp2048::MODULUS, + dh::Modp2048::GENERATOR, + ), + "modp15" => ( + dh::DiffieHellman::group::(), + dh::Modp3072::MODULUS, + dh::Modp3072::GENERATOR, + ), + "modp16" => ( + dh::DiffieHellman::group::(), + dh::Modp4096::MODULUS, + dh::Modp4096::GENERATOR, + ), + "modp17" => ( + dh::DiffieHellman::group::(), + dh::Modp6144::MODULUS, + dh::Modp6144::GENERATOR, + ), + "modp18" => ( + dh::DiffieHellman::group::(), + dh::Modp8192::MODULUS, + dh::Modp8192::GENERATOR, + ), _ => return Err(type_error("Unsupported group name")), }; + let params = DhParameter { + prime: asn1::Int::new(u32_slice_to_u8_slice(prime)).unwrap(), + base: asn1::Int::new(generator.to_be_bytes().as_slice()).unwrap(), + private_value_length: None, + }; Ok(KeyObjectHandlePair::new( - AsymmetricPrivateKey::Dh(dh.private_key), - AsymmetricPublicKey::Dh(dh.public_key), + AsymmetricPrivateKey::Dh(DhPrivateKey { + key: dh.private_key, + params: params.clone(), + }), + AsymmetricPublicKey::Dh(DhPublicKey { + key: dh.public_key, + params, + }), )) } @@ -1558,10 +1637,21 @@ fn dh_generate( let prime = prime .map(|p| p.into()) .unwrap_or_else(|| Prime::generate(prime_len)); - let dh = dh::DiffieHellman::new(prime, generator); + let dh = dh::DiffieHellman::new(prime.clone(), generator); + let params = DhParameter { + prime: asn1::Int::new(&prime.0.to_bytes_be()).unwrap(), + base: asn1::Int::new(generator.to_be_bytes().as_slice()).unwrap(), + private_value_length: None, + }; Ok(KeyObjectHandlePair::new( - AsymmetricPrivateKey::Dh(dh.private_key), - AsymmetricPublicKey::Dh(dh.public_key), + AsymmetricPrivateKey::Dh(DhPrivateKey { + key: dh.private_key, + params: params.clone(), + }), + AsymmetricPublicKey::Dh(DhPublicKey { + key: dh.public_key, + params, + }), )) } diff --git a/ext/node/ops/crypto/mod.rs b/ext/node/ops/crypto/mod.rs index 07d901cbca..567affd528 100644 --- a/ext/node/ops/crypto/mod.rs +++ b/ext/node/ops/crypto/mod.rs @@ -10,6 +10,10 @@ use deno_core::StringOrBuffer; use deno_core::ToJsBuffer; use elliptic_curve::sec1::ToEncodedPoint; use hkdf::Hkdf; +use keys::AsymmetricPrivateKey; +use keys::AsymmetricPublicKey; +use keys::EcPrivateKey; +use keys::EcPublicKey; use keys::KeyObjectHandle; use num_bigint::BigInt; use num_bigint_dig::BigUint; @@ -34,6 +38,7 @@ mod dh; mod digest; pub mod keys; mod md5_sha1; +mod pkcs3; mod primes; mod sign; pub mod x509; @@ -839,3 +844,84 @@ pub async fn op_node_gen_prime_async( ) -> Result { Ok(spawn_blocking(move || gen_prime(size)).await?) } + +#[op2] +#[buffer] +pub fn op_node_diffie_hellman( + #[cppgc] private: &KeyObjectHandle, + #[cppgc] public: &KeyObjectHandle, +) -> Result, AnyError> { + let private = private + .as_private_key() + .ok_or_else(|| type_error("Expected private key"))?; + let public = public + .as_public_key() + .ok_or_else(|| type_error("Expected public key"))?; + + let res = match (private, &*public) { + ( + AsymmetricPrivateKey::Ec(EcPrivateKey::P224(private)), + AsymmetricPublicKey::Ec(EcPublicKey::P224(public)), + ) => p224::ecdh::diffie_hellman( + private.to_nonzero_scalar(), + public.as_affine(), + ) + .raw_secret_bytes() + .to_vec() + .into_boxed_slice(), + ( + AsymmetricPrivateKey::Ec(EcPrivateKey::P256(private)), + AsymmetricPublicKey::Ec(EcPublicKey::P256(public)), + ) => p256::ecdh::diffie_hellman( + private.to_nonzero_scalar(), + public.as_affine(), + ) + .raw_secret_bytes() + .to_vec() + .into_boxed_slice(), + ( + AsymmetricPrivateKey::Ec(EcPrivateKey::P384(private)), + AsymmetricPublicKey::Ec(EcPublicKey::P384(public)), + ) => p384::ecdh::diffie_hellman( + private.to_nonzero_scalar(), + public.as_affine(), + ) + .raw_secret_bytes() + .to_vec() + .into_boxed_slice(), + ( + AsymmetricPrivateKey::X25519(private), + AsymmetricPublicKey::X25519(public), + ) => private + .diffie_hellman(public) + .to_bytes() + .into_iter() + .collect(), + (AsymmetricPrivateKey::Dh(private), AsymmetricPublicKey::Dh(public)) => { + if private.params.prime != public.params.prime + || private.params.base != public.params.base + { + return Err(type_error("DH parameters mismatch")); + } + + // OSIP - Octet-String-to-Integer primitive + let public_key = public.key.clone().into_vec(); + let pubkey = BigUint::from_bytes_be(&public_key); + + // Exponentiation (z = y^x mod p) + let prime = BigUint::from_bytes_be(private.params.prime.as_bytes()); + let private_key = private.key.clone().into_vec(); + let private_key = BigUint::from_bytes_be(&private_key); + let shared_secret = pubkey.modpow(&private_key, &prime); + + shared_secret.to_bytes_be().into() + } + _ => { + return Err(type_error( + "Unsupported key type for diffie hellman, or key type mismatch", + )) + } + }; + + Ok(res) +} diff --git a/ext/node/ops/crypto/pkcs3.rs b/ext/node/ops/crypto/pkcs3.rs new file mode 100644 index 0000000000..5772514608 --- /dev/null +++ b/ext/node/ops/crypto/pkcs3.rs @@ -0,0 +1,20 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +// +// PKCS #3: Diffie-Hellman Key Agreement Standard + +use der::Sequence; +use spki::der; +use spki::der::asn1; + +// The parameters fields associated with OID dhKeyAgreement +// +// DHParameter ::= SEQUENCE { +// prime INTEGER, -- p +// base INTEGER, -- g +// privateValueLength INTEGER OPTIONAL } +#[derive(Clone, Sequence)] +pub struct DhParameter { + pub prime: asn1::Int, + pub base: asn1::Int, + pub private_value_length: Option, +} diff --git a/ext/node/ops/crypto/primes.rs b/ext/node/ops/crypto/primes.rs index 5c9f4c7687..2e5d1ff63c 100644 --- a/ext/node/ops/crypto/primes.rs +++ b/ext/node/ops/crypto/primes.rs @@ -8,6 +8,7 @@ use num_traits::Zero; use rand::Rng; use std::ops::Deref; +#[derive(Clone)] pub struct Prime(pub num_bigint_dig::BigUint); impl Prime { diff --git a/ext/node/polyfills/internal/crypto/diffiehellman.ts b/ext/node/polyfills/internal/crypto/diffiehellman.ts index 16a1f2498e..a439306a97 100644 --- a/ext/node/polyfills/internal/crypto/diffiehellman.ts +++ b/ext/node/polyfills/internal/crypto/diffiehellman.ts @@ -7,6 +7,7 @@ import { op_node_dh_compute_secret, op_node_dh_keys_generate_and_export, + op_node_diffie_hellman, op_node_ecdh_compute_public_key, op_node_ecdh_compute_secret, op_node_ecdh_encode_pubkey, @@ -40,7 +41,12 @@ import type { BinaryToTextEncoding, ECDHKeyFormat, } from "ext:deno_node/internal/crypto/types.ts"; -import { KeyObject } from "ext:deno_node/internal/crypto/keys.ts"; +import { + getKeyObjectHandle, + kConsumePrivate, + kConsumePublic, + KeyObject, +} from "ext:deno_node/internal/crypto/keys.ts"; import type { BufferEncoding } from "ext:deno_node/_global.d.ts"; const DH_GENERATOR = 2; @@ -1305,11 +1311,14 @@ export class ECDH { } } -export function diffieHellman(_options: { +export function diffieHellman(options: { privateKey: KeyObject; publicKey: KeyObject; }): Buffer { - notImplemented("crypto.diffieHellman"); + const privateKey = getKeyObjectHandle(options.privateKey, kConsumePrivate); + const publicKey = getKeyObjectHandle(options.publicKey, kConsumePublic); + const bytes = op_node_diffie_hellman(privateKey, publicKey); + return Buffer.from(bytes); } export default { diff --git a/ext/node/polyfills/internal/crypto/keys.ts b/ext/node/polyfills/internal/crypto/keys.ts index 31d674e67b..62cec47d67 100644 --- a/ext/node/polyfills/internal/crypto/keys.ts +++ b/ext/node/polyfills/internal/crypto/keys.ts @@ -232,7 +232,7 @@ export interface JsonWebKeyInput { format: "jwk"; } -function getKeyObjectHandle(key: KeyObject, ctx: KeyHandleContext) { +export function getKeyObjectHandle(key: KeyObject, ctx: KeyHandleContext) { if (ctx === kCreatePrivate) { throw new ERR_INVALID_ARG_TYPE( "key",