MDEV-9804 Implement a caching_sha2_password plugin

but without caching
This commit is contained in:
Sergei Golubchik 2025-05-04 19:13:43 +02:00
parent 6a2afb42ba
commit e15904d564
30 changed files with 1080 additions and 4 deletions

View File

@ -36,6 +36,7 @@ usr/bin/wsrep_sst_mysqldump
usr/bin/wsrep_sst_rsync
usr/bin/wsrep_sst_rsync_wan
usr/lib/mysql/plugin/auth_ed25519.so
usr/lib/mysql/plugin/auth_mysql_sha2.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

View File

@ -8,6 +8,7 @@ typedef struct st_plugin_vio_info
enum { MYSQL_VIO_INVALID, MYSQL_VIO_TCP, MYSQL_VIO_SOCKET,
MYSQL_VIO_PIPE, MYSQL_VIO_MEMORY } protocol;
int socket;
int tls;
} MYSQL_PLUGIN_VIO_INFO;
typedef struct st_plugin_vio
{

View File

@ -27,7 +27,7 @@
#include <mysql/plugin.h>
#define MYSQL_AUTHENTICATION_INTERFACE_VERSION 0x0202
#define MYSQL_AUTHENTICATION_INTERFACE_VERSION 0x0203
#include <mysql/plugin_auth_common.h>

View File

@ -707,6 +707,7 @@ typedef struct st_plugin_vio_info
enum { MYSQL_VIO_INVALID, MYSQL_VIO_TCP, MYSQL_VIO_SOCKET,
MYSQL_VIO_PIPE, MYSQL_VIO_MEMORY } protocol;
int socket;
int tls;
} MYSQL_PLUGIN_VIO_INFO;
typedef struct st_plugin_vio
{

View File

@ -98,6 +98,7 @@ typedef struct st_plugin_vio_info
#ifdef _WIN32
HANDLE handle; /**< it's set, if the protocol is PIPE or MEMORY */
#endif
int tls;
} MYSQL_PLUGIN_VIO_INFO;
/**

View File

@ -8,6 +8,7 @@ plugins,^
mariabackup,^
roles,^
auth_gssapi,^
mysql_sha2,^
query_response_time,^
rocksdb,^
sysschema

View File

@ -1,5 +1,5 @@
if ($CLIENT_TLS_LIBRARY != "OpenSSL") {
if ($CLIENT_TLS_LIBRARY != "LibreSSL") {
skip "Test requires Connector/C with OpenSSL library";
skip Test requires Connector/C with OpenSSL library;
}
}

View File

@ -40,7 +40,7 @@ perl;
test-sql-discovery query-cache-info password-reuse-check
query-response-time metadata-lock-info locales unix-socket
wsrep file-key-management cracklib-password-check user-variables
provider-bzip2 provider-lzma provider-lzo
provider-bzip2 provider-lzma provider-lzo caching-sha2-password
thread-pool-groups thread-pool-queues thread-pool-stats
thread-pool-waits hashicorp provider gssapi parsec/;

View File

@ -25,7 +25,7 @@ PLUGIN_NAME ed25519
PLUGIN_VERSION 1.1
PLUGIN_STATUS ACTIVE
PLUGIN_TYPE AUTHENTICATION
PLUGIN_TYPE_VERSION 2.2
PLUGIN_TYPE_VERSION 2.3
PLUGIN_LIBRARY auth_ed25519.so
PLUGIN_LIBRARY_VERSION 1.15
PLUGIN_AUTHOR Sergei Golubchik

View File

@ -0,0 +1,8 @@
ADD_DEFINITIONS(${SSL_DEFINES})
IF(WITH_SSL STREQUAL "bundled")
# WolfSSL is static, we don't want it linked both into plugin and server
SET(static STATIC_ONLY DEFAULT)
ENDIF()
MYSQL_ADD_PLUGIN(auth_mysql_sha2
mysql_sha2.c sha256crypt.c ssl_stuff.c openssl1-compat.c
LINK_LIBRARIES ${SSL_LIBRARIES} ${static})

View File

@ -0,0 +1,3 @@
drop procedure checkme;
drop user test1@'%';
drop user test2@'%';

View File

@ -0,0 +1,21 @@
source include/not_embedded.inc;
call mtr.add_suppression('failed to read private_key.pem: 2 "No such file or directory"');
if (`select count(*) = 0 from information_schema.plugins where plugin_name = 'caching_sha2_password'`)
{
--skip Needs caching_sha2_password plugin
}
show status like 'caching_sha2_password%';
create user test1@'%' identified via caching_sha2_password using PASSWORD('pwd');
create user test2@'%' identified via caching_sha2_password;
show grants for test2@'%';
create procedure checkme() sql security invoker
select user(), current_user(), variable_value > '' as 'have_ssl'
from information_schema.session_status
where variable_name='ssl_cipher';
grant execute on test.* to test1@'%', test2@'%';

View File

@ -0,0 +1,3 @@
--plugin-load-add=$AUTH_MYSQL_SHA2_SO
--loose-caching-sha2-password
--loose-disable-caching-sha2-password-auto-generate-rsa-keys

View File

@ -0,0 +1 @@
--loose-enable-named-pipe

View File

@ -0,0 +1,39 @@
call mtr.add_suppression('failed to read private_key.pem: 2 "No such file or directory"');
show status like 'caching_sha2_password%';
Variable_name Value
Caching_sha2_password_rsa_public_key
create user test1@'%' identified via caching_sha2_password using PASSWORD('pwd');
create user test2@'%' identified via caching_sha2_password;
show grants for test2@'%';
Grants for test2@%
GRANT USAGE ON *.* TO `test2`@`%` IDENTIFIED VIA caching_sha2_password
create procedure checkme() sql security invoker
select user(), current_user(), variable_value > '' as 'have_ssl'
from information_schema.session_status
where variable_name='ssl_cipher';
grant execute on test.* to test1@'%', test2@'%';
connect con1, localhost,test1,pwd,,,,$proto NOSSL;
call checkme();
user() current_user() have_ssl
test1@localhost test1@% 0
disconnect con1;
connect con2, localhost,test1,pwd,,,,$proto NOSSL;
call checkme();
user() current_user() have_ssl
test1@localhost test1@% 0
disconnect con2;
connect(localhost,test1,wrong_pwd,test,MASTER_MYPORT,MASTER_MYSOCK);
connect con3, localhost,test1,wrong_pwd,,,,$proto NOSSL;
ERROR 28000: Access denied for user 'test1'@'localhost' (using password: YES)
connect con4, localhost,test2,,,,,$proto NOSSL;
call checkme();
user() current_user() have_ssl
test2@localhost test2@% 0
disconnect con4;
connect(localhost,test2 pwd,,test,MASTER_MYPORT,MASTER_MYSOCK);
connect con5, localhost,test2 pwd,,,,,$proto NOSSL;
ERROR 28000: Access denied for user 'test2 pwd'@'localhost' (using password: NO)
connection default;
drop procedure checkme;
drop user test1@'%';
drop user test2@'%';

View File

@ -0,0 +1,27 @@
source include/platform.inc;
source init.inc;
let proto=SOCKET;
if ($MTR_COMBINATION_WIN) {
let proto=PIPE;
}
connect con1, localhost,test1,pwd,,,,$proto NOSSL;
call checkme();
disconnect con1;
connect con2, localhost,test1,pwd,,,,$proto NOSSL;
call checkme();
disconnect con2;
replace_result $MASTER_MYSOCK MASTER_MYSOCK $MASTER_MYPORT MASTER_MYPORT;
error ER_ACCESS_DENIED_ERROR;
connect con3, localhost,test1,wrong_pwd,,,,$proto NOSSL;
connect con4, localhost,test2,,,,,$proto NOSSL;
call checkme();
disconnect con4;
replace_result $MASTER_MYSOCK MASTER_MYSOCK $MASTER_MYPORT MASTER_MYPORT;
error ER_ACCESS_DENIED_ERROR;
connect con5, localhost,test2 pwd,,,,,$proto NOSSL;
connection default;
source fini.inc;

View File

@ -0,0 +1,3 @@
--ssl-key=
--ssl-cert=
--ssl-ca=

View File

@ -0,0 +1,29 @@
call mtr.add_suppression('failed to read private_key.pem: 2 "No such file or directory"');
show status like 'caching_sha2_password%';
Variable_name Value
Caching_sha2_password_rsa_public_key
create user test1@'%' identified via caching_sha2_password using PASSWORD('pwd');
create user test2@'%' identified via caching_sha2_password;
show grants for test2@'%';
Grants for test2@%
GRANT USAGE ON *.* TO `test2`@`%` IDENTIFIED VIA caching_sha2_password
create procedure checkme() sql security invoker
select user(), current_user(), variable_value > '' as 'have_ssl'
from information_schema.session_status
where variable_name='ssl_cipher';
grant execute on test.* to test1@'%', test2@'%';
# mysql -utest1 -ppwd --disable-ssl-verify-server-cert -e "call test.checkme()"
user() current_user() have_ssl
test1@localhost test1@% 1
# mysql -utest1 -pwrong_pwd --disable-ssl-verify-server-cert -e "call test.checkme()"
ERROR 1045 (28000): Access denied for user 'test1'@'localhost' (using password: YES)
# mysql -utest2 --disable-ssl-verify-server-cert -e "call test.checkme()"
user() current_user() have_ssl
test2@localhost test2@% 1
# mysql -utest2 -ppwd --disable-ssl-verify-server-cert -e "call test.checkme()"
ERROR 1045 (28000): Access denied for user 'test2'@'localhost' (using password: YES)
# mysql -utest1 -ppwd --ssl-verify-server-cert -e "call test.checkme()"
ERROR 2026 (HY000): TLS/SSL error: Certificate verification failure: The certificate is NOT trusted.
drop procedure checkme;
drop user test1@'%';
drop user test2@'%';

View File

@ -0,0 +1,27 @@
source init.inc;
let MYSQL=$MYSQL --protocol tcp;
if ($MARIADB_UPGRADE_EXE) { # windows
# see ssl_autoverify.test
let MYSQL=$MYSQL --host=127.0.0.2;
}
--echo # mysql -utest1 -ppwd --disable-ssl-verify-server-cert -e "call test.checkme()"
--exec $MYSQL -utest1 -ppwd --disable-ssl-verify-server-cert -e "call test.checkme()" 2>&1
--echo # mysql -utest1 -pwrong_pwd --disable-ssl-verify-server-cert -e "call test.checkme()"
--error 1
--exec $MYSQL -utest1 -pwrong_pwd --disable-ssl-verify-server-cert -e "call test.checkme()" 2>&1
--echo # mysql -utest2 --disable-ssl-verify-server-cert -e "call test.checkme()"
--exec $MYSQL -utest2 --disable-ssl-verify-server-cert -e "call test.checkme()" 2>&1
--echo # mysql -utest2 -ppwd --disable-ssl-verify-server-cert -e "call test.checkme()"
--error 1
--exec $MYSQL -utest2 -ppwd --disable-ssl-verify-server-cert -e "call test.checkme()" 2>&1
--echo # mysql -utest1 -ppwd --ssl-verify-server-cert -e "call test.checkme()"
--error 1
--exec $MYSQL -utest1 -ppwd --ssl-verify-server-cert -e "call test.checkme()" 2>&1
source fini.inc;

View File

@ -0,0 +1,39 @@
call mtr.add_suppression('failed to read private_key.pem: 2 "No such file or directory"');
show status like 'caching_sha2_password%';
Variable_name Value
Caching_sha2_password_rsa_public_key
create user test1@'%' identified via caching_sha2_password using PASSWORD('pwd');
create user test2@'%' identified via caching_sha2_password;
show grants for test2@'%';
Grants for test2@%
GRANT USAGE ON *.* TO `test2`@`%` IDENTIFIED VIA caching_sha2_password
create procedure checkme() sql security invoker
select user(), current_user(), variable_value > '' as 'have_ssl'
from information_schema.session_status
where variable_name='ssl_cipher';
grant execute on test.* to test1@'%', test2@'%';
connect con1, localhost,test1,pwd,,,,TCP SSL;
call checkme();
user() current_user() have_ssl
test1@localhost test1@% 1
disconnect con1;
connect con2, localhost,test1,pwd,,,,TCP SSL;
call checkme();
user() current_user() have_ssl
test1@localhost test1@% 1
disconnect con2;
connect(localhost,test1,wrong_pwd,test,MASTER_MYPORT,MASTER_MYSOCK);
connect con3, localhost,test1,wrong_pwd,,,,TCP SSL;
ERROR 28000: Access denied for user 'test1'@'localhost' (using password: YES)
connect con4, localhost,test2,,,,,TCP SSL;
call checkme();
user() current_user() have_ssl
test2@localhost test2@% 1
disconnect con4;
connect(localhost,test2 pwd,,test,MASTER_MYPORT,MASTER_MYSOCK);
connect con5, localhost,test2 pwd,,,,,TCP SSL;
ERROR 28000: Access denied for user 'test2 pwd'@'localhost' (using password: NO)
connection default;
drop procedure checkme;
drop user test1@'%';
drop user test2@'%';

View File

@ -0,0 +1,21 @@
source init.inc;
connect con1, localhost,test1,pwd,,,,TCP SSL;
call checkme();
disconnect con1;
connect con2, localhost,test1,pwd,,,,TCP SSL;
call checkme();
disconnect con2;
replace_result $MASTER_MYSOCK MASTER_MYSOCK $MASTER_MYPORT MASTER_MYPORT;
error ER_ACCESS_DENIED_ERROR;
connect con3, localhost,test1,wrong_pwd,,,,TCP SSL;
connect con4, localhost,test2,,,,,TCP SSL;
call checkme();
disconnect con4;
replace_result $MASTER_MYSOCK MASTER_MYSOCK $MASTER_MYPORT MASTER_MYPORT;
error ER_ACCESS_DENIED_ERROR;
connect con5, localhost,test2 pwd,,,,,TCP SSL;
connection default;
source fini.inc;

View File

@ -0,0 +1,5 @@
package My::Suite::AuthSHA2;
@ISA = qw(My::Suite);
return "Not run for embedded server" if $::opt_embedded_server;
sub is_default { 1 }
bless { };

View File

@ -0,0 +1,159 @@
call mtr.add_suppression('failed to read private_key.pem: 2 "No such file or directory"');
call mtr.add_suppression('Authentication requires either RSA keys or secure transport');
call mtr.add_suppression('failed to read private_key.pem: 2 "No such file or directory"');
show status like 'caching_sha2_password%';
Variable_name Value
Caching_sha2_password_rsa_public_key
create user test1@'%' identified via caching_sha2_password using PASSWORD('pwd');
create user test2@'%' identified via caching_sha2_password;
show grants for test2@'%';
Grants for test2@%
GRANT USAGE ON *.* TO `test2`@`%` IDENTIFIED VIA caching_sha2_password
create procedure checkme() sql security invoker
select user(), current_user(), variable_value > '' as 'have_ssl'
from information_schema.session_status
where variable_name='ssl_cipher';
grant execute on test.* to test1@'%', test2@'%';
select * from information_schema.system_variables where variable_name like 'caching_sha2_password%' order by 1;
VARIABLE_NAME CACHING_SHA2_PASSWORD_AUTO_GENERATE_RSA_KEYS
SESSION_VALUE NULL
GLOBAL_VALUE OFF
GLOBAL_VALUE_ORIGIN COMMAND-LINE
DEFAULT_VALUE ON
VARIABLE_SCOPE GLOBAL
VARIABLE_TYPE BOOLEAN
VARIABLE_COMMENT Auto generate RSA keys at server startup if key paths are not explicitly set and key files are not present at their default locations
NUMERIC_MIN_VALUE NULL
NUMERIC_MAX_VALUE NULL
NUMERIC_BLOCK_SIZE NULL
ENUM_VALUE_LIST OFF,ON
READ_ONLY YES
COMMAND_LINE_ARGUMENT OPTIONAL
GLOBAL_VALUE_PATH NULL
VARIABLE_NAME CACHING_SHA2_PASSWORD_DIGEST_ROUNDS
SESSION_VALUE NULL
GLOBAL_VALUE 5000
GLOBAL_VALUE_ORIGIN COMPILE-TIME
DEFAULT_VALUE 5000
VARIABLE_SCOPE GLOBAL
VARIABLE_TYPE INT UNSIGNED
VARIABLE_COMMENT Number of SHA2 rounds to be performed when computing a password hash
NUMERIC_MIN_VALUE 5000
NUMERIC_MAX_VALUE 4095000
NUMERIC_BLOCK_SIZE 1
ENUM_VALUE_LIST NULL
READ_ONLY YES
COMMAND_LINE_ARGUMENT REQUIRED
GLOBAL_VALUE_PATH NULL
VARIABLE_NAME CACHING_SHA2_PASSWORD_PRIVATE_KEY_PATH
SESSION_VALUE NULL
GLOBAL_VALUE private_key.pem
GLOBAL_VALUE_ORIGIN COMPILE-TIME
DEFAULT_VALUE private_key.pem
VARIABLE_SCOPE GLOBAL
VARIABLE_TYPE VARCHAR
VARIABLE_COMMENT A path to the private RSA key used for authentication
NUMERIC_MIN_VALUE NULL
NUMERIC_MAX_VALUE NULL
NUMERIC_BLOCK_SIZE NULL
ENUM_VALUE_LIST NULL
READ_ONLY YES
COMMAND_LINE_ARGUMENT REQUIRED
GLOBAL_VALUE_PATH NULL
VARIABLE_NAME CACHING_SHA2_PASSWORD_PUBLIC_KEY_PATH
SESSION_VALUE NULL
GLOBAL_VALUE public_key.pem
GLOBAL_VALUE_ORIGIN COMPILE-TIME
DEFAULT_VALUE public_key.pem
VARIABLE_SCOPE GLOBAL
VARIABLE_TYPE VARCHAR
VARIABLE_COMMENT A path to the public RSA key used for authentication
NUMERIC_MIN_VALUE NULL
NUMERIC_MAX_VALUE NULL
NUMERIC_BLOCK_SIZE NULL
ENUM_VALUE_LIST NULL
READ_ONLY YES
COMMAND_LINE_ARGUMENT REQUIRED
GLOBAL_VALUE_PATH NULL
create user test3@'%' identified via caching_sha2_password using 'pwd';
ERROR HY000: Password hash should be 70 characters long
create user test3@'%' identified via caching_sha2_password using '0000000000000000000000000000000000000000000000000000000000000000000000';
ERROR HY000: Invalid password hash
connect(localhost,test1,pwd,test,MASTER_MYPORT,MASTER_MYSOCK);
connect con1, localhost,test1,pwd,,,,TCP NOSSL;
ERROR HY000: Couldn't read RSA public key from server
connect(localhost,test1,wrong_pwd,test,MASTER_MYPORT,MASTER_MYSOCK);
connect con3, localhost,test1,wrong_pwd,,,,TCP NOSSL;
ERROR HY000: Couldn't read RSA public key from server
connect con4, localhost,test2,,,,,TCP NOSSL;
call checkme();
user() current_user() have_ssl
test2@localhost test2@% 0
disconnect con4;
connect(localhost,test2 pwd,,test,MASTER_MYPORT,MASTER_MYSOCK);
connect con5, localhost,test2 pwd,,,,,TCP NOSSL;
ERROR 28000: Access denied for user 'test2 pwd'@'localhost' (using password: NO)
connection default;
# restart: --caching_sha2_password-auto_generate_rsa_keys
select length(variable_value) from information_schema.global_status
where variable_name like 'caching_sha2_password%';
length(variable_value)
451
# restart: --caching_sha2_password-auto_generate_rsa_keys
select variable_value="$pubkey" as 'key did not change'
from information_schema.global_status
where variable_name like 'caching_sha2_password%';
key did not change
1
connect con1, localhost,test1,pwd,,,,TCP NOSSL;
call checkme();
user() current_user() have_ssl
test1@localhost test1@% 0
disconnect con1;
connect con2, localhost,test1,pwd,,,,TCP NOSSL;
call checkme();
user() current_user() have_ssl
test1@localhost test1@% 0
disconnect con2;
connect(localhost,test1,wrong_pwd,test,MASTER_MYPORT,MASTER_MYSOCK);
connect con3, localhost,test1,wrong_pwd,,,,TCP NOSSL;
ERROR 28000: Access denied for user 'test1'@'localhost' (using password: YES)
connect con4, localhost,test2,,,,,TCP NOSSL;
call checkme();
user() current_user() have_ssl
test2@localhost test2@% 0
disconnect con4;
connect(localhost,test2 pwd,,test,MASTER_MYPORT,MASTER_MYSOCK);
connect con5, localhost,test2 pwd,,,,,TCP NOSSL;
ERROR 28000: Access denied for user 'test2 pwd'@'localhost' (using password: NO)
connection default;
create user u1@localhost identified via caching_sha2_password using '$A$005$5dx;X)z |kX]\ZNx7QTrl0oTy2C0/f4bggQMFIDnSDeZ7koLoO417jc9D';
create user u2@localhost identified via caching_sha2_password using '$A$005$dL\Zq]<7d[YAbk }x!;^.qMuuUUBmB5aF7x7GsAKZzpb24p94NCCs8qPgwAvwc1';
create user u3@localhost identified via caching_sha2_password using '$A$005$ L9\ZKiwT''=%dMoqrPGFbywI9G8NecJqiy9D04S2abTLRvD32powG8nIxI9';
grant execute on test.* to u1@localhost, u2@localhost, u3@localhost;
connect u1,localhost,u1,abcd,,,,TCP NOSSL;
call checkme();
user() current_user() have_ssl
u1@localhost u1@localhost 0
disconnect u1;
connect u2,localhost,u2,efghi,,,,TCP NOSSL;
call checkme();
user() current_user() have_ssl
u2@localhost u2@localhost 0
disconnect u2;
connect u3,localhost,u3,xyz,,,,TCP NOSSL;
call checkme();
user() current_user() have_ssl
u3@localhost u3@localhost 0
disconnect u3;
connection default;
drop user u1@localhost;
drop user u2@localhost;
drop user u3@localhost;
# restart
show status like 'caching_sha2_password%';
Variable_name Value
Caching_sha2_password_rsa_public_key
drop procedure checkme;
drop user test1@'%';
drop user test2@'%';

View File

@ -0,0 +1,93 @@
call mtr.add_suppression('failed to read private_key.pem: 2 "No such file or directory"');
call mtr.add_suppression('Authentication requires either RSA keys or secure transport');
source include/require_openssl_client.inc;
source init.inc;
query_vertical select * from information_schema.system_variables where variable_name like 'caching_sha2_password%' order by 1;
--error ER_PASSWD_LENGTH
create user test3@'%' identified via caching_sha2_password using 'pwd';
--error ER_PASSWD_LENGTH
create user test3@'%' identified via caching_sha2_password using '0000000000000000000000000000000000000000000000000000000000000000000000';
replace_result $MASTER_MYSOCK MASTER_MYSOCK $MASTER_MYPORT MASTER_MYPORT;
error 2061;
connect con1, localhost,test1,pwd,,,,TCP NOSSL;
replace_result $MASTER_MYSOCK MASTER_MYSOCK $MASTER_MYPORT MASTER_MYPORT;
error 2061;
connect con3, localhost,test1,wrong_pwd,,,,TCP NOSSL;
connect con4, localhost,test2,,,,,TCP NOSSL;
call checkme();
disconnect con4;
replace_result $MASTER_MYSOCK MASTER_MYSOCK $MASTER_MYPORT MASTER_MYPORT;
error ER_ACCESS_DENIED_ERROR;
connect con5, localhost,test2 pwd,,,,,TCP NOSSL;
connection default;
let $restart_parameters= --caching_sha2_password-auto_generate_rsa_keys;
source include/restart_mysqld.inc;
select length(variable_value) from information_schema.global_status
where variable_name like 'caching_sha2_password%';
let pubkey=`select variable_value from information_schema.global_status
where variable_name like 'caching_sha2_password%'`;
let $restart_parameters= --caching_sha2_password-auto_generate_rsa_keys;
source include/restart_mysqld.inc;
evalp select variable_value="$pubkey" as 'key did not change'
from information_schema.global_status
where variable_name like 'caching_sha2_password%';
# again, this time with keys
connect con1, localhost,test1,pwd,,,,TCP NOSSL;
call checkme();
disconnect con1;
connect con2, localhost,test1,pwd,,,,TCP NOSSL;
call checkme();
disconnect con2;
replace_result $MASTER_MYSOCK MASTER_MYSOCK $MASTER_MYPORT MASTER_MYPORT;
error ER_ACCESS_DENIED_ERROR;
connect con3, localhost,test1,wrong_pwd,,,,TCP NOSSL;
connect con4, localhost,test2,,,,,TCP NOSSL;
call checkme();
disconnect con4;
replace_result $MASTER_MYSOCK MASTER_MYSOCK $MASTER_MYPORT MASTER_MYPORT;
error ER_ACCESS_DENIED_ERROR;
connect con5, localhost,test2 pwd,,,,,TCP NOSSL;
connection default;
#
# Compatibility with MySQL password hashes
#
create user u1@localhost identified via caching_sha2_password using '$A$005$5dx;X)z |kX]\ZNx7QTrl0oTy2C0/f4bggQMFIDnSDeZ7koLoO417jc9D';
create user u2@localhost identified via caching_sha2_password using '$A$005$dL\Zq]<7d[YAbk }x!;^.qMuuUUBmB5aF7x7GsAKZzpb24p94NCCs8qPgwAvwc1';
create user u3@localhost identified via caching_sha2_password using '$A$005$ L9\ZKiwT''=%dMoqrPGFbywI9G8NecJqiy9D04S2abTLRvD32powG8nIxI9';
grant execute on test.* to u1@localhost, u2@localhost, u3@localhost;
connect u1,localhost,u1,abcd,,,,TCP NOSSL;
call checkme();
disconnect u1;
connect u2,localhost,u2,efghi,,,,TCP NOSSL;
call checkme();
disconnect u2;
connect u3,localhost,u3,xyz,,,,TCP NOSSL;
call checkme();
disconnect u3;
# cleanup
connection default;
drop user u1@localhost;
drop user u2@localhost;
drop user u3@localhost;
let datadir=`select @@datadir`;
remove_file $datadir/private_key.pem;
remove_file $datadir/public_key.pem;
let $restart_parameters=;
source include/restart_mysqld.inc;
show status like 'caching_sha2_password%';
source fini.inc;

View File

@ -0,0 +1,256 @@
/*
Copyright (c) 2025, 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 */
#if _WIN32
#include <io.h>
#define access _access
#define F_OK 0
#else
#include <unistd.h>
#endif
#include "mysql_sha2.h"
#include <mysql/plugin_auth.h>
#include <mysqld_error.h>
char *private_key_path, *public_key_path, public_key[1024]={0};
size_t public_key_len=0;
EVP_PKEY *private_key= 0;
static my_bool auto_generate_keys;
static unsigned int digest_rounds;
struct digest {
unsigned int iterations;
unsigned char salt[SCRAMBLE_LENGTH];
unsigned char crypted[SHA256CRYPT_LEN];
};
#define PASSWORD_LEN (SCRAMBLE_LENGTH + SHA256CRYPT_LEN + sizeof("$A$005$")-1)
#define ITERATION_MULTIPLIER 1000
static unsigned char request_public_key = '\2';
static unsigned char perform_full_authentication = '\4';
static void make_salt(unsigned char *to)
{
unsigned char *end= to + SCRAMBLE_LENGTH;
my_random_bytes(to, SCRAMBLE_LENGTH);
for (; to < end; to++)
*to = (*to % 90) + '$' + 1;
/* in MySQL: if (*to == '\0' || *to == '$') (*to)++; */
}
static int auth(MYSQL_PLUGIN_VIO *vio, MYSQL_SERVER_AUTH_INFO *info)
{
struct digest *authstr;
unsigned char to[SHA256CRYPT_LEN];
unsigned char scramble[SCRAMBLE_LENGTH + 1], *pkt;
int pkt_len;
MYSQL_PLUGIN_VIO_INFO vio_info;
unsigned char plain_text[1025];
size_t plain_text_len= sizeof(plain_text)-1;
make_salt(scramble);
scramble[SCRAMBLE_LENGTH]= 0;
if (vio->write_packet(vio, scramble, sizeof(scramble)))
return CR_ERROR;
if ((pkt_len= vio->read_packet(vio, &pkt)) < 0)
return CR_ERROR;
if (!pkt_len || (pkt_len == 1 && *pkt == 0)) /* sic! */
{
if (info->auth_string_length == 0)
return CR_OK;
return CR_AUTH_USER_CREDENTIALS;
}
info->password_used= PASSWORD_USED_YES;
if (info->auth_string_length == 0)
return CR_AUTH_USER_CREDENTIALS;
if (pkt_len != SHA256_DIGEST_LENGTH)
return CR_ERROR;
/*
TODO support caching: user@host -> plaintext password
but for now - request full auth unconditionally
*/
if (vio->write_packet(vio, &perform_full_authentication, 1))
return CR_ERROR;
if ((pkt_len= vio->read_packet(vio, &pkt)) <= 0)
return CR_ERROR;
vio->info(vio, &vio_info);
/* secure transport, as in MySQL. SSL is "secure" even if not verified */
if (vio_info.protocol == MYSQL_VIO_TCP && !vio_info.tls)
{
if (!private_key || !public_key_len)
{
my_printf_error(1, SELF ": Authentication requires either RSA keys "
"or secure transport", ME_ERROR_LOG_ONLY);
return CR_AUTH_PLUGIN_ERROR;
}
if (pkt_len == 1 && *pkt == request_public_key)
{
if (vio->write_packet(vio, (unsigned char *)public_key,
(int)public_key_len))
return CR_ERROR;
if ((pkt_len= vio->read_packet(vio, &pkt)) <= 0)
return CR_ERROR;
}
if (ssl_decrypt(private_key, pkt, pkt_len, plain_text, &plain_text_len))
return CR_ERROR;
for (size_t i=0; i < plain_text_len; i++)
plain_text[i]^= scramble[i % SCRAMBLE_LENGTH];
pkt= plain_text;
pkt_len= (int)plain_text_len;
}
/* now pkt contains plaintext password */
authstr= (struct digest*)info->auth_string;
sha256_crypt_r(pkt, pkt_len-1, authstr->salt, sizeof(authstr->salt),
to, authstr->iterations);
if (memcmp(to, authstr->crypted, SHA256CRYPT_LEN))
return CR_AUTH_USER_CREDENTIALS;
return CR_OK;
}
static int password_hash(const char *password, size_t password_length,
char *hash, size_t *hash_length)
{
struct digest authstr;
if (*hash_length < PASSWORD_LEN)
return 1;
if (!password_length)
return (int)(*hash_length= 0);
make_salt(authstr.salt);
sha256_crypt_r((unsigned char*)password, password_length,
authstr.salt, sizeof(authstr.salt),
authstr.crypted, digest_rounds);
*hash_length= my_snprintf(hash, *hash_length, "$A$%03X$%.20s%.43s",
digest_rounds/ITERATION_MULTIPLIER,
authstr.salt, authstr.crypted);
assert(*hash_length == PASSWORD_LEN);
return 0;
}
static int digest_to_binary(const char *hash, size_t hash_length,
unsigned char *out, size_t *out_length)
{
struct digest *authstr= (struct digest*)out;
assert(*out_length > sizeof(*authstr));
*out_length= sizeof(*authstr);
memset(out, 0, *out_length);
if (hash_length != PASSWORD_LEN)
{
my_printf_error(ER_PASSWD_LENGTH, "Password hash should be "
"%zu characters long", 0, PASSWORD_LEN);
return 1;
}
if (sscanf(hash, "$A$%X$%20c%43c", &authstr->iterations, authstr->salt,
authstr->crypted) < 3)
{
my_printf_error(ER_PASSWD_LENGTH, "Invalid password hash", 0);
return 1;
}
authstr->iterations*= ITERATION_MULTIPLIER;
return 0;
}
static struct st_mysql_auth info=
{
MYSQL_AUTHENTICATION_INTERFACE_VERSION,
SELF, auth, password_hash, digest_to_binary
};
static MYSQL_SYSVAR_STR(private_key_path, private_key_path, PLUGIN_VAR_READONLY,
"A path to the private RSA key used for authentication",
NULL, NULL, "private_key.pem");
static MYSQL_SYSVAR_STR(public_key_path, public_key_path, PLUGIN_VAR_READONLY,
"A path to the public RSA key used for authentication",
NULL, NULL, "public_key.pem");
static MYSQL_SYSVAR_BOOL(auto_generate_rsa_keys, auto_generate_keys,
PLUGIN_VAR_READONLY | PLUGIN_VAR_OPCMDARG,
"Auto generate RSA keys at server startup if key paths "
"are not explicitly set and key files are not present "
"at their default locations", NULL, NULL, 1);
static MYSQL_SYSVAR_UINT(digest_rounds, digest_rounds, PLUGIN_VAR_READONLY,
"Number of SHA2 rounds to be performed when computing a password hash",
NULL, NULL, 5000, 5000, 0xfff * ITERATION_MULTIPLIER, 1);
static int init_keys(void *p)
{
if (private_key_path == MYSQL_SYSVAR_NAME(private_key_path).def_val &&
public_key_path == MYSQL_SYSVAR_NAME(public_key_path).def_val &&
access(private_key_path, F_OK) && access(public_key_path, F_OK) &&
auto_generate_keys)
ssl_genkeys();
ssl_loadkeys();
return 0;
}
static int free_keys(void *p)
{
EVP_PKEY_free(private_key);
return 0;
}
static struct st_mysql_sys_var *sysvars[]=
{
MYSQL_SYSVAR(private_key_path),
MYSQL_SYSVAR(public_key_path),
MYSQL_SYSVAR(auto_generate_rsa_keys),
MYSQL_SYSVAR(digest_rounds),
NULL
};
static struct st_mysql_show_var status_variables[]=
{
{"rsa_public_key", public_key, SHOW_CHAR},
{NULL, NULL, 0}
};
maria_declare_plugin(auth_mysql_sha2)
{
MYSQL_AUTHENTICATION_PLUGIN,
&info,
SELF,
"Oracle Corporation, Sergei Golubchik",
"MySQL-compatible SHA2 authentication",
PLUGIN_LICENSE_GPL,
init_keys,
free_keys,
0x0100,
status_variables,
sysvars,
"1.0",
MariaDB_PLUGIN_MATURITY_GAMMA
}
maria_declare_plugin_end;

