From 4c8c94a18879c73dcf40d4aad8e0fe678eaff879 Mon Sep 17 00:00:00 2001 From: devgianlu Date: Mon, 23 Dec 2024 21:30:38 +0100 Subject: [PATCH] tmp --- Libraries/LibCrypto/OpenSSL.h | 55 +- Libraries/LibCrypto/PK/PK.h | 4 +- Libraries/LibCrypto/PK/RSA.cpp | 39 +- Libraries/LibCrypto/PK/RSA.h | 8 +- Libraries/LibCrypto/Padding/PSS.h | 27 + Libraries/LibWeb/Crypto/CryptoAlgorithms.cpp | 521 +++++++++++++++++++ Libraries/LibWeb/Crypto/CryptoAlgorithms.h | 19 + Libraries/LibWeb/Crypto/SubtleCrypto.cpp | 10 +- 8 files changed, 639 insertions(+), 44 deletions(-) create mode 100644 Libraries/LibCrypto/Padding/PSS.h diff --git a/Libraries/LibCrypto/OpenSSL.h b/Libraries/LibCrypto/OpenSSL.h index 1b30307d05952..2ed115cb62e3f 100644 --- a/Libraries/LibCrypto/OpenSSL.h +++ b/Libraries/LibCrypto/OpenSSL.h @@ -14,29 +14,42 @@ #include namespace Crypto { - -#define OPENSSL_TRY_PTR(...) \ - ({ \ - auto* _temporary_result = (__VA_ARGS__); \ - if (!_temporary_result) [[unlikely]] { \ - auto err = ERR_get_error(); \ - VERIFY(err); \ - auto* err_message = ERR_error_string(err, nullptr); \ - return Error::from_string_view(StringView { err_message, strlen(err_message) }); \ - } \ - _temporary_result; \ +#define OPENSSL_TRY_PTR(...) \ + ({ \ + auto* _temporary_result = (__VA_ARGS__); \ + if (!_temporary_result) [[unlikely]] { \ + StringBuilder err_builder; \ + err_builder.append(#__VA_ARGS__ ": "_string); \ + auto err = ERR_get_error(); \ + while (err != 0) { \ + auto err_str = ERR_error_string(err, nullptr); \ + err_builder.append(err_str, strlen(err_str)); \ + err_builder.append(", "_string); \ + err = ERR_get_error(); \ + } \ + auto err_message = MUST(err_builder.to_string()); \ + return Error::from_string_view(err_message.bytes_as_string_view()); \ + } \ + _temporary_result; \ }) -#define OPENSSL_TRY(...) \ - ({ \ - auto _temporary_result = (__VA_ARGS__); \ - if (_temporary_result != 1) [[unlikely]] { \ - auto err = ERR_get_error(); \ - VERIFY(err); \ - auto* err_message = ERR_error_string(err, nullptr); \ - return Error::from_string_view(StringView { err_message, strlen(err_message) }); \ - } \ - _temporary_result; \ +#define OPENSSL_TRY(...) \ + ({ \ + auto _temporary_result = (__VA_ARGS__); \ + if (_temporary_result != 1) [[unlikely]] { \ + StringBuilder err_builder; \ + err_builder.append(#__VA_ARGS__ ": "_string); \ + auto err = ERR_get_error(); \ + while (err != 0) { \ + auto err_str = ERR_error_string(err, nullptr); \ + err_builder.append(err_str, strlen(err_str)); \ + err_builder.append(", "_string); \ + err = ERR_get_error(); \ + } \ + auto err_message = MUST(err_builder.to_string()); \ + return Error::from_string_view(err_message.bytes_as_string_view()); \ + } \ + _temporary_result; \ }) #define OPENSSL_WRAPPER_CLASS(class_name, openssl_type, openssl_prefix) \ diff --git a/Libraries/LibCrypto/PK/PK.h b/Libraries/LibCrypto/PK/PK.h index baa4ae81c83a4..9574aa9a81e21 100644 --- a/Libraries/LibCrypto/PK/PK.h +++ b/Libraries/LibCrypto/PK/PK.h @@ -235,8 +235,8 @@ class PKSystem { virtual ErrorOr encrypt(ReadonlyBytes in, Bytes& out) = 0; virtual ErrorOr decrypt(ReadonlyBytes in, Bytes& out) = 0; - virtual ErrorOr verify(ReadonlyBytes in, Bytes& out) = 0; - virtual ErrorOr sign(ReadonlyBytes in, Bytes& out) = 0; + virtual ErrorOr sign(ReadonlyBytes message, Bytes& signature) = 0; + virtual ErrorOr verify(ReadonlyBytes message, ReadonlyBytes signature) = 0; virtual ByteString class_name() const = 0; diff --git a/Libraries/LibCrypto/PK/RSA.cpp b/Libraries/LibCrypto/PK/RSA.cpp index 396a28789d1e1..d04101e21d8ae 100644 --- a/Libraries/LibCrypto/PK/RSA.cpp +++ b/Libraries/LibCrypto/PK/RSA.cpp @@ -243,22 +243,37 @@ ErrorOr RSA::decrypt(ReadonlyBytes in, Bytes& out) return {}; } -ErrorOr RSA::sign(ReadonlyBytes in, Bytes& out) +ErrorOr RSA::sign(ReadonlyBytes message, Bytes& signature) { - auto in_integer = UnsignedBigInteger::import_data(in.data(), in.size()); - auto exp = NumberTheory::ModularPower(in_integer, m_private_key.private_exponent(), m_private_key.modulus()); - auto size = exp.export_data(out); - out = out.slice(out.size() - size, size); + auto key = TRY(private_key_to_openssl_pkey(m_private_key)); + + auto ctx = TRY(OpenSSL_PKEY_CTX::wrap(EVP_PKEY_CTX_new_from_pkey(nullptr, key.ptr(), nullptr))); + + OPENSSL_TRY(EVP_PKEY_sign_init(ctx.ptr())); + OPENSSL_TRY(EVP_PKEY_CTX_set_rsa_padding(ctx.ptr(), RSA_NO_PADDING)); + + size_t signature_size = signature.size(); + OPENSSL_TRY(EVP_PKEY_sign(ctx.ptr(), signature.data(), &signature_size, message.data(), message.size())); + signature = signature.slice(0, signature_size); return {}; } -ErrorOr RSA::verify(ReadonlyBytes in, Bytes& out) +ErrorOr RSA::verify(ReadonlyBytes message, ReadonlyBytes signature) { - auto in_integer = UnsignedBigInteger::import_data(in.data(), in.size()); - auto exp = NumberTheory::ModularPower(in_integer, m_public_key.public_exponent(), m_public_key.modulus()); - auto size = exp.export_data(out); - out = out.slice(out.size() - size, size); - return {}; + auto key = TRY(public_key_to_openssl_pkey(m_public_key)); + + auto ctx = TRY(OpenSSL_PKEY_CTX::wrap(EVP_PKEY_CTX_new_from_pkey(nullptr, key.ptr(), nullptr))); + + OPENSSL_TRY(EVP_PKEY_verify_init(ctx.ptr())); + OPENSSL_TRY(EVP_PKEY_CTX_set_rsa_padding(ctx.ptr(), RSA_NO_PADDING)); + + auto ret = EVP_PKEY_verify(ctx.ptr(), signature.data(), signature.size(), message.data(), message.size()); + if (ret == 1) + return true; + if (ret == 0) + return false; + OPENSSL_TRY(ret); + VERIFY_NOT_REACHED(); } void RSA::import_private_key(ReadonlyBytes bytes, bool pem) @@ -394,7 +409,7 @@ ErrorOr RSA_PKCS1_EME::sign(ReadonlyBytes, Bytes&) return Error::from_string_literal("FIXME: RSA_PKCS_EME::sign"); } -ErrorOr RSA_PKCS1_EME::verify(ReadonlyBytes, Bytes&) +ErrorOr RSA_PKCS1_EME::verify(ReadonlyBytes, ReadonlyBytes) { return Error::from_string_literal("FIXME: RSA_PKCS_EME::verify"); } diff --git a/Libraries/LibCrypto/PK/RSA.h b/Libraries/LibCrypto/PK/RSA.h index e12aa46928a02..6f1cf3818ac14 100644 --- a/Libraries/LibCrypto/PK/RSA.h +++ b/Libraries/LibCrypto/PK/RSA.h @@ -199,8 +199,8 @@ class RSA : public PKSystem, RSAPublicKey encrypt(ReadonlyBytes in, Bytes& out) override; virtual ErrorOr decrypt(ReadonlyBytes in, Bytes& out) override; - virtual ErrorOr verify(ReadonlyBytes in, Bytes& out) override; - virtual ErrorOr sign(ReadonlyBytes in, Bytes& out) override; + virtual ErrorOr sign(ReadonlyBytes message, Bytes& signature) override; + virtual ErrorOr verify(ReadonlyBytes message, ReadonlyBytes signature) override; virtual ByteString class_name() const override { @@ -240,8 +240,8 @@ class RSA_PKCS1_EME : public RSA { virtual ErrorOr encrypt(ReadonlyBytes in, Bytes& out) override; virtual ErrorOr decrypt(ReadonlyBytes in, Bytes& out) override; - virtual ErrorOr verify(ReadonlyBytes in, Bytes& out) override; - virtual ErrorOr sign(ReadonlyBytes in, Bytes& out) override; + virtual ErrorOr sign(ReadonlyBytes message, Bytes& signature) override; + virtual ErrorOr verify(ReadonlyBytes message, ReadonlyBytes signature) override; virtual ByteString class_name() const override { diff --git a/Libraries/LibCrypto/Padding/PSS.h b/Libraries/LibCrypto/Padding/PSS.h new file mode 100644 index 0000000000000..fce2acb6e894f --- /dev/null +++ b/Libraries/LibCrypto/Padding/PSS.h @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2024, stelar7 + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace Crypto::Padding { + +class PSS { +public: + template + static ErrorOr emsa_encode(ReadonlyBytes, size_t) + { + return Error::from_string_literal("Not implemented"); + } +}; + +} diff --git a/Libraries/LibWeb/Crypto/CryptoAlgorithms.cpp b/Libraries/LibWeb/Crypto/CryptoAlgorithms.cpp index 84cfbb6f6f716..e5121b774afb1 100644 --- a/Libraries/LibWeb/Crypto/CryptoAlgorithms.cpp +++ b/Libraries/LibWeb/Crypto/CryptoAlgorithms.cpp @@ -30,6 +30,7 @@ #include #include #include +#include #include #include #include @@ -1243,6 +1244,526 @@ WebIDL::ExceptionOr> RSAOAEP::export_key(Bindings::KeyFormat return GC::Ref { *result }; } +// https://w3c.github.io/webcrypto/#rsa-pss-registration +WebIDL::ExceptionOr, GC::Ref>> RSAPSS::generate_key(AlgorithmParams const& params, bool extractable, Vector const& key_usages) +{ + // 1. If usages contains a value which is not one of "sign" or "verify", then throw a SyntaxError. + for (auto const& usage : key_usages) { + if (usage != Bindings::KeyUsage::Sign && usage != Bindings::KeyUsage::Verify) { + return WebIDL::SyntaxError::create(m_realm, MUST(String::formatted("Invalid key usage '{}'", idl_enum_to_string(usage)))); + } + } + + // 2. Generate an RSA key pair, as defined in [RFC3447], with RSA modulus length equal to the modulusLength member of normalizedAlgorithm + // and RSA public exponent equal to the publicExponent member of normalizedAlgorithm. + // 3. If performing the operation results in an error, then throw an OperationError. + auto const& normalized_algorithm = static_cast(params); + auto maybe_key_pair = ::Crypto::PK::RSA::generate_key_pair(normalized_algorithm.modulus_length, normalized_algorithm.public_exponent); + if (maybe_key_pair.is_error()) + return WebIDL::OperationError::create(m_realm, "Failed to generate RSA key pair"_string); + + auto key_pair = maybe_key_pair.release_value(); + + // 4. Let algorithm be a new RsaHashedKeyAlgorithm object. + auto algorithm = RsaHashedKeyAlgorithm::create(m_realm); + + // 5. Set the name attribute of algorithm to "RSA-PSS". + algorithm->set_name("RSA-PSS"_string); + + // 6. Set the modulusLength attribute of algorithm to equal the modulusLength member of normalizedAlgorithm. + algorithm->set_modulus_length(normalized_algorithm.modulus_length); + + // 7. Set the publicExponent attribute of algorithm to equal the publicExponent member of normalizedAlgorithm. + TRY(algorithm->set_public_exponent(normalized_algorithm.public_exponent)); + + // 8. Set the hash attribute of algorithm to equal the hash member of normalizedAlgorithm. + algorithm->set_hash(normalized_algorithm.hash); + + // 9. Let publicKey be a new CryptoKey representing the public key of the generated key pair. + auto public_key = CryptoKey::create(m_realm, CryptoKey::InternalKeyData { key_pair.public_key }); + + // 10. Set the [[type]] internal slot of publicKey to "public" + public_key->set_type(Bindings::KeyType::Public); + + // 11. Set the [[algorithm]] internal slot of publicKey to algorithm. + public_key->set_algorithm(algorithm); + + // 12. Set the [[extractable]] internal slot of publicKey to true. + public_key->set_extractable(true); + + // 13. Set the [[usages]] internal slot of publicKey to be the usage intersection of usages and [ "verify" ]. + public_key->set_usages(usage_intersection(key_usages, { { Bindings::KeyUsage::Verify } })); + + // 14. Let privateKey be a new CryptoKey representing the private key of the generated key pair. + auto private_key = CryptoKey::create(m_realm, CryptoKey::InternalKeyData { key_pair.private_key }); + + // 15. Set the [[type]] internal slot of privateKey to "private" + private_key->set_type(Bindings::KeyType::Private); + + // 16. Set the [[algorithm]] internal slot of privateKey to algorithm. + private_key->set_algorithm(algorithm); + + // 17. Set the [[extractable]] internal slot of privateKey to extractable. + private_key->set_extractable(extractable); + + // 18. Set the [[usages]] internal slot of privateKey to be the usage intersection of usages and [ "sign" ]. + private_key->set_usages(usage_intersection(key_usages, { { Bindings::KeyUsage::Sign } })); + + // 19. Let result be a new CryptoKeyPair dictionary. + // 20. Set the publicKey attribute of result to be publicKey. + // 21. Set the privateKey attribute of result to be privateKey. + // 22. Return result. + return Variant, GC::Ref> { CryptoKeyPair::create(m_realm, public_key, private_key) }; +} + +// https://w3c.github.io/webcrypto/#rsa-pss-registration +WebIDL::ExceptionOr> RSAPSS::sign(AlgorithmParams const&, GC::Ref key, ByteBuffer const&) +{ + auto& realm = *m_realm; + + // 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(realm, "Key is not a private key"_string); + + // 3. Perform the signature generation operation defined in Section 8.1 of [RFC3447] with the key represented by the [[handle]] internal slot + // of key as the signer's private key, K, and the contents of message as the message to be signed, M, and using the hash function specified + // by the hash attribute of the [[algorithm]] internal slot of key as the Hash option, MGF1 (defined in Section B.2.1 of [RFC3447]) + // as the MGF option and the saltLength member of normalizedAlgorithm as the salt length option for the EMSA-PSS-ENCODE operation. + // 4. If performing the operation results in an error, then throw an OperationError. + // 5. Let signature be the signature, S, that results from performing the operation. + // 6. Return signature. + return WebIDL::OperationError::create(realm, "Not implemented"_string); +} + +// https://w3c.github.io/webcrypto/#rsa-pss-registration +WebIDL::ExceptionOr RSAPSS::verify(AlgorithmParams const&, GC::Ref key, ByteBuffer const&, ByteBuffer const&) +{ + auto& realm = *m_realm; + + // 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(realm, "Key is not a public key"_string); + + return WebIDL::OperationError::create(realm, "Not implemented"_string); +} + +// https://w3c.github.io/webcrypto/#rsa-pss-registration +WebIDL::ExceptionOr> RSAPSS::import_key(AlgorithmParams const& params, Bindings::KeyFormat key_format, CryptoKey::InternalKeyData key_data, bool extractable, Vector const& usages) +{ + auto& realm = *m_realm; + + // 1. Let keyData be the key data to be imported. + + GC::Ptr key = nullptr; + auto const& normalized_algorithm = static_cast(params); + + // 2. -> If format is "spki": + if (key_format == Bindings::KeyFormat::Spki) { + // 1. If usages contains an entry which is not "verify" then throw a SyntaxError. + for (auto const& usage : usages) { + if (usage != Bindings::KeyUsage::Verify) { + return WebIDL::SyntaxError::create(m_realm, MUST(String::formatted("Invalid key usage '{}'", idl_enum_to_string(usage)))); + } + } + + VERIFY(key_data.has()); + + // 2. Let spki be the result of running the parse a subjectPublicKeyInfo algorithm over keyData. + // 3. If an error occurred while parsing, then throw a DataError. + auto spki = TRY(parse_a_subject_public_key_info(m_realm, key_data.get())); + + // 4. If the algorithm object identifier field of the algorithm AlgorithmIdentifier field of spki + // is not equal to the rsaEncryption object identifier defined in [RFC3447], then throw a DataError. + if (spki.algorithm.identifier != ::Crypto::ASN1::rsa_encryption_oid) + return WebIDL::DataError::create(m_realm, "Algorithm object identifier is not the rsaEncryption object identifier"_string); + + // 5. Let publicKey be the result of performing the parse an ASN.1 structure algorithm, + // with data as the subjectPublicKeyInfo field of spki, structure as the RSAPublicKey structure + // specified in Section A.1.1 of [RFC3447], and exactData set to true. + // NOTE: We already did this in parse_a_subject_public_key_info + auto& public_key = spki.rsa; + + // 6. If an error occurred while parsing, or it can be determined that publicKey is not + // a valid public key according to [RFC3447], then throw a DataError. + // FIXME: Validate the public key + + // 7. Let key be a new CryptoKey that represents the RSA public key identified by publicKey. + key = CryptoKey::create(m_realm, CryptoKey::InternalKeyData { public_key }); + + // 8. Set the [[type]] internal slot of key to "public" + key->set_type(Bindings::KeyType::Public); + } + + // -> If format is "pkcs8": + else if (key_format == Bindings::KeyFormat::Pkcs8) { + // 1. If usages contains an entry which is not "sign" then throw a SyntaxError. + for (auto const& usage : usages) { + if (usage != Bindings::KeyUsage::Sign) { + return WebIDL::SyntaxError::create(m_realm, MUST(String::formatted("Invalid key usage '{}'", idl_enum_to_string(usage)))); + } + } + + VERIFY(key_data.has()); + + // 2. Let privateKeyInfo be the result of running the parse a privateKeyInfo algorithm over keyData. + // 3. If an error occurred while parsing, then throw a DataError. + auto private_key_info = TRY(parse_a_private_key_info(m_realm, key_data.get())); + + // 4. If the algorithm object identifier field of the privateKeyAlgorithm PrivateKeyAlgorithm field of privateKeyInfo + // is not equal to the rsaEncryption object identifier defined in [RFC3447], then throw a DataError. + if (private_key_info.algorithm.identifier != ::Crypto::ASN1::rsa_encryption_oid) + return WebIDL::DataError::create(m_realm, "Algorithm object identifier is not the rsaEncryption object identifier"_string); + + // 5. Let rsaPrivateKey be the result of performing the parse an ASN.1 structure algorithm, + // with data as the privateKey field of privateKeyInfo, structure as the RSAPrivateKey structure + // specified in Section A.1.2 of [RFC3447], and exactData set to true. + // NOTE: We already did this in parse_a_private_key_info + auto& rsa_private_key = private_key_info.rsa; + + // 6. If an error occurred while parsing, or if rsaPrivateKey is not + // a valid RSA private key according to [RFC3447], then throw a DataError. + // FIXME: Validate the private key + + // 7. Let key be a new CryptoKey that represents the RSA private key identified by rsaPrivateKey. + key = CryptoKey::create(m_realm, CryptoKey::InternalKeyData { rsa_private_key }); + + // 8. Set the [[type]] internal slot of key to "private" + key->set_type(Bindings::KeyType::Private); + } + + // -> If format is "jwk": + else if (key_format == Bindings::KeyFormat::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 d field of jwk is present and usages contains an entry which is not "sign", or, + // if the d field of jwk is not present and usages contains an entry which is not "verify" + // then throw a SyntaxError. + if (jwk.d.has_value()) { + for (auto const& usage : usages) { + if (usage != Bindings::KeyUsage::Sign) { + return WebIDL::SyntaxError::create(m_realm, MUST(String::formatted("Invalid key usage '{}'", Bindings::idl_enum_to_string(usage)))); + } + } + } else { + for (auto const& usage : usages) { + if (usage != Bindings::KeyUsage::Verify) { + return WebIDL::SyntaxError::create(m_realm, MUST(String::formatted("Invalid key usage '{}'", Bindings::idl_enum_to_string(usage)))); + } + } + } + + // 3. If the kty field of jwk is not a case-sensitive string match to "RSA", then throw a DataError. + if (jwk.kty != "RSA"_string) + return WebIDL::DataError::create(m_realm, "Invalid key type"_string); + + // 4. If usages is non-empty and the use field of jwk is present and is not a case-sensitive string match to "sig", then throw a DataError. + if (!usages.is_empty() && jwk.use.has_value() && *jwk.use != "sig"_string) + return WebIDL::DataError::create(m_realm, "Invalid use field"_string); + + // 5. 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. + TRY(validate_jwk_key_ops(realm, jwk, usages)); + + // 6. 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); + + Optional hash = {}; + // 7. -> If the alg field of jwk is not present: + if (!jwk.alg.has_value()) { + // Let hash be undefined. + } + // -> If the alg field of jwk is equal to "PS1": + else if (jwk.alg == "PS1"sv) { + // Let hash be the string "SHA-1". + hash = "SHA-1"_string; + } + // -> If the alg field of jwk is equal to "PS256": + else if (jwk.alg == "PS256"sv) { + // Let hash be the string "SHA-256". + hash = "SHA-256"_string; + } + // -> If the alg field of jwk is equal to "PS384": + else if (jwk.alg == "PS384"sv) { + // Let hash be the string "SHA-384". + hash = "SHA-384"_string; + } + // -> If the alg field of jwk is equal to "PS512": + else if (jwk.alg == "PS512"sv) { + // Let hash be the string "SHA-512". + hash = "SHA-512"_string; + } + // -> Otherwise: + else { + // FIXME: Support 'other applicable specifications' + // 1. Perform any key import steps defined by other applicable specifications, passing format, jwk and obtaining hash. + // 2. If an error occurred or there are no applicable specifications, throw a DataError. + return WebIDL::DataError::create(m_realm, "Invalid alg field"_string); + } + + // 8. If hash is not undefined: + if (hash.has_value()) { + // 1. Let normalizedHash be the result of normalize an algorithm with alg set to hash and op set to digest. + auto normalized_hash = TRY(normalize_an_algorithm(m_realm, AlgorithmIdentifier { *hash }, "digest"_string)); + + // 2. If normalizedHash is not equal to the hash member of normalizedAlgorithm, throw a DataError. + if (normalized_hash.parameter->name != TRY(normalized_algorithm.hash.name(realm.vm()))) + return WebIDL::DataError::create(m_realm, "Invalid hash"_string); + } + + // 9. -> If the d field of jwk is present: + if (jwk.d.has_value()) { + // 1. If jwk does not meet the requirements of Section 6.3.2 of JSON Web Algorithms [JWA], then throw a DataError. + bool meets_requirements = jwk.e.has_value() && jwk.n.has_value() && jwk.d.has_value(); + if (jwk.p.has_value() || jwk.q.has_value() || jwk.dp.has_value() || jwk.dq.has_value() || jwk.qi.has_value()) + meets_requirements |= jwk.p.has_value() && jwk.q.has_value() && jwk.dp.has_value() && jwk.dq.has_value() && jwk.qi.has_value(); + + if (jwk.oth.has_value()) { + // FIXME: We don't support > 2 primes in RSA keys + meets_requirements = false; + } + + if (!meets_requirements) + return WebIDL::DataError::create(m_realm, "Invalid JWK private key"_string); + + // FIXME: Spec error, it should say 'the RSA private key identified by interpreting jwk according to section 6.3.2' + // 2. Let privateKey represent the RSA public key identified by interpreting jwk according to Section 6.3.1 of JSON Web Algorithms [JWA]. + auto private_key = TRY(parse_jwk_rsa_private_key(realm, jwk)); + + // FIXME: Spec error, it should say 'not to be a valid RSA private key' + // 3. If privateKey can be determined to not be a valid RSA public key according to [RFC3447], then throw a DataError. + // FIXME: Validate the private key + + // 4. Let key be a new CryptoKey representing privateKey. + key = CryptoKey::create(m_realm, CryptoKey::InternalKeyData { private_key }); + + // 5. Set the [[type]] internal slot of key to "private" + key->set_type(Bindings::KeyType::Private); + } + + // -> Otherwise: + else { + // 1. If jwk does not meet the requirements of Section 6.3.1 of JSON Web Algorithms [JWA], then throw a DataError. + if (!jwk.e.has_value() || !jwk.n.has_value()) + return WebIDL::DataError::create(m_realm, "Invalid JWK public key"_string); + + // 2. Let publicKey represent the RSA public key identified by interpreting jwk according to Section 6.3.1 of JSON Web Algorithms [JWA]. + auto public_key = TRY(parse_jwk_rsa_public_key(realm, jwk)); + + // 3. If publicKey can be determined to not be a valid RSA public key according to [RFC3447], then throw a DataError. + // FIXME: Validate the public key + + // 4. Let key be a new CryptoKey representing publicKey. + key = CryptoKey::create(m_realm, CryptoKey::InternalKeyData { public_key }); + + // 5. Set the [[type]] internal slot of key to "public" + key->set_type(Bindings::KeyType::Public); + } + } + + // -> Otherwise: throw a NotSupportedError. + else { + return WebIDL::NotSupportedError::create(m_realm, "Unsupported key format"_string); + } + + // 3. Let algorithm be a new RsaHashedKeyAlgorithm. + auto algorithm = RsaHashedKeyAlgorithm::create(m_realm); + + // 4. Set the name attribute of algorithm to "RSA-PSS" + algorithm->set_name("RSA-PSS"_string); + + // 5. Set the modulusLength attribute of algorithm to the length, in bits, of the RSA public modulus. + // 6. Set the publicExponent attribute of algorithm to the BigInteger representation of the RSA public exponent. + TRY(key->handle().visit( + [&](::Crypto::PK::RSAPublicKey<> const& public_key) -> WebIDL::ExceptionOr { + algorithm->set_modulus_length(public_key.modulus().trimmed_byte_length() * 8); + TRY(algorithm->set_public_exponent(public_key.public_exponent())); + return {}; + }, + [&](::Crypto::PK::RSAPrivateKey<> const& private_key) -> WebIDL::ExceptionOr { + algorithm->set_modulus_length(private_key.modulus().trimmed_byte_length() * 8); + TRY(algorithm->set_public_exponent(private_key.public_exponent())); + return {}; + }, + [](auto) -> WebIDL::ExceptionOr { VERIFY_NOT_REACHED(); })); + + // 7. Set the hash attribute of algorithm to the hash member of normalizedAlgorithm. + algorithm->set_hash(normalized_algorithm.hash); + + // 8. Set the [[algorithm]] internal slot of key to algorithm + key->set_algorithm(algorithm); + + // 9. Return key. + return GC::Ref { *key }; +} + +// https://w3c.github.io/webcrypto/#rsa-pss-registration +WebIDL::ExceptionOr> RSAPSS::export_key(Bindings::KeyFormat format, GC::Ref key) +{ + auto& realm = *m_realm; + auto& vm = realm.vm(); + + // 1. Let key be the key 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(); + + GC::Ptr 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(realm, "Key is not public"_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 field to the OID rsaEncryption defined in [RFC3447]. + // - Set the params field to the ASN.1 type NULL. + // - Set the subjectPublicKey field to the result of DER-encoding an RSAPublicKey ASN.1 type, as defined in [RFC3447], Appendix A.1.1, + // that represents the RSA public key represented by the [[handle]] internal slot of key + auto maybe_data = handle.visit( + [&](::Crypto::PK::RSAPublicKey<> const& public_key) -> ErrorOr { + return TRY(::Crypto::PK::wrap_in_subject_public_key_info(public_key, ::Crypto::ASN1::rsa_encryption_oid, nullptr)); + }, + [](auto) -> ErrorOr { + VERIFY_NOT_REACHED(); + }); + // FIXME: clang-format butchers the visit if we do the TRY inline + auto data = TRY_OR_THROW_OOM(vm, maybe_data); + + // 3. Let result be the result of creating an ArrayBuffer containing data. + result = JS::ArrayBuffer::create(realm, data); + } + + // 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(realm, "Key is not private"_string); + + // 2. Let data be the result of encoding a privateKeyInfo structure with the following properties: + // - Set the version field to 0. + // - Set the privateKeyAlgorithm field to an PrivateKeyAlgorithmIdentifier ASN.1 type with the following properties: + // - - Set the algorithm field to the OID rsaEncryption defined in [RFC3447]. + // - - Set the params field to the ASN.1 type NULL. + // - Set the privateKey field to the result of DER-encoding an RSAPrivateKey ASN.1 type, as defined in [RFC3447], Appendix A.1.2, + // that represents the RSA private key represented by the [[handle]] internal slot of key + auto maybe_data = handle.visit( + [&](::Crypto::PK::RSAPrivateKey<> const& private_key) -> ErrorOr { + return TRY(::Crypto::PK::wrap_in_private_key_info(private_key, ::Crypto::ASN1::rsa_encryption_oid, nullptr)); + }, + [](auto) -> ErrorOr { + VERIFY_NOT_REACHED(); + }); + + // FIXME: clang-format butchers the visit if we do the TRY inline + auto data = TRY_OR_THROW_OOM(vm, maybe_data); + + // 3. Let result be the result of creating an ArrayBuffer containing data. + result = JS::ArrayBuffer::create(realm, data); + } + + // If format is "jwk" + else if (format == Bindings::KeyFormat::Jwk) { + // 1. Let jwk be a new JsonWebKey dictionary. + Bindings::JsonWebKey jwk = {}; + + // 2. Set the kty attribute of jwk to the string "RSA". + jwk.kty = "RSA"_string; + + // 3. Let hash be the name attribute of the hash attribute of the [[algorithm]] internal slot of key. + auto hash = TRY(verify_cast(*key->algorithm()).hash().name(vm)); + + // 4. If hash is "SHA-1": + // - Set the alg attribute of jwk to the string "PS1". + if (hash == "SHA-1"sv) { + jwk.alg = "PS1"_string; + } + // If hash is "SHA-256": + // - Set the alg attribute of jwk to the string "PS256". + else if (hash == "SHA-256"sv) { + jwk.alg = "PS256"_string; + } + // If hash is "SHA-384": + // - Set the alg attribute of jwk to the string "PS384". + else if (hash == "SHA-384"sv) { + jwk.alg = "PS384"_string; + } + // If hash is "SHA-512": + // - Set the alg attribute of jwk to the string "PS512". + else if (hash == "SHA-512"sv) { + jwk.alg = "PS512"_string; + } else { + // FIXME: Support 'other applicable specifications' + // - Perform any key export steps defined by other applicable specifications, + // passing format and the hash attribute of the [[algorithm]] internal slot of key and obtaining alg. + // - Set the alg attribute of jwk to alg. + return WebIDL::NotSupportedError::create(realm, TRY_OR_THROW_OOM(vm, String::formatted("Unsupported hash algorithm '{}'", hash))); + } + + // 5. Set the attributes n and e of jwk according to the corresponding definitions in JSON Web Algorithms [JWA], Section 6.3.1. + auto maybe_error = handle.visit( + [&](::Crypto::PK::RSAPublicKey<> const& public_key) -> ErrorOr { + jwk.n = TRY(base64_url_uint_encode(public_key.modulus())); + jwk.e = TRY(base64_url_uint_encode(public_key.public_exponent())); + return {}; + }, + // 6. If the [[type]] internal slot of key is "private": + [&](::Crypto::PK::RSAPrivateKey<> const& private_key) -> ErrorOr { + jwk.n = TRY(base64_url_uint_encode(private_key.modulus())); + jwk.e = TRY(base64_url_uint_encode(private_key.public_exponent())); + + // 1. Set the attributes named d, p, q, dp, dq, and qi of jwk according to the corresponding definitions + // in JSON Web Algorithms [JWA], Section 6.3.2. + jwk.d = TRY(base64_url_uint_encode(private_key.private_exponent())); + jwk.p = TRY(base64_url_uint_encode(private_key.prime1())); + jwk.q = TRY(base64_url_uint_encode(private_key.prime2())); + jwk.dp = TRY(base64_url_uint_encode(private_key.exponent1())); + jwk.dq = TRY(base64_url_uint_encode(private_key.exponent2())); + jwk.qi = TRY(base64_url_uint_encode(private_key.coefficient())); + + // 2. If the underlying RSA private key represented by the [[handle]] internal slot of key is represented by more than two primes, + // set the attribute named oth of jwk according to the corresponding definition in JSON Web Algorithms [JWA], Section 6.3.2.7 + // FIXME: We don't support more than 2 primes on RSA keys + return {}; + }, + [](auto) -> ErrorOr { + VERIFY_NOT_REACHED(); + }); + // FIXME: clang-format butchers the visit if we do the TRY inline + TRY_OR_THROW_OOM(vm, maybe_error); + + // 7. Set the key_ops attribute of jwk to the usages attribute of key. + jwk.key_ops = Vector {}; + jwk.key_ops->ensure_capacity(key->internal_usages().size()); + for (auto const& usage : key->internal_usages()) { + jwk.key_ops->append(Bindings::idl_enum_to_string(usage)); + } + + // 8. Set the ext attribute of jwk to the [[extractable]] internal slot of key. + jwk.ext = key->extractable(); + + // 9. Let result be the result of converting jwk to an ECMAScript Object, as defined by [WebIDL]. + result = TRY(jwk.to_object(realm)); + } + + // Otherwise throw a NotSupportedError. + else { + return WebIDL::NotSupportedError::create(realm, TRY_OR_THROW_OOM(vm, String::formatted("Exporting to format {} is not supported", Bindings::idl_enum_to_string(format)))); + } + + // 8. Return result + return GC::Ref { *result }; +} + // https://w3c.github.io/webcrypto/#aes-cbc-operations WebIDL::ExceptionOr> AesCbc::encrypt(AlgorithmParams const& params, GC::Ref key, ByteBuffer const& plaintext) { diff --git a/Libraries/LibWeb/Crypto/CryptoAlgorithms.h b/Libraries/LibWeb/Crypto/CryptoAlgorithms.h index 24dc9eed4f398..437af69acaa24 100644 --- a/Libraries/LibWeb/Crypto/CryptoAlgorithms.h +++ b/Libraries/LibWeb/Crypto/CryptoAlgorithms.h @@ -377,6 +377,25 @@ class RSAOAEP : public AlgorithmMethods { } }; +class RSAPSS : public AlgorithmMethods { +public: + virtual WebIDL::ExceptionOr> sign(AlgorithmParams const&, GC::Ref, ByteBuffer const&) override; + virtual WebIDL::ExceptionOr verify(AlgorithmParams const&, GC::Ref, ByteBuffer const&, ByteBuffer const&) override; + + virtual WebIDL::ExceptionOr, GC::Ref>> 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, GC::Ref) override; + + static NonnullOwnPtr create(JS::Realm& realm) { return adopt_own(*new RSAPSS(realm)); } + +private: + explicit RSAPSS(JS::Realm& realm) + : AlgorithmMethods(realm) + { + } +}; + class AesCbc : public AlgorithmMethods { public: virtual WebIDL::ExceptionOr> encrypt(AlgorithmParams const&, GC::Ref, ByteBuffer const&) override; diff --git a/Libraries/LibWeb/Crypto/SubtleCrypto.cpp b/Libraries/LibWeb/Crypto/SubtleCrypto.cpp index 112bdafc151b6..4d4d47082b60b 100644 --- a/Libraries/LibWeb/Crypto/SubtleCrypto.cpp +++ b/Libraries/LibWeb/Crypto/SubtleCrypto.cpp @@ -1079,11 +1079,11 @@ SupportedAlgorithmsMap const& supported_algorithms() // FIXME: define_an_algorithm("exportKey"_string, "RSASSA-PKCS1-v1_5"_string); // https://w3c.github.io/webcrypto/#rsa-pss-registration - // FIXME: define_an_algorithm("sign"_string, "RSA-PSS"_string); - // FIXME: define_an_algorithm("verify"_string, "RSA-PSS"_string); - // FIXME: define_an_algorithm("generateKey"_string, "RSA-PSS"_string); - // FIXME: define_an_algorithm("importKey"_string, "RSA-PSS"_string); - // FIXME: define_an_algorithm("exportKey"_string, "RSA-PSS"_string); + define_an_algorithm("sign"_string, "RSA-PSS"_string); + define_an_algorithm("verify"_string, "RSA-PSS"_string); + define_an_algorithm("generateKey"_string, "RSA-PSS"_string); + define_an_algorithm("importKey"_string, "RSA-PSS"_string); + define_an_algorithm("exportKey"_string, "RSA-PSS"_string); // https://w3c.github.io/webcrypto/#rsa-oaep-registration define_an_algorithm("encrypt"_string, "RSA-OAEP"_string);