Skip to content

Commit

Permalink
MDEV-31855 validate ssl certificates using client password
Browse files Browse the repository at this point in the history
if the client enabled --ssl-verify-server-cert, then
the server certificate is verified as follows:

* if --ssl-ca or --ssl-capath were specified, the cert must have
  a proper signature by the specified CA (or CA in the path)
  and the cert's hostname must match the server's hostname.
  If the cert isn't signed or a hostname is wrong - the
  connection is aborted.

* if MARIADB_OPT_TLS_PEER_FP was used and the fingerprint matches,
  the connection is allowed, if it doesn't match - aborted.

* If the connection uses unix socket or named pipes - it's allowed.
  (consistent with server's --require-secure-transport behavior)

otherwise the cert is still in doubt, we don't know if we can trust
it or there's an active MitM in progress.

* If the user has provided no password or the server requested an
  authentication plugin that sends the password in cleartext -
  the connection is aborted.

* Perform the authentication. If the server accepts the password,
  it'll send SHA2(scramble || password hash || cert fingerprint)
  with the OK packet.

* Verify the SHA2 digest, if it matches - the connection is allowed,
  otherwise it's aborted.
  • Loading branch information
vuvova committed Feb 4, 2024
1 parent 585c096 commit 1ef1bab
Show file tree
Hide file tree
Showing 13 changed files with 196 additions and 18 deletions.
1 change: 0 additions & 1 deletion debian/libmariadb-dev.install
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 1 addition & 2 deletions include/sslopt-longopts.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion libmariadb
2 changes: 1 addition & 1 deletion mysql-test/lib/generate-ssl-certs.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion mysql-test/main/mysqldump.result
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
8 changes: 4 additions & 4 deletions mysql-test/main/ssl_7937.test
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
14 changes: 14 additions & 0 deletions mysql-test/main/ssl_autoverify,win.rdiff
Original file line number Diff line number Diff line change
@@ -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
1 change: 1 addition & 0 deletions mysql-test/main/ssl_autoverify.opt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
--loose-enable-named-pipe
47 changes: 47 additions & 0 deletions mysql-test/main/ssl_autoverify.result
Original file line number Diff line number Diff line change
@@ -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;
84 changes: 84 additions & 0 deletions mysql-test/main/ssl_autoverify.test
Original file line number Diff line number Diff line change
@@ -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;
12 changes: 11 additions & 1 deletion sql/mysqld.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand All @@ -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;
Expand All @@ -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);
}
};

Expand All @@ -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)
Expand Down
1 change: 1 addition & 0 deletions sql/mysqld.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 */

/*
Expand Down
37 changes: 30 additions & 7 deletions sql/sql_acl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -201,15 +201,11 @@ class ACL_USER_PARAM
};


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)
{
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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");
Expand Down Expand Up @@ -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<uint>(strlen(thd->main_security_ctx.user)),
Expand Down

0 comments on commit 1ef1bab

Please sign in to comment.