script: Check whether the generated crypto key has empty usages (#39328)

The WebCryptoAPI spec requires that when we generate crypto keys by the
generateKey method of SubtleCrypto interface we have to check whether
the usages is empty. If the usages is empty, throw a SyntaxError.

FYI, Step 9 of
https://w3c.github.io/webcrypto/#SubtleCrypto-method-generateKey

We have not yet implemented this logic, and this patch implements it.

Testing: Pass WPT tests that were expected to fail.

---------

Signed-off-by: Kingsley Yung <kingsley@kkoyung.dev>
This commit is contained in:
Kingsley Yung 2025-09-17 01:23:42 +08:00 committed by GitHub
parent 7b755471c7
commit f3d5617349
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 69 additions and 108 deletions

View file

@ -20,6 +20,11 @@ use crate::dom::bindings::str::DOMString;
use crate::dom::globalscope::GlobalScope;
use crate::script_runtime::{CanGc, JSContext};
pub(crate) enum CryptoKeyOrCryptoKeyPair {
CryptoKey(DomRoot<CryptoKey>),
// TODO: CryptoKeyPair(CryptoKeyPair),
}
/// The underlying cryptographic data this key represents
#[allow(dead_code)]
#[derive(MallocSizeOf)]

View file

@ -46,7 +46,7 @@ use crate::dom::bindings::reflector::{DomGlobal, Reflector, reflect_dom_object};
use crate::dom::bindings::root::DomRoot;
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::cryptokey::{CryptoKey, CryptoKeyOrCryptoKeyPair, Handle};
use crate::dom::globalscope::GlobalScope;
use crate::dom::promise::Promise;
use crate::realms::InRealm;
@ -524,12 +524,49 @@ impl SubtleCryptoMethods<crate::DomTypeHolder> for SubtleCrypto {
.queue(task!(generate_key: move || {
let subtle = this.root();
let promise = trusted_promise.root();
let key = normalized_algorithm.generate_key(&subtle, key_usages, extractable, CanGc::note());
// Step 8. Let result be the result of performing the generate key operation
// specified by normalizedAlgorithm using algorithm, extractable and usages.
let key = match normalized_algorithm
.generate_key(&subtle, key_usages, extractable, CanGc::note())
{
Ok(key) => key,
Err(error) => {
promise.reject_error(error, CanGc::note());
return;
}
};
// Step 9.
// If result is a CryptoKey object:
// If the [[type]] internal slot of result is "secret" or "private" and usages
// is empty, then throw a SyntaxError.
// If result is a CryptoKeyPair object:
// If the [[usages]] internal slot of the privateKey attribute of result is the
// empty sequence, then throw a SyntaxError.
// TODO: Implement CryptoKeyPair case
match &key {
CryptoKeyOrCryptoKeyPair::CryptoKey(crpyto_key) => {
if matches!(crpyto_key.Type(), KeyType::Secret | KeyType::Private)
&& crpyto_key.usages().is_empty()
{
promise.reject_error(Error::Syntax(None), CanGc::note());
return;
}
},
};
// TODO: Step 10. Queue a global task on the crypto task source, given realm's
// global object, to perform the remaining steps.
// Step 11. Let result be the result of converting result to an ECMAScript Object
// in realm, as defined by [WebIDL].
// Step 12. Resolve promise with result.
// TODO: Implement CryptoKeyPair case
match key {
Ok(key) => promise.resolve_native(&key, CanGc::note()),
Err(e) => promise.reject_error(e, CanGc::note()),
}
CryptoKeyOrCryptoKeyPair::CryptoKey(crypto_key) =>
promise.resolve_native(&crypto_key, CanGc::note()),
};
}));
promise
@ -3531,11 +3568,21 @@ impl KeyGenerationAlgorithm {
usages: Vec<KeyUsage>,
extractable: bool,
can_gc: CanGc,
) -> Result<DomRoot<CryptoKey>, Error> {
match self {
Self::Aes(params) => subtle.generate_key_aes(usages, params, extractable, can_gc),
Self::Hmac(params) => subtle.generate_key_hmac(usages, params, extractable, can_gc),
}
) -> Result<CryptoKeyOrCryptoKeyPair, Error> {
let key_or_key_pair =
match self {
Self::Aes(params) => CryptoKeyOrCryptoKeyPair::CryptoKey(subtle.generate_key_aes(
usages,
params,
extractable,
can_gc,
)?),
Self::Hmac(params) => CryptoKeyOrCryptoKeyPair::CryptoKey(
subtle.generate_key_hmac(usages, params, extractable, can_gc)?,
),
};
Ok(key_or_key_pair)
}
}

