mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-07-29 12:19:54 +00:00
LibWeb: Implement wrapKey
and unwrapKey
methods
This implements the last WebCryptoAPI methods `wrapKey` and `unwrapKey`. Most of the functionality is already there because they rely on `encrypt` and `decrypt`. The only test failures are for `AES-GCM` which is not implemented yet.
This commit is contained in:
parent
c1a65f3d53
commit
584cbcf3ef
Notes:
github-actions[bot]
2024-12-16 10:35:53 +00:00
Author: https://github.com/devgianlu
Commit: 584cbcf3ef
Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/2915
8 changed files with 1221 additions and 2 deletions
|
@ -6,9 +6,11 @@
|
|||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <AK/ByteBuffer.h>
|
||||
#include <AK/QuickSort.h>
|
||||
#include <LibCrypto/Hash/HashManager.h>
|
||||
#include <LibJS/Runtime/ArrayBuffer.h>
|
||||
#include <LibJS/Runtime/JSONObject.h>
|
||||
#include <LibWeb/Bindings/ExceptionOrUtils.h>
|
||||
#include <LibWeb/Bindings/Intrinsics.h>
|
||||
#include <LibWeb/Bindings/SubtleCryptoPrototype.h>
|
||||
|
@ -744,6 +746,293 @@ JS::ThrowCompletionOr<GC::Ref<WebIDL::Promise>> SubtleCrypto::derive_key(Algorit
|
|||
return promise;
|
||||
}
|
||||
|
||||
// https://w3c.github.io/webcrypto/#SubtleCrypto-method-wrapKey
|
||||
JS::ThrowCompletionOr<GC::Ref<WebIDL::Promise>> SubtleCrypto::wrap_key(Bindings::KeyFormat format, GC::Ref<CryptoKey> key, GC::Ref<CryptoKey> wrapping_key, AlgorithmIdentifier algorithm)
|
||||
{
|
||||
auto& realm = this->realm();
|
||||
// 1. Let format, key, wrappingKey and algorithm be the format, key, wrappingKey and wrapAlgorithm parameters passed to the wrapKey() method, respectively.
|
||||
|
||||
StringView operation;
|
||||
auto normalized_algorithm_or_error = [&]() -> WebIDL::ExceptionOr<NormalizedAlgorithmAndParameter> {
|
||||
// 2. Let normalizedAlgorithm be the result of normalizing an algorithm, with alg set to algorithm and op set to "wrapKey".
|
||||
auto normalized_algorithm_wrap_key_or_error = normalize_an_algorithm(realm, algorithm, "wrapKey"_string);
|
||||
|
||||
// 3. If an error occurred, let normalizedAlgorithm be the result of normalizing an algorithm, with alg set to algorithm and op set to "encrypt".
|
||||
// 4. If an error occurred, return a Promise rejected with normalizedAlgorithm.
|
||||
if (normalized_algorithm_wrap_key_or_error.is_error()) {
|
||||
auto normalized_algorithm_encrypt_or_error = normalize_an_algorithm(realm, algorithm, "encrypt"_string);
|
||||
if (normalized_algorithm_encrypt_or_error.is_error())
|
||||
return normalized_algorithm_encrypt_or_error.release_error();
|
||||
|
||||
operation = "encrypt"sv;
|
||||
return normalized_algorithm_encrypt_or_error.release_value();
|
||||
} else {
|
||||
operation = "wrapKey"sv;
|
||||
return normalized_algorithm_wrap_key_or_error.release_value();
|
||||
}
|
||||
}();
|
||||
if (normalized_algorithm_or_error.is_error())
|
||||
return WebIDL::create_rejected_promise_from_exception(realm, normalized_algorithm_or_error.release_error());
|
||||
|
||||
auto normalized_algorithm = normalized_algorithm_or_error.release_value();
|
||||
|
||||
// 5. Let promise be a new Promise.
|
||||
auto promise = WebIDL::create_promise(realm);
|
||||
|
||||
// 6. Return promise and perform the remaining steps in parallel.
|
||||
Platform::EventLoopPlugin::the().deferred_invoke(GC::create_function(realm.heap(), [&realm, normalized_algorithm = move(normalized_algorithm), promise, wrapping_key = move(wrapping_key), key = move(key), format, operation]() mutable -> void {
|
||||
HTML::TemporaryExecutionContext context(realm, HTML::TemporaryExecutionContext::CallbacksEnabled::Yes);
|
||||
// 7. If the following steps or referenced procedures say to throw an error, reject promise with the returned error and then terminate the algorithm.
|
||||
|
||||
// 8. If the name member of normalizedAlgorithm is not equal to the name attribute
|
||||
// of the [[algorithm]] internal slot of wrappingKey then throw an InvalidAccessError.
|
||||
if (normalized_algorithm.parameter->name != wrapping_key->algorithm_name()) {
|
||||
WebIDL::reject_promise(realm, promise, WebIDL::InvalidAccessError::create(realm, "Algorithm mismatch"_string));
|
||||
return;
|
||||
}
|
||||
|
||||
// 9. If the [[usages]] internal slot of wrappingKey does not contain an entry that is "wrapKey", then throw an InvalidAccessError.
|
||||
if (!wrapping_key->internal_usages().contains_slow(Bindings::KeyUsage::Wrapkey)) {
|
||||
WebIDL::reject_promise(realm, promise, WebIDL::InvalidAccessError::create(realm, "Key does not support wrapping keys"_string));
|
||||
return;
|
||||
}
|
||||
|
||||
// 10. If the algorithm identified by the [[algorithm]] internal slot of key does not support the export key operation, then throw a NotSupportedError.
|
||||
// Note: Handled by the base AlgorithmMethods implementation
|
||||
|
||||
// 11. If the [[extractable]] internal slot of key is false, then throw an InvalidAccessError.
|
||||
if (!key->extractable()) {
|
||||
WebIDL::reject_promise(realm, promise, WebIDL::InvalidAccessError::create(realm, "Key is not extractable"_string));
|
||||
return;
|
||||
}
|
||||
|
||||
// 12. Let key be the result of performing the export key operation specified the [[algorithm]] internal slot of key using key and format.
|
||||
// NOTE: The spec does not mention we need to normalize this, but it's the only way we have to get to export_key.
|
||||
auto& key_algorithm = verify_cast<KeyAlgorithm>(*key->algorithm());
|
||||
auto normalized_key_algorithm = normalize_an_algorithm(realm, key_algorithm.name(), "exportKey"_string);
|
||||
if (normalized_key_algorithm.is_error()) {
|
||||
WebIDL::reject_promise(realm, promise, Bindings::exception_to_throw_completion(realm.vm(), normalized_key_algorithm.release_error()).release_value().value());
|
||||
return;
|
||||
}
|
||||
|
||||
auto key_data_or_error = normalized_key_algorithm.release_value().methods->export_key(format, key);
|
||||
if (key_data_or_error.is_error()) {
|
||||
WebIDL::reject_promise(realm, promise, Bindings::exception_to_throw_completion(realm.vm(), key_data_or_error.release_error()).release_value().value());
|
||||
return;
|
||||
}
|
||||
|
||||
auto key_data = key_data_or_error.release_value();
|
||||
|
||||
ByteBuffer bytes;
|
||||
// 13. If format is equal to the strings "raw", "pkcs8", or "spki":
|
||||
if (format == Bindings::KeyFormat::Raw || format == Bindings::KeyFormat::Pkcs8 || format == Bindings::KeyFormat::Spki) {
|
||||
// Set bytes be set to key.
|
||||
bytes = verify_cast<JS::ArrayBuffer>(*key_data).buffer();
|
||||
}
|
||||
|
||||
// If format is equal to the string "jwk":
|
||||
else if (format == Bindings::KeyFormat::Jwk) {
|
||||
// 1. Convert key to an ECMAScript Object, as specified in [WEBIDL],
|
||||
// performing the conversion in the context of a new global object.
|
||||
// 2. Let json be the result of representing key as a UTF-16 string conforming to the JSON grammar;
|
||||
// for example, by executing the JSON.stringify algorithm specified in [ECMA-262] in the context of a new global object.
|
||||
auto maybe_json = JS::JSONObject::stringify_impl(realm.vm(), key_data, JS::Value {}, JS::Value {});
|
||||
if (maybe_json.is_error()) {
|
||||
WebIDL::reject_promise(realm, promise, maybe_json.release_error().release_value().value());
|
||||
return;
|
||||
}
|
||||
|
||||
// 3. Let bytes be the result of UTF-8 encoding json.
|
||||
bytes = maybe_json.release_value()->to_byte_buffer();
|
||||
} else {
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
JS::Value result;
|
||||
// 14. If normalizedAlgorithm supports the wrap key operation:
|
||||
if (operation == "wrapKey") {
|
||||
// Let result be the result of performing the wrap key operation specified by normalizedAlgorithm
|
||||
// using algorithm, wrappingKey as key and bytes as plaintext.
|
||||
auto result_or_error = normalized_algorithm.methods->wrap_key(*normalized_algorithm.parameter, wrapping_key, bytes);
|
||||
if (result_or_error.is_error()) {
|
||||
WebIDL::reject_promise(realm, promise, Bindings::exception_to_throw_completion(realm.vm(), result_or_error.release_error()).release_value().value());
|
||||
return;
|
||||
}
|
||||
|
||||
result = result_or_error.release_value();
|
||||
}
|
||||
|
||||
// Otherwise, if normalizedAlgorithm supports the encrypt operation:
|
||||
else if (operation == "encrypt") {
|
||||
// Let result be the result of performing the encrypt operation specified by normalizedAlgorithm
|
||||
// using algorithm, wrappingKey as key and bytes as plaintext.
|
||||
auto result_or_error = normalized_algorithm.methods->encrypt(*normalized_algorithm.parameter, wrapping_key, bytes);
|
||||
if (result_or_error.is_error()) {
|
||||
WebIDL::reject_promise(realm, promise, Bindings::exception_to_throw_completion(realm.vm(), result_or_error.release_error()).release_value().value());
|
||||
return;
|
||||
}
|
||||
|
||||
result = result_or_error.release_value();
|
||||
}
|
||||
|
||||
// Otherwise:
|
||||
else {
|
||||
// throw a NotSupportedError.
|
||||
WebIDL::reject_promise(realm, promise, WebIDL::NotSupportedError::create(realm, "Algorithm does not support wrapping"_string));
|
||||
return;
|
||||
}
|
||||
|
||||
// 15. Resolve promise with result.
|
||||
WebIDL::resolve_promise(realm, promise, result);
|
||||
}));
|
||||
|
||||
return promise;
|
||||
}
|
||||
|
||||
// https://w3c.github.io/webcrypto/#SubtleCrypto-method-unwrapKey
|
||||
JS::ThrowCompletionOr<GC::Ref<WebIDL::Promise>> SubtleCrypto::unwrap_key(Bindings::KeyFormat format, KeyDataType wrapped_key, GC::Ref<CryptoKey> unwrapping_key, AlgorithmIdentifier algorithm, AlgorithmIdentifier unwrapped_key_algorithm, bool extractable, Vector<Bindings::KeyUsage> key_usages)
|
||||
{
|
||||
auto& realm = this->realm();
|
||||
// 1. Let format, unwrappingKey, algorithm, unwrappedKeyAlgorithm, extractable and usages, be the format, unwrappingKey, unwrapAlgorithm,
|
||||
// unwrappedKeyAlgorithm, extractable and keyUsages parameters passed to the unwrapKey() method, respectively.
|
||||
|
||||
// 2. Let wrappedKey be the result of getting a copy of the bytes held by the wrappedKey parameter passed to the unwrapKey() method.
|
||||
auto real_wrapped_key = MUST(WebIDL::get_buffer_source_copy(*wrapped_key.get<GC::Root<WebIDL::BufferSource>>()->raw_object()));
|
||||
|
||||
StringView operation;
|
||||
auto normalized_algorithm_or_error = [&]() -> WebIDL::ExceptionOr<NormalizedAlgorithmAndParameter> {
|
||||
// 3. Let normalizedAlgorithm be the result of normalizing an algorithm, with alg set to algorithm and op set to "unwrapKey".
|
||||
auto normalized_algorithm_unwrap_key_or_error = normalize_an_algorithm(realm, algorithm, "unwrapKey"_string);
|
||||
|
||||
// 4. If an error occurred, let normalizedAlgorithm be the result of normalizing an algorithm, with alg set to algorithm and op set to "decrypt".
|
||||
// 5. If an error occurred, return a Promise rejected with normalizedAlgorithm.
|
||||
if (normalized_algorithm_unwrap_key_or_error.is_error()) {
|
||||
auto normalized_algorithm_decrypt_or_error = normalize_an_algorithm(realm, algorithm, "decrypt"_string);
|
||||
if (normalized_algorithm_decrypt_or_error.is_error())
|
||||
return normalized_algorithm_decrypt_or_error.release_error();
|
||||
|
||||
operation = "decrypt"sv;
|
||||
return normalized_algorithm_decrypt_or_error.release_value();
|
||||
} else {
|
||||
operation = "unwrapKey"sv;
|
||||
return normalized_algorithm_unwrap_key_or_error.release_value();
|
||||
}
|
||||
}();
|
||||
if (normalized_algorithm_or_error.is_error())
|
||||
return WebIDL::create_rejected_promise_from_exception(realm, normalized_algorithm_or_error.release_error());
|
||||
|
||||
auto normalized_algorithm = normalized_algorithm_or_error.release_value();
|
||||
|
||||
// 6. Let normalizedKeyAlgorithm be the result of normalizing an algorithm, with alg set to unwrappedKeyAlgorithm and op set to "importKey".
|
||||
auto normalized_key_algorithm_or_error = normalize_an_algorithm(realm, unwrapped_key_algorithm, "importKey"_string);
|
||||
if (normalized_key_algorithm_or_error.is_error()) {
|
||||
// 7. If an error occurred, return a Promise rejected with normalizedKeyAlgorithm.
|
||||
return WebIDL::create_rejected_promise_from_exception(realm, normalized_key_algorithm_or_error.release_error());
|
||||
}
|
||||
|
||||
auto normalized_key_algorithm = normalized_key_algorithm_or_error.release_value();
|
||||
|
||||
// 8. Let promise be a new Promise.
|
||||
auto promise = WebIDL::create_promise(realm);
|
||||
|
||||
// 9. Return promise and perform the remaining steps in parallel.
|
||||
Platform::EventLoopPlugin::the().deferred_invoke(GC::create_function(realm.heap(), [&realm, normalized_algorithm = move(normalized_algorithm), promise, unwrapping_key = unwrapping_key, real_wrapped_key = move(real_wrapped_key), operation, format, extractable, key_usages = move(key_usages), normalized_key_algorithm = move(normalized_key_algorithm)]() mutable -> void {
|
||||
HTML::TemporaryExecutionContext context(realm, HTML::TemporaryExecutionContext::CallbacksEnabled::Yes);
|
||||
// 10. If the following steps or referenced procedures say to throw an error, reject promise with the returned error and then terminate the algorithm.
|
||||
|
||||
// 11. If the name member of normalizedAlgorithm is not equal to the name attribute of the [[algorithm]] internal slot
|
||||
// of unwrappingKey then throw an InvalidAccessError.
|
||||
if (normalized_algorithm.parameter->name != unwrapping_key->algorithm_name()) {
|
||||
WebIDL::reject_promise(realm, promise, WebIDL::InvalidAccessError::create(realm, "Algorithm mismatch"_string));
|
||||
return;
|
||||
}
|
||||
|
||||
// 12. If the [[usages]] internal slot of unwrappingKey does not contain an entry that is "unwrapKey", then throw an InvalidAccessError.
|
||||
if (!unwrapping_key->internal_usages().contains_slow(Bindings::KeyUsage::Unwrapkey)) {
|
||||
WebIDL::reject_promise(realm, promise, WebIDL::InvalidAccessError::create(realm, "Key does not support unwrapping keys"_string));
|
||||
return;
|
||||
}
|
||||
|
||||
auto key_or_error = [&]() -> WebIDL::ExceptionOr<GC::Ref<JS::ArrayBuffer>> {
|
||||
// 13. If normalizedAlgorithm supports an unwrap key operation:
|
||||
if (operation == "unwrapKey") {
|
||||
// Let key be the result of performing the unwrap key operation specified by normalizedAlgorithm
|
||||
// using algorithm, unwrappingKey as key and wrappedKey as ciphertext.
|
||||
return normalized_algorithm.methods->unwrap_key(*normalized_algorithm.parameter, unwrapping_key, real_wrapped_key);
|
||||
}
|
||||
|
||||
// Otherwise, if normalizedAlgorithm supports a decrypt operation:
|
||||
else if (operation == "decrypt") {
|
||||
// Let key be the result of performing the decrypt operation specified by normalizedAlgorithm
|
||||
// using algorithm, unwrappingKey as key and wrappedKey as ciphertext.
|
||||
return normalized_algorithm.methods->decrypt(*normalized_algorithm.parameter, unwrapping_key, real_wrapped_key);
|
||||
}
|
||||
|
||||
// Otherwise:
|
||||
else {
|
||||
// throw a NotSupportedError.
|
||||
return WebIDL::NotSupportedError::create(realm, "Algorithm does not support wrapping"_string);
|
||||
}
|
||||
}();
|
||||
if (key_or_error.is_error()) {
|
||||
WebIDL::reject_promise(realm, promise, Bindings::exception_to_throw_completion(realm.vm(), key_or_error.release_error()).release_value().value());
|
||||
return;
|
||||
}
|
||||
|
||||
auto key = key_or_error.release_value();
|
||||
|
||||
Variant<ByteBuffer, Bindings::JsonWebKey, Empty> bytes;
|
||||
|
||||
// 14. If format is equal to the strings "raw", "pkcs8", or "spki":
|
||||
if (format == Bindings::KeyFormat::Raw || format == Bindings::KeyFormat::Pkcs8 || format == Bindings::KeyFormat::Spki) {
|
||||
// Set bytes be set to key.
|
||||
bytes = key->buffer();
|
||||
}
|
||||
|
||||
// If format is equal to the string "jwk":
|
||||
else if (format == Bindings::KeyFormat::Jwk) {
|
||||
// Let bytes be the result of executing the parse a JWK algorithm, with key as the data to be parsed.
|
||||
auto maybe_parsed = Bindings::JsonWebKey::parse(realm, key->buffer());
|
||||
if (maybe_parsed.is_error()) {
|
||||
WebIDL::reject_promise(realm, promise, maybe_parsed.release_error().release_value().value());
|
||||
return;
|
||||
}
|
||||
|
||||
bytes = maybe_parsed.release_value();
|
||||
} else {
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
// 15. Let result be the result of performing the import key operation specified by normalizedKeyAlgorithm
|
||||
// using unwrappedKeyAlgorithm as algorithm, format, usages and extractable and with bytes as keyData.
|
||||
auto result_or_error = normalized_key_algorithm.methods->import_key(*normalized_key_algorithm.parameter, format, bytes.downcast<CryptoKey::InternalKeyData>(), extractable, key_usages);
|
||||
if (result_or_error.is_error()) {
|
||||
WebIDL::reject_promise(realm, promise, Bindings::exception_to_throw_completion(realm.vm(), result_or_error.release_error()).release_value().value());
|
||||
return;
|
||||
}
|
||||
|
||||
auto result = result_or_error.release_value();
|
||||
|
||||
// 16. If the [[type]] internal slot of result is "secret" or "private" and usages is empty, then throw a SyntaxError.
|
||||
if ((result->type() == Bindings::KeyType::Secret || result->type() == Bindings::KeyType::Private) && key_usages.is_empty()) {
|
||||
WebIDL::reject_promise(realm, promise, WebIDL::SyntaxError::create(realm, "Usages must not be empty"_string));
|
||||
return;
|
||||
}
|
||||
|
||||
// 17. Set the [[extractable]] internal slot of result to extractable.
|
||||
result->set_extractable(extractable);
|
||||
|
||||
// 18. Set the [[usages]] internal slot of result to the normalized value of usages.
|
||||
normalize_key_usages(key_usages);
|
||||
result->set_usages(key_usages);
|
||||
|
||||
// 19. Resolve promise with result.
|
||||
WebIDL::resolve_promise(realm, promise, result);
|
||||
}));
|
||||
|
||||
return promise;
|
||||
}
|
||||
|
||||
SupportedAlgorithmsMap& supported_algorithms_internal()
|
||||
{
|
||||
static SupportedAlgorithmsMap s_supported_algorithms;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue