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

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.
This commit is contained in:
Andrew Kaster 2024-03-08 16:30:17 -07:00 committed by Andrew Kaster
parent 008c89edde
commit a9d240c647
12 changed files with 536 additions and 81 deletions

View file

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

View file

@ -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]

View file

@ -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>

View file

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

View file

@ -4,17 +4,59 @@
* SPDX-License-Identifier: BSD-2-Clause * SPDX-License-Identifier: BSD-2-Clause
*/ */
#include <AK/QuickSort.h>
#include <LibCrypto/Hash/HashManager.h> #include <LibCrypto/Hash/HashManager.h>
#include <LibCrypto/PK/RSA.h>
#include <LibJS/Runtime/ArrayBuffer.h> #include <LibJS/Runtime/ArrayBuffer.h>
#include <LibJS/Runtime/DataView.h> #include <LibJS/Runtime/DataView.h>
#include <LibJS/Runtime/TypedArray.h> #include <LibJS/Runtime/TypedArray.h>
#include <LibWeb/Crypto/CryptoAlgorithms.h> #include <LibWeb/Crypto/CryptoAlgorithms.h>
#include <LibWeb/Crypto/KeyAlgorithms.h>
namespace Web::Crypto { 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 // Out of line to ensure this class has a key function
AlgorithmMethods::~AlgorithmMethods() = default; 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) JS::ThrowCompletionOr<NonnullOwnPtr<AlgorithmParams>> AlgorithmParams::from_value(JS::VM& vm, JS::Value value)
{ {
auto& object = value.as_object(); 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>() }); 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) 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 // 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); key->set_extractable(false);
// 7. Let algorithm be a new KeyAlgorithm object. // 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". // 8. Set the name attribute of algorithm to "PBKDF2".
algorithm->set_name("PBKDF2"_string); algorithm->set_name("PBKDF2"_string);

View file

@ -8,6 +8,7 @@
#include <AK/EnumBits.h> #include <AK/EnumBits.h>
#include <AK/String.h> #include <AK/String.h>
#include <LibCrypto/BigInt/UnsignedBigInteger.h>
#include <LibJS/Forward.h> #include <LibJS/Forward.h>
#include <LibJS/Heap/GCPtr.h> #include <LibJS/Heap/GCPtr.h>
#include <LibWeb/Bindings/SubtleCryptoPrototype.h> #include <LibWeb/Bindings/SubtleCryptoPrototype.h>
@ -18,12 +19,11 @@
namespace Web::Crypto { namespace Web::Crypto {
using KeyDataType = Variant<JS::Handle<WebIDL::BufferSource>, Bindings::JsonWebKey>;
using AlgorithmIdentifier = Variant<JS::Handle<JS::Object>, String>; using AlgorithmIdentifier = Variant<JS::Handle<JS::Object>, String>;
using HashAlgorithmIdentifier = AlgorithmIdentifier; using HashAlgorithmIdentifier = AlgorithmIdentifier;
using KeyDataType = Variant<JS::Handle<WebIDL::BufferSource>, Bindings::JsonWebKey>;
// https://w3c.github.io/webcrypto/#algorithm-overview // https://w3c.github.io/webcrypto/#algorithm-overview
struct AlgorithmParams { struct AlgorithmParams {
String name; String name;
@ -39,6 +39,22 @@ struct PBKDF2Params : public AlgorithmParams {
static JS::ThrowCompletionOr<NonnullOwnPtr<AlgorithmParams>> from_value(JS::VM&, JS::Value); 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 { class AlgorithmMethods {
public: public:
virtual ~AlgorithmMethods(); virtual ~AlgorithmMethods();
@ -69,6 +85,19 @@ protected:
JS::Realm& m_realm; 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 { class PBKDF2 : public AlgorithmMethods {
public: public:
virtual WebIDL::ExceptionOr<JS::NonnullGCPtr<CryptoKey>> import_key(AlgorithmParams const&, Bindings::KeyFormat, CryptoKey::InternalKeyData, bool, Vector<Bindings::KeyUsage> const&) override; virtual WebIDL::ExceptionOr<JS::NonnullGCPtr<CryptoKey>> import_key(AlgorithmParams const&, Bindings::KeyFormat, CryptoKey::InternalKeyData, bool, Vector<Bindings::KeyUsage> const&) override;

View file

@ -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);
}
}

View file

@ -44,25 +44,4 @@ struct JsonWebKey {
Optional<String> k; 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;
};
};

View file

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

View file

@ -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;
});
}
}

View file

@ -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;
};
}

View file

@ -337,6 +337,10 @@ SubtleCrypto::SupportedAlgorithmsMap SubtleCrypto::supported_algorithms()
// FIXME: define_an_algorithm("deriveBits"_string, "PBKDF2"_string, "Pbkdf2Params"_string); // FIXME: define_an_algorithm("deriveBits"_string, "PBKDF2"_string, "Pbkdf2Params"_string);
// FIXME: define_an_algorithm("get key length"_string, "PBKDF2"_string, ""_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; return internal_object;
} }