Skip to content

Commit 6a67a34

Browse files
committed
CONC-527 "SEC_E_ALGORITHM_MISMATCH" connecting Windows client to Ubuntu
The bug happens only when connecting with SSL with client certificates. Apparently if client certificates are used in TLS handshake, private keys for cert should be loaded into named persistent container.This is because AcquireCredentialsHandle is done partically out-of-process in lsass.exe, and lsass wants to read private keys from disk See discussion in dotnet/runtime#23749 Schannel has legacy behavior for ephemeral keys, not involving lsass, and this is why it worked for us so far, however there are limitations. It appears to only use rsa_sha1 for signature verification, and newer OpenSSL no longer allows SHA1 for it, and this ends up in "algorithm mismatch" message from schannel. The above is just my understanding of how it works, because there is no real documentation, the conclusion is based on discussion in dotnet/runtime#23749 The fix: So storing the key in persistent named container evidently fixes it, and this is what is done in this patch. Care is takes to destroy key container after key is no longer needed, to avoid filling %AppData%\Roaming\Microsoft\Crypto\RSA with tiny encrypted key files. Thus the "persistency window" of the key in container on disk is only for duration of AcquireCredentialsHandle
1 parent 01b6b32 commit 6a67a34

File tree

3 files changed

+93
-57
lines changed

3 files changed

+93
-57
lines changed

libmariadb/secure/schannel.c

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -206,13 +206,14 @@ void ma_tls_end()
206206
}
207207

