From e580cf7ae08d0d2bb19381e1102529cfca2fede2 Mon Sep 17 00:00:00 2001 From: Nikita Malyavin Date: Tue, 21 May 2024 22:11:04 +0200 Subject: [PATCH] MDEV-32618 new auth plugin PARSEC: Password Authentication using Response Signed with Elliptic Curve new authentication plugin that uses salted passwords, key derivation, extensible password storage format, and both server- and client-side scrambles. It signs the response with ed25519, but it uses stock unmodified ed25519 as provided by OpenSSL/WolfSSL/GnuTLS. Edited by: Sergei Golubchik --- cmake/mariadb_connector_c.cmake | 1 + cmake/ssl.cmake | 5 +- debian/libmariadb3.install | 1 + debian/mariadb-server.install | 1 + extra/wolfssl/CMakeLists.txt | 6 + extra/wolfssl/user_settings.h.in | 2 + mysql-test/suite/plugins/r/parsec.result | 32 ++ mysql-test/suite/plugins/r/rpl_auth.result | 12 +- mysql-test/suite/plugins/t/parsec.opt | 3 + mysql-test/suite/plugins/t/parsec.test | 45 +++ .../suite/plugins/t/rpl_auth.combinations | 2 + mysql-test/suite/plugins/t/rpl_auth.test | 42 ++- plugin/auth_parsec/CMakeLists.txt | 4 + plugin/auth_parsec/server_parsec.cc | 309 ++++++++++++++++++ 14 files changed, 445 insertions(+), 20 deletions(-) create mode 100644 mysql-test/suite/plugins/r/parsec.result create mode 100644 mysql-test/suite/plugins/t/parsec.opt create mode 100644 mysql-test/suite/plugins/t/parsec.test create mode 100644 mysql-test/suite/plugins/t/rpl_auth.combinations create mode 100644 plugin/auth_parsec/CMakeLists.txt create mode 100644 plugin/auth_parsec/server_parsec.cc diff --git a/cmake/mariadb_connector_c.cmake b/cmake/mariadb_connector_c.cmake index b4f56597775..3e9ff840439 100644 --- a/cmake/mariadb_connector_c.cmake +++ b/cmake/mariadb_connector_c.cmake @@ -7,6 +7,7 @@ ENDIF() SET(CONC_WITH_SIGNCODE ${SIGNCODE}) SET(SIGN_OPTIONS ${SIGNTOOL_PARAMETERS}) SET(CONC_WITH_EXTERNAL_ZLIB ON) +SET(CLIENT_PLUGIN_PARSEC DYNAMIC) IF(SSL_DEFINES MATCHES "WOLFSSL") IF(WIN32) diff --git a/cmake/ssl.cmake b/cmake/ssl.cmake index eed1673f6d4..b72f959828f 100644 --- a/cmake/ssl.cmake +++ b/cmake/ssl.cmake @@ -54,7 +54,8 @@ MACRO (MYSQL_USE_BUNDLED_SSL) SET(HAVE_EncryptAes128Ctr ON CACHE INTERNAL "wolfssl does support AES-CTR") SET(HAVE_EncryptAes128Gcm OFF CACHE INTERNAL "wolfssl does not support AES-GCM") SET(HAVE_des ON CACHE INTERNAL "wolfssl does support DES API") - SET(HAVE_hkdf ON CACHE INTERNAL "wolfssl does support EVP_PKEY API") + SET(HAVE_evp_pkey ON CACHE INTERNAL "wolfssl does support EVP_PKEY API") + SET(HAVE_hkdf ON CACHE INTERNAL "wolfssl does support EVP_PKEY_HKDF API") CHANGE_SSL_SETTINGS("bundled") ADD_SUBDIRECTORY(extra/wolfssl) MESSAGE_ONCE(SSL_LIBRARIES "SSL_LIBRARIES = ${SSL_LIBRARIES}") @@ -137,6 +138,8 @@ MACRO (MYSQL_CHECK_SSL) HAVE_EncryptAes128Gcm) CHECK_SYMBOL_EXISTS(DES_set_key_unchecked "openssl/des.h" HAVE_des) + CHECK_SYMBOL_EXISTS(EVP_PKEY_get_raw_public_key "openssl/evp.h" + HAVE_evp_pkey) CHECK_SYMBOL_EXISTS(EVP_PKEY_CTX_set_hkdf_md "string.h;stdarg.h;openssl/kdf.h" HAVE_hkdf) SET(CMAKE_REQUIRED_INCLUDES) diff --git a/debian/libmariadb3.install b/debian/libmariadb3.install index 5cd0aad00e0..be143ad5f13 100644 --- a/debian/libmariadb3.install +++ b/debian/libmariadb3.install @@ -4,3 +4,4 @@ usr/lib/*/libmariadb3/plugin/client_ed25519.so usr/lib/*/libmariadb3/plugin/dialog.so usr/lib/*/libmariadb3/plugin/mysql_clear_password.so usr/lib/*/libmariadb3/plugin/sha256_password.so +usr/lib/*/libmariadb3/plugin/parsec.so diff --git a/debian/mariadb-server.install b/debian/mariadb-server.install index bf4a47e3916..5889bd827ae 100644 --- a/debian/mariadb-server.install +++ b/debian/mariadb-server.install @@ -39,6 +39,7 @@ usr/lib/mysql/plugin/auth_ed25519.so usr/lib/mysql/plugin/auth_pam.so usr/lib/mysql/plugin/auth_pam_tool_dir/auth_pam_tool usr/lib/mysql/plugin/auth_pam_v1.so +usr/lib/mysql/plugin/auth_parsec.so usr/lib/mysql/plugin/disks.so usr/lib/mysql/plugin/file_key_management.so usr/lib/mysql/plugin/ha_archive.so diff --git a/extra/wolfssl/CMakeLists.txt b/extra/wolfssl/CMakeLists.txt index e3f8da21f76..866d922d25a 100644 --- a/extra/wolfssl/CMakeLists.txt +++ b/extra/wolfssl/CMakeLists.txt @@ -60,7 +60,10 @@ ${WOLFCRYPT_SRCDIR}/des3.c ${WOLFCRYPT_SRCDIR}/dh.c ${WOLFCRYPT_SRCDIR}/dsa.c ${WOLFCRYPT_SRCDIR}/ecc.c +${WOLFCRYPT_SRCDIR}/ed25519.c ${WOLFCRYPT_SRCDIR}/error.c +${WOLFCRYPT_SRCDIR}/fe_operations.c +${WOLFCRYPT_SRCDIR}/ge_operations.c ${WOLFCRYPT_SRCDIR}/hmac.c ${WOLFCRYPT_SRCDIR}/logging.c ${WOLFCRYPT_SRCDIR}/md4.c @@ -112,6 +115,7 @@ if(WOLFSSL_INTELASM) ${WOLFCRYPT_SRCDIR}/aes_asm.S ${WOLFCRYPT_SRCDIR}/aes_gcm_asm.S ${WOLFCRYPT_SRCDIR}/chacha_asm.S + ${WOLFCRYPT_SRCDIR}/fe_x25519_asm.S ${WOLFCRYPT_SRCDIR}/poly1305_asm.S ${WOLFCRYPT_SRCDIR}/sha512_asm.S ${WOLFCRYPT_SRCDIR}/sha256_asm.S @@ -132,5 +136,7 @@ if(MSVC) endif() endif() +set_property(TARGET wolfssl PROPERTY POSITION_INDEPENDENT_CODE ON) + CONFIGURE_FILE(user_settings.h.in user_settings.h) diff --git a/extra/wolfssl/user_settings.h.in b/extra/wolfssl/user_settings.h.in index 62f8c5f700e..c786076b211 100644 --- a/extra/wolfssl/user_settings.h.in +++ b/extra/wolfssl/user_settings.h.in @@ -69,4 +69,6 @@ #cmakedefine WOLFSSL_SP_X86_64 #cmakedefine WOLFSSL_SP_X86_64_ASM +#define HAVE_ED25519 + #endif /* WOLFSSL_USER_SETTINGS_H */ diff --git a/mysql-test/suite/plugins/r/parsec.result b/mysql-test/suite/plugins/r/parsec.result new file mode 100644 index 00000000000..58b430e660c --- /dev/null +++ b/mysql-test/suite/plugins/r/parsec.result @@ -0,0 +1,32 @@ +install soname 'auth_parsec'; +create user test1@'%' identified via parsec using 'pwd'; +ERROR HY000: Operation CREATE USER failed for 'test1'@'%' +create user test1@'%' identified via parsec using PASSWORD('pwd'); +show grants for test1@'%'; +Grants for test1@% +GRANT USAGE ON *.* TO `test1`@`%` IDENTIFIED VIA parsec USING 'P0:salt:password' +connect con1, localhost, test1, pwd; +select 1, USER(), CURRENT_USER(); +1 USER() CURRENT_USER() +1 test1@localhost test1@% +disconnect con1; +connect con2, localhost, test1, pwd; +select 2, USER(), CURRENT_USER(); +2 USER() CURRENT_USER() +2 test1@localhost test1@% +disconnect con2; +connect(localhost,test1,wrong_pwd,test,MASTER_MYPORT,MASTER_MYSOCK); +connect con3, localhost, test1, wrong_pwd; +ERROR 28000: Access denied for user 'test1'@'localhost' (using password: NO) +connection default; +create function have_ssl() returns char(3) +return (select if(variable_value > '','yes','no') as 'have_ssl' + from information_schema.session_status +where variable_name='ssl_cipher'); +grant execute on test.* to test1@'%'; +# mysql -utest1 -ppwd --ssl-verify-server-cert -e "select test.have_ssl()" +test.have_ssl() +yes +drop function have_ssl; +drop user test1@'%'; +uninstall soname 'auth_parsec'; diff --git a/mysql-test/suite/plugins/r/rpl_auth.result b/mysql-test/suite/plugins/r/rpl_auth.result index e7eff75ec73..010458ceb62 100644 --- a/mysql-test/suite/plugins/r/rpl_auth.result +++ b/mysql-test/suite/plugins/r/rpl_auth.result @@ -1,12 +1,12 @@ -install soname 'client_ed25519'; +install soname 'CLIENT_PLUGIN'; Got one of the listed errors include/master-slave.inc [connection master] connection slave; -install soname 'auth_ed25519'; +install soname 'auth_PLUGIN'; connection master; -install soname 'auth_ed25519'; -create user rpluser@'%' identified via ed25519 using PASSWORD('rpl_pass'); +install soname 'auth_plugin'; +create user rpluser@'%' identified via PLUGIN using PASSWORD('rpl_pass'); grant replication slave on *.* to rpluser@'%'; connection master; connection slave; @@ -19,7 +19,7 @@ change master to master_user='root', master_password=''; include/start_slave.inc include/stop_slave.inc drop user rpluser@'%'; -uninstall soname 'auth_ed25519'; +uninstall soname 'auth_plugin'; connection master; drop user rpluser@'%'; -uninstall soname 'auth_ed25519'; +uninstall soname 'auth_plugin'; diff --git a/mysql-test/suite/plugins/t/parsec.opt b/mysql-test/suite/plugins/t/parsec.opt new file mode 100644 index 00000000000..b3a45ea8eac --- /dev/null +++ b/mysql-test/suite/plugins/t/parsec.opt @@ -0,0 +1,3 @@ +--ssl-key= +--ssl-cert= +--ssl-ca= diff --git a/mysql-test/suite/plugins/t/parsec.test b/mysql-test/suite/plugins/t/parsec.test new file mode 100644 index 00000000000..d1fc6f8d869 --- /dev/null +++ b/mysql-test/suite/plugins/t/parsec.test @@ -0,0 +1,45 @@ +source include/platform.inc; +source include/not_embedded.inc; + +if (!$AUTH_PARSEC_SO) { + skip No auth_parsec plugin; +} +if (!$PARSEC_SO) { + skip No auth_parsec plugin; +} +install soname 'auth_parsec'; +--error ER_CANNOT_USER +create user test1@'%' identified via parsec using 'pwd'; +create user test1@'%' identified via parsec using PASSWORD('pwd'); +--replace_regex /:[A-Za-z0-9+\/]{43}'/:password'/ /:[A-Za-z0-9+\/]{24}:/:salt:/ +show grants for test1@'%'; +connect con1, localhost, test1, pwd; +select 1, USER(), CURRENT_USER(); +disconnect con1; +connect con2, localhost, test1, pwd; +select 2, USER(), CURRENT_USER(); +disconnect con2; +--replace_result $MASTER_MYSOCK MASTER_MYSOCK $MASTER_MYPORT MASTER_MYPORT +--error ER_ACCESS_DENIED_ERROR +connect con3, localhost, test1, wrong_pwd; + +connection default; + +create function have_ssl() returns char(3) + return (select if(variable_value > '','yes','no') as 'have_ssl' + from information_schema.session_status + where variable_name='ssl_cipher'); +grant execute on test.* to test1@'%'; + +let host=; +if ($MTR_COMBINATION_WIN) { + # see ssl_autoverify.test + let host=--host=127.0.0.2; +} + +--echo # mysql -utest1 -ppwd --ssl-verify-server-cert -e "select test.have_ssl()" +--exec $MYSQL --protocol tcp $host -utest1 -ppwd --ssl-verify-server-cert -e "select test.have_ssl()" 2>&1 + +drop function have_ssl; +drop user test1@'%'; +uninstall soname 'auth_parsec'; diff --git a/mysql-test/suite/plugins/t/rpl_auth.combinations b/mysql-test/suite/plugins/t/rpl_auth.combinations new file mode 100644 index 00000000000..799567fca67 --- /dev/null +++ b/mysql-test/suite/plugins/t/rpl_auth.combinations @@ -0,0 +1,2 @@ +[parsec] +[ed25519] diff --git a/mysql-test/suite/plugins/t/rpl_auth.test b/mysql-test/suite/plugins/t/rpl_auth.test index 9abba0092e5..0754b209d7a 100644 --- a/mysql-test/suite/plugins/t/rpl_auth.test +++ b/mysql-test/suite/plugins/t/rpl_auth.test @@ -1,29 +1,43 @@ -if (!$AUTH_ED25519_SO) { - skip No auth_ed25519 plugin; +if ($MTR_COMBINATION_ED25519) { + if (!$AUTH_ED25519_SO) { + skip No auth_ed25519 plugin; + } + let $AUTH_PLUGIN = ed25519; + let $CLIENT_PLUGIN=client_ed25519; } -if (!$CLIENT_ED25519_SO) { - skip No client_ed25519 plugin; +if ($MTR_COMBINATION_PARSEC) { + if (!$AUTH_PARSEC_SO) { + skip No auth_parsec plugin; + } + let $AUTH_PLUGIN = parsec; + let $CLIENT_PLUGIN=parsec; } + +--replace_result $CLIENT_PLUGIN CLIENT_PLUGIN --error ER_CANT_OPEN_LIBRARY,ER_CANT_FIND_DL_ENTRY -install soname 'client_ed25519'; +eval install soname '$CLIENT_PLUGIN'; if ($errno == 1126) { # this happens in bintars when C/C is linked with gnutls - skip client_ed25519 contains unresolved symbols; + skip $CLIENT_PLUGIN is not found or contains unresolved symbols; } source include/master-slave.inc; sync_slave_with_master; -install soname 'auth_ed25519'; -# create a user for replication with ed25519 auth plugin +--replace_result $AUTH_PLUGIN PLUGIN +eval install soname 'auth_$AUTH_PLUGIN'; +# create a user for replication with auth plugin connection master; -install soname 'auth_ed25519'; -create user rpluser@'%' identified via ed25519 using PASSWORD('rpl_pass'); +--replace_result $AUTH_PLUGIN plugin +eval install soname 'auth_$AUTH_PLUGIN'; + +--replace_result $AUTH_PLUGIN PLUGIN +eval create user rpluser@'%' identified via $AUTH_PLUGIN using PASSWORD('rpl_pass'); grant replication slave on *.* to rpluser@'%'; connection master; sync_slave_with_master; -# Set the slave to connect using the user created with the ed25519 plugin for replication +# Set the slave to connect using the user created with the auth plugin for replication source include/stop_slave.inc; --replace_result $MYSQL_TEST_DIR MYSQL_TEST_DIR change master to master_user='rpluser', master_password='rpl_pass'; @@ -35,7 +49,9 @@ change master to master_user='root', master_password=''; source include/start_slave.inc; source include/stop_slave.inc; drop user rpluser@'%'; -uninstall soname 'auth_ed25519'; +--replace_result $AUTH_PLUGIN plugin +eval uninstall soname 'auth_$AUTH_PLUGIN'; connection master; drop user rpluser@'%'; -uninstall soname 'auth_ed25519'; +--replace_result $AUTH_PLUGIN plugin +eval uninstall soname 'auth_$AUTH_PLUGIN'; diff --git a/plugin/auth_parsec/CMakeLists.txt b/plugin/auth_parsec/CMakeLists.txt new file mode 100644 index 00000000000..6c43bc041d3 --- /dev/null +++ b/plugin/auth_parsec/CMakeLists.txt @@ -0,0 +1,4 @@ +IF (HAVE_evp_pkey) + ADD_DEFINITIONS(${SSL_DEFINES}) + MYSQL_ADD_PLUGIN(auth_parsec server_parsec.cc LINK_LIBRARIES ${SSL_LIBRARIES}) +ENDIF() diff --git a/plugin/auth_parsec/server_parsec.cc b/plugin/auth_parsec/server_parsec.cc new file mode 100644 index 00000000000..7a0d2a172d6 --- /dev/null +++ b/plugin/auth_parsec/server_parsec.cc @@ -0,0 +1,309 @@ +/* + Copyright (c) 2024, MariaDB plc + + 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 +#include +#include +#ifdef HAVE_WOLFSSL +#include +#include +#endif + +#include +#include +#include "scope.h" + +#include + +typedef unsigned char uchar; +constexpr size_t CHALLENGE_SCRAMBLE_LENGTH= 32; +constexpr size_t CHALLENGE_SALT_LENGTH= 18; +constexpr size_t ED25519_SIG_LENGTH= 64; +constexpr size_t ED25519_KEY_LENGTH= 32; +constexpr size_t PBKDF2_HASH_LENGTH= ED25519_KEY_LENGTH; +constexpr size_t CLIENT_RESPONSE_LENGTH= CHALLENGE_SCRAMBLE_LENGTH + + ED25519_SIG_LENGTH; + +constexpr size_t base64_length(size_t input_length) +{ + return ((input_length + 2) / 3) * 4; // with padding +} + +constexpr size_t base64_length_raw(size_t input_length) +{ + return (input_length * 4 + 2) / 3; // no padding +} + +struct alignas(1) Client_signed_response +{ + union { + struct { + uchar client_scramble[CHALLENGE_SCRAMBLE_LENGTH]; + uchar signature[ED25519_SIG_LENGTH]; + }; + uchar start[1]; + }; +}; + +static_assert(sizeof(Client_signed_response) == CLIENT_RESPONSE_LENGTH, + "Client_signed_response is not aligned."); + +struct alignas(1) Passwd_as_stored +{ + char algorithm; + uchar iterations; + char colon; + char salt[base64_length_raw(CHALLENGE_SALT_LENGTH)]; + char colon2; + char pub_key[base64_length_raw(ED25519_KEY_LENGTH)]; +}; + +struct alignas(1) Passwd_in_memory +{ + char algorithm; + uchar iterations; + uchar salt[CHALLENGE_SALT_LENGTH]; + uchar pub_key[ED25519_KEY_LENGTH]; +}; + +int print_ssl_error() +{ + char buf[512]; + unsigned long err= ERR_get_error(); + ERR_error_string_n(err, buf, sizeof buf); + my_printf_error(err, "parsec: %s", ME_ERROR_LOG_ONLY, buf); + return 1; +} + +static +int compute_derived_key(const char* password, size_t pass_len, + const Passwd_in_memory *params, uchar *derived_key) +{ + assert(params->algorithm == 'P'); + int ret = PKCS5_PBKDF2_HMAC(password, (int)pass_len, params->salt, + sizeof(params->salt), 1024 << params->iterations, + EVP_sha512(), PBKDF2_HASH_LENGTH, derived_key); + if(ret == 0) + return print_ssl_error(); + + return 0; +} + + +static +int verify_ed25519(const uchar *public_key, const uchar *signature, + const uchar *message, size_t message_len) +{ +#ifdef HAVE_WOLFSSL + int res= wolfSSL_ED25519_verify(message, (unsigned)message_len, + public_key, ED25519_KEY_LENGTH, + signature, ED25519_SIG_LENGTH); + return res != WOLFSSL_SUCCESS; +#else + int ret= 1; + EVP_MD_CTX *mdctx= EVP_MD_CTX_new(); + EVP_PKEY *pkey= EVP_PKEY_new_raw_public_key(EVP_PKEY_ED25519, NULL, + public_key, 32); + + if (pkey && mdctx && EVP_DigestVerifyInit(mdctx, NULL, NULL, NULL, pkey)) + ret= !EVP_DigestVerify(mdctx, signature, ED25519_SIG_LENGTH, + message, message_len); + EVP_MD_CTX_free(mdctx); + EVP_PKEY_free(pkey); + return ret; +#endif +} + +static +int ed25519_derive_public_key(const uchar *raw_private_key, uchar *pub_key) +{ +#ifdef HAVE_WOLFSSL + ed25519_key key; + + int ret = wc_ed25519_init(&key); + if (ret != 0) + return print_ssl_error(); + + SCOPE_EXIT([&key](){ wc_ed25519_free(&key); }); + + ret = wc_ed25519_import_private_only(raw_private_key, ED25519_KEY_LENGTH, + &key); + if (ret != 0) + return print_ssl_error(); + + ret = wc_ed25519_make_public(&key, pub_key, ED25519_KEY_LENGTH); + if (ret != 0) + return print_ssl_error(); + + return false; +#else + EVP_PKEY *pkey = EVP_PKEY_new_raw_private_key(EVP_PKEY_ED25519, NULL, + raw_private_key, + ED25519_KEY_LENGTH); + bool res= pkey != NULL; + size_t len= ED25519_KEY_LENGTH; + if (pkey) + res= EVP_PKEY_get_raw_public_key(pkey, pub_key, &len); // 1 == success + + if (!res) + print_ssl_error(); + + EVP_PKEY_free(pkey); + return !res; +#endif +} + +static +int hash_password(const char *password, size_t password_length, + char *hash, size_t *hash_length) +{ + auto stored= (Passwd_as_stored*)hash; + if (*hash_length < sizeof(*stored) + 2) + return 1; + + Passwd_in_memory memory; + memory.algorithm= 'P'; + memory.iterations= 0; + my_random_bytes(memory.salt, sizeof(memory.salt)); + + uchar derived_key[PBKDF2_HASH_LENGTH]; + if (compute_derived_key(password, password_length, &memory, derived_key)) + return 1; + + if (ed25519_derive_public_key(derived_key, memory.pub_key)) + return 1; + + stored->algorithm= memory.algorithm; + stored->iterations= memory.iterations + '0'; + my_base64_encode(memory.salt, sizeof(memory.salt), stored->salt); + my_base64_encode(memory.pub_key, sizeof(memory.pub_key), stored->pub_key); + stored->colon= stored->colon2= ':'; + + *hash_length = sizeof *stored; + hash[*hash_length]= 0; // safety + + return 0; +} + +static +int digest_to_binary(const char *hash, size_t hash_length, + unsigned char *out, size_t *out_length) +{ + auto stored= (Passwd_as_stored*)hash; + auto memory= (Passwd_in_memory*)out; + + if (hash_length != sizeof (*stored) || *out_length < sizeof(*memory) || + stored->algorithm != 'P' || + stored->iterations < '0' || stored->iterations > '3' || + stored->colon != ':' || stored->colon2 != ':') + return 1; + + *out_length = sizeof(*memory); + memory->algorithm= stored->algorithm; + memory->iterations= stored->iterations - '0'; + + static_assert(base64_length(CHALLENGE_SALT_LENGTH) == base64_length_raw(CHALLENGE_SALT_LENGTH), + "Salt is base64-aligned"); + if (my_base64_decode(stored->salt, base64_length(CHALLENGE_SALT_LENGTH), + memory->salt, NULL, 0) < 0) + return 1; + + char buf[base64_length(ED25519_KEY_LENGTH)+1]; + constexpr int pad= (int)base64_length(ED25519_KEY_LENGTH) + - (int)base64_length_raw(ED25519_KEY_LENGTH); + static_assert(pad > 0, "base64 length calculation check"); + memcpy(buf, stored->pub_key, base64_length_raw(ED25519_KEY_LENGTH)); + memset(buf + base64_length_raw(ED25519_KEY_LENGTH), '=', pad); + if (my_base64_decode(buf, base64_length(ED25519_KEY_LENGTH), + memory->pub_key, NULL, 0) < 0) + return 1; + + return 0; +} + +static +int auth(MYSQL_PLUGIN_VIO *vio, MYSQL_SERVER_AUTH_INFO *info) +{ + union + { + struct + { + uchar server[CHALLENGE_SCRAMBLE_LENGTH]; + uchar client[CHALLENGE_SCRAMBLE_LENGTH]; + }; + uchar start[1]; + } scramble_pair; + + my_random_bytes(scramble_pair.server, CHALLENGE_SCRAMBLE_LENGTH); + + if (vio->write_packet(vio, scramble_pair.server, sizeof(scramble_pair.server))) + return CR_ERROR; + + // Begin with reading the handshake packet. It should be empty (for now). + uchar *dummy; + int bytes_read= vio->read_packet(vio, &dummy); + if (bytes_read != 0) + return CR_ERROR; + + auto passwd= (Passwd_in_memory*)info->auth_string; + + if (vio->write_packet(vio, (uchar*)info->auth_string, 2 + CHALLENGE_SALT_LENGTH)) + return CR_ERROR; + + Client_signed_response *client_response; + bytes_read= vio->read_packet(vio, (uchar**)&client_response); + if (bytes_read < 0) + return CR_ERROR; + if (bytes_read != sizeof *client_response) + return CR_AUTH_HANDSHAKE; + + memcpy(scramble_pair.client, client_response->client_scramble, + CHALLENGE_SCRAMBLE_LENGTH); + + if (verify_ed25519(passwd->pub_key, client_response->signature, + scramble_pair.start, sizeof(scramble_pair))) + return CR_AUTH_HANDSHAKE; + + return CR_OK; +} + +static struct st_mysql_auth info = +{ + MYSQL_AUTHENTICATION_INTERFACE_VERSION, + "parsec", + auth, + hash_password, + digest_to_binary +}; + + +maria_declare_plugin(auth_parsec) +{ + MYSQL_AUTHENTICATION_PLUGIN, + &info, + "parsec", + "Nikita Maliavin", + "Password Authentication using Response Signed with Elliptic Curve", + PLUGIN_LICENSE_GPL, + NULL, + NULL, + 0x0100, + NULL, + NULL, + "1.0", + MariaDB_PLUGIN_MATURITY_BETA +} +maria_declare_plugin_end;