View file

@ -15,3 +15,10 @@ interface CryptoKey {
readonly attribute object algorithm;
readonly attribute object usages;
};
// https://w3c.github.io/webcrypto/#dfn-CryptoKeyPair
dictionary CryptoKeyPair {
CryptoKey publicKey;
CryptoKey privateKey;
};

View file

@ -1,98 +0,0 @@
[failures_HMAC.https.any.worker.html]
[Empty usages: generateKey({hash: SHA-1, length: 160, name: HMAC}, false, [\])]
expected: FAIL
[Empty usages: generateKey({hash: SHA-1, length: 160, name: HMAC}, true, [\])]
expected: FAIL
[Empty usages: generateKey({hash: SHA-256, length: 256, name: HMAC}, false, [\])]
expected: FAIL
[Empty usages: generateKey({hash: SHA-256, length: 256, name: HMAC}, true, [\])]
expected: FAIL
[Empty usages: generateKey({hash: SHA-384, length: 384, name: HMAC}, false, [\])]
expected: FAIL
[Empty usages: generateKey({hash: SHA-384, length: 384, name: HMAC}, true, [\])]
expected: FAIL
[Empty usages: generateKey({hash: SHA-512, length: 512, name: HMAC}, false, [\])]
expected: FAIL
[Empty usages: generateKey({hash: SHA-512, length: 512, name: HMAC}, true, [\])]
expected: FAIL
[Empty usages: generateKey({hash: SHA-1, name: HMAC}, false, [\])]
expected: FAIL
[Empty usages: generateKey({hash: SHA-1, name: HMAC}, true, [\])]
expected: FAIL
[Empty usages: generateKey({hash: SHA-256, name: HMAC}, false, [\])]
expected: FAIL
[Empty usages: generateKey({hash: SHA-256, name: HMAC}, true, [\])]
expected: FAIL
[Empty usages: generateKey({hash: SHA-384, name: HMAC}, false, [\])]
expected: FAIL
[Empty usages: generateKey({hash: SHA-384, name: HMAC}, true, [\])]
expected: FAIL
[Empty usages: generateKey({hash: SHA-512, name: HMAC}, false, [\])]
expected: FAIL
[Empty usages: generateKey({hash: SHA-512, name: HMAC}, true, [\])]
expected: FAIL
[failures_HMAC.https.any.html]
[Empty usages: generateKey({hash: SHA-1, length: 160, name: HMAC}, false, [\])]
expected: FAIL
[Empty usages: generateKey({hash: SHA-1, length: 160, name: HMAC}, true, [\])]
expected: FAIL
[Empty usages: generateKey({hash: SHA-256, length: 256, name: HMAC}, false, [\])]
expected: FAIL
[Empty usages: generateKey({hash: SHA-256, length: 256, name: HMAC}, true, [\])]
expected: FAIL
[Empty usages: generateKey({hash: SHA-384, length: 384, name: HMAC}, false, [\])]
expected: FAIL
[Empty usages: generateKey({hash: SHA-384, length: 384, name: HMAC}, true, [\])]
expected: FAIL
[Empty usages: generateKey({hash: SHA-512, length: 512, name: HMAC}, false, [\])]
expected: FAIL
[Empty usages: generateKey({hash: SHA-512, length: 512, name: HMAC}, true, [\])]
expected: FAIL
[Empty usages: generateKey({hash: SHA-1, name: HMAC}, false, [\])]
expected: FAIL
[Empty usages: generateKey({hash: SHA-1, name: HMAC}, true, [\])]
expected: FAIL
[Empty usages: generateKey({hash: SHA-256, name: HMAC}, false, [\])]
expected: FAIL
[Empty usages: generateKey({hash: SHA-256, name: HMAC}, true, [\])]
expected: FAIL
[Empty usages: generateKey({hash: SHA-384, name: HMAC}, false, [\])]
expected: FAIL
[Empty usages: generateKey({hash: SHA-384, name: HMAC}, true, [\])]
expected: FAIL
[Empty usages: generateKey({hash: SHA-512, name: HMAC}, false, [\])]
expected: FAIL
[Empty usages: generateKey({hash: SHA-512, name: HMAC}, true, [\])]
expected: FAIL