Browse Source

LibWeb: Implement SubtleCrypto.generateKey for RSA-OAEP

This patch implements and tests window.crypto.sublte.generateKey with
an RSA-OAEP algorithm. In order for the types to be happy, the
KeyAlgorithms objects are moved to their own .h/.cpp pair, and the new
KeyAlgorithms for RSA are added there.
Andrew Kaster 1 year ago
parent
commit
a9d240c647

+ 2 - 1
Meta/gn/secondary/Userland/Libraries/LibWeb/Crypto/BUILD.gn

@@ -6,10 +6,11 @@ source_set("Crypto") {
     "Crypto.h",
     "CryptoAlgorithms.cpp",
     "CryptoAlgorithms.h",
-    "CryptoBindings.cpp",
     "CryptoBindings.h",
     "CryptoKey.cpp",
     "CryptoKey.h",
+    "KeyAlgorithms.cpp",
+    "KeyAlgorithms.h",
     "SubtleCrypto.cpp",
     "SubtleCrypto.h",
   ]

+ 15 - 0
Tests/LibWeb/Text/expected/Crypto/SubtleCrypto-generateKey.txt

@@ -0,0 +1,15 @@
+generateKey with RSA-OAEP algorithm
+publicKey: [object CryptoKey]
+publicKey algorithm: {"name":"RSA-OAEP","modulusLength":512,"publicExponent":{"0":0,"1":1,"2":0},"hash":"SHA-256"}
+publicKey type: public
+publicKey extractable: true
+publicKey usages: encrypt,wrapKey
+privateKey: [object CryptoKey]
+privateKey algorithm: {"name":"RSA-OAEP","modulusLength":512,"publicExponent":{"0":0,"1":1,"2":0},"hash":"SHA-256"}
+privateKey type: private
+privateKey extractable: true
+privateKey usages: decrypt,unwrapKey
+invalid usages throw
+Error: [object DOMException]
+no usages for private key throws
+Error: [object DOMException]

+ 66 - 0
Tests/LibWeb/Text/input/Crypto/SubtleCrypto-generateKey.html

@@ -0,0 +1,66 @@
+<script src="../include.js"></script>
+<script>
+    function bufferToHex(buffer) {
+        return [...new Uint8Array(buffer)].map(b => b.toString(16).padStart(2, "0")).join("");
+    }
+
+    asyncTest(async done => {
+
+        // FIXME: Generate a key with module lengths longer than 512, when they don't take an eternity to generate.
+        let algorithm = {
+            name: "RSA-OAEP",
+            modulusLength: 512,
+            publicExponent: new Uint8Array([1, 0, 1]),
+            hash: "SHA-256",
+        };
+
+        println("generateKey with RSA-OAEP algorithm");
+        var key = undefined;
+        try {
+            key = await window.crypto.subtle.generateKey(
+                algorithm,
+                true,
+                ["encrypt", "decrypt", "wrapKey", "unwrapKey"]
+            );
+        } catch (e) {
+            println(`FAIL: ${e}`);
+        }
+
+        // FIXME: Report bugs to Chrome/Firefox about the order of properties on algorithm not matching us/Safari
+        println(`publicKey: ${key.publicKey}`);
+        println(`publicKey algorithm: ${JSON.stringify(key.publicKey.algorithm)}`);
+        println(`publicKey type: ${key.publicKey.type}`);
+        println(`publicKey extractable: ${key.publicKey.extractable}`);
+        println(`publicKey usages: ${key.publicKey.usages}`);
+
+        println(`privateKey: ${key.privateKey}`);
+        println(`privateKey algorithm: ${JSON.stringify(key.privateKey.algorithm)}`);
+        println(`privateKey type: ${key.privateKey.type}`);
+        println(`privateKey extractable: ${key.privateKey.extractable}`);
+        println(`privateKey usages: ${key.privateKey.usages}`);
+
+        println("invalid usages throw");
+        try {
+            const key2 = await window.crypto.subtle.generateKey(
+                algorithm,
+                true,
+                ["encrypt", "decrypt", "wrapKey", "unwrapKey", "sign"]
+            );
+        } catch (e) {
+            println(`Error: ${e}`);
+        }
+
+        println("no usages for private key throws");
+        try {
+            const key3 = await window.crypto.subtle.generateKey(
+                algorithm,
+                true,
+                ["encrypt", "wrapKey"]
+            );
+        } catch (e) {
+            println(`Error: ${e}`);
+        }
+
+        done();
+    });
+</script>

