From e11f44ca70cdd5821e2bf9d321a158a85752ac9a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 10 Feb 2026 02:00:42 +0000 Subject: [PATCH 1/4] Initial plan From 2d685d999250844ea91d07bf9b922dcf843549ba Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 10 Feb 2026 02:06:16 +0000 Subject: [PATCH 2/4] Add caching_sha2_password unit test Co-authored-by: kerryjiang <456060+kerryjiang@users.noreply.github.com> --- tests/SuperSocket.MySQL.Test/HandshakeTest.cs | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/tests/SuperSocket.MySQL.Test/HandshakeTest.cs b/tests/SuperSocket.MySQL.Test/HandshakeTest.cs index b5db76c..6f06b07 100644 --- a/tests/SuperSocket.MySQL.Test/HandshakeTest.cs +++ b/tests/SuperSocket.MySQL.Test/HandshakeTest.cs @@ -1,6 +1,8 @@ using System; using System.Buffers; using System.Collections.Generic; +using System.Reflection; +using System.Security.Cryptography; using System.Text; using System.Threading.Tasks; using Xunit; @@ -202,6 +204,41 @@ public void MySQLConnection_GenerateAuthResponse_ShouldHandleDifferentPasswords( Assert.NotNull(connection); } + [Fact] + public void MySQLConnection_GenerateCachingSha2Response_ShouldMatchExpected() + { + // Arrange + const string password = "test_password"; + var connection = new MySQLConnection("localhost", 3306, "user", password); + var salt = new byte[] { 0x33, 0x21, 0x55, 0x42, 0x19, 0x76, 0xA1, 0x0B, 0x10, 0x5C, 0x2D, 0x48, 0x5A, 0x00 }; + var method = typeof(MySQLConnection).GetMethod("GenerateCachingSha2Response", BindingFlags.NonPublic | BindingFlags.Instance); + Assert.NotNull(method); + + // Act + var response = (byte[])method.Invoke(connection, new object[] { salt }); + + // Assert + var trimmedSaltLength = salt[^1] == 0 ? salt.Length - 1 : salt.Length; + var trimmedSalt = new byte[trimmedSaltLength]; + Array.Copy(salt, trimmedSalt, trimmedSaltLength); + + using var sha256 = SHA256.Create(); + var passwordBytes = Encoding.UTF8.GetBytes(password); + var sha256Password = sha256.ComputeHash(passwordBytes); + var sha256Sha256Password = sha256.ComputeHash(sha256Password); + var hashAndSalt = new byte[sha256Sha256Password.Length + trimmedSalt.Length]; + Array.Copy(sha256Sha256Password, 0, hashAndSalt, 0, sha256Sha256Password.Length); + Array.Copy(trimmedSalt, 0, hashAndSalt, sha256Sha256Password.Length, trimmedSalt.Length); + var sha256Combined = sha256.ComputeHash(hashAndSalt); + var expected = new byte[sha256Password.Length]; + for (int i = 0; i < expected.Length; i++) + { + expected[i] = (byte)(sha256Password[i] ^ sha256Combined[i]); + } + + Assert.Equal(expected, response); + } + [Fact] public void EOFPacket_ShouldNotIndicateAuthenticationSuccess() { From 4eacadc43a1fc50c9ce4443df2924eb99834f8dd Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 10 Feb 2026 02:07:03 +0000 Subject: [PATCH 3/4] Expose caching_sha2 helper for tests Co-authored-by: kerryjiang <456060+kerryjiang@users.noreply.github.com> --- src/SuperSocket.MySQL/MySQLConnection.cs | 4 ++-- tests/SuperSocket.MySQL.Test/HandshakeTest.cs | 6 ++---- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/SuperSocket.MySQL/MySQLConnection.cs b/src/SuperSocket.MySQL/MySQLConnection.cs index 80f13ff..cc92a97 100644 --- a/src/SuperSocket.MySQL/MySQLConnection.cs +++ b/src/SuperSocket.MySQL/MySQLConnection.cs @@ -199,7 +199,7 @@ private byte[] GenerateNativePasswordResponse(byte[] salt) } } - private byte[] GenerateCachingSha2Response(byte[] salt) + internal byte[] GenerateCachingSha2Response(byte[] salt) { if (string.IsNullOrEmpty(_password)) return Array.Empty(); @@ -383,4 +383,4 @@ protected override void OnClosed(object sender, EventArgs e) base.OnClosed(sender, e); } } -} \ No newline at end of file +} diff --git a/tests/SuperSocket.MySQL.Test/HandshakeTest.cs b/tests/SuperSocket.MySQL.Test/HandshakeTest.cs index 6f06b07..fdaa829 100644 --- a/tests/SuperSocket.MySQL.Test/HandshakeTest.cs +++ b/tests/SuperSocket.MySQL.Test/HandshakeTest.cs @@ -1,7 +1,6 @@ using System; using System.Buffers; using System.Collections.Generic; -using System.Reflection; using System.Security.Cryptography; using System.Text; using System.Threading.Tasks; @@ -210,12 +209,11 @@ public void MySQLConnection_GenerateCachingSha2Response_ShouldMatchExpected() // Arrange const string password = "test_password"; var connection = new MySQLConnection("localhost", 3306, "user", password); + // Include a trailing null byte to validate trimming of the salt. var salt = new byte[] { 0x33, 0x21, 0x55, 0x42, 0x19, 0x76, 0xA1, 0x0B, 0x10, 0x5C, 0x2D, 0x48, 0x5A, 0x00 }; - var method = typeof(MySQLConnection).GetMethod("GenerateCachingSha2Response", BindingFlags.NonPublic | BindingFlags.Instance); - Assert.NotNull(method); // Act - var response = (byte[])method.Invoke(connection, new object[] { salt }); + var response = connection.GenerateCachingSha2Response(salt); // Assert var trimmedSaltLength = salt[^1] == 0 ? salt.Length - 1 : salt.Length; From 59edf9654e1e8d4f3ce43bb0636f87f4475f44ba Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 10 Feb 2026 02:13:55 +0000 Subject: [PATCH 4/4] Add caching_sha2 integration test Co-authored-by: kerryjiang <456060+kerryjiang@users.noreply.github.com> --- .../MySQLIntegrationTest.cs | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/tests/SuperSocket.MySQL.Test/MySQLIntegrationTest.cs b/tests/SuperSocket.MySQL.Test/MySQLIntegrationTest.cs index 59c0563..40785e4 100644 --- a/tests/SuperSocket.MySQL.Test/MySQLIntegrationTest.cs +++ b/tests/SuperSocket.MySQL.Test/MySQLIntegrationTest.cs @@ -56,6 +56,52 @@ public async Task MySQLConnection_InvalidCredentials_ShouldFailHandshake() "Connection should not be authenticated after failed handshake"); } + [Fact] + [Trait("Category", "Integration")] + public async Task MySQLConnection_CachingSha2PasswordUser_ShouldAuthenticate() + { + var userName = $"sha2_user_{Guid.NewGuid():N}"; + var password = $"sha2_pass_{Guid.NewGuid():N}"; + var host = TestConst.Host; + var adminConnection = new MySQLConnection(host, TestConst.DefaultPort, TestConst.Username, TestConst.Password); + MySQLConnection userConnection = null; + + try + { + await adminConnection.ConnectAsync(); + + var pluginResult = await adminConnection.ExecuteQueryAsync( + "SELECT PLUGIN_NAME FROM INFORMATION_SCHEMA.PLUGINS WHERE PLUGIN_NAME='caching_sha2_password' AND PLUGIN_STATUS='ACTIVE'"); + + if (!pluginResult.IsSuccess || pluginResult.RowCount == 0) + return; + + await adminConnection.ExecuteQueryAsync( + $"CREATE USER '{userName}'@'{host}' IDENTIFIED WITH caching_sha2_password BY '{password}'"); + await adminConnection.ExecuteQueryAsync($"GRANT USAGE ON *.* TO '{userName}'@'{host}'"); + + userConnection = new MySQLConnection(host, TestConst.DefaultPort, userName, password); + await userConnection.ConnectAsync(); + + Assert.True(userConnection.IsAuthenticated, + "Connection should be authenticated using caching_sha2_password."); + } + finally + { + if (userConnection != null) + { + await userConnection.DisconnectAsync(); + } + + if (adminConnection.IsAuthenticated) + { + await adminConnection.ExecuteQueryAsync($"DROP USER IF EXISTS '{userName}'@'{host}'"); + } + + await adminConnection.DisconnectAsync(); + } + } + [Fact] [Trait("Category", "Integration")] public async Task MySQLConnection_ConcurrentConnections_ShouldWork()