1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-05-31 13:18:13 +00:00

LibWeb: Refactor SubtleCrypto to allow adding more algorithms easier

This patch throws away some of the spec suggestions for how to implement
the normalize_algorithm AO and uses a new pattern that we can actually
extend in our C++.

Also update CryptoKey to store the key data.
This commit is contained in:
Andrew Kaster 2024-03-06 16:53:50 -07:00 committed by Andrew Kaster
parent 644e764620
commit 2d59d6c98c
9 changed files with 294 additions and 136 deletions

View file

@ -41,17 +41,17 @@ void SubtleCrypto::initialize(JS::Realm& realm)
}
// https://w3c.github.io/webcrypto/#dfn-normalize-an-algorithm
JS::ThrowCompletionOr<Bindings::Algorithm> SubtleCrypto::normalize_an_algorithm(AlgorithmIdentifier const& algorithm, String operation)
WebIDL::ExceptionOr<SubtleCrypto::NormalizedAlgorithmAndParameter> SubtleCrypto::normalize_an_algorithm(AlgorithmIdentifier const& algorithm, String operation)
{
auto& realm = this->realm();
auto& vm = this->vm();
// If alg is an instance of a DOMString:
if (algorithm.has<String>()) {
// Return the result of running the normalize an algorithm algorithm,
// with the alg set to a new Algorithm dictionary whose name attribute is alg, and with the op set to op.
auto dictionary = JS::make_handle(JS::Object::create(realm, realm.intrinsics().object_prototype()));
TRY(dictionary->create_data_property("name", JS::PrimitiveString::create(realm.vm(), algorithm.get<String>())));
TRY(dictionary->create_data_property("op", JS::PrimitiveString::create(realm.vm(), operation)));
TRY(dictionary->create_data_property("name", JS::PrimitiveString::create(vm, algorithm.get<String>())));
return normalize_an_algorithm(dictionary, operation);
}
@ -65,49 +65,38 @@ JS::ThrowCompletionOr<Bindings::Algorithm> SubtleCrypto::normalize_an_algorithm(
// 2. Let initialAlg be the result of converting the ECMAScript object represented by alg to
// the IDL dictionary type Algorithm, as defined by [WebIDL].
// FIXME: How do we turn this into an "Algorithm" in a nice way?
// NOTE: For now, we just use the object as-is.
auto initial_algorithm = algorithm.get<JS::Handle<JS::Object>>();
// 3. If an error occurred, return the error and terminate this algorithm.
auto has_name = TRY(initial_algorithm->has_property("name"));
if (!has_name) {
return realm.vm().throw_completion<JS::TypeError>(JS::ErrorType::NotAnObjectOfType, "Algorithm");
}
// Note: We're not going to bother creating an Algorithm object, all we want is the name attribute so that we can
// fetch the actual algorithm factory from the registeredAlgorithms map.
auto initial_algorithm = TRY(algorithm.get<JS::Handle<JS::Object>>()->get("name"));
// 4. Let algName be the value of the name attribute of initialAlg.
auto algorithm_name = TRY(TRY(initial_algorithm->get("name")).to_string(realm.vm()));
auto algorithm_name = TRY(initial_algorithm.to_string(vm));
String desired_type;
RegisteredAlgorithm desired_type;
// 5. If registeredAlgorithms contains a key that is a case-insensitive string match for algName:
if (registered_algorithms.contains(algorithm_name)) {
if (auto it = registered_algorithms.find(algorithm_name); it != registered_algorithms.end()) {
// 1. Set algName to the value of the matching key.
auto it = registered_algorithms.find(algorithm_name);
algorithm_name = (*it).key;
// 2. Let desiredType be the IDL dictionary type stored at algName in registeredAlgorithms.
desired_type = (*it).value;
desired_type = it->value;
} else {
// Otherwise:
// Return a new NotSupportedError and terminate this algorithm.
// FIXME: This should be a DOMException
return realm.vm().throw_completion<JS::TypeError>(JS::ErrorType::NotImplemented, algorithm_name);
return WebIDL::NotSupportedError::create(realm, MUST(String::formatted("Algorithm '{}' is not supported", algorithm_name)));
}
// 8. Let normalizedAlgorithm be the result of converting the ECMAScript object represented by alg
// to the IDL dictionary type desiredType, as defined by [WebIDL].
// FIXME: Should IDL generate a struct for each of these?
Bindings::Algorithm normalized_algorithm;
// 9. Set the name attribute of normalizedAlgorithm to algName.
normalized_algorithm.name = algorithm_name;
// 10. If an error occurred, return the error and terminate this algorithm.
// FIXME: 11. Let dictionaries be a list consisting of the IDL dictionary type desiredType
// 11. Let dictionaries be a list consisting of the IDL dictionary type desiredType
// and all of desiredType's inherited dictionaries, in order from least to most derived.
// FIXME: 12. For each dictionary dictionary in dictionaries:
// 12. For each dictionary dictionary in dictionaries:
// Note: All of these steps are handled by the create_methods and parameter_from_value methods.
auto methods = desired_type.create_methods(realm);
auto parameter = TRY(desired_type.parameter_from_value(vm, algorithm.get<JS::Handle<JS::Object>>()));
auto normalized_algorithm = NormalizedAlgorithmAndParameter { move(methods), move(parameter) };
// 13. Return normalizedAlgorithm.
return normalized_algorithm;
@ -145,36 +134,15 @@ JS::NonnullGCPtr<JS::Promise> SubtleCrypto::digest(AlgorithmIdentifier const& al
// FIXME: Need spec reference to https://webidl.spec.whatwg.org/#reject
// 8. Let result be the result of performing the digest operation specified by normalizedAlgorithm using algorithm, with data as message.
auto algorithm_name = algorithm_object.name;
auto result = algorithm_object.methods->digest(*algorithm_object.parameter, data_buffer);
::Crypto::Hash::HashKind hash_kind;
if (algorithm_name.equals_ignoring_ascii_case("SHA-1"sv)) {
hash_kind = ::Crypto::Hash::HashKind::SHA1;
} else if (algorithm_name.equals_ignoring_ascii_case("SHA-256"sv)) {
hash_kind = ::Crypto::Hash::HashKind::SHA256;
} else if (algorithm_name.equals_ignoring_ascii_case("SHA-384"sv)) {
hash_kind = ::Crypto::Hash::HashKind::SHA384;
} else if (algorithm_name.equals_ignoring_ascii_case("SHA-512"sv)) {
hash_kind = ::Crypto::Hash::HashKind::SHA512;
} else {
WebIDL::reject_promise(realm, promise, WebIDL::NotSupportedError::create(realm, MUST(String::formatted("Invalid hash function '{}'", algorithm_name))));
if (result.is_exception()) {
WebIDL::reject_promise(realm, promise, Bindings::dom_exception_to_throw_completion(realm.vm(), result.release_error()).release_value().value());
return;
}
::Crypto::Hash::Manager hash { hash_kind };
hash.update(data_buffer);
auto digest = hash.digest();
auto result_buffer = ByteBuffer::copy(digest.immutable_data(), hash.digest_size());
if (result_buffer.is_error()) {
WebIDL::reject_promise(realm, promise, WebIDL::OperationError::create(realm, "Failed to create result buffer"_fly_string));
return;
}
auto result = JS::ArrayBuffer::create(realm, result_buffer.release_value());
// 9. Resolve promise with result.
WebIDL::resolve_promise(realm, promise, result);
WebIDL::resolve_promise(realm, promise, result.release_value());
});
return verify_cast<JS::Promise>(*promise->promise());
@ -222,19 +190,14 @@ JS::ThrowCompletionOr<JS::NonnullGCPtr<JS::Promise>> SubtleCrypto::import_key(Bi
auto promise = WebIDL::create_promise(realm);
// 8. Return promise and perform the remaining steps in parallel.
Platform::EventLoopPlugin::the().deferred_invoke([&realm, this, real_key_data = move(real_key_data), normalized_algorithm = normalized_algorithm.release_value(), promise, format, extractable, key_usages = move(key_usages), algorithm = move(algorithm)]() -> void {
Platform::EventLoopPlugin::the().deferred_invoke([&realm, real_key_data = move(real_key_data), normalized_algorithm = normalized_algorithm.release_value(), promise, format, extractable, key_usages = move(key_usages), algorithm = move(algorithm)]() -> void {
HTML::TemporaryExecutionContext context(Bindings::host_defined_environment_settings_object(realm), HTML::TemporaryExecutionContext::CallbacksEnabled::Yes);
// 9. If the following steps or referenced procedures say to throw an error, reject promise with the returned error and then terminate the algorithm.
// 10. Let result be the CryptoKey object that results from performing the import key operation
// specified by normalizedAlgorithm using keyData, algorithm, format, extractable and usages.
if (normalized_algorithm.name != "PBKDF2"sv) {
WebIDL::reject_promise(realm, promise, WebIDL::NotSupportedError::create(realm, MUST(String::formatted("Invalid algorithm '{}'", normalized_algorithm.name))));
return;
}
auto maybe_result = pbkdf2_import_key(real_key_data, algorithm, format, extractable, key_usages);
auto maybe_result = normalized_algorithm.methods->import_key(*normalized_algorithm.parameter, format, real_key_data.downcast<CryptoKey::InternalKeyData>(), extractable, key_usages);
if (maybe_result.is_error()) {
WebIDL::reject_promise(realm, promise, Bindings::dom_exception_to_throw_completion(realm.vm(), maybe_result.release_error()).release_value().value());
return;
@ -298,13 +261,13 @@ SubtleCrypto::SupportedAlgorithmsMap SubtleCrypto::supported_algorithms()
// https://w3c.github.io/webcrypto/#algorithm-conventions
// https://w3c.github.io/webcrypto/#sha
define_an_algorithm("digest"_string, "SHA-1"_string, ""_string);
define_an_algorithm("digest"_string, "SHA-256"_string, ""_string);
define_an_algorithm("digest"_string, "SHA-384"_string, ""_string);
define_an_algorithm("digest"_string, "SHA-512"_string, ""_string);
define_an_algorithm<SHA>("digest"_string, "SHA-1"_string);
define_an_algorithm<SHA>("digest"_string, "SHA-256"_string);
define_an_algorithm<SHA>("digest"_string, "SHA-384"_string);
define_an_algorithm<SHA>("digest"_string, "SHA-512"_string);
// https://w3c.github.io/webcrypto/#pbkdf2
define_an_algorithm("importKey"_string, "PBKDF2"_string, ""_string);
define_an_algorithm<PBKDF2>("importKey"_string, "PBKDF2"_string);
// FIXME: define_an_algorithm("deriveBits"_string, "PBKDF2"_string, "Pbkdf2Params"_string);
// FIXME: define_an_algorithm("get key length"_string, "PBKDF2"_string, ""_string);
@ -312,7 +275,8 @@ SubtleCrypto::SupportedAlgorithmsMap SubtleCrypto::supported_algorithms()
}
// https://w3c.github.io/webcrypto/#concept-define-an-algorithm
void SubtleCrypto::define_an_algorithm(String op, String algorithm, String type)
template<typename Methods, typename Param>
void SubtleCrypto::define_an_algorithm(AK::String op, AK::String algorithm)
{
auto& internal_object = supported_algorithms_internal();
@ -322,51 +286,8 @@ void SubtleCrypto::define_an_algorithm(String op, String algorithm, String type)
auto registered_algorithms = maybe_registered_algorithms.value();
// 2. Set the alg key of registeredAlgorithms to the IDL dictionary type type.
registered_algorithms.set(algorithm, type);
registered_algorithms.set(algorithm, RegisteredAlgorithm { &Methods::create, &Param::from_value });
internal_object.set(op, registered_algorithms);
}
// https://w3c.github.io/webcrypto/#pbkdf2-operations
WebIDL::ExceptionOr<JS::NonnullGCPtr<CryptoKey>> SubtleCrypto::pbkdf2_import_key([[maybe_unused]] Variant<ByteBuffer, Bindings::JsonWebKey, Empty> key_data, [[maybe_unused]] AlgorithmIdentifier algorithm_parameter, Bindings::KeyFormat format, bool extractable, Vector<Bindings::KeyUsage> key_usages)
{
auto& realm = this->realm();
// 1. If format is not "raw", throw a NotSupportedError
if (format != Bindings::KeyFormat::Raw) {
return WebIDL::NotSupportedError::create(realm, "Only raw format is supported"_fly_string);
}
// 2. If usages contains a value that is not "deriveKey" or "deriveBits", then throw a SyntaxError.
for (auto& usage : key_usages) {
if (usage != Bindings::KeyUsage::Derivekey && usage != Bindings::KeyUsage::Derivebits) {
return WebIDL::SyntaxError::create(realm, MUST(String::formatted("Invalid key usage '{}'", idl_enum_to_string(usage))));
}
}
// 3. If extractable is not false, then throw a SyntaxError.
if (extractable)
return WebIDL::SyntaxError::create(realm, "extractable must be false"_fly_string);
// 4. Let key be a new CryptoKey representing keyData.
auto key = CryptoKey::create(realm);
// 5. Set the [[type]] internal slot of key to "secret".
key->set_type(Bindings::KeyType::Secret);
// 6. Set the [[extractable]] internal slot of key to false.
key->set_extractable(false);
// 7. Let algorithm be a new KeyAlgorithm object.
auto algorithm = Bindings::KeyAlgorithm::create(realm);
// 8. Set the name attribute of algorithm to "PBKDF2".
algorithm->set_name("PBKDF2"_string);
// 9. Set the [[algorithm]] internal slot of key to algorithm.
key->set_algorithm(algorithm);
// 10. Return key.
return key;
}
}