Spec-Zone .ru
спецификации, руководства, описания, API

21.2.5.6. Writing a Custom Authentication Plugin

Advanced users with special security requirements can create their own authentication plugins for Connector/Net applications. You can extend the handshake protocol, adding custom logic. This capability requires Connector/Net 6.6.3 or higher, and MySQL 5.5.16 or higher. For background and usage information about MySQL authentication plugins, see, Section 22.2.3.7, "Authentication Plugins" and Section 22.2.4.9, "Writing Authentication Plugins".

To write a custom authentication plugin, you will need a reference to the assembly MySql.Data.dll. The classes relevant for writing authentication plugins are available at the namespace MySql.Data.MySqlClient.Authentication.

How the Custom Authentication Plugin Works

At some point during handshake, the internal method

void Authenticate(bool reset)

Creating the Authentication Plugin Class

You put the authentication plugin logic inside a new class derived from MySql.Data.MySqlClient.Authentication.MySqlAuthenticationPlugin. The following methods are available to be overridden:

protected virtual void CheckConstraints()protected virtual void AuthenticationFailed(Exception ex)protected virtual void AuthenticationSuccessful()protected virtual byte[] MoreData(byte[] data)protected virtual void AuthenticationChange()public abstract string PluginName { get; }public virtual string GetUsername()public virtual object GetPassword()protected byte[] AuthData;

The following is a brief explanation of each one:

/// <summary>/// This method must check authentication method specific constraints in the environment and throw an Exception/// if the conditions are not met. The default implementation does nothing./// </summary>protected virtual void CheckConstraints()/// <summary>/// This method, called when the authentication failed, provides a chance to plugins to manage the error/// the way they consider decide (either showing a message, logging it, etc.)./// The default implementation wraps the original exception in a MySqlException with an standard message and rethrows it./// </summary>/// <param name="ex">The exception with extra information on the error.</param>protected virtual void AuthenticationFailed(Exception ex)/// <summary>/// This method is invoked when the authentication phase was successful accepted by the server./// Derived classes must override this if they want to be notified of such condition./// </summary>/// <remarks>The default implementation does nothing.</remarks>protected virtual void AuthenticationSuccessful()/// <summary>/// This method provides a chance for the plugin to send more data when the server requests so during the /// authentication phase. This method will be called at least once, and more than one depending upon whether the/// server response packets have the 0x01 prefix./// </summary>/// <param name="data">The response data from the server, during the authentication phase the first time is called is null, in subsequent calls contains the server response.</param>/// <returns>The data generated by the plugin for server consumption.</returns>/// <remarks>The default implementation always returns null.</remarks>protected virtual byte[] MoreData(byte[] data)/// <summary>/// The plugin name./// </summary>public abstract string PluginName { get; }/// <summary>/// Gets the user name to send to the server in the authentication phase./// </summary>/// <returns>An string with the user name</returns>/// <remarks>Default implementation returns the UserId passed from the connection string.</remarks>public virtual string GetUsername()/// <summary>/// Gets the password to send to the server in the authentication phase. This can can be an string or a/// </summary>/// <returns>An object, can be byte[], string or null, with the password.</returns>/// <remarks>Default implementation returns null.</remarks>public virtual object GetPassword()/// <summary>/// The authentication data passed when creating the plugin. /// For example in mysql_native_password this is the seed to encrypt the password./// </summary>protected byte[] AuthData;

Sample Authentication Plugin

Here is an example showing how to create the authentication plugin, then enable it by means of a configuration file. Follow these steps:

  1. Create a console app, adding a reference to MySql.Data.dll.

  2. Design the main program as follows:

    using System;using System.Collections.Generic;using System.Linq;using System.Text;using MySql.Data.MySqlClient;namespace AuthPluginTest{  class Program  {    static void Main(string[] args)    {      // Customize the connection string as necessary.      MySqlConnection con = new MySqlConnection("server=localhost; database=test; user id=myuser; password=mypass");      con.Open();      con.Close();    }  }}
  3. Create your plugin class. In this example, we add an "alternative" implementation of the Native password plugin by just using the same code from the original plugin. We name our class MySqlNativePasswordPlugin2:

    using System.IO;using System;using System.Text;using System.Security.Cryptography;using MySql.Data.MySqlClient.Authentication;using System.Diagnostics;namespace AuthPluginTest{  public class MySqlNativePasswordPlugin2 : MySqlAuthenticationPlugin  {    public override string PluginName    {      get { return "mysql_native_password"; }    }    public override object GetPassword()    {      Debug.WriteLine("Calling MySqlNativePasswordPlugin2.GetPassword");      return Get411Password(Settings.Password, AuthData);    }    /// <summary>    /// Returns a byte array containing the proper encryption of the     /// given password/seed according to the new 4.1.1 authentication scheme.    /// </summary>    /// <param name="password"></param>    /// <param name="seed"></param>    /// <returns></returns>    private byte[] Get411Password(string password, byte[] seedBytes)    {      // if we have no password, then we just return 1 zero byte      if (password.Length == 0) return new byte[1];      SHA1 sha = new SHA1CryptoServiceProvider();      byte[] firstHash = sha.ComputeHash(Encoding.Default.GetBytes(password));      byte[] secondHash = sha.ComputeHash(firstHash);      byte[] input = new byte[seedBytes.Length + secondHash.Length];      Array.Copy(seedBytes, 0, input, 0, seedBytes.Length);      Array.Copy(secondHash, 0, input, seedBytes.Length, secondHash.Length);      byte[] thirdHash = sha.ComputeHash(input);      byte[] finalHash = new byte[thirdHash.Length + 1];      finalHash[0] = 0x14;      Array.Copy(thirdHash, 0, finalHash, 1, thirdHash.Length);      for (int i = 1; i < finalHash.Length; i++)        finalHash[i] = (byte)(finalHash[i] ^ firstHash[i - 1]);      return finalHash;    }  }}
  4. Notice that the plugin implementation just overrides GetPassword, and provides an implementation to encrypt the password using the 4.1 protocol. We also put the following line in the GetPassword body:

    Debug.WriteLine("Calling MySqlNativePasswordPlugin2.GetPassword");
  5. Enable the new plugin in the configuration file:

    <?xml version="1.0"?><configuration>  <configSections>    <section name="MySQL" type="MySql.Data.MySqlClient.MySqlConfiguration, MySql.Data"/>  </configSections>  <MySQL>    <AuthenticationPlugins>      <add name="mysql_native_password" type="AuthPluginTest.MySqlNativePasswordPlugin2, AuthPluginTest"></add>    </AuthenticationPlugins>      </MySQL><startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/></startup></configuration>
  6. Run the application. In Visual Studio, you will see the message Calling MySqlNativePasswordPlugin2.GetPassword in the debug window.

  7. Continue enhancing the authentication logic, overriding more methods if you required.