Skip to content

Commit 22d78d6

Browse files
Copilotkerryjiang
andauthored
Add caching_sha2_password auth response unit and integration coverage (#6)
* Initial plan * Add caching_sha2_password unit test Co-authored-by: kerryjiang <456060+kerryjiang@users.noreply.github.com> * Expose caching_sha2 helper for tests Co-authored-by: kerryjiang <456060+kerryjiang@users.noreply.github.com> * Add caching_sha2 integration test Co-authored-by: kerryjiang <456060+kerryjiang@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: kerryjiang <456060+kerryjiang@users.noreply.github.com>
1 parent f214991 commit 22d78d6

3 files changed

Lines changed: 83 additions & 2 deletions

File tree

src/SuperSocket.MySQL/MySQLConnection.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,7 @@ private byte[] GenerateNativePasswordResponse(byte[] salt)
199199
}
200200
}
201201

202-
private byte[] GenerateCachingSha2Response(byte[] salt)
202+
internal byte[] GenerateCachingSha2Response(byte[] salt)
203203
{
204204
if (string.IsNullOrEmpty(_password))
205205
return Array.Empty<byte>();
@@ -383,4 +383,4 @@ protected override void OnClosed(object sender, EventArgs e)
383383
base.OnClosed(sender, e);
384384
}
385385
}
386-
}
386+
}

tests/SuperSocket.MySQL.Test/HandshakeTest.cs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System;
22
using System.Buffers;
33
using System.Collections.Generic;
4+
using System.Security.Cryptography;
45
using System.Text;
56
using System.Threading.Tasks;
67
using Xunit;
@@ -202,6 +203,40 @@ public void MySQLConnection_GenerateAuthResponse_ShouldHandleDifferentPasswords(
202203
Assert.NotNull(connection);
203204
}
204205

206+
[Fact]
207+
public void MySQLConnection_GenerateCachingSha2Response_ShouldMatchExpected()
208+
{
209+
// Arrange
210+
const string password = "test_password";
211+
var connection = new MySQLConnection("localhost", 3306, "user", password);
212+
// Include a trailing null byte to validate trimming of the salt.
213+
var salt = new byte[] { 0x33, 0x21, 0x55, 0x42, 0x19, 0x76, 0xA1, 0x0B, 0x10, 0x5C, 0x2D, 0x48, 0x5A, 0x00 };
214+
215+
// Act
216+
var response = connection.GenerateCachingSha2Response(salt);
217+
218+
// Assert
219+
var trimmedSaltLength = salt[^1] == 0 ? salt.Length - 1 : salt.Length;
220+
var trimmedSalt = new byte[trimmedSaltLength];
221+
Array.Copy(salt, trimmedSalt, trimmedSaltLength);
222+
223+
using var sha256 = SHA256.Create();
224+
var passwordBytes = Encoding.UTF8.GetBytes(password);
225+
var sha256Password = sha256.ComputeHash(passwordBytes);
226+
var sha256Sha256Password = sha256.ComputeHash(sha256Password);
227+
var hashAndSalt = new byte[sha256Sha256Password.Length + trimmedSalt.Length];
228+
Array.Copy(sha256Sha256Password, 0, hashAndSalt, 0, sha256Sha256Password.Length);
229+
Array.Copy(trimmedSalt, 0, hashAndSalt, sha256Sha256Password.Length, trimmedSalt.Length);
230+
var sha256Combined = sha256.ComputeHash(hashAndSalt);
231+
var expected = new byte[sha256Password.Length];
232+
for (int i = 0; i < expected.Length; i++)
233+
{
234+
expected[i] = (byte)(sha256Password[i] ^ sha256Combined[i]);
235+
}
236+
237+
Assert.Equal(expected, response);
238+
}
239+
205240
[Fact]
206241
public void EOFPacket_ShouldNotIndicateAuthenticationSuccess()
207242
{

tests/SuperSocket.MySQL.Test/MySQLIntegrationTest.cs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,52 @@ public async Task MySQLConnection_InvalidCredentials_ShouldFailHandshake()
5656
"Connection should not be authenticated after failed handshake");
5757
}
5858

59+
[Fact]
60+
[Trait("Category", "Integration")]
61+
public async Task MySQLConnection_CachingSha2PasswordUser_ShouldAuthenticate()
62+
{
63+
var userName = $"sha2_user_{Guid.NewGuid():N}";
64+
var password = $"sha2_pass_{Guid.NewGuid():N}";
65+
var host = TestConst.Host;
66+
var adminConnection = new MySQLConnection(host, TestConst.DefaultPort, TestConst.Username, TestConst.Password);
67+
MySQLConnection userConnection = null;
68+
69+
try
70+
{
71+
await adminConnection.ConnectAsync();
72+
73+
var pluginResult = await adminConnection.ExecuteQueryAsync(
74+
"SELECT PLUGIN_NAME FROM INFORMATION_SCHEMA.PLUGINS WHERE PLUGIN_NAME='caching_sha2_password' AND PLUGIN_STATUS='ACTIVE'");
75+
76+
if (!pluginResult.IsSuccess || pluginResult.RowCount == 0)
77+
return;
78+
79+
await adminConnection.ExecuteQueryAsync(
80+
$"CREATE USER '{userName}'@'{host}' IDENTIFIED WITH caching_sha2_password BY '{password}'");
81+
await adminConnection.ExecuteQueryAsync($"GRANT USAGE ON *.* TO '{userName}'@'{host}'");
82+
83+
userConnection = new MySQLConnection(host, TestConst.DefaultPort, userName, password);
84+
await userConnection.ConnectAsync();
85+
86+
Assert.True(userConnection.IsAuthenticated,
87+
"Connection should be authenticated using caching_sha2_password.");
88+
}
89+
finally
90+
{
91+
if (userConnection != null)
92+
{
93+
await userConnection.DisconnectAsync();
94+
}
95+
96+
if (adminConnection.IsAuthenticated)
97+
{
98+
await adminConnection.ExecuteQueryAsync($"DROP USER IF EXISTS '{userName}'@'{host}'");
99+
}
100+
101+
await adminConnection.DisconnectAsync();
102+
}
103+
}
104+
59105
[Fact]
60106
[Trait("Category", "Integration")]
61107
public async Task MySQLConnection_ConcurrentConnections_ShouldWork()

0 commit comments

Comments
 (0)