Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-08-17 08:51:24

0001 //
0002 // Copyright (c) 2019-2025 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
0003 //
0004 // Distributed under the Boost Software License, Version 1.0. (See accompanying
0005 // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
0006 //
0007 
0008 #ifndef BOOST_MYSQL_IMPL_INTERNAL_SANSIO_CACHING_SHA2_PASSWORD_HPP
0009 #define BOOST_MYSQL_IMPL_INTERNAL_SANSIO_CACHING_SHA2_PASSWORD_HPP
0010 
0011 #include <boost/mysql/client_errc.hpp>
0012 #include <boost/mysql/error_code.hpp>
0013 #include <boost/mysql/string_view.hpp>
0014 
0015 #include <boost/mysql/detail/next_action.hpp>
0016 
0017 #include <boost/mysql/impl/internal/coroutine.hpp>
0018 #include <boost/mysql/impl/internal/protocol/impl/protocol_types.hpp>
0019 #include <boost/mysql/impl/internal/protocol/impl/serialization_context.hpp>
0020 #include <boost/mysql/impl/internal/protocol/static_buffer.hpp>
0021 #include <boost/mysql/impl/internal/sansio/auth_plugin_common.hpp>
0022 #include <boost/mysql/impl/internal/sansio/connection_state_data.hpp>
0023 #include <boost/mysql/impl/internal/sansio/csha2p_encrypt_password.hpp>
0024 
0025 #include <boost/asio/ssl/error.hpp>
0026 #include <boost/container/small_vector.hpp>
0027 #include <boost/core/span.hpp>
0028 #include <boost/system/result.hpp>
0029 #include <boost/system/system_category.hpp>
0030 
0031 #include <array>
0032 #include <cstddef>
0033 #include <cstdint>
0034 #include <openssl/sha.h>
0035 
0036 // Reference:
0037 // https://dev.mysql.com/doc/dev/mysql-server/latest/page_caching_sha2_authentication_exchanges.html
0038 
0039 namespace boost {
0040 namespace mysql {
0041 namespace detail {
0042 
0043 // Constants
0044 BOOST_INLINE_CONSTEXPR std::size_t csha2p_hash_size = 32;
0045 BOOST_INLINE_CONSTEXPR const char* csha2p_plugin_name = "caching_sha2_password";
0046 static_assert(csha2p_hash_size <= max_hash_size, "");
0047 static_assert(csha2p_hash_size == SHA256_DIGEST_LENGTH, "Buffer size mismatch");
0048 
0049 inline void csha2p_hash_password_impl(
0050     string_view password,
0051     span<const std::uint8_t, scramble_size> scramble,
0052     span<std::uint8_t, csha2p_hash_size> output
0053 )
0054 {
0055     // SHA(SHA(password_sha) concat scramble) XOR password_sha
0056     // hash1 = SHA(pass)
0057     std::array<std::uint8_t, csha2p_hash_size> password_sha;
0058     SHA256(reinterpret_cast<const unsigned char*>(password.data()), password.size(), password_sha.data());
0059 
0060     // SHA(password_sha) concat scramble = buffer
0061     std::array<std::uint8_t, csha2p_hash_size + scramble_size> buffer;
0062     SHA256(password_sha.data(), password_sha.size(), buffer.data());
0063     std::memcpy(buffer.data() + csha2p_hash_size, scramble.data(), scramble.size());
0064 
0065     // SHA(SHA(password_sha) concat scramble) = SHA(buffer) = salted_password
0066     std::array<std::uint8_t, csha2p_hash_size> salted_password;
0067     SHA256(buffer.data(), buffer.size(), salted_password.data());
0068 
0069     // salted_password XOR password_sha
0070     for (unsigned i = 0; i < csha2p_hash_size; ++i)
0071     {
0072         output[i] = salted_password[i] ^ password_sha[i];
0073     }
0074 }
0075 
0076 inline static_buffer<max_hash_size> csha2p_hash_password(
0077     string_view password,
0078     span<const std::uint8_t, scramble_size> scramble
0079 )
0080 {
0081     // Empty passwords are not hashed
0082     if (password.empty())
0083         return {};
0084 
0085     // Run the algorithm
0086     static_buffer<max_hash_size> res(csha2p_hash_size);
0087     csha2p_hash_password_impl(
0088         password,
0089         scramble,
0090         span<std::uint8_t, csha2p_hash_size>(res.data(), csha2p_hash_size)
0091     );
0092     return res;
0093 }
0094 
0095 class csha2p_algo
0096 {
0097     int resume_point_{0};
0098 
0099     static bool is_perform_full_auth(span<const std::uint8_t> server_data)
0100     {
0101         return server_data.size() == 1u && server_data[0] == 4;
0102     }
0103 
0104     static bool is_fast_auth_ok(span<const std::uint8_t> server_data)
0105     {
0106         return server_data.size() == 1u && server_data[0] == 3;
0107     }
0108 
0109     static next_action encrypt_password(
0110         connection_state_data& st,
0111         std::uint8_t& seqnum,
0112         string_view password,
0113         span<const std::uint8_t, scramble_size> scramble,
0114         span<const std::uint8_t> server_key
0115     )
0116     {
0117         container::small_vector<std::uint8_t, 512> buff;
0118         auto ec = csha2p_encrypt_password(password, scramble, server_key, buff, asio::error::ssl_category);
0119         if (ec)
0120             return ec;
0121         return st.write(
0122             string_eof{string_view(reinterpret_cast<const char*>(buff.data()), buff.size())},
0123             seqnum
0124         );
0125     }
0126 
0127 public:
0128     csha2p_algo() = default;
0129 
0130     next_action resume(
0131         connection_state_data& st,
0132         span<const std::uint8_t> server_data,
0133         string_view password,
0134         span<const std::uint8_t, scramble_size> scramble,
0135         bool secure_channel,
0136         std::uint8_t& seqnum
0137     )
0138     {
0139         switch (resume_point_)
0140         {
0141         case 0:
0142             // If we got a more data packet, the server either required us to perform full auth,
0143             // or told us to read again because an OK packet or error packet is coming.
0144             if (is_perform_full_auth(server_data))
0145             {
0146                 if (secure_channel)
0147                 {
0148                     // We should send a packet with just the password, as a NULL-terminated string
0149                     BOOST_MYSQL_YIELD(resume_point_, 1, st.write(string_null{password}, seqnum))
0150 
0151                     // The server shouldn't send us any more packets
0152                     return error_code(client_errc::bad_handshake_packet_type);
0153                 }
0154                 else
0155                 {
0156                     // Request the server's public key
0157                     BOOST_MYSQL_YIELD(resume_point_, 2, st.write(int1{2}, seqnum))
0158 
0159                     // Encrypt the password with the key we were given
0160                     BOOST_MYSQL_YIELD(
0161                         resume_point_,
0162                         3,
0163                         encrypt_password(st, seqnum, password, scramble, server_data)
0164                     )
0165 
0166                     // The server shouldn't send us any more packets
0167                     return error_code(client_errc::bad_handshake_packet_type);
0168                 }
0169             }
0170             else if (is_fast_auth_ok(server_data))
0171             {
0172                 // We should wait for the server to send an OK or an error
0173                 BOOST_MYSQL_YIELD(resume_point_, 4, st.read(seqnum))
0174             }
0175             else
0176             {
0177                 // The server sent a data packet and we don't know what it means.
0178                 // Treat it as a protocol violation error and exit
0179                 return error_code(client_errc::bad_handshake_packet_type);
0180             }
0181         }
0182 
0183         // If we got here, the server sent us more data, which is a protocol violation
0184         return error_code(client_errc::bad_handshake_packet_type);
0185     }
0186 };
0187 
0188 }  // namespace detail
0189 }  // namespace mysql
0190 }  // namespace boost
0191 
0192 #endif