use std::num::NonZero;
use std::ptr;
use std::rc::Rc;
use aes::cipher::block_padding::Pkcs7;
use aes::cipher::generic_array::GenericArray;
use aes::cipher::{BlockDecryptMut, BlockEncryptMut, KeyIvInit, StreamCipher};
use aes::{Aes128, Aes192, Aes256};
use base64::prelude::*;
use dom_struct::dom_struct;
use js::conversions::ConversionResult;
use js::jsapi::{JSObject, JS_NewObject};
use js::jsval::ObjectValue;
use js::rust::MutableHandleObject;
use js::typedarray::ArrayBufferU8;
use ring::{digest, pbkdf2};
use servo_rand::{RngCore, ServoRng};
use crate::dom::bindings::buffer_source::create_buffer_source;
use crate::dom::bindings::cell::DomRefCell;
use crate::dom::bindings::codegen::Bindings::CryptoKeyBinding::{
CryptoKeyMethods, KeyType, KeyUsage,
};
use crate::dom::bindings::codegen::Bindings::SubtleCryptoBinding::{
AesCbcParams, AesCtrParams, AesDerivedKeyParams, AesKeyAlgorithm, AesKeyGenParams, Algorithm,
AlgorithmIdentifier, JsonWebKey, KeyAlgorithm, KeyFormat, Pbkdf2Params, SubtleCryptoMethods,
};
use crate::dom::bindings::codegen::UnionTypes::{
ArrayBufferViewOrArrayBuffer, ArrayBufferViewOrArrayBufferOrJsonWebKey,
};
use crate::dom::bindings::error::{Error, Fallible};
use crate::dom::bindings::import::module::SafeJSContext;
use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::refcounted::{Trusted, TrustedPromise};
use crate::dom::bindings::reflector::{reflect_dom_object, DomObject, Reflector};
use crate::dom::bindings::root::DomRoot;
use crate::dom::bindings::str::DOMString;
use crate::dom::bindings::trace::RootedTraceableBox;
use crate::dom::cryptokey::{CryptoKey, Handle};
use crate::dom::globalscope::GlobalScope;
use crate::dom::promise::Promise;
use crate::dom::window::Window;
use crate::dom::workerglobalscope::WorkerGlobalScope;
use crate::realms::InRealm;
use crate::script_runtime::{CanGc, JSContext};
use crate::task::TaskCanceller;
use crate::task_source::dom_manipulation::DOMManipulationTaskSource;
use crate::task_source::TaskSource;
const ALG_AES_CBC: &str = "AES-CBC";
const ALG_AES_CTR: &str = "AES-CTR";
const ALG_AES_GCM: &str = "AES-GCM";
const ALG_AES_KW: &str = "AES-KW";
const ALG_SHA1: &str = "SHA-1";
const ALG_SHA256: &str = "SHA-256";
const ALG_SHA384: &str = "SHA-384";
const ALG_SHA512: &str = "SHA-512";
const ALG_HMAC: &str = "HMAC";
const ALG_HKDF: &str = "HKDF";
const ALG_PBKDF2: &str = "PBKDF2";
const ALG_RSASSA_PKCS1: &str = "RSASSA-PKCS1-v1_5";
const ALG_RSA_OAEP: &str = "RSA-OAEP";
const ALG_RSA_PSS: &str = "RSA-PSS";
const ALG_ECDH: &str = "ECDH";
const ALG_ECDSA: &str = "ECDSA";
#[allow(dead_code)]
static SUPPORTED_ALGORITHMS: &[&str] = &[
ALG_AES_CBC,
ALG_AES_CTR,
ALG_AES_GCM,
ALG_AES_KW,
ALG_SHA1,
ALG_SHA256,
ALG_SHA384,
ALG_SHA512,
ALG_HMAC,
ALG_HKDF,
ALG_PBKDF2,
ALG_RSASSA_PKCS1,
ALG_RSA_OAEP,
ALG_RSA_PSS,
ALG_ECDH,
ALG_ECDSA,
];
const NAMED_CURVE_P256: &str = "P-256";
const NAMED_CURVE_P384: &str = "P-384";
const NAMED_CURVE_P521: &str = "P-521";
#[allow(dead_code)]
static SUPPORTED_CURVES: &[&str] = &[NAMED_CURVE_P256, NAMED_CURVE_P384, NAMED_CURVE_P521];
type Aes128CbcEnc = cbc::Encryptor<Aes128>;
type Aes128CbcDec = cbc::Decryptor<Aes128>;
type Aes192CbcEnc = cbc::Encryptor<Aes192>;
type Aes192CbcDec = cbc::Decryptor<Aes192>;
type Aes256CbcEnc = cbc::Encryptor<Aes256>;
type Aes256CbcDec = cbc::Decryptor<Aes256>;
type Aes128Ctr = ctr::Ctr64BE<Aes128>;
type Aes192Ctr = ctr::Ctr64BE<Aes192>;
type Aes256Ctr = ctr::Ctr64BE<Aes256>;
#[dom_struct]
pub struct SubtleCrypto {
reflector_: Reflector,
#[no_trace]
rng: DomRefCell<ServoRng>,
}
impl SubtleCrypto {
fn new_inherited() -> SubtleCrypto {
SubtleCrypto {
reflector_: Reflector::new(),
rng: DomRefCell::new(ServoRng::default()),
}
}
pub(crate) fn new(global: &GlobalScope) -> DomRoot<SubtleCrypto> {
reflect_dom_object(Box::new(SubtleCrypto::new_inherited()), global)
}
fn task_source_with_canceller(&self) -> (DOMManipulationTaskSource, TaskCanceller) {
if let Some(window) = self.global().downcast::<Window>() {
window
.task_manager()
.dom_manipulation_task_source_with_canceller()
} else if let Some(worker_global) = self.global().downcast::<WorkerGlobalScope>() {
let task_source = worker_global.dom_manipulation_task_source();
let canceller = worker_global.task_canceller();
(task_source, canceller)
} else {
unreachable!("Couldn't downcast to Window or WorkerGlobalScope!");
}
}
}
impl SubtleCryptoMethods for SubtleCrypto {
fn Encrypt(
&self,
cx: JSContext,
algorithm: AlgorithmIdentifier,
key: &CryptoKey,
data: ArrayBufferViewOrArrayBuffer,
comp: InRealm,
can_gc: CanGc,
) -> Rc<Promise> {
let promise = Promise::new_in_current_realm(comp, can_gc);
let normalized_algorithm = match normalize_algorithm_for_encrypt_or_decrypt(cx, &algorithm)
{
Ok(algorithm) => algorithm,
Err(e) => {
promise.reject_error(e);
return promise;
},
};
let data = match data {
ArrayBufferViewOrArrayBuffer::ArrayBufferView(view) => view.to_vec(),
ArrayBufferViewOrArrayBuffer::ArrayBuffer(buffer) => buffer.to_vec(),
};
let (task_source, canceller) = self.task_source_with_canceller();
let this = Trusted::new(self);
let trusted_promise = TrustedPromise::new(promise.clone());
let trusted_key = Trusted::new(key);
let key_alg = key.algorithm();
let valid_usage = key.usages().contains(&KeyUsage::Encrypt);
let _ = task_source.queue_with_canceller(
task!(encrypt: move || {
let subtle = this.root();
let promise = trusted_promise.root();
let key = trusted_key.root();
if !valid_usage || normalized_algorithm.name() != key_alg {
promise.reject_error(Error::InvalidAccess);
return;
}
let cx = GlobalScope::get_cx();
rooted!(in(*cx) let mut array_buffer_ptr = ptr::null_mut::<JSObject>());
if let Err(e) = normalized_algorithm.encrypt(&subtle, &key, &data, cx, array_buffer_ptr.handle_mut()) {
promise.reject_error(e);
return;
}
promise.resolve_native(&*array_buffer_ptr.handle());
}),
&canceller,
);
promise
}
fn Decrypt(
&self,
cx: JSContext,
algorithm: AlgorithmIdentifier,
key: &CryptoKey,
data: ArrayBufferViewOrArrayBuffer,
comp: InRealm,
can_gc: CanGc,
) -> Rc<Promise> {
let promise = Promise::new_in_current_realm(comp, can_gc);
let normalized_algorithm = match normalize_algorithm_for_encrypt_or_decrypt(cx, &algorithm)
{
Ok(algorithm) => algorithm,
Err(e) => {
promise.reject_error(e);
return promise;
},
};
let data = match data {
ArrayBufferViewOrArrayBuffer::ArrayBufferView(view) => view.to_vec(),
ArrayBufferViewOrArrayBuffer::ArrayBuffer(buffer) => buffer.to_vec(),
};
let (task_source, canceller) = self.task_source_with_canceller();
let this = Trusted::new(self);
let trusted_promise = TrustedPromise::new(promise.clone());
let trusted_key = Trusted::new(key);
let key_alg = key.algorithm();
let valid_usage = key.usages().contains(&KeyUsage::Decrypt);
let _ = task_source.queue_with_canceller(
task!(decrypt: move || {
let subtle = this.root();
let promise = trusted_promise.root();
let key = trusted_key.root();
let cx = GlobalScope::get_cx();
rooted!(in(*cx) let mut array_buffer_ptr = ptr::null_mut::<JSObject>());
if !valid_usage || normalized_algorithm.name() != key_alg {
promise.reject_error(Error::InvalidAccess);
return;
}
if let Err(e) = normalized_algorithm.decrypt(&subtle, &key, &data, cx, array_buffer_ptr.handle_mut()) {
promise.reject_error(e);
return;
}
promise.resolve_native(&*array_buffer_ptr.handle());
}),
&canceller,
);
promise
}
fn Digest(
&self,
cx: SafeJSContext,
algorithm: AlgorithmIdentifier,
data: ArrayBufferViewOrArrayBuffer,
comp: InRealm,
can_gc: CanGc,
) -> Rc<Promise> {
let data = match data {
ArrayBufferViewOrArrayBuffer::ArrayBufferView(view) => view.to_vec(),
ArrayBufferViewOrArrayBuffer::ArrayBuffer(buffer) => buffer.to_vec(),
};
let promise = Promise::new_in_current_realm(comp, can_gc);
let normalized_algorithm = match normalize_algorithm_for_digest(cx, &algorithm) {
Ok(normalized_algorithm) => normalized_algorithm,
Err(e) => {
promise.reject_error(e);
return promise;
},
};
let (task_source, canceller) = self.task_source_with_canceller();
let trusted_promise = TrustedPromise::new(promise.clone());
let _ = task_source.queue_with_canceller(
task!(generate_key: move || {
let promise = trusted_promise.root();
let digest = match normalized_algorithm.digest(&data) {
Ok(digest) => digest,
Err(e) => {
promise.reject_error(e);
return;
}
};
let cx = GlobalScope::get_cx();
rooted!(in(*cx) let mut array_buffer_ptr = ptr::null_mut::<JSObject>());
create_buffer_source::<ArrayBufferU8>(cx, digest.as_ref(), array_buffer_ptr.handle_mut())
.expect("failed to create buffer source for exported key.");
promise.resolve_native(&*array_buffer_ptr);
}),
&canceller,
);
promise
}
fn GenerateKey(
&self,
cx: JSContext,
algorithm: AlgorithmIdentifier,
extractable: bool,
key_usages: Vec<KeyUsage>,
comp: InRealm,
can_gc: CanGc,
) -> Rc<Promise> {
let promise = Promise::new_in_current_realm(comp, can_gc);
let normalized_algorithm = match normalize_algorithm_for_generate_key(cx, &algorithm) {
Ok(algorithm) => algorithm,
Err(e) => {
promise.reject_error(e);
return promise;
},
};
let (task_source, canceller) = self.task_source_with_canceller();
let this = Trusted::new(self);
let trusted_promise = TrustedPromise::new(promise.clone());
let _ = task_source.queue_with_canceller(
task!(generate_key: move || {
let subtle = this.root();
let promise = trusted_promise.root();
let key = normalized_algorithm.generate_key(&subtle, key_usages, extractable);
match key {
Ok(key) => promise.resolve_native(&key),
Err(e) => promise.reject_error(e),
}
}),
&canceller,
);
promise
}
fn DeriveKey(
&self,
cx: SafeJSContext,
algorithm: AlgorithmIdentifier,
base_key: &CryptoKey,
derived_key_type: AlgorithmIdentifier,
extractable: bool,
key_usages: Vec<KeyUsage>,
comp: InRealm,
can_gc: CanGc,
) -> Rc<Promise> {
let promise = Promise::new_in_current_realm(comp, can_gc);
let normalized_algorithm = match normalize_algorithm_for_derive_bits(cx, &algorithm) {
Ok(algorithm) => algorithm,
Err(e) => {
promise.reject_error(e);
return promise;
},
};
let normalized_derived_key_algorithm_import =
match normalize_algorithm_for_import_key(cx, &derived_key_type) {
Ok(algorithm) => algorithm,
Err(e) => {
promise.reject_error(e);
return promise;
},
};
let normalized_derived_key_algorithm_length =
match normalize_algorithm_for_get_key_length(cx, &derived_key_type) {
Ok(algorithm) => algorithm,
Err(e) => {
promise.reject_error(e);
return promise;
},
};
let (task_source, canceller) = self.task_source_with_canceller();
let trusted_promise = TrustedPromise::new(promise.clone());
let trusted_base_key = Trusted::new(base_key);
let this = Trusted::new(self);
let _ = task_source.queue_with_canceller(
task!(derive_key: move || {
let promise = trusted_promise.root();
let base_key = trusted_base_key.root();
let subtle = this.root();
if !base_key.usages().contains(&KeyUsage::DeriveKey) {
promise.reject_error(Error::InvalidAccess);
return;
}
let length = match normalized_derived_key_algorithm_length.get_key_length() {
Ok(length) => length,
Err(e) => {
promise.reject_error(e);
return;
}
};
let secret = match normalized_algorithm.derive_bits(&base_key, Some(length as u32)){
Ok(secret) => secret,
Err(e) => {
promise.reject_error(e);
return;
}
};
let result = normalized_derived_key_algorithm_import.import_key(
&subtle,
KeyFormat::Raw,
&secret,
extractable,
key_usages
);
let result = match result {
Ok(key) => key,
Err(e) => {
promise.reject_error(e);
return;
}
};
if matches!(result.Type(), KeyType::Secret | KeyType::Private) && result.usages().is_empty() {
promise.reject_error(Error::Syntax);
return;
}
promise.resolve_native(&*result);
}),
&canceller,
);
promise
}
fn DeriveBits(
&self,
cx: SafeJSContext,
algorithm: AlgorithmIdentifier,
base_key: &CryptoKey,
length: Option<u32>,
comp: InRealm,
can_gc: CanGc,
) -> Rc<Promise> {
let promise = Promise::new_in_current_realm(comp, can_gc);
let normalized_algorithm = match normalize_algorithm_for_derive_bits(cx, &algorithm) {
Ok(algorithm) => algorithm,
Err(e) => {
promise.reject_error(e);
return promise;
},
};
let (task_source, canceller) = self.task_source_with_canceller();
let trusted_promise = TrustedPromise::new(promise.clone());
let trusted_base_key = Trusted::new(base_key);
let _ = task_source.queue_with_canceller(
task!(import_key: move || {
let promise = trusted_promise.root();
let base_key = trusted_base_key.root();
if !base_key.usages().contains(&KeyUsage::DeriveBits) {
promise.reject_error(Error::InvalidAccess);
return;
}
let cx = GlobalScope::get_cx();
rooted!(in(*cx) let mut array_buffer_ptr = ptr::null_mut::<JSObject>());
let result = match normalized_algorithm.derive_bits(&base_key, length) {
Ok(derived_bits) => derived_bits,
Err(e) => {
promise.reject_error(e);
return;
}
};
create_buffer_source::<ArrayBufferU8>(cx, &result, array_buffer_ptr.handle_mut())
.expect("failed to create buffer source for derived bits.");
promise.resolve_native(&*array_buffer_ptr);
}),
&canceller,
);
promise
}
fn ImportKey(
&self,
cx: JSContext,
format: KeyFormat,
key_data: ArrayBufferViewOrArrayBufferOrJsonWebKey,
algorithm: AlgorithmIdentifier,
extractable: bool,
key_usages: Vec<KeyUsage>,
comp: InRealm,
can_gc: CanGc,
) -> Rc<Promise> {
let promise = Promise::new_in_current_realm(comp, can_gc);
let normalized_algorithm = match normalize_algorithm_for_import_key(cx, &algorithm) {
Ok(algorithm) => algorithm,
Err(e) => {
promise.reject_error(e);
return promise;
},
};
let data = match key_data {
ArrayBufferViewOrArrayBufferOrJsonWebKey::ArrayBufferView(view) => view.to_vec(),
ArrayBufferViewOrArrayBufferOrJsonWebKey::JsonWebKey(json_web_key) => {
if let Some(mut data_string) = json_web_key.k {
while data_string.len() % 4 != 0 {
data_string.push_str("=");
}
match BASE64_STANDARD.decode(data_string.to_string()) {
Ok(data) => data,
Err(_) => {
promise.reject_error(Error::Syntax);
return promise;
},
}
} else {
promise.reject_error(Error::Syntax);
return promise;
}
},
ArrayBufferViewOrArrayBufferOrJsonWebKey::ArrayBuffer(array_buffer) => {
array_buffer.to_vec()
},
};
let (task_source, canceller) = self.task_source_with_canceller();
let this = Trusted::new(self);
let trusted_promise = TrustedPromise::new(promise.clone());
let _ = task_source.queue_with_canceller(
task!(import_key: move || {
let subtle = this.root();
let promise = trusted_promise.root();
let imported_key = normalized_algorithm.import_key(&subtle, format, &data, extractable, key_usages);
match imported_key {
Ok(k) => promise.resolve_native(&k),
Err(e) => promise.reject_error(e),
};
}),
&canceller,
);
promise
}
fn ExportKey(
&self,
format: KeyFormat,
key: &CryptoKey,
comp: InRealm,
can_gc: CanGc,
) -> Rc<Promise> {
let promise = Promise::new_in_current_realm(comp, can_gc);
let (task_source, canceller) = self.task_source_with_canceller();
let this = Trusted::new(self);
let trusted_key = Trusted::new(key);
let trusted_promise = TrustedPromise::new(promise.clone());
let _ = task_source.queue_with_canceller(
task!(export_key: move || {
let subtle = this.root();
let promise = trusted_promise.root();
let key = trusted_key.root();
let alg_name = key.algorithm();
if matches!(
alg_name.as_str(), ALG_SHA1 | ALG_SHA256 | ALG_SHA384 | ALG_SHA512 | ALG_HKDF | ALG_PBKDF2
) {
promise.reject_error(Error::NotSupported);
return;
}
if !key.Extractable() {
promise.reject_error(Error::InvalidAccess);
return;
}
let exported_key = match alg_name.as_str() {
ALG_AES_CBC | ALG_AES_CTR => subtle.export_key_aes(format, &key),
_ => Err(Error::NotSupported),
};
match exported_key {
Ok(k) => {
match k {
AesExportedKey::Raw(k) => {
let cx = GlobalScope::get_cx();
rooted!(in(*cx) let mut array_buffer_ptr = ptr::null_mut::<JSObject>());
create_buffer_source::<ArrayBufferU8>(cx, &k, array_buffer_ptr.handle_mut())
.expect("failed to create buffer source for exported key.");
promise.resolve_native(&array_buffer_ptr.get())
},
AesExportedKey::Jwk(k) => {
promise.resolve_native(&k)
},
}
},
Err(e) => promise.reject_error(e),
}
}),
&canceller,
);
promise
}
}
#[derive(Clone, Debug)]
pub struct SubtleAlgorithm {
#[allow(dead_code)]
pub name: String,
}
impl From<DOMString> for SubtleAlgorithm {
fn from(name: DOMString) -> Self {
SubtleAlgorithm {
name: name.to_string(),
}
}
}
#[derive(Clone, Debug)]
pub struct SubtleAesCbcParams {
#[allow(dead_code)]
pub name: String,
pub iv: Vec<u8>,
}
impl From<RootedTraceableBox<AesCbcParams>> for SubtleAesCbcParams {
fn from(params: RootedTraceableBox<AesCbcParams>) -> Self {
let iv = match ¶ms.iv {
ArrayBufferViewOrArrayBuffer::ArrayBufferView(view) => view.to_vec(),
ArrayBufferViewOrArrayBuffer::ArrayBuffer(buffer) => buffer.to_vec(),
};
SubtleAesCbcParams {
name: params.parent.name.to_string(),
iv,
}
}
}
#[derive(Clone, Debug)]
pub struct SubtleAesCtrParams {
pub name: String,
pub counter: Vec<u8>,
pub length: u8,
}
impl From<RootedTraceableBox<AesCtrParams>> for SubtleAesCtrParams {
fn from(params: RootedTraceableBox<AesCtrParams>) -> Self {
let counter = match ¶ms.counter {
ArrayBufferViewOrArrayBuffer::ArrayBufferView(view) => view.to_vec(),
ArrayBufferViewOrArrayBuffer::ArrayBuffer(buffer) => buffer.to_vec(),
};
SubtleAesCtrParams {
name: params.parent.name.to_string(),
counter,
length: params.length,
}
}
}
#[derive(Clone, Debug)]
pub struct SubtleAesKeyGenParams {
pub name: String,
pub length: u16,
}
impl From<AesKeyGenParams> for SubtleAesKeyGenParams {
fn from(params: AesKeyGenParams) -> Self {
SubtleAesKeyGenParams {
name: params.parent.name.to_string().to_uppercase(),
length: params.length,
}
}
}
#[derive(Clone, Debug)]
pub struct SubtlePbkdf2Params {
salt: Vec<u8>,
iterations: u32,
hash: DigestAlgorithm,
}
impl SubtlePbkdf2Params {
fn new(cx: JSContext, params: RootedTraceableBox<Pbkdf2Params>) -> Fallible<Self> {
let salt = match ¶ms.salt {
ArrayBufferViewOrArrayBuffer::ArrayBufferView(view) => view.to_vec(),
ArrayBufferViewOrArrayBuffer::ArrayBuffer(buffer) => buffer.to_vec(),
};
let params = Self {
salt,
iterations: params.iterations,
hash: normalize_algorithm_for_digest(cx, ¶ms.hash)?,
};
Ok(params)
}
}
enum GetKeyLengthAlgorithm {
Aes(u16),
}
#[derive(Clone, Copy, Debug)]
enum DigestAlgorithm {
Sha1,
Sha256,
Sha384,
Sha512,
}
enum ImportKeyAlgorithm {
AesCbc,
AesCtr,
Pbkdf2,
}
enum DeriveBitsAlgorithm {
Pbkdf2(SubtlePbkdf2Params),
}
enum EncryptionAlgorithm {
AesCbc(SubtleAesCbcParams),
AesCtr(SubtleAesCtrParams),
}
enum KeyGenerationAlgorithm {
Aes(SubtleAesKeyGenParams),
}
macro_rules! value_from_js_object {
($t: ty, $cx: ident, $value: ident) => {{
let params_result = <$t>::new($cx, $value.handle()).map_err(|_| Error::Operation)?;
let ConversionResult::Success(params) = params_result else {
return Err(Error::Syntax);
};
params
}};
}
fn normalize_algorithm_for_get_key_length(
cx: JSContext,
algorithm: &AlgorithmIdentifier,
) -> Result<GetKeyLengthAlgorithm, Error> {
match algorithm {
AlgorithmIdentifier::Object(obj) => {
rooted!(in(*cx) let value = ObjectValue(obj.get()));
let Ok(ConversionResult::Success(algorithm)) = Algorithm::new(cx, value.handle())
else {
return Err(Error::Syntax);
};
let name = algorithm.name.str();
let normalized_algorithm = if name.eq_ignore_ascii_case(ALG_AES_CBC) ||
name.eq_ignore_ascii_case(ALG_AES_CTR)
{
let params = value_from_js_object!(AesDerivedKeyParams, cx, value);
GetKeyLengthAlgorithm::Aes(params.length)
} else {
return Err(Error::NotSupported);
};
Ok(normalized_algorithm)
},
AlgorithmIdentifier::String(_) => {
Err(Error::NotSupported)
},
}
}
fn normalize_algorithm_for_digest(
cx: JSContext,
algorithm: &AlgorithmIdentifier,
) -> Result<DigestAlgorithm, Error> {
let name = match algorithm {
AlgorithmIdentifier::Object(obj) => {
rooted!(in(*cx) let value = ObjectValue(obj.get()));
let Ok(ConversionResult::Success(algorithm)) = Algorithm::new(cx, value.handle())
else {
return Err(Error::Syntax);
};
algorithm.name.str().to_uppercase()
},
AlgorithmIdentifier::String(name) => name.str().to_uppercase(),
};
let normalized_algorithm = match name.as_str() {
ALG_SHA1 => DigestAlgorithm::Sha1,
ALG_SHA256 => DigestAlgorithm::Sha256,
ALG_SHA384 => DigestAlgorithm::Sha384,
ALG_SHA512 => DigestAlgorithm::Sha512,
_ => return Err(Error::NotSupported),
};
Ok(normalized_algorithm)
}
fn normalize_algorithm_for_import_key(
cx: JSContext,
algorithm: &AlgorithmIdentifier,
) -> Result<ImportKeyAlgorithm, Error> {
let name = match algorithm {
AlgorithmIdentifier::Object(obj) => {
rooted!(in(*cx) let value = ObjectValue(obj.get()));
let Ok(ConversionResult::Success(algorithm)) = Algorithm::new(cx, value.handle())
else {
return Err(Error::Syntax);
};
algorithm.name.str().to_uppercase()
},
AlgorithmIdentifier::String(name) => name.str().to_uppercase(),
};
let normalized_algorithm = match name.as_str() {
ALG_AES_CBC => ImportKeyAlgorithm::AesCbc,
ALG_AES_CTR => ImportKeyAlgorithm::AesCtr,
ALG_PBKDF2 => ImportKeyAlgorithm::Pbkdf2,
_ => return Err(Error::NotSupported),
};
Ok(normalized_algorithm)
}
fn normalize_algorithm_for_derive_bits(
cx: JSContext,
algorithm: &AlgorithmIdentifier,
) -> Result<DeriveBitsAlgorithm, Error> {
let AlgorithmIdentifier::Object(obj) = algorithm else {
return Err(Error::NotSupported);
};
rooted!(in(*cx) let value = ObjectValue(obj.get()));
let Ok(ConversionResult::Success(algorithm)) = Algorithm::new(cx, value.handle()) else {
return Err(Error::Syntax);
};
let normalized_algorithm = if algorithm.name.str().eq_ignore_ascii_case(ALG_PBKDF2) {
let params = value_from_js_object!(Pbkdf2Params, cx, value);
let subtle_params = SubtlePbkdf2Params::new(cx, params)?;
DeriveBitsAlgorithm::Pbkdf2(subtle_params)
} else {
return Err(Error::NotSupported);
};
Ok(normalized_algorithm)
}
fn normalize_algorithm_for_encrypt_or_decrypt(
cx: JSContext,
algorithm: &AlgorithmIdentifier,
) -> Result<EncryptionAlgorithm, Error> {
let AlgorithmIdentifier::Object(obj) = algorithm else {
return Err(Error::NotSupported);
};
rooted!(in(*cx) let value = ObjectValue(obj.get()));
let Ok(ConversionResult::Success(algorithm)) = Algorithm::new(cx, value.handle()) else {
return Err(Error::Syntax);
};
let name = algorithm.name.str();
let normalized_algorithm = if name.eq_ignore_ascii_case(ALG_AES_CBC) {
let params = value_from_js_object!(AesCbcParams, cx, value);
EncryptionAlgorithm::AesCbc(params.into())
} else if name.eq_ignore_ascii_case(ALG_AES_CTR) {
let params = value_from_js_object!(AesCtrParams, cx, value);
EncryptionAlgorithm::AesCtr(params.into())
} else {
return Err(Error::NotSupported);
};
Ok(normalized_algorithm)
}
fn normalize_algorithm_for_generate_key(
cx: JSContext,
algorithm: &AlgorithmIdentifier,
) -> Result<KeyGenerationAlgorithm, Error> {
let AlgorithmIdentifier::Object(obj) = algorithm else {
return Err(Error::NotSupported);
};
rooted!(in(*cx) let value = ObjectValue(obj.get()));
let Ok(ConversionResult::Success(algorithm)) = Algorithm::new(cx, value.handle()) else {
return Err(Error::Syntax);
};
let name = algorithm.name.str();
let normalized_algorithm =
if name.eq_ignore_ascii_case(ALG_AES_CBC) || name.eq_ignore_ascii_case(ALG_AES_CTR) {
let params = value_from_js_object!(AesKeyGenParams, cx, value);
KeyGenerationAlgorithm::Aes(params.into())
} else {
return Err(Error::NotSupported);
};
Ok(normalized_algorithm)
}
impl SubtleCrypto {
fn encrypt_aes_cbc(
&self,
params: &SubtleAesCbcParams,
key: &CryptoKey,
data: &[u8],
cx: JSContext,
handle: MutableHandleObject,
) -> Result<(), Error> {
if params.iv.len() != 16 {
return Err(Error::Operation);
}
let plaintext = Vec::from(data);
let iv = GenericArray::from_slice(¶ms.iv);
let ct = match key.handle() {
Handle::Aes128(data) => {
let key_data = GenericArray::from_slice(data);
Aes128CbcEnc::new(key_data, iv).encrypt_padded_vec_mut::<Pkcs7>(&plaintext)
},
Handle::Aes192(data) => {
let key_data = GenericArray::from_slice(data);
Aes192CbcEnc::new(key_data, iv).encrypt_padded_vec_mut::<Pkcs7>(&plaintext)
},
Handle::Aes256(data) => {
let key_data = GenericArray::from_slice(data);
Aes256CbcEnc::new(key_data, iv).encrypt_padded_vec_mut::<Pkcs7>(&plaintext)
},
_ => return Err(Error::Data),
};
create_buffer_source::<ArrayBufferU8>(cx, &ct, handle)
.expect("failed to create buffer source for exported key.");
Ok(())
}
fn decrypt_aes_cbc(
&self,
params: &SubtleAesCbcParams,
key: &CryptoKey,
data: &[u8],
cx: JSContext,
handle: MutableHandleObject,
) -> Result<(), Error> {
if params.iv.len() != 16 {
return Err(Error::Operation);
}
let mut ciphertext = Vec::from(data);
let iv = GenericArray::from_slice(¶ms.iv);
let plaintext = match key.handle() {
Handle::Aes128(data) => {
let key_data = GenericArray::from_slice(data);
Aes128CbcDec::new(key_data, iv)
.decrypt_padded_mut::<Pkcs7>(ciphertext.as_mut_slice())
.map_err(|_| Error::Operation)?
},
Handle::Aes192(data) => {
let key_data = GenericArray::from_slice(data);
Aes192CbcDec::new(key_data, iv)
.decrypt_padded_mut::<Pkcs7>(ciphertext.as_mut_slice())
.map_err(|_| Error::Operation)?
},
Handle::Aes256(data) => {
let key_data = GenericArray::from_slice(data);
Aes256CbcDec::new(key_data, iv)
.decrypt_padded_mut::<Pkcs7>(ciphertext.as_mut_slice())
.map_err(|_| Error::Operation)?
},
_ => return Err(Error::Data),
};
create_buffer_source::<ArrayBufferU8>(cx, plaintext, handle)
.expect("failed to create buffer source for exported key.");
Ok(())
}
fn encrypt_decrypt_aes_ctr(
&self,
params: &SubtleAesCtrParams,
key: &CryptoKey,
data: &[u8],
cx: JSContext,
handle: MutableHandleObject,
) -> Result<(), Error> {
if params.counter.len() != 16 || params.length == 0 || params.length > 128 {
return Err(Error::Operation);
}
let mut ciphertext = Vec::from(data);
let counter = GenericArray::from_slice(¶ms.counter);
match key.handle() {
Handle::Aes128(data) => {
let key_data = GenericArray::from_slice(data);
Aes128Ctr::new(key_data, counter).apply_keystream(&mut ciphertext)
},
Handle::Aes192(data) => {
let key_data = GenericArray::from_slice(data);
Aes192Ctr::new(key_data, counter).apply_keystream(&mut ciphertext)
},
Handle::Aes256(data) => {
let key_data = GenericArray::from_slice(data);
Aes256Ctr::new(key_data, counter).apply_keystream(&mut ciphertext)
},
_ => return Err(Error::Data),
};
create_buffer_source::<ArrayBufferU8>(cx, &ciphertext, handle)
.expect("failed to create buffer source for exported key.");
Ok(())
}
#[allow(unsafe_code)]
fn generate_key_aes(
&self,
usages: Vec<KeyUsage>,
key_gen_params: &SubtleAesKeyGenParams,
extractable: bool,
) -> Result<DomRoot<CryptoKey>, Error> {
let mut rand = vec![0; key_gen_params.length as usize];
self.rng.borrow_mut().fill_bytes(&mut rand);
let handle = match key_gen_params.length {
128 => Handle::Aes128(rand),
192 => Handle::Aes192(rand),
256 => Handle::Aes256(rand),
_ => return Err(Error::Operation),
};
if usages.iter().any(|usage| {
!matches!(
usage,
KeyUsage::Encrypt | KeyUsage::Decrypt | KeyUsage::WrapKey | KeyUsage::UnwrapKey
)
}) || usages.is_empty()
{
return Err(Error::Syntax);
}
let name = match key_gen_params.name.as_str() {
ALG_AES_CBC => DOMString::from(ALG_AES_CBC),
ALG_AES_CTR => DOMString::from(ALG_AES_CTR),
_ => return Err(Error::NotSupported),
};
let cx = GlobalScope::get_cx();
rooted!(in(*cx) let mut algorithm_object = unsafe {JS_NewObject(*cx, ptr::null()) });
assert!(!algorithm_object.is_null());
AesKeyAlgorithm::from_name_and_size(
name.clone(),
key_gen_params.length,
algorithm_object.handle_mut(),
cx,
);
let crypto_key = CryptoKey::new(
&self.global(),
KeyType::Secret,
extractable,
name,
algorithm_object.handle(),
usages,
handle,
);
Ok(crypto_key)
}
#[allow(unsafe_code)]
fn import_key_aes(
&self,
format: KeyFormat,
data: &[u8],
extractable: bool,
usages: Vec<KeyUsage>,
alg_name: &str,
) -> Result<DomRoot<CryptoKey>, Error> {
if usages.iter().any(|usage| {
!matches!(
usage,
KeyUsage::Encrypt | KeyUsage::Decrypt | KeyUsage::WrapKey | KeyUsage::UnwrapKey
)
}) || usages.is_empty()
{
return Err(Error::Syntax);
}
if !matches!(format, KeyFormat::Raw | KeyFormat::Jwk) {
return Err(Error::NotSupported);
}
let handle = match data.len() * 8 {
128 => Handle::Aes128(data.to_vec()),
192 => Handle::Aes192(data.to_vec()),
256 => Handle::Aes256(data.to_vec()),
_ => return Err(Error::Data),
};
let name = DOMString::from(alg_name.to_string());
let cx = GlobalScope::get_cx();
rooted!(in(*cx) let mut algorithm_object = unsafe {JS_NewObject(*cx, ptr::null()) });
assert!(!algorithm_object.is_null());
AesKeyAlgorithm::from_name_and_size(
name.clone(),
(data.len() * 8) as u16,
algorithm_object.handle_mut(),
cx,
);
let crypto_key = CryptoKey::new(
&self.global(),
KeyType::Secret,
extractable,
name,
algorithm_object.handle(),
usages,
handle,
);
Ok(crypto_key)
}
fn export_key_aes(&self, format: KeyFormat, key: &CryptoKey) -> Result<AesExportedKey, Error> {
match format {
KeyFormat::Raw => match key.handle() {
Handle::Aes128(key_data) => Ok(AesExportedKey::Raw(key_data.as_slice().to_vec())),
Handle::Aes192(key_data) => Ok(AesExportedKey::Raw(key_data.as_slice().to_vec())),
Handle::Aes256(key_data) => Ok(AesExportedKey::Raw(key_data.as_slice().to_vec())),
_ => Err(Error::Data),
},
KeyFormat::Jwk => {
let (alg, k) = match key.handle() {
Handle::Aes128(key_data) => {
data_to_jwk_params(key.algorithm().as_str(), "128", key_data.as_slice())
},
Handle::Aes192(key_data) => {
data_to_jwk_params(key.algorithm().as_str(), "192", key_data.as_slice())
},
Handle::Aes256(key_data) => {
data_to_jwk_params(key.algorithm().as_str(), "256", key_data.as_slice())
},
_ => return Err(Error::Data),
};
let jwk = JsonWebKey {
alg: Some(alg),
crv: None,
d: None,
dp: None,
dq: None,
e: None,
ext: Some(key.Extractable()),
k: Some(k),
key_ops: None,
kty: Some(DOMString::from("oct")),
n: None,
oth: None,
p: None,
q: None,
qi: None,
use_: None,
x: None,
y: None,
};
Ok(AesExportedKey::Jwk(Box::new(jwk)))
},
_ => Err(Error::NotSupported),
}
}
#[allow(unsafe_code)]
fn import_key_pbkdf2(
&self,
format: KeyFormat,
data: &[u8],
extractable: bool,
usages: Vec<KeyUsage>,
) -> Result<DomRoot<CryptoKey>, Error> {
if format != KeyFormat::Raw {
return Err(Error::NotSupported);
}
if usages
.iter()
.any(|usage| !matches!(usage, KeyUsage::DeriveKey | KeyUsage::DeriveBits))
{
return Err(Error::Syntax);
}
if extractable {
return Err(Error::Syntax);
}
let name = DOMString::from(ALG_PBKDF2);
let cx = GlobalScope::get_cx();
rooted!(in(*cx) let mut algorithm_object = unsafe {JS_NewObject(*cx, ptr::null()) });
assert!(!algorithm_object.is_null());
KeyAlgorithm::from_name(name.clone(), algorithm_object.handle_mut(), cx);
let key = CryptoKey::new(
&self.global(),
KeyType::Secret,
extractable,
name,
algorithm_object.handle(),
usages,
Handle::Pbkdf2(data.to_vec()),
);
Ok(key)
}
}
pub enum AesExportedKey {
Raw(Vec<u8>),
Jwk(Box<JsonWebKey>),
}
fn data_to_jwk_params(alg: &str, size: &str, key: &[u8]) -> (DOMString, DOMString) {
let jwk_alg = match alg {
ALG_AES_CBC => DOMString::from(format!("A{}CBC", size)),
ALG_AES_CTR => DOMString::from(format!("A{}CTR", size)),
_ => unreachable!(),
};
let mut data = BASE64_STANDARD.encode(key);
data.retain(|c| c != '=');
(jwk_alg, DOMString::from(data))
}
impl KeyAlgorithm {
#[allow(unsafe_code)]
fn from_name(name: DOMString, out: MutableHandleObject, cx: JSContext) {
let key_algorithm = Self { name };
unsafe {
key_algorithm.to_jsobject(*cx, out);
}
}
}
impl AesKeyAlgorithm {
#[allow(unsafe_code)]
fn from_name_and_size(name: DOMString, size: u16, out: MutableHandleObject, cx: JSContext) {
let key_algorithm = Self {
parent: KeyAlgorithm { name },
length: size,
};
unsafe {
key_algorithm.to_jsobject(*cx, out);
}
}
}
impl SubtlePbkdf2Params {
fn derive_bits(&self, key: &CryptoKey, length: Option<u32>) -> Result<Vec<u8>, Error> {
let Some(length) = length else {
return Err(Error::Operation);
};
if length == 0 || length % 8 != 0 {
return Err(Error::Operation);
};
let Ok(iterations) = NonZero::<u32>::try_from(self.iterations) else {
return Err(Error::Operation);
};
let prf = match self.hash {
DigestAlgorithm::Sha1 => pbkdf2::PBKDF2_HMAC_SHA1,
DigestAlgorithm::Sha256 => pbkdf2::PBKDF2_HMAC_SHA256,
DigestAlgorithm::Sha384 => pbkdf2::PBKDF2_HMAC_SHA384,
DigestAlgorithm::Sha512 => pbkdf2::PBKDF2_HMAC_SHA512,
};
let mut result = vec![0; length as usize / 8];
pbkdf2::derive(
prf,
iterations,
&self.salt,
key.handle().as_bytes(),
&mut result,
);
Ok(result)
}
}
fn get_key_length_for_aes(length: u16) -> Result<u16, Error> {
if !matches!(length, 128 | 192 | 256) {
return Err(Error::Operation);
}
Ok(length)
}
impl GetKeyLengthAlgorithm {
fn get_key_length(&self) -> Result<u16, Error> {
match self {
Self::Aes(length) => get_key_length_for_aes(*length),
}
}
}
impl DigestAlgorithm {
fn digest(&self, data: &[u8]) -> Result<impl AsRef<[u8]>, Error> {
let algorithm = match self {
Self::Sha1 => &digest::SHA1_FOR_LEGACY_USE_ONLY,
Self::Sha256 => &digest::SHA256,
Self::Sha384 => &digest::SHA384,
Self::Sha512 => &digest::SHA512,
};
Ok(digest::digest(algorithm, data))
}
}
impl ImportKeyAlgorithm {
fn import_key(
&self,
subtle: &SubtleCrypto,
format: KeyFormat,
secret: &[u8],
extractable: bool,
key_usages: Vec<KeyUsage>,
) -> Result<DomRoot<CryptoKey>, Error> {
match self {
Self::AesCbc => {
subtle.import_key_aes(format, secret, extractable, key_usages, ALG_AES_CBC)
},
Self::AesCtr => {
subtle.import_key_aes(format, secret, extractable, key_usages, ALG_AES_CTR)
},
Self::Pbkdf2 => subtle.import_key_pbkdf2(format, secret, extractable, key_usages),
}
}
}
impl DeriveBitsAlgorithm {
fn derive_bits(&self, key: &CryptoKey, length: Option<u32>) -> Result<Vec<u8>, Error> {
match self {
Self::Pbkdf2(pbkdf2_params) => pbkdf2_params.derive_bits(key, length),
}
}
}
impl EncryptionAlgorithm {
fn name(&self) -> &str {
match self {
Self::AesCbc(key_gen_params) => &key_gen_params.name,
Self::AesCtr(key_gen_params) => &key_gen_params.name,
}
}
fn encrypt(
&self,
subtle: &SubtleCrypto,
key: &CryptoKey,
data: &[u8],
cx: JSContext,
result: MutableHandleObject,
) -> Result<(), Error> {
match self {
Self::AesCbc(key_gen_params) => {
subtle.encrypt_aes_cbc(key_gen_params, key, data, cx, result)
},
Self::AesCtr(key_gen_params) => {
subtle.encrypt_decrypt_aes_ctr(key_gen_params, key, data, cx, result)
},
}
}
fn decrypt(
&self,
subtle: &SubtleCrypto,
key: &CryptoKey,
data: &[u8],
cx: JSContext,
result: MutableHandleObject,
) -> Result<(), Error> {
match self {
Self::AesCbc(key_gen_params) => {
subtle.decrypt_aes_cbc(key_gen_params, key, data, cx, result)
},
Self::AesCtr(key_gen_params) => {
subtle.encrypt_decrypt_aes_ctr(key_gen_params, key, data, cx, result)
},
}
}
}
impl KeyGenerationAlgorithm {
fn generate_key(
&self,
subtle: &SubtleCrypto,
usages: Vec<KeyUsage>,
extractable: bool,
) -> Result<DomRoot<CryptoKey>, Error> {
match self {
Self::Aes(params) => subtle.generate_key_aes(usages, params, extractable),
}
}
}