use crate::exceptions::DOMException;
use crate::utils::{bytes::ObjectBytes, object::ObjectExt};
use rquickjs::{Ctx, FromJs, Result, Value};
use super::{
algorithm_not_supported_error, enforce_range_u8, get_optional_dictionary_value,
get_required_dictionary_value, normalize_algorithm_name, to_name_and_maybe_object,
};
#[derive(Debug)]
pub enum EncryptionAlgorithm {
AesCbc {
iv: Box<[u8]>,
},
AesCtr {
counter: Box<[u8]>,
length: u32,
},
AesGcm {
iv: Box<[u8]>,
tag_length: u8,
additional_data: Option<Box<[u8]>>,
},
ChaCha20Poly1305 {
iv: Box<[u8]>,
tag_length: u8,
additional_data: Option<Box<[u8]>>,
},
RsaOaep {
label: Option<Box<[u8]>>,
},
AesKw,
}
impl<'js> FromJs<'js> for EncryptionAlgorithm {
fn from_js(ctx: &Ctx<'js>, value: Value<'js>) -> Result<Self> {
let (name, obj) = to_name_and_maybe_object(ctx, value)?;
let name = normalize_algorithm_name(&name);
match name.as_str() {
"AES-CBC" => {
let obj = obj?;
let iv = obj
.get_required::<_, ObjectBytes>("iv", "algorithm")?
.into_bytes(ctx)?
.into_boxed_slice();
if iv.len() != 16 {
return Err(DOMException::operation_error(
ctx,
"invalid length of iv. Currently supported 16 bytes",
));
}
Ok(EncryptionAlgorithm::AesCbc { iv })
},
"AES-CTR" => {
let obj = obj?;
let counter = obj
.get_required::<_, ObjectBytes>("counter", "algorithm")?
.into_bytes(ctx)?
.into_boxed_slice();
let value = get_required_dictionary_value(&obj, "length", "algorithm")?;
let length = u32::from(enforce_range_u8(ctx, value, "length")?);
if !matches!(length, 32 | 64 | 128) {
return Err(DOMException::operation_error(
ctx,
"invalid counter length. Currently supported 32/64/128 bits",
));
}
Ok(EncryptionAlgorithm::AesCtr { counter, length })
},
"AES-GCM" => {
let obj = obj?;
let iv = obj
.get_required::<_, ObjectBytes>("iv", "algorithm")?
.into_bytes(ctx)?
.into_boxed_slice();
let additional_data = obj
.get_optional::<_, ObjectBytes>("additionalData")?
.map(|v| v.into_bytes(ctx))
.transpose()?
.map(|vec| vec.into_boxed_slice());
let tag_length = get_optional_dictionary_value(&obj, "tagLength")?
.map(|value| enforce_range_u8(ctx, value, "tagLength"))
.transpose()?
.unwrap_or(128);
if !matches!(tag_length, 32 | 64 | 96 | 104 | 112 | 120 | 128) {
return Err(DOMException::operation_error(ctx, "Invalid tagLength"));
}
Ok(EncryptionAlgorithm::AesGcm {
iv,
additional_data,
tag_length,
})
},
"ChaCha20-Poly1305" => {
let obj = obj?;
let iv = obj
.get_required::<_, ObjectBytes>("iv", "algorithm")?
.into_bytes(ctx)?
.into_boxed_slice();
let additional_data = obj
.get_optional::<_, ObjectBytes>("additionalData")?
.map(|value| value.into_bytes(ctx))
.transpose()?
.map(Vec::into_boxed_slice);
let tag_length = get_optional_dictionary_value(&obj, "tagLength")?
.map(|value| enforce_range_u8(ctx, value, "tagLength"))
.transpose()?
.unwrap_or(128);
Ok(EncryptionAlgorithm::ChaCha20Poly1305 {
iv,
tag_length,
additional_data,
})
},
"RSA-OAEP" => {
let label = if let Ok(obj) = obj {
let value: Value = obj.get("label")?;
if value.is_undefined() {
None
} else if value.is_null() {
return Err(rquickjs::Exception::throw_type(
ctx,
"RSA-OAEP label must be a BufferSource",
));
} else {
Some(
ObjectBytes::from_js(ctx, value)?
.into_bytes(ctx)?
.into_boxed_slice(),
)
}
} else {
None
};
Ok(EncryptionAlgorithm::RsaOaep { label })
},
"AES-KW" => Ok(EncryptionAlgorithm::AesKw),
_ => algorithm_not_supported_error(ctx),
}
}
}