From 080ada8bfb5bb37428ed1d647c5393e068fcb4cb Mon Sep 17 00:00:00 2001 From: Ruben Perez Date: Fri, 9 May 2025 16:16:22 +0200 Subject: [PATCH 01/68] Initial impl --- .../impl/internal/protocol/static_buffer.hpp | 6 + .../internal/sansio/caching_sha2_password.hpp | 139 +++++++++++++++++- .../mysql/impl/internal/sansio/handshake.hpp | 74 ++++++---- 3 files changed, 187 insertions(+), 32 deletions(-) diff --git a/include/boost/mysql/impl/internal/protocol/static_buffer.hpp b/include/boost/mysql/impl/internal/protocol/static_buffer.hpp index 0f8396adb..cca6423a1 100644 --- a/include/boost/mysql/impl/internal/protocol/static_buffer.hpp +++ b/include/boost/mysql/impl/internal/protocol/static_buffer.hpp @@ -53,6 +53,12 @@ class static_buffer } } + void resize(std::size_t new_size) + { + BOOST_ASSERT(new_size <= N); + size_ = new_size; + } + void clear() noexcept { size_ = 0; } }; diff --git a/include/boost/mysql/impl/internal/sansio/caching_sha2_password.hpp b/include/boost/mysql/impl/internal/sansio/caching_sha2_password.hpp index d7416b6b4..73f22707f 100644 --- a/include/boost/mysql/impl/internal/sansio/caching_sha2_password.hpp +++ b/include/boost/mysql/impl/internal/sansio/caching_sha2_password.hpp @@ -20,12 +20,23 @@ #include #include +#include +#include #include #include #include +#include #include +#include +#include +#include +#include +#include +#include +#include #include +#include // Reference: // https://dev.mysql.com/doc/dev/mysql-server/latest/page_caching_sha2_authentication_exchanges.html @@ -92,6 +103,90 @@ inline system::result> csha2p_hash_password( return res; } +// TODO: we may want to take this to a separate file? +struct bio_deleter +{ + void operator()(BIO* bio) const noexcept { BIO_free(bio); } +}; +using unique_bio = std::unique_ptr; + +struct evp_pkey_deleter +{ + void operator()(EVP_PKEY* pkey) const noexcept { EVP_PKEY_free(pkey); } +}; +using unique_evp_pkey = std::unique_ptr; + +struct evp_pkey_ctx_deleter +{ + void operator()(EVP_PKEY_CTX* ctx) const noexcept { EVP_PKEY_CTX_free(ctx); } +}; +using unique_evp_pkey_ctx = std::unique_ptr; + +inline error_code get_last_openssl_error() +{ + return error_code(::ERR_get_error(), asio::error::get_ssl_category()); // TODO: is this OK? +} + +using csha2p_password_buffer = container::small_vector; + +inline error_code csha2p_encrypt_password( + string_view password, + span challenge, + span server_key, + csha2p_password_buffer& output +) +{ + // TODO: test that these can really never happen + BOOST_ASSERT(!password.empty()); + BOOST_ASSERT(!challenge.empty()); + + // Try to parse the private key. TODO: size check here + unique_bio bio{BIO_new_mem_buf(server_key.data(), server_key.size())}; + if (!bio) + return get_last_openssl_error(); + unique_evp_pkey key(PEM_read_bio_PUBKEY(bio.get(), nullptr, nullptr, nullptr)); + if (!key) + return get_last_openssl_error(); + + // Salt the password, as a NULL-terminated string + csha2p_password_buffer salted_password(password.size() + 1u, 0); + for (std::size_t i = 0; i < password.size(); ++i) + salted_password[i] = password[i] ^ challenge[i % challenge.size()]; + + // Add the NULL terminator. It should be salted, too. Since 0 ^ U = U, + // the byte should be the challenge at the position we're in + salted_password[password.size()] = challenge[password.size() % challenge.size()]; + + // Set up the encryption context + unique_evp_pkey_ctx ctx(EVP_PKEY_CTX_new(key.get(), nullptr)); + if (!ctx) + return get_last_openssl_error(); + if (EVP_PKEY_encrypt_init(ctx.get()) <= 0) + return get_last_openssl_error(); + if (EVP_PKEY_CTX_set_rsa_padding(ctx.get(), RSA_PKCS1_OAEP_PADDING) <= 0) + return get_last_openssl_error(); + + // Encrypt + int max_size = EVP_PKEY_get_size(key.get()); + BOOST_ASSERT(max_size >= 0); + output.resize(max_size); + std::size_t actual_size = static_cast(max_size); + if (EVP_PKEY_encrypt( + ctx.get(), + output.data(), + &actual_size, + salted_password.data(), + salted_password.size() + ) <= 0) + { + return get_last_openssl_error(); + } + output.resize(actual_size); + + // Done + return error_code(); +} + class csha2p_algo { int resume_point_{0}; @@ -106,6 +201,24 @@ class csha2p_algo return server_data.size() == 1u && server_data[0] == 3; } + static next_action encrypt_password( + connection_state_data& st, + std::uint8_t& seqnum, + string_view password, + span challenge, + span server_key + ) + { + csha2p_password_buffer buff; + auto ec = csha2p_encrypt_password(password, challenge, server_key, buff); + if (ec) + return ec; + return st.write( + string_eof{string_view(reinterpret_cast(buff.data()), buff.size())}, + seqnum + ); + } + public: csha2p_algo() = default; @@ -113,6 +226,7 @@ class csha2p_algo connection_state_data& st, span server_data, string_view password, + span challenge, bool secure_channel, std::uint8_t& seqnum ) @@ -124,14 +238,29 @@ class csha2p_algo // or told us to read again because an OK packet or error packet is coming. if (is_perform_full_auth(server_data)) { - // At this point, we don't support full auth over insecure channels - if (!secure_channel) + if (secure_channel) { - return make_error_code(client_errc::auth_plugin_requires_ssl); + // We should send a packet with just the password, as a NULL-terminated string + BOOST_MYSQL_YIELD(resume_point_, 1, st.write(string_null{password}, seqnum)) + + // The server shouldn't send us any more packets + return error_code(client_errc::bad_handshake_packet_type); } + else + { + // Request the server's public key + BOOST_MYSQL_YIELD(resume_point_, 99, st.write(int1{2}, seqnum)) - // We should send a packet with just the password, as a NULL-terminated string - BOOST_MYSQL_YIELD(resume_point_, 1, st.write(string_null{password}, seqnum)) + // Encrypt the password with the key we were given + BOOST_MYSQL_YIELD( + resume_point_, + 100, + encrypt_password(st, seqnum, password, challenge, server_data) + ) + + // The server shouldn't send us any more packets + return error_code(client_errc::bad_handshake_packet_type); + } } else if (is_fast_auth_ok(server_data)) { diff --git a/include/boost/mysql/impl/internal/sansio/handshake.hpp b/include/boost/mysql/impl/internal/sansio/handshake.hpp index 54feb5e19..09119a3cc 100644 --- a/include/boost/mysql/impl/internal/sansio/handshake.hpp +++ b/include/boost/mysql/impl/internal/sansio/handshake.hpp @@ -30,6 +30,7 @@ #include #include +#include #include #include #include @@ -58,23 +59,18 @@ class any_authentication_plugin public: any_authentication_plugin() = default; - // Emplaces the plugin and computes the first authentication response by hashing the password - system::result> bootstrap_plugin( - string_view plugin_name, - string_view password, - span challenge - ) + error_code emplace_plugin(string_view plugin_name) { if (plugin_name == mnp_plugin_name) { type_ = type_t::mnp; - return mnp_hash_password(password, challenge); + return error_code(); } else if (plugin_name == csha2p_plugin_name) { type_ = type_t::csha2p; csha2p_ = csha2p_algo(); // Reset any leftover state, just in case - return csha2p_hash_password(password, challenge); + return error_code(); } else { @@ -82,10 +78,21 @@ class any_authentication_plugin } } + system::result> hash_password(string_view password, span challenge) + { + switch (type_) + { + case type_t::mnp: return mnp_hash_password(password, challenge); + case type_t::csha2p: return csha2p_hash_password(password, challenge); + default: BOOST_ASSERT(false); return client_errc::unknown_auth_plugin; // LCOV_EXCL_LINE + } + } + next_action resume( connection_state_data& st, boost::span server_data, string_view password, + boost::span challenge, bool secure_channel, std::uint8_t& seqnum ) @@ -95,7 +102,8 @@ class any_authentication_plugin case type_t::mnp: // This algorithm doesn't allow more data frames return error_code(client_errc::bad_handshake_packet_type); - case type_t::csha2p: return csha2p_.resume(st, server_data, password, secure_channel, seqnum); + case type_t::csha2p: + return csha2p_.resume(st, server_data, password, challenge, secure_channel, seqnum); default: BOOST_ASSERT(false); return next_action(client_errc::bad_handshake_packet_type); // LCOV_EXCL_LINE @@ -118,7 +126,7 @@ class handshake_algo int resume_point_{0}; handshake_params hparams_; any_authentication_plugin plugin_; - static_buffer<32> hashed_password_; + container::small_vector challenge_; // TODO: make this a static vector std::uint8_t sequence_number_{0}; bool secure_channel_{false}; @@ -219,18 +227,12 @@ class handshake_algo // If we're using SSL, mark the channel as secure secure_channel_ = secure_channel_ || has_capabilities(*negotiated_caps, capabilities::ssl); - // Emplace the authentication plugin and compute the first response - auto hashed_password = plugin_.bootstrap_plugin( - hello.auth_plugin_name, - hparams_.password(), - hello.auth_plugin_data - ); - if (hashed_password.has_error()) - return hashed_password.error(); + // Save the challenge for later + span auth_data(hello.auth_plugin_data); + challenge_.assign(auth_data.begin(), auth_data.end()); - // Save it for later - hashed_password_ = *hashed_password; - return error_code(); + // Emplace the authentication plugin + return plugin_.emplace_plugin(hello.auth_plugin_name); } // Response to that initial greeting @@ -243,24 +245,41 @@ class handshake_algo }; } - login_request compose_login_request(const connection_state_data& st) + next_action compose_login_request(connection_state_data& st) { - return login_request{ + // Hash the password + auto hashed_password = plugin_.hash_password(hparams_.password(), challenge_); + if (hashed_password.has_error()) + return hashed_password.error(); + + // Compose the message + login_request msg{ st.current_capabilities, static_cast(max_packet_size), hparams_.connection_collation(), hparams_.username(), - hashed_password_, + *hashed_password, hparams_.database(), plugin_.name(), }; + + // Serialize it + return st.write(msg, sequence_number_); } // Processes auth_switch and auth_more_data messages, and leaves the result in auth_resp_ next_action process_auth_switch(connection_state_data& st, auth_switch msg) { - // Emplace the authentication plugin and compute the first response - auto hashed_password = plugin_.bootstrap_plugin(msg.plugin_name, hparams_.password(), msg.auth_data); + // Emplace the new authentication plugin + auto ec = plugin_.emplace_plugin(msg.plugin_name); + if (ec) + return ec; + + // Store the challenge for later + challenge_.assign(msg.auth_data.begin(), msg.auth_data.end()); + + // Hash the password + auto hashed_password = plugin_.hash_password(hparams_.password(), msg.auth_data); if (hashed_password.has_error()) return hashed_password.error(); @@ -312,7 +331,7 @@ class handshake_algo } // Compose and send handshake response - BOOST_MYSQL_YIELD(resume_point_, 4, st.write(compose_login_request(st), sequence_number_)) + BOOST_MYSQL_YIELD(resume_point_, 4, compose_login_request(st)) // Receive the response BOOST_MYSQL_YIELD(resume_point_, 5, st.read(sequence_number_)) @@ -342,6 +361,7 @@ class handshake_algo st, resp.data.more_data, hparams_.password(), + challenge_, secure_channel_, sequence_number_ ); From a7a0b74cc38dad454e5c1e6c924836e68f0d4728 Mon Sep 17 00:00:00 2001 From: Ruben Perez Date: Fri, 9 May 2025 17:37:41 +0200 Subject: [PATCH 02/68] Fix integration tests --- test/integration/test/handshake.cpp | 35 ++++++++--------------------- 1 file changed, 9 insertions(+), 26 deletions(-) diff --git a/test/integration/test/handshake.cpp b/test/integration/test/handshake.cpp index 7700b9685..d85e2b917 100644 --- a/test/integration/test/handshake.cpp +++ b/test/integration/test/handshake.cpp @@ -165,10 +165,11 @@ BOOST_FIXTURE_TEST_CASE(tcp_connection_, tcp_connection_fixture) BOOST_AUTO_TEST_SUITE_END() // mysql_native_password -// caching_sha2_password. We acquire a named lock -// to avoid race conditions with other test runs +// caching_sha2_password +// https://dev.mysql.com/doc/refman/8.4/en/caching-sha2-pluggable-authentication.html +// The plugin has a server-wide cache that influences the message exchange. +// We acquire a named lock to avoid race conditions with other test runs // (which happens in b2 builds). -// The sha256 cache is shared between all clients. struct caching_sha2_lock : any_connection_fixture { caching_sha2_lock() @@ -216,7 +217,6 @@ static void clear_sha256_cache() fix.conn.async_execute("FLUSH PRIVILEGES", result, as_netresult).validate_no_error(); }; -// Cache hit means that we are sending the password hashed, so it is OK to not have SSL for this BOOST_DATA_TEST_CASE_F(any_connection_fixture, cache_hit, all_transports) { // Setup @@ -230,8 +230,7 @@ BOOST_DATA_TEST_CASE_F(any_connection_fixture, cache_hit, all_transports) check_ssl(conn, sample.expect_ssl); } -// Cache miss succeeds only if the underlying transport is secure -BOOST_DATA_TEST_CASE_F(any_connection_fixture, cache_miss_success, secure_transports) +BOOST_DATA_TEST_CASE_F(any_connection_fixture, cache_miss, all_transports) { // Setup connect_params params = sample.params; @@ -244,21 +243,7 @@ BOOST_DATA_TEST_CASE_F(any_connection_fixture, cache_miss_success, secure_transp check_ssl(conn, sample.expect_ssl); } -// A cache miss would force us send a plaintext password over a non-TLS connection, so we fail -BOOST_FIXTURE_TEST_CASE(cache_miss_error, any_connection_fixture) -{ - // Setup - connect_params params = connect_params_builder() - .ssl(ssl_mode::disable) - .credentials(regular_user, regular_passwd) - .build(); - clear_sha256_cache(); - - // Handshake fails - conn.async_connect(params, as_netresult).validate_error(client_errc::auth_plugin_requires_ssl); -} - -// Empty password users can log in regardless of the SSL usage or cache state +// The protocol behaves differently with empty passwords BOOST_DATA_TEST_CASE_F(any_connection_fixture, empty_password_cache_hit, all_transports) { // Setup @@ -287,9 +272,8 @@ BOOST_DATA_TEST_CASE_F(any_connection_fixture, empty_password_cache_miss, all_tr BOOST_FIXTURE_TEST_CASE(bad_password_cache_hit, any_connection_fixture) { - // Note: test over non-TLS would return "ssl required" auto params = connect_params_builder() - .ssl(ssl_mode::require) + .ssl(ssl_mode::disable) .credentials(regular_user, "bad_password") .build(); load_sha256_cache(regular_user, regular_passwd); @@ -299,9 +283,8 @@ BOOST_FIXTURE_TEST_CASE(bad_password_cache_hit, any_connection_fixture) BOOST_FIXTURE_TEST_CASE(bad_password_cache_miss, any_connection_fixture) { - // Note: test over non-TLS would return "ssl required" auto params = connect_params_builder() - .ssl(ssl_mode::require) + .ssl(ssl_mode::disable) .credentials(regular_user, "bad_password") .build(); clear_sha256_cache(); @@ -314,7 +297,7 @@ BOOST_FIXTURE_TEST_CASE(bad_db_cache_miss, any_connection_fixture) { // Setup auto params = connect_params_builder() - .ssl(ssl_mode::require) + .ssl(ssl_mode::disable) .credentials(regular_user, regular_passwd) .database("bad_db") .build(); From 0a3ee2783678ed9589c9f32d3408821dd04e50a4 Mon Sep 17 00:00:00 2001 From: Ruben Perez Date: Fri, 9 May 2025 17:40:23 +0200 Subject: [PATCH 03/68] simplify integration tests --- test/integration/test/handshake.cpp | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/test/integration/test/handshake.cpp b/test/integration/test/handshake.cpp index d85e2b917..e19ea7b00 100644 --- a/test/integration/test/handshake.cpp +++ b/test/integration/test/handshake.cpp @@ -65,10 +65,11 @@ struct transport_test_case }; std::ostream& operator<<(std::ostream& os, const transport_test_case& tc) { return os << tc.name; } -std::vector make_secure_transports() +std::vector make_all_transports() { std::vector res{ - {"tcp_ssl", connect_params_builder().ssl(ssl_mode::require).build(), true}, + {"tcp", connect_params_builder().ssl(ssl_mode::disable).build(), false}, + {"tcp_ssl", connect_params_builder().ssl(ssl_mode::require).build(), true }, }; #ifdef BOOST_ASIO_HAS_LOCAL_SOCKETS @@ -81,14 +82,6 @@ std::vector make_secure_transports() return res; } -std::vector make_all_transports() -{ - auto res = make_secure_transports(); - res.push_back({"tcp", connect_params_builder().ssl(ssl_mode::disable).build(), false}); - return res; -} - -auto secure_transports = make_secure_transports(); auto all_transports = make_all_transports(); // Check whether the connection is using SSL or not From 8804521386b021d87accd2b7ad4a87152f44a5fd Mon Sep 17 00:00:00 2001 From: Ruben Perez Date: Fri, 9 May 2025 18:07:46 +0200 Subject: [PATCH 04/68] Fixed scramble size --- .../internal/sansio/auth_plugin_common.hpp | 29 +++++++ .../internal/sansio/caching_sha2_password.hpp | 47 +++++----- .../mysql/impl/internal/sansio/handshake.hpp | 87 +++++++++++-------- .../internal/sansio/mysql_native_password.hpp | 41 ++++----- 4 files changed, 117 insertions(+), 87 deletions(-) create mode 100644 include/boost/mysql/impl/internal/sansio/auth_plugin_common.hpp diff --git a/include/boost/mysql/impl/internal/sansio/auth_plugin_common.hpp b/include/boost/mysql/impl/internal/sansio/auth_plugin_common.hpp new file mode 100644 index 000000000..9a06df2bc --- /dev/null +++ b/include/boost/mysql/impl/internal/sansio/auth_plugin_common.hpp @@ -0,0 +1,29 @@ +// +// Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BOOST_MYSQL_IMPL_INTERNAL_SANSIO_AUTH_PLUGIN_COMMON_HPP +#define BOOST_MYSQL_IMPL_INTERNAL_SANSIO_AUTH_PLUGIN_COMMON_HPP + +#include + +#include + +namespace boost { +namespace mysql { +namespace detail { + +// All scrambles in all the plugins we know have this size +BOOST_INLINE_CONSTEXPR std::size_t scramble_size = 20u; + +// Hashed passwords vary in size, but they all fit in a buffer like this +BOOST_INLINE_CONSTEXPR std::size_t max_hash_size = 32u; + +} // namespace detail +} // namespace mysql +} // namespace boost + +#endif diff --git a/include/boost/mysql/impl/internal/sansio/caching_sha2_password.hpp b/include/boost/mysql/impl/internal/sansio/caching_sha2_password.hpp index 73f22707f..79fa7abcb 100644 --- a/include/boost/mysql/impl/internal/sansio/caching_sha2_password.hpp +++ b/include/boost/mysql/impl/internal/sansio/caching_sha2_password.hpp @@ -18,6 +18,7 @@ #include #include #include +#include #include #include @@ -46,59 +47,53 @@ namespace mysql { namespace detail { // Constants -BOOST_INLINE_CONSTEXPR std::size_t csha2p_challenge_length = 20; -BOOST_INLINE_CONSTEXPR std::size_t csha2p_response_length = 32; +BOOST_INLINE_CONSTEXPR std::size_t csha2p_hash_size = 32; BOOST_INLINE_CONSTEXPR const char* csha2p_plugin_name = "caching_sha2_password"; +static_assert(csha2p_hash_size <= max_hash_size, ""); +static_assert(csha2p_hash_size == SHA256_DIGEST_LENGTH, "Buffer size mismatch"); inline void csha2p_hash_password_impl( string_view password, - span challenge, - span output + span scramble, + span output ) { - static_assert(csha2p_response_length == SHA256_DIGEST_LENGTH, "Buffer size mismatch"); - // SHA(SHA(password_sha) concat challenge) XOR password_sha // hash1 = SHA(pass) - std::array password_sha; + std::array password_sha; SHA256(reinterpret_cast(password.data()), password.size(), password_sha.data()); // SHA(password_sha) concat challenge = buffer - std::array buffer; + std::array buffer; SHA256(password_sha.data(), password_sha.size(), buffer.data()); - std::memcpy(buffer.data() + csha2p_response_length, challenge.data(), csha2p_challenge_length); + std::memcpy(buffer.data() + csha2p_hash_size, scramble.data(), csha2p_hash_size); // SHA(SHA(password_sha) concat challenge) = SHA(buffer) = salted_password - std::array salted_password; + std::array salted_password; SHA256(buffer.data(), buffer.size(), salted_password.data()); // salted_password XOR password_sha - for (unsigned i = 0; i < csha2p_response_length; ++i) + for (unsigned i = 0; i < csha2p_hash_size; ++i) { output[i] = salted_password[i] ^ password_sha[i]; } } -inline system::result> csha2p_hash_password( +inline static_buffer csha2p_hash_password( string_view password, - span challenge + span scramble ) { - // If the challenge doesn't match the expected size, - // something wrong is going on and we should fail - if (challenge.size() != csha2p_challenge_length) - return client_errc::protocol_value_error; - // Empty passwords are not hashed if (password.empty()) return {}; // Run the algorithm - static_buffer<32> res(csha2p_response_length); + static_buffer res(csha2p_hash_size); csha2p_hash_password_impl( password, - span(challenge), - span(res.data(), csha2p_response_length) + scramble, + span(res.data(), csha2p_hash_size) ); return res; } @@ -226,7 +221,7 @@ class csha2p_algo connection_state_data& st, span server_data, string_view password, - span challenge, + span scramble, bool secure_channel, std::uint8_t& seqnum ) @@ -249,13 +244,13 @@ class csha2p_algo else { // Request the server's public key - BOOST_MYSQL_YIELD(resume_point_, 99, st.write(int1{2}, seqnum)) + BOOST_MYSQL_YIELD(resume_point_, 2, st.write(int1{2}, seqnum)) // Encrypt the password with the key we were given BOOST_MYSQL_YIELD( resume_point_, - 100, - encrypt_password(st, seqnum, password, challenge, server_data) + 3, + encrypt_password(st, seqnum, password, scramble, server_data) ) // The server shouldn't send us any more packets @@ -265,7 +260,7 @@ class csha2p_algo else if (is_fast_auth_ok(server_data)) { // We should wait for the server to send an OK or an error - BOOST_MYSQL_YIELD(resume_point_, 2, st.read(seqnum)) + BOOST_MYSQL_YIELD(resume_point_, 4, st.read(seqnum)) } else { diff --git a/include/boost/mysql/impl/internal/sansio/handshake.hpp b/include/boost/mysql/impl/internal/sansio/handshake.hpp index 09119a3cc..281f83ea5 100644 --- a/include/boost/mysql/impl/internal/sansio/handshake.hpp +++ b/include/boost/mysql/impl/internal/sansio/handshake.hpp @@ -26,21 +26,24 @@ #include #include #include +#include #include #include #include -#include #include #include #include +#include #include +#include namespace boost { namespace mysql { namespace detail { +// Stores which authentication plugin we're using, plus any required state. Variant-like class any_authentication_plugin { enum class type_t @@ -59,7 +62,8 @@ class any_authentication_plugin public: any_authentication_plugin() = default; - error_code emplace_plugin(string_view plugin_name) + // Emplaces a plugin of the type given by plugin_name. Errors on unknown plugin + error_code emplace_by_name(string_view plugin_name) { if (plugin_name == mnp_plugin_name) { @@ -78,21 +82,26 @@ class any_authentication_plugin } } - system::result> hash_password(string_view password, span challenge) + // Hashes the password with the selected plugin + static_buffer hash_password( + string_view password, + span scramble + ) const { switch (type_) { - case type_t::mnp: return mnp_hash_password(password, challenge); - case type_t::csha2p: return csha2p_hash_password(password, challenge); - default: BOOST_ASSERT(false); return client_errc::unknown_auth_plugin; // LCOV_EXCL_LINE + case type_t::mnp: return mnp_hash_password(password, scramble); + case type_t::csha2p: return csha2p_hash_password(password, scramble); + default: BOOST_ASSERT(false); return {}; // LCOV_EXCL_LINE } } + // Invokes the plugin action. Use when a more_data packet is received. next_action resume( connection_state_data& st, - boost::span server_data, + span server_data, string_view password, - boost::span challenge, + span scramble, bool secure_channel, std::uint8_t& seqnum ) @@ -103,7 +112,7 @@ class any_authentication_plugin // This algorithm doesn't allow more data frames return error_code(client_errc::bad_handshake_packet_type); case type_t::csha2p: - return csha2p_.resume(st, server_data, password, challenge, secure_channel, seqnum); + return csha2p_.resume(st, server_data, password, scramble, secure_channel, seqnum); default: BOOST_ASSERT(false); return next_action(client_errc::bad_handshake_packet_type); // LCOV_EXCL_LINE @@ -126,7 +135,7 @@ class handshake_algo int resume_point_{0}; handshake_params hparams_; any_authentication_plugin plugin_; - container::small_vector challenge_; // TODO: make this a static vector + std::array scramble_; std::uint8_t sequence_number_{0}; bool secure_channel_{false}; @@ -206,6 +215,20 @@ class handshake_algo } } + // Saves the scramble, checking that it has the right size + error_code save_scramble(span value) + { + // All scrambles must have exactly this size. Otherwise, it's a protocol violation error + if (value.size() != scramble_size) + return client_errc::protocol_value_error; + + // Store the scramble + std::memcpy(scramble_.data(), value.data(), scramble_size); + + // Done + return error_code(); + } + error_code process_hello(connection_state_data& st, diagnostics& diag, span buffer) { // Deserialize server hello @@ -227,12 +250,13 @@ class handshake_algo // If we're using SSL, mark the channel as secure secure_channel_ = secure_channel_ || has_capabilities(*negotiated_caps, capabilities::ssl); - // Save the challenge for later - span auth_data(hello.auth_plugin_data); - challenge_.assign(auth_data.begin(), auth_data.end()); + // Save the scramble for later + err = save_scramble(hello.auth_plugin_data); + if (err) + return err; - // Emplace the authentication plugin - return plugin_.emplace_plugin(hello.auth_plugin_name); + // Save which authentication plugin we're using + return plugin_.emplace_by_name(hello.auth_plugin_name); } // Response to that initial greeting @@ -245,46 +269,37 @@ class handshake_algo }; } - next_action compose_login_request(connection_state_data& st) + login_request compose_login_request(const connection_state_data& st) const { - // Hash the password - auto hashed_password = plugin_.hash_password(hparams_.password(), challenge_); - if (hashed_password.has_error()) - return hashed_password.error(); - - // Compose the message - login_request msg{ + return { st.current_capabilities, static_cast(max_packet_size), hparams_.connection_collation(), hparams_.username(), - *hashed_password, + plugin_.hash_password(hparams_.password(), scramble_), hparams_.database(), plugin_.name(), }; - - // Serialize it - return st.write(msg, sequence_number_); } // Processes auth_switch and auth_more_data messages, and leaves the result in auth_resp_ next_action process_auth_switch(connection_state_data& st, auth_switch msg) { // Emplace the new authentication plugin - auto ec = plugin_.emplace_plugin(msg.plugin_name); + auto ec = plugin_.emplace_by_name(msg.plugin_name); if (ec) return ec; - // Store the challenge for later - challenge_.assign(msg.auth_data.begin(), msg.auth_data.end()); + // Store the scramble for later (required by caching_sha2_password, for instance) + ec = save_scramble(msg.auth_data); + if (ec) + return ec; // Hash the password - auto hashed_password = plugin_.hash_password(hparams_.password(), msg.auth_data); - if (hashed_password.has_error()) - return hashed_password.error(); + auto hashed_password = plugin_.hash_password(hparams_.password(), scramble_); // Serialize the response - return st.write(auth_switch_response{*hashed_password}, sequence_number_); + return st.write(auth_switch_response{hashed_password}, sequence_number_); } void on_success(connection_state_data& st, const ok_view& ok) @@ -331,7 +346,7 @@ class handshake_algo } // Compose and send handshake response - BOOST_MYSQL_YIELD(resume_point_, 4, compose_login_request(st)) + BOOST_MYSQL_YIELD(resume_point_, 4, st.write(compose_login_request(st), sequence_number_)) // Receive the response BOOST_MYSQL_YIELD(resume_point_, 5, st.read(sequence_number_)) @@ -361,7 +376,7 @@ class handshake_algo st, resp.data.more_data, hparams_.password(), - challenge_, + scramble_, secure_channel_, sequence_number_ ); diff --git a/include/boost/mysql/impl/internal/sansio/mysql_native_password.hpp b/include/boost/mysql/impl/internal/sansio/mysql_native_password.hpp index 04f615647..04f125765 100644 --- a/include/boost/mysql/impl/internal/sansio/mysql_native_password.hpp +++ b/include/boost/mysql/impl/internal/sansio/mysql_native_password.hpp @@ -12,6 +12,7 @@ #include #include +#include #include #include @@ -30,59 +31,49 @@ namespace mysql { namespace detail { // Constants -BOOST_INLINE_CONSTEXPR std::size_t mnp_challenge_length = 20; -BOOST_INLINE_CONSTEXPR std::size_t mnp_response_length = 20; +BOOST_INLINE_CONSTEXPR std::size_t mnp_hash_size = 20; BOOST_INLINE_CONSTEXPR const char* mnp_plugin_name = "mysql_native_password"; +static_assert(mnp_hash_size <= max_hash_size, ""); +static_assert(mnp_hash_size == SHA_DIGEST_LENGTH, "Buffer size mismatch"); // SHA1( password ) XOR SHA1( "20-bytes random data from server" SHA1( SHA1( password ) ) ) inline void mnp_hash_password_impl( string_view password, - span challenge, - span output + span scramble, + span output ) { - static_assert(mnp_response_length == SHA_DIGEST_LENGTH, "Buffer size mismatch"); - // SHA1 (password) - std::array password_sha1; + std::array password_sha1; SHA1(reinterpret_cast(password.data()), password.size(), password_sha1.data()); // Add server challenge (salt) - std::array salted_buffer; - std::memcpy(salted_buffer.data(), challenge.data(), challenge.size()); - SHA1(password_sha1.data(), password_sha1.size(), salted_buffer.data() + mnp_challenge_length); - std::array salted_sha1; + std::array salted_buffer; + std::memcpy(salted_buffer.data(), scramble.data(), scramble.size()); + SHA1(password_sha1.data(), password_sha1.size(), salted_buffer.data() + mnp_hash_size); + std::array salted_sha1; SHA1(salted_buffer.data(), salted_buffer.size(), salted_sha1.data()); // XOR - for (std::size_t i = 0; i < SHA_DIGEST_LENGTH; ++i) + for (std::size_t i = 0; i < mnp_hash_size; ++i) { output[i] = password_sha1[i] ^ salted_sha1[i]; } } // The static buffer size is chosen so that every plugin uses the same size -inline system::result> mnp_hash_password( +inline static_buffer mnp_hash_password( string_view password, - span challenge + span scramble ) { - // If the challenge doesn't match the expected size, - // something wrong is going on and we should fail - if (challenge.size() != mnp_challenge_length) - return client_errc::protocol_value_error; - // Empty passwords are not hashed if (password.empty()) return {}; // Run the algorithm - static_buffer<32> res(mnp_response_length); - mnp_hash_password_impl( - password, - span(challenge), - span(res.data(), mnp_response_length) - ); + static_buffer res(mnp_hash_size); + mnp_hash_password_impl(password, scramble, span(res.data(), mnp_hash_size)); return res; } From 063d46a98edcc8c33cde94a43d7e2ea4a4985a92 Mon Sep 17 00:00:00 2001 From: Ruben Perez Date: Fri, 9 May 2025 18:10:47 +0200 Subject: [PATCH 05/68] update challenge => scramble terminology --- .../internal/sansio/caching_sha2_password.hpp | 21 ++- .../internal/sansio/mysql_native_password.hpp | 2 +- test/unit/test/sansio/handshake/handshake.cpp | 128 ++++++++-------- .../handshake/handshake_capabilities.cpp | 50 +++---- .../sansio/handshake/handshake_common.hpp | 10 +- .../handshake_connection_state_data.cpp | 24 +-- .../sansio/handshake/handshake_csha2p.cpp | 138 ++++++++---------- .../test/sansio/handshake/handshake_mnp.cpp | 46 +++--- 8 files changed, 195 insertions(+), 224 deletions(-) diff --git a/include/boost/mysql/impl/internal/sansio/caching_sha2_password.hpp b/include/boost/mysql/impl/internal/sansio/caching_sha2_password.hpp index 79fa7abcb..75a1556b1 100644 --- a/include/boost/mysql/impl/internal/sansio/caching_sha2_password.hpp +++ b/include/boost/mysql/impl/internal/sansio/caching_sha2_password.hpp @@ -58,17 +58,17 @@ inline void csha2p_hash_password_impl( span output ) { - // SHA(SHA(password_sha) concat challenge) XOR password_sha + // SHA(SHA(password_sha) concat scramble) XOR password_sha // hash1 = SHA(pass) std::array password_sha; SHA256(reinterpret_cast(password.data()), password.size(), password_sha.data()); - // SHA(password_sha) concat challenge = buffer + // SHA(password_sha) concat scramble = buffer std::array buffer; SHA256(password_sha.data(), password_sha.size(), buffer.data()); std::memcpy(buffer.data() + csha2p_hash_size, scramble.data(), csha2p_hash_size); - // SHA(SHA(password_sha) concat challenge) = SHA(buffer) = salted_password + // SHA(SHA(password_sha) concat scramble) = SHA(buffer) = salted_password std::array salted_password; SHA256(buffer.data(), buffer.size(), salted_password.data()); @@ -126,14 +126,13 @@ using csha2p_password_buffer = container::small_vector; inline error_code csha2p_encrypt_password( string_view password, - span challenge, + span scramble, span server_key, csha2p_password_buffer& output ) { - // TODO: test that these can really never happen + // TODO: this is not guaranteed BOOST_ASSERT(!password.empty()); - BOOST_ASSERT(!challenge.empty()); // Try to parse the private key. TODO: size check here unique_bio bio{BIO_new_mem_buf(server_key.data(), server_key.size())}; @@ -146,11 +145,11 @@ inline error_code csha2p_encrypt_password( // Salt the password, as a NULL-terminated string csha2p_password_buffer salted_password(password.size() + 1u, 0); for (std::size_t i = 0; i < password.size(); ++i) - salted_password[i] = password[i] ^ challenge[i % challenge.size()]; + salted_password[i] = password[i] ^ scramble[i % scramble.size()]; // Add the NULL terminator. It should be salted, too. Since 0 ^ U = U, - // the byte should be the challenge at the position we're in - salted_password[password.size()] = challenge[password.size() % challenge.size()]; + // the byte should be the scramble at the position we're in + salted_password[password.size()] = scramble[password.size() % scramble.size()]; // Set up the encryption context unique_evp_pkey_ctx ctx(EVP_PKEY_CTX_new(key.get(), nullptr)); @@ -200,12 +199,12 @@ class csha2p_algo connection_state_data& st, std::uint8_t& seqnum, string_view password, - span challenge, + span scramble, span server_key ) { csha2p_password_buffer buff; - auto ec = csha2p_encrypt_password(password, challenge, server_key, buff); + auto ec = csha2p_encrypt_password(password, scramble, server_key, buff); if (ec) return ec; return st.write( diff --git a/include/boost/mysql/impl/internal/sansio/mysql_native_password.hpp b/include/boost/mysql/impl/internal/sansio/mysql_native_password.hpp index 04f125765..0b2b13a42 100644 --- a/include/boost/mysql/impl/internal/sansio/mysql_native_password.hpp +++ b/include/boost/mysql/impl/internal/sansio/mysql_native_password.hpp @@ -47,7 +47,7 @@ inline void mnp_hash_password_impl( std::array password_sha1; SHA1(reinterpret_cast(password.data()), password.size(), password_sha1.data()); - // Add server challenge (salt) + // Add server scramble (salt) std::array salted_buffer; std::memcpy(salted_buffer.data(), scramble.data(), scramble.size()); SHA1(password_sha1.data(), password_sha1.size(), salted_buffer.data() + mnp_hash_size); diff --git a/test/unit/test/sansio/handshake/handshake.cpp b/test/unit/test/sansio/handshake/handshake.cpp index b3455f99e..664dd52b6 100644 --- a/test/unit/test/sansio/handshake/handshake.cpp +++ b/test/unit/test/sansio/handshake/handshake.cpp @@ -64,7 +64,7 @@ BOOST_AUTO_TEST_CASE(hello_unknown_plugin) // Run the test algo_test() - .expect_read(server_hello_builder().auth_plugin("unknown").auth_data(csha2p_challenge).build()) + .expect_read(server_hello_builder().auth_plugin("unknown").auth_data(csha2p_scramble).build()) .check(fix, client_errc::unknown_auth_plugin); } @@ -80,9 +80,8 @@ BOOST_AUTO_TEST_CASE(initial_response_error_flavor) // Run the test algo_test() - .expect_read(server_hello_builder().auth_data(mnp_challenge).version("11.4.2-MariaDB-ubu2404").build() - ) - .expect_write(login_request_builder().auth_response(mnp_response).build()) + .expect_read(server_hello_builder().auth_data(mnp_scramble).version("11.4.2-MariaDB-ubu2404").build()) + .expect_write(login_request_builder().auth_response(mnp_hash).build()) .expect_read( err_builder().seqnum(2).code(mariadb_server_errc::er_bad_data).message("bad data").build_frame() ) @@ -107,12 +106,11 @@ BOOST_AUTO_TEST_CASE(authswitch_hash_password_error) // Run the test algo_test() .expect_read( - server_hello_builder().auth_plugin("caching_sha2_password").auth_data(csha2p_challenge).build() + server_hello_builder().auth_plugin("caching_sha2_password").auth_data(csha2p_scramble).build() + ) + .expect_write( + login_request_builder().auth_plugin("caching_sha2_password").auth_response(csha2p_hash).build() ) - .expect_write(login_request_builder() - .auth_plugin("caching_sha2_password") - .auth_response(csha2p_response) - .build()) .expect_read(create_auth_switch_frame(2, "mysql_native_password", std::vector(21, 0x0a)) ) .check(fix, client_errc::protocol_value_error); @@ -127,14 +125,13 @@ BOOST_AUTO_TEST_CASE(authswitch_error) // Run the test algo_test() .expect_read( - server_hello_builder().auth_plugin("caching_sha2_password").auth_data(csha2p_challenge).build() + server_hello_builder().auth_plugin("caching_sha2_password").auth_data(csha2p_scramble).build() + ) + .expect_write( + login_request_builder().auth_plugin("caching_sha2_password").auth_response(csha2p_hash).build() ) - .expect_write(login_request_builder() - .auth_plugin("caching_sha2_password") - .auth_response(csha2p_response) - .build()) - .expect_read(create_auth_switch_frame(2, "mysql_native_password", mnp_challenge)) - .expect_write(create_frame(3, mnp_response)) + .expect_read(create_auth_switch_frame(2, "mysql_native_password", mnp_scramble)) + .expect_write(create_frame(3, mnp_hash)) .expect_read(err_builder() .seqnum(4) .code(common_server_errc::er_access_denied_error) @@ -154,14 +151,13 @@ BOOST_AUTO_TEST_CASE(authswitch_error_flavor) .expect_read(server_hello_builder() .version("11.4.2-MariaDB-ubu2404") .auth_plugin("caching_sha2_password") - .auth_data(csha2p_challenge) + .auth_data(csha2p_scramble) .build()) - .expect_write(login_request_builder() - .auth_plugin("caching_sha2_password") - .auth_response(csha2p_response) - .build()) - .expect_read(create_auth_switch_frame(2, "mysql_native_password", mnp_challenge)) - .expect_write(create_frame(3, mnp_response)) + .expect_write( + login_request_builder().auth_plugin("caching_sha2_password").auth_response(csha2p_hash).build() + ) + .expect_read(create_auth_switch_frame(2, "mysql_native_password", mnp_scramble)) + .expect_write(create_frame(3, mnp_hash)) .expect_read( err_builder().seqnum(4).code(mariadb_server_errc::er_bad_data).message("Denied").build_frame() ) @@ -181,15 +177,14 @@ BOOST_AUTO_TEST_CASE(authswitch_authswitch) // Run the test algo_test() .expect_read( - server_hello_builder().auth_plugin("caching_sha2_password").auth_data(csha2p_challenge).build() + server_hello_builder().auth_plugin("caching_sha2_password").auth_data(csha2p_scramble).build() ) - .expect_write(login_request_builder() - .auth_plugin("caching_sha2_password") - .auth_response(csha2p_response) - .build()) - .expect_read(create_auth_switch_frame(2, "mysql_native_password", mnp_challenge)) - .expect_write(create_frame(3, mnp_response)) - .expect_read(create_auth_switch_frame(4, "mysql_native_password", mnp_challenge)) + .expect_write( + login_request_builder().auth_plugin("caching_sha2_password").auth_response(csha2p_hash).build() + ) + .expect_read(create_auth_switch_frame(2, "mysql_native_password", mnp_scramble)) + .expect_write(create_frame(3, mnp_hash)) + .expect_read(create_auth_switch_frame(4, "mysql_native_password", mnp_scramble)) .check(fix, client_errc::bad_handshake_packet_type); } @@ -202,13 +197,12 @@ BOOST_AUTO_TEST_CASE(authswitch_unknown_plugin) // Run the test algo_test() .expect_read( - server_hello_builder().auth_plugin("caching_sha2_password").auth_data(csha2p_challenge).build() + server_hello_builder().auth_plugin("caching_sha2_password").auth_data(csha2p_scramble).build() ) - .expect_write(login_request_builder() - .auth_plugin("caching_sha2_password") - .auth_response(csha2p_response) - .build()) - .expect_read(create_auth_switch_frame(2, "unknown", mnp_challenge)) + .expect_write( + login_request_builder().auth_plugin("caching_sha2_password").auth_response(csha2p_hash).build() + ) + .expect_read(create_auth_switch_frame(2, "unknown", mnp_scramble)) .check(fix, client_errc::unknown_auth_plugin); } @@ -221,14 +215,13 @@ BOOST_AUTO_TEST_CASE(authswitch_to_itself) // Run the test algo_test() .expect_read( - server_hello_builder().auth_plugin("caching_sha2_password").auth_data(csha2p_challenge).build() + server_hello_builder().auth_plugin("caching_sha2_password").auth_data(csha2p_scramble).build() + ) + .expect_write( + login_request_builder().auth_plugin("caching_sha2_password").auth_response(csha2p_hash).build() ) - .expect_write(login_request_builder() - .auth_plugin("caching_sha2_password") - .auth_response(csha2p_response) - .build()) - .expect_read(create_auth_switch_frame(2, "caching_sha2_password", csha2p_challenge)) - .expect_write(create_frame(3, csha2p_response)) + .expect_read(create_auth_switch_frame(2, "caching_sha2_password", csha2p_scramble)) + .expect_write(create_frame(3, csha2p_hash)) .expect_read(create_more_data_frame(4, csha2p_fast_auth_ok)) .expect_read(create_ok_frame(5, ok_builder().build())) .will_set_status(connection_status::ready) @@ -251,14 +244,13 @@ BOOST_AUTO_TEST_CASE(moredata_authswitch) // Run the test algo_test() .expect_read( - server_hello_builder().auth_plugin("caching_sha2_password").auth_data(csha2p_challenge).build() + server_hello_builder().auth_plugin("caching_sha2_password").auth_data(csha2p_scramble).build() + ) + .expect_write( + login_request_builder().auth_plugin("caching_sha2_password").auth_response(csha2p_hash).build() ) - .expect_write(login_request_builder() - .auth_plugin("caching_sha2_password") - .auth_response(csha2p_response) - .build()) .expect_read(create_more_data_frame(2, csha2p_fast_auth_ok)) - .expect_read(create_auth_switch_frame(3, "mysql_native_password", mnp_challenge)) + .expect_read(create_auth_switch_frame(3, "mysql_native_password", mnp_scramble)) .check(fix, client_errc::bad_handshake_packet_type); } @@ -270,14 +262,14 @@ BOOST_AUTO_TEST_CASE(authswitch_moredata_authswitch) // Run the test algo_test() .expect_read( - server_hello_builder().auth_plugin("mysql_native_password").auth_data(mnp_challenge).build() + server_hello_builder().auth_plugin("mysql_native_password").auth_data(mnp_scramble).build() ) .expect_write( - login_request_builder().auth_plugin("mysql_native_password").auth_response(mnp_response).build() + login_request_builder().auth_plugin("mysql_native_password").auth_response(mnp_hash).build() ) - .expect_read(create_auth_switch_frame(2, "caching_sha2_password", csha2p_challenge)) - .expect_write(create_frame(3, csha2p_response)) - .expect_read(create_auth_switch_frame(4, "caching_sha2_password", csha2p_challenge)) + .expect_read(create_auth_switch_frame(2, "caching_sha2_password", csha2p_scramble)) + .expect_write(create_frame(3, csha2p_hash)) + .expect_read(create_auth_switch_frame(4, "caching_sha2_password", csha2p_scramble)) .check(fix, client_errc::bad_handshake_packet_type); } @@ -292,12 +284,11 @@ BOOST_AUTO_TEST_CASE(moredata_error_flavor) .expect_read(server_hello_builder() .version("11.4.2-MariaDB-ubu2404") .auth_plugin("caching_sha2_password") - .auth_data(csha2p_challenge) + .auth_data(csha2p_scramble) .build()) - .expect_write(login_request_builder() - .auth_plugin("caching_sha2_password") - .auth_response(csha2p_response) - .build()) + .expect_write( + login_request_builder().auth_plugin("caching_sha2_password").auth_response(csha2p_hash).build() + ) .expect_read(create_more_data_frame(2, csha2p_fast_auth_ok)) .expect_read( err_builder().seqnum(3).code(mariadb_server_errc::er_bad_data).message("Denied").build_frame() @@ -324,12 +315,12 @@ BOOST_AUTO_TEST_CASE(network_errors) // Run the test algo_test() - .expect_read(server_hello_builder().caps(tls_caps).auth_data(mnp_challenge).build()) + .expect_read(server_hello_builder().caps(tls_caps).auth_data(mnp_scramble).build()) .expect_write(create_ssl_request()) .expect_ssl_handshake() - .expect_write(login_request_builder().seqnum(2).caps(tls_caps).auth_response(mnp_response).build()) - .expect_read(create_auth_switch_frame(3, "caching_sha2_password", csha2p_challenge)) - .expect_write(create_frame(4, csha2p_response)) + .expect_write(login_request_builder().seqnum(2).caps(tls_caps).auth_response(mnp_hash).build()) + .expect_read(create_auth_switch_frame(3, "caching_sha2_password", csha2p_scramble)) + .expect_write(create_frame(4, csha2p_hash)) .expect_read(create_more_data_frame(5, csha2p_perform_full_auth)) .expect_write(create_frame(6, null_terminated_password())) .expect_read(create_ok_frame(7, ok_builder().build())) @@ -344,12 +335,11 @@ BOOST_AUTO_TEST_CASE(network_errors_read_moredata) // Run the test algo_test() .expect_read( - server_hello_builder().auth_plugin("caching_sha2_password").auth_data(csha2p_challenge).build() + server_hello_builder().auth_plugin("caching_sha2_password").auth_data(csha2p_scramble).build() + ) + .expect_write( + login_request_builder().auth_plugin("caching_sha2_password").auth_response(csha2p_hash).build() ) - .expect_write(login_request_builder() - .auth_plugin("caching_sha2_password") - .auth_response(csha2p_response) - .build()) .expect_read(client_errc::wrong_num_params) .check(fix, client_errc::wrong_num_params); } diff --git a/test/unit/test/sansio/handshake/handshake_capabilities.cpp b/test/unit/test/sansio/handshake/handshake_capabilities.cpp index 825813e13..973aa4d11 100644 --- a/test/unit/test/sansio/handshake/handshake_capabilities.cpp +++ b/test/unit/test/sansio/handshake/handshake_capabilities.cpp @@ -32,8 +32,8 @@ BOOST_AUTO_TEST_CASE(db_nonempty_supported) // Run the test algo_test() - .expect_read(server_hello_builder().caps(db_caps).auth_data(mnp_challenge).build()) - .expect_write(login_request_builder().caps(db_caps).auth_response(mnp_response).db("mydb").build()) + .expect_read(server_hello_builder().caps(db_caps).auth_data(mnp_scramble).build()) + .expect_write(login_request_builder().caps(db_caps).auth_response(mnp_hash).db("mydb").build()) .expect_read(create_ok_frame(2, ok_builder().build())) .will_set_status(connection_status::ready) .will_set_capabilities(db_caps) @@ -49,7 +49,7 @@ BOOST_AUTO_TEST_CASE(db_nonempty_unsupported) // Run the test algo_test() - .expect_read(server_hello_builder().caps(min_caps).auth_data(mnp_challenge).build()) + .expect_read(server_hello_builder().caps(min_caps).auth_data(mnp_scramble).build()) .check(fix, client_errc::server_unsupported); } @@ -61,8 +61,8 @@ BOOST_AUTO_TEST_CASE(db_empty_supported) // Run the test algo_test() - .expect_read(server_hello_builder().caps(db_caps).auth_data(mnp_challenge).build()) - .expect_write(login_request_builder().caps(min_caps).auth_response(mnp_response).build()) + .expect_read(server_hello_builder().caps(db_caps).auth_data(mnp_scramble).build()) + .expect_write(login_request_builder().caps(min_caps).auth_response(mnp_hash).build()) .expect_read(create_ok_frame(2, ok_builder().build())) .will_set_status(connection_status::ready) .will_set_capabilities(min_caps) @@ -79,8 +79,8 @@ BOOST_AUTO_TEST_CASE(db_empty_unsupported) // Run the test algo_test() - .expect_read(server_hello_builder().auth_data(mnp_challenge).build()) - .expect_write(login_request_builder().auth_response(mnp_response).build()) + .expect_read(server_hello_builder().auth_data(mnp_scramble).build()) + .expect_write(login_request_builder().auth_response(mnp_hash).build()) .expect_read(create_ok_frame(2, ok_builder().build())) .will_set_status(connection_status::ready) .will_set_capabilities(min_caps) @@ -105,8 +105,8 @@ BOOST_AUTO_TEST_CASE(multiq_true_supported) // Run the test algo_test() - .expect_read(server_hello_builder().caps(multiq_caps).auth_data(mnp_challenge).build()) - .expect_write(login_request_builder().caps(multiq_caps).auth_response(mnp_response).build()) + .expect_read(server_hello_builder().caps(multiq_caps).auth_data(mnp_scramble).build()) + .expect_write(login_request_builder().caps(multiq_caps).auth_response(mnp_hash).build()) .expect_read(create_ok_frame(2, ok_builder().build())) .will_set_status(connection_status::ready) .will_set_capabilities(multiq_caps) @@ -125,7 +125,7 @@ BOOST_AUTO_TEST_CASE(multiq_true_unsupported) // Run the test algo_test() - .expect_read(server_hello_builder().caps(min_caps).auth_data(mnp_challenge).build()) + .expect_read(server_hello_builder().caps(min_caps).auth_data(mnp_scramble).build()) .check(fix, client_errc::server_unsupported); } @@ -137,8 +137,8 @@ BOOST_AUTO_TEST_CASE(multiq_false_supported) // Run the test algo_test() - .expect_read(server_hello_builder().caps(multiq_caps).auth_data(mnp_challenge).build()) - .expect_write(login_request_builder().caps(min_caps).auth_response(mnp_response).build()) + .expect_read(server_hello_builder().caps(multiq_caps).auth_data(mnp_scramble).build()) + .expect_write(login_request_builder().caps(min_caps).auth_response(mnp_hash).build()) .expect_read(create_ok_frame(2, ok_builder().build())) .will_set_status(connection_status::ready) .will_set_capabilities(min_caps) @@ -155,8 +155,8 @@ BOOST_AUTO_TEST_CASE(multiq_false_unsupported) // Run the test algo_test() - .expect_read(server_hello_builder().caps(min_caps).auth_data(mnp_challenge).build()) - .expect_write(login_request_builder().caps(min_caps).auth_response(mnp_response).build()) + .expect_read(server_hello_builder().caps(min_caps).auth_data(mnp_scramble).build()) + .expect_write(login_request_builder().caps(min_caps).auth_response(mnp_hash).build()) .expect_read(create_ok_frame(2, ok_builder().build())) .will_set_status(connection_status::ready) .will_set_capabilities(min_caps) @@ -184,11 +184,10 @@ BOOST_AUTO_TEST_CASE(tls_on) // Run the test algo_test() - .expect_read(server_hello_builder().caps(tls_caps).auth_data(mnp_challenge).build()) + .expect_read(server_hello_builder().caps(tls_caps).auth_data(mnp_scramble).build()) .expect_write(create_ssl_request()) .expect_ssl_handshake() - .expect_write( - login_request_builder().seqnum(2).caps(tls_caps).auth_response(mnp_response).build() + .expect_write(login_request_builder().seqnum(2).caps(tls_caps).auth_response(mnp_hash).build() ) .expect_read(create_ok_frame(3, ok_builder().build())) .will_set_status(connection_status::ready) @@ -271,8 +270,8 @@ BOOST_AUTO_TEST_CASE(tls_off) // Run the test algo_test() - .expect_read(server_hello_builder().caps(tc.server_caps).auth_data(mnp_challenge).build()) - .expect_write(login_request_builder().caps(min_caps).auth_response(mnp_response).build()) + .expect_read(server_hello_builder().caps(tc.server_caps).auth_data(mnp_scramble).build()) + .expect_write(login_request_builder().caps(min_caps).auth_response(mnp_hash).build()) .expect_read(create_ok_frame(2, ok_builder().build())) .will_set_status(connection_status::ready) .will_set_capabilities(min_caps) @@ -294,7 +293,7 @@ BOOST_AUTO_TEST_CASE(tls_error_unsupported) // Run the test algo_test() - .expect_read(server_hello_builder().caps(min_caps).auth_data(mnp_challenge).build()) + .expect_read(server_hello_builder().caps(min_caps).auth_data(mnp_scramble).build()) .check(fix, client_errc::server_doesnt_support_ssl); } @@ -338,7 +337,7 @@ BOOST_AUTO_TEST_CASE(caps_mandatory) // Run the test algo_test() - .expect_read(server_hello_builder().caps(tc.caps).auth_data(mnp_challenge).build()) + .expect_read(server_hello_builder().caps(tc.caps).auth_data(mnp_scramble).build()) .check(fix, client_errc::server_unsupported); } } @@ -365,9 +364,8 @@ BOOST_AUTO_TEST_CASE(caps_optional) // Run the test algo_test() - .expect_read(server_hello_builder().caps(min_caps | tc.caps).auth_data(mnp_challenge).build()) - .expect_write( - login_request_builder().caps(min_caps | tc.caps).auth_response(mnp_response).build() + .expect_read(server_hello_builder().caps(min_caps | tc.caps).auth_data(mnp_scramble).build()) + .expect_write(login_request_builder().caps(min_caps | tc.caps).auth_response(mnp_hash).build() ) .expect_read(create_ok_frame(2, ok_builder().build())) .will_set_status(connection_status::ready) @@ -416,8 +414,8 @@ BOOST_AUTO_TEST_CASE(caps_ignored) // Run the test algo_test() - .expect_read(server_hello_builder().caps(min_caps | tc.caps).auth_data(mnp_challenge).build()) - .expect_write(login_request_builder().caps(min_caps).auth_response(mnp_response).build()) + .expect_read(server_hello_builder().caps(min_caps | tc.caps).auth_data(mnp_scramble).build()) + .expect_write(login_request_builder().caps(min_caps).auth_response(mnp_hash).build()) .expect_read(create_ok_frame(2, ok_builder().build())) .will_set_status(connection_status::ready) .will_set_capabilities(min_caps) diff --git a/test/unit/test/sansio/handshake/handshake_common.hpp b/test/unit/test/sansio/handshake/handshake_common.hpp index 6e943f0a1..8e79f85a4 100644 --- a/test/unit/test/sansio/handshake/handshake_common.hpp +++ b/test/unit/test/sansio/handshake/handshake_common.hpp @@ -223,25 +223,25 @@ inline std::vector create_more_data_frame( })); } -// These challenge/responses have been captured with Wireshark. +// These scrambles/hashes have been captured with Wireshark. constexpr const char* password = "example_password"; -constexpr std::uint8_t mnp_challenge[] = { +constexpr std::uint8_t mnp_scramble[] = { 0x1b, 0x0f, 0x6e, 0x59, 0x1b, 0x70, 0x33, 0x01, 0x0c, 0x01, 0x7e, 0x2e, 0x30, 0x7a, 0x79, 0x5c, 0x02, 0x50, 0x51, 0x35, }; -constexpr std::uint8_t mnp_response[] = { +constexpr std::uint8_t mnp_hash[] = { 0xbe, 0xa5, 0xb5, 0xe7, 0x9c, 0x05, 0x23, 0x34, 0xda, 0x06, 0x1d, 0xaf, 0xd9, 0x8b, 0x4b, 0x09, 0x86, 0xe5, 0xd1, 0x4a, }; -constexpr std::uint8_t csha2p_challenge[] = { +constexpr std::uint8_t csha2p_scramble[] = { 0x6f, 0x1b, 0x3b, 0x64, 0x39, 0x01, 0x46, 0x44, 0x53, 0x3b, 0x74, 0x3c, 0x3e, 0x3c, 0x3c, 0x0b, 0x30, 0x77, 0x1a, 0x49, }; -constexpr std::uint8_t csha2p_response[] = { +constexpr std::uint8_t csha2p_hash[] = { 0xa7, 0xc3, 0x7f, 0x88, 0x25, 0xec, 0x92, 0x2c, 0x88, 0xba, 0x47, 0x04, 0x14, 0xd2, 0xa3, 0xa3, 0x5e, 0xa9, 0x41, 0x8e, 0xdc, 0x89, 0xeb, 0xe2, 0xa1, 0xec, 0xd8, 0x4f, 0x73, 0xa1, 0x49, 0x60, }; diff --git a/test/unit/test/sansio/handshake/handshake_connection_state_data.cpp b/test/unit/test/sansio/handshake/handshake_connection_state_data.cpp index a71759a5c..a35a9da92 100644 --- a/test/unit/test/sansio/handshake/handshake_connection_state_data.cpp +++ b/test/unit/test/sansio/handshake/handshake_connection_state_data.cpp @@ -31,8 +31,8 @@ BOOST_AUTO_TEST_CASE(hello_connection_id) // Run the test algo_test() - .expect_read(server_hello_builder().connection_id(value).auth_data(mnp_challenge).build()) - .expect_write(login_request_builder().auth_response(mnp_response).build()) + .expect_read(server_hello_builder().connection_id(value).auth_data(mnp_scramble).build()) + .expect_write(login_request_builder().auth_response(mnp_hash).build()) .expect_read(create_ok_frame(2, ok_builder().build())) .will_set_status(connection_status::ready) .will_set_capabilities(min_caps) @@ -65,8 +65,8 @@ BOOST_AUTO_TEST_CASE(flavor) // Run the test algo_test() - .expect_read(server_hello_builder().version(tc.version).auth_data(mnp_challenge).build()) - .expect_write(login_request_builder().auth_response(mnp_response).build()) + .expect_read(server_hello_builder().version(tc.version).auth_data(mnp_scramble).build()) + .expect_write(login_request_builder().auth_response(mnp_hash).build()) .expect_read(create_ok_frame(2, ok_builder().build())) .will_set_status(connection_status::ready) .will_set_capabilities(min_caps) @@ -91,10 +91,10 @@ BOOST_AUTO_TEST_CASE(unknown_collation) // Run the test algo_test() - .expect_read(server_hello_builder().auth_data(mnp_challenge).build()) + .expect_read(server_hello_builder().auth_data(mnp_scramble).build()) .expect_write(login_request_builder() .collation(mysql_collations::utf8mb4_0900_as_ci) - .auth_response(mnp_response) + .auth_response(mnp_hash) .build()) .expect_read(create_ok_frame(2, ok_builder().build())) .will_set_status(connection_status::ready) @@ -113,8 +113,8 @@ BOOST_AUTO_TEST_CASE(backslash_escapes) // Run the test algo_test() - .expect_read(server_hello_builder().auth_data(mnp_challenge).build()) - .expect_write(login_request_builder().auth_response(mnp_response).build()) + .expect_read(server_hello_builder().auth_data(mnp_scramble).build()) + .expect_write(login_request_builder().auth_response(mnp_hash).build()) .expect_read(create_ok_frame(2, ok_builder().no_backslash_escapes(true).build())) .will_set_status(connection_status::ready) .will_set_capabilities(min_caps) @@ -133,8 +133,8 @@ BOOST_AUTO_TEST_CASE(meta_mode) // Run the test algo_test() - .expect_read(server_hello_builder().auth_data(mnp_challenge).build()) - .expect_write(login_request_builder().auth_response(mnp_response).build()) + .expect_read(server_hello_builder().auth_data(mnp_scramble).build()) + .expect_write(login_request_builder().auth_response(mnp_hash).build()) .expect_read(create_ok_frame(2, ok_builder().build())) .will_set_status(connection_status::ready) .will_set_capabilities(min_caps) @@ -166,8 +166,8 @@ BOOST_AUTO_TEST_CASE(connection_status_success) // Run the test algo_test() - .expect_read(server_hello_builder().auth_data(mnp_challenge).build()) - .expect_write(login_request_builder().auth_response(mnp_response).build()) + .expect_read(server_hello_builder().auth_data(mnp_scramble).build()) + .expect_write(login_request_builder().auth_response(mnp_hash).build()) .expect_read(create_ok_frame(2, ok_builder().build())) .will_set_status(connection_status::ready) .will_set_capabilities(min_caps) diff --git a/test/unit/test/sansio/handshake/handshake_csha2p.cpp b/test/unit/test/sansio/handshake/handshake_csha2p.cpp index fd8f0c46f..d41696409 100644 --- a/test/unit/test/sansio/handshake/handshake_csha2p.cpp +++ b/test/unit/test/sansio/handshake/handshake_csha2p.cpp @@ -36,12 +36,11 @@ BOOST_AUTO_TEST_CASE(ok) // Run the test algo_test() .expect_read( - server_hello_builder().auth_plugin("caching_sha2_password").auth_data(csha2p_challenge).build() + server_hello_builder().auth_plugin("caching_sha2_password").auth_data(csha2p_scramble).build() + ) + .expect_write( + login_request_builder().auth_plugin("caching_sha2_password").auth_response(csha2p_hash).build() ) - .expect_write(login_request_builder() - .auth_plugin("caching_sha2_password") - .auth_response(csha2p_response) - .build()) .expect_read(create_ok_frame(2, ok_builder().build())) .will_set_status(connection_status::ready) .will_set_capabilities(min_caps) @@ -60,12 +59,11 @@ BOOST_AUTO_TEST_CASE(err) // Run the test algo_test() .expect_read( - server_hello_builder().auth_plugin("caching_sha2_password").auth_data(csha2p_challenge).build() + server_hello_builder().auth_plugin("caching_sha2_password").auth_data(csha2p_scramble).build() + ) + .expect_write( + login_request_builder().auth_plugin("caching_sha2_password").auth_response(csha2p_hash).build() ) - .expect_write(login_request_builder() - .auth_plugin("caching_sha2_password") - .auth_response(csha2p_response) - .build()) .expect_read(err_builder() .seqnum(2) .code(common_server_errc::er_access_denied_error) @@ -85,12 +83,11 @@ BOOST_AUTO_TEST_CASE(fullauth) .expect_read(server_hello_builder() .caps(tls_caps) .auth_plugin("caching_sha2_password") - .auth_data(csha2p_challenge) + .auth_data(csha2p_scramble) .build()) - .expect_write(login_request_builder() - .auth_plugin("caching_sha2_password") - .auth_response(csha2p_response) - .build()) + .expect_write( + login_request_builder().auth_plugin("caching_sha2_password").auth_response(csha2p_hash).build() + ) .expect_read(create_more_data_frame(2, csha2p_perform_full_auth)) .check(fix, client_errc::auth_plugin_requires_ssl); } @@ -104,12 +101,11 @@ BOOST_AUTO_TEST_CASE(moredata) // Run the test algo_test() .expect_read( - server_hello_builder().auth_plugin("caching_sha2_password").auth_data(csha2p_challenge).build() + server_hello_builder().auth_plugin("caching_sha2_password").auth_data(csha2p_scramble).build() + ) + .expect_write( + login_request_builder().auth_plugin("caching_sha2_password").auth_response(csha2p_hash).build() ) - .expect_write(login_request_builder() - .auth_plugin("caching_sha2_password") - .auth_response(csha2p_response) - .build()) .expect_read(create_more_data_frame(2, std::vector{3, 4})) .check(fix, client_errc::bad_handshake_packet_type); } @@ -123,12 +119,11 @@ BOOST_AUTO_TEST_CASE(fastok_ok) // Run the test algo_test() .expect_read( - server_hello_builder().auth_plugin("caching_sha2_password").auth_data(csha2p_challenge).build() + server_hello_builder().auth_plugin("caching_sha2_password").auth_data(csha2p_scramble).build() + ) + .expect_write( + login_request_builder().auth_plugin("caching_sha2_password").auth_response(csha2p_hash).build() ) - .expect_write(login_request_builder() - .auth_plugin("caching_sha2_password") - .auth_response(csha2p_response) - .build()) .expect_read(create_more_data_frame(2, csha2p_fast_auth_ok)) .expect_read(create_ok_frame(3, ok_builder().build())) .will_set_status(connection_status::ready) @@ -148,12 +143,11 @@ BOOST_AUTO_TEST_CASE(fastok_err) // Run the test algo_test() .expect_read( - server_hello_builder().auth_plugin("caching_sha2_password").auth_data(csha2p_challenge).build() + server_hello_builder().auth_plugin("caching_sha2_password").auth_data(csha2p_scramble).build() + ) + .expect_write( + login_request_builder().auth_plugin("caching_sha2_password").auth_response(csha2p_hash).build() ) - .expect_write(login_request_builder() - .auth_plugin("caching_sha2_password") - .auth_response(csha2p_response) - .build()) .expect_read(create_more_data_frame(2, csha2p_fast_auth_ok)) .expect_read(err_builder() .seqnum(3) @@ -172,12 +166,11 @@ BOOST_AUTO_TEST_CASE(fastok_fastok) // Run the test algo_test() .expect_read( - server_hello_builder().auth_plugin("caching_sha2_password").auth_data(csha2p_challenge).build() + server_hello_builder().auth_plugin("caching_sha2_password").auth_data(csha2p_scramble).build() + ) + .expect_write( + login_request_builder().auth_plugin("caching_sha2_password").auth_response(csha2p_hash).build() ) - .expect_write(login_request_builder() - .auth_plugin("caching_sha2_password") - .auth_response(csha2p_response) - .build()) .expect_read(create_more_data_frame(2, csha2p_fast_auth_ok)) .expect_read(create_more_data_frame(3, csha2p_fast_auth_ok)) .check(fix, client_errc::bad_handshake_packet_type); @@ -192,12 +185,11 @@ BOOST_AUTO_TEST_CASE(fastok_fullauth) // Run the test algo_test() .expect_read( - server_hello_builder().auth_plugin("caching_sha2_password").auth_data(csha2p_challenge).build() + server_hello_builder().auth_plugin("caching_sha2_password").auth_data(csha2p_scramble).build() + ) + .expect_write( + login_request_builder().auth_plugin("caching_sha2_password").auth_response(csha2p_hash).build() ) - .expect_write(login_request_builder() - .auth_plugin("caching_sha2_password") - .auth_response(csha2p_response) - .build()) .expect_read(create_more_data_frame(2, csha2p_fast_auth_ok)) .expect_read(create_more_data_frame(3, csha2p_perform_full_auth)) .check(fix, client_errc::bad_handshake_packet_type); @@ -212,12 +204,11 @@ BOOST_AUTO_TEST_CASE(fastok_moredata) // Run the test algo_test() .expect_read( - server_hello_builder().auth_plugin("caching_sha2_password").auth_data(csha2p_challenge).build() + server_hello_builder().auth_plugin("caching_sha2_password").auth_data(csha2p_scramble).build() + ) + .expect_write( + login_request_builder().auth_plugin("caching_sha2_password").auth_response(csha2p_hash).build() ) - .expect_write(login_request_builder() - .auth_plugin("caching_sha2_password") - .auth_response(csha2p_response) - .build()) .expect_read(create_more_data_frame(2, csha2p_fast_auth_ok)) .expect_read(create_more_data_frame(3, std::vector{10, 20, 30})) .check(fix, client_errc::bad_handshake_packet_type); @@ -232,13 +223,13 @@ BOOST_AUTO_TEST_CASE(authswitch_fastok_ok) // Run the test algo_test() .expect_read( - server_hello_builder().auth_plugin("mysql_native_password").auth_data(mnp_challenge).build() + server_hello_builder().auth_plugin("mysql_native_password").auth_data(mnp_scramble).build() ) .expect_write( - login_request_builder().auth_plugin("mysql_native_password").auth_response(mnp_response).build() + login_request_builder().auth_plugin("mysql_native_password").auth_response(mnp_hash).build() ) - .expect_read(create_auth_switch_frame(2, "caching_sha2_password", csha2p_challenge)) - .expect_write(create_frame(3, csha2p_response)) + .expect_read(create_auth_switch_frame(2, "caching_sha2_password", csha2p_scramble)) + .expect_write(create_frame(3, csha2p_hash)) .expect_read(create_more_data_frame(4, csha2p_fast_auth_ok)) .expect_read(create_ok_frame(5, ok_builder().build())) .will_set_status(connection_status::ready) @@ -258,12 +249,11 @@ BOOST_AUTO_TEST_CASE(securetransport_fullauth_ok) // Run the test algo_test() .expect_read( - server_hello_builder().auth_plugin("caching_sha2_password").auth_data(csha2p_challenge).build() + server_hello_builder().auth_plugin("caching_sha2_password").auth_data(csha2p_scramble).build() + ) + .expect_write( + login_request_builder().auth_plugin("caching_sha2_password").auth_response(csha2p_hash).build() ) - .expect_write(login_request_builder() - .auth_plugin("caching_sha2_password") - .auth_response(csha2p_response) - .build()) .expect_read(create_more_data_frame(2, csha2p_perform_full_auth)) .expect_write(create_frame(3, null_terminated_password())) .expect_read(create_ok_frame(4, ok_builder().build())) @@ -285,12 +275,11 @@ BOOST_AUTO_TEST_CASE(securetransport_fullauth_err) // Run the test algo_test() .expect_read( - server_hello_builder().auth_plugin("caching_sha2_password").auth_data(csha2p_challenge).build() + server_hello_builder().auth_plugin("caching_sha2_password").auth_data(csha2p_scramble).build() + ) + .expect_write( + login_request_builder().auth_plugin("caching_sha2_password").auth_response(csha2p_hash).build() ) - .expect_write(login_request_builder() - .auth_plugin("caching_sha2_password") - .auth_response(csha2p_response) - .build()) .expect_read(create_more_data_frame(2, csha2p_perform_full_auth)) .expect_write(create_frame(3, null_terminated_password())) .expect_read(err_builder() @@ -310,12 +299,11 @@ BOOST_AUTO_TEST_CASE(securetransport_fullauth_fastok) // Run the test algo_test() .expect_read( - server_hello_builder().auth_plugin("caching_sha2_password").auth_data(csha2p_challenge).build() + server_hello_builder().auth_plugin("caching_sha2_password").auth_data(csha2p_scramble).build() + ) + .expect_write( + login_request_builder().auth_plugin("caching_sha2_password").auth_response(csha2p_hash).build() ) - .expect_write(login_request_builder() - .auth_plugin("caching_sha2_password") - .auth_response(csha2p_response) - .build()) .expect_read(create_more_data_frame(2, csha2p_perform_full_auth)) .expect_write(create_frame(3, null_terminated_password())) .expect_read(create_more_data_frame(4, csha2p_fast_auth_ok)) @@ -331,12 +319,11 @@ BOOST_AUTO_TEST_CASE(securetransport_fullauth_fullauth) // Run the test algo_test() .expect_read( - server_hello_builder().auth_plugin("caching_sha2_password").auth_data(csha2p_challenge).build() + server_hello_builder().auth_plugin("caching_sha2_password").auth_data(csha2p_scramble).build() + ) + .expect_write( + login_request_builder().auth_plugin("caching_sha2_password").auth_response(csha2p_hash).build() ) - .expect_write(login_request_builder() - .auth_plugin("caching_sha2_password") - .auth_response(csha2p_response) - .build()) .expect_read(create_more_data_frame(2, csha2p_perform_full_auth)) .expect_write(create_frame(3, null_terminated_password())) .expect_read(create_more_data_frame(4, csha2p_perform_full_auth)) @@ -352,12 +339,11 @@ BOOST_AUTO_TEST_CASE(securetransport_fullauth_moredata) // Run the test algo_test() .expect_read( - server_hello_builder().auth_plugin("caching_sha2_password").auth_data(csha2p_challenge).build() + server_hello_builder().auth_plugin("caching_sha2_password").auth_data(csha2p_scramble).build() + ) + .expect_write( + login_request_builder().auth_plugin("caching_sha2_password").auth_response(csha2p_hash).build() ) - .expect_write(login_request_builder() - .auth_plugin("caching_sha2_password") - .auth_response(csha2p_response) - .build()) .expect_read(create_more_data_frame(2, csha2p_perform_full_auth)) .expect_write(create_frame(3, null_terminated_password())) .expect_read(create_more_data_frame(4, std::vector{4, 3, 2})) @@ -376,7 +362,7 @@ BOOST_AUTO_TEST_CASE(tls) .expect_read(server_hello_builder() .caps(tls_caps) .auth_plugin("caching_sha2_password") - .auth_data(csha2p_challenge) + .auth_data(csha2p_scramble) .build()) .expect_write(create_ssl_request()) .expect_ssl_handshake() @@ -384,7 +370,7 @@ BOOST_AUTO_TEST_CASE(tls) .seqnum(2) .caps(tls_caps) .auth_plugin("caching_sha2_password") - .auth_response(csha2p_response) + .auth_response(csha2p_hash) .build()) .expect_read(create_more_data_frame(3, csha2p_perform_full_auth)) .expect_write(create_frame(4, null_terminated_password())) diff --git a/test/unit/test/sansio/handshake/handshake_mnp.cpp b/test/unit/test/sansio/handshake/handshake_mnp.cpp index d1acd077e..a40da24ad 100644 --- a/test/unit/test/sansio/handshake/handshake_mnp.cpp +++ b/test/unit/test/sansio/handshake/handshake_mnp.cpp @@ -29,8 +29,8 @@ BOOST_AUTO_TEST_CASE(ok) // Run the test algo_test() - .expect_read(server_hello_builder().auth_data(mnp_challenge).build()) - .expect_write(login_request_builder().auth_response(mnp_response).build()) + .expect_read(server_hello_builder().auth_data(mnp_scramble).build()) + .expect_write(login_request_builder().auth_response(mnp_hash).build()) .expect_read(create_ok_frame(2, ok_builder().build())) .will_set_status(connection_status::ready) .will_set_capabilities(min_caps) @@ -46,8 +46,8 @@ BOOST_AUTO_TEST_CASE(err) // Run the test algo_test() - .expect_read(server_hello_builder().auth_data(mnp_challenge).build()) - .expect_write(login_request_builder().auth_response(mnp_response).build()) + .expect_read(server_hello_builder().auth_data(mnp_scramble).build()) + .expect_write(login_request_builder().auth_response(mnp_hash).build()) .expect_read(err_builder() .seqnum(2) .code(common_server_errc::er_access_denied_error) @@ -65,14 +65,13 @@ BOOST_AUTO_TEST_CASE(authswitch_ok) // Run the test algo_test() .expect_read( - server_hello_builder().auth_plugin("caching_sha2_password").auth_data(csha2p_challenge).build() + server_hello_builder().auth_plugin("caching_sha2_password").auth_data(csha2p_scramble).build() ) - .expect_write(login_request_builder() - .auth_plugin("caching_sha2_password") - .auth_response(csha2p_response) - .build()) - .expect_read(create_auth_switch_frame(2, "mysql_native_password", mnp_challenge)) - .expect_write(create_frame(3, mnp_response)) + .expect_write( + login_request_builder().auth_plugin("caching_sha2_password").auth_response(csha2p_hash).build() + ) + .expect_read(create_auth_switch_frame(2, "mysql_native_password", mnp_scramble)) + .expect_write(create_frame(3, mnp_hash)) .expect_read(create_ok_frame(4, ok_builder().build())) .will_set_status(connection_status::ready) .will_set_capabilities(min_caps) @@ -90,10 +89,10 @@ BOOST_AUTO_TEST_CASE(mnp_tls) // Run the test algo_test() - .expect_read(server_hello_builder().caps(tls_caps).auth_data(mnp_challenge).build()) + .expect_read(server_hello_builder().caps(tls_caps).auth_data(mnp_scramble).build()) .expect_write(create_ssl_request()) .expect_ssl_handshake() - .expect_write(login_request_builder().seqnum(2).caps(tls_caps).auth_response(mnp_response).build()) + .expect_write(login_request_builder().seqnum(2).caps(tls_caps).auth_response(mnp_hash).build()) .expect_read(create_ok_frame(3, ok_builder().build())) .will_set_status(connection_status::ready) .will_set_tls_active(true) @@ -111,9 +110,9 @@ BOOST_AUTO_TEST_CASE(moredata) // Run the test algo_test() - .expect_read(server_hello_builder().auth_data(mnp_challenge).build()) - .expect_write(login_request_builder().auth_response(mnp_response).build()) - .expect_read(create_more_data_frame(2, mnp_challenge)) + .expect_read(server_hello_builder().auth_data(mnp_scramble).build()) + .expect_write(login_request_builder().auth_response(mnp_hash).build()) + .expect_read(create_more_data_frame(2, mnp_scramble)) .check(fix, client_errc::bad_handshake_packet_type); } @@ -125,15 +124,14 @@ BOOST_AUTO_TEST_CASE(authswitch_moredata) // Run the test algo_test() .expect_read( - server_hello_builder().auth_plugin("caching_sha2_password").auth_data(csha2p_challenge).build() + server_hello_builder().auth_plugin("caching_sha2_password").auth_data(csha2p_scramble).build() + ) + .expect_write( + login_request_builder().auth_plugin("caching_sha2_password").auth_response(csha2p_hash).build() ) - .expect_write(login_request_builder() - .auth_plugin("caching_sha2_password") - .auth_response(csha2p_response) - .build()) - .expect_read(create_auth_switch_frame(2, "mysql_native_password", mnp_challenge)) - .expect_write(create_frame(3, mnp_response)) - .expect_read(create_more_data_frame(4, mnp_challenge)) + .expect_read(create_auth_switch_frame(2, "mysql_native_password", mnp_scramble)) + .expect_write(create_frame(3, mnp_hash)) + .expect_read(create_more_data_frame(4, mnp_scramble)) .check(fix, client_errc::bad_handshake_packet_type); } From a1eb3ff0f817b7e479c1ac6bcc075c259b462f5c Mon Sep 17 00:00:00 2001 From: Ruben Perez Date: Fri, 9 May 2025 18:13:10 +0200 Subject: [PATCH 06/68] remove assertion --- .../mysql/impl/internal/sansio/caching_sha2_password.hpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/include/boost/mysql/impl/internal/sansio/caching_sha2_password.hpp b/include/boost/mysql/impl/internal/sansio/caching_sha2_password.hpp index 75a1556b1..1acd41abe 100644 --- a/include/boost/mysql/impl/internal/sansio/caching_sha2_password.hpp +++ b/include/boost/mysql/impl/internal/sansio/caching_sha2_password.hpp @@ -131,9 +131,7 @@ inline error_code csha2p_encrypt_password( csha2p_password_buffer& output ) { - // TODO: this is not guaranteed - BOOST_ASSERT(!password.empty()); - + // TODO: test that password.size() == 0u does not cause trouble // Try to parse the private key. TODO: size check here unique_bio bio{BIO_new_mem_buf(server_key.data(), server_key.size())}; if (!bio) From 9bfd88bdc8b4a895e509ba1a4cb2d44e984bb26c Mon Sep 17 00:00:00 2001 From: Ruben Perez Date: Fri, 9 May 2025 20:43:15 +0200 Subject: [PATCH 07/68] Move encryption function to a separate file --- .../internal/sansio/caching_sha2_password.hpp | 90 ++------------ .../sansio/csha2p_encrypt_password.hpp | 110 ++++++++++++++++++ test/unit/test/sansio/handshake/handshake.cpp | 2 + 3 files changed, 119 insertions(+), 83 deletions(-) create mode 100644 include/boost/mysql/impl/internal/sansio/csha2p_encrypt_password.hpp diff --git a/include/boost/mysql/impl/internal/sansio/caching_sha2_password.hpp b/include/boost/mysql/impl/internal/sansio/caching_sha2_password.hpp index 1acd41abe..a468b5e99 100644 --- a/include/boost/mysql/impl/internal/sansio/caching_sha2_password.hpp +++ b/include/boost/mysql/impl/internal/sansio/caching_sha2_password.hpp @@ -20,6 +20,7 @@ #include #include #include +#include #include #include @@ -29,7 +30,6 @@ #include #include #include -#include #include #include #include @@ -98,85 +98,9 @@ inline static_buffer csha2p_hash_password( return res; } -// TODO: we may want to take this to a separate file? -struct bio_deleter +inline error_code translate_openssl_error(unsigned long code) { - void operator()(BIO* bio) const noexcept { BIO_free(bio); } -}; -using unique_bio = std::unique_ptr; - -struct evp_pkey_deleter -{ - void operator()(EVP_PKEY* pkey) const noexcept { EVP_PKEY_free(pkey); } -}; -using unique_evp_pkey = std::unique_ptr; - -struct evp_pkey_ctx_deleter -{ - void operator()(EVP_PKEY_CTX* ctx) const noexcept { EVP_PKEY_CTX_free(ctx); } -}; -using unique_evp_pkey_ctx = std::unique_ptr; - -inline error_code get_last_openssl_error() -{ - return error_code(::ERR_get_error(), asio::error::get_ssl_category()); // TODO: is this OK? -} - -using csha2p_password_buffer = container::small_vector; - -inline error_code csha2p_encrypt_password( - string_view password, - span scramble, - span server_key, - csha2p_password_buffer& output -) -{ - // TODO: test that password.size() == 0u does not cause trouble - // Try to parse the private key. TODO: size check here - unique_bio bio{BIO_new_mem_buf(server_key.data(), server_key.size())}; - if (!bio) - return get_last_openssl_error(); - unique_evp_pkey key(PEM_read_bio_PUBKEY(bio.get(), nullptr, nullptr, nullptr)); - if (!key) - return get_last_openssl_error(); - - // Salt the password, as a NULL-terminated string - csha2p_password_buffer salted_password(password.size() + 1u, 0); - for (std::size_t i = 0; i < password.size(); ++i) - salted_password[i] = password[i] ^ scramble[i % scramble.size()]; - - // Add the NULL terminator. It should be salted, too. Since 0 ^ U = U, - // the byte should be the scramble at the position we're in - salted_password[password.size()] = scramble[password.size() % scramble.size()]; - - // Set up the encryption context - unique_evp_pkey_ctx ctx(EVP_PKEY_CTX_new(key.get(), nullptr)); - if (!ctx) - return get_last_openssl_error(); - if (EVP_PKEY_encrypt_init(ctx.get()) <= 0) - return get_last_openssl_error(); - if (EVP_PKEY_CTX_set_rsa_padding(ctx.get(), RSA_PKCS1_OAEP_PADDING) <= 0) - return get_last_openssl_error(); - - // Encrypt - int max_size = EVP_PKEY_get_size(key.get()); - BOOST_ASSERT(max_size >= 0); - output.resize(max_size); - std::size_t actual_size = static_cast(max_size); - if (EVP_PKEY_encrypt( - ctx.get(), - output.data(), - &actual_size, - salted_password.data(), - salted_password.size() - ) <= 0) - { - return get_last_openssl_error(); - } - output.resize(actual_size); - - // Done - return error_code(); + return error_code(code, asio::error::get_ssl_category()); // TODO: is this OK? } class csha2p_algo @@ -201,10 +125,10 @@ class csha2p_algo span server_key ) { - csha2p_password_buffer buff; - auto ec = csha2p_encrypt_password(password, scramble, server_key, buff); - if (ec) - return ec; + container::small_vector buff; + unsigned long err = csha2p_encrypt_password(password, scramble, server_key, buff); + if (err) + return translate_openssl_error(err); return st.write( string_eof{string_view(reinterpret_cast(buff.data()), buff.size())}, seqnum diff --git a/include/boost/mysql/impl/internal/sansio/csha2p_encrypt_password.hpp b/include/boost/mysql/impl/internal/sansio/csha2p_encrypt_password.hpp new file mode 100644 index 000000000..2a50c3af0 --- /dev/null +++ b/include/boost/mysql/impl/internal/sansio/csha2p_encrypt_password.hpp @@ -0,0 +1,110 @@ +// +// Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BOOST_MYSQL_IMPL_INTERNAL_SANSIO_CSHA2P_ENCRYPT_PASSWORD_HPP +#define BOOST_MYSQL_IMPL_INTERNAL_SANSIO_CSHA2P_ENCRYPT_PASSWORD_HPP + +// Having this in a separate file allows us to mock the OpenSSL API in the tests + +#include +#include + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace boost { +namespace mysql { +namespace detail { + +inline unsigned long csha2p_encrypt_password( + string_view password, + span scramble, + span server_key, + container::small_vector& output +) +{ + // RAII helpers + struct bio_deleter + { + void operator()(BIO* bio) const noexcept { BIO_free(bio); } + }; + using unique_bio = std::unique_ptr; + + struct evp_pkey_deleter + { + void operator()(EVP_PKEY* pkey) const noexcept { EVP_PKEY_free(pkey); } + }; + using unique_evp_pkey = std::unique_ptr; + + struct evp_pkey_ctx_deleter + { + void operator()(EVP_PKEY_CTX* ctx) const noexcept { EVP_PKEY_CTX_free(ctx); } + }; + using unique_evp_pkey_ctx = std::unique_ptr; + + // TODO: test that password.size() == 0u does not cause trouble + // Try to parse the private key. TODO: size check here + unique_bio bio{BIO_new_mem_buf(server_key.data(), server_key.size())}; + if (!bio) + return ERR_get_error(); + unique_evp_pkey key(PEM_read_bio_PUBKEY(bio.get(), nullptr, nullptr, nullptr)); + if (!key) + return ERR_get_error(); + + // Salt the password, as a NULL-terminated string + container::small_vector salted_password(password.size() + 1u, 0); + for (std::size_t i = 0; i < password.size(); ++i) + salted_password[i] = password[i] ^ scramble[i % scramble.size()]; + + // Add the NULL terminator. It should be salted, too. Since 0 ^ U = U, + // the byte should be the scramble at the position we're in + salted_password[password.size()] = scramble[password.size() % scramble.size()]; + + // Set up the encryption context + unique_evp_pkey_ctx ctx(EVP_PKEY_CTX_new(key.get(), nullptr)); + if (!ctx) + return ERR_get_error(); + if (EVP_PKEY_encrypt_init(ctx.get()) <= 0) + return ERR_get_error(); + if (EVP_PKEY_CTX_set_rsa_padding(ctx.get(), RSA_PKCS1_OAEP_PADDING) <= 0) + return ERR_get_error(); + + // Encrypt + int max_size = EVP_PKEY_get_size(key.get()); + BOOST_ASSERT(max_size >= 0); + output.resize(max_size); + std::size_t actual_size = static_cast(max_size); + if (EVP_PKEY_encrypt( + ctx.get(), + output.data(), + &actual_size, + salted_password.data(), + salted_password.size() + ) <= 0) + { + return ERR_get_error(); + } + output.resize(actual_size); + + // Done + return 0u; +} + +} // namespace detail +} // namespace mysql +} // namespace boost + +#endif diff --git a/test/unit/test/sansio/handshake/handshake.cpp b/test/unit/test/sansio/handshake/handshake.cpp index 664dd52b6..ee78f71d5 100644 --- a/test/unit/test/sansio/handshake/handshake.cpp +++ b/test/unit/test/sansio/handshake/handshake.cpp @@ -68,6 +68,8 @@ BOOST_AUTO_TEST_CASE(hello_unknown_plugin) .check(fix, client_errc::unknown_auth_plugin); } +// TODO: check that this is the error even if we get a scramble of != size + // // Errors processing the initial server response // From b26efd6b187e836c8d9bed6a228c9fd5971a6e78 Mon Sep 17 00:00:00 2001 From: Ruben Perez Date: Fri, 9 May 2025 21:25:18 +0200 Subject: [PATCH 08/68] Prototype to test openssl edge cases --- .../sansio/csha2p_encrypt_password.hpp | 4 +- test/unit/CMakeLists.txt | 13 +++ .../test_csha2p_encrypt_password_errors.cpp | 88 +++++++++++++++++++ tools/scripts/file_headers.py | 2 +- 4 files changed, 105 insertions(+), 2 deletions(-) create mode 100644 test/unit/test_csha2p_encrypt_password_errors.cpp diff --git a/include/boost/mysql/impl/internal/sansio/csha2p_encrypt_password.hpp b/include/boost/mysql/impl/internal/sansio/csha2p_encrypt_password.hpp index 2a50c3af0..f157f4736 100644 --- a/include/boost/mysql/impl/internal/sansio/csha2p_encrypt_password.hpp +++ b/include/boost/mysql/impl/internal/sansio/csha2p_encrypt_password.hpp @@ -15,14 +15,16 @@ #include -#include #include +#include #include #include #include #include #include +#include +#include #include namespace boost { diff --git a/test/unit/CMakeLists.txt b/test/unit/CMakeLists.txt index d65841353..5e45d6590 100644 --- a/test/unit/CMakeLists.txt +++ b/test/unit/CMakeLists.txt @@ -154,3 +154,16 @@ add_test( NAME boost_mysql_unittests COMMAND boost_mysql_unittests ) + +# TODO: refactor this +add_executable(boost_mysql_test_csha2p_encrypt_password_errors test_csha2p_encrypt_password_errors.cpp) +get_target_property(boost_mysql_deps Boost::mysql INTERFACE_LINK_LIBRARIES) +list(REMOVE_ITEM boost_mysql_deps OpenSSL::Crypto OpenSSL::SSL) +target_link_libraries(boost_mysql_test_csha2p_encrypt_password_errors PRIVATE ${boost_mysql_deps}) +target_include_directories(boost_mysql_test_csha2p_encrypt_password_errors PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../../include ${OPENSSL_INCLUDE_DIR}) +target_compile_features(boost_mysql INTERFACE cxx_std_11) +boost_mysql_test_target_settings(boost_mysql_test_csha2p_encrypt_password_errors) +add_test( + NAME boost_mysql_test_csha2p_encrypt_password_errors + COMMAND boost_mysql_test_csha2p_encrypt_password_errors +) diff --git a/test/unit/test_csha2p_encrypt_password_errors.cpp b/test/unit/test_csha2p_encrypt_password_errors.cpp new file mode 100644 index 000000000..abd75400e --- /dev/null +++ b/test/unit/test_csha2p_encrypt_password_errors.cpp @@ -0,0 +1,88 @@ +// +// Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#include + +#include +#include + +#include + +using namespace boost::mysql; +using detail::csha2p_encrypt_password; + +namespace { + +constexpr std::uint8_t scramble[20]{}; + +using vector_type = boost::container::small_vector; + +struct +{ + BIO* bio{reinterpret_cast(static_cast(100))}; + EVP_PKEY* key{reinterpret_cast(static_cast(200))}; + EVP_PKEY_CTX* ctx{reinterpret_cast(static_cast(300))}; + int encrypt_init_result{0}; + unsigned long last_error{0}; + +} openssl_mock; + +void test_bio_new_error() +{ + openssl_mock.last_error = 42u; + vector_type out; + unsigned long err = csha2p_encrypt_password("passwd", scramble, {}, out); + BOOST_TEST_EQ(err, 42u); +} + +} // namespace + +BIO* BIO_new_mem_buf(const void*, int) { return openssl_mock.bio; } +int BIO_free(BIO*) { return 0; } + +EVP_PKEY* PEM_read_bio_PUBKEY(BIO* bio, EVP_PKEY**, pem_password_cb*, void*) +{ + BOOST_TEST_EQ(bio, openssl_mock.bio); + return openssl_mock.key; +} +void EVP_PKEY_free(EVP_PKEY*) {} + +EVP_PKEY_CTX* EVP_PKEY_CTX_new(EVP_PKEY* pkey, ENGINE*) +{ + BOOST_TEST_EQ(pkey, openssl_mock.key); + return openssl_mock.ctx; +} +void EVP_PKEY_CTX_free(EVP_PKEY_CTX*) {} +int EVP_PKEY_encrypt_init(EVP_PKEY_CTX* ctx) +{ + BOOST_TEST_EQ(ctx, openssl_mock.ctx); + return openssl_mock.encrypt_init_result; +} +int EVP_PKEY_CTX_set_rsa_padding(EVP_PKEY_CTX* ctx, int) +{ + BOOST_TEST_EQ(ctx, openssl_mock.ctx); + return 0; +} +int EVP_PKEY_get_size(const EVP_PKEY* pkey) +{ + BOOST_TEST_EQ(pkey, openssl_mock.key); + return 256; +} +int EVP_PKEY_encrypt(EVP_PKEY_CTX* ctx, unsigned char*, size_t*, const unsigned char*, size_t) +{ + BOOST_TEST_EQ(ctx, openssl_mock.ctx); + return 0; +} + +unsigned long ERR_get_error() { return openssl_mock.last_error; } + +int main() +{ + test_bio_new_error(); + + return boost::report_errors(); +} diff --git a/tools/scripts/file_headers.py b/tools/scripts/file_headers.py index 8bcd06bcf..df7f42c95 100755 --- a/tools/scripts/file_headers.py +++ b/tools/scripts/file_headers.py @@ -277,7 +277,7 @@ def verify_test_consistency(): for test_type in ('unit', 'integration'): for ftocheck in ('Jamfile', 'CMakeLists.txt'): base_path = path.join(REPO_BASE, 'test', test_type) - tests = glob.glob(base_path + '/**/*.cpp', recursive=True) + tests = glob.glob(base_path + '/test/**/*.cpp', recursive=True) tests = [elm.replace(base_path + '/', '') for elm in tests] with open(path.join(REPO_BASE, 'test', test_type, ftocheck), 'rt') as f: From 17cc582d0d5dd3ed196212d4eec9ab68ee3ed3bb Mon Sep 17 00:00:00 2001 From: Ruben Perez Date: Sat, 10 May 2025 18:35:30 +0200 Subject: [PATCH 09/68] Fixed regression in csha2p hashing --- .../mysql/impl/internal/sansio/caching_sha2_password.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/boost/mysql/impl/internal/sansio/caching_sha2_password.hpp b/include/boost/mysql/impl/internal/sansio/caching_sha2_password.hpp index a468b5e99..6a81caa16 100644 --- a/include/boost/mysql/impl/internal/sansio/caching_sha2_password.hpp +++ b/include/boost/mysql/impl/internal/sansio/caching_sha2_password.hpp @@ -64,9 +64,9 @@ inline void csha2p_hash_password_impl( SHA256(reinterpret_cast(password.data()), password.size(), password_sha.data()); // SHA(password_sha) concat scramble = buffer - std::array buffer; + std::array buffer; SHA256(password_sha.data(), password_sha.size(), buffer.data()); - std::memcpy(buffer.data() + csha2p_hash_size, scramble.data(), csha2p_hash_size); + std::memcpy(buffer.data() + csha2p_hash_size, scramble.data(), scramble.size()); // SHA(SHA(password_sha) concat scramble) = SHA(buffer) = salted_password std::array salted_password; From 4ae5fb99b71763ec1ed188a67bb98e17c1cdf0f7 Mon Sep 17 00:00:00 2001 From: Ruben Perez Date: Sat, 10 May 2025 18:35:52 +0200 Subject: [PATCH 10/68] unit tests now build again --- .../handshake_csha2p_hash_password.cpp | 32 +++---------------- .../handshake/handshake_mnp_hash_password.cpp | 31 +++--------------- 2 files changed, 10 insertions(+), 53 deletions(-) diff --git a/test/unit/test/sansio/handshake/handshake_csha2p_hash_password.cpp b/test/unit/test/sansio/handshake/handshake_csha2p_hash_password.cpp index ffada8bdc..583c97803 100644 --- a/test/unit/test/sansio/handshake/handshake_csha2p_hash_password.cpp +++ b/test/unit/test/sansio/handshake/handshake_csha2p_hash_password.cpp @@ -10,7 +10,6 @@ #include #include "test_common/assert_buffer_equals.hpp" -#include "test_common/printing.hpp" using namespace boost::mysql; using namespace boost::mysql::test; @@ -21,46 +20,25 @@ namespace { BOOST_AUTO_TEST_SUITE(test_handshake_csha2p_hash_password) // Values snooped using the MySQL Python connector -constexpr std::uint8_t challenge[20] = { +// TODO: this doesn't make a lot of sense as a separate test now +constexpr std::uint8_t scramble[20] = { 0x3e, 0x3b, 0x4, 0x55, 0x4, 0x70, 0x16, 0x3a, 0x4c, 0x15, 0x35, 0x3, 0x15, 0x76, 0x73, 0x22, 0x46, 0x8, 0x18, 0x1, }; -constexpr std::uint8_t expected[32] = { +constexpr std::uint8_t hash[32] = { 0xa1, 0xc1, 0xe1, 0xe9, 0x1b, 0xb6, 0x54, 0x4b, 0xa7, 0x37, 0x4b, 0x9c, 0x56, 0x6d, 0x69, 0x3e, 0x6, 0xca, 0x7, 0x2, 0x98, 0xac, 0xd1, 0x6, 0x18, 0xc6, 0x90, 0x38, 0x9d, 0x88, 0xe1, 0x20, }; BOOST_AUTO_TEST_CASE(nonempty_password) { - auto res = csha2p_hash_password("hola", challenge); - BOOST_MYSQL_ASSERT_BUFFER_EQUALS(res.value(), expected); + BOOST_MYSQL_ASSERT_BUFFER_EQUALS(csha2p_hash_password("hola", scramble), hash); } BOOST_AUTO_TEST_CASE(empty_password) { - auto res = csha2p_hash_password("", challenge); - BOOST_MYSQL_ASSERT_BUFFER_EQUALS(res.value(), std::vector()); -} - -BOOST_AUTO_TEST_CASE(bad_challenge_length_nonempty_password) -{ - constexpr std::uint8_t bad_challenge[] = {0x00, 0x01, 0x02}; - auto res = csha2p_hash_password("hola", bad_challenge); - BOOST_TEST(res.error() == client_errc::protocol_value_error); -} - -BOOST_AUTO_TEST_CASE(bad_challenge_length_nempty_password) -{ - constexpr std::uint8_t bad_challenge[] = {0x00, 0x01, 0x02}; - auto res = csha2p_hash_password("", bad_challenge); - BOOST_TEST(res.error() == client_errc::protocol_value_error); -} - -BOOST_AUTO_TEST_CASE(empty_challenge) -{ - auto res = csha2p_hash_password("", {}); - BOOST_TEST(res.error() == client_errc::protocol_value_error); + BOOST_MYSQL_ASSERT_BUFFER_EQUALS(csha2p_hash_password("", scramble), std::vector()); } BOOST_AUTO_TEST_SUITE_END() diff --git a/test/unit/test/sansio/handshake/handshake_mnp_hash_password.cpp b/test/unit/test/sansio/handshake/handshake_mnp_hash_password.cpp index ed1dbbcec..57dd18eb0 100644 --- a/test/unit/test/sansio/handshake/handshake_mnp_hash_password.cpp +++ b/test/unit/test/sansio/handshake/handshake_mnp_hash_password.cpp @@ -20,46 +20,25 @@ namespace { BOOST_AUTO_TEST_SUITE(test_handshake_mnp_hash_password) +// TODO: this doesn't make a lot of sense as a separate test now // Values snooped using Wireshark -constexpr std::uint8_t challenge[20] = { +constexpr std::uint8_t scramble[20] = { 0x79, 0x64, 0x3d, 0x12, 0x1d, 0x71, 0x74, 0x47, 0x5f, 0x48, 0x3e, 0x3e, 0x0b, 0x62, 0x0a, 0x03, 0x3d, 0x27, 0x3a, 0x4c, }; -constexpr std::uint8_t expected[20] = { +constexpr std::uint8_t hash[20] = { 0xf1, 0xb2, 0xfb, 0x1c, 0x8d, 0xe7, 0x5d, 0xb8, 0xeb, 0xa8, 0x12, 0x6a, 0xd1, 0x0f, 0xe9, 0xb1, 0x10, 0x50, 0xd4, 0x28, }; BOOST_AUTO_TEST_CASE(nonempty_password) { - auto res = mnp_hash_password("root", challenge); - BOOST_MYSQL_ASSERT_BUFFER_EQUALS(res.value(), expected); + BOOST_MYSQL_ASSERT_BUFFER_EQUALS(mnp_hash_password("root", scramble), hash); } BOOST_AUTO_TEST_CASE(empty_password) { - auto res = mnp_hash_password("", challenge); - BOOST_MYSQL_ASSERT_BUFFER_EQUALS(res.value(), std::vector()); -} - -BOOST_AUTO_TEST_CASE(bad_challenge_length_nonempty_password) -{ - constexpr std::uint8_t bad_challenge[] = {0x01, 0x02, 0x03}; - auto res = mnp_hash_password("root", bad_challenge); - BOOST_TEST(res.error() == client_errc::protocol_value_error); -} - -BOOST_AUTO_TEST_CASE(bad_challenge_length_empty_password) -{ - constexpr std::uint8_t bad_challenge[] = {0x01, 0x02, 0x03}; - auto res = mnp_hash_password("", bad_challenge); - BOOST_TEST(res.error() == client_errc::protocol_value_error); -} - -BOOST_AUTO_TEST_CASE(empty_challenge) -{ - auto res = mnp_hash_password("root", {}); - BOOST_TEST(res.error() == client_errc::protocol_value_error); + BOOST_MYSQL_ASSERT_BUFFER_EQUALS(mnp_hash_password("", scramble), std::vector()); } BOOST_AUTO_TEST_SUITE_END() From 6514182aca342c14c4f839c6597658700b4f6579 Mon Sep 17 00:00:00 2001 From: Ruben Perez Date: Sat, 10 May 2025 18:53:42 +0200 Subject: [PATCH 11/68] test cases for encrypt --- .../handshake_csha2p_hash_password.cpp | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/test/unit/test/sansio/handshake/handshake_csha2p_hash_password.cpp b/test/unit/test/sansio/handshake/handshake_csha2p_hash_password.cpp index 583c97803..aa1f05f12 100644 --- a/test/unit/test/sansio/handshake/handshake_csha2p_hash_password.cpp +++ b/test/unit/test/sansio/handshake/handshake_csha2p_hash_password.cpp @@ -43,4 +43,37 @@ BOOST_AUTO_TEST_CASE(empty_password) BOOST_AUTO_TEST_SUITE_END() +BOOST_AUTO_TEST_SUITE(test_handshake_csha2p_encrypt_password) + +/** +encrypt password success + success + success password is 20 bytes + success password is > 20 bytes + success password is max length + success password is > max length but key is longer than default max length + password is > 512 (small buffer) + digest is > 512 (small buffer) +error creating buffer (mock) +error loading key + buffer is empty + key is malformed + TODO: should we fuzz this function? + key is not RSA + key is smaller than what we expect? +determining the size of the hash + failure (EVP_PKEY_get_size < 0: mock) + not available (EVP_PKEY_get_size = 0: mock) +error creating ctx (mock) +error setting RSA padding (mock) +encrypting + the returned size is < buffer + the returned size is == buffer + the returned size is > buffer (mock) + encryption fails (probably merge with the one below) + password is too big for encryption (with 2 sizes) +*/ + +BOOST_AUTO_TEST_SUITE_END() + } // namespace \ No newline at end of file From b206dcf2817d2e408fcb87e51ae8b753b4236ea8 Mon Sep 17 00:00:00 2001 From: Ruben Perez Date: Sun, 11 May 2025 16:38:54 +0200 Subject: [PATCH 12/68] unit test strategy --- .../handshake_csha2p_hash_password.cpp | 152 +++++++++++++++++- 1 file changed, 151 insertions(+), 1 deletion(-) diff --git a/test/unit/test/sansio/handshake/handshake_csha2p_hash_password.cpp b/test/unit/test/sansio/handshake/handshake_csha2p_hash_password.cpp index aa1f05f12..bf45041c2 100644 --- a/test/unit/test/sansio/handshake/handshake_csha2p_hash_password.cpp +++ b/test/unit/test/sansio/handshake/handshake_csha2p_hash_password.cpp @@ -8,13 +8,26 @@ #include #include +#include + +#include +#include + +#include +#include +#include +#include +#include #include "test_common/assert_buffer_equals.hpp" using namespace boost::mysql; using namespace boost::mysql::test; +using detail::csha2p_encrypt_password; using detail::csha2p_hash_password; +using buffer_type = boost::container::small_vector; + namespace { BOOST_AUTO_TEST_SUITE(test_handshake_csha2p_hash_password) @@ -43,11 +56,147 @@ BOOST_AUTO_TEST_CASE(empty_password) BOOST_AUTO_TEST_SUITE_END() +// Encrypting with RSA and OAEP padding involves random numbers for padding. +// There isn't a reliable way to seed OpenSSL's random number generators so that +// the output is deterministic. So we do the following: +// 1. We know the server's public and private keys and the password (the constants below). +// 2. We capture a scramble and a corresponding ciphertext using Wireshark. +// 3. We decrypt the ciphertext with OpenSSL to obtain the expected plaintext (the salted password). +// 4. Tests run csha2p_encrypt_password, decrypt its output, and verify that it matches the expected +// plaintext. BOOST_AUTO_TEST_SUITE(test_handshake_csha2p_encrypt_password) +constexpr unsigned char public_key_2048[] = R"%(-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA36OYSpdiy1lFDrdO1Vux +GwjPTo35R2+mXqW2SZV7kH5C6BSCeoTk6STVRBJbgOCabtp5bpUZ+x2bYWOZp4fs +JakC75CN2YTJoYg5z5U6XUBEWn6WNBpvEoSJaUtrzfU69J07uWqB6v0MdJf3JTgd +ILfGKvk2T+maxqUiYObs0BJd5eKJZDlUaf2r4a9KC8zGUZzHdgtZEXlkHVNLEbbD +Ju4KjtCtJCG1NEBAh3oSnNp/Q1FKFywqU7YnEBWI0B9C5UcKNFbg7M35daimXfGp +V7WJKhO9w7iBJYL1SW+PwyUCh3DNsuSm3nLmuwKhTvGQHZJS/5OVdSHgZhjDnk2V +WwIDAQAB +-----END PUBLIC KEY----- +)%"; + +constexpr unsigned char private_key_2048[] = R"%(-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDfo5hKl2LLWUUO +t07VW7EbCM9OjflHb6ZepbZJlXuQfkLoFIJ6hOTpJNVEEluA4Jpu2nlulRn7HZth +Y5mnh+wlqQLvkI3ZhMmhiDnPlTpdQERafpY0Gm8ShIlpS2vN9Tr0nTu5aoHq/Qx0 +l/clOB0gt8Yq+TZP6ZrGpSJg5uzQEl3l4olkOVRp/avhr0oLzMZRnMd2C1kReWQd +U0sRtsMm7gqO0K0kIbU0QECHehKc2n9DUUoXLCpTticQFYjQH0LlRwo0VuDszfl1 +qKZd8alXtYkqE73DuIElgvVJb4/DJQKHcM2y5Kbecua7AqFO8ZAdklL/k5V1IeBm +GMOeTZVbAgMBAAECggEANI0UtDJunKoVeCfK9ofdTiT70dG6yfaKeaMm+pONvZ5t +ymtHXdLsl3x4QM6vgdFFeNcNwdZ3jHKgmHn3GU7vRso4TmMBciOp3bNNImJGnLMF +XN5yHTw47XkHcR6v7m25tNFdv2wvqzBbROqQwMY20gFdJ6v3/z89h4A2W97nttyp +ixcNdSTHOfu6iUceEGi2PjHrvw4STPQeihXFNTnYG7hvlWzAerQ5STx5K2n4JoXz +xI5MuHS6PGj5EBPUoq0+EQhmhORWBdNCMcijpHqobVDjifPRY2JbI3zZHtVjYpGH +otmc72hIDjNW7RX1ePKW/gsq6p2by3U8+4dOdya4AQKBgQDz4/6uo7atJSTgVo3d +Zr/7UJ4Qi2mlc992jLAqTQle6JchhNERoPvb19Qy8BaOz6LcxmuMXnRij05mxdyb +LmoH8TPe6RFLCOJrRapkUjUtRD8dIg6UNFLk98LD5t3o5PoHwNpBFfokRlchwoHL +uBvbEHQkrPX2xPbgla5e7zJLgQKBgQDqvjCUMvmap9+AlqxGFhv51V9dIlKvT0xW +p5KEMVfs3JXCHAM7o0ClZ8NitHXw18E8+iMG4pWw3+FX4K6tKGR+rCeGCUNGuiQT +FzXjrEej+Pnuk8zacjXkbS+PNnEqhpSq6STZVFn2UW+ZWAoq2iAMR7qw0eAjylln +h//Wad3+2wKBgAjwWEtKUM2zyNA4G+b7dxnc8I4mre6UeqI7sdE7FZbW64Mc/RSq +U9DQ7kQXrJv7XDq/Qv3YEGf0XKlDozxEzToRSxdmb23Sm4nW+dHHeY95Kt8EeohQ +CqG9uvO3KHb6vXc/SECOb6aYtWTVXjB7RPoYdklJ1ZH/0hSVJ9ju52cBAoGBAK63 +A90p25F6ZOWGP46iogve/e2JwFTvBnhwnKJ7P1/yBhzFULqwlUsG4euzOR0a2J6T +5kIXnyZYW5ZWimwi5jlJ1Nj0R/h6TqNO4TMlZOTsSMmDhDMKUoZDpeRHtw7ZwAk9 +IcoH+DVXA2L0ngyq8LNzJ8a3TsYUs1pVZNunTC2FAoGARe4x29tdri8akxxwF3BV +bjJ9qRUIfIDK8rGWRdw94vVCB7XVmSWCEchmLqA1DqGYvAhYMYjkXTg9akfBTUQS +s+8JasUuQam8Y88JAfC0QqGbLgUsh0TpRUOXj+YQuoNiMVu14NNgYgFkx71WtvAq +kUmkxr/moPcZ+O1ahVjv/Us= +-----END PRIVATE KEY----- +)%"; + +// Decrypts the output of +std::vector decrypt( + boost::span private_key, + boost::span ciphertext +) +{ + // RAII helpers + struct bio_deleter + { + void operator()(BIO* bio) const noexcept { BIO_free(bio); } + }; + using unique_bio = std::unique_ptr; + + struct evp_pkey_deleter + { + void operator()(EVP_PKEY* pkey) const noexcept { EVP_PKEY_free(pkey); } + }; + using unique_evp_pkey = std::unique_ptr; + + struct evp_pkey_ctx_deleter + { + void operator()(EVP_PKEY_CTX* ctx) const noexcept { EVP_PKEY_CTX_free(ctx); } + }; + using unique_evp_pkey_ctx = std::unique_ptr; + + // Create a BIO with the key + unique_bio bio(BIO_new_mem_buf(private_key.data(), private_key.size())); + BOOST_TEST_REQUIRE(bio != nullptr, "Creating a BIO failed: " << ERR_get_error()); + + // Load the key + unique_evp_pkey pkey(PEM_read_bio_PrivateKey(bio.get(), nullptr, nullptr, nullptr)); + BOOST_TEST_REQUIRE(bio != nullptr, "Loading the key failed: " << ERR_get_error()); + + // Create a decryption context + unique_evp_pkey_ctx ctx(EVP_PKEY_CTX_new(pkey.get(), nullptr)); + BOOST_TEST_REQUIRE(ctx != nullptr, "Creating a context failed: " << ERR_get_error()); + + // Initialize it + BOOST_TEST_REQUIRE( + EVP_PKEY_decrypt_init(ctx.get()) > 0, + "Initializing decryption failed: " << ERR_get_error() + ); + + // Set the padding scheme + BOOST_TEST_REQUIRE( + EVP_PKEY_CTX_set_rsa_padding(ctx.get(), RSA_PKCS1_OAEP_PADDING) > 0, + "Setting the padding scheme failed: " << ERR_get_error() + ); + + // Determine the size of the decrypted buffer + std::size_t out_size = 0; + BOOST_TEST_REQUIRE( + EVP_PKEY_decrypt(ctx.get(), nullptr, &out_size, ciphertext.data(), ciphertext.size()) > 0, + "Determining decryption size failed: " << ERR_get_error() + ); + std::vector res(out_size, 0); + + // Actually decrypt + BOOST_TEST_REQUIRE( + EVP_PKEY_decrypt(ctx.get(), res.data(), &out_size, ciphertext.data(), ciphertext.size()) > 0, + "Decrypting failed: " << ERR_get_error() + ); + res.resize(out_size); + + // Done + return res; +} + +// +BOOST_AUTO_TEST_CASE(password_lt20) +{ + // Setup + constexpr std::uint8_t scramble[] = { + 0x0f, 0x64, 0x4f, 0x2f, 0x2b, 0x3b, 0x27, 0x6b, 0x45, 0x5c, + 0x53, 0x01, 0x13, 0x7e, 0x4f, 0x10, 0x26, 0x23, 0x5d, 0x27, + }; + constexpr std::uint8_t expected_decrypted[] = + {0x6c, 0x17, 0x27, 0x4e, 0x19, 0x4b, 0x78, 0x1b, 0x24, 0x2f, 0x20, 0x76, 0x7c, 0x0c, 0x2b, 0x10}; + buffer_type buff; + + // Call the function + unsigned long err = csha2p_encrypt_password("csha2p_password", scramble, public_key_2048, buff); + + // Verify + BOOST_TEST_REQUIRE(err == 0u); + BOOST_MYSQL_ASSERT_BUFFER_EQUALS(decrypt(private_key_2048, buff), expected_decrypted); +} + /** encrypt password success - success success password is 20 bytes success password is > 20 bytes success password is max length @@ -72,6 +221,7 @@ encrypting the returned size is > buffer (mock) encryption fails (probably merge with the one below) password is too big for encryption (with 2 sizes) +buffer is reset? */ BOOST_AUTO_TEST_SUITE_END() From 36d53d94e07b83de5505ca730a939105959efca5 Mon Sep 17 00:00:00 2001 From: Ruben Perez Date: Fri, 30 May 2025 13:20:52 +0200 Subject: [PATCH 13/68] encrypt success cases 1 --- .../handshake_csha2p_hash_password.cpp | 99 ++++++++++++++++++- 1 file changed, 94 insertions(+), 5 deletions(-) diff --git a/test/unit/test/sansio/handshake/handshake_csha2p_hash_password.cpp b/test/unit/test/sansio/handshake/handshake_csha2p_hash_password.cpp index bf45041c2..2626c59d7 100644 --- a/test/unit/test/sansio/handshake/handshake_csha2p_hash_password.cpp +++ b/test/unit/test/sansio/handshake/handshake_csha2p_hash_password.cpp @@ -6,6 +6,7 @@ // #include +#include #include #include @@ -175,8 +176,8 @@ std::vector decrypt( return res; } -// -BOOST_AUTO_TEST_CASE(password_lt20) +// Password is < length of the scramble +BOOST_AUTO_TEST_CASE(password_shorter_scramble) { // Setup constexpr std::uint8_t scramble[] = { @@ -195,14 +196,102 @@ BOOST_AUTO_TEST_CASE(password_lt20) BOOST_MYSQL_ASSERT_BUFFER_EQUALS(decrypt(private_key_2048, buff), expected_decrypted); } +// Password (with NULL byte) is equal to the length of the scramble +BOOST_AUTO_TEST_CASE(password_same_size_scramble) +{ + // Setup + constexpr std::uint8_t scramble[] = { + 0x0f, 0x64, 0x4f, 0x2f, 0x2b, 0x3b, 0x27, 0x6b, 0x45, 0x5c, + 0x53, 0x01, 0x13, 0x7e, 0x4f, 0x10, 0x26, 0x23, 0x5d, 0x27, + }; + constexpr std::uint8_t expected_decrypted[] = { + 0x67, 0x0e, 0x2d, 0x45, 0x4f, 0x02, 0x15, 0x58, 0x0e, 0x17, + 0x1f, 0x68, 0x7c, 0x0d, 0x20, 0x79, 0x1f, 0x13, 0x17, 0x27, + }; + buffer_type buff; + + // Call the function + unsigned long err = csha2p_encrypt_password("hjbjd923KKLiosoi90J", scramble, public_key_2048, buff); + + // Verify + BOOST_TEST_REQUIRE(err == 0u); + BOOST_MYSQL_ASSERT_BUFFER_EQUALS(decrypt(private_key_2048, buff), expected_decrypted); +} + +// Passwords longer than the scramble use it cyclically +BOOST_AUTO_TEST_CASE(password_longer_scramble) +{ + // Setup + constexpr std::uint8_t scramble[] = { + 0x0f, 0x64, 0x4f, 0x2f, 0x2b, 0x3b, 0x27, 0x6b, 0x45, 0x5c, + 0x53, 0x01, 0x13, 0x7e, 0x4f, 0x10, 0x26, 0x23, 0x5d, 0x27, + }; + constexpr std::uint8_t expected_decrypted[] = { + 0x64, 0x0e, 0x2e, 0x5c, 0x40, 0x52, 0x1f, 0x59, 0x7c, 0x6f, 0x6b, 0x31, 0x79, 0x08, 0x21, 0x7e, 0x6b, + 0x0f, 0x36, 0x46, 0x34, 0x5e, 0x75, 0x70, 0x40, 0x48, 0x4f, 0x0d, 0x7c, 0x6f, 0x1a, 0x4b, 0x50, 0x32, + 0x06, 0x5a, 0x69, 0x0a, 0x37, 0x44, 0x65, 0x17, 0x21, 0x4e, 0x40, 0x57, 0x68, 0x54, 0x24, 0x5c, + }; + buffer_type buff; + + // Call the function + unsigned long err = csha2p_encrypt_password( + "kjaski829380jvnnM,ka;::_kshf93IJCLIJO)jcjsnaklO?a", + scramble, + public_key_2048, + buff + ); + + // Verify + BOOST_TEST_REQUIRE(err == 0u); + BOOST_MYSQL_ASSERT_BUFFER_EQUALS(decrypt(private_key_2048, buff), expected_decrypted); +} + +// The longest password that a 2048 RSA key can encrypt with the OAEP scheme +BOOST_AUTO_TEST_CASE(password_max_size_2048) +{ + // Setup + constexpr std::uint8_t scramble[] = { + 0x0f, 0x64, 0x4f, 0x2f, 0x2b, 0x3b, 0x27, 0x6b, 0x45, 0x5c, + 0x53, 0x01, 0x13, 0x7e, 0x4f, 0x10, 0x26, 0x23, 0x5d, 0x27, + }; + constexpr std::uint8_t expected_decrypted[] = { + 0x67, 0x0e, 0x2d, 0x45, 0x4f, 0x02, 0x15, 0x58, 0x0e, 0x17, 0x1f, 0x6a, 0x79, 0x1c, 0x2b, 0x7b, 0x45, + 0x49, 0x2a, 0x4f, 0x6a, 0x0f, 0x26, 0x56, 0x13, 0x08, 0x1e, 0x58, 0x2a, 0x29, 0x61, 0x76, 0x76, 0x0b, + 0x3c, 0x79, 0x42, 0x4b, 0x34, 0x46, 0x67, 0x2e, 0x0d, 0x64, 0x61, 0x70, 0x6f, 0x28, 0x0c, 0x14, 0x10, + 0x4a, 0x59, 0x37, 0x3a, 0x29, 0x6d, 0x6b, 0x12, 0x17, 0x36, 0x2d, 0x05, 0x66, 0x5b, 0x54, 0x4d, 0x0a, + 0x23, 0x6c, 0x24, 0x32, 0x2a, 0x14, 0x2e, 0x7c, 0x55, 0x49, 0x10, 0x6a, 0x44, 0x0e, 0x24, 0x45, 0x43, + 0x52, 0x52, 0x0e, 0x7c, 0x6f, 0x1a, 0x3c, 0x3a, 0x57, 0x1a, 0x48, 0x6f, 0x6c, 0x17, 0x6c, 0x57, 0x2a, + 0x04, 0x61, 0x43, 0x50, 0x46, 0x02, 0x7d, 0x65, 0x61, 0x32, 0x7c, 0x17, 0x2e, 0x67, 0x4f, 0x42, 0x36, + 0x54, 0x7c, 0x05, 0x24, 0x41, 0x43, 0x5a, 0x4c, 0x03, 0x0c, 0x15, 0x1b, 0x48, 0x50, 0x36, 0x06, 0x5f, + 0x0f, 0x60, 0x08, 0x0e, 0x46, 0x2c, 0x0c, 0x64, 0x63, 0x78, 0x6c, 0x21, 0x2d, 0x35, 0x20, 0x68, 0x64, + 0x1b, 0x26, 0x7f, 0x6e, 0x6b, 0x17, 0x6f, 0x5a, 0x27, 0x07, 0x66, 0x62, 0x72, 0x6d, 0x22, 0x0a, 0x0c, + 0x38, 0x6b, 0x74, 0x09, 0x26, 0x7a, 0x4f, 0x4c, 0x2e, 0x48, 0x66, 0x5d, 0x25, 0x5c, 0x5e, 0x03, 0x13, + 0x23, 0x0d, 0x09, 0x1b, 0x42, 0x5b, 0x37, 0x76, 0x28, 0x15, 0x1a, 0x35, 0x43, 0x65, 0x17, 0x2d, 0x5c, + 0x4f, 0x51, 0x52, 0x3e, 0x0d, 0x16, 0x39, 0x63, 0x59, 0x7e, + }; + buffer_type buff; + + string_view password = + "hjbjd923KKLkjbdkcjwhekiy8393ou2weusidhiahJBKJKHCIHCKJIu9KHO09IJIpojaf0w39jalsjMMKjkjhiue93I=))" + "UXIOJKXNKNhkai8923oiawiakssaknhakhIIHICHIO)CU)" + "IHCKHCKJhisiweioHHJHUCHIIIJIOPkjgwijiosoi9jsu84HHUHCHI9839hdjsbsdjuUHJjbJ"; + + BOOST_TEST(password.size() == 213u); + + // Call the function + unsigned long err = csha2p_encrypt_password(password, scramble, public_key_2048, buff); + + // Verify + BOOST_TEST_REQUIRE(err == 0u); + BOOST_MYSQL_ASSERT_BUFFER_EQUALS(decrypt(private_key_2048, buff), expected_decrypted); +} + /** encrypt password success - success password is 20 bytes - success password is > 20 bytes - success password is max length success password is > max length but key is longer than default max length password is > 512 (small buffer) digest is > 512 (small buffer) + no character causes problems error creating buffer (mock) error loading key buffer is empty From fc3b2589e40a9296459857261f1c6e9ca8edad76 Mon Sep 17 00:00:00 2001 From: Ruben Perez Date: Fri, 30 May 2025 13:32:31 +0200 Subject: [PATCH 14/68] 8192 key --- .../handshake_csha2p_hash_password.cpp | 192 +++++++++++++++++- 1 file changed, 189 insertions(+), 3 deletions(-) diff --git a/test/unit/test/sansio/handshake/handshake_csha2p_hash_password.cpp b/test/unit/test/sansio/handshake/handshake_csha2p_hash_password.cpp index 2626c59d7..1c25f5ee5 100644 --- a/test/unit/test/sansio/handshake/handshake_csha2p_hash_password.cpp +++ b/test/unit/test/sansio/handshake/handshake_csha2p_hash_password.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include #include "test_common/assert_buffer_equals.hpp" @@ -108,6 +109,135 @@ kUmkxr/moPcZ+O1ahVjv/Us= -----END PRIVATE KEY----- )%"; +constexpr unsigned char public_key_8192[] = R"%(-----BEGIN PUBLIC KEY----- +MIIEIjANBgkqhkiG9w0BAQEFAAOCBA8AMIIECgKCBAEA43dhEnCSwC/hAAm9XrZc +t0QaC4bkoSifeiL00+U8xBbAuAYnZSQ4PBbEERnxIRLpgCf0b2SDvCXPJmXB6lKz +1W4vVcw2fVuG4Kmp5C9skhXeGXrXoEOgSxBp8VkWWWB1tbpKmKwdGnh6O0JOwWRq +wWxefR3J9EWBNTz0vvbjGxYWt0XfcCjULPuXuPVlrbZkTsdCl8ncWG8G/OjRNHr3 +f9eiWHb2gA+xsUJfP/iWlUmy+9MiEGBT+JMW2wZAOu7JgVbyZWXV/pkwodP1IVFy +c9RTexnC7Th3dDlT+HcAWXDCTuWwq2KhD/WUnfXO6uZEjyp3aJiJOa7Mceag0B9t +T9d1AZkXQs3PZYn+Sl1cF3hq6e8OoKHZsA95PAtNrXfDe7vkyICB4sp1Dkqg75Ws +arOTC45CWcr/woReqCDYRiSr0aQodlJmSvSIlOz5IAyB40nFzA3VgEgTgHJ4mU7O +QcsOSStmZNvk6Hl9xfUfJi9xTB/vwf7FKWe+IcDGdkU9WV3yJ6UTXPW4TQu25uTc +6WoK4Us8Lk2h7f+Tk2dsyt+VTDOma5qbwTT8U/NKSd8Se5ewS4++ERqaRikLmp0o +AciYxH00Wk3ZLPFUn9/svcA4BAwOCTkYjIQvW/gLDrYyu4qyrgkGXiNs0L2QYB7l +AUTOl5M0Bmez5Nl8SIdaVk4bj5D+4BbHrDZGLMkkP1KWRgNFOJOfLT3JgMPxDwme +n4qFs/HV/d4qh8kzyjITsBRQj1EloqqU+40WJ9X3mIEo7xQ1DPloQMWADDYrO/bX +fFtYnTtnJLV6ndTSdQz0yZ7ubROJFatPy1VQ6AiBeN24sq9iKm6NXuWjszS0paZ/ +JfO+QOTB9D3lVnFzV2yHRIboHNpm/X1eVeXDlzzVTobqVr4eN78bL4+VQiGQav1I +8ebv3Gx9+NO1qVfsWHC89I+hw9HSN69gEyCmeWwIuY8hMGfTBgERVKPkWhSPM0cU +JlkM+ER1Pngz93TR7tWPsp9tYaKMG4DVth0fINRSVsFFkeHu1CLeKboUW8QMoH+A +vPDVnSE2M3ecAeFz0yNXTwVtHbQT2YpYZ88gnYvYcRNdqeOvRKkrGUON0fDGvBnF +xxcuNzZVP2wBEUTkYljnLrJ5zR+uabPgrQiIhTVdDplRRZd6DVRh9s357L5b94tX +wtNAFfHT3AsU/C5p8O6taxckrxYMqNsOJ/6zYp7gUTTdb3e/V1c3xgi/x8+gUrUn +ivGZZdOaWUbSDZf1cz2kfQPsaiDDPVKL7goo0ZA5gnbjkhHUNpGmE5KauHek9rxO +2KX3dpjl3YJyHSmOKv7Lf46Uja0cNWbTh/0nHlG5xtbzBNGvxF90iGmHDmr+u6BT +zwIDAQAB +-----END PUBLIC KEY----- +)%"; + +constexpr unsigned char private_key_8192[] = R"%(-----BEGIN PRIVATE KEY----- +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 +2vrUYCC8gWblftYH+yTWu2bQ4Y1pDw== +-----END PRIVATE KEY----- +)%"; + // Decrypts the output of std::vector decrypt( boost::span private_key, @@ -286,11 +416,67 @@ BOOST_AUTO_TEST_CASE(password_max_size_2048) BOOST_MYSQL_ASSERT_BUFFER_EQUALS(decrypt(private_key_2048, buff), expected_decrypted); } +// Longer passwords are accepted if the key supports them. +// Concretely, passwords longer than 512 (size of our SBO) are supported +BOOST_AUTO_TEST_CASE(password_longer_sbo) +{ + // Setup + constexpr std::uint8_t scramble[] = { + 0x0f, 0x64, 0x4f, 0x2f, 0x2b, 0x3b, 0x27, 0x6b, 0x45, 0x5c, + 0x53, 0x01, 0x13, 0x7e, 0x4f, 0x10, 0x26, 0x23, 0x5d, 0x27, + }; + constexpr std::uint8_t expected_decrypted[] = { + 0x3a, 0x51, 0x7a, 0x1a, 0x1e, 0x0e, 0x12, 0x5e, 0x70, 0x69, 0x66, 0x34, 0x26, 0x4b, 0x7a, 0x25, 0x13, + 0x16, 0x68, 0x12, 0x3a, 0x51, 0x7a, 0x1a, 0x1e, 0x0e, 0x12, 0x5e, 0x70, 0x69, 0x66, 0x34, 0x26, 0x4b, + 0x7a, 0x25, 0x13, 0x16, 0x68, 0x12, 0x3a, 0x51, 0x7a, 0x1a, 0x1e, 0x0e, 0x12, 0x5e, 0x70, 0x69, 0x66, + 0x34, 0x26, 0x4b, 0x7a, 0x25, 0x13, 0x16, 0x68, 0x12, 0x3a, 0x51, 0x7a, 0x1a, 0x1e, 0x0e, 0x12, 0x5e, + 0x70, 0x69, 0x66, 0x34, 0x26, 0x4b, 0x7a, 0x25, 0x13, 0x16, 0x68, 0x12, 0x3a, 0x51, 0x7a, 0x1a, 0x1e, + 0x0e, 0x12, 0x5e, 0x70, 0x69, 0x66, 0x34, 0x26, 0x4b, 0x7a, 0x25, 0x13, 0x16, 0x68, 0x12, 0x3a, 0x51, + 0x7a, 0x1a, 0x1e, 0x0e, 0x12, 0x5e, 0x70, 0x69, 0x66, 0x34, 0x26, 0x4b, 0x7a, 0x25, 0x13, 0x16, 0x68, + 0x12, 0x3a, 0x51, 0x7a, 0x1a, 0x1e, 0x0e, 0x12, 0x5e, 0x70, 0x69, 0x66, 0x34, 0x26, 0x4b, 0x7a, 0x25, + 0x13, 0x16, 0x68, 0x12, 0x3a, 0x51, 0x7a, 0x1a, 0x1e, 0x0e, 0x12, 0x5e, 0x70, 0x69, 0x66, 0x34, 0x26, + 0x4b, 0x7a, 0x25, 0x13, 0x16, 0x68, 0x12, 0x3a, 0x51, 0x7a, 0x1a, 0x1e, 0x0e, 0x12, 0x5e, 0x70, 0x69, + 0x66, 0x34, 0x26, 0x4b, 0x7a, 0x25, 0x13, 0x16, 0x68, 0x12, 0x3a, 0x51, 0x7a, 0x1a, 0x1e, 0x0e, 0x12, + 0x5e, 0x70, 0x69, 0x66, 0x34, 0x26, 0x4b, 0x7a, 0x25, 0x13, 0x16, 0x68, 0x12, 0x3a, 0x51, 0x7a, 0x1a, + 0x1e, 0x0e, 0x12, 0x5e, 0x70, 0x69, 0x66, 0x34, 0x26, 0x4b, 0x7a, 0x25, 0x13, 0x16, 0x68, 0x12, 0x3a, + 0x51, 0x7a, 0x1a, 0x1e, 0x0e, 0x12, 0x5e, 0x70, 0x69, 0x66, 0x34, 0x26, 0x4b, 0x7a, 0x25, 0x13, 0x16, + 0x68, 0x12, 0x3a, 0x51, 0x7a, 0x1a, 0x1e, 0x0e, 0x12, 0x5e, 0x70, 0x69, 0x66, 0x34, 0x26, 0x4b, 0x7a, + 0x25, 0x13, 0x16, 0x68, 0x12, 0x3a, 0x51, 0x7a, 0x1a, 0x1e, 0x0e, 0x12, 0x5e, 0x70, 0x69, 0x66, 0x34, + 0x26, 0x4b, 0x7a, 0x25, 0x13, 0x16, 0x68, 0x12, 0x3a, 0x51, 0x7a, 0x1a, 0x1e, 0x0e, 0x12, 0x5e, 0x70, + 0x69, 0x66, 0x34, 0x26, 0x4b, 0x7a, 0x25, 0x13, 0x16, 0x68, 0x12, 0x3a, 0x51, 0x7a, 0x1a, 0x1e, 0x0e, + 0x12, 0x5e, 0x70, 0x69, 0x66, 0x34, 0x26, 0x4b, 0x7a, 0x25, 0x13, 0x16, 0x68, 0x12, 0x3a, 0x51, 0x7a, + 0x1a, 0x1e, 0x0e, 0x12, 0x5e, 0x70, 0x69, 0x66, 0x34, 0x26, 0x4b, 0x7a, 0x25, 0x13, 0x16, 0x68, 0x12, + 0x3a, 0x51, 0x7a, 0x1a, 0x1e, 0x0e, 0x12, 0x5e, 0x70, 0x69, 0x66, 0x34, 0x26, 0x4b, 0x7a, 0x25, 0x13, + 0x16, 0x68, 0x12, 0x3a, 0x51, 0x7a, 0x1a, 0x1e, 0x0e, 0x12, 0x5e, 0x70, 0x69, 0x66, 0x34, 0x26, 0x4b, + 0x7a, 0x25, 0x13, 0x16, 0x68, 0x12, 0x3a, 0x51, 0x7a, 0x1a, 0x1e, 0x0e, 0x12, 0x5e, 0x70, 0x69, 0x66, + 0x34, 0x26, 0x4b, 0x7a, 0x25, 0x13, 0x16, 0x68, 0x12, 0x3a, 0x51, 0x7a, 0x1a, 0x1e, 0x0e, 0x12, 0x5e, + 0x70, 0x69, 0x66, 0x34, 0x26, 0x4b, 0x7a, 0x25, 0x13, 0x16, 0x68, 0x12, 0x3a, 0x51, 0x7a, 0x1a, 0x1e, + 0x0e, 0x12, 0x5e, 0x70, 0x69, 0x66, 0x34, 0x26, 0x4b, 0x7a, 0x25, 0x13, 0x16, 0x68, 0x12, 0x3a, 0x51, + 0x7a, 0x1a, 0x1e, 0x0e, 0x12, 0x5e, 0x70, 0x69, 0x66, 0x34, 0x26, 0x4b, 0x7a, 0x25, 0x13, 0x16, 0x68, + 0x12, 0x3a, 0x51, 0x7a, 0x1a, 0x1e, 0x0e, 0x12, 0x5e, 0x70, 0x69, 0x66, 0x34, 0x26, 0x4b, 0x7a, 0x25, + 0x13, 0x16, 0x68, 0x12, 0x3a, 0x51, 0x7a, 0x1a, 0x1e, 0x0e, 0x12, 0x5e, 0x70, 0x69, 0x66, 0x34, 0x26, + 0x4b, 0x7a, 0x25, 0x13, 0x16, 0x68, 0x12, 0x3a, 0x51, 0x7a, 0x1a, 0x1e, 0x0e, 0x12, 0x5e, 0x70, 0x69, + 0x66, 0x34, 0x26, 0x4b, 0x7a, 0x25, 0x13, 0x16, 0x68, 0x12, 0x3a, 0x51, 0x7a, 0x1a, 0x1e, 0x0e, 0x12, + 0x5e, 0x70, 0x69, 0x66, 0x34, 0x26, 0x4b, 0x7a, 0x25, 0x13, 0x16, 0x68, 0x12, 0x3a, 0x51, 0x7a, 0x1a, + 0x1e, 0x0e, 0x12, 0x5e, 0x70, 0x69, 0x66, 0x34, 0x26, 0x4b, 0x7a, 0x25, 0x13, 0x16, 0x68, 0x12, 0x3a, + 0x51, 0x7a, 0x1a, 0x1e, 0x0e, 0x12, 0x5e, 0x70, 0x69, 0x66, 0x34, 0x26, 0x4b, 0x7a, 0x25, 0x13, 0x16, + 0x68, 0x12, 0x3a, 0x51, 0x7a, 0x1a, 0x1e, 0x0e, 0x12, 0x5e, 0x70, 0x69, 0x66, 0x34, 0x26, 0x4b, 0x7a, + 0x25, 0x13, 0x16, 0x68, 0x12, 0x0f + }; + buffer_type buff; + + std::string password(600, '5'); + + // Call the function + unsigned long err = csha2p_encrypt_password(password, scramble, public_key_8192, buff); + + // Verify + BOOST_TEST_REQUIRE(err == 0u); + BOOST_MYSQL_ASSERT_BUFFER_EQUALS(decrypt(private_key_8192, buff), expected_decrypted); +} + /** encrypt password success - success password is > max length but key is longer than default max length - password is > 512 (small buffer) - digest is > 512 (small buffer) no character causes problems error creating buffer (mock) error loading key From 07eaee81589b797937de0c388abfaafc91d32c60 Mon Sep 17 00:00:00 2001 From: Ruben Perez Date: Fri, 30 May 2025 13:36:29 +0200 Subject: [PATCH 15/68] all characters test --- .../handshake_csha2p_hash_password.cpp | 42 ++++++++++++++++++- 1 file changed, 40 insertions(+), 2 deletions(-) diff --git a/test/unit/test/sansio/handshake/handshake_csha2p_hash_password.cpp b/test/unit/test/sansio/handshake/handshake_csha2p_hash_password.cpp index 1c25f5ee5..b8e59795a 100644 --- a/test/unit/test/sansio/handshake/handshake_csha2p_hash_password.cpp +++ b/test/unit/test/sansio/handshake/handshake_csha2p_hash_password.cpp @@ -475,9 +475,47 @@ BOOST_AUTO_TEST_CASE(password_longer_sbo) BOOST_MYSQL_ASSERT_BUFFER_EQUALS(decrypt(private_key_8192, buff), expected_decrypted); } +// No character causes trouble +BOOST_AUTO_TEST_CASE(password_all_characters) +{ + // Setup + constexpr std::uint8_t scramble[] = { + 0x0f, 0x64, 0x4f, 0x2f, 0x2b, 0x3b, 0x27, 0x6b, 0x45, 0x5c, + 0x53, 0x01, 0x13, 0x7e, 0x4f, 0x10, 0x26, 0x23, 0x5d, 0x27, + }; + constexpr std::uint8_t expected_decrypted[] = { + 0x0f, 0x65, 0x4d, 0x2c, 0x2f, 0x3e, 0x21, 0x6c, 0x4d, 0x55, 0x59, 0x0a, 0x1f, 0x73, 0x41, 0x1f, 0x36, + 0x32, 0x4f, 0x34, 0x1b, 0x71, 0x59, 0x38, 0x33, 0x22, 0x3d, 0x70, 0x59, 0x41, 0x4d, 0x1e, 0x33, 0x5f, + 0x6d, 0x33, 0x02, 0x06, 0x7b, 0x00, 0x27, 0x4d, 0x65, 0x04, 0x07, 0x16, 0x09, 0x44, 0x75, 0x6d, 0x61, + 0x32, 0x27, 0x4b, 0x79, 0x27, 0x1e, 0x1a, 0x67, 0x1c, 0x33, 0x59, 0x71, 0x10, 0x6b, 0x7a, 0x65, 0x28, + 0x01, 0x19, 0x15, 0x46, 0x5b, 0x37, 0x05, 0x5b, 0x6a, 0x6e, 0x13, 0x68, 0x5f, 0x35, 0x1d, 0x7c, 0x7f, + 0x6e, 0x71, 0x3c, 0x1d, 0x05, 0x09, 0x5a, 0x4f, 0x23, 0x11, 0x4f, 0x46, 0x42, 0x3f, 0x44, 0x6b, 0x01, + 0x29, 0x48, 0x43, 0x52, 0x4d, 0x00, 0x29, 0x31, 0x3d, 0x6e, 0x63, 0x0f, 0x3d, 0x63, 0x52, 0x56, 0x2b, + 0x50, 0x77, 0x1d, 0x35, 0x54, 0x57, 0x46, 0x59, 0x14, 0xc5, 0xdd, 0xd1, 0x82, 0x97, 0xfb, 0xc9, 0x97, + 0xae, 0xaa, 0xd7, 0xac, 0x83, 0xe9, 0xc1, 0xa0, 0xbb, 0xaa, 0xb5, 0xf8, 0xd1, 0xc9, 0xc5, 0x96, 0x8b, + 0xe7, 0xd5, 0x8b, 0xba, 0xbe, 0xc3, 0xb8, 0xaf, 0xc5, 0xed, 0x8c, 0x8f, 0x9e, 0x81, 0xcc, 0xed, 0xf5, + 0xf9, 0xaa, 0xbf, 0xd3, 0xe1, 0xbf, 0x96, 0x92, 0xef, 0x94, 0xbb, 0xd1, 0xf9, 0x98, 0x93, 0x82, 0x9d, + 0xd0, 0xf9, 0xe1, 0xed, 0xbe, 0xd3, 0xbf, 0x8d, 0xd3, 0xe2, 0xe6, 0x9b, 0xe0, 0xc7, 0xad, 0x85, 0xe4, + 0xe7, 0xf6, 0xe9, 0xa4, 0x95, 0x8d, 0x81, 0xd2, 0xc7, 0xab, 0x99, 0xc7, 0xfe, 0xfa, 0x87, 0xfc, 0xd3, + 0xb9, 0x91, 0xf0, 0xcb, 0xda, 0xc5, 0x88, 0xa1, 0xb9, 0xb5, 0xe6, 0xfb, 0x97, 0xa5, 0xfb, 0xca, 0xce, + 0xb3, 0xc8, 0xff, 0x95, 0xbd, 0xdc, 0xdf, 0xce, 0xd1, 0x9c, 0xbd, 0xa5, 0xa9, 0xfa, 0xef, 0x83, 0xb1, + 0xef, 0x26 + }; + buffer_type buff; + + std::string password; + for (int i = 0; i < 256; ++i) + password.push_back(static_cast(i)); + + // Call the function + unsigned long err = csha2p_encrypt_password(password, scramble, public_key_8192, buff); + + // Verify + BOOST_TEST_REQUIRE(err == 0u); + BOOST_MYSQL_ASSERT_BUFFER_EQUALS(decrypt(private_key_8192, buff), expected_decrypted); +} + /** -encrypt password success - no character causes problems error creating buffer (mock) error loading key buffer is empty From dbefe00ff7fc4157c0f6a4545c0a94bdde76be89 Mon Sep 17 00:00:00 2001 From: Ruben Perez Date: Fri, 30 May 2025 14:12:57 +0200 Subject: [PATCH 16/68] Sanitize translate_openssl_error --- .../internal/sansio/caching_sha2_password.hpp | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/include/boost/mysql/impl/internal/sansio/caching_sha2_password.hpp b/include/boost/mysql/impl/internal/sansio/caching_sha2_password.hpp index 6a81caa16..2c4956bb3 100644 --- a/include/boost/mysql/impl/internal/sansio/caching_sha2_password.hpp +++ b/include/boost/mysql/impl/internal/sansio/caching_sha2_password.hpp @@ -26,6 +26,7 @@ #include #include #include +#include #include #include @@ -100,7 +101,21 @@ inline static_buffer csha2p_hash_password( inline error_code translate_openssl_error(unsigned long code) { - return error_code(code, asio::error::get_ssl_category()); // TODO: is this OK? + // TODO: it'd be helpful to include source code info here + // If ERR_SYSTEM_ERROR is true, the error code is a system error. + // This function only exists since OpenSSL 3 +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + if (ERR_SYSTEM_ERROR(code)) + { + return error_code(ERR_GET_REASON(code), system::system_category()); + } +#endif + + // In OpenSSL < 3, error codes > 0x80000000 are reserved for the user, + // so it's unlikely that we will encounter these here. Overflow here + // is implementation-defined behavior (and not UB), so we're fine. + // This is what Asio does, anyway. + return error_code(static_cast(code), asio::error::get_ssl_category()); } class csha2p_algo From b74b2342bb2897ce40913d155d71c2a7d4c6953e Mon Sep 17 00:00:00 2001 From: Ruben Perez Date: Fri, 30 May 2025 14:13:11 +0200 Subject: [PATCH 17/68] empty key buffer test --- .../handshake_csha2p_hash_password.cpp | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/test/unit/test/sansio/handshake/handshake_csha2p_hash_password.cpp b/test/unit/test/sansio/handshake/handshake_csha2p_hash_password.cpp index b8e59795a..755c8e78f 100644 --- a/test/unit/test/sansio/handshake/handshake_csha2p_hash_password.cpp +++ b/test/unit/test/sansio/handshake/handshake_csha2p_hash_password.cpp @@ -515,6 +515,24 @@ BOOST_AUTO_TEST_CASE(password_all_characters) BOOST_MYSQL_ASSERT_BUFFER_EQUALS(decrypt(private_key_8192, buff), expected_decrypted); } +// +// Errors. It's not defined what exact error code will each function return. +// + +// We passed an empty buffer to the key parser +BOOST_AUTO_TEST_CASE(error_key_buffer_empty) +{ + constexpr std::uint8_t scramble[] = { + 0x0f, 0x64, 0x4f, 0x2f, 0x2b, 0x3b, 0x27, 0x6b, 0x45, 0x5c, + 0x53, 0x01, 0x13, 0x7e, 0x4f, 0x10, 0x26, 0x23, 0x5d, 0x27, + }; + buffer_type buff; + unsigned long err = csha2p_encrypt_password("csha2p_password", scramble, {}, buff); + BOOST_TEST(err > 0u); // is an error + BOOST_TEST(err < 0x80000000); // not a system or user-defined error + BOOST_TEST(detail::translate_openssl_error(err).message() != ""); // produces some output +} + /** error creating buffer (mock) error loading key From a5b60db8d909df6db2051923e8a35624426a5765 Mon Sep 17 00:00:00 2001 From: Ruben Perez Date: Fri, 30 May 2025 14:46:48 +0200 Subject: [PATCH 18/68] key errors --- .../handshake_csha2p_hash_password.cpp | 62 +++++++++++++++++-- 1 file changed, 58 insertions(+), 4 deletions(-) diff --git a/test/unit/test/sansio/handshake/handshake_csha2p_hash_password.cpp b/test/unit/test/sansio/handshake/handshake_csha2p_hash_password.cpp index 755c8e78f..f0fef3593 100644 --- a/test/unit/test/sansio/handshake/handshake_csha2p_hash_password.cpp +++ b/test/unit/test/sansio/handshake/handshake_csha2p_hash_password.cpp @@ -533,19 +533,73 @@ BOOST_AUTO_TEST_CASE(error_key_buffer_empty) BOOST_TEST(detail::translate_openssl_error(err).message() != ""); // produces some output } +BOOST_AUTO_TEST_CASE(error_key_malformed) +{ + constexpr std::uint8_t scramble[] = { + 0x0f, 0x64, 0x4f, 0x2f, 0x2b, 0x3b, 0x27, 0x6b, 0x45, 0x5c, + 0x53, 0x01, 0x13, 0x7e, 0x4f, 0x10, 0x26, 0x23, 0x5d, 0x27, + }; + constexpr std::uint8_t key_buffer[] = "-----BEGIN PUBLIC KEY-----zwIDAQAB__kaj0?))="; + buffer_type buff; + unsigned long err = csha2p_encrypt_password("csha2p_password", scramble, key_buffer, buff); + BOOST_TEST(err > 0u); // is an error + BOOST_TEST(err < 0x80000000); // not a system or user-defined error + BOOST_TEST(detail::translate_openssl_error(err).message() != ""); // produces some output +} + +// Passing in a public key type that does not support encryption operations +// (like ECDSA) fails +BOOST_AUTO_TEST_CASE(error_key_doesnt_support_encryption) +{ + constexpr std::uint8_t scramble[] = { + 0x0f, 0x64, 0x4f, 0x2f, 0x2b, 0x3b, 0x27, 0x6b, 0x45, 0x5c, + 0x53, 0x01, 0x13, 0x7e, 0x4f, 0x10, 0x26, 0x23, 0x5d, 0x27, + }; + constexpr unsigned char key_buffer[] = R"%(-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAERDkCI/degPJXEIYYncyvGsTdj9YI +4xQ6KPTzoF+DY2jM09w1TCncxk9XV1eOo44UMDUuK9K01halQy70mohFSQ== +-----END PUBLIC KEY----- +)%"; + + buffer_type buff; + unsigned long err = csha2p_encrypt_password("csha2p_password", scramble, key_buffer, buff); + BOOST_TEST(err > 0u); // is an error + BOOST_TEST(err < 0x80000000); // not a system or user-defined error + BOOST_TEST(detail::translate_openssl_error(err).message() != ""); // produces some output +} + +// Passing in a public key type that allows encryption but is not RSA fails as expected. +// This is a SM2 key, generated by: +// openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:sm2 -out public.pem +// openssl pkey -in private.pem -pubout -out public.pem +BOOST_AUTO_TEST_CASE(error_key_not_rsa) +{ + constexpr std::uint8_t scramble[] = { + 0x0f, 0x64, 0x4f, 0x2f, 0x2b, 0x3b, 0x27, 0x6b, 0x45, 0x5c, + 0x53, 0x01, 0x13, 0x7e, 0x4f, 0x10, 0x26, 0x23, 0x5d, 0x27, + }; + constexpr unsigned char key_buffer[] = R"%(-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAEWGAXSpPHb2bWQjROuegjWPcuVwNW +mVpTh++3j7pnpWUjnFuarvWmWh/H6t96/pTx566FKGxZpLn3H9TLHZJsog== +-----END PUBLIC KEY----- +)%"; + + buffer_type buff; + unsigned long err = csha2p_encrypt_password("csha2p_password", scramble, key_buffer, buff); + BOOST_TEST(err > 0u); // is an error + BOOST_TEST(err < 0x80000000); // not a system or user-defined error + BOOST_TEST(detail::translate_openssl_error(err).message() == ""); // produces some output +} + /** error creating buffer (mock) error loading key - buffer is empty - key is malformed TODO: should we fuzz this function? - key is not RSA key is smaller than what we expect? determining the size of the hash failure (EVP_PKEY_get_size < 0: mock) not available (EVP_PKEY_get_size = 0: mock) error creating ctx (mock) -error setting RSA padding (mock) encrypting the returned size is < buffer the returned size is == buffer From 0b9a1923ea19f8ca2b09464fb309c475a1715c69 Mon Sep 17 00:00:00 2001 From: Ruben Perez Date: Fri, 30 May 2025 17:48:07 +0200 Subject: [PATCH 19/68] empty password --- .../sansio/csha2p_encrypt_password.hpp | 1 - .../handshake_csha2p_hash_password.cpp | 22 ++++++++++++++++++- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/include/boost/mysql/impl/internal/sansio/csha2p_encrypt_password.hpp b/include/boost/mysql/impl/internal/sansio/csha2p_encrypt_password.hpp index f157f4736..25b4d7db0 100644 --- a/include/boost/mysql/impl/internal/sansio/csha2p_encrypt_password.hpp +++ b/include/boost/mysql/impl/internal/sansio/csha2p_encrypt_password.hpp @@ -57,7 +57,6 @@ inline unsigned long csha2p_encrypt_password( }; using unique_evp_pkey_ctx = std::unique_ptr; - // TODO: test that password.size() == 0u does not cause trouble // Try to parse the private key. TODO: size check here unique_bio bio{BIO_new_mem_buf(server_key.data(), server_key.size())}; if (!bio) diff --git a/test/unit/test/sansio/handshake/handshake_csha2p_hash_password.cpp b/test/unit/test/sansio/handshake/handshake_csha2p_hash_password.cpp index f0fef3593..f26d63c3e 100644 --- a/test/unit/test/sansio/handshake/handshake_csha2p_hash_password.cpp +++ b/test/unit/test/sansio/handshake/handshake_csha2p_hash_password.cpp @@ -306,6 +306,26 @@ std::vector decrypt( return res; } +// An empty password does not cause trouble. This is an edge case, since +// an empty password should employ a different workflow +BOOST_AUTO_TEST_CASE(password_empty) +{ + // Setup + constexpr std::uint8_t scramble[] = { + 0x0f, 0x64, 0x4f, 0x2f, 0x2b, 0x3b, 0x27, 0x6b, 0x45, 0x5c, + 0x53, 0x01, 0x13, 0x7e, 0x4f, 0x10, 0x26, 0x23, 0x5d, 0x27, + }; + constexpr std::uint8_t expected_decrypted[] = {0x0f}; + buffer_type buff; + + // Call the function + unsigned long err = csha2p_encrypt_password({}, scramble, public_key_2048, buff); + + // Verify + BOOST_TEST_REQUIRE(err == 0u); + BOOST_MYSQL_ASSERT_BUFFER_EQUALS(decrypt(private_key_2048, buff), expected_decrypted); +} + // Password is < length of the scramble BOOST_AUTO_TEST_CASE(password_shorter_scramble) { @@ -588,7 +608,7 @@ mVpTh++3j7pnpWUjnFuarvWmWh/H6t96/pTx566FKGxZpLn3H9TLHZJsog== unsigned long err = csha2p_encrypt_password("csha2p_password", scramble, key_buffer, buff); BOOST_TEST(err > 0u); // is an error BOOST_TEST(err < 0x80000000); // not a system or user-defined error - BOOST_TEST(detail::translate_openssl_error(err).message() == ""); // produces some output + BOOST_TEST(detail::translate_openssl_error(err).message() != ""); // produces some output } /** From d9bcb589d28e2b9d89ac4b30dfabb2954e56a5c6 Mon Sep 17 00:00:00 2001 From: Ruben Perez Date: Sun, 1 Jun 2025 17:33:11 +0200 Subject: [PATCH 20/68] proper handling of EVP_PKEY_CTX_set_rsa_padding failure --- .../internal/sansio/caching_sha2_password.hpp | 25 +----- .../sansio/csha2p_encrypt_password.hpp | 80 ++++++++++++++++--- .../handshake_csha2p_hash_password.cpp | 66 ++++++++------- 3 files changed, 108 insertions(+), 63 deletions(-) diff --git a/include/boost/mysql/impl/internal/sansio/caching_sha2_password.hpp b/include/boost/mysql/impl/internal/sansio/caching_sha2_password.hpp index 2c4956bb3..881b3399b 100644 --- a/include/boost/mysql/impl/internal/sansio/caching_sha2_password.hpp +++ b/include/boost/mysql/impl/internal/sansio/caching_sha2_password.hpp @@ -99,25 +99,6 @@ inline static_buffer csha2p_hash_password( return res; } -inline error_code translate_openssl_error(unsigned long code) -{ - // TODO: it'd be helpful to include source code info here - // If ERR_SYSTEM_ERROR is true, the error code is a system error. - // This function only exists since OpenSSL 3 -#if OPENSSL_VERSION_NUMBER >= 0x30000000L - if (ERR_SYSTEM_ERROR(code)) - { - return error_code(ERR_GET_REASON(code), system::system_category()); - } -#endif - - // In OpenSSL < 3, error codes > 0x80000000 are reserved for the user, - // so it's unlikely that we will encounter these here. Overflow here - // is implementation-defined behavior (and not UB), so we're fine. - // This is what Asio does, anyway. - return error_code(static_cast(code), asio::error::get_ssl_category()); -} - class csha2p_algo { int resume_point_{0}; @@ -141,9 +122,9 @@ class csha2p_algo ) { container::small_vector buff; - unsigned long err = csha2p_encrypt_password(password, scramble, server_key, buff); - if (err) - return translate_openssl_error(err); + auto ec = csha2p_encrypt_password(password, scramble, server_key, buff, asio::error::ssl_category); + if (ec) + return ec; return st.write( string_eof{string_view(reinterpret_cast(buff.data()), buff.size())}, seqnum diff --git a/include/boost/mysql/impl/internal/sansio/csha2p_encrypt_password.hpp b/include/boost/mysql/impl/internal/sansio/csha2p_encrypt_password.hpp index 25b4d7db0..715bf8a9d 100644 --- a/include/boost/mysql/impl/internal/sansio/csha2p_encrypt_password.hpp +++ b/include/boost/mysql/impl/internal/sansio/csha2p_encrypt_password.hpp @@ -10,13 +10,17 @@ // Having this in a separate file allows us to mock the OpenSSL API in the tests +#include #include #include #include +#include #include #include +#include +#include #include #include @@ -31,11 +35,37 @@ namespace boost { namespace mysql { namespace detail { -inline unsigned long csha2p_encrypt_password( +// The OpenSSL category is passed as parameter to avoid including asio/ssl headers here. +// Doing so would make mocking OpenSSL more difficult (more functions used) +// TODO: give it a try +inline error_code translate_openssl_error( + unsigned long code, + const source_location* loc, + const system::error_category& openssl_category +) +{ + // If ERR_SYSTEM_ERROR is true, the error code is a system error. + // This function only exists since OpenSSL 3 +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + if (ERR_SYSTEM_ERROR(code)) + { + return error_code(ERR_GET_REASON(code), system::system_category(), loc); + } +#endif + + // In OpenSSL < 3, error codes > 0x80000000 are reserved for the user, + // so it's unlikely that we will encounter these here. Overflow here + // is implementation-defined behavior (and not UB), so we're fine. + // This is what Asio does, anyway. + return error_code(static_cast(code), openssl_category, loc); +} + +inline error_code csha2p_encrypt_password( string_view password, span scramble, span server_key, - container::small_vector& output + container::small_vector& output, + const system::error_category& openssl_category ) { // RAII helpers @@ -60,10 +90,16 @@ inline unsigned long csha2p_encrypt_password( // Try to parse the private key. TODO: size check here unique_bio bio{BIO_new_mem_buf(server_key.data(), server_key.size())}; if (!bio) - return ERR_get_error(); + { + static constexpr auto loc = BOOST_CURRENT_LOCATION; + return translate_openssl_error(ERR_get_error(), &loc, openssl_category); + } unique_evp_pkey key(PEM_read_bio_PUBKEY(bio.get(), nullptr, nullptr, nullptr)); if (!key) - return ERR_get_error(); + { + static constexpr auto loc = BOOST_CURRENT_LOCATION; + return translate_openssl_error(ERR_get_error(), &loc, openssl_category); + } // Salt the password, as a NULL-terminated string container::small_vector salted_password(password.size() + 1u, 0); @@ -77,11 +113,34 @@ inline unsigned long csha2p_encrypt_password( // Set up the encryption context unique_evp_pkey_ctx ctx(EVP_PKEY_CTX_new(key.get(), nullptr)); if (!ctx) - return ERR_get_error(); + { + static constexpr auto loc = BOOST_CURRENT_LOCATION; + return translate_openssl_error(ERR_get_error(), &loc, openssl_category); + } if (EVP_PKEY_encrypt_init(ctx.get()) <= 0) - return ERR_get_error(); - if (EVP_PKEY_CTX_set_rsa_padding(ctx.get(), RSA_PKCS1_OAEP_PADDING) <= 0) - return ERR_get_error(); + { + static constexpr auto loc = BOOST_CURRENT_LOCATION; + return translate_openssl_error(ERR_get_error(), &loc, openssl_category); + } + int rsa_pad_res = EVP_PKEY_CTX_set_rsa_padding(ctx.get(), RSA_PKCS1_OAEP_PADDING); + if (rsa_pad_res <= 0) + { + // If the server passed us a key type that does not support encryption, + // OpenSSL returns -2 and does not add an error to the stack (ERR_get_error returns 0). + // This shouldn't happen with real servers, so we re-use an existing error code and set + // the source location to allow diagnosis + static constexpr auto loc = BOOST_CURRENT_LOCATION; + if (rsa_pad_res == -2) + { + return error_code( + static_cast(client_errc::protocol_value_error), + get_client_category(), + &loc + ); + } + else + return translate_openssl_error(ERR_get_error(), &loc, openssl_category); + } // Encrypt int max_size = EVP_PKEY_get_size(key.get()); @@ -96,12 +155,13 @@ inline unsigned long csha2p_encrypt_password( salted_password.size() ) <= 0) { - return ERR_get_error(); + static constexpr auto loc = BOOST_CURRENT_LOCATION; + return translate_openssl_error(ERR_get_error(), &loc, openssl_category); } output.resize(actual_size); // Done - return 0u; + return error_code(); } } // namespace detail diff --git a/test/unit/test/sansio/handshake/handshake_csha2p_hash_password.cpp b/test/unit/test/sansio/handshake/handshake_csha2p_hash_password.cpp index f26d63c3e..509cc58ac 100644 --- a/test/unit/test/sansio/handshake/handshake_csha2p_hash_password.cpp +++ b/test/unit/test/sansio/handshake/handshake_csha2p_hash_password.cpp @@ -11,6 +11,7 @@ #include #include +#include #include #include @@ -22,9 +23,11 @@ #include #include "test_common/assert_buffer_equals.hpp" +#include "test_common/printing.hpp" using namespace boost::mysql; using namespace boost::mysql::test; +using boost::asio::error::ssl_category; using detail::csha2p_encrypt_password; using detail::csha2p_hash_password; @@ -319,10 +322,10 @@ BOOST_AUTO_TEST_CASE(password_empty) buffer_type buff; // Call the function - unsigned long err = csha2p_encrypt_password({}, scramble, public_key_2048, buff); + auto ec = csha2p_encrypt_password({}, scramble, public_key_2048, buff, ssl_category); // Verify - BOOST_TEST_REQUIRE(err == 0u); + BOOST_TEST_REQUIRE(ec == error_code()); BOOST_MYSQL_ASSERT_BUFFER_EQUALS(decrypt(private_key_2048, buff), expected_decrypted); } @@ -339,10 +342,10 @@ BOOST_AUTO_TEST_CASE(password_shorter_scramble) buffer_type buff; // Call the function - unsigned long err = csha2p_encrypt_password("csha2p_password", scramble, public_key_2048, buff); + auto ec = csha2p_encrypt_password("csha2p_password", scramble, public_key_2048, buff, ssl_category); // Verify - BOOST_TEST_REQUIRE(err == 0u); + BOOST_TEST_REQUIRE(ec == error_code()); BOOST_MYSQL_ASSERT_BUFFER_EQUALS(decrypt(private_key_2048, buff), expected_decrypted); } @@ -361,10 +364,10 @@ BOOST_AUTO_TEST_CASE(password_same_size_scramble) buffer_type buff; // Call the function - unsigned long err = csha2p_encrypt_password("hjbjd923KKLiosoi90J", scramble, public_key_2048, buff); + auto ec = csha2p_encrypt_password("hjbjd923KKLiosoi90J", scramble, public_key_2048, buff, ssl_category); // Verify - BOOST_TEST_REQUIRE(err == 0u); + BOOST_TEST_REQUIRE(ec == error_code()); BOOST_MYSQL_ASSERT_BUFFER_EQUALS(decrypt(private_key_2048, buff), expected_decrypted); } @@ -384,15 +387,16 @@ BOOST_AUTO_TEST_CASE(password_longer_scramble) buffer_type buff; // Call the function - unsigned long err = csha2p_encrypt_password( + auto ec = csha2p_encrypt_password( "kjaski829380jvnnM,ka;::_kshf93IJCLIJO)jcjsnaklO?a", scramble, public_key_2048, - buff + buff, + ssl_category ); // Verify - BOOST_TEST_REQUIRE(err == 0u); + BOOST_TEST_REQUIRE(ec == error_code()); BOOST_MYSQL_ASSERT_BUFFER_EQUALS(decrypt(private_key_2048, buff), expected_decrypted); } @@ -429,10 +433,10 @@ BOOST_AUTO_TEST_CASE(password_max_size_2048) BOOST_TEST(password.size() == 213u); // Call the function - unsigned long err = csha2p_encrypt_password(password, scramble, public_key_2048, buff); + auto ec = csha2p_encrypt_password(password, scramble, public_key_2048, buff, ssl_category); // Verify - BOOST_TEST_REQUIRE(err == 0u); + BOOST_TEST_REQUIRE(ec == error_code()); BOOST_MYSQL_ASSERT_BUFFER_EQUALS(decrypt(private_key_2048, buff), expected_decrypted); } @@ -488,10 +492,10 @@ BOOST_AUTO_TEST_CASE(password_longer_sbo) std::string password(600, '5'); // Call the function - unsigned long err = csha2p_encrypt_password(password, scramble, public_key_8192, buff); + auto ec = csha2p_encrypt_password(password, scramble, public_key_8192, buff, ssl_category); // Verify - BOOST_TEST_REQUIRE(err == 0u); + BOOST_TEST_REQUIRE(ec == error_code()); BOOST_MYSQL_ASSERT_BUFFER_EQUALS(decrypt(private_key_8192, buff), expected_decrypted); } @@ -528,10 +532,10 @@ BOOST_AUTO_TEST_CASE(password_all_characters) password.push_back(static_cast(i)); // Call the function - unsigned long err = csha2p_encrypt_password(password, scramble, public_key_8192, buff); + auto ec = csha2p_encrypt_password(password, scramble, public_key_8192, buff, ssl_category); // Verify - BOOST_TEST_REQUIRE(err == 0u); + BOOST_TEST_REQUIRE(ec == error_code()); BOOST_MYSQL_ASSERT_BUFFER_EQUALS(decrypt(private_key_8192, buff), expected_decrypted); } @@ -547,10 +551,10 @@ BOOST_AUTO_TEST_CASE(error_key_buffer_empty) 0x53, 0x01, 0x13, 0x7e, 0x4f, 0x10, 0x26, 0x23, 0x5d, 0x27, }; buffer_type buff; - unsigned long err = csha2p_encrypt_password("csha2p_password", scramble, {}, buff); - BOOST_TEST(err > 0u); // is an error - BOOST_TEST(err < 0x80000000); // not a system or user-defined error - BOOST_TEST(detail::translate_openssl_error(err).message() != ""); // produces some output + auto ec = csha2p_encrypt_password("csha2p_password", scramble, {}, buff, ssl_category); + BOOST_TEST((ec.category() == ssl_category)); + BOOST_TEST(ec.value() > 0u); // is an error + BOOST_TEST(ec.message() != ""); // produces some output } BOOST_AUTO_TEST_CASE(error_key_malformed) @@ -561,10 +565,10 @@ BOOST_AUTO_TEST_CASE(error_key_malformed) }; constexpr std::uint8_t key_buffer[] = "-----BEGIN PUBLIC KEY-----zwIDAQAB__kaj0?))="; buffer_type buff; - unsigned long err = csha2p_encrypt_password("csha2p_password", scramble, key_buffer, buff); - BOOST_TEST(err > 0u); // is an error - BOOST_TEST(err < 0x80000000); // not a system or user-defined error - BOOST_TEST(detail::translate_openssl_error(err).message() != ""); // produces some output + auto ec = csha2p_encrypt_password("csha2p_password", scramble, key_buffer, buff, ssl_category); + BOOST_TEST((ec.category() == ssl_category)); + BOOST_TEST(ec.value() > 0u); // is an error + BOOST_TEST(ec.message() != ""); // produces some output } // Passing in a public key type that does not support encryption operations @@ -582,10 +586,10 @@ MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAERDkCI/degPJXEIYYncyvGsTdj9YI )%"; buffer_type buff; - unsigned long err = csha2p_encrypt_password("csha2p_password", scramble, key_buffer, buff); - BOOST_TEST(err > 0u); // is an error - BOOST_TEST(err < 0x80000000); // not a system or user-defined error - BOOST_TEST(detail::translate_openssl_error(err).message() != ""); // produces some output + auto ec = csha2p_encrypt_password("csha2p_password", scramble, key_buffer, buff, ssl_category); + BOOST_TEST((ec.category() == ssl_category)); + BOOST_TEST(ec.value() > 0u); // is an error + BOOST_TEST(ec.message() != ""); // produces some output } // Passing in a public key type that allows encryption but is not RSA fails as expected. @@ -605,10 +609,8 @@ mVpTh++3j7pnpWUjnFuarvWmWh/H6t96/pTx566FKGxZpLn3H9TLHZJsog== )%"; buffer_type buff; - unsigned long err = csha2p_encrypt_password("csha2p_password", scramble, key_buffer, buff); - BOOST_TEST(err > 0u); // is an error - BOOST_TEST(err < 0x80000000); // not a system or user-defined error - BOOST_TEST(detail::translate_openssl_error(err).message() != ""); // produces some output + auto ec = csha2p_encrypt_password("csha2p_password", scramble, key_buffer, buff, ssl_category); + BOOST_TEST(ec == client_errc::protocol_value_error); // OpenSSL does not provide an error code here } /** @@ -616,6 +618,8 @@ error creating buffer (mock) error loading key TODO: should we fuzz this function? key is smaller than what we expect? + EVP_PKEY_CTX_set_rsa_padding fails with a value != -2 (mock) + TODO: if openssl returns 0 in ERR_get_error, does the error code count as failed, too? determining the size of the hash failure (EVP_PKEY_get_size < 0: mock) not available (EVP_PKEY_get_size = 0: mock) From 985b5a560823041d9998d89e48df7c0d87fdf950 Mon Sep 17 00:00:00 2001 From: Ruben Perez Date: Sun, 1 Jun 2025 17:54:51 +0200 Subject: [PATCH 21/68] make success cases use parametric tests --- .../handshake_csha2p_hash_password.cpp | 397 ++++++++---------- 1 file changed, 177 insertions(+), 220 deletions(-) diff --git a/test/unit/test/sansio/handshake/handshake_csha2p_hash_password.cpp b/test/unit/test/sansio/handshake/handshake_csha2p_hash_password.cpp index 509cc58ac..ff4b07fb0 100644 --- a/test/unit/test/sansio/handshake/handshake_csha2p_hash_password.cpp +++ b/test/unit/test/sansio/handshake/handshake_csha2p_hash_password.cpp @@ -6,6 +6,7 @@ // #include +#include #include #include @@ -309,234 +310,190 @@ std::vector decrypt( return res; } -// An empty password does not cause trouble. This is an edge case, since -// an empty password should employ a different workflow -BOOST_AUTO_TEST_CASE(password_empty) +BOOST_AUTO_TEST_CASE(success) { - // Setup + // Common for all tests constexpr std::uint8_t scramble[] = { 0x0f, 0x64, 0x4f, 0x2f, 0x2b, 0x3b, 0x27, 0x6b, 0x45, 0x5c, 0x53, 0x01, 0x13, 0x7e, 0x4f, 0x10, 0x26, 0x23, 0x5d, 0x27, }; - constexpr std::uint8_t expected_decrypted[] = {0x0f}; - buffer_type buff; - - // Call the function - auto ec = csha2p_encrypt_password({}, scramble, public_key_2048, buff, ssl_category); - - // Verify - BOOST_TEST_REQUIRE(ec == error_code()); - BOOST_MYSQL_ASSERT_BUFFER_EQUALS(decrypt(private_key_2048, buff), expected_decrypted); -} - -// Password is < length of the scramble -BOOST_AUTO_TEST_CASE(password_shorter_scramble) -{ - // Setup - constexpr std::uint8_t scramble[] = { - 0x0f, 0x64, 0x4f, 0x2f, 0x2b, 0x3b, 0x27, 0x6b, 0x45, 0x5c, - 0x53, 0x01, 0x13, 0x7e, 0x4f, 0x10, 0x26, 0x23, 0x5d, 0x27, - }; - constexpr std::uint8_t expected_decrypted[] = - {0x6c, 0x17, 0x27, 0x4e, 0x19, 0x4b, 0x78, 0x1b, 0x24, 0x2f, 0x20, 0x76, 0x7c, 0x0c, 0x2b, 0x10}; - buffer_type buff; - - // Call the function - auto ec = csha2p_encrypt_password("csha2p_password", scramble, public_key_2048, buff, ssl_category); - - // Verify - BOOST_TEST_REQUIRE(ec == error_code()); - BOOST_MYSQL_ASSERT_BUFFER_EQUALS(decrypt(private_key_2048, buff), expected_decrypted); -} - -// Password (with NULL byte) is equal to the length of the scramble -BOOST_AUTO_TEST_CASE(password_same_size_scramble) -{ - // Setup - constexpr std::uint8_t scramble[] = { - 0x0f, 0x64, 0x4f, 0x2f, 0x2b, 0x3b, 0x27, 0x6b, 0x45, 0x5c, - 0x53, 0x01, 0x13, 0x7e, 0x4f, 0x10, 0x26, 0x23, 0x5d, 0x27, - }; - constexpr std::uint8_t expected_decrypted[] = { - 0x67, 0x0e, 0x2d, 0x45, 0x4f, 0x02, 0x15, 0x58, 0x0e, 0x17, - 0x1f, 0x68, 0x7c, 0x0d, 0x20, 0x79, 0x1f, 0x13, 0x17, 0x27, - }; - buffer_type buff; - - // Call the function - auto ec = csha2p_encrypt_password("hjbjd923KKLiosoi90J", scramble, public_key_2048, buff, ssl_category); - - // Verify - BOOST_TEST_REQUIRE(ec == error_code()); - BOOST_MYSQL_ASSERT_BUFFER_EQUALS(decrypt(private_key_2048, buff), expected_decrypted); -} - -// Passwords longer than the scramble use it cyclically -BOOST_AUTO_TEST_CASE(password_longer_scramble) -{ - // Setup - constexpr std::uint8_t scramble[] = { - 0x0f, 0x64, 0x4f, 0x2f, 0x2b, 0x3b, 0x27, 0x6b, 0x45, 0x5c, - 0x53, 0x01, 0x13, 0x7e, 0x4f, 0x10, 0x26, 0x23, 0x5d, 0x27, - }; - constexpr std::uint8_t expected_decrypted[] = { - 0x64, 0x0e, 0x2e, 0x5c, 0x40, 0x52, 0x1f, 0x59, 0x7c, 0x6f, 0x6b, 0x31, 0x79, 0x08, 0x21, 0x7e, 0x6b, - 0x0f, 0x36, 0x46, 0x34, 0x5e, 0x75, 0x70, 0x40, 0x48, 0x4f, 0x0d, 0x7c, 0x6f, 0x1a, 0x4b, 0x50, 0x32, - 0x06, 0x5a, 0x69, 0x0a, 0x37, 0x44, 0x65, 0x17, 0x21, 0x4e, 0x40, 0x57, 0x68, 0x54, 0x24, 0x5c, - }; - buffer_type buff; - - // Call the function - auto ec = csha2p_encrypt_password( - "kjaski829380jvnnM,ka;::_kshf93IJCLIJO)jcjsnaklO?a", - scramble, - public_key_2048, - buff, - ssl_category - ); - - // Verify - BOOST_TEST_REQUIRE(ec == error_code()); - BOOST_MYSQL_ASSERT_BUFFER_EQUALS(decrypt(private_key_2048, buff), expected_decrypted); -} -// The longest password that a 2048 RSA key can encrypt with the OAEP scheme -BOOST_AUTO_TEST_CASE(password_max_size_2048) -{ - // Setup - constexpr std::uint8_t scramble[] = { - 0x0f, 0x64, 0x4f, 0x2f, 0x2b, 0x3b, 0x27, 0x6b, 0x45, 0x5c, - 0x53, 0x01, 0x13, 0x7e, 0x4f, 0x10, 0x26, 0x23, 0x5d, 0x27, - }; - constexpr std::uint8_t expected_decrypted[] = { - 0x67, 0x0e, 0x2d, 0x45, 0x4f, 0x02, 0x15, 0x58, 0x0e, 0x17, 0x1f, 0x6a, 0x79, 0x1c, 0x2b, 0x7b, 0x45, - 0x49, 0x2a, 0x4f, 0x6a, 0x0f, 0x26, 0x56, 0x13, 0x08, 0x1e, 0x58, 0x2a, 0x29, 0x61, 0x76, 0x76, 0x0b, - 0x3c, 0x79, 0x42, 0x4b, 0x34, 0x46, 0x67, 0x2e, 0x0d, 0x64, 0x61, 0x70, 0x6f, 0x28, 0x0c, 0x14, 0x10, - 0x4a, 0x59, 0x37, 0x3a, 0x29, 0x6d, 0x6b, 0x12, 0x17, 0x36, 0x2d, 0x05, 0x66, 0x5b, 0x54, 0x4d, 0x0a, - 0x23, 0x6c, 0x24, 0x32, 0x2a, 0x14, 0x2e, 0x7c, 0x55, 0x49, 0x10, 0x6a, 0x44, 0x0e, 0x24, 0x45, 0x43, - 0x52, 0x52, 0x0e, 0x7c, 0x6f, 0x1a, 0x3c, 0x3a, 0x57, 0x1a, 0x48, 0x6f, 0x6c, 0x17, 0x6c, 0x57, 0x2a, - 0x04, 0x61, 0x43, 0x50, 0x46, 0x02, 0x7d, 0x65, 0x61, 0x32, 0x7c, 0x17, 0x2e, 0x67, 0x4f, 0x42, 0x36, - 0x54, 0x7c, 0x05, 0x24, 0x41, 0x43, 0x5a, 0x4c, 0x03, 0x0c, 0x15, 0x1b, 0x48, 0x50, 0x36, 0x06, 0x5f, - 0x0f, 0x60, 0x08, 0x0e, 0x46, 0x2c, 0x0c, 0x64, 0x63, 0x78, 0x6c, 0x21, 0x2d, 0x35, 0x20, 0x68, 0x64, - 0x1b, 0x26, 0x7f, 0x6e, 0x6b, 0x17, 0x6f, 0x5a, 0x27, 0x07, 0x66, 0x62, 0x72, 0x6d, 0x22, 0x0a, 0x0c, - 0x38, 0x6b, 0x74, 0x09, 0x26, 0x7a, 0x4f, 0x4c, 0x2e, 0x48, 0x66, 0x5d, 0x25, 0x5c, 0x5e, 0x03, 0x13, - 0x23, 0x0d, 0x09, 0x1b, 0x42, 0x5b, 0x37, 0x76, 0x28, 0x15, 0x1a, 0x35, 0x43, 0x65, 0x17, 0x2d, 0x5c, - 0x4f, 0x51, 0x52, 0x3e, 0x0d, 0x16, 0x39, 0x63, 0x59, 0x7e, - }; - buffer_type buff; - - string_view password = - "hjbjd923KKLkjbdkcjwhekiy8393ou2weusidhiahJBKJKHCIHCKJIu9KHO09IJIpojaf0w39jalsjMMKjkjhiue93I=))" - "UXIOJKXNKNhkai8923oiawiakssaknhakhIIHICHIO)CU)" - "IHCKHCKJhisiweioHHJHUCHIIIJIOPkjgwijiosoi9jsu84HHUHCHI9839hdjsbsdjuUHJjbJ"; - - BOOST_TEST(password.size() == 213u); - - // Call the function - auto ec = csha2p_encrypt_password(password, scramble, public_key_2048, buff, ssl_category); - - // Verify - BOOST_TEST_REQUIRE(ec == error_code()); - BOOST_MYSQL_ASSERT_BUFFER_EQUALS(decrypt(private_key_2048, buff), expected_decrypted); -} - -// Longer passwords are accepted if the key supports them. -// Concretely, passwords longer than 512 (size of our SBO) are supported -BOOST_AUTO_TEST_CASE(password_longer_sbo) -{ - // Setup - constexpr std::uint8_t scramble[] = { - 0x0f, 0x64, 0x4f, 0x2f, 0x2b, 0x3b, 0x27, 0x6b, 0x45, 0x5c, - 0x53, 0x01, 0x13, 0x7e, 0x4f, 0x10, 0x26, 0x23, 0x5d, 0x27, - }; - constexpr std::uint8_t expected_decrypted[] = { - 0x3a, 0x51, 0x7a, 0x1a, 0x1e, 0x0e, 0x12, 0x5e, 0x70, 0x69, 0x66, 0x34, 0x26, 0x4b, 0x7a, 0x25, 0x13, - 0x16, 0x68, 0x12, 0x3a, 0x51, 0x7a, 0x1a, 0x1e, 0x0e, 0x12, 0x5e, 0x70, 0x69, 0x66, 0x34, 0x26, 0x4b, - 0x7a, 0x25, 0x13, 0x16, 0x68, 0x12, 0x3a, 0x51, 0x7a, 0x1a, 0x1e, 0x0e, 0x12, 0x5e, 0x70, 0x69, 0x66, - 0x34, 0x26, 0x4b, 0x7a, 0x25, 0x13, 0x16, 0x68, 0x12, 0x3a, 0x51, 0x7a, 0x1a, 0x1e, 0x0e, 0x12, 0x5e, - 0x70, 0x69, 0x66, 0x34, 0x26, 0x4b, 0x7a, 0x25, 0x13, 0x16, 0x68, 0x12, 0x3a, 0x51, 0x7a, 0x1a, 0x1e, - 0x0e, 0x12, 0x5e, 0x70, 0x69, 0x66, 0x34, 0x26, 0x4b, 0x7a, 0x25, 0x13, 0x16, 0x68, 0x12, 0x3a, 0x51, - 0x7a, 0x1a, 0x1e, 0x0e, 0x12, 0x5e, 0x70, 0x69, 0x66, 0x34, 0x26, 0x4b, 0x7a, 0x25, 0x13, 0x16, 0x68, - 0x12, 0x3a, 0x51, 0x7a, 0x1a, 0x1e, 0x0e, 0x12, 0x5e, 0x70, 0x69, 0x66, 0x34, 0x26, 0x4b, 0x7a, 0x25, - 0x13, 0x16, 0x68, 0x12, 0x3a, 0x51, 0x7a, 0x1a, 0x1e, 0x0e, 0x12, 0x5e, 0x70, 0x69, 0x66, 0x34, 0x26, - 0x4b, 0x7a, 0x25, 0x13, 0x16, 0x68, 0x12, 0x3a, 0x51, 0x7a, 0x1a, 0x1e, 0x0e, 0x12, 0x5e, 0x70, 0x69, - 0x66, 0x34, 0x26, 0x4b, 0x7a, 0x25, 0x13, 0x16, 0x68, 0x12, 0x3a, 0x51, 0x7a, 0x1a, 0x1e, 0x0e, 0x12, - 0x5e, 0x70, 0x69, 0x66, 0x34, 0x26, 0x4b, 0x7a, 0x25, 0x13, 0x16, 0x68, 0x12, 0x3a, 0x51, 0x7a, 0x1a, - 0x1e, 0x0e, 0x12, 0x5e, 0x70, 0x69, 0x66, 0x34, 0x26, 0x4b, 0x7a, 0x25, 0x13, 0x16, 0x68, 0x12, 0x3a, - 0x51, 0x7a, 0x1a, 0x1e, 0x0e, 0x12, 0x5e, 0x70, 0x69, 0x66, 0x34, 0x26, 0x4b, 0x7a, 0x25, 0x13, 0x16, - 0x68, 0x12, 0x3a, 0x51, 0x7a, 0x1a, 0x1e, 0x0e, 0x12, 0x5e, 0x70, 0x69, 0x66, 0x34, 0x26, 0x4b, 0x7a, - 0x25, 0x13, 0x16, 0x68, 0x12, 0x3a, 0x51, 0x7a, 0x1a, 0x1e, 0x0e, 0x12, 0x5e, 0x70, 0x69, 0x66, 0x34, - 0x26, 0x4b, 0x7a, 0x25, 0x13, 0x16, 0x68, 0x12, 0x3a, 0x51, 0x7a, 0x1a, 0x1e, 0x0e, 0x12, 0x5e, 0x70, - 0x69, 0x66, 0x34, 0x26, 0x4b, 0x7a, 0x25, 0x13, 0x16, 0x68, 0x12, 0x3a, 0x51, 0x7a, 0x1a, 0x1e, 0x0e, - 0x12, 0x5e, 0x70, 0x69, 0x66, 0x34, 0x26, 0x4b, 0x7a, 0x25, 0x13, 0x16, 0x68, 0x12, 0x3a, 0x51, 0x7a, - 0x1a, 0x1e, 0x0e, 0x12, 0x5e, 0x70, 0x69, 0x66, 0x34, 0x26, 0x4b, 0x7a, 0x25, 0x13, 0x16, 0x68, 0x12, - 0x3a, 0x51, 0x7a, 0x1a, 0x1e, 0x0e, 0x12, 0x5e, 0x70, 0x69, 0x66, 0x34, 0x26, 0x4b, 0x7a, 0x25, 0x13, - 0x16, 0x68, 0x12, 0x3a, 0x51, 0x7a, 0x1a, 0x1e, 0x0e, 0x12, 0x5e, 0x70, 0x69, 0x66, 0x34, 0x26, 0x4b, - 0x7a, 0x25, 0x13, 0x16, 0x68, 0x12, 0x3a, 0x51, 0x7a, 0x1a, 0x1e, 0x0e, 0x12, 0x5e, 0x70, 0x69, 0x66, - 0x34, 0x26, 0x4b, 0x7a, 0x25, 0x13, 0x16, 0x68, 0x12, 0x3a, 0x51, 0x7a, 0x1a, 0x1e, 0x0e, 0x12, 0x5e, - 0x70, 0x69, 0x66, 0x34, 0x26, 0x4b, 0x7a, 0x25, 0x13, 0x16, 0x68, 0x12, 0x3a, 0x51, 0x7a, 0x1a, 0x1e, - 0x0e, 0x12, 0x5e, 0x70, 0x69, 0x66, 0x34, 0x26, 0x4b, 0x7a, 0x25, 0x13, 0x16, 0x68, 0x12, 0x3a, 0x51, - 0x7a, 0x1a, 0x1e, 0x0e, 0x12, 0x5e, 0x70, 0x69, 0x66, 0x34, 0x26, 0x4b, 0x7a, 0x25, 0x13, 0x16, 0x68, - 0x12, 0x3a, 0x51, 0x7a, 0x1a, 0x1e, 0x0e, 0x12, 0x5e, 0x70, 0x69, 0x66, 0x34, 0x26, 0x4b, 0x7a, 0x25, - 0x13, 0x16, 0x68, 0x12, 0x3a, 0x51, 0x7a, 0x1a, 0x1e, 0x0e, 0x12, 0x5e, 0x70, 0x69, 0x66, 0x34, 0x26, - 0x4b, 0x7a, 0x25, 0x13, 0x16, 0x68, 0x12, 0x3a, 0x51, 0x7a, 0x1a, 0x1e, 0x0e, 0x12, 0x5e, 0x70, 0x69, - 0x66, 0x34, 0x26, 0x4b, 0x7a, 0x25, 0x13, 0x16, 0x68, 0x12, 0x3a, 0x51, 0x7a, 0x1a, 0x1e, 0x0e, 0x12, - 0x5e, 0x70, 0x69, 0x66, 0x34, 0x26, 0x4b, 0x7a, 0x25, 0x13, 0x16, 0x68, 0x12, 0x3a, 0x51, 0x7a, 0x1a, - 0x1e, 0x0e, 0x12, 0x5e, 0x70, 0x69, 0x66, 0x34, 0x26, 0x4b, 0x7a, 0x25, 0x13, 0x16, 0x68, 0x12, 0x3a, - 0x51, 0x7a, 0x1a, 0x1e, 0x0e, 0x12, 0x5e, 0x70, 0x69, 0x66, 0x34, 0x26, 0x4b, 0x7a, 0x25, 0x13, 0x16, - 0x68, 0x12, 0x3a, 0x51, 0x7a, 0x1a, 0x1e, 0x0e, 0x12, 0x5e, 0x70, 0x69, 0x66, 0x34, 0x26, 0x4b, 0x7a, - 0x25, 0x13, 0x16, 0x68, 0x12, 0x0f - }; - buffer_type buff; - - std::string password(600, '5'); - - // Call the function - auto ec = csha2p_encrypt_password(password, scramble, public_key_8192, buff, ssl_category); - - // Verify - BOOST_TEST_REQUIRE(ec == error_code()); - BOOST_MYSQL_ASSERT_BUFFER_EQUALS(decrypt(private_key_8192, buff), expected_decrypted); -} - -// No character causes trouble -BOOST_AUTO_TEST_CASE(password_all_characters) -{ - // Setup - constexpr std::uint8_t scramble[] = { - 0x0f, 0x64, 0x4f, 0x2f, 0x2b, 0x3b, 0x27, 0x6b, 0x45, 0x5c, - 0x53, 0x01, 0x13, 0x7e, 0x4f, 0x10, 0x26, 0x23, 0x5d, 0x27, - }; - constexpr std::uint8_t expected_decrypted[] = { - 0x0f, 0x65, 0x4d, 0x2c, 0x2f, 0x3e, 0x21, 0x6c, 0x4d, 0x55, 0x59, 0x0a, 0x1f, 0x73, 0x41, 0x1f, 0x36, - 0x32, 0x4f, 0x34, 0x1b, 0x71, 0x59, 0x38, 0x33, 0x22, 0x3d, 0x70, 0x59, 0x41, 0x4d, 0x1e, 0x33, 0x5f, - 0x6d, 0x33, 0x02, 0x06, 0x7b, 0x00, 0x27, 0x4d, 0x65, 0x04, 0x07, 0x16, 0x09, 0x44, 0x75, 0x6d, 0x61, - 0x32, 0x27, 0x4b, 0x79, 0x27, 0x1e, 0x1a, 0x67, 0x1c, 0x33, 0x59, 0x71, 0x10, 0x6b, 0x7a, 0x65, 0x28, - 0x01, 0x19, 0x15, 0x46, 0x5b, 0x37, 0x05, 0x5b, 0x6a, 0x6e, 0x13, 0x68, 0x5f, 0x35, 0x1d, 0x7c, 0x7f, - 0x6e, 0x71, 0x3c, 0x1d, 0x05, 0x09, 0x5a, 0x4f, 0x23, 0x11, 0x4f, 0x46, 0x42, 0x3f, 0x44, 0x6b, 0x01, - 0x29, 0x48, 0x43, 0x52, 0x4d, 0x00, 0x29, 0x31, 0x3d, 0x6e, 0x63, 0x0f, 0x3d, 0x63, 0x52, 0x56, 0x2b, - 0x50, 0x77, 0x1d, 0x35, 0x54, 0x57, 0x46, 0x59, 0x14, 0xc5, 0xdd, 0xd1, 0x82, 0x97, 0xfb, 0xc9, 0x97, - 0xae, 0xaa, 0xd7, 0xac, 0x83, 0xe9, 0xc1, 0xa0, 0xbb, 0xaa, 0xb5, 0xf8, 0xd1, 0xc9, 0xc5, 0x96, 0x8b, - 0xe7, 0xd5, 0x8b, 0xba, 0xbe, 0xc3, 0xb8, 0xaf, 0xc5, 0xed, 0x8c, 0x8f, 0x9e, 0x81, 0xcc, 0xed, 0xf5, - 0xf9, 0xaa, 0xbf, 0xd3, 0xe1, 0xbf, 0x96, 0x92, 0xef, 0x94, 0xbb, 0xd1, 0xf9, 0x98, 0x93, 0x82, 0x9d, - 0xd0, 0xf9, 0xe1, 0xed, 0xbe, 0xd3, 0xbf, 0x8d, 0xd3, 0xe2, 0xe6, 0x9b, 0xe0, 0xc7, 0xad, 0x85, 0xe4, - 0xe7, 0xf6, 0xe9, 0xa4, 0x95, 0x8d, 0x81, 0xd2, 0xc7, 0xab, 0x99, 0xc7, 0xfe, 0xfa, 0x87, 0xfc, 0xd3, - 0xb9, 0x91, 0xf0, 0xcb, 0xda, 0xc5, 0x88, 0xa1, 0xb9, 0xb5, 0xe6, 0xfb, 0x97, 0xa5, 0xfb, 0xca, 0xce, - 0xb3, 0xc8, 0xff, 0x95, 0xbd, 0xdc, 0xdf, 0xce, 0xd1, 0x9c, 0xbd, 0xa5, 0xa9, 0xfa, 0xef, 0x83, 0xb1, - 0xef, 0x26 - }; - buffer_type buff; - - std::string password; + // A password with all the possible ASCII values + std::string all_chars_password; for (int i = 0; i < 256; ++i) - password.push_back(static_cast(i)); + all_chars_password.push_back(static_cast(i)); - // Call the function - auto ec = csha2p_encrypt_password(password, scramble, public_key_8192, buff, ssl_category); + struct + { + string_view name; + std::string password; + boost::span public_key; + boost::span private_key; + std::vector expected_decrypted; + } test_cases[] = { + // clang-format off + // An empty password does not cause trouble. This is an edge case, since + // an empty password should employ a different workflow + { + "password_empty", + {}, + public_key_2048, + private_key_2048, + {0x0f} + }, + + // Password is < length of the scramble + { + "password_shorter_scramble", + "csha2p_password", + public_key_2048, + private_key_2048, + { + 0x6c, 0x17, 0x27, 0x4e, 0x19, 0x4b, 0x78, 0x1b, 0x24, 0x2f, 0x20, 0x76, 0x7c, 0x0c, 0x2b, 0x10 + } + }, + + // Password (with NULL byte) is equal to the length of the scramble + { + "password_same_size_scramble", + "hjbjd923KKLiosoi90J", + public_key_2048, + private_key_2048, + { + 0x67, 0x0e, 0x2d, 0x45, 0x4f, 0x02, 0x15, 0x58, 0x0e, 0x17, + 0x1f, 0x68, 0x7c, 0x0d, 0x20, 0x79, 0x1f, 0x13, 0x17, 0x27 + } + }, + + // Passwords longer than the scramble use it cyclically + { + "password_longer_scramble", + "kjaski829380jvnnM,ka;::_kshf93IJCLIJO)jcjsnaklO?a", + public_key_2048, + private_key_2048, + { + 0x64, 0x0e, 0x2e, 0x5c, 0x40, 0x52, 0x1f, 0x59, 0x7c, 0x6f, 0x6b, 0x31, 0x79, 0x08, 0x21, 0x7e, 0x6b, + 0x0f, 0x36, 0x46, 0x34, 0x5e, 0x75, 0x70, 0x40, 0x48, 0x4f, 0x0d, 0x7c, 0x6f, 0x1a, 0x4b, 0x50, 0x32, + 0x06, 0x5a, 0x69, 0x0a, 0x37, 0x44, 0x65, 0x17, 0x21, 0x4e, 0x40, 0x57, 0x68, 0x54, 0x24, 0x5c, + } + }, + + // The longest password that a 2048 RSA key can encrypt with the OAEP scheme + { + "password_max_size_2048", + "hjbjd923KKLkjbdkcjwhekiy8393ou2weusidhiahJBKJKHCIHCKJIu9KHO09IJIpojaf0w39jalsjMMKjkjhiue93I=))" + "UXIOJKXNKNhkai8923oiawiakssaknhakhIIHICHIO)CU)" + "IHCKHCKJhisiweioHHJHUCHIIIJIOPkjgwijiosoi9jsu84HHUHCHI9839hdjsbsdjuUHJjbJ", + public_key_2048, + private_key_2048, + { + 0x67, 0x0e, 0x2d, 0x45, 0x4f, 0x02, 0x15, 0x58, 0x0e, 0x17, 0x1f, 0x6a, 0x79, 0x1c, 0x2b, 0x7b, 0x45, + 0x49, 0x2a, 0x4f, 0x6a, 0x0f, 0x26, 0x56, 0x13, 0x08, 0x1e, 0x58, 0x2a, 0x29, 0x61, 0x76, 0x76, 0x0b, + 0x3c, 0x79, 0x42, 0x4b, 0x34, 0x46, 0x67, 0x2e, 0x0d, 0x64, 0x61, 0x70, 0x6f, 0x28, 0x0c, 0x14, 0x10, + 0x4a, 0x59, 0x37, 0x3a, 0x29, 0x6d, 0x6b, 0x12, 0x17, 0x36, 0x2d, 0x05, 0x66, 0x5b, 0x54, 0x4d, 0x0a, + 0x23, 0x6c, 0x24, 0x32, 0x2a, 0x14, 0x2e, 0x7c, 0x55, 0x49, 0x10, 0x6a, 0x44, 0x0e, 0x24, 0x45, 0x43, + 0x52, 0x52, 0x0e, 0x7c, 0x6f, 0x1a, 0x3c, 0x3a, 0x57, 0x1a, 0x48, 0x6f, 0x6c, 0x17, 0x6c, 0x57, 0x2a, + 0x04, 0x61, 0x43, 0x50, 0x46, 0x02, 0x7d, 0x65, 0x61, 0x32, 0x7c, 0x17, 0x2e, 0x67, 0x4f, 0x42, 0x36, + 0x54, 0x7c, 0x05, 0x24, 0x41, 0x43, 0x5a, 0x4c, 0x03, 0x0c, 0x15, 0x1b, 0x48, 0x50, 0x36, 0x06, 0x5f, + 0x0f, 0x60, 0x08, 0x0e, 0x46, 0x2c, 0x0c, 0x64, 0x63, 0x78, 0x6c, 0x21, 0x2d, 0x35, 0x20, 0x68, 0x64, + 0x1b, 0x26, 0x7f, 0x6e, 0x6b, 0x17, 0x6f, 0x5a, 0x27, 0x07, 0x66, 0x62, 0x72, 0x6d, 0x22, 0x0a, 0x0c, + 0x38, 0x6b, 0x74, 0x09, 0x26, 0x7a, 0x4f, 0x4c, 0x2e, 0x48, 0x66, 0x5d, 0x25, 0x5c, 0x5e, 0x03, 0x13, + 0x23, 0x0d, 0x09, 0x1b, 0x42, 0x5b, 0x37, 0x76, 0x28, 0x15, 0x1a, 0x35, 0x43, 0x65, 0x17, 0x2d, 0x5c, + 0x4f, 0x51, 0x52, 0x3e, 0x0d, 0x16, 0x39, 0x63, 0x59, 0x7e, + } + }, + + // Longer passwords are accepted if the key supports them. + // Concretely, passwords longer than 512 (size of our SBO) are supported + { + "password_longer_sbo", + std::string(600, '5'), + public_key_8192, + private_key_8192, + { + 0x3a, 0x51, 0x7a, 0x1a, 0x1e, 0x0e, 0x12, 0x5e, 0x70, 0x69, 0x66, 0x34, 0x26, 0x4b, 0x7a, 0x25, 0x13, + 0x16, 0x68, 0x12, 0x3a, 0x51, 0x7a, 0x1a, 0x1e, 0x0e, 0x12, 0x5e, 0x70, 0x69, 0x66, 0x34, 0x26, 0x4b, + 0x7a, 0x25, 0x13, 0x16, 0x68, 0x12, 0x3a, 0x51, 0x7a, 0x1a, 0x1e, 0x0e, 0x12, 0x5e, 0x70, 0x69, 0x66, + 0x34, 0x26, 0x4b, 0x7a, 0x25, 0x13, 0x16, 0x68, 0x12, 0x3a, 0x51, 0x7a, 0x1a, 0x1e, 0x0e, 0x12, 0x5e, + 0x70, 0x69, 0x66, 0x34, 0x26, 0x4b, 0x7a, 0x25, 0x13, 0x16, 0x68, 0x12, 0x3a, 0x51, 0x7a, 0x1a, 0x1e, + 0x0e, 0x12, 0x5e, 0x70, 0x69, 0x66, 0x34, 0x26, 0x4b, 0x7a, 0x25, 0x13, 0x16, 0x68, 0x12, 0x3a, 0x51, + 0x7a, 0x1a, 0x1e, 0x0e, 0x12, 0x5e, 0x70, 0x69, 0x66, 0x34, 0x26, 0x4b, 0x7a, 0x25, 0x13, 0x16, 0x68, + 0x12, 0x3a, 0x51, 0x7a, 0x1a, 0x1e, 0x0e, 0x12, 0x5e, 0x70, 0x69, 0x66, 0x34, 0x26, 0x4b, 0x7a, 0x25, + 0x13, 0x16, 0x68, 0x12, 0x3a, 0x51, 0x7a, 0x1a, 0x1e, 0x0e, 0x12, 0x5e, 0x70, 0x69, 0x66, 0x34, 0x26, + 0x4b, 0x7a, 0x25, 0x13, 0x16, 0x68, 0x12, 0x3a, 0x51, 0x7a, 0x1a, 0x1e, 0x0e, 0x12, 0x5e, 0x70, 0x69, + 0x66, 0x34, 0x26, 0x4b, 0x7a, 0x25, 0x13, 0x16, 0x68, 0x12, 0x3a, 0x51, 0x7a, 0x1a, 0x1e, 0x0e, 0x12, + 0x5e, 0x70, 0x69, 0x66, 0x34, 0x26, 0x4b, 0x7a, 0x25, 0x13, 0x16, 0x68, 0x12, 0x3a, 0x51, 0x7a, 0x1a, + 0x1e, 0x0e, 0x12, 0x5e, 0x70, 0x69, 0x66, 0x34, 0x26, 0x4b, 0x7a, 0x25, 0x13, 0x16, 0x68, 0x12, 0x3a, + 0x51, 0x7a, 0x1a, 0x1e, 0x0e, 0x12, 0x5e, 0x70, 0x69, 0x66, 0x34, 0x26, 0x4b, 0x7a, 0x25, 0x13, 0x16, + 0x68, 0x12, 0x3a, 0x51, 0x7a, 0x1a, 0x1e, 0x0e, 0x12, 0x5e, 0x70, 0x69, 0x66, 0x34, 0x26, 0x4b, 0x7a, + 0x25, 0x13, 0x16, 0x68, 0x12, 0x3a, 0x51, 0x7a, 0x1a, 0x1e, 0x0e, 0x12, 0x5e, 0x70, 0x69, 0x66, 0x34, + 0x26, 0x4b, 0x7a, 0x25, 0x13, 0x16, 0x68, 0x12, 0x3a, 0x51, 0x7a, 0x1a, 0x1e, 0x0e, 0x12, 0x5e, 0x70, + 0x69, 0x66, 0x34, 0x26, 0x4b, 0x7a, 0x25, 0x13, 0x16, 0x68, 0x12, 0x3a, 0x51, 0x7a, 0x1a, 0x1e, 0x0e, + 0x12, 0x5e, 0x70, 0x69, 0x66, 0x34, 0x26, 0x4b, 0x7a, 0x25, 0x13, 0x16, 0x68, 0x12, 0x3a, 0x51, 0x7a, + 0x1a, 0x1e, 0x0e, 0x12, 0x5e, 0x70, 0x69, 0x66, 0x34, 0x26, 0x4b, 0x7a, 0x25, 0x13, 0x16, 0x68, 0x12, + 0x3a, 0x51, 0x7a, 0x1a, 0x1e, 0x0e, 0x12, 0x5e, 0x70, 0x69, 0x66, 0x34, 0x26, 0x4b, 0x7a, 0x25, 0x13, + 0x16, 0x68, 0x12, 0x3a, 0x51, 0x7a, 0x1a, 0x1e, 0x0e, 0x12, 0x5e, 0x70, 0x69, 0x66, 0x34, 0x26, 0x4b, + 0x7a, 0x25, 0x13, 0x16, 0x68, 0x12, 0x3a, 0x51, 0x7a, 0x1a, 0x1e, 0x0e, 0x12, 0x5e, 0x70, 0x69, 0x66, + 0x34, 0x26, 0x4b, 0x7a, 0x25, 0x13, 0x16, 0x68, 0x12, 0x3a, 0x51, 0x7a, 0x1a, 0x1e, 0x0e, 0x12, 0x5e, + 0x70, 0x69, 0x66, 0x34, 0x26, 0x4b, 0x7a, 0x25, 0x13, 0x16, 0x68, 0x12, 0x3a, 0x51, 0x7a, 0x1a, 0x1e, + 0x0e, 0x12, 0x5e, 0x70, 0x69, 0x66, 0x34, 0x26, 0x4b, 0x7a, 0x25, 0x13, 0x16, 0x68, 0x12, 0x3a, 0x51, + 0x7a, 0x1a, 0x1e, 0x0e, 0x12, 0x5e, 0x70, 0x69, 0x66, 0x34, 0x26, 0x4b, 0x7a, 0x25, 0x13, 0x16, 0x68, + 0x12, 0x3a, 0x51, 0x7a, 0x1a, 0x1e, 0x0e, 0x12, 0x5e, 0x70, 0x69, 0x66, 0x34, 0x26, 0x4b, 0x7a, 0x25, + 0x13, 0x16, 0x68, 0x12, 0x3a, 0x51, 0x7a, 0x1a, 0x1e, 0x0e, 0x12, 0x5e, 0x70, 0x69, 0x66, 0x34, 0x26, + 0x4b, 0x7a, 0x25, 0x13, 0x16, 0x68, 0x12, 0x3a, 0x51, 0x7a, 0x1a, 0x1e, 0x0e, 0x12, 0x5e, 0x70, 0x69, + 0x66, 0x34, 0x26, 0x4b, 0x7a, 0x25, 0x13, 0x16, 0x68, 0x12, 0x3a, 0x51, 0x7a, 0x1a, 0x1e, 0x0e, 0x12, + 0x5e, 0x70, 0x69, 0x66, 0x34, 0x26, 0x4b, 0x7a, 0x25, 0x13, 0x16, 0x68, 0x12, 0x3a, 0x51, 0x7a, 0x1a, + 0x1e, 0x0e, 0x12, 0x5e, 0x70, 0x69, 0x66, 0x34, 0x26, 0x4b, 0x7a, 0x25, 0x13, 0x16, 0x68, 0x12, 0x3a, + 0x51, 0x7a, 0x1a, 0x1e, 0x0e, 0x12, 0x5e, 0x70, 0x69, 0x66, 0x34, 0x26, 0x4b, 0x7a, 0x25, 0x13, 0x16, + 0x68, 0x12, 0x3a, 0x51, 0x7a, 0x1a, 0x1e, 0x0e, 0x12, 0x5e, 0x70, 0x69, 0x66, 0x34, 0x26, 0x4b, 0x7a, + 0x25, 0x13, 0x16, 0x68, 0x12, 0x0f + } + }, + + // No character causes trouble + { + "password_all_characters", + all_chars_password, + public_key_8192, + private_key_8192, + { + 0x0f, 0x65, 0x4d, 0x2c, 0x2f, 0x3e, 0x21, 0x6c, 0x4d, 0x55, 0x59, 0x0a, 0x1f, 0x73, 0x41, 0x1f, 0x36, + 0x32, 0x4f, 0x34, 0x1b, 0x71, 0x59, 0x38, 0x33, 0x22, 0x3d, 0x70, 0x59, 0x41, 0x4d, 0x1e, 0x33, 0x5f, + 0x6d, 0x33, 0x02, 0x06, 0x7b, 0x00, 0x27, 0x4d, 0x65, 0x04, 0x07, 0x16, 0x09, 0x44, 0x75, 0x6d, 0x61, + 0x32, 0x27, 0x4b, 0x79, 0x27, 0x1e, 0x1a, 0x67, 0x1c, 0x33, 0x59, 0x71, 0x10, 0x6b, 0x7a, 0x65, 0x28, + 0x01, 0x19, 0x15, 0x46, 0x5b, 0x37, 0x05, 0x5b, 0x6a, 0x6e, 0x13, 0x68, 0x5f, 0x35, 0x1d, 0x7c, 0x7f, + 0x6e, 0x71, 0x3c, 0x1d, 0x05, 0x09, 0x5a, 0x4f, 0x23, 0x11, 0x4f, 0x46, 0x42, 0x3f, 0x44, 0x6b, 0x01, + 0x29, 0x48, 0x43, 0x52, 0x4d, 0x00, 0x29, 0x31, 0x3d, 0x6e, 0x63, 0x0f, 0x3d, 0x63, 0x52, 0x56, 0x2b, + 0x50, 0x77, 0x1d, 0x35, 0x54, 0x57, 0x46, 0x59, 0x14, 0xc5, 0xdd, 0xd1, 0x82, 0x97, 0xfb, 0xc9, 0x97, + 0xae, 0xaa, 0xd7, 0xac, 0x83, 0xe9, 0xc1, 0xa0, 0xbb, 0xaa, 0xb5, 0xf8, 0xd1, 0xc9, 0xc5, 0x96, 0x8b, + 0xe7, 0xd5, 0x8b, 0xba, 0xbe, 0xc3, 0xb8, 0xaf, 0xc5, 0xed, 0x8c, 0x8f, 0x9e, 0x81, 0xcc, 0xed, 0xf5, + 0xf9, 0xaa, 0xbf, 0xd3, 0xe1, 0xbf, 0x96, 0x92, 0xef, 0x94, 0xbb, 0xd1, 0xf9, 0x98, 0x93, 0x82, 0x9d, + 0xd0, 0xf9, 0xe1, 0xed, 0xbe, 0xd3, 0xbf, 0x8d, 0xd3, 0xe2, 0xe6, 0x9b, 0xe0, 0xc7, 0xad, 0x85, 0xe4, + 0xe7, 0xf6, 0xe9, 0xa4, 0x95, 0x8d, 0x81, 0xd2, 0xc7, 0xab, 0x99, 0xc7, 0xfe, 0xfa, 0x87, 0xfc, 0xd3, + 0xb9, 0x91, 0xf0, 0xcb, 0xda, 0xc5, 0x88, 0xa1, 0xb9, 0xb5, 0xe6, 0xfb, 0x97, 0xa5, 0xfb, 0xca, 0xce, + 0xb3, 0xc8, 0xff, 0x95, 0xbd, 0xdc, 0xdf, 0xce, 0xd1, 0x9c, 0xbd, 0xa5, 0xa9, 0xfa, 0xef, 0x83, 0xb1, + 0xef, 0x26 + } + } + + // clang-format on + }; - // Verify - BOOST_TEST_REQUIRE(ec == error_code()); - BOOST_MYSQL_ASSERT_BUFFER_EQUALS(decrypt(private_key_8192, buff), expected_decrypted); + for (const auto& tc : test_cases) + { + BOOST_TEST_CONTEXT(tc.name) + { + // Setup + buffer_type buff; + + // Call the function + auto ec = csha2p_encrypt_password(tc.password, scramble, tc.public_key, buff, ssl_category); + + // Verify + BOOST_TEST_REQUIRE(ec == error_code()); + BOOST_MYSQL_ASSERT_BUFFER_EQUALS(decrypt(tc.private_key, buff), tc.expected_decrypted); + } + } } // @@ -619,7 +576,7 @@ error loading key TODO: should we fuzz this function? key is smaller than what we expect? EVP_PKEY_CTX_set_rsa_padding fails with a value != -2 (mock) - TODO: if openssl returns 0 in ERR_get_error, does the error code count as failed, too? + TODO: if openssl returns 0 in ERR_get_error, does the error code count as failed, too? no it does not determining the size of the hash failure (EVP_PKEY_get_size < 0: mock) not available (EVP_PKEY_get_size = 0: mock) From 6b21e68aadeadfc6261257657278fdf78b2e16d3 Mon Sep 17 00:00:00 2001 From: Ruben Perez Date: Sun, 1 Jun 2025 18:03:11 +0200 Subject: [PATCH 22/68] integration test --- test/integration/db_setup_sha256.sql | 12 ++++++++++-- test/integration/test/handshake.cpp | 12 ++++++++++++ 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/test/integration/db_setup_sha256.sql b/test/integration/db_setup_sha256.sql index b6be0e504..2b61d5c7f 100644 --- a/test/integration/db_setup_sha256.sql +++ b/test/integration/db_setup_sha256.sql @@ -14,17 +14,25 @@ CREATE USER 'sha2p_user'@'%' IDENTIFIED WITH 'sha256_password'; ALTER USER 'sha2p_user'@'%' IDENTIFIED BY 'sha2p_password'; GRANT ALL PRIVILEGES ON boost_mysql_integtests.* TO 'sha2p_user'@'%'; --- User that uses caching_sha2_password plugin +-- User that uses the caching_sha2_password plugin DROP USER IF EXISTS 'csha2p_user'@'%'; CREATE USER 'csha2p_user'@'%' IDENTIFIED WITH 'caching_sha2_password'; ALTER USER 'csha2p_user'@'%' IDENTIFIED BY 'csha2p_password'; GRANT ALL PRIVILEGES ON boost_mysql_integtests.* TO 'csha2p_user'@'%'; --- User that uses caching_sha2_password plugin with an empty password +-- User that uses the caching_sha2_password plugin with an empty password DROP USER IF EXISTS 'csha2p_empty_password_user'@'%'; CREATE USER 'csha2p_empty_password_user'@'%' IDENTIFIED WITH 'caching_sha2_password'; ALTER USER 'csha2p_empty_password_user'@'%' IDENTIFIED BY ''; GRANT ALL PRIVILEGES ON boost_mysql_integtests.* TO 'csha2p_empty_password_user'@'%'; +-- User that uses the caching_sha2_password plugin with a password longer than the scramble +-- (mora than 20 characters). Relevant for the slow path without TLS +DROP USER IF EXISTS 'csha2p_long_password_user'@'%'; +CREATE USER 'csha2p_long_password_user'@'%' IDENTIFIED WITH 'caching_sha2_password'; +ALTER USER 'csha2p_long_password_user'@'%' IDENTIFIED BY '1234567890abcdefghijklmnopqrstuvwxyz'; +GRANT ALL PRIVILEGES ON boost_mysql_integtests.* TO 'csha2p_long_password_user'@'%'; + + FLUSH PRIVILEGES; diff --git a/test/integration/test/handshake.cpp b/test/integration/test/handshake.cpp index e19ea7b00..186e63f23 100644 --- a/test/integration/test/handshake.cpp +++ b/test/integration/test/handshake.cpp @@ -263,6 +263,18 @@ BOOST_DATA_TEST_CASE_F(any_connection_fixture, empty_password_cache_miss, all_tr check_ssl(conn, sample.expect_ssl); } +// Passwords longer than the scramble work correctly. +// This is only relevant for cache misses over insecure channels. +BOOST_FIXTURE_TEST_CASE(long_password_cache_miss, any_connection_fixture) +{ + auto params = connect_params_builder() + .ssl(ssl_mode::disable) + .credentials("csha2p_long_password_user", "1234567890abcdefghijklmnopqrstuvwxyz") + .build(); + clear_sha256_cache(); + conn.async_connect(params, as_netresult).validate_no_error(); +} + BOOST_FIXTURE_TEST_CASE(bad_password_cache_hit, any_connection_fixture) { auto params = connect_params_builder() From 9dc5abce4334d9ace3f11eccc061f034508e3ec2 Mon Sep 17 00:00:00 2001 From: Ruben Perez Date: Sun, 1 Jun 2025 18:25:36 +0200 Subject: [PATCH 23/68] fix mocking tests --- .../sansio/csha2p_encrypt_password.hpp | 1 - .../handshake_csha2p_hash_password.cpp | 20 +--------- .../test_csha2p_encrypt_password_errors.cpp | 37 ++++++++++++++++++- 3 files changed, 36 insertions(+), 22 deletions(-) diff --git a/include/boost/mysql/impl/internal/sansio/csha2p_encrypt_password.hpp b/include/boost/mysql/impl/internal/sansio/csha2p_encrypt_password.hpp index 715bf8a9d..213ed7cb0 100644 --- a/include/boost/mysql/impl/internal/sansio/csha2p_encrypt_password.hpp +++ b/include/boost/mysql/impl/internal/sansio/csha2p_encrypt_password.hpp @@ -37,7 +37,6 @@ namespace detail { // The OpenSSL category is passed as parameter to avoid including asio/ssl headers here. // Doing so would make mocking OpenSSL more difficult (more functions used) -// TODO: give it a try inline error_code translate_openssl_error( unsigned long code, const source_location* loc, diff --git a/test/unit/test/sansio/handshake/handshake_csha2p_hash_password.cpp b/test/unit/test/sansio/handshake/handshake_csha2p_hash_password.cpp index ff4b07fb0..02bd2c801 100644 --- a/test/unit/test/sansio/handshake/handshake_csha2p_hash_password.cpp +++ b/test/unit/test/sansio/handshake/handshake_csha2p_hash_password.cpp @@ -570,25 +570,7 @@ mVpTh++3j7pnpWUjnFuarvWmWh/H6t96/pTx566FKGxZpLn3H9TLHZJsog== BOOST_TEST(ec == client_errc::protocol_value_error); // OpenSSL does not provide an error code here } -/** -error creating buffer (mock) -error loading key - TODO: should we fuzz this function? - key is smaller than what we expect? - EVP_PKEY_CTX_set_rsa_padding fails with a value != -2 (mock) - TODO: if openssl returns 0 in ERR_get_error, does the error code count as failed, too? no it does not -determining the size of the hash - failure (EVP_PKEY_get_size < 0: mock) - not available (EVP_PKEY_get_size = 0: mock) -error creating ctx (mock) -encrypting - the returned size is < buffer - the returned size is == buffer - the returned size is > buffer (mock) - encryption fails (probably merge with the one below) - password is too big for encryption (with 2 sizes) -buffer is reset? -*/ +// TODO: verify that these error codes have source code info BOOST_AUTO_TEST_SUITE_END() diff --git a/test/unit/test_csha2p_encrypt_password_errors.cpp b/test/unit/test_csha2p_encrypt_password_errors.cpp index abd75400e..668028a8c 100644 --- a/test/unit/test_csha2p_encrypt_password_errors.cpp +++ b/test/unit/test_csha2p_encrypt_password_errors.cpp @@ -9,14 +9,29 @@ #include #include +#include #include +#include using namespace boost::mysql; using detail::csha2p_encrypt_password; +// Contains tests that need mocking OpenSSL functions. +// We do this at link time, by defining the functions declared in OpenSSL headers here +// and not linking to libssl/libcrypto + namespace { +// If we use asio::error::ssl_category, many more other OpenSSL functions +// become used, and mocking becomes problematic. +class mock_ssl_category final : public boost::system::error_category +{ +public: + const char* name() const noexcept override { return "mock_ssl"; } + std::string message(int) const override { return {}; } +} ssl_category; + constexpr std::uint8_t scramble[20]{}; using vector_type = boost::container::small_vector; @@ -35,10 +50,28 @@ void test_bio_new_error() { openssl_mock.last_error = 42u; vector_type out; - unsigned long err = csha2p_encrypt_password("passwd", scramble, {}, out); - BOOST_TEST_EQ(err, 42u); + auto ec = csha2p_encrypt_password("passwd", scramble, {}, out, ssl_category); + BOOST_TEST_EQ(ec, error_code(42, ssl_category)); } +/** +error loading key + TODO: should we fuzz this function? + EVP_PKEY_CTX_set_rsa_padding fails with a value != -2 (mock) + TODO: if openssl returns 0 in ERR_get_error, does the error code count as failed, too? no it does not +determining the size of the hash + failure (EVP_PKEY_get_size < 0: mock) + not available (EVP_PKEY_get_size = 0: mock) +error creating ctx (mock) +encrypting + the returned size is < buffer + the returned size is == buffer + the returned size is > buffer (mock) + encryption fails (probably merge with the one below) + password is too big for encryption (with 2 sizes) +buffer is reset? +*/ + } // namespace BIO* BIO_new_mem_buf(const void*, int) { return openssl_mock.bio; } From 380dd60725bfd077a2d53ead99b4ad481b4bce7e Mon Sep 17 00:00:00 2001 From: Ruben Perez Date: Sun, 1 Jun 2025 18:28:33 +0200 Subject: [PATCH 24/68] use Boost.Test --- .../test_csha2p_encrypt_password_errors.cpp | 28 ++++++++----------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/test/unit/test_csha2p_encrypt_password_errors.cpp b/test/unit/test_csha2p_encrypt_password_errors.cpp index 668028a8c..ec955a649 100644 --- a/test/unit/test_csha2p_encrypt_password_errors.cpp +++ b/test/unit/test_csha2p_encrypt_password_errors.cpp @@ -5,11 +5,14 @@ // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // +#define BOOST_TEST_MODULE test_csha2p_encrypt_password_errors + #include #include -#include #include +#include +#include #include #include @@ -46,12 +49,12 @@ struct } openssl_mock; -void test_bio_new_error() +BOOST_AUTO_TEST_CASE(error_creating_bio) { openssl_mock.last_error = 42u; vector_type out; auto ec = csha2p_encrypt_password("passwd", scramble, {}, out, ssl_category); - BOOST_TEST_EQ(ec, error_code(42, ssl_category)); + BOOST_TEST(ec == error_code(42, ssl_category)); } /** @@ -79,43 +82,36 @@ int BIO_free(BIO*) { return 0; } EVP_PKEY* PEM_read_bio_PUBKEY(BIO* bio, EVP_PKEY**, pem_password_cb*, void*) { - BOOST_TEST_EQ(bio, openssl_mock.bio); + BOOST_TEST(bio == openssl_mock.bio); return openssl_mock.key; } void EVP_PKEY_free(EVP_PKEY*) {} EVP_PKEY_CTX* EVP_PKEY_CTX_new(EVP_PKEY* pkey, ENGINE*) { - BOOST_TEST_EQ(pkey, openssl_mock.key); + BOOST_TEST(pkey == openssl_mock.key); return openssl_mock.ctx; } void EVP_PKEY_CTX_free(EVP_PKEY_CTX*) {} int EVP_PKEY_encrypt_init(EVP_PKEY_CTX* ctx) { - BOOST_TEST_EQ(ctx, openssl_mock.ctx); + BOOST_TEST(ctx == openssl_mock.ctx); return openssl_mock.encrypt_init_result; } int EVP_PKEY_CTX_set_rsa_padding(EVP_PKEY_CTX* ctx, int) { - BOOST_TEST_EQ(ctx, openssl_mock.ctx); + BOOST_TEST(ctx == openssl_mock.ctx); return 0; } int EVP_PKEY_get_size(const EVP_PKEY* pkey) { - BOOST_TEST_EQ(pkey, openssl_mock.key); + BOOST_TEST(pkey == openssl_mock.key); return 256; } int EVP_PKEY_encrypt(EVP_PKEY_CTX* ctx, unsigned char*, size_t*, const unsigned char*, size_t) { - BOOST_TEST_EQ(ctx, openssl_mock.ctx); + BOOST_TEST(ctx == openssl_mock.ctx); return 0; } unsigned long ERR_get_error() { return openssl_mock.last_error; } - -int main() -{ - test_bio_new_error(); - - return boost::report_errors(); -} From e3cb4d141b63840a4409924ef5376c59b9b1c6d5 Mon Sep 17 00:00:00 2001 From: Ruben Perez Date: Sun, 1 Jun 2025 18:39:54 +0200 Subject: [PATCH 25/68] track num calls --- .../test_csha2p_encrypt_password_errors.cpp | 42 ++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/test/unit/test_csha2p_encrypt_password_errors.cpp b/test/unit/test_csha2p_encrypt_password_errors.cpp index ec955a649..d00c9076e 100644 --- a/test/unit/test_csha2p_encrypt_password_errors.cpp +++ b/test/unit/test_csha2p_encrypt_password_errors.cpp @@ -5,6 +5,7 @@ // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // +#include "boost/mysql/error_code.hpp" #define BOOST_TEST_MODULE test_csha2p_encrypt_password_errors #include @@ -41,6 +42,16 @@ using vector_type = boost::container::small_vector; struct { + // Number of times that each function has been called. + // Tracking this helps us to check that we're actually covering the case we want + std::size_t BIO_new_mem_buf_calls{}; + std::size_t PEM_read_bio_PUBKEY_calls{}; + std::size_t EVP_PKEY_CTX_new_calls{}; + std::size_t EVP_PKEY_encrypt_init_calls{}; + std::size_t EVP_PKEY_CTX_set_rsa_padding_calls{}; + std::size_t EVP_PKEY_get_size_calls{}; + std::size_t EVP_PKEY_encrypt_calls{}; + BIO* bio{reinterpret_cast(static_cast(100))}; EVP_PKEY* key{reinterpret_cast(static_cast(200))}; EVP_PKEY_CTX* ctx{reinterpret_cast(static_cast(300))}; @@ -51,12 +62,31 @@ struct BOOST_AUTO_TEST_CASE(error_creating_bio) { + // Setup + openssl_mock = {}; + openssl_mock.bio = nullptr; openssl_mock.last_error = 42u; vector_type out; + + // Call the function auto ec = csha2p_encrypt_password("passwd", scramble, {}, out, ssl_category); + + // Check BOOST_TEST(ec == error_code(42, ssl_category)); + BOOST_TEST(ec.has_location()); + BOOST_TEST(openssl_mock.BIO_new_mem_buf_calls == 1u); + BOOST_TEST(openssl_mock.PEM_read_bio_PUBKEY_calls == 0u); } +// // Determining the maximum size of the ciphertext fails +// BOOST_AUTO_TEST_CASE(error_get_size) +// { +// openssl_mock.last_error = 43u; +// vector_type out; +// auto ec = csha2p_encrypt_password("passwd", scramble, {}, out, ssl_category); +// BOOST_TEST(ec == error_code(42, ssl_category)); +// } + /** error loading key TODO: should we fuzz this function? @@ -77,11 +107,16 @@ buffer is reset? } // namespace -BIO* BIO_new_mem_buf(const void*, int) { return openssl_mock.bio; } +BIO* BIO_new_mem_buf(const void*, int) +{ + ++openssl_mock.BIO_new_mem_buf_calls; + return openssl_mock.bio; +} int BIO_free(BIO*) { return 0; } EVP_PKEY* PEM_read_bio_PUBKEY(BIO* bio, EVP_PKEY**, pem_password_cb*, void*) { + ++openssl_mock.PEM_read_bio_PUBKEY_calls; BOOST_TEST(bio == openssl_mock.bio); return openssl_mock.key; } @@ -89,27 +124,32 @@ void EVP_PKEY_free(EVP_PKEY*) {} EVP_PKEY_CTX* EVP_PKEY_CTX_new(EVP_PKEY* pkey, ENGINE*) { + ++openssl_mock.EVP_PKEY_CTX_new_calls; BOOST_TEST(pkey == openssl_mock.key); return openssl_mock.ctx; } void EVP_PKEY_CTX_free(EVP_PKEY_CTX*) {} int EVP_PKEY_encrypt_init(EVP_PKEY_CTX* ctx) { + ++openssl_mock.EVP_PKEY_encrypt_init_calls; BOOST_TEST(ctx == openssl_mock.ctx); return openssl_mock.encrypt_init_result; } int EVP_PKEY_CTX_set_rsa_padding(EVP_PKEY_CTX* ctx, int) { + ++openssl_mock.EVP_PKEY_CTX_set_rsa_padding_calls; BOOST_TEST(ctx == openssl_mock.ctx); return 0; } int EVP_PKEY_get_size(const EVP_PKEY* pkey) { + ++openssl_mock.EVP_PKEY_get_size_calls; BOOST_TEST(pkey == openssl_mock.key); return 256; } int EVP_PKEY_encrypt(EVP_PKEY_CTX* ctx, unsigned char*, size_t*, const unsigned char*, size_t) { + ++openssl_mock.EVP_PKEY_encrypt_calls; BOOST_TEST(ctx == openssl_mock.ctx); return 0; } From 463d2d552ac6392f87a627dcbaf9de604a633031 Mon Sep 17 00:00:00 2001 From: Ruben Perez Date: Sun, 1 Jun 2025 19:17:12 +0200 Subject: [PATCH 26/68] more edge cases --- .../sansio/csha2p_encrypt_password.hpp | 13 ++- .../test_csha2p_encrypt_password_errors.cpp | 83 ++++++++++++++----- 2 files changed, 74 insertions(+), 22 deletions(-) diff --git a/include/boost/mysql/impl/internal/sansio/csha2p_encrypt_password.hpp b/include/boost/mysql/impl/internal/sansio/csha2p_encrypt_password.hpp index 213ed7cb0..d84f2f082 100644 --- a/include/boost/mysql/impl/internal/sansio/csha2p_encrypt_password.hpp +++ b/include/boost/mysql/impl/internal/sansio/csha2p_encrypt_password.hpp @@ -141,10 +141,16 @@ inline error_code csha2p_encrypt_password( return translate_openssl_error(ERR_get_error(), &loc, openssl_category); } - // Encrypt + // Allocate a buffer for encryption int max_size = EVP_PKEY_get_size(key.get()); - BOOST_ASSERT(max_size >= 0); + if (max_size <= 0) + { + static constexpr auto loc = BOOST_CURRENT_LOCATION; + return translate_openssl_error(ERR_get_error(), &loc, openssl_category); + } output.resize(max_size); + + // Encrypt std::size_t actual_size = static_cast(max_size); if (EVP_PKEY_encrypt( ctx.get(), @@ -157,6 +163,9 @@ inline error_code csha2p_encrypt_password( static constexpr auto loc = BOOST_CURRENT_LOCATION; return translate_openssl_error(ERR_get_error(), &loc, openssl_category); } + + // Adjust size + BOOST_ASSERT(actual_size >= output.size()); output.resize(actual_size); // Done diff --git a/test/unit/test_csha2p_encrypt_password_errors.cpp b/test/unit/test_csha2p_encrypt_password_errors.cpp index d00c9076e..c6b5e389a 100644 --- a/test/unit/test_csha2p_encrypt_password_errors.cpp +++ b/test/unit/test_csha2p_encrypt_password_errors.cpp @@ -5,7 +5,6 @@ // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // -#include "boost/mysql/error_code.hpp" #define BOOST_TEST_MODULE test_csha2p_encrypt_password_errors #include @@ -23,7 +22,9 @@ using detail::csha2p_encrypt_password; // Contains tests that need mocking OpenSSL functions. // We do this at link time, by defining the functions declared in OpenSSL headers here -// and not linking to libssl/libcrypto +// and not linking to libssl/libcrypto. +// These tests cover cases that can't be covered directly by the unit tests using the real OpenSSL. +// Try to put as few tests here as possible. namespace { @@ -56,7 +57,9 @@ struct EVP_PKEY* key{reinterpret_cast(static_cast(200))}; EVP_PKEY_CTX* ctx{reinterpret_cast(static_cast(300))}; int encrypt_init_result{0}; - unsigned long last_error{0}; + int set_rsa_padding_result{0}; + int get_size_result{512}; + unsigned long last_error{0u}; } openssl_mock; @@ -78,35 +81,75 @@ BOOST_AUTO_TEST_CASE(error_creating_bio) BOOST_TEST(openssl_mock.PEM_read_bio_PUBKEY_calls == 0u); } -// // Determining the maximum size of the ciphertext fails -// BOOST_AUTO_TEST_CASE(error_get_size) -// { -// openssl_mock.last_error = 43u; -// vector_type out; -// auto ec = csha2p_encrypt_password("passwd", scramble, {}, out, ssl_category); -// BOOST_TEST(ec == error_code(42, ssl_category)); -// } +BOOST_AUTO_TEST_CASE(error_creating_pkey_ctx) +{ + // Setup + openssl_mock = {}; + openssl_mock.ctx = nullptr; + openssl_mock.last_error = 42u; + vector_type out; + + // Call the function + auto ec = csha2p_encrypt_password("passwd", scramble, {}, out, ssl_category); + + // Check + BOOST_TEST(ec == error_code(42, ssl_category)); + BOOST_TEST(ec.has_location()); + BOOST_TEST(openssl_mock.EVP_PKEY_CTX_new_calls == 1u); + BOOST_TEST(openssl_mock.EVP_PKEY_encrypt_init_calls == 0u); +} + +BOOST_AUTO_TEST_CASE(error_setting_rsa_padding) +{ + // Setup. The return value should be != -2, which indicates + // operation not supported and is handled separately + openssl_mock = {}; + openssl_mock.set_rsa_padding_result = -1; + openssl_mock.last_error = 42u; + vector_type out; + + // Call the function + auto ec = csha2p_encrypt_password("passwd", scramble, {}, out, ssl_category); + + // Check + BOOST_TEST(ec == error_code(42, ssl_category)); + BOOST_TEST(ec.has_location()); + BOOST_TEST(openssl_mock.EVP_PKEY_CTX_set_rsa_padding_calls == 1u); + BOOST_TEST(openssl_mock.EVP_PKEY_encrypt_calls == 0u); +} + +// Getting a zero size as max buffer size might happen in theory (although it shouldn't for RSA) +BOOST_AUTO_TEST_CASE(get_size_zero) +{ + // Setup + openssl_mock = {}; + openssl_mock.get_size_result = 0; + openssl_mock.last_error = 42u; + vector_type out; + + // Call the function + auto ec = csha2p_encrypt_password("passwd", scramble, {}, out, ssl_category); + + // Check + BOOST_TEST(ec == error_code(42, ssl_category)); + BOOST_TEST(ec.has_location()); + BOOST_TEST(openssl_mock.EVP_PKEY_get_size_calls == 1u); + BOOST_TEST(openssl_mock.EVP_PKEY_encrypt_calls == 0u); +} /** error loading key TODO: should we fuzz this function? - EVP_PKEY_CTX_set_rsa_padding fails with a value != -2 (mock) TODO: if openssl returns 0 in ERR_get_error, does the error code count as failed, too? no it does not -determining the size of the hash - failure (EVP_PKEY_get_size < 0: mock) - not available (EVP_PKEY_get_size = 0: mock) -error creating ctx (mock) encrypting the returned size is < buffer the returned size is == buffer the returned size is > buffer (mock) - encryption fails (probably merge with the one below) - password is too big for encryption (with 2 sizes) -buffer is reset? */ } // namespace +// Implement the OpenSSL functions BIO* BIO_new_mem_buf(const void*, int) { ++openssl_mock.BIO_new_mem_buf_calls; @@ -139,7 +182,7 @@ int EVP_PKEY_CTX_set_rsa_padding(EVP_PKEY_CTX* ctx, int) { ++openssl_mock.EVP_PKEY_CTX_set_rsa_padding_calls; BOOST_TEST(ctx == openssl_mock.ctx); - return 0; + return openssl_mock.set_rsa_padding_result; } int EVP_PKEY_get_size(const EVP_PKEY* pkey) { From 1e7f38a1f186a8be72bbd29a0cc9eb5f97172723 Mon Sep 17 00:00:00 2001 From: Ruben Perez Date: Sun, 1 Jun 2025 19:32:58 +0200 Subject: [PATCH 27/68] refactor and fixes --- .../sansio/csha2p_encrypt_password.hpp | 29 +++++++++++++------ 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/include/boost/mysql/impl/internal/sansio/csha2p_encrypt_password.hpp b/include/boost/mysql/impl/internal/sansio/csha2p_encrypt_password.hpp index d84f2f082..29ffe2f05 100644 --- a/include/boost/mysql/impl/internal/sansio/csha2p_encrypt_password.hpp +++ b/include/boost/mysql/impl/internal/sansio/csha2p_encrypt_password.hpp @@ -59,6 +59,23 @@ inline error_code translate_openssl_error( return error_code(static_cast(code), openssl_category, loc); } +inline container::small_vector csha2p_salt_password( + string_view password, + span scramble +) +{ + // Salt the password, as a NULL-terminated string + container::small_vector res(password.size() + 1u, 0); + for (std::size_t i = 0; i < password.size(); ++i) + res[i] = password[i] ^ scramble[i % scramble.size()]; + + // Add the NULL terminator. It should be salted, too. Since 0 ^ U = U, + // the byte should be the scramble at the position we're in + res[password.size()] = scramble[password.size() % scramble.size()]; + + return res; +} + inline error_code csha2p_encrypt_password( string_view password, span scramble, @@ -100,14 +117,8 @@ inline error_code csha2p_encrypt_password( return translate_openssl_error(ERR_get_error(), &loc, openssl_category); } - // Salt the password, as a NULL-terminated string - container::small_vector salted_password(password.size() + 1u, 0); - for (std::size_t i = 0; i < password.size(); ++i) - salted_password[i] = password[i] ^ scramble[i % scramble.size()]; - - // Add the NULL terminator. It should be salted, too. Since 0 ^ U = U, - // the byte should be the scramble at the position we're in - salted_password[password.size()] = scramble[password.size() % scramble.size()]; + // Salt the password + auto salted_password = csha2p_salt_password(password, scramble); // Set up the encryption context unique_evp_pkey_ctx ctx(EVP_PKEY_CTX_new(key.get(), nullptr)); @@ -165,7 +176,7 @@ inline error_code csha2p_encrypt_password( } // Adjust size - BOOST_ASSERT(actual_size >= output.size()); + BOOST_ASSERT(actual_size <= output.size()); output.resize(actual_size); // Done From ae32f4f8623abb22f703317fdd598e7054a2bf85 Mon Sep 17 00:00:00 2001 From: Ruben Perez Date: Sun, 1 Jun 2025 19:33:11 +0200 Subject: [PATCH 28/68] finished mocking tests --- .../test_csha2p_encrypt_password_errors.cpp | 38 +++++++++++++------ 1 file changed, 27 insertions(+), 11 deletions(-) diff --git a/test/unit/test_csha2p_encrypt_password_errors.cpp b/test/unit/test_csha2p_encrypt_password_errors.cpp index c6b5e389a..d48b9941e 100644 --- a/test/unit/test_csha2p_encrypt_password_errors.cpp +++ b/test/unit/test_csha2p_encrypt_password_errors.cpp @@ -56,9 +56,9 @@ struct BIO* bio{reinterpret_cast(static_cast(100))}; EVP_PKEY* key{reinterpret_cast(static_cast(200))}; EVP_PKEY_CTX* ctx{reinterpret_cast(static_cast(300))}; - int encrypt_init_result{0}; - int set_rsa_padding_result{0}; - int get_size_result{512}; + int set_rsa_padding_result{1}; + int get_size_result{256}; + std::size_t actual_ciphertext_size{256u}; unsigned long last_error{0u}; } openssl_mock; @@ -137,14 +137,28 @@ BOOST_AUTO_TEST_CASE(get_size_zero) BOOST_TEST(openssl_mock.EVP_PKEY_encrypt_calls == 0u); } +// In theory, the encryption function may communicate that it didn't use all the bytes +// in the buffer. This shouldn't happen in RSA, but we handle the case anyway +BOOST_AUTO_TEST_CASE(encrypt_actual_size_lt_max_size) +{ + // Setup + openssl_mock = {}; + openssl_mock.get_size_result = 256; + openssl_mock.actual_ciphertext_size = 200u; + vector_type out; + + // Call the function + auto ec = csha2p_encrypt_password("passwd", scramble, {}, out, ssl_category); + + // Check + BOOST_TEST(ec == error_code()); + BOOST_TEST(out.size() == 200u); +} + /** error loading key TODO: should we fuzz this function? TODO: if openssl returns 0 in ERR_get_error, does the error code count as failed, too? no it does not -encrypting - the returned size is < buffer - the returned size is == buffer - the returned size is > buffer (mock) */ } // namespace @@ -176,7 +190,7 @@ int EVP_PKEY_encrypt_init(EVP_PKEY_CTX* ctx) { ++openssl_mock.EVP_PKEY_encrypt_init_calls; BOOST_TEST(ctx == openssl_mock.ctx); - return openssl_mock.encrypt_init_result; + return 1; } int EVP_PKEY_CTX_set_rsa_padding(EVP_PKEY_CTX* ctx, int) { @@ -188,13 +202,15 @@ int EVP_PKEY_get_size(const EVP_PKEY* pkey) { ++openssl_mock.EVP_PKEY_get_size_calls; BOOST_TEST(pkey == openssl_mock.key); - return 256; + return openssl_mock.get_size_result; } -int EVP_PKEY_encrypt(EVP_PKEY_CTX* ctx, unsigned char*, size_t*, const unsigned char*, size_t) +int EVP_PKEY_encrypt(EVP_PKEY_CTX* ctx, unsigned char*, size_t* actual_size, const unsigned char*, size_t) { ++openssl_mock.EVP_PKEY_encrypt_calls; BOOST_TEST(ctx == openssl_mock.ctx); - return 0; + if (actual_size) + *actual_size = openssl_mock.actual_ciphertext_size; + return 1; } unsigned long ERR_get_error() { return openssl_mock.last_error; } From 193664a3fb1a5b1cfd8e4ebfda52e49e6a89cd0d Mon Sep 17 00:00:00 2001 From: Ruben Perez Date: Mon, 2 Jun 2025 19:16:43 +0200 Subject: [PATCH 29/68] Added algo_test::expect_any_write --- test/unit/include/test_unit/algo_test.hpp | 14 +++++++++++++- test/unit/src/utils.cpp | 10 +++++++--- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/test/unit/include/test_unit/algo_test.hpp b/test/unit/include/test_unit/algo_test.hpp index cd6badae2..60cdd2ef4 100644 --- a/test/unit/include/test_unit/algo_test.hpp +++ b/test/unit/include/test_unit/algo_test.hpp @@ -70,6 +70,7 @@ class BOOST_ATTRIBUTE_NODISCARD algo_test detail::next_action_type type; std::vector bytes; error_code result; + bool check; }; std::vector steps_; @@ -98,7 +99,12 @@ class BOOST_ATTRIBUTE_NODISCARD algo_test std::size_t num_steps_to_run ) const; - algo_test& add_step(detail::next_action_type act_type, std::vector bytes, error_code ec); + algo_test& add_step( + detail::next_action_type act_type, + std::vector bytes, + error_code ec, + bool check = true + ); void check_impl( any_algo_ref algo, @@ -126,6 +132,12 @@ class BOOST_ATTRIBUTE_NODISCARD algo_test return add_step(detail::next_action_type::write, std::move(bytes), result); } + BOOST_ATTRIBUTE_NODISCARD + algo_test& expect_any_write(error_code result = {}) + { + return add_step(detail::next_action_type::write, {}, result, false); + } + BOOST_ATTRIBUTE_NODISCARD algo_test& expect_read(std::vector result_bytes) { diff --git a/test/unit/src/utils.cpp b/test/unit/src/utils.cpp index c483d7416..5bb25ae97 100644 --- a/test/unit/src/utils.cpp +++ b/test/unit/src/utils.cpp @@ -113,7 +113,10 @@ detail::next_action boost::mysql::test::algo_test::run_algo_until_step( if (step.type == detail::next_action_type::read) handle_read(st, step); else if (step.type == detail::next_action_type::write) - BOOST_MYSQL_ASSERT_BUFFER_EQUALS(act.write_args().buffer, step.bytes); + { + if (step.check) + BOOST_MYSQL_ASSERT_BUFFER_EQUALS(act.write_args().buffer, step.bytes); + } // Other actions don't need any handling act = algo.resume(st, diag, step.result); @@ -126,10 +129,11 @@ detail::next_action boost::mysql::test::algo_test::run_algo_until_step( boost::mysql::test::algo_test& boost::mysql::test::algo_test::add_step( detail::next_action_type act_type, std::vector bytes, - error_code ec + error_code ec, + bool check ) { - steps_.push_back(step_t{act_type, std::move(bytes), ec}); + steps_.push_back(step_t{act_type, std::move(bytes), ec, check}); return *this; } From efef293f3a7b1764da109e7e22f689ce1a9d2639 Mon Sep 17 00:00:00 2001 From: Ruben Perez Date: Mon, 2 Jun 2025 19:17:10 +0200 Subject: [PATCH 30/68] first handshake_csha2p test --- .../sansio/handshake/handshake_common.hpp | 1 + .../sansio/handshake/handshake_csh2p_keys.hpp | 189 ++++++++++++++++++ .../sansio/handshake/handshake_csha2p.cpp | 59 ++++-- .../handshake_csha2p_hash_password.cpp | 171 +--------------- 4 files changed, 230 insertions(+), 190 deletions(-) create mode 100644 test/unit/test/sansio/handshake/handshake_csh2p_keys.hpp diff --git a/test/unit/test/sansio/handshake/handshake_common.hpp b/test/unit/test/sansio/handshake/handshake_common.hpp index 8e79f85a4..9fcdb95f5 100644 --- a/test/unit/test/sansio/handshake/handshake_common.hpp +++ b/test/unit/test/sansio/handshake/handshake_common.hpp @@ -246,6 +246,7 @@ constexpr std::uint8_t csha2p_hash[] = { 0x5e, 0xa9, 0x41, 0x8e, 0xdc, 0x89, 0xeb, 0xe2, 0xa1, 0xec, 0xd8, 0x4f, 0x73, 0xa1, 0x49, 0x60, }; +constexpr std::uint8_t csha2p_request_key[] = {0x02}; constexpr std::uint8_t csha2p_fast_auth_ok[] = {0x03}; constexpr std::uint8_t csha2p_perform_full_auth[] = {0x04}; diff --git a/test/unit/test/sansio/handshake/handshake_csh2p_keys.hpp b/test/unit/test/sansio/handshake/handshake_csh2p_keys.hpp new file mode 100644 index 000000000..369a55f9a --- /dev/null +++ b/test/unit/test/sansio/handshake/handshake_csh2p_keys.hpp @@ -0,0 +1,189 @@ +// +// Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#ifndef BOOST_MYSQL_TEST_UNIT_TEST_SANSIO_HANDSHAKE_HANDSHAKE_CSH2P_KEYS_HPP +#define BOOST_MYSQL_TEST_UNIT_TEST_SANSIO_HANDSHAKE_HANDSHAKE_CSH2P_KEYS_HPP + +namespace boost { +namespace mysql { +namespace test { + +constexpr unsigned char public_key_2048[] = R"%(-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA36OYSpdiy1lFDrdO1Vux +GwjPTo35R2+mXqW2SZV7kH5C6BSCeoTk6STVRBJbgOCabtp5bpUZ+x2bYWOZp4fs +JakC75CN2YTJoYg5z5U6XUBEWn6WNBpvEoSJaUtrzfU69J07uWqB6v0MdJf3JTgd +ILfGKvk2T+maxqUiYObs0BJd5eKJZDlUaf2r4a9KC8zGUZzHdgtZEXlkHVNLEbbD +Ju4KjtCtJCG1NEBAh3oSnNp/Q1FKFywqU7YnEBWI0B9C5UcKNFbg7M35daimXfGp +V7WJKhO9w7iBJYL1SW+PwyUCh3DNsuSm3nLmuwKhTvGQHZJS/5OVdSHgZhjDnk2V +WwIDAQAB +-----END PUBLIC KEY----- +)%"; + +constexpr unsigned char private_key_2048[] = R"%(-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDfo5hKl2LLWUUO +t07VW7EbCM9OjflHb6ZepbZJlXuQfkLoFIJ6hOTpJNVEEluA4Jpu2nlulRn7HZth +Y5mnh+wlqQLvkI3ZhMmhiDnPlTpdQERafpY0Gm8ShIlpS2vN9Tr0nTu5aoHq/Qx0 +l/clOB0gt8Yq+TZP6ZrGpSJg5uzQEl3l4olkOVRp/avhr0oLzMZRnMd2C1kReWQd +U0sRtsMm7gqO0K0kIbU0QECHehKc2n9DUUoXLCpTticQFYjQH0LlRwo0VuDszfl1 +qKZd8alXtYkqE73DuIElgvVJb4/DJQKHcM2y5Kbecua7AqFO8ZAdklL/k5V1IeBm +GMOeTZVbAgMBAAECggEANI0UtDJunKoVeCfK9ofdTiT70dG6yfaKeaMm+pONvZ5t +ymtHXdLsl3x4QM6vgdFFeNcNwdZ3jHKgmHn3GU7vRso4TmMBciOp3bNNImJGnLMF +XN5yHTw47XkHcR6v7m25tNFdv2wvqzBbROqQwMY20gFdJ6v3/z89h4A2W97nttyp +ixcNdSTHOfu6iUceEGi2PjHrvw4STPQeihXFNTnYG7hvlWzAerQ5STx5K2n4JoXz +xI5MuHS6PGj5EBPUoq0+EQhmhORWBdNCMcijpHqobVDjifPRY2JbI3zZHtVjYpGH +otmc72hIDjNW7RX1ePKW/gsq6p2by3U8+4dOdya4AQKBgQDz4/6uo7atJSTgVo3d +Zr/7UJ4Qi2mlc992jLAqTQle6JchhNERoPvb19Qy8BaOz6LcxmuMXnRij05mxdyb +LmoH8TPe6RFLCOJrRapkUjUtRD8dIg6UNFLk98LD5t3o5PoHwNpBFfokRlchwoHL +uBvbEHQkrPX2xPbgla5e7zJLgQKBgQDqvjCUMvmap9+AlqxGFhv51V9dIlKvT0xW +p5KEMVfs3JXCHAM7o0ClZ8NitHXw18E8+iMG4pWw3+FX4K6tKGR+rCeGCUNGuiQT +FzXjrEej+Pnuk8zacjXkbS+PNnEqhpSq6STZVFn2UW+ZWAoq2iAMR7qw0eAjylln +h//Wad3+2wKBgAjwWEtKUM2zyNA4G+b7dxnc8I4mre6UeqI7sdE7FZbW64Mc/RSq +U9DQ7kQXrJv7XDq/Qv3YEGf0XKlDozxEzToRSxdmb23Sm4nW+dHHeY95Kt8EeohQ +CqG9uvO3KHb6vXc/SECOb6aYtWTVXjB7RPoYdklJ1ZH/0hSVJ9ju52cBAoGBAK63 +A90p25F6ZOWGP46iogve/e2JwFTvBnhwnKJ7P1/yBhzFULqwlUsG4euzOR0a2J6T +5kIXnyZYW5ZWimwi5jlJ1Nj0R/h6TqNO4TMlZOTsSMmDhDMKUoZDpeRHtw7ZwAk9 +IcoH+DVXA2L0ngyq8LNzJ8a3TsYUs1pVZNunTC2FAoGARe4x29tdri8akxxwF3BV +bjJ9qRUIfIDK8rGWRdw94vVCB7XVmSWCEchmLqA1DqGYvAhYMYjkXTg9akfBTUQS +s+8JasUuQam8Y88JAfC0QqGbLgUsh0TpRUOXj+YQuoNiMVu14NNgYgFkx71WtvAq +kUmkxr/moPcZ+O1ahVjv/Us= +-----END PRIVATE KEY----- +)%"; + +constexpr unsigned char public_key_8192[] = R"%(-----BEGIN PUBLIC KEY----- +MIIEIjANBgkqhkiG9w0BAQEFAAOCBA8AMIIECgKCBAEA43dhEnCSwC/hAAm9XrZc +t0QaC4bkoSifeiL00+U8xBbAuAYnZSQ4PBbEERnxIRLpgCf0b2SDvCXPJmXB6lKz +1W4vVcw2fVuG4Kmp5C9skhXeGXrXoEOgSxBp8VkWWWB1tbpKmKwdGnh6O0JOwWRq +wWxefR3J9EWBNTz0vvbjGxYWt0XfcCjULPuXuPVlrbZkTsdCl8ncWG8G/OjRNHr3 +f9eiWHb2gA+xsUJfP/iWlUmy+9MiEGBT+JMW2wZAOu7JgVbyZWXV/pkwodP1IVFy +c9RTexnC7Th3dDlT+HcAWXDCTuWwq2KhD/WUnfXO6uZEjyp3aJiJOa7Mceag0B9t +T9d1AZkXQs3PZYn+Sl1cF3hq6e8OoKHZsA95PAtNrXfDe7vkyICB4sp1Dkqg75Ws +arOTC45CWcr/woReqCDYRiSr0aQodlJmSvSIlOz5IAyB40nFzA3VgEgTgHJ4mU7O +QcsOSStmZNvk6Hl9xfUfJi9xTB/vwf7FKWe+IcDGdkU9WV3yJ6UTXPW4TQu25uTc +6WoK4Us8Lk2h7f+Tk2dsyt+VTDOma5qbwTT8U/NKSd8Se5ewS4++ERqaRikLmp0o +AciYxH00Wk3ZLPFUn9/svcA4BAwOCTkYjIQvW/gLDrYyu4qyrgkGXiNs0L2QYB7l +AUTOl5M0Bmez5Nl8SIdaVk4bj5D+4BbHrDZGLMkkP1KWRgNFOJOfLT3JgMPxDwme +n4qFs/HV/d4qh8kzyjITsBRQj1EloqqU+40WJ9X3mIEo7xQ1DPloQMWADDYrO/bX +fFtYnTtnJLV6ndTSdQz0yZ7ubROJFatPy1VQ6AiBeN24sq9iKm6NXuWjszS0paZ/ +JfO+QOTB9D3lVnFzV2yHRIboHNpm/X1eVeXDlzzVTobqVr4eN78bL4+VQiGQav1I +8ebv3Gx9+NO1qVfsWHC89I+hw9HSN69gEyCmeWwIuY8hMGfTBgERVKPkWhSPM0cU +JlkM+ER1Pngz93TR7tWPsp9tYaKMG4DVth0fINRSVsFFkeHu1CLeKboUW8QMoH+A +vPDVnSE2M3ecAeFz0yNXTwVtHbQT2YpYZ88gnYvYcRNdqeOvRKkrGUON0fDGvBnF +xxcuNzZVP2wBEUTkYljnLrJ5zR+uabPgrQiIhTVdDplRRZd6DVRh9s357L5b94tX +wtNAFfHT3AsU/C5p8O6taxckrxYMqNsOJ/6zYp7gUTTdb3e/V1c3xgi/x8+gUrUn +ivGZZdOaWUbSDZf1cz2kfQPsaiDDPVKL7goo0ZA5gnbjkhHUNpGmE5KauHek9rxO +2KX3dpjl3YJyHSmOKv7Lf46Uja0cNWbTh/0nHlG5xtbzBNGvxF90iGmHDmr+u6BT +zwIDAQAB +-----END PUBLIC KEY----- +)%"; + +constexpr unsigned char private_key_8192[] = R"%(-----BEGIN PRIVATE KEY----- +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 +2vrUYCC8gWblftYH+yTWu2bQ4Y1pDw== +-----END PRIVATE KEY----- +)%"; + +} // namespace test +} // namespace mysql +} // namespace boost + +#endif diff --git a/test/unit/test/sansio/handshake/handshake_csha2p.cpp b/test/unit/test/sansio/handshake/handshake_csha2p.cpp index d41696409..a9b37679c 100644 --- a/test/unit/test/sansio/handshake/handshake_csha2p.cpp +++ b/test/unit/test/sansio/handshake/handshake_csha2p.cpp @@ -13,8 +13,10 @@ #include #include "handshake_common.hpp" +#include "handshake_csh2p_keys.hpp" #include "test_common/create_diagnostics.hpp" #include "test_unit/create_err.hpp" +#include "test_unit/create_frame.hpp" #include "test_unit/create_ok.hpp" #include "test_unit/create_ok_frame.hpp" @@ -72,26 +74,6 @@ BOOST_AUTO_TEST_CASE(err) .check(fix, common_server_errc::er_access_denied_error, create_server_diag("Denied")); } -// At the moment, this plugin requires TLS, so this is an error -BOOST_AUTO_TEST_CASE(fullauth) -{ - // Setup - handshake_fixture fix; - - // Run the test - algo_test() - .expect_read(server_hello_builder() - .caps(tls_caps) - .auth_plugin("caching_sha2_password") - .auth_data(csha2p_scramble) - .build()) - .expect_write( - login_request_builder().auth_plugin("caching_sha2_password").auth_response(csha2p_hash).build() - ) - .expect_read(create_more_data_frame(2, csha2p_perform_full_auth)) - .check(fix, client_errc::auth_plugin_requires_ssl); -} - // Receiving an unknown more data frame (something != fullauth or fastok) is illegal BOOST_AUTO_TEST_CASE(moredata) { @@ -239,6 +221,43 @@ BOOST_AUTO_TEST_CASE(authswitch_fastok_ok) .check(fix); } +// If the server requests us to perform full auth and we're using plaintext, +// we request the server's public key +BOOST_AUTO_TEST_CASE(fullauth_key_ok) +{ + // Setup + handshake_fixture fix; + + // Run the test + algo_test() + .expect_read(server_hello_builder() + .caps(tls_caps) + .auth_plugin("caching_sha2_password") + .auth_data(csha2p_scramble) + .build()) + .expect_write( + login_request_builder().auth_plugin("caching_sha2_password").auth_response(csha2p_hash).build() + ) + .expect_read(create_more_data_frame(2, csha2p_perform_full_auth)) + .expect_write(create_frame(3, csha2p_request_key)) + .expect_read(create_more_data_frame(4, public_key_2048)) + .expect_any_write() // the exact encryption result is not deterministic + .expect_read(create_ok_frame(6, ok_builder().build())) + .will_set_status(connection_status::ready) + .will_set_capabilities(min_caps) + .will_set_current_charset(utf8mb4_charset) + .will_set_connection_id(42) + .check(fix); +} + +// TODO +// fullauth key error +// fullauth invalid_key +// fullauth error +// fullauth fullauth +// fullauth more_data +// fullauth key more_data + // If we're using a secure transport (e.g. UNIX socket), caching_sha2_password // just sends the raw password BOOST_AUTO_TEST_CASE(securetransport_fullauth_ok) diff --git a/test/unit/test/sansio/handshake/handshake_csha2p_hash_password.cpp b/test/unit/test/sansio/handshake/handshake_csha2p_hash_password.cpp index 02bd2c801..ca329449c 100644 --- a/test/unit/test/sansio/handshake/handshake_csha2p_hash_password.cpp +++ b/test/unit/test/sansio/handshake/handshake_csha2p_hash_password.cpp @@ -23,6 +23,7 @@ #include #include +#include "handshake_csh2p_keys.hpp" #include "test_common/assert_buffer_equals.hpp" #include "test_common/printing.hpp" @@ -72,176 +73,6 @@ BOOST_AUTO_TEST_SUITE_END() // plaintext. BOOST_AUTO_TEST_SUITE(test_handshake_csha2p_encrypt_password) -constexpr unsigned char public_key_2048[] = R"%(-----BEGIN PUBLIC KEY----- -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA36OYSpdiy1lFDrdO1Vux -GwjPTo35R2+mXqW2SZV7kH5C6BSCeoTk6STVRBJbgOCabtp5bpUZ+x2bYWOZp4fs -JakC75CN2YTJoYg5z5U6XUBEWn6WNBpvEoSJaUtrzfU69J07uWqB6v0MdJf3JTgd -ILfGKvk2T+maxqUiYObs0BJd5eKJZDlUaf2r4a9KC8zGUZzHdgtZEXlkHVNLEbbD -Ju4KjtCtJCG1NEBAh3oSnNp/Q1FKFywqU7YnEBWI0B9C5UcKNFbg7M35daimXfGp -V7WJKhO9w7iBJYL1SW+PwyUCh3DNsuSm3nLmuwKhTvGQHZJS/5OVdSHgZhjDnk2V -WwIDAQAB ------END PUBLIC KEY----- -)%"; - -constexpr unsigned char private_key_2048[] = R"%(-----BEGIN PRIVATE KEY----- -MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDfo5hKl2LLWUUO -t07VW7EbCM9OjflHb6ZepbZJlXuQfkLoFIJ6hOTpJNVEEluA4Jpu2nlulRn7HZth -Y5mnh+wlqQLvkI3ZhMmhiDnPlTpdQERafpY0Gm8ShIlpS2vN9Tr0nTu5aoHq/Qx0 -l/clOB0gt8Yq+TZP6ZrGpSJg5uzQEl3l4olkOVRp/avhr0oLzMZRnMd2C1kReWQd -U0sRtsMm7gqO0K0kIbU0QECHehKc2n9DUUoXLCpTticQFYjQH0LlRwo0VuDszfl1 -qKZd8alXtYkqE73DuIElgvVJb4/DJQKHcM2y5Kbecua7AqFO8ZAdklL/k5V1IeBm -GMOeTZVbAgMBAAECggEANI0UtDJunKoVeCfK9ofdTiT70dG6yfaKeaMm+pONvZ5t -ymtHXdLsl3x4QM6vgdFFeNcNwdZ3jHKgmHn3GU7vRso4TmMBciOp3bNNImJGnLMF -XN5yHTw47XkHcR6v7m25tNFdv2wvqzBbROqQwMY20gFdJ6v3/z89h4A2W97nttyp -ixcNdSTHOfu6iUceEGi2PjHrvw4STPQeihXFNTnYG7hvlWzAerQ5STx5K2n4JoXz -xI5MuHS6PGj5EBPUoq0+EQhmhORWBdNCMcijpHqobVDjifPRY2JbI3zZHtVjYpGH -otmc72hIDjNW7RX1ePKW/gsq6p2by3U8+4dOdya4AQKBgQDz4/6uo7atJSTgVo3d -Zr/7UJ4Qi2mlc992jLAqTQle6JchhNERoPvb19Qy8BaOz6LcxmuMXnRij05mxdyb -LmoH8TPe6RFLCOJrRapkUjUtRD8dIg6UNFLk98LD5t3o5PoHwNpBFfokRlchwoHL -uBvbEHQkrPX2xPbgla5e7zJLgQKBgQDqvjCUMvmap9+AlqxGFhv51V9dIlKvT0xW -p5KEMVfs3JXCHAM7o0ClZ8NitHXw18E8+iMG4pWw3+FX4K6tKGR+rCeGCUNGuiQT -FzXjrEej+Pnuk8zacjXkbS+PNnEqhpSq6STZVFn2UW+ZWAoq2iAMR7qw0eAjylln -h//Wad3+2wKBgAjwWEtKUM2zyNA4G+b7dxnc8I4mre6UeqI7sdE7FZbW64Mc/RSq -U9DQ7kQXrJv7XDq/Qv3YEGf0XKlDozxEzToRSxdmb23Sm4nW+dHHeY95Kt8EeohQ -CqG9uvO3KHb6vXc/SECOb6aYtWTVXjB7RPoYdklJ1ZH/0hSVJ9ju52cBAoGBAK63 -A90p25F6ZOWGP46iogve/e2JwFTvBnhwnKJ7P1/yBhzFULqwlUsG4euzOR0a2J6T -5kIXnyZYW5ZWimwi5jlJ1Nj0R/h6TqNO4TMlZOTsSMmDhDMKUoZDpeRHtw7ZwAk9 -IcoH+DVXA2L0ngyq8LNzJ8a3TsYUs1pVZNunTC2FAoGARe4x29tdri8akxxwF3BV -bjJ9qRUIfIDK8rGWRdw94vVCB7XVmSWCEchmLqA1DqGYvAhYMYjkXTg9akfBTUQS -s+8JasUuQam8Y88JAfC0QqGbLgUsh0TpRUOXj+YQuoNiMVu14NNgYgFkx71WtvAq -kUmkxr/moPcZ+O1ahVjv/Us= ------END PRIVATE KEY----- -)%"; - -constexpr unsigned char public_key_8192[] = R"%(-----BEGIN PUBLIC KEY----- -MIIEIjANBgkqhkiG9w0BAQEFAAOCBA8AMIIECgKCBAEA43dhEnCSwC/hAAm9XrZc -t0QaC4bkoSifeiL00+U8xBbAuAYnZSQ4PBbEERnxIRLpgCf0b2SDvCXPJmXB6lKz -1W4vVcw2fVuG4Kmp5C9skhXeGXrXoEOgSxBp8VkWWWB1tbpKmKwdGnh6O0JOwWRq -wWxefR3J9EWBNTz0vvbjGxYWt0XfcCjULPuXuPVlrbZkTsdCl8ncWG8G/OjRNHr3 -f9eiWHb2gA+xsUJfP/iWlUmy+9MiEGBT+JMW2wZAOu7JgVbyZWXV/pkwodP1IVFy -c9RTexnC7Th3dDlT+HcAWXDCTuWwq2KhD/WUnfXO6uZEjyp3aJiJOa7Mceag0B9t -T9d1AZkXQs3PZYn+Sl1cF3hq6e8OoKHZsA95PAtNrXfDe7vkyICB4sp1Dkqg75Ws -arOTC45CWcr/woReqCDYRiSr0aQodlJmSvSIlOz5IAyB40nFzA3VgEgTgHJ4mU7O -QcsOSStmZNvk6Hl9xfUfJi9xTB/vwf7FKWe+IcDGdkU9WV3yJ6UTXPW4TQu25uTc -6WoK4Us8Lk2h7f+Tk2dsyt+VTDOma5qbwTT8U/NKSd8Se5ewS4++ERqaRikLmp0o -AciYxH00Wk3ZLPFUn9/svcA4BAwOCTkYjIQvW/gLDrYyu4qyrgkGXiNs0L2QYB7l -AUTOl5M0Bmez5Nl8SIdaVk4bj5D+4BbHrDZGLMkkP1KWRgNFOJOfLT3JgMPxDwme -n4qFs/HV/d4qh8kzyjITsBRQj1EloqqU+40WJ9X3mIEo7xQ1DPloQMWADDYrO/bX -fFtYnTtnJLV6ndTSdQz0yZ7ubROJFatPy1VQ6AiBeN24sq9iKm6NXuWjszS0paZ/ -JfO+QOTB9D3lVnFzV2yHRIboHNpm/X1eVeXDlzzVTobqVr4eN78bL4+VQiGQav1I -8ebv3Gx9+NO1qVfsWHC89I+hw9HSN69gEyCmeWwIuY8hMGfTBgERVKPkWhSPM0cU -JlkM+ER1Pngz93TR7tWPsp9tYaKMG4DVth0fINRSVsFFkeHu1CLeKboUW8QMoH+A -vPDVnSE2M3ecAeFz0yNXTwVtHbQT2YpYZ88gnYvYcRNdqeOvRKkrGUON0fDGvBnF -xxcuNzZVP2wBEUTkYljnLrJ5zR+uabPgrQiIhTVdDplRRZd6DVRh9s357L5b94tX -wtNAFfHT3AsU/C5p8O6taxckrxYMqNsOJ/6zYp7gUTTdb3e/V1c3xgi/x8+gUrUn -ivGZZdOaWUbSDZf1cz2kfQPsaiDDPVKL7goo0ZA5gnbjkhHUNpGmE5KauHek9rxO -2KX3dpjl3YJyHSmOKv7Lf46Uja0cNWbTh/0nHlG5xtbzBNGvxF90iGmHDmr+u6BT -zwIDAQAB ------END PUBLIC KEY----- -)%"; - -constexpr unsigned char private_key_8192[] = R"%(-----BEGIN PRIVATE KEY----- -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 -2vrUYCC8gWblftYH+yTWu2bQ4Y1pDw== ------END PRIVATE KEY----- -)%"; - // Decrypts the output of std::vector decrypt( boost::span private_key, From 88a79cd78ac0b9c36ad927ca82456ce0c9ccb19b Mon Sep 17 00:00:00 2001 From: Ruben Perez Date: Mon, 2 Jun 2025 19:19:10 +0200 Subject: [PATCH 31/68] fullauth key error --- .../sansio/handshake/handshake_csha2p.cpp | 29 ++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/test/unit/test/sansio/handshake/handshake_csha2p.cpp b/test/unit/test/sansio/handshake/handshake_csha2p.cpp index a9b37679c..5ebd6b4be 100644 --- a/test/unit/test/sansio/handshake/handshake_csha2p.cpp +++ b/test/unit/test/sansio/handshake/handshake_csha2p.cpp @@ -250,8 +250,35 @@ BOOST_AUTO_TEST_CASE(fullauth_key_ok) .check(fix); } +// The server might send us an error (e.g. if the password is invalid) +BOOST_AUTO_TEST_CASE(fullauth_key_error) +{ + // Setup + handshake_fixture fix; + + // Run the test + algo_test() + .expect_read(server_hello_builder() + .caps(tls_caps) + .auth_plugin("caching_sha2_password") + .auth_data(csha2p_scramble) + .build()) + .expect_write( + login_request_builder().auth_plugin("caching_sha2_password").auth_response(csha2p_hash).build() + ) + .expect_read(create_more_data_frame(2, csha2p_perform_full_auth)) + .expect_write(create_frame(3, csha2p_request_key)) + .expect_read(create_more_data_frame(4, public_key_2048)) + .expect_any_write() // the exact encryption result is not deterministic + .expect_read(err_builder() + .seqnum(6) + .code(common_server_errc::er_access_denied_error) + .message("Denied") + .build_frame()) + .check(fix, common_server_errc::er_access_denied_error, create_server_diag("Denied")); +} + // TODO -// fullauth key error // fullauth invalid_key // fullauth error // fullauth fullauth From be7023d60b3e3fcad177df62a31e8e9c848429a7 Mon Sep 17 00:00:00 2001 From: Ruben Perez Date: Mon, 2 Jun 2025 19:21:04 +0200 Subject: [PATCH 32/68] fullauth error --- .../sansio/handshake/handshake_csha2p.cpp | 30 +++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/test/unit/test/sansio/handshake/handshake_csha2p.cpp b/test/unit/test/sansio/handshake/handshake_csha2p.cpp index 5ebd6b4be..126e26ac7 100644 --- a/test/unit/test/sansio/handshake/handshake_csha2p.cpp +++ b/test/unit/test/sansio/handshake/handshake_csha2p.cpp @@ -250,7 +250,7 @@ BOOST_AUTO_TEST_CASE(fullauth_key_ok) .check(fix); } -// The server might send us an error (e.g. if the password is invalid) +// The server might send us an error after we sent the password (e.g. unauthorized) BOOST_AUTO_TEST_CASE(fullauth_key_error) { // Setup @@ -278,9 +278,35 @@ BOOST_AUTO_TEST_CASE(fullauth_key_error) .check(fix, common_server_errc::er_access_denied_error, create_server_diag("Denied")); } +// The server might send us an error instead of the key, +// if the key pair for caching_sha2_password was misconfigured +BOOST_AUTO_TEST_CASE(fullauth_error) +{ + // Setup + handshake_fixture fix; + + // Run the test + algo_test() + .expect_read(server_hello_builder() + .caps(tls_caps) + .auth_plugin("caching_sha2_password") + .auth_data(csha2p_scramble) + .build()) + .expect_write( + login_request_builder().auth_plugin("caching_sha2_password").auth_response(csha2p_hash).build() + ) + .expect_read(create_more_data_frame(2, csha2p_perform_full_auth)) + .expect_write(create_frame(3, csha2p_request_key)) + .expect_read(err_builder() + .seqnum(4) + .code(common_server_errc::er_access_denied_error) + .message("Bad key") + .build_frame()) + .check(fix, common_server_errc::er_access_denied_error, create_server_diag("Bad key")); +} + // TODO // fullauth invalid_key -// fullauth error // fullauth fullauth // fullauth more_data // fullauth key more_data From c0638a21872e4899f509e2df8ec3a65e91de8766 Mon Sep 17 00:00:00 2001 From: Ruben Perez Date: Mon, 2 Jun 2025 19:26:42 +0200 Subject: [PATCH 33/68] fullauth_encrypterror --- .../sansio/handshake/handshake_csha2p.cpp | 25 ++++++++++++++++++- .../handshake_csha2p_hash_password.cpp | 9 ++----- ...h2p_keys.hpp => handshake_csha2p_keys.hpp} | 11 ++++++-- 3 files changed, 35 insertions(+), 10 deletions(-) rename test/unit/test/sansio/handshake/{handshake_csh2p_keys.hpp => handshake_csha2p_keys.hpp} (97%) diff --git a/test/unit/test/sansio/handshake/handshake_csha2p.cpp b/test/unit/test/sansio/handshake/handshake_csha2p.cpp index 126e26ac7..dcdb27307 100644 --- a/test/unit/test/sansio/handshake/handshake_csha2p.cpp +++ b/test/unit/test/sansio/handshake/handshake_csha2p.cpp @@ -13,7 +13,7 @@ #include #include "handshake_common.hpp" -#include "handshake_csh2p_keys.hpp" +#include "handshake_csha2p_keys.hpp" #include "test_common/create_diagnostics.hpp" #include "test_unit/create_err.hpp" #include "test_unit/create_frame.hpp" @@ -305,6 +305,29 @@ BOOST_AUTO_TEST_CASE(fullauth_error) .check(fix, common_server_errc::er_access_denied_error, create_server_diag("Bad key")); } +// If encryption fails (e.g. because the server sent us an invalid key), we fail appropriately. +// Using a SM2 key yields a predictable error code. +BOOST_AUTO_TEST_CASE(fullauth_encrypterror) +{ + // Setup + handshake_fixture fix; + + // Run the test + algo_test() + .expect_read(server_hello_builder() + .caps(tls_caps) + .auth_plugin("caching_sha2_password") + .auth_data(csha2p_scramble) + .build()) + .expect_write( + login_request_builder().auth_plugin("caching_sha2_password").auth_response(csha2p_hash).build() + ) + .expect_read(create_more_data_frame(2, csha2p_perform_full_auth)) + .expect_write(create_frame(3, csha2p_request_key)) + .expect_read(create_more_data_frame(4, public_key_sm2)) + .check(fix, client_errc::protocol_value_error); +} + // TODO // fullauth invalid_key // fullauth fullauth diff --git a/test/unit/test/sansio/handshake/handshake_csha2p_hash_password.cpp b/test/unit/test/sansio/handshake/handshake_csha2p_hash_password.cpp index ca329449c..9e2e756cd 100644 --- a/test/unit/test/sansio/handshake/handshake_csha2p_hash_password.cpp +++ b/test/unit/test/sansio/handshake/handshake_csha2p_hash_password.cpp @@ -23,7 +23,7 @@ #include #include -#include "handshake_csh2p_keys.hpp" +#include "handshake_csha2p_keys.hpp" #include "test_common/assert_buffer_equals.hpp" #include "test_common/printing.hpp" @@ -390,14 +390,9 @@ BOOST_AUTO_TEST_CASE(error_key_not_rsa) 0x0f, 0x64, 0x4f, 0x2f, 0x2b, 0x3b, 0x27, 0x6b, 0x45, 0x5c, 0x53, 0x01, 0x13, 0x7e, 0x4f, 0x10, 0x26, 0x23, 0x5d, 0x27, }; - constexpr unsigned char key_buffer[] = R"%(-----BEGIN PUBLIC KEY----- -MFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAEWGAXSpPHb2bWQjROuegjWPcuVwNW -mVpTh++3j7pnpWUjnFuarvWmWh/H6t96/pTx566FKGxZpLn3H9TLHZJsog== ------END PUBLIC KEY----- -)%"; buffer_type buff; - auto ec = csha2p_encrypt_password("csha2p_password", scramble, key_buffer, buff, ssl_category); + auto ec = csha2p_encrypt_password("csha2p_password", scramble, public_key_sm2, buff, ssl_category); BOOST_TEST(ec == client_errc::protocol_value_error); // OpenSSL does not provide an error code here } diff --git a/test/unit/test/sansio/handshake/handshake_csh2p_keys.hpp b/test/unit/test/sansio/handshake/handshake_csha2p_keys.hpp similarity index 97% rename from test/unit/test/sansio/handshake/handshake_csh2p_keys.hpp rename to test/unit/test/sansio/handshake/handshake_csha2p_keys.hpp index 369a55f9a..69ffce546 100644 --- a/test/unit/test/sansio/handshake/handshake_csh2p_keys.hpp +++ b/test/unit/test/sansio/handshake/handshake_csha2p_keys.hpp @@ -5,8 +5,8 @@ // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // -#ifndef BOOST_MYSQL_TEST_UNIT_TEST_SANSIO_HANDSHAKE_HANDSHAKE_CSH2P_KEYS_HPP -#define BOOST_MYSQL_TEST_UNIT_TEST_SANSIO_HANDSHAKE_HANDSHAKE_CSH2P_KEYS_HPP +#ifndef BOOST_MYSQL_TEST_UNIT_TEST_SANSIO_HANDSHAKE_HANDSHAKE_CSHA2P_KEYS_HPP +#define BOOST_MYSQL_TEST_UNIT_TEST_SANSIO_HANDSHAKE_HANDSHAKE_CSHA2P_KEYS_HPP namespace boost { namespace mysql { @@ -182,6 +182,13 @@ FsKX4nSNK6rJ1uONSJu+dIiOA1YCabhCab7NRMCn+Bj+fHHkpvir24QbmqX1R8Au -----END PRIVATE KEY----- )%"; +// ECDSA public key set up for SM2 encryption +constexpr unsigned char public_key_sm2[] = R"%(-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAEWGAXSpPHb2bWQjROuegjWPcuVwNW +mVpTh++3j7pnpWUjnFuarvWmWh/H6t96/pTx566FKGxZpLn3H9TLHZJsog== +-----END PUBLIC KEY----- +)%"; + } // namespace test } // namespace mysql } // namespace boost From 20d92274084f9427d890607512dbfbbc795e19e5 Mon Sep 17 00:00:00 2001 From: Ruben Perez Date: Mon, 2 Jun 2025 19:31:11 +0200 Subject: [PATCH 34/68] Finished handshake tests --- .../sansio/handshake/handshake_csha2p.cpp | 28 +++++++++++++++---- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/test/unit/test/sansio/handshake/handshake_csha2p.cpp b/test/unit/test/sansio/handshake/handshake_csha2p.cpp index dcdb27307..c7982dc5c 100644 --- a/test/unit/test/sansio/handshake/handshake_csha2p.cpp +++ b/test/unit/test/sansio/handshake/handshake_csha2p.cpp @@ -328,11 +328,29 @@ BOOST_AUTO_TEST_CASE(fullauth_encrypterror) .check(fix, client_errc::protocol_value_error); } -// TODO -// fullauth invalid_key -// fullauth fullauth -// fullauth more_data -// fullauth key more_data +// After encrypting the password, no more messages are expected +BOOST_AUTO_TEST_CASE(fullauth_key_moredata) +{ + // Setup + handshake_fixture fix; + + // Run the test + algo_test() + .expect_read(server_hello_builder() + .caps(tls_caps) + .auth_plugin("caching_sha2_password") + .auth_data(csha2p_scramble) + .build()) + .expect_write( + login_request_builder().auth_plugin("caching_sha2_password").auth_response(csha2p_hash).build() + ) + .expect_read(create_more_data_frame(2, csha2p_perform_full_auth)) + .expect_write(create_frame(3, csha2p_request_key)) + .expect_read(create_more_data_frame(4, public_key_2048)) + .expect_any_write() // the exact encryption result is not deterministic + .expect_read(create_more_data_frame(6, csha2p_perform_full_auth)) + .check(fix, client_errc::bad_handshake_packet_type); +} // If we're using a secure transport (e.g. UNIX socket), caching_sha2_password // just sends the raw password From c2cc560e84f32a98cbc790b65f7f8ccb0761b3b5 Mon Sep 17 00:00:00 2001 From: Ruben Perez Date: Mon, 2 Jun 2025 19:37:46 +0200 Subject: [PATCH 35/68] Docs --- README.md | 2 +- doc/qbk/05_connection_establishment.qbk | 5 +++-- include/boost/mysql/client_errc.hpp | 5 ++++- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index cf76f4c80..0d77d98fc 100644 --- a/README.md +++ b/README.md @@ -74,7 +74,7 @@ And with the following databases: of executing a prepared statement is sent in binary format rather than in text. - Stored procedures. - Authentication methods (authentication plugins): mysql_native_password and - caching_sha2_password. These are the default methods in MySQL 5 and MySQL 8, + caching_sha2_password. These are the default methods in MySQL 5/MariaDB and MySQL 8, respectively. - Encrypted connections (TLS). - TCP and UNIX socket transports. diff --git a/doc/qbk/05_connection_establishment.qbk b/doc/qbk/05_connection_establishment.qbk index 1c3ac2afc..f548baa42 100644 --- a/doc/qbk/05_connection_establishment.qbk +++ b/doc/qbk/05_connection_establishment.qbk @@ -41,8 +41,9 @@ This library implements the two most common authentication plugins: connections. It sends the password hashed, salted by a nonce. * [mysqllink caching-sha2-pluggable-authentication.html `caching_sha2_password`]. This is the default plugin for - MySQL 8.0+. It can only be used over secure transports, - like TCP with TLS or UNIX sockets. + MySQL 8.0+. This plugin used to require the use of TLS. + Since Boost 1.89, this is no longer the case, and it can be used + with plaintext connections, too. Multi-factor authentication is not yet supported. diff --git a/include/boost/mysql/client_errc.hpp b/include/boost/mysql/client_errc.hpp index dc453b257..887d5251e 100644 --- a/include/boost/mysql/client_errc.hpp +++ b/include/boost/mysql/client_errc.hpp @@ -50,7 +50,10 @@ enum class client_errc : int /// The user employs an authentication plugin not known to this library. unknown_auth_plugin, - /// The authentication plugin requires the connection to use SSL. + /** + * \brief (Legacy) The authentication plugin requires the connection to use SSL. + * This code is no longer used, since all supported plugins support plaintext connections. + */ auth_plugin_requires_ssl, /** From 245c878077f005e129e8a5a7285827442e5565e8 Mon Sep 17 00:00:00 2001 From: Ruben Perez Date: Mon, 2 Jun 2025 19:50:37 +0200 Subject: [PATCH 36/68] Properly add the Boost.Container dependency --- CMakeLists.txt | 1 + build.jam | 1 + 2 files changed, 2 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index df6ace8ac..4c092cb19 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -36,6 +36,7 @@ target_link_libraries( Boost::charconv Boost::compat Boost::config + Boost::container Boost::core Boost::describe Boost::endian diff --git a/build.jam b/build.jam index a7af9a2bf..a560ff210 100644 --- a/build.jam +++ b/build.jam @@ -11,6 +11,7 @@ constant boost_dependencies : /boost/charconv//boost_charconv /boost/compat//boost_compat /boost/config//boost_config + /boost/container//boost_container /boost/core//boost_core /boost/describe//boost_describe /boost/endian//boost_endian From 1a197e1baa073288c02e2eb06195b03f5eb73d40 Mon Sep 17 00:00:00 2001 From: Ruben Perez Date: Mon, 2 Jun 2025 19:52:57 +0200 Subject: [PATCH 37/68] proper cmake for boost_mysql_test_csha2p_encrypt_password_errors --- test/unit/CMakeLists.txt | 34 +++++++++++++++++++++++++++------- 1 file changed, 27 insertions(+), 7 deletions(-) diff --git a/test/unit/CMakeLists.txt b/test/unit/CMakeLists.txt index 5e45d6590..f573ca175 100644 --- a/test/unit/CMakeLists.txt +++ b/test/unit/CMakeLists.txt @@ -155,13 +155,33 @@ add_test( COMMAND boost_mysql_unittests ) -# TODO: refactor this -add_executable(boost_mysql_test_csha2p_encrypt_password_errors test_csha2p_encrypt_password_errors.cpp) -get_target_property(boost_mysql_deps Boost::mysql INTERFACE_LINK_LIBRARIES) -list(REMOVE_ITEM boost_mysql_deps OpenSSL::Crypto OpenSSL::SSL) -target_link_libraries(boost_mysql_test_csha2p_encrypt_password_errors PRIVATE ${boost_mysql_deps}) -target_include_directories(boost_mysql_test_csha2p_encrypt_password_errors PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../../include ${OPENSSL_INCLUDE_DIR}) -target_compile_features(boost_mysql INTERFACE cxx_std_11) +# Tests edge cases when encrypting passwords that require mocking OpenSSL. +# This executable should not link to any OpenSSL libraries. +add_executable( + boost_mysql_test_csha2p_encrypt_password_errors + test_csha2p_encrypt_password_errors.cpp +) +target_include_directories( + boost_mysql_test_csha2p_encrypt_password_errors + PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/../../include # our own includes + ${OPENSSL_INCLUDE_DIR} # we only need the includes +) +target_link_libraries( + boost_mysql_test_csha2p_encrypt_password_errors + PRIVATE + Boost::unit_test_framework + Boost::assert + Boost::config + Boost::container + Boost::core + Boost::system +) +target_compile_features( + boost_mysql_test_csha2p_encrypt_password_errors + INTERFACE + cxx_std_11 +) boost_mysql_test_target_settings(boost_mysql_test_csha2p_encrypt_password_errors) add_test( NAME boost_mysql_test_csha2p_encrypt_password_errors From ffd20d8e71533d959aaa2bc1c1dd2e3842025bd5 Mon Sep 17 00:00:00 2001 From: Ruben Perez Date: Mon, 2 Jun 2025 20:10:18 +0200 Subject: [PATCH 38/68] B2 prototype --- test/unit/Jamfile | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/test/unit/Jamfile b/test/unit/Jamfile index dfd463fc6..448440148 100644 --- a/test/unit/Jamfile +++ b/test/unit/Jamfile @@ -144,3 +144,12 @@ run include : target-name boost_mysql_unittests ; + + +# Tests edge cases when encrypting passwords that require mocking OpenSSL. +# This executable should not link to any OpenSSL libraries. +# TODO: does this really work?? +run test_csha2p_encrypt_password_errors.cpp + /boost/mysql/test//boost_mysql + /boost/test//boost_unit_test_framework/off +; From fa4ff380d7b2e853c6e353aceab4dff903b54023 Mon Sep 17 00:00:00 2001 From: Ruben Perez Date: Mon, 2 Jun 2025 20:18:35 +0200 Subject: [PATCH 39/68] mock test cleanup --- .../handshake/handshake_csha2p_hash_password.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/test/unit/test/sansio/handshake/handshake_csha2p_hash_password.cpp b/test/unit/test/sansio/handshake/handshake_csha2p_hash_password.cpp index 9e2e756cd..a971f62fd 100644 --- a/test/unit/test/sansio/handshake/handshake_csha2p_hash_password.cpp +++ b/test/unit/test/sansio/handshake/handshake_csha2p_hash_password.cpp @@ -100,15 +100,15 @@ std::vector decrypt( // Create a BIO with the key unique_bio bio(BIO_new_mem_buf(private_key.data(), private_key.size())); - BOOST_TEST_REQUIRE(bio != nullptr, "Creating a BIO failed: " << ERR_get_error()); + BOOST_TEST_REQUIRE((bio != nullptr), "Creating a BIO failed: " << ERR_get_error()); // Load the key unique_evp_pkey pkey(PEM_read_bio_PrivateKey(bio.get(), nullptr, nullptr, nullptr)); - BOOST_TEST_REQUIRE(bio != nullptr, "Loading the key failed: " << ERR_get_error()); + BOOST_TEST_REQUIRE((bio != nullptr), "Loading the key failed: " << ERR_get_error()); // Create a decryption context unique_evp_pkey_ctx ctx(EVP_PKEY_CTX_new(pkey.get(), nullptr)); - BOOST_TEST_REQUIRE(ctx != nullptr, "Creating a context failed: " << ERR_get_error()); + BOOST_TEST_REQUIRE((ctx != nullptr), "Creating a context failed: " << ERR_get_error()); // Initialize it BOOST_TEST_REQUIRE( @@ -341,7 +341,7 @@ BOOST_AUTO_TEST_CASE(error_key_buffer_empty) buffer_type buff; auto ec = csha2p_encrypt_password("csha2p_password", scramble, {}, buff, ssl_category); BOOST_TEST((ec.category() == ssl_category)); - BOOST_TEST(ec.value() > 0u); // is an error + BOOST_TEST(ec.value() > 0); // is an error BOOST_TEST(ec.message() != ""); // produces some output } @@ -355,7 +355,7 @@ BOOST_AUTO_TEST_CASE(error_key_malformed) buffer_type buff; auto ec = csha2p_encrypt_password("csha2p_password", scramble, key_buffer, buff, ssl_category); BOOST_TEST((ec.category() == ssl_category)); - BOOST_TEST(ec.value() > 0u); // is an error + BOOST_TEST(ec.value() > 0); // is an error BOOST_TEST(ec.message() != ""); // produces some output } @@ -376,7 +376,7 @@ MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAERDkCI/degPJXEIYYncyvGsTdj9YI buffer_type buff; auto ec = csha2p_encrypt_password("csha2p_password", scramble, key_buffer, buff, ssl_category); BOOST_TEST((ec.category() == ssl_category)); - BOOST_TEST(ec.value() > 0u); // is an error + BOOST_TEST(ec.value() > 0); // is an error BOOST_TEST(ec.message() != ""); // produces some output } From 397255a6c2dcebb4f5312a085d4315b874a273b6 Mon Sep 17 00:00:00 2001 From: Ruben Perez Date: Mon, 2 Jun 2025 20:21:32 +0200 Subject: [PATCH 40/68] todo cleanup --- .../mysql/impl/internal/sansio/csha2p_encrypt_password.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/boost/mysql/impl/internal/sansio/csha2p_encrypt_password.hpp b/include/boost/mysql/impl/internal/sansio/csha2p_encrypt_password.hpp index 29ffe2f05..d960f845f 100644 --- a/include/boost/mysql/impl/internal/sansio/csha2p_encrypt_password.hpp +++ b/include/boost/mysql/impl/internal/sansio/csha2p_encrypt_password.hpp @@ -103,7 +103,7 @@ inline error_code csha2p_encrypt_password( }; using unique_evp_pkey_ctx = std::unique_ptr; - // Try to parse the private key. TODO: size check here + // Try to parse the private key unique_bio bio{BIO_new_mem_buf(server_key.data(), server_key.size())}; if (!bio) { From 5eb1f1c5da4b79d16c247f07db21a9e6e53f6181 Mon Sep 17 00:00:00 2001 From: Ruben Perez Date: Mon, 2 Jun 2025 20:25:35 +0200 Subject: [PATCH 41/68] split hash/encrypt password tests --- test/unit/CMakeLists.txt | 1 + test/unit/Jamfile | 1 + .../handshake_csha2p_encrypt_password.cpp | 375 ++++++++++++++++++ .../handshake_csha2p_hash_password.cpp | 373 +---------------- .../handshake/handshake_mnp_hash_password.cpp | 13 +- 5 files changed, 390 insertions(+), 373 deletions(-) create mode 100644 test/unit/test/sansio/handshake/handshake_csha2p_encrypt_password.cpp diff --git a/test/unit/CMakeLists.txt b/test/unit/CMakeLists.txt index f573ca175..85bcf8d6a 100644 --- a/test/unit/CMakeLists.txt +++ b/test/unit/CMakeLists.txt @@ -34,6 +34,7 @@ add_executable( test/sansio/handshake/handshake_mnp_hash_password.cpp test/sansio/handshake/handshake_csha2p.cpp test/sansio/handshake/handshake_csha2p_hash_password.cpp + test/sansio/handshake/handshake_csha2p_encrypt_password.cpp test/sansio/handshake/handshake_capabilities.cpp test/sansio/handshake/handshake_connection_state_data.cpp test/sansio/read_resultset_head.cpp diff --git a/test/unit/Jamfile b/test/unit/Jamfile index 448440148..97b176fb6 100644 --- a/test/unit/Jamfile +++ b/test/unit/Jamfile @@ -43,6 +43,7 @@ run test/sansio/handshake/handshake_mnp_hash_password.cpp test/sansio/handshake/handshake_csha2p.cpp test/sansio/handshake/handshake_csha2p_hash_password.cpp + test/sansio/handshake/handshake_csha2p_encrypt_password.cpp test/sansio/handshake/handshake_capabilities.cpp test/sansio/handshake/handshake_connection_state_data.cpp test/sansio/read_resultset_head.cpp diff --git a/test/unit/test/sansio/handshake/handshake_csha2p_encrypt_password.cpp b/test/unit/test/sansio/handshake/handshake_csha2p_encrypt_password.cpp new file mode 100644 index 000000000..0bdd89579 --- /dev/null +++ b/test/unit/test/sansio/handshake/handshake_csha2p_encrypt_password.cpp @@ -0,0 +1,375 @@ +// +// Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// + +#include +#include +#include + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "handshake_csha2p_keys.hpp" +#include "test_common/assert_buffer_equals.hpp" +#include "test_common/printing.hpp" + +using namespace boost::mysql; +using namespace boost::mysql::test; +using boost::asio::error::ssl_category; +using detail::csha2p_encrypt_password; + +using buffer_type = boost::container::small_vector; + +namespace { + +// Encrypting with RSA and OAEP padding involves random numbers for padding. +// There isn't a reliable way to seed OpenSSL's random number generators so that +// the output is deterministic. So we do the following: +// 1. We know the server's public and private keys and the password (the constants below). +// 2. We capture a scramble and a corresponding ciphertext using Wireshark. +// 3. We decrypt the ciphertext with OpenSSL to obtain the expected plaintext (the salted password). +// 4. Tests run csha2p_encrypt_password, decrypt its output, and verify that it matches the expected +// plaintext. +BOOST_AUTO_TEST_SUITE(test_handshake_csha2p_encrypt_password) + +// Decrypts the output of +std::vector decrypt( + boost::span private_key, + boost::span ciphertext +) +{ + // RAII helpers + struct bio_deleter + { + void operator()(BIO* bio) const noexcept { BIO_free(bio); } + }; + using unique_bio = std::unique_ptr; + + struct evp_pkey_deleter + { + void operator()(EVP_PKEY* pkey) const noexcept { EVP_PKEY_free(pkey); } + }; + using unique_evp_pkey = std::unique_ptr; + + struct evp_pkey_ctx_deleter + { + void operator()(EVP_PKEY_CTX* ctx) const noexcept { EVP_PKEY_CTX_free(ctx); } + }; + using unique_evp_pkey_ctx = std::unique_ptr; + + // Create a BIO with the key + unique_bio bio(BIO_new_mem_buf(private_key.data(), private_key.size())); + BOOST_TEST_REQUIRE((bio != nullptr), "Creating a BIO failed: " << ERR_get_error()); + + // Load the key + unique_evp_pkey pkey(PEM_read_bio_PrivateKey(bio.get(), nullptr, nullptr, nullptr)); + BOOST_TEST_REQUIRE((bio != nullptr), "Loading the key failed: " << ERR_get_error()); + + // Create a decryption context + unique_evp_pkey_ctx ctx(EVP_PKEY_CTX_new(pkey.get(), nullptr)); + BOOST_TEST_REQUIRE((ctx != nullptr), "Creating a context failed: " << ERR_get_error()); + + // Initialize it + BOOST_TEST_REQUIRE( + EVP_PKEY_decrypt_init(ctx.get()) > 0, + "Initializing decryption failed: " << ERR_get_error() + ); + + // Set the padding scheme + BOOST_TEST_REQUIRE( + EVP_PKEY_CTX_set_rsa_padding(ctx.get(), RSA_PKCS1_OAEP_PADDING) > 0, + "Setting the padding scheme failed: " << ERR_get_error() + ); + + // Determine the size of the decrypted buffer + std::size_t out_size = 0; + BOOST_TEST_REQUIRE( + EVP_PKEY_decrypt(ctx.get(), nullptr, &out_size, ciphertext.data(), ciphertext.size()) > 0, + "Determining decryption size failed: " << ERR_get_error() + ); + std::vector res(out_size, 0); + + // Actually decrypt + BOOST_TEST_REQUIRE( + EVP_PKEY_decrypt(ctx.get(), res.data(), &out_size, ciphertext.data(), ciphertext.size()) > 0, + "Decrypting failed: " << ERR_get_error() + ); + res.resize(out_size); + + // Done + return res; +} + +BOOST_AUTO_TEST_CASE(success) +{ + // Common for all tests + constexpr std::uint8_t scramble[] = { + 0x0f, 0x64, 0x4f, 0x2f, 0x2b, 0x3b, 0x27, 0x6b, 0x45, 0x5c, + 0x53, 0x01, 0x13, 0x7e, 0x4f, 0x10, 0x26, 0x23, 0x5d, 0x27, + }; + + // A password with all the possible ASCII values + std::string all_chars_password; + for (int i = 0; i < 256; ++i) + all_chars_password.push_back(static_cast(i)); + + struct + { + string_view name; + std::string password; + boost::span public_key; + boost::span private_key; + std::vector expected_decrypted; + } test_cases[] = { + // clang-format off + // An empty password does not cause trouble. This is an edge case, since + // an empty password should employ a different workflow + { + "password_empty", + {}, + public_key_2048, + private_key_2048, + {0x0f} + }, + + // Password is < length of the scramble + { + "password_shorter_scramble", + "csha2p_password", + public_key_2048, + private_key_2048, + { + 0x6c, 0x17, 0x27, 0x4e, 0x19, 0x4b, 0x78, 0x1b, 0x24, 0x2f, 0x20, 0x76, 0x7c, 0x0c, 0x2b, 0x10 + } + }, + + // Password (with NULL byte) is equal to the length of the scramble + { + "password_same_size_scramble", + "hjbjd923KKLiosoi90J", + public_key_2048, + private_key_2048, + { + 0x67, 0x0e, 0x2d, 0x45, 0x4f, 0x02, 0x15, 0x58, 0x0e, 0x17, + 0x1f, 0x68, 0x7c, 0x0d, 0x20, 0x79, 0x1f, 0x13, 0x17, 0x27 + } + }, + + // Passwords longer than the scramble use it cyclically + { + "password_longer_scramble", + "kjaski829380jvnnM,ka;::_kshf93IJCLIJO)jcjsnaklO?a", + public_key_2048, + private_key_2048, + { + 0x64, 0x0e, 0x2e, 0x5c, 0x40, 0x52, 0x1f, 0x59, 0x7c, 0x6f, 0x6b, 0x31, 0x79, 0x08, 0x21, 0x7e, 0x6b, + 0x0f, 0x36, 0x46, 0x34, 0x5e, 0x75, 0x70, 0x40, 0x48, 0x4f, 0x0d, 0x7c, 0x6f, 0x1a, 0x4b, 0x50, 0x32, + 0x06, 0x5a, 0x69, 0x0a, 0x37, 0x44, 0x65, 0x17, 0x21, 0x4e, 0x40, 0x57, 0x68, 0x54, 0x24, 0x5c, + } + }, + + // The longest password that a 2048 RSA key can encrypt with the OAEP scheme + { + "password_max_size_2048", + "hjbjd923KKLkjbdkcjwhekiy8393ou2weusidhiahJBKJKHCIHCKJIu9KHO09IJIpojaf0w39jalsjMMKjkjhiue93I=))" + "UXIOJKXNKNhkai8923oiawiakssaknhakhIIHICHIO)CU)" + "IHCKHCKJhisiweioHHJHUCHIIIJIOPkjgwijiosoi9jsu84HHUHCHI9839hdjsbsdjuUHJjbJ", + public_key_2048, + private_key_2048, + { + 0x67, 0x0e, 0x2d, 0x45, 0x4f, 0x02, 0x15, 0x58, 0x0e, 0x17, 0x1f, 0x6a, 0x79, 0x1c, 0x2b, 0x7b, 0x45, + 0x49, 0x2a, 0x4f, 0x6a, 0x0f, 0x26, 0x56, 0x13, 0x08, 0x1e, 0x58, 0x2a, 0x29, 0x61, 0x76, 0x76, 0x0b, + 0x3c, 0x79, 0x42, 0x4b, 0x34, 0x46, 0x67, 0x2e, 0x0d, 0x64, 0x61, 0x70, 0x6f, 0x28, 0x0c, 0x14, 0x10, + 0x4a, 0x59, 0x37, 0x3a, 0x29, 0x6d, 0x6b, 0x12, 0x17, 0x36, 0x2d, 0x05, 0x66, 0x5b, 0x54, 0x4d, 0x0a, + 0x23, 0x6c, 0x24, 0x32, 0x2a, 0x14, 0x2e, 0x7c, 0x55, 0x49, 0x10, 0x6a, 0x44, 0x0e, 0x24, 0x45, 0x43, + 0x52, 0x52, 0x0e, 0x7c, 0x6f, 0x1a, 0x3c, 0x3a, 0x57, 0x1a, 0x48, 0x6f, 0x6c, 0x17, 0x6c, 0x57, 0x2a, + 0x04, 0x61, 0x43, 0x50, 0x46, 0x02, 0x7d, 0x65, 0x61, 0x32, 0x7c, 0x17, 0x2e, 0x67, 0x4f, 0x42, 0x36, + 0x54, 0x7c, 0x05, 0x24, 0x41, 0x43, 0x5a, 0x4c, 0x03, 0x0c, 0x15, 0x1b, 0x48, 0x50, 0x36, 0x06, 0x5f, + 0x0f, 0x60, 0x08, 0x0e, 0x46, 0x2c, 0x0c, 0x64, 0x63, 0x78, 0x6c, 0x21, 0x2d, 0x35, 0x20, 0x68, 0x64, + 0x1b, 0x26, 0x7f, 0x6e, 0x6b, 0x17, 0x6f, 0x5a, 0x27, 0x07, 0x66, 0x62, 0x72, 0x6d, 0x22, 0x0a, 0x0c, + 0x38, 0x6b, 0x74, 0x09, 0x26, 0x7a, 0x4f, 0x4c, 0x2e, 0x48, 0x66, 0x5d, 0x25, 0x5c, 0x5e, 0x03, 0x13, + 0x23, 0x0d, 0x09, 0x1b, 0x42, 0x5b, 0x37, 0x76, 0x28, 0x15, 0x1a, 0x35, 0x43, 0x65, 0x17, 0x2d, 0x5c, + 0x4f, 0x51, 0x52, 0x3e, 0x0d, 0x16, 0x39, 0x63, 0x59, 0x7e, + } + }, + + // Longer passwords are accepted if the key supports them. + // Concretely, passwords longer than 512 (size of our SBO) are supported + { + "password_longer_sbo", + std::string(600, '5'), + public_key_8192, + private_key_8192, + { + 0x3a, 0x51, 0x7a, 0x1a, 0x1e, 0x0e, 0x12, 0x5e, 0x70, 0x69, 0x66, 0x34, 0x26, 0x4b, 0x7a, 0x25, 0x13, + 0x16, 0x68, 0x12, 0x3a, 0x51, 0x7a, 0x1a, 0x1e, 0x0e, 0x12, 0x5e, 0x70, 0x69, 0x66, 0x34, 0x26, 0x4b, + 0x7a, 0x25, 0x13, 0x16, 0x68, 0x12, 0x3a, 0x51, 0x7a, 0x1a, 0x1e, 0x0e, 0x12, 0x5e, 0x70, 0x69, 0x66, + 0x34, 0x26, 0x4b, 0x7a, 0x25, 0x13, 0x16, 0x68, 0x12, 0x3a, 0x51, 0x7a, 0x1a, 0x1e, 0x0e, 0x12, 0x5e, + 0x70, 0x69, 0x66, 0x34, 0x26, 0x4b, 0x7a, 0x25, 0x13, 0x16, 0x68, 0x12, 0x3a, 0x51, 0x7a, 0x1a, 0x1e, + 0x0e, 0x12, 0x5e, 0x70, 0x69, 0x66, 0x34, 0x26, 0x4b, 0x7a, 0x25, 0x13, 0x16, 0x68, 0x12, 0x3a, 0x51, + 0x7a, 0x1a, 0x1e, 0x0e, 0x12, 0x5e, 0x70, 0x69, 0x66, 0x34, 0x26, 0x4b, 0x7a, 0x25, 0x13, 0x16, 0x68, + 0x12, 0x3a, 0x51, 0x7a, 0x1a, 0x1e, 0x0e, 0x12, 0x5e, 0x70, 0x69, 0x66, 0x34, 0x26, 0x4b, 0x7a, 0x25, + 0x13, 0x16, 0x68, 0x12, 0x3a, 0x51, 0x7a, 0x1a, 0x1e, 0x0e, 0x12, 0x5e, 0x70, 0x69, 0x66, 0x34, 0x26, + 0x4b, 0x7a, 0x25, 0x13, 0x16, 0x68, 0x12, 0x3a, 0x51, 0x7a, 0x1a, 0x1e, 0x0e, 0x12, 0x5e, 0x70, 0x69, + 0x66, 0x34, 0x26, 0x4b, 0x7a, 0x25, 0x13, 0x16, 0x68, 0x12, 0x3a, 0x51, 0x7a, 0x1a, 0x1e, 0x0e, 0x12, + 0x5e, 0x70, 0x69, 0x66, 0x34, 0x26, 0x4b, 0x7a, 0x25, 0x13, 0x16, 0x68, 0x12, 0x3a, 0x51, 0x7a, 0x1a, + 0x1e, 0x0e, 0x12, 0x5e, 0x70, 0x69, 0x66, 0x34, 0x26, 0x4b, 0x7a, 0x25, 0x13, 0x16, 0x68, 0x12, 0x3a, + 0x51, 0x7a, 0x1a, 0x1e, 0x0e, 0x12, 0x5e, 0x70, 0x69, 0x66, 0x34, 0x26, 0x4b, 0x7a, 0x25, 0x13, 0x16, + 0x68, 0x12, 0x3a, 0x51, 0x7a, 0x1a, 0x1e, 0x0e, 0x12, 0x5e, 0x70, 0x69, 0x66, 0x34, 0x26, 0x4b, 0x7a, + 0x25, 0x13, 0x16, 0x68, 0x12, 0x3a, 0x51, 0x7a, 0x1a, 0x1e, 0x0e, 0x12, 0x5e, 0x70, 0x69, 0x66, 0x34, + 0x26, 0x4b, 0x7a, 0x25, 0x13, 0x16, 0x68, 0x12, 0x3a, 0x51, 0x7a, 0x1a, 0x1e, 0x0e, 0x12, 0x5e, 0x70, + 0x69, 0x66, 0x34, 0x26, 0x4b, 0x7a, 0x25, 0x13, 0x16, 0x68, 0x12, 0x3a, 0x51, 0x7a, 0x1a, 0x1e, 0x0e, + 0x12, 0x5e, 0x70, 0x69, 0x66, 0x34, 0x26, 0x4b, 0x7a, 0x25, 0x13, 0x16, 0x68, 0x12, 0x3a, 0x51, 0x7a, + 0x1a, 0x1e, 0x0e, 0x12, 0x5e, 0x70, 0x69, 0x66, 0x34, 0x26, 0x4b, 0x7a, 0x25, 0x13, 0x16, 0x68, 0x12, + 0x3a, 0x51, 0x7a, 0x1a, 0x1e, 0x0e, 0x12, 0x5e, 0x70, 0x69, 0x66, 0x34, 0x26, 0x4b, 0x7a, 0x25, 0x13, + 0x16, 0x68, 0x12, 0x3a, 0x51, 0x7a, 0x1a, 0x1e, 0x0e, 0x12, 0x5e, 0x70, 0x69, 0x66, 0x34, 0x26, 0x4b, + 0x7a, 0x25, 0x13, 0x16, 0x68, 0x12, 0x3a, 0x51, 0x7a, 0x1a, 0x1e, 0x0e, 0x12, 0x5e, 0x70, 0x69, 0x66, + 0x34, 0x26, 0x4b, 0x7a, 0x25, 0x13, 0x16, 0x68, 0x12, 0x3a, 0x51, 0x7a, 0x1a, 0x1e, 0x0e, 0x12, 0x5e, + 0x70, 0x69, 0x66, 0x34, 0x26, 0x4b, 0x7a, 0x25, 0x13, 0x16, 0x68, 0x12, 0x3a, 0x51, 0x7a, 0x1a, 0x1e, + 0x0e, 0x12, 0x5e, 0x70, 0x69, 0x66, 0x34, 0x26, 0x4b, 0x7a, 0x25, 0x13, 0x16, 0x68, 0x12, 0x3a, 0x51, + 0x7a, 0x1a, 0x1e, 0x0e, 0x12, 0x5e, 0x70, 0x69, 0x66, 0x34, 0x26, 0x4b, 0x7a, 0x25, 0x13, 0x16, 0x68, + 0x12, 0x3a, 0x51, 0x7a, 0x1a, 0x1e, 0x0e, 0x12, 0x5e, 0x70, 0x69, 0x66, 0x34, 0x26, 0x4b, 0x7a, 0x25, + 0x13, 0x16, 0x68, 0x12, 0x3a, 0x51, 0x7a, 0x1a, 0x1e, 0x0e, 0x12, 0x5e, 0x70, 0x69, 0x66, 0x34, 0x26, + 0x4b, 0x7a, 0x25, 0x13, 0x16, 0x68, 0x12, 0x3a, 0x51, 0x7a, 0x1a, 0x1e, 0x0e, 0x12, 0x5e, 0x70, 0x69, + 0x66, 0x34, 0x26, 0x4b, 0x7a, 0x25, 0x13, 0x16, 0x68, 0x12, 0x3a, 0x51, 0x7a, 0x1a, 0x1e, 0x0e, 0x12, + 0x5e, 0x70, 0x69, 0x66, 0x34, 0x26, 0x4b, 0x7a, 0x25, 0x13, 0x16, 0x68, 0x12, 0x3a, 0x51, 0x7a, 0x1a, + 0x1e, 0x0e, 0x12, 0x5e, 0x70, 0x69, 0x66, 0x34, 0x26, 0x4b, 0x7a, 0x25, 0x13, 0x16, 0x68, 0x12, 0x3a, + 0x51, 0x7a, 0x1a, 0x1e, 0x0e, 0x12, 0x5e, 0x70, 0x69, 0x66, 0x34, 0x26, 0x4b, 0x7a, 0x25, 0x13, 0x16, + 0x68, 0x12, 0x3a, 0x51, 0x7a, 0x1a, 0x1e, 0x0e, 0x12, 0x5e, 0x70, 0x69, 0x66, 0x34, 0x26, 0x4b, 0x7a, + 0x25, 0x13, 0x16, 0x68, 0x12, 0x0f + } + }, + + // No character causes trouble + { + "password_all_characters", + all_chars_password, + public_key_8192, + private_key_8192, + { + 0x0f, 0x65, 0x4d, 0x2c, 0x2f, 0x3e, 0x21, 0x6c, 0x4d, 0x55, 0x59, 0x0a, 0x1f, 0x73, 0x41, 0x1f, 0x36, + 0x32, 0x4f, 0x34, 0x1b, 0x71, 0x59, 0x38, 0x33, 0x22, 0x3d, 0x70, 0x59, 0x41, 0x4d, 0x1e, 0x33, 0x5f, + 0x6d, 0x33, 0x02, 0x06, 0x7b, 0x00, 0x27, 0x4d, 0x65, 0x04, 0x07, 0x16, 0x09, 0x44, 0x75, 0x6d, 0x61, + 0x32, 0x27, 0x4b, 0x79, 0x27, 0x1e, 0x1a, 0x67, 0x1c, 0x33, 0x59, 0x71, 0x10, 0x6b, 0x7a, 0x65, 0x28, + 0x01, 0x19, 0x15, 0x46, 0x5b, 0x37, 0x05, 0x5b, 0x6a, 0x6e, 0x13, 0x68, 0x5f, 0x35, 0x1d, 0x7c, 0x7f, + 0x6e, 0x71, 0x3c, 0x1d, 0x05, 0x09, 0x5a, 0x4f, 0x23, 0x11, 0x4f, 0x46, 0x42, 0x3f, 0x44, 0x6b, 0x01, + 0x29, 0x48, 0x43, 0x52, 0x4d, 0x00, 0x29, 0x31, 0x3d, 0x6e, 0x63, 0x0f, 0x3d, 0x63, 0x52, 0x56, 0x2b, + 0x50, 0x77, 0x1d, 0x35, 0x54, 0x57, 0x46, 0x59, 0x14, 0xc5, 0xdd, 0xd1, 0x82, 0x97, 0xfb, 0xc9, 0x97, + 0xae, 0xaa, 0xd7, 0xac, 0x83, 0xe9, 0xc1, 0xa0, 0xbb, 0xaa, 0xb5, 0xf8, 0xd1, 0xc9, 0xc5, 0x96, 0x8b, + 0xe7, 0xd5, 0x8b, 0xba, 0xbe, 0xc3, 0xb8, 0xaf, 0xc5, 0xed, 0x8c, 0x8f, 0x9e, 0x81, 0xcc, 0xed, 0xf5, + 0xf9, 0xaa, 0xbf, 0xd3, 0xe1, 0xbf, 0x96, 0x92, 0xef, 0x94, 0xbb, 0xd1, 0xf9, 0x98, 0x93, 0x82, 0x9d, + 0xd0, 0xf9, 0xe1, 0xed, 0xbe, 0xd3, 0xbf, 0x8d, 0xd3, 0xe2, 0xe6, 0x9b, 0xe0, 0xc7, 0xad, 0x85, 0xe4, + 0xe7, 0xf6, 0xe9, 0xa4, 0x95, 0x8d, 0x81, 0xd2, 0xc7, 0xab, 0x99, 0xc7, 0xfe, 0xfa, 0x87, 0xfc, 0xd3, + 0xb9, 0x91, 0xf0, 0xcb, 0xda, 0xc5, 0x88, 0xa1, 0xb9, 0xb5, 0xe6, 0xfb, 0x97, 0xa5, 0xfb, 0xca, 0xce, + 0xb3, 0xc8, 0xff, 0x95, 0xbd, 0xdc, 0xdf, 0xce, 0xd1, 0x9c, 0xbd, 0xa5, 0xa9, 0xfa, 0xef, 0x83, 0xb1, + 0xef, 0x26 + } + } + + // clang-format on + }; + + for (const auto& tc : test_cases) + { + BOOST_TEST_CONTEXT(tc.name) + { + // Setup + buffer_type buff; + + // Call the function + auto ec = csha2p_encrypt_password(tc.password, scramble, tc.public_key, buff, ssl_category); + + // Verify + BOOST_TEST_REQUIRE(ec == error_code()); + BOOST_MYSQL_ASSERT_BUFFER_EQUALS(decrypt(tc.private_key, buff), tc.expected_decrypted); + } + } +} + +// +// Errors. It's not defined what exact error code will each function return. +// + +// We passed an empty buffer to the key parser +BOOST_AUTO_TEST_CASE(error_key_buffer_empty) +{ + constexpr std::uint8_t scramble[] = { + 0x0f, 0x64, 0x4f, 0x2f, 0x2b, 0x3b, 0x27, 0x6b, 0x45, 0x5c, + 0x53, 0x01, 0x13, 0x7e, 0x4f, 0x10, 0x26, 0x23, 0x5d, 0x27, + }; + buffer_type buff; + auto ec = csha2p_encrypt_password("csha2p_password", scramble, {}, buff, ssl_category); + BOOST_TEST((ec.category() == ssl_category)); + BOOST_TEST(ec.value() > 0); // is an error + BOOST_TEST(ec.message() != ""); // produces some output +} + +BOOST_AUTO_TEST_CASE(error_key_malformed) +{ + constexpr std::uint8_t scramble[] = { + 0x0f, 0x64, 0x4f, 0x2f, 0x2b, 0x3b, 0x27, 0x6b, 0x45, 0x5c, + 0x53, 0x01, 0x13, 0x7e, 0x4f, 0x10, 0x26, 0x23, 0x5d, 0x27, + }; + constexpr std::uint8_t key_buffer[] = "-----BEGIN PUBLIC KEY-----zwIDAQAB__kaj0?))="; + buffer_type buff; + auto ec = csha2p_encrypt_password("csha2p_password", scramble, key_buffer, buff, ssl_category); + BOOST_TEST((ec.category() == ssl_category)); + BOOST_TEST(ec.value() > 0); // is an error + BOOST_TEST(ec.message() != ""); // produces some output +} + +// Passing in a public key type that does not support encryption operations +// (like ECDSA) fails +BOOST_AUTO_TEST_CASE(error_key_doesnt_support_encryption) +{ + constexpr std::uint8_t scramble[] = { + 0x0f, 0x64, 0x4f, 0x2f, 0x2b, 0x3b, 0x27, 0x6b, 0x45, 0x5c, + 0x53, 0x01, 0x13, 0x7e, 0x4f, 0x10, 0x26, 0x23, 0x5d, 0x27, + }; + constexpr unsigned char key_buffer[] = R"%(-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAERDkCI/degPJXEIYYncyvGsTdj9YI +4xQ6KPTzoF+DY2jM09w1TCncxk9XV1eOo44UMDUuK9K01halQy70mohFSQ== +-----END PUBLIC KEY----- +)%"; + + buffer_type buff; + auto ec = csha2p_encrypt_password("csha2p_password", scramble, key_buffer, buff, ssl_category); + BOOST_TEST((ec.category() == ssl_category)); + BOOST_TEST(ec.value() > 0); // is an error + BOOST_TEST(ec.message() != ""); // produces some output +} + +// Passing in a public key type that allows encryption but is not RSA fails as expected. +// This is a SM2 key, generated by: +// openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:sm2 -out public.pem +// openssl pkey -in private.pem -pubout -out public.pem +BOOST_AUTO_TEST_CASE(error_key_not_rsa) +{ + constexpr std::uint8_t scramble[] = { + 0x0f, 0x64, 0x4f, 0x2f, 0x2b, 0x3b, 0x27, 0x6b, 0x45, 0x5c, + 0x53, 0x01, 0x13, 0x7e, 0x4f, 0x10, 0x26, 0x23, 0x5d, 0x27, + }; + + buffer_type buff; + auto ec = csha2p_encrypt_password("csha2p_password", scramble, public_key_sm2, buff, ssl_category); + BOOST_TEST(ec == client_errc::protocol_value_error); // OpenSSL does not provide an error code here +} + +// TODO: verify that these error codes have source code info + +BOOST_AUTO_TEST_SUITE_END() + +} // namespace \ No newline at end of file diff --git a/test/unit/test/sansio/handshake/handshake_csha2p_hash_password.cpp b/test/unit/test/sansio/handshake/handshake_csha2p_hash_password.cpp index a971f62fd..2fbcaefa9 100644 --- a/test/unit/test/sansio/handshake/handshake_csha2p_hash_password.cpp +++ b/test/unit/test/sansio/handshake/handshake_csha2p_hash_password.cpp @@ -5,399 +5,40 @@ // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // -#include -#include -#include - #include -#include -#include -#include #include -#include -#include -#include -#include -#include -#include - -#include "handshake_csha2p_keys.hpp" #include "test_common/assert_buffer_equals.hpp" -#include "test_common/printing.hpp" using namespace boost::mysql; -using namespace boost::mysql::test; -using boost::asio::error::ssl_category; -using detail::csha2p_encrypt_password; using detail::csha2p_hash_password; -using buffer_type = boost::container::small_vector; - namespace { BOOST_AUTO_TEST_SUITE(test_handshake_csha2p_hash_password) -// Values snooped using the MySQL Python connector -// TODO: this doesn't make a lot of sense as a separate test now +// Values snooped using Wireshark constexpr std::uint8_t scramble[20] = { 0x3e, 0x3b, 0x4, 0x55, 0x4, 0x70, 0x16, 0x3a, 0x4c, 0x15, 0x35, 0x3, 0x15, 0x76, 0x73, 0x22, 0x46, 0x8, 0x18, 0x1, }; -constexpr std::uint8_t hash[32] = { - 0xa1, 0xc1, 0xe1, 0xe9, 0x1b, 0xb6, 0x54, 0x4b, 0xa7, 0x37, 0x4b, 0x9c, 0x56, 0x6d, 0x69, 0x3e, - 0x6, 0xca, 0x7, 0x2, 0x98, 0xac, 0xd1, 0x6, 0x18, 0xc6, 0x90, 0x38, 0x9d, 0x88, 0xe1, 0x20, -}; - BOOST_AUTO_TEST_CASE(nonempty_password) { - BOOST_MYSQL_ASSERT_BUFFER_EQUALS(csha2p_hash_password("hola", scramble), hash); -} - -BOOST_AUTO_TEST_CASE(empty_password) -{ - BOOST_MYSQL_ASSERT_BUFFER_EQUALS(csha2p_hash_password("", scramble), std::vector()); -} - -BOOST_AUTO_TEST_SUITE_END() - -// Encrypting with RSA and OAEP padding involves random numbers for padding. -// There isn't a reliable way to seed OpenSSL's random number generators so that -// the output is deterministic. So we do the following: -// 1. We know the server's public and private keys and the password (the constants below). -// 2. We capture a scramble and a corresponding ciphertext using Wireshark. -// 3. We decrypt the ciphertext with OpenSSL to obtain the expected plaintext (the salted password). -// 4. Tests run csha2p_encrypt_password, decrypt its output, and verify that it matches the expected -// plaintext. -BOOST_AUTO_TEST_SUITE(test_handshake_csha2p_encrypt_password) - -// Decrypts the output of -std::vector decrypt( - boost::span private_key, - boost::span ciphertext -) -{ - // RAII helpers - struct bio_deleter - { - void operator()(BIO* bio) const noexcept { BIO_free(bio); } + constexpr std::uint8_t expected_hash[32] = { + 0xa1, 0xc1, 0xe1, 0xe9, 0x1b, 0xb6, 0x54, 0x4b, 0xa7, 0x37, 0x4b, 0x9c, 0x56, 0x6d, 0x69, 0x3e, + 0x6, 0xca, 0x7, 0x2, 0x98, 0xac, 0xd1, 0x6, 0x18, 0xc6, 0x90, 0x38, 0x9d, 0x88, 0xe1, 0x20, }; - using unique_bio = std::unique_ptr; - struct evp_pkey_deleter - { - void operator()(EVP_PKEY* pkey) const noexcept { EVP_PKEY_free(pkey); } - }; - using unique_evp_pkey = std::unique_ptr; - - struct evp_pkey_ctx_deleter - { - void operator()(EVP_PKEY_CTX* ctx) const noexcept { EVP_PKEY_CTX_free(ctx); } - }; - using unique_evp_pkey_ctx = std::unique_ptr; - - // Create a BIO with the key - unique_bio bio(BIO_new_mem_buf(private_key.data(), private_key.size())); - BOOST_TEST_REQUIRE((bio != nullptr), "Creating a BIO failed: " << ERR_get_error()); - - // Load the key - unique_evp_pkey pkey(PEM_read_bio_PrivateKey(bio.get(), nullptr, nullptr, nullptr)); - BOOST_TEST_REQUIRE((bio != nullptr), "Loading the key failed: " << ERR_get_error()); - - // Create a decryption context - unique_evp_pkey_ctx ctx(EVP_PKEY_CTX_new(pkey.get(), nullptr)); - BOOST_TEST_REQUIRE((ctx != nullptr), "Creating a context failed: " << ERR_get_error()); - - // Initialize it - BOOST_TEST_REQUIRE( - EVP_PKEY_decrypt_init(ctx.get()) > 0, - "Initializing decryption failed: " << ERR_get_error() - ); - - // Set the padding scheme - BOOST_TEST_REQUIRE( - EVP_PKEY_CTX_set_rsa_padding(ctx.get(), RSA_PKCS1_OAEP_PADDING) > 0, - "Setting the padding scheme failed: " << ERR_get_error() - ); - - // Determine the size of the decrypted buffer - std::size_t out_size = 0; - BOOST_TEST_REQUIRE( - EVP_PKEY_decrypt(ctx.get(), nullptr, &out_size, ciphertext.data(), ciphertext.size()) > 0, - "Determining decryption size failed: " << ERR_get_error() - ); - std::vector res(out_size, 0); - - // Actually decrypt - BOOST_TEST_REQUIRE( - EVP_PKEY_decrypt(ctx.get(), res.data(), &out_size, ciphertext.data(), ciphertext.size()) > 0, - "Decrypting failed: " << ERR_get_error() - ); - res.resize(out_size); - - // Done - return res; + BOOST_MYSQL_ASSERT_BUFFER_EQUALS(csha2p_hash_password("hola", scramble), expected_hash); } -BOOST_AUTO_TEST_CASE(success) -{ - // Common for all tests - constexpr std::uint8_t scramble[] = { - 0x0f, 0x64, 0x4f, 0x2f, 0x2b, 0x3b, 0x27, 0x6b, 0x45, 0x5c, - 0x53, 0x01, 0x13, 0x7e, 0x4f, 0x10, 0x26, 0x23, 0x5d, 0x27, - }; - - // A password with all the possible ASCII values - std::string all_chars_password; - for (int i = 0; i < 256; ++i) - all_chars_password.push_back(static_cast(i)); - - struct - { - string_view name; - std::string password; - boost::span public_key; - boost::span private_key; - std::vector expected_decrypted; - } test_cases[] = { - // clang-format off - // An empty password does not cause trouble. This is an edge case, since - // an empty password should employ a different workflow - { - "password_empty", - {}, - public_key_2048, - private_key_2048, - {0x0f} - }, - - // Password is < length of the scramble - { - "password_shorter_scramble", - "csha2p_password", - public_key_2048, - private_key_2048, - { - 0x6c, 0x17, 0x27, 0x4e, 0x19, 0x4b, 0x78, 0x1b, 0x24, 0x2f, 0x20, 0x76, 0x7c, 0x0c, 0x2b, 0x10 - } - }, - - // Password (with NULL byte) is equal to the length of the scramble - { - "password_same_size_scramble", - "hjbjd923KKLiosoi90J", - public_key_2048, - private_key_2048, - { - 0x67, 0x0e, 0x2d, 0x45, 0x4f, 0x02, 0x15, 0x58, 0x0e, 0x17, - 0x1f, 0x68, 0x7c, 0x0d, 0x20, 0x79, 0x1f, 0x13, 0x17, 0x27 - } - }, - - // Passwords longer than the scramble use it cyclically - { - "password_longer_scramble", - "kjaski829380jvnnM,ka;::_kshf93IJCLIJO)jcjsnaklO?a", - public_key_2048, - private_key_2048, - { - 0x64, 0x0e, 0x2e, 0x5c, 0x40, 0x52, 0x1f, 0x59, 0x7c, 0x6f, 0x6b, 0x31, 0x79, 0x08, 0x21, 0x7e, 0x6b, - 0x0f, 0x36, 0x46, 0x34, 0x5e, 0x75, 0x70, 0x40, 0x48, 0x4f, 0x0d, 0x7c, 0x6f, 0x1a, 0x4b, 0x50, 0x32, - 0x06, 0x5a, 0x69, 0x0a, 0x37, 0x44, 0x65, 0x17, 0x21, 0x4e, 0x40, 0x57, 0x68, 0x54, 0x24, 0x5c, - } - }, - - // The longest password that a 2048 RSA key can encrypt with the OAEP scheme - { - "password_max_size_2048", - "hjbjd923KKLkjbdkcjwhekiy8393ou2weusidhiahJBKJKHCIHCKJIu9KHO09IJIpojaf0w39jalsjMMKjkjhiue93I=))" - "UXIOJKXNKNhkai8923oiawiakssaknhakhIIHICHIO)CU)" - "IHCKHCKJhisiweioHHJHUCHIIIJIOPkjgwijiosoi9jsu84HHUHCHI9839hdjsbsdjuUHJjbJ", - public_key_2048, - private_key_2048, - { - 0x67, 0x0e, 0x2d, 0x45, 0x4f, 0x02, 0x15, 0x58, 0x0e, 0x17, 0x1f, 0x6a, 0x79, 0x1c, 0x2b, 0x7b, 0x45, - 0x49, 0x2a, 0x4f, 0x6a, 0x0f, 0x26, 0x56, 0x13, 0x08, 0x1e, 0x58, 0x2a, 0x29, 0x61, 0x76, 0x76, 0x0b, - 0x3c, 0x79, 0x42, 0x4b, 0x34, 0x46, 0x67, 0x2e, 0x0d, 0x64, 0x61, 0x70, 0x6f, 0x28, 0x0c, 0x14, 0x10, - 0x4a, 0x59, 0x37, 0x3a, 0x29, 0x6d, 0x6b, 0x12, 0x17, 0x36, 0x2d, 0x05, 0x66, 0x5b, 0x54, 0x4d, 0x0a, - 0x23, 0x6c, 0x24, 0x32, 0x2a, 0x14, 0x2e, 0x7c, 0x55, 0x49, 0x10, 0x6a, 0x44, 0x0e, 0x24, 0x45, 0x43, - 0x52, 0x52, 0x0e, 0x7c, 0x6f, 0x1a, 0x3c, 0x3a, 0x57, 0x1a, 0x48, 0x6f, 0x6c, 0x17, 0x6c, 0x57, 0x2a, - 0x04, 0x61, 0x43, 0x50, 0x46, 0x02, 0x7d, 0x65, 0x61, 0x32, 0x7c, 0x17, 0x2e, 0x67, 0x4f, 0x42, 0x36, - 0x54, 0x7c, 0x05, 0x24, 0x41, 0x43, 0x5a, 0x4c, 0x03, 0x0c, 0x15, 0x1b, 0x48, 0x50, 0x36, 0x06, 0x5f, - 0x0f, 0x60, 0x08, 0x0e, 0x46, 0x2c, 0x0c, 0x64, 0x63, 0x78, 0x6c, 0x21, 0x2d, 0x35, 0x20, 0x68, 0x64, - 0x1b, 0x26, 0x7f, 0x6e, 0x6b, 0x17, 0x6f, 0x5a, 0x27, 0x07, 0x66, 0x62, 0x72, 0x6d, 0x22, 0x0a, 0x0c, - 0x38, 0x6b, 0x74, 0x09, 0x26, 0x7a, 0x4f, 0x4c, 0x2e, 0x48, 0x66, 0x5d, 0x25, 0x5c, 0x5e, 0x03, 0x13, - 0x23, 0x0d, 0x09, 0x1b, 0x42, 0x5b, 0x37, 0x76, 0x28, 0x15, 0x1a, 0x35, 0x43, 0x65, 0x17, 0x2d, 0x5c, - 0x4f, 0x51, 0x52, 0x3e, 0x0d, 0x16, 0x39, 0x63, 0x59, 0x7e, - } - }, - - // Longer passwords are accepted if the key supports them. - // Concretely, passwords longer than 512 (size of our SBO) are supported - { - "password_longer_sbo", - std::string(600, '5'), - public_key_8192, - private_key_8192, - { - 0x3a, 0x51, 0x7a, 0x1a, 0x1e, 0x0e, 0x12, 0x5e, 0x70, 0x69, 0x66, 0x34, 0x26, 0x4b, 0x7a, 0x25, 0x13, - 0x16, 0x68, 0x12, 0x3a, 0x51, 0x7a, 0x1a, 0x1e, 0x0e, 0x12, 0x5e, 0x70, 0x69, 0x66, 0x34, 0x26, 0x4b, - 0x7a, 0x25, 0x13, 0x16, 0x68, 0x12, 0x3a, 0x51, 0x7a, 0x1a, 0x1e, 0x0e, 0x12, 0x5e, 0x70, 0x69, 0x66, - 0x34, 0x26, 0x4b, 0x7a, 0x25, 0x13, 0x16, 0x68, 0x12, 0x3a, 0x51, 0x7a, 0x1a, 0x1e, 0x0e, 0x12, 0x5e, - 0x70, 0x69, 0x66, 0x34, 0x26, 0x4b, 0x7a, 0x25, 0x13, 0x16, 0x68, 0x12, 0x3a, 0x51, 0x7a, 0x1a, 0x1e, - 0x0e, 0x12, 0x5e, 0x70, 0x69, 0x66, 0x34, 0x26, 0x4b, 0x7a, 0x25, 0x13, 0x16, 0x68, 0x12, 0x3a, 0x51, - 0x7a, 0x1a, 0x1e, 0x0e, 0x12, 0x5e, 0x70, 0x69, 0x66, 0x34, 0x26, 0x4b, 0x7a, 0x25, 0x13, 0x16, 0x68, - 0x12, 0x3a, 0x51, 0x7a, 0x1a, 0x1e, 0x0e, 0x12, 0x5e, 0x70, 0x69, 0x66, 0x34, 0x26, 0x4b, 0x7a, 0x25, - 0x13, 0x16, 0x68, 0x12, 0x3a, 0x51, 0x7a, 0x1a, 0x1e, 0x0e, 0x12, 0x5e, 0x70, 0x69, 0x66, 0x34, 0x26, - 0x4b, 0x7a, 0x25, 0x13, 0x16, 0x68, 0x12, 0x3a, 0x51, 0x7a, 0x1a, 0x1e, 0x0e, 0x12, 0x5e, 0x70, 0x69, - 0x66, 0x34, 0x26, 0x4b, 0x7a, 0x25, 0x13, 0x16, 0x68, 0x12, 0x3a, 0x51, 0x7a, 0x1a, 0x1e, 0x0e, 0x12, - 0x5e, 0x70, 0x69, 0x66, 0x34, 0x26, 0x4b, 0x7a, 0x25, 0x13, 0x16, 0x68, 0x12, 0x3a, 0x51, 0x7a, 0x1a, - 0x1e, 0x0e, 0x12, 0x5e, 0x70, 0x69, 0x66, 0x34, 0x26, 0x4b, 0x7a, 0x25, 0x13, 0x16, 0x68, 0x12, 0x3a, - 0x51, 0x7a, 0x1a, 0x1e, 0x0e, 0x12, 0x5e, 0x70, 0x69, 0x66, 0x34, 0x26, 0x4b, 0x7a, 0x25, 0x13, 0x16, - 0x68, 0x12, 0x3a, 0x51, 0x7a, 0x1a, 0x1e, 0x0e, 0x12, 0x5e, 0x70, 0x69, 0x66, 0x34, 0x26, 0x4b, 0x7a, - 0x25, 0x13, 0x16, 0x68, 0x12, 0x3a, 0x51, 0x7a, 0x1a, 0x1e, 0x0e, 0x12, 0x5e, 0x70, 0x69, 0x66, 0x34, - 0x26, 0x4b, 0x7a, 0x25, 0x13, 0x16, 0x68, 0x12, 0x3a, 0x51, 0x7a, 0x1a, 0x1e, 0x0e, 0x12, 0x5e, 0x70, - 0x69, 0x66, 0x34, 0x26, 0x4b, 0x7a, 0x25, 0x13, 0x16, 0x68, 0x12, 0x3a, 0x51, 0x7a, 0x1a, 0x1e, 0x0e, - 0x12, 0x5e, 0x70, 0x69, 0x66, 0x34, 0x26, 0x4b, 0x7a, 0x25, 0x13, 0x16, 0x68, 0x12, 0x3a, 0x51, 0x7a, - 0x1a, 0x1e, 0x0e, 0x12, 0x5e, 0x70, 0x69, 0x66, 0x34, 0x26, 0x4b, 0x7a, 0x25, 0x13, 0x16, 0x68, 0x12, - 0x3a, 0x51, 0x7a, 0x1a, 0x1e, 0x0e, 0x12, 0x5e, 0x70, 0x69, 0x66, 0x34, 0x26, 0x4b, 0x7a, 0x25, 0x13, - 0x16, 0x68, 0x12, 0x3a, 0x51, 0x7a, 0x1a, 0x1e, 0x0e, 0x12, 0x5e, 0x70, 0x69, 0x66, 0x34, 0x26, 0x4b, - 0x7a, 0x25, 0x13, 0x16, 0x68, 0x12, 0x3a, 0x51, 0x7a, 0x1a, 0x1e, 0x0e, 0x12, 0x5e, 0x70, 0x69, 0x66, - 0x34, 0x26, 0x4b, 0x7a, 0x25, 0x13, 0x16, 0x68, 0x12, 0x3a, 0x51, 0x7a, 0x1a, 0x1e, 0x0e, 0x12, 0x5e, - 0x70, 0x69, 0x66, 0x34, 0x26, 0x4b, 0x7a, 0x25, 0x13, 0x16, 0x68, 0x12, 0x3a, 0x51, 0x7a, 0x1a, 0x1e, - 0x0e, 0x12, 0x5e, 0x70, 0x69, 0x66, 0x34, 0x26, 0x4b, 0x7a, 0x25, 0x13, 0x16, 0x68, 0x12, 0x3a, 0x51, - 0x7a, 0x1a, 0x1e, 0x0e, 0x12, 0x5e, 0x70, 0x69, 0x66, 0x34, 0x26, 0x4b, 0x7a, 0x25, 0x13, 0x16, 0x68, - 0x12, 0x3a, 0x51, 0x7a, 0x1a, 0x1e, 0x0e, 0x12, 0x5e, 0x70, 0x69, 0x66, 0x34, 0x26, 0x4b, 0x7a, 0x25, - 0x13, 0x16, 0x68, 0x12, 0x3a, 0x51, 0x7a, 0x1a, 0x1e, 0x0e, 0x12, 0x5e, 0x70, 0x69, 0x66, 0x34, 0x26, - 0x4b, 0x7a, 0x25, 0x13, 0x16, 0x68, 0x12, 0x3a, 0x51, 0x7a, 0x1a, 0x1e, 0x0e, 0x12, 0x5e, 0x70, 0x69, - 0x66, 0x34, 0x26, 0x4b, 0x7a, 0x25, 0x13, 0x16, 0x68, 0x12, 0x3a, 0x51, 0x7a, 0x1a, 0x1e, 0x0e, 0x12, - 0x5e, 0x70, 0x69, 0x66, 0x34, 0x26, 0x4b, 0x7a, 0x25, 0x13, 0x16, 0x68, 0x12, 0x3a, 0x51, 0x7a, 0x1a, - 0x1e, 0x0e, 0x12, 0x5e, 0x70, 0x69, 0x66, 0x34, 0x26, 0x4b, 0x7a, 0x25, 0x13, 0x16, 0x68, 0x12, 0x3a, - 0x51, 0x7a, 0x1a, 0x1e, 0x0e, 0x12, 0x5e, 0x70, 0x69, 0x66, 0x34, 0x26, 0x4b, 0x7a, 0x25, 0x13, 0x16, - 0x68, 0x12, 0x3a, 0x51, 0x7a, 0x1a, 0x1e, 0x0e, 0x12, 0x5e, 0x70, 0x69, 0x66, 0x34, 0x26, 0x4b, 0x7a, - 0x25, 0x13, 0x16, 0x68, 0x12, 0x0f - } - }, - - // No character causes trouble - { - "password_all_characters", - all_chars_password, - public_key_8192, - private_key_8192, - { - 0x0f, 0x65, 0x4d, 0x2c, 0x2f, 0x3e, 0x21, 0x6c, 0x4d, 0x55, 0x59, 0x0a, 0x1f, 0x73, 0x41, 0x1f, 0x36, - 0x32, 0x4f, 0x34, 0x1b, 0x71, 0x59, 0x38, 0x33, 0x22, 0x3d, 0x70, 0x59, 0x41, 0x4d, 0x1e, 0x33, 0x5f, - 0x6d, 0x33, 0x02, 0x06, 0x7b, 0x00, 0x27, 0x4d, 0x65, 0x04, 0x07, 0x16, 0x09, 0x44, 0x75, 0x6d, 0x61, - 0x32, 0x27, 0x4b, 0x79, 0x27, 0x1e, 0x1a, 0x67, 0x1c, 0x33, 0x59, 0x71, 0x10, 0x6b, 0x7a, 0x65, 0x28, - 0x01, 0x19, 0x15, 0x46, 0x5b, 0x37, 0x05, 0x5b, 0x6a, 0x6e, 0x13, 0x68, 0x5f, 0x35, 0x1d, 0x7c, 0x7f, - 0x6e, 0x71, 0x3c, 0x1d, 0x05, 0x09, 0x5a, 0x4f, 0x23, 0x11, 0x4f, 0x46, 0x42, 0x3f, 0x44, 0x6b, 0x01, - 0x29, 0x48, 0x43, 0x52, 0x4d, 0x00, 0x29, 0x31, 0x3d, 0x6e, 0x63, 0x0f, 0x3d, 0x63, 0x52, 0x56, 0x2b, - 0x50, 0x77, 0x1d, 0x35, 0x54, 0x57, 0x46, 0x59, 0x14, 0xc5, 0xdd, 0xd1, 0x82, 0x97, 0xfb, 0xc9, 0x97, - 0xae, 0xaa, 0xd7, 0xac, 0x83, 0xe9, 0xc1, 0xa0, 0xbb, 0xaa, 0xb5, 0xf8, 0xd1, 0xc9, 0xc5, 0x96, 0x8b, - 0xe7, 0xd5, 0x8b, 0xba, 0xbe, 0xc3, 0xb8, 0xaf, 0xc5, 0xed, 0x8c, 0x8f, 0x9e, 0x81, 0xcc, 0xed, 0xf5, - 0xf9, 0xaa, 0xbf, 0xd3, 0xe1, 0xbf, 0x96, 0x92, 0xef, 0x94, 0xbb, 0xd1, 0xf9, 0x98, 0x93, 0x82, 0x9d, - 0xd0, 0xf9, 0xe1, 0xed, 0xbe, 0xd3, 0xbf, 0x8d, 0xd3, 0xe2, 0xe6, 0x9b, 0xe0, 0xc7, 0xad, 0x85, 0xe4, - 0xe7, 0xf6, 0xe9, 0xa4, 0x95, 0x8d, 0x81, 0xd2, 0xc7, 0xab, 0x99, 0xc7, 0xfe, 0xfa, 0x87, 0xfc, 0xd3, - 0xb9, 0x91, 0xf0, 0xcb, 0xda, 0xc5, 0x88, 0xa1, 0xb9, 0xb5, 0xe6, 0xfb, 0x97, 0xa5, 0xfb, 0xca, 0xce, - 0xb3, 0xc8, 0xff, 0x95, 0xbd, 0xdc, 0xdf, 0xce, 0xd1, 0x9c, 0xbd, 0xa5, 0xa9, 0xfa, 0xef, 0x83, 0xb1, - 0xef, 0x26 - } - } - - // clang-format on - }; - - for (const auto& tc : test_cases) - { - BOOST_TEST_CONTEXT(tc.name) - { - // Setup - buffer_type buff; - - // Call the function - auto ec = csha2p_encrypt_password(tc.password, scramble, tc.public_key, buff, ssl_category); - - // Verify - BOOST_TEST_REQUIRE(ec == error_code()); - BOOST_MYSQL_ASSERT_BUFFER_EQUALS(decrypt(tc.private_key, buff), tc.expected_decrypted); - } - } -} - -// -// Errors. It's not defined what exact error code will each function return. -// - -// We passed an empty buffer to the key parser -BOOST_AUTO_TEST_CASE(error_key_buffer_empty) -{ - constexpr std::uint8_t scramble[] = { - 0x0f, 0x64, 0x4f, 0x2f, 0x2b, 0x3b, 0x27, 0x6b, 0x45, 0x5c, - 0x53, 0x01, 0x13, 0x7e, 0x4f, 0x10, 0x26, 0x23, 0x5d, 0x27, - }; - buffer_type buff; - auto ec = csha2p_encrypt_password("csha2p_password", scramble, {}, buff, ssl_category); - BOOST_TEST((ec.category() == ssl_category)); - BOOST_TEST(ec.value() > 0); // is an error - BOOST_TEST(ec.message() != ""); // produces some output -} - -BOOST_AUTO_TEST_CASE(error_key_malformed) -{ - constexpr std::uint8_t scramble[] = { - 0x0f, 0x64, 0x4f, 0x2f, 0x2b, 0x3b, 0x27, 0x6b, 0x45, 0x5c, - 0x53, 0x01, 0x13, 0x7e, 0x4f, 0x10, 0x26, 0x23, 0x5d, 0x27, - }; - constexpr std::uint8_t key_buffer[] = "-----BEGIN PUBLIC KEY-----zwIDAQAB__kaj0?))="; - buffer_type buff; - auto ec = csha2p_encrypt_password("csha2p_password", scramble, key_buffer, buff, ssl_category); - BOOST_TEST((ec.category() == ssl_category)); - BOOST_TEST(ec.value() > 0); // is an error - BOOST_TEST(ec.message() != ""); // produces some output -} - -// Passing in a public key type that does not support encryption operations -// (like ECDSA) fails -BOOST_AUTO_TEST_CASE(error_key_doesnt_support_encryption) -{ - constexpr std::uint8_t scramble[] = { - 0x0f, 0x64, 0x4f, 0x2f, 0x2b, 0x3b, 0x27, 0x6b, 0x45, 0x5c, - 0x53, 0x01, 0x13, 0x7e, 0x4f, 0x10, 0x26, 0x23, 0x5d, 0x27, - }; - constexpr unsigned char key_buffer[] = R"%(-----BEGIN PUBLIC KEY----- -MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAERDkCI/degPJXEIYYncyvGsTdj9YI -4xQ6KPTzoF+DY2jM09w1TCncxk9XV1eOo44UMDUuK9K01halQy70mohFSQ== ------END PUBLIC KEY----- -)%"; - - buffer_type buff; - auto ec = csha2p_encrypt_password("csha2p_password", scramble, key_buffer, buff, ssl_category); - BOOST_TEST((ec.category() == ssl_category)); - BOOST_TEST(ec.value() > 0); // is an error - BOOST_TEST(ec.message() != ""); // produces some output -} - -// Passing in a public key type that allows encryption but is not RSA fails as expected. -// This is a SM2 key, generated by: -// openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:sm2 -out public.pem -// openssl pkey -in private.pem -pubout -out public.pem -BOOST_AUTO_TEST_CASE(error_key_not_rsa) +BOOST_AUTO_TEST_CASE(empty_password) { - constexpr std::uint8_t scramble[] = { - 0x0f, 0x64, 0x4f, 0x2f, 0x2b, 0x3b, 0x27, 0x6b, 0x45, 0x5c, - 0x53, 0x01, 0x13, 0x7e, 0x4f, 0x10, 0x26, 0x23, 0x5d, 0x27, - }; - - buffer_type buff; - auto ec = csha2p_encrypt_password("csha2p_password", scramble, public_key_sm2, buff, ssl_category); - BOOST_TEST(ec == client_errc::protocol_value_error); // OpenSSL does not provide an error code here + BOOST_MYSQL_ASSERT_BUFFER_EQUALS(csha2p_hash_password("", scramble), std::vector()); } -// TODO: verify that these error codes have source code info - BOOST_AUTO_TEST_SUITE_END() } // namespace \ No newline at end of file diff --git a/test/unit/test/sansio/handshake/handshake_mnp_hash_password.cpp b/test/unit/test/sansio/handshake/handshake_mnp_hash_password.cpp index 57dd18eb0..214813846 100644 --- a/test/unit/test/sansio/handshake/handshake_mnp_hash_password.cpp +++ b/test/unit/test/sansio/handshake/handshake_mnp_hash_password.cpp @@ -10,7 +10,6 @@ #include #include "test_common/assert_buffer_equals.hpp" -#include "test_common/printing.hpp" using namespace boost::mysql; using namespace boost::mysql::test; @@ -20,20 +19,20 @@ namespace { BOOST_AUTO_TEST_SUITE(test_handshake_mnp_hash_password) -// TODO: this doesn't make a lot of sense as a separate test now // Values snooped using Wireshark constexpr std::uint8_t scramble[20] = { 0x79, 0x64, 0x3d, 0x12, 0x1d, 0x71, 0x74, 0x47, 0x5f, 0x48, 0x3e, 0x3e, 0x0b, 0x62, 0x0a, 0x03, 0x3d, 0x27, 0x3a, 0x4c, }; -constexpr std::uint8_t hash[20] = { - 0xf1, 0xb2, 0xfb, 0x1c, 0x8d, 0xe7, 0x5d, 0xb8, 0xeb, 0xa8, - 0x12, 0x6a, 0xd1, 0x0f, 0xe9, 0xb1, 0x10, 0x50, 0xd4, 0x28, -}; BOOST_AUTO_TEST_CASE(nonempty_password) { - BOOST_MYSQL_ASSERT_BUFFER_EQUALS(mnp_hash_password("root", scramble), hash); + constexpr std::uint8_t expected_hash[20] = { + 0xf1, 0xb2, 0xfb, 0x1c, 0x8d, 0xe7, 0x5d, 0xb8, 0xeb, 0xa8, + 0x12, 0x6a, 0xd1, 0x0f, 0xe9, 0xb1, 0x10, 0x50, 0xd4, 0x28, + }; + + BOOST_MYSQL_ASSERT_BUFFER_EQUALS(mnp_hash_password("root", scramble), expected_hash); } BOOST_AUTO_TEST_CASE(empty_password) From 16433ddaea64888314768e56ca0d58703da7ef73 Mon Sep 17 00:00:00 2001 From: Ruben Perez Date: Mon, 2 Jun 2025 20:28:03 +0200 Subject: [PATCH 42/68] add source info checks --- .../handshake/handshake_csha2p_encrypt_password.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/test/unit/test/sansio/handshake/handshake_csha2p_encrypt_password.cpp b/test/unit/test/sansio/handshake/handshake_csha2p_encrypt_password.cpp index 0bdd89579..1d00ea336 100644 --- a/test/unit/test/sansio/handshake/handshake_csha2p_encrypt_password.cpp +++ b/test/unit/test/sansio/handshake/handshake_csha2p_encrypt_password.cpp @@ -300,7 +300,7 @@ BOOST_AUTO_TEST_CASE(success) } // -// Errors. It's not defined what exact error code will each function return. +// Errors. For OpenSSL errors, it's not defined what exact code will each function return. // // We passed an empty buffer to the key parser @@ -315,6 +315,7 @@ BOOST_AUTO_TEST_CASE(error_key_buffer_empty) BOOST_TEST((ec.category() == ssl_category)); BOOST_TEST(ec.value() > 0); // is an error BOOST_TEST(ec.message() != ""); // produces some output + BOOST_TEST(ec.has_location()); } BOOST_AUTO_TEST_CASE(error_key_malformed) @@ -329,6 +330,7 @@ BOOST_AUTO_TEST_CASE(error_key_malformed) BOOST_TEST((ec.category() == ssl_category)); BOOST_TEST(ec.value() > 0); // is an error BOOST_TEST(ec.message() != ""); // produces some output + BOOST_TEST(ec.has_location()); } // Passing in a public key type that does not support encryption operations @@ -350,6 +352,7 @@ MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAERDkCI/degPJXEIYYncyvGsTdj9YI BOOST_TEST((ec.category() == ssl_category)); BOOST_TEST(ec.value() > 0); // is an error BOOST_TEST(ec.message() != ""); // produces some output + BOOST_TEST(ec.has_location()); } // Passing in a public key type that allows encryption but is not RSA fails as expected. @@ -366,10 +369,9 @@ BOOST_AUTO_TEST_CASE(error_key_not_rsa) buffer_type buff; auto ec = csha2p_encrypt_password("csha2p_password", scramble, public_key_sm2, buff, ssl_category); BOOST_TEST(ec == client_errc::protocol_value_error); // OpenSSL does not provide an error code here + BOOST_TEST(ec.has_location()); } -// TODO: verify that these error codes have source code info - BOOST_AUTO_TEST_SUITE_END() } // namespace \ No newline at end of file From 03a96a1d44f263e554316a540369837a03152044 Mon Sep 17 00:00:00 2001 From: Ruben Perez Date: Mon, 2 Jun 2025 20:29:45 +0200 Subject: [PATCH 43/68] remove spurius includes --- .../mysql/impl/internal/sansio/caching_sha2_password.hpp | 7 ------- 1 file changed, 7 deletions(-) diff --git a/include/boost/mysql/impl/internal/sansio/caching_sha2_password.hpp b/include/boost/mysql/impl/internal/sansio/caching_sha2_password.hpp index 881b3399b..8d9a55539 100644 --- a/include/boost/mysql/impl/internal/sansio/caching_sha2_password.hpp +++ b/include/boost/mysql/impl/internal/sansio/caching_sha2_password.hpp @@ -31,14 +31,7 @@ #include #include #include -#include -#include -#include -#include -#include -#include #include -#include // Reference: // https://dev.mysql.com/doc/dev/mysql-server/latest/page_caching_sha2_authentication_exchanges.html From 2859abe7ac3ea1f561f48ad0c078e902246b8ab0 Mon Sep 17 00:00:00 2001 From: Ruben Perez Date: Tue, 3 Jun 2025 19:08:24 +0200 Subject: [PATCH 44/68] Remove problematic openssl/types.h include --- .../boost/mysql/impl/internal/sansio/csha2p_encrypt_password.hpp | 1 - 1 file changed, 1 deletion(-) diff --git a/include/boost/mysql/impl/internal/sansio/csha2p_encrypt_password.hpp b/include/boost/mysql/impl/internal/sansio/csha2p_encrypt_password.hpp index d960f845f..ee6a1d2b6 100644 --- a/include/boost/mysql/impl/internal/sansio/csha2p_encrypt_password.hpp +++ b/include/boost/mysql/impl/internal/sansio/csha2p_encrypt_password.hpp @@ -29,7 +29,6 @@ #include #include #include -#include namespace boost { namespace mysql { From cc247cbf0ac06f9426cec064adec6b62536126c2 Mon Sep 17 00:00:00 2001 From: Ruben Perez Date: Tue, 3 Jun 2025 19:27:22 +0200 Subject: [PATCH 45/68] Fixed a memory corruption --- .../mysql/impl/internal/sansio/handshake.hpp | 26 +++++++++++-------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/include/boost/mysql/impl/internal/sansio/handshake.hpp b/include/boost/mysql/impl/internal/sansio/handshake.hpp index 281f83ea5..58305d844 100644 --- a/include/boost/mysql/impl/internal/sansio/handshake.hpp +++ b/include/boost/mysql/impl/internal/sansio/handshake.hpp @@ -269,17 +269,21 @@ class handshake_algo }; } - login_request compose_login_request(const connection_state_data& st) const + next_action serialize_login_request(connection_state_data& st) { - return { - st.current_capabilities, - static_cast(max_packet_size), - hparams_.connection_collation(), - hparams_.username(), - plugin_.hash_password(hparams_.password(), scramble_), - hparams_.database(), - plugin_.name(), - }; + auto hashed_password = plugin_.hash_password(hparams_.password(), scramble_); + return st.write( + login_request{ + st.current_capabilities, + static_cast(max_packet_size), + hparams_.connection_collation(), + hparams_.username(), + hashed_password, + hparams_.database(), + plugin_.name(), + }, + sequence_number_ + ); } // Processes auth_switch and auth_more_data messages, and leaves the result in auth_resp_ @@ -346,7 +350,7 @@ class handshake_algo } // Compose and send handshake response - BOOST_MYSQL_YIELD(resume_point_, 4, st.write(compose_login_request(st), sequence_number_)) + BOOST_MYSQL_YIELD(resume_point_, 4, serialize_login_request(st)) // Receive the response BOOST_MYSQL_YIELD(resume_point_, 5, st.read(sequence_number_)) From 3d7b42ace39f6f1c512709a36b9914011165a533 Mon Sep 17 00:00:00 2001 From: Ruben Perez Date: Tue, 3 Jun 2025 19:44:30 +0200 Subject: [PATCH 46/68] Add client_errc::unknown_openssl_error --- include/boost/mysql/client_errc.hpp | 3 +++ include/boost/mysql/impl/error_categories.ipp | 2 ++ include/boost/mysql/impl/is_fatal_error.ipp | 3 ++- test/unit/test/client_errc.cpp | 2 +- 4 files changed, 8 insertions(+), 2 deletions(-) diff --git a/include/boost/mysql/client_errc.hpp b/include/boost/mysql/client_errc.hpp index 887d5251e..91b59bdbe 100644 --- a/include/boost/mysql/client_errc.hpp +++ b/include/boost/mysql/client_errc.hpp @@ -172,6 +172,9 @@ enum class client_errc : int * (protocol violation). */ bad_handshake_packet_type, + + /// An OpenSSL function failed and did not provide any extra diagnostics. + unknown_openssl_error, }; BOOST_MYSQL_DECL diff --git a/include/boost/mysql/impl/error_categories.ipp b/include/boost/mysql/impl/error_categories.ipp index ebef09fc4..9e5ea884b 100644 --- a/include/boost/mysql/impl/error_categories.ipp +++ b/include/boost/mysql/impl/error_categories.ipp @@ -103,6 +103,8 @@ inline const char* error_to_string(client_errc error) case client_errc::bad_handshake_packet_type: return "During handshake, the server sent a packet type that is not allowed in the current state " "(protocol violation)."; + case client_errc::unknown_openssl_error: + return "An OpenSSL function failed and did not provide any extra diagnostics."; default: return ""; } } diff --git a/include/boost/mysql/impl/is_fatal_error.ipp b/include/boost/mysql/impl/is_fatal_error.ipp index 4256a0be6..d384e59ea 100644 --- a/include/boost/mysql/impl/is_fatal_error.ipp +++ b/include/boost/mysql/impl/is_fatal_error.ipp @@ -84,7 +84,8 @@ bool boost::mysql::is_fatal_error(error_code ec) noexcept case client_errc::unknown_auth_plugin: case client_errc::server_unsupported: case client_errc::auth_plugin_requires_ssl: - case client_errc::bad_handshake_packet_type: return true; + case client_errc::bad_handshake_packet_type: + case client_errc::unknown_openssl_error: return true; default: return false; } diff --git a/test/unit/test/client_errc.cpp b/test/unit/test/client_errc.cpp index cc62cf4d3..7310bc2e6 100644 --- a/test/unit/test/client_errc.cpp +++ b/test/unit/test/client_errc.cpp @@ -33,7 +33,7 @@ BOOST_AUTO_TEST_CASE(coverage) { // Check that no value causes problems. // Ensure that all branches of the switch/case are covered - for (int i = 1; i <= 30; ++i) + for (int i = 1; i <= 31; ++i) { BOOST_TEST_CONTEXT(i) { From a70479ed79596699004fe44605b955e4ec116c7b Mon Sep 17 00:00:00 2001 From: Ruben Perez Date: Tue, 3 Jun 2025 19:50:11 +0200 Subject: [PATCH 47/68] make things fail even if openssl does not provide extra error info --- .../sansio/csha2p_encrypt_password.hpp | 10 ++++++++- .../test_csha2p_encrypt_password_errors.cpp | 21 ++++++++++++++++++- 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/include/boost/mysql/impl/internal/sansio/csha2p_encrypt_password.hpp b/include/boost/mysql/impl/internal/sansio/csha2p_encrypt_password.hpp index ee6a1d2b6..1149b6cd6 100644 --- a/include/boost/mysql/impl/internal/sansio/csha2p_encrypt_password.hpp +++ b/include/boost/mysql/impl/internal/sansio/csha2p_encrypt_password.hpp @@ -55,7 +55,15 @@ inline error_code translate_openssl_error( // so it's unlikely that we will encounter these here. Overflow here // is implementation-defined behavior (and not UB), so we're fine. // This is what Asio does, anyway. - return error_code(static_cast(code), openssl_category, loc); + int int_code = static_cast(code); + + // An error code of zero would mean success, while this function is always + // called because an OpenSSL primitive failed. It might indicate that OpenSSL + // did not provide any extra error information. But it should still be an error + if (int_code == 0) + return error_code(static_cast(client_errc::unknown_openssl_error), get_client_category(), loc); + else + return error_code(int_code, openssl_category, loc); } inline container::small_vector csha2p_salt_password( diff --git a/test/unit/test_csha2p_encrypt_password_errors.cpp b/test/unit/test_csha2p_encrypt_password_errors.cpp index d48b9941e..e7a5a8add 100644 --- a/test/unit/test_csha2p_encrypt_password_errors.cpp +++ b/test/unit/test_csha2p_encrypt_password_errors.cpp @@ -155,10 +155,29 @@ BOOST_AUTO_TEST_CASE(encrypt_actual_size_lt_max_size) BOOST_TEST(out.size() == 200u); } +// OpenSSL functions might fail without adding an error to the stack. +// If that's the case, the operation must still fail +BOOST_AUTO_TEST_CASE(error_code_zero) +{ + // Setup. The return value should be != -2, which indicates + // operation not supported and is handled separately + openssl_mock = {}; + openssl_mock.set_rsa_padding_result = -1; + vector_type out; + + // Call the function + auto ec = csha2p_encrypt_password("passwd", scramble, {}, out, ssl_category); + + // Check + BOOST_TEST(ec == error_code(client_errc::unknown_openssl_error)); + BOOST_TEST(ec.has_location()); + BOOST_TEST(openssl_mock.EVP_PKEY_CTX_set_rsa_padding_calls == 1u); + BOOST_TEST(openssl_mock.EVP_PKEY_encrypt_calls == 0u); +} + /** error loading key TODO: should we fuzz this function? - TODO: if openssl returns 0 in ERR_get_error, does the error code count as failed, too? no it does not */ } // namespace From dd2e4fa2f05051f95e2d70833dc2d4844f6429b4 Mon Sep 17 00:00:00 2001 From: Ruben Perez Date: Tue, 3 Jun 2025 19:56:24 +0200 Subject: [PATCH 48/68] Use EVP_PKEY_size for compatibility --- .../impl/internal/sansio/csha2p_encrypt_password.hpp | 2 +- test/unit/test_csha2p_encrypt_password_errors.cpp | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/include/boost/mysql/impl/internal/sansio/csha2p_encrypt_password.hpp b/include/boost/mysql/impl/internal/sansio/csha2p_encrypt_password.hpp index 1149b6cd6..ca9ee1172 100644 --- a/include/boost/mysql/impl/internal/sansio/csha2p_encrypt_password.hpp +++ b/include/boost/mysql/impl/internal/sansio/csha2p_encrypt_password.hpp @@ -160,7 +160,7 @@ inline error_code csha2p_encrypt_password( } // Allocate a buffer for encryption - int max_size = EVP_PKEY_get_size(key.get()); + int max_size = EVP_PKEY_size(key.get()); if (max_size <= 0) { static constexpr auto loc = BOOST_CURRENT_LOCATION; diff --git a/test/unit/test_csha2p_encrypt_password_errors.cpp b/test/unit/test_csha2p_encrypt_password_errors.cpp index e7a5a8add..ec69b7395 100644 --- a/test/unit/test_csha2p_encrypt_password_errors.cpp +++ b/test/unit/test_csha2p_encrypt_password_errors.cpp @@ -178,6 +178,8 @@ BOOST_AUTO_TEST_CASE(error_code_zero) /** error loading key TODO: should we fuzz this function? + TODO: test openssl system errors + TODO: are we missing any static_buffer tests? */ } // namespace @@ -217,7 +219,14 @@ int EVP_PKEY_CTX_set_rsa_padding(EVP_PKEY_CTX* ctx, int) BOOST_TEST(ctx == openssl_mock.ctx); return openssl_mock.set_rsa_padding_result; } + +// OpenSSL >=3: EVP_PKEY_get_size is a function, EVP_PKEY_size is a macro +// OpenSSL < 3: EVP_PKEY_size is a function +#if OPENSSL_VERSION_NUMBER >= 0x30000000L int EVP_PKEY_get_size(const EVP_PKEY* pkey) +#else +int EVP_PKEY_size(const EVP_PKEY* pkey) +#endif { ++openssl_mock.EVP_PKEY_get_size_calls; BOOST_TEST(pkey == openssl_mock.key); From af8b222d573bc846d911d5f45cf159aae026e0b4 Mon Sep 17 00:00:00 2001 From: Ruben Perez Date: Tue, 3 Jun 2025 20:05:13 +0200 Subject: [PATCH 49/68] test system errors --- .../test_csha2p_encrypt_password_errors.cpp | 27 ++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/test/unit/test_csha2p_encrypt_password_errors.cpp b/test/unit/test_csha2p_encrypt_password_errors.cpp index ec69b7395..76724c4d1 100644 --- a/test/unit/test_csha2p_encrypt_password_errors.cpp +++ b/test/unit/test_csha2p_encrypt_password_errors.cpp @@ -175,10 +175,35 @@ BOOST_AUTO_TEST_CASE(error_code_zero) BOOST_TEST(openssl_mock.EVP_PKEY_encrypt_calls == 0u); } +// OpenSSL 3+ might report system errors represented as codes > 0x80000000. +// Previous versions used them for user-defined errors, so we should tolerate them, too. +BOOST_AUTO_TEST_CASE(error_code_system) +{ + // Setup. The return value should be != -2, which indicates + // operation not supported and is handled separately + openssl_mock = {}; + openssl_mock.set_rsa_padding_result = -1; + openssl_mock.last_error = 0x800000ab; + vector_type out; + + // Call the function + auto ec = csha2p_encrypt_password("passwd", scramble, {}, out, ssl_category); + + // Check + BOOST_TEST(ec.failed()); + BOOST_TEST(ec.has_location()); +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + BOOST_TEST((ec.category() == boost::system::system_category())); +#else + BOOST_TEST((ec.category() == ssl_category)); +#endif + BOOST_TEST(openssl_mock.EVP_PKEY_CTX_set_rsa_padding_calls == 1u); + BOOST_TEST(openssl_mock.EVP_PKEY_encrypt_calls == 0u); +} + /** error loading key TODO: should we fuzz this function? - TODO: test openssl system errors TODO: are we missing any static_buffer tests? */ From f9c1e2955a408348f066d64105aed7a697545aba Mon Sep 17 00:00:00 2001 From: Ruben Perez Date: Tue, 3 Jun 2025 20:07:12 +0200 Subject: [PATCH 50/68] remove unnecessary static_buffer::resize --- .../boost/mysql/impl/internal/protocol/static_buffer.hpp | 6 ------ test/unit/test_csha2p_encrypt_password_errors.cpp | 1 - 2 files changed, 7 deletions(-) diff --git a/include/boost/mysql/impl/internal/protocol/static_buffer.hpp b/include/boost/mysql/impl/internal/protocol/static_buffer.hpp index cca6423a1..0f8396adb 100644 --- a/include/boost/mysql/impl/internal/protocol/static_buffer.hpp +++ b/include/boost/mysql/impl/internal/protocol/static_buffer.hpp @@ -53,12 +53,6 @@ class static_buffer } } - void resize(std::size_t new_size) - { - BOOST_ASSERT(new_size <= N); - size_ = new_size; - } - void clear() noexcept { size_ = 0; } }; diff --git a/test/unit/test_csha2p_encrypt_password_errors.cpp b/test/unit/test_csha2p_encrypt_password_errors.cpp index 76724c4d1..5f52cde85 100644 --- a/test/unit/test_csha2p_encrypt_password_errors.cpp +++ b/test/unit/test_csha2p_encrypt_password_errors.cpp @@ -204,7 +204,6 @@ BOOST_AUTO_TEST_CASE(error_code_system) /** error loading key TODO: should we fuzz this function? - TODO: are we missing any static_buffer tests? */ } // namespace From 47fd21461f2c31c129937af551d2b83553ecde40 Mon Sep 17 00:00:00 2001 From: Ruben Perez Date: Tue, 3 Jun 2025 20:08:18 +0200 Subject: [PATCH 51/68] Remove TODO --- test/unit/test_csha2p_encrypt_password_errors.cpp | 5 ----- 1 file changed, 5 deletions(-) diff --git a/test/unit/test_csha2p_encrypt_password_errors.cpp b/test/unit/test_csha2p_encrypt_password_errors.cpp index 5f52cde85..d2ca7b3c8 100644 --- a/test/unit/test_csha2p_encrypt_password_errors.cpp +++ b/test/unit/test_csha2p_encrypt_password_errors.cpp @@ -201,11 +201,6 @@ BOOST_AUTO_TEST_CASE(error_code_system) BOOST_TEST(openssl_mock.EVP_PKEY_encrypt_calls == 0u); } -/** -error loading key - TODO: should we fuzz this function? -*/ - } // namespace // Implement the OpenSSL functions From 426e34f140f8909b112dd0c9b534f9d0bca2e4e5 Mon Sep 17 00:00:00 2001 From: Ruben Perez Date: Tue, 3 Jun 2025 20:14:02 +0200 Subject: [PATCH 52/68] Disable problematic PFR check --- test/unit/test/pfr.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/unit/test/pfr.cpp b/test/unit/test/pfr.cpp index a8e7a156e..f583489db 100644 --- a/test/unit/test/pfr.cpp +++ b/test/unit/test/pfr.cpp @@ -102,7 +102,8 @@ BOOST_AUTO_TEST_CASE(get_row_name_table_) const string_view expected_s1[] = {"i", "f", "double_field"}; const string_view expected_s2[] = {"s"}; - compare_name_tables(get_row_name_table>(), name_table_t()); + // TODO: recover this as part of https://github.com/boostorg/mysql/issues/483 + // compare_name_tables(get_row_name_table>(), name_table_t()); compare_name_tables(get_row_name_table>(), expected_s1); compare_name_tables(get_row_name_table>(), expected_s2); } From 153c2265bd3bcd6039716774d48ac4f28ab71a02 Mon Sep 17 00:00:00 2001 From: Ruben Perez Date: Tue, 3 Jun 2025 20:15:40 +0200 Subject: [PATCH 53/68] remove Jamfile comment --- test/unit/Jamfile | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/unit/Jamfile b/test/unit/Jamfile index 97b176fb6..cc989d47f 100644 --- a/test/unit/Jamfile +++ b/test/unit/Jamfile @@ -148,8 +148,7 @@ run # Tests edge cases when encrypting passwords that require mocking OpenSSL. -# This executable should not link to any OpenSSL libraries. -# TODO: does this really work?? +# It looks like this works even if boost_mysql links to the SSL libraries. run test_csha2p_encrypt_password_errors.cpp /boost/mysql/test//boost_mysql /boost/test//boost_unit_test_framework/off From aa32bd309332f3776973ca77592eda50f97ed91b Mon Sep 17 00:00:00 2001 From: Ruben Perez Date: Tue, 3 Jun 2025 20:21:12 +0200 Subject: [PATCH 54/68] rephrase docs --- doc/qbk/05_connection_establishment.qbk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/qbk/05_connection_establishment.qbk b/doc/qbk/05_connection_establishment.qbk index f548baa42..cd812cfa5 100644 --- a/doc/qbk/05_connection_establishment.qbk +++ b/doc/qbk/05_connection_establishment.qbk @@ -41,7 +41,7 @@ This library implements the two most common authentication plugins: connections. It sends the password hashed, salted by a nonce. * [mysqllink caching-sha2-pluggable-authentication.html `caching_sha2_password`]. This is the default plugin for - MySQL 8.0+. This plugin used to require the use of TLS. + MySQL 8.0+. This plugin used to require using TLS. Since Boost 1.89, this is no longer the case, and it can be used with plaintext connections, too. From a2faaad359732a255cbf7b31f9a035291c385586 Mon Sep 17 00:00:00 2001 From: Ruben Perez Date: Tue, 3 Jun 2025 20:28:40 +0200 Subject: [PATCH 55/68] fix edge case with scramble sizes --- .../mysql/impl/internal/sansio/handshake.hpp | 9 ++--- test/unit/test/sansio/handshake/handshake.cpp | 35 ++++++++++++++++++- 2 files changed, 39 insertions(+), 5 deletions(-) diff --git a/include/boost/mysql/impl/internal/sansio/handshake.hpp b/include/boost/mysql/impl/internal/sansio/handshake.hpp index 58305d844..a51347196 100644 --- a/include/boost/mysql/impl/internal/sansio/handshake.hpp +++ b/include/boost/mysql/impl/internal/sansio/handshake.hpp @@ -250,13 +250,14 @@ class handshake_algo // If we're using SSL, mark the channel as secure secure_channel_ = secure_channel_ || has_capabilities(*negotiated_caps, capabilities::ssl); - // Save the scramble for later - err = save_scramble(hello.auth_plugin_data); + // Save which authentication plugin we're using. Do this before saving the scramble, + // as an unknown plugin might have a scramble size different to what we know + err = plugin_.emplace_by_name(hello.auth_plugin_name); if (err) return err; - // Save which authentication plugin we're using - return plugin_.emplace_by_name(hello.auth_plugin_name); + // Save the scramble for later + return save_scramble(hello.auth_plugin_data); } // Response to that initial greeting diff --git a/test/unit/test/sansio/handshake/handshake.cpp b/test/unit/test/sansio/handshake/handshake.cpp index ee78f71d5..9a7b23b5e 100644 --- a/test/unit/test/sansio/handshake/handshake.cpp +++ b/test/unit/test/sansio/handshake/handshake.cpp @@ -10,6 +10,8 @@ #include #include +#include + #include "handshake_common.hpp" #include "test_common/create_diagnostics.hpp" #include "test_unit/create_err.hpp" @@ -68,7 +70,20 @@ BOOST_AUTO_TEST_CASE(hello_unknown_plugin) .check(fix, client_errc::unknown_auth_plugin); } -// TODO: check that this is the error even if we get a scramble of != size +// If the plugin is unknown, the scramble size might be different +BOOST_AUTO_TEST_CASE(hello_unknown_plugin_different_scramble_size) +{ + // Setup + handshake_fixture fix; + + // Run the test + algo_test() + .expect_read(server_hello_builder() + .auth_plugin("unknown") + .auth_data(std::vector(30, 0xab)) + .build()) + .check(fix, client_errc::unknown_auth_plugin); +} // // Errors processing the initial server response @@ -208,6 +223,24 @@ BOOST_AUTO_TEST_CASE(authswitch_unknown_plugin) .check(fix, client_errc::unknown_auth_plugin); } +// If the plugin is unknown, the scramble may have a different size +BOOST_AUTO_TEST_CASE(authswitch_unknown_plugin_different_scramble_size) +{ + // Setup + handshake_fixture fix; + + // Run the test + algo_test() + .expect_read( + server_hello_builder().auth_plugin("caching_sha2_password").auth_data(csha2p_scramble).build() + ) + .expect_write( + login_request_builder().auth_plugin("caching_sha2_password").auth_response(csha2p_hash).build() + ) + .expect_read(create_auth_switch_frame(2, "unknown", std::vector(30, 0xab))) + .check(fix, client_errc::unknown_auth_plugin); +} + // Performing an auth switch to the same plugin is okay BOOST_AUTO_TEST_CASE(authswitch_to_itself) { From 2370f8a2eb2f4a48e6b4097da8eccb86eb47e135 Mon Sep 17 00:00:00 2001 From: Ruben Perez Date: Tue, 3 Jun 2025 20:35:11 +0200 Subject: [PATCH 56/68] Recover build_unix_local structure --- tools/scripts/build_unix_local.sh | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tools/scripts/build_unix_local.sh b/tools/scripts/build_unix_local.sh index 7a6e5a286..46e110a5d 100755 --- a/tools/scripts/build_unix_local.sh +++ b/tools/scripts/build_unix_local.sh @@ -10,11 +10,11 @@ set -e repo_base=$(realpath $(dirname $(realpath $0))/../..) -BK=b2 -IMAGE=build-clang11 +BK=cmake +IMAGE=build-cmake3_8 SHA=e9696175806589fc91e160399eedeac8fdc88c68 CONTAINER=builder-$IMAGE -FULL_IMAGE=bench +FULL_IMAGE=ghcr.io/anarthal-containers/$IMAGE:$SHA DB=mysql-8.4.1 docker start $DB || docker run -d \ @@ -50,8 +50,8 @@ case $BK in cmake) cmd="$db_args --cmake-build-type=Debug --build-shared-libs=1 - --cxxstd=20 - --install-test=1 + --cxxstd=11 + --install-test=0 " ;; From cb0ac8cccb7b01f982759e3c8c99091a716188fa Mon Sep 17 00:00:00 2001 From: Ruben Perez Date: Tue, 3 Jun 2025 20:40:17 +0200 Subject: [PATCH 57/68] restrict the errors test to openssl 3+ --- .../test_csha2p_encrypt_password_errors.cpp | 20 ++++++++----------- 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/test/unit/test_csha2p_encrypt_password_errors.cpp b/test/unit/test_csha2p_encrypt_password_errors.cpp index d2ca7b3c8..cf4425267 100644 --- a/test/unit/test_csha2p_encrypt_password_errors.cpp +++ b/test/unit/test_csha2p_encrypt_password_errors.cpp @@ -25,6 +25,9 @@ using detail::csha2p_encrypt_password; // and not linking to libssl/libcrypto. // These tests cover cases that can't be covered directly by the unit tests using the real OpenSSL. // Try to put as few tests here as possible. +// Some of the mocked functions are macros in OpenSSL versions before 3, so this setup can't work + +#if OPENSSL_VERSION_NUMBER >= 0x30000000L namespace { @@ -175,8 +178,7 @@ BOOST_AUTO_TEST_CASE(error_code_zero) BOOST_TEST(openssl_mock.EVP_PKEY_encrypt_calls == 0u); } -// OpenSSL 3+ might report system errors represented as codes > 0x80000000. -// Previous versions used them for user-defined errors, so we should tolerate them, too. +// OpenSSL 3+ might report system errors represented as codes > 0x80000000 BOOST_AUTO_TEST_CASE(error_code_system) { // Setup. The return value should be != -2, which indicates @@ -192,11 +194,7 @@ BOOST_AUTO_TEST_CASE(error_code_system) // Check BOOST_TEST(ec.failed()); BOOST_TEST(ec.has_location()); -#if OPENSSL_VERSION_NUMBER >= 0x30000000L BOOST_TEST((ec.category() == boost::system::system_category())); -#else - BOOST_TEST((ec.category() == ssl_category)); -#endif BOOST_TEST(openssl_mock.EVP_PKEY_CTX_set_rsa_padding_calls == 1u); BOOST_TEST(openssl_mock.EVP_PKEY_encrypt_calls == 0u); } @@ -239,13 +237,7 @@ int EVP_PKEY_CTX_set_rsa_padding(EVP_PKEY_CTX* ctx, int) return openssl_mock.set_rsa_padding_result; } -// OpenSSL >=3: EVP_PKEY_get_size is a function, EVP_PKEY_size is a macro -// OpenSSL < 3: EVP_PKEY_size is a function -#if OPENSSL_VERSION_NUMBER >= 0x30000000L int EVP_PKEY_get_size(const EVP_PKEY* pkey) -#else -int EVP_PKEY_size(const EVP_PKEY* pkey) -#endif { ++openssl_mock.EVP_PKEY_get_size_calls; BOOST_TEST(pkey == openssl_mock.key); @@ -261,3 +253,7 @@ int EVP_PKEY_encrypt(EVP_PKEY_CTX* ctx, unsigned char*, size_t* actual_size, con } unsigned long ERR_get_error() { return openssl_mock.last_error; } + +#else +BOOST_AUTO_TEST_CASE(dummy) {} +#endif From 969ef528b98cf7efbf7155d981cc6e3af3d97478 Mon Sep 17 00:00:00 2001 From: Ruben Perez Date: Tue, 3 Jun 2025 20:51:00 +0200 Subject: [PATCH 58/68] fix span conversion problems --- .../handshake_csha2p_encrypt_password.cpp | 38 +++++++++---------- 1 file changed, 18 insertions(+), 20 deletions(-) diff --git a/test/unit/test/sansio/handshake/handshake_csha2p_encrypt_password.cpp b/test/unit/test/sansio/handshake/handshake_csha2p_encrypt_password.cpp index 1d00ea336..82abcfcd1 100644 --- a/test/unit/test/sansio/handshake/handshake_csha2p_encrypt_password.cpp +++ b/test/unit/test/sansio/handshake/handshake_csha2p_encrypt_password.cpp @@ -28,6 +28,7 @@ using namespace boost::mysql; using namespace boost::mysql::test; +using boost::span; using boost::asio::error::ssl_category; using detail::csha2p_encrypt_password; @@ -46,10 +47,7 @@ namespace { BOOST_AUTO_TEST_SUITE(test_handshake_csha2p_encrypt_password) // Decrypts the output of -std::vector decrypt( - boost::span private_key, - boost::span ciphertext -) +std::vector decrypt(span private_key, span ciphertext) { // RAII helpers struct bio_deleter @@ -130,8 +128,8 @@ BOOST_AUTO_TEST_CASE(success) { string_view name; std::string password; - boost::span public_key; - boost::span private_key; + span public_key; + span private_key; std::vector expected_decrypted; } test_cases[] = { // clang-format off @@ -140,8 +138,8 @@ BOOST_AUTO_TEST_CASE(success) { "password_empty", {}, - public_key_2048, - private_key_2048, + span(public_key_2048), + span(private_key_2048), {0x0f} }, @@ -149,8 +147,8 @@ BOOST_AUTO_TEST_CASE(success) { "password_shorter_scramble", "csha2p_password", - public_key_2048, - private_key_2048, + span(public_key_2048), + span(private_key_2048), { 0x6c, 0x17, 0x27, 0x4e, 0x19, 0x4b, 0x78, 0x1b, 0x24, 0x2f, 0x20, 0x76, 0x7c, 0x0c, 0x2b, 0x10 } @@ -160,8 +158,8 @@ BOOST_AUTO_TEST_CASE(success) { "password_same_size_scramble", "hjbjd923KKLiosoi90J", - public_key_2048, - private_key_2048, + span(public_key_2048), + span(private_key_2048), { 0x67, 0x0e, 0x2d, 0x45, 0x4f, 0x02, 0x15, 0x58, 0x0e, 0x17, 0x1f, 0x68, 0x7c, 0x0d, 0x20, 0x79, 0x1f, 0x13, 0x17, 0x27 @@ -172,8 +170,8 @@ BOOST_AUTO_TEST_CASE(success) { "password_longer_scramble", "kjaski829380jvnnM,ka;::_kshf93IJCLIJO)jcjsnaklO?a", - public_key_2048, - private_key_2048, + span(public_key_2048), + span(private_key_2048), { 0x64, 0x0e, 0x2e, 0x5c, 0x40, 0x52, 0x1f, 0x59, 0x7c, 0x6f, 0x6b, 0x31, 0x79, 0x08, 0x21, 0x7e, 0x6b, 0x0f, 0x36, 0x46, 0x34, 0x5e, 0x75, 0x70, 0x40, 0x48, 0x4f, 0x0d, 0x7c, 0x6f, 0x1a, 0x4b, 0x50, 0x32, @@ -187,8 +185,8 @@ BOOST_AUTO_TEST_CASE(success) "hjbjd923KKLkjbdkcjwhekiy8393ou2weusidhiahJBKJKHCIHCKJIu9KHO09IJIpojaf0w39jalsjMMKjkjhiue93I=))" "UXIOJKXNKNhkai8923oiawiakssaknhakhIIHICHIO)CU)" "IHCKHCKJhisiweioHHJHUCHIIIJIOPkjgwijiosoi9jsu84HHUHCHI9839hdjsbsdjuUHJjbJ", - public_key_2048, - private_key_2048, + span(public_key_2048), + span(private_key_2048), { 0x67, 0x0e, 0x2d, 0x45, 0x4f, 0x02, 0x15, 0x58, 0x0e, 0x17, 0x1f, 0x6a, 0x79, 0x1c, 0x2b, 0x7b, 0x45, 0x49, 0x2a, 0x4f, 0x6a, 0x0f, 0x26, 0x56, 0x13, 0x08, 0x1e, 0x58, 0x2a, 0x29, 0x61, 0x76, 0x76, 0x0b, @@ -211,8 +209,8 @@ BOOST_AUTO_TEST_CASE(success) { "password_longer_sbo", std::string(600, '5'), - public_key_8192, - private_key_8192, + span(public_key_8192), + span(private_key_8192), { 0x3a, 0x51, 0x7a, 0x1a, 0x1e, 0x0e, 0x12, 0x5e, 0x70, 0x69, 0x66, 0x34, 0x26, 0x4b, 0x7a, 0x25, 0x13, 0x16, 0x68, 0x12, 0x3a, 0x51, 0x7a, 0x1a, 0x1e, 0x0e, 0x12, 0x5e, 0x70, 0x69, 0x66, 0x34, 0x26, 0x4b, @@ -257,8 +255,8 @@ BOOST_AUTO_TEST_CASE(success) { "password_all_characters", all_chars_password, - public_key_8192, - private_key_8192, + span(public_key_8192), + span(private_key_8192), { 0x0f, 0x65, 0x4d, 0x2c, 0x2f, 0x3e, 0x21, 0x6c, 0x4d, 0x55, 0x59, 0x0a, 0x1f, 0x73, 0x41, 0x1f, 0x36, 0x32, 0x4f, 0x34, 0x1b, 0x71, 0x59, 0x38, 0x33, 0x22, 0x3d, 0x70, 0x59, 0x41, 0x4d, 0x1e, 0x33, 0x5f, From 170144b0dca6a34d3d9596129be62b1df6a2617f Mon Sep 17 00:00:00 2001 From: Ruben Perez Date: Tue, 3 Jun 2025 21:00:40 +0200 Subject: [PATCH 59/68] introduce size check and simplify --- .../internal/sansio/csha2p_encrypt_password.hpp | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/include/boost/mysql/impl/internal/sansio/csha2p_encrypt_password.hpp b/include/boost/mysql/impl/internal/sansio/csha2p_encrypt_password.hpp index ca9ee1172..997a45359 100644 --- a/include/boost/mysql/impl/internal/sansio/csha2p_encrypt_password.hpp +++ b/include/boost/mysql/impl/internal/sansio/csha2p_encrypt_password.hpp @@ -61,7 +61,7 @@ inline error_code translate_openssl_error( // called because an OpenSSL primitive failed. It might indicate that OpenSSL // did not provide any extra error information. But it should still be an error if (int_code == 0) - return error_code(static_cast(client_errc::unknown_openssl_error), get_client_category(), loc); + return error_code(client_errc::unknown_openssl_error, loc); else return error_code(int_code, openssl_category, loc); } @@ -124,6 +124,13 @@ inline error_code csha2p_encrypt_password( return translate_openssl_error(ERR_get_error(), &loc, openssl_category); } + // Apply a sanity check to the key buffer size + if (server_key.size() > 1024u * 1024u * 1024u) + { + static constexpr auto loc = BOOST_CURRENT_LOCATION; + return error_code(client_errc::protocol_value_error, &loc); + } + // Salt the password auto salted_password = csha2p_salt_password(password, scramble); @@ -148,13 +155,7 @@ inline error_code csha2p_encrypt_password( // the source location to allow diagnosis static constexpr auto loc = BOOST_CURRENT_LOCATION; if (rsa_pad_res == -2) - { - return error_code( - static_cast(client_errc::protocol_value_error), - get_client_category(), - &loc - ); - } + return error_code(client_errc::protocol_value_error, &loc); else return translate_openssl_error(ERR_get_error(), &loc, openssl_category); } From 209aeee7f24ea557aee5ffaf7912e4ba82f6756e Mon Sep 17 00:00:00 2001 From: Ruben Perez Date: Tue, 3 Jun 2025 21:00:54 +0200 Subject: [PATCH 60/68] relax check in key_not_rsa --- .../test/sansio/handshake/handshake_csha2p_encrypt_password.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/unit/test/sansio/handshake/handshake_csha2p_encrypt_password.cpp b/test/unit/test/sansio/handshake/handshake_csha2p_encrypt_password.cpp index 82abcfcd1..439db3769 100644 --- a/test/unit/test/sansio/handshake/handshake_csha2p_encrypt_password.cpp +++ b/test/unit/test/sansio/handshake/handshake_csha2p_encrypt_password.cpp @@ -366,7 +366,7 @@ BOOST_AUTO_TEST_CASE(error_key_not_rsa) buffer_type buff; auto ec = csha2p_encrypt_password("csha2p_password", scramble, public_key_sm2, buff, ssl_category); - BOOST_TEST(ec == client_errc::protocol_value_error); // OpenSSL does not provide an error code here + BOOST_TEST(ec.failed()); // OpenSSL might or might not provide an error code here BOOST_TEST(ec.has_location()); } From 1ce0d524e6ea2190a8ffb4608357e7f9dd5b8c11 Mon Sep 17 00:00:00 2001 From: Ruben Perez Date: Tue, 3 Jun 2025 21:09:41 +0200 Subject: [PATCH 61/68] correct the check and add tests --- .../sansio/csha2p_encrypt_password.hpp | 15 ++++---- .../handshake_csha2p_encrypt_password.cpp | 37 +++++++------------ 2 files changed, 22 insertions(+), 30 deletions(-) diff --git a/include/boost/mysql/impl/internal/sansio/csha2p_encrypt_password.hpp b/include/boost/mysql/impl/internal/sansio/csha2p_encrypt_password.hpp index 997a45359..9b1a34cb0 100644 --- a/include/boost/mysql/impl/internal/sansio/csha2p_encrypt_password.hpp +++ b/include/boost/mysql/impl/internal/sansio/csha2p_encrypt_password.hpp @@ -110,6 +110,14 @@ inline error_code csha2p_encrypt_password( }; using unique_evp_pkey_ctx = std::unique_ptr; + // Apply a sanity check to the key buffer size + constexpr std::size_t max_key_buffer_size = 1024u * 1024u; // 1MB + if (server_key.size() > max_key_buffer_size) + { + static constexpr auto loc = BOOST_CURRENT_LOCATION; + return error_code(client_errc::protocol_value_error, &loc); + } + // Try to parse the private key unique_bio bio{BIO_new_mem_buf(server_key.data(), server_key.size())}; if (!bio) @@ -124,13 +132,6 @@ inline error_code csha2p_encrypt_password( return translate_openssl_error(ERR_get_error(), &loc, openssl_category); } - // Apply a sanity check to the key buffer size - if (server_key.size() > 1024u * 1024u * 1024u) - { - static constexpr auto loc = BOOST_CURRENT_LOCATION; - return error_code(client_errc::protocol_value_error, &loc); - } - // Salt the password auto salted_password = csha2p_salt_password(password, scramble); diff --git a/test/unit/test/sansio/handshake/handshake_csha2p_encrypt_password.cpp b/test/unit/test/sansio/handshake/handshake_csha2p_encrypt_password.cpp index 439db3769..8905d7e2c 100644 --- a/test/unit/test/sansio/handshake/handshake_csha2p_encrypt_password.cpp +++ b/test/unit/test/sansio/handshake/handshake_csha2p_encrypt_password.cpp @@ -46,6 +46,11 @@ namespace { // plaintext. BOOST_AUTO_TEST_SUITE(test_handshake_csha2p_encrypt_password) +constexpr std::uint8_t scramble[] = { + 0x0f, 0x64, 0x4f, 0x2f, 0x2b, 0x3b, 0x27, 0x6b, 0x45, 0x5c, + 0x53, 0x01, 0x13, 0x7e, 0x4f, 0x10, 0x26, 0x23, 0x5d, 0x27, +}; + // Decrypts the output of std::vector decrypt(span private_key, span ciphertext) { @@ -113,12 +118,6 @@ std::vector decrypt(span private_key, span key_buffer(2u * 1024u * 1024u, 0x0a); + auto ec = csha2p_encrypt_password("csha2p_password", scramble, key_buffer, buff, ssl_category); + BOOST_TEST(ec == client_errc::protocol_value_error); + BOOST_TEST(ec.has_location()); +} + BOOST_AUTO_TEST_SUITE_END() } // namespace \ No newline at end of file From 54fa0d824bf0da6983d9f9ccf404f052e8ad17fe Mon Sep 17 00:00:00 2001 From: Ruben Perez Date: Tue, 3 Jun 2025 21:21:46 +0200 Subject: [PATCH 62/68] fix unreliable test across openssl versions --- test/unit/include/test_unit/algo_test.hpp | 2 +- test/unit/test/sansio/handshake/handshake_csha2p.cpp | 4 ++-- .../sansio/handshake/handshake_csha2p_encrypt_password.cpp | 7 +++++++ test/unit/test/sansio/handshake/handshake_csha2p_keys.hpp | 7 ------- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/test/unit/include/test_unit/algo_test.hpp b/test/unit/include/test_unit/algo_test.hpp index 60cdd2ef4..7952cd091 100644 --- a/test/unit/include/test_unit/algo_test.hpp +++ b/test/unit/include/test_unit/algo_test.hpp @@ -238,7 +238,7 @@ class BOOST_ATTRIBUTE_NODISCARD algo_test struct algo_fixture_base { - static constexpr std::size_t default_max_buffsize = 1024u; + static constexpr std::size_t default_max_buffsize = 4u * 1024u * 1024u; detail::connection_state_data st; diff --git a/test/unit/test/sansio/handshake/handshake_csha2p.cpp b/test/unit/test/sansio/handshake/handshake_csha2p.cpp index c7982dc5c..6cd4b4d1a 100644 --- a/test/unit/test/sansio/handshake/handshake_csha2p.cpp +++ b/test/unit/test/sansio/handshake/handshake_csha2p.cpp @@ -306,7 +306,7 @@ BOOST_AUTO_TEST_CASE(fullauth_error) } // If encryption fails (e.g. because the server sent us an invalid key), we fail appropriately. -// Using a SM2 key yields a predictable error code. +// Size checks are the only ones that yield a predictable error code BOOST_AUTO_TEST_CASE(fullauth_encrypterror) { // Setup @@ -324,7 +324,7 @@ BOOST_AUTO_TEST_CASE(fullauth_encrypterror) ) .expect_read(create_more_data_frame(2, csha2p_perform_full_auth)) .expect_write(create_frame(3, csha2p_request_key)) - .expect_read(create_more_data_frame(4, public_key_sm2)) + .expect_read(create_more_data_frame(4, std::vector(2u * 1024u * 1024u, 0xab))) .check(fix, client_errc::protocol_value_error); } diff --git a/test/unit/test/sansio/handshake/handshake_csha2p_encrypt_password.cpp b/test/unit/test/sansio/handshake/handshake_csha2p_encrypt_password.cpp index 8905d7e2c..c2f493d34 100644 --- a/test/unit/test/sansio/handshake/handshake_csha2p_encrypt_password.cpp +++ b/test/unit/test/sansio/handshake/handshake_csha2p_encrypt_password.cpp @@ -346,6 +346,13 @@ MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAERDkCI/degPJXEIYYncyvGsTdj9YI // openssl pkey -in private.pem -pubout -out public.pem BOOST_AUTO_TEST_CASE(error_key_not_rsa) { + // ECDSA public key set up for SM2 encryption + constexpr unsigned char public_key_sm2[] = R"%(-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAEWGAXSpPHb2bWQjROuegjWPcuVwNW +mVpTh++3j7pnpWUjnFuarvWmWh/H6t96/pTx566FKGxZpLn3H9TLHZJsog== +-----END PUBLIC KEY----- +)%"; + buffer_type buff; auto ec = csha2p_encrypt_password("csha2p_password", scramble, public_key_sm2, buff, ssl_category); BOOST_TEST(ec.failed()); // OpenSSL might or might not provide an error code here diff --git a/test/unit/test/sansio/handshake/handshake_csha2p_keys.hpp b/test/unit/test/sansio/handshake/handshake_csha2p_keys.hpp index 69ffce546..5977c2108 100644 --- a/test/unit/test/sansio/handshake/handshake_csha2p_keys.hpp +++ b/test/unit/test/sansio/handshake/handshake_csha2p_keys.hpp @@ -182,13 +182,6 @@ FsKX4nSNK6rJ1uONSJu+dIiOA1YCabhCab7NRMCn+Bj+fHHkpvir24QbmqX1R8Au -----END PRIVATE KEY----- )%"; -// ECDSA public key set up for SM2 encryption -constexpr unsigned char public_key_sm2[] = R"%(-----BEGIN PUBLIC KEY----- -MFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAEWGAXSpPHb2bWQjROuegjWPcuVwNW -mVpTh++3j7pnpWUjnFuarvWmWh/H6t96/pTx566FKGxZpLn3H9TLHZJsog== ------END PUBLIC KEY----- -)%"; - } // namespace test } // namespace mysql } // namespace boost From 0e840e94cd0ae77624d11105ae1898d0b8727179 Mon Sep 17 00:00:00 2001 From: Ruben Perez Date: Wed, 4 Jun 2025 17:34:01 +0200 Subject: [PATCH 63/68] fix narrowing conversion --- .../mysql/impl/internal/sansio/csha2p_encrypt_password.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/boost/mysql/impl/internal/sansio/csha2p_encrypt_password.hpp b/include/boost/mysql/impl/internal/sansio/csha2p_encrypt_password.hpp index 9b1a34cb0..2a96859ec 100644 --- a/include/boost/mysql/impl/internal/sansio/csha2p_encrypt_password.hpp +++ b/include/boost/mysql/impl/internal/sansio/csha2p_encrypt_password.hpp @@ -119,7 +119,7 @@ inline error_code csha2p_encrypt_password( } // Try to parse the private key - unique_bio bio{BIO_new_mem_buf(server_key.data(), server_key.size())}; + unique_bio bio{BIO_new_mem_buf(server_key.data(), static_cast(server_key.size()))}; if (!bio) { static constexpr auto loc = BOOST_CURRENT_LOCATION; From 09de83108d21ca5d424e39814f2aa9008a5c0f0a Mon Sep 17 00:00:00 2001 From: Ruben Perez Date: Wed, 4 Jun 2025 17:43:04 +0200 Subject: [PATCH 64/68] Disable PFR problematic tests --- test/unit/test/pfr.cpp | 58 ++++++++++++++++++++---------------------- 1 file changed, 27 insertions(+), 31 deletions(-) diff --git a/test/unit/test/pfr.cpp b/test/unit/test/pfr.cpp index f583489db..a38cab5cb 100644 --- a/test/unit/test/pfr.cpp +++ b/test/unit/test/pfr.cpp @@ -19,6 +19,7 @@ using namespace boost::mysql; using namespace boost::mysql::test; +using boost::test_tools::per_element; using detail::get_row_name_table; using detail::get_row_size; using detail::is_pfr_reflectable; @@ -90,22 +91,15 @@ static_assert(get_row_size>() == 3u, ""); static_assert(get_row_size>() == 1u, ""); // name table -static void compare_name_tables(name_table_t lhs, name_table_t rhs) -{ - std::vector lhsvec(lhs.begin(), lhs.end()); - std::vector rhsvec(rhs.begin(), rhs.end()); - BOOST_TEST(lhsvec == rhsvec); -} - BOOST_AUTO_TEST_CASE(get_row_name_table_) { const string_view expected_s1[] = {"i", "f", "double_field"}; const string_view expected_s2[] = {"s"}; // TODO: recover this as part of https://github.com/boostorg/mysql/issues/483 - // compare_name_tables(get_row_name_table>(), name_table_t()); - compare_name_tables(get_row_name_table>(), expected_s1); - compare_name_tables(get_row_name_table>(), expected_s2); + // BOOST_TEST(get_row_name_table>() == name_table_t(), per_element()); + BOOST_TEST(get_row_name_table>() == expected_s1, per_element()); + BOOST_TEST(get_row_name_table>() == expected_s2, per_element()); } // meta check @@ -140,17 +134,18 @@ BOOST_AUTO_TEST_CASE(meta_check_fail) ); } -BOOST_AUTO_TEST_CASE(meta_check_empty_struct) -{ - diagnostics diag; - auto err = detail::meta_check>( - boost::span(), - metadata_collection_view(), - diag - ); - BOOST_TEST(err == error_code()); - BOOST_TEST(diag.client_message() == ""); -} +// TODO: recover this as part of https://github.com/boostorg/mysql/issues/483 +// BOOST_AUTO_TEST_CASE(meta_check_empty_struct) +// { +// diagnostics diag; +// auto err = detail::meta_check>( +// boost::span(), +// metadata_collection_view(), +// diag +// ); +// BOOST_TEST(err == error_code()); +// BOOST_TEST(diag.client_message() == ""); +// } // parsing BOOST_AUTO_TEST_CASE(parse_success) @@ -176,16 +171,17 @@ BOOST_AUTO_TEST_CASE(parse_error) BOOST_TEST(err == client_errc::static_row_parsing_error); } -BOOST_AUTO_TEST_CASE(parse_empty_struct) -{ - empty value; - auto err = detail::parse>( - boost::span(), - boost::span(), - value - ); - BOOST_TEST(err == error_code()); -} +// TODO: recover this as part of https://github.com/boostorg/mysql/issues/483 +// BOOST_AUTO_TEST_CASE(parse_empty_struct) +// { +// empty value; +// auto err = detail::parse>( +// boost::span(), +// boost::span(), +// value +// ); +// BOOST_TEST(err == error_code()); +// } BOOST_AUTO_TEST_SUITE_END() #endif From dbfd8ab0b9f31ae339d0a014febd8e9c347302b9 Mon Sep 17 00:00:00 2001 From: Ruben Perez Date: Wed, 4 Jun 2025 18:05:57 +0200 Subject: [PATCH 65/68] Fix narrowing conversion warning --- .../test/sansio/handshake/handshake_csha2p_encrypt_password.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/unit/test/sansio/handshake/handshake_csha2p_encrypt_password.cpp b/test/unit/test/sansio/handshake/handshake_csha2p_encrypt_password.cpp index c2f493d34..26802af5f 100644 --- a/test/unit/test/sansio/handshake/handshake_csha2p_encrypt_password.cpp +++ b/test/unit/test/sansio/handshake/handshake_csha2p_encrypt_password.cpp @@ -74,7 +74,7 @@ std::vector decrypt(span private_key, span; // Create a BIO with the key - unique_bio bio(BIO_new_mem_buf(private_key.data(), private_key.size())); + unique_bio bio(BIO_new_mem_buf(private_key.data(), static_cast(private_key.size()))); BOOST_TEST_REQUIRE((bio != nullptr), "Creating a BIO failed: " << ERR_get_error()); // Load the key From 11ad921e3dae17f6d0192b3d0cfb3198a885be32 Mon Sep 17 00:00:00 2001 From: Ruben Perez Date: Wed, 4 Jun 2025 18:08:58 +0200 Subject: [PATCH 66/68] Fixed PFR problem --- include/boost/mysql/impl/pfr.hpp | 8 +----- test/unit/test/pfr.cpp | 47 +++++++++++++++----------------- 2 files changed, 23 insertions(+), 32 deletions(-) diff --git a/include/boost/mysql/impl/pfr.hpp b/include/boost/mysql/impl/pfr.hpp index bdacdae92..1f2c62c21 100644 --- a/include/boost/mysql/impl/pfr.hpp +++ b/include/boost/mysql/impl/pfr.hpp @@ -62,18 +62,12 @@ using pfr_fields_t = decltype(pfr::structure_to_tuple(std::declval())) template constexpr std::array to_name_table_storage(std::array input) noexcept { - std::array res; + std::array res{}; for (std::size_t i = 0; i < N; ++i) res[i] = input[i]; return res; } -// Workaround for https://github.com/boostorg/pfr/issues/165 -constexpr std::array to_name_table_storage(std::array) noexcept -{ - return {}; -} - template constexpr inline auto pfr_names_storage = to_name_table_storage(pfr::names_as_array()); diff --git a/test/unit/test/pfr.cpp b/test/unit/test/pfr.cpp index a38cab5cb..05cf263eb 100644 --- a/test/unit/test/pfr.cpp +++ b/test/unit/test/pfr.cpp @@ -96,8 +96,7 @@ BOOST_AUTO_TEST_CASE(get_row_name_table_) const string_view expected_s1[] = {"i", "f", "double_field"}; const string_view expected_s2[] = {"s"}; - // TODO: recover this as part of https://github.com/boostorg/mysql/issues/483 - // BOOST_TEST(get_row_name_table>() == name_table_t(), per_element()); + BOOST_TEST(get_row_name_table>() == name_table_t(), per_element()); BOOST_TEST(get_row_name_table>() == expected_s1, per_element()); BOOST_TEST(get_row_name_table>() == expected_s2, per_element()); } @@ -134,18 +133,17 @@ BOOST_AUTO_TEST_CASE(meta_check_fail) ); } -// TODO: recover this as part of https://github.com/boostorg/mysql/issues/483 -// BOOST_AUTO_TEST_CASE(meta_check_empty_struct) -// { -// diagnostics diag; -// auto err = detail::meta_check>( -// boost::span(), -// metadata_collection_view(), -// diag -// ); -// BOOST_TEST(err == error_code()); -// BOOST_TEST(diag.client_message() == ""); -// } +BOOST_AUTO_TEST_CASE(meta_check_empty_struct) +{ + diagnostics diag; + auto err = detail::meta_check>( + boost::span(), + metadata_collection_view(), + diag + ); + BOOST_TEST(err == error_code()); + BOOST_TEST(diag.client_message() == ""); +} // parsing BOOST_AUTO_TEST_CASE(parse_success) @@ -171,17 +169,16 @@ BOOST_AUTO_TEST_CASE(parse_error) BOOST_TEST(err == client_errc::static_row_parsing_error); } -// TODO: recover this as part of https://github.com/boostorg/mysql/issues/483 -// BOOST_AUTO_TEST_CASE(parse_empty_struct) -// { -// empty value; -// auto err = detail::parse>( -// boost::span(), -// boost::span(), -// value -// ); -// BOOST_TEST(err == error_code()); -// } +BOOST_AUTO_TEST_CASE(parse_empty_struct) +{ + empty value; + auto err = detail::parse>( + boost::span(), + boost::span(), + value + ); + BOOST_TEST(err == error_code()); +} BOOST_AUTO_TEST_SUITE_END() #endif From a045836f712b0c8f0302bf004e60a85c2b649a74 Mon Sep 17 00:00:00 2001 From: Ruben Perez Date: Thu, 5 Jun 2025 11:48:31 +0200 Subject: [PATCH 67/68] Workaround PFR MSVC problem --- include/boost/mysql/impl/pfr.hpp | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/include/boost/mysql/impl/pfr.hpp b/include/boost/mysql/impl/pfr.hpp index 1f2c62c21..061086712 100644 --- a/include/boost/mysql/impl/pfr.hpp +++ b/include/boost/mysql/impl/pfr.hpp @@ -69,7 +69,25 @@ constexpr std::array to_name_table_storage(std::array -constexpr inline auto pfr_names_storage = to_name_table_storage(pfr::names_as_array()); +constexpr inline std::size_t pfr_row_size_v = mp11::mp_size>::value; + +template +constexpr std::array> create_pfr_name_table() noexcept +{ + // Some MSVC compilers have trouble with pfr::names_as_array() when + // the row type is empty + if constexpr (pfr_row_size_v == 0u) + { + return {}; + } + else + { + return to_name_table_storage(pfr::names_as_array()); + } +} + +template +constexpr inline auto pfr_names_storage = create_pfr_name_table(); template class row_traits, false> From e774a8c34d6ab9dcbf10a79cc44e213612d8bdb5 Mon Sep 17 00:00:00 2001 From: Ruben Perez Date: Fri, 6 Jun 2025 18:55:51 +0200 Subject: [PATCH 68/68] Add missing test --- .../handshake/handshake_csha2p_encrypt_password.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/test/unit/test/sansio/handshake/handshake_csha2p_encrypt_password.cpp b/test/unit/test/sansio/handshake/handshake_csha2p_encrypt_password.cpp index 26802af5f..cafcf2c6e 100644 --- a/test/unit/test/sansio/handshake/handshake_csha2p_encrypt_password.cpp +++ b/test/unit/test/sansio/handshake/handshake_csha2p_encrypt_password.cpp @@ -368,6 +368,18 @@ BOOST_AUTO_TEST_CASE(error_key_too_big) BOOST_TEST(ec.has_location()); } +// If a password longer than the longest allowed plaintext is provided, we fail +BOOST_AUTO_TEST_CASE(error_password_too_big) +{ + buffer_type buff; + std::string passwd(214u, 'a'); // 213 is the max RSA/OAEP plaintext size + auto ec = csha2p_encrypt_password(passwd, scramble, public_key_2048, buff, ssl_category); + BOOST_TEST((ec.category() == ssl_category)); + BOOST_TEST(ec.value() > 0); // is an error + BOOST_TEST(ec.message() != ""); // produces some output + BOOST_TEST(ec.has_location()); +} + BOOST_AUTO_TEST_SUITE_END() } // namespace \ No newline at end of file