diff options
Diffstat (limited to 'mariadb-connector-c-v_2.3.7/libmariadb/ma_secure.c')
-rw-r--r-- | mariadb-connector-c-v_2.3.7/libmariadb/ma_secure.c | 700 |
1 files changed, 700 insertions, 0 deletions
diff --git a/mariadb-connector-c-v_2.3.7/libmariadb/ma_secure.c b/mariadb-connector-c-v_2.3.7/libmariadb/ma_secure.c new file mode 100644 index 0000000..5dfc981 --- /dev/null +++ b/mariadb-connector-c-v_2.3.7/libmariadb/ma_secure.c @@ -0,0 +1,700 @@ +/************************************************************************************ + 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 <http://www.gnu.org/licenses> + or write to the Free Software Foundation, Inc., + 51 Franklin St., Fifth Floor, Boston, MA 02110, USA + + *************************************************************************************/ +unsigned int mariadb_deinitialize_ssl= 1; +#ifdef HAVE_OPENSSL + +#include <my_global.h> +#include <my_sys.h> +#include <ma_common.h> +#include <ma_secure.h> +#include <errmsg.h> +#include <violite.h> +#include <mysql_async.h> +#include <my_context.h> +#include <string.h> + +static my_bool my_ssl_initialized= FALSE; +static SSL_CTX *SSL_context= NULL; + +#define MAX_SSL_ERR_LEN 100 + +extern pthread_mutex_t LOCK_ssl_config; +static pthread_mutex_t *LOCK_crypto= NULL; + +/* + SSL error handling +*/ +static void my_SSL_error(MYSQL *mysql) +{ + ulong ssl_errno= ERR_get_error(); + char ssl_error[MAX_SSL_ERR_LEN]; + const char *ssl_error_reason; + + DBUG_ENTER("my_SSL_error"); + + if (mysql_errno(mysql)) + DBUG_VOID_RETURN; + + if (!ssl_errno) + { + my_set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, "Unknown SSL error"); + DBUG_VOID_RETURN; + } + if ((ssl_error_reason= ERR_reason_error_string(ssl_errno))) + { + my_set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, + ER(CR_SSL_CONNECTION_ERROR), ssl_error_reason); + DBUG_VOID_RETURN; + } + my_snprintf(ssl_error, MAX_SSL_ERR_LEN, "SSL errno=%lu", ssl_errno, mysql->charset); + my_set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, + ER(CR_SSL_CONNECTION_ERROR), ssl_error); + DBUG_VOID_RETURN; +} + +/* + thread safe callbacks for OpenSSL + Crypto call back functions will be + set during ssl_initialization + */ +#if OPENSSL_VERSION_NUMBER < 0x10100000 +#if (OPENSSL_VERSION_NUMBER < 0x10000000) +static unsigned long my_cb_threadid(void) +{ + /* cast pthread_t to unsigned long */ + return (unsigned long) pthread_self(); +} +#else +static void my_cb_threadid(CRYPTO_THREADID *id) +{ + CRYPTO_THREADID_set_numeric(id, (unsigned long)pthread_self()); +} +#endif + +static void my_cb_locking(int mode, int n, const char *file, int line) +{ + if (mode & CRYPTO_LOCK) + pthread_mutex_lock(&LOCK_crypto[n]); + else + pthread_mutex_unlock(&LOCK_crypto[n]); +} + + +static int ssl_crypto_init() +{ + int i, rc= 1, max= CRYPTO_num_locks(); + +#if (OPENSSL_VERSION_NUMBER < 0x10000000) + CRYPTO_set_id_callback(my_cb_threadid); +#else + rc= CRYPTO_THREADID_set_callback(my_cb_threadid); +#endif + + /* if someone else already set callbacks + * there is nothing do */ + if (!rc) + return 0; + + if (LOCK_crypto == NULL) + { + if (!(LOCK_crypto= + (pthread_mutex_t *)my_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 + +/* + Initializes SSL and allocate global + context SSL_context + + SYNOPSIS + my_ssl_start + mysql connection handle + + RETURN VALUES + 0 success + 1 error +*/ +int my_ssl_start(MYSQL *mysql) +{ + int rc= 0; + DBUG_ENTER("my_ssl_start"); + /* lock mutex to prevent multiple initialization */ + pthread_mutex_lock(&LOCK_ssl_config); + if (!my_ssl_initialized) + { +#if OPENSSL_VERSION_NUMBER < 0x10100000 + if (ssl_crypto_init()) + goto end; +#endif +#if OPENSSL_VERSION_NUMBER >= 0x10100000L + OPENSSL_init_ssl(OPENSSL_INIT_LOAD_CONFIG, NULL); +#else + SSL_library_init(); +#if SSLEAY_VERSION_NUMBER >= 0x00907000L + OPENSSL_config(NULL); +#endif +#endif + + /* load errors */ + SSL_load_error_strings(); + /* digests and ciphers */ + OpenSSL_add_all_algorithms(); + +#if OPENSSL_VERSION_NUMBER >= 0x10100000L + if (!(SSL_context= SSL_CTX_new(TLS_client_method()))) +#else + if (!(SSL_context= SSL_CTX_new(SSLv23_client_method()))) +#endif + { + my_SSL_error(mysql); + rc= 1; + goto end; + } + /* use server preferences instead of client preferences: + client sends lowest tls version (=1.0) first, and will + update to the version the server sent in client hellp + response packet */ + SSL_CTX_set_options(SSL_context, SSL_OP_ALL); + my_ssl_initialized= TRUE; + } +end: + pthread_mutex_unlock(&LOCK_ssl_config); + DBUG_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 my_ssl_end() +{ + DBUG_ENTER("my_ssl_end"); + pthread_mutex_lock(&LOCK_ssl_config); + if (my_ssl_initialized) + { + int i; + + if (LOCK_crypto) + { + CRYPTO_set_locking_callback(NULL); + CRYPTO_set_id_callback(NULL); + + for (i=0; i < CRYPTO_num_locks(); i++) + pthread_mutex_destroy(&LOCK_crypto[i]); + + my_free(LOCK_crypto); + LOCK_crypto= NULL; + } + + if (SSL_context) + { + SSL_CTX_free(SSL_context); + SSL_context= FALSE; + } + if (mariadb_deinitialize_ssl) + { +#if OPENSSL_VERSION_NUMBER < 0x10100000 + ERR_remove_state(0); +#endif + EVP_cleanup(); + CRYPTO_cleanup_all_ex_data(); + ERR_free_strings(); + CONF_modules_free(); + CONF_modules_unload(1); + } + my_ssl_initialized= FALSE; + } + pthread_mutex_unlock(&LOCK_ssl_config); + pthread_mutex_destroy(&LOCK_ssl_config); + DBUG_VOID_RETURN; +} + +/* + Set certification stuff. +*/ +static int my_ssl_set_certs(MYSQL *mysql) +{ + char *certfile= mysql->options.ssl_cert, + *keyfile= mysql->options.ssl_key; + + DBUG_ENTER("my_ssl_set_certs"); + + /* Make sure that ssl was allocated and + ssl_system was initialized */ + DBUG_ASSERT(my_ssl_initialized == TRUE); + + /* 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) + goto error; + + /* ca_file and ca_path */ + if (SSL_CTX_load_verify_locations(SSL_context, + mysql->options.ssl_ca, + mysql->options.ssl_capath) == 0) + { + if (mysql->options.ssl_ca || mysql->options.ssl_capath) + goto error; + if (SSL_CTX_set_default_verify_paths(SSL_context) == 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_file(SSL_context, certfile, SSL_FILETYPE_PEM) != 1) + goto error; + + /* set key */ + if (keyfile && keyfile[0]) + { + if (SSL_CTX_use_PrivateKey_file(SSL_context, keyfile, SSL_FILETYPE_PEM) != 1) + goto error; + } + /* verify key */ + if (certfile && !SSL_CTX_check_private_key(SSL_context)) + 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(SSL_context))) + { + if (X509_STORE_load_locations(certstore, mysql->options.extension->ssl_crl, + mysql->options.extension->ssl_crlpath) == 0 || + X509_STORE_set_flags(certstore, X509_V_FLAG_CRL_CHECK | X509_V_FLAG_CRL_CHECK_ALL) == 0) + goto error; + } + } + + DBUG_RETURN(0); + +error: + my_SSL_error(mysql); + DBUG_RETURN(1); +} + +static unsigned int ma_get_cert_fingerprint(X509 *cert, EVP_MD *digest, + unsigned char *fingerprint, unsigned int *fp_length) +{ + if (*fp_length < EVP_MD_size(digest)) + return 0; + if (!X509_digest(cert, digest, fingerprint, fp_length)) + return 0; + return *fp_length; +} + +static my_bool ma_check_fingerprint(char *fp1, unsigned int fp1_len, + char *fp2, unsigned int fp2_len) +{ + /* SHA1 fingerprint (160 bit) / 8 * 2 + 1 */ + char hexstr[41]; + + fp1_len= (unsigned int)mysql_hex_string(hexstr, fp1, fp1_len); +#ifdef _WIN32 + if (_strnicmp(hexstr, fp2, fp1_len) != 0) +#else + if (strncasecmp(hexstr, fp2, fp1_len) != 0) +#endif + return 1; + return 0; +} + +/* + allocates a new ssl object + + SYNOPSIS + my_ssl_init + mysql connection object + + RETURN VALUES + NULL on error + SSL new SSL object +*/ +SSL *my_ssl_init(MYSQL *mysql) +{ + SSL *ssl= NULL; + + DBUG_ENTER("my_ssl_init"); + + DBUG_ASSERT(mysql->net.vio->ssl == NULL); + + if (!my_ssl_initialized) + my_ssl_start(mysql); + + pthread_mutex_lock(&LOCK_ssl_config); + if (my_ssl_set_certs(mysql)) + goto error; + + if (!(ssl= SSL_new(SSL_context))) + goto error; + + if (!SSL_set_app_data(ssl, mysql)) + goto error; + + pthread_mutex_unlock(&LOCK_ssl_config); + DBUG_RETURN(ssl); +error: + pthread_mutex_unlock(&LOCK_ssl_config); + if (ssl) + SSL_free(ssl); + DBUG_RETURN(NULL); +} + +/* + establish SSL connection between client + and server + + SYNOPSIS + my_ssl_connect + ssl ssl object + + RETURN VALUES + 0 success + 1 error +*/ +int my_ssl_connect(SSL *ssl) +{ + my_bool blocking; + MYSQL *mysql; + long rc; + my_bool try_connect= 1; + + DBUG_ENTER("my_ssl_connect"); + + DBUG_ASSERT(ssl != NULL); + + mysql= (MYSQL *)SSL_get_app_data(ssl); + CLEAR_CLIENT_ERROR(mysql); + + /* Set socket to non blocking */ + if (!(blocking= vio_is_blocking(mysql->net.vio))) + vio_blocking(mysql->net.vio, FALSE, 0); + + SSL_clear(ssl); + SSL_SESSION_set_timeout(SSL_get_session(ssl), + mysql->options.connect_timeout); + SSL_set_fd(ssl, mysql->net.vio->sd); + + while (try_connect && (rc= SSL_connect(ssl)) == -1) + { + switch(SSL_get_error(ssl, rc)) { + case SSL_ERROR_WANT_READ: + if (vio_wait_or_timeout(mysql->net.vio, TRUE, mysql->options.connect_timeout) < 1) + try_connect= 0; + break; + case SSL_ERROR_WANT_WRITE: + if (vio_wait_or_timeout(mysql->net.vio, TRUE, mysql->options.connect_timeout) < 1) + try_connect= 0; + break; + default: + try_connect= 0; + } + } + if (rc != 1) + { + my_SSL_error(mysql); + DBUG_RETURN(1); + } + + if ((mysql->client_flag & CLIENT_SSL_VERIFY_SERVER_CERT)) + { + rc= SSL_get_verify_result(ssl); + if (rc != X509_V_OK) + { + my_set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, + ER(CR_SSL_CONNECTION_ERROR), X509_verify_cert_error_string(rc)); + /* restore blocking mode */ + if (!blocking) + vio_blocking(mysql->net.vio, FALSE, 0); + + DBUG_RETURN(1); + } + } + + vio_reset(mysql->net.vio, VIO_TYPE_SSL, mysql->net.vio->sd, 0, 0); + mysql->net.vio->ssl= ssl; + DBUG_RETURN(0); +} + +int ma_ssl_verify_fingerprint(SSL *ssl) +{ + X509 *cert= SSL_get_peer_certificate(ssl); + MYSQL *mysql= (MYSQL *)SSL_get_app_data(ssl); + unsigned char fingerprint[EVP_MAX_MD_SIZE]; + EVP_MD *digest; + unsigned int fp_length; + + DBUG_ENTER("ma_ssl_verify_fingerprint"); + + if (!cert) + { + my_set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, + ER(CR_SSL_CONNECTION_ERROR), + "Unable to get server certificate"); + DBUG_RETURN(1); + } + + digest= (EVP_MD *)EVP_sha1(); + fp_length= sizeof(fingerprint); + + if (!ma_get_cert_fingerprint(cert, digest, fingerprint, &fp_length)) + { + my_set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, + ER(CR_SSL_CONNECTION_ERROR), + "Unable to get finger print of server certificate"); + DBUG_RETURN(1); + } + + /* single finger print was specified */ + if (mysql->options.extension->ssl_fp) + { + if (ma_check_fingerprint(fingerprint, fp_length, mysql->options.extension->ssl_fp, + strlen(mysql->options.extension->ssl_fp))) + { + my_set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, + ER(CR_SSL_CONNECTION_ERROR), + "invalid finger print of server certificate"); + DBUG_RETURN(1); + } + } + + /* white list of finger prints was specified */ + if (mysql->options.extension->ssl_fp_list) + { + FILE *fp; + char buff[255]; + + if (!(fp = my_fopen(mysql->options.extension->ssl_fp_list ,O_RDONLY, MYF(0)))) + { + my_set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, + ER(CR_SSL_CONNECTION_ERROR), + "Can't open finger print list"); + DBUG_RETURN(1); + } + + while (fgets(buff, sizeof(buff)-1, fp)) + { + /* remove trailing new line character */ + char *pos= strchr(buff, '\r'); + if (!pos) + pos= strchr(buff, '\n'); + if (pos) + *pos= '\0'; + + if (!ma_check_fingerprint(fingerprint, fp_length, buff, strlen(buff))) + { + /* finger print is valid: close file and exit */ + my_fclose(fp, MYF(0)); + DBUG_RETURN(0); + } + } + + /* No finger print matched - close file and return error */ + my_fclose(fp, MYF(0)); + my_set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, + ER(CR_SSL_CONNECTION_ERROR), + "invalid finger print of server certificate"); + DBUG_RETURN(1); + } + DBUG_RETURN(0); +} + +/* + verify server certificate + + SYNOPSIS + my_ssl_verify_server_cert() + MYSQL mysql + mybool verify_server_cert; + + RETURN VALUES + 1 Error + 0 OK +*/ + +int my_ssl_verify_server_cert(SSL *ssl) +{ + X509 *cert; + MYSQL *mysql; + X509_NAME *x509sn; + int cn_pos; + X509_NAME_ENTRY *cn_entry; + ASN1_STRING *cn_asn1; + const char *cn_str; + + DBUG_ENTER("my_ssl_verify_server_cert"); + + mysql= (MYSQL *)SSL_get_app_data(ssl); + + if (!mysql->host) + { + my_set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, + ER(CR_SSL_CONNECTION_ERROR), + "Invalid (empty) hostname"); + DBUG_RETURN(1); + } + + if (!(cert= SSL_get_peer_certificate(ssl))) + { + my_set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, + ER(CR_SSL_CONNECTION_ERROR), + "Unable to get server certificate"); + DBUG_RETURN(1); + } + + x509sn= X509_get_subject_name(cert); + + if ((cn_pos= X509_NAME_get_index_by_NID(x509sn, NID_commonName, -1)) < 0) + goto error; + + if (!(cn_entry= X509_NAME_get_entry(x509sn, cn_pos))) + goto error; + + if (!(cn_asn1 = X509_NAME_ENTRY_get_data(cn_entry))) + 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)) + goto error; + + if (strcmp(cn_str, mysql->host)) + goto error; + + X509_free(cert); + + DBUG_RETURN(0); + +error: + X509_free(cert); + + my_set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, + ER(CR_SSL_CONNECTION_ERROR), + "Validation of SSL server certificate failed"); + DBUG_RETURN(1); +} +/* + write to ssl socket + + SYNOPSIS + my_ssl_write() + vio vio + buf write buffer + size size of buffer + + RETURN VALUES + bytes written +*/ +size_t my_ssl_write(Vio *vio, const uchar* buf, size_t size) +{ + size_t written; + DBUG_ENTER("my_ssl_write"); + if (vio->async_context && vio->async_context->active) + written= my_ssl_write_async(vio->async_context, (SSL *)vio->ssl, buf, + size); + else + written= SSL_write((SSL*) vio->ssl, buf, size); + DBUG_RETURN(written); +} + +/* + read from ssl socket + + SYNOPSIS + my_ssl_read() + vio vio + buf read buffer + size_t max number of bytes to read + + RETURN VALUES + number of bytes read +*/ +size_t my_ssl_read(Vio *vio, uchar* buf, size_t size) +{ + size_t read; + DBUG_ENTER("my_ssl_read"); + + if (vio->async_context && vio->async_context->active) + read= my_ssl_read_async(vio->async_context, (SSL *)vio->ssl, buf, size); + else + read= SSL_read((SSL*) vio->ssl, buf, size); + DBUG_RETURN(read); +} + +/* + close ssl connection and free + ssl object + + SYNOPSIS + my_ssl_close() + vio vio + + RETURN VALUES + 1 ok + 0 or -1 on error +*/ +int my_ssl_close(Vio *vio) +{ + int i, rc; + DBUG_ENTER("my_ssl_close"); + + if (!vio || !vio->ssl) + DBUG_RETURN(1); + + SSL_set_quiet_shutdown(vio->ssl, 1); + /* 2 x pending + 2 * data = 4 */ + for (i=0; i < 4; i++) + if ((rc= SSL_shutdown(vio->ssl))) + break; + + SSL_free(vio->ssl); + vio->ssl= NULL; + + DBUG_RETURN(rc); +} + +#endif /* HAVE_OPENSSL */ |