1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-11-21 15:04:11 -05:00

fix(ext/node): add crypto.diffieHellman (#24938)

Co-authored-by: Divy Srivastava <dj.srivastava23@gmail.com>

Closes #21806
This commit is contained in:
Luca Casonato 2024-08-08 11:35:29 +02:00 committed by GitHub
parent 507e5b74ff
commit 93d479252b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 288 additions and 67 deletions

13
Cargo.lock generated
View file

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

View file

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

View file

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

View file

@ -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(&params.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(&params.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, &params).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, &params).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<KeyObjectHandlePair, AnyError> {
let dh = match group_name {
"modp5" => dh::DiffieHellman::group::<dh::Modp1536>(),
"modp14" => dh::DiffieHellman::group::<dh::Modp2048>(),
"modp15" => dh::DiffieHellman::group::<dh::Modp3072>(),
"modp16" => dh::DiffieHellman::group::<dh::Modp4096>(),
"modp17" => dh::DiffieHellman::group::<dh::Modp6144>(),
"modp18" => dh::DiffieHellman::group::<dh::Modp8192>(),
let (dh, prime, generator) = match group_name {
"modp5" => (
dh::DiffieHellman::group::<dh::Modp1536>(),
dh::Modp1536::MODULUS,
dh::Modp1536::GENERATOR,
),
"modp14" => (
dh::DiffieHellman::group::<dh::Modp2048>(),
dh::Modp2048::MODULUS,
dh::Modp2048::GENERATOR,
),
"modp15" => (
dh::DiffieHellman::group::<dh::Modp3072>(),
dh::Modp3072::MODULUS,
dh::Modp3072::GENERATOR,
),
"modp16" => (
dh::DiffieHellman::group::<dh::Modp4096>(),
dh::Modp4096::MODULUS,
dh::Modp4096::GENERATOR,
),
"modp17" => (
dh::DiffieHellman::group::<dh::Modp6144>(),
dh::Modp6144::MODULUS,
dh::Modp6144::GENERATOR,
),
"modp18" => (
dh::DiffieHellman::group::<dh::Modp8192>(),
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,
}),
))
}

View file

@ -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<ToJsBuffer, AnyError> {
Ok(spawn_blocking(move || gen_prime(size)).await?)
}
#[op2]
#[buffer]
pub fn op_node_diffie_hellman(
#[cppgc] private: &KeyObjectHandle,
#[cppgc] public: &KeyObjectHandle,
) -> Result<Box<[u8]>, 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)
}

View file

@ -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<asn1::Int>,
}

View file

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

View file

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

View file

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