From 9b436eaaf7d988d407cae16cd0112920b5c47c48 Mon Sep 17 00:00:00 2001 From: Georg Richter Date: Tue, 13 Sep 2016 16:12:34 +0200 Subject: [PATCH] TLS fixes: - don't use password in global context - load keys and certs via callback functions - don't use gnutls_bye since server is not able to detect dead socket - fixed valgrind errors in gnutls --- libmariadb/ma_pvio.c | 2 +- libmariadb/ma_tls.c | 12 +- libmariadb/secure/gnutls.c | 175 ++++++++++++++++++----- libmariadb/secure/openssl.c | 85 +++++++----- unittest/libmariadb/CMakeLists.txt | 6 +- unittest/libmariadb/my_test.h | 1 - unittest/libmariadb/ssl.c.in | 216 +++++++++++++++++++++++++---- 7 files changed, 387 insertions(+), 110 deletions(-) diff --git a/libmariadb/ma_pvio.c b/libmariadb/ma_pvio.c index 775e3a6e..90b739fb 100644 --- a/libmariadb/ma_pvio.c +++ b/libmariadb/ma_pvio.c @@ -521,11 +521,11 @@ my_bool ma_pvio_start_ssl(MARIADB_PVIO *pvio) ma_pvio_tls_verify_server_cert(pvio->ctls)) return 1; + if (pvio->mysql->options.extension && ((pvio->mysql->options.extension->tls_fp && pvio->mysql->options.extension->tls_fp[0]) || (pvio->mysql->options.extension->tls_fp_list && pvio->mysql->options.extension->tls_fp_list[0]))) { - if (ma_pvio_tls_check_fp(pvio->ctls, pvio->mysql->options.extension->tls_fp, pvio->mysql->options.extension->tls_fp_list)) diff --git a/libmariadb/ma_tls.c b/libmariadb/ma_tls.c index 71d171f8..a3a5176d 100644 --- a/libmariadb/ma_tls.c +++ b/libmariadb/ma_tls.c @@ -124,10 +124,10 @@ static my_bool ma_pvio_tls_compare_fp(const char *fp1, unsigned int fp1_len, { char hexstr[64]; + fp1_len= (unsigned int)mysql_hex_string(hexstr, fp1, fp1_len); if (fp1_len != fp2_len) return 1; - fp1_len= (unsigned int)mysql_hex_string(hexstr, fp1, fp1_len); #ifdef WIN32 if (strnicmp(hexstr, fp2, fp1_len) != 0) #else @@ -140,10 +140,12 @@ static my_bool ma_pvio_tls_compare_fp(const char *fp1, unsigned int fp1_len, my_bool ma_pvio_tls_check_fp(MARIADB_TLS *ctls, const char *fp, const char *fp_list) { unsigned int cert_fp_len= 64; - char cert_fp[64]; + char *cert_fp= NULL; my_bool rc=1; MYSQL *mysql= ctls->pvio->mysql; + cert_fp= (char *)malloc(cert_fp_len); + if ((cert_fp_len= ma_tls_get_finger_print(ctls, cert_fp, cert_fp_len)) < 1) goto end; if (fp) @@ -154,9 +156,7 @@ my_bool ma_pvio_tls_check_fp(MARIADB_TLS *ctls, const char *fp, const char *fp_l char buff[255]; if (!(fp = ma_open(fp_list, "r", mysql))) - { - return 1; - } + goto end; while (ma_gets(buff, sizeof(buff)-1, fp)) { @@ -181,6 +181,8 @@ my_bool ma_pvio_tls_check_fp(MARIADB_TLS *ctls, const char *fp, const char *fp_l } end: + if (cert_fp) + free(cert_fp); if (rc) { my_set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, diff --git a/libmariadb/secure/gnutls.c b/libmariadb/secure/gnutls.c index 22077d30..e37e2fb8 100644 --- a/libmariadb/secure/gnutls.c +++ b/libmariadb/secure/gnutls.c @@ -21,6 +21,7 @@ #include #include +#include #include #include #include @@ -39,6 +40,12 @@ extern unsigned int mariadb_deinitialize_ssl; static int my_verify_callback(gnutls_session_t ssl); +struct st_gnutls_data { + MYSQL *mysql; + gnutls_privkey_t key; + gnutls_pcert_st cert; +}; + struct st_cipher_map { const char *openssl_name; const char *priority; @@ -96,6 +103,20 @@ const struct st_cipher_map gtls_ciphers[]= {NULL, NULL, 0, 0, 0} }; +/* free data assigned to the connection */ +static void free_gnutls_data(struct st_gnutls_data *data) +{ + if (data) + { + if (data->key) + gnutls_privkey_deinit(data->key); + gnutls_pcert_deinit(&data->cert); + free(data); + } +} + +/* map the gnutls cipher suite (defined by key exchange algorithm, cipher + and mac algorithm) to the corresponding OpenSSL cipher name */ static const char *openssl_cipher_name(gnutls_kx_algorithm_t kx, gnutls_cipher_algorithm_t cipher, gnutls_mac_algorithm_t mac) @@ -112,6 +133,7 @@ static const char *openssl_cipher_name(gnutls_kx_algorithm_t kx, return NULL; } +/* get priority string for a given openssl cipher name */ static const char *get_priority(const char *cipher_name) { unsigned int i= 0; @@ -126,7 +148,7 @@ static const char *get_priority(const char *cipher_name) #define MAX_SSL_ERR_LEN 100 -static void ma_tls_set_error(MYSQL *mysql, int ssl_errno) +static void ma_tls_set_error(MYSQL *mysql, void *ssl, int ssl_errno) { char ssl_error[MAX_SSL_ERR_LEN]; const char *ssl_error_reason; @@ -137,6 +159,21 @@ static void ma_tls_set_error(MYSQL *mysql, int ssl_errno) pvio->set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, "Unknown SSL error"); return; } + + /* give a more descriptive error message for alerts */ + if (ssl_errno == GNUTLS_E_FATAL_ALERT_RECEIVED) + { + gnutls_alert_description_t alert_desc; + const char *alert_name; + alert_desc= gnutls_alert_get((gnutls_session_t)ssl); + alert_name= gnutls_alert_get_name(alert_desc); + snprintf(ssl_error, MAX_SSL_ERR_LEN, "fatal alert received: %s", + alert_name); + pvio->set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, 0, + ssl_error); + return; + } + if ((ssl_error_reason= gnutls_strerror(ssl_errno))) { pvio->set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, 0, @@ -249,15 +286,7 @@ static int ma_gnutls_set_ciphers(gnutls_session_t ssl, char *cipher_str) while (token) { const char *p= get_priority(token); - /* if cipher was not found, we pass the original token to - the priority string, this will allow to specify gnutls - specific settings via cipher */ - if (!p) - { - strncat(prio, ":", PRIO_SIZE - strlen(prio) - 1); - strncat(prio, token, PRIO_SIZE - strlen(prio) - 1); - } - else + if (p) strncat(prio, p, PRIO_SIZE - strlen(prio) - 1); token = strtok(NULL, ":"); } @@ -266,8 +295,6 @@ static int ma_gnutls_set_ciphers(gnutls_session_t ssl, char *cipher_str) static int ma_tls_set_certs(MYSQL *mysql) { - char *certfile= mysql->options.ssl_cert, - *keyfile= mysql->options.ssl_key; int ssl_error= 0; if (mysql->options.ssl_ca) @@ -282,32 +309,78 @@ static int ma_tls_set_certs(MYSQL *mysql) gnutls_certificate_set_verify_function(GNUTLS_xcred, my_verify_callback); - /* GNUTLS doesn't support ca_path */ - - if (keyfile && !certfile) - certfile= keyfile; - if (certfile && !keyfile) - keyfile= certfile; - - /* set key */ - if (certfile || keyfile) - { - if ((ssl_error= gnutls_certificate_set_x509_key_file2(GNUTLS_xcred, - certfile, keyfile, GNUTLS_X509_FMT_PEM, - OPT_HAS_EXT_VAL(mysql, tls_pw) ? mysql->options.extension->tls_pw : NULL, - 0)) < 0) - goto error; - } return 1; error: return ssl_error; } +static int +client_cert_callback(gnutls_session_t session, + const gnutls_datum_t * req_ca_rdn __attribute__((unused)), + int nreqs __attribute__((unused)), + const gnutls_pk_algorithm_t * sign_algos __attribute__((unused)), + int sign_algos_length __attribute__((unused)), + gnutls_pcert_st ** pcert, + unsigned int *pcert_length, gnutls_privkey_t * pkey) +{ + struct st_gnutls_data *session_data; + char *certfile, *keyfile; + gnutls_datum_t data; + MYSQL *mysql; + gnutls_certificate_type_t type= gnutls_certificate_type_get(session); + + session_data= (struct st_gnutls_data *)gnutls_session_get_ptr(session); + + if (!session_data->mysql || + type != GNUTLS_CRT_X509) + return -1; + + mysql= session_data->mysql; + + certfile= session_data->mysql->options.ssl_cert; + keyfile= session_data->mysql->options.ssl_key; + + if (!certfile && !keyfile) + return 0; + if (keyfile && !certfile) + certfile= keyfile; + if (certfile && !keyfile) + keyfile= certfile; + + if (gnutls_load_file(certfile, &data) < 0) + return -1; + if (gnutls_pcert_import_x509_raw(&session_data->cert, &data, GNUTLS_X509_FMT_PEM, 0) < 0) + { + gnutls_free(data.data); + return -1; + } + gnutls_free(data.data); + + if (gnutls_load_file(keyfile, &data) < 0) + return -1; + gnutls_privkey_init(&session_data->key); + if (gnutls_privkey_import_x509_raw(session_data->key, &data, + GNUTLS_X509_FMT_PEM, + mysql->options.extension ? mysql->options.extension->tls_pw : NULL, + 0) < 0) + { + gnutls_free(data.data); + return -1; + } + gnutls_free(data.data); + + *pcert_length= 1; + *pcert= &session_data->cert; + *pkey= session_data->key; + return 0; +} + void *ma_tls_init(MYSQL *mysql) { gnutls_session_t ssl= NULL; int ssl_error= 0; + struct st_gnutls_data *data= NULL; pthread_mutex_lock(&LOCK_gnutls_config); @@ -316,19 +389,28 @@ void *ma_tls_init(MYSQL *mysql) if ((ssl_error = gnutls_init(&ssl, GNUTLS_CLIENT & GNUTLS_NONBLOCK)) < 0) goto error; - gnutls_session_set_ptr(ssl, (void *)mysql); + + if (!(data= (struct st_gnutls_data *)calloc(1, sizeof(struct st_gnutls_data)))) + goto error; + + data->mysql= mysql; + gnutls_certificate_set_retrieve_function2(GNUTLS_xcred, client_cert_callback); + gnutls_session_set_ptr(ssl, (void *)data); ssl_error= ma_gnutls_set_ciphers(ssl, mysql->options.ssl_cipher); if (ssl_error < 0) goto error; + /* we don't load private key and cert by default - if the server requests + a client certificate we will send it via callback function */ if ((ssl_error= gnutls_credentials_set(ssl, GNUTLS_CRD_CERTIFICATE, GNUTLS_xcred)) < 0) goto error; pthread_mutex_unlock(&LOCK_gnutls_config); return (void *)ssl; error: - ma_tls_set_error(mysql, ssl_error); + free_gnutls_data(data); + ma_tls_set_error(mysql, ssl, ssl_error); if (ssl) gnutls_deinit(ssl); pthread_mutex_unlock(&LOCK_gnutls_config); @@ -362,7 +444,9 @@ my_bool ma_tls_connect(MARIADB_TLS *ctls) MYSQL *mysql; MARIADB_PVIO *pvio; int ret; - mysql= (MYSQL *)gnutls_session_get_ptr(ssl); + struct st_gnutls_data *data; + data= (struct st_gnutls_data *)gnutls_session_get_ptr(ssl); + mysql= data->mysql; if (!mysql) return 1; @@ -386,16 +470,16 @@ my_bool ma_tls_connect(MARIADB_TLS *ctls) if (ret < 0) { - ma_tls_set_error(mysql, ret); + ma_tls_set_error(mysql, ssl, ret); /* restore blocking mode */ gnutls_deinit((gnutls_session_t )ctls->ssl); + free_gnutls_data(data); ctls->ssl= NULL; if (!blocking) pvio->methods->blocking(pvio, FALSE, 0); return 1; } ctls->ssl= (void *)ssl; - return 0; } @@ -413,7 +497,15 @@ my_bool ma_tls_close(MARIADB_TLS *ctls) { if (ctls->ssl) { - gnutls_bye((gnutls_session_t )ctls->ssl, GNUTLS_SHUT_WR); + MARIADB_PVIO *pvio= ctls->pvio; + struct st_gnutls_data *data= + (struct st_gnutls_data *)gnutls_session_get_ptr(ctls->ssl); + /* this would be the correct way, however can't dectect afterwards + if the socket is closed or not, so we don't send encrypted + finish alert. + rc= gnutls_bye((gnutls_session_t )ctls->ssl, GNUTLS_SHUT_WR); + */ + free_gnutls_data(data); gnutls_deinit((gnutls_session_t )ctls->ssl); ctls->ssl= NULL; } @@ -443,14 +535,19 @@ const char *ma_tls_get_cipher(MARIADB_TLS *ctls) static int my_verify_callback(gnutls_session_t ssl) { - unsigned int status; + unsigned int status= 0; const gnutls_datum_t *cert_list; unsigned int cert_list_size; - MYSQL *mysql= (MYSQL *)gnutls_session_get_ptr(ssl); - MARIADB_PVIO *pvio= mysql->net.pvio; + struct st_gnutls_data *data= (struct st_gnutls_data *)gnutls_session_get_ptr(ssl); + MYSQL *mysql; + MARIADB_PVIO *pvio; + gnutls_x509_crt_t cert; const char *hostname; + mysql= data->mysql; + pvio= mysql->net.pvio; + /* read hostname */ hostname = mysql->host; @@ -518,11 +615,13 @@ unsigned int ma_tls_get_finger_print(MARIADB_TLS *ctls, char *fp, unsigned int l size_t fp_len= len; const gnutls_datum_t *cert_list; unsigned int cert_list_size; + struct st_gnutls_data *data; if (!ctls || !ctls->ssl) return 0; - mysql= (MYSQL *)gnutls_session_get_ptr(ctls->ssl); + data= (struct st_gnutls_data *)gnutls_session_get_ptr(ctls->ssl); + mysql= (MYSQL *)data->mysql; cert_list = gnutls_certificate_get_peers (ctls->ssl, &cert_list_size); if (cert_list == NULL) diff --git a/libmariadb/secure/openssl.c b/libmariadb/secure/openssl.c index 0a6db6df..1b671ae0 100644 --- a/libmariadb/secure/openssl.c +++ b/libmariadb/secure/openssl.c @@ -30,7 +30,12 @@ #include #include -#define HAVE_TLS_SESSION_CACHE 1 +#ifdef HAVE_TLS_SESSION_CACHE +#undef HAVE_TLS_SESSION_CACHE +#endif +#if OPENSSL_USE_BIOMETHOD +#undef OPENSSL_USE_BIOMETHOD +#endif #ifndef HAVE_OPENSSL_DEFAULT #include #define ma_malloc(A,B) malloc((A)) @@ -329,6 +334,7 @@ int ma_tls_start(char *errmsg, size_t errmsg_len) ma_tls_get_error(errmsg, errmsg_len); goto end; } + SSL_CTX_set_options(SSL_context, SSL_OP_ALL); #ifdef HAVE_TLS_SESSION_CACHE SSL_CTX_set_session_cache_mode(SSL_context, SSL_SESS_CACHE_CLIENT); ma_tls_sessions= (MA_SSL_SESSION *)calloc(1, sizeof(struct st_ma_tls_session) * ma_tls_session_cache_size); @@ -411,15 +417,18 @@ int ma_tls_get_password(char *buf, int size, } -static int ma_tls_set_certs(MYSQL *mysql) +static int ma_tls_set_certs(MYSQL *mysql, SSL *ssl) { char *certfile= mysql->options.ssl_cert, *keyfile= mysql->options.ssl_key; + char *pw= (mysql->options.extension) ? + mysql->options.extension->tls_pw : NULL; + /* add cipher */ if ((mysql->options.ssl_cipher && mysql->options.ssl_cipher[0] != 0) && - SSL_CTX_set_cipher_list(SSL_context, mysql->options.ssl_cipher) == 0) + SSL_set_cipher_list(ssl, mysql->options.ssl_cipher) == 0) goto error; /* ca_file and ca_path */ @@ -439,35 +448,37 @@ static int ma_tls_set_certs(MYSQL *mysql) keyfile= certfile; /* set cert */ - if (certfile && certfile[0] != 0) - if (SSL_CTX_use_certificate_file(SSL_context, certfile, SSL_FILETYPE_PEM) != 1) - goto error; - - /* If the private key file is encrypted, we need to register a callback function - * for providing password. */ - if (OPT_HAS_EXT_VAL(mysql, tls_pw)) + if (certfile && certfile[0] != 0) { - SSL_CTX_set_default_passwd_cb_userdata(SSL_context, (void *)mysql->options.extension->tls_pw); - SSL_CTX_set_default_passwd_cb(SSL_context, ma_tls_get_password); + if (SSL_CTX_use_certificate_chain_file(SSL_context, certfile) != 1 || + SSL_use_certificate_file(ssl, certfile, SSL_FILETYPE_PEM) != 1) + goto error; } - if (keyfile && keyfile[0]) { - if (SSL_CTX_use_PrivateKey_file(SSL_context, keyfile, SSL_FILETYPE_PEM) != 1) + FILE *fp; + if ((fp= fopen(keyfile, "rb"))) { - unsigned long err= ERR_peek_error(); - if (!(ERR_GET_LIB(err) == ERR_LIB_X509 && - ERR_GET_REASON(err) == X509_R_CERT_ALREADY_IN_HASH_TABLE)) - goto error; + EVP_PKEY *key= EVP_PKEY_new(); + PEM_read_PrivateKey(fp, &key, NULL, pw); + fclose(fp); + if (SSL_use_PrivateKey(ssl, key) != 1) + { + unsigned long err= ERR_peek_error(); + EVP_PKEY_free(key); + if (!(ERR_GET_LIB(err) == ERR_LIB_X509 && + ERR_GET_REASON(err) == X509_R_CERT_ALREADY_IN_HASH_TABLE)) + goto error; + } + EVP_PKEY_free(key); + } else { + my_set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, + CER(CR_FILE_NOT_FOUND), keyfile); + return 1; } } - if (OPT_HAS_EXT_VAL(mysql, tls_pw)) - { - SSL_CTX_set_default_passwd_cb_userdata(SSL_context, NULL); - SSL_CTX_set_default_passwd_cb(SSL_context, NULL); - } /* verify key */ - if (certfile && !SSL_CTX_check_private_key(SSL_context)) + if (certfile && !SSL_check_private_key(ssl)) goto error; if (mysql->options.extension && @@ -499,14 +510,14 @@ void *ma_tls_init(MYSQL *mysql) #endif pthread_mutex_lock(&LOCK_openssl_config); - if (ma_tls_set_certs(mysql)) + if (!(ssl= SSL_new(SSL_context))) + goto error; + + if (ma_tls_set_certs(mysql, ssl)) { goto error; } - if (!(ssl= SSL_new(SSL_context))) - goto error; - if (!SSL_set_app_data(ssl, mysql)) goto error; @@ -703,8 +714,7 @@ const char *ma_tls_get_cipher(MARIADB_TLS *ctls) unsigned int ma_tls_get_finger_print(MARIADB_TLS *ctls, char *fp, unsigned int len) { - EVP_MD *digest= (EVP_MD *)EVP_sha1(); - X509 *cert; + X509 *cert= NULL; MYSQL *mysql; unsigned int fp_len; @@ -718,7 +728,7 @@ unsigned int ma_tls_get_finger_print(MARIADB_TLS *ctls, char *fp, unsigned int l my_set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, ER(CR_SSL_CONNECTION_ERROR), "Unable to get server certificate"); - return 0; + goto end; } if (len < EVP_MAX_MD_SIZE) @@ -726,18 +736,21 @@ unsigned int ma_tls_get_finger_print(MARIADB_TLS *ctls, char *fp, unsigned int l my_set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, ER(CR_SSL_CONNECTION_ERROR), "Finger print buffer too small"); - return 0; + goto end; } - fp_len= len; - if (!X509_digest(cert, digest, (unsigned char *)fp, &fp_len)) + if (!X509_digest(cert, EVP_sha1(), (unsigned char *)fp, &fp_len)) { - ma_free(fp); my_set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, ER(CR_SSL_CONNECTION_ERROR), "invalid finger print of server certificate"); - return 0; + goto end; } + + X509_free(cert); return (fp_len); +end: + X509_free(cert); + return 0; } diff --git a/unittest/libmariadb/CMakeLists.txt b/unittest/libmariadb/CMakeLists.txt index 2059c447..0d44b394 100644 --- a/unittest/libmariadb/CMakeLists.txt +++ b/unittest/libmariadb/CMakeLists.txt @@ -59,13 +59,13 @@ IF(WITH_SSL) STRING(REPLACE "SHA1 Fingerprint=" "" FINGER_PRINT "${FINGER_PRINT}") STRING(REPLACE "\n" "" FINGER_PRINT "${FINGER_PRINT}") STRING(REPLACE ":" "" SSL_CERT_FINGER_PRINT "${FINGER_PRINT}") + CONFIGURE_FILE(${PROJECT_SOURCE_DIR}/unittest/libmariadb/fingerprint.list.in + ${PROJECT_SOURCE_DIR}/unittest/libmariadb/fingerprint.list) ENDIF() + SET(API_TESTS ${API_TESTS} "ssl") CONFIGURE_FILE(${PROJECT_SOURCE_DIR}/unittest/libmariadb/ssl.c.in ${PROJECT_SOURCE_DIR}/unittest/libmariadb/ssl.c) - CONFIGURE_FILE(${PROJECT_SOURCE_DIR}/unittest/libmariadb/fingerprint.list.in - ${PROJECT_SOURCE_DIR}/unittest/libmariadb/fingerprint.list) - SET(API_TESTS ${API_TESTS} "ssl") ENDIF() ADD_LIBRARY(ma_getopt ma_getopt.c) diff --git a/unittest/libmariadb/my_test.h b/unittest/libmariadb/my_test.h index 6dc88bae..a1113b24 100644 --- a/unittest/libmariadb/my_test.h +++ b/unittest/libmariadb/my_test.h @@ -555,6 +555,5 @@ void run_tests(struct my_tests_st *test) { diag("close default"); mysql_close(mysql_default); } - mysql_server_end(); } diff --git a/unittest/libmariadb/ssl.c.in b/unittest/libmariadb/ssl.c.in index 0d911d26..9efb508b 100644 --- a/unittest/libmariadb/ssl.c.in +++ b/unittest/libmariadb/ssl.c.in @@ -173,9 +173,9 @@ static int test_conc95(MYSQL *unused __attribute__((unused))) mysql= mysql_init(NULL); mysql_ssl_set(mysql, - "@CMAKE_SOURCE_DIR@/unittest/libmariadb/certs/server-key.pem", - "@CMAKE_SOURCE_DIR@/unittest/libmariadb/certs/server-cert.pem", - "@CMAKE_SOURCE_DIR@/unittest/libmariadb/certs/ca-cert.pem", + "@CMAKE_SOURCE_DIR@/unittest/libmariadb/certs/client-key.pem", + "@CMAKE_SOURCE_DIR@/unittest/libmariadb/certs/client-cert.pem", + NULL, NULL, NULL); @@ -541,13 +541,14 @@ static int verify_ssl_server_cert(MYSQL *unused __attribute__((unused))) mysql= mysql_init(NULL); FAIL_IF(!mysql, "Can't allocate memory"); - mysql_ssl_set(mysql, NULL, NULL, "@CMAKE_SOURCE_DIR@/unittest/libmariadb/certs/ca-cert.pem", NULL, NULL); + mysql_ssl_set(mysql, NULL, NULL, "@CMAKE_SOURCE_DIR@/unittest/libmariadb/certs/cacert.pem", NULL, NULL); mysql_options(mysql, MYSQL_OPT_SSL_VERIFY_SERVER_CERT, &verify); mysql_real_connect(mysql, hostname, ssluser, sslpw, schema, port, socketname, 0); FAIL_IF(!mysql_errno(mysql), "Expected error"); + diag("Error (expected): %s", mysql_error(mysql)); mysql_close(mysql); return OK; @@ -758,8 +759,13 @@ static int test_ssl_fp_list(MYSQL *unused __attribute__((unused))) mysql_options(my, MARIADB_OPT_SSL_FP_LIST, "./fingerprint.list"); - FAIL_IF(!mysql_real_connect(my, hostname, ssluser, sslpw, schema, - port, socketname, 0), mysql_error(my)); + if(!mysql_real_connect(my, hostname, ssluser, sslpw, schema, + port, socketname, 0)) + { + diag("Error: %s", mysql_error(my)); + mysql_close(my); + return FAIL; + } FAIL_IF(check_cipher(my) != 0, "Invalid cipher"); mysql_close(my); @@ -814,7 +820,6 @@ static int test_schannel_cipher(MYSQL *unused __attribute__((unused))) port, socketname, 0), mysql_error(my)); diag("cipher: %s", mysql_get_ssl_cipher(my)); - FAIL_IF(strcmp(mysql_get_ssl_cipher(my), "CALG_AES_256") != 0, "expected cipher with 256bit strength"); mysql_close(my); @@ -823,7 +828,7 @@ static int test_schannel_cipher(MYSQL *unused __attribute__((unused))) #endif -#ifdef HAVE_GNUTLS +#if defined(HAVE_GNUTLS) || defined(HAVE_OPENSSL) static int test_cipher_mapping(MYSQL *unused __attribute__((unused))) { @@ -847,44 +852,204 @@ static int test_cipher_mapping(MYSQL *unused __attribute__((unused))) MYSQL_RES *res; char c[100]; int rc; - mysql_options(mysql, MYSQL_OPT_SSL_CIPHER, ciphers[i]); + + mysql_ssl_set(mysql, NULL, NULL, NULL, NULL, ciphers[i]); diag("%s", ciphers[i]); + + mysql->options.use_ssl= 1; FAIL_IF(!mysql_real_connect(mysql, hostname, username, password, schema, port, socketname, 0), mysql_error(mysql)); - if (strcmp(ciphers[i], mysql_get_ssl_cipher(mysql)) != 0) + if (!mysql_get_ssl_cipher(mysql) || + strcmp(ciphers[i], mysql_get_ssl_cipher(mysql)) != 0) { - diag("expected: %s instead of %s", ciphers[i], mysql_get_ssl_cipher(mysql)); + diag("cipher %s failed", ciphers[i]); mysql_close(mysql); return FAIL; } - rc= mysql_query(mysql, "SHOW STATUS LIKE 'ssl_cipher'"); - check_mysql_rc(rc, mysql); - res= mysql_store_result(mysql); - row= mysql_fetch_row(res); - strcpy(c, row[1]); - mysql_free_result(res); - mysql_close(mysql); - if (strcmp(ciphers[i], c) != 0) + else { - diag("expected: %s instead of %s", ciphers[i], c); - return FAIL; + rc= mysql_query(mysql, "SHOW STATUS LIKE 'ssl_cipher'"); + check_mysql_rc(rc, mysql); + res= mysql_store_result(mysql); + row= mysql_fetch_row(res); + strcpy(c, row[1]); + mysql_free_result(res); + mysql_close(mysql); + if (strcmp(ciphers[i], c) != 0) + { + diag("expected: %s instead of %s", ciphers[i], c); + return FAIL; + } } - i++; } return OK; } - #endif +static int test_openssl_1(MYSQL *mysql) +{ + int rc; + MYSQL *my; + uchar val= 1; + char query[1024]; + int i; + + if (check_skip_ssl()) + return SKIP; + + for (i=1; i < 6; i++) + { + sprintf(query, "DROP USER IF EXISTS 'ssluser%d'@'%s'", i, sslhost); + rc= mysql_query(mysql, query); + check_mysql_rc(rc, mysql); + sprintf(query, "CREATE USER 'ssluser%d'@'%s'", i, sslhost); + rc= mysql_query(mysql, query); + check_mysql_rc(rc, mysql); + } + rc= mysql_query(mysql, "FLUSH PRIVILEGES"); + check_mysql_rc(rc, mysql); + + sprintf(query, "grant select on %s.* to 'ssluser1'@'%s' require ssl", schema, sslhost); + rc= mysql_query(mysql, query); + check_mysql_rc(rc, mysql); + + + my= mysql_init(NULL); + mysql_ssl_set(my, NULL, NULL, NULL, NULL, "AES128-SHA"); + FAIL_IF(!mysql_real_connect(my, hostname, "ssluser1", NULL, schema, + port, socketname, 0), mysql_error(my)); + FAIL_IF(!mysql_get_ssl_cipher(my), "No TLS connection"); + mysql_close(my); + + my= mysql_init(NULL); + mysql_options(my, MYSQL_OPT_SSL_ENFORCE, &val); + my->options.use_ssl= 1; + FAIL_IF(!mysql_real_connect(my, hostname, "ssluser1", NULL, schema, + port, socketname, 0), mysql_error(my)); + FAIL_IF(!mysql_get_ssl_cipher(my), "No TLS connection"); + mysql_close(my); + + sprintf(query, "grant select on %s.* to 'ssluser2'@'%s' require cipher 'AES256-SHA'", schema, sslhost); + rc= mysql_query(mysql, query); + check_mysql_rc(rc, mysql); + +/* ssl_user1: connect with enforce should work */ + my= mysql_init(NULL); + mysql_options(my, MYSQL_OPT_SSL_ENFORCE, &val); + FAIL_IF(mysql_real_connect(my, hostname, "ssluser2", NULL, schema, + port, socketname, 0), "Error expected"); + mysql_close(my); + + /* ssl_user2: connect with cipher should work */ + my= mysql_init(NULL); + mysql_ssl_set(my, NULL, NULL, NULL, NULL, "AES128-SHA"); + FAIL_IF(mysql_real_connect(my, hostname, "ssluser2", NULL, schema, + port, socketname, 0), "Error expected"); + mysql_close(my); + + + /* ssl_user2: connect with correct cipher */ + my= mysql_init(NULL); + mysql_ssl_set(my, NULL, NULL, NULL, NULL, "AES256-SHA"); + FAIL_IF(!mysql_real_connect(my, hostname, "ssluser2", NULL, schema, + port, socketname, 0), mysql_error(my)); + FAIL_IF(strcmp("AES256-SHA", mysql_get_ssl_cipher(my)) != 0, "expected cipher AES256-SHA"); + mysql_close(my); + + + sprintf(query, "grant select on %s.* to 'ssluser3'@'%s' require cipher 'AES256-SHA' AND " + " SUBJECT '/DC=com/DC=example/CN=client'", schema, sslhost); + rc= mysql_query(mysql, query); + check_mysql_rc(rc, mysql); + + /* ssluser3: connect with cipher only */ + my= mysql_init(NULL); + mysql_ssl_set(my, NULL, NULL, NULL, NULL, "AES256-SHA"); + FAIL_IF(mysql_real_connect(my, hostname, "ssluser3", NULL, schema, + port, socketname, 0), "Error expected"); + mysql_close(my); + + /* ssluser3 connect with cipher and certs */ + my= mysql_init(NULL); + mysql_ssl_set(my, "@CMAKE_SOURCE_DIR@/unittest/libmariadb/certs/client-key.pem", + "@CMAKE_SOURCE_DIR@/unittest/libmariadb/certs/client-cert.pem", + "@CMAKE_SOURCE_DIR@/unittest/libmariadb/certs/ca-cert.pem", + NULL, + "AES256-SHA"); + FAIL_IF(!mysql_real_connect(my, hostname, "ssluser3", NULL, schema, + port, socketname, 0), mysql_error(my)); + + mysql_close(my); + + sprintf(query, "grant select on %s.* to 'ssluser4'@'%s' require cipher 'AES256-SHA' AND " + " ISSUER '/DC=com/DC=example/CN=client'", schema, sslhost); + rc= mysql_query(mysql, query); + check_mysql_rc(rc, mysql); + + /* ssluser4: connect with cipher only */ + my= mysql_init(NULL); + mysql_ssl_set(my, NULL, NULL, NULL, NULL, "AES256-SHA"); + FAIL_IF(mysql_real_connect(my, hostname, "ssluser4", NULL, schema, + port, socketname, 0), "Error expected"); + mysql_close(my); + + /* ssluser4 connect with cipher and certs */ + my= mysql_init(NULL); + mysql_ssl_set(my, "@CMAKE_SOURCE_DIR@/unittest/libmariadb/certs/client-key.pem", + "@CMAKE_SOURCE_DIR@/unittest/libmariadb/certs/client-cert.pem", + "@CMAKE_SOURCE_DIR@/unittest/libmariadb/certs/ca-cert.pem", + NULL, + "AES256-SHA"); + FAIL_IF(!mysql_real_connect(my, hostname, "ssluser4", NULL, schema, + port, socketname, 0), mysql_error(my)); + + mysql_close(my); + + return OK; +} + +static int test_ssl_timeout(MYSQL *unused __attribute__((unused))) +{ + MYSQL *mysql; + my_bool enforce= 1; + int read_timeout= 1; + int rc; + + if (check_skip_ssl()) + return SKIP; + + mysql= mysql_init(NULL); + mysql_options(mysql, MYSQL_OPT_SSL_ENFORCE, &enforce); + mysql_options(mysql, MYSQL_OPT_READ_TIMEOUT, &read_timeout); + mysql->options.use_ssl= 1; + FAIL_IF(!mysql_real_connect(mysql, hostname, username, password, schema, + port, socketname, 0), mysql_error(mysql)); + diag("cipher: %s\n", mysql_get_ssl_cipher(mysql)); + rc= mysql_query(mysql, "SELECT SLEEP(600)"); + if (!rc) + { + diag("error expected (timeout)"); + return FAIL; + } + + mysql_close(mysql); + return OK; +} struct my_tests_st my_tests[] = { {"test_ssl", test_ssl, TEST_CONNECTION_NEW, 0, NULL, NULL}, -#ifdef HAVE_GNUTLS + {"test_ssl_timeout", test_ssl_timeout, TEST_CONNECTION_NEW, 0, NULL, NULL}, + {"test_openssl_1", test_openssl_1, TEST_CONNECTION_NEW, 0, NULL, NULL}, +#ifndef HAVE_SCHANNEL {"test_cipher_mapping", test_cipher_mapping, TEST_CONNECTION_NONE, 0, NULL, NULL}, #endif {"test_conc127", test_conc127, TEST_CONNECTION_NEW, 0, NULL, NULL}, +/* Both tests work with GNU tls, however we can't create fingerprints with + gnutls-cli in CMakeLists.txt */ +#ifdef HAVE_OPENSSL {"test_ssl_fp", test_ssl_fp, TEST_CONNECTION_NEW, 0, NULL, NULL}, {"test_ssl_fp_list", test_ssl_fp_list, TEST_CONNECTION_NEW, 0, NULL, NULL}, +#endif {"test_conc50", test_conc50, TEST_CONNECTION_NEW, 0, NULL, NULL}, {"test_conc50_1", test_conc50_1, TEST_CONNECTION_NEW, 0, NULL, NULL}, {"test_conc50_2", test_conc50_2, TEST_CONNECTION_NEW, 0, NULL, NULL}, @@ -900,7 +1065,7 @@ struct my_tests_st my_tests[] = { {"test_ssl_version", test_ssl_version, TEST_CONNECTION_NEW, 0, NULL, NULL}, {"test_ssl_threads", test_ssl_threads, TEST_CONNECTION_NEW, 0, NULL, NULL}, #ifndef HAVE_SCHANNEL - {"test_password_protected", test_password_protected, TEST_CONNECTION_NEW, 0, NULL, NULL}, + {"test_password_protected", test_password_protected, TEST_CONNECTION_NEW, 0, NULL, NULL}, #else {"test_schannel_cipher", test_schannel_cipher, TEST_CONNECTION_NEW, 0, NULL, NULL}, #endif @@ -914,7 +1079,6 @@ int main(int argc, char **argv) if (argc > 1) get_options(argc, argv); - run_tests(my_tests); mysql_server_end();