mirror of
https://github.com/denoland/deno.git
synced 2024-12-25 00:29:09 -05:00
419 lines
12 KiB
Rust
419 lines
12 KiB
Rust
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
|
|
|
|
use deno_core::error::custom_error;
|
|
use deno_core::error::not_supported;
|
|
use deno_core::error::null_opbuf;
|
|
use deno_core::error::type_error;
|
|
use deno_core::error::AnyError;
|
|
use deno_core::include_js_files;
|
|
use deno_core::op_async;
|
|
use deno_core::op_sync;
|
|
use deno_core::Extension;
|
|
use deno_core::OpState;
|
|
use deno_core::ZeroCopyBuf;
|
|
use serde::Deserialize;
|
|
|
|
use std::cell::RefCell;
|
|
use std::convert::TryInto;
|
|
use std::rc::Rc;
|
|
|
|
use lazy_static::lazy_static;
|
|
use num_traits::cast::FromPrimitive;
|
|
use rand::rngs::OsRng;
|
|
use rand::rngs::StdRng;
|
|
use rand::thread_rng;
|
|
use rand::Rng;
|
|
use rand::SeedableRng;
|
|
use ring::digest;
|
|
use ring::hmac::Algorithm as HmacAlgorithm;
|
|
use ring::hmac::Key as HmacKey;
|
|
use ring::rand as RingRand;
|
|
use ring::rand::SecureRandom;
|
|
use ring::signature::EcdsaKeyPair;
|
|
use ring::signature::EcdsaSigningAlgorithm;
|
|
use rsa::padding::PaddingScheme;
|
|
use rsa::BigUint;
|
|
use rsa::PrivateKeyEncoding;
|
|
use rsa::RSAPrivateKey;
|
|
use sha1::Sha1;
|
|
use sha2::Digest;
|
|
use sha2::Sha256;
|
|
use sha2::Sha384;
|
|
use sha2::Sha512;
|
|
use std::path::PathBuf;
|
|
|
|
pub use rand; // Re-export rand
|
|
|
|
mod key;
|
|
|
|
use crate::key::Algorithm;
|
|
use crate::key::CryptoHash;
|
|
use crate::key::CryptoNamedCurve;
|
|
|
|
// Allowlist for RSA public exponents.
|
|
lazy_static! {
|
|
static ref PUB_EXPONENT_1: BigUint = BigUint::from_u64(3).unwrap();
|
|
static ref PUB_EXPONENT_2: BigUint = BigUint::from_u64(65537).unwrap();
|
|
}
|
|
|
|
pub fn init(maybe_seed: Option<u64>) -> Extension {
|
|
Extension::builder()
|
|
.js(include_js_files!(
|
|
prefix "deno:extensions/crypto",
|
|
"00_crypto.js",
|
|
"01_webidl.js",
|
|
))
|
|
.ops(vec![
|
|
(
|
|
"op_crypto_get_random_values",
|
|
op_sync(op_crypto_get_random_values),
|
|
),
|
|
("op_crypto_generate_key", op_async(op_crypto_generate_key)),
|
|
("op_crypto_sign_key", op_async(op_crypto_sign_key)),
|
|
("op_crypto_subtle_digest", op_async(op_crypto_subtle_digest)),
|
|
("op_crypto_random_uuid", op_sync(op_crypto_random_uuid)),
|
|
])
|
|
.state(move |state| {
|
|
if let Some(seed) = maybe_seed {
|
|
state.put(StdRng::seed_from_u64(seed));
|
|
}
|
|
Ok(())
|
|
})
|
|
.build()
|
|
}
|
|
|
|
pub fn op_crypto_get_random_values(
|
|
state: &mut OpState,
|
|
mut zero_copy: ZeroCopyBuf,
|
|
_: (),
|
|
) -> Result<(), AnyError> {
|
|
if zero_copy.len() > 65536 {
|
|
return Err(
|
|
deno_web::DomExceptionQuotaExceededError::new(&format!("The ArrayBufferView's byte length ({}) exceeds the number of bytes of entropy available via this API (65536)", zero_copy.len()))
|
|
.into(),
|
|
);
|
|
}
|
|
|
|
let maybe_seeded_rng = state.try_borrow_mut::<StdRng>();
|
|
if let Some(seeded_rng) = maybe_seeded_rng {
|
|
seeded_rng.fill(&mut *zero_copy);
|
|
} else {
|
|
let mut rng = thread_rng();
|
|
rng.fill(&mut *zero_copy);
|
|
}
|
|
|
|
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 => {
|
|
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_pkcs8()?
|
|
}
|
|
Algorithm::Ecdsa => {
|
|
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::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 {
|
|
Raw,
|
|
Pkcs8,
|
|
}
|
|
|
|
#[derive(Deserialize)]
|
|
#[serde(rename_all = "lowercase")]
|
|
pub struct KeyData {
|
|
// TODO(littledivy): Kept here to be used to importKey() in future.
|
|
#[allow(dead_code)]
|
|
r#type: KeyFormat,
|
|
data: ZeroCopyBuf,
|
|
}
|
|
|
|
#[derive(Deserialize)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct SignArg {
|
|
key: KeyData,
|
|
algorithm: Algorithm,
|
|
salt_length: Option<u32>,
|
|
hash: Option<CryptoHash>,
|
|
named_curve: Option<CryptoNamedCurve>,
|
|
}
|
|
|
|
pub async fn op_crypto_sign_key(
|
|
_state: Rc<RefCell<OpState>>,
|
|
args: SignArg,
|
|
zero_copy: Option<ZeroCopyBuf>,
|
|
) -> Result<ZeroCopyBuf, AnyError> {
|
|
let zero_copy = zero_copy.ok_or_else(null_opbuf)?;
|
|
let data = &*zero_copy;
|
|
let algorithm = args.algorithm;
|
|
|
|
let signature = match algorithm {
|
|
Algorithm::RsassaPkcs1v15 => {
|
|
let private_key = RSAPrivateKey::from_pkcs8(&*args.key.data)?;
|
|
let (padding, hashed) = match args
|
|
.hash
|
|
.ok_or_else(|| type_error("Missing argument hash".to_string()))?
|
|
{
|
|
CryptoHash::Sha1 => {
|
|
let mut hasher = Sha1::new();
|
|
hasher.update(&data);
|
|
(
|
|
PaddingScheme::PKCS1v15Sign {
|
|
hash: Some(rsa::hash::Hash::SHA1),
|
|
},
|
|
hasher.finalize()[..].to_vec(),
|
|
)
|
|
}
|
|
CryptoHash::Sha256 => {
|
|
let mut hasher = Sha256::new();
|
|
hasher.update(&data);
|
|
(
|
|
PaddingScheme::PKCS1v15Sign {
|
|
hash: Some(rsa::hash::Hash::SHA2_256),
|
|
},
|
|
hasher.finalize()[..].to_vec(),
|
|
)
|
|
}
|
|
CryptoHash::Sha384 => {
|
|
let mut hasher = Sha384::new();
|
|
hasher.update(&data);
|
|
(
|
|
PaddingScheme::PKCS1v15Sign {
|
|
hash: Some(rsa::hash::Hash::SHA2_384),
|
|
},
|
|
hasher.finalize()[..].to_vec(),
|
|
)
|
|
}
|
|
CryptoHash::Sha512 => {
|
|
let mut hasher = Sha512::new();
|
|
hasher.update(&data);
|
|
(
|
|
PaddingScheme::PKCS1v15Sign {
|
|
hash: Some(rsa::hash::Hash::SHA2_512),
|
|
},
|
|
hasher.finalize()[..].to_vec(),
|
|
)
|
|
}
|
|
};
|
|
|
|
private_key.sign(padding, &hashed)?
|
|
}
|
|
Algorithm::RsaPss => {
|
|
let private_key = RSAPrivateKey::from_pkcs8(&*args.key.data)?;
|
|
|
|
let salt_len = args
|
|
.salt_length
|
|
.ok_or_else(|| type_error("Missing argument saltLength".to_string()))?
|
|
as usize;
|
|
|
|
let rng = OsRng;
|
|
let (padding, digest_in) = match args
|
|
.hash
|
|
.ok_or_else(|| type_error("Missing argument hash".to_string()))?
|
|
{
|
|
CryptoHash::Sha1 => {
|
|
let mut hasher = Sha1::new();
|
|
hasher.update(&data);
|
|
(
|
|
PaddingScheme::new_pss_with_salt::<Sha1, _>(rng, salt_len),
|
|
hasher.finalize()[..].to_vec(),
|
|
)
|
|
}
|
|
CryptoHash::Sha256 => {
|
|
let mut hasher = Sha256::new();
|
|
hasher.update(&data);
|
|
(
|
|
PaddingScheme::new_pss_with_salt::<Sha256, _>(rng, salt_len),
|
|
hasher.finalize()[..].to_vec(),
|
|
)
|
|
}
|
|
CryptoHash::Sha384 => {
|
|
let mut hasher = Sha384::new();
|
|
hasher.update(&data);
|
|
(
|
|
PaddingScheme::new_pss_with_salt::<Sha384, _>(rng, salt_len),
|
|
hasher.finalize()[..].to_vec(),
|
|
)
|
|
}
|
|
CryptoHash::Sha512 => {
|
|
let mut hasher = Sha512::new();
|
|
hasher.update(&data);
|
|
(
|
|
PaddingScheme::new_pss_with_salt::<Sha512, _>(rng, salt_len),
|
|
hasher.finalize()[..].to_vec(),
|
|
)
|
|
}
|
|
};
|
|
|
|
// Sign data based on computed padding and return buffer
|
|
private_key.sign(padding, &digest_in)?
|
|
}
|
|
Algorithm::Ecdsa => {
|
|
let curve: &EcdsaSigningAlgorithm =
|
|
args.named_curve.ok_or_else(not_supported)?.try_into()?;
|
|
|
|
let key_pair = EcdsaKeyPair::from_pkcs8(curve, &*args.key.data)?;
|
|
// We only support P256-SHA256 & P384-SHA384. These are recommended signature pairs.
|
|
// https://briansmith.org/rustdoc/ring/signature/index.html#statics
|
|
if let Some(hash) = args.hash {
|
|
match hash {
|
|
CryptoHash::Sha256 | CryptoHash::Sha384 => (),
|
|
_ => return Err(type_error("Unsupported algorithm")),
|
|
}
|
|
};
|
|
|
|
let rng = RingRand::SystemRandom::new();
|
|
let signature = key_pair.sign(&rng, &data)?;
|
|
|
|
// Signature data as buffer.
|
|
signature.as_ref().to_vec()
|
|
}
|
|
Algorithm::Hmac => {
|
|
let hash: HmacAlgorithm = args.hash.ok_or_else(not_supported)?.into();
|
|
|
|
let key = HmacKey::new(hash, &*args.key.data);
|
|
|
|
let signature = ring::hmac::sign(&key, &data);
|
|
signature.as_ref().to_vec()
|
|
}
|
|
_ => return Err(type_error("Unsupported algorithm".to_string())),
|
|
};
|
|
|
|
Ok(signature.into())
|
|
}
|
|
|
|
pub fn op_crypto_random_uuid(
|
|
state: &mut OpState,
|
|
_: (),
|
|
_: (),
|
|
) -> Result<String, AnyError> {
|
|
let maybe_seeded_rng = state.try_borrow_mut::<StdRng>();
|
|
let uuid = if let Some(seeded_rng) = maybe_seeded_rng {
|
|
let mut bytes = [0u8; 16];
|
|
seeded_rng.fill(&mut bytes);
|
|
uuid::Builder::from_bytes(bytes)
|
|
.set_version(uuid::Version::Random)
|
|
.build()
|
|
} else {
|
|
uuid::Uuid::new_v4()
|
|
};
|
|
|
|
Ok(uuid.to_string())
|
|
}
|
|
|
|
pub async fn op_crypto_subtle_digest(
|
|
_state: Rc<RefCell<OpState>>,
|
|
algorithm: CryptoHash,
|
|
data: Option<ZeroCopyBuf>,
|
|
) -> Result<ZeroCopyBuf, AnyError> {
|
|
let input = data.ok_or_else(null_opbuf)?;
|
|
let output = tokio::task::spawn_blocking(move || {
|
|
digest::digest(algorithm.into(), &input)
|
|
.as_ref()
|
|
.to_vec()
|
|
.into()
|
|
})
|
|
.await?;
|
|
|
|
Ok(output)
|
|
}
|
|
|
|
pub fn get_declaration() -> PathBuf {
|
|
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("lib.deno_crypto.d.ts")
|
|
}
|