diff --git a/libmariadb/secure/ma_schannel.c b/libmariadb/secure/ma_schannel.c index 4b281401..06c9705f 100644 --- a/libmariadb/secure/ma_schannel.c +++ b/libmariadb/secure/ma_schannel.c @@ -133,11 +133,11 @@ void ma_schannel_set_win_error(MARIADB_PVIO *pvio) */ static LPBYTE ma_schannel_load_pem(MARIADB_PVIO *pvio, const char *PemFileName, DWORD *buffer_len) { - HANDLE hfile; + HANDLE hfile= 0; char *buffer= NULL; DWORD dwBytesRead= 0; LPBYTE der_buffer= NULL; - DWORD der_buffer_length; + DWORD der_buffer_length= 0; if (buffer_len == NULL) return NULL; @@ -156,7 +156,7 @@ static LPBYTE ma_schannel_load_pem(MARIADB_PVIO *pvio, const char *PemFileName, goto end; } - if (!(buffer= LocalAlloc(0, *buffer_len + 1))) + if (!(buffer= malloc((size_t)(*buffer_len + 1)))) { pvio->set_error(pvio->mysql, CR_OUT_OF_MEMORY, SQLSTATE_UNKNOWN, NULL); goto end; @@ -192,7 +192,7 @@ static LPBYTE ma_schannel_load_pem(MARIADB_PVIO *pvio, const char *PemFileName, } *buffer_len= der_buffer_length; - LocalFree(buffer); + free(buffer); return der_buffer; @@ -208,6 +208,196 @@ end: } /* }}} */ +static LPBYTE ma_schannel_read(MARIADB_PVIO* pvio, const char* PemFile, DWORD* buffer_len) +{ + HANDLE hfile = NULL; + char* buffer = NULL; + DWORD dwBytesRead = 0; + + if ((hfile = CreateFile(PemFile, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, NULL)) == INVALID_HANDLE_VALUE) + { + ma_schannel_set_win_error(pvio); + return NULL; + } + + if (!(*buffer_len = GetFileSize(hfile, NULL))) + { + pvio->set_error(pvio->mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, "SSL connection error: Invalid pem format"); + goto end; + } + + if (!(buffer = malloc((size_t)* buffer_len + 1))) + { + pvio->set_error(pvio->mysql, CR_OUT_OF_MEMORY, SQLSTATE_UNKNOWN, NULL); + goto end; + } + + if (!ReadFile(hfile, buffer, *buffer_len, &dwBytesRead, NULL)) + { + ma_schannel_set_win_error(pvio); + goto end; + } + + buffer[*buffer_len] = 0; + + CloseHandle(hfile); + return buffer; +end: + if (hfile != INVALID_HANDLE_VALUE) + CloseHandle(hfile); + if (buffer) + free(buffer); + *buffer_len = 0; + return NULL; +} + +LPBYTE ma_schannel_convert_base64(MARIADB_PVIO* pvio, char* buffer, DWORD buffer_len, DWORD* der_len) +{ + LPBYTE der_buffer = NULL; + + *der_len = 0; + + /* calculate the length of DER binary */ + if (!CryptStringToBinaryA(buffer, buffer_len, CRYPT_STRING_BASE64HEADER, + NULL, der_len, NULL, NULL)) + { + ma_schannel_set_win_error(pvio); + goto end; + } + /* allocate DER binary buffer */ + if (!(der_buffer = (LPBYTE)malloc(*der_len))) + { + pvio->set_error(pvio->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_len, NULL, NULL)) + { + ma_schannel_set_win_error(pvio); + goto end; + } + return der_buffer; +end: + if (der_buffer) + free(der_buffer); + return NULL; +} + + +DWORD ma_schannel_load_certs_and_keys(MARIADB_PVIO* pvio, const char* PemFileName, SC_CTX* ctx) +{ + char* buffer = NULL; + char* p, * type; + DWORD buffer_len = 0; + LPBYTE der_buffer = NULL; + DWORD der_buffer_length = 0; + + /* check if cert and key was already loaded */ + if (ctx->client_cert_ctx && ctx->der_key) + return 0; + + if (!(buffer = ma_schannel_read(pvio, PemFileName, &buffer_len))) + return 0; + + p = buffer; + + while ((p = strstr(p, "-----BEGIN"))) + { + my_bool is_cert = 0; + char* cert_end = strstr(p, "-----END"); + + if (!cert_end) + { + pvio->set_error(pvio->mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, "SSL connection error: Unknown or unsupported X509 PEM type"); goto error; + goto error; + } + + if (!(cert_end = strchr(cert_end, '\n'))) + goto error; + + if ((type = strstr(p, "CERTIFICATE")) && type < cert_end) + { + is_cert = 1; + /* We only read first certificate, further certificates will be ignored */ + if (ctx->client_cert_ctx) + { + p = cert_end; + continue; + } + } + else if ((type = strstr(p, "PRIVATE KEY")) && type < cert_end) + { + /* We only read the first key, further keys will be ignored */ + if (ctx->der_key) + { + p = cert_end; + continue; + } + } + else + { + pvio->set_error(pvio->mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, "SSL connection error: Unknown or unsupported X509 PEM type"); + goto error; + } + + if (!(der_buffer = ma_schannel_convert_base64(pvio, p, (DWORD)(cert_end - p), &der_buffer_length))) + goto error; + + if (is_cert) + { + if (!(ctx->client_cert_ctx = (CERT_CONTEXT*)CertCreateCertificateContext(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, + der_buffer, der_buffer_length))) + { + ma_schannel_set_win_error(pvio); + goto error; + } + } + else + { + if (!(ctx->der_key= (struct st_DER *)malloc(sizeof(struct st_DER)))) + { + pvio->set_error(pvio->mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, "SSL connection error: Not enough memory"); + goto error; + } + + ctx->der_key->der_buffer = der_buffer; + ctx->der_key->der_length = der_buffer_length; + + der_buffer = 0; + der_buffer_length = 0; + p = cert_end; + } + + free(der_buffer); + der_buffer = 0; + der_buffer_length = 0; + p = cert_end; + + if (ctx->client_cert_ctx && ctx->der_key) + break; + } + free(buffer); + return 0; +error: + if (buffer) + free(buffer); + if (der_buffer) + free(der_buffer); + return 1; +} + +void ma_delete_key_buffer(SC_CTX* ctx) +{ + if (!ctx->der_key) + return; + free(ctx->der_key->der_buffer); + free(ctx->der_key); + ctx->der_key = 0; +} + + /* {{{ CERT_CONTEXT *ma_schannel_create_cert_context(MARIADB_PVIO *pvio, const char *pem_file) */ /* Create a certification context from ca or cert file @@ -281,7 +471,7 @@ PCCRL_CONTEXT ma_schannel_create_crl_context(MARIADB_PVIO *pvio, const char *pem ma_schannel_set_win_error(pvio); end: if (der_buffer) - LocalFree(der_buffer); + free(der_buffer); return ctx; } /* }}} */ @@ -306,10 +496,8 @@ end: PCCRL_CONTEXT A pointer to a certification context structure */ -my_bool ma_schannel_load_private_key(MARIADB_PVIO *pvio, const CERT_CONTEXT *ctx, char *key_file) +my_bool ma_schannel_load_private_key(MARIADB_PVIO *pvio, SC_CTX *ctx) { - DWORD der_buffer_len= 0; - LPBYTE der_buffer= NULL; DWORD priv_key_len= 0; LPBYTE priv_key= NULL; HCRYPTPROV crypt_prov= 0; @@ -317,14 +505,16 @@ my_bool ma_schannel_load_private_key(MARIADB_PVIO *pvio, const CERT_CONTEXT *ctx CERT_KEY_CONTEXT kpi={ 0 }; my_bool rc= 0; - /* load private key into der binary object */ - if (!(der_buffer= ma_schannel_load_pem(pvio, key_file, &der_buffer_len))) - return 0; + if (!ctx->der_key || !ctx->client_cert_ctx) + { + pvio->set_error(pvio->mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, "SSL connection error: Invalid certificate or key"); + goto end; + } /* determine required buffer size for decoded private key */ if (!CryptDecodeObjectEx(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, PKCS_RSA_PRIVATE_KEY, - der_buffer, der_buffer_len, + ctx->der_key->der_buffer, ctx->der_key->der_length, 0, NULL, NULL, &priv_key_len)) { @@ -333,18 +523,17 @@ my_bool ma_schannel_load_private_key(MARIADB_PVIO *pvio, const CERT_CONTEXT *ctx } /* allocate buffer for decoded private key */ - if (!(priv_key= LocalAlloc(0, priv_key_len))) + if (!(priv_key= malloc(priv_key_len))) { pvio->set_error(pvio->mysql, CR_OUT_OF_MEMORY, SQLSTATE_UNKNOWN, NULL); goto end; } - /* decode */ if (!CryptDecodeObjectEx(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, - PKCS_RSA_PRIVATE_KEY, - der_buffer, der_buffer_len, - 0, NULL, - priv_key, &priv_key_len)) + PKCS_RSA_PRIVATE_KEY, + ctx->der_key->der_buffer, ctx->der_key->der_length, + 0, NULL, + priv_key, &priv_key_len)) { ma_schannel_set_win_error(pvio); goto end; @@ -368,19 +557,23 @@ my_bool ma_schannel_load_private_key(MARIADB_PVIO *pvio, const CERT_CONTEXT *ctx kpi.cbSize= sizeof(kpi); /* assign private key to certificate context */ - if (CertSetCertificateContextProperty(ctx, CERT_KEY_CONTEXT_PROP_ID, 0, &kpi)) + if (CertSetCertificateContextProperty(ctx->client_cert_ctx, CERT_KEY_CONTEXT_PROP_ID, 0, &kpi)) rc= 1; else ma_schannel_set_win_error(pvio); end: - if (der_buffer) - LocalFree(der_buffer); + if (ctx->der_key) + { + free(ctx->der_key->der_buffer); + free(ctx->der_key); + ctx->der_key = 0; + } if (priv_key) { if (crypt_key) CryptDestroyKey(crypt_key); - LocalFree(priv_key); + free(priv_key); if (!rc) if (crypt_prov) CryptReleaseContext(crypt_prov, 0); diff --git a/libmariadb/secure/ma_schannel.h b/libmariadb/secure/ma_schannel.h index 6471d8f7..abe54a63 100644 --- a/libmariadb/secure/ma_schannel.h +++ b/libmariadb/secure/ma_schannel.h @@ -46,9 +46,15 @@ #include +struct st_DER { + char* der_buffer; + DWORD der_length; +}; + struct st_schannel { HCERTSTORE cert_store; const CERT_CONTEXT *client_cert_ctx; + struct st_DER *der_key; CredHandle CredHdl; my_bool FreeCredHdl; PUCHAR IoBuffer; @@ -70,8 +76,9 @@ extern my_bool ca_Check, crl_Check; CERT_CONTEXT *ma_schannel_create_cert_context(MARIADB_PVIO *pvio, const char *pem_file); SECURITY_STATUS ma_schannel_client_handshake(MARIADB_TLS *ctls); SECURITY_STATUS ma_schannel_handshake_loop(MARIADB_PVIO *pvio, my_bool InitialRead, SecBuffer *pExtraData); -my_bool ma_schannel_load_private_key(MARIADB_PVIO *pvio, const CERT_CONTEXT *ctx, char *key_file); +my_bool ma_schannel_load_private_key(MARIADB_PVIO *pvio, SC_CTX *ctx); PCCRL_CONTEXT ma_schannel_create_crl_context(MARIADB_PVIO *pvio, const char *pem_file); +void ma_delete_key_buffer(SC_CTX* ctx); my_bool ma_schannel_verify_certs(MARIADB_TLS *ctls); ssize_t ma_schannel_write_encrypt(MARIADB_PVIO *pvio, uchar *WriteBuffer, diff --git a/libmariadb/secure/schannel.c b/libmariadb/secure/schannel.c index afa69a19..a6452849 100644 --- a/libmariadb/secure/schannel.c +++ b/libmariadb/secure/schannel.c @@ -154,7 +154,6 @@ cipher_map[] = "TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", "DHE-RSA-AES256-GCM-SHA384", { CALG_DH_EPHEM, CALG_AES_256, CALG_SHA_384, CALG_RSA_SIGN } } - }; #define MAX_ALG_ID 50 @@ -206,6 +205,7 @@ static int ma_tls_set_client_certs(MARIADB_TLS *ctls) MARIADB_PVIO *pvio= ctls->pvio; sctx->client_cert_ctx= NULL; + sctx->der_key = NULL; if (!certfile && keyfile) certfile= keyfile; @@ -215,15 +215,26 @@ static int ma_tls_set_client_certs(MARIADB_TLS *ctls) if (!certfile) return 0; - if (!(sctx->client_cert_ctx = ma_schannel_create_cert_context(ctls->pvio, certfile))) + if (certfile && ma_schannel_load_certs_and_keys(pvio, certfile, sctx)) return 1; - if (!ma_schannel_load_private_key(pvio, sctx->client_cert_ctx, keyfile)) + if (keyfile && ma_schannel_load_certs_and_keys(pvio, keyfile, sctx)) + return 1; + + if (sctx->client_cert_ctx) { - CertFreeCertificateContext(sctx->client_cert_ctx); - sctx->client_cert_ctx= NULL; + if (!ma_schannel_load_private_key(pvio, sctx)) + return 1; + } + else if (sctx->der_key) + { + free(sctx->der_key->der_buffer); + free(sctx->der_key); + sctx->der_key = 0; + pvio->set_error(pvio->mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, "SSL connection error: Cert not found"); return 1; } + return 0; } /* }}} */ @@ -293,6 +304,9 @@ my_bool ma_tls_connect(MARIADB_TLS *ctls) SC_CTX *sctx; SECURITY_STATUS sRet; ALG_ID AlgId[MAX_ALG_ID]; + size_t i; + DWORD protocol = 0; + if (!ctls || !ctls->pvio) return 1;; @@ -310,10 +324,8 @@ my_bool ma_tls_connect(MARIADB_TLS *ctls) /* Set cipher */ if (mysql->options.ssl_cipher) { - size_t i; - DWORD protocol = 0; - /* check if a protocol was specified as a cipher: + /* check if a protocol was specified as a cipher: * In this case don't allow cipher suites which belong to newer protocols * Please note: There are no cipher suites for TLS1.1 */ @@ -533,13 +545,15 @@ const char *ma_tls_get_cipher(MARIADB_TLS *ctls) SecPkgContext_CipherInfo CipherInfo = { SECPKGCONTEXT_CIPHERINFO_V1 }; SECURITY_STATUS sRet; SC_CTX *sctx; + SecPkgContext_ConnectionInfo ci; if (!ctls || !ctls->ssl) return NULL; sctx= (SC_CTX *)ctls->ssl; + sRet = QueryContextAttributesA(&sctx->ctxt, SECPKG_ATTR_CONNECTION_INFO, (PVOID)&ci); + sRet= QueryContextAttributesA(&sctx->ctxt, SECPKG_ATTR_CIPHER_INFO, (PVOID)&CipherInfo); - sRet= QueryContextAttributes(&sctx->ctxt, SECPKG_ATTR_CIPHER_INFO, (PVOID)&CipherInfo); if (sRet != SEC_E_OK) return NULL; diff --git a/unittest/libmariadb/ssl.c.in b/unittest/libmariadb/ssl.c.in index 6dd4ed85..c3ce6875 100644 --- a/unittest/libmariadb/ssl.c.in +++ b/unittest/libmariadb/ssl.c.in @@ -16,6 +16,11 @@ or write to the Free Software Foundation, Inc., 51 Franklin St., Fifth Floor, Boston, MA 02110, USA *************************************************************************************/ +#if defined(WIN32) && defined(HEAP_CHECK) +#define _CRTDBG_MAP_ALLOC +#include +#include +#endif #include "my_test.h" #include @@ -34,6 +39,7 @@ const char *ssluser= "ssluser"; const char *sslpw= "sslpw"; char sslhost[128]; char sslcert[FNLEN]; +char sslcombined[FNLEN]; char sslkey[FNLEN]; char sslkey_enc[FNLEN]; char sslca[FNLEN]; @@ -63,6 +69,7 @@ int check_skip_ssl() } } snprintf(sslcert, FNLEN - 1, "%s/%s", ssldir, "client-cert.pem"); + snprintf(sslcombined, FNLEN - 1, "%s/%s", ssldir, "client-certkey.pem"); snprintf(sslkey, FNLEN - 1, "%s/%s", ssldir, "client-key.pem"); snprintf(sslkey_enc, FNLEN - 1, "%s/%s", ssldir, "client-key-enc.pem"); snprintf(sslca, FNLEN - 1, "%s/%s", ssldir, "cacert.pem"); @@ -1287,6 +1294,33 @@ static int test_mdev14101(MYSQL *my __attribute__((unused))) return OK; } +static int test_conc386(MYSQL *mysql) +{ +#ifdef WIN32 + if (_access(sslcombined, 0) == -1) +#else + if (access(sslcombined, R_OK) != 0) +#endif + { + diag("combined cert/key file not found"); + return SKIP; + } + + mysql= mysql_init(NULL); + mysql_ssl_set(mysql, + sslcombined, + NULL, + NULL, + NULL, + NULL); + FAIL_IF(!mysql_real_connect(mysql, hostname, username, password, schema, + port, socketname, 0), mysql_error(mysql)); + FAIL_IF(check_cipher(mysql) != 0, "Invalid cipher"); + mysql_close(mysql); + unlink(sslcombined); + return OK; +} + struct my_tests_st my_tests[] = { {"test_ssl", test_ssl, TEST_CONNECTION_NEW, 0, NULL, NULL}, {"test_mdev14101", test_mdev14101, TEST_CONNECTION_NEW, 0, NULL, NULL}, @@ -1323,6 +1357,8 @@ struct my_tests_st my_tests[] = { #else {"test_schannel_cipher", test_schannel_cipher, TEST_CONNECTION_NEW, 0, NULL, NULL}, #endif + + {"test_conc386", test_conc386, TEST_CONNECTION_NEW, 0, NULL, NULL}, {"drop_ssl_user", drop_ssl_user, TEST_CONNECTION_NEW, 0, NULL, NULL}, {NULL, NULL, 0, 0, NULL, NULL} }; @@ -1330,6 +1366,15 @@ struct my_tests_st my_tests[] = { int main(int argc, char **argv) { +#if defined(WIN32) && defined(HEAP_CHECK) + _CrtSetReportMode( _CRT_WARN, _CRTDBG_MODE_FILE ); + _CrtSetReportFile( _CRT_WARN, _CRTDBG_FILE_STDOUT ); + _CrtSetReportMode( _CRT_ERROR, _CRTDBG_MODE_FILE ); + _CrtSetReportFile( _CRT_ERROR, _CRTDBG_FILE_STDOUT ); + _CrtSetReportMode( _CRT_ASSERT, _CRTDBG_MODE_FILE ); + _CrtSetReportFile( _CRT_ASSERT, _CRTDBG_FILE_STDOUT ); +#endif + get_envvars(); if (argc > 1) @@ -1337,6 +1382,9 @@ int main(int argc, char **argv) run_tests(my_tests); mysql_server_end(); +#if defined(WIN32) && defined(HEAP_CHECK) + _CrtDumpMemoryLeaks(); +#endif return(exit_status()); }