From d86dcac4f7e72bba81d9e6cf61c75b5d018f27d5 Mon Sep 17 00:00:00 2001 From: Ben Wiederhake Date: Fri, 25 Oct 2024 10:38:33 +0200 Subject: [PATCH] LibWeb: Implement WebCrypto AES-CBC generateKey operation This is progress towards passing more WPT tests, although none of them gets green due to this commit. --- .../LibWeb/Crypto/CryptoAlgorithms.cpp | 98 ++++++++++++++++++- .../LibWeb/Crypto/CryptoAlgorithms.h | 30 ++++++ .../Libraries/LibWeb/Crypto/SubtleCrypto.cpp | 4 +- 3 files changed, 126 insertions(+), 6 deletions(-) diff --git a/Userland/Libraries/LibWeb/Crypto/CryptoAlgorithms.cpp b/Userland/Libraries/LibWeb/Crypto/CryptoAlgorithms.cpp index 48013255838..4a3544d0cc8 100644 --- a/Userland/Libraries/LibWeb/Crypto/CryptoAlgorithms.cpp +++ b/Userland/Libraries/LibWeb/Crypto/CryptoAlgorithms.cpp @@ -228,6 +228,13 @@ static WebIDL::ExceptionOr parse_jwk_symmetric_key(JS::Realm& realm, return base64_url_bytes_decode(realm, *jwk.k); } +static WebIDL::ExceptionOr generate_aes_key(JS::VM& vm, u16 bits) +{ + auto key_buffer = TRY_OR_THROW_OOM(vm, ByteBuffer::create_uninitialized(bits / 8)); + fill_with_random(key_buffer); + return key_buffer; +} + AlgorithmParams::~AlgorithmParams() = default; JS::ThrowCompletionOr> AlgorithmParams::from_value(JS::VM& vm, JS::Value value) @@ -444,6 +451,36 @@ JS::ThrowCompletionOr> EcKeyGenParams::from_value return adopt_own(*new EcKeyGenParams { name, curve }); } +AesKeyGenParams::~AesKeyGenParams() = default; + +JS::ThrowCompletionOr> AesKeyGenParams::from_value(JS::VM& vm, JS::Value value) +{ + auto& object = value.as_object(); + + auto name_value = TRY(object.get("name")); + auto name = TRY(name_value.to_string(vm)); + + auto length_value = TRY(object.get("length")); + auto length = TRY(length_value.to_u16(vm)); + + return adopt_own(*new AesKeyGenParams { name, length }); +} + +AesDerivedKeyParams::~AesDerivedKeyParams() = default; + +JS::ThrowCompletionOr> AesDerivedKeyParams::from_value(JS::VM& vm, JS::Value value) +{ + auto& object = value.as_object(); + + auto name_value = TRY(object.get("name")); + auto name = TRY(name_value.to_string(vm)); + + auto length_value = TRY(object.get("length")); + auto length = TRY(length_value.to_u16(vm)); + + return adopt_own(*new AesDerivedKeyParams { name, length }); +} + // https://w3c.github.io/webcrypto/#rsa-oaep-operations WebIDL::ExceptionOr> RSAOAEP::encrypt(AlgorithmParams const& params, JS::NonnullGCPtr key, ByteBuffer const& plaintext) { @@ -1121,9 +1158,55 @@ WebIDL::ExceptionOr> AesCbc::import_key(AlgorithmPar return key; } -WebIDL::ExceptionOr, JS::NonnullGCPtr>> AesCbc::generate_key(AlgorithmParams const&, bool, Vector const&) +WebIDL::ExceptionOr, JS::NonnullGCPtr>> AesCbc::generate_key(AlgorithmParams const& params, bool extractable, Vector const& key_usages) { - VERIFY_NOT_REACHED(); + // 1. If usages contains any entry which is not one of "encrypt", "decrypt", "wrapKey" or "unwrapKey", then throw a SyntaxError. + for (auto const& usage : key_usages) { + if (usage != Bindings::KeyUsage::Encrypt && usage != Bindings::KeyUsage::Decrypt && usage != Bindings::KeyUsage::Wrapkey && usage != Bindings::KeyUsage::Unwrapkey) { + return WebIDL::SyntaxError::create(m_realm, MUST(String::formatted("Invalid key usage '{}'", idl_enum_to_string(usage)))); + } + } + + auto const& normalized_algorithm = static_cast(params); + + // 2. If the length member of normalizedAlgorithm is not equal to one of 128, 192 or 256, then throw an OperationError. + auto bits = normalized_algorithm.length; + if (bits != 128 && bits != 192 && bits != 256) { + return WebIDL::OperationError::create(m_realm, MUST(String::formatted("Cannot create AES-CBC key with unusual amount of {} bits", bits))); + } + + // 3. Generate an AES key of length equal to the length member of normalizedAlgorithm. + auto key_buffer = TRY(generate_aes_key(m_realm->vm(), bits / 8)); + + // 4. If the key generation step fails, then throw an OperationError. + // Note: Cannot happen in our implementation; and if we OOM, then allocating the Exception is probably going to crash anyway. + + // 5. Let key be a new CryptoKey object representing the generated AES key. + auto key = CryptoKey::create(m_realm, CryptoKey::InternalKeyData { key_buffer }); + + // 6. Let algorithm be a new AesKeyAlgorithm. + auto algorithm = AesKeyAlgorithm::create(m_realm); + + // 7. Set the name attribute of algorithm to "AES-CBC". + algorithm->set_name("AES-CBC"_string); + + // 8. Set the length attribute of algorithm to equal the length member of normalizedAlgorithm. + algorithm->set_length(bits); + + // 9. Set the [[type]] internal slot of key to "secret". + key->set_type(Bindings::KeyType::Secret); + + // 10. Set the [[algorithm]] internal slot of key to algorithm. + key->set_algorithm(algorithm); + + // 11. Set the [[extractable]] internal slot of key to be extractable. + key->set_extractable(extractable); + + // 12. Set the [[usages]] internal slot of key to be usages. + key->set_usages(key_usages); + + // 13. Return key. + return { key }; } WebIDL::ExceptionOr> AesCbc::export_key(Bindings::KeyFormat format, JS::NonnullGCPtr key) @@ -1194,9 +1277,16 @@ WebIDL::ExceptionOr> AesCbc::export_key(Bindings::K return JS::NonnullGCPtr { *result }; } -WebIDL::ExceptionOr AesCbc::get_key_length(AlgorithmParams const&) +WebIDL::ExceptionOr AesCbc::get_key_length(AlgorithmParams const& params) { - VERIFY_NOT_REACHED(); + // 1. If the length member of normalizedDerivedKeyAlgorithm is not 128, 192 or 256, then throw an OperationError. + auto const& normalized_algorithm = static_cast(params); + auto length = normalized_algorithm.length; + if (length != 128 && length != 192 && length != 256) + return WebIDL::OperationError::create(m_realm, "Invalid key length"_string); + + // 2. Return the length member of normalizedDerivedKeyAlgorithm. + return JS::Value(length); } // https://w3c.github.io/webcrypto/#hkdf-operations diff --git a/Userland/Libraries/LibWeb/Crypto/CryptoAlgorithms.h b/Userland/Libraries/LibWeb/Crypto/CryptoAlgorithms.h index 16b4815eb45..0509a72ad9b 100644 --- a/Userland/Libraries/LibWeb/Crypto/CryptoAlgorithms.h +++ b/Userland/Libraries/LibWeb/Crypto/CryptoAlgorithms.h @@ -180,6 +180,36 @@ struct EcKeyGenParams : public AlgorithmParams { static JS::ThrowCompletionOr> from_value(JS::VM&, JS::Value); }; +// https://w3c.github.io/webcrypto/#dfn-AesKeyGenParams +struct AesKeyGenParams : public AlgorithmParams { + virtual ~AesKeyGenParams() override; + + AesKeyGenParams(String name, u16 length) + : AlgorithmParams(move(name)) + , length(length) + { + } + + u16 length; + + static JS::ThrowCompletionOr> from_value(JS::VM&, JS::Value); +}; + +// https://w3c.github.io/webcrypto/#dfn-AesDerivedKeyParams +struct AesDerivedKeyParams : public AlgorithmParams { + virtual ~AesDerivedKeyParams() override; + + AesDerivedKeyParams(String name, u16 length) + : AlgorithmParams(move(name)) + , length(length) + { + } + + u16 length; + + static JS::ThrowCompletionOr> from_value(JS::VM&, JS::Value); +}; + class AlgorithmMethods { public: virtual ~AlgorithmMethods(); diff --git a/Userland/Libraries/LibWeb/Crypto/SubtleCrypto.cpp b/Userland/Libraries/LibWeb/Crypto/SubtleCrypto.cpp index 3d5fd8d11f1..54b78a8e692 100644 --- a/Userland/Libraries/LibWeb/Crypto/SubtleCrypto.cpp +++ b/Userland/Libraries/LibWeb/Crypto/SubtleCrypto.cpp @@ -771,10 +771,10 @@ SupportedAlgorithmsMap supported_algorithms() // https://w3c.github.io/webcrypto/#aes-cbc-registration // FIXME: define_an_algorithm("encrypt"_string, "AES-CBC"_string); // FIXME: define_an_algorithm("decrypt"_string, "AES-CBC"_string); - // FIXME: define_an_algorithm("generateKey"_string, "AES-CBC"_string); + define_an_algorithm("generateKey"_string, "AES-CBC"_string); define_an_algorithm("importKey"_string, "AES-CBC"_string); define_an_algorithm("exportKey"_string, "AES-CBC"_string); - // FIXME: define_an_algorithm("get key length"_string, "AES-CBC"_string); + define_an_algorithm("get key length"_string, "AES-CBC"_string); // https://w3c.github.io/webcrypto/#hkdf define_an_algorithm("importKey"_string, "HKDF"_string);