diff --git a/debian/libmariadb-dev.install b/debian/libmariadb-dev.install index c015d59ae9b..48120822f81 100644 --- a/debian/libmariadb-dev.install +++ b/debian/libmariadb-dev.install @@ -20,7 +20,6 @@ usr/include/mariadb/mysql.h usr/include/mariadb/mysql/ usr/include/mariadb/mysql/client_plugin.h usr/include/mariadb/mysql/plugin_auth.h -usr/include/mariadb/mysql/plugin_auth_common.h usr/include/mariadb/mysql_com.h usr/include/mariadb/mysql_version.h usr/include/mariadb/mysqld_error.h diff --git a/include/sslopt-longopts.h b/include/sslopt-longopts.h index 63d1a42c1bd..15f4730b7d8 100644 --- a/include/sslopt-longopts.h +++ b/include/sslopt-longopts.h @@ -53,8 +53,7 @@ #ifdef MYSQL_CLIENT {"ssl-verify-server-cert", OPT_SSL_VERIFY_SERVER_CERT, - "Verify server's \"Common Name\" in its cert against hostname used " - "when connecting. This option is disabled by default.", + "Verify server's certificate to prevent man-in-the-middle attacks", &opt_ssl_verify_server_cert, &opt_ssl_verify_server_cert, 0, GET_BOOL, OPT_ARG, 0, 0, 0, 0, 0, 0}, #endif diff --git a/libmariadb b/libmariadb index 75ab6fb1746..fcef411ecb4 160000 --- a/libmariadb +++ b/libmariadb @@ -1 +1 @@ -Subproject commit 75ab6fb1746824648ce09805acbe535f9501df37 +Subproject commit fcef411ecb4a2c013e7aac655a96669474110225 diff --git a/mysql-test/lib/generate-ssl-certs.sh b/mysql-test/lib/generate-ssl-certs.sh index 57caf3b7a4e..050a5aa8491 100755 --- a/mysql-test/lib/generate-ssl-certs.sh +++ b/mysql-test/lib/generate-ssl-certs.sh @@ -27,7 +27,7 @@ openssl rsa -in server-key.pem -out server-key.pem # sign the server certificate with CA certificate openssl ca -keyfile cakey.pem -days 7300 -batch -cert cacert.pem -policy policy_anything -out server-cert.pem -in demoCA/server-req.pem -# server certificate with different validity period (MDEV-7598) +# server certificate with different validity period (MDEV-16266) openssl req -newkey rsa:4096 -keyout server-new-key.pem -out demoCA/server-new-req.pem -days 7301 -nodes -subj '/CN=server-new/C=FI/ST=Helsinki/L=Helsinki/O=MariaDB' openssl rsa -in server-new-key.pem -out server-new-key.pem openssl ca -keyfile cakey.pem -days 7301 -batch -cert cacert.pem -policy policy_anything -out server-new-cert.pem -in demoCA/server-new-req.pem diff --git a/mysql-test/main/mysqldump.result b/mysql-test/main/mysqldump.result index 4268c612b2f..293e880186c 100644 --- a/mysql-test/main/mysqldump.result +++ b/mysql-test/main/mysqldump.result @@ -6666,7 +6666,7 @@ drop table t1; mariadb-dump: --xml can't be used with --tab. select @@max_connections into @save_max_connections; set global max_connections=10; -mariadb-dump: Got error: 1040: "Too many connections" when trying to connect +mariadb-dump: Got error: 2002: "Received error packet before completion of TLS handshake. The authenticity of the following error cannot be verified: 1040 - Too many connections" when trying to connect set global max_connections=300; mariadb-dump: Too many connections set global max_connections=@save_max_connections; diff --git a/mysql-test/main/ssl_7937.test b/mysql-test/main/ssl_7937.test index be3d43ee4a0..17a4c2894c4 100644 --- a/mysql-test/main/ssl_7937.test +++ b/mysql-test/main/ssl_7937.test @@ -14,15 +14,15 @@ create procedure have_ssl() --disable_abort_on_error --echo mysql --ssl-ca=cacert.pem -e "call test.have_ssl()" ---exec $MYSQL --ssl-ca=$MYSQL_TEST_DIR/std_data/cacert.pem -e "call test.have_ssl()" 2>&1 +--exec $MYSQL --protocol tcp --ssl-ca=$MYSQL_TEST_DIR/std_data/cacert.pem -e "call test.have_ssl()" 2>&1 --echo mysql --ssl -e "call test.have_ssl()" ---exec $MYSQL --ssl -e "call test.have_ssl()" 2>&1 +--exec $MYSQL --protocol tcp --ssl -e "call test.have_ssl()" 2>&1 --echo mysql --ssl-ca=cacert.pem --ssl-verify-server-cert -e "call test.have_ssl()" ---exec $MYSQL --ssl-ca=$MYSQL_TEST_DIR/std_data/cacert.pem --ssl-verify-server-cert -e "call test.have_ssl()" 2>&1 +--exec $MYSQL --protocol tcp --ssl-ca=$MYSQL_TEST_DIR/std_data/cacert.pem --ssl-verify-server-cert -e "call test.have_ssl()" 2>&1 --echo mysql --ssl --ssl-verify-server-cert -e "call test.have_ssl()" --replace_regex /TLS\/SSL error.*certificate[^\n]*/TLS\/SSL error: Failed to verify the server certificate/ ---exec $MYSQL --ssl --ssl-verify-server-cert -e "call test.have_ssl()" 2>&1 +--exec $MYSQL --protocol tcp --ssl --ssl-verify-server-cert -e "call test.have_ssl()" 2>&1 --echo # --echo # MDEV-27105 --ssl option set as default for mariadb CLI diff --git a/mysql-test/main/ssl_autoverify,win.rdiff b/mysql-test/main/ssl_autoverify,win.rdiff new file mode 100644 index 00000000000..2477a42485b --- /dev/null +++ b/mysql-test/main/ssl_autoverify,win.rdiff @@ -0,0 +1,14 @@ +--- main/ssl_autoverify.reject ++++ main/ssl_autoverify.result +@@ -18,9 +18,9 @@ + yes + # mysql -uroot --ssl-verify-server-cert -e "select test.have_ssl()" + ERROR 2026 (HY000): TLS/SSL error: Failed to verify the server certificate +-# mysql --protocol socket -uroot --ssl-verify-server-cert -e "select test.have_ssl()" ++# mysql --protocol pipe -uroot --ssl-verify-server-cert -e "select test.have_ssl()" + test.have_ssl() +-yes ++no + # mysql -unative -pfoo --ssl-verify-server-cert -e "select test.have_ssl()" + test.have_ssl() + yes diff --git a/mysql-test/main/ssl_autoverify.opt b/mysql-test/main/ssl_autoverify.opt new file mode 100644 index 00000000000..e534ae1eae5 --- /dev/null +++ b/mysql-test/main/ssl_autoverify.opt @@ -0,0 +1 @@ +--loose-enable-named-pipe diff --git a/mysql-test/main/ssl_autoverify.result b/mysql-test/main/ssl_autoverify.result new file mode 100644 index 00000000000..5eabad8a098 --- /dev/null +++ b/mysql-test/main/ssl_autoverify.result @@ -0,0 +1,47 @@ +install soname 'auth_ed25519'; +install plugin three_attempts soname 'dialog_examples'; +create user native@'%' identified via mysql_native_password using password('foo'); +create user ed@'%' identified via ed25519 using password('bar'); +create user nohash@'%' identified via three_attempts using 'onetwothree'; +create user multi@'%' identified via mysql_native_password using password('pw1') +or ed25519 using password('pw2'); +grant all privileges on test.* to native@'%'; +grant all privileges on test.* to ed@'%'; +grant all privileges on test.* to nohash@'%'; +grant all privileges on test.* to multi@'%'; +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'); +# mysql -uroot --disable-ssl-verify-server-cert -e "select test.have_ssl()" +test.have_ssl() +yes +# mysql -uroot --ssl-verify-server-cert -e "select test.have_ssl()" +ERROR 2026 (HY000): TLS/SSL error: Failed to verify the server certificate +# mysql --protocol socket -uroot --ssl-verify-server-cert -e "select test.have_ssl()" +test.have_ssl() +yes +# mysql -unative -pfoo --ssl-verify-server-cert -e "select test.have_ssl()" +test.have_ssl() +yes +# mysql -ued -pbar --ssl-verify-server-cert -e "select test.have_ssl()" +test.have_ssl() +yes +# mysql -unohash -ponetwothree --disable-ssl-verify-server-cert -e "select test.have_ssl()" +test.have_ssl() +yes +# mysql -unohash -ponetwothree --ssl-verify-server-cert -e "select test.have_ssl()" +ERROR 2026 (HY000): TLS/SSL error: Failed to verify the server certificate +# mysql -umulti -ppw1 --ssl-verify-server-cert -e "select test.have_ssl()" +test.have_ssl() +yes +# mysql -umulti -ppw2 --ssl-verify-server-cert -e "select test.have_ssl()" +test.have_ssl() +yes +drop function have_ssl; +drop user native@'%'; +drop user ed@'%'; +drop user nohash@'%'; +drop user multi@'%'; +uninstall plugin ed25519; +uninstall plugin three_attempts; diff --git a/mysql-test/main/ssl_autoverify.test b/mysql-test/main/ssl_autoverify.test new file mode 100644 index 00000000000..8ade01483ca --- /dev/null +++ b/mysql-test/main/ssl_autoverify.test @@ -0,0 +1,84 @@ +source include/platform.inc; +source include/not_embedded.inc; +if (!$AUTH_ED25519_SO) { + skip No auth_ed25519 plugin; +} +if (!$DIALOG_EXAMPLES_SO) { + skip No dialog_examples plugin; +} + +install soname 'auth_ed25519'; +install plugin three_attempts soname 'dialog_examples'; + +create user native@'%' identified via mysql_native_password using password('foo'); +create user ed@'%' identified via ed25519 using password('bar'); +create user nohash@'%' identified via three_attempts using 'onetwothree'; +create user multi@'%' identified via mysql_native_password using password('pw1') + or ed25519 using password('pw2'); +grant all privileges on test.* to native@'%'; +grant all privileges on test.* to ed@'%'; +grant all privileges on test.* to nohash@'%'; +grant all privileges on test.* to multi@'%'; + +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'); + +# +# root user, no password, so cannot validate cert. +# +--echo # mysql -uroot --disable-ssl-verify-server-cert -e "select test.have_ssl()" +--exec $MYSQL --protocol tcp -uroot --disable-ssl-verify-server-cert -e "select test.have_ssl()" 2>&1 +--echo # mysql -uroot --ssl-verify-server-cert -e "select test.have_ssl()" +--replace_regex /TLS\/SSL error.*certificate[^\n]*/TLS\/SSL error: Failed to verify the server certificate/ +--error 1 +--exec $MYSQL --protocol tcp -uroot --ssl-verify-server-cert -e "select test.have_ssl()" 2>&1 +# +# unless using a secure transport, like unix_socket or named pipes +# +# note that SSL works over unix_socket, and it doesn't work over named pipes +# but the connection is allowed either way, as the transport is secure +# +let proto=socket; +if ($MTR_COMBINATION_WIN) { + let proto=pipe; +} +--echo # mysql --protocol $proto -uroot --ssl-verify-server-cert -e "select test.have_ssl()" +--exec $MYSQL --protocol $proto -uroot --ssl-verify-server-cert -e "select test.have_ssl()" 2>&1 +# +# mysql_native_password with password works fine +# +--echo # mysql -unative -pfoo --ssl-verify-server-cert -e "select test.have_ssl()" +--exec $MYSQL --protocol tcp -unative -pfoo --ssl-verify-server-cert -e "select test.have_ssl()" 2>&1 +# +# ed25519 with password works fine +# +--echo # mysql -ued -pbar --ssl-verify-server-cert -e "select test.have_ssl()" +--exec $MYSQL --protocol tcp -ued -pbar --ssl-verify-server-cert -e "select test.have_ssl()" 2>&1 +# +# three_attempts uses auth string as is, doesn't hash. +# so it's not safe over untrusted connection and thus cannot validate cert +# +--echo # mysql -unohash -ponetwothree --disable-ssl-verify-server-cert -e "select test.have_ssl()" +--exec $MYSQL --protocol tcp -unohash -ponetwothree --disable-ssl-verify-server-cert -e "select test.have_ssl()" 2>&1 +--echo # mysql -unohash -ponetwothree --ssl-verify-server-cert -e "select test.have_ssl()" +--replace_regex /TLS\/SSL error.*certificate[^\n]*/TLS\/SSL error: Failed to verify the server certificate/ +--error 1 +--exec $MYSQL --protocol tcp -unohash -ponetwothree --ssl-verify-server-cert -e "select test.have_ssl()" 2>&1 +# +# multi-auth case, both client and server must use +# the same plugin for cert validation +# +--echo # mysql -umulti -ppw1 --ssl-verify-server-cert -e "select test.have_ssl()" +--exec $MYSQL --protocol tcp -umulti -ppw1 --ssl-verify-server-cert -e "select test.have_ssl()" 2>&1 +--echo # mysql -umulti -ppw2 --ssl-verify-server-cert -e "select test.have_ssl()" +--exec $MYSQL --protocol tcp -umulti -ppw2 --ssl-verify-server-cert -e "select test.have_ssl()" 2>&1 + +drop function have_ssl; +drop user native@'%'; +drop user ed@'%'; +drop user nohash@'%'; +drop user multi@'%'; +uninstall plugin ed25519; +uninstall plugin three_attempts; diff --git a/sql/mysqld.cc b/sql/mysqld.cc index ec80d903c0e..6b004639c4b 100644 --- a/sql/mysqld.cc +++ b/sql/mysqld.cc @@ -4518,6 +4518,7 @@ struct SSL_ACCEPTOR_STATS long verify_depth; long zero; const char *session_cache_mode; + uchar fprint[256/8]; SSL_ACCEPTOR_STATS(): accept(),accept_good(),cache_size(),verify_mode(),verify_depth(),zero(), @@ -4527,7 +4528,8 @@ struct SSL_ACCEPTOR_STATS void init() { - DBUG_ASSERT(ssl_acceptor_fd !=0 && ssl_acceptor_fd->ssl_context != 0); + DBUG_ASSERT(ssl_acceptor_fd !=0); + DBUG_ASSERT(ssl_acceptor_fd->ssl_context != 0); SSL_CTX *ctx= ssl_acceptor_fd->ssl_context; accept= 0; accept_good= 0; @@ -4551,6 +4553,9 @@ struct SSL_ACCEPTOR_STATS default: session_cache_mode= "Unknown"; break; } + X509 *cert= SSL_CTX_get0_certificate(ctx); + uint fplen= sizeof(fprint); + X509_digest(cert, EVP_sha256(), fprint, &fplen); } }; @@ -4562,6 +4567,11 @@ void ssl_acceptor_stats_update(int sslaccept_ret) statistic_increment(ssl_acceptor_stats.accept_good,&LOCK_status); } +LEX_CUSTRING ssl_acceptor_fingerprint() +{ + return { ssl_acceptor_stats.fprint, sizeof(ssl_acceptor_stats.fprint) }; +} + static void init_ssl() { #if !defined(EMBEDDED_LIBRARY) diff --git a/sql/mysqld.h b/sql/mysqld.h index 2967de9d728..2ee87f0dae9 100644 --- a/sql/mysqld.h +++ b/sql/mysqld.h @@ -728,6 +728,7 @@ extern pthread_t signal_thread; #ifdef HAVE_OPENSSL extern struct st_VioSSLFd * ssl_acceptor_fd; +extern LEX_CUSTRING ssl_acceptor_fingerprint(); #endif /* HAVE_OPENSSL */ /* diff --git a/sql/sql_acl.cc b/sql/sql_acl.cc index fb86bfebacc..2a9777a0ae0 100644 --- a/sql/sql_acl.cc +++ b/sql/sql_acl.cc @@ -201,15 +201,11 @@ public: }; -class ACL_USER :public ACL_USER_BASE, - public ACL_USER_PARAM +class ACL_USER :public ACL_USER_BASE, public ACL_USER_PARAM { public: - ACL_USER() = default; - ACL_USER(THD *thd, const LEX_USER &combo, - const Account_options &options, - const privilege_t privileges); + ACL_USER(THD *, const LEX_USER &, const Account_options &, const privilege_t); ACL_USER *copy(MEM_ROOT *root) { @@ -14390,6 +14386,29 @@ static bool acl_check_ssl(THD *thd, const ACL_USER *acl_user) return 1; } +static void make_ssl_info(THD *thd, LEX_CSTRING salt, char *info) +{ +#ifdef HAVE_OPENSSL + uchar digest[256/8]; + if (!salt.length) + return; + + /* + mark that it's after-auth mysql->info version 1. + meaning, it contains sha2(salt, scramble, sha2_cert_fingerprint) + encoded in 64 lowercase letters 'a'..'p', one letter per 4 bits (0..15) + */ + *info++= 1; // Version 1 + + DBUG_ASSERT(thd->scramble[SCRAMBLE_LENGTH] == 0); + + LEX_CUSTRING fp= ssl_acceptor_fingerprint(); + my_sha256_multi(digest, salt.str, salt.length, thd->scramble, + (size_t)SCRAMBLE_LENGTH, fp.str, fp.length, NULL); + octet2hex(info, digest, sizeof(digest)); + +#endif +} static int do_auth_once(THD *thd, const LEX_CSTRING *auth_plugin_name, MPVIO_EXT *mpvio) @@ -14507,6 +14526,7 @@ bool acl_authenticate(THD *thd, uint com_change_user_pkt_len) { int res= CR_OK; MPVIO_EXT mpvio; + char ssl_info[256/4 + 2]= {0}; // '\1', SHA256 (1 char per 4 bits), '\0' enum enum_server_command command= com_change_user_pkt_len ? COM_CHANGE_USER : COM_CONNECT; DBUG_ENTER("acl_authenticate"); @@ -14844,7 +14864,10 @@ bool acl_authenticate(THD *thd, uint com_change_user_pkt_len) sctx->external_user= my_strdup(key_memory_MPVIO_EXT_auth_info, mpvio.auth_info.external_user, MYF(0)); - my_ok(thd); + if (initialized && !com_change_user_pkt_len) + make_ssl_info(thd, acl_user->auth[mpvio.curr_auth-1].salt, ssl_info); + + my_ok(thd, 0, 0, ssl_info[0] == '\1' ? ssl_info : NULL); PSI_CALL_set_thread_account (thd->main_security_ctx.user, static_cast(strlen(thd->main_security_ctx.user)),