Skip to content

Commit

Permalink
WL#15484 [Re-implementation of Asynchronous functions]
Browse files Browse the repository at this point in the history
This patch re implemented the asynchronous methods for Classic protocol.

Change-Id: I51c4366949bce532bd04cc384aadf330687c75f8
  • Loading branch information
davaldezr committed Jan 11, 2023
1 parent 808cd1b commit 291c2f3
Show file tree
Hide file tree
Showing 79 changed files with 2,768 additions and 2,855 deletions.
1 change: 1 addition & 0 deletions CHANGES
@@ -1,5 +1,6 @@
8.0.33
- Added support for OCI ephemeral key-based authentication (WL15489).
- Re implemented the asynchronous methods for the Classic protocol (WL15484).


8.0.32
Expand Down
Expand Up @@ -29,6 +29,7 @@
using System;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;

namespace MySql.Data.MySqlClient.Authentication
{
Expand All @@ -53,7 +54,7 @@ protected override void SetAuthData(byte[] data)
else base.SetAuthData(data);
}

protected override byte[] MoreData(byte[] data)
protected override Task<byte[]> MoreDataAsync(byte[] data, bool execAsync)
{
rawPubkey = data;

Expand All @@ -63,16 +64,16 @@ protected override byte[] MoreData(byte[] data)
byte[] scramble = GetPassword() as byte[];
byte[] buffer = new byte[scramble.Length - 1];
Array.Copy(scramble, 1, buffer, 0, scramble.Length - 1);
return buffer;
return Task.FromResult<byte[]>(buffer);
}
// Fast authentication.
else if (data[0] == 3)
{
_authStage = AuthStage.FAST_AUTH;
return null;
return Task.FromResult<byte[]>(null);
}
else
return GeneratePassword() as byte[];
return Task.FromResult<byte[]>(GeneratePassword());
}

/// <summary>
Expand Down
6 changes: 4 additions & 2 deletions MySQL.Data/src/Authentication/ClearPasswordPlugin.cs
Expand Up @@ -26,6 +26,8 @@
// along with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA

using System.Threading.Tasks;

