ソースを参照

LibWeb: Implement X25519.exportKey

stelar7 9 ヶ月 前
コミット
f9b511a7d6

+ 52 - 0
Userland/Libraries/LibCrypto/PK/PK.h

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

+ 127 - 0
Userland/Libraries/LibWeb/Crypto/CryptoAlgorithms.cpp

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

+ 1 - 0
Userland/Libraries/LibWeb/Crypto/CryptoAlgorithms.h

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

+ 1 - 0
Userland/Libraries/LibWeb/Crypto/SubtleCrypto.cpp

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