208208
/* {{{ static int ma_tls_set_client_certs(MARIADB_TLS *ctls) */
209-
static int ma_tls_set_client_certs(MARIADB_TLS *ctls,const CERT_CONTEXT **cert_ctx)
209+
static int ma_tls_set_client_certs(MARIADB_TLS *ctls, client_cert_handle *cert_handle)
210210
{
211211
MYSQL *mysql= ctls->pvio->mysql;
212212
char *certfile= mysql->options.ssl_cert,
213213
*keyfile= mysql->options.ssl_key;
214214
MARIADB_PVIO *pvio= ctls->pvio;
215215
char errmsg[256];
216+
SECURITY_STATUS status;
216217

217218
if (!certfile && keyfile)
218219
certfile= keyfile;
@@ -222,8 +223,8 @@ static int ma_tls_set_client_certs(MARIADB_TLS *ctls,const CERT_CONTEXT **cert_c
222223
if (!certfile)
223224
return 0;
224225

225-
*cert_ctx = schannel_create_cert_context(certfile, keyfile, errmsg, sizeof(errmsg));
226-
if (!*cert_ctx)
226+
status = schannel_create_cert_context(certfile, keyfile, cert_handle, errmsg, sizeof(errmsg));
227+
if (status)
227228
{
228229
pvio->set_error(pvio->mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, 0, errmsg);
229230
return 1;
@@ -372,7 +373,7 @@ my_bool ma_tls_connect(MARIADB_TLS *ctls)
372373
size_t i;
373374
DWORD protocol = 0;
374375
int verify_certs;
375-
const CERT_CONTEXT* cert_context = NULL;
376+
client_cert_handle cert_handle= {0};
376377

377378
if (!ctls)
378379
return 1;
@@ -429,16 +430,18 @@ my_bool ma_tls_connect(MARIADB_TLS *ctls)
429430
Cred.grbitEnabledProtocols = SP_PROT_TLS1_0_CLIENT | SP_PROT_TLS1_1_CLIENT | SP_PROT_TLS1_2_CLIENT;
430431

431432

432-
if (ma_tls_set_client_certs(ctls, &cert_context))
433+
if (ma_tls_set_client_certs(ctls, &cert_handle))
433434
goto end;
434435

435-
if (cert_context)
436+
if (cert_handle.cert)
436437
{
437438
Cred.cCreds = 1;
438-
Cred.paCred = &cert_context;
439+
Cred.paCred = &cert_handle.cert;
439440
}
440441
sRet= AcquireCredentialsHandleA(NULL, UNISP_NAME_A, SECPKG_CRED_OUTBOUND,
441442
NULL, &Cred, NULL, NULL, &sctx->CredHdl, NULL);
443+
/* We do not need to keep certificates after this point */
444+
schannel_free_cert_context(&cert_handle);
442445
if (sRet)
443446
{
444447
ma_schannel_set_sec_error(pvio, sRet);
@@ -459,8 +462,8 @@ my_bool ma_tls_connect(MARIADB_TLS *ctls)
459462
rc = 0;
460463

461464
end:
462-
if (cert_context)
463-
schannel_free_cert_context(cert_context);
465+
if (cert_handle.cert)
466+
schannel_free_cert_context(&cert_handle);
464467
return rc;
465468
}
466469

libmariadb/secure/schannel_certs.c

Lines changed: 69 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -636,16 +636,29 @@ SECURITY_STATUS schannel_verify_server_certificate(
636636
return status;
637637
}
638638

639+
/*
640+
Generate a random key container name.
641+
Used to store private key in Windows key store.
642+
*/
643+
#define KEY_CONTAINER_NAME_PREFIX L"MariaDB-Connector-C-"
644+
static void generate_key_container_name(wchar_t* key_container_name, size_t key_container_name_len)
645+
{
646+
LARGE_INTEGER now;
647+
QueryPerformanceCounter(&now);
648+
swprintf_s(key_container_name, key_container_name_len,
649+
L"MariaDB-Connector-C-%u-%u-%lld",GetCurrentProcessId(),GetCurrentThreadId(),now.QuadPart);
650+
}
639651

640652
/* Attach private key (in PEM format) to client certificate */
641-
static SECURITY_STATUS load_private_key(CERT_CONTEXT* cert, char* private_key_str, size_t len, char* errmsg, size_t errmsg_len)
653+
static SECURITY_STATUS load_private_key(client_cert_handle *cert_handle, char *private_key_str,
654+
size_t len, char *errmsg,
655+
size_t errmsg_len)
642656
{
643657
DWORD derlen = (DWORD)len;
644658
BYTE* derbuf = NULL;
645659
DWORD keyblob_len = 0;
646660
BYTE* keyblob = NULL;
647-
HCRYPTPROV hProv = 0;
648-
HCRYPTKEY hKey = 0;
661+
649662
CERT_KEY_CONTEXT cert_key_context = { 0 };
650663
PCRYPT_PRIVATE_KEY_INFO pki = NULL;
651664
DWORD pki_len = 0;
@@ -695,24 +708,30 @@ static SECURITY_STATUS load_private_key(CERT_CONTEXT* cert, char* private_key_st
695708
{
696709
FAIL("Failed to parse private key");
697710
}
711+
generate_key_container_name(cert_handle->key_container_name, sizeof(cert_handle->key_container_name) / sizeof(wchar_t));
698712

699-
if (!CryptAcquireContext(&hProv, NULL, MS_ENHANCED_PROV, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT))
713+
if (!CryptAcquireContextW(&cert_handle->prov,
714+
cert_handle->key_container_name, MS_ENHANCED_PROV_W,
715+
PROV_RSA_FULL, CRYPT_NEWKEYSET))
700716
{
701717
FAIL("CryptAcquireContext failed");
702718
}
703719

704-
if (!CryptImportKey(hProv, keyblob, keyblob_len, 0, 0, (HCRYPTKEY*)&hKey))
720+
if (!CryptImportKey(cert_handle->prov, keyblob, keyblob_len, 0, 0,
721+
&cert_handle->key))
705722
{
706723
FAIL("CryptImportKey failed");
707724
}
708-
cert_key_context.hCryptProv = hProv;
709-
cert_key_context.dwKeySpec = AT_KEYEXCHANGE;
710-
cert_key_context.cbSize = sizeof(cert_key_context);
711-
712-
/* assign private key to certificate context */
713-
if (!CertSetCertificateContextProperty(cert, CERT_KEY_CONTEXT_PROP_ID,
714-
CERT_STORE_NO_CRYPT_RELEASE_FLAG,
715-
&cert_key_context))
725+
// Link the private key to the certificate
726+
CRYPT_KEY_PROV_INFO keyProvInfo= {0};
727+
keyProvInfo.pwszContainerName= cert_handle->key_container_name;
728+
keyProvInfo.pwszProvName= NULL;
729+
keyProvInfo.dwProvType= PROV_RSA_FULL;
730+
keyProvInfo.dwFlags= 0;
731+
keyProvInfo.cProvParam= 0;
732+
keyProvInfo.rgProvParam= NULL;
733+
keyProvInfo.dwKeySpec= AT_KEYEXCHANGE;
734+
if (!CertSetCertificateContextProperty(cert_handle->cert, CERT_KEY_PROV_INFO_PROP_ID, 0, &keyProvInfo))
716735
{
717736
FAIL("CertSetCertificateContextProperty failed");
718737
}
@@ -721,23 +740,17 @@ static SECURITY_STATUS load_private_key(CERT_CONTEXT* cert, char* private_key_st
721740
LocalFree(derbuf);
722741
LocalFree(keyblob);
723742
LocalFree(pki);
724-
if (hKey)
725-
CryptDestroyKey(hKey);
726-
if (status)
727-
{
728-
if (hProv)
729-
CryptReleaseContext(hProv, 0);
730-
}
731743
return status;
732744
}
733745

734746
/*
735747
Given PEM strings for certificate and private key,
736748
create a client certificate*
737749
*/
738-
static CERT_CONTEXT* create_client_certificate_mem(
750+
static SECURITY_STATUS create_client_certificate_mem(
739751
char* cert_file_content,
740752
char* key_file_content,
753+
client_cert_handle* cert_handle,
741754
char* errmsg,
742755
size_t errmsg_len)
743756
{
@@ -764,7 +777,7 @@ static CERT_CONTEXT* create_client_certificate_mem(
764777
CERT_QUERY_OBJECT_BLOB, &cert_blob,
765778
CERT_QUERY_CONTENT_FLAG_CERT,
766779
CERT_QUERY_FORMAT_FLAG_ALL, 0, NULL, &actual_content_type,
767-
NULL, NULL, NULL, (const void**)&ctx))
780+
NULL, NULL, NULL, (const void**)&cert_handle->cert))
768781
{
769782
FAIL("Can't parse client certficate");
770783
}
@@ -777,7 +790,7 @@ static CERT_CONTEXT* create_client_certificate_mem(
777790
if (begin && end)
778791
{
779792
/* Assign key to certificate.*/
780-
status = load_private_key(ctx, begin, (end - begin), errmsg, errmsg_len);
793+
status = load_private_key(cert_handle, begin, (end - begin), errmsg, errmsg_len);
781794
goto cleanup;
782795
}
783796
}
@@ -789,21 +802,20 @@ static CERT_CONTEXT* create_client_certificate_mem(
789802
}
790803

791804
cleanup:
792-
if (status && ctx)
793-
{
794-
CertFreeCertificateContext(ctx);
795-
ctx = NULL;
796-
}
797-
return ctx;
805+
return status;
798806
}
799807

800808

801809
/* Given cert and key, as PEM file names, create a client certificate */
802-
CERT_CONTEXT* schannel_create_cert_context(char* cert_file, char* key_file, char* errmsg, size_t errmsg_len)
810+
SECURITY_STATUS schannel_create_cert_context(char* cert_file, char* key_file,
811+
client_cert_handle *cert_handle,
812+
char* errmsg, size_t errmsg_len)
803813
{
804-
CERT_CONTEXT* ctx = NULL;
805814
char* key_file_content = NULL;
806815
char* cert_file_content = NULL;
816+
SECURITY_STATUS status= SEC_E_INTERNAL_ERROR;
817+
818+
memset(cert_handle, 0, sizeof(client_cert_handle));
807819

808820
cert_file_content = pem_file_to_string(cert_file, errmsg, errmsg_len);
809821

@@ -821,34 +833,45 @@ CERT_CONTEXT* schannel_create_cert_context(char* cert_file, char* key_file, char
821833
goto cleanup;
822834
}
823835

824-
ctx = create_client_certificate_mem(cert_file_content, key_file_content, errmsg, errmsg_len);
836+
status = create_client_certificate_mem(cert_file_content, key_file_content, cert_handle, errmsg, errmsg_len);
825837

826838
cleanup:
827839
LocalFree(cert_file_content);
828840
if (cert_file != key_file)
829841
LocalFree(key_file_content);
830-
831-
return ctx;
842+
if (status)
843+
schannel_free_cert_context(cert_handle);
844+
return status;
832845
}
833846

834847
/*
835848
Free certificate, and all resources, created by schannel_create_cert_context()
836849
*/
837-
void schannel_free_cert_context(const CERT_CONTEXT* cert)
850+
void schannel_free_cert_context(client_cert_handle* cert_handle)
838851
{
839-
/* release provider handle which was acquires in load_private_key() */
840-
CERT_KEY_CONTEXT cert_key_context = { 0 };
841-
cert_key_context.cbSize = sizeof(cert_key_context);
842-
DWORD cbData = sizeof(CERT_KEY_CONTEXT);
843-
HCRYPTPROV hProv = 0;
844-
845-
if (CertGetCertificateContextProperty(cert, CERT_KEY_CONTEXT_PROP_ID, &cert_key_context, &cbData))
852+
if (cert_handle->key)
846853
{
847-
hProv = cert_key_context.hCryptProv;
854+
CryptDestroyKey(cert_handle->key);
855+
cert_handle->key = 0;
848856
}
849-
CertFreeCertificateContext(cert);
850-
if (hProv)
857+
if (cert_handle->prov)
851858
{
852-
CryptReleaseContext(cert_key_context.hCryptProv, 0);
859+
CryptReleaseContext(cert_handle->prov, 0);
860+
cert_handle->prov = 0;
861+
}
862+
if (cert_handle->cert)
863+
{
864+
CertFreeCertificateContext(cert_handle->cert);
865+
cert_handle->cert = 0;
866+
}
867+
if (cert_handle->key_container_name[0])
868+
{
869+
if (!CryptAcquireContextW(&cert_handle->prov, cert_handle->key_container_name,
870+
MS_ENHANCED_PROV_W, PROV_RSA_FULL,
871+
CRYPT_DELETEKEYSET))
872+
{
873+
assert(GetLastError() == NTE_BAD_KEYSET);
874+
}
875+
cert_handle->key_container_name[0] = 0;
853876
}
854877
}

libmariadb/secure/schannel_certs.h

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,15 @@
2121
#pragma once
2222
#include <windows.h>
2323
#include <wincrypt.h>
24+
typedef struct _client_cert_handle {
25+
HCRYPTPROV prov; /* provider */
26+
HCRYPTKEY key; /* private key */
27+
PCCERT_CONTEXT cert; /* client certificate */
28+
wchar_t key_container_name[64]; /* key container name */
29+
} client_cert_handle;
30+
31+
32+
void schannel_destroy_client_cert_handle(client_cert_handle *cert);
2433

2534
extern SECURITY_STATUS schannel_create_store(
2635
const char* CAFile,
@@ -43,11 +52,12 @@ extern SECURITY_STATUS schannel_verify_server_certificate(
4352

4453
extern void schannel_free_store(HCERTSTORE store);
4554

46-
extern CERT_CONTEXT* schannel_create_cert_context(
55+
extern SECURITY_STATUS schannel_create_cert_context(
4756
char* cert_file,
4857
char* key_file,
58+
client_cert_handle* cert_handle,
4959
char* errmsg,
5060
size_t errmsg_len);
5161

52-
extern void schannel_free_cert_context(const CERT_CONTEXT* cert);
62+
extern void schannel_free_cert_context(client_cert_handle *cert_handle);
5363

0 commit comments

Comments
 (0)