mirror of
https://github.com/denoland/deno.git
synced 2024-12-27 17:49:08 -05:00
dda0f1c343
`ZeroCopyBuf` was convenient to use, but sometimes it did hide details that some copies were necessary in certain cases. Also it made it way to easy for the caller to pass around and convert into different values. This commit splits `ZeroCopyBuf` into `JsBuffer` (an array buffer coming from V8) and `ToJsBuffer` (a Rust buffer that will be converted into a V8 array buffer). As a result some magical conversions were removed (they were never used) limiting the API surface and preparing for changes in #19534.
312 lines
8.8 KiB
Rust
312 lines
8.8 KiB
Rust
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
|
|
|
|
use aes::cipher::block_padding::Pkcs7;
|
|
use aes::cipher::BlockEncryptMut;
|
|
use aes::cipher::KeyIvInit;
|
|
use aes::cipher::StreamCipher;
|
|
use aes_gcm::aead::generic_array::typenum::U12;
|
|
use aes_gcm::aead::generic_array::typenum::U16;
|
|
use aes_gcm::aead::generic_array::ArrayLength;
|
|
use aes_gcm::aes::Aes128;
|
|
use aes_gcm::aes::Aes192;
|
|
use aes_gcm::aes::Aes256;
|
|
use aes_gcm::AeadInPlace;
|
|
use aes_gcm::KeyInit;
|
|
use aes_gcm::Nonce;
|
|
use ctr::Ctr128BE;
|
|
use ctr::Ctr32BE;
|
|
use ctr::Ctr64BE;
|
|
use deno_core::error::type_error;
|
|
use deno_core::error::AnyError;
|
|
use deno_core::op;
|
|
use deno_core::task::spawn_blocking;
|
|
use deno_core::JsBuffer;
|
|
use deno_core::ToJsBuffer;
|
|
use rand::rngs::OsRng;
|
|
use rsa::pkcs1::DecodeRsaPublicKey;
|
|
use rsa::PaddingScheme;
|
|
use rsa::PublicKey;
|
|
use serde::Deserialize;
|
|
use sha1::Digest;
|
|
use sha1::Sha1;
|
|
use sha2::Sha256;
|
|
use sha2::Sha384;
|
|
use sha2::Sha512;
|
|
|
|
use crate::shared::*;
|
|
|
|
#[derive(Deserialize)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct EncryptOptions {
|
|
key: V8RawKeyData,
|
|
#[serde(flatten)]
|
|
algorithm: EncryptAlgorithm,
|
|
}
|
|
|
|
#[derive(Deserialize)]
|
|
#[serde(rename_all = "camelCase", tag = "algorithm")]
|
|
pub enum EncryptAlgorithm {
|
|
#[serde(rename = "RSA-OAEP")]
|
|
RsaOaep {
|
|
hash: ShaHash,
|
|
#[serde(with = "serde_bytes")]
|
|
label: Vec<u8>,
|
|
},
|
|
#[serde(rename = "AES-CBC", rename_all = "camelCase")]
|
|
AesCbc {
|
|
#[serde(with = "serde_bytes")]
|
|
iv: Vec<u8>,
|
|
length: usize,
|
|
},
|
|
#[serde(rename = "AES-GCM", rename_all = "camelCase")]
|
|
AesGcm {
|
|
#[serde(with = "serde_bytes")]
|
|
iv: Vec<u8>,
|
|
#[serde(with = "serde_bytes")]
|
|
additional_data: Option<Vec<u8>>,
|
|
length: usize,
|
|
tag_length: usize,
|
|
},
|
|
#[serde(rename = "AES-CTR", rename_all = "camelCase")]
|
|
AesCtr {
|
|
#[serde(with = "serde_bytes")]
|
|
counter: Vec<u8>,
|
|
ctr_length: usize,
|
|
key_length: usize,
|
|
},
|
|
}
|
|
|
|
#[op]
|
|
pub async fn op_crypto_encrypt(
|
|
opts: EncryptOptions,
|
|
data: JsBuffer,
|
|
) -> Result<ToJsBuffer, AnyError> {
|
|
let key = opts.key;
|
|
let fun = move || match opts.algorithm {
|
|
EncryptAlgorithm::RsaOaep { hash, label } => {
|
|
encrypt_rsa_oaep(key, hash, label, &data)
|
|
}
|
|
EncryptAlgorithm::AesCbc { iv, length } => {
|
|
encrypt_aes_cbc(key, length, iv, &data)
|
|
}
|
|
EncryptAlgorithm::AesGcm {
|
|
iv,
|
|
additional_data,
|
|
length,
|
|
tag_length,
|
|
} => encrypt_aes_gcm(key, length, tag_length, iv, additional_data, &data),
|
|
EncryptAlgorithm::AesCtr {
|
|
counter,
|
|
ctr_length,
|
|
key_length,
|
|
} => encrypt_aes_ctr(key, key_length, &counter, ctr_length, &data),
|
|
};
|
|
let buf = spawn_blocking(fun).await.unwrap()?;
|
|
Ok(buf.into())
|
|
}
|
|
|
|
fn encrypt_rsa_oaep(
|
|
key: V8RawKeyData,
|
|
hash: ShaHash,
|
|
label: Vec<u8>,
|
|
data: &[u8],
|
|
) -> Result<Vec<u8>, AnyError> {
|
|
let label = String::from_utf8_lossy(&label).to_string();
|
|
|
|
let public_key = key.as_rsa_public_key()?;
|
|
let public_key = rsa::RsaPublicKey::from_pkcs1_der(&public_key)
|
|
.map_err(|_| operation_error("failed to decode public key"))?;
|
|
let mut rng = OsRng;
|
|
let padding = match hash {
|
|
ShaHash::Sha1 => PaddingScheme::OAEP {
|
|
digest: Box::new(Sha1::new()),
|
|
mgf_digest: Box::new(Sha1::new()),
|
|
label: Some(label),
|
|
},
|
|
ShaHash::Sha256 => PaddingScheme::OAEP {
|
|
digest: Box::new(Sha256::new()),
|
|
mgf_digest: Box::new(Sha256::new()),
|
|
label: Some(label),
|
|
},
|
|
ShaHash::Sha384 => PaddingScheme::OAEP {
|
|
digest: Box::new(Sha384::new()),
|
|
mgf_digest: Box::new(Sha384::new()),
|
|
label: Some(label),
|
|
},
|
|
ShaHash::Sha512 => PaddingScheme::OAEP {
|
|
digest: Box::new(Sha512::new()),
|
|
mgf_digest: Box::new(Sha512::new()),
|
|
label: Some(label),
|
|
},
|
|
};
|
|
let encrypted = public_key
|
|
.encrypt(&mut rng, padding, data)
|
|
.map_err(|_| operation_error("Encryption failed"))?;
|
|
Ok(encrypted)
|
|
}
|
|
|
|
fn encrypt_aes_cbc(
|
|
key: V8RawKeyData,
|
|
length: usize,
|
|
iv: Vec<u8>,
|
|
data: &[u8],
|
|
) -> Result<Vec<u8>, AnyError> {
|
|
let key = key.as_secret_key()?;
|
|
let ciphertext = match length {
|
|
128 => {
|
|
// Section 10.3 Step 2 of RFC 2315 https://www.rfc-editor.org/rfc/rfc2315
|
|
type Aes128CbcEnc = cbc::Encryptor<aes::Aes128>;
|
|
|
|
let cipher = Aes128CbcEnc::new_from_slices(key, &iv)
|
|
.map_err(|_| operation_error("invalid key or iv".to_string()))?;
|
|
cipher.encrypt_padded_vec_mut::<Pkcs7>(data)
|
|
}
|
|
192 => {
|
|
// Section 10.3 Step 2 of RFC 2315 https://www.rfc-editor.org/rfc/rfc2315
|
|
type Aes192CbcEnc = cbc::Encryptor<aes::Aes192>;
|
|
|
|
let cipher = Aes192CbcEnc::new_from_slices(key, &iv)
|
|
.map_err(|_| operation_error("invalid key or iv".to_string()))?;
|
|
cipher.encrypt_padded_vec_mut::<Pkcs7>(data)
|
|
}
|
|
256 => {
|
|
// Section 10.3 Step 2 of RFC 2315 https://www.rfc-editor.org/rfc/rfc2315
|
|
type Aes256CbcEnc = cbc::Encryptor<aes::Aes256>;
|
|
|
|
let cipher = Aes256CbcEnc::new_from_slices(key, &iv)
|
|
.map_err(|_| operation_error("invalid key or iv".to_string()))?;
|
|
cipher.encrypt_padded_vec_mut::<Pkcs7>(data)
|
|
}
|
|
_ => return Err(type_error("invalid length")),
|
|
};
|
|
Ok(ciphertext)
|
|
}
|
|
|
|
fn encrypt_aes_gcm_general<N: ArrayLength<u8>>(
|
|
key: &[u8],
|
|
iv: Vec<u8>,
|
|
length: usize,
|
|
ciphertext: &mut [u8],
|
|
additional_data: Vec<u8>,
|
|
) -> Result<aes_gcm::Tag, AnyError> {
|
|
let nonce = Nonce::<N>::from_slice(&iv);
|
|
let tag = match length {
|
|
128 => {
|
|
let cipher = aes_gcm::AesGcm::<Aes128, N>::new_from_slice(key)
|
|
.map_err(|_| operation_error("Encryption failed"))?;
|
|
cipher
|
|
.encrypt_in_place_detached(nonce, &additional_data, ciphertext)
|
|
.map_err(|_| operation_error("Encryption failed"))?
|
|
}
|
|
192 => {
|
|
let cipher = aes_gcm::AesGcm::<Aes192, N>::new_from_slice(key)
|
|
.map_err(|_| operation_error("Encryption failed"))?;
|
|
cipher
|
|
.encrypt_in_place_detached(nonce, &additional_data, ciphertext)
|
|
.map_err(|_| operation_error("Encryption failed"))?
|
|
}
|
|
256 => {
|
|
let cipher = aes_gcm::AesGcm::<Aes256, N>::new_from_slice(key)
|
|
.map_err(|_| operation_error("Encryption failed"))?;
|
|
cipher
|
|
.encrypt_in_place_detached(nonce, &additional_data, ciphertext)
|
|
.map_err(|_| operation_error("Encryption failed"))?
|
|
}
|
|
_ => return Err(type_error("invalid length")),
|
|
};
|
|
|
|
Ok(tag)
|
|
}
|
|
|
|
fn encrypt_aes_gcm(
|
|
key: V8RawKeyData,
|
|
length: usize,
|
|
tag_length: usize,
|
|
iv: Vec<u8>,
|
|
additional_data: Option<Vec<u8>>,
|
|
data: &[u8],
|
|
) -> Result<Vec<u8>, AnyError> {
|
|
let key = key.as_secret_key()?;
|
|
let additional_data = additional_data.unwrap_or_default();
|
|
|
|
let mut ciphertext = data.to_vec();
|
|
// Fixed 96-bit OR 128-bit nonce
|
|
let tag = match iv.len() {
|
|
12 => encrypt_aes_gcm_general::<U12>(
|
|
key,
|
|
iv,
|
|
length,
|
|
&mut ciphertext,
|
|
additional_data,
|
|
)?,
|
|
16 => encrypt_aes_gcm_general::<U16>(
|
|
key,
|
|
iv,
|
|
length,
|
|
&mut ciphertext,
|
|
additional_data,
|
|
)?,
|
|
_ => return Err(type_error("iv length not equal to 12 or 16")),
|
|
};
|
|
|
|
// Truncated tag to the specified tag length.
|
|
// `tag` is fixed to be 16 bytes long and (tag_length / 8) is always <= 16
|
|
let tag = &tag[..(tag_length / 8)];
|
|
|
|
// C | T
|
|
ciphertext.extend_from_slice(tag);
|
|
|
|
Ok(ciphertext)
|
|
}
|
|
|
|
fn encrypt_aes_ctr_gen<B>(
|
|
key: &[u8],
|
|
counter: &[u8],
|
|
data: &[u8],
|
|
) -> Result<Vec<u8>, AnyError>
|
|
where
|
|
B: KeyIvInit + StreamCipher,
|
|
{
|
|
let mut cipher = B::new(key.into(), counter.into());
|
|
|
|
let mut ciphertext = data.to_vec();
|
|
cipher
|
|
.try_apply_keystream(&mut ciphertext)
|
|
.map_err(|_| operation_error("tried to encrypt too much data"))?;
|
|
|
|
Ok(ciphertext)
|
|
}
|
|
|
|
fn encrypt_aes_ctr(
|
|
key: V8RawKeyData,
|
|
key_length: usize,
|
|
counter: &[u8],
|
|
ctr_length: usize,
|
|
data: &[u8],
|
|
) -> Result<Vec<u8>, AnyError> {
|
|
let key = key.as_secret_key()?;
|
|
|
|
match ctr_length {
|
|
32 => match key_length {
|
|
128 => encrypt_aes_ctr_gen::<Ctr32BE<aes::Aes128>>(key, counter, data),
|
|
192 => encrypt_aes_ctr_gen::<Ctr32BE<aes::Aes192>>(key, counter, data),
|
|
256 => encrypt_aes_ctr_gen::<Ctr32BE<aes::Aes256>>(key, counter, data),
|
|
_ => Err(type_error("invalid length")),
|
|
},
|
|
64 => match key_length {
|
|
128 => encrypt_aes_ctr_gen::<Ctr64BE<aes::Aes128>>(key, counter, data),
|
|
192 => encrypt_aes_ctr_gen::<Ctr64BE<aes::Aes192>>(key, counter, data),
|
|
256 => encrypt_aes_ctr_gen::<Ctr64BE<aes::Aes256>>(key, counter, data),
|
|
_ => Err(type_error("invalid length")),
|
|
},
|
|
128 => match key_length {
|
|
128 => encrypt_aes_ctr_gen::<Ctr128BE<aes::Aes128>>(key, counter, data),
|
|
192 => encrypt_aes_ctr_gen::<Ctr128BE<aes::Aes192>>(key, counter, data),
|
|
256 => encrypt_aes_ctr_gen::<Ctr128BE<aes::Aes256>>(key, counter, data),
|
|
_ => Err(type_error("invalid length")),
|
|
},
|
|
_ => Err(type_error(
|
|
"invalid counter length. Currently supported 32/64/128 bits",
|
|
)),
|
|
}
|
|
}
|