LibWeb: Implement generateKey for ECDSA

This commit is contained in:
stelar7 2024-03-27 01:27:42 +01:00 committed by Andrew Kaster
parent cfae6523be
commit 41449814db
Notes: sideshowbarker 2024-07-17 07:06:47 +09:00
5 changed files with 201 additions and 0 deletions

View file

@ -7,6 +7,7 @@
#include <AK/Base64.h>
#include <AK/QuickSort.h>
#include <LibCrypto/ASN1/DER.h>
#include <LibCrypto/Curves/SECPxxxr1.h>
#include <LibCrypto/Hash/HashManager.h>
#include <LibCrypto/PK/RSA.h>
#include <LibJS/Runtime/ArrayBuffer.h>
@ -344,6 +345,20 @@ JS::ThrowCompletionOr<NonnullOwnPtr<AlgorithmParams>> RsaOaepParams::from_value(
return adopt_own<AlgorithmParams>(*new RsaOaepParams { name, move(label) });
}
EcKeyGenParams::~EcKeyGenParams() = default;
JS::ThrowCompletionOr<NonnullOwnPtr<AlgorithmParams>> EcKeyGenParams::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 curve_value = TRY(object.get("namedCurve"));
auto curve = TRY(curve_value.to_string(vm));
return adopt_own<AlgorithmParams>(*new EcKeyGenParams { name, curve });
}
// https://w3c.github.io/webcrypto/#rsa-oaep-operations
WebIDL::ExceptionOr<JS::NonnullGCPtr<JS::ArrayBuffer>> RSAOAEP::encrypt(AlgorithmParams const& params, JS::NonnullGCPtr<CryptoKey> key, ByteBuffer const& plaintext)
@ -927,4 +942,105 @@ WebIDL::ExceptionOr<JS::NonnullGCPtr<JS::ArrayBuffer>> SHA::digest(AlgorithmPara
return JS::ArrayBuffer::create(m_realm, result_buffer.release_value());
}
// https://w3c.github.io/webcrypto/#ecdsa-operations
WebIDL::ExceptionOr<Variant<JS::NonnullGCPtr<CryptoKey>, JS::NonnullGCPtr<CryptoKeyPair>>> ECDSA::generate_key(AlgorithmParams const& params, bool extractable, Vector<Bindings::KeyUsage> const& key_usages)
{
// 1. If usages contains a value which is not one of "sign" or "verify", then throw a SyntaxError.
for (auto const& usage : key_usages) {
if (usage != Bindings::KeyUsage::Sign && usage != Bindings::KeyUsage::Verify) {
return WebIDL::SyntaxError::create(m_realm, MUST(String::formatted("Invalid key usage '{}'", idl_enum_to_string(usage))));
}
}
auto const& normalized_algorithm = static_cast<EcKeyGenParams const&>(params);
// 2. If the namedCurve member of normalizedAlgorithm is "P-256", "P-384" or "P-521":
// Generate an Elliptic Curve key pair, as defined in [RFC6090]
// with domain parameters for the curve identified by the namedCurve member of normalizedAlgorithm.
Variant<Empty, ::Crypto::Curves::SECP256r1, ::Crypto::Curves::SECP384r1> curve;
if (normalized_algorithm.named_curve.is_one_of("P-256"sv, "P-384"sv, "P-521"sv)) {
if (normalized_algorithm.named_curve.equals_ignoring_ascii_case("P-256"sv))
curve = ::Crypto::Curves::SECP256r1 {};
if (normalized_algorithm.named_curve.equals_ignoring_ascii_case("P-384"sv))
curve = ::Crypto::Curves::SECP384r1 {};
// FIXME: Support P-521
if (normalized_algorithm.named_curve.equals_ignoring_ascii_case("P-521"sv))
return WebIDL::NotSupportedError::create(m_realm, "'P-521' is not supported yet"_fly_string);
} else {
// If the namedCurve member of normalizedAlgorithm is a value specified in an applicable specification:
// Perform the ECDSA generation steps specified in that specification,
// passing in normalizedAlgorithm and resulting in an elliptic curve key pair.
// Otherwise: throw a NotSupportedError
return WebIDL::NotSupportedError::create(m_realm, "Only 'P-256', 'P-384' and 'P-521' is supported"_fly_string);
}
// NOTE: Spec jumps to 6 here for some reason
// 6. If performing the key generation operation results in an error, then throw an OperationError.
auto maybe_private_key_data = curve.visit(
[](Empty const&) -> ErrorOr<ByteBuffer> { return Error::from_string_view("noop error"sv); },
[](auto instance) { return instance.generate_private_key(); });
if (maybe_private_key_data.is_error())
return WebIDL::OperationError::create(m_realm, "Failed to create valid crypto instance"_fly_string);
auto private_key_data = maybe_private_key_data.release_value();
auto maybe_public_key_data = curve.visit(
[](Empty const&) -> ErrorOr<ByteBuffer> { return Error::from_string_view("noop error"sv); },
[&](auto instance) { return instance.generate_public_key(private_key_data); });
if (maybe_public_key_data.is_error())
return WebIDL::OperationError::create(m_realm, "Failed to create valid crypto instance"_fly_string);
auto public_key_data = maybe_public_key_data.release_value();
// 7. Let algorithm be a new EcKeyAlgorithm object.
auto algorithm = EcKeyAlgorithm::create(m_realm);
// 8. Set the name attribute of algorithm to "ECDSA".
algorithm->set_name("ECDSA"_string);
// 9. Set the namedCurve attribute of algorithm to equal the namedCurve member of normalizedAlgorithm.
algorithm->set_named_curve(normalized_algorithm.named_curve);
// 10. Let publicKey be a new CryptoKey representing the public key of the generated key pair.
auto public_key = CryptoKey::create(m_realm, CryptoKey::InternalKeyData { public_key_data });
// 11. Set the [[type]] internal slot of publicKey to "public"
public_key->set_type(Bindings::KeyType::Public);
// 12. Set the [[algorithm]] internal slot of publicKey to algorithm.
public_key->set_algorithm(algorithm);
// 13. Set the [[extractable]] internal slot of publicKey to true.
public_key->set_extractable(true);
// 14. Set the [[usages]] internal slot of publicKey to be the usage intersection of usages and [ "verify" ].
public_key->set_usages(usage_intersection(key_usages, { { Bindings::KeyUsage::Verify } }));
// 15. Let privateKey be a new CryptoKey representing the private key of the generated key pair.
auto private_key = CryptoKey::create(m_realm, CryptoKey::InternalKeyData { private_key_data });
// 16. Set the [[type]] internal slot of privateKey to "private"
private_key->set_type(Bindings::KeyType::Private);
// 17. Set the [[algorithm]] internal slot of privateKey to algorithm.
private_key->set_algorithm(algorithm);
// 18. Set the [[extractable]] internal slot of privateKey to extractable.
private_key->set_extractable(extractable);
// 19. Set the [[usages]] internal slot of privateKey to be the usage intersection of usages and [ "sign" ].
private_key->set_usages(usage_intersection(key_usages, { { Bindings::KeyUsage::Sign } }));
// 20. Let result be a new CryptoKeyPair dictionary.
// 21. Set the publicKey attribute of result to be publicKey.
// 22. Set the privateKey attribute of result to be privateKey.
// 23. 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) };
}
}

