diff --git a/components/script/dom/cryptokey.rs b/components/script/dom/cryptokey.rs index d98afff0e11..6d9a6c562be 100644 --- a/components/script/dom/cryptokey.rs +++ b/components/script/dom/cryptokey.rs @@ -10,6 +10,7 @@ use js::jsapi::{Heap, JSObject, Value}; use js::rust::HandleObject; use script_bindings::conversions::SafeToJSValConvertible; +use crate::dom::bindings::cell::DomRefCell; use crate::dom::bindings::codegen::Bindings::CryptoKeyBinding::{ CryptoKeyMethods, KeyType, KeyUsage, }; @@ -54,7 +55,7 @@ pub(crate) struct CryptoKey { algorithm_object: Heap<*mut JSObject>, /// - usages: Vec, + usages: DomRefCell>, #[no_trace] handle: Handle, } @@ -73,7 +74,7 @@ impl CryptoKey { extractable: Cell::new(extractable), algorithm, algorithm_object: Heap::default(), - usages, + usages: DomRefCell::new(usages), handle, } } @@ -110,13 +111,21 @@ impl CryptoKey { self.algorithm.to_string() } - pub(crate) fn usages(&self) -> &[KeyUsage] { - &self.usages + pub(crate) fn usages(&self) -> Vec { + self.usages.borrow().clone() } pub(crate) fn handle(&self) -> &Handle { &self.handle } + + pub(crate) fn set_extractable(&self, extractable: bool) { + self.extractable.set(extractable); + } + + pub(crate) fn set_usages(&self, usages: &[KeyUsage]) { + *self.usages.borrow_mut() = usages.to_owned(); + } } impl CryptoKeyMethods for CryptoKey { @@ -138,7 +147,7 @@ impl CryptoKeyMethods for CryptoKey { /// fn Usages(&self, cx: JSContext) -> NonNull { rooted!(in(*cx) let mut usages: Value); - self.usages.safe_to_jsval(cx, usages.handle_mut()); + self.usages.borrow().safe_to_jsval(cx, usages.handle_mut()); NonNull::new(usages.to_object()).unwrap() } } diff --git a/components/script/dom/subtlecrypto.rs b/components/script/dom/subtlecrypto.rs index d866cc58641..12aeb799735 100644 --- a/components/script/dom/subtlecrypto.rs +++ b/components/script/dom/subtlecrypto.rs @@ -39,11 +39,12 @@ use crate::dom::bindings::codegen::Bindings::SubtleCryptoBinding::{ use crate::dom::bindings::codegen::UnionTypes::{ ArrayBufferViewOrArrayBuffer, ArrayBufferViewOrArrayBufferOrJsonWebKey, }; +use crate::dom::bindings::conversions::SafeToJSValConvertible; use crate::dom::bindings::error::{Error, Fallible}; use crate::dom::bindings::refcounted::{Trusted, TrustedPromise}; use crate::dom::bindings::reflector::{DomGlobal, Reflector, reflect_dom_object}; use crate::dom::bindings::root::DomRoot; -use crate::dom::bindings::str::DOMString; +use crate::dom::bindings::str::{DOMString, serialize_jsval_to_json_utf8}; use crate::dom::bindings::trace::RootedTraceableBox; use crate::dom::cryptokey::{CryptoKey, Handle}; use crate::dom::globalscope::GlobalScope; @@ -751,42 +752,84 @@ impl SubtleCryptoMethods for SubtleCrypto { comp: InRealm, can_gc: CanGc, ) -> Rc { + // Step 1. Let format, algorithm, extractable and usages, be the format, algorithm, + // extractable and keyUsages parameters passed to the importKey() method, respectively. + + // Step 5. Let realm be the relevant realm of this. + // Step 6. Let promise be a new Promise. let promise = Promise::new_in_current_realm(comp, can_gc); + + // Step 2. + let key_data = match format { + // If format is equal to the string "raw", "pkcs8", or "spki": + KeyFormat::Raw | KeyFormat::Pkcs8 | KeyFormat::Spki => { + match key_data { + // Step 2.1. If the keyData parameter passed to the importKey() method is a + // JsonWebKey dictionary, throw a TypeError. + ArrayBufferViewOrArrayBufferOrJsonWebKey::JsonWebKey(_) => { + promise.reject_error( + Error::Type("The keyData type does not match the format".to_string()), + can_gc, + ); + return promise; + }, + + // Step 2.2. Let keyData be the result of getting a copy of the bytes held by + // the keyData parameter passed to the importKey() method. + ArrayBufferViewOrArrayBufferOrJsonWebKey::ArrayBufferView(view) => { + view.to_vec() + }, + ArrayBufferViewOrArrayBufferOrJsonWebKey::ArrayBuffer(buffer) => { + buffer.to_vec() + }, + } + }, + // If format is equal to the string "jwk": + KeyFormat::Jwk => { + match key_data { + ArrayBufferViewOrArrayBufferOrJsonWebKey::ArrayBufferView(_) | + ArrayBufferViewOrArrayBufferOrJsonWebKey::ArrayBuffer(_) => { + // Step 2.1. If the keyData parameter passed to the importKey() method is + // not a JsonWebKey dictionary, throw a TypeError. + promise.reject_error( + Error::Type("The keyData type does not match the format".to_string()), + can_gc, + ); + return promise; + }, + + ArrayBufferViewOrArrayBufferOrJsonWebKey::JsonWebKey(jwk) => { + // Step 2.2. Let keyData be the keyData parameter passed to the importKey() method. + // + // NOTE: Serialize JsonWebKey throught stringifying it. + // JsonWebKey::stringify internally relies on ToJSON, so it will raise an + // exception when a JS error is thrown. When this happens, we report the + // error. + match jwk.stringify(cx) { + Ok(stringified) => stringified.as_bytes().to_vec(), + Err(error) => { + promise.reject_error(error, can_gc); + return promise; + }, + } + }, + } + }, + }; + + // Step 3. Let normalizedAlgorithm be the result of normalizing an algorithm, with alg set + // to algorithm and op set to "importKey". let normalized_algorithm = match normalize_algorithm_for_import_key(cx, &algorithm, can_gc) { Ok(algorithm) => algorithm, - Err(e) => { - promise.reject_error(e, can_gc); + Err(error) => { + // Step 4. If an error occurred, return a Promise rejected with normalizedAlgorithm. + promise.reject_error(error, can_gc); return promise; }, }; - let data = match key_data { - ArrayBufferViewOrArrayBufferOrJsonWebKey::ArrayBufferView(view) => view.to_vec(), - ArrayBufferViewOrArrayBufferOrJsonWebKey::JsonWebKey(json_web_key) => { - let data_string = match json_web_key.k { - Some(s) => s.to_string(), - None => { - promise.reject_error(Error::Syntax(None), can_gc); - return promise; - }, - }; - - match base64::engine::general_purpose::STANDARD_NO_PAD - .decode(data_string.as_bytes()) - { - Ok(data) => data, - Err(_) => { - promise.reject_error(Error::Syntax(None), can_gc); - return promise; - }, - } - }, - ArrayBufferViewOrArrayBufferOrJsonWebKey::ArrayBuffer(array_buffer) => { - array_buffer.to_vec() - }, - }; - + // Step 7. Return promise and perform the remaining steps in parallel. let this = Trusted::new(self); let trusted_promise = TrustedPromise::new(promise.clone()); self.global() @@ -795,12 +838,50 @@ impl SubtleCryptoMethods for SubtleCrypto { .queue(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, CanGc::note()); - match imported_key { - Ok(k) => promise.resolve_native(&k, CanGc::note()), - Err(e) => promise.reject_error(e, CanGc::note()), + + // TODO: Step 8. If the following steps or referenced procedures say to throw an + // error, queue a global task on the crypto task source, given realm's global + // object, to reject promise with the returned error; and then terminate the + // algorithm. + + // Step 9. Let result be the CryptoKey object that results from performing the + // import key operation specified by normalizedAlgorithm using keyData, algorithm, + // format, extractable and usages. + let result = match normalized_algorithm.import_key( + &subtle, + format, + &key_data, + extractable, + key_usages.clone(), + CanGc::note() + ) { + Ok(key) => key, + Err(error) => { + promise.reject_error(error, CanGc::note()); + return; + }, }; + + // Step 10. If the [[type]] internal slot of result is "secret" or "private" and + // usages is empty, then throw a SyntaxError. + if matches!(result.Type(), KeyType::Secret | KeyType::Private) && key_usages.is_empty() { + promise.reject_error(Error::Syntax(None), CanGc::note()); + return; + } + + // Step 11. Set the [[extractable]] internal slot of result to extractable. + result.set_extractable(extractable); + + // Step 12. Set the [[usages]] internal slot of result to the normalized value of usages. + result.set_usages(&key_usages); + + // TODO: Step 13. Queue a global task on the crypto task source, given realm's + // global object, to perform the remaining steps. + + // Step 14. Let result be the result of converting result to an ECMAScript Object + // in realm, as defined by [WebIDL]. + // Step 15. Resolve promise with result. + promise.resolve_native(&result, CanGc::note()); })); promise @@ -1076,18 +1157,37 @@ impl SubtleCryptoMethods for SubtleCrypto { }, }; + // Step 15. let import_key_bytes = match format { - KeyFormat::Raw | KeyFormat::Spki | KeyFormat::Pkcs8 => bytes, + // If format is equal to the strings "raw", "pkcs8", or "spki": + KeyFormat::Raw | KeyFormat::Pkcs8 | KeyFormat::Spki => { + // Let key be bytes. + bytes + }, + // If format is equal to the string "jwk": KeyFormat::Jwk => { - match parse_jwk(cx, &bytes, normalized_key_algorithm.clone(), extractable, &key_usages) { - Ok(bytes) => bytes, - Err(e) => { - promise.reject_error(e, CanGc::note()); + // Let key be the result of executing the parse a JWK algorithm, with bytes + // as the data to be parsed. + let jwk = match JsonWebKey::parse(cx, &bytes) { + Ok(jwk) => jwk, + Err(error) => { + promise.reject_error(error, CanGc::note()); return; - } + }, + }; + + // NOTE: Further convert the stringified JsonWebKey to the bytes so that we + // can pass it to normialized algorithm. + match jwk.stringify(cx) { + Ok(stringified) => stringified.as_bytes().to_vec(), + Err(error) => { + promise.reject_error(error, CanGc::note()); + return; + }, } }, }; + match normalized_key_algorithm.import_key(&subtle, format, &import_key_bytes, extractable, key_usages, CanGc::note()) { Ok(imported_key) => promise.resolve_native(&imported_key, CanGc::note()), @@ -2375,18 +2475,22 @@ impl SubtleCrypto { Ok(key) } - /// - /// + /// + /// + /// + /// #[allow(unsafe_code)] fn import_key_aes( &self, format: KeyFormat, - data: &[u8], + key_data: &[u8], extractable: bool, usages: Vec, alg_name: &str, can_gc: CanGc, ) -> Result, Error> { + // Step 1. If usages contains an entry which is not one of "encrypt", "decrypt", "wrapKey" + // or "unwrapKey", then throw a SyntaxError. if usages.iter().any(|usage| { !matches!( usage, @@ -2396,9 +2500,134 @@ impl SubtleCrypto { { return Err(Error::Syntax(None)); } - if !matches!(format, KeyFormat::Raw | KeyFormat::Jwk) { - return Err(Error::NotSupported); - } + + // Step 2. + let data; + match format { + // If format is "raw": + KeyFormat::Raw => { + // Step 2.1. Let data be keyData. + data = key_data.to_vec(); + + // Step 2.2. If the length in bits of data is not 128, 192 or 256 then throw a DataError. + if !matches!(data.len() * 8, 128 | 192 | 256) { + return Err(Error::Data); + } + }, + // If format is "jwk": + KeyFormat::Jwk => { + // Step 2.1. If keyData is a JsonWebKey dictionary: Let jwk equal keyData. + // Otherwise: Throw a DataError. + // NOTE: Deserialize keyData to JsonWebKey dictionary by calling JsonWebKey::parse + let jwk = JsonWebKey::parse(GlobalScope::get_cx(), key_data)?; + + // Step 2.2. If the kty field of jwk is not "oct", then throw a DataError. + if jwk.kty.as_ref().is_none_or(|kty| kty != "oct") { + return Err(Error::Data); + } + + // Step 2.3. If jwk does not meet the requirements of Section 6.4 of JSON Web + // Algorithms [JWA], then throw a DataError. + // NOTE: Done by Step 2.4 and 2.5. + + // Step 2.4. Let data be the byte sequence obtained by decoding the k field of jwk. + data = base64::engine::general_purpose::STANDARD_NO_PAD + .decode(jwk.k.as_ref().ok_or(Error::Data)?.as_bytes()) + .map_err(|_| Error::Data)?; + + // NOTE: This function is shared by AES-CBC, AES-CTR, AES-GCM and AES-KW. + // Different static texts are used in different AES types, in the following step. + let alg_matching = match alg_name { + ALG_AES_CBC => ["A128CBC", "A192CBC", "A256CBC"], + ALG_AES_CTR => ["A128CTR", "A192CTR", "A256CTR"], + ALG_AES_GCM => ["A128GCM", "A192GCM", "A256GCM"], + ALG_AES_KW => ["A128KW", "A192KW", "A256KW"], + _ => unreachable!(), + }; + + // Step 2.5. + match data.len() * 8 { + // If the length in bits of data is 128: + 128 => { + // If the alg field of jwk is present, and is not "A128CBC", then throw a DataError. + // If the alg field of jwk is present, and is not "A128CTR", then throw a DataError. + // If the alg field of jwk is present, and is not "A128GCM", then throw a DataError. + // If the alg field of jwk is present, and is not "A128KW", then throw a DataError. + // NOTE: Only perform the step of the corresponding AES type. + if jwk.alg.as_ref().is_some_and(|alg| alg != alg_matching[0]) { + return Err(Error::Data); + } + }, + // If the length in bits of data is 192: + 192 => { + // If the alg field of jwk is present, and is not "A192CBC", then throw a DataError. + // If the alg field of jwk is present, and is not "A192CTR", then throw a DataError. + // If the alg field of jwk is present, and is not "A192GCM", then throw a DataError. + // If the alg field of jwk is present, and is not "A192KW", then throw a DataError. + // NOTE: Only perform the step of the corresponding AES type. + if jwk.alg.as_ref().is_some_and(|alg| alg != alg_matching[1]) { + return Err(Error::Data); + } + }, + // If the length in bits of data is 256: + 256 => { + // If the alg field of jwk is present, and is not "A256CBC", then throw a DataError. + // If the alg field of jwk is present, and is not "A256CTR", then throw a DataError. + // If the alg field of jwk is present, and is not "A256GCM", then throw a DataError. + // If the alg field of jwk is present, and is not "A256KW", then throw a DataError. + // NOTE: Only perform the step of the corresponding AES type. + if jwk.alg.as_ref().is_some_and(|alg| alg != alg_matching[2]) { + return Err(Error::Data); + } + }, + // Otherwise: + _ => { + // throw a DataError. + return Err(Error::Data); + }, + } + + // Step 2.6. If usages is non-empty and the use field of jwk is present and is not + // "enc", then throw a DataError. + if !usages.is_empty() && jwk.use_.as_ref().is_some_and(|use_| use_ != "enc") { + return Err(Error::Data); + } + + // Step 2.7. If the key_ops field of jwk is present, and is invalid according to + // the requirements of JSON Web Key [JWK] or does not contain all of the specified + // usages values, then throw a DataError. + jwk.check_key_ops(&usages)?; + + // Step 2.8. If the ext field of jwk is present and has the value false and + // extractable is true, then throw a DataError. + if jwk.ext.is_some_and(|ext| !ext) && extractable { + return Err(Error::Data); + } + }, + // Otherwise: + _ => { + // throw a NotSupportedError + return Err(Error::NotSupported); + }, + }; + + // Step 5. Let algorithm be a new AesKeyAlgorithm. + // Step 6. Set the name attribute of algorithm to "AES-CBC". + // Step 7. Set the length attribute of algorithm to the length, in bits, of 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, + ); + + // Step 3. Let key be a new CryptoKey object representing an AES key with value data. + // Step 4. Set the [[type]] internal slot of key to "secret". + // Step 8. Set the [[algorithm]] internal slot of key to algorithm. let handle = match data.len() * 8 { 128 => Handle::Aes128(data.to_vec()), 192 => Handle::Aes192(data.to_vec()), @@ -2407,20 +2636,7 @@ impl SubtleCrypto { 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( + let key = CryptoKey::new( &self.global(), KeyType::Secret, extractable, @@ -2431,61 +2647,121 @@ impl SubtleCrypto { can_gc, ); - Ok(crypto_key) + // Return key. + Ok(key) } - /// - /// + /// + /// + /// + /// fn export_key_aes(&self, format: KeyFormat, key: &CryptoKey) -> Result { + // Step 1. If the underlying cryptographic key material represented by the [[handle]] + // internal slot of key cannot be accessed, then throw an OperationError. + // NOTE: key.handle() guarantees access. + + // Step 2. + let result; match format { + // If format is "raw": KeyFormat::Raw => match key.handle() { - Handle::Aes128(key_data) => Ok(ExportedKey::Raw(key_data.as_slice().to_vec())), - Handle::Aes192(key_data) => Ok(ExportedKey::Raw(key_data.as_slice().to_vec())), - Handle::Aes256(key_data) => Ok(ExportedKey::Raw(key_data.as_slice().to_vec())), - _ => Err(Error::Data), + // Step 2.1. Let data be a byte sequence containing the raw octets of the key + // represented by the [[handle]] internal slot of key. + // Step 2.2. Let result be data. + Handle::Aes128(key_data) => { + result = ExportedKey::Raw(key_data.clone()); + }, + Handle::Aes192(key_data) => { + result = ExportedKey::Raw(key_data.clone()); + }, + Handle::Aes256(key_data) => { + result = ExportedKey::Raw(key_data.clone()); + }, + _ => unreachable!(), }, + // If format is "jwk": 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()) + // Step 2.3. Set the k attribute of jwk to be a string containing the raw octets of + // the key represented by the [[handle]] internal slot of key, encoded according to + // Section 6.4 of JSON Web Algorithms [JWA]. + let k = match key.handle() { + Handle::Aes128(key) => { + base64::engine::general_purpose::STANDARD_NO_PAD.encode(key) }, - Handle::Aes192(key_data) => { - data_to_jwk_params(key.algorithm().as_str(), "192", key_data.as_slice()) + Handle::Aes192(key) => { + base64::engine::general_purpose::STANDARD_NO_PAD.encode(key) }, - Handle::Aes256(key_data) => { - data_to_jwk_params(key.algorithm().as_str(), "256", key_data.as_slice()) + Handle::Aes256(key) => { + base64::engine::general_purpose::STANDARD_NO_PAD.encode(key) }, - _ => return Err(Error::Data), + _ => unreachable!(), }; + + // Step 2.4. + // If the length attribute of key is 128: Set the alg attribute of jwk to the string "A128CTR". + // If the length attribute of key is 192: Set the alg attribute of jwk to the string "A192CTR". + // If the length attribute of key is 256: Set the alg attribute of jwk to the string "A256CTR". + // + // If the length attribute of key is 128: Set the alg attribute of jwk to the string "A128CTR". + // If the length attribute of key is 192: Set the alg attribute of jwk to the string "A192CTR". + // If the length attribute of key is 256: Set the alg attribute of jwk to the string "A256CTR". + // + // If the length attribute of key is 128: Set the alg attribute of jwk to the string "A128CTR". + // If the length attribute of key is 192: Set the alg attribute of jwk to the string "A192CTR". + // If the length attribute of key is 256: Set the alg attribute of jwk to the string "A256CTR". + // + // If the length attribute of key is 128: Set the alg attribute of jwk to the string "A128CTR". + // If the length attribute of key is 192: Set the alg attribute of jwk to the string "A192CTR". + // If the length attribute of key is 256: Set the alg attribute of jwk to the string "A256CTR". + // + // NOTE: Check key length via key.handle() + let alg = match (key.handle(), key.algorithm().as_str()) { + (Handle::Aes128(_), ALG_AES_CTR) => "A128CTR", + (Handle::Aes192(_), ALG_AES_CTR) => "A192CTR", + (Handle::Aes256(_), ALG_AES_CTR) => "A256CTR", + (Handle::Aes128(_), ALG_AES_CBC) => "A128CBC", + (Handle::Aes192(_), ALG_AES_CBC) => "A192CBC", + (Handle::Aes256(_), ALG_AES_CBC) => "A256CBC", + (Handle::Aes128(_), ALG_AES_GCM) => "A128GCM", + (Handle::Aes192(_), ALG_AES_GCM) => "A192GCM", + (Handle::Aes256(_), ALG_AES_GCM) => "A256GCM", + (Handle::Aes128(_), ALG_AES_KW) => "A128KW", + (Handle::Aes192(_), ALG_AES_KW) => "A192KW", + (Handle::Aes256(_), ALG_AES_KW) => "A256KW", + _ => unreachable!(), + }; + + // Step 2.5. Set the key_ops attribute of jwk to equal the [[usages]] internal slot of key. let key_ops = key .usages() .iter() .map(|usage| DOMString::from(usage.as_str())) .collect::>(); + + // Step 2.1. Let jwk be a new JsonWebKey dictionary. + // Step 2.2. Set the kty attribute of jwk to the string "oct". + // Step 2.6. Set the ext attribute of jwk to equal the [[extractable]] internal slot of key. 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: Some(key_ops), kty: Some(DOMString::from("oct")), - n: None, - oth: None, - p: None, - q: None, - qi: None, - use_: None, - x: None, - y: None, + k: Some(DOMString::from(k)), + alg: Some(DOMString::from(alg)), + key_ops: Some(key_ops), + ext: Some(key.Extractable()), + ..Default::default() }; - Ok(ExportedKey::Jwk(Box::new(jwk))) + + // Step 2.7. Let result be jwk. + result = ExportedKey::Jwk(Box::new(jwk)); }, - _ => Err(Error::NotSupported), - } + // Otherwise: + _ => { + // throw a NotSupportedError. + return Err(Error::NotSupported); + }, + }; + + // Step 3. Return result. + Ok(result) } /// @@ -2545,7 +2821,7 @@ impl SubtleCrypto { } } - /// + /// #[allow(unsafe_code)] fn import_key_hmac( &self, @@ -2573,15 +2849,95 @@ impl SubtleCrypto { // Step 4. let data; match format { - // Key data has already been extracted in the case of JWK, - // so both raw and jwk can be treated the same here. - KeyFormat::Raw | KeyFormat::Jwk => { - // Step 4.1 Let data be the octet string contained in keyData. + // If format is "raw": + KeyFormat::Raw => { + // Step 4.1. Let data be keyData. data = key_data.to_vec(); - // Step 4.2 Set hash to equal the hash member of normalizedAlgorithm. + // Step 4.2. Set hash to equal the hash member of normalizedAlgorithm. hash = normalized_algorithm.hash; }, + // If format is "jwk": + KeyFormat::Jwk => { + // Step 2.1. If keyData is a JsonWebKey dictionary: Let jwk equal keyData. + // Otherwise: Throw a DataError. + // NOTE: Deserialize keyData to JsonWebKey dictionary by running JsonWebKey::parse + let jwk = JsonWebKey::parse(GlobalScope::get_cx(), key_data)?; + + // Step 2.2. If the kty field of jwk is not "oct", then throw a DataError. + if jwk.kty.as_ref().is_none_or(|kty| kty != "oct") { + return Err(Error::Data); + } + + // Step 2.3. If jwk does not meet the requirements of Section 6.4 of JSON Web + // Algorithms [JWA], then throw a DataError. + // NOTE: Done by Step 2.4 and 2.6. + + // Step 2.4. Let data be the byte sequence obtained by decoding the k field of jwk. + data = base64::engine::general_purpose::STANDARD_NO_PAD + .decode(jwk.k.as_ref().ok_or(Error::Data)?.as_bytes()) + .map_err(|_| Error::Data)?; + + // Step 2.5. Set the hash to equal the hash member of normalizedAlgorithm. + hash = normalized_algorithm.hash; + + // Step 2.6. + match hash.name().to_string().as_str() { + // If the name attribute of hash is "SHA-1": + ALG_SHA1 => { + // If the alg field of jwk is present and is not "HS1", then throw a DataError. + if jwk.alg.as_ref().is_some_and(|alg| alg != "HS1") { + return Err(Error::Data); + } + }, + // If the name attribute of hash is "SHA-256": + ALG_SHA256 => { + // If the alg field of jwk is present and is not "HS256", then throw a DataError. + if jwk.alg.as_ref().is_some_and(|alg| alg != "HS256") { + return Err(Error::Data); + } + }, + // If the name attribute of hash is "SHA-384": + ALG_SHA384 => { + // If the alg field of jwk is present and is not "HS384", then throw a DataError. + if jwk.alg.as_ref().is_some_and(|alg| alg != "HS384") { + return Err(Error::Data); + } + }, + // If the name attribute of hash is "SHA-512": + ALG_SHA512 => { + // If the alg field of jwk is present and is not "HS512", then throw a DataError. + if jwk.alg.as_ref().is_some_and(|alg| alg != "HS512") { + return Err(Error::Data); + } + }, + // Otherwise, + _name => { + // if the name attribute of hash is defined in another applicable specification: + // Perform any key import steps defined by other applicable specifications, + // passing format, jwk and hash and obtaining hash + // NOTE: Currently not support applicable specification. + return Err(Error::NotSupported); + }, + } + + // Step 2.7. If usages is non-empty and the use field of jwk is present and is not + // "sig", then throw a DataError. + if !usages.is_empty() && jwk.use_.as_ref().is_some_and(|use_| use_ != "sig") { + return Err(Error::Data); + } + + // Step 2.8. If the key_ops field of jwk is present, and is invalid according to + // the requirements of JSON Web Key [JWK] or does not contain all of the specified + // usages values, then throw a DataError. + jwk.check_key_ops(&usages)?; + + // Step 2.9. If the ext field of jwk is present and has the value false and + // extractable is true, then throw a DataError. + if jwk.ext.is_some_and(|ext| !ext) && extractable { + return Err(Error::Data); + } + }, // Otherwise: _ => { // throw a NotSupportedError. @@ -2813,18 +3169,6 @@ pub(crate) enum ExportedKey { Jwk(Box), } -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)), - ALG_AES_KW => DOMString::from(format!("A{}KW", size)), - ALG_AES_GCM => DOMString::from(format!("A{}GCM", size)), - _ => unreachable!(), - }; - let data = base64::engine::general_purpose::STANDARD_NO_PAD.encode(key); - (jwk_alg, DOMString::from(data)) -} - trait AlgorithmFromName { fn from_name(name: DOMString, out: MutableHandleObject, cx: JSContext); } @@ -3048,31 +3392,51 @@ impl ImportKeyAlgorithm { &self, subtle: &SubtleCrypto, format: KeyFormat, - secret: &[u8], + key_data: &[u8], extractable: bool, key_usages: Vec, can_gc: CanGc, ) -> Result, Error> { match self { - Self::AesCbc => { - subtle.import_key_aes(format, secret, extractable, key_usages, ALG_AES_CBC, can_gc) - }, - Self::AesCtr => { - subtle.import_key_aes(format, secret, extractable, key_usages, ALG_AES_CTR, can_gc) - }, - Self::AesKw => { - subtle.import_key_aes(format, secret, extractable, key_usages, ALG_AES_KW, can_gc) - }, - Self::AesGcm => { - subtle.import_key_aes(format, secret, extractable, key_usages, ALG_AES_GCM, can_gc) - }, + Self::AesCbc => subtle.import_key_aes( + format, + key_data, + extractable, + key_usages, + ALG_AES_CBC, + can_gc, + ), + Self::AesCtr => subtle.import_key_aes( + format, + key_data, + extractable, + key_usages, + ALG_AES_CTR, + can_gc, + ), + Self::AesKw => subtle.import_key_aes( + format, + key_data, + extractable, + key_usages, + ALG_AES_KW, + can_gc, + ), + Self::AesGcm => subtle.import_key_aes( + format, + key_data, + extractable, + key_usages, + ALG_AES_GCM, + can_gc, + ), Self::Hmac(params) => { - subtle.import_key_hmac(params, format, secret, extractable, key_usages, can_gc) + subtle.import_key_hmac(params, format, key_data, extractable, key_usages, can_gc) }, Self::Pbkdf2 => { - subtle.import_key_pbkdf2(format, secret, extractable, key_usages, can_gc) + subtle.import_key_pbkdf2(format, key_data, extractable, key_usages, can_gc) }, - Self::Hkdf => subtle.import_key_hkdf(format, secret, extractable, key_usages, can_gc), + Self::Hkdf => subtle.import_key_hkdf(format, key_data, extractable, key_usages, can_gc), } } } @@ -3243,105 +3607,6 @@ impl KeyWrapAlgorithm { } } -fn parse_jwk( - cx: JSContext, - bytes: &[u8], - import_alg: ImportKeyAlgorithm, - extractable: bool, - key_usages: &[KeyUsage], -) -> Result, Error> { - let jwk = JsonWebKey::parse(cx, bytes)?; - - let kty = jwk.kty.as_ref().ok_or(Error::Data)?; - let ext = jwk.ext.ok_or(Error::Data)?; - if !ext && extractable { - return Err(Error::Data); - } - - // If the key_ops field of jwk is present, and is invalid according to the requirements of JSON Web Key [JWK] - // or does not contain all of the specified usages values, then throw a DataError. - if jwk.key_ops.is_some() { - let key_ops_usages = jwk.get_usages_from_key_ops()?; - - if !key_usages - .iter() - .all(|usage| key_ops_usages.contains(usage)) - { - return Err(Error::Data); - } - } - - match import_alg { - ImportKeyAlgorithm::AesCbc | - ImportKeyAlgorithm::AesCtr | - ImportKeyAlgorithm::AesKw | - ImportKeyAlgorithm::AesGcm => { - if kty != "oct" { - return Err(Error::Data); - } - let k = jwk.k.as_ref().ok_or(Error::Data)?; - let alg = jwk.alg.as_ref().ok_or(Error::Data)?; - - let data = base64::engine::general_purpose::STANDARD_NO_PAD - .decode(k.as_bytes()) - .map_err(|_| Error::Data)?; - - let expected_alg = match (data.len() * 8, &import_alg) { - (128, ImportKeyAlgorithm::AesCbc) => "A128CBC", - (128, ImportKeyAlgorithm::AesCtr) => "A128CTR", - (128, ImportKeyAlgorithm::AesKw) => "A128KW", - (128, ImportKeyAlgorithm::AesGcm) => "A128GCM", - (192, ImportKeyAlgorithm::AesCbc) => "A192CBC", - (192, ImportKeyAlgorithm::AesCtr) => "A192CTR", - (192, ImportKeyAlgorithm::AesKw) => "A192KW", - (192, ImportKeyAlgorithm::AesGcm) => "A192GCM", - (256, ImportKeyAlgorithm::AesCbc) => "A256CBC", - (256, ImportKeyAlgorithm::AesCtr) => "A256CTR", - (256, ImportKeyAlgorithm::AesKw) => "A256KW", - (256, ImportKeyAlgorithm::AesGcm) => "A256GCM", - _ => return Err(Error::Data), - }; - - if alg != expected_alg { - return Err(Error::Data); - } - - if !key_usages.is_empty() && jwk.use_.as_ref().is_some_and(|use_| use_ != "enc") { - return Err(Error::Data); - } - - Ok(data) - }, - ImportKeyAlgorithm::Hmac(params) => { - if kty != "oct" { - return Err(Error::Data); - } - let k = jwk.k.as_ref().ok_or(Error::Data)?; - let alg = jwk.alg.as_ref().ok_or(Error::Data)?; - - let expected_alg = match params.hash { - DigestAlgorithm::Sha1 => "HS1", - DigestAlgorithm::Sha256 => "HS256", - DigestAlgorithm::Sha384 => "HS384", - DigestAlgorithm::Sha512 => "HS512", - }; - - if alg != expected_alg { - return Err(Error::Data); - } - - if !key_usages.is_empty() && jwk.use_.as_ref().is_some_and(|use_| use_ != "sign") { - return Err(Error::Data); - } - - base64::engine::general_purpose::STANDARD_NO_PAD - .decode(k.as_bytes()) - .map_err(|_| Error::Data) - }, - _ => Err(Error::NotSupported), - } -} - #[expect(unused)] trait RsaOtherPrimesInfoExt { fn from_value(value: &serde_json::Value) -> Result; @@ -3381,9 +3646,11 @@ impl RsaOtherPrimesInfoExt for RsaOtherPrimesInfo { trait JsonWebKeyExt { fn parse(cx: JSContext, data: &[u8]) -> Result; + fn stringify(&self, cx: JSContext) -> Result; fn get_usages_from_key_ops(&self) -> Result, Error>; #[expect(unused)] fn get_rsa_other_primes_info_from_oth(&self) -> Result<&[RsaOtherPrimesInfo], Error>; + fn check_key_ops(&self, specified_usages: &[KeyUsage]) -> Result<(), Error>; } impl JsonWebKeyExt for JsonWebKey { @@ -3429,6 +3696,17 @@ impl JsonWebKeyExt for JsonWebKey { Ok(key) } + /// Convert a JsonWebKey value to DOMString. We first convert the JsonWebKey value to + /// JavaScript value, and then serialize it by performing steps in + /// . This acts + /// like the opposite of JsonWebKey::parse if you further convert the stringified result to + /// bytes. + fn stringify(&self, cx: JSContext) -> Result { + rooted!(in(*cx) let mut data = UndefinedValue()); + self.safe_to_jsval(cx, data.handle_mut()); + serialize_jsval_to_json_utf8(cx, data.handle()) + } + fn get_usages_from_key_ops(&self) -> Result, Error> { let mut usages = vec![]; for op in self.key_ops.as_ref().ok_or(Error::Data)? { @@ -3440,4 +3718,41 @@ impl JsonWebKeyExt for JsonWebKey { fn get_rsa_other_primes_info_from_oth(&self) -> Result<&[RsaOtherPrimesInfo], Error> { self.oth.as_deref().ok_or(Error::Data) } + + /// If the key_ops field of jwk is present, and is invalid according to the requirements of + /// JSON Web Key [JWK] or does not contain all of the specified usages values, then throw a + /// DataError. + fn check_key_ops(&self, specified_usages: &[KeyUsage]) -> Result<(), Error> { + // If the key_ops field of jwk is present, + if let Some(ref key_ops) = self.key_ops { + // and is invalid according to the requirements of JSON Web Key [JWK]: + // 1. Duplicate key operation values MUST NOT be present in the array. + if key_ops + .iter() + .collect::>() + .len() < + key_ops.len() + { + return Err(Error::Data); + } + // 2. The "use" and "key_ops" JWK members SHOULD NOT be used together; however, if both + // are used, the information they convey MUST be consistent. + if let Some(ref use_) = self.use_ { + if key_ops.iter().any(|op| op != use_) { + return Err(Error::Data); + } + } + + // or does not contain all of the specified usages values + let key_ops_as_usages = self.get_usages_from_key_ops()?; + if !specified_usages + .iter() + .all(|specified_usage| key_ops_as_usages.contains(specified_usage)) + { + return Err(Error::Data); + } + } + + Ok(()) + } }