From 4597cd6a8090ffaf3328356014639c0c68d447b3 Mon Sep 17 00:00:00 2001 From: Georg Richter Date: Thu, 10 Sep 2015 17:16:21 +0200 Subject: [PATCH] Various ssl and schannel fixes --- include/ma_secure.h | 45 -- include/ma_ssl.h | 1 + libmariadb/libmariadb.c | 30 +- libmariadb/ma_cio.c | 2 +- libmariadb/ma_secure.c | 688 ---------------- libmariadb/ma_ssl.c | 10 +- libmariadb/my_malloc.c | 9 +- libmariadb/secure/gnutls.c | 2 +- libmariadb/secure/ma_schannel.c | 394 +++++++--- libmariadb/secure/ma_schannel.h | 35 +- libmariadb/secure/openssl.c | 7 +- libmariadb/secure/schannel.c | 542 +++++++------ plugins/builtin/cio_socket.c | 1 + plugins/cio/tls_schannel.c | 1257 ------------------------------ plugins/trace/trace_example.c | 1 - plugins/trace/trace_example.so | Bin 36859 -> 43254 bytes unittest/libmariadb/connection.c | 2 +- unittest/libmariadb/dyncol.c | 3 +- unittest/libmariadb/misc.c | 1 + unittest/libmariadb/ssl.c.in | 74 +- 20 files changed, 731 insertions(+), 2373 deletions(-) delete mode 100644 include/ma_secure.h delete mode 100644 libmariadb/ma_secure.c delete mode 100644 plugins/cio/tls_schannel.c diff --git a/include/ma_secure.h b/include/ma_secure.h deleted file mode 100644 index b380630a..00000000 --- a/include/ma_secure.h +++ /dev/null @@ -1,45 +0,0 @@ -/************************************************************************************ - 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 - - Part of this code includes code from the PHP project which - is freely available from http://www.php.net -*************************************************************************************/ -#ifndef _ma_secure_h_ -#define _ma_secure_h_ - -#ifdef HAVE_OPENSSL -#include -#include /* SSL and SSL_CTX */ -#include /* error reporting */ -#include - -struct MYSQL; - -size_t my_ssl_read(Vio *vio, uchar* buf, size_t size); -int my_ssl_close(Vio *vio); -size_t my_ssl_write(Vio *vio, const uchar* buf, size_t size); -SSL *my_ssl_init(MYSQL *mysql); -int my_ssl_connect(SSL *ssl); -int my_ssl_verify_server_cert(SSL *ssl); -int ma_ssl_verify_fingerprint(SSL *ssl); - -int my_ssl_start(MYSQL *mysql); -void my_ssl_end(void); - -#endif /* HAVE_OPENSSL */ -#endif /* _ma_secure_h_ */ diff --git a/include/ma_ssl.h b/include/ma_ssl.h index cdd33162..b9c84b77 100644 --- a/include/ma_ssl.h +++ b/include/ma_ssl.h @@ -133,5 +133,6 @@ my_bool ma_cio_ssl_close(MARIADB_SSL *cssl); int ma_cio_ssl_verify_server_cert(MARIADB_SSL *cssl); const char *ma_cio_ssl_cipher(MARIADB_SSL *cssl); my_bool ma_cio_ssl_check_fp(MARIADB_SSL *cssl, const char *fp, const char *fp_list); +my_bool ma_cio_start_ssl(MARIADB_CIO *cio); #endif /* _ma_ssl_h_ */ diff --git a/libmariadb/libmariadb.c b/libmariadb/libmariadb.c index 4710f644..bbc03e18 100644 --- a/libmariadb/libmariadb.c +++ b/libmariadb/libmariadb.c @@ -1145,13 +1145,15 @@ int STDCALL mysql_ssl_set(MYSQL *mysql, const char *key, const char *cert, const char *ca, const char *capath, const char *cipher) { - mysql->options.ssl_key = key==0 ? 0 : my_strdup(key,MYF(0)); - mysql->options.ssl_cert = cert==0 ? 0 : my_strdup(cert,MYF(0)); - mysql->options.ssl_ca = ca==0 ? 0 : my_strdup(ca,MYF(0)); - mysql->options.ssl_capath = capath==0 ? 0 : my_strdup(capath,MYF(0)); - mysql->options.ssl_cipher = cipher==0 ? 0 : my_strdup(cipher,MYF(0)); -/* todo: add crl stuff */ +#ifdef HAVE_SSL + return (mysql_optionsv(mysql, MYSQL_OPT_SSL_KEY, key) | + mysql_optionsv(mysql, MYSQL_OPT_SSL_CERT, cert) | + mysql_optionsv(mysql, MYSQL_OPT_SSL_CA, ca) | + mysql_optionsv(mysql, MYSQL_OPT_SSL_CAPATH, capath) | + mysql_optionsv(mysql, MYSQL_OPT_SSL_CIPHER, cipher)) ? 1 : 0; +#else return 0; +#endif } /************************************************************************** @@ -2674,23 +2676,23 @@ mysql_optionsv(MYSQL *mysql,enum mysql_option option, ...) break; case MYSQL_OPT_SSL_KEY: my_free(mysql->options.ssl_key); - mysql->options.ssl_key=my_strdup((char *)arg1,MYF(MY_WME)); + mysql->options.ssl_key=my_strdup((char *)arg1,MYF(MY_WME | MY_ALLOW_ZERO_PTR)); break; case MYSQL_OPT_SSL_CERT: my_free(mysql->options.ssl_cert); - mysql->options.ssl_cert=my_strdup((char *)arg1,MYF(MY_WME)); + mysql->options.ssl_cert=my_strdup((char *)arg1,MYF(MY_WME | MY_ALLOW_ZERO_PTR)); break; case MYSQL_OPT_SSL_CA: my_free(mysql->options.ssl_ca); - mysql->options.ssl_ca=my_strdup((char *)arg1,MYF(MY_WME)); + mysql->options.ssl_ca=my_strdup((char *)arg1,MYF(MY_WME | MY_ALLOW_ZERO_PTR)); break; case MYSQL_OPT_SSL_CAPATH: my_free(mysql->options.ssl_capath); - mysql->options.ssl_capath=my_strdup((char *)arg1,MYF(MY_WME)); + mysql->options.ssl_capath=my_strdup((char *)arg1,MYF(MY_WME | MY_ALLOW_ZERO_PTR)); break; case MYSQL_OPT_SSL_CIPHER: my_free(mysql->options.ssl_cipher); - mysql->options.ssl_cipher=my_strdup((char *)arg1,MYF(MY_WME)); + mysql->options.ssl_cipher=my_strdup((char *)arg1,MYF(MY_WME | MY_ALLOW_ZERO_PTR)); break; case MYSQL_OPT_SSL_CRL: OPT_SET_EXTENDED_VALUE_STR(&mysql->options, ssl_crl, (char *)arg1); @@ -2789,6 +2791,12 @@ mysql_optionsv(MYSQL *mysql,enum mysql_option option, ...) my_free(mysql->options.bind_address); mysql->options.bind_address= my_strdup(arg1, MYF(MY_WME)); break; + case MARIADB_OPT_SSL_FP: + OPT_SET_EXTENDED_VALUE_STR(&mysql->options, ssl_fp, (char *)arg1); + break; + case MARIADB_OPT_SSL_FP_LIST: + OPT_SET_EXTENDED_VALUE_STR(&mysql->options, ssl_fp_list, (char *)arg1); + break; default: va_end(ap); DBUG_RETURN(-1); diff --git a/libmariadb/ma_cio.c b/libmariadb/ma_cio.c index 0b234a48..fabb1417 100644 --- a/libmariadb/ma_cio.c +++ b/libmariadb/ma_cio.c @@ -283,7 +283,7 @@ size_t ma_cio_cache_read(MARIADB_CIO *cio, uchar *buffer, size_t length) } memcpy(buffer, cio->cache, r); } - } + } return r; } /* }}} */ diff --git a/libmariadb/ma_secure.c b/libmariadb/ma_secure.c deleted file mode 100644 index cda76f38..00000000 --- a/libmariadb/ma_secure.c +++ /dev/null @@ -1,688 +0,0 @@ -/************************************************************************************ - 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 - - *************************************************************************************/ -unsigned int mariadb_deinitialize_ssl= 1; -#ifdef HAVE_OPENSSL - -#include -#include -#include -#include -#include -#include -#include -#include - -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 < 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; -} - - -/* - 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 (ssl_crypto_init()) - goto end; - SSL_library_init(); - -#if SSLEAY_VERSION_NUMBER >= 0x00907000L - OPENSSL_config(NULL); -#endif - /* load errors */ - SSL_load_error_strings(); - /* digests and ciphers */ - OpenSSL_add_all_algorithms(); - - if (!(SSL_context= SSL_CTX_new(TLSv1_client_method()))) - { - my_SSL_error(mysql); - rc= 1; - goto end; - } - 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) - { - ERR_remove_state(0); - EVP_cleanup(); - CRYPTO_cleanup_all_ex_data(); - ERR_free_strings(); - CONF_modules_free(); - CONF_modules_unload(1); - sk_SSL_COMP_free(SSL_COMP_get_compression_methods()); - } - 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.ssl_ca, - mysql->options.ssl_capath) == 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) -{ - char hexstr[fp1_len * 2 + 1]; - - fp1_len= (unsigned int)mysql_hex_string(hexstr, fp1, fp1_len); - if (strncasecmp(hexstr, fp2, fp1_len) != 0) - return 1; - return 0; -} - -int my_verify_callback(int ok, X509_STORE_CTX *ctx) -{ - X509 *check_cert; - SSL *ssl; - MYSQL *mysql; - DBUG_ENTER("my_verify_callback"); - - ssl = X509_STORE_CTX_get_ex_data(ctx, SSL_get_ex_data_X509_STORE_CTX_idx()); - DBUG_ASSERT(ssl != NULL); - mysql= (MYSQL *)SSL_get_app_data(ssl); - DBUG_ASSERT(mysql != NULL); - - /* skip verification if no ca_file/path was specified */ - if (!mysql->options.ssl_ca && !mysql->options.ssl_capath) - { - ok= 1; - DBUG_RETURN(1); - } - - if (!ok) - { - uint depth; - if (!(check_cert= X509_STORE_CTX_get_current_cert(ctx))) - DBUG_RETURN(0); - depth= X509_STORE_CTX_get_error_depth(ctx); - if (depth == 0) - ok= 1; - } - -/* - my_set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, - ER(CR_SSL_CONNECTION_ERROR), - X509_verify_cert_error_string(ctx->error)); -*/ - DBUG_RETURN(ok); -} - - -/* - 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) -{ - int verify; - 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; - - verify= (!mysql->options.ssl_ca && !mysql->options.ssl_capath) ? - SSL_VERIFY_NONE : SSL_VERIFY_PEER; - - SSL_CTX_set_verify(SSL_context, verify, my_verify_callback); - SSL_CTX_set_verify_depth(SSL_context, 1); - - 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; - - DBUG_ENTER("my_ssl_connect"); - - DBUG_ASSERT(ssl != NULL); - - mysql= (MYSQL *)SSL_get_app_data(ssl); - CLEAR_CLIENT_ERROR(mysql); - - /* Set socket to blocking if not already set */ - if (!(blocking= vio_is_blocking(mysql->net.vio))) - vio_blocking(mysql->net.vio, TRUE, 0); - - SSL_clear(ssl); - SSL_SESSION_set_timeout(SSL_get_session(ssl), - mysql->options.connect_timeout); - SSL_set_fd(ssl, mysql->net.vio->sd); - - if (SSL_connect(ssl) != 1) - { - my_SSL_error(mysql); - /* restore blocking mode */ - if (!blocking) - vio_blocking(mysql->net.vio, FALSE, 0); - DBUG_RETURN(1); - } - - 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; - char *p1, *p2, buf[256]; - - 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); - } - - X509_NAME_oneline(X509_get_subject_name(cert), buf, 256); - X509_free(cert); - - /* Extract the server name from buffer: - Format: ....CN=/hostname/.... */ - if ((p1= strstr(buf, "/CN="))) - { - p1+= 4; - if ((p2= strchr(p1, '/'))) - *p2= 0; - if (!strcmp(mysql->host, p1)) - DBUG_RETURN(0); - } - 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 */ diff --git a/libmariadb/ma_ssl.c b/libmariadb/ma_ssl.c index 272df926..3e9708ee 100644 --- a/libmariadb/ma_ssl.c +++ b/libmariadb/ma_ssl.c @@ -49,6 +49,7 @@ /* Errors should be handled via cio callback function */ my_bool ma_ssl_initialized= FALSE; +unsigned int mariadb_deinitialize_ssl= 1; MARIADB_SSL *ma_cio_ssl_init(MYSQL *mysql) { @@ -106,10 +107,14 @@ const char *ma_cio_ssl_cipher(MARIADB_SSL *cssl) static my_bool ma_cio_ssl_compare_fp(char *fp1, unsigned int fp1_len, char *fp2, unsigned int fp2_len) { - char hexstr[fp1_len * 2 + 1]; + char hexstr[64]; 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; } @@ -121,9 +126,8 @@ my_bool ma_cio_ssl_check_fp(MARIADB_SSL *cssl, const char *fp, const char *fp_li MYSQL *mysql; my_bool rc=1; - if (ma_ssl_get_finger_print(cssl, cert_fp, cert_fp_len) < 1) + if ((cert_fp_len= ma_ssl_get_finger_print(cssl, cert_fp, cert_fp_len)) < 1) goto end; - if (fp) rc= ma_cio_ssl_compare_fp(cert_fp, cert_fp_len, fp, strlen(fp)); else if (fp_list) diff --git a/libmariadb/my_malloc.c b/libmariadb/my_malloc.c index 0e681cae..429932d4 100644 --- a/libmariadb/my_malloc.c +++ b/libmariadb/my_malloc.c @@ -77,7 +77,14 @@ gptr my_memdup(const unsigned char *from, size_t length, myf MyFlags) my_string my_strdup(const char *from, myf MyFlags) { gptr ptr; - uint length=(uint) strlen(from)+1; + uint length; + + if (MyFlags & MY_ALLOW_ZERO_PTR) + if (!from) + return NULL; + + length=(uint) strlen(from)+1; + if ((ptr=my_malloc(length,MyFlags)) != 0) memcpy((unsigned char*) ptr, (unsigned char*) from,(size_t) length); return((my_string) ptr); diff --git a/libmariadb/secure/gnutls.c b/libmariadb/secure/gnutls.c index bb22230b..ee48201d 100644 --- a/libmariadb/secure/gnutls.c +++ b/libmariadb/secure/gnutls.c @@ -386,7 +386,7 @@ unsigned int ma_ssl_get_finger_print(MARIADB_SSL *cssl, unsigned char *fp, unsig return 0; } - if (gnutls_fingerprint(GNUTLS_DIG_MD5, &cert_list[0], fp, &fp_len) > 0) + if (gnutls_fingerprint(GNUTLS_DIG_SHA1, &cert_list[0], fp, &fp_len) == 0) return fp_len; else { diff --git a/libmariadb/secure/ma_schannel.c b/libmariadb/secure/ma_schannel.c index c66684dd..d27d895f 100644 --- a/libmariadb/secure/ma_schannel.c +++ b/libmariadb/secure/ma_schannel.c @@ -20,8 +20,75 @@ *************************************************************************************/ #include "ma_schannel.h" +#include #define SC_IO_BUFFER_SIZE 0x4000 +#define MAX_SSL_ERR_LEN 100 + +#define SCHANNEL_PAYLOAD(A) (A).cbMaximumMessage - (A).cbHeader - (A).cbTrailer + +/* {{{ void ma_schannel_set_sec_error */ +void ma_schannel_set_sec_error(MARIADB_CIO *cio, DWORD ErrorNo) +{ + MYSQL *mysql= cio->mysql; + switch(ErrorNo) { + case SEC_E_UNTRUSTED_ROOT: + cio->set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, "Untrusted root certificate"); + break; + case SEC_E_BUFFER_TOO_SMALL: + cio->set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, "Buffer too small"); + break; + case SEC_E_CRYPTO_SYSTEM_INVALID: + cio->set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, "Cipher is not supported"); + break; + case SEC_E_INSUFFICIENT_MEMORY: + cio->set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, "Out of memory"); + break; + case SEC_E_OUT_OF_SEQUENCE: + cio->set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, "Invalid message sequence"); + break; + case SEC_E_DECRYPT_FAILURE: + cio->set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, "An error occured during decrypting data"); + break; + case SEC_I_INCOMPLETE_CREDENTIALS: + cio->set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, "Incomplete credentials"); + break; + case SEC_E_ENCRYPT_FAILURE: + cio->set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, "An error occured during encrypting data"); + break; + case SEC_I_CONTEXT_EXPIRED: + cio->set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, "Context expired: "); + case SEC_E_OK: + break; + default: + cio->set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, "Unknown SSL error (%d)", ErrorNo); + } +} +/* }}} */ + +/* {{{ void ma_schnnel_set_win_error */ +void ma_schannel_set_win_error(MARIADB_CIO *cio) +{ + ulong ssl_errno= GetLastError(); + char ssl_error[MAX_SSL_ERR_LEN]; + char *ssl_error_reason= NULL; + + if (!ssl_errno) + { + cio->set_error(cio->mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, "Unknown SSL error"); + return; + } + /* todo: obtain error messge */ + FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, ssl_errno, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPTSTR) &ssl_error_reason, 0, NULL ); + cio->set_error(cio->mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, ssl_error_reason); + + if (ssl_error_reason) + LocalFree(ssl_error_reason); + return; +} +/* }}} */ /* {{{ LPBYTE ma_schannel_load_pem(const char *PemFileName, DWORD *buffer_len) */ /* @@ -44,7 +111,7 @@ LPBYTE * a pointer to a binary der object buffer_len will contain the length of binary der object */ -static LPBYTE ma_schannel_load_pem(const char *PemFileName, DWORD *buffer_len) +static LPBYTE ma_schannel_load_pem(MARIADB_CIO *cio, const char *PemFileName, DWORD *buffer_len) { HANDLE hfile; char *buffer= NULL; @@ -57,32 +124,53 @@ static LPBYTE ma_schannel_load_pem(const char *PemFileName, DWORD *buffer_len) return NULL; - if ((hfile= CreateFile(PemFileName, GENERIC_READ, 0, NULL, OPEN_EXISTING, + if ((hfile= CreateFile(PemFileName, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL )) == INVALID_HANDLE_VALUE) + { + ma_schannel_set_win_error(cio); return NULL; + } if (!(*buffer_len = GetFileSize(hfile, NULL))) + { + cio->set_error(cio->mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, "Invalid pem format"); goto end; + } if (!(buffer= LocalAlloc(0, *buffer_len + 1))) + { + cio->set_error(cio->mysql, CR_OUT_OF_MEMORY, SQLSTATE_UNKNOWN, NULL); goto end; + } if (!ReadFile(hfile, buffer, *buffer_len, &dwBytesRead, NULL)) + { + ma_schannel_set_win_error(cio); goto end; + } CloseHandle(hfile); /* calculate the length of DER binary */ if (!CryptStringToBinaryA(buffer, *buffer_len, CRYPT_STRING_BASE64HEADER, NULL, &der_buffer_length, NULL, NULL)) + { + ma_schannel_set_win_error(cio); goto end; + } /* allocate DER binary buffer */ if (!(der_buffer= (LPBYTE)LocalAlloc(0, der_buffer_length))) + { + cio->set_error(cio->mysql, CR_OUT_OF_MEMORY, SQLSTATE_UNKNOWN, NULL); goto end; + } /* convert to DER binary */ if (!CryptStringToBinaryA(buffer, *buffer_len, CRYPT_STRING_BASE64HEADER, der_buffer, &der_buffer_length, NULL, NULL)) + { + ma_schannel_set_win_error(cio); goto end; + } *buffer_len= der_buffer_length; LocalFree(buffer); @@ -101,12 +189,13 @@ end: } /* }}} */ -/* {{{ CERT_CONTEXT *ma_schannel_create_cert_context(const char *pem_file) */ +/* {{{ CERT_CONTEXT *ma_schannel_create_cert_context(MARIADB_CIO *cio, const char *pem_file) */ /* Create a certification context from ca or cert file SYNOPSIS ma_schannel_create_cert_context() + cio cio object pem_file name of certificate or ca file DESCRIPTION @@ -119,7 +208,7 @@ end: NULL If loading of the file or creating context failed CERT_CONTEXT * A pointer to a certification context structure */ -CERT_CONTEXT *ma_schannel_create_cert_context(const char *pem_file) +CERT_CONTEXT *ma_schannel_create_cert_context(MARIADB_CIO *cio, const char *pem_file) { DWORD der_buffer_length; LPBYTE der_buffer= NULL; @@ -127,10 +216,12 @@ CERT_CONTEXT *ma_schannel_create_cert_context(const char *pem_file) CERT_CONTEXT *ctx= NULL; /* create DER binary object from ca/certification file */ - if (!(der_buffer= ma_schannel_load_pem(pem_file, (DWORD *)&der_buffer_length))) + if (!(der_buffer= ma_schannel_load_pem(cio, pem_file, (DWORD *)&der_buffer_length))) goto end; - ctx= CertCreateCertificateContext(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, - der_buffer, der_buffer_length); + if (!(ctx= CertCreateCertificateContext(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, + der_buffer, der_buffer_length))) + ma_schannel_set_win_error(cio); + end: if (der_buffer) LocalFree(der_buffer); @@ -138,7 +229,7 @@ end: } /* }}} */ -/* {{{ PCCRL_CONTEXT ma_schannel_create_crl_context(const char *pem_file) */ +/* {{{ PCCRL_CONTEXT ma_schannel_create_crl_context(MARIADB_CIO *cio, const char *pem_file) */ /* Create a crl context from crlfile @@ -156,7 +247,7 @@ end: NULL If loading of the file or creating context failed PCCRL_CONTEXT A pointer to a certification context structure */ -PCCRL_CONTEXT ma_schannel_create_crl_context(const char *pem_file) +PCCRL_CONTEXT ma_schannel_create_crl_context(MARIADB_CIO *cio, const char *pem_file) { DWORD der_buffer_length; LPBYTE der_buffer= NULL; @@ -164,10 +255,11 @@ PCCRL_CONTEXT ma_schannel_create_crl_context(const char *pem_file) PCCRL_CONTEXT ctx= NULL; /* load ca pem file into memory */ - if (!(der_buffer= ma_schannel_load_pem(pem_file, (DWORD *)&der_buffer_length))) + if (!(der_buffer= ma_schannel_load_pem(cio, pem_file, (DWORD *)&der_buffer_length))) goto end; - ctx= CertCreateCRLContext(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, - der_buffer, der_buffer_length); + if (!(ctx= CertCreateCRLContext(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, + der_buffer, der_buffer_length))) + ma_schannel_set_win_error(cio); end: if (der_buffer) LocalFree(der_buffer); @@ -175,7 +267,7 @@ end: } /* }}} */ -/* {{{ my_bool ma_schannel_load_private_key(CERT_CONTEXT *ctx, char *key_file) */ +/* {{{ my_bool ma_schannel_load_private_key(MARIADB_CIO *cio, CERT_CONTEXT *ctx, char *key_file) */ /* Load privte key into context @@ -195,7 +287,7 @@ end: PCCRL_CONTEXT A pointer to a certification context structure */ -my_bool ma_schannel_load_private_key(CERT_CONTEXT *ctx, char *key_file) +my_bool ma_schannel_load_private_key(MARIADB_CIO *cio, CERT_CONTEXT *ctx, char *key_file) { DWORD der_buffer_len= 0; LPBYTE der_buffer= NULL; @@ -203,11 +295,11 @@ my_bool ma_schannel_load_private_key(CERT_CONTEXT *ctx, char *key_file) LPBYTE priv_key= NULL; HCRYPTPROV crypt_prov= NULL; HCRYPTKEY crypt_key= NULL; - CRYPT_KEY_PROV_INFO kpi; + CERT_KEY_CONTEXT kpi; my_bool rc= 0; /* load private key into der binary object */ - if (!(der_buffer= ma_schannel_load_pem(key_file, &der_buffer_len))) + if (!(der_buffer= ma_schannel_load_pem(cio, key_file, &der_buffer_len))) return 0; /* determine required buffer size for decoded private key */ @@ -216,11 +308,17 @@ my_bool ma_schannel_load_private_key(CERT_CONTEXT *ctx, char *key_file) der_buffer, der_buffer_len, 0, NULL, NULL, &priv_key_len)) + { + ma_schannel_set_win_error(cio); goto end; + } /* allocate buffer for decoded private key */ if (!(priv_key= LocalAlloc(0, priv_key_len))) + { + cio->set_error(cio->mysql, CR_OUT_OF_MEMORY, SQLSTATE_UNKNOWN, NULL); goto end; + } /* decode */ if (!CryptDecodeObjectEx(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, @@ -228,24 +326,37 @@ my_bool ma_schannel_load_private_key(CERT_CONTEXT *ctx, char *key_file) der_buffer, der_buffer_len, 0, NULL, priv_key, &priv_key_len)) + { + ma_schannel_set_win_error(cio); goto end; + } - /* Acquire context */ + /* Acquire context: + If cio_schannel context doesn't exist, create a new one */ if (!CryptAcquireContext(&crypt_prov, "cio_schannel", MS_ENHANCED_PROV, PROV_RSA_FULL, 0)) + if (!CryptAcquireContext(&crypt_prov, "cio_schannel", MS_ENHANCED_PROV, PROV_RSA_FULL, CRYPT_NEWKEYSET)) + { + ma_schannel_set_win_error(cio); goto end; - + } /* ... and import the private key */ if (!CryptImportKey(crypt_prov, priv_key, priv_key_len, NULL, 0, &crypt_key)) + { + ma_schannel_set_win_error(cio); goto end; + } SecureZeroMemory(&kpi, sizeof(kpi)); - kpi.pwszContainerName = "cio-schanel"; + kpi.hCryptProv= crypt_prov; kpi.dwKeySpec = AT_KEYEXCHANGE; - kpi.dwFlags = CRYPT_MACHINE_KEYSET; + kpi.cbSize= sizeof(kpi); /* assign private key to certificate context */ - if (CertSetCertificateContextProperty(ctx, CERT_KEY_PROV_INFO_PROP_ID, 0, &kpi)) + if (CertSetCertificateContextProperty(ctx, CERT_KEY_CONTEXT_PROP_ID, 0, &kpi)) rc= 1; + else + ma_schannel_set_win_error(cio); + end: if (der_buffer) LocalFree(der_buffer); @@ -285,7 +396,7 @@ SECURITY_STATUS ma_schannel_handshake_loop(MARIADB_CIO *cio, my_bool InitialRead PUCHAR IoBuffer; BOOL fDoRead; MARIADB_SSL *cssl= cio->cssl; - SC_CTX *sctx= (SC_CTX *)cssl->data; + SC_CTX *sctx= (SC_CTX *)cssl->ssl; dwSSPIFlags = ISC_REQ_SEQUENCE_DETECT | @@ -416,6 +527,7 @@ SECURITY_STATUS ma_schannel_handshake_loop(MARIADB_CIO *cio, my_bool InitialRead pExtraData->cbBuffer= 0; } break; + case SEC_I_INCOMPLETE_CREDENTIALS: /* Provided credentials didn't contain a valid client certificate. We will try to connect anonymously, using current credentials */ @@ -425,7 +537,10 @@ SECURITY_STATUS ma_schannel_handshake_loop(MARIADB_CIO *cio, my_bool InitialRead break; default: if (FAILED(rc)) + { + ma_schannel_set_sec_error(cio, rc); goto loopend; + } break; } @@ -472,22 +587,24 @@ SECURITY_STATUS ma_schannel_client_handshake(MARIADB_SSL *cssl) SecBuffer ExtraData; DWORD SFlags= ISC_REQ_SEQUENCE_DETECT | ISC_REQ_REPLAY_DETECT | ISC_REQ_CONFIDENTIALITY | ISC_RET_EXTENDED_ERROR | + ISC_REQ_USE_SUPPLIED_CREDS | ISC_REQ_ALLOCATE_MEMORY | ISC_REQ_STREAM; SecBufferDesc BufferIn, BufferOut; SecBuffer BuffersOut[1], BuffersIn[2]; - if (!cssl || !cssl->cio || !cssl->data) + if (!cssl || !cssl->cio) return 1; cio= cssl->cio; - sctx= (SC_CTX *)cssl->data; + sctx= (SC_CTX *)cssl->ssl; /* Initialie securifty context */ BuffersOut[0].BufferType= SECBUFFER_TOKEN; BuffersOut[0].cbBuffer= 0; BuffersOut[0].pvBuffer= NULL; + BufferOut.cBuffers= 1; BufferOut.pBuffers= BuffersOut; BufferOut.ulVersion= SECBUFFER_VERSION; @@ -506,7 +623,18 @@ SECURITY_STATUS ma_schannel_client_handshake(MARIADB_SSL *cssl) NULL); if(sRet != SEC_I_CONTINUE_NEEDED) + { + ma_schannel_set_sec_error(cio, sRet); return sRet; + } + + /* Allocate IO-Buffer */ + sctx->IoBufferSize= 2 * net_buffer_length; + if (!(sctx->IoBuffer= (PUCHAR)LocalAlloc(LMEM_ZEROINIT, sctx->IoBufferSize))) + { + sRet= SEC_E_INSUFFICIENT_MEMORY; + goto end; + } /* send client hello packaet */ if(BuffersOut[0].cbBuffer != 0 && BuffersOut[0].pvBuffer != NULL) @@ -518,51 +646,26 @@ SECURITY_STATUS ma_schannel_client_handshake(MARIADB_SSL *cssl) goto end; } } - return ma_schannel_handshake_loop(cio, TRUE, &ExtraData); + sRet= ma_schannel_handshake_loop(cio, TRUE, &ExtraData); + /* Reallocate IO-Buffer for write operations: After handshake + was successfull, we are able now to calculate payload */ + QueryContextAttributes( &sctx->ctxt, SECPKG_ATTR_STREAM_SIZES, &sctx->Sizes ); + sctx->IoBufferSize= SCHANNEL_PAYLOAD(sctx->Sizes); + sctx->IoBuffer= LocalReAlloc(sctx->IoBuffer, sctx->IoBufferSize, LMEM_ZEROINIT); + + return sRet; end: + LocalFree(sctx->IoBuffer); + sctx->IoBufferSize= 0; FreeContextBuffer(BuffersOut[0].pvBuffer); DeleteSecurityContext(&sctx->ctxt); return sRet; } /* }}} */ -/* {{{ static PUCHAR ma_schannel_alloc_iobuffer(CtxtHandle *Context, DWORD *BufferLength) */ -/* - alloates an IO Buffer for ssl communication - - SYNOPSUS - ma_schannel_alloc_iobuffer() - Context SChannel context handle - BufferLength a pointer for the buffer length - - DESCRIPTION - calculates the required buffer length and allocates a memory buffer for en- and - decryption. The calculated buffer length will be returned in BufferLength pointer. - The allocated buffer needs to be freed at end of the connection. - - RETURN - NULL if an error occured - PUCHAR an IO Buffer -*/ -static PUCHAR ma_schannel_alloc_iobuffer(CtxtHandle *Context, DWORD *BufferLength) -{ - SecPkgContext_StreamSizes StreamSizes; - - PUCHAR Buffer= NULL; - - if (!BufferLength || QueryContextAttributes(Context, SECPKG_ATTR_STREAM_SIZES, &StreamSizes) != SEC_E_OK) - return NULL; - - /* Calculate BufferLength */ - *BufferLength= StreamSizes.cbHeader + StreamSizes.cbTrailer + StreamSizes.cbMaximumMessage; - - Buffer= LocalAlloc(LMEM_FIXED, *BufferLength); - return Buffer; -} -/* }}} */ - -/* {{{ SECURITY_STATUS ma_schannel_read(MARIADB_CIO *cio, PCredHandle phCreds, CtxtHandle * phContext) */ +/* {{{ SECURITY_STATUS ma_schannel_read_decrypt(MARIADB_CIO *cio, PCredHandle phCreds, CtxtHandle * phContext, + DWORD DecryptLength, uchar *ReadBuffer, DWORD ReadBufferSize) */ /* Reads encrypted data from a SSL stream and decrypts it. @@ -583,44 +686,34 @@ static PUCHAR ma_schannel_alloc_iobuffer(CtxtHandle *Context, DWORD *BufferLengt SEC_E_* if an error occured */ -SECURITY_STATUS ma_schannel_read(MARIADB_CIO *cio, - PCredHandle phCreds, - CtxtHandle * phContext, - DWORD *DecryptLength, - uchar *ReadBuffer, - DWORD ReadBufferSize) +SECURITY_STATUS ma_schannel_read_decrypt(MARIADB_CIO *cio, + PCredHandle phCreds, + CtxtHandle * phContext, + DWORD *DecryptLength, + uchar *ReadBuffer, + DWORD ReadBufferSize) { DWORD dwBytesRead= 0; DWORD dwOffset= 0; SC_CTX *sctx; SECURITY_STATUS sRet= 0; - SecBuffersDesc Msg; + SecBufferDesc Msg; SecBuffer Buffers[4], ExtraBuffer, *pData, *pExtra; int i; - if (!cio || !cio->methods || !cio->methods->read || !cio->cssl || !cio->cssl->data | !DecryptLength) + if (!cio || !cio->methods || !cio->methods->read || !cio->cssl || !DecryptLength) return SEC_E_INTERNAL_ERROR; - sctx= (SC_CTX *)cio->cssl->data; + sctx= (SC_CTX *)cio->cssl->ssl; *DecryptLength= 0; - /* Allocate IoBuffer */ - if (!sctx->IoBuffer) - { - if (!(sctx->IoBuffer= ma_schannel_alloc_iobuffer(&sctx->ctxt, &sctx->IoBufferSize))) - { - /* todo: error */ - return NULL; - } - } - while (1) { - if (!dwOffset || sRet == SEC_E_INCOMPLETE_MESSAGE) + if (!dwBytesRead || sRet == SEC_E_INCOMPLETE_MESSAGE) { - dwBytesRead= cio->methods->read(cio, sctx->IoBuffer + dwOffset, cbIoBufferLength - dwOffset); + dwBytesRead= cio->methods->read(cio, sctx->IoBuffer + dwOffset, sctx->IoBufferSize - dwOffset); if (dwBytesRead == 0) { /* server closed connection */ @@ -633,8 +726,9 @@ SECURITY_STATUS ma_schannel_read(MARIADB_CIO *cio, printf("Socket error\n"); return NULL; } + dwOffset+= dwBytesRead; } - ZeroMem(Buffers, sizeof(SecBuffer) * 4); + ZeroMemory(Buffers, sizeof(SecBuffer) * 4); Buffers[0].pvBuffer= sctx->IoBuffer; Buffers[0].cbBuffer= dwOffset; @@ -652,9 +746,9 @@ SECURITY_STATUS ma_schannel_read(MARIADB_CIO *cio, /* Check for possible errors: we continue in case context has expired or renogitiation is required */ if (sRet != SEC_E_OK && sRet != SEC_I_CONTEXT_EXPIRED && - sRet != SEC_I_RENEGOTIARE) + sRet != SEC_I_RENEGOTIATE && sRet != SEC_E_INCOMPLETE_MESSAGE) { - // set error + ma_schannel_set_sec_error(cio, sRet); return sRet; } @@ -668,11 +762,12 @@ SECURITY_STATUS ma_schannel_read(MARIADB_CIO *cio, if (pData && pExtra) break; } - + if (pData && pData->cbBuffer) { - memcpy(ReadBuffer + *DecrypthLength, pData->pvBuffer, pData->cbBuffer); + memcpy(ReadBuffer + *DecryptLength, pData->pvBuffer, pData->cbBuffer); *DecryptLength+= pData->cbBuffer; + return sRet; } if (pExtra) @@ -684,4 +779,129 @@ SECURITY_STATUS ma_schannel_read(MARIADB_CIO *cio, dwOffset= 0; } } + +my_bool ma_schannel_verify_certs(SC_CTX *sctx, DWORD dwCertFlags) +{ + SECURITY_STATUS sRet; + DWORD flags; + MARIADB_CIO *cio= sctx->mysql->net.cio; + PCCERT_CONTEXT pServerCert= NULL; + + if ((sRet= QueryContextAttributes(&sctx->ctxt, SECPKG_ATTR_REMOTE_CERT_CONTEXT, (PVOID)&pServerCert)) != SEC_E_OK) + { + ma_schannel_set_sec_error(cio, sRet); + return 0; + } + + flags= CERT_STORE_SIGNATURE_FLAG | + CERT_STORE_TIME_VALIDITY_FLAG; + + + + if (sctx->client_ca_ctx) + { + if (!(sRet= CertVerifySubjectCertificateContext(pServerCert, + sctx->client_ca_ctx, + &flags))) + { + ma_schannel_set_win_error(cio); + return 0; + } + + if (flags) + { + if ((flags & CERT_STORE_SIGNATURE_FLAG) != 0) + cio->set_error(sctx->mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, "Client certificate signature check failed"); + else if ((flags & CERT_STORE_REVOCATION_FLAG) != 0) + cio->set_error(sctx->mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, "Client certificate was revoked"); + else if ((flags & CERT_STORE_TIME_VALIDITY_FLAG) != 0) + cio->set_error(sctx->mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, "Client certificate has expired"); + else + cio->set_error(sctx->mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, "Unknown error during client certificate validation"); + return 0; + } + } + + /* Check if none of the certificates in the certificate chain have been revoked. */ + if (sctx->client_crl_ctx) + { + PCRL_INFO Info[1]; + + Info[0]= sctx->client_crl_ctx->pCrlInfo; + if (!(CertVerifyCRLRevocation(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, + pServerCert->pCertInfo, + 1, Info)) ) + { + cio->set_error(cio->mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, "CRL Revocation failed"); + return 0; + } + } + return 1; +} + + +/* {{{ size_t ma_schannel_write_encrypt(MARIADB_CIO *cio, PCredHandle phCreds, CtxtHandle * phContext) */ +/* + Decrypts data and write to SSL stream + SYNOPSIS + ma_schannel_write_decrypt + cio pointer to Communication IO structure + phContext a context handle + DecryptLength size of decrypted buffer + ReadBuffer Buffer for decrypted data + ReadBufferSize size of ReadBuffer + + DESCRIPTION + Write encrypted data to SSL stream. + + RETURN + SEC_E_OK on success + SEC_E_* if an error occured +*/ +size_t ma_schannel_write_encrypt(MARIADB_CIO *cio, + uchar *WriteBuffer, + size_t WriteBufferSize) +{ + SECURITY_STATUS scRet; + SecBufferDesc Message; + SecBuffer Buffers[4]; + DWORD cbMessage, cbData; + PBYTE pbMessage; + SC_CTX *sctx= (SC_CTX *)cio->cssl->ssl; + size_t payload; + + + payload= MIN(WriteBufferSize, sctx->IoBufferSize); + + memcpy(&sctx->IoBuffer[sctx->Sizes.cbHeader], WriteBuffer, payload); + pbMessage = sctx->IoBuffer + sctx->Sizes.cbHeader; + cbMessage = payload; + + Buffers[0].pvBuffer = sctx->IoBuffer; + Buffers[0].cbBuffer = sctx->Sizes.cbHeader; + Buffers[0].BufferType = SECBUFFER_STREAM_HEADER; // Type of the buffer + + Buffers[1].pvBuffer = &sctx->IoBuffer[sctx->Sizes.cbHeader]; + Buffers[1].cbBuffer = payload; + Buffers[1].BufferType = SECBUFFER_DATA; + + Buffers[2].pvBuffer = &sctx->IoBuffer[sctx->Sizes.cbHeader] + payload; + Buffers[2].cbBuffer = sctx->Sizes.cbTrailer; + Buffers[2].BufferType = SECBUFFER_STREAM_TRAILER; + + Buffers[3].pvBuffer = SECBUFFER_EMPTY; // Pointer to buffer 4 + Buffers[3].cbBuffer = SECBUFFER_EMPTY; // length of buffer 4 + Buffers[3].BufferType = SECBUFFER_EMPTY; // Type of the buffer 4 + + + Message.ulVersion = SECBUFFER_VERSION; + Message.cBuffers = 4; + Message.pBuffers = Buffers; + if ((scRet = EncryptMessage(&sctx->ctxt, 0, &Message, 0))!= SEC_E_OK) + return -1; + + if (cio->methods->write(cio, sctx->IoBuffer, Buffers[0].cbBuffer + Buffers[1].cbBuffer + Buffers[2].cbBuffer)) + return payload; +} /* }}} */ + diff --git a/libmariadb/secure/ma_schannel.h b/libmariadb/secure/ma_schannel.h index 006613ef..6aa937c1 100644 --- a/libmariadb/secure/ma_schannel.h +++ b/libmariadb/secure/ma_schannel.h @@ -28,23 +28,23 @@ #include #include #include -#include + typedef void VOID; #include -#define SECURITY_WIN32 +#include + + #include + #include #undef SECURITY_WIN32 #include +#include #define SC_IO_BUFFER_SIZE 0x4000 -CERT_CONTEXT *ma_schannel_create_cert_context(const char *pem_file); -SECURITY_STATUS ma_schannel_handshake_loop(MARIADB_CIO *cio, my_bool InitialRead, SecBuffer *pExtraData); -my_bool ma_schannel_load_private_key(CERT_CONTEXT *ctx, char *key_file); -PCCRL_CONTEXT ma_schannel_create_crl_context(const char *pem_file); #ifndef HAVE_SCHANNEL_DEFAULT #define my_snprintf snprintf @@ -61,12 +61,35 @@ struct st_schannel { CredHandle CredHdl; PUCHAR IoBuffer; DWORD IoBufferSize; +/* PUCHAR EncryptBuffer; + DWORD EncryptBufferSize; + DWORD EncryptBufferLength; PUCHAR DecryptBuffer; DWORD DecryptBufferSize; DWORD DecryptBufferLength; + uchar thumbprint[21]; */ + SecPkgContext_StreamSizes Sizes; + CtxtHandle ctxt; + MYSQL *mysql; }; typedef struct st_schannel SC_CTX; +CERT_CONTEXT *ma_schannel_create_cert_context(MARIADB_CIO *cio, const char *pem_file); +SECURITY_STATUS ma_schannel_handshake_loop(MARIADB_CIO *cio, my_bool InitialRead, SecBuffer *pExtraData); +my_bool ma_schannel_load_private_key(MARIADB_CIO *cio, CERT_CONTEXT *ctx, char *key_file); +PCCRL_CONTEXT ma_schannel_create_crl_context(MARIADB_CIO *cio, const char *pem_file); +my_bool ma_schannel_verify_certs(SC_CTX *sctx, DWORD dwCertFlags); +size_t ma_schannel_write_encrypt(MARIADB_CIO *cio, + uchar *WriteBuffer, + size_t WriteBufferSize); + size_t ma_schannel_read_decrypt(MARIADB_CIO *cio, + PCredHandle phCreds, + CtxtHandle * phContext, + DWORD *DecryptLength, + uchar *ReadBuffer, + DWORD ReadBufferSize); + + #endif /* _ma_schannel_h_ */ diff --git a/libmariadb/secure/openssl.c b/libmariadb/secure/openssl.c index 18a140c6..26809b11 100644 --- a/libmariadb/secure/openssl.c +++ b/libmariadb/secure/openssl.c @@ -287,10 +287,9 @@ static int ma_ssl_set_certs(MYSQL *mysql) if ((certstore= SSL_CTX_get_cert_store(SSL_context))) { - if (X509_STORE_load_locations(certstore, mysql->options.ssl_ca, - mysql->options.ssl_capath) == 0 || - X509_STORE_set_flags(certstore, X509_V_FLAG_CRL_CHECK | - X509_V_FLAG_CRL_CHECK_ALL) == 0) + 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; } } diff --git a/libmariadb/secure/schannel.c b/libmariadb/secure/schannel.c index 26b49f99..e77e661c 100644 --- a/libmariadb/secure/schannel.c +++ b/libmariadb/secure/schannel.c @@ -24,77 +24,72 @@ #define VOID void -static my_bool my_schannel_initialized= FALSE; - -#define MAX_SSL_ERR_LEN 100 +extern my_bool ma_ssl_initialized; static pthread_mutex_t LOCK_schannel_config; static pthread_mutex_t *LOCK_crypto= NULL; -int cio_schannel_start(char *errmsg, size_t errmsg_len, int count, va_list); -int cio_schannel_end(); -void *cio_schannel_init(MARIADB_SSL *cssl, MYSQL *mysql); -my_bool cio_schannel_connect(MARIADB_SSL *cssl); -size_t cio_schannel_read(MARIADB_SSL *cssl, const uchar* buffer, size_t length); -size_t cio_schannel_write(MARIADB_SSL *cssl, const uchar* buffer, size_t length); -my_bool cio_schannel_close(MARIADB_SSL *cssl); -int cio_schannel_verify_server_cert(MARIADB_SSL *cssl); -const char *cio_schannel_cipher(MARIADB_SSL *cssl); - -struct st_ma_cio_ssl_methods cio_schannel_methods= { - cio_schannel_init, - cio_schannel_connect, - cio_schannel_read, - cio_schannel_write, - cio_schannel_close, - cio_schannel_verify_server_cert, - cio_schannel_cipher +struct st_cipher_suite { + DWORD aid; + CHAR *cipher; }; -#ifndef HAVE_SCHANNEL_DEFAULT -MARIADB_CIO_PLUGIN _mysql_client_plugin_declaration_= -#else -MARIADB_CIO_PLUGIN cio_schannel_plugin= -#endif +void ma_schannel_set_sec_error(MARIADB_CIO *cio, DWORD ErrorNo); +void ma_schannel_set_win_error(MYSQL *mysql); + +const struct st_cipher_suite sc_ciphers[]= { - MYSQL_CLIENT_CIO_PLUGIN, - MYSQL_CLIENT_CIO_PLUGIN_INTERFACE_VERSION, - "cio_schannel", - "Georg Richter", - "MariaDB communication IO plugin for Windows SSL/SChannel communication", - {1, 0, 0}, - "LGPL", - cio_schannel_start, - cio_schannel_end, - NULL, - &cio_schannel_methods, - NULL + {CALG_3DES, "CALG_3DES"}, + {CALG_3DES_112, "CALG_3DES_112"}, + {CALG_AES, "CALG_AES"}, + {CALG_AES_128, "CALG_AES_128"}, + {CALG_AES_192, "CALG_AES_192"}, + {CALG_AES_256, "CALG_AES_256"}, + {CALG_AGREEDKEY_ANY, "CALG_AGREEDKEY_ANY"}, + {CALG_CYLINK_MEK, "CALG_CYLINK_MEK"}, + {CALG_DES, "CALG_DES"}, + {CALG_DESX, "CALG_DESX"}, + {CALG_DH_EPHEM, "CALG_DH_EPHEM"}, + {CALG_DH_SF, "CALG_DH_SF"}, + {CALG_DSS_SIGN, "CALG_DSS_SIGN"}, + {CALG_ECDH, "CALG_ECDH"}, + {CALG_ECDSA, "CALG_ECDSA"}, + {CALG_ECMQV, "CALG_ECMQV"}, + {CALG_HASH_REPLACE_OWF, "CALG_HASH_REPLACE_OWF"}, + {CALG_HUGHES_MD5, "CALG_HUGHES_MD5"}, + {CALG_HMAC, "CALG_HMAC"}, + {CALG_KEA_KEYX, "CALG_KEA_KEYX"}, + {CALG_MAC, "CALG_MAC"}, + {CALG_MD2, "CALG_MD2"}, + {CALG_MD4, "CALG_MD4"}, + {CALG_MD4, "CALG_MD5"}, + {CALG_NO_SIGN, "CALG_NO_SIGN"}, + {CALG_OID_INFO_CNG_ONLY, "CALG_OID_INFO_CNG_ONLY"}, + {CALG_OID_INFO_PARAMETERS, "CALG_OID_INFO_PARAMETERS"}, + {CALG_PCT1_MASTER, "CALG_PCT1_MASTER"}, + {CALG_RC2, "CALG_RC2"}, + {CALG_RC4, "CALG_RC4"}, + {CALG_RC5, "CALG_RC5"}, + {CALG_RSA_KEYX, "CALG_RSA_KEYX"}, + {CALG_RSA_SIGN, "CALG_RSA_SIGN"}, + {CALG_SCHANNEL_MAC_KEY, "CALG_SCHANNEL_MAC_KEY"}, + {CALG_SCHANNEL_MASTER_HASH, "CALG_SCHANNEL_MASTER_HASH"}, + {CALG_SEAL, "CALG_SEAL"}, + {CALG_SHA, "CALG_SHA"}, + {CALG_SHA1, "CALG_SHA1"}, + {CALG_SHA_256, "CALG_SHA_256"}, + {CALG_SHA_384, "CALG_SHA_384"}, + {CALG_SHA_512, "CALG_SHA_512"}, + {CALG_SKIPJACK, "CALG_SKIPJACK"}, + {CALG_SSL2_MASTER, "CALG_SSL2_MASTER"}, + {CALG_SSL3_MASTER, "CALG_SSL3_MASTER"}, + {CALG_SSL3_SHAMD5, "CALG_SSL3_SHAMD5"}, + {CALG_TEK, "CALG_TEK"}, + {CALG_TLS1_MASTER, "CALG_TLS1_MASTER"}, + {CALG_TLS1PRF, "CALG_TLS1PRF"}, + {0, NULL} }; -static void cio_schannel_set_error(MYSQL *mysql) -{ - ulong ssl_errno= GetLastError(); - char ssl_error[MAX_SSL_ERR_LEN]; - char *ssl_error_reason= NULL; - MARIADB_CIO *cio= mysql->net.cio; - - if (!ssl_errno) - { - cio->set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, "Unknown SSL error"); - return; - } - /* todo: obtain error messge */ - FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, - NULL, ssl_errno, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), - (LPTSTR) &ssl_error_reason, 0, NULL ); - cio->set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, ssl_error_reason); - - if (ssl_error_reason) - LocalFree(ssl_error_reason); - return; -} - - static int ssl_thread_init() { return 0; @@ -106,22 +101,19 @@ static int ssl_thread_init() context SSL_context SYNOPSIS - cio_schannel_start + ma_ssl_start RETURN VALUES 0 success 1 error */ -int cio_schannel_start(char *errmsg, size_t errmsg_len, int count, va_list list) +int ma_ssl_start(char *errmsg, size_t errmsg_len, int count, va_list list) { - if (!my_schannel_initialized) + if (!ma_ssl_initialized) { pthread_mutex_init(&LOCK_schannel_config,MY_MUTEX_INIT_FAST); pthread_mutex_lock(&LOCK_schannel_config); - - // SecureZeroMemory(&SC_CTX, sizeof(struct st_schannel_global)); - - my_schannel_initialized= TRUE; + ma_ssl_initialized= TRUE; } pthread_mutex_unlock(&LOCK_schannel_config); return 0; @@ -133,27 +125,27 @@ int cio_schannel_start(char *errmsg, size_t errmsg_len, int count, va_list list) mysql_server_end() function SYNOPSIS - cio_schannel_end() + ma_ssl_end() void RETURN VALUES void */ -int cio_schannel_end() +void ma_ssl_end() { pthread_mutex_lock(&LOCK_schannel_config); - if (my_schannel_initialized) + if (ma_ssl_initialized) { - my_schannel_initialized= FALSE; + ma_ssl_initialized= FALSE; } pthread_mutex_unlock(&LOCK_schannel_config); pthread_mutex_destroy(&LOCK_schannel_config); - return 0; + return; } -/* {{{ static int cio_schannel_set_client_certs(MARIADB_SSL *cssl) */ -static int cio_schannel_set_client_certs(MARIADB_SSL *cssl) +/* {{{ static int ma_ssl_set_client_certs(MARIADB_SSL *cssl) */ +static int ma_ssl_set_client_certs(MARIADB_SSL *cssl) { MYSQL *mysql= cssl->cio->mysql; char *certfile= mysql->options.ssl_cert, @@ -161,24 +153,50 @@ static int cio_schannel_set_client_certs(MARIADB_SSL *cssl) *cafile= mysql->options.ssl_ca; SC_CTX *sctx= (SC_CTX *)cssl->ssl; + MARIADB_CIO *cio= cssl->cio; if (cafile) - if (!(sctx->client_ca_ctx = ma_schannel_create_cert_context(cafile))) + { + HCERTSTORE myCS= NULL; + char szName[64]; + + if (!(sctx->client_ca_ctx = ma_schannel_create_cert_context(cio, cafile))) goto end; - if (certfile) - { - if (!(sctx->client_cert_ctx = ma_schannel_create_cert_context(certfile))) + /* For X509 authentication we need to add ca certificate to local MY store. + Schannel doesn't provide a callback to send ca to server during handshake */ + if ((myCS= CertOpenStore(CERT_STORE_PROV_SYSTEM, + 0, //X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, + 0, + CERT_SYSTEM_STORE_CURRENT_USER, + L"CA"))) + { + CertAddCertificateContextToStore(myCS, sctx->client_ca_ctx, CERT_STORE_ADD_NEWER, NULL); + CertCloseStore(myCS, 0); + } + else { + ma_schannel_set_win_error(sctx->mysql); goto end; - if (keyfile) - if (!ma_schannel_load_private_key(sctx->client_cert_ctx, keyfile)) - goto end; + } } + if (!certfile && keyfile) + certfile= keyfile; + if (!keyfile && certfile) + keyfile= certfile; + + if (certfile && certfile[0]) + if (!(sctx->client_cert_ctx = ma_schannel_create_cert_context(cssl->cio, certfile))) + goto end; + + if (sctx->client_cert_ctx && keyfile[0]) + if (!ma_schannel_load_private_key(cio, sctx->client_cert_ctx, keyfile)) + goto end; + if (mysql->options.extension && mysql->options.extension->ssl_crl) { - sctx->client_crl_ctx= ma_schannel_create_crl_context(mysql->options.extension->ssl_crl); - + if (!(sctx->client_crl_ctx= ma_schannel_create_crl_context(cio, mysql->options.extension->ssl_crl))) + goto end; } return 0; @@ -189,95 +207,207 @@ end: CertFreeCertificateContext(sctx->client_cert_ctx); if (sctx->client_crl_ctx) CertFreeCRLContext(sctx->client_crl_ctx); - - cio_schannel_set_error(mysql); + sctx->client_ca_ctx= sctx->client_cert_ctx= NULL; + sctx->client_crl_ctx= NULL; return 1; } /* }}} */ -/* {{{ void *cio_schannel_init(MARIADB_SSL *cssl, MYSQL *mysql) */ -void *cio_schannel_init(MARIADB_SSL *cssl, MYSQL *mysql) +/* {{{ void *ma_ssl_init(MARIADB_SSL *cssl, MYSQL *mysql) */ +void *ma_ssl_init(MYSQL *mysql) { int verify; - SC_CTX *sctx; - - if (!(sctx= LocalAlloc(0, sizeof(SC_CTX)))) - return NULL; - ZeroMemory(sctx, sizeof(SC_CTX)); - - cssl->data= (void *)sctx; + SC_CTX *sctx= NULL; pthread_mutex_lock(&LOCK_schannel_config); - return (void *)sctx; -error: + + if ((sctx= LocalAlloc(0, sizeof(SC_CTX)))) + { + ZeroMemory(sctx, sizeof(SC_CTX)); + sctx->mysql= mysql; + } + pthread_mutex_unlock(&LOCK_schannel_config); - return NULL; + return sctx; } /* }}} */ - -static my_bool VerifyServerCertificate(SC_CTX *sctx, PCCERT_CONTEXT pServerCert, PSTR pszServerName, DWORD dwCertFlags ) +my_bool ma_ssl_connect(MARIADB_SSL *cssl) { + my_bool blocking; + MYSQL *mysql; + SCHANNEL_CRED Cred; + MARIADB_CIO *cio; + SC_CTX *sctx; SECURITY_STATUS sRet; - DWORD flags; - char *szName= NULL; - int rc= 0; + PCCERT_CONTEXT pRemoteCertContext = NULL, + pLocalCertContext= NULL; + ALG_ID AlgId[2]= {0, 0}; + + if (!cssl || !cssl->cio) + return 1;; + + cio= cssl->cio; + sctx= (SC_CTX *)cssl->ssl; - /* We perform a manually validation, as described at - http://msdn.microsoft.com/en-us/library/windows/desktop/aa378740%28v=vs.85%29.aspx - */ + /* Set socket to blocking if not already set */ + if (!(blocking= cio->methods->is_blocking(cio))) + cio->methods->blocking(cio, TRUE, 0); - /* Check if - - The certificate chain is complete and the root is a certificate from a trusted certification authority (CA). - - The current time is not beyond the begin and end dates for each of the certificates in the certificate chain. - */ - flags= CERT_STORE_SIGNATURE_FLAG | - CERT_STORE_TIME_VALIDITY_FLAG; - - if (!(sRet= CertVerifySubjectCertificateContext(pServerCert, - sctx->client_ca_ctx, - &flags))) + mysql= cio->mysql; + + if (ma_ssl_set_client_certs(cssl)) + goto end; + + /* Set cipher */ + if (mysql->options.ssl_cipher) { - /* todo: error handling */ - return 0; - } - - /* Check if none of the certificates in the certificate chain have been revoked. */ - if (sctx->client_crl_ctx) - { - PCRL_INFO Info[1]; - - Info[0]= sctx->client_crl_ctx->pCrlInfo; - if (!(CertVerifyCRLRevocation(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, - pServerCert->pCertInfo, - 1, Info)) ) - { - /* todo: error handling */ - return 0; + DWORD i= 0; + while (sc_ciphers[i].cipher) { + if (!strcmp(sc_ciphers[i].cipher, mysql->options.ssl_cipher)) + { + AlgId[0]= sc_ciphers[i].aid; + break; + } } + Cred.palgSupportedAlgs= &AlgId; } + + ZeroMemory(&Cred, sizeof(SCHANNEL_CRED)); + Cred.dwVersion= SCHANNEL_CRED_VERSION; + Cred.dwFlags |= SCH_CRED_NO_SERVERNAME_CHECK | SCH_SEND_ROOT_CERT | + SCH_CRED_NO_DEFAULT_CREDS | SCH_CRED_MANUAL_CRED_VALIDATION; + if (sctx->client_cert_ctx) + { + Cred.cCreds = 1; + Cred.paCred = &sctx->client_cert_ctx; + } + Cred.grbitEnabledProtocols= SP_PROT_TLS1; + + if ((sRet= AcquireCredentialsHandleA(NULL, UNISP_NAME_A, SECPKG_CRED_OUTBOUND, + NULL, &Cred, NULL, NULL, &sctx->CredHdl, NULL)) != SEC_E_OK) + { + ma_schannel_set_sec_error(cio, sRet); + goto end; + } + + if (ma_schannel_client_handshake(cssl)) + goto end; + + sRet= QueryContextAttributes(&sctx->ctxt, SECPKG_ATTR_REMOTE_CERT_CONTEXT, (PVOID)&pRemoteCertContext); + if (sRet != SEC_E_OK) + { + ma_schannel_set_sec_error(cio, sRet); + goto end; + } + + if (!ma_schannel_verify_certs(sctx, 0)) + goto end; + + + return 0; +end: + /* todo: cleanup */ + if (sctx->IoBufferSize) + LocalFree(sctx->IoBuffer); + if (sctx->client_ca_ctx) + CertFreeCertificateContext(sctx->client_ca_ctx); + if (sctx->client_cert_ctx) + CertFreeCertificateContext(sctx->client_cert_ctx); + if (sctx->client_crl_ctx) + CertFreeCRLContext(sctx->client_crl_ctx); + return 1; +} + +size_t ma_ssl_read(MARIADB_SSL *cssl, const uchar* buffer, size_t length) +{ + SC_CTX *sctx= (SC_CTX *)cssl->ssl; + MARIADB_CIO *cio= sctx->mysql->net.cio; + size_t dlength= -1; + + ma_schannel_read_decrypt(cio, &sctx->CredHdl, &sctx->ctxt, &dlength, buffer, length); + return dlength; +} + +size_t ma_ssl_write(MARIADB_SSL *cssl, const uchar* buffer, size_t length) +{ + SC_CTX *sctx= (SC_CTX *)cssl->ssl; + MARIADB_CIO *cio= sctx->mysql->net.cio; + size_t rc, wlength= 0; + size_t remain= length; + + while (remain) + { + if ((rc= ma_schannel_write_encrypt(cio, (uchar *)buffer + wlength, remain)) <= 0) + return rc; + wlength+= rc; + remain-= rc; + } + return length; +} + +/* {{{ void ma_ssl_close(MARIADB_CIO *cio) */ +void ma_ssl_close(MARIADB_CIO *cio) +{ + SC_CTX *sctx; + if (!cio || !cio->cssl) + return; + + if ((sctx= (SC_CTX *)cio->cssl)) + { + if (sctx->IoBufferSize) + LocalFree(sctx->IoBuffer); + if (sctx->client_ca_ctx) + CertFreeCertificateContext(sctx->client_ca_ctx); + if (sctx->client_cert_ctx) + CertFreeCertificateContext(sctx->client_cert_ctx); + if (sctx->client_crl_ctx) + CertFreeCRLContext(sctx->client_crl_ctx); + FreeCredentialHandle(&sctx->CredHdl); + DeleteSecurityContext(&sctx->ctxt); + } + LocalFree(sctx); +} +/* }}} */ + +int ma_ssl_verify_server_cert(MARIADB_SSL *cssl) +{ + SC_CTX *sctx= (SC_CTX *)cssl->ssl; + MARIADB_CIO *cio= cssl->cio; + int rc= 1; + char *szName= NULL; + char *pszServerName= cio->mysql->host; + /* check server name */ - if (pszServerName) + if (pszServerName && (sctx->mysql->client_flag & CLIENT_SSL_VERIFY_SERVER_CERT)) { + PCCERT_CONTEXT pServerCert; DWORD NameSize= 0; char *p1, *p2; + SECURITY_STATUS sRet; + + if ((sRet= QueryContextAttributes(&sctx->ctxt, SECPKG_ATTR_REMOTE_CERT_CONTEXT, (PVOID)&pServerCert)) != SEC_E_OK) + { + ma_schannel_set_sec_error(cio, sRet); + return 1; + } if (!(NameSize= CertNameToStr(pServerCert->dwCertEncodingType, &pServerCert->pCertInfo->Subject, CERT_X500_NAME_STR | CERT_NAME_STR_NO_PLUS_FLAG, NULL, 0))) { - /* todo: error handling */ - return 0; + cio->set_error(sctx->mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, "Can't retrieve name of server certificate"); + return 1; } if (!(szName= LocalAlloc(0, NameSize + 1))) { - /* error handling */ - return 0; + cio->set_error(sctx->mysql, CR_OUT_OF_MEMORY, SQLSTATE_UNKNOWN, NULL); + goto end; } if (!CertNameToStr(pServerCert->dwCertEncodingType, @@ -285,7 +415,7 @@ static my_bool VerifyServerCertificate(SC_CTX *sctx, PCCERT_CONTEXT pServerCert, CERT_X500_NAME_STR | CERT_NAME_STR_NO_PLUS_FLAG, szName, NameSize)) { - /* error handling */ + cio->set_error(sctx->mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, "Can't retrieve name of server certificate"); goto end; } if ((p1 = strstr(szName, "CN="))) @@ -295,116 +425,50 @@ static my_bool VerifyServerCertificate(SC_CTX *sctx, PCCERT_CONTEXT pServerCert, *p2= 0; if (!strcmp(pszServerName, p1)) { - rc= 1; + rc= 0; goto end; } - + cio->set_error(cio->mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, + "Name of server certificate didn't match"); } - } - end: - if (szName) - LocalFree(szName); - return rc; - -} - -my_bool cio_schannel_connect(MARIADB_SSL *cssl) -{ - my_bool blocking; - MYSQL *mysql; - SCHANNEL_CRED Cred; - MARIADB_CIO *cio; - SC_CTX *sctx; - SECURITY_STATUS sRet; - PCCERT_CONTEXT pRemoteCertContext = NULL; - - if (!cssl || !cssl->cio || !cssl->data) - return 1;; - - cio= cssl->cio; - sctx= (SC_CTX *)cssl->data; - - /* Set socket to blocking if not already set */ - if (!(blocking= cio->methods->is_blocking(cio))) - cio->methods->blocking(cio, TRUE, 0); - - mysql= cio->mysql; - - if (cio_schannel_set_client_certs(cssl)) - { - cio_schannel_set_error(mysql); - goto end; - } - - ZeroMemory(&Cred, sizeof(SCHANNEL_CRED)); - Cred.dwVersion= SCHANNEL_CRED_VERSION; - Cred.dwFlags |= SCH_CRED_NO_SERVERNAME_CHECK | - SCH_CRED_NO_DEFAULT_CREDS | - SCH_CRED_MANUAL_CRED_VALIDATION; - if (sctx->client_cert_ctx) - { - Cred.cCreds = 1; - Cred.paCred = &sctx->client_cert_ctx; - } - Cred.grbitEnabledProtocols= SP_PROT_TLS1; - - /* We allocate 2 x net_buffer_length */ - if (!(sctx->IoBuffer= (PUCHAR)LocalAlloc(0, 0x4000))) - goto end; - - if (AcquireCredentialsHandleA(NULL, UNISP_NAME_A, SECPKG_CRED_OUTBOUND, - NULL, &Cred, NULL, NULL, &sctx->CredHdl, NULL) != SEC_E_OK) - goto end; - - if (ma_schannel_client_handshake(cssl)) - goto end; - - sRet= QueryContextAttributes(&sctx->ctxt, SECPKG_ATTR_REMOTE_CERT_CONTEXT, (PVOID)&pRemoteCertContext); - if (sRet != SEC_E_OK) - goto end; - - if (!VerifyServerCertificate(sctx, - pRemoteCertContext, - mysql->host, - 0 )) - goto end; - - - return 0; -end: - /* todo: cleanup */ - if (sctx->IoBuffer) - LocalFree(sctx->IoBuffer); - if (sctx->client_ca_ctx) - CertFreeCertificateContext(sctx->client_ca_ctx); - if (sctx->client_cert_ctx) - CertFreeCertificateContext(sctx->client_cert_ctx); - return 1; -} - -size_t cio_schannel_read(MARIADB_SSL *cssl, const uchar* buffer, size_t length) -{ -} - -size_t cio_schannel_write(MARIADB_SSL *cssl, const uchar* buffer, size_t length) -{ -} - -my_bool cio_schannel_close(MARIADB_SSL *cssl) -{ - int i, rc; - + if (szName) + LocalFree(szName); return rc; } -int cio_schannel_verify_server_cert(MARIADB_SSL *cssl) +const char *ma_ssl_get_cipher(MARIADB_SSL *cssl) { -} + SecPkgContext_ConnectionInfo cinfo; + SECURITY_STATUS sRet; + SC_CTX *sctx; + DWORD i= 0; -const char *cio_schannel_cipher(MARIADB_SSL *cssl) -{ if (!cssl || !cssl->ssl) return NULL; + + sctx= (SC_CTX *)cssl->ssl; + + sRet= QueryContextAttributes(&sctx->ctxt, SECPKG_ATTR_CONNECTION_INFO, (PVOID)&cinfo); + if (sRet != SEC_E_OK) + return NULL; + + while (sc_ciphers[i].cipher) + { + if (sc_ciphers[i].aid == cinfo.aiCipher) + return sc_ciphers[i].cipher; + i++; + } + return NULL; } + +unsigned int ma_ssl_get_finger_print(MARIADB_SSL *cssl, unsigned char *fp, unsigned int len) +{ + SC_CTX *sctx= (SC_CTX *)cssl->ssl; + PCCERT_CONTEXT pRemoteCertContext = NULL; + if (QueryContextAttributes(&sctx->ctxt, SECPKG_ATTR_REMOTE_CERT_CONTEXT, (PVOID)&pRemoteCertContext) != SEC_E_OK) + return NULL; + CertGetCertificateContextProperty(pRemoteCertContext, CERT_HASH_PROP_ID, fp, &len); + return len; +} \ No newline at end of file diff --git a/plugins/builtin/cio_socket.c b/plugins/builtin/cio_socket.c index a4060ab7..485b1c2c 100644 --- a/plugins/builtin/cio_socket.c +++ b/plugins/builtin/cio_socket.c @@ -436,6 +436,7 @@ int cio_socket_wait_io_or_timeout(MARIADB_CIO *cio, my_bool is_read, int timeout csock= (struct st_cio_socket *)cio->data; { #ifndef _WIN32 + memset(&p_fd, 0, sizeof(p_fd)); p_fd.fd= csock->socket; p_fd.events= (is_read) ? POLLIN : POLLOUT; diff --git a/plugins/cio/tls_schannel.c b/plugins/cio/tls_schannel.c deleted file mode 100644 index 6fb27536..00000000 --- a/plugins/cio/tls_schannel.c +++ /dev/null @@ -1,1257 +0,0 @@ - * SSL/TLS interface functions for Microsoft Schannel - - * Copyright (c) 2005-2009, Jouni Malinen - - * - - * This program is free software; you can redistribute it and/or modify - - * it under the terms of the GNU General Public License version 2 as - - * published by the Free Software Foundation. - - * - - * Alternatively, this software may be distributed under the terms of BSD - - * license. - - * - - * See README and COPYING for more details. - - */ - -/* - - * FIX: Go through all SSPI functions and verify what needs to be freed - - * FIX: session resumption - - * TODO: add support for server cert chain validation - - * TODO: add support for CA cert validation - - * TODO: add support for EAP-TLS (client cert/key conf) - - */ - -#include "includes.h" - -#include - -#include - -#include - -#define SECURITY_WIN32 - -#include - -#include - -#include "common.h" - -#include "tls.h" - -struct tls_global { - - HMODULE hsecurity; - - PSecurityFunctionTable sspi; - - HCERTSTORE my_cert_store; - -}; - -struct tls_connection { - - int established, start; - - int failed, read_alerts, write_alerts; - - SCHANNEL_CRED schannel_cred; - - CredHandle creds; - - CtxtHandle context; - - u8 eap_tls_prf[128]; - - int eap_tls_prf_set; - -}; - -static int schannel_load_lib(struct tls_global *global) - -{ - - INIT_SECURITY_INTERFACE pInitSecurityInterface; - - global->hsecurity = LoadLibrary(TEXT("Secur32.dll")); - - if (global->hsecurity == NULL) { - - wpa_printf(MSG_ERROR, "%s: Could not load Secur32.dll - 0x%x", - - __func__, (unsigned int) GetLastError()); - - return -1; - - } - - pInitSecurityInterface = (INIT_SECURITY_INTERFACE) GetProcAddress( - - global->hsecurity, "InitSecurityInterfaceA"); - - if (pInitSecurityInterface == NULL) { - - wpa_printf(MSG_ERROR, "%s: Could not find " - - "InitSecurityInterfaceA from Secur32.dll", - - __func__); - - FreeLibrary(global->hsecurity); - - global->hsecurity = NULL; - - return -1; - - } - - global->sspi = pInitSecurityInterface(); - - if (global->sspi == NULL) { - - wpa_printf(MSG_ERROR, "%s: Could not read security " - - "interface - 0x%x", - - __func__, (unsigned int) GetLastError()); - - FreeLibrary(global->hsecurity); - - global->hsecurity = NULL; - - return -1; - - } - - return 0; - -} - -void * tls_init(const struct tls_config *conf) - -{ - - struct tls_global *global; - - global = os_zalloc(sizeof(*global)); - - if (global == NULL) - - return NULL; - - if (schannel_load_lib(global)) { - - os_free(global); - - return NULL; - - } - - return global; - -} - -void tls_deinit(void *ssl_ctx) - -{ - - struct tls_global *global = ssl_ctx; - - if (global->my_cert_store) - - CertCloseStore(global->my_cert_store, 0); - - FreeLibrary(global->hsecurity); - - os_free(global); - -} - -int tls_get_errors(void *ssl_ctx) - -{ - - return 0; - -} - -struct tls_connection * tls_connection_init(void *ssl_ctx) - -{ - - struct tls_connection *conn; - - conn = os_zalloc(sizeof(*conn)); - - if (conn == NULL) - - return NULL; - - conn->start = 1; - - return conn; - -} - -void tls_connection_deinit(void *ssl_ctx, struct tls_connection *conn) - -{ - - if (conn == NULL) - - return; - - os_free(conn); - -} - -int tls_connection_established(void *ssl_ctx, struct tls_connection *conn) - -{ - - return conn ? conn->established : 0; - -} - -int tls_connection_shutdown(void *ssl_ctx, struct tls_connection *conn) - -{ - - struct tls_global *global = ssl_ctx; - - if (conn == NULL) - - return -1; - - conn->eap_tls_prf_set = 0; - - conn->established = conn->failed = 0; - - conn->read_alerts = conn->write_alerts = 0; - - global->sspi->DeleteSecurityContext(&conn->context); - - /* FIX: what else needs to be reseted? */ - - return 0; - -} - -int tls_global_set_params(void *tls_ctx, - - const struct tls_connection_params *params) - -{ - - return -1; - -} - -int tls_global_set_verify(void *ssl_ctx, int check_crl) - -{ - - return -1; - -} - -int tls_connection_set_verify(void *ssl_ctx, struct tls_connection *conn, - - int verify_peer) - -{ - - return -1; - -} - -int tls_connection_get_keys(void *ssl_ctx, struct tls_connection *conn, - - struct tls_keys *keys) - -{ - - /* Schannel does not export master secret or client/server random. */ - - return -1; - -} - -int tls_connection_prf(void *tls_ctx, struct tls_connection *conn, - - const char *label, int server_random_first, - - u8 *out, size_t out_len) - -{ - - /* - - * Cannot get master_key from Schannel, but EapKeyBlock can be used to - - * generate session keys for EAP-TLS and EAP-PEAPv0. EAP-PEAPv2 and - - * EAP-TTLS cannot use this, though, since they are using different - - * labels. The only option could be to implement TLSv1 completely here - - * and just use Schannel or CryptoAPI for low-level crypto - - * functionality.. - - */ - - if (conn == NULL || !conn->eap_tls_prf_set || server_random_first || - - os_strcmp(label, "client EAP encryption") != 0 || - - out_len > sizeof(conn->eap_tls_prf)) - - return -1; - - os_memcpy(out, conn->eap_tls_prf, out_len); - - return 0; - -} - -static struct wpabuf * tls_conn_hs_clienthello(struct tls_global *global, - - struct tls_connection *conn) - -{ - - DWORD sspi_flags, sspi_flags_out; - - SecBufferDesc outbuf; - - SecBuffer outbufs[1]; - - SECURITY_STATUS status; - - TimeStamp ts_expiry; - - sspi_flags = ISC_REQ_REPLAY_DETECT | - - ISC_REQ_CONFIDENTIALITY | - - ISC_RET_EXTENDED_ERROR | - - ISC_REQ_ALLOCATE_MEMORY | - - ISC_REQ_MANUAL_CRED_VALIDATION; - - wpa_printf(MSG_DEBUG, "%s: Generating ClientHello", __func__); - - outbufs[0].pvBuffer = NULL; - - outbufs[0].BufferType = SECBUFFER_TOKEN; - - outbufs[0].cbBuffer = 0; - - outbuf.cBuffers = 1; - - outbuf.pBuffers = outbufs; - - outbuf.ulVersion = SECBUFFER_VERSION; - -#ifdef UNICODE - - status = global->sspi->InitializeSecurityContextW( - - &conn->creds, NULL, NULL /* server name */, sspi_flags, 0, - - SECURITY_NATIVE_DREP, NULL, 0, &conn->context, - - &outbuf, &sspi_flags_out, &ts_expiry); - -#else /* UNICODE */ - - status = global->sspi->InitializeSecurityContextA( - - &conn->creds, NULL, NULL /* server name */, sspi_flags, 0, - - SECURITY_NATIVE_DREP, NULL, 0, &conn->context, - - &outbuf, &sspi_flags_out, &ts_expiry); - -#endif /* UNICODE */ - - if (status != SEC_I_CONTINUE_NEEDED) { - - wpa_printf(MSG_ERROR, "%s: InitializeSecurityContextA " - - "failed - 0x%x", - - __func__, (unsigned int) status); - - return NULL; - - } - - if (outbufs[0].cbBuffer != 0 && outbufs[0].pvBuffer) { - - struct wpabuf *buf; - - wpa_hexdump(MSG_MSGDUMP, "SChannel - ClientHello", - - outbufs[0].pvBuffer, outbufs[0].cbBuffer); - - conn->start = 0; - - buf = wpabuf_alloc_copy(outbufs[0].pvBuffer, - - outbufs[0].cbBuffer); - - if (buf == NULL) - - return NULL; - - global->sspi->FreeContextBuffer(outbufs[0].pvBuffer); - - return buf; - - } - - wpa_printf(MSG_ERROR, "SChannel: Failed to generate ClientHello"); - - return NULL; - -} - -#ifndef SECPKG_ATTR_EAP_KEY_BLOCK - -#define SECPKG_ATTR_EAP_KEY_BLOCK 0x5b - -typedef struct _SecPkgContext_EapKeyBlock { - - BYTE rgbKeys[128]; - - BYTE rgbIVs[64]; - -} SecPkgContext_EapKeyBlock, *PSecPkgContext_EapKeyBlock; - -#endif /* !SECPKG_ATTR_EAP_KEY_BLOCK */ - -static int tls_get_eap(struct tls_global *global, struct tls_connection *conn) - -{ - - SECURITY_STATUS status; - - SecPkgContext_EapKeyBlock kb; - - /* Note: Windows NT and Windows Me/98/95 do not support getting - - * EapKeyBlock */ - - status = global->sspi->QueryContextAttributes( - - &conn->context, SECPKG_ATTR_EAP_KEY_BLOCK, &kb); - - if (status != SEC_E_OK) { - - wpa_printf(MSG_DEBUG, "%s: QueryContextAttributes(" - - "SECPKG_ATTR_EAP_KEY_BLOCK) failed (%d)", - - __func__, (int) status); - - return -1; - - } - - wpa_hexdump_key(MSG_MSGDUMP, "Schannel - EapKeyBlock - rgbKeys", - - kb.rgbKeys, sizeof(kb.rgbKeys)); - - wpa_hexdump_key(MSG_MSGDUMP, "Schannel - EapKeyBlock - rgbIVs", - - kb.rgbIVs, sizeof(kb.rgbIVs)); - - os_memcpy(conn->eap_tls_prf, kb.rgbKeys, sizeof(kb.rgbKeys)); - - conn->eap_tls_prf_set = 1; - - return 0; - -} - -struct wpabuf * tls_connection_handshake(void *tls_ctx, - - struct tls_connection *conn, - - const struct wpabuf *in_data, - - struct wpabuf **appl_data) - -{ - - struct tls_global *global = tls_ctx; - - DWORD sspi_flags, sspi_flags_out; - - SecBufferDesc inbuf, outbuf; - - SecBuffer inbufs[2], outbufs[1]; - - SECURITY_STATUS status; - - TimeStamp ts_expiry; - - struct wpabuf *out_buf = NULL; - - if (appl_data) - - *appl_data = NULL; - - if (conn->start) - - return tls_conn_hs_clienthello(global, conn); - - wpa_printf(MSG_DEBUG, "SChannel: %d bytes handshake data to process", - - (int) wpabuf_len(in_data)); - - sspi_flags = ISC_REQ_REPLAY_DETECT | - - ISC_REQ_CONFIDENTIALITY | - - ISC_RET_EXTENDED_ERROR | - - ISC_REQ_ALLOCATE_MEMORY | - - ISC_REQ_MANUAL_CRED_VALIDATION; - - /* Input buffer for Schannel */ - - inbufs[0].pvBuffer = (u8 *) wpabuf_head(in_data); - - inbufs[0].cbBuffer = wpabuf_len(in_data); - - inbufs[0].BufferType = SECBUFFER_TOKEN; - - /* Place for leftover data from Schannel */ - - inbufs[1].pvBuffer = NULL; - - inbufs[1].cbBuffer = 0; - - inbufs[1].BufferType = SECBUFFER_EMPTY; - - inbuf.cBuffers = 2; - - inbuf.pBuffers = inbufs; - - inbuf.ulVersion = SECBUFFER_VERSION; - - /* Output buffer for Schannel */ - - outbufs[0].pvBuffer = NULL; - - outbufs[0].cbBuffer = 0; - - outbufs[0].BufferType = SECBUFFER_TOKEN; - - outbuf.cBuffers = 1; - - outbuf.pBuffers = outbufs; - - outbuf.ulVersion = SECBUFFER_VERSION; - -#ifdef UNICODE - - status = global->sspi->InitializeSecurityContextW( - - &conn->creds, &conn->context, NULL, sspi_flags, 0, - - SECURITY_NATIVE_DREP, &inbuf, 0, NULL, - - &outbuf, &sspi_flags_out, &ts_expiry); - -#else /* UNICODE */ - - status = global->sspi->InitializeSecurityContextA( - - &conn->creds, &conn->context, NULL, sspi_flags, 0, - - SECURITY_NATIVE_DREP, &inbuf, 0, NULL, - - &outbuf, &sspi_flags_out, &ts_expiry); - -#endif /* UNICODE */ - - wpa_printf(MSG_MSGDUMP, "Schannel: InitializeSecurityContext -> " - - "status=%d inlen[0]=%d intype[0]=%d inlen[1]=%d " - - "intype[1]=%d outlen[0]=%d", - - (int) status, (int) inbufs[0].cbBuffer, - - (int) inbufs[0].BufferType, (int) inbufs[1].cbBuffer, - - (int) inbufs[1].BufferType, - - (int) outbufs[0].cbBuffer); - - if (status == SEC_E_OK || status == SEC_I_CONTINUE_NEEDED || - - (FAILED(status) && (sspi_flags_out & ISC_RET_EXTENDED_ERROR))) { - - if (outbufs[0].cbBuffer != 0 && outbufs[0].pvBuffer) { - - wpa_hexdump(MSG_MSGDUMP, "SChannel - output", - - outbufs[0].pvBuffer, outbufs[0].cbBuffer); - - out_buf = wpabuf_alloc_copy(outbufs[0].pvBuffer, - - outbufs[0].cbBuffer); - - global->sspi->FreeContextBuffer(outbufs[0].pvBuffer); - - outbufs[0].pvBuffer = NULL; - - if (out_buf == NULL) - - return NULL; - - } - - } - - switch (status) { - - case SEC_E_INCOMPLETE_MESSAGE: - - wpa_printf(MSG_DEBUG, "Schannel: SEC_E_INCOMPLETE_MESSAGE"); - - break; - - case SEC_I_CONTINUE_NEEDED: - - wpa_printf(MSG_DEBUG, "Schannel: SEC_I_CONTINUE_NEEDED"); - - break; - - case SEC_E_OK: - - /* TODO: verify server certificate chain */ - - wpa_printf(MSG_DEBUG, "Schannel: SEC_E_OK - Handshake " - - "completed successfully"); - - conn->established = 1; - - tls_get_eap(global, conn); - - /* Need to return something to get final TLS ACK. */ - - if (out_buf == NULL) - - out_buf = wpabuf_alloc(0); - - if (inbufs[1].BufferType == SECBUFFER_EXTRA) { - - wpa_hexdump(MSG_MSGDUMP, "SChannel - Encrypted " - - "application data", - - inbufs[1].pvBuffer, inbufs[1].cbBuffer); - - if (appl_data) { - - *appl_data = wpabuf_alloc_copy( - - outbufs[1].pvBuffer, - - outbufs[1].cbBuffer); - - } - - global->sspi->FreeContextBuffer(inbufs[1].pvBuffer); - - inbufs[1].pvBuffer = NULL; - - } - - break; - - case SEC_I_INCOMPLETE_CREDENTIALS: - - wpa_printf(MSG_DEBUG, - - "Schannel: SEC_I_INCOMPLETE_CREDENTIALS"); - - break; - - case SEC_E_WRONG_PRINCIPAL: - - wpa_printf(MSG_DEBUG, "Schannel: SEC_E_WRONG_PRINCIPAL"); - - break; - - case SEC_E_INTERNAL_ERROR: - - wpa_printf(MSG_DEBUG, "Schannel: SEC_E_INTERNAL_ERROR"); - - break; - - } - - if (FAILED(status)) { - - wpa_printf(MSG_DEBUG, "Schannel: Handshake failed " - - "(out_buf=%p)", out_buf); - - conn->failed++; - - global->sspi->DeleteSecurityContext(&conn->context); - - return out_buf; - - } - - if (inbufs[1].BufferType == SECBUFFER_EXTRA) { - - /* TODO: Can this happen? What to do with this data? */ - - wpa_hexdump(MSG_MSGDUMP, "SChannel - Leftover data", - - inbufs[1].pvBuffer, inbufs[1].cbBuffer); - - global->sspi->FreeContextBuffer(inbufs[1].pvBuffer); - - inbufs[1].pvBuffer = NULL; - - } - - return out_buf; - -} - -struct wpabuf * tls_connection_server_handshake(void *tls_ctx, - - struct tls_connection *conn, - - const struct wpabuf *in_data, - - struct wpabuf **appl_data) - -{ - - return NULL; - -} - -struct wpabuf * tls_connection_encrypt(void *tls_ctx, - - struct tls_connection *conn, - - const struct wpabuf *in_data) - -{ - - struct tls_global *global = tls_ctx; - - SECURITY_STATUS status; - - SecBufferDesc buf; - - SecBuffer bufs[4]; - - SecPkgContext_StreamSizes sizes; - - int i; - - struct wpabuf *out; - - status = global->sspi->QueryContextAttributes(&conn->context, - - SECPKG_ATTR_STREAM_SIZES, - - &sizes); - - if (status != SEC_E_OK) { - - wpa_printf(MSG_DEBUG, "%s: QueryContextAttributes failed", - - __func__); - - return NULL; - - } - - wpa_printf(MSG_DEBUG, "%s: Stream sizes: header=%u trailer=%u", - - __func__, - - (unsigned int) sizes.cbHeader, - - (unsigned int) sizes.cbTrailer); - - out = wpabuf_alloc(sizes.cbHeader + wpabuf_len(in_data) + - - sizes.cbTrailer); - - os_memset(&bufs, 0, sizeof(bufs)); - - bufs[0].pvBuffer = wpabuf_put(out, sizes.cbHeader); - - bufs[0].cbBuffer = sizes.cbHeader; - - bufs[0].BufferType = SECBUFFER_STREAM_HEADER; - - bufs[1].pvBuffer = wpabuf_put(out, 0); - - wpabuf_put_buf(out, in_data); - - bufs[1].cbBuffer = wpabuf_len(in_data); - - bufs[1].BufferType = SECBUFFER_DATA; - - bufs[2].pvBuffer = wpabuf_put(out, sizes.cbTrailer); - - bufs[2].cbBuffer = sizes.cbTrailer; - - bufs[2].BufferType = SECBUFFER_STREAM_TRAILER; - - buf.ulVersion = SECBUFFER_VERSION; - - buf.cBuffers = 3; - - buf.pBuffers = bufs; - - status = global->sspi->EncryptMessage(&conn->context, 0, &buf, 0); - - wpa_printf(MSG_MSGDUMP, "Schannel: EncryptMessage -> " - - "status=%d len[0]=%d type[0]=%d len[1]=%d type[1]=%d " - - "len[2]=%d type[2]=%d", - - (int) status, - - (int) bufs[0].cbBuffer, (int) bufs[0].BufferType, - - (int) bufs[1].cbBuffer, (int) bufs[1].BufferType, - - (int) bufs[2].cbBuffer, (int) bufs[2].BufferType); - - wpa_printf(MSG_MSGDUMP, "Schannel: EncryptMessage pointers: " - - "out_data=%p bufs %p %p %p", - - wpabuf_head(out), bufs[0].pvBuffer, bufs[1].pvBuffer, - - bufs[2].pvBuffer); - - for (i = 0; i < 3; i++) { - - if (bufs[i].pvBuffer && bufs[i].BufferType != SECBUFFER_EMPTY) - - { - - wpa_hexdump(MSG_MSGDUMP, "SChannel: bufs", - - bufs[i].pvBuffer, bufs[i].cbBuffer); - - } - - } - - if (status == SEC_E_OK) { - - wpa_printf(MSG_DEBUG, "%s: SEC_E_OK", __func__); - - wpa_hexdump_buf_key(MSG_MSGDUMP, "Schannel: Encrypted data " - - "from EncryptMessage", out); - - return out; - - } - - wpa_printf(MSG_DEBUG, "%s: Failed - status=%d", - - __func__, (int) status); - - wpabuf_free(out); - - return NULL; - -} - -struct wpabuf * tls_connection_decrypt(void *tls_ctx, - - struct tls_connection *conn, - - const struct wpabuf *in_data) - -{ - - struct tls_global *global = tls_ctx; - - SECURITY_STATUS status; - - SecBufferDesc buf; - - SecBuffer bufs[4]; - - int i; - - struct wpabuf *out, *tmp; - - wpa_hexdump_buf(MSG_MSGDUMP, - - "Schannel: Encrypted data to DecryptMessage", in_data); - - os_memset(&bufs, 0, sizeof(bufs)); - - tmp = wpabuf_dup(in_data); - - if (tmp == NULL) - - return NULL; - - bufs[0].pvBuffer = wpabuf_mhead(tmp); - - bufs[0].cbBuffer = wpabuf_len(in_data); - - bufs[0].BufferType = SECBUFFER_DATA; - - bufs[1].BufferType = SECBUFFER_EMPTY; - - bufs[2].BufferType = SECBUFFER_EMPTY; - - bufs[3].BufferType = SECBUFFER_EMPTY; - - buf.ulVersion = SECBUFFER_VERSION; - - buf.cBuffers = 4; - - buf.pBuffers = bufs; - - status = global->sspi->DecryptMessage(&conn->context, &buf, 0, - - NULL); - - wpa_printf(MSG_MSGDUMP, "Schannel: DecryptMessage -> " - - "status=%d len[0]=%d type[0]=%d len[1]=%d type[1]=%d " - - "len[2]=%d type[2]=%d len[3]=%d type[3]=%d", - - (int) status, - - (int) bufs[0].cbBuffer, (int) bufs[0].BufferType, - - (int) bufs[1].cbBuffer, (int) bufs[1].BufferType, - - (int) bufs[2].cbBuffer, (int) bufs[2].BufferType, - - (int) bufs[3].cbBuffer, (int) bufs[3].BufferType); - - wpa_printf(MSG_MSGDUMP, "Schannel: DecryptMessage pointers: " - - "out_data=%p bufs %p %p %p %p", - - wpabuf_head(tmp), bufs[0].pvBuffer, bufs[1].pvBuffer, - - bufs[2].pvBuffer, bufs[3].pvBuffer); - - switch (status) { - - case SEC_E_INCOMPLETE_MESSAGE: - - wpa_printf(MSG_DEBUG, "%s: SEC_E_INCOMPLETE_MESSAGE", - - __func__); - - break; - - case SEC_E_OK: - - wpa_printf(MSG_DEBUG, "%s: SEC_E_OK", __func__); - - for (i = 0; i < 4; i++) { - - if (bufs[i].BufferType == SECBUFFER_DATA) - - break; - - } - - if (i == 4) { - - wpa_printf(MSG_DEBUG, "%s: No output data from " - - "DecryptMessage", __func__); - - wpabuf_free(tmp); - - return NULL; - - } - - wpa_hexdump_key(MSG_MSGDUMP, "Schannel: Decrypted data from " - - "DecryptMessage", - - bufs[i].pvBuffer, bufs[i].cbBuffer); - - out = wpabuf_alloc_copy(bufs[i].pvBuffer, bufs[i].cbBuffer); - - wpabuf_free(tmp); - - return out; - - } - - wpa_printf(MSG_DEBUG, "%s: Failed - status=%d", - - __func__, (int) status); - - wpabuf_free(tmp); - - return NULL; - -} - -int tls_connection_resumed(void *ssl_ctx, struct tls_connection *conn) - -{ - - return 0; - -} - -int tls_connection_set_cipher_list(void *tls_ctx, struct tls_connection *conn, - - u8 *ciphers) - -{ - - return -1; - -} - -int tls_get_cipher(void *ssl_ctx, struct tls_connection *conn, - - char *buf, size_t buflen) - -{ - - return -1; - -} - -int tls_connection_enable_workaround(void *ssl_ctx, - - struct tls_connection *conn) - -{ - - return 0; - -} - -int tls_connection_client_hello_ext(void *ssl_ctx, struct tls_connection *conn, - - int ext_type, const u8 *data, - - size_t data_len) - -{ - - return -1; - -} - -int tls_connection_get_failed(void *ssl_ctx, struct tls_connection *conn) - -{ - - if (conn == NULL) - - return -1; - - return conn->failed; - -} - -int tls_connection_get_read_alerts(void *ssl_ctx, struct tls_connection *conn) - -{ - - if (conn == NULL) - - return -1; - - return conn->read_alerts; - -} - -int tls_connection_get_write_alerts(void *ssl_ctx, struct tls_connection *conn) - -{ - - if (conn == NULL) - - return -1; - - return conn->write_alerts; - -} - -int tls_connection_set_params(void *tls_ctx, struct tls_connection *conn, - - const struct tls_connection_params *params) - -{ - - struct tls_global *global = tls_ctx; - - ALG_ID algs[1]; - - SECURITY_STATUS status; - - TimeStamp ts_expiry; - - if (conn == NULL) - - return -1; - - if (global->my_cert_store == NULL && - - (global->my_cert_store = CertOpenSystemStore(0, TEXT("MY"))) == - - NULL) { - - wpa_printf(MSG_ERROR, "%s: CertOpenSystemStore failed - 0x%x", - - __func__, (unsigned int) GetLastError()); - - return -1; - - } - - os_memset(&conn->schannel_cred, 0, sizeof(conn->schannel_cred)); - - conn->schannel_cred.dwVersion = SCHANNEL_CRED_VERSION; - - conn->schannel_cred.grbitEnabledProtocols = SP_PROT_TLS1; - - algs[0] = CALG_RSA_KEYX; - - conn->schannel_cred.cSupportedAlgs = 1; - - conn->schannel_cred.palgSupportedAlgs = algs; - - conn->schannel_cred.dwFlags |= SCH_CRED_NO_DEFAULT_CREDS; - -#ifdef UNICODE - - status = global->sspi->AcquireCredentialsHandleW( - - NULL, UNISP_NAME_W, SECPKG_CRED_OUTBOUND, NULL, - - &conn->schannel_cred, NULL, NULL, &conn->creds, &ts_expiry); - -#else /* UNICODE */ - - status = global->sspi->AcquireCredentialsHandleA( - - NULL, UNISP_NAME_A, SECPKG_CRED_OUTBOUND, NULL, - - &conn->schannel_cred, NULL, NULL, &conn->creds, &ts_expiry); - -#endif /* UNICODE */ - - if (status != SEC_E_OK) { - - wpa_printf(MSG_DEBUG, "%s: AcquireCredentialsHandleA failed - " - - "0x%x", __func__, (unsigned int) status); - - return -1; - - } - - return 0; - -} - -unsigned int tls_capabilities(void *tls_ctx) - -{ - - return 0; - -} - -int tls_connection_set_ia(void *tls_ctx, struct tls_connection *conn, - - int tls_ia) - -{ - - return -1; - -} - -int tls_connection_ia_send_phase_finished(void *tls_ctx, - - struct tls_connection *conn, - - int final, - - u8 *out_data, size_t out_len) - -{ - - return -1; - -} - -int tls_connection_ia_final_phase_finished(void *tls_ctx, - - struct tls_connection *conn) - -{ - - return -1; - -} - -int tls_connection_ia_permute_inner_secret(void *tls_ctx, - - struct tls_connection *conn, - - const u8 *key, size_t key_len) - -{ - - return -1; - -} - - diff --git a/plugins/trace/trace_example.c b/plugins/trace/trace_example.c index 25854e48..1ce5d1b8 100644 --- a/plugins/trace/trace_example.c +++ b/plugins/trace/trace_example.c @@ -52,7 +52,6 @@ mysql_declare_client_plugin(TRACE) "LGPL", &trace_init, &trace_deinit, - NULL mysql_end_client_plugin; static char *commands[]= { diff --git a/plugins/trace/trace_example.so b/plugins/trace/trace_example.so index e92d7a54026704c8debef2a6bda56caf9e13e266..c26ca8261e24bde91e467fe4e5d004be123b6773 100755 GIT binary patch literal 43254 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