/* * Copyright (c) 2023, stelar7 * Copyright (c) 2024, Andrew Kaster * * SPDX-License-Identifier: BSD-2-Clause */ #include #include #include #include #include #include #include #include #include #include namespace Web::Crypto { namespace { enum class HandleTag : u8 { ByteBuffer = 0, JsonWebKey = 1, RSAPublicKey = 2, RSAPrivateKey = 3, ECPublicKey = 4, ECPrivateKey = 5, MLDSAPublicKey = 6, MLDSAPrivateKey = 7, MLKEMPublicKey = 8, MLKEMPrivateKey = 9, }; enum class KeyAlgorithmTag : u8 { KeyAlgorithm = 0, RsaKeyAlgorithm = 1, RsaHashedKeyAlgorithm = 2, EcKeyAlgorithm = 3, AesKeyAlgorithm = 4, HmacKeyAlgorithm = 5, KmacKeyAlgorithm = 6, }; ::Crypto::UnsignedBigInteger big_integer_from_api_big_integer(JS::Uint8Array const& big_integer) { auto buffer = MUST(WebIDL::get_buffer_source_copy(big_integer)); if (!buffer.is_empty()) return ::Crypto::UnsignedBigInteger::import_data(buffer); return ::Crypto::UnsignedBigInteger(0); } void serialize_key_algorithm(HTML::TransferDataEncoder& encoder, JS::Object const& object) { if (auto const* algorithm = as_if(object)) { encoder.encode(KeyAlgorithmTag::RsaHashedKeyAlgorithm); encoder.encode(algorithm->name()); encoder.encode(algorithm->modulus_length()); encoder.encode_unsigned_big_integer(big_integer_from_api_big_integer(*algorithm->public_exponent())); encoder.encode(MUST(algorithm->hash().name(algorithm->vm()))); return; } if (auto const* algorithm = as_if(object)) { encoder.encode(KeyAlgorithmTag::RsaKeyAlgorithm); encoder.encode(algorithm->name()); encoder.encode(algorithm->modulus_length()); encoder.encode_unsigned_big_integer(big_integer_from_api_big_integer(*algorithm->public_exponent())); return; } if (auto const* algorithm = as_if(object)) { encoder.encode(KeyAlgorithmTag::EcKeyAlgorithm); encoder.encode(algorithm->name()); encoder.encode(algorithm->named_curve()); return; } if (auto const* algorithm = as_if(object)) { encoder.encode(KeyAlgorithmTag::AesKeyAlgorithm); encoder.encode(algorithm->name()); encoder.encode(algorithm->length()); return; } if (auto const* algorithm = as_if(object)) { encoder.encode(KeyAlgorithmTag::HmacKeyAlgorithm); encoder.encode(algorithm->name()); encoder.encode(algorithm->hash()->name()); encoder.encode(algorithm->length()); return; } if (auto const* algorithm = as_if(object)) { encoder.encode(KeyAlgorithmTag::KmacKeyAlgorithm); encoder.encode(algorithm->name()); encoder.encode(algorithm->length()); return; } auto const& algorithm = as(object); encoder.encode(KeyAlgorithmTag::KeyAlgorithm); encoder.encode(algorithm.name()); } WebIDL::ExceptionOr> deserialize_key_algorithm(HTML::TransferDataDecoder& decoder, JS::Realm& realm) { auto tag = decoder.decode(); switch (tag) { case KeyAlgorithmTag::KeyAlgorithm: { auto algorithm = KeyAlgorithm::create(realm); algorithm->set_name(decoder.decode()); return algorithm; } case KeyAlgorithmTag::RsaKeyAlgorithm: { auto algorithm = RsaKeyAlgorithm::create(realm); algorithm->set_name(decoder.decode()); algorithm->set_modulus_length(decoder.decode()); TRY(algorithm->set_public_exponent(TRY(decoder.decode_unsigned_big_integer(realm)))); return algorithm; } case KeyAlgorithmTag::RsaHashedKeyAlgorithm: { auto algorithm = RsaHashedKeyAlgorithm::create(realm); algorithm->set_name(decoder.decode()); algorithm->set_modulus_length(decoder.decode()); TRY(algorithm->set_public_exponent(TRY(decoder.decode_unsigned_big_integer(realm)))); algorithm->set_hash(decoder.decode()); return algorithm; } case KeyAlgorithmTag::EcKeyAlgorithm: { auto algorithm = EcKeyAlgorithm::create(realm); algorithm->set_name(decoder.decode()); algorithm->set_named_curve(decoder.decode()); return algorithm; } case KeyAlgorithmTag::AesKeyAlgorithm: { auto algorithm = AesKeyAlgorithm::create(realm); algorithm->set_name(decoder.decode()); algorithm->set_length(decoder.decode()); return algorithm; } case KeyAlgorithmTag::HmacKeyAlgorithm: { auto algorithm = HmacKeyAlgorithm::create(realm); algorithm->set_name(decoder.decode()); auto hash = KeyAlgorithm::create(realm); hash->set_name(decoder.decode()); algorithm->set_hash(hash); algorithm->set_length(decoder.decode()); return algorithm; } case KeyAlgorithmTag::KmacKeyAlgorithm: { auto algorithm = KmacKeyAlgorithm::create(realm); algorithm->set_name(decoder.decode()); algorithm->set_length(decoder.decode()); return algorithm; } } VERIFY_NOT_REACHED(); } void serialize_handle(HTML::TransferDataEncoder& encoder, ByteBuffer const& buffer) { encoder.encode(HandleTag::ByteBuffer); encoder.encode(buffer); } void serialize_handle(HTML::TransferDataEncoder& encoder, RsaOtherPrimesInfo const& info) { encoder.encode(info.r); encoder.encode(info.d); encoder.encode(info.t); } RsaOtherPrimesInfo deserialize_rsa_other_primes_info(HTML::TransferDataDecoder& decoder) { return RsaOtherPrimesInfo { .r = decoder.decode>(), .d = decoder.decode>(), .t = decoder.decode>(), }; } void serialize_handle(HTML::TransferDataEncoder& encoder, JsonWebKey const& jwk) { encoder.encode(HandleTag::JsonWebKey); encoder.encode(jwk.kty); encoder.encode(jwk.use); encoder.encode(jwk.key_ops); encoder.encode(jwk.alg); encoder.encode(jwk.ext); encoder.encode(jwk.crv); encoder.encode(jwk.x); encoder.encode(jwk.y); encoder.encode(jwk.d); encoder.encode(jwk.n); encoder.encode(jwk.e); encoder.encode(jwk.p); encoder.encode(jwk.q); encoder.encode(jwk.dp); encoder.encode(jwk.dq); encoder.encode(jwk.qi); encoder.encode(jwk.oth.has_value()); if (jwk.oth.has_value()) { encoder.encode(static_cast(jwk.oth->size())); for (auto const& info : *jwk.oth) serialize_handle(encoder, info); } encoder.encode(jwk.k); encoder.encode(jwk.pub); encoder.encode(jwk.priv); } JsonWebKey deserialize_json_web_key(HTML::TransferDataDecoder& decoder) { JsonWebKey jwk; jwk.kty = decoder.decode>(); jwk.use = decoder.decode>(); jwk.key_ops = decoder.decode>>(); jwk.alg = decoder.decode>(); jwk.ext = decoder.decode>(); jwk.crv = decoder.decode>(); jwk.x = decoder.decode>(); jwk.y = decoder.decode>(); jwk.d = decoder.decode>(); jwk.n = decoder.decode>(); jwk.e = decoder.decode>(); jwk.p = decoder.decode>(); jwk.q = decoder.decode>(); jwk.dp = decoder.decode>(); jwk.dq = decoder.decode>(); jwk.qi = decoder.decode>(); if (decoder.decode()) { auto size = decoder.decode(); Vector oth; oth.ensure_capacity(size); for (u64 i = 0; i < size; ++i) oth.unchecked_append(deserialize_rsa_other_primes_info(decoder)); jwk.oth = move(oth); } jwk.k = decoder.decode>(); jwk.pub = decoder.decode>(); jwk.priv = decoder.decode>(); return jwk; } void serialize_handle(HTML::TransferDataEncoder& encoder, ::Crypto::PK::RSAPublicKey const& key) { encoder.encode(HandleTag::RSAPublicKey); encoder.encode_unsigned_big_integer(key.modulus()); encoder.encode_unsigned_big_integer(key.public_exponent()); } WebIDL::ExceptionOr<::Crypto::PK::RSAPublicKey> deserialize_rsa_public_key(HTML::TransferDataDecoder& decoder, JS::Realm& realm) { auto modulus = TRY(decoder.decode_unsigned_big_integer(realm)); auto public_exponent = TRY(decoder.decode_unsigned_big_integer(realm)); return ::Crypto::PK::RSAPublicKey { move(modulus), move(public_exponent) }; } void serialize_handle(HTML::TransferDataEncoder& encoder, ::Crypto::PK::RSAPrivateKey const& key) { encoder.encode(HandleTag::RSAPrivateKey); encoder.encode_unsigned_big_integer(key.modulus()); encoder.encode_unsigned_big_integer(key.private_exponent()); encoder.encode_unsigned_big_integer(key.public_exponent()); encoder.encode_unsigned_big_integer(key.prime1()); encoder.encode_unsigned_big_integer(key.prime2()); encoder.encode_unsigned_big_integer(key.exponent1()); encoder.encode_unsigned_big_integer(key.exponent2()); encoder.encode_unsigned_big_integer(key.coefficient()); } WebIDL::ExceptionOr<::Crypto::PK::RSAPrivateKey> deserialize_rsa_private_key(HTML::TransferDataDecoder& decoder, JS::Realm& realm) { auto modulus = TRY(decoder.decode_unsigned_big_integer(realm)); auto private_exponent = TRY(decoder.decode_unsigned_big_integer(realm)); auto public_exponent = TRY(decoder.decode_unsigned_big_integer(realm)); auto prime1 = TRY(decoder.decode_unsigned_big_integer(realm)); auto prime2 = TRY(decoder.decode_unsigned_big_integer(realm)); auto exponent1 = TRY(decoder.decode_unsigned_big_integer(realm)); auto exponent2 = TRY(decoder.decode_unsigned_big_integer(realm)); auto coefficient = TRY(decoder.decode_unsigned_big_integer(realm)); return ::Crypto::PK::RSAPrivateKey { move(modulus), move(private_exponent), move(public_exponent), move(prime1), move(prime2), move(exponent1), move(exponent2), move(coefficient) }; } void serialize_ec_public_key(HTML::TransferDataEncoder& encoder, ::Crypto::PK::ECPublicKey const& key) { encoder.encode(MUST(key.x_bytes())); encoder.encode(MUST(key.y_bytes())); } void serialize_handle(HTML::TransferDataEncoder& encoder, ::Crypto::PK::ECPublicKey const& key) { encoder.encode(HandleTag::ECPublicKey); serialize_ec_public_key(encoder, key); } WebIDL::ExceptionOr<::Crypto::PK::ECPublicKey> deserialize_ec_public_key(HTML::TransferDataDecoder& decoder, JS::Realm& realm) { auto x_bytes = TRY(decoder.decode_buffer(realm)); auto y_bytes = TRY(decoder.decode_buffer(realm)); auto scalar_size = x_bytes.size(); return ::Crypto::PK::ECPublicKey { ::Crypto::UnsignedBigInteger::import_data(x_bytes), ::Crypto::UnsignedBigInteger::import_data(y_bytes), scalar_size }; } void serialize_handle(HTML::TransferDataEncoder& encoder, ::Crypto::PK::ECPrivateKey const& key) { encoder.encode(HandleTag::ECPrivateKey); encoder.encode(MUST(key.d_bytes())); encoder.encode(key.parameters()); encoder.encode(key.public_key().has_value()); if (key.public_key().has_value()) serialize_ec_public_key(encoder, *key.public_key()); } WebIDL::ExceptionOr<::Crypto::PK::ECPrivateKey> deserialize_ec_private_key(HTML::TransferDataDecoder& decoder, JS::Realm& realm) { auto d_bytes = TRY(decoder.decode_buffer(realm)); auto scalar_size = d_bytes.size(); auto d = ::Crypto::UnsignedBigInteger::import_data(d_bytes); auto parameters = decoder.decode>>(); Optional<::Crypto::PK::ECPublicKey> public_key; if (decoder.decode()) public_key = TRY(deserialize_ec_public_key(decoder, realm)); return ::Crypto::PK::ECPrivateKey { move(d), scalar_size, move(parameters), move(public_key) }; } void serialize_handle(HTML::TransferDataEncoder& encoder, ::Crypto::PK::MLDSAPublicKey const& key) { encoder.encode(HandleTag::MLDSAPublicKey); encoder.encode(key.public_key()); } WebIDL::ExceptionOr<::Crypto::PK::MLDSAPublicKey> deserialize_mldsa_public_key(HTML::TransferDataDecoder& decoder, JS::Realm& realm) { return ::Crypto::PK::MLDSAPublicKey { TRY(decoder.decode_buffer(realm)) }; } void serialize_handle(HTML::TransferDataEncoder& encoder, ::Crypto::PK::MLDSAPrivateKey const& key) { encoder.encode(HandleTag::MLDSAPrivateKey); encoder.encode(key.seed()); encoder.encode(key.public_key()); encoder.encode(key.private_key()); } WebIDL::ExceptionOr<::Crypto::PK::MLDSAPrivateKey> deserialize_mldsa_private_key(HTML::TransferDataDecoder& decoder, JS::Realm& realm) { auto seed = TRY(decoder.decode_buffer(realm)); auto public_key = TRY(decoder.decode_buffer(realm)); auto private_key = TRY(decoder.decode_buffer(realm)); return ::Crypto::PK::MLDSAPrivateKey { move(seed), move(public_key), move(private_key) }; } void serialize_handle(HTML::TransferDataEncoder& encoder, ::Crypto::PK::MLKEMPublicKey const& key) { encoder.encode(HandleTag::MLKEMPublicKey); encoder.encode(key.public_key()); } WebIDL::ExceptionOr<::Crypto::PK::MLKEMPublicKey> deserialize_mlkem_public_key(HTML::TransferDataDecoder& decoder, JS::Realm& realm) { return ::Crypto::PK::MLKEMPublicKey { TRY(decoder.decode_buffer(realm)) }; } void serialize_handle(HTML::TransferDataEncoder& encoder, ::Crypto::PK::MLKEMPrivateKey const& key) { encoder.encode(HandleTag::MLKEMPrivateKey); encoder.encode(key.seed()); encoder.encode(key.public_key()); encoder.encode(key.private_key()); } WebIDL::ExceptionOr<::Crypto::PK::MLKEMPrivateKey> deserialize_mlkem_private_key(HTML::TransferDataDecoder& decoder, JS::Realm& realm) { auto seed = TRY(decoder.decode_buffer(realm)); auto public_key = TRY(decoder.decode_buffer(realm)); auto private_key = TRY(decoder.decode_buffer(realm)); return ::Crypto::PK::MLKEMPrivateKey { move(seed), move(public_key), move(private_key) }; } } GC_DEFINE_ALLOCATOR(CryptoKey); GC_DEFINE_ALLOCATOR(CryptoKeyPair); GC::Ref CryptoKey::create(JS::Realm& realm, InternalKeyData key_data) { return realm.create(realm, move(key_data)); } GC::Ref CryptoKey::create(JS::Realm& realm) { return realm.create(realm); } CryptoKey::CryptoKey(JS::Realm& realm, InternalKeyData key_data) : PlatformObject(realm) , m_algorithm_cached(Object::create(realm, nullptr)) , m_usages_cached(Object::create(realm, nullptr)) , m_key_data(move(key_data)) { } CryptoKey::CryptoKey(JS::Realm& realm) : PlatformObject(realm) , m_algorithm_cached(Object::create(realm, nullptr)) , m_usages_cached(Object::create(realm, nullptr)) , m_key_data(MUST(ByteBuffer::create_uninitialized(0))) { } void CryptoKey::finalize() { Base::finalize(); m_key_data.visit( [](ByteBuffer& data) { secure_zero(data.data(), data.size()); }, [](auto& data) { secure_zero(reinterpret_cast(&data), sizeof(data)); }); } void CryptoKey::initialize(JS::Realm& realm) { WEB_SET_PROTOTYPE_FOR_INTERFACE(CryptoKey); Base::initialize(realm); } void CryptoKey::visit_edges(Visitor& visitor) { Base::visit_edges(visitor); visitor.visit(m_algorithm_cached); visitor.visit(m_usages_cached); } void CryptoKey::set_usages(Vector usages) { m_usages = move(usages); auto& realm = this->realm(); m_usages_cached = JS::Array::create_from(realm, m_usages.span(), [&](auto& key_usage) -> JS::Value { return JS::PrimitiveString::create(realm.vm(), Bindings::idl_enum_to_string(key_usage)); }); } String const& CryptoKey::algorithm_name() const { if (m_algorithm_name.is_empty()) { auto name = MUST(m_algorithm_cached->get("name"_utf16_fly_string)); m_algorithm_name = MUST(name.to_utf16_string(vm())).to_utf8_but_should_be_ported_to_utf16(); } return m_algorithm_name; } GC::Ref CryptoKeyPair::create(JS::Realm& realm, GC::Ref public_key, GC::Ref private_key) { return realm.create(realm, public_key, private_key); } CryptoKeyPair::CryptoKeyPair(JS::Realm& realm, GC::Ref public_key, GC::Ref private_key) : Object(ConstructWithPrototypeTag::Tag, realm.intrinsics().object_prototype()) , m_public_key(public_key) , m_private_key(private_key) { } void CryptoKeyPair::initialize(JS::Realm& realm) { define_native_accessor(realm, "publicKey"_utf16_fly_string, public_key_getter, {}, JS::Attribute::Enumerable | JS::Attribute::Configurable); define_native_accessor(realm, "privateKey"_utf16_fly_string, private_key_getter, {}, JS::Attribute::Enumerable | JS::Attribute::Configurable); Base::initialize(realm); } void CryptoKeyPair::visit_edges(Visitor& visitor) { Base::visit_edges(visitor); visitor.visit(m_public_key); visitor.visit(m_private_key); } static JS::ThrowCompletionOr 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)); auto* crypto_key_pair = as_if(this_object); if (!crypto_key_pair) return vm.throw_completion(JS::ErrorType::NotAnObjectOfType, "CryptoKeyPair"); return crypto_key_pair; } JS_DEFINE_NATIVE_FUNCTION(CryptoKeyPair::public_key_getter) { auto* impl = TRY(impl_from(vm)); return TRY(Bindings::throw_dom_exception_if_needed(vm, [&] { return impl->public_key(); })); } JS_DEFINE_NATIVE_FUNCTION(CryptoKeyPair::private_key_getter) { auto* impl = TRY(impl_from(vm)); return TRY(Bindings::throw_dom_exception_if_needed(vm, [&] { return impl->private_key(); })); } WebIDL::ExceptionOr CryptoKey::serialization_steps(HTML::TransferDataEncoder& serialized, bool, HTML::SerializationMemory&) { // 1. Set serialized.[[Type]] to the [[type]] internal slot of value. serialized.encode(m_type); // 2. Set serialized.[[Extractable]] to the [[extractable]] internal slot of value. serialized.encode(m_extractable); // 3. Set serialized.[[Algorithm]] to the sub-serialization of the [[algorithm]] internal slot of value. serialize_key_algorithm(serialized, m_algorithm_cached); // 4. Set serialized.[[Usages]] to the sub-serialization of the [[usages]] internal slot of value. serialized.encode(m_usages); // 5. Set serialized.[[Handle]] to the [[handle]] internal slot of value. m_key_data.visit([&](auto const& handle) { serialize_handle(serialized, handle); }); return {}; } WebIDL::ExceptionOr CryptoKey::deserialization_steps(HTML::TransferDataDecoder& serialized, HTML::DeserializationMemory&) { auto& realm = this->realm(); // 1. Initialize the [[type]] internal slot of value to serialized.[[Type]]. m_type = serialized.decode(); // 2. Initialize the [[extractable]] internal slot of value to serialized.[[Extractable]]. m_extractable = serialized.decode(); // 3. Initialize the [[algorithm]] internal slot of value to the sub-deserialization of serialized.[[Algorithm]]. m_algorithm_cached = TRY(deserialize_key_algorithm(serialized, realm)); // 4. Initialize the [[usages]] internal slot of value to the sub-deserialization of serialized.[[Usages]]. auto usages = serialized.decode>(); set_usages(move(usages)); // 5. Initialize the [[handle]] internal slot of value to serialized.[[Handle]]. auto tag = serialized.decode(); switch (tag) { case HandleTag::ByteBuffer: m_key_data = TRY(serialized.decode_buffer(realm)); break; case HandleTag::JsonWebKey: m_key_data = deserialize_json_web_key(serialized); break; case HandleTag::RSAPublicKey: m_key_data = TRY(deserialize_rsa_public_key(serialized, realm)); break; case HandleTag::RSAPrivateKey: m_key_data = TRY(deserialize_rsa_private_key(serialized, realm)); break; case HandleTag::ECPublicKey: m_key_data = TRY(deserialize_ec_public_key(serialized, realm)); break; case HandleTag::ECPrivateKey: m_key_data = TRY(deserialize_ec_private_key(serialized, realm)); break; case HandleTag::MLDSAPublicKey: m_key_data = TRY(deserialize_mldsa_public_key(serialized, realm)); break; case HandleTag::MLDSAPrivateKey: m_key_data = TRY(deserialize_mldsa_private_key(serialized, realm)); break; case HandleTag::MLKEMPublicKey: m_key_data = TRY(deserialize_mlkem_public_key(serialized, realm)); break; case HandleTag::MLKEMPrivateKey: m_key_data = TRY(deserialize_mlkem_private_key(serialized, realm)); break; } return {}; } }