From b481c0a494d8dfbb770154098f78a32877d5a4e6 Mon Sep 17 00:00:00 2001 From: Georg Richter Date: Mon, 9 Sep 2024 10:36:45 +0200 Subject: [PATCH] CONC-724: Added TLS verification callback support For testing purposes (the python3 dummy server can't handle further communication after TLS handshake succeeded) support for verification callback was added. my_bool callback(MYSQL *mysql, unsigned int *flags, my_bool verified) Parameter: - mysql connection handle for current connection - flags verification flags - verified true if callback was called after verification, otherwise false Return value: - False (0) to continue - True (1) to abort tls connection The callback function can be registered via mysql_optionsv(mysql, MARIADB_OPT_TLS_VERIFICATION_CALLBACK, callback); --- include/ma_common.h | 1 + include/mariadb_com.h | 2 +- include/mysql.h | 3 +- libmariadb/ma_tls.c | 12 ++++++- libmariadb/mariadb_lib.c | 3 ++ unittest/libmariadb/tls.c.in | 67 ++++++++++++++++++++++++++++++++++-- 6 files changed, 82 insertions(+), 6 deletions(-) diff --git a/include/ma_common.h b/include/ma_common.h index 980e6331..48b893fc 100644 --- a/include/ma_common.h +++ b/include/ma_common.h @@ -88,6 +88,7 @@ struct st_mysql_options_extension { void (*status_callback)(void *ptr, enum enum_mariadb_status_info type, ...); void *status_data; my_bool tls_allow_invalid_server_cert; + my_bool (*tls_verification_callback)(MYSQL *mysql, unsigned int *verification_flags, my_bool verified); }; typedef struct st_connection_handler diff --git a/include/mariadb_com.h b/include/mariadb_com.h index d1e9f2ba..63185dbe 100644 --- a/include/mariadb_com.h +++ b/include/mariadb_com.h @@ -297,7 +297,7 @@ typedef struct st_net { unsigned char reading_or_writing; char save_char; char unused_1; - my_bool tls_verify_status; + unsigned char tls_verify_status; my_bool compress; my_bool unused_2; char *unused_3; diff --git a/include/mysql.h b/include/mysql.h index e050fc55..fdf6d1ba 100644 --- a/include/mysql.h +++ b/include/mysql.h @@ -259,7 +259,8 @@ extern const char *SQLSTATE_UNKNOWN; MARIADB_OPT_RPL_REGISTER_REPLICA, MARIADB_OPT_STATUS_CALLBACK, MARIADB_OPT_SERVER_PLUGINS, - MARIADB_OPT_BULK_UNIT_RESULTS + MARIADB_OPT_BULK_UNIT_RESULTS, + MARIADB_OPT_TLS_VERIFICATION_CALLBACK }; enum mariadb_value { diff --git a/libmariadb/ma_tls.c b/libmariadb/ma_tls.c index 26951b4b..408f558e 100644 --- a/libmariadb/ma_tls.c +++ b/libmariadb/ma_tls.c @@ -119,7 +119,17 @@ int ma_pvio_tls_verify_server_cert(MARIADB_TLS *ctls, unsigned int flags) return 0; } - rc= ma_tls_verify_server_cert(ctls, flags); + if (mysql->options.extension->tls_verification_callback && + mysql->options.extension->tls_verification_callback(mysql, &flags, 0)) + rc= 1; + else { + rc= ma_tls_verify_server_cert(ctls, flags); + if (mysql->options.extension->tls_verification_callback && + mysql->options.extension->tls_verification_callback(mysql, &flags, 1)) + { + rc= 1; + } + } /* Set error messages */ if (!mysql->net.last_errno) diff --git a/libmariadb/mariadb_lib.c b/libmariadb/mariadb_lib.c index 5252648d..6ac20c94 100644 --- a/libmariadb/mariadb_lib.c +++ b/libmariadb/mariadb_lib.c @@ -3853,6 +3853,9 @@ mysql_optionsv(MYSQL *mysql,enum mysql_option option, ...) case MARIADB_OPT_BULK_UNIT_RESULTS: OPT_SET_EXTENDED_VALUE_INT(&mysql->options, bulk_unit_results, *(my_bool *)arg1); break; + case MARIADB_OPT_TLS_VERIFICATION_CALLBACK: + OPT_SET_EXTENDED_VALUE(&mysql->options, tls_verification_callback, arg1); + break; default: va_end(ap); SET_CLIENT_ERROR(mysql, CR_NOT_IMPLEMENTED, SQLSTATE_UNKNOWN, 0); diff --git a/unittest/libmariadb/tls.c.in b/unittest/libmariadb/tls.c.in index 1ca9545a..ade78892 100644 --- a/unittest/libmariadb/tls.c.in +++ b/unittest/libmariadb/tls.c.in @@ -42,11 +42,11 @@ with this program; if not, write to the Free Software Foundation, Inc., FAIL_IF(!(status & (flag)), (text));\ } -#define CHECK_NO_TLS_FLAGS(m)\ +#define CHECK_NO_TLS_FLAG(m, flag, text)\ {\ unsigned int status;\ mariadb_get_infov(mysql, MARIADB_TLS_VERIFY_STATUS, &status);\ - FAIL_IF(status), "Expected MARIADB_TLS_VERIFY_OK");\ + FAIL_IF((status & (flag)), (text));\ } my_bool auto_generated_cert= 0; @@ -141,7 +141,7 @@ static int test_start_tls_server(MYSQL *my __attribute__((unused))) snprintf(hostname, sizeof(hostname), "--host=%s", tls_dummy_host); snprintf(port, sizeof(port), "--port=%d", tls_dummy_port); - execlp("@Python3_EXECUTABLE@", "@Python3_EXECUTABLE@", "tls_server.py", hostname, port, NULL); + execlp("@Python3_EXECUTABLE@", "@Python3_EXECUTABLE@", "@CC_SOURCE_DIR@/unittest/libmariadb/tls_server.py", hostname, port, NULL); } #endif @@ -658,11 +658,72 @@ static int stop_tls_server(MYSQL *my __attribute__((unused))) return OK; } +my_bool tls_wildcard_callback(MYSQL *mysql, unsigned int *flags, my_bool verified) +{ + if (!verified) + { + free(mysql->host); + mysql->host= strdup("test.example.com"); + *flags= MARIADB_TLS_VERIFY_HOST; + return 0; + } + /* Indicate error, since the dummy server can't handle further client server + communication after TLS handshake */ + mysql->net.tls_verify_status|= MARIADB_TLS_VERIFY_ERROR; + my_set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, + ER(CR_SSL_CONNECTION_ERROR), + "Certificate verification aborted by callback"); + return 1; +} + +static int test_cert_wildcard(MYSQL *my __attribute((unused))) +{ + MYSQL *mysql= mysql_init(NULL); + if (set_tls_dummy_options("CMD:create_new=True commonName='*.example.com'")) + { + diag("Error when setting TLS options"); + return FAIL; + } + mysql_ssl_set(mysql, NULL, NULL, NULL, NULL, NULL); + mysql_optionsv(mysql, MARIADB_OPT_TLS_VERIFICATION_CALLBACK, tls_wildcard_callback); + + if (!my_test_connect(mysql, tls_dummy_host, "tlsuser", "foo", NULL, tls_dummy_port, NULL, 0, 0)) + { + CHECK_NO_TLS_FLAG(mysql, MARIADB_TLS_VERIFY_HOST, "Hostname verification didn't pass"); + CHECK_TLS_FLAGS(mysql, MARIADB_TLS_VERIFY_TRUST, "Self signed certificate expected"); + mysql_close(mysql); + } else { + mysql_close(mysql); + return FAIL; + } + + mysql= mysql_init(NULL); + if (set_tls_dummy_options("CMD:create_new=True commonName='*.noexample.com'")) + { + diag("Error when setting TLS options"); + return FAIL; + } + mysql_ssl_set(mysql, NULL, NULL, NULL, NULL, NULL); + mysql_optionsv(mysql, MARIADB_OPT_TLS_VERIFICATION_CALLBACK, tls_wildcard_callback); + + if (!my_test_connect(mysql, tls_dummy_host, "tlsuser", "foo", NULL, tls_dummy_port, NULL, 0, 0)) + { + CHECK_TLS_FLAGS(mysql, MARIADB_TLS_VERIFY_HOST, "Hostname verification passed with wrong wildcard"); + mysql_close(mysql); + } else { + mysql_close(mysql); + return FAIL; + } + return OK; +} + + struct my_tests_st my_tests[] = { /* Don't add test above, test_init needs to be run first */ {"test_start_tls_server", test_start_tls_server, TEST_CONNECTION_NONE, 0, NULL, NULL}, {"test_init", test_init, TEST_CONNECTION_NONE, 0, NULL, NULL}, /* Here you can add more tests */ + {"test_cert_wildcard", test_cert_wildcard, TEST_CONNECTION_NEW, 0, NULL, NULL}, {"test_cert_expired", test_cert_expired, TEST_CONNECTION_NEW, 0, NULL, NULL}, {"test_pw_check", test_pw_check, TEST_CONNECTION_NEW, 0, NULL, NULL}, {"test_ca_cert_check", test_ca_cert_check, TEST_CONNECTION_NONE, 0, NULL, NULL},