diff --git a/Libraries/LibCrypto/PK/RSA.cpp b/Libraries/LibCrypto/PK/RSA.cpp index 4ef8fe755a9..98454ddf11c 100644 --- a/Libraries/LibCrypto/PK/RSA.cpp +++ b/Libraries/LibCrypto/PK/RSA.cpp @@ -172,7 +172,7 @@ ErrorOr RSA::generate_key_pair(size_t bits, UnsignedBigInteger OPENSSL_TRY(OSSL_PARAM_BLD_push_BN(params_bld, openssl_name, param##_bn.ptr())); \ } -ErrorOr RSA::public_key_to_openssl_pkey(PublicKeyType const& public_key) +static ErrorOr public_key_to_openssl_pkey(RSAPublicKey const& public_key) { auto ctx = TRY(OpenSSL_PKEY_CTX::wrap(EVP_PKEY_CTX_new_from_name(nullptr, "RSA", nullptr))); @@ -193,7 +193,7 @@ ErrorOr RSA::public_key_to_openssl_pkey(PublicKeyType const& publi return key; } -ErrorOr RSA::private_key_to_openssl_pkey(PrivateKeyType const& private_key) +static ErrorOr private_key_to_openssl_pkey(RSAPrivateKey const& private_key) { auto ctx = TRY(OpenSSL_PKEY_CTX::wrap(EVP_PKEY_CTX_new_from_name(nullptr, "RSA", nullptr))); @@ -222,6 +222,125 @@ ErrorOr RSA::private_key_to_openssl_pkey(PrivateKeyType const& pri #undef OPENSSL_SET_KEY_PARAM_NOT_ZERO +// https://www.rfc-editor.org/rfc/rfc3447.html#section-3.1 +ErrorOr RSAPublicKey::is_valid() const +{ + // In a valid RSA public key, the RSA modulus n is a product of u + // distinct odd primes r_i, i = 1, 2, ..., u, where u >= 2, and the RSA + // public exponent e is an integer between 3 and n - 1 satisfying GCD(e, + // \lambda(n)) = 1, where \lambda(n) = LCM(r_1 - 1, ..., r_u - 1). + + if (!m_public_exponent.is_odd()) + return false; + + if (m_public_exponent < 3 || m_public_exponent >= m_modulus) + return false; + + return true; +} + +// https://www.rfc-editor.org/rfc/rfc3447.html#section-3.2 +ErrorOr RSAPrivateKey::is_valid() const +{ + if (!m_public_exponent.is_odd()) + return false; + + if (m_public_exponent < 3 || m_public_exponent >= m_modulus) + return false; + + if (!m_prime_1.is_zero() && !m_prime_2.is_zero() && !m_exponent_1.is_zero() && !m_exponent_2.is_zero() && !m_coefficient.is_zero()) { + // In a valid RSA private key with the second representation, the two + // factors p and q are the first two prime factors of the RSA modulus n + // (i.e., r_1 and r_2), the CRT exponents dP and dQ are positive + // integers less than p and q respectively satisfying + // e * dP == 1 (mod (p-1)) + // e * dQ == 1 (mod (q-1)) , + // and the CRT coefficient qInv is a positive integer less than p + // satisfying + // q * qInv == 1 (mod p). + // If u > 2, the representation will include one or more triplets (r_i, + // d_i, t_i), i = 3, ..., u. The factors r_i are the additional prime + // factors of the RSA modulus n. Each CRT exponent d_i (i = 3, ..., u) + // satisfies + // e * d_i == 1 (mod (r_i - 1)). + // Each CRT coefficient t_i (i = 3, ..., u) is a positive integer less + // than r_i satisfying + // R_i * t_i == 1 (mod r_i) , + // where R_i = r_1 * r_2 * ... * r_(i-1). + + if (m_exponent_1 >= m_prime_1 || m_exponent_2 >= m_prime_2 || m_coefficient >= m_prime_1) + return false; + + if (m_prime_1.multiplied_by(m_prime_2) != m_modulus) + return false; + + auto tmp_bn = TRY(OpenSSL_BN::create()); + + auto e = TRY(unsigned_big_integer_to_openssl_bignum(m_public_exponent)), + p = TRY(unsigned_big_integer_to_openssl_bignum(m_prime_1)), + q = TRY(unsigned_big_integer_to_openssl_bignum(m_prime_2)); + + auto dp = TRY(unsigned_big_integer_to_openssl_bignum(m_exponent_1)), + dq = TRY(unsigned_big_integer_to_openssl_bignum(m_exponent_2)); + + auto* bn_ctx = OPENSSL_TRY_PTR(BN_CTX_new()); + ScopeGuard const free_bn_ctx = [&] { BN_CTX_free(bn_ctx); }; + + auto p1 = TRY(OpenSSL_BN::create()); + OPENSSL_TRY(BN_sub(p1.ptr(), p.ptr(), BN_value_one())); + + OPENSSL_TRY(BN_mod_mul(tmp_bn.ptr(), e.ptr(), dp.ptr(), p1.ptr(), bn_ctx)); + if (!BN_is_one(tmp_bn.ptr())) + return false; + + auto q1 = TRY(OpenSSL_BN::create()); + OPENSSL_TRY(BN_sub(q1.ptr(), q.ptr(), BN_value_one())); + + OPENSSL_TRY(BN_mod_mul(tmp_bn.ptr(), e.ptr(), dq.ptr(), q1.ptr(), bn_ctx)); + if (!BN_is_one(tmp_bn.ptr())) + return false; + + auto q_inv = TRY(unsigned_big_integer_to_openssl_bignum(m_coefficient)); + OPENSSL_TRY(BN_mod_mul(tmp_bn.ptr(), q.ptr(), q_inv.ptr(), p.ptr(), bn_ctx)); + if (!BN_is_one(tmp_bn.ptr())) + return false; + + if (!m_private_exponent.is_zero()) { + if (m_private_exponent >= m_modulus) + return false; + + auto lambda = TRY(m_prime_1.minus(1)).lcm(TRY(m_prime_2.minus(1))); + auto lambda_bn = TRY(unsigned_big_integer_to_openssl_bignum(lambda)); + + auto d = TRY(unsigned_big_integer_to_openssl_bignum(m_private_exponent)); + + OPENSSL_TRY(BN_mod_mul(tmp_bn.ptr(), d.ptr(), e.ptr(), lambda_bn.ptr(), bn_ctx)); + if (!BN_is_one(tmp_bn.ptr())) + return false; + } + + return true; + } + + if (!m_modulus.is_zero() && !m_private_exponent.is_zero()) { + // In a valid RSA private key with the first representation, the RSA + // modulus n is the same as in the corresponding RSA public key and is + // the product of u distinct odd primes r_i, i = 1, 2, ..., u, where u + // >= 2. The RSA private exponent d is a positive integer less than n + // satisfying + // e * d == 1 (mod \lambda(n)), + // where e is the corresponding RSA public exponent and \lambda(n) is + // defined as in Section 3.1. + + if (m_private_exponent >= m_modulus) + return false; + + return true; + } + + return false; +} + ErrorOr RSA::configure(OpenSSL_PKEY_CTX& ctx) { OPENSSL_TRY(EVP_PKEY_CTX_set_rsa_padding(ctx.ptr(), RSA_NO_PADDING)); diff --git a/Libraries/LibCrypto/PK/RSA.h b/Libraries/LibCrypto/PK/RSA.h index 93f72b29319..10c7bb67ee0 100644 --- a/Libraries/LibCrypto/PK/RSA.h +++ b/Libraries/LibCrypto/PK/RSA.h @@ -35,6 +35,8 @@ public: UnsignedBigInteger const& public_exponent() const { return m_public_exponent; } size_t length() const { return m_length; } + ErrorOr is_valid() const; + ErrorOr export_as_der() const { ASN1::Encoder encoder; @@ -88,6 +90,8 @@ public: UnsignedBigInteger const& coefficient() const { return m_coefficient; } size_t length() const { return m_length; } + ErrorOr is_valid() const; + ErrorOr export_as_der() const { if (m_prime_1.is_zero() || m_prime_2.is_zero()) { @@ -196,9 +200,6 @@ public: protected: virtual ErrorOr configure(OpenSSL_PKEY_CTX& ctx); - - static ErrorOr public_key_to_openssl_pkey(PublicKeyType const& public_key); - static ErrorOr private_key_to_openssl_pkey(PrivateKeyType const& private_key); }; ErrorOr hash_kind_to_hash_type(Hash::HashKind hash_kind); diff --git a/Libraries/LibWeb/Crypto/CryptoAlgorithms.cpp b/Libraries/LibWeb/Crypto/CryptoAlgorithms.cpp index d255ed1a3af..e72a32dd0cb 100644 --- a/Libraries/LibWeb/Crypto/CryptoAlgorithms.cpp +++ b/Libraries/LibWeb/Crypto/CryptoAlgorithms.cpp @@ -837,7 +837,11 @@ WebIDL::ExceptionOr> RSAOAEP::import_key(Web::Crypto::Algorit // 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 + auto maybe_valid = public_key.is_valid(); + if (maybe_valid.is_error()) + return WebIDL::DataError::create(m_realm, "Failed to verify key"_string); + if (!maybe_valid.value()) + return WebIDL::DataError::create(m_realm, "Invalid key"_string); // 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 }); @@ -874,7 +878,11 @@ WebIDL::ExceptionOr> RSAOAEP::import_key(Web::Crypto::Algorit // 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 + auto maybe_valid = rsa_private_key.is_valid(); + if (maybe_valid.is_error()) + return WebIDL::DataError::create(m_realm, "Failed to verify key"_string); + if (!maybe_valid.value()) + return WebIDL::DataError::create(m_realm, "Invalid key"_string); // 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 }); @@ -989,7 +997,11 @@ WebIDL::ExceptionOr> RSAOAEP::import_key(Web::Crypto::Algorit auto private_key = TRY(parse_jwk_rsa_private_key(realm, jwk)); // 3. If privateKey can be determined to not be a valid RSA private key according to [RFC3447], then throw a DataError. - // FIXME: Validate the private key + auto maybe_valid = private_key.is_valid(); + if (maybe_valid.is_error()) + return WebIDL::DataError::create(m_realm, "Failed to verify key"_string); + if (!maybe_valid.value()) + return WebIDL::DataError::create(m_realm, "Invalid key"_string); // 4. Let key be a new CryptoKey representing privateKey. key = CryptoKey::create(m_realm, CryptoKey::InternalKeyData { private_key }); @@ -1008,7 +1020,11 @@ WebIDL::ExceptionOr> RSAOAEP::import_key(Web::Crypto::Algorit 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 + auto maybe_valid = public_key.is_valid(); + if (maybe_valid.is_error()) + return WebIDL::DataError::create(m_realm, "Failed to verify key"_string); + if (!maybe_valid.value()) + return WebIDL::DataError::create(m_realm, "Invalid key"_string); // 4. Let key be a new CryptoKey representing publicKey. key = CryptoKey::create(m_realm, CryptoKey::InternalKeyData { public_key }); @@ -1414,7 +1430,11 @@ WebIDL::ExceptionOr> RSAPSS::import_key(AlgorithmParams const // 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 + auto maybe_valid = public_key.is_valid(); + if (maybe_valid.is_error()) + return WebIDL::DataError::create(m_realm, "Failed to verify key"_string); + if (!maybe_valid.value()) + return WebIDL::DataError::create(m_realm, "Invalid key"_string); // 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 }); @@ -1451,7 +1471,11 @@ WebIDL::ExceptionOr> RSAPSS::import_key(AlgorithmParams const // 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 + auto maybe_valid = rsa_private_key.is_valid(); + if (maybe_valid.is_error()) + return WebIDL::DataError::create(m_realm, "Failed to verify key"_string); + if (!maybe_valid.value()) + return WebIDL::DataError::create(m_realm, "Invalid key"_string); // 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 }); @@ -1567,7 +1591,11 @@ WebIDL::ExceptionOr> RSAPSS::import_key(AlgorithmParams const // 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 + auto maybe_valid = private_key.is_valid(); + if (maybe_valid.is_error()) + return WebIDL::DataError::create(m_realm, "Failed to verify key"_string); + if (!maybe_valid.value()) + return WebIDL::DataError::create(m_realm, "Invalid key"_string); // 4. Let key be a new CryptoKey representing privateKey. key = CryptoKey::create(m_realm, CryptoKey::InternalKeyData { private_key }); @@ -1586,7 +1614,11 @@ WebIDL::ExceptionOr> RSAPSS::import_key(AlgorithmParams const 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 + auto maybe_valid = public_key.is_valid(); + if (maybe_valid.is_error()) + return WebIDL::DataError::create(m_realm, "Failed to verify key"_string); + if (!maybe_valid.value()) + return WebIDL::DataError::create(m_realm, "Invalid key"_string); // 4. Let key be a new CryptoKey representing publicKey. key = CryptoKey::create(m_realm, CryptoKey::InternalKeyData { public_key }); @@ -1987,7 +2019,11 @@ WebIDL::ExceptionOr> RSASSAPKCS1::import_key(AlgorithmParams // 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 + auto maybe_valid = public_key.is_valid(); + if (maybe_valid.is_error()) + return WebIDL::DataError::create(m_realm, "Failed to verify key"_string); + if (!maybe_valid.value()) + return WebIDL::DataError::create(m_realm, "Invalid key"_string); // 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 }); @@ -2024,7 +2060,11 @@ WebIDL::ExceptionOr> RSASSAPKCS1::import_key(AlgorithmParams // 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 + auto maybe_valid = rsa_private_key.is_valid(); + if (maybe_valid.is_error()) + return WebIDL::DataError::create(m_realm, "Failed to verify key"_string); + if (!maybe_valid.value()) + return WebIDL::DataError::create(m_realm, "Invalid key"_string); // 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 }); @@ -2138,7 +2178,11 @@ WebIDL::ExceptionOr> RSASSAPKCS1::import_key(AlgorithmParams auto private_key = TRY(parse_jwk_rsa_private_key(realm, jwk)); // 3. If privateKey can be determined to not be a valid RSA private key according to [RFC3447], then throw a DataError. - // FIXME: Validate the private key + auto maybe_valid = private_key.is_valid(); + if (maybe_valid.is_error()) + return WebIDL::DataError::create(m_realm, "Failed to verify key"_string); + if (!maybe_valid.value()) + return WebIDL::DataError::create(m_realm, "Invalid key"_string); // 4. Let key be a new CryptoKey representing privateKey. key = CryptoKey::create(m_realm, CryptoKey::InternalKeyData { private_key }); @@ -2157,7 +2201,11 @@ WebIDL::ExceptionOr> RSASSAPKCS1::import_key(AlgorithmParams 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 + auto maybe_valid = public_key.is_valid(); + if (maybe_valid.is_error()) + return WebIDL::DataError::create(m_realm, "Failed to verify key"_string); + if (!maybe_valid.value()) + return WebIDL::DataError::create(m_realm, "Invalid key"_string); // 4. Let key be a new CryptoKey representing publicKey. key = CryptoKey::create(m_realm, CryptoKey::InternalKeyData { public_key }); diff --git a/Tests/LibWeb/Text/expected/Crypto/SubtleCrypto-rsa-bad-import-key.txt b/Tests/LibWeb/Text/expected/Crypto/SubtleCrypto-rsa-bad-import-key.txt new file mode 100644 index 00000000000..9dc3fd4d9c2 --- /dev/null +++ b/Tests/LibWeb/Text/expected/Crypto/SubtleCrypto-rsa-bad-import-key.txt @@ -0,0 +1,36 @@ +RSASSA-PKCS1-v1_5 SHA-1 PRIV - jwk OK: DataError: Invalid key +RSASSA-PKCS1-v1_5 SHA-1 PRIV - pkcs8 OK: DataError: Invalid key +RSASSA-PKCS1-v1_5 SHA-1 PRIV - pkcs8 OK: DataError: Invalid key +RSASSA-PKCS1-v1_5 SHA-256 PRIV - jwk OK: DataError: Invalid key +RSASSA-PKCS1-v1_5 SHA-256 PRIV - pkcs8 OK: DataError: Invalid key +RSASSA-PKCS1-v1_5 SHA-256 PRIV - pkcs8 OK: DataError: Invalid key +RSASSA-PKCS1-v1_5 SHA-384 PRIV - jwk OK: DataError: Invalid key +RSASSA-PKCS1-v1_5 SHA-384 PRIV - pkcs8 OK: DataError: Invalid key +RSASSA-PKCS1-v1_5 SHA-384 PRIV - pkcs8 OK: DataError: Invalid key +RSASSA-PKCS1-v1_5 SHA-512 PRIV - jwk OK: DataError: Invalid key +RSASSA-PKCS1-v1_5 SHA-512 PRIV - pkcs8 OK: DataError: Invalid key +RSASSA-PKCS1-v1_5 SHA-512 PRIV - pkcs8 OK: DataError: Invalid key +RSA-PSS SHA-1 PRIV - jwk OK: DataError: Invalid key +RSA-PSS SHA-1 PRIV - pkcs8 OK: DataError: Invalid key +RSA-PSS SHA-1 PRIV - pkcs8 OK: DataError: Invalid key +RSA-PSS SHA-256 PRIV - jwk OK: DataError: Invalid key +RSA-PSS SHA-256 PRIV - pkcs8 OK: DataError: Invalid key +RSA-PSS SHA-256 PRIV - pkcs8 OK: DataError: Invalid key +RSA-PSS SHA-384 PRIV - jwk OK: DataError: Invalid key +RSA-PSS SHA-384 PRIV - pkcs8 OK: DataError: Invalid key +RSA-PSS SHA-384 PRIV - pkcs8 OK: DataError: Invalid key +RSA-PSS SHA-512 PRIV - jwk OK: DataError: Invalid key +RSA-PSS SHA-512 PRIV - pkcs8 OK: DataError: Invalid key +RSA-PSS SHA-512 PRIV - pkcs8 OK: DataError: Invalid key +RSA-OAEP SHA-1 PRIV - jwk OK: DataError: Invalid key +RSA-OAEP SHA-1 PRIV - pkcs8 OK: DataError: Invalid key +RSA-OAEP SHA-1 PRIV - pkcs8 OK: DataError: Invalid key +RSA-OAEP SHA-256 PRIV - jwk OK: DataError: Invalid key +RSA-OAEP SHA-256 PRIV - pkcs8 OK: DataError: Invalid key +RSA-OAEP SHA-256 PRIV - pkcs8 OK: DataError: Invalid key +RSA-OAEP SHA-384 PRIV - jwk OK: DataError: Invalid key +RSA-OAEP SHA-384 PRIV - pkcs8 OK: DataError: Invalid key +RSA-OAEP SHA-384 PRIV - pkcs8 OK: DataError: Invalid key +RSA-OAEP SHA-512 PRIV - jwk OK: DataError: Invalid key +RSA-OAEP SHA-512 PRIV - pkcs8 OK: DataError: Invalid key +RSA-OAEP SHA-512 PRIV - pkcs8 OK: DataError: Invalid key diff --git a/Tests/LibWeb/Text/input/Crypto/SubtleCrypto-rsa-bad-import-key.html b/Tests/LibWeb/Text/input/Crypto/SubtleCrypto-rsa-bad-import-key.html new file mode 100644 index 00000000000..4ae19fbbe60 --- /dev/null +++ b/Tests/LibWeb/Text/input/Crypto/SubtleCrypto-rsa-bad-import-key.html @@ -0,0 +1,80 @@ + + +