View file

@ -21,6 +21,7 @@ namespace Web::Crypto {
using AlgorithmIdentifier = Variant<JS::Handle<JS::Object>, String>;
using HashAlgorithmIdentifier = AlgorithmIdentifier;
using NamedCurve = String;
using KeyDataType = Variant<JS::Handle<WebIDL::BufferSource>, Bindings::JsonWebKey>;
// https://w3c.github.io/webcrypto/#algorithm-overview
@ -116,6 +117,20 @@ struct RsaOaepParams : public AlgorithmParams {
static JS::ThrowCompletionOr<NonnullOwnPtr<AlgorithmParams>> from_value(JS::VM&, JS::Value);
};
// https://w3c.github.io/webcrypto/#dfn-EcKeyGenParams
struct EcKeyGenParams : public AlgorithmParams {
virtual ~EcKeyGenParams() override;
EcKeyGenParams(String name, NamedCurve named_curve)
: AlgorithmParams(move(name))
, named_curve(move(named_curve))
{
}
NamedCurve named_curve;
static JS::ThrowCompletionOr<NonnullOwnPtr<AlgorithmParams>> from_value(JS::VM&, JS::Value);
};
class AlgorithmMethods {
public:
@ -212,6 +227,19 @@ private:
}
};
class ECDSA : 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 ECDSA(realm)); }
private:
explicit ECDSA(JS::Realm& realm)
: AlgorithmMethods(realm)
{
}
};
ErrorOr<String> base64_url_uint_encode(::Crypto::UnsignedBigInteger);
WebIDL::ExceptionOr<::Crypto::UnsignedBigInteger> base64_url_uint_decode(JS::Realm&, String const& base64_url_string);

