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

refactor(ext/crypto): generateKey rust cleanup (#13069)

This commit is contained in:
Luca Casonato 2021-12-13 18:45:08 +01:00 committed by GitHub
parent 308813ae29
commit 8fdade79da
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 163 additions and 126 deletions

1
Cargo.lock generated
View file

@ -780,6 +780,7 @@ dependencies = [
"ring", "ring",
"rsa", "rsa",
"serde", "serde",
"serde_bytes",
"sha-1", "sha-1",
"sha2", "sha2",
"spki", "spki",

View file

@ -1468,7 +1468,7 @@
const keyData = await core.opAsync( const keyData = await core.opAsync(
"op_crypto_generate_key", "op_crypto_generate_key",
{ {
name: algorithmName, algorithm: "RSA",
modulusLength: normalizedAlgorithm.modulusLength, modulusLength: normalizedAlgorithm.modulusLength,
publicExponent: normalizedAlgorithm.publicExponent, publicExponent: normalizedAlgorithm.publicExponent,
}, },
@ -1528,7 +1528,7 @@
const keyData = await core.opAsync( const keyData = await core.opAsync(
"op_crypto_generate_key", "op_crypto_generate_key",
{ {
name: algorithmName, algorithm: "RSA",
modulusLength: normalizedAlgorithm.modulusLength, modulusLength: normalizedAlgorithm.modulusLength,
publicExponent: normalizedAlgorithm.publicExponent, publicExponent: normalizedAlgorithm.publicExponent,
}, },
@ -1590,7 +1590,7 @@
) )
) { ) {
const keyData = await core.opAsync("op_crypto_generate_key", { const keyData = await core.opAsync("op_crypto_generate_key", {
name: algorithmName, algorithm: "EC",
namedCurve, namedCurve,
}); });
WeakMapPrototypeSet(KEY_STORE, handle, { WeakMapPrototypeSet(KEY_STORE, handle, {
@ -1650,7 +1650,7 @@
) )
) { ) {
const keyData = await core.opAsync("op_crypto_generate_key", { const keyData = await core.opAsync("op_crypto_generate_key", {
name: algorithmName, algorithm: "EC",
namedCurve, namedCurve,
}); });
WeakMapPrototypeSet(KEY_STORE, handle, { WeakMapPrototypeSet(KEY_STORE, handle, {
@ -1745,7 +1745,7 @@
// 3-4. // 3-4.
const keyData = await core.opAsync("op_crypto_generate_key", { const keyData = await core.opAsync("op_crypto_generate_key", {
name: algorithmName, algorithm: "HMAC",
hash: normalizedAlgorithm.hash.name, hash: normalizedAlgorithm.hash.name,
length, length,
}); });
@ -2586,7 +2586,7 @@
// 3. // 3.
const keyData = await core.opAsync("op_crypto_generate_key", { const keyData = await core.opAsync("op_crypto_generate_key", {
name: algorithmName, algorithm: "AES",
length: normalizedAlgorithm.length, length: normalizedAlgorithm.length,
}); });
const handle = {}; const handle = {};

View file

@ -28,6 +28,7 @@ rand = "0.8.4"
ring = { version = "0.16.20", features = ["std"] } ring = { version = "0.16.20", features = ["std"] }
rsa = { version = "0.5.0", default-features = false, features = ["std"] } rsa = { version = "0.5.0", default-features = false, features = ["std"] }
serde = { version = "1.0.129", features = ["derive"] } serde = { version = "1.0.129", features = ["derive"] }
serde_bytes = "0.11"
sha-1 = "0.9.7" sha-1 = "0.9.7"
sha2 = "0.9.5" sha2 = "0.9.5"
spki = "0.4.1" spki = "0.4.1"

147
ext/crypto/generate_key.rs Normal file
View file

@ -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<u8>,
},
#[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<usize>,
},
}
pub async fn op_crypto_generate_key(
_state: Rc<RefCell<OpState>>,
opts: GenerateKeyOptions,
_: (),
) -> Result<ZeroCopyBuf, AnyError> {
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<Vec<u8>, 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<Vec<u8>, 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<Vec<u8>, 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<usize>,
) -> Result<Vec<u8>, 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)
}

View file

@ -10,7 +10,6 @@ use deno_core::op_sync;
use deno_core::Extension; use deno_core::Extension;
use deno_core::OpState; use deno_core::OpState;
use deno_core::ZeroCopyBuf; use deno_core::ZeroCopyBuf;
use export_key::op_crypto_export_key;
use serde::Deserialize; use serde::Deserialize;
use std::cell::RefCell; use std::cell::RefCell;
@ -31,7 +30,6 @@ use ring::hmac::Algorithm as HmacAlgorithm;
use ring::hmac::Key as HmacKey; use ring::hmac::Key as HmacKey;
use ring::pbkdf2; use ring::pbkdf2;
use ring::rand as RingRand; use ring::rand as RingRand;
use ring::rand::SecureRandom;
use ring::signature::EcdsaKeyPair; use ring::signature::EcdsaKeyPair;
use ring::signature::EcdsaSigningAlgorithm; use ring::signature::EcdsaSigningAlgorithm;
use ring::signature::EcdsaVerificationAlgorithm; use ring::signature::EcdsaVerificationAlgorithm;
@ -41,7 +39,6 @@ use rsa::pkcs1::der::Decodable;
use rsa::pkcs1::der::Encodable; use rsa::pkcs1::der::Encodable;
use rsa::pkcs1::FromRsaPrivateKey; use rsa::pkcs1::FromRsaPrivateKey;
use rsa::pkcs1::FromRsaPublicKey; use rsa::pkcs1::FromRsaPublicKey;
use rsa::pkcs1::ToRsaPrivateKey;
use rsa::pkcs8::der::asn1; use rsa::pkcs8::der::asn1;
use rsa::pkcs8::FromPrivateKey; use rsa::pkcs8::FromPrivateKey;
use rsa::BigUint; use rsa::BigUint;
@ -58,16 +55,19 @@ use std::path::PathBuf;
pub use rand; // Re-export rand pub use rand; // Re-export rand
mod export_key; mod export_key;
mod generate_key;
mod import_key; mod import_key;
mod key; mod key;
mod shared; 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::Algorithm;
use crate::key::CryptoHash; use crate::key::CryptoHash;
use crate::key::CryptoNamedCurve; use crate::key::CryptoNamedCurve;
use crate::key::HkdfOutput; use crate::key::HkdfOutput;
pub use crate::import_key::op_crypto_import_key;
use crate::shared::ID_MFG1; use crate::shared::ID_MFG1;
use crate::shared::ID_P_SPECIFIED; use crate::shared::ID_P_SPECIFIED;
use crate::shared::ID_SHA1_OID; use crate::shared::ID_SHA1_OID;
@ -133,122 +133,6 @@ pub fn op_crypto_get_random_values(
Ok(()) Ok(())
} }
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AlgorithmArg {
name: Algorithm,
modulus_length: Option<u32>,
public_exponent: Option<ZeroCopyBuf>,
named_curve: Option<CryptoNamedCurve>,
hash: Option<CryptoHash>,
length: Option<usize>,
}
pub async fn op_crypto_generate_key(
_state: Rc<RefCell<OpState>>,
args: AlgorithmArg,
_: (),
) -> Result<ZeroCopyBuf, AnyError> {
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, rsa::errors::Error> {
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<u8> = tokio::task::spawn_blocking(
move || -> Result<Vec<u8>, 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)] #[derive(Deserialize)]
#[serde(rename_all = "lowercase")] #[serde(rename_all = "lowercase")]
pub enum KeyFormat { pub enum KeyFormat {

View file

@ -100,6 +100,10 @@ pub fn not_supported_error(msg: impl Into<Cow<'static, str>>) -> AnyError {
custom_error("DOMExceptionNotSupportedError", msg) custom_error("DOMExceptionNotSupportedError", msg)
} }
pub fn operation_error(msg: impl Into<Cow<'static, str>>) -> AnyError {
custom_error("DOMExceptionOperationError", msg)
}
pub fn unsupported_format() -> AnyError { pub fn unsupported_format() -> AnyError {
not_supported_error("unsupported format") not_supported_error("unsupported format")
} }