From f9b511a7d6f1f0a04f07377d7d642fb4f3adf897 Mon Sep 17 00:00:00 2001 From: stelar7 Date: Sat, 26 Oct 2024 22:02:51 +0200 Subject: [PATCH] LibWeb: Implement X25519.exportKey --- Userland/Libraries/LibCrypto/PK/PK.h | 52 +++++++ .../LibWeb/Crypto/CryptoAlgorithms.cpp | 127 ++++++++++++++++++ .../LibWeb/Crypto/CryptoAlgorithms.h | 1 + .../Libraries/LibWeb/Crypto/SubtleCrypto.cpp | 1 + 4 files changed, 181 insertions(+) diff --git a/Userland/Libraries/LibCrypto/PK/PK.h b/Userland/Libraries/LibCrypto/PK/PK.h index 8018e6810bd..00669a7cdd3 100644 --- a/Userland/Libraries/LibCrypto/PK/PK.h +++ b/Userland/Libraries/LibCrypto/PK/PK.h @@ -1,5 +1,6 @@ /* * Copyright (c) 2020, Ali Mohammad Pur + * Copyright (c) 2024, stelar7 * * SPDX-License-Identifier: BSD-2-Clause */ @@ -12,6 +13,32 @@ namespace Crypto::PK { +template +ErrorOr wrap_in_private_key_info(ByteBuffer key, Span algorithm_identifier) +{ + ASN1::Encoder encoder; + TRY(encoder.write_constructed(ASN1::Class::Universal, ASN1::Kind::Sequence, [&]() -> ErrorOr { + TRY(encoder.write(0x00u)); // version + + // AlgorithmIdentifier + TRY(encoder.write_constructed(ASN1::Class::Universal, ASN1::Kind::Sequence, [&]() -> ErrorOr { + 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 ErrorOr wrap_in_private_key_info(ExportableKey key, Span algorithm_identifier) requires requires(ExportableKey k) { @@ -42,6 +69,31 @@ requires requires(ExportableKey k) { return encoder.finish(); } +template +ErrorOr wrap_in_subject_public_key_info(ByteBuffer key, Span algorithm_identifier) +{ + ASN1::Encoder encoder; + TRY(encoder.write_constructed(ASN1::Class::Universal, ASN1::Kind::Sequence, [&]() -> ErrorOr { + // AlgorithmIdentifier + TRY(encoder.write_constructed(ASN1::Class::Universal, ASN1::Kind::Sequence, [&]() -> ErrorOr { + 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 ErrorOr wrap_in_subject_public_key_info(ExportableKey key, Span algorithm_identifier) requires requires(ExportableKey k) { diff --git a/Userland/Libraries/LibWeb/Crypto/CryptoAlgorithms.cpp b/Userland/Libraries/LibWeb/Crypto/CryptoAlgorithms.cpp index 9457cbc93ba..e8169644085 100644 --- a/Userland/Libraries/LibWeb/Crypto/CryptoAlgorithms.cpp +++ b/Userland/Libraries/LibWeb/Crypto/CryptoAlgorithms.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -2709,4 +2710,130 @@ WebIDL::ExceptionOr> X25519::import_key([[maybe_unus return JS::NonnullGCPtr { *key }; } +WebIDL::ExceptionOr> X25519::export_key(Bindings::KeyFormat format, JS::NonnullGCPtr 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 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(); + auto x25519_oid = Array { 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(); + auto x25519_oid = Array { 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(); + 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())); + 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(); + 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 {}; + auto key_usages = verify_cast(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(); + + // 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 }; +} + } diff --git a/Userland/Libraries/LibWeb/Crypto/CryptoAlgorithms.h b/Userland/Libraries/LibWeb/Crypto/CryptoAlgorithms.h index 39e21e4b757..1722adf4b2c 100644 --- a/Userland/Libraries/LibWeb/Crypto/CryptoAlgorithms.h +++ b/Userland/Libraries/LibWeb/Crypto/CryptoAlgorithms.h @@ -442,6 +442,7 @@ public: virtual WebIDL::ExceptionOr> derive_bits(AlgorithmParams const&, JS::NonnullGCPtr, Optional) override; virtual WebIDL::ExceptionOr, JS::NonnullGCPtr>> generate_key(AlgorithmParams const&, bool, Vector const&) override; virtual WebIDL::ExceptionOr> import_key(AlgorithmParams const&, Bindings::KeyFormat, CryptoKey::InternalKeyData, bool, Vector const&) override; + virtual WebIDL::ExceptionOr> export_key(Bindings::KeyFormat, JS::NonnullGCPtr) override; static NonnullOwnPtr create(JS::Realm& realm) { return adopt_own(*new X25519(realm)); } diff --git a/Userland/Libraries/LibWeb/Crypto/SubtleCrypto.cpp b/Userland/Libraries/LibWeb/Crypto/SubtleCrypto.cpp index 1ff67bd238f..0c08e0d7b96 100644 --- a/Userland/Libraries/LibWeb/Crypto/SubtleCrypto.cpp +++ b/Userland/Libraries/LibWeb/Crypto/SubtleCrypto.cpp @@ -815,6 +815,7 @@ SupportedAlgorithmsMap supported_algorithms() define_an_algorithm("deriveBits"_string, "X25519"_string); define_an_algorithm("generateKey"_string, "X25519"_string); define_an_algorithm("importKey"_string, "X25519"_string); + define_an_algorithm("exportKey"_string, "X25519"_string); return internal_object; }