View File

@ -0,0 +1,41 @@
/*
Copyright (c) 2025, 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 <string.h>
#include <openssl/opensslv.h>
#include <openssl/sha.h>
#include <openssl/evp.h>
#include <mysql/plugin.h>
#define SELF "caching_sha2_password"
#define SHA256CRYPT_LEN 43
extern char *private_key_path, *public_key_path, public_key[1024];
extern size_t public_key_len;
extern EVP_PKEY *private_key;
int ssl_decrypt(EVP_PKEY *pkey, unsigned char *src, size_t srclen,
unsigned char *dst, size_t *dstlen);
int ssl_genkeys();
int ssl_loadkeys();
void sha256_crypt_r(const unsigned char *key, size_t key_len,
const unsigned char *salt, size_t salt_len,
unsigned char *buffer, size_t rounds);
#if OPENSSL_VERSION_NUMBER < 0x30000000L
EVP_PKEY *EVP_RSA_gen(unsigned int bits);
#endif

View File

@ -0,0 +1,39 @@
/*
Copyright (c) 2025, 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 "mysql_sha2.h"
#include <openssl/rsa.h>
#if OPENSSL_VERSION_NUMBER < 0x30000000L
EVP_PKEY *EVP_RSA_gen(unsigned int bits)
{
EVP_PKEY_CTX *ctx;
EVP_PKEY *pkey = NULL;
ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL);
if (!ctx)
return NULL;
if (EVP_PKEY_keygen_init(ctx) <= 0 ||
EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, bits) <= 0)
goto err;
EVP_PKEY_keygen(ctx, &pkey);
err:
EVP_PKEY_CTX_free(ctx);
return pkey;
}
#endif

View File

@ -0,0 +1,120 @@
/*
Copyright (c) 2025, 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 <my_alloca.h>
#include "mysql_sha2.h"
/* based on https://www.akkadia.org/drepper/SHA-crypt.txt */
/* SHA256-based Unix crypt implementation.
Released into the Public Domain by Ulrich Drepper <drepper@redhat.com>. */
void sha256_crypt_r(const unsigned char *key, size_t key_len,
const unsigned char *salt, size_t salt_len,
unsigned char *buffer, size_t rounds)
{
unsigned char tmp[SHA256_DIGEST_LENGTH];
unsigned char alt[SHA256_DIGEST_LENGTH];
size_t cnt;
static const char b64t[64] =
"./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
void *ctx = alloca(my_sha256_context_size());
unsigned char *p_bytes = alloca(key_len);
unsigned char *s_bytes = alloca(salt_len);
my_sha256_multi(alt, key, key_len, salt, salt_len, key, key_len, NULL);
my_sha256_init(ctx);
my_sha256_input(ctx, key, key_len);
my_sha256_input(ctx, salt, salt_len);
/* Add for every byte in the key one byte of the alternate sum. */
for (cnt = key_len; cnt > sizeof(alt); cnt -= sizeof(alt))
my_sha256_input(ctx, alt, sizeof(alt));
my_sha256_input(ctx, alt, cnt);
/* Take the binary representation of the length of the key and for every
1 add the alternate sum, for every 0 the key. */
for (cnt = key_len; cnt > 0; cnt >>= 1)
if ((cnt & 1) != 0)
my_sha256_input(ctx, alt, sizeof(alt));
else
my_sha256_input(ctx, key, key_len);
my_sha256_result(ctx, alt);
/* Start computing S byte sequence. */
my_sha256_init(ctx);
for (cnt = 0; cnt < 16u + alt[0]; ++cnt)
my_sha256_input(ctx, salt, salt_len);
my_sha256_result(ctx, tmp);
/* Create byte sequence S. */
for (cnt = salt_len; cnt >= sizeof(tmp); cnt -= sizeof(tmp))
memcpy(s_bytes + salt_len - cnt, tmp, sizeof(tmp));
memcpy(s_bytes + salt_len - cnt, tmp, cnt);
/* Start computing P byte sequence. */
my_sha256_init(ctx);
for (cnt = 0; cnt < key_len; ++cnt)
my_sha256_input(ctx, key, key_len);
my_sha256_result(ctx, tmp);
/* Create byte sequence P. */
for (cnt = key_len; cnt >= sizeof(tmp); cnt -= sizeof(tmp))
memcpy(p_bytes + key_len - cnt, tmp, sizeof(tmp));
memcpy(p_bytes + key_len - cnt, tmp, cnt);
/* Repeatedly run the collected hash value through SHA256 to burn
CPU cycles. */
for (cnt = 0; cnt < rounds; ++cnt)
{
my_sha256_init(ctx);
if ((cnt & 1) != 0)
my_sha256_input(ctx, p_bytes, key_len);
else
my_sha256_input(ctx, cnt ? tmp : alt, sizeof(tmp));
if (cnt % 3 != 0)
my_sha256_input(ctx, s_bytes, salt_len);
if (cnt % 7 != 0)
my_sha256_input(ctx, p_bytes, key_len);
if ((cnt & 1) != 0)
my_sha256_input(ctx, tmp, sizeof(tmp));
else
my_sha256_input(ctx, p_bytes, key_len);
my_sha256_result(ctx, tmp);
}
#define b64_from_24bit(B2, B1, B0, N) \
do { \
unsigned int w = ((B2) << 16) | ((B1) << 8) | (B0); \
int n = (N); \
while (n-- > 0) \
{ \
*buffer++ = b64t[w & 0x3f]; \
w >>= 6; \
} \
} while (0)
b64_from_24bit (tmp[0], tmp[10], tmp[20], 4);
b64_from_24bit (tmp[21], tmp[1], tmp[11], 4);
b64_from_24bit (tmp[12], tmp[22], tmp[2], 4);
b64_from_24bit (tmp[3], tmp[13], tmp[23], 4);
b64_from_24bit (tmp[24], tmp[4], tmp[14], 4);
b64_from_24bit (tmp[15], tmp[25], tmp[5], 4);
b64_from_24bit (tmp[6], tmp[16], tmp[26], 4);
b64_from_24bit (tmp[27], tmp[7], tmp[17], 4);
b64_from_24bit (tmp[18], tmp[28], tmp[8], 4);
b64_from_24bit (tmp[9], tmp[19], tmp[29], 4);
b64_from_24bit (0, tmp[31], tmp[30], 3); /* == 43 bytes in total */
}

