Skip to content

Commit ec3619e

Browse files
committed
Enable TLS validation with parsec.
Introduce new IAuthenticationPlugin3 interface and deprecate IAuthenticationPlugin2. Authentication plugins will now compute the password hash and the authentication response in one call, and the session will cache the password hash for later use. Signed-off-by: Bradley Grainger <[email protected]>
1 parent 227f5b0 commit ec3619e

File tree

9 files changed

+121
-86
lines changed

9 files changed

+121
-86
lines changed

src/MySqlConnector.Authentication.Ed25519/Chaos.NaCl/Ed25519.cs

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -34,19 +34,6 @@ public static byte[] Sign(byte[] message, byte[] expandedPrivateKey)
3434
return signature;
3535
}
3636

37-
public static byte[] ExpandedPrivateKeyFromSeed(byte[] privateKeySeed)
38-
{
39-
byte[] privateKey;
40-
byte[] publicKey;
41-
KeyPairFromSeed(out publicKey, out privateKey, privateKeySeed);
42-
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP2_1_OR_GREATER
43-
CryptographicOperations.ZeroMemory(publicKey);
44-
#else
45-
CryptoBytes.Wipe(publicKey);
46-
#endif
47-
return privateKey;
48-
}
49-
5037
public static void KeyPairFromSeed(out byte[] publicKey, out byte[] expandedPrivateKey, byte[] privateKeySeed)
5138
{
5239
if (privateKeySeed == null)

src/MySqlConnector.Authentication.Ed25519/Ed25519AuthenticationPlugin.cs

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@ namespace MySqlConnector.Authentication.Ed25519;
1010
/// Provides an implementation of the <c>client_ed25519</c> authentication plugin for MariaDB.
1111
/// </summary>
1212
/// <remarks>See <a href="https://mariadb.com/kb/en/library/authentication-plugin-ed25519/">Authentication Plugin - ed25519</a>.</remarks>
13-
public sealed class Ed25519AuthenticationPlugin : IAuthenticationPlugin2
13+
#pragma warning disable CS0618 // Type or member is obsolete
14+
public sealed class Ed25519AuthenticationPlugin : IAuthenticationPlugin3, IAuthenticationPlugin2
15+
#pragma warning restore CS0618 // Type or member is obsolete
1416
{
1517
/// <summary>
1618
/// Registers the Ed25519 authentication plugin with MySqlConnector. You must call this method once before
@@ -32,7 +34,7 @@ public static void Install()
3234
/// </summary>
3335
public byte[] CreateResponse(string password, ReadOnlySpan<byte> authenticationData)
3436
{
35-
CreateResponseAndHash(password, authenticationData, out _, out var authenticationResponse);
37+
CreateResponseAndPasswordHash(password, authenticationData, out var authenticationResponse, out _);
3638
return authenticationResponse;
3739
}
3840

@@ -41,11 +43,20 @@ public byte[] CreateResponse(string password, ReadOnlySpan<byte> authenticationD
4143
/// </summary>
4244
public byte[] CreatePasswordHash(string password, ReadOnlySpan<byte> authenticationData)
4345
{
44-
CreateResponseAndHash(password, authenticationData, out var passwordHash, out _);
46+
CreateResponseAndPasswordHash(password, authenticationData, out _, out var passwordHash);
4547
return passwordHash;
4648
}
4749

48-
private static void CreateResponseAndHash(string password, ReadOnlySpan<byte> authenticationData, out byte[] passwordHash, out byte[] authenticationResponse)
50+
/// <summary>
51+
/// Creates the authentication response and hashes the client's password (e.g., for TLS certificate fingerprint verification).
52+
/// </summary>
53+
/// <param name="password">The client's password.</param>
54+
/// <param name="authenticationData">The authentication data supplied by the server; this is the <code>auth method data</code>
55+
/// from the <a href="https://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::AuthSwitchRequest">Authentication
56+
/// Method Switch Request Packet</a>.</param>
57+
/// <param name="authenticationResponse">The authentication response.</param>
58+
/// <param name="passwordHash">The authentication-method-specific hash of the client's password.</param>
59+
public void CreateResponseAndPasswordHash(string password, ReadOnlySpan<byte> authenticationData, out byte[] authenticationResponse, out byte[] passwordHash)
4960
{
5061
// Java reference: https://github.com/MariaDB/mariadb-connector-j/blob/master/src/main/java/org/mariadb/jdbc/internal/com/send/authentication/Ed25519PasswordPlugin.java
5162
// C reference: https://github.com/MariaDB/server/blob/592fe954ef82be1bc08b29a8e54f7729eb1e1343/plugin/auth_ed25519/ref10/sign.c#L7

src/MySqlConnector.Authentication.Ed25519/ParsecAuthenticationPlugin.cs

Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ namespace MySqlConnector.Authentication.Ed25519;
88
/// <summary>
99
/// Provides an implementation of the Parsec authentication plugin for MariaDB.
1010
/// </summary>
11-
public sealed class ParsecAuthenticationPlugin : IAuthenticationPlugin
11+
public sealed class ParsecAuthenticationPlugin : IAuthenticationPlugin3
1212
{
1313
/// <summary>
1414
/// Registers the Parsec authentication plugin with MySqlConnector. You must call this method once before
@@ -29,6 +29,15 @@ public static void Install()
2929
/// Creates the authentication response.
3030
/// </summary>
3131
public byte[] CreateResponse(string password, ReadOnlySpan<byte> authenticationData)
32+
{
33+
CreateResponseAndPasswordHash(password, authenticationData, out var response, out _);
34+
return response;
35+
}
36+
37+
/// <summary>
38+
/// Creates the authentication response.
39+
/// </summary>
40+
public void CreateResponseAndPasswordHash(string password, ReadOnlySpan<byte> authenticationData, out byte[] authenticationResponse, out byte[] passwordHash)
3241
{
3342
// first 32 bytes are server scramble
3443
var serverScramble = authenticationData.Slice(0, 32);
@@ -54,32 +63,37 @@ public byte[] CreateResponse(string password, ReadOnlySpan<byte> authenticationD
5463
var salt = extendedSalt.Slice(2);
5564

5665
// derive private key using PBKDF2-SHA512
57-
byte[] privateKey;
66+
byte[] privateKeySeed;
5867
#if NET6_0_OR_GREATER
59-
privateKey = Rfc2898DeriveBytes.Pbkdf2(Encoding.UTF8.GetBytes(password), salt, iterationCount, HashAlgorithmName.SHA512, 32);
68+
privateKeySeed = Rfc2898DeriveBytes.Pbkdf2(Encoding.UTF8.GetBytes(password), salt, iterationCount, HashAlgorithmName.SHA512, 32);
6069
#elif NET472_OR_GREATER || NETSTANDARD2_1_OR_GREATER
6170
using (var pbkdf2 = new Rfc2898DeriveBytes(Encoding.UTF8.GetBytes(password), salt.ToArray(), iterationCount, HashAlgorithmName.SHA512))
62-
privateKey = pbkdf2.GetBytes(32);
71+
privateKeySeed = pbkdf2.GetBytes(32);
6372
#else
64-
privateKey = Microsoft.AspNetCore.Cryptography.KeyDerivation.KeyDerivation.Pbkdf2(
73+
privateKeySeed = Microsoft.AspNetCore.Cryptography.KeyDerivation.KeyDerivation.Pbkdf2(
6574
password, salt.ToArray(), Microsoft.AspNetCore.Cryptography.KeyDerivation.KeyDerivationPrf.HMACSHA512,
6675
iterationCount, numBytesRequested: 32);
6776
#endif
68-
var expandedPrivateKey = Chaos.NaCl.Ed25519.ExpandedPrivateKeyFromSeed(privateKey);
77+
Chaos.NaCl.Ed25519.KeyPairFromSeed(out var publicKey, out var privateKey, privateKeySeed);
6978

7079
// generate Ed25519 keypair and sign concatenated scrambles
7180
var message = new byte[serverScramble.Length + clientScramble.Length];
7281
serverScramble.CopyTo(message);
7382
clientScramble.CopyTo(message.AsSpan(serverScramble.Length));
7483

75-
var signature = Chaos.NaCl.Ed25519.Sign(message, expandedPrivateKey);
84+
var signature = Chaos.NaCl.Ed25519.Sign(message, privateKey);
85+
86+
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP2_1_OR_GREATER
87+
CryptographicOperations.ZeroMemory(privateKey);
88+
#endif
7689

7790
// return client scramble followed by signature
78-
var response = new byte[clientScramble.Length + signature.Length];
79-
clientScramble.CopyTo(response.AsSpan());
80-
signature.CopyTo(response.AsSpan(clientScramble.Length));
81-
82-
return response;
91+
authenticationResponse = new byte[clientScramble.Length + signature.Length];
92+
clientScramble.CopyTo(authenticationResponse.AsSpan());
93+
signature.CopyTo(authenticationResponse.AsSpan(clientScramble.Length));
94+
95+
// "password hash" for parsec is the extended salt followed by the public key
96+
passwordHash = [(byte) 'P', (byte) iterationCount, .. salt, .. publicKey];
8397
}
8498

8599
private ParsecAuthenticationPlugin()

src/MySqlConnector.Authentication.Ed25519/docs/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ This package implements the following authentication plugins for MariaDB:
77

88
## How to Use
99

10-
Call either the following methods from your application startup code to enable the corresponding authentication plugin:
10+
Call either of the following methods from your application startup code to enable the corresponding authentication plugin:
1111

1212
* `Ed25519AuthenticationPlugin.Install()`
1313
* `ParsecAuthenticationPlugin.Install()`

src/MySqlConnector/Authentication/IAuthenticationPlugin.cs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ public interface IAuthenticationPlugin
2424
/// <summary>
2525
/// <see cref="IAuthenticationPlugin2"/> is an extension to <see cref="IAuthenticationPlugin"/> that returns a hash of the client's password.
2626
/// </summary>
27+
[Obsolete("Use IAuthenticationPlugin3 instead.")]
2728
public interface IAuthenticationPlugin2 : IAuthenticationPlugin
2829
{
2930
/// <summary>
@@ -36,3 +37,21 @@ public interface IAuthenticationPlugin2 : IAuthenticationPlugin
3637
/// <returns>The authentication-method-specific hash of the client's password.</returns>
3738
byte[] CreatePasswordHash(string password, ReadOnlySpan<byte> authenticationData);
3839
}
40+
41+
/// <summary>
42+
/// <see cref="IAuthenticationPlugin3"/> is an extension to <see cref="IAuthenticationPlugin"/> that also returns a hash of the client's password.
43+
/// </summary>
44+
/// <remarks>If an authentication plugin supports this interface, the base <see cref="IAuthenticationPlugin.CreateResponse(string, ReadOnlySpan{byte})"/> method will not be called.</remarks>
45+
public interface IAuthenticationPlugin3 : IAuthenticationPlugin
46+
{
47+
/// <summary>
48+
/// Creates the authentication response and hashes the client's password (e.g., for TLS certificate fingerprint verification).
49+
/// </summary>
50+
/// <param name="password">The client's password.</param>
51+
/// <param name="authenticationData">The authentication data supplied by the server; this is the <code>auth method data</code>
52+
/// from the <a href="https://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::AuthSwitchRequest">Authentication
53+
/// Method Switch Request Packet</a>.</param>
54+
/// <param name="authenticationResponse">The authentication response.</param>
55+
/// <param name="passwordHash">The authentication-method-specific hash of the client's password.</param>
56+
void CreateResponseAndPasswordHash(string password, ReadOnlySpan<byte> authenticationData, out byte[] authenticationResponse, out byte[] passwordHash);
57+
}

0 commit comments

Comments
 (0)