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:
parent
308813ae29
commit
8fdade79da
6 changed files with 163 additions and 126 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -780,6 +780,7 @@ dependencies = [
|
||||||
"ring",
|
"ring",
|
||||||
"rsa",
|
"rsa",
|
||||||
"serde",
|
"serde",
|
||||||
|
"serde_bytes",
|
||||||
"sha-1",
|
"sha-1",
|
||||||
"sha2",
|
"sha2",
|
||||||
"spki",
|
"spki",
|
||||||
|
|
|
@ -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 = {};
|
||||||
|
|
|
@ -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
147
ext/crypto/generate_key.rs
Normal 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)
|
||||||
|
}
|
|
@ -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 {
|
||||||
|
|
|
@ -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")
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue