diff --git a/Userland/Libraries/LibWeb/Crypto/CryptoAlgorithms.cpp b/Userland/Libraries/LibWeb/Crypto/CryptoAlgorithms.cpp index d3197ef9e53..7d68f5167c0 100644 --- a/Userland/Libraries/LibWeb/Crypto/CryptoAlgorithms.cpp +++ b/Userland/Libraries/LibWeb/Crypto/CryptoAlgorithms.cpp @@ -106,7 +106,7 @@ ErrorOr base64_url_uint_encode(::Crypto::UnsignedBigInteger integer) return encoded; } -WebIDL::ExceptionOr<::Crypto::UnsignedBigInteger> base64_url_uint_decode(JS::Realm& realm, String const& base64_url_string) +WebIDL::ExceptionOr base64_url_bytes_decode(JS::Realm& realm, String const& base64_url_string) { auto& vm = realm.vm(); @@ -122,7 +122,12 @@ WebIDL::ExceptionOr<::Crypto::UnsignedBigInteger> base64_url_uint_decode(JS::Rea return vm.throw_completion(vm.error_message(::JS::VM::ErrorMessage::OutOfMemory)); return WebIDL::DataError::create(realm, MUST(String::formatted("base64 decode: {}", base64_bytes_or_error.release_error()))); } - auto base64_bytes_be = base64_bytes_or_error.release_value(); + return base64_bytes_or_error.release_value(); +} + +WebIDL::ExceptionOr<::Crypto::UnsignedBigInteger> base64_url_uint_decode(JS::Realm& realm, String const& base64_url_string) +{ + auto base64_bytes_be = TRY(base64_url_bytes_decode(realm, base64_url_string)); if constexpr (AK::HostIsLittleEndian) { // We need to swap the integer's big-endian representation to little endian in order to import it @@ -215,6 +220,14 @@ static WebIDL::ExceptionOr<::Crypto::PK::RSAPublicKey<>> parse_jwk_rsa_public_ke return ::Crypto::PK::RSAPublicKey<>(move(n), move(e)); } +static WebIDL::ExceptionOr parse_jwk_symmetric_key(JS::Realm& realm, Bindings::JsonWebKey const& jwk) +{ + if (!jwk.k.has_value()) { + return WebIDL::DataError::create(realm, "JWK has no 'k' field"_string); + } + return base64_url_bytes_decode(realm, *jwk.k); +} + AlgorithmParams::~AlgorithmParams() = default; JS::ThrowCompletionOr> AlgorithmParams::from_value(JS::VM& vm, JS::Value value) @@ -227,6 +240,23 @@ JS::ThrowCompletionOr> AlgorithmParams::from_valu return adopt_own(*new AlgorithmParams { name_string }); } +AesCbcParams::~AesCbcParams() = default; + +JS::ThrowCompletionOr> AesCbcParams::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 iv_value = TRY(object.get("iv")); + if (!iv_value.is_object() || !(is(iv_value.as_object()) || is(iv_value.as_object()) || is(iv_value.as_object()))) + return vm.throw_completion(JS::ErrorType::NotAnObjectOfType, "BufferSource"); + auto iv = TRY_OR_THROW_OOM(vm, WebIDL::get_buffer_source_copy(iv_value.as_object())); + + return adopt_own(*new AesCbcParams { name, iv }); +} + HKDFParams::~HKDFParams() = default; JS::ThrowCompletionOr> HKDFParams::from_value(JS::VM& vm, JS::Value value) @@ -967,6 +997,145 @@ WebIDL::ExceptionOr> RSAOAEP::export_key(Bindings:: return JS::NonnullGCPtr { *result }; } +// https://w3c.github.io/webcrypto/#aes-cbc-operations +WebIDL::ExceptionOr> AesCbc::encrypt(AlgorithmParams const&, JS::NonnullGCPtr, ByteBuffer const&) +{ + VERIFY_NOT_REACHED(); +} + +WebIDL::ExceptionOr> AesCbc::decrypt(AlgorithmParams const&, JS::NonnullGCPtr, ByteBuffer const&) +{ + VERIFY_NOT_REACHED(); +} + +WebIDL::ExceptionOr> AesCbc::import_key(AlgorithmParams const&, Bindings::KeyFormat format, CryptoKey::InternalKeyData key_data, bool extractable, Vector const& key_usages) +{ + // 1. If usages contains an entry which is not one of "encrypt", "decrypt", "wrapKey" or "unwrapKey", then throw a SyntaxError. + for (auto& usage : key_usages) { + if (usage != Bindings::KeyUsage::Encrypt && usage != Bindings::KeyUsage::Decrypt && usage != Bindings::KeyUsage::Wrapkey && usage != Bindings::KeyUsage::Unwrapkey) { + return WebIDL::SyntaxError::create(m_realm, MUST(String::formatted("Invalid key usage '{}'", idl_enum_to_string(usage)))); + } + } + + // 2. + ByteBuffer data; + if (format == Bindings::KeyFormat::Raw) { + // -> If format is "raw": + // 1. Let data be the octet string contained in keyData. + // 2. If the length in bits of data is not 128, 192 or 256 then throw a DataError. + data = key_data.get(); + auto length_in_bits = data.size() * 8; + if (length_in_bits != 128 && length_in_bits != 192 && length_in_bits != 256) { + return WebIDL::DataError::create(m_realm, MUST(String::formatted("Invalid key length '{}' bits (must be either 128, 192, or 256 bits)", length_in_bits))); + } + } else if (format == Bindings::KeyFormat::Jwk) { + // -> If format is "jwk": + // 1. -> If keyData is a JsonWebKey dictionary: + // Let jwk equal keyData. + // -> Otherwise: + // Throw a DataError. + if (!key_data.has()) + return WebIDL::DataError::create(m_realm, "keyData is not a JsonWebKey dictionary"_string); + auto& jwk = key_data.get(); + + // 2. If the kty field of jwk is not "oct", then throw a DataError. + if (jwk.kty != "oct"_string) + return WebIDL::DataError::create(m_realm, "Invalid key type"_string); + + // 3. If jwk does not meet the requirements of Section 6.4 of JSON Web Algorithms [JWA], then throw a DataError. + // Specifically, those requirements are: + // - ".k" is a valid bas64url encoded octet stream, which we do by just parsing it, in step 4. + // - ".alg" is checked only in step 5. + + // 4. Let data be the octet string obtained by decoding the k field of jwk. + data = TRY(parse_jwk_symmetric_key(m_realm, jwk)); + + // 5. -> If data has length 128 bits: + // If the alg field of jwk is present, and is not "A128CBC", then throw a DataError. + // -> If data has length 192 bits: + // If the alg field of jwk is present, and is not "A192CBC", then throw a DataError. + // -> If data has length 256 bits: + // If the alg field of jwk is present, and is not "A256CBC", then throw a DataError. + // -> Otherwise: + // throw a DataError. + auto data_bits = data.size() * 8; + auto const& alg = jwk.alg; + if (data_bits == 128) { + if (alg.has_value() && alg != "A128CBC") { + return WebIDL::DataError::create(m_realm, "Contradictory key size: key has 128 bits, but alg specifies non-128-bit algorithm"_string); + } + } else if (data_bits == 192) { + if (alg.has_value() && alg != "A192CBC") { + return WebIDL::DataError::create(m_realm, "Contradictory key size: key has 192 bits, but alg specifies non-192-bit algorithm"_string); + } + } else if (data_bits == 256) { + if (alg.has_value() && alg != "A256CBC") { + return WebIDL::DataError::create(m_realm, "Contradictory key size: key has 256 bits, but alg specifies non-256-bit algorithm"_string); + } + } else { + return WebIDL::DataError::create(m_realm, MUST(String::formatted("Invalid key size: {} bits", data_bits))); + } + + // 6. If usages is non-empty and the use field of jwk is present and is not "enc", then throw a DataError. + if (!key_usages.is_empty() && jwk.use.has_value() && *jwk.use != "enc"_string) + return WebIDL::DataError::create(m_realm, "Invalid use field"_string); + + // 7. If the key_ops field of jwk is present, and is invalid according to the requirements of JSON Web Key [JWK] or does not contain all of the specified usages values, then throw a DataError. + if (jwk.key_ops.has_value()) { + for (auto const& usage : key_usages) { + if (!jwk.key_ops->contains_slow(Bindings::idl_enum_to_string(usage))) + return WebIDL::DataError::create(m_realm, MUST(String::formatted("Missing key_ops field: {}", Bindings::idl_enum_to_string(usage)))); + } + } + // FIXME: Validate jwk.key_ops against requirements in https://www.rfc-editor.org/rfc/rfc7517#section-4.3 + + // 8. If the ext field of jwk is present and has the value false and extractable is true, then throw a DataError. + if (jwk.ext.has_value() && !*jwk.ext && extractable) + return WebIDL::DataError::create(m_realm, "Invalid ext field"_string); + } else { + // Otherwise: + // throw a NotSupportedError + return WebIDL::NotSupportedError::create(m_realm, "Only raw and jwk formats are supported"_string); + } + + // 3. Let key be a new CryptoKey object representing an AES key with value data. + auto data_bits = data.size() * 8; + auto key = CryptoKey::create(m_realm, move(data)); + + // 4. Set the [[type]] internal slot of key to "secret". + key->set_type(Bindings::KeyType::Secret); + + // 5. Let algorithm be a new AesKeyAlgorithm. + auto algorithm = AesKeyAlgorithm::create(m_realm); + + // 6. Set the name attribute of algorithm to "AES-CBC". + algorithm->set_name("AES-CBC"_string); + + // 7. Set the length attribute of algorithm to the length, in bits, of data. + algorithm->set_length(data_bits); + + // 8. Set the [[algorithm]] internal slot of key to algorithm. + key->set_algorithm(algorithm); + + // 9. Return key. + return key; +} + +WebIDL::ExceptionOr, JS::NonnullGCPtr>> AesCbc::generate_key(AlgorithmParams const&, bool, Vector const&) +{ + VERIFY_NOT_REACHED(); +} + +WebIDL::ExceptionOr> AesCbc::export_key(Bindings::KeyFormat, JS::NonnullGCPtr) +{ + VERIFY_NOT_REACHED(); +} + +WebIDL::ExceptionOr AesCbc::get_key_length(AlgorithmParams const&) +{ + VERIFY_NOT_REACHED(); +} + // https://w3c.github.io/webcrypto/#hkdf-operations WebIDL::ExceptionOr> HKDF::import_key(AlgorithmParams const&, Bindings::KeyFormat format, CryptoKey::InternalKeyData key_data, bool extractable, Vector const& key_usages) { diff --git a/Userland/Libraries/LibWeb/Crypto/CryptoAlgorithms.h b/Userland/Libraries/LibWeb/Crypto/CryptoAlgorithms.h index fd52f8c9906..16b4815eb45 100644 --- a/Userland/Libraries/LibWeb/Crypto/CryptoAlgorithms.h +++ b/Userland/Libraries/LibWeb/Crypto/CryptoAlgorithms.h @@ -37,6 +37,20 @@ struct AlgorithmParams { static JS::ThrowCompletionOr> from_value(JS::VM&, JS::Value); }; +// https://w3c.github.io/webcrypto/#aes-cbc +struct AesCbcParams : public AlgorithmParams { + virtual ~AesCbcParams() override; + AesCbcParams(String name, ByteBuffer iv) + : AlgorithmParams(move(name)) + , iv(move(iv)) + { + } + + ByteBuffer iv; + + static JS::ThrowCompletionOr> from_value(JS::VM&, JS::Value); +}; + // https://w3c.github.io/webcrypto/#hkdf-params struct HKDFParams : public AlgorithmParams { virtual ~HKDFParams() override; @@ -250,6 +264,24 @@ private: } }; +class AesCbc : public AlgorithmMethods { +public: + virtual WebIDL::ExceptionOr> encrypt(AlgorithmParams const&, JS::NonnullGCPtr, ByteBuffer const&) override; + virtual WebIDL::ExceptionOr> decrypt(AlgorithmParams const&, JS::NonnullGCPtr, ByteBuffer const&) override; + virtual WebIDL::ExceptionOr> import_key(AlgorithmParams const&, Bindings::KeyFormat, CryptoKey::InternalKeyData, bool, Vector const&) override; + virtual WebIDL::ExceptionOr, JS::NonnullGCPtr>> generate_key(AlgorithmParams const&, bool, Vector const&) override; + virtual WebIDL::ExceptionOr> export_key(Bindings::KeyFormat, JS::NonnullGCPtr) override; + virtual WebIDL::ExceptionOr get_key_length(AlgorithmParams const&) override; + + static NonnullOwnPtr create(JS::Realm& realm) { return adopt_own(*new AesCbc(realm)); } + +private: + explicit AesCbc(JS::Realm& realm) + : AlgorithmMethods(realm) + { + } +}; + class HKDF : public AlgorithmMethods { public: virtual WebIDL::ExceptionOr> import_key(AlgorithmParams const&, Bindings::KeyFormat, CryptoKey::InternalKeyData, bool, Vector const&) override; @@ -326,6 +358,7 @@ private: }; ErrorOr base64_url_uint_encode(::Crypto::UnsignedBigInteger); +WebIDL::ExceptionOr base64_url_bytes_decode(JS::Realm&, String const& base64_url_string); WebIDL::ExceptionOr<::Crypto::UnsignedBigInteger> base64_url_uint_decode(JS::Realm&, String const& base64_url_string); } diff --git a/Userland/Libraries/LibWeb/Crypto/KeyAlgorithms.cpp b/Userland/Libraries/LibWeb/Crypto/KeyAlgorithms.cpp index b81b6287d4b..8b3e5c5e021 100644 --- a/Userland/Libraries/LibWeb/Crypto/KeyAlgorithms.cpp +++ b/Userland/Libraries/LibWeb/Crypto/KeyAlgorithms.cpp @@ -17,6 +17,7 @@ JS_DEFINE_ALLOCATOR(KeyAlgorithm); JS_DEFINE_ALLOCATOR(RsaKeyAlgorithm); JS_DEFINE_ALLOCATOR(RsaHashedKeyAlgorithm); JS_DEFINE_ALLOCATOR(EcKeyAlgorithm); +JS_DEFINE_ALLOCATOR(AesKeyAlgorithm); template static JS::ThrowCompletionOr impl_from(JS::VM& vm, StringView Name) @@ -183,4 +184,29 @@ JS_DEFINE_NATIVE_FUNCTION(RsaHashedKeyAlgorithm::hash_getter) }); } +JS::NonnullGCPtr AesKeyAlgorithm::create(JS::Realm& realm) +{ + return realm.heap().allocate(realm, realm); +} + +AesKeyAlgorithm::AesKeyAlgorithm(JS::Realm& realm) + : KeyAlgorithm(realm) + , m_length(0) +{ +} + +void AesKeyAlgorithm::initialize(JS::Realm& realm) +{ + Base::initialize(realm); + + define_native_accessor(realm, "length", length_getter, {}, JS::Attribute::Enumerable | JS::Attribute::Configurable); +} + +JS_DEFINE_NATIVE_FUNCTION(AesKeyAlgorithm::length_getter) +{ + auto* impl = TRY(impl_from(vm, "AesKeyAlgorithm"sv)); + auto length = TRY(Bindings::throw_dom_exception_if_needed(vm, [&] { return impl->length(); })); + return length; +} + } diff --git a/Userland/Libraries/LibWeb/Crypto/KeyAlgorithms.h b/Userland/Libraries/LibWeb/Crypto/KeyAlgorithms.h index 27cd299472d..1618dbedbcc 100644 --- a/Userland/Libraries/LibWeb/Crypto/KeyAlgorithms.h +++ b/Userland/Libraries/LibWeb/Crypto/KeyAlgorithms.h @@ -121,4 +121,28 @@ private: NamedCurve m_named_curve; }; +// https://w3c.github.io/webcrypto/#AesKeyAlgorithm-dictionary +struct AesKeyAlgorithm : public KeyAlgorithm { + JS_OBJECT(AesKeyAlgorithm, KeyAlgorithm); + JS_DECLARE_ALLOCATOR(AesKeyAlgorithm); + +public: + static JS::NonnullGCPtr create(JS::Realm&); + + virtual ~AesKeyAlgorithm() override = default; + + u16 length() const { return m_length; } + void set_length(u16 length) { m_length = length; } + +protected: + AesKeyAlgorithm(JS::Realm&); + + virtual void initialize(JS::Realm&) override; + +private: + JS_DECLARE_NATIVE_FUNCTION(length_getter); + + u16 m_length; +}; + } diff --git a/Userland/Libraries/LibWeb/Crypto/SubtleCrypto.cpp b/Userland/Libraries/LibWeb/Crypto/SubtleCrypto.cpp index 8e218153db7..b267cd70ae6 100644 --- a/Userland/Libraries/LibWeb/Crypto/SubtleCrypto.cpp +++ b/Userland/Libraries/LibWeb/Crypto/SubtleCrypto.cpp @@ -768,6 +768,14 @@ SupportedAlgorithmsMap supported_algorithms() define_an_algorithm("digest"_string, "SHA-384"_string); define_an_algorithm("digest"_string, "SHA-512"_string); + // https://w3c.github.io/webcrypto/#aes-cbc-registration + // FIXME: define_an_algorithm("encrypt"_string, "AES-CBC"_string); + // FIXME: define_an_algorithm("decrypt"_string, "AES-CBC"_string); + // FIXME: define_an_algorithm("generateKey"_string, "AES-CBC"_string); + define_an_algorithm("importKey"_string, "AES-CBC"_string); + // FIXME: define_an_algorithm("exportKey"_string, "AES-CBC"_string); + // FIXME: define_an_algorithm("get key length"_string, "AES-CBC"_string); + // https://w3c.github.io/webcrypto/#hkdf define_an_algorithm("importKey"_string, "HKDF"_string); define_an_algorithm("deriveBits"_string, "HKDF"_string);