diff --git a/CMakeLists.txt b/CMakeLists.txt index cc44428f..6150fed3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -305,7 +305,8 @@ IF(NOT WITH_SSL STREQUAL "OFF") ENDIF() IF(OPENSSL_FOUND) ADD_DEFINITIONS(-DHAVE_OPENSSL -DHAVE_TLS) - SET(SSL_SOURCES "${CC_SOURCE_DIR}/libmariadb/secure/openssl.c") + SET(SSL_SOURCES "${CC_SOURCE_DIR}/libmariadb/secure/openssl.c" + "${CC_SOURCE_DIR}/libmariadb/secure/openssl_crypt.c") SET(SSL_LIBRARIES ${OPENSSL_SSL_LIBRARY} ${OPENSSL_CRYPTO_LIBRARY}) IF(WIN32) CHECK_INCLUDE_FILES (${OPENSSL_INCLUDE_DIR}/openssl/applink.c HAVE_OPENSSL_APPLINK_C) @@ -332,7 +333,8 @@ IF(NOT WITH_SSL STREQUAL "OFF") FIND_PACKAGE(GnuTLS "3.3.24" REQUIRED) IF(GNUTLS_FOUND) ADD_DEFINITIONS(-DHAVE_GNUTLS -DHAVE_TLS) - SET(SSL_SOURCES "${CC_SOURCE_DIR}/libmariadb/secure/gnutls.c") + SET(SSL_SOURCES "${CC_SOURCE_DIR}/libmariadb/secure/gnutls.c" + "${CC_SOURCE_DIR}/libmariadb/secure/gnutls_crypt.c") SET(SSL_LIBRARIES ${GNUTLS_LIBRARY}) SET(TLS_LIBRARY_VERSION "GnuTLS ${GNUTLS_VERSION_STRING}") INCLUDE_DIRECTORIES(${GNUTLS_INCLUDE_DIR}) @@ -342,12 +344,13 @@ IF(NOT WITH_SSL STREQUAL "OFF") ENDIF() IF(WIN32) IF(WITH_SSL STREQUAL "SCHANNEL") - ADD_DEFINITIONS(-DHAVE_SCHANNEL -DHAVE_TLS) + ADD_DEFINITIONS(-DHAVE_SCHANNEL -DHAVE_TLS -DHAVE_WINCRYPT) SET(SSL_SOURCES "${CC_SOURCE_DIR}/libmariadb/secure/schannel.c" + "${CC_SOURCE_DIR}/libmariadb/secure/win_crypt.c" "${CC_SOURCE_DIR}/libmariadb/secure/ma_schannel.c" "${CC_SOURCE_DIR}/libmariadb/secure/schannel_certs.c") INCLUDE_DIRECTORIES("${CC_SOURCE_DIR}/plugins/pvio/") - SET(SSL_LIBRARIES secur32) + SET(SSL_LIBRARIES secur32 crypt32 bcrypt) SET(TLS_LIBRARY_VERSION "Schannel ${CMAKE_SYSTEM_VERSION}") ENDIF() ENDIF() diff --git a/include/ma_common.h b/include/ma_common.h index 1ac0cb68..f1310128 100644 --- a/include/ma_common.h +++ b/include/ma_common.h @@ -129,3 +129,16 @@ typedef struct st_mariadb_field_extension { MARIADB_CONST_STRING metadata[MARIADB_FIELD_ATTR_LAST+1]; /* 10.5 */ } MA_FIELD_EXTENSION; + +#if defined(HAVE_SCHANNEL) || defined(HAVE_GNUTLS) +#define reset_tls_self_signed_error(mysql) \ + do { \ + free((char*)mysql->net.tls_self_signed_error); \ + mysql->net.tls_self_signed_error= 0; \ + } while(0) +#else +#define reset_tls_self_signed_error(mysql) \ + do { \ + mysql->net.tls_self_signed_error= 0; \ + } while(0) +#endif diff --git a/include/mariadb_com.h b/include/mariadb_com.h index 67461b4f..7dc18383 100644 --- a/include/mariadb_com.h +++ b/include/mariadb_com.h @@ -288,7 +288,7 @@ typedef struct st_net { my_bool unused_2; my_bool compress; my_bool unused_3; - void *unused_4; + const char *tls_self_signed_error; unsigned int last_errno; unsigned char error; my_bool unused_5; diff --git a/include/mysql/client_plugin.h b/include/mysql/client_plugin.h index 667074ce..aa7e5363 100644 --- a/include/mysql/client_plugin.h +++ b/include/mysql/client_plugin.h @@ -43,7 +43,7 @@ #define MYSQL_CLIENT_PLUGIN_RESERVED2 1 #define MYSQL_CLIENT_AUTHENTICATION_PLUGIN 2 /* authentication */ -#define MYSQL_CLIENT_AUTHENTICATION_PLUGIN_INTERFACE_VERSION 0x0100 +#define MYSQL_CLIENT_AUTHENTICATION_PLUGIN_INTERFACE_VERSION 0x0101 #define MYSQL_CLIENT_MAX_PLUGINS 3 /* Connector/C specific plugin types */ @@ -128,6 +128,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); }; /******** trace plugin *******/ diff --git a/libmariadb/ma_client_plugin.c.in b/libmariadb/ma_client_plugin.c.in index 6e0433b3..573feb4e 100644 --- a/libmariadb/ma_client_plugin.c.in +++ b/libmariadb/ma_client_plugin.c.in @@ -111,8 +111,7 @@ static int get_plugin_nr(uint type) static const char *check_plugin_version(struct st_mysql_client_plugin *plugin, unsigned int version) { - if (plugin->interface_version < version || - (plugin->interface_version >> 8) > (version >> 8)) + if (plugin->interface_version >> 8 != version >> 8) return "Incompatible client plugin interface"; return 0; } diff --git a/libmariadb/ma_pvio.c b/libmariadb/ma_pvio.c index a8a653b6..0c9235f6 100644 --- a/libmariadb/ma_pvio.c +++ b/libmariadb/ma_pvio.c @@ -545,6 +545,7 @@ my_bool ma_pvio_start_ssl(MARIADB_PVIO *pvio) 3. verrify finger print */ if (pvio->mysql->options.extension->tls_verify_server_cert && + !pvio->mysql->net.tls_self_signed_error && ma_pvio_tls_verify_server_cert(pvio->ctls)) return 1; @@ -556,6 +557,7 @@ my_bool ma_pvio_start_ssl(MARIADB_PVIO *pvio) pvio->mysql->options.extension->tls_fp, pvio->mysql->options.extension->tls_fp_list)) return 1; + reset_tls_self_signed_error(pvio->mysql); // validated } return 0; diff --git a/libmariadb/mariadb_lib.c b/libmariadb/mariadb_lib.c index c9253ec1..dd181bc4 100644 --- a/libmariadb/mariadb_lib.c +++ b/libmariadb/mariadb_lib.c @@ -1440,6 +1440,8 @@ mysql_real_connect(MYSQL *mysql, const char *host, const char *user, if (!mysql->options.extension || !mysql->options.extension->status_callback) mysql_optionsv(mysql, MARIADB_OPT_STATUS_CALLBACK, NULL, NULL); + reset_tls_self_signed_error(mysql); + /* if host contains a semicolon, we need to parse connection string */ if (host && strchr(host, ';')) { @@ -2444,6 +2446,7 @@ mysql_close(MYSQL *mysql) mysql_close_memory(mysql); mysql_close_options(mysql); ma_clear_session_state(mysql); + reset_tls_self_signed_error(mysql); if (mysql->net.extension) { diff --git a/libmariadb/secure/gnutls.c b/libmariadb/secure/gnutls.c index 55aca26b..9e9265fc 100644 --- a/libmariadb/secure/gnutls.c +++ b/libmariadb/secure/gnutls.c @@ -1371,10 +1371,22 @@ static int my_verify_callback(gnutls_session_t ssl) { gnutls_datum_t out; int type; - /* accept self signed certificates if we don't have to verify server cert */ - if (!(mysql->options.extension->tls_verify_server_cert) && - (status & GNUTLS_CERT_SIGNER_NOT_FOUND)) - return 0; + + if (status & GNUTLS_CERT_SIGNER_NOT_FOUND) + { + /* accept self signed certificates if we don't have to verify server cert */ + if (!mysql->options.extension->tls_verify_server_cert) + return 0; + + /* postpone the error for self signed certificates if CA isn't set */ + if (!mysql->options.ssl_ca && !mysql->options.ssl_capath) + { + type= gnutls_certificate_type_get(ssl); + gnutls_certificate_verification_status_print(status, type, &out, 0); + mysql->net.tls_self_signed_error= (char*)out.data; + return 0; + } + } /* gnutls default error message "certificate validation failed" isn't very descriptive, so we provide more information about the error here */ diff --git a/libmariadb/secure/ma_schannel.c b/libmariadb/secure/ma_schannel.c index be4148c7..10ea3318 100644 --- a/libmariadb/secure/ma_schannel.c +++ b/libmariadb/secure/ma_schannel.c @@ -526,6 +526,13 @@ my_bool ma_schannel_verify_certs(MARIADB_TLS *ctls, BOOL verify_server_name) end: if (!ret) { + /* postpone the error for self signed certificates if CA isn't set */ + if (status == CERT_E_UNTRUSTEDROOT && !ca_file && !ca_path) + { + mysql->net.tls_self_signed_error= strdup(errmsg); + ret= 1; + } + else pvio->set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, 0, errmsg); } if (pServerCert) diff --git a/libmariadb/secure/openssl.c b/libmariadb/secure/openssl.c index 53061312..7309e9e0 100644 --- a/libmariadb/secure/openssl.c +++ b/libmariadb/secure/openssl.c @@ -510,7 +510,11 @@ my_bool ma_tls_connect(MARIADB_TLS *ctls) mysql->options.ssl_ca || mysql->options.ssl_capath) { long x509_err= SSL_get_verify_result(ssl); - if (x509_err != X509_V_OK) + if ((x509_err == X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT || + x509_err == X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN) && rc == 1 && + !mysql->options.ssl_ca && !mysql->options.ssl_capath) + mysql->net.tls_self_signed_error= X509_verify_cert_error_string(x509_err); + else if (x509_err != X509_V_OK) { my_set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, ER(CR_SSL_CONNECTION_ERROR), X509_verify_cert_error_string(x509_err)); diff --git a/plugins/auth/auth_gssapi_client.c b/plugins/auth/auth_gssapi_client.c index 6f6c6ceb..19cdf246 100644 --- a/plugins/auth/auth_gssapi_client.c +++ b/plugins/auth/auth_gssapi_client.c @@ -117,5 +117,6 @@ struct st_mysql_client_plugin_AUTHENTICATION _mysql_client_plugin_declaration_ = NULL, NULL, NULL, - gssapi_auth_client + gssapi_auth_client, + NULL }; diff --git a/plugins/auth/caching_sha2_pw.c b/plugins/auth/caching_sha2_pw.c index b442e477..14abb0c8 100644 --- a/plugins/auth/caching_sha2_pw.c +++ b/plugins/auth/caching_sha2_pw.c @@ -145,7 +145,8 @@ struct st_mysql_client_plugin_AUTHENTICATION _mysql_client_plugin_declaration_ = auth_caching_sha2_init, auth_caching_sha2_deinit, NULL, - auth_caching_sha2_client + auth_caching_sha2_client, + NULL }; #ifdef HAVE_WINCRYPT diff --git a/plugins/auth/dialog.c b/plugins/auth/dialog.c index 31d7b7d8..8513e9ac 100644 --- a/plugins/auth/dialog.c +++ b/plugins/auth/dialog.c @@ -58,7 +58,8 @@ struct st_mysql_client_plugin_AUTHENTICATION _mysql_client_plugin_declaration_ = auth_dialog_init, NULL, NULL, - auth_dialog_open + auth_dialog_open, + NULL }; diff --git a/plugins/auth/ed25519.c b/plugins/auth/ed25519.c index 38b896f8..03f89ac7 100644 --- a/plugins/auth/ed25519.c +++ b/plugins/auth/ed25519.c @@ -83,7 +83,8 @@ struct st_mysql_client_plugin_AUTHENTICATION _mysql_client_plugin_declaration_ = auth_ed25519_init, auth_ed25519_deinit, NULL, - auth_ed25519_client + auth_ed25519_client, + NULL }; diff --git a/plugins/auth/mariadb_cleartext.c b/plugins/auth/mariadb_cleartext.c index b63c1d3b..92e56547 100644 --- a/plugins/auth/mariadb_cleartext.c +++ b/plugins/auth/mariadb_cleartext.c @@ -70,7 +70,8 @@ struct st_mysql_client_plugin_AUTHENTICATION _mysql_client_plugin_declaration_ = NULL, NULL, NULL, - clear_password_auth_client + clear_password_auth_client, + NULL }; diff --git a/plugins/auth/my_auth.c b/plugins/auth/my_auth.c index 72773079..72f71a7f 100644 --- a/plugins/auth/my_auth.c +++ b/plugins/auth/my_auth.c @@ -3,6 +3,7 @@ #include #include #include +#include #include typedef struct st_mysql_client_plugin_AUTHENTICATION auth_plugin_t; @@ -14,6 +15,16 @@ extern char *ma_send_connect_attr(MYSQL *mysql, unsigned char *buffer); extern int ma_read_ok_packet(MYSQL *mysql, uchar *pos, ulong length); extern unsigned char *mysql_net_store_length(unsigned char *packet, ulonglong length); +#define hashing(p) (p->interface_version >= 0x0101 && p->hash_password_bin) + +static int set_error_from_tls_self_signed_error(MYSQL *mysql) +{ + my_set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, + ER(CR_SSL_CONNECTION_ERROR), mysql->net.tls_self_signed_error); + reset_tls_self_signed_error(mysql); + return 1; +} + typedef struct { int (*read_packet)(struct st_plugin_vio *vio, uchar **buf); int (*write_packet)(struct st_plugin_vio *vio, const uchar *pkt, size_t pkt_len); @@ -50,7 +61,8 @@ auth_plugin_t mysql_native_password_client_plugin= NULL, NULL, NULL, - native_password_auth_client + native_password_auth_client, + NULL }; @@ -110,7 +122,8 @@ auth_plugin_t dummy_fallback_client_plugin= NULL, NULL, NULL, - dummy_fallback_auth_client + dummy_fallback_auth_client, + NULL }; @@ -350,6 +363,13 @@ static int send_client_reply_packet(MCPVIO_EXT *mpvio, } if (ma_pvio_start_ssl(mysql->net.pvio)) goto error; + if (mysql->net.tls_self_signed_error && + (!mysql->passwd || !mysql->passwd[0] || !hashing(mpvio->plugin))) + { + /* cannot use auth to validate the cert */ + set_error_from_tls_self_signed_error(mysql); + goto error; + } } #endif /* HAVE_TLS */ @@ -727,15 +747,62 @@ retry: auth_plugin_name, MYSQL_CLIENT_AUTHENTICATION_PLUGIN))) auth_plugin= &dummy_fallback_client_plugin; + /* can we use this plugin with this tls server cert ? */ + if (mysql->net.tls_self_signed_error && !hashing(auth_plugin)) + return set_error_from_tls_self_signed_error(mysql); goto retry; - } /* net->read_pos[0] should always be 0 here if the server implements the protocol correctly */ - if (mysql->net.read_pos[0] == 0) - return ma_read_ok_packet(mysql, mysql->net.read_pos + 1, pkt_length); - return 1; + if (mysql->net.read_pos[0] != 0) + return 1; + if (ma_read_ok_packet(mysql, mysql->net.read_pos + 1, pkt_length)) + return -1; + + if (!mysql->net.tls_self_signed_error) + return 0; + + assert(mysql->options.use_ssl); + assert(mysql->options.extension->tls_verify_server_cert); + assert(!mysql->options.ssl_ca); + assert(!mysql->options.ssl_capath); + assert(!mysql->options.extension->tls_fp); + assert(!mysql->options.extension->tls_fp_list); + assert(hashing(auth_plugin)); + assert(mysql->passwd[0]); + if (mysql->info && mysql->info[0] == '\1') + { + MA_HASH_CTX *ctx = NULL; + unsigned char buf[1024], digest[MA_SHA256_HASH_SIZE]; + char fp[128], hexdigest[sizeof(digest)*2+1], *hexsig= mysql->info + 1; + size_t buflen= sizeof(buf) - 1, fplen; + + mysql->info= NULL; /* no need to confuse the client with binary info */ + + if (!(fplen= ma_tls_get_finger_print(mysql->net.pvio->ctls, MA_HASH_SHA256, + fp, sizeof(fp)))) + return 1; /* error is already set */ + + if (auth_plugin->hash_password_bin(mysql, buf, &buflen) || + !(ctx= ma_hash_new(MA_HASH_SHA256))) + { + SET_CLIENT_ERROR(mysql, CR_OUT_OF_MEMORY, SQLSTATE_UNKNOWN, 0); + return 1; + } + + ma_hash_input(ctx, (unsigned char*)buf, buflen); + ma_hash_input(ctx, (unsigned char*)mysql->scramble_buff, SCRAMBLE_LENGTH); + ma_hash_input(ctx, (unsigned char*)fp, fplen); + ma_hash_result(ctx, digest); + ma_hash_free(ctx); + + mysql_hex_string(hexdigest, (char*)digest, sizeof(digest)); + if (strcmp(hexdigest, hexsig) == 0) + return 0; /* phew. self-signed certificate is validated! */ + } + + return set_error_from_tls_self_signed_error(mysql); } diff --git a/plugins/auth/old_password.c b/plugins/auth/old_password.c index 07756e92..3f7d533c 100644 --- a/plugins/auth/old_password.c +++ b/plugins/auth/old_password.c @@ -63,7 +63,8 @@ struct st_mysql_client_plugin_AUTHENTICATION _mysql_client_plugin_declaration_ = NULL, NULL, NULL, - auth_old_password + auth_old_password, + NULL }; /** diff --git a/plugins/auth/sha256_pw.c b/plugins/auth/sha256_pw.c index 79febf1a..66fb1071 100644 --- a/plugins/auth/sha256_pw.c +++ b/plugins/auth/sha256_pw.c @@ -76,7 +76,8 @@ struct st_mysql_client_plugin_AUTHENTICATION _mysql_client_plugin_declaration_ = auth_sha256_init, NULL, NULL, - auth_sha256_client + auth_sha256_client, + NULL }; #ifdef HAVE_WINCRYPT