View file

@ -16,6 +16,7 @@ namespace Web::Crypto {
JS_DEFINE_ALLOCATOR(KeyAlgorithm);
JS_DEFINE_ALLOCATOR(RsaKeyAlgorithm);
JS_DEFINE_ALLOCATOR(RsaHashedKeyAlgorithm);
JS_DEFINE_ALLOCATOR(EcKeyAlgorithm);
template<typename T>
static JS::ThrowCompletionOr<T*> impl_from(JS::VM& vm, StringView Name)
@ -119,6 +120,34 @@ JS_DEFINE_NATIVE_FUNCTION(RsaKeyAlgorithm::public_exponent_getter)
return impl->public_exponent();
}
JS::NonnullGCPtr<EcKeyAlgorithm> EcKeyAlgorithm::create(JS::Realm& realm)
{
return realm.heap().allocate<EcKeyAlgorithm>(realm, realm);
}
EcKeyAlgorithm::EcKeyAlgorithm(JS::Realm& realm)
: KeyAlgorithm(realm)
{
}
void EcKeyAlgorithm::initialize(JS::Realm& realm)
{
Base::initialize(realm);
define_native_accessor(realm, "namedCurve", named_curve_getter, {}, JS::Attribute::Enumerable | JS::Attribute::Configurable);
}
JS_DEFINE_NATIVE_FUNCTION(EcKeyAlgorithm::named_curve_getter)
{
auto* impl = TRY(impl_from<EcKeyAlgorithm>(vm, "EcKeyAlgorithm"sv));
return JS::PrimitiveString::create(vm, impl->named_curve());
}
void EcKeyAlgorithm::visit_edges(Visitor& visitor)
{
Base::visit_edges(visitor);
}
JS::NonnullGCPtr<RsaHashedKeyAlgorithm> RsaHashedKeyAlgorithm::create(JS::Realm& realm)
{
return realm.heap().allocate<RsaHashedKeyAlgorithm>(realm, realm);

View file

@ -96,4 +96,29 @@ private:
HashAlgorithmIdentifier m_hash;
};
// https://w3c.github.io/webcrypto/#EcKeyAlgorithm-dictionary
class EcKeyAlgorithm : public KeyAlgorithm {
JS_OBJECT(EcKeyAlgorithm, KeyAlgorithm);
JS_DECLARE_ALLOCATOR(EcKeyAlgorithm);
public:
static JS::NonnullGCPtr<EcKeyAlgorithm> create(JS::Realm&);
virtual ~EcKeyAlgorithm() override = default;
NamedCurve named_curve() const { return m_named_curve; }
void set_named_curve(NamedCurve named_curve) { m_named_curve = named_curve; }
protected:
EcKeyAlgorithm(JS::Realm&);
virtual void initialize(JS::Realm&) override;
virtual void visit_edges(Visitor&) override;
private:
JS_DECLARE_NATIVE_FUNCTION(named_curve_getter);
NamedCurve m_named_curve;
};
}

View file

@ -575,6 +575,9 @@ SupportedAlgorithmsMap supported_algorithms()
define_an_algorithm<RSAOAEP, RsaOaepParams>("encrypt"_string, "RSA-OAEP"_string);
define_an_algorithm<RSAOAEP, RsaOaepParams>("decrypt"_string, "RSA-OAEP"_string);
// https://w3c.github.io/webcrypto/#ecdsa
define_an_algorithm<ECDSA, EcKeyGenParams>("generateKey"_string, "ECDSA"_string);
return internal_object;
}