mirror of
https://github.com/RGBCube/serenity
synced 2025-05-31 03:08:11 +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:
parent
008c89edde
commit
a9d240c647
12 changed files with 536 additions and 81 deletions
|
@ -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",
|
||||
]
|
||||
|
|
|
@ -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
Tests/LibWeb/Text/input/Crypto/SubtleCrypto-generateKey.html
Normal file
66
Tests/LibWeb/Text/input/Crypto/SubtleCrypto-generateKey.html
Normal 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>
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
};
|
||||
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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
Userland/Libraries/LibWeb/Crypto/KeyAlgorithms.cpp
Normal file
152
Userland/Libraries/LibWeb/Crypto/KeyAlgorithms.cpp
Normal 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;
|
||||
});
|
||||
}
|
||||
|
||||
}
|
99
Userland/Libraries/LibWeb/Crypto/KeyAlgorithms.h
Normal file
99
Userland/Libraries/LibWeb/Crypto/KeyAlgorithms.h
Normal 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;
|
||||
};
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue