MDEV-31855 validate ssl certificates using client password in the internal client

port the client-side implementation from C/C to the internal client.
add the test.
This commit is contained in:
Sergei Golubchik 2023-09-03 09:44:01 +02:00
parent 386df8793b
commit e0c30390a7
15 changed files with 240 additions and 60 deletions

View file

@ -289,8 +289,9 @@ typedef struct st_mysql
/* session-wide random string */
char scramble[SCRAMBLE_LENGTH+1];
my_bool auto_local_infile;
void *unused2, *unused3, *unused4;
void *unused2, *unused3;
MYSQL_FIELD *fields;
const char *tls_self_signed_error;
LIST *stmts; /* list of all statements */
const struct st_mysql_methods *methods;

View file

@ -56,7 +56,7 @@
#define MYSQL_CLIENT_reserved2 1
#define MYSQL_CLIENT_AUTHENTICATION_PLUGIN 2
#define MYSQL_CLIENT_AUTHENTICATION_PLUGIN_INTERFACE_VERSION 0x0100
#define MYSQL_CLIENT_AUTHENTICATION_PLUGIN_INTERFACE_VERSION 0x0101
#define MYSQL_CLIENT_MAX_PLUGINS 3
@ -96,6 +96,7 @@ struct st_mysql_client_plugin_AUTHENTICATION
{
MYSQL_CLIENT_PLUGIN_HEADER
int (*authenticate_user)(MYSQL_PLUGIN_VIO *vio, struct st_mysql *mysql);
int (*hash_password_bin)(struct st_mysql *mysql, unsigned char *hash, size_t *hash_length);
};
#include <mysql/auth_dialog_client.h>

View file