+ 1 - 1
Userland/Libraries/LibWeb/CMakeLists.txt

@@ -26,7 +26,7 @@ set(SOURCES
     Clipboard/Clipboard.cpp
     Crypto/Crypto.cpp
     Crypto/CryptoAlgorithms.cpp
-    Crypto/CryptoBindings.cpp
+    Crypto/KeyAlgorithms.cpp
     Crypto/CryptoKey.cpp
     Crypto/SubtleCrypto.cpp
     CSS/Angle.cpp

+ 163 - 1
Userland/Libraries/LibWeb/Crypto/CryptoAlgorithms.cpp

@@ -4,17 +4,59 @@
  * SPDX-License-Identifier: BSD-2-Clause
  */
 
+#include <AK/QuickSort.h>
 #include <LibCrypto/Hash/HashManager.h>
+#include <LibCrypto/PK/RSA.h>
 #include <LibJS/Runtime/ArrayBuffer.h>
 #include <LibJS/Runtime/DataView.h>
 #include <LibJS/Runtime/TypedArray.h>
 #include <LibWeb/Crypto/CryptoAlgorithms.h>
+#include <LibWeb/Crypto/KeyAlgorithms.h>
 
 namespace Web::Crypto {
 
+// https://w3c.github.io/webcrypto/#concept-usage-intersection
+static Vector<Bindings::KeyUsage> usage_intersection(ReadonlySpan<Bindings::KeyUsage> a, ReadonlySpan<Bindings::KeyUsage> b)
+{
+    Vector<Bindings::KeyUsage> result;
+    for (auto const& usage : a) {
+        if (b.contains_slow(usage))
+            result.append(usage);
+    }
+    quick_sort(result);
+    return result;
+}
+
 // Out of line to ensure this class has a key function
 AlgorithmMethods::~AlgorithmMethods() = default;
 
+// https://w3c.github.io/webcrypto/#big-integer
+static ::Crypto::UnsignedBigInteger big_integer_from_api_big_integer(JS::GCPtr<JS::Uint8Array> const& big_integer)
+{
+    static_assert(AK::HostIsLittleEndian, "This method needs special treatment for BE");
+
+    // The BigInteger typedef is a Uint8Array that holds an arbitrary magnitude unsigned integer
+    // **in big-endian order**. Values read from the API SHALL have minimal typed array length
+    // (that is, at most 7 leading zero bits, except the value 0 which shall have length 8 bits).
+    // The API SHALL accept values with any number of leading zero bits, including the empty array, which represents zero.
+
+    auto const& buffer = big_integer->viewed_array_buffer()->buffer();
+
+    ::Crypto::UnsignedBigInteger result(0);
+    if (buffer.size() > 0) {
+
+        // We need to reverse the buffer to get it into little-endian order
+        Vector<u8, 32> reversed_buffer;
+        reversed_buffer.resize(buffer.size());
+        for (size_t i = 0; i < buffer.size(); ++i) {
+            reversed_buffer[buffer.size() - i - 1] = buffer[i];
+        }
+
+        result = ::Crypto::UnsignedBigInteger::import_data(reversed_buffer.data(), reversed_buffer.size());
+    }
+    return result;
+}
+
 JS::ThrowCompletionOr<NonnullOwnPtr<AlgorithmParams>> AlgorithmParams::from_value(JS::VM& vm, JS::Value value)
 {
     auto& object = value.as_object();
@@ -57,6 +99,126 @@ JS::ThrowCompletionOr<NonnullOwnPtr<AlgorithmParams>> PBKDF2Params::from_value(J
     return adopt_own<AlgorithmParams>(*new PBKDF2Params { { name }, salt, iterations, hash.downcast<HashAlgorithmIdentifier>() });
 }
 
+JS::ThrowCompletionOr<NonnullOwnPtr<AlgorithmParams>> RsaKeyGenParams::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 modulus_length_value = TRY(object.get("modulusLength"));
+    auto modulus_length = TRY(modulus_length_value.to_u32(vm));
+
+    auto public_exponent_value = TRY(object.get("publicExponent"));
+    JS::GCPtr<JS::Uint8Array> public_exponent;
+
+    if (!public_exponent_value.is_object() || !is<JS::Uint8Array>(public_exponent_value.as_object()))
+        return vm.throw_completion<JS::TypeError>(JS::ErrorType::NotAnObjectOfType, "Uint8Array");
+
+    public_exponent = static_cast<JS::Uint8Array&>(public_exponent_value.as_object());
+
+    return adopt_own<AlgorithmParams>(*new RsaKeyGenParams { { name }, modulus_length, big_integer_from_api_big_integer(public_exponent) });
+}
+
+JS::ThrowCompletionOr<NonnullOwnPtr<AlgorithmParams>> RsaHashedKeyGenParams::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 modulus_length_value = TRY(object.get("modulusLength"));
+    auto modulus_length = TRY(modulus_length_value.to_u32(vm));
+
+    auto public_exponent_value = TRY(object.get("publicExponent"));
+    JS::GCPtr<JS::Uint8Array> public_exponent;
+
+    if (!public_exponent_value.is_object() || !is<JS::Uint8Array>(public_exponent_value.as_object()))
+        return vm.throw_completion<JS::TypeError>(JS::ErrorType::NotAnObjectOfType, "Uint8Array");
+
+    public_exponent = static_cast<JS::Uint8Array&>(public_exponent_value.as_object());
+
+    auto hash_value = TRY(object.get("hash"));
+    auto hash = Variant<Empty, HashAlgorithmIdentifier> { Empty {} };
+    if (hash_value.is_string()) {
+        auto hash_string = TRY(hash_value.to_string(vm));
+        hash = HashAlgorithmIdentifier { hash_string };
+    } else {
+        auto hash_object = TRY(hash_value.to_object(vm));
+        hash = HashAlgorithmIdentifier { hash_object };
+    }
+
+    return adopt_own<AlgorithmParams>(*new RsaHashedKeyGenParams { { { name }, modulus_length, big_integer_from_api_big_integer(public_exponent) }, hash.get<HashAlgorithmIdentifier>() });
+}
+
+// https://w3c.github.io/webcrypto/#rsa-oaep-operations
+WebIDL::ExceptionOr<Variant<JS::NonnullGCPtr<CryptoKey>, JS::NonnullGCPtr<CryptoKeyPair>>> RSAOAEP::generate_key(AlgorithmParams const& params, bool extractable, Vector<Bindings::KeyUsage> const& key_usages)
+{
+    // 1. If usages contains an entry which is not "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))));
+        }
+    }
+
+    // 2. Generate an RSA key pair, as defined in [RFC3447], with RSA modulus length equal to the modulusLength member of normalizedAlgorithm
+    //    and RSA public exponent equal to the publicExponent member of normalizedAlgorithm.
+    // 3. If performing the operation results in an error, then throw an OperationError.
+    auto const& normalized_algorithm = static_cast<RsaHashedKeyGenParams const&>(params);
+    auto key_pair = ::Crypto::PK::RSA::generate_key_pair(normalized_algorithm.modulus_length, normalized_algorithm.public_exponent);
+
+    // 4. Let algorithm be a new RsaHashedKeyAlgorithm object.
+    auto algorithm = RsaHashedKeyAlgorithm::create(m_realm);
+
+    // 5. Set the name attribute of algorithm to "RSA-OAEP".
+    algorithm->set_name("RSA-OAEP"_string);
+
+    // 6. Set the modulusLength attribute of algorithm to equal the modulusLength member of normalizedAlgorithm.
+    algorithm->set_modulus_length(normalized_algorithm.modulus_length);
+
+    // 7. Set the publicExponent attribute of algorithm to equal the publicExponent member of normalizedAlgorithm.
+    TRY(algorithm->set_public_exponent(normalized_algorithm.public_exponent));
+
+    // 8. Set the hash attribute of algorithm to equal the hash member of normalizedAlgorithm.
+    algorithm->set_hash(normalized_algorithm.hash);
+
+    // 9. Let publicKey be a new CryptoKey representing the public key of the generated key pair.
+    auto public_key = CryptoKey::create(m_realm, CryptoKey::InternalKeyData { key_pair.public_key });
+
+    // 10. Set the [[type]] internal slot of publicKey to "public"
+    public_key->set_type(Bindings::KeyType::Public);
+
+    // 11. Set the [[algorithm]] internal slot of publicKey to algorithm.
+    public_key->set_algorithm(algorithm);
+
+    // 12. Set the [[extractable]] internal slot of publicKey to true.
+    public_key->set_extractable(true);
+
+    // 13. Set the [[usages]] internal slot of publicKey to be the usage intersection of usages and [ "encrypt", "wrapKey" ].
+    public_key->set_usages(usage_intersection(key_usages, { { Bindings::KeyUsage::Encrypt, Bindings::KeyUsage::Wrapkey } }));
+
+    // 14. Let privateKey be a new CryptoKey representing the private key of the generated key pair.
+    auto private_key = CryptoKey::create(m_realm, CryptoKey::InternalKeyData { key_pair.private_key });
+
+    // 15. Set the [[type]] internal slot of privateKey to "private"
+    private_key->set_type(Bindings::KeyType::Private);
+
+    // 16. Set the [[algorithm]] internal slot of privateKey to algorithm.
+    private_key->set_algorithm(algorithm);
+
+    // 17. Set the [[extractable]] internal slot of privateKey to extractable.
+    private_key->set_extractable(extractable);
+
+    // 18. Set the [[usages]] internal slot of privateKey to be the usage intersection of usages and [ "decrypt", "unwrapKey" ].
+    private_key->set_usages(usage_intersection(key_usages, { { Bindings::KeyUsage::Decrypt, Bindings::KeyUsage::Unwrapkey } }));
+
+    // 19. Let result be a new CryptoKeyPair dictionary.
+    // 20. Set the publicKey attribute of result to be publicKey.
+    // 21. Set the privateKey attribute of result to be privateKey.
+    // 22. Return the result of converting result to an ECMAScript Object, as defined by [WebIDL].
+    return Variant<JS::NonnullGCPtr<CryptoKey>, JS::NonnullGCPtr<CryptoKeyPair>> { CryptoKeyPair::create(m_realm, public_key, private_key) };
+}
+
 WebIDL::ExceptionOr<JS::NonnullGCPtr<CryptoKey>> PBKDF2::import_key(AlgorithmParams const&, Bindings::KeyFormat format, CryptoKey::InternalKeyData key_data, bool extractable, Vector<Bindings::KeyUsage> const& key_usages)
 {
     // 1. If format is not "raw", throw a NotSupportedError
@@ -85,7 +247,7 @@ WebIDL::ExceptionOr<JS::NonnullGCPtr<CryptoKey>> PBKDF2::import_key(AlgorithmPar
     key->set_extractable(false);
 
     // 7. Let algorithm be a new KeyAlgorithm object.
-    auto algorithm = Bindings::KeyAlgorithm::create(m_realm);
+    auto algorithm = KeyAlgorithm::create(m_realm);
 
     // 8. Set the name attribute of algorithm to "PBKDF2".
     algorithm->set_name("PBKDF2"_string);

+ 31 - 2
Userland/Libraries/LibWeb/Crypto/CryptoAlgorithms.h

@@ -8,6 +8,7 @@
 
 #include <AK/EnumBits.h>
 #include <AK/String.h>
+#include <LibCrypto/BigInt/UnsignedBigInteger.h>
 #include <LibJS/Forward.h>
 #include <LibJS/Heap/GCPtr.h>
 #include <LibWeb/Bindings/SubtleCryptoPrototype.h>
@@ -18,12 +19,11 @@
 
 namespace Web::Crypto {
 
-using KeyDataType = Variant<JS::Handle<WebIDL::BufferSource>, Bindings::JsonWebKey>;
 using AlgorithmIdentifier = Variant<JS::Handle<JS::Object>, String>;
 using HashAlgorithmIdentifier = AlgorithmIdentifier;
+using KeyDataType = Variant<JS::Handle<WebIDL::BufferSource>, Bindings::JsonWebKey>;
 
 // https://w3c.github.io/webcrypto/#algorithm-overview
-
 struct AlgorithmParams {
     String name;
 
@@ -39,6 +39,22 @@ struct PBKDF2Params : public AlgorithmParams {
     static JS::ThrowCompletionOr<NonnullOwnPtr<AlgorithmParams>> from_value(JS::VM&, JS::Value);
 };
 
+// https://w3c.github.io/webcrypto/#dfn-RsaKeyGenParams
+struct RsaKeyGenParams : public AlgorithmParams {
+    u32 modulus_length;
+    // NOTE that the raw data is going to be in Big Endian u8[] format
+    ::Crypto::UnsignedBigInteger public_exponent;
+
+    static JS::ThrowCompletionOr<NonnullOwnPtr<AlgorithmParams>> from_value(JS::VM&, JS::Value);
+};
+
+// https://w3c.github.io/webcrypto/#dfn-RsaHashedKeyGenParams
+struct RsaHashedKeyGenParams : public RsaKeyGenParams {
+    HashAlgorithmIdentifier hash;
+
+    static JS::ThrowCompletionOr<NonnullOwnPtr<AlgorithmParams>> from_value(JS::VM&, JS::Value);
+};
+
 class AlgorithmMethods {
 public:
     virtual ~AlgorithmMethods();
@@ -69,6 +85,19 @@ protected:
     JS::Realm& m_realm;
 };
 
+class RSAOAEP : public AlgorithmMethods {
+public:
+    virtual WebIDL::ExceptionOr<Variant<JS::NonnullGCPtr<CryptoKey>, JS::NonnullGCPtr<CryptoKeyPair>>> generate_key(AlgorithmParams const&, bool, Vector<Bindings::KeyUsage> const&) override;
+
+    static NonnullOwnPtr<AlgorithmMethods> create(JS::Realm& realm) { return adopt_own(*new RSAOAEP(realm)); }
+
+private:
+    explicit RSAOAEP(JS::Realm& realm)
+        : AlgorithmMethods(realm)
+    {
+    }
+};
+
 class PBKDF2 : public AlgorithmMethods {
 public:
     virtual WebIDL::ExceptionOr<JS::NonnullGCPtr<CryptoKey>> import_key(AlgorithmParams const&, Bindings::KeyFormat, CryptoKey::InternalKeyData, bool, Vector<Bindings::KeyUsage> const&) override;

+ 0 - 53
Userland/Libraries/LibWeb/Crypto/CryptoBindings.cpp

@@ -1,53 +0,0 @@
-/*
- * Copyright (c) 2023, stelar7 <dudedbz@gmail.com>
- *
- * SPDX-License-Identifier: BSD-2-Clause
- */
-
-#include <LibJS/Runtime/AbstractOperations.h>
-#include <LibJS/Runtime/GlobalObject.h>
-#include <LibWeb/Bindings/ExceptionOrUtils.h>
-#include <LibWeb/Crypto/CryptoBindings.h>
-
-namespace Web::Bindings {
-
-JS_DEFINE_ALLOCATOR(KeyAlgorithm);
-
-JS::NonnullGCPtr<KeyAlgorithm> KeyAlgorithm::create(JS::Realm& realm)
-{
-    return realm.heap().allocate<KeyAlgorithm>(realm, realm);
-}
-
-KeyAlgorithm::KeyAlgorithm(JS::Realm& realm)
-    : Object(ConstructWithPrototypeTag::Tag, realm.intrinsics().object_prototype())
-{
-}
-
-void KeyAlgorithm::initialize(JS::Realm& realm)
-{
-    define_native_accessor(realm, "name", name_getter, {}, JS::Attribute::Enumerable | JS::Attribute::Configurable);
-    Base::initialize(realm);
-}
-
-static JS::ThrowCompletionOr<KeyAlgorithm*> impl_from(JS::VM& vm)
-{
-    auto this_value = vm.this_value();
-    JS::Object* this_object = nullptr;
-    if (this_value.is_nullish())
-        this_object = &vm.current_realm()->global_object();
-    else
-        this_object = TRY(this_value.to_object(vm));
-
-    if (!is<KeyAlgorithm>(this_object))
-        return vm.throw_completion<JS::TypeError>(JS::ErrorType::NotAnObjectOfType, "KeyAlgorithm");
-    return static_cast<KeyAlgorithm*>(this_object);
-}
-
-JS_DEFINE_NATIVE_FUNCTION(KeyAlgorithm::name_getter)
-{
-    auto* impl = TRY(impl_from(vm));
-    auto name = TRY(throw_dom_exception_if_needed(vm, [&] { return impl->name(); }));
-    return JS::PrimitiveString::create(vm, name);
-}
-
-}

+ 1 - 22
Userland/Libraries/LibWeb/Crypto/CryptoBindings.h

@@ -44,25 +44,4 @@ struct JsonWebKey {
     Optional<String> k;
 };
 
-// https://w3c.github.io/webcrypto/#key-algorithm-dictionary
-class KeyAlgorithm : public JS::Object {
-    JS_OBJECT(KeyAlgorithm, Object);
-    JS_DECLARE_ALLOCATOR(KeyAlgorithm);
-
-public:
-    static JS::NonnullGCPtr<KeyAlgorithm> create(JS::Realm&);
-    virtual ~KeyAlgorithm() override = default;
-
-    String const& name() const { return m_name; }
-    void set_name(String name) { m_name = move(name); }
-
-private:
-    KeyAlgorithm(JS::Realm&);
-    virtual void initialize(JS::Realm&) override;
-
-    JS_DECLARE_NATIVE_FUNCTION(name_getter);
-
-    String m_name;
-};
-
-};
+}

+ 2 - 1
Userland/Libraries/LibWeb/Crypto/CryptoKey.h

@@ -6,6 +6,7 @@
 
 #pragma once
 
+#include <LibCrypto/PK/RSA.h>
 #include <LibJS/Forward.h>
 #include <LibJS/Heap/GCPtr.h>
 #include <LibWeb/Bindings/CryptoKeyPrototype.h>
@@ -20,7 +21,7 @@ class CryptoKey final : public Bindings::PlatformObject {
     JS_DECLARE_ALLOCATOR(CryptoKey);
 
 public:
-    using InternalKeyData = Variant<ByteBuffer, Bindings::JsonWebKey>;
+    using InternalKeyData = Variant<ByteBuffer, Bindings::JsonWebKey, ::Crypto::PK::RSAPublicKey<>, ::Crypto::PK::RSAPrivateKey<>>;
 
     [[nodiscard]] static JS::NonnullGCPtr<CryptoKey> create(JS::Realm&, InternalKeyData);
 

+ 152 - 0
Userland/Libraries/LibWeb/Crypto/KeyAlgorithms.cpp

@@ -0,0 +1,152 @@
+/*
+ * Copyright (c) 2023, stelar7 <dudedbz@gmail.com>
+ * Copyright (c) 2024, Andrew Kaster <akaster@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <LibJS/Runtime/GlobalObject.h>
+#include <LibJS/Runtime/PrimitiveString.h>
+#include <LibJS/Runtime/TypedArray.h>
+#include <LibWeb/Bindings/ExceptionOrUtils.h>
+#include <LibWeb/Crypto/KeyAlgorithms.h>
+
+namespace Web::Crypto {
+
+JS_DEFINE_ALLOCATOR(KeyAlgorithm);
+JS_DEFINE_ALLOCATOR(RsaKeyAlgorithm);
+JS_DEFINE_ALLOCATOR(RsaHashedKeyAlgorithm);
+
+template<typename T>
+static JS::ThrowCompletionOr<T*> impl_from(JS::VM& vm, StringView Name)
+{
+    auto this_value = vm.this_value();
+    JS::Object* this_object = nullptr;
+    if (this_value.is_nullish())
+        this_object = &vm.current_realm()->global_object();
+    else
+        this_object = TRY(this_value.to_object(vm));
+
+    if (!is<T>(this_object))
+        return vm.throw_completion<JS::TypeError>(JS::ErrorType::NotAnObjectOfType, Name);
+    return static_cast<T*>(this_object);
+}
+
+JS::NonnullGCPtr<KeyAlgorithm> KeyAlgorithm::create(JS::Realm& realm)
+{
+    return realm.heap().allocate<KeyAlgorithm>(realm, realm);
+}
+
+KeyAlgorithm::KeyAlgorithm(JS::Realm& realm)
+    : Object(ConstructWithPrototypeTag::Tag, realm.intrinsics().object_prototype())
+    , m_realm(realm)
+{
+}
+
+void KeyAlgorithm::initialize(JS::Realm& realm)
+{
+    define_native_accessor(realm, "name", name_getter, {}, JS::Attribute::Enumerable | JS::Attribute::Configurable);
+    Base::initialize(realm);
+}
+
+JS_DEFINE_NATIVE_FUNCTION(KeyAlgorithm::name_getter)
+{
+    auto* impl = TRY(impl_from<KeyAlgorithm>(vm, "KeyAlgorithm"sv));
+    auto name = TRY(Bindings::throw_dom_exception_if_needed(vm, [&] { return impl->name(); }));
+    return JS::PrimitiveString::create(vm, name);
+}
+
+JS::NonnullGCPtr<RsaKeyAlgorithm> RsaKeyAlgorithm::create(JS::Realm& realm)
+{
+    return realm.heap().allocate<RsaKeyAlgorithm>(realm, realm);
+}
+
+RsaKeyAlgorithm::RsaKeyAlgorithm(JS::Realm& realm)
+    : KeyAlgorithm(realm)
+    , m_public_exponent(MUST(JS::Uint8Array::create(realm, 0)))
+{
+}
+
+void RsaKeyAlgorithm::initialize(JS::Realm& realm)
+{
+    Base::initialize(realm);
+
+    define_native_accessor(realm, "modulusLength", modulus_length_getter, {}, JS::Attribute::Enumerable | JS::Attribute::Configurable);
+    define_native_accessor(realm, "publicExponent", public_exponent_getter, {}, JS::Attribute::Enumerable | JS::Attribute::Configurable);
+}
+
+void RsaKeyAlgorithm::visit_edges(Visitor& visitor)
+{
+    Base::visit_edges(visitor);
+    visitor.visit(m_public_exponent);
+}
+
+WebIDL::ExceptionOr<void> RsaKeyAlgorithm::set_public_exponent(::Crypto::UnsignedBigInteger exponent)
+{
+    static_assert(AK::HostIsLittleEndian, "This code assumes a little endian host");
+
+    auto& realm = this->realm();
+    auto& vm = this->vm();
+
+    auto bytes = TRY_OR_THROW_OOM(vm, ByteBuffer::create_uninitialized(exponent.trimmed_byte_length()));
+
+    bool const strip_leading_zeroes = true;
+    auto data_size = exponent.export_data(bytes.span(), strip_leading_zeroes);
+
+    // The BigInteger typedef from the WebCrypto spec requires the bytes in the Uint8Array be ordered in Big Endian
+
+    Vector<u8, 32> byte_swapped_data;
+    byte_swapped_data.ensure_capacity(data_size);
+    for (size_t i = 0; i < data_size; ++i)
+        byte_swapped_data.append(bytes[data_size - i - 1]);
+
+    m_public_exponent = TRY(JS::Uint8Array::create(realm, byte_swapped_data.size()));
+    m_public_exponent->viewed_array_buffer()->buffer().overwrite(0, byte_swapped_data.data(), byte_swapped_data.size());
+
+    return {};
+}
+
+JS_DEFINE_NATIVE_FUNCTION(RsaKeyAlgorithm::modulus_length_getter)
+{
+    auto* impl = TRY(impl_from<RsaKeyAlgorithm>(vm, "RsaKeyAlgorithm"sv));
+    return JS::Value(impl->modulus_length());
+}
+
+JS_DEFINE_NATIVE_FUNCTION(RsaKeyAlgorithm::public_exponent_getter)
+{
+    auto* impl = TRY(impl_from<RsaKeyAlgorithm>(vm, "RsaKeyAlgorithm"sv));
+    return impl->public_exponent();
+}
+
+JS::NonnullGCPtr<RsaHashedKeyAlgorithm> RsaHashedKeyAlgorithm::create(JS::Realm& realm)
+{
+    return realm.heap().allocate<RsaHashedKeyAlgorithm>(realm, realm);
+}
+
+RsaHashedKeyAlgorithm::RsaHashedKeyAlgorithm(JS::Realm& realm)
+    : RsaKeyAlgorithm(realm)
+    , m_hash(String {})
+{
+}
+
+void RsaHashedKeyAlgorithm::initialize(JS::Realm& realm)
+{
+    Base::initialize(realm);
+
+    define_native_accessor(realm, "hash", hash_getter, {}, JS::Attribute::Enumerable | JS::Attribute::Configurable);
+}
+
+JS_DEFINE_NATIVE_FUNCTION(RsaHashedKeyAlgorithm::hash_getter)
+{
+    auto* impl = TRY(impl_from<RsaHashedKeyAlgorithm>(vm, "RsaHashedKeyAlgorithm"sv));
+    auto hash = TRY(Bindings::throw_dom_exception_if_needed(vm, [&] { return impl->hash(); }));
+    return hash.visit(
+        [&](String const& hash_string) -> JS::Value {
+            return JS::PrimitiveString::create(vm, hash_string);
+        },
+        [&](JS::Handle<JS::Object> const& hash) -> JS::Value {
+            return hash;
+        });
+}
+
+}

+ 99 - 0
Userland/Libraries/LibWeb/Crypto/KeyAlgorithms.h

@@ -0,0 +1,99 @@
+/*
+ * Copyright (c) 2023, stelar7 <dudedbz@gmail.com>
+ * Copyright (c) 2024, Andrew Kaster <akaster@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#pragma once
+
+#include <AK/String.h>
+#include <LibCrypto/BigInt/UnsignedBigInteger.h>
+#include <LibJS/Runtime/Object.h>
+#include <LibWeb/Crypto/CryptoAlgorithms.h>
+#include <LibWeb/WebIDL/ExceptionOr.h>
+
+namespace Web::Crypto {
+
+// https://w3c.github.io/webcrypto/#key-algorithm-dictionary
+class KeyAlgorithm : public JS::Object {
+    JS_OBJECT(KeyAlgorithm, Object);
+    JS_DECLARE_ALLOCATOR(KeyAlgorithm);
+
+public:
+    static JS::NonnullGCPtr<KeyAlgorithm> create(JS::Realm&);
+    virtual ~KeyAlgorithm() override = default;
+
+    String const& name() const { return m_name; }
+    void set_name(String name) { m_name = move(name); }
+
+    JS::Realm& realm() const { return m_realm; }
+
+protected:
+    KeyAlgorithm(JS::Realm&);
+
+    virtual void initialize(JS::Realm&) override;
+
+private:
+    JS_DECLARE_NATIVE_FUNCTION(name_getter);
+
+    String m_name;
+    JS::Realm& m_realm;
+};
+
+// https://w3c.github.io/webcrypto/#RsaKeyAlgorithm-dictionary
+class RsaKeyAlgorithm : public KeyAlgorithm {
+    JS_OBJECT(RsaKeyAlgorithm, KeyAlgorithm);
+    JS_DECLARE_ALLOCATOR(RsaKeyAlgorithm);
+
+public:
+    static JS::NonnullGCPtr<RsaKeyAlgorithm> create(JS::Realm&);
+
+    virtual ~RsaKeyAlgorithm() override = default;
+
+    u32 modulus_length() const { return m_modulus_length; }
+    void set_modulus_length(u32 modulus_length) { m_modulus_length = modulus_length; }
+
+    JS::NonnullGCPtr<JS::Uint8Array> public_exponent() const { return m_public_exponent; }
+    void set_public_exponent(JS::NonnullGCPtr<JS::Uint8Array> public_exponent) { m_public_exponent = public_exponent; }
+    WebIDL::ExceptionOr<void> set_public_exponent(::Crypto::UnsignedBigInteger);
+
+protected:
+    RsaKeyAlgorithm(JS::Realm&);
+
+    virtual void initialize(JS::Realm&) override;
+    virtual void visit_edges(Visitor&) override;
+
+private:
+    JS_DECLARE_NATIVE_FUNCTION(modulus_length_getter);
+    JS_DECLARE_NATIVE_FUNCTION(public_exponent_getter);
+
+    u32 m_modulus_length { 0 };
+    JS::NonnullGCPtr<JS::Uint8Array> m_public_exponent;
+};
+
+// https://w3c.github.io/webcrypto/#RsaHashedKeyAlgorithm-dictionary
+class RsaHashedKeyAlgorithm : public RsaKeyAlgorithm {
+    JS_OBJECT(RsaHashedKeyAlgorithm, RsaKeyAlgorithm);
+    JS_DECLARE_ALLOCATOR(RsaHashedKeyAlgorithm);
+
+public:
+    static JS::NonnullGCPtr<RsaHashedKeyAlgorithm> create(JS::Realm&);
+
+    virtual ~RsaHashedKeyAlgorithm() override = default;
+
+    HashAlgorithmIdentifier const& hash() const { return m_hash; }
+    void set_hash(HashAlgorithmIdentifier hash) { m_hash = move(hash); }
+
+protected:
+    RsaHashedKeyAlgorithm(JS::Realm&);
+
+    virtual void initialize(JS::Realm&) override;
+
+private:
+    JS_DECLARE_NATIVE_FUNCTION(hash_getter);
+
+    HashAlgorithmIdentifier m_hash;
+};
+
+}

+ 4 - 0
Userland/Libraries/LibWeb/Crypto/SubtleCrypto.cpp

@@ -337,6 +337,10 @@ SubtleCrypto::SupportedAlgorithmsMap SubtleCrypto::supported_algorithms()
     // FIXME: define_an_algorithm("deriveBits"_string, "PBKDF2"_string, "Pbkdf2Params"_string);
     // FIXME: define_an_algorithm("get key length"_string, "PBKDF2"_string, ""_string);
 
+    // https://w3c.github.io/webcrypto/#rsa-oaep
+    define_an_algorithm<RSAOAEP, RsaHashedKeyGenParams>("generateKey"_string, "RSA-OAEP"_string);
+    // FIXME: encrypt, decrypt, importKey, exportKey
+
     return internal_object;
 }