summaryrefslogtreecommitdiff
path: root/ext/crypto/lib.rs
diff options
context:
space:
mode:
Diffstat (limited to 'ext/crypto/lib.rs')
-rw-r--r--ext/crypto/lib.rs558
1 files changed, 558 insertions, 0 deletions
diff --git a/ext/crypto/lib.rs b/ext/crypto/lib.rs
new file mode 100644
index 000000000..e77b34987
--- /dev/null
+++ b/ext/crypto/lib.rs
@@ -0,0 +1,558 @@
+// 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::pkcs8::FromPrivateKey;
+use rsa::pkcs8::ToPrivateKey;
+use rsa::BigUint;
+use rsa::PublicKey;
+use rsa::RsaPrivateKey;
+use rsa::RsaPublicKey;
+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:ext/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_verify_key", op_async(op_crypto_verify_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_der()?.as_ref().to_vec()
+ }
+ 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_der(&*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_der(&*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())
+}
+
+#[derive(Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub struct VerifyArg {
+ key: KeyData,
+ algorithm: Algorithm,
+ salt_length: Option<u32>,
+ hash: Option<CryptoHash>,
+ signature: ZeroCopyBuf,
+}
+
+pub async fn op_crypto_verify_key(
+ _state: Rc<RefCell<OpState>>,
+ args: VerifyArg,
+ zero_copy: Option<ZeroCopyBuf>,
+) -> Result<bool, AnyError> {
+ let zero_copy = zero_copy.ok_or_else(null_opbuf)?;
+ let data = &*zero_copy;
+ let algorithm = args.algorithm;
+
+ let verification = match algorithm {
+ Algorithm::RsassaPkcs1v15 => {
+ let public_key: RsaPublicKey =
+ RsaPrivateKey::from_pkcs8_der(&*args.key.data)?.to_public_key();
+ 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(),
+ )
+ }
+ };
+
+ public_key
+ .verify(padding, &hashed, &*args.signature)
+ .is_ok()
+ }
+ Algorithm::RsaPss => {
+ let salt_len = args
+ .salt_length
+ .ok_or_else(|| type_error("Missing argument saltLength".to_string()))?
+ as usize;
+ let public_key: RsaPublicKey =
+ RsaPrivateKey::from_pkcs8_der(&*args.key.data)?.to_public_key();
+
+ let rng = OsRng;
+ 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::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(),
+ )
+ }
+ };
+
+ public_key
+ .verify(padding, &hashed, &*args.signature)
+ .is_ok()
+ }
+ Algorithm::Hmac => {
+ let hash: HmacAlgorithm = args.hash.ok_or_else(not_supported)?.into();
+ let key = HmacKey::new(hash, &*args.key.data);
+ ring::hmac::verify(&key, data, &*args.signature).is_ok()
+ }
+ _ => return Err(type_error("Unsupported algorithm".to_string())),
+ };
+
+ Ok(verification)
+}
+
+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")
+}