text: /*
     Copyright (c) 2017, 2021, MariaDB
  
     This program is free software; you can redistribute it and/or modify
     it under the terms of the GNU General Public License as published by
     the Free Software Foundation; version 2 of the License.
  
     This program is distributed in the hope that it will be useful,
     but WITHOUT ANY WARRANTY; without even the implied warranty of
     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     GNU General Public License for more details.
  
     You should have received a copy of the GNU General Public License
     along with this program; if not, write to the Free Software
     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335 USA */
  
  #include <mysql/plugin_auth.h>
  #include <mysqld_error.h>
  #include "common.h"
  
  #if !defined(__attribute__) && !defined(__GNUC__)
  #define __attribute__(A)
  #endif
  
  #define PASSWORD_LEN_BUF 44 /* base64 of 32 bytes */
  #define PASSWORD_LEN 43     /* we won't store the last byte, padding '=' */
  
  #define CRYPTO_LONGS (CRYPTO_BYTES/sizeof(long))
  #define NONCE_LONGS  (NONCE_BYTES/sizeof(long))
  
  /************************** SERVER *************************************/
  
  static int loaded= 0;
  
  static int auth(MYSQL_PLUGIN_VIO *vio, MYSQL_SERVER_AUTH_INFO *info)
  {
    int pkt_len;
    unsigned long nonce[CRYPTO_LONGS + NONCE_LONGS];
    unsigned char *pkt, *reply= (unsigned char*)nonce;
  
    info->password_used= PASSWORD_USED_YES;
  
    /* prepare random nonce */
    if (my_random_bytes((unsigned char *)nonce, (int)sizeof(nonce)))
      return CR_ERROR; // eh? OpenSSL error
  
    /* send it */
    if (vio->write_packet(vio, reply + CRYPTO_BYTES, NONCE_BYTES))
      return CR_AUTH_HANDSHAKE;
  
    /* read the signature */
    if ((pkt_len= vio->read_packet(vio, &pkt)) != CRYPTO_BYTES)
      return CR_AUTH_HANDSHAKE;
    memcpy(reply, pkt, CRYPTO_BYTES);
  
    if (crypto_sign_open(reply, CRYPTO_BYTES + NONCE_BYTES,
                         (unsigned char*)info->auth_string))
      return CR_AUTH_USER_CREDENTIALS; // wrong password provided by the user
  
    return CR_OK;
  }
  
  static int compute_password_digest(const char *pw, size_t pwlen,
                                     char *d, size_t *dlen)
  {
    unsigned char pk[CRYPTO_PUBLICKEYBYTES];
    if (*dlen < PASSWORD_LEN || pwlen == 0)
      return 1;
    *dlen= PASSWORD_LEN;
    crypto_sign_keypair(pk, (unsigned char*)pw, pwlen);
    my_base64_encode(pk, CRYPTO_PUBLICKEYBYTES, d);
    return 0;
  }
  
  static int digest_to_binary(const char *d, size_t dlen,
                              unsigned char *b, size_t *blen)
  {
    char pw[PASSWORD_LEN_BUF];
  
    if (*blen < CRYPTO_PUBLICKEYBYTES || dlen != PASSWORD_LEN)
    {
      my_printf_error(ER_PASSWD_LENGTH, "Password hash should be %d characters long", 0, PASSWORD_LEN);
      return 1;
    }
  
    *blen= CRYPTO_PUBLICKEYBYTES;
    memcpy(pw, d, PASSWORD_LEN);
    pw[PASSWORD_LEN]= '=';
    if (my_base64_decode(pw, PASSWORD_LEN_BUF, b, 0, 0) == CRYPTO_PUBLICKEYBYTES)
      return 0;
    my_printf_error(ER_PASSWD_LENGTH, "Password hash should be base64 encoded", 0);
    return 1;
  }
  
  static struct st_mysql_auth info =
  {
    MYSQL_AUTHENTICATION_INTERFACE_VERSION,
    "client_ed25519",
    auth,
    compute_password_digest,
    digest_to_binary
  };
  
  static int init(void *p __attribute__((unused)))
  {
    loaded= 1;
    return 0;
  }
  
  static int deinit(void *p __attribute__((unused)))
  {
    loaded= 0;
    return 0;
  }
  
  maria_declare_plugin(ed25519)
  {
    MYSQL_AUTHENTICATION_PLUGIN,
    &info,
    "ed25519",
    "Sergei Golubchik",
    "Elliptic curve ED25519 based authentication",
    PLUGIN_LICENSE_GPL,
    init,
    deinit,
    0x0101,
    NULL,
    NULL,
    "1.1",
    MariaDB_PLUGIN_MATURITY_STABLE
  }
  maria_declare_plugin_end;
  
  /************************** UDF ****************************************/
  MYSQL_PLUGIN_EXPORT
  char *ed25519_password(UDF_INIT *initid __attribute__((unused)),
                         UDF_ARGS *args, char *result, unsigned long *length,
                         char *is_null, char *error __attribute__((unused)))
  {
    unsigned char pk[CRYPTO_PUBLICKEYBYTES];
  
    if ((*is_null= !args->args[0]))
      return NULL;
  
    *length= PASSWORD_LEN;
    crypto_sign_keypair(pk, (unsigned char*)args->args[0], args->lengths[0]);
    my_base64_encode(pk, CRYPTO_PUBLICKEYBYTES, result);
    return result;
  }
  
  /*
    At least one of _init/_deinit is needed unless the server is started
    with --allow_suspicious_udfs.
  */
  MYSQL_PLUGIN_EXPORT
  my_bool ed25519_password_init(UDF_INIT *initid, UDF_ARGS *args, char *message)
  {
    if (args->arg_count != 1 || args->arg_type[0] != STRING_RESULT)
    {
      strcpy(message,"Wrong arguments to ed25519_password()");
      return 1;
    }
    if (!loaded)
    {
      /* cannot work unless the plugin is loaded, we need services. */
      strcpy(message,"Authentication plugin ed25519 is not loaded");
      return 1;
    }
    initid->max_length= PASSWORD_LEN_BUF;
    return 0;
  }
  