diff --git a/Cargo.lock b/Cargo.lock index 8932ca2fed..8c58bfe22d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -780,6 +780,7 @@ dependencies = [ "ring", "rsa", "serde", + "serde_bytes", "sha-1", "sha2", "spki", diff --git a/ext/crypto/00_crypto.js b/ext/crypto/00_crypto.js index 8180613878..258363586f 100644 --- a/ext/crypto/00_crypto.js +++ b/ext/crypto/00_crypto.js @@ -1468,7 +1468,7 @@ const keyData = await core.opAsync( "op_crypto_generate_key", { - name: algorithmName, + algorithm: "RSA", modulusLength: normalizedAlgorithm.modulusLength, publicExponent: normalizedAlgorithm.publicExponent, }, @@ -1528,7 +1528,7 @@ const keyData = await core.opAsync( "op_crypto_generate_key", { - name: algorithmName, + algorithm: "RSA", modulusLength: normalizedAlgorithm.modulusLength, publicExponent: normalizedAlgorithm.publicExponent, }, @@ -1590,7 +1590,7 @@ ) ) { const keyData = await core.opAsync("op_crypto_generate_key", { - name: algorithmName, + algorithm: "EC", namedCurve, }); WeakMapPrototypeSet(KEY_STORE, handle, { @@ -1650,7 +1650,7 @@ ) ) { const keyData = await core.opAsync("op_crypto_generate_key", { - name: algorithmName, + algorithm: "EC", namedCurve, }); WeakMapPrototypeSet(KEY_STORE, handle, { @@ -1745,7 +1745,7 @@ // 3-4. const keyData = await core.opAsync("op_crypto_generate_key", { - name: algorithmName, + algorithm: "HMAC", hash: normalizedAlgorithm.hash.name, length, }); @@ -2586,7 +2586,7 @@ // 3. const keyData = await core.opAsync("op_crypto_generate_key", { - name: algorithmName, + algorithm: "AES", length: normalizedAlgorithm.length, }); const handle = {}; diff --git a/ext/crypto/Cargo.toml b/ext/crypto/Cargo.toml index daa809422f..d5c2a1acbb 100644 --- a/ext/crypto/Cargo.toml +++ b/ext/crypto/Cargo.toml @@ -28,6 +28,7 @@ rand = "0.8.4" ring = { version = "0.16.20", features = ["std"] } rsa = { version = "0.5.0", default-features = false, features = ["std"] } serde = { version = "1.0.129", features = ["derive"] } +serde_bytes = "0.11" sha-1 = "0.9.7" sha2 = "0.9.5" spki = "0.4.1" diff --git a/ext/crypto/generate_key.rs b/ext/crypto/generate_key.rs new file mode 100644 index 0000000000..3f9df44a13 --- /dev/null +++ b/ext/crypto/generate_key.rs @@ -0,0 +1,147 @@ +use std::cell::RefCell; +use std::rc::Rc; + +use deno_core::error::AnyError; +use deno_core::OpState; +use deno_core::ZeroCopyBuf; +use elliptic_curve::rand_core::OsRng; +use num_traits::FromPrimitive; +use ring::rand::SecureRandom; +use ring::signature::EcdsaKeyPair; +use rsa::pkcs1::ToRsaPrivateKey; +use rsa::BigUint; +use rsa::RsaPrivateKey; +use serde::Deserialize; + +use crate::shared::*; + +// Allowlist for RSA public exponents. +lazy_static::lazy_static! { + static ref PUB_EXPONENT_1: BigUint = BigUint::from_u64(3).unwrap(); + static ref PUB_EXPONENT_2: BigUint = BigUint::from_u64(65537).unwrap(); +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase", tag = "algorithm")] +pub enum GenerateKeyOptions { + #[serde(rename = "RSA", rename_all = "camelCase")] + Rsa { + modulus_length: u32, + #[serde(with = "serde_bytes")] + public_exponent: Vec, + }, + #[serde(rename = "EC", rename_all = "camelCase")] + Ec { named_curve: EcNamedCurve }, + #[serde(rename = "AES", rename_all = "camelCase")] + Aes { length: usize }, + #[serde(rename = "HMAC", rename_all = "camelCase")] + Hmac { + hash: ShaHash, + length: Option, + }, +} + +pub async fn op_crypto_generate_key( + _state: Rc>, + opts: GenerateKeyOptions, + _: (), +) -> Result { + let fun = || match opts { + GenerateKeyOptions::Rsa { + modulus_length, + public_exponent, + } => generate_key_rsa(modulus_length, &public_exponent), + GenerateKeyOptions::Ec { named_curve } => generate_key_ec(named_curve), + GenerateKeyOptions::Aes { length } => generate_key_aes(length), + GenerateKeyOptions::Hmac { hash, length } => { + generate_key_hmac(hash, length) + } + }; + let buf = tokio::task::spawn_blocking(fun).await.unwrap()?; + Ok(buf.into()) +} + +fn generate_key_rsa( + modulus_length: u32, + public_exponent: &[u8], +) -> Result, AnyError> { + let exponent = BigUint::from_bytes_be(public_exponent); + if exponent != *PUB_EXPONENT_1 && exponent != *PUB_EXPONENT_2 { + return Err(operation_error("Bad public exponent")); + } + + let mut rng = OsRng; + + let private_key = + RsaPrivateKey::new_with_exp(&mut rng, modulus_length as usize, &exponent) + .map_err(|_| operation_error("Failed to generate RSA key"))?; + + let private_key = private_key + .to_pkcs1_der() + .map_err(|_| operation_error("Failed to serialize RSA key"))?; + + Ok(private_key.as_ref().to_vec()) +} + +fn generate_key_ec(named_curve: EcNamedCurve) -> Result, AnyError> { + let curve = match named_curve { + EcNamedCurve::P256 => &ring::signature::ECDSA_P256_SHA256_FIXED_SIGNING, + EcNamedCurve::P384 => &ring::signature::ECDSA_P384_SHA384_FIXED_SIGNING, + }; + + let rng = ring::rand::SystemRandom::new(); + + let pkcs8 = EcdsaKeyPair::generate_pkcs8(curve, &rng) + .map_err(|_| operation_error("Failed to generate EC key"))?; + + Ok(pkcs8.as_ref().to_vec()) +} + +fn generate_key_aes(length: usize) -> Result, AnyError> { + if length % 8 != 0 || length > 256 { + return Err(operation_error("Invalid AES key length")); + } + + let mut key = vec![0u8; length / 8]; + let rng = ring::rand::SystemRandom::new(); + rng + .fill(&mut key) + .map_err(|_| operation_error("Failed to generate key"))?; + + Ok(key) +} + +fn generate_key_hmac( + hash: ShaHash, + length: Option, +) -> Result, AnyError> { + let hash = match hash { + ShaHash::Sha1 => &ring::hmac::HMAC_SHA1_FOR_LEGACY_USE_ONLY, + ShaHash::Sha256 => &ring::hmac::HMAC_SHA256, + ShaHash::Sha384 => &ring::hmac::HMAC_SHA384, + ShaHash::Sha512 => &ring::hmac::HMAC_SHA512, + }; + + let length = if let Some(length) = length { + if length % 8 != 0 { + return Err(operation_error("Invalid HMAC key length")); + } + + let length = length / 8; + if length > ring::digest::MAX_BLOCK_LEN { + return Err(operation_error("Invalid HMAC key length")); + } + + length + } else { + hash.digest_algorithm().block_len + }; + + let rng = ring::rand::SystemRandom::new(); + let mut key = vec![0u8; length]; + rng + .fill(&mut key) + .map_err(|_| operation_error("Failed to generate key"))?; + + Ok(key) +} diff --git a/ext/crypto/lib.rs b/ext/crypto/lib.rs index 971b32bbba..a15f2d986c 100644 --- a/ext/crypto/lib.rs +++ b/ext/crypto/lib.rs @@ -10,7 +10,6 @@ use deno_core::op_sync; use deno_core::Extension; use deno_core::OpState; use deno_core::ZeroCopyBuf; -use export_key::op_crypto_export_key; use serde::Deserialize; use std::cell::RefCell; @@ -31,7 +30,6 @@ use ring::hmac::Algorithm as HmacAlgorithm; use ring::hmac::Key as HmacKey; use ring::pbkdf2; use ring::rand as RingRand; -use ring::rand::SecureRandom; use ring::signature::EcdsaKeyPair; use ring::signature::EcdsaSigningAlgorithm; use ring::signature::EcdsaVerificationAlgorithm; @@ -41,7 +39,6 @@ use rsa::pkcs1::der::Decodable; use rsa::pkcs1::der::Encodable; use rsa::pkcs1::FromRsaPrivateKey; use rsa::pkcs1::FromRsaPublicKey; -use rsa::pkcs1::ToRsaPrivateKey; use rsa::pkcs8::der::asn1; use rsa::pkcs8::FromPrivateKey; use rsa::BigUint; @@ -58,16 +55,19 @@ use std::path::PathBuf; pub use rand; // Re-export rand mod export_key; +mod generate_key; mod import_key; mod key; mod shared; +pub use crate::export_key::op_crypto_export_key; +pub use crate::generate_key::op_crypto_generate_key; +pub use crate::import_key::op_crypto_import_key; use crate::key::Algorithm; use crate::key::CryptoHash; use crate::key::CryptoNamedCurve; use crate::key::HkdfOutput; -pub use crate::import_key::op_crypto_import_key; use crate::shared::ID_MFG1; use crate::shared::ID_P_SPECIFIED; use crate::shared::ID_SHA1_OID; @@ -133,122 +133,6 @@ pub fn op_crypto_get_random_values( Ok(()) } -#[derive(Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct AlgorithmArg { - name: Algorithm, - modulus_length: Option, - public_exponent: Option, - named_curve: Option, - hash: Option, - length: Option, -} - -pub async fn op_crypto_generate_key( - _state: Rc>, - args: AlgorithmArg, - _: (), -) -> Result { - let algorithm = args.name; - - let key = match algorithm { - Algorithm::RsassaPkcs1v15 | Algorithm::RsaPss | Algorithm::RsaOaep => { - let public_exponent = args.public_exponent.ok_or_else(not_supported)?; - let modulus_length = args.modulus_length.ok_or_else(not_supported)?; - - let exponent = BigUint::from_bytes_be(&public_exponent); - if exponent != *PUB_EXPONENT_1 && exponent != *PUB_EXPONENT_2 { - return Err(custom_error( - "DOMExceptionOperationError", - "Bad public exponent", - )); - } - - let mut rng = OsRng; - - let private_key: RsaPrivateKey = tokio::task::spawn_blocking( - move || -> Result { - RsaPrivateKey::new_with_exp( - &mut rng, - modulus_length as usize, - &exponent, - ) - }, - ) - .await - .unwrap() - .map_err(|e| custom_error("DOMExceptionOperationError", e.to_string()))?; - - private_key.to_pkcs1_der()?.as_ref().to_vec() - } - Algorithm::Ecdsa | Algorithm::Ecdh => { - let curve: &EcdsaSigningAlgorithm = - args.named_curve.ok_or_else(not_supported)?.into(); - let rng = RingRand::SystemRandom::new(); - let private_key: Vec = tokio::task::spawn_blocking( - move || -> Result, ring::error::Unspecified> { - let pkcs8 = EcdsaKeyPair::generate_pkcs8(curve, &rng)?; - Ok(pkcs8.as_ref().to_vec()) - }, - ) - .await - .unwrap() - .map_err(|_| { - custom_error("DOMExceptionOperationError", "Key generation failed") - })?; - - private_key - } - Algorithm::AesCtr - | Algorithm::AesCbc - | Algorithm::AesGcm - | Algorithm::AesKw => { - let length = args.length.ok_or_else(not_supported)?; - // Caller must guarantee divisibility by 8 - let mut key_data = vec![0u8; length / 8]; - let rng = RingRand::SystemRandom::new(); - rng.fill(&mut key_data).map_err(|_| { - custom_error("DOMExceptionOperationError", "Key generation failed") - })?; - key_data - } - Algorithm::Hmac => { - let hash: HmacAlgorithm = args.hash.ok_or_else(not_supported)?.into(); - - let length = if let Some(length) = args.length { - if (length % 8) != 0 { - return Err(custom_error( - "DOMExceptionOperationError", - "hmac block length must be byte aligned", - )); - } - let length = length / 8; - if length > ring::digest::MAX_BLOCK_LEN { - return Err(custom_error( - "DOMExceptionOperationError", - "hmac block length is too large", - )); - } - length - } else { - hash.digest_algorithm().block_len - }; - - let rng = RingRand::SystemRandom::new(); - let mut key_bytes = [0; ring::digest::MAX_BLOCK_LEN]; - let key_bytes = &mut key_bytes[..length]; - rng.fill(key_bytes).map_err(|_| { - custom_error("DOMExceptionOperationError", "Key generation failed") - })?; - - key_bytes.to_vec() - } - _ => return Err(not_supported()), - }; - - Ok(key.into()) -} - #[derive(Deserialize)] #[serde(rename_all = "lowercase")] pub enum KeyFormat { diff --git a/ext/crypto/shared.rs b/ext/crypto/shared.rs index 1af0169efd..0b70db24ea 100644 --- a/ext/crypto/shared.rs +++ b/ext/crypto/shared.rs @@ -100,6 +100,10 @@ pub fn not_supported_error(msg: impl Into>) -> AnyError { custom_error("DOMExceptionNotSupportedError", msg) } +pub fn operation_error(msg: impl Into>) -> AnyError { + custom_error("DOMExceptionOperationError", msg) +} + pub fn unsupported_format() -> AnyError { not_supported_error("unsupported format") }