#![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::pkcs1::DecodeRsaPrivateKey;
use rsa::pkcs1::DecodeRsaPublicKey;
use rsa::pkcs8::DecodePrivateKey;
use rsa::pkcs8::DecodePublicKey;
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_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_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_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_encrypt,
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,
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_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> {
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);
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,
}
#[op2]
pub fn op_node_private_encrypt(
#[serde] key: StringOrBuffer,
#[serde] msg: StringOrBuffer,
#[smi] padding: u32,
) -> 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 mut rng = rand::thread_rng();
match padding {
1 => Ok(
key
.as_ref()
.encrypt(&mut rng, Pkcs1v15Encrypt, &msg)?
.into(),
),
4 => Ok(
key
.as_ref()
.encrypt(&mut rng, Oaep::new::<sha1::Sha1>(), &msg)?
.into(),
),
_ => Err(PrivateEncryptDecryptError::UnknownPadding),
}
}
#[op2]
pub fn op_node_private_decrypt(
#[serde] key: StringOrBuffer,
#[serde] msg: StringOrBuffer,
#[smi] padding: u32,
) -> 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)
})?,
};
match padding {
1 => Ok(key.decrypt(Pkcs1v15Encrypt, &msg)?.into()),
4 => Ok(key.decrypt(Oaep::new::<sha1::Sha1>(), &msg)?.into()),
_ => Err(PrivateEncryptDecryptError::UnknownPadding),
}
}
#[op2]
pub fn op_node_public_encrypt(
#[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))
.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)
})?,
};
let mut rng = rand::thread_rng();
match padding {
1 => Ok(key.encrypt(&mut rng, Pkcs1v15Encrypt, &msg)?.into()),
4 => Ok(
key
.encrypt(&mut rng, Oaep::new::<sha1::Sha1>(), &msg)?
.into(),
),
_ => Err(PrivateEncryptDecryptError::UnknownPadding),
}
}
#[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,
};
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,
};
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<u32>,
#[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<u32>,
#[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)]
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> {
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();
let dist = Uniform::from(min..max);
dist.sample(&mut rng)
}
#[allow(clippy::too_many_arguments)]
fn scrypt(
password: StringOrBuffer,
salt: StringOrBuffer,
keylen: u32,
cost: u32,
block_size: u32,
parallelization: u32,
_maxmem: u32,
output_buffer: &mut [u8],
) -> Result<(), JsErrorBox> {
let params = scrypt::Params::new(
cost as u8,
block_size,
parallelization,
keylen as usize,
)
.map_err(|_| JsErrorBox::generic("Invalid scrypt param"))?;
let res = scrypt::scrypt(&password, &salt, ¶ms, output_buffer);
if res.is_ok() {
Ok(())
} else {
Err(JsErrorBox::generic("scrypt key derivation failed"))
}
}
#[allow(clippy::too_many_arguments)]
#[op2]
pub fn op_node_scrypt_sync(
#[serde] password: StringOrBuffer,
#[serde] salt: StringOrBuffer,
#[smi] keylen: u32,
#[smi] cost: u32,
#[smi] block_size: u32,
#[smi] parallelization: u32,
#[smi] maxmem: u32,
#[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,
#[smi] keylen: u32,
#[smi] cost: u32,
#[smi] block_size: u32,
#[smi] parallelization: u32,
#[smi] maxmem: u32,
) -> Result<Uint8Array, ScryptAsyncError> {
spawn_blocking(move || {
let mut output_buffer = vec![0u8; keylen as usize];
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,
)?,
);
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)?,
);
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)?,
);
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)?,
);
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
))),
}
}
#[op2]
pub fn op_node_ecdh_compute_secret(
#[string] curve: &str,
#[buffer] this_priv: Option<JsBuffer>,
#[buffer] their_pub: &mut [u8],
#[buffer] secret: &mut [u8],
) -> Result<(), JsErrorBox> {
match curve {
"secp256k1" => {
let their_public_key =
elliptic_curve::PublicKey::<k256::Secp256k1>::from_sec1_bytes(
their_pub,
)
.expect("bad public key");
let this_private_key =
elliptic_curve::SecretKey::<k256::Secp256k1>::from_slice(
&this_priv.expect("must supply private key"),
)
.expect("bad private key");
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)
.expect("bad public key");
let this_private_key = elliptic_curve::SecretKey::<NistP256>::from_slice(
&this_priv.expect("must supply private key"),
)
.expect("bad private key");
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)
.expect("bad public key");
let this_private_key = elliptic_curve::SecretKey::<NistP384>::from_slice(
&this_priv.expect("must supply private key"),
)
.expect("bad private key");
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(|_| JsErrorBox::type_error("bad public key"))?;
let this_private_key = elliptic_curve::SecretKey::<NistP521>::from_slice(
&this_priv
.ok_or_else(|| JsErrorBox::type_error("must supply private key"))?,
)
.map_err(|_| JsErrorBox::type_error("bad private key"))?;
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)
.expect("bad public key");
let this_private_key = elliptic_curve::SecretKey::<NistP224>::from_slice(
&this_priv.expect("must supply private key"),
)
.expect("bad private key");
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());
}
&_ => todo!(),
}
Ok(())
}
#[op2(fast)]
pub fn op_node_ecdh_compute_public_key(
#[string] curve: &str,
#[buffer] privkey: &[u8],
#[buffer] pubkey: &mut [u8],
) {
match curve {
"secp256k1" => {
let this_private_key =
elliptic_curve::SecretKey::<k256::Secp256k1>::from_slice(privkey)
.expect("bad private key");
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)
.expect("bad private key");
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)
.expect("bad private key");
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)
.expect("bad private key");
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)
.expect("bad private key");
let public_key = this_private_key.public_key();
pubkey.copy_from_slice(public_key.to_encoded_point(false).as_ref());
}
&_ => todo!(),
}
}
#[inline]
fn gen_prime(size: usize) -> Uint8Array {
primes::Prime::generate(size).0.to_bytes_be().into()
}
#[op2]
pub fn op_node_gen_prime(#[number] size: usize) -> Uint8Array {
gen_prime(size)
}
#[op2]
pub async fn op_node_gen_prime_async(
#[number] size: usize,
) -> Result<Uint8Array, tokio::task::JoinError> {
spawn_blocking(move || gen_prime(size)).await
}
#[derive(Debug, thiserror::Error, deno_error::JsError)]
#[class(type)]
pub enum DiffieHellmanError {
#[error("Expected private key")]
ExpectedPrivateKey,
#[error("Expected public key")]
ExpectedPublicKey,
#[error("DH parameters mismatch")]
DhParametersMismatch,
#[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::X25519(private),
AsymmetricPublicKey::X25519(public),
) => private
.diffie_hellman(public)
.to_bytes()
.into_iter()
.collect(),
(AsymmetricPrivateKey::Dh(private), AsymmetricPublicKey::Dh(public)) => {
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::DhParametersMismatch);
}
let public_key = public.key.clone().into_vec();
let pubkey = BigUint::from_bytes_be(&public_key);
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);
shared_secret.to_bytes_be().into()
}
_ => {
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))
}