/
githubmirror
/
deno
Обзор
Документация
Войти
/
githubmirror
/
deno
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
ext/node_crypto/lib.rs
2 025 строк
58 KB
denobot
2.9.0 (#35502)
25 июн 2026, 14:49
Не верифицирован
25 июн 2026, 14:49
94d375d
Код
Авторство
О чём код?
// Copyright 2018-2026 the Deno authors. MIT license. #![deny(clippy::print_stderr)] #![deny(clippy::print_stdout)] use std::future::Future; use std::rc::Rc; use aws_lc_rs::signature::Ed25519KeyPair; use deno_core::JsBuffer; use deno_core::OpState; use deno_core::StringOrBuffer; use deno_core::convert::Uint8Array; use deno_core::op2; use deno_core::unsync::spawn_blocking; use deno_error::JsErrorBox; 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; use p224::NistP224; use p256::NistP256; use p384::NistP384; use p521::NistP521; use rand::Rng; use rand::distributions::Distribution; use rand::distributions::Uniform; use rsa::Oaep; use rsa::Pkcs1v15Encrypt; use rsa::RsaPrivateKey; use rsa::RsaPublicKey; use rsa::hazmat::rsa_decrypt_and_check; use rsa::hazmat::rsa_encrypt; use rsa::pkcs1::DecodeRsaPrivateKey; use rsa::pkcs1::DecodeRsaPublicKey; use rsa::pkcs8::DecodePrivateKey; use rsa::pkcs8::DecodePublicKey; use rsa::traits::PublicKeyParts; pub mod cipher; pub(crate) mod dh; pub mod digest; pub mod keys; pub(crate) mod md5_sha1; pub(crate) mod pkcs3; pub(crate) mod primes; pub mod sign; pub mod x509; use self::digest::match_fixed_digest_with_eager_block_buffer; deno_core::extension!( deno_node_crypto, ops = [ op_node_check_prime_async, op_node_check_prime_bytes_async, op_node_check_prime_bytes, op_node_check_prime, op_node_aes_wrap_key, op_node_aes_unwrap_key, op_node_cipheriv_encrypt, op_node_cipheriv_final, op_node_cipheriv_set_aad, op_node_cipheriv_take, op_node_create_cipheriv, op_node_create_decipheriv, op_node_create_hash, op_node_decipheriv_decrypt, op_node_decipheriv_final, op_node_decipheriv_set_aad, op_node_decipheriv_auth_tag, op_node_dh_check, op_node_dh_compute_secret, op_node_diffie_hellman, op_node_ecdh_compute_public_key, op_node_ecdh_compute_secret, op_node_ecdh_encode_pubkey, op_node_ecdh_generate_keys, op_node_ecdh_validate_private_key, op_node_ecdh_validate_public_key, op_node_fill_random_async, op_node_fill_random, op_node_gen_prime_async, op_node_gen_prime, op_node_get_hash_size, op_node_get_hashes, op_node_hash_clone, op_node_hash_digest_hex, op_node_hash_digest, op_node_hash_update_str, op_node_hash_update, op_node_hkdf_async, op_node_hkdf, op_node_pbkdf2_async, op_node_pbkdf2, op_node_pbkdf2_validate, op_node_private_decrypt, op_node_private_encrypt, op_node_public_decrypt, op_node_public_encrypt, op_node_validate_oaep_hash, op_node_random_int, op_node_scrypt_async, op_node_scrypt_sync, op_node_sign, op_node_sign_ed25519, op_node_sign_ed448, op_node_verify, op_node_verify_ed25519, op_node_verify_ed448, op_node_verify_spkac, op_node_cert_export_public_key, op_node_cert_export_challenge, keys::op_node_create_private_key, keys::op_node_create_ed_raw, keys::op_node_create_rsa_jwk, keys::op_node_create_ec_jwk, keys::op_node_create_public_key, keys::op_node_create_secret_key, keys::op_node_derive_public_key_from_private_key, keys::op_node_dh_keys_generate_and_export, keys::op_node_export_private_key_der, keys::op_node_export_private_key_jwk, keys::op_node_export_private_key_pem, keys::op_node_export_public_key_der, keys::op_node_export_public_key_pem, keys::op_node_export_public_key_jwk, keys::op_node_export_secret_key_b64url, keys::op_node_export_secret_key, keys::op_node_generate_dh_group_key_async, keys::op_node_generate_dh_group_key, keys::op_node_generate_dh_key_async, keys::op_node_generate_dh_key, keys::op_node_generate_dsa_key_async, keys::op_node_generate_dsa_key, keys::op_node_generate_ec_key_async, keys::op_node_generate_ec_key, keys::op_node_generate_ed25519_key_async, keys::op_node_generate_ed25519_key, keys::op_node_generate_x448_key_async, keys::op_node_generate_x448_key, keys::op_node_generate_ed448_key_async, keys::op_node_generate_ed448_key, keys::op_node_generate_rsa_key_async, keys::op_node_generate_rsa_key, keys::op_node_generate_rsa_pss_key, keys::op_node_generate_rsa_pss_key_async, keys::op_node_generate_secret_key_async, keys::op_node_generate_secret_key, keys::op_node_generate_x25519_key_async, keys::op_node_generate_x25519_key, keys::op_node_get_asymmetric_key_details, keys::op_node_get_asymmetric_key_type, keys::op_node_get_private_key_from_pair, keys::op_node_get_public_key_from_pair, keys::op_node_get_symmetric_key_size, keys::op_node_key_equals, keys::op_node_key_type, keys::op_node_load_pfx, keys::op_node_validate_crl, x509::op_node_x509_parse, x509::op_node_x509_ca, x509::op_node_x509_check_email, x509::op_node_x509_check_host, x509::op_node_x509_fingerprint, x509::op_node_x509_fingerprint256, x509::op_node_x509_fingerprint512, x509::op_node_x509_get_issuer, x509::op_node_x509_get_subject, x509::op_node_x509_get_valid_from, x509::op_node_x509_get_valid_to, x509::op_node_x509_get_serial_number, x509::op_node_x509_key_usage, x509::op_node_x509_public_key, x509::op_node_x509_to_string, x509::op_node_x509_get_raw, x509::op_node_x509_get_subject_alt_name, x509::op_node_x509_check_ip, x509::op_node_x509_check_issued, x509::op_node_x509_check_private_key, x509::op_node_x509_verify, x509::op_node_x509_get_info_access, x509::op_node_x509_get_signature_algorithm_name, x509::op_node_x509_get_signature_algorithm_oid, x509::op_node_x509_to_legacy_object, ], objects = [digest::Hasher,], ); #[op2(fast)] pub fn op_node_check_prime( #[bigint] num: i64, #[number] checks: usize, ) -> bool { primes::is_probably_prime(&BigInt::from(num), checks) } #[op2] pub fn op_node_check_prime_bytes( #[anybuffer] bytes: &[u8], #[number] checks: usize, ) -> bool { let candidate = BigInt::from_bytes_be(num_bigint::Sign::Plus, bytes); primes::is_probably_prime(&candidate, checks) } #[op2] pub async fn op_node_check_prime_async( #[bigint] num: i64, #[number] checks: usize, ) -> Result<bool, tokio::task::JoinError> { // TODO(@littledivy): use rayon for CPU-bound tasks spawn_blocking(move || primes::is_probably_prime(&BigInt::from(num), checks)) .await } #[op2] pub fn op_node_check_prime_bytes_async( #[anybuffer] bytes: &[u8], #[number] checks: usize, ) -> impl Future<Output = Result<bool, tokio::task::JoinError>> + use<> { let candidate = BigInt::from_bytes_be(num_bigint::Sign::Plus, bytes); // TODO(@littledivy): use rayon for CPU-bound tasks async move { spawn_blocking(move || primes::is_probably_prime(&candidate, checks)).await } } #[op2] #[cppgc] pub fn op_node_create_hash( #[string] algorithm: &str, output_length: Option<u32>, ) -> Result<digest::Hasher, digest::HashError> { digest::Hasher::new(algorithm, output_length.map(|l| l as usize)) } #[op2] pub fn op_node_get_hashes() -> Vec<&'static str> { digest::Hash::get_hashes() } #[op2] pub fn op_node_get_hash_size(#[string] algorithm: &str) -> Option<u8> { digest::Hash::get_size(algorithm) } #[op2(fast)] pub fn op_node_hash_update( #[cppgc] hasher: &digest::Hasher, #[buffer] data: &[u8], ) -> bool { hasher.update(data) } #[op2(fast)] pub fn op_node_hash_update_str( #[cppgc] hasher: &digest::Hasher, #[string] data: &str, ) -> bool { hasher.update(data.as_bytes()) } #[op2] #[buffer] pub fn op_node_hash_digest( #[cppgc] hasher: &digest::Hasher, ) -> Option<Box<[u8]>> { hasher.digest() } #[op2] #[string] pub fn op_node_hash_digest_hex( #[cppgc] hasher: &digest::Hasher, ) -> Option<String> { let digest = hasher.digest()?; Some(faster_hex::hex_string(&digest)) } #[op2] #[cppgc] pub fn op_node_hash_clone( #[cppgc] hasher: &digest::Hasher, output_length: Option<u32>, ) -> Result<Option<digest::Hasher>, digest::HashError> { hasher.clone_inner(output_length.map(|l| l as usize)) } #[derive(Debug, thiserror::Error, deno_error::JsError)] pub enum PrivateEncryptDecryptError { #[class(generic)] #[error(transparent)] Pkcs8(#[from] pkcs8::Error), #[class(generic)] #[error(transparent)] Spki(#[from] spki::Error), #[class(generic)] #[error(transparent)] Rsa(#[from] rsa::Error), #[class(type)] #[error("Unknown padding")] UnknownPadding, #[class(generic)] #[property("code" = "ERR_OSSL_EVP_INVALID_DIGEST")] #[error( "error:060800A3:digital envelope routines::unknown message digest algorithm" )] InvalidDigest, #[class(generic)] #[property("code" = "ERR_OSSL_RSA_OAEP_DECODING_ERROR")] #[error( "error:04099079:rsa routines:RSA_padding_check_PKCS1_OAEP_mgf1:oaep decoding error" )] OaepDecodingError, #[class(generic)] #[property("code" = "ERR_OSSL_CRYPTO_INTERRUPTED_OR_CANCELLED")] #[error("error:07880109:common libcrypto routines::interrupted or cancelled")] EncryptedKeyRequiresPassphrase, } /// PKCS#1 v1.5 type 1 padding for private key encryption (signing). /// EM = 0x00 || 0x01 || PS || 0x00 || M /// where PS is filled with 0xFF bytes and has length k - mLen - 3. fn pkcs1v15_type1_pad( msg: &[u8], k: usize, ) -> Result<Vec<u8>, PrivateEncryptDecryptError> { if msg.len() > k - 11 { return Err(PrivateEncryptDecryptError::Rsa(rsa::Error::MessageTooLong)); } let mut em = vec![0xffu8; k]; em[0] = 0; em[1] = 1; em[k - msg.len() - 1] = 0; em[k - msg.len()..].copy_from_slice(msg); Ok(em) } /// Remove PKCS#1 v1.5 type 1 padding. /// EM = 0x00 || 0x01 || PS (0xFF bytes) || 0x00 || M fn pkcs1v15_type1_unpad( em: &[u8], k: usize, ) -> Result<Vec<u8>, PrivateEncryptDecryptError> { if k < 11 || em.len() != k || em[0] != 0 || em[1] != 1 { return Err(PrivateEncryptDecryptError::Rsa(rsa::Error::Decryption)); } // Find the 0x00 separator after the 0xFF padding let mut sep = None; for (i, &byte) in em.iter().enumerate().take(k).skip(2) { if byte == 0 { sep = Some(i); break; } if byte != 0xff { return Err(PrivateEncryptDecryptError::Rsa(rsa::Error::Decryption)); } } let sep = sep.ok_or(PrivateEncryptDecryptError::Rsa(rsa::Error::Decryption))?; if sep < 10 { // PS must be at least 8 bytes return Err(PrivateEncryptDecryptError::Rsa(rsa::Error::Decryption)); } Ok(em[sep + 1..].to_vec()) } fn create_oaep( hash: Option<&str>, label: Option<&[u8]>, ) -> Result<Oaep, PrivateEncryptDecryptError> { let hash = hash.unwrap_or("sha1"); let mut oaep = digest::match_fixed_digest!(hash, fn <D>() { Oaep::new::<D>() }, _ => { return Err(PrivateEncryptDecryptError::InvalidDigest); }); if let Some(label) = label { // The rsa crate stores the label as String but only uses // .as_bytes() on it, so the UTF-8 invariant is not relied upon. // SAFETY: label bytes are only ever hashed, never interpreted as text. oaep.label = Some(unsafe { String::from_utf8_unchecked(label.to_vec()) }); } Ok(oaep) } #[op2] pub fn op_node_private_encrypt( #[serde] key: StringOrBuffer, #[serde] msg: StringOrBuffer, #[smi] padding: u32, #[string] oaep_hash: Option<String>, #[serde] oaep_label: Option<JsBuffer>, ) -> Result<Uint8Array, PrivateEncryptDecryptError> { let key = match std::str::from_utf8(&key) { Ok(pem) => RsaPrivateKey::from_pkcs8_pem(pem) .or_else(|_| rsa::pkcs1::DecodeRsaPrivateKey::from_pkcs1_pem(pem)) .map_err(|e| PrivateEncryptDecryptError::Pkcs8(e.into()))?, Err(_) => RsaPrivateKey::from_pkcs8_der(&key).or_else(|_| { RsaPrivateKey::from_pkcs1_der(&key).map_err(pkcs8::Error::from) })?, }; let k = key.size(); let mut rng = rand::thread_rng(); match padding { 1 => { // PKCS1 type 1 padding + raw RSA private key operation let em = pkcs1v15_type1_pad(&msg, k)?; let int = num_bigint_dig::BigUint::from_bytes_be(&em); let result = rsa_decrypt_and_check(&key, Some(&mut rng), &int)?; let mut result_bytes = result.to_bytes_be(); // Left-pad with zeros to key size while result_bytes.len() < k { result_bytes.insert(0, 0); } Ok(result_bytes.into()) } 4 => { let oaep = create_oaep(oaep_hash.as_deref(), oaep_label.as_deref())?; Ok(key.as_ref().encrypt(&mut rng, oaep, &msg)?.into()) } _ => Err(PrivateEncryptDecryptError::UnknownPadding), } } #[op2] pub fn op_node_private_decrypt( #[serde] key: StringOrBuffer, #[serde] msg: StringOrBuffer, #[smi] padding: u32, #[string] oaep_hash: Option<String>, #[serde] oaep_label: Option<JsBuffer>, ) -> Result<Uint8Array, PrivateEncryptDecryptError> { let key = match std::str::from_utf8(&key) { Ok(pem) => { if pem.contains("Proc-Type: 4,ENCRYPTED") || pem .trim_start() .starts_with("-----BEGIN ENCRYPTED PRIVATE KEY-----") { return Err(PrivateEncryptDecryptError::EncryptedKeyRequiresPassphrase); } RsaPrivateKey::from_pkcs8_pem(pem) .or_else(|_| rsa::pkcs1::DecodeRsaPrivateKey::from_pkcs1_pem(pem)) .map_err(|e| PrivateEncryptDecryptError::Pkcs8(e.into()))? } Err(_) => RsaPrivateKey::from_pkcs8_der(&key).or_else(|_| { RsaPrivateKey::from_pkcs1_der(&key).map_err(pkcs8::Error::from) })?, }; let k = key.size(); let mut rng = rand::thread_rng(); match padding { 1 => Ok(key.decrypt(Pkcs1v15Encrypt, &msg)?.into()), 3 => { // RSA_NO_PADDING: raw private-key exponentiation let int = BigUint::from_bytes_be(&msg); let result = rsa_decrypt_and_check(&key, Some(&mut rng), &int)?; let mut result_bytes = result.to_bytes_be(); while result_bytes.len() < k { result_bytes.insert(0, 0); } Ok(result_bytes.into()) } 4 => { let oaep = create_oaep(oaep_hash.as_deref(), oaep_label.as_deref())?; key .decrypt(oaep, &msg) .map(|v| v.into()) .map_err(|_| PrivateEncryptDecryptError::OaepDecodingError) } _ => Err(PrivateEncryptDecryptError::UnknownPadding), } } fn rsa_pub_key_from_cert_pem(pem: &str) -> Result<RsaPublicKey, pkcs8::Error> { use x509_parser::prelude::FromDer; let (_, pem_obj) = x509_parser::pem::parse_x509_pem(pem.as_bytes()) .map_err(|_| pkcs8::Error::KeyMalformed)?; let spki_der = { let (_, cert) = x509_parser::prelude::X509Certificate::from_der(&pem_obj.contents) .map_err(|_| pkcs8::Error::KeyMalformed)?; cert.tbs_certificate.subject_pki.raw.to_vec() }; RsaPublicKey::from_public_key_der(&spki_der) .map_err(|_| pkcs8::Error::KeyMalformed) } #[op2(fast)] pub fn op_node_validate_oaep_hash( #[string] hash: &str, ) -> Result<(), PrivateEncryptDecryptError> { create_oaep(Some(hash), None).map(|_| ()) } #[op2] pub fn op_node_public_encrypt( #[serde] key: StringOrBuffer, #[serde] msg: StringOrBuffer, #[smi] padding: u32, #[string] oaep_hash: Option<String>, #[serde] oaep_label: Option<JsBuffer>, ) -> Result<Uint8Array, PrivateEncryptDecryptError> { let key = match std::str::from_utf8(&key) { Ok(pem) => RsaPublicKey::from_public_key_pem(pem) .or_else(|_| rsa::pkcs1::DecodeRsaPublicKey::from_pkcs1_pem(pem)) .or_else(|_| { RsaPrivateKey::from_pkcs8_pem(pem) .or_else(|_| { rsa::pkcs1::DecodeRsaPrivateKey::from_pkcs1_pem(pem) .map_err(pkcs8::Error::from) }) .map(|k| k.to_public_key()) }) .or_else(|_| rsa_pub_key_from_cert_pem(pem)) .map_err(|e| PrivateEncryptDecryptError::Spki(e.into()))?, Err(_) => RsaPublicKey::from_public_key_der(&key) .or_else(|_| { RsaPublicKey::from_pkcs1_der(&key).map_err(spki::Error::from) }) .or_else(|_| { RsaPrivateKey::from_pkcs8_der(&key) .or_else(|_| { RsaPrivateKey::from_pkcs1_der(&key).map_err(pkcs8::Error::from) }) .map(|k| k.to_public_key()) .map_err(spki::Error::from) })?, }; let k = key.size(); let mut rng = rand::thread_rng(); match padding { 1 => Ok(key.encrypt(&mut rng, Pkcs1v15Encrypt, &msg)?.into()), 3 => { // RSA_NO_PADDING: raw public-key exponentiation, zero-pad input to key size let mut padded = vec![0u8; k]; let copy_len = msg.len().min(k); padded[k - copy_len..].copy_from_slice(&msg[msg.len() - copy_len..]); let int = BigUint::from_bytes_be(&padded); let result = rsa_encrypt(&key, &int)?; let mut result_bytes = result.to_bytes_be(); while result_bytes.len() < k { result_bytes.insert(0, 0); } Ok(result_bytes.into()) } 4 => { let oaep = create_oaep(oaep_hash.as_deref(), oaep_label.as_deref())?; Ok(key.encrypt(&mut rng, oaep, &msg)?.into()) } _ => Err(PrivateEncryptDecryptError::UnknownPadding), } } #[op2] pub fn op_node_public_decrypt( #[serde] key: StringOrBuffer, #[serde] msg: StringOrBuffer, #[smi] padding: u32, ) -> Result<Uint8Array, PrivateEncryptDecryptError> { let key = match std::str::from_utf8(&key) { Ok(pem) => RsaPublicKey::from_public_key_pem(pem) .or_else(|_| rsa::pkcs1::DecodeRsaPublicKey::from_pkcs1_pem(pem)) .or_else(|_| { RsaPrivateKey::from_pkcs8_pem(pem) .or_else(|_| { rsa::pkcs1::DecodeRsaPrivateKey::from_pkcs1_pem(pem) .map_err(pkcs8::Error::from) }) .map(|k| k.to_public_key()) }) .or_else(|_| rsa_pub_key_from_cert_pem(pem)) .map_err(|e| PrivateEncryptDecryptError::Spki(e.into()))?, Err(_) => RsaPublicKey::from_public_key_der(&key) .or_else(|_| { RsaPublicKey::from_pkcs1_der(&key).map_err(spki::Error::from) }) .or_else(|_| { RsaPrivateKey::from_pkcs8_der(&key) .or_else(|_| { RsaPrivateKey::from_pkcs1_der(&key).map_err(pkcs8::Error::from) }) .map(|k| k.to_public_key()) .map_err(spki::Error::from) })?, }; let k = key.size(); match padding { 1 => { // Raw RSA with public key, then remove PKCS1 type 1 padding let int = num_bigint_dig::BigUint::from_bytes_be(&msg); let result = rsa_encrypt(&key, &int)?; let mut em = result.to_bytes_be(); // Left-pad with zeros to key size while em.len() < k { em.insert(0, 0); } Ok(pkcs1v15_type1_unpad(&em, k)?.into()) } 4 => { // OAEP not supported for public decrypt Err(PrivateEncryptDecryptError::UnknownPadding) } _ => Err(PrivateEncryptDecryptError::UnknownPadding), } } #[op2] pub fn op_node_aes_wrap_key( #[string] algorithm: &str, #[buffer] key: &[u8], #[buffer] iv: &[u8], #[buffer] data: &[u8], ) -> Result<Uint8Array, cipher::AesWrapError> { cipher::aes_wrap_key(algorithm, key, iv, data).map(Into::into) } #[op2] pub fn op_node_aes_unwrap_key( #[string] algorithm: &str, #[buffer] key: &[u8], #[buffer] iv: &[u8], #[buffer] data: &[u8], ) -> Result<Uint8Array, cipher::AesWrapError> { cipher::aes_unwrap_key(algorithm, key, iv, data).map(Into::into) } #[op2(fast)] #[smi] pub fn op_node_create_cipheriv( state: &mut OpState, #[string] algorithm: &str, #[buffer] key: &[u8], #[buffer] iv: &[u8], #[smi] auth_tag_length: i32, ) -> Result<u32, cipher::CipherContextError> { let auth_tag_length = if auth_tag_length == -1 { None } else { Some(auth_tag_length as usize) }; let context = cipher::CipherContext::new(algorithm, key, iv, auth_tag_length)?; Ok(state.resource_table.add(context)) } #[op2(fast)] pub fn op_node_cipheriv_set_aad( state: &mut OpState, #[smi] rid: u32, #[buffer] aad: &[u8], ) -> bool { let context = match state.resource_table.get::<cipher::CipherContext>(rid) { Ok(context) => context, Err(_) => return false, }; context.set_aad(aad); true } #[op2(fast)] pub fn op_node_cipheriv_encrypt( state: &mut OpState, #[smi] rid: u32, #[buffer] input: &[u8], #[buffer] output: &mut [u8], ) -> bool { let context = match state.resource_table.get::<cipher::CipherContext>(rid) { Ok(context) => context, Err(_) => return false, }; if output.len() < input.len() { return false; } context.encrypt(input, output); true } #[op2] pub fn op_node_cipheriv_final( state: &mut OpState, #[smi] rid: u32, auto_pad: bool, #[buffer] input: &[u8], #[anybuffer] output: &mut [u8], ) -> Result<Option<Uint8Array>, cipher::CipherContextError> { let context = state.resource_table.take::<cipher::CipherContext>(rid)?; let context = Rc::try_unwrap(context) .map_err(|_| cipher::CipherContextError::ContextInUse)?; context .r#final(auto_pad, input, output) .map(|tag| tag.map(Into::into)) } #[op2] pub fn op_node_cipheriv_take( state: &mut OpState, #[smi] rid: u32, ) -> Result<Option<Uint8Array>, cipher::CipherContextError> { let context = state.resource_table.take::<cipher::CipherContext>(rid)?; let context = Rc::try_unwrap(context) .map_err(|_| cipher::CipherContextError::ContextInUse)?; Ok(context.take_tag().map(Into::into)) } #[op2(fast)] #[smi] pub fn op_node_create_decipheriv( state: &mut OpState, #[string] algorithm: &str, #[buffer] key: &[u8], #[buffer] iv: &[u8], #[smi] auth_tag_length: i32, ) -> Result<u32, cipher::DecipherContextError> { let auth_tag_length = if auth_tag_length == -1 { None } else { Some(auth_tag_length as usize) }; let context = cipher::DecipherContext::new(algorithm, key, iv, auth_tag_length)?; Ok(state.resource_table.add(context)) } #[op2(fast)] pub fn op_node_decipheriv_auth_tag( state: &mut OpState, #[smi] rid: u32, #[smi] length: u32, ) -> Result<(), cipher::DecipherContextError> { let context = state.resource_table.get::<cipher::DecipherContext>(rid)?; context.validate_auth_tag(length as usize) } #[op2(fast)] pub fn op_node_decipheriv_set_aad( state: &mut OpState, #[smi] rid: u32, #[buffer] aad: &[u8], ) -> bool { let context = match state.resource_table.get::<cipher::DecipherContext>(rid) { Ok(context) => context, Err(_) => return false, }; context.set_aad(aad); true } #[op2(fast)] pub fn op_node_decipheriv_decrypt( state: &mut OpState, #[smi] rid: u32, #[buffer] input: &[u8], #[buffer] output: &mut [u8], ) -> bool { let context = match state.resource_table.get::<cipher::DecipherContext>(rid) { Ok(context) => context, Err(_) => return false, }; if output.len() < input.len() { return false; } context.decrypt(input, output); true } #[op2] pub fn op_node_decipheriv_final( state: &mut OpState, #[smi] rid: u32, auto_pad: bool, #[buffer] input: &[u8], #[anybuffer] output: &mut [u8], #[buffer] auth_tag: &[u8], ) -> Result<(), cipher::DecipherContextError> { let context = state.resource_table.take::<cipher::DecipherContext>(rid)?; let context = Rc::try_unwrap(context) .map_err(|_| cipher::DecipherContextError::ContextInUse)?; context.r#final(auto_pad, input, output, auth_tag) } #[op2] #[buffer] pub fn op_node_sign( #[cppgc] handle: &KeyObjectHandle, #[buffer] digest: &[u8], #[string] digest_type: &str, #[smi] pss_salt_length: Option<i32>, #[smi] padding: Option<u32>, #[smi] dsa_signature_encoding: u32, ) -> Result<Box<[u8]>, sign::KeyObjectHandlePrehashedSignAndVerifyError> { handle.sign_prehashed( digest_type, digest, pss_salt_length, padding, dsa_signature_encoding, ) } #[op2] pub fn op_node_verify( #[cppgc] handle: &KeyObjectHandle, #[buffer] digest: &[u8], #[string] digest_type: &str, #[buffer] signature: &[u8], #[smi] pss_salt_length: Option<i32>, #[smi] padding: Option<u32>, #[smi] dsa_signature_encoding: u32, ) -> Result<bool, sign::KeyObjectHandlePrehashedSignAndVerifyError> { handle.verify_prehashed( digest_type, digest, signature, pss_salt_length, padding, dsa_signature_encoding, ) } #[derive(Debug, PartialEq, Eq)] #[allow(non_camel_case_types, reason = "matches Node.js error code naming")] enum ErrorCode { ERR_CRYPTO_INVALID_DIGEST, } impl std::fmt::Display for ErrorCode { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.as_str()) } } impl ErrorCode { pub fn as_str(&self) -> &str { match self { Self::ERR_CRYPTO_INVALID_DIGEST => "ERR_CRYPTO_INVALID_DIGEST", } } } impl From<ErrorCode> for deno_error::PropertyValue { fn from(code: ErrorCode) -> Self { deno_error::PropertyValue::from(code.as_str().to_string()) } } #[derive(Debug, thiserror::Error, deno_error::JsError)] pub enum Pbkdf2Error { #[class(type)] #[error("Invalid digest: {0}")] #[property("code" = self.code())] UnsupportedDigest(String), #[class(inherit)] #[error(transparent)] Join(#[from] tokio::task::JoinError), } impl Pbkdf2Error { fn code(&self) -> ErrorCode { match self { Self::UnsupportedDigest(_) => ErrorCode::ERR_CRYPTO_INVALID_DIGEST, Self::Join(_) => unreachable!(), } } } fn pbkdf2_sync( password: &[u8], salt: &[u8], iterations: u32, algorithm_name: &str, derived_key: &mut [u8], ) -> Result<(), Pbkdf2Error> { match_fixed_digest_with_eager_block_buffer!( algorithm_name, fn <D>() { pbkdf2::pbkdf2_hmac::<D>(password, salt, iterations, derived_key); Ok(()) }, _ => { Err(Pbkdf2Error::UnsupportedDigest(algorithm_name.to_string())) } ) } #[op2] pub fn op_node_pbkdf2( #[anybuffer] password: &[u8], #[anybuffer] salt: &[u8], #[smi] iterations: u32, #[string] digest: &str, #[buffer] derived_key: &mut [u8], ) -> bool { pbkdf2_sync(password, salt, iterations, digest, derived_key).is_ok() } #[op2(fast)] pub fn op_node_pbkdf2_validate( #[string] digest: &str, ) -> Result<(), Pbkdf2Error> { // Validate the digest algorithm name match_fixed_digest_with_eager_block_buffer!( digest, fn <_D>() { Ok(()) }, _ => { Err(Pbkdf2Error::UnsupportedDigest(digest.to_string())) } ) } #[op2] pub async fn op_node_pbkdf2_async( #[anybuffer] password: JsBuffer, #[anybuffer] salt: JsBuffer, #[smi] iterations: u32, #[string] digest: String, #[number] keylen: usize, ) -> Result<Uint8Array, Pbkdf2Error> { spawn_blocking(move || { let mut derived_key = vec![0; keylen]; pbkdf2_sync(&password, &salt, iterations, &digest, &mut derived_key) .map(|_| derived_key.into()) }) .await? } #[op2(fast)] pub fn op_node_fill_random(#[buffer] buf: &mut [u8]) { rand::thread_rng().fill(buf); } #[op2] pub async fn op_node_fill_random_async(#[smi] len: i32) -> Uint8Array { spawn_blocking(move || { let mut buf = vec![0u8; len as usize]; rand::thread_rng().fill(&mut buf[..]); buf.into() }) .await .unwrap() } #[derive(Debug, thiserror::Error, deno_error::JsError)] pub enum HkdfError { #[class(type)] #[error("expected secret key")] ExpectedSecretKey, #[class(type)] #[error("HKDF-Expand failed")] HkdfExpandFailed, #[class(type)] #[error("Unsupported digest: {0}")] UnsupportedDigest(String), #[class(inherit)] #[error(transparent)] Join(#[from] tokio::task::JoinError), } fn hkdf_sync( digest_algorithm: &str, handle: &KeyObjectHandle, salt: &[u8], info: &[u8], okm: &mut [u8], ) -> Result<(), HkdfError> { let Some(ikm) = handle.as_secret_key() else { return Err(HkdfError::ExpectedSecretKey); }; match_fixed_digest_with_eager_block_buffer!( digest_algorithm, fn <D>() { let hk = Hkdf::<D>::new(Some(salt), ikm); hk.expand(info, okm) .map_err(|_| HkdfError::HkdfExpandFailed) }, _ => { Err(HkdfError::UnsupportedDigest(digest_algorithm.to_string())) } ) } #[op2(fast)] pub fn op_node_hkdf( #[string] digest_algorithm: &str, #[cppgc] handle: &KeyObjectHandle, #[buffer] salt: &[u8], #[buffer] info: &[u8], #[buffer] okm: &mut [u8], ) -> Result<(), HkdfError> { hkdf_sync(digest_algorithm, handle, salt, info, okm) } #[op2] pub async fn op_node_hkdf_async( #[string] digest_algorithm: String, #[cppgc] handle: &KeyObjectHandle, #[buffer] salt: JsBuffer, #[buffer] info: JsBuffer, #[number] okm_len: usize, ) -> Result<Uint8Array, HkdfError> { let handle = handle.clone(); spawn_blocking(move || { let mut okm = vec![0u8; okm_len]; hkdf_sync(&digest_algorithm, &handle, &salt, &info, &mut okm)?; Ok(okm.into()) }) .await? } #[op2] pub fn op_node_dh_compute_secret( #[buffer] prime: JsBuffer, #[buffer] private_key: JsBuffer, #[buffer] their_public_key: JsBuffer, ) -> Uint8Array { 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); shared_secret.to_bytes_be().into() } #[op2(fast)] #[number] pub fn op_node_random_int(#[number] min: i64, #[number] max: i64) -> i64 { let mut rng = rand::thread_rng(); // Uniform distribution is required to avoid Modulo Bias // https://en.wikipedia.org/wiki/Fisher–Yates_shuffle#Modulo_bias let dist = Uniform::from(min..max); dist.sample(&mut rng) } #[allow(clippy::too_many_arguments, reason = "all arguments are needed")] fn scrypt( password: StringOrBuffer, salt: StringOrBuffer, keylen: usize, cost: u64, block_size: u64, parallelization: u64, maxmem: usize, output_buffer: &mut [u8], ) -> Result<(), JsErrorBox> { assert!( output_buffer.len() >= keylen, "output_buffer too small for scrypt keylen", ); let cost = u32::try_from(cost) .ok() .filter(|cost| *cost < 64) .ok_or_else(|| JsErrorBox::generic("Invalid scrypt param"))?; let n = 1u64 .checked_shl(cost) .ok_or_else(|| JsErrorBox::generic("Invalid scrypt param"))?; // SAFETY: // - `password.as_ptr()`/`password.len()` describe a valid contiguous byte // slice because `StringOrBuffer` dereferences to `[u8]`. // - `salt.as_ptr()`/`salt.len()` likewise describe a valid contiguous byte // slice. // - `output_buffer.as_mut_ptr()` points to at least `keylen` writable bytes, // enforced by the assertion above and by the callers allocating a buffer of // that exact size. // - `n` is derived with `checked_shl`, so the `N` parameter passed to // `EVP_PBE_scrypt` cannot overflow the shift. // - AWS-LC documents `EVP_PBE_scrypt` as thread-safe for independent inputs; // this call does not alias mutable state across threads. let result = unsafe { aws_lc_sys::EVP_PBE_scrypt( password.as_ptr().cast(), password.len(), salt.as_ptr(), salt.len(), n, block_size, parallelization, maxmem, output_buffer.as_mut_ptr(), keylen, ) }; if result == 1 { Ok(()) } else { Err(JsErrorBox::generic("scrypt key derivation failed")) } } #[allow(clippy::too_many_arguments, reason = "all arguments are needed")] #[op2] pub fn op_node_scrypt_sync( #[serde] password: StringOrBuffer, #[serde] salt: StringOrBuffer, #[number] keylen: usize, #[number] cost: u64, #[number] block_size: u64, #[number] parallelization: u64, #[number] maxmem: usize, #[anybuffer] output_buffer: &mut [u8], ) -> Result<(), JsErrorBox> { scrypt( password, salt, keylen, cost, block_size, parallelization, maxmem, output_buffer, ) } #[derive(Debug, thiserror::Error, deno_error::JsError)] pub enum ScryptAsyncError { #[class(inherit)] #[error(transparent)] Join(#[from] tokio::task::JoinError), #[class(inherit)] #[error(transparent)] Other(JsErrorBox), } #[op2] pub async fn op_node_scrypt_async( #[serde] password: StringOrBuffer, #[serde] salt: StringOrBuffer, #[number] keylen: usize, #[number] cost: u64, #[number] block_size: u64, #[number] parallelization: u64, #[number] maxmem: usize, ) -> Result<Uint8Array, ScryptAsyncError> { spawn_blocking(move || { let mut output_buffer = vec![0u8; keylen]; scrypt( password, salt, keylen, cost, block_size, parallelization, maxmem, &mut output_buffer, ) .map(|_| output_buffer.into()) .map_err(ScryptAsyncError::Other) }) .await? } #[derive(Debug, thiserror::Error, deno_error::JsError)] pub enum EcdhEncodePubKey { #[class(type)] #[error("Invalid public key")] InvalidPublicKey, #[class(type)] #[error("Unsupported curve")] UnsupportedCurve, #[class(generic)] #[error(transparent)] Sec1(#[from] sec1::Error), } #[op2] pub fn op_node_ecdh_encode_pubkey( #[string] curve: &str, #[buffer] pubkey: &[u8], compress: bool, ) -> Result<Uint8Array, EcdhEncodePubKey> { use elliptic_curve::sec1::FromEncodedPoint; match curve { "secp256k1" => { let pubkey = elliptic_curve::PublicKey::<k256::Secp256k1>::from_encoded_point( &elliptic_curve::sec1::EncodedPoint::<k256::Secp256k1>::from_bytes( pubkey, )?, ); // CtOption does not expose its variants. if pubkey.is_none().into() { return Err(EcdhEncodePubKey::InvalidPublicKey); } let pubkey = pubkey.unwrap(); Ok(pubkey.to_encoded_point(compress).as_ref().to_vec().into()) } "prime256v1" | "secp256r1" => { let pubkey = elliptic_curve::PublicKey::<NistP256>::from_encoded_point( &elliptic_curve::sec1::EncodedPoint::<NistP256>::from_bytes(pubkey)?, ); // CtOption does not expose its variants. if pubkey.is_none().into() { return Err(EcdhEncodePubKey::InvalidPublicKey); } let pubkey = pubkey.unwrap(); Ok(pubkey.to_encoded_point(compress).as_ref().to_vec().into()) } "secp384r1" => { let pubkey = elliptic_curve::PublicKey::<NistP384>::from_encoded_point( &elliptic_curve::sec1::EncodedPoint::<NistP384>::from_bytes(pubkey)?, ); // CtOption does not expose its variants. if pubkey.is_none().into() { return Err(EcdhEncodePubKey::InvalidPublicKey); } let pubkey = pubkey.unwrap(); Ok(pubkey.to_encoded_point(compress).as_ref().to_vec().into()) } "secp521r1" => { let Some(pubkey): Option<elliptic_curve::PublicKey<NistP521>> = elliptic_curve::PublicKey::<NistP521>::from_encoded_point( &elliptic_curve::sec1::EncodedPoint::<NistP521>::from_bytes(pubkey)?, ) .into() else { return Err(EcdhEncodePubKey::InvalidPublicKey); }; Ok(pubkey.to_encoded_point(compress).as_ref().to_vec().into()) } "secp224r1" => { let pubkey = elliptic_curve::PublicKey::<NistP224>::from_encoded_point( &elliptic_curve::sec1::EncodedPoint::<NistP224>::from_bytes(pubkey)?, ); // CtOption does not expose its variants. if pubkey.is_none().into() { return Err(EcdhEncodePubKey::InvalidPublicKey); } let pubkey = pubkey.unwrap(); Ok(pubkey.to_encoded_point(compress).as_ref().to_vec().into()) } &_ => Err(EcdhEncodePubKey::UnsupportedCurve), } } #[op2(fast)] pub fn op_node_ecdh_generate_keys( #[string] curve: &str, #[buffer] pubbuf: &mut [u8], #[buffer] privbuf: &mut [u8], #[string] format: &str, ) -> Result<(), JsErrorBox> { let mut rng = rand::thread_rng(); let compress = format == "compressed"; match curve { "secp256k1" => { let privkey = elliptic_curve::SecretKey::<k256::Secp256k1>::random(&mut rng); let pubkey = privkey.public_key(); pubbuf.copy_from_slice(pubkey.to_encoded_point(compress).as_ref()); privbuf.copy_from_slice(privkey.to_nonzero_scalar().to_bytes().as_ref()); Ok(()) } "prime256v1" | "secp256r1" => { let privkey = elliptic_curve::SecretKey::<NistP256>::random(&mut rng); let pubkey = privkey.public_key(); pubbuf.copy_from_slice(pubkey.to_encoded_point(compress).as_ref()); privbuf.copy_from_slice(privkey.to_nonzero_scalar().to_bytes().as_ref()); Ok(()) } "secp384r1" => { let privkey = elliptic_curve::SecretKey::<NistP384>::random(&mut rng); let pubkey = privkey.public_key(); pubbuf.copy_from_slice(pubkey.to_encoded_point(compress).as_ref()); privbuf.copy_from_slice(privkey.to_nonzero_scalar().to_bytes().as_ref()); Ok(()) } "secp521r1" => { let privkey = elliptic_curve::SecretKey::<NistP521>::random(&mut rng); let pubkey = privkey.public_key(); pubbuf.copy_from_slice(pubkey.to_encoded_point(compress).as_ref()); privbuf.copy_from_slice(privkey.to_nonzero_scalar().to_bytes().as_ref()); Ok(()) } "secp224r1" => { let privkey = elliptic_curve::SecretKey::<NistP224>::random(&mut rng); let pubkey = privkey.public_key(); pubbuf.copy_from_slice(pubkey.to_encoded_point(compress).as_ref()); privbuf.copy_from_slice(privkey.to_nonzero_scalar().to_bytes().as_ref()); Ok(()) } &_ => Err(JsErrorBox::type_error(format!( "Unsupported curve: {}", curve ))), } } #[derive(Debug, thiserror::Error, deno_error::JsError)] pub enum EcdhError { #[class("ERR_CRYPTO_ECDH_INVALID_PUBLIC_KEY")] #[error("Public key is not valid for specified curve")] InvalidPublicKey, #[class(generic)] #[error("Private key is not valid for specified curve")] InvalidPrivateKey, #[class(type)] #[error("Unsupported curve")] UnsupportedCurve, #[class(generic)] #[error("Invalid key pair")] InvalidKeyPair, } #[op2] pub fn op_node_ecdh_compute_secret( #[string] curve: &str, #[buffer] this_priv: Option<JsBuffer>, #[buffer] this_pub: Option<JsBuffer>, #[buffer] their_pub: &mut [u8], #[buffer] secret: &mut [u8], ) -> Result<(), EcdhError> { let this_priv = this_priv.ok_or(EcdhError::InvalidPrivateKey)?; match curve { "secp256k1" => { let their_public_key = elliptic_curve::PublicKey::<k256::Secp256k1>::from_sec1_bytes( their_pub, ) .map_err(|_| EcdhError::InvalidPublicKey)?; let this_private_key = elliptic_curve::SecretKey::<k256::Secp256k1>::from_slice(&this_priv) .map_err(|_| EcdhError::InvalidPrivateKey)?; if let Some(this_pub) = this_pub { let derived = this_private_key.public_key(); let stored = elliptic_curve::PublicKey::<k256::Secp256k1>::from_sec1_bytes( &this_pub, ) .map_err(|_| EcdhError::InvalidKeyPair)?; if derived != stored { return Err(EcdhError::InvalidKeyPair); } } let shared_secret = elliptic_curve::ecdh::diffie_hellman( this_private_key.to_nonzero_scalar(), their_public_key.as_affine(), ); secret.copy_from_slice(shared_secret.raw_secret_bytes()); } "prime256v1" | "secp256r1" => { let their_public_key = elliptic_curve::PublicKey::<NistP256>::from_sec1_bytes(their_pub) .map_err(|_| EcdhError::InvalidPublicKey)?; let this_private_key = elliptic_curve::SecretKey::<NistP256>::from_slice(&this_priv) .map_err(|_| EcdhError::InvalidPrivateKey)?; if let Some(this_pub) = this_pub { let derived = this_private_key.public_key(); let stored = elliptic_curve::PublicKey::<NistP256>::from_sec1_bytes(&this_pub) .map_err(|_| EcdhError::InvalidKeyPair)?; if derived != stored { return Err(EcdhError::InvalidKeyPair); } } let shared_secret = elliptic_curve::ecdh::diffie_hellman( this_private_key.to_nonzero_scalar(), their_public_key.as_affine(), ); secret.copy_from_slice(shared_secret.raw_secret_bytes()); } "secp384r1" => { let their_public_key = elliptic_curve::PublicKey::<NistP384>::from_sec1_bytes(their_pub) .map_err(|_| EcdhError::InvalidPublicKey)?; let this_private_key = elliptic_curve::SecretKey::<NistP384>::from_slice(&this_priv) .map_err(|_| EcdhError::InvalidPrivateKey)?; if let Some(this_pub) = this_pub { let derived = this_private_key.public_key(); let stored = elliptic_curve::PublicKey::<NistP384>::from_sec1_bytes(&this_pub) .map_err(|_| EcdhError::InvalidKeyPair)?; if derived != stored { return Err(EcdhError::InvalidKeyPair); } } let shared_secret = elliptic_curve::ecdh::diffie_hellman( this_private_key.to_nonzero_scalar(), their_public_key.as_affine(), ); secret.copy_from_slice(shared_secret.raw_secret_bytes()); } "secp521r1" => { let their_public_key = elliptic_curve::PublicKey::<NistP521>::from_sec1_bytes(their_pub) .map_err(|_| EcdhError::InvalidPublicKey)?; let this_private_key = elliptic_curve::SecretKey::<NistP521>::from_slice(&this_priv) .map_err(|_| EcdhError::InvalidPrivateKey)?; if let Some(this_pub) = this_pub { let derived = this_private_key.public_key(); let stored = elliptic_curve::PublicKey::<NistP521>::from_sec1_bytes(&this_pub) .map_err(|_| EcdhError::InvalidKeyPair)?; if derived != stored { return Err(EcdhError::InvalidKeyPair); } } let shared_secret = elliptic_curve::ecdh::diffie_hellman( this_private_key.to_nonzero_scalar(), their_public_key.as_affine(), ); secret.copy_from_slice(shared_secret.raw_secret_bytes()); } "secp224r1" => { let their_public_key = elliptic_curve::PublicKey::<NistP224>::from_sec1_bytes(their_pub) .map_err(|_| EcdhError::InvalidPublicKey)?; let this_private_key = elliptic_curve::SecretKey::<NistP224>::from_slice(&this_priv) .map_err(|_| EcdhError::InvalidPrivateKey)?; if let Some(this_pub) = this_pub { let derived = this_private_key.public_key(); let stored = elliptic_curve::PublicKey::<NistP224>::from_sec1_bytes(&this_pub) .map_err(|_| EcdhError::InvalidKeyPair)?; if derived != stored { return Err(EcdhError::InvalidKeyPair); } } let shared_secret = elliptic_curve::ecdh::diffie_hellman( this_private_key.to_nonzero_scalar(), their_public_key.as_affine(), ); secret.copy_from_slice(shared_secret.raw_secret_bytes()); } _ => return Err(EcdhError::UnsupportedCurve), } Ok(()) } #[op2(fast)] pub fn op_node_ecdh_compute_public_key( #[string] curve: &str, #[buffer] privkey: &[u8], #[buffer] pubkey: &mut [u8], ) -> Result<(), EcdhError> { match curve { "secp256k1" => { let this_private_key = elliptic_curve::SecretKey::<k256::Secp256k1>::from_slice(privkey) .map_err(|_| EcdhError::InvalidPrivateKey)?; let public_key = this_private_key.public_key(); pubkey.copy_from_slice(public_key.to_encoded_point(false).as_ref()); } "prime256v1" | "secp256r1" => { let this_private_key = elliptic_curve::SecretKey::<NistP256>::from_slice(privkey) .map_err(|_| EcdhError::InvalidPrivateKey)?; let public_key = this_private_key.public_key(); pubkey.copy_from_slice(public_key.to_encoded_point(false).as_ref()); } "secp384r1" => { let this_private_key = elliptic_curve::SecretKey::<NistP384>::from_slice(privkey) .map_err(|_| EcdhError::InvalidPrivateKey)?; let public_key = this_private_key.public_key(); pubkey.copy_from_slice(public_key.to_encoded_point(false).as_ref()); } "secp521r1" => { let this_private_key = elliptic_curve::SecretKey::<NistP521>::from_slice(privkey) .map_err(|_| EcdhError::InvalidPrivateKey)?; let public_key = this_private_key.public_key(); pubkey.copy_from_slice(public_key.to_encoded_point(false).as_ref()); } "secp224r1" => { let this_private_key = elliptic_curve::SecretKey::<NistP224>::from_slice(privkey) .map_err(|_| EcdhError::InvalidPrivateKey)?; let public_key = this_private_key.public_key(); pubkey.copy_from_slice(public_key.to_encoded_point(false).as_ref()); } _ => return Err(EcdhError::UnsupportedCurve), } Ok(()) } #[op2(fast)] pub fn op_node_ecdh_validate_private_key( #[string] curve: &str, #[buffer] privkey: &[u8], ) -> bool { match curve { "secp256k1" => { elliptic_curve::SecretKey::<k256::Secp256k1>::from_slice(privkey).is_ok() } "prime256v1" | "secp256r1" => { elliptic_curve::SecretKey::<NistP256>::from_slice(privkey).is_ok() } "secp384r1" => { elliptic_curve::SecretKey::<NistP384>::from_slice(privkey).is_ok() } "secp521r1" => { elliptic_curve::SecretKey::<NistP521>::from_slice(privkey).is_ok() } "secp224r1" => { elliptic_curve::SecretKey::<NistP224>::from_slice(privkey).is_ok() } _ => false, } } #[op2(fast)] pub fn op_node_ecdh_validate_public_key( #[string] curve: &str, #[buffer] pubkey: &[u8], ) -> bool { use elliptic_curve::sec1::FromEncodedPoint; match curve { "secp256k1" => { let Ok(point) = elliptic_curve::sec1::EncodedPoint::<k256::Secp256k1>::from_bytes( pubkey, ) else { return false; }; let pk = elliptic_curve::PublicKey::<k256::Secp256k1>::from_encoded_point( &point, ); bool::from(pk.is_some()) } "prime256v1" | "secp256r1" => { let Ok(point) = elliptic_curve::sec1::EncodedPoint::<NistP256>::from_bytes(pubkey) else { return false; }; let pk = elliptic_curve::PublicKey::<NistP256>::from_encoded_point(&point); bool::from(pk.is_some()) } "secp384r1" => { let Ok(point) = elliptic_curve::sec1::EncodedPoint::<NistP384>::from_bytes(pubkey) else { return false; }; let pk = elliptic_curve::PublicKey::<NistP384>::from_encoded_point(&point); bool::from(pk.is_some()) } "secp521r1" => { let Ok(point) = elliptic_curve::sec1::EncodedPoint::<NistP521>::from_bytes(pubkey) else { return false; }; let pk: Option<elliptic_curve::PublicKey<NistP521>> = elliptic_curve::PublicKey::<NistP521>::from_encoded_point(&point) .into(); pk.is_some() } "secp224r1" => { let Ok(point) = elliptic_curve::sec1::EncodedPoint::<NistP224>::from_bytes(pubkey) else { return false; }; let pk = elliptic_curve::PublicKey::<NistP224>::from_encoded_point(&point); bool::from(pk.is_some()) } _ => false, } } /// Performs the structural portion of OpenSSL's DH_check(): verifies that /// the generator is in the valid range (1 < g < p) and that the modulus is /// at least 2. Returns a bitmask of error codes: /// 0x02 - generator is not suitable for p (g <= 1 or g >= p) /// 0x10 - p is degenerate (less than 2) /// /// The full DH_check also tests p for primality (Miller-Rabin) and tests /// whether (p-1)/2 is prime. Those checks are intentionally omitted: in a /// debug build, Miller-Rabin on a 2048-bit modulus is slow enough to push /// `createDiffieHellman` past test timeouts, and Node tests never expect /// `verifyError === 0` for a non-prime large modulus. #[op2(fast)] pub fn op_node_dh_check( #[buffer] prime: &[u8], #[buffer] generator: &[u8], ) -> i32 { let p = BigInt::from_bytes_be(num_bigint::Sign::Plus, prime); let g = BigInt::from_bytes_be(num_bigint::Sign::Plus, generator); let mut code: i32 = 0; if p < BigInt::from(2) { code |= 0x10; } if g <= BigInt::from(1) || g >= p { code |= 0x02; } code } #[inline] fn gen_prime( size: usize, safe: bool, add: Option<&[u8]>, rem: Option<&[u8]>, ) -> Result<Uint8Array, GeneratePrimeError> { if safe || add.is_some() || rem.is_some() { let prime = primes::Prime::generate_with_options(size, safe, add, rem)?; Ok(prime.0.to_bytes_be().into()) } else { Ok(primes::Prime::generate(size).0.to_bytes_be().into()) } } #[derive(Debug, thiserror::Error, deno_error::JsError)] pub enum GeneratePrimeError { #[class("ERR_OUT_OF_RANGE")] #[error("invalid options.add")] InvalidAdd, #[class("ERR_OUT_OF_RANGE")] #[error("invalid options.rem")] InvalidRem, #[class("ERR_OUT_OF_RANGE")] #[error("prime generation failed: no suitable prime found in range")] OutOfRange, #[class(generic)] #[error(transparent)] JoinError(#[from] tokio::task::JoinError), } impl From<primes::GeneratePrimeError> for GeneratePrimeError { fn from(e: primes::GeneratePrimeError) -> Self { match e { primes::GeneratePrimeError::InvalidAdd => GeneratePrimeError::InvalidAdd, primes::GeneratePrimeError::InvalidRem => GeneratePrimeError::InvalidRem, primes::GeneratePrimeError::OutOfRange => GeneratePrimeError::OutOfRange, } } } #[op2] pub fn op_node_gen_prime( #[number] size: usize, safe: bool, #[buffer] add: Option<JsBuffer>, #[buffer] rem: Option<JsBuffer>, ) -> Result<Uint8Array, GeneratePrimeError> { gen_prime(size, safe, add.as_deref(), rem.as_deref()) } #[op2] pub async fn op_node_gen_prime_async( #[number] size: usize, safe: bool, #[buffer] add: Option<JsBuffer>, #[buffer] rem: Option<JsBuffer>, ) -> Result<Uint8Array, GeneratePrimeError> { spawn_blocking(move || gen_prime(size, safe, add.as_deref(), rem.as_deref())) .await? } #[derive(Debug, thiserror::Error, deno_error::JsError)] pub enum DiffieHellmanError { #[class(type)] #[error("Expected private key")] ExpectedPrivateKey, #[class(type)] #[error("Expected public key")] ExpectedPublicKey, #[class(generic)] #[error( "error:0308010C:digital envelope routines::mismatching domain parameters" )] MismatchingDomainParameters, #[class(generic)] #[error("error:030000A9:digital envelope routines::failed during derivation")] FailedDuringDerivation, #[class(type)] #[error("Unsupported key type for diffie hellman, or key type mismatch")] UnsupportedKeyTypeForDiffieHellmanOrKeyTypeMismatch, } #[op2] #[buffer] pub fn op_node_diffie_hellman( #[cppgc] private: &KeyObjectHandle, #[cppgc] public: &KeyObjectHandle, ) -> Result<Box<[u8]>, DiffieHellmanError> { let private = private .as_private_key() .ok_or(DiffieHellmanError::ExpectedPrivateKey)?; let public = public .as_public_key() .ok_or(DiffieHellmanError::ExpectedPublicKey)?; 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::Ec(EcPrivateKey::P521(private)), AsymmetricPublicKey::Ec(EcPublicKey::P521(public)), ) => p521::ecdh::diffie_hellman( private.to_nonzero_scalar(), public.as_affine(), ) .raw_secret_bytes() .to_vec() .into_boxed_slice(), ( AsymmetricPrivateKey::Ec(EcPrivateKey::Secp256k1(private)), AsymmetricPublicKey::Ec(EcPublicKey::Secp256k1(public)), ) => k256::ecdh::diffie_hellman( private.to_nonzero_scalar(), public.as_affine(), ) .raw_secret_bytes() .to_vec() .into_boxed_slice(), (AsymmetricPrivateKey::Ec(_), AsymmetricPublicKey::Ec(_)) => { // Both EC keys but on different curves. return Err(DiffieHellmanError::MismatchingDomainParameters); } ( AsymmetricPrivateKey::X25519(private), AsymmetricPublicKey::X25519(public), ) => { let shared = private.diffie_hellman(public); let bytes = shared.to_bytes(); // Reject all-zero shared secrets (low-order points), matching OpenSSL. if bytes.iter().all(|b| *b == 0) { return Err(DiffieHellmanError::FailedDuringDerivation); } bytes.into_iter().collect() } ( AsymmetricPrivateKey::X448(private), AsymmetricPublicKey::X448(public), ) => { let mut scalar_bytes = [0u8; 57]; scalar_bytes[..56].copy_from_slice(&private[..56]); let scalar = ed448_goldilocks::EdwardsScalar::from_bytes_mod_order( &scalar_bytes.into(), ); let point = ed448_goldilocks::MontgomeryPoint(*public); let shared = &point * &scalar; if shared.0.iter().all(|b| *b == 0) { return Err(DiffieHellmanError::FailedDuringDerivation); } shared.0.to_vec().into_boxed_slice() } (AsymmetricPrivateKey::Dh(private), AsymmetricPublicKey::Dh(public)) => { // Compare DH parameters by integer value, not byte encoding, // since different generation paths may produce different ASN.1 // encodings of the same integer (e.g. with/without leading 0x00). let priv_prime = BigUint::from_bytes_be(private.params.prime.as_bytes()); let pub_prime = BigUint::from_bytes_be(public.params.prime.as_bytes()); let priv_base = BigUint::from_bytes_be(private.params.base.as_bytes()); let pub_base = BigUint::from_bytes_be(public.params.base.as_bytes()); if priv_prime != pub_prime || priv_base != pub_base { return Err(DiffieHellmanError::MismatchingDomainParameters); } // 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 private_key = private.key.clone().into_vec(); let private_key = BigUint::from_bytes_be(&private_key); let shared_secret = pubkey.modpow(&private_key, &priv_prime); // Pad to the byte length of the prime, matching OpenSSL's // DH_compute_key_padded behaviour used by EVP_PKEY_derive. let prime_len = priv_prime.bits().div_ceil(8); let secret_bytes = shared_secret.to_bytes_be(); if secret_bytes.len() < prime_len { let mut padded = vec![0u8; prime_len]; padded[prime_len - secret_bytes.len()..].copy_from_slice(&secret_bytes); padded.into_boxed_slice() } else { secret_bytes.into_boxed_slice() } } _ => { return Err( DiffieHellmanError::UnsupportedKeyTypeForDiffieHellmanOrKeyTypeMismatch, ); } }; Ok(res) } #[derive(Debug, thiserror::Error, deno_error::JsError)] #[class(type)] pub enum SignEd25519Error { #[error("Expected private key")] ExpectedPrivateKey, #[error("Expected Ed25519 private key")] ExpectedEd25519PrivateKey, #[error("Invalid Ed25519 private key")] InvalidEd25519PrivateKey, } #[op2(fast)] pub fn op_node_sign_ed25519( #[cppgc] key: &KeyObjectHandle, #[buffer] data: &[u8], #[buffer] signature: &mut [u8], ) -> Result<(), SignEd25519Error> { let private = key .as_private_key() .ok_or(SignEd25519Error::ExpectedPrivateKey)?; let ed25519 = match private { AsymmetricPrivateKey::Ed25519(private) => private, _ => return Err(SignEd25519Error::ExpectedEd25519PrivateKey), }; let pair = Ed25519KeyPair::from_seed_unchecked(ed25519.as_bytes().as_slice()) .map_err(|_| SignEd25519Error::InvalidEd25519PrivateKey)?; signature.copy_from_slice(pair.sign(data).as_ref()); Ok(()) } #[derive(Debug, thiserror::Error, deno_error::JsError)] #[class(type)] pub enum VerifyEd25519Error { #[error("Expected public key")] ExpectedPublicKey, #[error("Expected Ed25519 public key")] ExpectedEd25519PublicKey, } #[op2(fast)] pub fn op_node_verify_ed25519( #[cppgc] key: &KeyObjectHandle, #[buffer] data: &[u8], #[buffer] signature: &[u8], ) -> Result<bool, VerifyEd25519Error> { let public = key .as_public_key() .ok_or(VerifyEd25519Error::ExpectedPublicKey)?; let ed25519 = match &*public { AsymmetricPublicKey::Ed25519(public) => public, _ => return Err(VerifyEd25519Error::ExpectedEd25519PublicKey), }; let verified = aws_lc_rs::signature::UnparsedPublicKey::new( &aws_lc_rs::signature::ED25519, ed25519.as_bytes().as_slice(), ) .verify(data, signature) .is_ok(); Ok(verified) } #[derive(Debug, thiserror::Error, deno_error::JsError)] #[class(type)] pub enum SignEd448Error { #[error("Expected private key")] ExpectedPrivateKey, #[error("Expected Ed448 private key")] ExpectedEd448PrivateKey, } #[op2(fast)] pub fn op_node_sign_ed448( #[cppgc] key: &KeyObjectHandle, #[buffer] data: &[u8], #[buffer] signature: &mut [u8], ) -> Result<(), SignEd448Error> { let private = key .as_private_key() .ok_or(SignEd448Error::ExpectedPrivateKey)?; let ed448 = match private { AsymmetricPrivateKey::Ed448(private) => private, _ => return Err(SignEd448Error::ExpectedEd448PrivateKey), }; let sig = ed448.sign_raw(data); signature.copy_from_slice(&sig.to_bytes()); Ok(()) } #[derive(Debug, thiserror::Error, deno_error::JsError)] #[class(type)] pub enum VerifyEd448Error { #[error("Expected public key")] ExpectedPublicKey, #[error("Expected Ed448 public key")] ExpectedEd448PublicKey, } #[op2(fast)] pub fn op_node_verify_ed448( #[cppgc] key: &KeyObjectHandle, #[buffer] data: &[u8], #[buffer] signature: &[u8], ) -> Result<bool, VerifyEd448Error> { let public = key .as_public_key() .ok_or(VerifyEd448Error::ExpectedPublicKey)?; let ed448 = match &*public { AsymmetricPublicKey::Ed448(public) => public, _ => return Err(VerifyEd448Error::ExpectedEd448PublicKey), }; let sig = match ed448_goldilocks::Signature::from_slice(signature) { Ok(sig) => sig, Err(_) => return Ok(false), }; Ok(ed448.verify_raw(&sig, data).is_ok()) } #[derive(Debug, thiserror::Error, deno_error::JsError)] pub enum SpkacError { #[error("spkac is too large")] #[property("code" = "ERR_OUT_OF_RANGE")] #[class(range)] BufferOutOfRange, } #[op2(fast)] pub fn op_node_verify_spkac( #[buffer] spkac: &[u8], ) -> Result<bool, SpkacError> { if spkac.len() > i32::MAX as usize { return Err(SpkacError::BufferOutOfRange); } Ok(deno_crypto_provider::spki::verify_spkac(spkac)) } #[op2] pub fn op_node_cert_export_public_key( #[buffer] spkac: &[u8], ) -> Result<Option<Uint8Array>, SpkacError> { if spkac.len() > i32::MAX as usize { return Err(SpkacError::BufferOutOfRange); } Ok(deno_crypto_provider::spki::export_public_key(spkac).map(Into::into)) } #[op2] pub fn op_node_cert_export_challenge( #[buffer] spkac: &[u8], ) -> Result<Option<Uint8Array>, SpkacError> { if spkac.len() > i32::MAX as usize { return Err(SpkacError::BufferOutOfRange); } Ok(deno_crypto_provider::spki::export_challenge(spkac).map(Into::into)) }