mirror of
https://github.com/servo/servo.git
synced 2025-09-16 09:58:23 +01:00
script: Unwrap imported key in JWK format after normalizing (#39234)
In our current implementation, the `importKey` method and `unwrapKey` method of `SubtleCrypto` interface unwrap JsonWebKey before running the normalized algorithms. Therefore, all cryptography algorithms share the same unwrapping mechanism. Our current unwrapping mechanism is not compatible with some cryptography algorithms, which we have not yet implemented such as Ed25519. Following the WebCrypto API spec, this patch moves the JsonWebKey unwrapping mechanism to normalized algorithms so that each cryptography algorithm can unwrap JsonWebKey in its own way. This does not introduce behavioral changes, but makes implementing the unwrap operation for new cryptography algorithms easier in the future. Remark: Step 8 and 13 of `SubtleCrypto::ImportKey` require the crypto task source in the script task manager, but we don't have it yet. So, they're marked as TODO. Testing: Existing tests should suffice. --------- Signed-off-by: Kingsley Yung <kingsley@kkoyung.dev> Signed-off-by: Josh Matthews <josh@joshmatthews.net> Co-authored-by: Josh Matthews <josh@joshmatthews.net>
This commit is contained in:
parent
97690b1cba
commit
5de041e6ef
2 changed files with 557 additions and 233 deletions
|
@ -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>,
|
||||
|
||||
/// <https://w3c.github.io/webcrypto/#dom-cryptokey-usages>
|
||||
usages: Vec<KeyUsage>,
|
||||
usages: DomRefCell<Vec<KeyUsage>>,
|
||||
#[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<KeyUsage> {
|
||||
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<crate::DomTypeHolder> for CryptoKey {
|
||||
|
@ -138,7 +147,7 @@ impl CryptoKeyMethods<crate::DomTypeHolder> for CryptoKey {
|
|||
/// <https://w3c.github.io/webcrypto/#dom-cryptokey-usages>
|
||||
fn Usages(&self, cx: JSContext) -> NonNull<JSObject> {
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<crate::DomTypeHolder> for SubtleCrypto {
|
|||
comp: InRealm,
|
||||
can_gc: CanGc,
|
||||
) -> Rc<Promise> {
|
||||
// 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<crate::DomTypeHolder> 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<crate::DomTypeHolder> 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)
|
||||
}
|
||||
|
||||
/// <https://w3c.github.io/webcrypto/#aes-cbc-operations>
|
||||
/// <https://w3c.github.io/webcrypto/#aes-ctr-operations>
|
||||
/// <https://w3c.github.io/webcrypto/#aes-ctr-operations-import-key>
|
||||
/// <https://w3c.github.io/webcrypto/#aes-cbc-operations-import-key>
|
||||
/// <https://w3c.github.io/webcrypto/#aes-gcm-operations-import-key>
|
||||
/// <https://w3c.github.io/webcrypto/#aes-kw-operations-import-key>
|
||||
#[allow(unsafe_code)]
|
||||
fn import_key_aes(
|
||||
&self,
|
||||
format: KeyFormat,
|
||||
data: &[u8],
|
||||
key_data: &[u8],
|
||||
extractable: bool,
|
||||
usages: Vec<KeyUsage>,
|
||||
alg_name: &str,
|
||||
can_gc: CanGc,
|
||||
) -> Result<DomRoot<CryptoKey>, 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)
|
||||
}
|
||||
|
||||
/// <https://w3c.github.io/webcrypto/#aes-cbc-operations>
|
||||
/// <https://w3c.github.io/webcrypto/#aes-ctr-operations>
|
||||
/// <https://w3c.github.io/webcrypto/#aes-ctr-operations-export-key>
|
||||
/// <https://w3c.github.io/webcrypto/#aes-cbc-operations-export-key>
|
||||
/// <https://w3c.github.io/webcrypto/#aes-gcm-operations-export-key>
|
||||
/// <https://w3c.github.io/webcrypto/#aes-kw-operations-export-key>
|
||||
fn export_key_aes(&self, format: KeyFormat, key: &CryptoKey) -> Result<ExportedKey, Error> {
|
||||
// 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::<Vec<DOMString>>();
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
/// <https://w3c.github.io/webcrypto/#hkdf-operations>
|
||||
|
@ -2545,7 +2821,7 @@ impl SubtleCrypto {
|
|||
}
|
||||
}
|
||||
|
||||
/// <https://w3c.github.io/webcrypto/#hmac-operations>
|
||||
/// <https://w3c.github.io/webcrypto/#hmac-operations-import-key>
|
||||
#[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<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)),
|
||||
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<KeyUsage>,
|
||||
can_gc: CanGc,
|
||||
) -> Result<DomRoot<CryptoKey>, 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<Vec<u8>, 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<RsaOtherPrimesInfo, Error>;
|
||||
|
@ -3381,9 +3646,11 @@ impl RsaOtherPrimesInfoExt for RsaOtherPrimesInfo {
|
|||
|
||||
trait JsonWebKeyExt {
|
||||
fn parse(cx: JSContext, data: &[u8]) -> Result<JsonWebKey, Error>;
|
||||
fn stringify(&self, cx: JSContext) -> Result<DOMString, Error>;
|
||||
fn get_usages_from_key_ops(&self) -> Result<Vec<KeyUsage>, 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
|
||||
/// <https://infra.spec.whatwg.org/#serialize-a-javascript-value-to-a-json-string>. This acts
|
||||
/// like the opposite of JsonWebKey::parse if you further convert the stringified result to
|
||||
/// bytes.
|
||||
fn stringify(&self, cx: JSContext) -> Result<DOMString, Error> {
|
||||
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<Vec<KeyUsage>, 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::<std::collections::HashSet<_>>()
|
||||
.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(())
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue