mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-11-21 15:10:19 +00:00
LibWeb: Implement X25519.exportKey
This commit is contained in:
parent
b281fa2b24
commit
f9b511a7d6
Notes:
github-actions[bot]
2024-10-31 08:53:22 +00:00
Author: https://github.com/stelar7 Commit: https://github.com/LadybirdBrowser/ladybird/commit/f9b511a7d6f Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/1983 Reviewed-by: https://github.com/BenWiederhake
4 changed files with 181 additions and 0 deletions
|
@ -1,5 +1,6 @@
|
|||
/*
|
||||
* Copyright (c) 2020, Ali Mohammad Pur <mpfard@serenityos.org>
|
||||
* Copyright (c) 2024, stelar7 <dudedbz@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
@ -12,6 +13,32 @@
|
|||
|
||||
namespace Crypto::PK {
|
||||
|
||||
template<class ByteBuffer>
|
||||
ErrorOr<ByteBuffer> wrap_in_private_key_info(ByteBuffer key, Span<int> algorithm_identifier)
|
||||
{
|
||||
ASN1::Encoder encoder;
|
||||
TRY(encoder.write_constructed(ASN1::Class::Universal, ASN1::Kind::Sequence, [&]() -> ErrorOr<void> {
|
||||
TRY(encoder.write(0x00u)); // version
|
||||
|
||||
// AlgorithmIdentifier
|
||||
TRY(encoder.write_constructed(ASN1::Class::Universal, ASN1::Kind::Sequence, [&]() -> ErrorOr<void> {
|
||||
TRY(encoder.write(algorithm_identifier)); // algorithm
|
||||
|
||||
// FIXME: This assumes we have a NULL parameter, this is not always the case
|
||||
TRY(encoder.write(nullptr)); // parameters
|
||||
|
||||
return {};
|
||||
}));
|
||||
|
||||
// PrivateKey
|
||||
TRY(encoder.write(key));
|
||||
|
||||
return {};
|
||||
}));
|
||||
|
||||
return encoder.finish();
|
||||
}
|
||||
|
||||
template<typename ExportableKey>
|
||||
ErrorOr<ByteBuffer> wrap_in_private_key_info(ExportableKey key, Span<int> algorithm_identifier)
|
||||
requires requires(ExportableKey k) {
|
||||
|
@ -42,6 +69,31 @@ requires requires(ExportableKey k) {
|
|||
return encoder.finish();
|
||||
}
|
||||
|
||||
template<class ByteBuffer>
|
||||
ErrorOr<ByteBuffer> wrap_in_subject_public_key_info(ByteBuffer key, Span<int> algorithm_identifier)
|
||||
{
|
||||
ASN1::Encoder encoder;
|
||||
TRY(encoder.write_constructed(ASN1::Class::Universal, ASN1::Kind::Sequence, [&]() -> ErrorOr<void> {
|
||||
// AlgorithmIdentifier
|
||||
TRY(encoder.write_constructed(ASN1::Class::Universal, ASN1::Kind::Sequence, [&]() -> ErrorOr<void> {
|
||||
TRY(encoder.write(algorithm_identifier)); // algorithm
|
||||
|
||||
// FIXME: This assumes we have a NULL parameter, this is not always the case
|
||||
TRY(encoder.write(nullptr)); // parameters
|
||||
|
||||
return {};
|
||||
}));
|
||||
|
||||
// subjectPublicKey
|
||||
auto bitstring = ::Crypto::ASN1::BitStringView(key, 0);
|
||||
TRY(encoder.write(bitstring));
|
||||
|
||||
return {};
|
||||
}));
|
||||
|
||||
return encoder.finish();
|
||||
}
|
||||
|
||||
template<typename ExportableKey>
|
||||
ErrorOr<ByteBuffer> wrap_in_subject_public_key_info(ExportableKey key, Span<int> algorithm_identifier)
|
||||
requires requires(ExportableKey k) {
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
#include <LibCrypto/Hash/SHA2.h>
|
||||
#include <LibCrypto/PK/RSA.h>
|
||||
#include <LibCrypto/Padding/OAEP.h>
|
||||
#include <LibJS/Runtime/Array.h>
|
||||
#include <LibJS/Runtime/ArrayBuffer.h>
|
||||
#include <LibJS/Runtime/DataView.h>
|
||||
#include <LibJS/Runtime/TypedArray.h>
|
||||
|
@ -2709,4 +2710,130 @@ WebIDL::ExceptionOr<JS::NonnullGCPtr<CryptoKey>> X25519::import_key([[maybe_unus
|
|||
return JS::NonnullGCPtr { *key };
|
||||
}
|
||||
|
||||
WebIDL::ExceptionOr<JS::NonnullGCPtr<JS::Object>> X25519::export_key(Bindings::KeyFormat format, JS::NonnullGCPtr<CryptoKey> key)
|
||||
{
|
||||
auto& vm = m_realm->vm();
|
||||
|
||||
// NOTE: This is a parameter to the function
|
||||
// 1. Let key be the CryptoKey to be exported.
|
||||
|
||||
// 2. If the underlying cryptographic key material represented by the [[handle]] internal slot of key cannot be accessed, then throw an OperationError.
|
||||
// Note: In our impl this is always accessible
|
||||
auto const& handle = key->handle();
|
||||
|
||||
JS::GCPtr<JS::Object> result = nullptr;
|
||||
|
||||
// 3. If format is "spki":
|
||||
if (format == Bindings::KeyFormat::Spki) {
|
||||
// 1. If the [[type]] internal slot of key is not "public", then throw an InvalidAccessError.
|
||||
if (key->type() != Bindings::KeyType::Public)
|
||||
return WebIDL::InvalidAccessError::create(m_realm, "Key is not a public key"_string);
|
||||
|
||||
// 2. Let data be an instance of the subjectPublicKeyInfo ASN.1 structure defined in [RFC5280] with the following properties:
|
||||
// Set the algorithm field to an AlgorithmIdentifier ASN.1 type with the following properties:
|
||||
// Set the algorithm object identifier to the id-X25519 OID defined in [RFC8410].
|
||||
// Set the subjectPublicKey field to keyData.
|
||||
auto public_key = handle.get<ByteBuffer>();
|
||||
auto x25519_oid = Array<int, 7> { 1, 3, 101, 110 };
|
||||
auto data = TRY_OR_THROW_OOM(vm, ::Crypto::PK::wrap_in_subject_public_key_info(public_key, x25519_oid));
|
||||
|
||||
// 3. Let result be a new ArrayBuffer associated with the relevant global object of this [HTML], and containing data.
|
||||
result = JS::ArrayBuffer::create(m_realm, data);
|
||||
}
|
||||
|
||||
// 3. If format is "pkcs8":
|
||||
else if (format == Bindings::KeyFormat::Pkcs8) {
|
||||
// 1. If the [[type]] internal slot of key is not "private", then throw an InvalidAccessError.
|
||||
if (key->type() != Bindings::KeyType::Private)
|
||||
return WebIDL::InvalidAccessError::create(m_realm, "Key is not a private key"_string);
|
||||
|
||||
// 2. Let data be an instance of the privateKeyInfo ASN.1 structure defined in [RFC5208] with the following properties:
|
||||
// Set the version field to 0.
|
||||
// Set the privateKeyAlgorithm field to a PrivateKeyAlgorithmIdentifier ASN.1 type with the following properties:
|
||||
// Set the algorithm object identifier to the id-X25519 OID defined in [RFC8410].
|
||||
// Set the privateKey field to the result of DER-encoding a CurvePrivateKey ASN.1 type, as defined in Section 7 of [RFC8410],
|
||||
// that represents the X25519 private key represented by the [[handle]] internal slot of key
|
||||
auto private_key = handle.get<ByteBuffer>();
|
||||
auto x25519_oid = Array<int, 7> { 1, 3, 101, 110 };
|
||||
auto data = TRY_OR_THROW_OOM(vm, ::Crypto::PK::wrap_in_private_key_info(private_key, x25519_oid));
|
||||
|
||||
// 3. Let result be a new ArrayBuffer associated with the relevant global object of this [HTML], and containing data.
|
||||
result = JS::ArrayBuffer::create(m_realm, data);
|
||||
}
|
||||
|
||||
// 3. If format is "jwt":
|
||||
else if (format == Bindings::KeyFormat::Jwk) {
|
||||
// 1. Let jwk be a new JsonWebKey dictionar1y.
|
||||
Bindings::JsonWebKey jwk = {};
|
||||
|
||||
// 2. Set the kty attribute of jwk to "OKP".
|
||||
jwk.kty = "OKP"_string;
|
||||
|
||||
// 3. Set the crv attribute of jwk to "X25519".
|
||||
jwk.crv = "X25519"_string;
|
||||
|
||||
// 4. Set the x attribute of jwk according to the definition in Section 2 of [RFC8037].
|
||||
if (key->type() == Bindings::KeyType::Public) {
|
||||
auto public_key = handle.get<ByteBuffer>();
|
||||
jwk.x = TRY_OR_THROW_OOM(vm, encode_base64url(public_key));
|
||||
} else {
|
||||
// The "x" parameter of the "epk" field is set as follows:
|
||||
// Apply the appropriate ECDH function to the ephemeral private key (as scalar input)
|
||||
// and the standard base point (as u-coordinate input).
|
||||
// The base64url encoding of the output is the value for the "x" parameter of the "epk" field.
|
||||
::Crypto::Curves::X25519 curve;
|
||||
auto public_key = TRY_OR_THROW_OOM(vm, curve.generate_public_key(handle.get<ByteBuffer>()));
|
||||
jwk.x = TRY_OR_THROW_OOM(vm, encode_base64url(public_key));
|
||||
}
|
||||
|
||||
// 5. If the [[type]] internal slot of key is "private"
|
||||
if (key->type() == Bindings::KeyType::Private) {
|
||||
// 1. Set the d attribute of jwk according to the definition in Section 2 of [RFC8037].
|
||||
auto private_key = handle.get<ByteBuffer>();
|
||||
jwk.d = TRY_OR_THROW_OOM(vm, encode_base64url(private_key));
|
||||
}
|
||||
|
||||
// 6. Set the key_ops attribute of jwk to the usages attribute of key.
|
||||
auto key_ops = Vector<String> {};
|
||||
auto key_usages = verify_cast<JS::Array>(key->usages());
|
||||
for (auto i = 0; i < 10; ++i) {
|
||||
auto usage = key_usages->get(i);
|
||||
if (!usage.has_value())
|
||||
break;
|
||||
|
||||
auto usage_string = TRY(usage.value().to_string(vm));
|
||||
key_ops.append(usage_string);
|
||||
}
|
||||
|
||||
jwk.key_ops = key_ops;
|
||||
|
||||
// 7. Set the ext attribute of jwk to the [[extractable]] internal slot of key.
|
||||
jwk.ext = key->extractable();
|
||||
|
||||
// 8. Let result be the result of converting jwk to an ECMAScript Object, as defined by [WebIDL].
|
||||
result = TRY(jwk.to_object(m_realm));
|
||||
}
|
||||
|
||||
// 3. If format is "raw":
|
||||
else if (format == Bindings::KeyFormat::Raw) {
|
||||
// 1. If the [[type]] internal slot of key is not "public", then throw an InvalidAccessError.
|
||||
if (key->type() != Bindings::KeyType::Public)
|
||||
return WebIDL::InvalidAccessError::create(m_realm, "Key is not a public key"_string);
|
||||
|
||||
// 2. Let data be an octet string representing the X25519 public key represented by the [[handle]] internal slot of key.
|
||||
auto public_key = handle.get<ByteBuffer>();
|
||||
|
||||
// 3. Let result be a new ArrayBuffer associated with the relevant global object of this [HTML], and containing data.
|
||||
result = JS::ArrayBuffer::create(m_realm, public_key);
|
||||
}
|
||||
|
||||
// 3. Otherwise:
|
||||
else {
|
||||
return WebIDL::NotSupportedError::create(m_realm, "Invalid key format"_string);
|
||||
}
|
||||
|
||||
// 4. Return result.
|
||||
return JS::NonnullGCPtr { *result };
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -442,6 +442,7 @@ public:
|
|||
virtual WebIDL::ExceptionOr<JS::NonnullGCPtr<JS::ArrayBuffer>> derive_bits(AlgorithmParams const&, JS::NonnullGCPtr<CryptoKey>, Optional<u32>) override;
|
||||
virtual WebIDL::ExceptionOr<Variant<JS::NonnullGCPtr<CryptoKey>, JS::NonnullGCPtr<CryptoKeyPair>>> generate_key(AlgorithmParams const&, 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;
|
||||
virtual WebIDL::ExceptionOr<JS::NonnullGCPtr<JS::Object>> export_key(Bindings::KeyFormat, JS::NonnullGCPtr<CryptoKey>) override;
|
||||
|
||||
static NonnullOwnPtr<AlgorithmMethods> create(JS::Realm& realm) { return adopt_own(*new X25519(realm)); }
|
||||
|
||||
|
|
|
@ -815,6 +815,7 @@ SupportedAlgorithmsMap supported_algorithms()
|
|||
define_an_algorithm<X25519, EcdhKeyDerivePrams>("deriveBits"_string, "X25519"_string);
|
||||
define_an_algorithm<X25519>("generateKey"_string, "X25519"_string);
|
||||
define_an_algorithm<X25519>("importKey"_string, "X25519"_string);
|
||||
define_an_algorithm<X25519>("exportKey"_string, "X25519"_string);
|
||||
|
||||
return internal_object;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue