Summary
caching_sha2_password reuses the mysql_native_password scramble helper, which prepends the nonce to the double-hashed password. The caching_sha2 exchange appends it. As a result the scramble MyXQL sends never matches what the server expects.
Against MySQL this is invisible — the server answers a mismatching scramble with perform_full_authentication instead of an error, and the connection then succeeds via the RSA public-key exchange. Against a server that verifies the scramble eagerly, every connection fails.
Affected code
lib/myxql/protocol/auth.ex:
def sha256_password(password, auth_plugin_data) do
sha_hash(:sha256, password, auth_plugin_data)
end
defp sha_hash(type, password, auth_plugin_data) do
password_sha = :crypto.hash(type, password)
bxor_binary(
password_sha,
:crypto.hash(type, auth_plugin_data <> :crypto.hash(type, password_sha))
# ^^^^^^^^^^^^^^^^ nonce first
)
end
sha_hash/3 is shared with mysql_native_password/2, where nonce-first is correct (SHA1). For caching_sha2 the documented exchange is:
XOR(SHA256(password), SHA256(SHA256(SHA256(password)) <> nonce))
i.e. the nonce is appended, not prepended.
A second bug this uncovers
Once the ordering is fixed, the server starts replying 0x01 0x03, which falls through to:
def decode_auth_response(<<0x01, rest::binary>>), do: auth_more_data(data: rest)
so <<3>> reaches :public_key.pem_decode/1 as if it were an RSA key and the connection crashes. The fast auth success reply also has an OK packet trailing it, which recv_packet/3 discards by design ("even if next packet follows, ignore it") — leaving it in the socket desyncs the next exchange. Both MySQL 8.4 and ProxySQL do send that trailing OK.
Why CI doesn't catch it
MySQL never errors on a wrong scramble, so tests pass. The existing coverage is caching_sha2_password (public key exchange) — the full-auth path — and there is no test asserting fast auth, which is exactly the path that never runs. #212 added 8.4 to CI but only changed ci.yml, docker-compose.yml and test_helper.exs; nothing in lib/. Its description states fast auth was verified working, but per the measurements above the server answers perform_full_authentication on every connection, so the connections it saw succeeding were going through the RSA exchange.
Fix
PR: #213
- corrects the caching_sha2 scramble ordering (split out from the native helper, which keeps nonce-first)
- decodes
0x01 0x03 as :fast_auth_success and consumes the trailing OK
- adds known-answer vectors for both plugins plus a regression test that caching_sha2 does not match the prepended-nonce ordering
- adds a
caching_sha2_password (cached, fast auth) test that connects twice so the second connection exercises the previously-unreachable path
Full suite on MySQL 8.4.10: 222 tests, 0 failures (excluding two UNIX-socket tests that fail identically before and after, because my server runs in Docker).
Environment
- MyXQL 0.9.0 and master (
fd9ffde)
- MySQL 8.4.10 (Docker
mysql:8.4), and ProxySQL 3306 fronting Percona XtraDB Cluster 8.4.8
- Elixir 1.19.5 / OTP 26
Summary
caching_sha2_passwordreuses themysql_native_passwordscramble helper, which prepends the nonce to the double-hashed password. The caching_sha2 exchange appends it. As a result the scramble MyXQL sends never matches what the server expects.Against MySQL this is invisible — the server answers a mismatching scramble with
perform_full_authenticationinstead of an error, and the connection then succeeds via the RSA public-key exchange. Against a server that verifies the scramble eagerly, every connection fails.Affected code
lib/myxql/protocol/auth.ex:sha_hash/3is shared withmysql_native_password/2, where nonce-first is correct (SHA1). For caching_sha2 the documented exchange is:i.e. the nonce is appended, not prepended.
A second bug this uncovers
Once the ordering is fixed, the server starts replying
0x01 0x03, which falls through to:so
<<3>>reaches:public_key.pem_decode/1as if it were an RSA key and the connection crashes. The fast auth success reply also has an OK packet trailing it, whichrecv_packet/3discards by design ("even if next packet follows, ignore it") — leaving it in the socket desyncs the next exchange. Both MySQL 8.4 and ProxySQL do send that trailing OK.Why CI doesn't catch it
MySQL never errors on a wrong scramble, so tests pass. The existing coverage is
caching_sha2_password (public key exchange)— the full-auth path — and there is no test asserting fast auth, which is exactly the path that never runs. #212 added 8.4 to CI but only changedci.yml,docker-compose.ymlandtest_helper.exs; nothing inlib/. Its description states fast auth was verified working, but per the measurements above the server answersperform_full_authenticationon every connection, so the connections it saw succeeding were going through the RSA exchange.Fix
PR: #213
0x01 0x03as:fast_auth_successand consumes the trailing OKcaching_sha2_password (cached, fast auth)test that connects twice so the second connection exercises the previously-unreachable pathFull suite on MySQL 8.4.10: 222 tests, 0 failures (excluding two UNIX-socket tests that fail identically before and after, because my server runs in Docker).
Environment
fd9ffde)mysql:8.4), and ProxySQL 3306 fronting Percona XtraDB Cluster 8.4.8