namespace MySql.Data.MySqlClient.Authentication
{
/// <summary>
Expand All @@ -35,14 +37,14 @@ public class MySqlClearPasswordPlugin : MySqlAuthenticationPlugin
{
private byte[] passBytes;
public override string PluginName => "mysql_clear_password";
protected override byte[] MoreData(byte[] data)
protected override Task<byte[]> MoreDataAsync(byte[] data, bool execAsync)
{
if ((Settings.SslMode != MySqlSslMode.Disabled &&
Settings.ConnectionProtocol != MySqlConnectionProtocol.UnixSocket) ||
(Settings.ConnectionProtocol == MySqlConnectionProtocol.UnixSocket))
{
passBytes = System.Text.Encoding.UTF8.GetBytes(GetMFAPassword());
return passBytes;
return Task.FromResult<byte[]>(passBytes);
}
else
{
Expand Down
34 changes: 23 additions & 11 deletions MySQL.Data/src/Authentication/FidoAuthenticationPlugin.cs
Expand Up @@ -30,6 +30,7 @@
using System;
using System.IO;
using System.Text;
using System.Threading.Tasks;

namespace MySql.Data.MySqlClient.Authentication
{
Expand All @@ -54,9 +55,10 @@ protected override void SetAuthData(byte[] data)
throw new MySqlException("FIDO registration missing.");
}

protected override byte[] MoreData(byte[] data)
protected override async Task<byte[]> MoreDataAsync(byte[] data, bool execAsync)
{
return SignChallenge();
Tuple<int, int, byte[], int, byte[]> response = BuildFidoAssertionStatement();
return await SignChallengeAsync(response, execAsync).ConfigureAwait(false);
}

/// <summary>
Expand Down Expand Up @@ -88,9 +90,23 @@ private void ParseChallenge(byte[] challenge)
}

/// <summary>
/// Method to obtains an assertion from a FIDO device.
/// Signs the challenge obtained from the FIDO device and returns it to the server.
/// </summary>
private byte[] SignChallenge()
private async Task<byte[]> SignChallengeAsync(Tuple<int, int, byte[], int, byte[]> response, bool execAsync)
{
var challenge = new MySqlPacket(new MemoryStream(response.Item1));
await challenge.WriteLengthAsync(response.Item2, execAsync).ConfigureAwait(false);
await challenge.WriteAsync(response.Item3, execAsync).ConfigureAwait(false);
await challenge.WriteLengthAsync(response.Item4, execAsync).ConfigureAwait(false);
await challenge.WriteAsync(response.Item5, execAsync).ConfigureAwait(false);

return challenge.Buffer;
}

/// <summary>
/// Method to obtain an assertion from a FIDO device.
/// </summary>
private Tuple<int, int, byte[], int, byte[]> BuildFidoAssertionStatement()
{
string devicePath;

Expand Down Expand Up @@ -118,13 +134,9 @@ private byte[] SignChallenge()
int responseLength = fidoAssertionStatement.SignatureLen + fidoAssertionStatement.AuthDataLen +
GetLengthSize((ulong)fidoAssertionStatement.SignatureLen) + GetLengthSize((ulong)fidoAssertionStatement.AuthDataLen);

var response = new MySqlPacket(new MemoryStream(responseLength));
response.WriteLength(fidoAssertionStatement.AuthDataLen);
response.Write(fidoAssertionStatement.AuthData.ToArray());
response.WriteLength(fidoAssertionStatement.SignatureLen);
response.Write(fidoAssertionStatement.Signature.ToArray());

return response.Buffer;
return new Tuple<int, int, byte[], int, byte[]>(responseLength,
fidoAssertionStatement.AuthDataLen, fidoAssertionStatement.AuthData.ToArray(),
fidoAssertionStatement.SignatureLen, fidoAssertionStatement.Signature.ToArray());
}
}
}
Expand Down
7 changes: 4 additions & 3 deletions MySQL.Data/src/Authentication/KerberosAuthenticationPlugin.cs
Expand Up @@ -30,6 +30,7 @@
using MySql.Data.Authentication.SSPI;
using System;
using System.Text;
using System.Threading.Tasks;

namespace MySql.Data.MySqlClient.Authentication
{
Expand Down Expand Up @@ -99,12 +100,12 @@ public override string GetUsername()
return posAt < 0 ? Username : Username.Substring(0, posAt);
}

protected override byte[] MoreData(byte[] data)
protected override Task<byte[]> MoreDataAsync(byte[] data, bool execAsync)
{
if (Settings.KerberosAuthMode == KerberosAuthMode.GSSAPI)
return GssapiMode(data);
return Task.FromResult<byte[]>(GssapiMode(data));
else
return SspiMode(data);
return Task.FromResult<byte[]>(SspiMode(data));
}

private byte[] SspiMode(byte[] data)
Expand Down
79 changes: 40 additions & 39 deletions MySQL.Data/src/Authentication/MySQLAuthenticationPlugin.cs
Expand Up @@ -29,6 +29,7 @@
using System;
using System.Diagnostics;
using System.Text;
using System.Threading.Tasks;

namespace MySql.Data.MySqlClient.Authentication
{
Expand Down Expand Up @@ -61,11 +62,11 @@ public abstract class MySqlAuthenticationPlugin
/// <param name="driver"></param>
/// <param name="authData"></param>
/// <returns></returns>
internal static MySqlAuthenticationPlugin GetPlugin(string method, NativeDriver driver, byte[] authData, int mfaIteration = 1)
internal static async Task<MySqlAuthenticationPlugin> GetPluginAsync(string method, NativeDriver driver, byte[] authData, bool execAsync, int mfaIteration = 1)
{
if (method == "mysql_old_password")
{
driver.Close(true);
await driver.CloseAsync(true, execAsync).ConfigureAwait(false);
throw new MySqlException(Resources.OldPasswordsNotSupported);
}
MySqlAuthenticationPlugin plugin = AuthenticationPluginManager.GetPlugin(method);
Expand Down Expand Up @@ -137,97 +138,97 @@ protected virtual void AuthenticationSuccessful()
/// <param name="data">The data returned by the server.</param>
/// <returns>The data to return to the server.</returns>
/// <remarks>This method is intended to be overriden.</remarks>
protected virtual byte[] MoreData(byte[] data)
protected virtual Task<byte[]> MoreDataAsync(byte[] data, bool execAsync)
{
return null;
return Task.FromResult<byte[]>(null);
}

internal void Authenticate(bool reset)
internal async Task AuthenticateAsync(bool reset, bool execAsync)
{
CheckConstraints();

MySqlPacket packet = _driver.Packet;

// send auth response
packet.WriteString(GetUsername());
await packet.WriteStringAsync(GetUsername(), execAsync).ConfigureAwait(false);

// now write the password
WritePassword(packet);
await WritePasswordAsync(packet, execAsync).ConfigureAwait(false);

if ((Flags & ClientFlags.CONNECT_WITH_DB) != 0 || reset)
{
if (!String.IsNullOrEmpty(Settings.Database))
packet.WriteString(Settings.Database);
await packet.WriteStringAsync(Settings.Database, execAsync).ConfigureAwait(false);
}

if (reset)
packet.WriteInteger(8, 2);
await packet.WriteIntegerAsync(8, 2, execAsync).ConfigureAwait(false);

if ((Flags & ClientFlags.PLUGIN_AUTH) != 0)
packet.WriteString(PluginName);
await packet.WriteStringAsync(PluginName, execAsync).ConfigureAwait(false);

_driver.SetConnectAttrs();
_driver.SendPacket(packet);
await _driver.SetConnectAttrsAsync(execAsync).ConfigureAwait(false);
await _driver.SendPacketAsync(packet, execAsync);

// Read server response.
packet = ReadPacket();
packet = await ReadPacketAsync(execAsync).ConfigureAwait(false);
byte[] b = packet.Buffer;

if (PluginName == "caching_sha2_password" && b[0] == 0x01)
{
// React to the authentication type set by server: FAST, FULL.
ContinueAuthentication(new byte[] { b[1] });
await ContinueAuthenticationAsync(execAsync, new byte[] { b[1] }).ConfigureAwait(false);
}

// Auth switch request Protocol::AuthSwitchRequest.
if (b[0] == 0xfe)
{
if (packet.IsLastPacket)
{
_driver.Close(true);
await _driver.CloseAsync(true, execAsync).ConfigureAwait(false);
throw new MySqlException(Resources.OldPasswordsNotSupported);
}
else
{
HandleAuthChange(packet);
await HandleAuthChangeAsync(packet, execAsync).ConfigureAwait(false);
}
}

// Auth request Protocol::AuthNextFactor.
while (packet.Buffer[0] == 0x02)
{
++_mfaIteration;
HandleMFA(packet);
await HandleMFAAsync(packet, execAsync).ConfigureAwait(false);
}

_driver.ReadOk(false);
await _driver.ReadOkAsync(false, execAsync).ConfigureAwait(false);

AuthenticationSuccessful();
}

private void WritePassword(MySqlPacket packet)
private async Task WritePasswordAsync(MySqlPacket packet, bool execAsync)
{
bool secure = (Flags & ClientFlags.SECURE_CONNECTION) != 0;
object password = GetPassword();
if (password is string)
{
if (secure)
packet.WriteLenString((string)password);
await packet.WriteLenStringAsync((string)password, execAsync).ConfigureAwait(false);
else
packet.WriteString((string)password);
await packet.WriteStringAsync((string)password, execAsync).ConfigureAwait(false);
}
else if (password == null)
packet.WriteByte(0);
else if (password is byte[])
packet.Write(password as byte[]);
await packet.WriteAsync(password as byte[], execAsync).ConfigureAwait(false);
else throw new MySqlException("Unexpected password format: " + password.GetType());
}

internal MySqlPacket ReadPacket()
internal async Task<MySqlPacket> ReadPacketAsync(bool execAsync)
{
try
{
MySqlPacket p = _driver.ReadPacket();
MySqlPacket p = await _driver.ReadPacketAsync(execAsync).ConfigureAwait(false);
return p;
}
catch (MySqlException ex)
Expand All @@ -238,61 +239,61 @@ internal MySqlPacket ReadPacket()
}
}

private void HandleMFA(MySqlPacket packet)
private async Task HandleMFAAsync(MySqlPacket packet, bool execAsync)
{
byte b = packet.ReadByte();
Debug.Assert(b == 0x02);

var nextPlugin = NextPlugin(packet);
var nextPlugin = await NextPluginAsync(packet, execAsync).ConfigureAwait(false);
nextPlugin.CheckConstraints();
nextPlugin.ContinueAuthentication();
await nextPlugin.ContinueAuthenticationAsync(execAsync).ConfigureAwait(false);
}

private void HandleAuthChange(MySqlPacket packet)
private async Task HandleAuthChangeAsync(MySqlPacket packet, bool execAsync)
{
byte b = packet.ReadByte();
Debug.Assert(b == 0xfe);

var nextPlugin = NextPlugin(packet);
var nextPlugin = await NextPluginAsync(packet, execAsync).ConfigureAwait(false);
nextPlugin.CheckConstraints();
nextPlugin.ContinueAuthentication();
await nextPlugin.ContinueAuthenticationAsync(execAsync).ConfigureAwait(false);
}

private MySqlAuthenticationPlugin NextPlugin(MySqlPacket packet)
private async Task<MySqlAuthenticationPlugin> NextPluginAsync(MySqlPacket packet, bool execAsync)
{
string method = packet.ReadString();
SwitchedPlugin = method;
byte[] authData = new byte[packet.Length - packet.Position];
Array.Copy(packet.Buffer, packet.Position, authData, 0, authData.Length);

MySqlAuthenticationPlugin plugin = GetPlugin(method, _driver, authData, _mfaIteration);
MySqlAuthenticationPlugin plugin = await GetPluginAsync(method, _driver, authData, execAsync, _mfaIteration).ConfigureAwait(false);
return plugin;
}

private void ContinueAuthentication(byte[] data = null)
private async Task ContinueAuthenticationAsync(bool execAsync, byte[] data = null)
{
MySqlPacket packet = _driver.Packet;
packet.Clear();

byte[] moreData = MoreData(data);
byte[] moreData = await MoreDataAsync(data, execAsync).ConfigureAwait(false);

while (moreData != null)
{
packet.Clear();
packet.Write(moreData);
_driver.SendPacket(packet);
await packet.WriteAsync(moreData, execAsync).ConfigureAwait(false);
await _driver.SendPacketAsync(packet, execAsync).ConfigureAwait(false);

packet = ReadPacket();
packet = await ReadPacketAsync(execAsync).ConfigureAwait(false);
byte prefixByte = packet.Buffer[0];
if (prefixByte != 1) return;

// A prefix of 0x01 means need more auth data.
byte[] responseData = new byte[packet.Length - 1];
Array.Copy(packet.Buffer, 1, responseData, 0, responseData.Length);
moreData = MoreData(responseData);
moreData = await MoreDataAsync(responseData, execAsync).ConfigureAwait(false);
}
// We get here if MoreData returned null but the last packet read was a more data packet.
ReadPacket();
await ReadPacketAsync(execAsync).ConfigureAwait(false);
}

/// <summary>
Expand Down

0 comments on commit 291c2f3

Please sign in to comment.