auth.ipp 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  1. //
  2. // Copyright (c) 2019-2023 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
  3. //
  4. // Distributed under the Boost Software License, Version 1.0. (See accompanying
  5. // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
  6. //
  7. #ifndef BHO_MYSQL_IMPL_INTERNAL_AUTH_AUTH_IPP
  8. #define BHO_MYSQL_IMPL_INTERNAL_AUTH_AUTH_IPP
  9. #include "asio2/bho/mysql/detail/config.hpp"
  10. #pragma once
  11. #include <asio2/bho/mysql/client_errc.hpp>
  12. #include <asio2/bho/mysql/string_view.hpp>
  13. #include <asio2/bho/mysql/impl/internal/auth/auth.hpp>
  14. #include <asio2/bho/mysql/impl/internal/make_string_view.hpp>
  15. #include <algorithm>
  16. #include <cstring>
  17. #include <openssl/sha.h>
  18. namespace bho {
  19. namespace mysql {
  20. namespace detail {
  21. // mysql_native_password
  22. // Authorization for this plugin is always challenge (nonce) -> response
  23. // (hashed password).
  24. BHO_MYSQL_STATIC_IF_COMPILED constexpr std::size_t mnp_challenge_length = 20;
  25. BHO_MYSQL_STATIC_IF_COMPILED constexpr std::size_t mnp_response_length = 20;
  26. // challenge must point to challenge_length bytes of data
  27. // output must point to response_length bytes of data
  28. // SHA1( password ) XOR SHA1( "20-bytes random data from server" <concat> SHA1( SHA1( password ) ) )
  29. BHO_MYSQL_STATIC_OR_INLINE
  30. void mnp_compute_auth_string(string_view password, const void* challenge, void* output)
  31. {
  32. // SHA1 (password)
  33. using sha1_buffer = unsigned char[SHA_DIGEST_LENGTH];
  34. sha1_buffer password_sha1;
  35. SHA1(reinterpret_cast<const unsigned char*>(password.data()), password.size(), password_sha1);
  36. // Add server challenge (salt)
  37. unsigned char salted_buffer[mnp_challenge_length + SHA_DIGEST_LENGTH];
  38. memcpy(salted_buffer, challenge, mnp_challenge_length);
  39. SHA1(password_sha1, sizeof(password_sha1), salted_buffer + 20);
  40. sha1_buffer salted_sha1;
  41. SHA1(salted_buffer, sizeof(salted_buffer), salted_sha1);
  42. // XOR
  43. static_assert(mnp_response_length == SHA_DIGEST_LENGTH, "Buffer size mismatch");
  44. for (std::size_t i = 0; i < SHA_DIGEST_LENGTH; ++i)
  45. {
  46. static_cast<std::uint8_t*>(output)[i] = password_sha1[i] ^ salted_sha1[i];
  47. }
  48. }
  49. BHO_MYSQL_STATIC_OR_INLINE
  50. error_code mnp_compute_response(
  51. string_view password,
  52. bho::span<const std::uint8_t> challenge,
  53. bool, // use_ssl
  54. std::vector<std::uint8_t>& output
  55. )
  56. {
  57. // Check challenge size
  58. if (challenge.size() != mnp_challenge_length)
  59. {
  60. return make_error_code(client_errc::protocol_value_error);
  61. }
  62. // Do the calculation
  63. output.resize(mnp_response_length);
  64. mnp_compute_auth_string(password, challenge.data(), output.data());
  65. return error_code();
  66. }
  67. // caching_sha2_password
  68. // Authorization for this plugin may be cleartext password or challenge/response.
  69. // The server has a cache that uses when employing challenge/response. When
  70. // the server sends a challenge of challenge_length bytes, we should send
  71. // the password hashed with the challenge. The server may send a challenge
  72. // equals to perform_full_auth, meaning it could not use the cache to
  73. // complete the auth. In this case, we should just send the cleartext password.
  74. // Doing the latter requires a SSL connection. It is possible to perform full
  75. // auth without an SSL connection, but that requires the server public key,
  76. // and we do not implement that.
  77. BHO_MYSQL_STATIC_IF_COMPILED constexpr std::size_t csha2p_challenge_length = 20;
  78. BHO_MYSQL_STATIC_IF_COMPILED constexpr std::size_t csha2p_response_length = 32;
  79. // challenge must point to challenge_length bytes of data
  80. // output must point to response_length bytes of data
  81. BHO_MYSQL_STATIC_OR_INLINE
  82. void csha2p_compute_auth_string(string_view password, const void* challenge, void* output)
  83. {
  84. static_assert(csha2p_response_length == SHA256_DIGEST_LENGTH, "Buffer size mismatch");
  85. // SHA(SHA(password_sha) concat challenge) XOR password_sha
  86. // hash1 = SHA(pass)
  87. using sha_buffer = std::uint8_t[csha2p_response_length];
  88. sha_buffer password_sha;
  89. SHA256(reinterpret_cast<const unsigned char*>(password.data()), password.size(), password_sha);
  90. // SHA(password_sha) concat challenge = buffer
  91. std::uint8_t buffer[csha2p_response_length + csha2p_challenge_length];
  92. SHA256(password_sha, csha2p_response_length, buffer);
  93. std::memcpy(buffer + csha2p_response_length, challenge, csha2p_challenge_length);
  94. // SHA(SHA(password_sha) concat challenge) = SHA(buffer) = salted_password
  95. sha_buffer salted_password;
  96. SHA256(buffer, sizeof(buffer), salted_password);
  97. // salted_password XOR password_sha
  98. for (unsigned i = 0; i < csha2p_response_length; ++i)
  99. {
  100. static_cast<std::uint8_t*>(output)[i] = salted_password[i] ^ password_sha[i];
  101. }
  102. }
  103. BHO_MYSQL_STATIC_OR_INLINE
  104. bool should_perform_full_auth(bho::span<const std::uint8_t> challenge) noexcept
  105. {
  106. // A challenge of "\4" means "perform full auth"
  107. return challenge.size() == 1u && challenge[0] == 4;
  108. }
  109. BHO_MYSQL_STATIC_OR_INLINE
  110. error_code csha2p_compute_response(
  111. string_view password,
  112. bho::span<const std::uint8_t> challenge,
  113. bool use_ssl,
  114. std::vector<std::uint8_t>& output
  115. )
  116. {
  117. if (should_perform_full_auth(challenge))
  118. {
  119. if (!use_ssl)
  120. {
  121. return make_error_code(client_errc::auth_plugin_requires_ssl);
  122. }
  123. output.assign(password.begin(), password.end());
  124. output.push_back(0);
  125. return error_code();
  126. }
  127. else
  128. {
  129. // Check challenge size
  130. if (challenge.size() != csha2p_challenge_length)
  131. {
  132. return make_error_code(client_errc::protocol_value_error);
  133. }
  134. // Do the calculation
  135. output.resize(csha2p_response_length);
  136. csha2p_compute_auth_string(password, challenge.data(), output.data());
  137. return error_code();
  138. }
  139. }
  140. // top-level API
  141. struct authentication_plugin
  142. {
  143. using calculator_signature = error_code (*)(
  144. string_view password,
  145. bho::span<const std::uint8_t> challenge,
  146. bool use_ssl,
  147. std::vector<std::uint8_t>& output
  148. );
  149. string_view name;
  150. calculator_signature calculator;
  151. };
  152. BHO_MYSQL_STATIC_IF_COMPILED
  153. constexpr authentication_plugin all_authentication_plugins[] = {
  154. {
  155. make_string_view("mysql_native_password"),
  156. &mnp_compute_response,
  157. },
  158. {
  159. make_string_view("caching_sha2_password"),
  160. &csha2p_compute_response,
  161. },
  162. };
  163. BHO_MYSQL_STATIC_OR_INLINE
  164. const authentication_plugin* find_plugin(string_view name)
  165. {
  166. auto it = std::find_if(
  167. std::begin(all_authentication_plugins),
  168. std::end(all_authentication_plugins),
  169. [name](const authentication_plugin& plugin) { return plugin.name == name; }
  170. );
  171. return it == std::end(all_authentication_plugins) ? nullptr : it;
  172. }
  173. } // namespace detail
  174. } // namespace mysql
  175. } // namespace bho
  176. bho::mysql::error_code bho::mysql::detail::compute_auth_response(
  177. string_view plugin_name,
  178. string_view password,
  179. span<const std::uint8_t> challenge,
  180. bool use_ssl,
  181. auth_response& output
  182. )
  183. {
  184. const auto* plugin = find_plugin(plugin_name);
  185. if (plugin)
  186. {
  187. output.plugin_name = plugin->name;
  188. if (password.empty())
  189. {
  190. // Blank password: we should just return an empty auth string
  191. output.data.clear();
  192. return error_code();
  193. }
  194. else
  195. {
  196. return plugin->calculator(password, challenge, use_ssl, output.data);
  197. }
  198. }
  199. else
  200. {
  201. return client_errc::unknown_auth_plugin;
  202. }
  203. }
  204. #endif