Skip to content

Commit 4a99d6a

Browse files
committed
Add IAuthenticationMethod2 interface to avoid breaking change.
Refactor duplicated code in Ed25519AuthenticationPlugin. Signed-off-by: Bradley Grainger <[email protected]>
1 parent 43ee64d commit 4a99d6a

File tree

3 files changed

+42
-41
lines changed

3 files changed

+42
-41
lines changed

src/MySqlConnector.Authentication.Ed25519/Ed25519AuthenticationPlugin.cs

Lines changed: 23 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ 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 : IAuthenticationPlugin
13+
public sealed class Ed25519AuthenticationPlugin : IAuthenticationPlugin2
1414
{
1515
/// <summary>
1616
/// Registers the Ed25519 authentication plugin with MySqlConnector. You must call this method once before
@@ -31,6 +31,24 @@ public static void Install()
3131
/// Creates the authentication response.
3232
/// </summary>
3333
public byte[] CreateResponse(string password, ReadOnlySpan<byte> authenticationData)
34+
{
35+
CreateResponseAndHash(password, authenticationData, out _, out var authenticationResponse);
36+
return authenticationResponse;
37+
}
38+
39+
/// <summary>
40+
/// Creates the Ed25519 password hash.
41+
/// </summary>
42+
public byte[] CreatePasswordHash(string password, ReadOnlySpan<byte> authenticationData)
43+
{
44+
CreateResponseAndHash(password, authenticationData, out var passwordHash, out _);
45+
return passwordHash;
46+
}
47+
48+
/// <summary>
49+
/// Creates the authentication response.
50+
/// </summary>
51+
private static void CreateResponseAndHash(string password, ReadOnlySpan<byte> authenticationData, out byte[] passwordHash, out byte[] authenticationResponse)
3452
{
3553
// Java reference: https://github.com/MariaDB/mariadb-connector-j/blob/master/src/main/java/org/mariadb/jdbc/internal/com/send/authentication/Ed25519PasswordPlugin.java
3654
// C reference: https://github.com/MariaDB/server/blob/592fe954ef82be1bc08b29a8e54f7729eb1e1343/plugin/auth_ed25519/ref10/sign.c#L7
@@ -109,6 +127,9 @@ public byte[] CreateResponse(string password, ReadOnlySpan<byte> authenticationD
109127
GroupOperations.ge_scalarmult_base(out var A, az, 0);
110128
GroupOperations.ge_p3_tobytes(sm, 32, ref A);
111129

130+
passwordHash = new byte[32];
131+
Array.Copy(sm, 32, passwordHash, 0, 32);
132+
112133
/*** Java
113134
nonce = scalar.reduce(nonce);
114135
GroupElement elementRvalue = spec.getB().scalarMultiply(nonce);
@@ -152,30 +173,7 @@ public byte[] CreateResponse(string password, ReadOnlySpan<byte> authenticationD
152173

153174
var result = new byte[64];
154175
Buffer.BlockCopy(sm, 0, result, 0, result.Length);
155-
return result;
156-
}
157-
158-
/// <summary>
159-
/// Creates the ed25519 password hash.
160-
/// </summary>
161-
public byte[] CreatePasswordHash(string password, ReadOnlySpan<byte> authenticationData)
162-
{
163-
byte[] passwordBytes = Encoding.UTF8.GetBytes(password);
164-
using var sha512 = SHA512.Create();
165-
byte[] az = sha512.ComputeHash(passwordBytes);
166-
ScalarOperations.sc_clamp(az, 0);
167-
168-
byte[] sm = new byte[64 + authenticationData.Length];
169-
authenticationData.CopyTo(sm.AsSpan().Slice(64));
170-
Buffer.BlockCopy(az, 32, sm, 32, 32);
171-
sha512.ComputeHash(sm, 32, authenticationData.Length + 32);
172-
173-
GroupOperations.ge_scalarmult_base(out var A, az, 0);
174-
GroupOperations.ge_p3_tobytes(sm, 32, ref A);
175-
176-
byte[] res = new byte[32];
177-
Array.Copy(sm, 32, res, 0, 32);
178-
return res;
176+
authenticationResponse = result;
179177
}
180178

181179
private Ed25519AuthenticationPlugin()

src/MySqlConnector/Authentication/IAuthenticationPlugin.cs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,20 @@ public interface IAuthenticationPlugin
1919
/// Method Switch Request Packet</a>.</param>
2020
/// <returns>The authentication response.</returns>
2121
byte[] CreateResponse(string password, ReadOnlySpan<byte> authenticationData);
22+
}
2223

24+
/// <summary>
25+
/// <see cref="IAuthenticationPlugin2"/> is an extension to <see cref="IAuthenticationPlugin"/> that returns a hash of the client's password.
26+
/// </summary>
27+
public interface IAuthenticationPlugin2 : IAuthenticationPlugin
28+
{
2329
/// <summary>
24-
/// create password hash for fingerprint verification
30+
/// Hashes the client's password (e.g., for TLS certificate fingerprint verification).
2531
/// </summary>
2632
/// <param name="password">The client's password.</param>
2733
/// <param name="authenticationData">The authentication data supplied by the server; this is the <code>auth method data</code>
2834
/// from the <a href="https://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::AuthSwitchRequest">Authentication
2935
/// Method Switch Request Packet</a>.</param>
30-
/// <returns>password hash</returns>
36+
/// <returns>The authentication-method-specific hash of the client's password.</returns>
3137
byte[] CreatePasswordHash(string password, ReadOnlySpan<byte> authenticationData);
3238
}

src/MySqlConnector/Core/ServerSession.cs

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,6 @@
1818
using MySqlConnector.Protocol.Payloads;
1919
using MySqlConnector.Protocol.Serialization;
2020
using MySqlConnector.Utilities;
21-
#if NET5_0_OR_GREATER
22-
using System.Runtime.CompilerServices;
23-
#endif
2421

2522
namespace MySqlConnector.Core;
2623

@@ -539,8 +536,7 @@ public async Task DisposeAsync(IOBehavior ioBehavior, CancellationToken cancella
539536
// * auth plugin is MitM-proof and check SHA2(user's hashed password, scramble, certificate fingerprint)
540537
if (cs.ConnectionProtocol != MySqlConnectionProtocol.UnixSocket)
541538
{
542-
if (string.IsNullOrEmpty(password) ||
543-
!ValidateFingerPrint(ok.StatusInfo, initialHandshake.AuthPluginData, password!))
539+
if (string.IsNullOrEmpty(password) || !ValidateFingerprint(ok.StatusInfo, initialHandshake.AuthPluginData.AsSpan(0, 20), password!))
544540
{
545541
// fingerprint validation fail.
546542
// now throwing SSL exception depending on m_rcbPolicyErrors
@@ -615,15 +611,16 @@ public async Task DisposeAsync(IOBehavior ioBehavior, CancellationToken cancella
615611
/// <param name="challenge">initial seed</param>
616612
/// <param name="password">password</param>
617613
/// <returns>true if validated</returns>
618-
private bool ValidateFingerPrint(byte[]? validationHash, ReadOnlySpan<byte> challenge, string password)
614+
private bool ValidateFingerprint(byte[]? validationHash, ReadOnlySpan<byte> challenge, string password)
619615
{
620-
if (validationHash is null || validationHash.Length == 0) return false;
616+
if (validationHash?.Length != 65)
617+
return false;
621618

622619
// ensure using SHA256 encryption
623620
if (validationHash[0] != 0x01)
624621
throw new FormatException($"Unexpected validation hash format. expected 0x01 but got 0x{validationHash[0]:X2}");
625622

626-
byte[] passwordHashResult;
623+
byte[]? passwordHashResult = null;
627624
switch (m_pluginName)
628625
{
629626
case "mysql_native_password":
@@ -632,17 +629,17 @@ private bool ValidateFingerPrint(byte[]? validationHash, ReadOnlySpan<byte> chal
632629

633630
case "client_ed25519":
634631
AuthenticationPlugins.TryGetPlugin("client_ed25519", out var ed25519Plugin);
635-
passwordHashResult = ed25519Plugin!.CreatePasswordHash(password, challenge);
632+
if (ed25519Plugin is IAuthenticationPlugin2 plugin2)
633+
passwordHashResult = plugin2!.CreatePasswordHash(password, challenge);
636634
break;
637-
638-
default:
639-
return false;
640635
}
636+
if (passwordHashResult is null)
637+
return false;
641638

642-
Span<byte> combined = stackalloc byte[32 + (challenge.Length - 1) + passwordHashResult.Length];
639+
Span<byte> combined = stackalloc byte[32 + challenge.Length + passwordHashResult.Length];
643640
passwordHashResult.CopyTo(combined);
644641
challenge.CopyTo(combined[passwordHashResult.Length..]);
645-
m_sha2Thumbprint!.CopyTo(combined[(passwordHashResult.Length + challenge.Length - 1)..]);
642+
m_sha2Thumbprint!.CopyTo(combined[(passwordHashResult.Length + challenge.Length)..]);
646643

647644
byte[] hashBytes;
648645
#if NET5_0_OR_GREATER

0 commit comments

Comments
 (0)