View File

@ -0,0 +1,136 @@
/*
Copyright (c) 2025, 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 "mysql_sha2.h"
#include <errno.h>
#include <openssl/rsa.h>
#include <openssl/pem.h>
#include <openssl/err.h>
/* openssl rsautl -decrypt -inkey private_key.pem -in src -out dst */
int ssl_decrypt(EVP_PKEY *pkey, unsigned char *src, size_t srclen,
unsigned char *dst, size_t *dstlen)
{
int res;
EVP_PKEY_CTX *ctx= EVP_PKEY_CTX_new(pkey, NULL);
if (!ctx)
return 1;
res= EVP_PKEY_decrypt_init(ctx) <= 0 ||
EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_OAEP_PADDING) <= 0 ||
EVP_PKEY_decrypt(ctx, dst, dstlen, src, srclen) <= 0;
EVP_PKEY_CTX_free(ctx);
return res;
}
#define FILE_ERROR(op,file) \
do { \
my_printf_error(1, SELF ": failed to " op " %s: %iE", \
ME_ERROR_LOG_ONLY, file, errno); \
goto err; \
} while(0)
#define SSL_ERROR(op,file) \
do { \
unsigned long e= ERR_get_error(); \
my_printf_error(1, SELF ": failed to " op " %s: %lu - %s", \
ME_ERROR_LOG_ONLY, file, e, ERR_reason_error_string(e)); \
goto err; \
} while(0)
/*
openssl genrsa -out private_key.pem 2048
openssl rsa -in private_key.pem -pubout -out public_key.pem
*/
int ssl_genkeys()
{
#ifdef OPENSSL_IS_WOLFSSL
/*
doesn't have few functions from below and libmariadb doesn't support RSA
encryption anyway, so not worth bothering
*/
my_printf_error(1, SELF ": cannot auto-generate keys with WolfSSL",
ME_ERROR_LOG_ONLY);
return 1;
#else
EVP_PKEY *pkey;
FILE *f= NULL;
if (!(pkey= EVP_RSA_gen(2048)))
goto err;
if (!(f= fopen(private_key_path, "w")))
FILE_ERROR("write", private_key_path);
if (PEM_write_PrivateKey(f, pkey, NULL, NULL, 0, NULL, NULL) != 1)
SSL_ERROR("write", private_key_path);
fclose(f);
if (!(f= fopen(public_key_path, "w")))
FILE_ERROR("write", public_key_path);
if (PEM_write_PUBKEY(f, pkey) != 1)
SSL_ERROR("write", public_key_path);
fclose(f);
EVP_PKEY_free(pkey);
return 0;
err:
if (f)
fclose(f);
if (pkey)
EVP_PKEY_free(pkey);
return 1;
#endif
}
int ssl_loadkeys()
{
EVP_PKEY *pkey= 0;
FILE *f;
size_t len;
if (!(f= fopen(private_key_path, "r")))
FILE_ERROR("read", private_key_path);
if (!(pkey= PEM_read_PrivateKey(f, NULL, NULL, NULL)))
SSL_ERROR("read", private_key_path);
fclose(f);
if (!(f= fopen(public_key_path, "r")))
FILE_ERROR("read", public_key_path);
len= fread(public_key, 1, sizeof(public_key)-1, f);
if (!feof(f))
{
my_printf_error(1, SELF ": failed to read %s: larger than %zu",
ME_ERROR_LOG_ONLY, private_key_path, sizeof(public_key)-1);
goto err;
}
fclose(f);
public_key[len]= 0;
public_key_len= len;
private_key= pkey;
return 0;
err:
if (f)
fclose(f);
if (pkey)
EVP_PKEY_free(pkey);
return 1;
}

View File

@ -2395,6 +2395,7 @@ void mpvio_info(Vio *vio, MYSQL_PLUGIN_VIO_INFO *info)
info->protocol= addr.sa_family == AF_UNIX ?
MYSQL_VIO_SOCKET : MYSQL_VIO_TCP;
info->socket= (int)vio_fd(vio);
info->tls= 1;
return;
}
#ifdef _WIN32