summaryrefslogtreecommitdiff
path: root/ext/node/ops/crypto/mod.rs
diff options
context:
space:
mode:
authorBartek Iwańczuk <biwanczuk@gmail.com>2023-04-24 12:22:21 +0200
committerGitHub <noreply@github.com>2023-04-24 12:22:21 +0200
commit1f0360c07382dbd86066d1aa8aa4bae34aff18c5 (patch)
treecc82d00aea829f0b3d3949f40df9696b099ee662 /ext/node/ops/crypto/mod.rs
parent28e2c7204fe02304a8fc3339d7758eec0f64f723 (diff)
refactor(ext/node): reorganize ops (#18799)
Move all op related code of "ext/node" to "ext/node/ops" module. These files were unnecessarily scattered around the extension.
Diffstat (limited to 'ext/node/ops/crypto/mod.rs')
-rw-r--r--ext/node/ops/crypto/mod.rs903
1 files changed, 903 insertions, 0 deletions
diff --git a/ext/node/ops/crypto/mod.rs b/ext/node/ops/crypto/mod.rs
new file mode 100644
index 000000000..d224b40f7
--- /dev/null
+++ b/ext/node/ops/crypto/mod.rs
@@ -0,0 +1,903 @@
+// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+use deno_core::error::generic_error;
+use deno_core::error::type_error;
+use deno_core::error::AnyError;
+use deno_core::op;
+use deno_core::serde_v8;
+use deno_core::OpState;
+use deno_core::ResourceId;
+use deno_core::StringOrBuffer;
+use deno_core::ZeroCopyBuf;
+use hkdf::Hkdf;
+use num_bigint::BigInt;
+use num_traits::FromPrimitive;
+use rand::distributions::Distribution;
+use rand::distributions::Uniform;
+use rand::thread_rng;
+use rand::Rng;
+use std::future::Future;
+use std::rc::Rc;
+
+use rsa::padding::PaddingScheme;
+use rsa::pkcs8::DecodePrivateKey;
+use rsa::pkcs8::DecodePublicKey;
+use rsa::PublicKey;
+use rsa::RsaPrivateKey;
+use rsa::RsaPublicKey;
+
+mod cipher;
+mod dh;
+mod digest;
+mod primes;
+pub mod x509;
+
+#[op]
+pub fn op_node_check_prime(num: serde_v8::BigInt, checks: usize) -> bool {
+ primes::is_probably_prime(&num, checks)
+}
+
+#[op]
+pub fn op_node_check_prime_bytes(
+ bytes: &[u8],
+ checks: usize,
+) -> Result<bool, AnyError> {
+ let candidate = BigInt::from_bytes_be(num_bigint::Sign::Plus, bytes);
+ Ok(primes::is_probably_prime(&candidate, checks))
+}
+
+#[op]
+pub async fn op_node_check_prime_async(
+ num: serde_v8::BigInt,
+ checks: usize,
+) -> Result<bool, AnyError> {
+ // TODO(@littledivy): use rayon for CPU-bound tasks
+ Ok(
+ tokio::task::spawn_blocking(move || {
+ primes::is_probably_prime(&num, checks)
+ })
+ .await?,
+ )
+}
+
+#[op]
+pub fn op_node_check_prime_bytes_async(
+ bytes: &[u8],
+ checks: usize,
+) -> Result<impl Future<Output = Result<bool, AnyError>> + 'static, AnyError> {
+ let candidate = BigInt::from_bytes_be(num_bigint::Sign::Plus, bytes);
+ // TODO(@littledivy): use rayon for CPU-bound tasks
+ Ok(async move {
+ Ok(
+ tokio::task::spawn_blocking(move || {
+ primes::is_probably_prime(&candidate, checks)
+ })
+ .await?,
+ )
+ })
+}
+
+#[op(fast)]
+pub fn op_node_create_hash(state: &mut OpState, algorithm: &str) -> u32 {
+ state
+ .resource_table
+ .add(match digest::Context::new(algorithm) {
+ Ok(context) => context,
+ Err(_) => return 0,
+ })
+}
+
+#[op(fast)]
+pub fn op_node_hash_update(state: &mut OpState, rid: u32, data: &[u8]) -> bool {
+ let context = match state.resource_table.get::<digest::Context>(rid) {
+ Ok(context) => context,
+ _ => return false,
+ };
+ context.update(data);
+ true
+}
+
+#[op(fast)]
+pub fn op_node_hash_update_str(
+ state: &mut OpState,
+ rid: u32,
+ data: &str,
+) -> bool {
+ let context = match state.resource_table.get::<digest::Context>(rid) {
+ Ok(context) => context,
+ _ => return false,
+ };
+ context.update(data.as_bytes());
+ true
+}
+
+#[op]
+pub fn op_node_hash_digest(
+ state: &mut OpState,
+ rid: ResourceId,
+) -> Result<ZeroCopyBuf, AnyError> {
+ let context = state.resource_table.take::<digest::Context>(rid)?;
+ let context = Rc::try_unwrap(context)
+ .map_err(|_| type_error("Hash context is already in use"))?;
+ Ok(context.digest()?.into())
+}
+
+#[op]
+pub fn op_node_hash_digest_hex(
+ state: &mut OpState,
+ rid: ResourceId,
+) -> Result<String, AnyError> {
+ let context = state.resource_table.take::<digest::Context>(rid)?;
+ let context = Rc::try_unwrap(context)
+ .map_err(|_| type_error("Hash context is already in use"))?;
+ let digest = context.digest()?;
+ Ok(hex::encode(digest))
+}
+
+#[op]
+pub fn op_node_hash_clone(
+ state: &mut OpState,
+ rid: ResourceId,
+) -> Result<ResourceId, AnyError> {
+ let context = state.resource_table.get::<digest::Context>(rid)?;
+ Ok(state.resource_table.add(context.as_ref().clone()))
+}
+
+#[op]
+pub fn op_node_private_encrypt(
+ key: StringOrBuffer,
+ msg: StringOrBuffer,
+ padding: u32,
+) -> Result<ZeroCopyBuf, AnyError> {
+ let key = RsaPrivateKey::from_pkcs8_pem((&key).try_into()?)?;
+
+ let mut rng = rand::thread_rng();
+ match padding {
+ 1 => Ok(
+ key
+ .encrypt(&mut rng, PaddingScheme::new_pkcs1v15_encrypt(), &msg)?
+ .into(),
+ ),
+ 4 => Ok(
+ key
+ .encrypt(&mut rng, PaddingScheme::new_oaep::<sha1::Sha1>(), &msg)?
+ .into(),
+ ),
+ _ => Err(type_error("Unknown padding")),
+ }
+}
+
+#[op]
+pub fn op_node_private_decrypt(
+ key: StringOrBuffer,
+ msg: StringOrBuffer,
+ padding: u32,
+) -> Result<ZeroCopyBuf, AnyError> {
+ let key = RsaPrivateKey::from_pkcs8_pem((&key).try_into()?)?;
+
+ match padding {
+ 1 => Ok(
+ key
+ .decrypt(PaddingScheme::new_pkcs1v15_encrypt(), &msg)?
+ .into(),
+ ),
+ 4 => Ok(
+ key
+ .decrypt(PaddingScheme::new_oaep::<sha1::Sha1>(), &msg)?
+ .into(),
+ ),
+ _ => Err(type_error("Unknown padding")),
+ }
+}
+
+#[op]
+pub fn op_node_public_encrypt(
+ key: StringOrBuffer,
+ msg: StringOrBuffer,
+ padding: u32,
+) -> Result<ZeroCopyBuf, AnyError> {
+ let key = RsaPublicKey::from_public_key_pem((&key).try_into()?)?;
+
+ let mut rng = rand::thread_rng();
+ match padding {
+ 1 => Ok(
+ key
+ .encrypt(&mut rng, PaddingScheme::new_pkcs1v15_encrypt(), &msg)?
+ .into(),
+ ),
+ 4 => Ok(
+ key
+ .encrypt(&mut rng, PaddingScheme::new_oaep::<sha1::Sha1>(), &msg)?
+ .into(),
+ ),
+ _ => Err(type_error("Unknown padding")),
+ }
+}
+
+#[op(fast)]
+pub fn op_node_create_cipheriv(
+ state: &mut OpState,
+ algorithm: &str,
+ key: &[u8],
+ iv: &[u8],
+) -> u32 {
+ state.resource_table.add(
+ match cipher::CipherContext::new(algorithm, key, iv) {
+ Ok(context) => context,
+ Err(_) => return 0,
+ },
+ )
+}
+
+#[op(fast)]
+pub fn op_node_cipheriv_encrypt(
+ state: &mut OpState,
+ rid: u32,
+ input: &[u8],
+ 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
+}
+
+#[op]
+pub fn op_node_cipheriv_final(
+ state: &mut OpState,
+ rid: u32,
+ input: &[u8],
+ output: &mut [u8],
+) -> Result<(), AnyError> {
+ let context = state.resource_table.take::<cipher::CipherContext>(rid)?;
+ let context = Rc::try_unwrap(context)
+ .map_err(|_| type_error("Cipher context is already in use"))?;
+ context.r#final(input, output)
+}
+
+#[op(fast)]
+pub fn op_node_create_decipheriv(
+ state: &mut OpState,
+ algorithm: &str,
+ key: &[u8],
+ iv: &[u8],
+) -> u32 {
+ state.resource_table.add(
+ match cipher::DecipherContext::new(algorithm, key, iv) {
+ Ok(context) => context,
+ Err(_) => return 0,
+ },
+ )
+}
+
+#[op(fast)]
+pub fn op_node_decipheriv_decrypt(
+ state: &mut OpState,
+ rid: u32,
+ input: &[u8],
+ 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
+}
+
+#[op]
+pub fn op_node_decipheriv_final(
+ state: &mut OpState,
+ rid: u32,
+ input: &[u8],
+ output: &mut [u8],
+) -> Result<(), AnyError> {
+ let context = state.resource_table.take::<cipher::DecipherContext>(rid)?;
+ let context = Rc::try_unwrap(context)
+ .map_err(|_| type_error("Cipher context is already in use"))?;
+ context.r#final(input, output)
+}
+
+#[op]
+pub fn op_node_sign(
+ digest: &[u8],
+ digest_type: &str,
+ key: StringOrBuffer,
+ key_type: &str,
+ key_format: &str,
+) -> Result<ZeroCopyBuf, AnyError> {
+ match key_type {
+ "rsa" => {
+ use rsa::pkcs1v15::SigningKey;
+ use signature::hazmat::PrehashSigner;
+ let key = match key_format {
+ "pem" => RsaPrivateKey::from_pkcs8_pem((&key).try_into()?)
+ .map_err(|_| type_error("Invalid RSA private key"))?,
+ // TODO(kt3k): Support der and jwk formats
+ _ => {
+ return Err(type_error(format!(
+ "Unsupported key format: {}",
+ key_format
+ )))
+ }
+ };
+ Ok(
+ match digest_type {
+ "sha224" => {
+ let signing_key = SigningKey::<sha2::Sha224>::new_with_prefix(key);
+ signing_key.sign_prehash(digest)?.to_vec()
+ }
+ "sha256" => {
+ let signing_key = SigningKey::<sha2::Sha256>::new_with_prefix(key);
+ signing_key.sign_prehash(digest)?.to_vec()
+ }
+ "sha384" => {
+ let signing_key = SigningKey::<sha2::Sha384>::new_with_prefix(key);
+ signing_key.sign_prehash(digest)?.to_vec()
+ }
+ "sha512" => {
+ let signing_key = SigningKey::<sha2::Sha512>::new_with_prefix(key);
+ signing_key.sign_prehash(digest)?.to_vec()
+ }
+ _ => {
+ return Err(type_error(format!(
+ "Unknown digest algorithm: {}",
+ digest_type
+ )))
+ }
+ }
+ .into(),
+ )
+ }
+ _ => Err(type_error(format!(
+ "Signing with {} keys is not supported yet",
+ key_type
+ ))),
+ }
+}
+
+#[op]
+fn op_node_verify(
+ digest: &[u8],
+ digest_type: &str,
+ key: StringOrBuffer,
+ key_type: &str,
+ key_format: &str,
+ signature: &[u8],
+) -> Result<bool, AnyError> {
+ match key_type {
+ "rsa" => {
+ use rsa::pkcs1v15::VerifyingKey;
+ use signature::hazmat::PrehashVerifier;
+ let key = match key_format {
+ "pem" => RsaPublicKey::from_public_key_pem((&key).try_into()?)
+ .map_err(|_| type_error("Invalid RSA public key"))?,
+ // TODO(kt3k): Support der and jwk formats
+ _ => {
+ return Err(type_error(format!(
+ "Unsupported key format: {}",
+ key_format
+ )))
+ }
+ };
+ Ok(match digest_type {
+ "sha224" => VerifyingKey::<sha2::Sha224>::new_with_prefix(key)
+ .verify_prehash(digest, &signature.to_vec().try_into()?)
+ .is_ok(),
+ "sha256" => VerifyingKey::<sha2::Sha256>::new_with_prefix(key)
+ .verify_prehash(digest, &signature.to_vec().try_into()?)
+ .is_ok(),
+ "sha384" => VerifyingKey::<sha2::Sha384>::new_with_prefix(key)
+ .verify_prehash(digest, &signature.to_vec().try_into()?)
+ .is_ok(),
+ "sha512" => VerifyingKey::<sha2::Sha512>::new_with_prefix(key)
+ .verify_prehash(digest, &signature.to_vec().try_into()?)
+ .is_ok(),
+ _ => {
+ return Err(type_error(format!(
+ "Unknown digest algorithm: {}",
+ digest_type
+ )))
+ }
+ })
+ }
+ _ => Err(type_error(format!(
+ "Verifying with {} keys is not supported yet",
+ key_type
+ ))),
+ }
+}
+
+fn pbkdf2_sync(
+ password: &[u8],
+ salt: &[u8],
+ iterations: u32,
+ digest: &str,
+ derived_key: &mut [u8],
+) -> Result<(), AnyError> {
+ macro_rules! pbkdf2_hmac {
+ ($digest:ty) => {{
+ pbkdf2::pbkdf2_hmac::<$digest>(password, salt, iterations, derived_key)
+ }};
+ }
+
+ match digest {
+ "md4" => pbkdf2_hmac!(md4::Md4),
+ "md5" => pbkdf2_hmac!(md5::Md5),
+ "ripemd160" => pbkdf2_hmac!(ripemd::Ripemd160),
+ "sha1" => pbkdf2_hmac!(sha1::Sha1),
+ "sha224" => pbkdf2_hmac!(sha2::Sha224),
+ "sha256" => pbkdf2_hmac!(sha2::Sha256),
+ "sha384" => pbkdf2_hmac!(sha2::Sha384),
+ "sha512" => pbkdf2_hmac!(sha2::Sha512),
+ _ => return Err(type_error("Unknown digest")),
+ }
+
+ Ok(())
+}
+
+#[op]
+pub fn op_node_pbkdf2(
+ password: StringOrBuffer,
+ salt: StringOrBuffer,
+ iterations: u32,
+ digest: &str,
+ derived_key: &mut [u8],
+) -> bool {
+ pbkdf2_sync(&password, &salt, iterations, digest, derived_key).is_ok()
+}
+
+#[op]
+pub async fn op_node_pbkdf2_async(
+ password: StringOrBuffer,
+ salt: StringOrBuffer,
+ iterations: u32,
+ digest: String,
+ keylen: usize,
+) -> Result<ZeroCopyBuf, AnyError> {
+ tokio::task::spawn_blocking(move || {
+ let mut derived_key = vec![0; keylen];
+ pbkdf2_sync(&password, &salt, iterations, &digest, &mut derived_key)
+ .map(|_| derived_key.into())
+ })
+ .await?
+}
+
+#[op]
+pub fn op_node_generate_secret(buf: &mut [u8]) {
+ rand::thread_rng().fill(buf);
+}
+
+#[op]
+pub async fn op_node_generate_secret_async(len: i32) -> ZeroCopyBuf {
+ tokio::task::spawn_blocking(move || {
+ let mut buf = vec![0u8; len as usize];
+ rand::thread_rng().fill(&mut buf[..]);
+ buf.into()
+ })
+ .await
+ .unwrap()
+}
+
+fn hkdf_sync(
+ hash: &str,
+ ikm: &[u8],
+ salt: &[u8],
+ info: &[u8],
+ okm: &mut [u8],
+) -> Result<(), AnyError> {
+ macro_rules! hkdf {
+ ($hash:ty) => {{
+ let hk = Hkdf::<$hash>::new(Some(salt), ikm);
+ hk.expand(info, okm)
+ .map_err(|_| type_error("HKDF-Expand failed"))?;
+ }};
+ }
+
+ match hash {
+ "md4" => hkdf!(md4::Md4),
+ "md5" => hkdf!(md5::Md5),
+ "ripemd160" => hkdf!(ripemd::Ripemd160),
+ "sha1" => hkdf!(sha1::Sha1),
+ "sha224" => hkdf!(sha2::Sha224),
+ "sha256" => hkdf!(sha2::Sha256),
+ "sha384" => hkdf!(sha2::Sha384),
+ "sha512" => hkdf!(sha2::Sha512),
+ _ => return Err(type_error("Unknown digest")),
+ }
+
+ Ok(())
+}
+
+#[op]
+pub fn op_node_hkdf(
+ hash: &str,
+ ikm: &[u8],
+ salt: &[u8],
+ info: &[u8],
+ okm: &mut [u8],
+) -> Result<(), AnyError> {
+ hkdf_sync(hash, ikm, salt, info, okm)
+}
+
+#[op]
+pub async fn op_node_hkdf_async(
+ hash: String,
+ ikm: ZeroCopyBuf,
+ salt: ZeroCopyBuf,
+ info: ZeroCopyBuf,
+ okm_len: usize,
+) -> Result<ZeroCopyBuf, AnyError> {
+ tokio::task::spawn_blocking(move || {
+ let mut okm = vec![0u8; okm_len];
+ hkdf_sync(&hash, &ikm, &salt, &info, &mut okm)?;
+ Ok(okm.into())
+ })
+ .await?
+}
+
+use rsa::pkcs1::EncodeRsaPrivateKey;
+use rsa::pkcs1::EncodeRsaPublicKey;
+
+use self::primes::Prime;
+
+fn generate_rsa(
+ modulus_length: usize,
+ public_exponent: usize,
+) -> Result<(ZeroCopyBuf, ZeroCopyBuf), AnyError> {
+ let mut rng = rand::thread_rng();
+ let private_key = RsaPrivateKey::new_with_exp(
+ &mut rng,
+ modulus_length,
+ &rsa::BigUint::from_usize(public_exponent).unwrap(),
+ )?;
+ let public_key = private_key.to_public_key();
+ let private_key_der = private_key.to_pkcs1_der()?.as_bytes().to_vec();
+ let public_key_der = public_key.to_pkcs1_der()?.to_vec();
+
+ Ok((private_key_der.into(), public_key_der.into()))
+}
+
+#[op]
+pub fn op_node_generate_rsa(
+ modulus_length: usize,
+ public_exponent: usize,
+) -> Result<(ZeroCopyBuf, ZeroCopyBuf), AnyError> {
+ generate_rsa(modulus_length, public_exponent)
+}
+
+#[op]
+pub async fn op_node_generate_rsa_async(
+ modulus_length: usize,
+ public_exponent: usize,
+) -> Result<(ZeroCopyBuf, ZeroCopyBuf), AnyError> {
+ tokio::task::spawn_blocking(move || {
+ generate_rsa(modulus_length, public_exponent)
+ })
+ .await?
+}
+
+fn dsa_generate(
+ modulus_length: usize,
+ divisor_length: usize,
+) -> Result<(ZeroCopyBuf, ZeroCopyBuf), AnyError> {
+ let mut rng = rand::thread_rng();
+ use dsa::pkcs8::EncodePrivateKey;
+ use dsa::pkcs8::EncodePublicKey;
+ use dsa::Components;
+ use dsa::KeySize;
+ use dsa::SigningKey;
+
+ let key_size = match (modulus_length, divisor_length) {
+ #[allow(deprecated)]
+ (1024, 160) => KeySize::DSA_1024_160,
+ (2048, 224) => KeySize::DSA_2048_224,
+ (2048, 256) => KeySize::DSA_2048_256,
+ (3072, 256) => KeySize::DSA_3072_256,
+ _ => return Err(type_error("Invalid modulus_length or divisor_length")),
+ };
+ let components = Components::generate(&mut rng, key_size);
+ let signing_key = SigningKey::generate(&mut rng, components);
+ let verifying_key = signing_key.verifying_key();
+
+ Ok((
+ signing_key
+ .to_pkcs8_der()
+ .map_err(|_| type_error("Not valid pkcs8"))?
+ .as_bytes()
+ .to_vec()
+ .into(),
+ verifying_key
+ .to_public_key_der()
+ .map_err(|_| type_error("Not valid spki"))?
+ .to_vec()
+ .into(),
+ ))
+}
+
+#[op]
+pub fn op_node_dsa_generate(
+ modulus_length: usize,
+ divisor_length: usize,
+) -> Result<(ZeroCopyBuf, ZeroCopyBuf), AnyError> {
+ dsa_generate(modulus_length, divisor_length)
+}
+
+#[op]
+pub async fn op_node_dsa_generate_async(
+ modulus_length: usize,
+ divisor_length: usize,
+) -> Result<(ZeroCopyBuf, ZeroCopyBuf), AnyError> {
+ tokio::task::spawn_blocking(move || {
+ dsa_generate(modulus_length, divisor_length)
+ })
+ .await?
+}
+
+fn ec_generate(
+ named_curve: &str,
+) -> Result<(ZeroCopyBuf, ZeroCopyBuf), AnyError> {
+ use ring::signature::EcdsaKeyPair;
+ use ring::signature::KeyPair;
+
+ let curve = match named_curve {
+ "P-256" => &ring::signature::ECDSA_P256_SHA256_FIXED_SIGNING,
+ "P-384" => &ring::signature::ECDSA_P384_SHA384_FIXED_SIGNING,
+ _ => return Err(type_error("Unsupported named curve")),
+ };
+
+ let rng = ring::rand::SystemRandom::new();
+
+ let pkcs8 = EcdsaKeyPair::generate_pkcs8(curve, &rng)
+ .map_err(|_| type_error("Failed to generate EC key"))?;
+
+ let public_key = EcdsaKeyPair::from_pkcs8(curve, pkcs8.as_ref())
+ .map_err(|_| type_error("Failed to generate EC key"))?
+ .public_key()
+ .as_ref()
+ .to_vec();
+ Ok((pkcs8.as_ref().to_vec().into(), public_key.into()))
+}
+
+#[op]
+pub fn op_node_ec_generate(
+ named_curve: &str,
+) -> Result<(ZeroCopyBuf, ZeroCopyBuf), AnyError> {
+ ec_generate(named_curve)
+}
+
+#[op]
+pub async fn op_node_ec_generate_async(
+ named_curve: String,
+) -> Result<(ZeroCopyBuf, ZeroCopyBuf), AnyError> {
+ tokio::task::spawn_blocking(move || ec_generate(&named_curve)).await?
+}
+
+fn ed25519_generate() -> Result<(ZeroCopyBuf, ZeroCopyBuf), AnyError> {
+ use ring::signature::Ed25519KeyPair;
+ use ring::signature::KeyPair;
+
+ let mut rng = thread_rng();
+ let mut seed = vec![0u8; 32];
+ rng.fill(seed.as_mut_slice());
+
+ let pair = Ed25519KeyPair::from_seed_unchecked(&seed)
+ .map_err(|_| type_error("Failed to generate Ed25519 key"))?;
+
+ let public_key = pair.public_key().as_ref().to_vec();
+ Ok((seed.into(), public_key.into()))
+}
+
+#[op]
+pub fn op_node_ed25519_generate() -> Result<(ZeroCopyBuf, ZeroCopyBuf), AnyError>
+{
+ ed25519_generate()
+}
+
+#[op]
+pub async fn op_node_ed25519_generate_async(
+) -> Result<(ZeroCopyBuf, ZeroCopyBuf), AnyError> {
+ tokio::task::spawn_blocking(ed25519_generate).await?
+}
+
+fn x25519_generate() -> Result<(ZeroCopyBuf, ZeroCopyBuf), AnyError> {
+ // u-coordinate of the base point.
+ const X25519_BASEPOINT_BYTES: [u8; 32] = [
+ 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0,
+ ];
+
+ let mut pkey = [0; 32];
+
+ let mut rng = thread_rng();
+ rng.fill(pkey.as_mut_slice());
+
+ let pkey_copy = pkey.to_vec();
+ // https://www.rfc-editor.org/rfc/rfc7748#section-6.1
+ // pubkey = x25519(a, 9) which is constant-time Montgomery ladder.
+ // https://eprint.iacr.org/2014/140.pdf page 4
+ // https://eprint.iacr.org/2017/212.pdf algorithm 8
+ // pubkey is in LE order.
+ let pubkey = x25519_dalek::x25519(pkey, X25519_BASEPOINT_BYTES);
+
+ Ok((pkey_copy.into(), pubkey.to_vec().into()))
+}
+
+#[op]
+pub fn op_node_x25519_generate() -> Result<(ZeroCopyBuf, ZeroCopyBuf), AnyError>
+{
+ x25519_generate()
+}
+
+#[op]
+pub async fn op_node_x25519_generate_async(
+) -> Result<(ZeroCopyBuf, ZeroCopyBuf), AnyError> {
+ tokio::task::spawn_blocking(x25519_generate).await?
+}
+
+fn dh_generate_group(
+ group_name: &str,
+) -> Result<(ZeroCopyBuf, ZeroCopyBuf), AnyError> {
+ let dh = match group_name {
+ "modp5" => dh::DiffieHellman::group::<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>(),
+ _ => return Err(type_error("Unsupported group name")),
+ };
+
+ Ok((
+ dh.private_key.into_vec().into(),
+ dh.public_key.into_vec().into(),
+ ))
+}
+
+#[op]
+pub fn op_node_dh_generate_group(
+ group_name: &str,
+) -> Result<(ZeroCopyBuf, ZeroCopyBuf), AnyError> {
+ dh_generate_group(group_name)
+}
+
+#[op]
+pub async fn op_node_dh_generate_group_async(
+ group_name: String,
+) -> Result<(ZeroCopyBuf, ZeroCopyBuf), AnyError> {
+ tokio::task::spawn_blocking(move || dh_generate_group(&group_name)).await?
+}
+
+fn dh_generate(
+ prime: Option<&[u8]>,
+ prime_len: usize,
+ generator: usize,
+) -> Result<(ZeroCopyBuf, ZeroCopyBuf), AnyError> {
+ let prime = prime
+ .map(|p| p.into())
+ .unwrap_or_else(|| Prime::generate(prime_len));
+ let dh = dh::DiffieHellman::new(prime, generator);
+
+ Ok((
+ dh.private_key.into_vec().into(),
+ dh.public_key.into_vec().into(),
+ ))
+}
+
+#[op]
+pub fn op_node_dh_generate(
+ prime: Option<&[u8]>,
+ prime_len: usize,
+ generator: usize,
+) -> Result<(ZeroCopyBuf, ZeroCopyBuf), AnyError> {
+ dh_generate(prime, prime_len, generator)
+}
+
+#[op]
+pub async fn op_node_dh_generate_async(
+ prime: Option<ZeroCopyBuf>,
+ prime_len: usize,
+ generator: usize,
+) -> Result<(ZeroCopyBuf, ZeroCopyBuf), AnyError> {
+ tokio::task::spawn_blocking(move || {
+ dh_generate(prime.as_deref(), prime_len, generator)
+ })
+ .await?
+}
+
+#[op]
+pub fn op_node_random_int(min: i32, max: i32) -> Result<i32, AnyError> {
+ 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);
+
+ Ok(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<(), AnyError> {
+ // Construct Params
+ let params = scrypt::Params::new(
+ cost as u8,
+ block_size,
+ parallelization,
+ keylen as usize,
+ )
+ .unwrap();
+
+ // Call into scrypt
+ let res = scrypt::scrypt(&password, &salt, &params, output_buffer);
+ if res.is_ok() {
+ Ok(())
+ } else {
+ // TODO(lev): key derivation failed, so what?
+ Err(generic_error("scrypt key derivation failed"))
+ }
+}
+
+#[op]
+pub fn op_node_scrypt_sync(
+ password: StringOrBuffer,
+ salt: StringOrBuffer,
+ keylen: u32,
+ cost: u32,
+ block_size: u32,
+ parallelization: u32,
+ maxmem: u32,
+ output_buffer: &mut [u8],
+) -> Result<(), AnyError> {
+ scrypt(
+ password,
+ salt,
+ keylen,
+ cost,
+ block_size,
+ parallelization,
+ maxmem,
+ output_buffer,
+ )
+}
+
+#[op]
+pub async fn op_node_scrypt_async(
+ password: StringOrBuffer,
+ salt: StringOrBuffer,
+ keylen: u32,
+ cost: u32,
+ block_size: u32,
+ parallelization: u32,
+ maxmem: u32,
+) -> Result<ZeroCopyBuf, AnyError> {
+ tokio::task::spawn_blocking(move || {
+ let mut output_buffer = vec![0u8; keylen as usize];
+ let res = scrypt(
+ password,
+ salt,
+ keylen,
+ cost,
+ block_size,
+ parallelization,
+ maxmem,
+ &mut output_buffer,
+ );
+
+ if res.is_ok() {
+ Ok(output_buffer.into())
+ } else {
+ // TODO(lev): rethrow the error?
+ Err(generic_error("scrypt failure"))
+ }
+ })
+ .await?
+}