#![allow(
clippy::too_many_arguments,
reason = "cppgc impl methods mirror the WebCrypto WebIDL slot lists \
(`unwrapKey(format, wrappedKey, unwrappingKey, unwrapAlgorithm, \
unwrappedKeyAlgorithm, extractable, keyUsages)`, \
`decapsulateKey(algorithm, decapsulationKey, ciphertext, \
sharedKeyAlgorithm, extractable, usages)`); the op2 macro \
expansion at the impl-block site is what trips the lint"
)]
use std::ffi::CStr;
use deno_core::GarbageCollected;
use deno_core::op2;
use deno_core::unsync::spawn_blocking;
use deno_core::v8;
use deno_core::webidl::WebIdlInterfaceConverter;
use crate::CryptoError;
use crate::algorithm::check_support_for_algorithm;
use crate::algorithm::compute_key_length;
use crate::algorithm::registered_algorithm;
use crate::digest::BufferSource;
use crate::digest::DigestAlgorithm;
use crate::digest::run as run_digest;
use crate::shared::SharedError;
use crate::subtle_decrypt::SubtleDecryptParams;
use crate::subtle_decrypt::run as run_decrypt;
use crate::subtle_derive_bits::SubtleDeriveBitsParams;
use crate::subtle_derive_bits::run as run_derive_bits;
use crate::subtle_derive_key::DerivedKey;
use crate::subtle_derive_key::check_base_key;
use crate::subtle_derive_key::key_length_for;
use crate::subtle_derive_key::run as run_derive_key;
use crate::subtle_encapsulate::EncapsulateBitsOutput;
use crate::subtle_encapsulate::SubtleEncapsulateParams;
use crate::subtle_encapsulate::run_decapsulate_bits;
use crate::subtle_encapsulate::run_encapsulate_bits;
use crate::subtle_encapsulate_key::EncapsulateKeyOutput;
use crate::subtle_encapsulate_key::run_decapsulate_key;
use crate::subtle_encapsulate_key::run_encapsulate_key;
use crate::subtle_encrypt::SubtleEncryptParams;
use crate::subtle_encrypt::run as run_encrypt;
use crate::subtle_encrypt::v8_str;
use crate::subtle_export_key::ExportKeyOutput;
use crate::subtle_export_key::KeyFormat;
use crate::subtle_export_key::run as run_export_key;
use crate::subtle_generate_key::GenerateKeyAlgorithm;
use crate::subtle_generate_key::GenerateKeyOutput;
use crate::subtle_generate_key::run as run_generate_key;
use crate::subtle_get_public_key::run as run_get_public_key;
use crate::subtle_import_key::ImportAlgorithm;
use crate::subtle_import_key::ImportKeyData;
use crate::subtle_import_key::run as run_import_key;
use crate::subtle_key::SubtleKey;
use crate::subtle_sign::SubtleSignParams;
use crate::subtle_sign::run as run_sign;
use crate::subtle_verify::SubtleVerifyParams;
use crate::subtle_verify::run as run_verify;
use crate::subtle_wrap_key::UnwrapAlgorithm;
use crate::subtle_wrap_key::WrapAlgorithm;
use crate::subtle_wrap_key::run_unwrap_key;
use crate::subtle_wrap_key::run_wrap_key;
pub struct SubtleCrypto;
impl WebIdlInterfaceConverter for SubtleCrypto {
const NAME: &'static str = "SubtleCrypto";
}
unsafe impl GarbageCollected for SubtleCrypto {
fn trace(&self, _visitor: &mut v8::cppgc::Visitor) {}
fn get_name(&self) -> &'static CStr {
c"SubtleCrypto"
}
}
#[op2]
impl SubtleCrypto {
#[constructor]
#[cppgc]
fn constructor(_: bool) -> Result<SubtleCrypto, SharedError> {
Err(SharedError::IllegalConstructor)
}
#[required(0)]
#[static_method]
#[cppgc]
fn create() -> SubtleCrypto {
SubtleCrypto
}
#[fast]
#[required(2)]
#[static_method]
fn supports<'s>(
scope: &mut v8::PinScope<'s, '_>,
#[string] operation: String,
algorithm: v8::Local<'s, v8::Value>,
length_or_hash: Option<v8::Local<'s, v8::Value>>,
) -> bool {
supports_inner(scope, &operation, algorithm, length_or_hash)
.unwrap_or(false)
}
#[required(2)]
#[arraybuffer]
async fn digest(
&self,
#[webidl] algorithm: DigestAlgorithm,
#[webidl] data: BufferSource,
) -> Result<Vec<u8>, CryptoError> {
spawn_blocking(move || run_digest(algorithm, data.0)).await?
}
#[required(3)]
#[arraybuffer]
async fn encrypt(
&self,
#[webidl] algorithm: SubtleEncryptParams,
#[webidl] key: SubtleKey,
#[webidl] data: BufferSource,
) -> Result<Vec<u8>, CryptoError> {
spawn_blocking(move || run_encrypt(algorithm, key, data.0)).await?
}
#[required(3)]
#[arraybuffer]
async fn decrypt(
&self,
#[webidl] algorithm: SubtleDecryptParams,
#[webidl] key: SubtleKey,
#[webidl] data: BufferSource,
) -> Result<Vec<u8>, CryptoError> {
spawn_blocking(move || run_decrypt(algorithm, key, data.0)).await?
}
#[required(3)]
#[arraybuffer]
async fn sign(
&self,
#[webidl] algorithm: SubtleSignParams,
#[webidl] key: SubtleKey,
#[webidl] data: BufferSource,
) -> Result<Vec<u8>, CryptoError> {
spawn_blocking(move || run_sign(algorithm, key, data.0)).await?
}
#[required(4)]
async fn verify(
&self,
#[webidl] algorithm: SubtleVerifyParams,
#[webidl] key: SubtleKey,
#[webidl] signature: BufferSource,
#[webidl] data: BufferSource,
) -> Result<bool, CryptoError> {
spawn_blocking(move || run_verify(algorithm, key, signature.0, data.0))
.await?
}
#[arraybuffer]
#[rename("deriveBits")]
async fn derive_bits(
&self,
#[webidl] algorithm: SubtleDeriveBitsParams,
#[webidl] base_key: SubtleKey,
length: Option<f64>,
) -> Result<Vec<u8>, CryptoError> {
if !base_key.has_usage("deriveBits") {
return Err(CryptoError::Other(deno_error::JsErrorBox::new(
"DOMExceptionInvalidAccessError",
"'baseKey' usages does not contain 'deriveBits'",
)));
}
spawn_blocking(move || run_derive_bits(algorithm, base_key, length)).await?
}
#[rename("deriveKey")]
async fn derive_key(
&self,
#[webidl] algorithm: SubtleDeriveBitsParams,
#[webidl] base_key: SubtleKey,
#[webidl] derived_key_type: crate::subtle_import_key::ImportAlgorithm,
extractable: bool,
#[webidl] usages: Vec<String>,
) -> Result<DerivedKey, CryptoError> {
check_base_key(&algorithm, &base_key)?;
let derived_length = key_length_for(&derived_key_type)?;
let bits = spawn_blocking(move || {
run_derive_bits(algorithm, base_key, derived_length.map(|l| l as f64))
})
.await??;
Ok(run_derive_key(bits, derived_key_type, extractable, usages))
}
#[rename("importKey")]
#[required(4)]
fn import_key<'s>(
&self,
scope: &mut v8::PinScope<'s, '_>,
#[webidl] format: KeyFormat,
key_data: v8::Local<'s, v8::Value>,
#[webidl] algorithm: ImportAlgorithm,
extractable: bool,
#[webidl] usages: Vec<String>,
) -> Result<v8::Local<'s, v8::Object>, CryptoError> {
let data = ImportKeyData::from_v8(scope, key_data, format)?;
let key =
run_import_key(scope, format, &algorithm, data, extractable, &usages)?;
let key_type = deno_core::cppgc::try_unwrap_cppgc_object::<
crate::crypto_key::CryptoKey,
>(scope, key.into())
.map(|p| p.key_type())
.ok_or_else(|| {
CryptoError::Other(deno_error::JsErrorBox::type_error(
"internal: imported key is not a CryptoKey",
))
})?;
if matches!(
key_type,
crate::crypto_key::CryptoKeyType::Private
| crate::crypto_key::CryptoKeyType::Secret
) && usages.is_empty()
{
return Err(CryptoError::Other(deno_error::JsErrorBox::new(
"DOMExceptionSyntaxError",
"Invalid key usage",
)));
}
Ok(key)
}
#[rename("exportKey")]
#[required(2)]
async fn export_key(
&self,
#[webidl] format: KeyFormat,
#[webidl] key: SubtleKey,
) -> Result<ExportKeyOutput, CryptoError> {
spawn_blocking(move || run_export_key(format, key)).await?
}
#[rename("encapsulateBits")]
async fn encapsulate_bits(
&self,
#[webidl] algorithm: SubtleEncapsulateParams,
#[webidl] encapsulation_key: SubtleKey,
) -> Result<EncapsulateBitsOutput, CryptoError> {
spawn_blocking(move || run_encapsulate_bits(algorithm, encapsulation_key))
.await?
}
#[arraybuffer]
#[rename("decapsulateBits")]
async fn decapsulate_bits(
&self,
#[webidl] algorithm: SubtleEncapsulateParams,
#[webidl] decapsulation_key: SubtleKey,
#[webidl] ciphertext: BufferSource,
) -> Result<Vec<u8>, CryptoError> {
spawn_blocking(move || {
run_decapsulate_bits(algorithm, decapsulation_key, ciphertext.0)
})
.await?
}
#[rename("generateKey")]
#[required(3)]
async fn generate_key(
&self,
#[webidl] algorithm: GenerateKeyAlgorithm,
extractable: bool,
#[webidl] usages: Vec<String>,
) -> Result<GenerateKeyOutput, CryptoError> {
run_generate_key(algorithm, extractable, usages).await
}
#[rename("getPublicKey")]
#[required(2)]
fn get_public_key<'s>(
&self,
scope: &mut v8::PinScope<'s, '_>,
#[webidl] key: SubtleKey,
#[webidl] key_usages: Vec<String>,
) -> Result<v8::Local<'s, v8::Object>, CryptoError> {
run_get_public_key(scope, key, key_usages)
}
#[arraybuffer]
#[rename("wrapKey")]
#[required(4)]
async fn wrap_key(
&self,
#[webidl] format: KeyFormat,
#[webidl] key: SubtleKey,
#[webidl] wrapping_key: SubtleKey,
#[webidl] wrap_algorithm: WrapAlgorithm,
) -> Result<Vec<u8>, CryptoError> {
let WrapAlgorithm { name, params } = wrap_algorithm;
spawn_blocking(move || {
run_wrap_key(format, key, &name, wrapping_key, params)
})
.await?
}
#[rename("unwrapKey")]
#[required(7)]
fn unwrap_key<'s>(
&self,
scope: &mut v8::PinScope<'s, '_>,
#[webidl] format: KeyFormat,
#[webidl] wrapped_key: BufferSource,
#[webidl] unwrapping_key: SubtleKey,
#[webidl] unwrap_algorithm: UnwrapAlgorithm,
#[webidl]
unwrapped_key_algorithm: crate::subtle_import_key::ImportAlgorithm,
extractable: bool,
#[webidl] usages: Vec<String>,
) -> Result<v8::Local<'s, v8::Object>, CryptoError> {
let UnwrapAlgorithm { name, params } = unwrap_algorithm;
run_unwrap_key(
scope,
format,
wrapped_key.0,
&name,
unwrapping_key,
params,
unwrapped_key_algorithm,
extractable,
usages,
)
}
#[rename("encapsulateKey")]
fn encapsulate_key<'s>(
&self,
scope: &mut v8::PinScope<'s, '_>,
#[webidl] algorithm: SubtleEncapsulateParams,
#[webidl] encapsulation_key: SubtleKey,
#[webidl] shared_key_algorithm: crate::subtle_import_key::ImportAlgorithm,
extractable: bool,
#[webidl] usages: Vec<String>,
) -> Result<EncapsulateKeyOutput<'s>, CryptoError> {
run_encapsulate_key(
scope,
algorithm,
encapsulation_key,
shared_key_algorithm,
extractable,
usages,
)
}
#[rename("decapsulateKey")]
fn decapsulate_key<'s>(
&self,
scope: &mut v8::PinScope<'s, '_>,
#[webidl] algorithm: SubtleEncapsulateParams,
#[webidl] decapsulation_key: SubtleKey,
#[webidl] ciphertext: BufferSource,
#[webidl] shared_key_algorithm: crate::subtle_import_key::ImportAlgorithm,
extractable: bool,
#[webidl] usages: Vec<String>,
) -> Result<v8::Local<'s, v8::Object>, CryptoError> {
run_decapsulate_key(
scope,
algorithm,
decapsulation_key,
shared_key_algorithm,
ciphertext.0,
extractable,
usages,
)
}
}
fn supports_inner<'s>(
scope: &mut v8::PinScope<'s, '_>,
operation: &str,
algorithm: v8::Local<'s, v8::Value>,
length_or_hash: Option<v8::Local<'s, v8::Value>>,
) -> Option<bool> {
let (algorithm_name, algorithm_obj) = extract_alg_name(scope, algorithm)?;
let mut length: Option<u32> = None;
let mut additional_algorithm: Option<v8::Local<'s, v8::Value>> = None;
if let Some(v) = length_or_hash
&& !v.is_undefined()
&& !v.is_null()
{
if v.is_number() {
length = v.uint32_value(scope);
} else {
additional_algorithm = Some(v);
}
}
if let Some(additional) = additional_algorithm {
let (additional_name, additional_obj) =
extract_alg_name(scope, additional)?;
let additional_check_op = match operation {
"deriveKey" | "unwrapKey" | "encapsulateKey" | "decapsulateKey" => {
Some("importKey")
}
"wrapKey" => Some("exportKey"),
_ => None,
};
if let Some(check_op) = additional_check_op
&& !check_support_for_algorithm(check_op, &additional_name)
{
return Some(false);
}
if operation == "deriveKey" {
let registered = registered_algorithm("get key length", &additional_name);
let Some((canonical_name, _)) = registered else {
return Some(false);
};
let dict_length =
additional_obj.and_then(|obj| read_u32_member(scope, obj, b"length"));
let dict_hash_name = additional_obj
.and_then(|obj| read_hash_name_member(scope, obj))
.unwrap_or_default();
let derived_len = match compute_key_length(
canonical_name,
dict_length,
Some(dict_hash_name.as_str()),
) {
Ok(len) => len,
Err(_) => return Some(false),
};
return Some(supports_check(
scope,
"deriveBits",
&algorithm_name,
algorithm_obj,
derived_len,
));
}
}
Some(supports_check(
scope,
operation,
&algorithm_name,
algorithm_obj,
length,
))
}
fn supports_check<'s>(
scope: &mut v8::PinScope<'s, '_>,
operation: &str,
algorithm_name: &str,
algorithm_obj: Option<v8::Local<'s, v8::Object>>,
length: Option<u32>,
) -> bool {
if !check_support_for_algorithm(operation, algorithm_name) {
return false;
}
let registered_op = match operation {
"encapsulateKey" | "encapsulateBits" => "encapsulate",
"decapsulateKey" | "decapsulateBits" => "decapsulate",
"deriveKey" => "deriveBits",
"exportKey" | "getPublicKey" => "importKey",
"wrapKey" => {
if registered_algorithm("wrapKey", algorithm_name).is_some() {
"wrapKey"
} else {
"encrypt"
}
}
"unwrapKey" => {
if registered_algorithm("unwrapKey", algorithm_name).is_some() {
"unwrapKey"
} else {
"decrypt"
}
}
other => other,
};
supports_params_valid(
scope,
registered_op,
algorithm_name,
algorithm_obj,
length,
)
}
fn supports_params_valid<'s>(
scope: &mut v8::PinScope<'s, '_>,
registered_op: &str,
algorithm_name: &str,
algorithm_obj: Option<v8::Local<'s, v8::Object>>,
length: Option<u32>,
) -> bool {
let upper = algorithm_name.to_ascii_uppercase();
if registered_op == "deriveBits"
&& (upper == "HKDF"
|| upper == "PBKDF2"
|| upper == "ARGON2I"
|| upper == "ARGON2D"
|| upper == "ARGON2ID")
{
let Some(l) = length else { return false };
if l == 0 || !l.is_multiple_of(8) {
return false;
}
}
let Some(obj) = algorithm_obj else {
return true;
};
if let Some(hash_name) = read_optional_hash_name(scope, obj)
&& registered_algorithm("digest", &hash_name).is_none()
{
return false;
}
match registered_op {
"digest" => match upper.as_str() {
"CSHAKE128" | "CSHAKE256" | "TURBOSHAKE128" | "TURBOSHAKE256"
| "KT128" | "KT256" | "KANGAROOTWELVE" => {
let Some(l) = read_u32_member(scope, obj, b"outputLength") else {
return true;
};
l != 0 && l.is_multiple_of(8)
}
_ => true,
},
"encrypt" | "decrypt" => match upper.as_str() {
"AES-CBC" => {
if let Some(n) = read_buffer_source_byte_length(scope, obj, b"iv")
&& n != 16
{
return false;
}
true
}
"AES-CTR" => {
if let Some(n) = read_buffer_source_byte_length(scope, obj, b"counter")
&& n != 16
{
return false;
}
if let Some(l) = read_u32_member(scope, obj, b"length")
&& (l == 0 || l > 128)
{
return false;
}
true
}
"AES-GCM" | "AES-OCB" => {
if let Some(n) = read_buffer_source_byte_length(scope, obj, b"iv")
&& n != 12
&& n != 16
{
return false;
}
if let Some(l) = read_u32_member(scope, obj, b"tagLength")
&& !matches!(l, 32 | 64 | 96 | 104 | 112 | 120 | 128)
{
return false;
}
true
}
"CHACHA20-POLY1305" => {
if let Some(n) = read_buffer_source_byte_length(scope, obj, b"iv")
&& n != 12
{
return false;
}
if let Some(l) = read_u32_member(scope, obj, b"tagLength")
&& l != 128
{
return false;
}
true
}
_ => true,
},
"generateKey" | "get key length" => match upper.as_str() {
"AES-CBC" | "AES-CTR" | "AES-GCM" | "AES-OCB" | "AES-KW" => {
if let Some(l) = read_u32_member(scope, obj, b"length")
&& !matches!(l, 128 | 192 | 256)
{
return false;
}
true
}
"HMAC" => {
if let Some(0) = read_u32_member(scope, obj, b"length") {
return false;
}
true
}
"KMAC128" | "KMAC256" => {
if let Some(l) = read_u32_member(scope, obj, b"length")
&& (l == 0 || !l.is_multiple_of(8))
{
return false;
}
true
}
"ECDSA" | "ECDH" => {
if let Some(curve) = read_string_member(scope, obj, b"namedCurve")
&& !matches!(curve.as_str(), "P-256" | "P-384" | "P-521")
{
return false;
}
true
}
_ => true,
},
"sign" | "verify" => match upper.as_str() {
"KMAC128" | "KMAC256" => {
let Some(l) = read_u32_member(scope, obj, b"outputLength") else {
return true;
};
l != 0 && l.is_multiple_of(8)
}
_ => true,
},
"deriveBits" => match upper.as_str() {
"ARGON2I" | "ARGON2D" | "ARGON2ID" => {
if let Some(memory) = read_u32_member(scope, obj, b"memory")
&& memory == 0
{
return false;
}
if let Some(passes) = read_u32_member(scope, obj, b"passes")
&& passes == 0
{
return false;
}
if let Some(parallelism) = read_u32_member(scope, obj, b"parallelism")
&& parallelism == 0
{
return false;
}
true
}
_ => true,
},
_ => true,
}
}
fn read_buffer_source_byte_length<'s>(
scope: &mut v8::PinScope<'s, '_>,
obj: v8::Local<'s, v8::Object>,
field: &[u8],
) -> Option<usize> {
let key = v8::String::new_from_one_byte(
scope,
field,
v8::NewStringType::Internalized,
)?;
let val = obj.get(scope, key.into())?;
if val.is_undefined() || val.is_null() {
return None;
}
if let Ok(view) = v8::Local::<v8::ArrayBufferView>::try_from(val) {
return Some(view.byte_length());
}
if let Ok(ab) = v8::Local::<v8::ArrayBuffer>::try_from(val) {
return Some(ab.byte_length());
}
None
}
fn read_string_member<'s>(
scope: &mut v8::PinScope<'s, '_>,
obj: v8::Local<'s, v8::Object>,
field: &[u8],
) -> Option<String> {
let key = v8::String::new_from_one_byte(
scope,
field,
v8::NewStringType::Internalized,
)?;
let val = obj.get(scope, key.into())?;
if val.is_undefined() || val.is_null() {
return None;
}
Some(val.to_string(scope)?.to_rust_string_lossy(scope))
}
fn read_optional_hash_name<'s>(
scope: &mut v8::PinScope<'s, '_>,
obj: v8::Local<'s, v8::Object>,
) -> Option<String> {
let key = v8_str(scope, "hash");
if !obj.has_own_property(scope, key.into())? {
return None;
}
let val = obj.get(scope, key.into())?;
if val.is_undefined() {
return None;
}
if val.is_string() {
return Some(val.to_rust_string_lossy(scope));
}
let inner = v8::Local::<v8::Object>::try_from(val).ok()?;
let name_key = v8_str(scope, "name");
let name_val = inner.get(scope, name_key.into())?;
Some(name_val.to_string(scope)?.to_rust_string_lossy(scope))
}
fn extract_alg_name<'s>(
scope: &mut v8::PinScope<'s, '_>,
value: v8::Local<'s, v8::Value>,
) -> Option<(String, Option<v8::Local<'s, v8::Object>>)> {
if value.is_string() {
return Some((value.to_rust_string_lossy(scope), None));
}
let obj = v8::Local::<v8::Object>::try_from(value).ok()?;
let name_key = v8_str(scope, "name");
let name_val = obj.get(scope, name_key.into())?;
if name_val.is_undefined() {
return None;
}
let s = name_val.to_string(scope)?.to_rust_string_lossy(scope);
Some((s, Some(obj)))
}
fn read_u32_member<'s>(
scope: &mut v8::PinScope<'s, '_>,
obj: v8::Local<'s, v8::Object>,
field: &[u8],
) -> Option<u32> {
let key = v8::String::new_from_one_byte(
scope,
field,
v8::NewStringType::Internalized,
)?;
let val = obj.get(scope, key.into())?;
if val.is_undefined() || val.is_null() {
return None;
}
val.uint32_value(scope)
}
fn read_hash_name_member<'s>(
scope: &mut v8::PinScope<'s, '_>,
obj: v8::Local<'s, v8::Object>,
) -> Option<String> {
let key = v8_str(scope, "hash");
let val = obj.get(scope, key.into())?;
if val.is_undefined() || val.is_null() {
return None;
}
if val.is_string() {
return Some(val.to_rust_string_lossy(scope));
}
let hash_obj = v8::Local::<v8::Object>::try_from(val).ok()?;
let name_key = v8_str(scope, "name");
let name_val = hash_obj.get(scope, name_key.into())?;
Some(name_val.to_string(scope)?.to_rust_string_lossy(scope))
}