diff --git a/debian/mariadb-server.install b/debian/mariadb-server.install index 5889bd827ae..c125d171661 100644 --- a/debian/mariadb-server.install +++ b/debian/mariadb-server.install @@ -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 diff --git a/include/mysql/client_plugin.h.pp b/include/mysql/client_plugin.h.pp index ff35364bd44..37a7c5391d5 100644 --- a/include/mysql/client_plugin.h.pp +++ b/include/mysql/client_plugin.h.pp @@ -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 { diff --git a/include/mysql/plugin_auth.h b/include/mysql/plugin_auth.h index 3827db33431..84dc83b1ad3 100644 --- a/include/mysql/plugin_auth.h +++ b/include/mysql/plugin_auth.h @@ -27,7 +27,7 @@ #include -#define MYSQL_AUTHENTICATION_INTERFACE_VERSION 0x0202 +#define MYSQL_AUTHENTICATION_INTERFACE_VERSION 0x0203 #include diff --git a/include/mysql/plugin_auth.h.pp b/include/mysql/plugin_auth.h.pp index 4e9bfcfd103..374d9666136 100644 --- a/include/mysql/plugin_auth.h.pp +++ b/include/mysql/plugin_auth.h.pp @@ -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 { diff --git a/include/mysql/plugin_auth_common.h b/include/mysql/plugin_auth_common.h index cba0257fa27..c57850bb7d3 100644 --- a/include/mysql/plugin_auth_common.h +++ b/include/mysql/plugin_auth_common.h @@ -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; /** diff --git a/mysql-test/collections/buildbot_suites.bat b/mysql-test/collections/buildbot_suites.bat index b1a4a6f0561..61c1a5c09a9 100644 --- a/mysql-test/collections/buildbot_suites.bat +++ b/mysql-test/collections/buildbot_suites.bat @@ -8,6 +8,7 @@ plugins,^ mariabackup,^ roles,^ auth_gssapi,^ +mysql_sha2,^ query_response_time,^ rocksdb,^ sysschema diff --git a/mysql-test/include/require_openssl_client.inc b/mysql-test/include/require_openssl_client.inc index 9b19960041b..463c44fc8b2 100644 --- a/mysql-test/include/require_openssl_client.inc +++ b/mysql-test/include/require_openssl_client.inc @@ -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; } } diff --git a/mysql-test/main/mysqld--help.test b/mysql-test/main/mysqld--help.test index fc9a5504a6f..1125edeff58 100644 --- a/mysql-test/main/mysqld--help.test +++ b/mysql-test/main/mysqld--help.test @@ -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/; diff --git a/mysql-test/suite/plugins/r/auth_ed25519.result b/mysql-test/suite/plugins/r/auth_ed25519.result index 70208f5adab..49034cbdabf 100644 --- a/mysql-test/suite/plugins/r/auth_ed25519.result +++ b/mysql-test/suite/plugins/r/auth_ed25519.result @@ -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 diff --git a/plugin/auth_mysql_sha2/CMakeLists.txt b/plugin/auth_mysql_sha2/CMakeLists.txt new file mode 100644 index 00000000000..b9c1a0238db --- /dev/null +++ b/plugin/auth_mysql_sha2/CMakeLists.txt @@ -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}) diff --git a/plugin/auth_mysql_sha2/mysql-test/mysql_sha2/fini.inc b/plugin/auth_mysql_sha2/mysql-test/mysql_sha2/fini.inc new file mode 100644 index 00000000000..53b9fb49da2 --- /dev/null +++ b/plugin/auth_mysql_sha2/mysql-test/mysql_sha2/fini.inc @@ -0,0 +1,3 @@ +drop procedure checkme; +drop user test1@'%'; +drop user test2@'%'; diff --git a/plugin/auth_mysql_sha2/mysql-test/mysql_sha2/init.inc b/plugin/auth_mysql_sha2/mysql-test/mysql_sha2/init.inc new file mode 100644 index 00000000000..b7153ad1c8c --- /dev/null +++ b/plugin/auth_mysql_sha2/mysql-test/mysql_sha2/init.inc @@ -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@'%'; diff --git a/plugin/auth_mysql_sha2/mysql-test/mysql_sha2/init.opt b/plugin/auth_mysql_sha2/mysql-test/mysql_sha2/init.opt new file mode 100644 index 00000000000..c0d56670b6f --- /dev/null +++ b/plugin/auth_mysql_sha2/mysql-test/mysql_sha2/init.opt @@ -0,0 +1,3 @@ +--plugin-load-add=$AUTH_MYSQL_SHA2_SO +--loose-caching-sha2-password +--loose-disable-caching-sha2-password-auto-generate-rsa-keys diff --git a/plugin/auth_mysql_sha2/mysql-test/mysql_sha2/socket.opt b/plugin/auth_mysql_sha2/mysql-test/mysql_sha2/socket.opt new file mode 100644 index 00000000000..e534ae1eae5 --- /dev/null +++ b/plugin/auth_mysql_sha2/mysql-test/mysql_sha2/socket.opt @@ -0,0 +1 @@ +--loose-enable-named-pipe diff --git a/plugin/auth_mysql_sha2/mysql-test/mysql_sha2/socket.result b/plugin/auth_mysql_sha2/mysql-test/mysql_sha2/socket.result new file mode 100644 index 00000000000..84aaaab0d05 --- /dev/null +++ b/plugin/auth_mysql_sha2/mysql-test/mysql_sha2/socket.result @@ -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@'%'; diff --git a/plugin/auth_mysql_sha2/mysql-test/mysql_sha2/socket.test b/plugin/auth_mysql_sha2/mysql-test/mysql_sha2/socket.test new file mode 100644 index 00000000000..a2b21bc20d7 --- /dev/null +++ b/plugin/auth_mysql_sha2/mysql-test/mysql_sha2/socket.test @@ -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; diff --git a/plugin/auth_mysql_sha2/mysql-test/mysql_sha2/ssl_auto.opt b/plugin/auth_mysql_sha2/mysql-test/mysql_sha2/ssl_auto.opt new file mode 100644 index 00000000000..b3a45ea8eac --- /dev/null +++ b/plugin/auth_mysql_sha2/mysql-test/mysql_sha2/ssl_auto.opt @@ -0,0 +1,3 @@ +--ssl-key= +--ssl-cert= +--ssl-ca= diff --git a/plugin/auth_mysql_sha2/mysql-test/mysql_sha2/ssl_auto.result b/plugin/auth_mysql_sha2/mysql-test/mysql_sha2/ssl_auto.result new file mode 100644 index 00000000000..4d45bc89c50 --- /dev/null +++ b/plugin/auth_mysql_sha2/mysql-test/mysql_sha2/ssl_auto.result @@ -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@'%'; diff --git a/plugin/auth_mysql_sha2/mysql-test/mysql_sha2/ssl_auto.test b/plugin/auth_mysql_sha2/mysql-test/mysql_sha2/ssl_auto.test new file mode 100644 index 00000000000..2c297b5632f --- /dev/null +++ b/plugin/auth_mysql_sha2/mysql-test/mysql_sha2/ssl_auto.test @@ -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; diff --git a/plugin/auth_mysql_sha2/mysql-test/mysql_sha2/ssl_manual.result b/plugin/auth_mysql_sha2/mysql-test/mysql_sha2/ssl_manual.result new file mode 100644 index 00000000000..8faf8ea0c95 --- /dev/null +++ b/plugin/auth_mysql_sha2/mysql-test/mysql_sha2/ssl_manual.result @@ -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@'%'; diff --git a/plugin/auth_mysql_sha2/mysql-test/mysql_sha2/ssl_manual.test b/plugin/auth_mysql_sha2/mysql-test/mysql_sha2/ssl_manual.test new file mode 100644 index 00000000000..55270cdfbd8 --- /dev/null +++ b/plugin/auth_mysql_sha2/mysql-test/mysql_sha2/ssl_manual.test @@ -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; diff --git a/plugin/auth_mysql_sha2/mysql-test/mysql_sha2/suite.pm b/plugin/auth_mysql_sha2/mysql-test/mysql_sha2/suite.pm new file mode 100644 index 00000000000..b2b3007e7da --- /dev/null +++ b/plugin/auth_mysql_sha2/mysql-test/mysql_sha2/suite.pm @@ -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 { }; diff --git a/plugin/auth_mysql_sha2/mysql-test/mysql_sha2/tcp_nossl.result b/plugin/auth_mysql_sha2/mysql-test/mysql_sha2/tcp_nossl.result new file mode 100644 index 00000000000..d81c438d2cb --- /dev/null +++ b/plugin/auth_mysql_sha2/mysql-test/mysql_sha2/tcp_nossl.result @@ -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@'%'; diff --git a/plugin/auth_mysql_sha2/mysql-test/mysql_sha2/tcp_nossl.test b/plugin/auth_mysql_sha2/mysql-test/mysql_sha2/tcp_nossl.test new file mode 100644 index 00000000000..2f43121a19a --- /dev/null +++ b/plugin/auth_mysql_sha2/mysql-test/mysql_sha2/tcp_nossl.test @@ -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; diff --git a/plugin/auth_mysql_sha2/mysql_sha2.c b/plugin/auth_mysql_sha2/mysql_sha2.c new file mode 100644 index 00000000000..d9dbd75f6c9 --- /dev/null +++ b/plugin/auth_mysql_sha2/mysql_sha2.c @@ -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 +#define access _access +#define F_OK 0 +#else +#include +#endif +#include "mysql_sha2.h" +#include +#include + +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; diff --git a/plugin/auth_mysql_sha2/mysql_sha2.h b/plugin/auth_mysql_sha2/mysql_sha2.h new file mode 100644 index 00000000000..b07f7ac4b96 --- /dev/null +++ b/plugin/auth_mysql_sha2/mysql_sha2.h @@ -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 +#include +#include +#include +#include + +#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 diff --git a/plugin/auth_mysql_sha2/openssl1-compat.c b/plugin/auth_mysql_sha2/openssl1-compat.c new file mode 100644 index 00000000000..feaa45a7abc --- /dev/null +++ b/plugin/auth_mysql_sha2/openssl1-compat.c @@ -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 + +#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 diff --git a/plugin/auth_mysql_sha2/sha256crypt.c b/plugin/auth_mysql_sha2/sha256crypt.c new file mode 100644 index 00000000000..7e4a2f285ca --- /dev/null +++ b/plugin/auth_mysql_sha2/sha256crypt.c @@ -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 +#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 . */ + +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 */ +} diff --git a/plugin/auth_mysql_sha2/ssl_stuff.c b/plugin/auth_mysql_sha2/ssl_stuff.c new file mode 100644 index 00000000000..6938c2d50bd --- /dev/null +++ b/plugin/auth_mysql_sha2/ssl_stuff.c @@ -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 +#include +#include +#include + +/* 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; +} diff --git a/sql-common/client.c b/sql-common/client.c index d0d5b290cdc..4e8092399d5 100644 --- a/sql-common/client.c +++ b/sql-common/client.c @@ -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