/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ use std::ptr; use std::rc::Rc; use dom_struct::dom_struct; use js::conversions::ConversionResult; use js::jsapi::JSObject; use js::jsval::ObjectValue; use js::typedarray::ArrayBufferU8; 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::{ AesKeyGenParams, Algorithm, AlgorithmIdentifier, KeyAlgorithm, KeyFormat, SubtleCryptoMethods, }; use crate::dom::bindings::error::Error; 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::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::JSContext; use crate::task::TaskCanceller; use crate::task_source::dom_manipulation::DOMManipulationTaskSource; use crate::task_source::TaskSource; // String constants for algorithms/curves 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 = "SHA1"; const ALG_SHA256: &str = "SHA256"; const ALG_SHA384: &str = "SHA384"; const ALG_SHA512: &str = "SHA512"; 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]; #[dom_struct] pub struct SubtleCrypto { reflector_: Reflector, #[no_trace] rng: DomRefCell, } impl SubtleCrypto { fn new_inherited() -> SubtleCrypto { SubtleCrypto { reflector_: Reflector::new(), rng: DomRefCell::new(ServoRng::default()), } } pub(crate) fn new(global: &GlobalScope) -> DomRoot { 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 .task_manager() .dom_manipulation_task_source_with_canceller() } else if let Some(worker_global) = self.global().downcast::() { 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 GenerateKey( &self, cx: JSContext, algorithm: AlgorithmIdentifier, extractable: bool, key_usages: Vec, comp: InRealm, ) -> Rc { let normalized_algorithm = normalize_algorithm(cx, algorithm, "generateKey"); let promise = Promise::new_in_current_realm(comp); if let Err(e) = normalized_algorithm { 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 alg = normalized_algorithm.clone(); let _ = task_source.queue_with_canceller( task!(generate_key: move || { let subtle = this.root(); let promise = trusted_promise.root(); let key = match alg { Ok(NormalizedAlgorithm::AesKeyGenParams(key_gen_params)) => { subtle.generate_key_aes_cbc(key_usages, key_gen_params, extractable) }, _ => Err(Error::NotSupported), }; match key { Ok(key) => promise.resolve_native(&key), Err(e) => promise.reject_error(e), } }), &canceller, ); promise } /// #[allow(unsafe_code)] fn ExportKey(&self, format: KeyFormat, key: &CryptoKey, comp: InRealm) -> Rc { let promise = Promise::new_in_current_realm(comp); 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 => subtle.export_key_aes_cbc(format, &key), _ => Err(Error::NotSupported), }; match exported_key { Ok(k) => { let cx = GlobalScope::get_cx(); rooted!(in(*cx) let mut array_buffer_ptr = ptr::null_mut::()); create_buffer_source::(cx, &k, array_buffer_ptr.handle_mut()) .expect("failed to create buffer source for exported key."); promise.resolve_native(&array_buffer_ptr.get()) }, Err(e) => promise.reject_error(e), } }), &canceller, ); promise } } #[derive(Clone)] pub enum NormalizedAlgorithm { #[allow(dead_code)] Algorithm(SubtleAlgorithm), AesKeyGenParams(SubtleAesKeyGenParams), } // These "subtle" structs are proxies for the codegen'd dicts which don't hold a DOMString // so they can be sent safely when running steps in parallel. #[derive(Clone)] pub struct SubtleAlgorithm { #[allow(dead_code)] pub name: String, } impl From for SubtleAlgorithm { fn from(name: DOMString) -> Self { SubtleAlgorithm { name: name.to_string(), } } } #[derive(Clone)] pub struct SubtleAesKeyGenParams { #[allow(dead_code)] pub name: String, pub length: u16, } impl From for SubtleAesKeyGenParams { fn from(params: AesKeyGenParams) -> Self { SubtleAesKeyGenParams { name: params.parent.name.to_string(), length: params.length, } } } /// #[allow(unsafe_code)] fn normalize_algorithm( cx: JSContext, algorithm: AlgorithmIdentifier, operation: &str, ) -> Result { match algorithm { AlgorithmIdentifier::String(name) => Ok(NormalizedAlgorithm::Algorithm(name.into())), AlgorithmIdentifier::Object(obj) => { rooted!(in(*cx) let value = ObjectValue(unsafe { *obj.get_unsafe() })); let Ok(ConversionResult::Success(algorithm)) = Algorithm::new(cx, value.handle()) else { return Err(Error::Syntax); }; match (algorithm.name.str().to_uppercase().as_str(), operation) { (ALG_AES_CBC, "generateKey") => { let params_result = AesKeyGenParams::new(cx, value.handle()).map_err(|_| Error::Operation)?; let ConversionResult::Success(params) = params_result else { return Err(Error::Syntax); }; Ok(NormalizedAlgorithm::AesKeyGenParams(params.into())) }, _ => Err(Error::NotSupported), } }, } } impl SubtleCrypto { /// fn generate_key_aes_cbc( &self, usages: Vec, key_gen_params: SubtleAesKeyGenParams, extractable: bool, ) -> Result, Error> { if !matches!(key_gen_params.length, 128 | 192 | 256) { 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 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), }; Ok(CryptoKey::new( &self.global(), KeyType::Secret, extractable, KeyAlgorithm { name: DOMString::from(ALG_AES_CBC), }, usages, handle, )) } /// fn export_key_aes_cbc(&self, format: KeyFormat, key: &CryptoKey) -> Result, Error> { match format { KeyFormat::Raw => match key.handle() { Handle::Aes128(key) => Ok(key.as_slice().to_vec()), Handle::Aes192(key) => Ok(key.as_slice().to_vec()), Handle::Aes256(key) => Ok(key.as_slice().to_vec()), }, KeyFormat::Jwk => { // TODO: Support jwk Err(Error::NotSupported) }, _ => Err(Error::NotSupported), } } }