/************************************************************************************ Copyright (C) 2012 Monty Program AB This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; if not see or write to the Free Software Foundation, Inc., 51 Franklin St., Fifth Floor, Boston, MA 02110, USA *************************************************************************************/ #include #include #include #include #include #include #include #include #include /* SSL and SSL_CTX */ #include /* error reporting */ #include #include #include #if OPENSSL_VERSION_NUMBER < 0x10100000L #include #endif #include #if defined(_WIN32) && !defined(_OPENSSL_Applink) && defined(HAVE_OPENSSL_APPLINK_C) #include #endif #if OPENSSL_VERSION_NUMBER >= 0x10002000L && !defined(LIBRESSL_VERSION_NUMBER) #include #define HAVE_OPENSSL_CHECK_HOST 1 #endif #if OPENSSL_VERSION_NUMBER >= 0x10100000L && !defined(LIBRESSL_VERSION_NUMBER) #define HAVE_OPENSSL_1_1_API #endif #if OPENSSL_VERSION_NUMBER < 0x10000000L #define SSL_OP_NO_TLSv1_1 0L #define SSL_OP_NO_TLSv1_2 0L #define CRYPTO_THREADID_set_callback CRYPTO_set_id_callback #define CRYPTO_THREADID_get_callback CRYPTO_get_id_callback #endif #if defined(OPENSSL_USE_BIOMETHOD) #undef OPENSSL_USE_BIOMETHOD #endif #ifndef HAVE_OPENSSL_DEFAULT #include #define ma_malloc(A,B) malloc((A)) #undef ma_free #define ma_free(A) free((A)) #define ma_snprintf snprintf #define ma_vsnprintf vsnprintf #undef SAFE_MUTEX #endif #include #include #include extern my_bool ma_tls_initialized; extern unsigned int mariadb_deinitialize_ssl; #define MAX_SSL_ERR_LEN 100 char tls_library_version[TLS_VERSION_LENGTH]; static pthread_mutex_t LOCK_openssl_config; #ifndef HAVE_OPENSSL_1_1_API static pthread_mutex_t *LOCK_crypto= NULL; #endif #if defined(OPENSSL_USE_BIOMETHOD) static int ma_bio_read(BIO *h, char *buf, int size); static int ma_bio_write(BIO *h, const char *buf, int size); static BIO_METHOD ma_BIO_method; #endif static int ma_verification_callback(int preverify_ok, X509_STORE_CTX *ctx); static long ma_tls_version_options(const char *version) { long protocol_options, disable_all_protocols; protocol_options= disable_all_protocols= SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_TLSv1 | SSL_OP_NO_TLSv1_1 | SSL_OP_NO_TLSv1_2 #ifdef TLS1_3_VERSION | SSL_OP_NO_TLSv1_3 #endif ; if (!version) return 0; if (strstr(version, "TLSv1.1")) protocol_options&= ~SSL_OP_NO_TLSv1_1; if (strstr(version, "TLSv1.2")) protocol_options&= ~SSL_OP_NO_TLSv1_2; #ifdef TLS1_3_VERSION if (strstr(version, "TLSv1.3")) protocol_options&= ~SSL_OP_NO_TLSv1_3; #endif if (protocol_options != disable_all_protocols) return protocol_options; return 0; } static void ma_tls_set_error(MYSQL *mysql) { ulong ssl_errno= ERR_get_error(); char ssl_error[MAX_SSL_ERR_LEN]; const char *ssl_error_reason; MARIADB_PVIO *pvio= mysql->net.pvio; int save_errno= errno; if (ssl_errno && (ssl_error_reason= ERR_reason_error_string(ssl_errno))) { pvio->set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, 0, ssl_error_reason); return; } else if (!save_errno) { pvio->set_error(mysql, CR_SERVER_LOST, SQLSTATE_UNKNOWN, ER(CR_SERVER_LOST)); return; } strerror_r(save_errno, ssl_error, MAX_SSL_ERR_LEN); pvio->set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, "TLS/SSL error: %s (%d)", ssl_error, save_errno); return; } #ifndef HAVE_OPENSSL_1_1_API static void my_cb_locking(int mode, int n, const char *file __attribute__((unused)), int line __attribute__((unused))) { if (mode & CRYPTO_LOCK) pthread_mutex_lock(&LOCK_crypto[n]); else pthread_mutex_unlock(&LOCK_crypto[n]); } static int ssl_thread_init() { if (LOCK_crypto == NULL) { int i, max= CRYPTO_num_locks(); if (!(LOCK_crypto= (pthread_mutex_t *)ma_malloc(sizeof(pthread_mutex_t) * max, MYF(0)))) return 1; for (i=0; i < max; i++) pthread_mutex_init(&LOCK_crypto[i], NULL); CRYPTO_set_locking_callback(my_cb_locking); } return 0; } #endif #if defined(_WIN32) || !defined(DISABLE_SIGPIPE) #define disable_sigpipe() #else #include static void ma_sigpipe_handler() { } static void disable_sigpipe() { struct sigaction old_handler, new_handler={NULL}; if (!sigaction (SIGPIPE, NULL, &old_handler) && !old_handler.sa_handler) { new_handler.sa_handler= ma_sigpipe_handler; new_handler.sa_flags= 0; if (!sigemptyset(&new_handler.sa_mask)) sigaction(SIGPIPE, &new_handler, NULL); } } #endif /* Initializes SSL SYNOPSIS my_ssl_start mysql connection handle RETURN VALUES 0 success 1 error */ int ma_tls_start(char *errmsg __attribute__((unused)), size_t errmsg_len __attribute__((unused))) { int rc= 1; char *p; if (ma_tls_initialized) return 0; /* lock mutex to prevent multiple initialization */ pthread_mutex_init(&LOCK_openssl_config, NULL); pthread_mutex_lock(&LOCK_openssl_config); #ifdef HAVE_OPENSSL_1_1_API if (!OPENSSL_init_ssl(OPENSSL_INIT_LOAD_CONFIG, NULL)) goto end; #else if (ssl_thread_init()) { strncpy(errmsg, "Not enough memory", errmsg_len); goto end; } SSL_library_init(); #if SSLEAY_VERSION_NUMBER >= 0x00907000L OPENSSL_config(NULL); #endif #endif #ifndef HAVE_OPENSSL_1_1_API /* load errors */ SSL_load_error_strings(); /* digests and ciphers */ OpenSSL_add_all_algorithms(); #endif disable_sigpipe(); #ifdef OPENSSL_USE_BIOMETHOD memcpy(&ma_BIO_method, BIO_s_socket(), sizeof(BIO_METHOD)); ma_BIO_method.bread= ma_bio_read; ma_BIO_method.bwrite= ma_bio_write; #endif snprintf(tls_library_version, TLS_VERSION_LENGTH - 1, "%s", #if defined(LIBRESSL_VERSION_NUMBER) || !defined(HAVE_OPENSSL_1_1_API) SSLeay_version(SSLEAY_VERSION)); #else OpenSSL_version(OPENSSL_VERSION)); #endif /* remove date from version */ if ((p= strstr(tls_library_version, " "))) *p= 0; rc= 0; ma_tls_initialized= TRUE; end: pthread_mutex_unlock(&LOCK_openssl_config); return rc; } /* Release SSL and free resources Will be automatically executed by mysql_server_end() function SYNOPSIS my_ssl_end() void RETURN VALUES void */ void ma_tls_end() { if (ma_tls_initialized) { pthread_mutex_lock(&LOCK_openssl_config); #ifndef HAVE_OPENSSL_1_1_API if (LOCK_crypto) { int i; CRYPTO_set_locking_callback(NULL); CRYPTO_THREADID_set_callback(NULL); for (i=0; i < CRYPTO_num_locks(); i++) pthread_mutex_destroy(&LOCK_crypto[i]); ma_free((gptr)LOCK_crypto); LOCK_crypto= NULL; } #endif if (mariadb_deinitialize_ssl) { #ifndef HAVE_OPENSSL_1_1_API ERR_remove_thread_state(NULL); EVP_cleanup(); CRYPTO_cleanup_all_ex_data(); ERR_free_strings(); CONF_modules_free(); CONF_modules_unload(1); #endif } ma_tls_initialized= FALSE; pthread_mutex_unlock(&LOCK_openssl_config); pthread_mutex_destroy(&LOCK_openssl_config); } return; } int ma_tls_get_password(char *buf, int size, int rwflag __attribute__((unused)), void *userdata) { memset(buf, 0, size); if (userdata) strncpy(buf, (char *)userdata, size); return (int)strlen(buf); } static int ma_tls_set_certs(MYSQL *mysql, SSL_CTX *ctx) { 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)) { if( #ifdef TLS1_3_VERSION SSL_CTX_set_ciphersuites(ctx, mysql->options.ssl_cipher) == 0 && #endif SSL_CTX_set_cipher_list(ctx, mysql->options.ssl_cipher) == 0) goto error; } /* ca_file and ca_path */ if (!SSL_CTX_load_verify_locations(ctx, mysql->options.ssl_ca, mysql->options.ssl_capath)) { if (mysql->options.ssl_ca || mysql->options.ssl_capath) goto error; if (SSL_CTX_set_default_verify_paths(ctx) == 0) goto error; } if (mysql->options.extension && (mysql->options.extension->ssl_crl || mysql->options.extension->ssl_crlpath)) { X509_STORE *certstore; if ((certstore= SSL_CTX_get_cert_store(ctx))) { if (X509_STORE_load_locations(certstore, mysql->options.extension->ssl_crl, mysql->options.extension->ssl_crlpath) == 0) goto error; if (X509_STORE_set_flags(certstore, X509_V_FLAG_CRL_CHECK | X509_V_FLAG_CRL_CHECK_ALL) == 0) goto error; } } if (keyfile && !certfile) certfile= keyfile; if (certfile && !keyfile) keyfile= certfile; /* set cert */ if (certfile && certfile[0] != 0) { if (SSL_CTX_use_certificate_chain_file(ctx, certfile) != 1) { goto error; } } if (keyfile && keyfile[0]) { FILE *fp; if ((fp= fopen(keyfile, "rb"))) { EVP_PKEY *key= EVP_PKEY_new(); PEM_read_PrivateKey(fp, &key, NULL, pw); fclose(fp); if (SSL_CTX_use_PrivateKey(ctx, 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; } } /* verify key */ if (certfile && SSL_CTX_check_private_key(ctx) != 1) goto error; SSL_CTX_set_verify(ctx, (mysql->options.ssl_ca || mysql->options.ssl_capath) ? SSL_VERIFY_PEER : SSL_VERIFY_NONE, NULL); return 0; error: ma_tls_set_error(mysql); return 1; } void *ma_tls_init(MYSQL *mysql) { SSL *ssl= NULL; SSL_CTX *ctx= NULL; long default_options= SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_TLSv1; long options= 0; pthread_mutex_lock(&LOCK_openssl_config); #if OPENSSL_VERSION_NUMBER >= 0x10100000L if (!(ctx= SSL_CTX_new(TLS_client_method()))) #else if (!(ctx= SSL_CTX_new(SSLv23_client_method()))) #endif goto error; if (mysql->options.extension) options= ma_tls_version_options(mysql->options.extension->tls_version); SSL_CTX_set_options(ctx, options ? options : default_options); if (ma_tls_set_certs(mysql, ctx)) { goto error; } if (!(ssl= SSL_new(ctx))) goto error; if (!SSL_set_app_data(ssl, mysql)) goto error; pthread_mutex_unlock(&LOCK_openssl_config); return (void *)ssl; error: pthread_mutex_unlock(&LOCK_openssl_config); if (ctx) SSL_CTX_free(ctx); if (ssl) SSL_free(ssl); return NULL; } unsigned int ma_tls_get_peer_cert_info(MARIADB_TLS *ctls, uint hash_size) { X509 *cert; unsigned int hash_alg; SSL *ssl; char fp[129]; switch (hash_size) { case 0: case 256: hash_alg= MA_HASH_SHA256; break; case 384: hash_alg= MA_HASH_SHA384; break; case 512: hash_alg= MA_HASH_SHA512; break; default: return 1; } if (!ctls || !ctls->ssl) return 1; ssl= (SSL *)ctls->ssl; /* Store peer certificate information */ if (!ctls->cert_info.version) { if ((cert= SSL_get_peer_certificate(ssl))) { #if OPENSSL_VERSION_NUMBER >= 0x10101000L const ASN1_TIME *not_before= X509_get0_notBefore(cert), *not_after= X509_get0_notAfter(cert); ASN1_TIME_to_tm(not_before, (struct tm *)&ctls->cert_info.not_before); ASN1_TIME_to_tm(not_after, (struct tm *)&ctls->cert_info.not_after); #else const ASN1_TIME *not_before= X509_get_notBefore(cert), *not_after= X509_get_notAfter(cert); time_t now, from, to; int pday, psec; /* ANS1_TIME_diff returns days and seconds between now and the specified ASN1_TIME */ time(&now); ASN1_TIME_diff(&pday, &psec, not_before, NULL); from= now - (pday * 86400 + psec); gmtime_r(&from, &ctls->cert_info.not_before); ASN1_TIME_diff(&pday, &psec, NULL, not_after); to= now + (pday * 86400 + psec); gmtime_r(&to, &ctls->cert_info.not_after); #endif ctls->cert_info.subject= X509_NAME_oneline(X509_get_subject_name(cert), NULL, 0); ctls->cert_info.issuer= X509_NAME_oneline(X509_get_issuer_name(cert), NULL, 0); ctls->cert_info.version= X509_get_version(cert) + 1; ctls->cert_info.fingerprint[0]= 0; X509_free(cert); } else return 1; } if (strlen(ctls->cert_info.fingerprint) != hash_size/4) { ma_tls_get_finger_print(ctls, hash_alg, fp, sizeof(fp)); mysql_hex_string(ctls->cert_info.fingerprint, fp, hash_size/8); } return 0; } my_bool ma_tls_connect(MARIADB_TLS *ctls) { SSL *ssl = (SSL *)ctls->ssl; my_bool blocking, try_connect= 1; MYSQL *mysql; MARIADB_PVIO *pvio; int rc; #ifdef OPENSSL_USE_BIOMETHOD BIO_METHOD *bio_method= NULL; BIO *bio; #endif mysql= (MYSQL *)SSL_get_app_data(ssl); pvio= mysql->net.pvio; /* Set socket to non blocking if not already set */ if (!(blocking= pvio->methods->is_blocking(pvio))) pvio->methods->blocking(pvio, FALSE, 0); SSL_clear(ssl); #ifdef OPENSSL_USE_BIOMETHOD bio= BIO_new(&ma_BIO_method); bio->ptr= pvio; SSL_set_bio(ssl, bio, bio); BIO_set_fd(bio, mysql_get_socket(mysql), BIO_NOCLOSE); #else SSL_set_fd(ssl, (int)mysql_get_socket(mysql)); #endif /* CONC-732: Always set verification callback to avoid OpenSSL output */ SSL_set_verify(ssl, SSL_VERIFY_PEER, ma_verification_callback); while (try_connect && (rc= SSL_connect(ssl)) == -1) { switch((SSL_get_error(ssl, rc))) { case SSL_ERROR_WANT_READ: /* use low timeout, see ma_tls_read */ if (pvio->methods->wait_io_or_timeout(pvio, TRUE, 5) < 1) try_connect= 0; break; case SSL_ERROR_WANT_WRITE: /* use low timeout, see ma_tls_read */ if (pvio->methods->wait_io_or_timeout(pvio, TRUE, 5) < 1) try_connect= 0; break; default: try_connect= 0; } } if (rc != 1) { ma_tls_set_error(mysql); return 1; } pvio->ctls->ssl= ctls->ssl= (void *)ssl; return 0; } static my_bool ma_tls_async_check_result(int res, struct mysql_async_context *b, SSL *ssl) { int ssl_err; b->events_to_wait_for= 0; if (res > 0) return 1; ssl_err= SSL_get_error(ssl, res); if (ssl_err == SSL_ERROR_WANT_READ) b->events_to_wait_for|= MYSQL_WAIT_READ; else if (ssl_err == SSL_ERROR_WANT_WRITE) b->events_to_wait_for|= MYSQL_WAIT_WRITE; else return 1; if (b->suspend_resume_hook) (*b->suspend_resume_hook)(TRUE, b->suspend_resume_hook_user_data); my_context_yield(&b->async_context); if (b->suspend_resume_hook) (*b->suspend_resume_hook)(FALSE, b->suspend_resume_hook_user_data); return 0; } ssize_t ma_tls_read_async(MARIADB_PVIO *pvio, const unsigned char *buffer, size_t length) { int res; struct mysql_async_context *b= pvio->mysql->options.extension->async_context; MARIADB_TLS *ctls= pvio->ctls; for (;;) { res= SSL_read((SSL *)ctls->ssl, (void *)buffer, (int)length); if (ma_tls_async_check_result(res, b, (SSL *)ctls->ssl)) return res; } } ssize_t ma_tls_write_async(MARIADB_PVIO *pvio, const unsigned char *buffer, size_t length) { int res; struct mysql_async_context *b= pvio->mysql->options.extension->async_context; MARIADB_TLS *ctls= pvio->ctls; for (;;) { res= SSL_write((SSL *)ctls->ssl, (void *)buffer, (int)length); if (ma_tls_async_check_result(res, b, (SSL *)ctls->ssl)) return res; } } ssize_t ma_tls_read(MARIADB_TLS *ctls, const uchar* buffer, size_t length) { int rc; MARIADB_PVIO *pvio= ctls->pvio; while ((rc= SSL_read((SSL *)ctls->ssl, (void *)buffer, (int)length)) <= 0) { int error= SSL_get_error((SSL *)ctls->ssl, rc); if (error != SSL_ERROR_WANT_READ) break; /* To get a more precise error message than "resource temporary unavailable" (=errno 11) after read timeout occured, we check the socket status using a very small timeout (=5 ms) */ if (pvio->methods->wait_io_or_timeout(pvio, TRUE, 5) < 1) break; } if (rc <= 0) { MYSQL *mysql= SSL_get_app_data(ctls->ssl); ma_tls_set_error(mysql); } return rc; } ssize_t ma_tls_write(MARIADB_TLS *ctls, const uchar* buffer, size_t length) { int rc; MARIADB_PVIO *pvio= ctls->pvio; while ((rc= SSL_write((SSL *)ctls->ssl, (void *)buffer, (int)length)) <= 0) { int error= SSL_get_error((SSL *)ctls->ssl, rc); if (error != SSL_ERROR_WANT_WRITE) break; /* use low timeout, see ma_tls_read */ if (pvio->methods->wait_io_or_timeout(pvio, TRUE, 5) < 1) break; } if (rc <= 0) { MYSQL *mysql= SSL_get_app_data(ctls->ssl); ma_tls_set_error(mysql); } return rc; } my_bool ma_tls_close(MARIADB_TLS *ctls) { int i, rc; SSL *ssl; SSL_CTX *ctx= NULL; if (!ctls || !ctls->ssl) return 1; ssl= (SSL *)ctls->ssl; ctx= SSL_get_SSL_CTX(ssl); if (ctx) SSL_CTX_free(ctx); SSL_set_quiet_shutdown(ssl, 1); /* 2 x pending + 2 * data = 4 */ for (i=0; i < 4; i++) if ((rc= SSL_shutdown(ssl))) break; /* Since we transferred ownership of BIO to ssl, BIO will automatically freed - no need for an explicit BIO_free_all */ SSL_free(ssl); ctls->ssl= NULL; OPENSSL_free(ctls->cert_info.issuer); OPENSSL_free(ctls->cert_info.subject); return rc; } /** Check for possible errors, and store the result in net.tls_verify_status. verification will happen after handshake by ma_tls_verify_server_cert(). To retrieve all errors, this callback function returns always true. (By default OpenSSL stops verification after first error */ static int ma_verification_callback(int preverify_ok __attribute__((unused)), X509_STORE_CTX *ctx) { SSL *ssl; if ((ssl = X509_STORE_CTX_get_ex_data(ctx, SSL_get_ex_data_X509_STORE_CTX_idx()))) { MYSQL *mysql= (MYSQL *)SSL_get_app_data(ssl); int x509_err= X509_STORE_CTX_get_error(ctx); my_bool verify_status= MARIADB_TLS_VERIFY_OK; if ((x509_err == X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT || x509_err == X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN)) verify_status= MARIADB_TLS_VERIFY_TRUST; else if (x509_err == X509_V_ERR_CERT_REVOKED) verify_status= MARIADB_TLS_VERIFY_REVOKED; else if (x509_err == X509_V_ERR_CERT_NOT_YET_VALID || x509_err == X509_V_ERR_CERT_HAS_EXPIRED) verify_status= MARIADB_TLS_VERIFY_PERIOD; else if (x509_err != X509_V_OK) verify_status= MARIADB_TLS_VERIFY_UNKNOWN; if (verify_status) { if (mysql->net.tls_verify_status < verify_status) my_set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, ER(CR_SSL_CONNECTION_ERROR), X509_verify_cert_error_string(x509_err)); mysql->net.tls_verify_status|= verify_status; } } /* continue verification */ return 1; } int ma_tls_verify_server_cert(MARIADB_TLS *ctls, unsigned int verify_flags) { X509 *cert= NULL; MYSQL *mysql; SSL *ssl; MARIADB_PVIO *pvio; #if !defined(HAVE_OPENSSL_CHECK_HOST) X509_NAME *x509sn; int cn_pos; X509_NAME_ENTRY *cn_entry; ASN1_STRING *cn_asn1; const char *cn_str; #endif if (!ctls || !ctls->ssl) return 1; ssl= (SSL *)ctls->ssl; mysql= (MYSQL *)SSL_get_app_data(ssl); pvio= mysql->net.pvio; if ((mysql->net.tls_verify_status > MARIADB_TLS_VERIFY_FINGERPRINT) || (mysql->net.tls_verify_status & verify_flags)) { return MARIADB_TLS_VERIFY_ERROR; } if (verify_flags & MARIADB_TLS_VERIFY_HOST) { if (!mysql->host) { pvio->set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, ER(CR_SSL_CONNECTION_ERROR), "Invalid (empty) hostname"); mysql->net.tls_verify_status|= MARIADB_TLS_VERIFY_HOST; return MARIADB_TLS_VERIFY_ERROR; } if (!(cert= SSL_get_peer_certificate(ssl))) { pvio->set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, ER(CR_SSL_CONNECTION_ERROR), "Unable to get server certificate"); mysql->net.tls_verify_status|= MARIADB_TLS_VERIFY_UNKNOWN; return MARIADB_TLS_VERIFY_ERROR; } #ifdef HAVE_OPENSSL_CHECK_HOST if (X509_check_host(cert, mysql->host, strlen(mysql->host), 0, 0) != 1 && X509_check_ip_asc(cert, mysql->host, 0) != 1) { mysql->net.tls_verify_status|= MARIADB_TLS_VERIFY_HOST; goto error; } #else x509sn= X509_get_subject_name(cert); if ((cn_pos= X509_NAME_get_index_by_NID(x509sn, NID_commonName, -1)) < 0) { mysql->net.tls_verify_status|= MARIADB_TLS_VERIFY_HOST; goto error; } if (!(cn_entry= X509_NAME_get_entry(x509sn, cn_pos))) { mysql->net.tls_verify_status|= MARIADB_TLS_VERIFY_HOST; goto error; } if (!(cn_asn1 = X509_NAME_ENTRY_get_data(cn_entry))) { mysql->net.tls_verify_status|= MARIADB_TLS_VERIFY_HOST; goto error; } cn_str = (char *)ASN1_STRING_data(cn_asn1); /* Make sure there is no embedded \0 in the CN */ if ((size_t)ASN1_STRING_length(cn_asn1) != strlen(cn_str)) { mysql->net.tls_verify_status|= MARIADB_TLS_VERIFY_HOST; goto error; } if (strcmp(cn_str, mysql->host)) { mysql->net.tls_verify_status|= MARIADB_TLS_VERIFY_HOST; goto error; } #endif X509_free(cert); } return 0; error: X509_free(cert); return 1; } const char *ma_tls_get_cipher(MARIADB_TLS *ctls) { if (!ctls || !ctls->ssl) return NULL; return SSL_get_cipher_name(ctls->ssl); } unsigned int ma_tls_get_finger_print(MARIADB_TLS *ctls, uint hash_type, char *fp, unsigned int len) { X509 *cert= NULL; MYSQL *mysql; unsigned int fp_len; const EVP_MD *hash_alg; unsigned int max_len= EVP_MAX_MD_SIZE; if (!ctls || !ctls->ssl) return 0; mysql = SSL_get_app_data(ctls->ssl); switch (hash_type) { case MA_HASH_SHA1: hash_alg = EVP_sha1(); max_len= 20; break; case MA_HASH_SHA224: hash_alg = EVP_sha224(); max_len= 28; break; case MA_HASH_SHA256: hash_alg = EVP_sha256(); max_len= 32; break; case MA_HASH_SHA384: hash_alg = EVP_sha384(); max_len= 48; break; case MA_HASH_SHA512: hash_alg = EVP_sha512(); break; default: my_set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, ER(CR_SSL_CONNECTION_ERROR), "Cannot detect hash algorithm for fingerprint verification"); return 0; } if (len < max_len) { my_set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, ER(CR_SSL_CONNECTION_ERROR), "Finger print buffer too small"); return 0; } if (!(cert= SSL_get_peer_certificate(ctls->ssl))) { my_set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, ER(CR_SSL_CONNECTION_ERROR), "Unable to get server certificate"); goto end; } if (!X509_digest(cert, hash_alg, (unsigned char *)fp, &fp_len)) { my_set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, ER(CR_SSL_CONNECTION_ERROR), "invalid finger print of server certificate"); goto end; } X509_free(cert); return (fp_len); end: X509_free(cert); return 0; } int ma_tls_get_protocol_version(MARIADB_TLS *ctls) { if (!ctls || !ctls->ssl) return -1; return SSL_version(ctls->ssl) & 0xFF; } void ma_tls_set_connection(MYSQL *mysql) { (void)SSL_set_app_data(mysql->net.pvio->ctls->ssl, mysql); }