@ -22,6 +22,7 @@ struct st_mysql_client_plugin_AUTHENTICATION
{
int type; unsigned int interface_version; const char *name; const char *author; const char *desc; unsigned int version[3]; const char *license; void *mysql_api; int (*init)(char *, size_t, int, va_list); int (*deinit)(); int (*options)(const char *option, const void *);
int (*authenticate_user)(MYSQL_PLUGIN_VIO *vio, struct st_mysql *mysql);
int (*hash_password_bin)(struct st_mysql *mysql, unsigned char *hash, size_t *hash_length);
};
struct st_mysql;
typedef char *(*mysql_authentication_dialog_ask_t)(struct st_mysql *mysql,

View file

@ -7,3 +7,7 @@ FLUSH PRIVILEGES;
# xtrabackup move back
# restart
DROP USER backup_user;
#
# MDEV-31855 validate ssl certificates using client password in the internal client
#
# tcp ssl ssl-verify-server-cert

View file

@ -14,3 +14,10 @@ exec $XTRABACKUP --prepare --target-dir=$targetdir;
DROP USER backup_user;
rmdir $targetdir;
echo #;
echo # MDEV-31855 validate ssl certificates using client password in the internal client;
echo #;
# fails to connect, passwordless root
echo # tcp ssl ssl-verify-server-cert;
error 1;
exec $XTRABACKUP --protocol=tcp --user=root --port=$MASTER_MYPORT --backup --target-dir=$targetdir;

View file

@ -0,0 +1,4 @@
#
# MDEV-31855 validate ssl certificates using client password in the internal client
#
# socket ssl ssl-verify-server-cert

View file

@ -0,0 +1,10 @@
source include/not_windows.inc;
let $targetdir=$MYSQLTEST_VARDIR/tmp/backup;
echo #;
echo # MDEV-31855 validate ssl certificates using client password in the internal client;
echo #;
# connects fine, unix socket is a secure transport
echo # socket ssl ssl-verify-server-cert;
exec $XTRABACKUP --protocol=socket --user=root --socket=$MASTER_MYSOCK --backup --target-dir=$targetdir;
rmdir $targetdir;

View file

@ -75,14 +75,42 @@ Master_SSL_Cert = 'MYSQL_TEST_DIR/std_data/client-cert.pem'
Master_SSL_Key = 'MYSQL_TEST_DIR/std_data/client-key.pem'
include/check_slave_is_running.inc
connection master;
create user replssl@127.0.0.1 identified by "sslrepl";
grant replication slave on *.* to replssl@127.0.0.1 require ssl;
connection slave;
stop slave;
include/wait_for_slave_to_stop.inc
change master to
master_host="127.0.0.1",
master_user='replssl',
master_password="sslrepl",
master_ssl=1,
master_ssl_verify_server_cert=1,
master_ssl_ca ='',
master_ssl_cert='',
master_ssl_key='';
start slave;
include/wait_for_slave_to_start.inc
show tables;
Tables_in_test
t1
connection master;
drop table t1;
connection slave;
show tables;
Tables_in_test
include/stop_slave.inc
CHANGE MASTER TO
master_host="127.0.0.1",
master_user='root',
master_password='',
master_ssl_ca ='',
master_ssl_cert='',
master_ssl_key='',
master_ssl_verify_server_cert=0,
master_ssl=0;
connection master;
drop user replssl@127.0.0.1;
connection slave;
drop user replssl@127.0.0.1;
include/rpl_end.inc

View file

@ -95,19 +95,47 @@ select * from t1;
source include/show_slave_status.inc;
--source include/check_slave_is_running.inc
# ==== Clean up ====
# MDEV-31855 validate with master_password
connection master;
create user replssl@127.0.0.1 identified by "sslrepl";
grant replication slave on *.* to replssl@127.0.0.1 require ssl;
connection slave;
stop slave;
--source include/wait_for_slave_to_stop.inc
eval change master to
master_host="127.0.0.1",
master_user='replssl',
master_password="sslrepl",
master_ssl=1,
master_ssl_verify_server_cert=1,
master_ssl_ca ='',
master_ssl_cert='',
master_ssl_key='';
start slave;
--source include/wait_for_slave_to_start.inc
show tables;
connection master;
drop table t1;
sync_slave_with_master;
show tables;
# ==== Clean up ====
--source include/stop_slave.inc
CHANGE MASTER TO
master_host="127.0.0.1",
master_user='root',
master_password='',
master_ssl_ca ='',
master_ssl_cert='',
master_ssl_key='',
master_ssl_verify_server_cert=0,
master_ssl=0;
connection master;
drop user replssl@127.0.0.1;
connection slave;
drop user replssl@127.0.0.1;
--let $rpl_only_running_threads= 1
--source include/rpl_end.inc

View file

@ -13,7 +13,7 @@
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 */
#error see libmariadb/plugins/auth/ed25519.c instead
/************************** CLIENT *************************************/
#include <stdlib.h>

View file

@ -113,5 +113,6 @@ mysql_declare_client_plugin(AUTHENTICATION)
NULL,
NULL,
NULL,
test_plugin_client
test_plugin_client,
NULL
mysql_end_client_plugin;

View file

@ -250,5 +250,6 @@ mysql_declare_client_plugin(AUTHENTICATION)
NULL,
NULL,
NULL,
test_plugin_client
test_plugin_client,
NULL
mysql_end_client_plugin;

View file

@ -230,5 +230,6 @@ mysql_declare_client_plugin(AUTHENTICATION)
NULL,
NULL,
NULL,
test_plugin_client
test_plugin_client,
NULL
mysql_end_client_plugin;

View file

@ -167,6 +167,7 @@ static void mysql_close_free_options(MYSQL *mysql);
static void mysql_close_free(MYSQL *mysql);
static void mysql_prune_stmt_list(MYSQL *mysql);
static int cli_report_progress(MYSQL *mysql, char *packet, uint length);
static my_bool parse_ok_packet(MYSQL *mysql, ulong length);
CHARSET_INFO *default_client_charset_info = &my_charset_latin1;
@ -1571,8 +1572,7 @@ mysql_get_ssl_cipher(MYSQL *mysql __attribute__((unused)))
SYNOPSIS
ssl_verify_server_cert()
vio pointer to a SSL connected vio
server_hostname name of the server that we connected to
MYSQL mysql
errptr if we fail, we'll return (a pointer to a string
describing) the reason here
@ -1586,22 +1586,22 @@ mysql_get_ssl_cipher(MYSQL *mysql __attribute__((unused)))
#include <openssl/x509v3.h>
static int ssl_verify_server_cert(Vio *vio, const char* server_hostname, const char **errptr)
static int ssl_verify_server_cert(MYSQL *mysql, const char **errptr)
{
SSL *ssl;
X509 *server_cert= NULL;
int ret_validation= 1;
DBUG_ENTER("ssl_verify_server_cert");
DBUG_PRINT("enter", ("server_hostname: %s", server_hostname));
DBUG_PRINT("enter", ("server_hostname: %s", mysql->host));
if (!(ssl= (SSL*)vio->ssl_arg))
if (!(ssl= (SSL*)mysql->net.vio->ssl_arg))
{
*errptr= "No SSL pointer found";
goto error;
}
if (!server_hostname)
if (!mysql->host)
{
*errptr= "No server hostname supplied";
goto error;
@ -1613,21 +1613,29 @@ static int ssl_verify_server_cert(Vio *vio, const char* server_hostname, const c
goto error;
}
if (X509_V_OK != SSL_get_verify_result(ssl))
switch (SSL_get_verify_result(ssl))
{
case X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT: /* OpenSSL */
case X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN: /* OpenSSL */
case X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE: /* wolfSSL */
/*
If the caller have specified CA - it'll define whether the
cert is good. Otherwise we'll do more checks.
*/
ret_validation= (mysql->options.ssl_ca && mysql->options.ssl_ca[0]) ||
(mysql->options.ssl_capath && mysql->options.ssl_capath[0]);
mysql->tls_self_signed_error= *errptr= "SSL certificate is self-signed";
break;
case X509_V_OK:
ret_validation= X509_check_host(server_cert, mysql->host,
strlen(mysql->host), 0, 0) != 1 &&
X509_check_ip_asc(server_cert, mysql->host, 0) != 1;
*errptr= "SSL certificate validation failure";
break;
default:
*errptr= "Failed to verify the server certificate";
goto error;
break;
}
/*
We already know that the certificate exchanged was valid; the SSL library
handled that. Now we need to verify that the contents of the certificate
are what we expect.
*/
ret_validation= X509_check_host(server_cert, server_hostname,
strlen(server_hostname), 0, 0) != 1 &&
X509_check_ip_asc(server_cert, server_hostname, 0) != 1;
*errptr= "SSL certificate validation failure";
error:
X509_free(server_cert);
@ -1759,6 +1767,7 @@ C_MODE_END
typedef struct st_mysql_client_plugin_AUTHENTICATION auth_plugin_t;
static int client_mpvio_write_packet(struct st_plugin_vio*, const uchar*, int);
static int native_password_auth_client(MYSQL_PLUGIN_VIO *vio, MYSQL *mysql);
static int native_password_auth_hash(MYSQL *mysql, uchar *out, size_t *outlen);
static int old_password_auth_client(MYSQL_PLUGIN_VIO *vio, MYSQL *mysql);
static auth_plugin_t native_password_client_plugin=
@ -1768,13 +1777,14 @@ static auth_plugin_t native_password_client_plugin=
native_password_plugin_name,
"R.J.Silk, Sergei Golubchik",
"Native MySQL authentication",
{1, 0, 0},
{1, 0, 1},
"GPL",
NULL,
NULL,
NULL,
NULL,
native_password_auth_client
native_password_auth_client,
native_password_auth_hash
};
static auth_plugin_t old_password_client_plugin=
@ -1790,7 +1800,8 @@ static auth_plugin_t old_password_client_plugin=
NULL,
NULL,
NULL,
old_password_auth_client
old_password_auth_client,
NULL
};
@ -2018,6 +2029,7 @@ static int send_client_reply_packet(MCPVIO_EXT *mpvio,
{
MYSQL *mysql= mpvio->mysql;
NET *net= &mysql->net;
enum enum_vio_type vio_type= net->vio->type;
char *buff, *end;
size_t buff_size;
size_t connect_attrs_len=
@ -2052,7 +2064,7 @@ static int send_client_reply_packet(MCPVIO_EXT *mpvio,
if (mpvio->db)
mysql->client_flag|= CLIENT_CONNECT_WITH_DB;
if (mysql->net.vio->type == VIO_TYPE_NAMEDPIPE)
if (vio_type == VIO_TYPE_NAMEDPIPE)
{
mysql->server_capabilities&= ~CLIENT_SSL;
mysql->options.use_ssl= 0;
@ -2141,7 +2153,7 @@ static int send_client_reply_packet(MCPVIO_EXT *mpvio,
ER(CR_SSL_CONNECTION_ERROR), sslGetErrString(ssl_init_error));
goto error;
}
mysql->connector_fd= (unsigned char *) ssl_fd;
mysql->connector_fd= (uchar *) ssl_fd;
/* Connect to the server */
DBUG_PRINT("info", ("IO layer change in progress..."));
@ -2160,12 +2172,32 @@ static int send_client_reply_packet(MCPVIO_EXT *mpvio,
/* Verify server cert */
if ((mysql->options.extension && mysql->options.extension->tls_verify_server_cert) &&
ssl_verify_server_cert(net->vio, mysql->host, &cert_error))
ssl_verify_server_cert(mysql, &cert_error))
{
set_mysql_extended_error(mysql, CR_SSL_CONNECTION_ERROR, unknown_sqlstate,
ER(CR_SSL_CONNECTION_ERROR), cert_error);
goto error;
}
if (mysql->tls_self_signed_error)
{
/*
If the transport is secure (see opt_require_secure_transport) we
allow a self-signed cert as we know it came from the server.
If no password or plugin uses insecure protocol - refuse the cert.
Otherwise one last cert check after auth.
*/
if (vio_type == VIO_TYPE_SOCKET)
mysql->tls_self_signed_error= 0;
else if (!mysql->passwd || !mysql->passwd[0] ||
!mpvio->plugin->hash_password_bin)
{
set_mysql_extended_error(mysql, CR_SSL_CONNECTION_ERROR, unknown_sqlstate,
ER(CR_SSL_CONNECTION_ERROR), mysql->tls_self_signed_error);
goto error;
}
}
}
#endif /* HAVE_OPENSSL */
@ -2520,6 +2552,14 @@ int run_plugin_auth(MYSQL *mysql, char *data, uint data_len,
auth_plugin_name, MYSQL_CLIENT_AUTHENTICATION_PLUGIN)))
DBUG_RETURN (1);
/* refuse insecure plugin if TLS is in doubt */
if (mysql->tls_self_signed_error && !auth_plugin->hash_password_bin)
{
set_mysql_extended_error(mysql, CR_SSL_CONNECTION_ERROR, unknown_sqlstate,
ER(CR_SSL_CONNECTION_ERROR), mysql->tls_self_signed_error);
DBUG_RETURN (1);
}
mpvio.plugin= auth_plugin;
res= auth_plugin->authenticate_user((struct st_plugin_vio *)&mpvio, mysql);
@ -2541,7 +2581,7 @@ int run_plugin_auth(MYSQL *mysql, char *data, uint data_len,
if (res != CR_OK_HANDSHAKE_COMPLETE)
{
/* Read what server thinks about out new auth message report */
if (cli_safe_read(mysql) == packet_error)
if ((pkt_length= cli_safe_read(mysql)) == packet_error)
{
if (mysql->net.last_errno == CR_SERVER_LOST)
set_mysql_extended_error(mysql, CR_SERVER_LOST, unknown_sqlstate,
@ -2556,7 +2596,43 @@ int run_plugin_auth(MYSQL *mysql, char *data, uint data_len,
net->read_pos[0] should always be 0 here if the server implements
the protocol correctly
*/
DBUG_RETURN (mysql->net.read_pos[0] != 0);
if (mysql->net.read_pos[0] != 0)
DBUG_RETURN(1);
if (!mysql->tls_self_signed_error)
DBUG_RETURN(0);
/* Last attempt to validate the cert: compare cert info packet */
DBUG_ASSERT(mysql->options.use_ssl);
DBUG_ASSERT(mysql->net.vio->ssl_arg);
DBUG_ASSERT(mysql->options.extension->tls_verify_server_cert);
DBUG_ASSERT(!mysql->options.ssl_ca || !mysql->options.ssl_ca[0]);
DBUG_ASSERT(!mysql->options.ssl_capath || !mysql->options.ssl_capath[0]);
DBUG_ASSERT(auth_plugin->hash_password_bin);
DBUG_ASSERT(mysql->passwd[0]);
parse_ok_packet(mysql, pkt_length); /* set mysql->info */
if (mysql->info && mysql->info[0] == '\1')
{
uchar fp[128], buf[1024], digest[256/8];
size_t buflen= sizeof(buf);
uint fplen= sizeof(fp);
char *hexsig= mysql->info + 1, hexdigest[sizeof(digest)*2+1];
X509 *cert= SSL_get_peer_certificate((SSL*)mysql->net.vio->ssl_arg);
X509_digest(cert, EVP_sha256(), fp, &fplen);
X509_free(cert);
auth_plugin->hash_password_bin(mysql, buf, &buflen);
my_sha256_multi(digest, buf, buflen, mysql->scramble, SCRAMBLE_LENGTH,
fp, fplen, NULL);
mysql->info= NULL; /* no need to confuse the client with binary info */
octet2hex(hexdigest, digest, sizeof(digest));
if (strcmp(hexdigest, hexsig) == 0)
DBUG_RETURN(0); /* phew. self-signed certificate is validated! */
}
set_mysql_extended_error(mysql, CR_SSL_CONNECTION_ERROR, unknown_sqlstate,
ER(CR_SSL_CONNECTION_ERROR), mysql->tls_self_signed_error);
DBUG_RETURN(1);
}
@ -2647,6 +2723,7 @@ CLI_MYSQL_REAL_CONNECT(MYSQL *mysql,const char *host, const char *user,
mysql->methods= &client_methods;
mysql->client_flag=0; /* For handshake */
mysql->tls_self_signed_error= 0;
/* use default options */
if (mysql->options.my_cnf_file || mysql->options.my_cnf_group)
@ -3401,6 +3478,29 @@ void STDCALL mysql_close(MYSQL *mysql)
}
static my_bool parse_ok_packet(MYSQL *mysql, ulong length)
{
uchar *pos= mysql->net.read_pos + 1;
DBUG_ASSERT(pos[-1] == 0);
mysql->affected_rows= net_field_length_ll(&pos);
mysql->insert_id= net_field_length_ll(&pos);
if (protocol_41(mysql))
{
mysql->server_status=uint2korr(pos); pos+=2;
mysql->warning_count=uint2korr(pos); pos+=2;
}
else if (mysql->server_capabilities & CLIENT_TRANSACTIONS)
{
mysql->server_status=uint2korr(pos); pos+=2;
mysql->warning_count= 0;
}
if (pos < mysql->net.read_pos + length && net_field_length(&pos))
mysql->info=(char*) pos;
return 0;
}
static my_bool cli_read_query_result(MYSQL *mysql)
{
uchar *pos;
@ -3421,31 +3521,10 @@ static my_bool cli_read_query_result(MYSQL *mysql)
#ifdef MYSQL_CLIENT /* Avoid warn of unused labels*/
get_info:
#endif
pos=(uchar*) mysql->net.read_pos;
pos= mysql->net.read_pos;
if ((field_count= net_field_length(&pos)) == 0)
{
mysql->affected_rows= net_field_length_ll(&pos);
mysql->insert_id= net_field_length_ll(&pos);
DBUG_PRINT("info",("affected_rows: %lu insert_id: %lu",
(ulong) mysql->affected_rows,
(ulong) mysql->insert_id));
if (protocol_41(mysql))
{
mysql->server_status=uint2korr(pos); pos+=2;
mysql->warning_count=uint2korr(pos); pos+=2;
}
else if (mysql->server_capabilities & CLIENT_TRANSACTIONS)
{
/* MySQL 4.0 protocol */
mysql->server_status=uint2korr(pos); pos+=2;
mysql->warning_count= 0;
}
DBUG_PRINT("info",("status: %u warning_count: %u",
mysql->server_status, mysql->warning_count));
if (pos < mysql->net.read_pos+length && net_field_length(&pos))
mysql->info=(char*) pos;
DBUG_RETURN(0);
}
DBUG_RETURN(parse_ok_packet(mysql, length));
#ifdef MYSQL_CLIENT
if (field_count == NULL_LENGTH) /* LOAD DATA LOCAL INFILE */
{
@ -4123,6 +4202,22 @@ static int native_password_auth_client(MYSQL_PLUGIN_VIO *vio, MYSQL *mysql)
DBUG_RETURN(CR_OK);
}
static int native_password_auth_hash(MYSQL *mysql, uchar *out, size_t *out_length)
{
uchar hash_stage1[MY_SHA1_HASH_SIZE];
if (*out_length < MY_SHA1_HASH_SIZE)
return 1;
*out_length= MY_SHA1_HASH_SIZE;
my_sha1(hash_stage1, mysql->passwd, strlen(mysql->passwd));
my_sha1(out, (char*)hash_stage1, MY_SHA1_HASH_SIZE);
return 0;
}
/**
client authentication plugin that does old MySQL authentication
using an 8-byte (4.0-) scramble

View file

@ -171,9 +171,7 @@ add_plugin(MYSQL *mysql, struct st_mysql_client_plugin *plugin, void *dlhandle,
goto err1;
}
if (plugin->interface_version < plugin_version[plugin->type] ||
(plugin->interface_version >> 8) >
(plugin_version[plugin->type] >> 8))
if (plugin->interface_version >> 8 != plugin_version[plugin->type] >> 8)
{
errmsg= "Incompatible client plugin interface";
goto err1;