1
1
using System ;
2
+ using System . Buffers . Binary ;
3
+ using System . Security . Cryptography ;
4
+ using System . Text ;
2
5
using System . Threading ;
3
6
4
7
namespace MySqlConnector . Authentication . Ed25519 ;
@@ -21,24 +24,86 @@ public static void Install()
21
24
/// <summary>
22
25
/// Gets the authentication plugin name.
23
26
/// </summary>
24
- public string Name => "client_parsec" ; // TODO: Update with correct name from parsec-spec.txt
27
+ public string Name => "client_parsec" ;
25
28
26
29
/// <summary>
27
30
/// Creates the authentication response.
28
31
/// </summary>
29
32
public byte [ ] CreateResponse ( string password , ReadOnlySpan < byte > authenticationData )
30
33
{
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 ;
33
74
}
34
75
35
76
/// <summary>
36
77
/// Creates the Parsec password hash.
37
78
/// </summary>
38
79
public byte [ ] CreatePasswordHash ( string password , ReadOnlySpan < byte > authenticationData )
39
80
{
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 ) ;
42
107
}
43
108
44
109
private ParsecAuthenticationPlugin ( )
0 commit comments