Skip to content

Commit 1d8f306

Browse files
committed
Implement PARSEC authentication protocol in ParsecAuthenticationPlugin.
1 parent 907d864 commit 1d8f306

File tree

1 file changed

+70
-5
lines changed

1 file changed

+70
-5
lines changed

src/MySqlConnector.Authentication.Ed25519/ParsecAuthenticationPlugin.cs

Lines changed: 70 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
using System;
2+
using System.Buffers.Binary;
3+
using System.Security.Cryptography;
4+
using System.Text;
25
using System.Threading;
36

47
namespace MySqlConnector.Authentication.Ed25519;
@@ -21,24 +24,86 @@ public static void Install()
2124
/// <summary>
2225
/// Gets the authentication plugin name.
2326
/// </summary>
24-
public string Name => "client_parsec"; // TODO: Update with correct name from parsec-spec.txt
27+
public string Name => "client_parsec";
2528

2629
/// <summary>
2730
/// Creates the authentication response.
2831
/// </summary>
2932
public byte[] CreateResponse(string password, ReadOnlySpan<byte> authenticationData)
3033
{
31-
// TODO: Implement Parsec authentication protocol
32-
throw new NotImplementedException();
34+
// First 32 bytes are server scramble
35+
var serverScramble = authenticationData[..32];
36+
37+
// Generate client scramble
38+
var clientScramble = new byte[32];
39+
RandomNumberGenerator.Fill(clientScramble);
40+
41+
// Parse extended salt from remaining auth data
42+
var extSalt = Encoding.UTF8.GetString(authenticationData[32..]);
43+
var parts = extSalt.Split(':');
44+
45+
// Parse iteration count (P0 = 1024, P1 = 2048, etc)
46+
var iterationCount = 1024 << (parts[0][1] - '0');
47+
var salt = Convert.FromBase64String(parts[1]);
48+
49+
// Derive private key using PBKDF2-SHA512
50+
var privateKey = new byte[32];
51+
using (var pbkdf2 = new Rfc2898DeriveBytes(
52+
password,
53+
salt,
54+
iterationCount,
55+
HashAlgorithmName.SHA512))
56+
{
57+
privateKey = pbkdf2.GetBytes(32);
58+
}
59+
60+
// Generate Ed25519 keypair and sign concatenated scrambles
61+
var keyPair = Ed25519.GenerateKeyPair(privateKey);
62+
var message = new byte[serverScramble.Length + clientScramble.Length];
63+
serverScramble.CopyTo(message);
64+
clientScramble.CopyTo(message.AsSpan(serverScramble.Length));
65+
66+
var signature = Ed25519.Sign(message, keyPair.PrivateKey);
67+
68+
// Return client scramble followed by signature
69+
var response = new byte[clientScramble.Length + signature.Length];
70+
clientScramble.CopyTo(response);
71+
signature.CopyTo(response.AsSpan(clientScramble.Length));
72+
73+
return response;
3374
}
3475

3576
/// <summary>
3677
/// Creates the Parsec password hash.
3778
/// </summary>
3879
public byte[] CreatePasswordHash(string password, ReadOnlySpan<byte> authenticationData)
3980
{
40-
// TODO: Implement Parsec password hashing
41-
throw new NotImplementedException();
81+
// Parse extended salt from auth data
82+
var extSalt = Encoding.UTF8.GetString(authenticationData);
83+
var parts = extSalt.Split(':');
84+
85+
// Parse iteration count (P0 = 1024, P1 = 2048, etc)
86+
var iterationCount = 1024 << (parts[0][1] - '0');
87+
var salt = Convert.FromBase64String(parts[1]);
88+
89+
// Derive private key using PBKDF2-SHA512
90+
var privateKey = new byte[32];
91+
using (var pbkdf2 = new Rfc2898DeriveBytes(
92+
password,
93+
salt,
94+
iterationCount,
95+
HashAlgorithmName.SHA512))
96+
{
97+
privateKey = pbkdf2.GetBytes(32);
98+
}
99+
100+
// Generate Ed25519 keypair and get public key
101+
var keyPair = Ed25519.GenerateKeyPair(privateKey);
102+
var publicKey = keyPair.PublicKey;
103+
104+
// Format hash string: P<iter>:<salt-b64>:<pubkey-b64>
105+
var hashString = $"{parts[0]}:{parts[1]}:{Convert.ToBase64String(publicKey)}";
106+
return Encoding.UTF8.GetBytes(hashString);
42107
}
43108

44109
private ParsecAuthenticationPlugin()

0 commit comments

Comments
 (0)