diff --git a/CMakeLists.txt b/CMakeLists.txt index 0294605d..3094afa2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -330,7 +330,8 @@ IF(NOT WITH_SSL STREQUAL "OFF") IF(WITH_SSL STREQUAL "SCHANNEL") ADD_DEFINITIONS(-DHAVE_SCHANNEL -DHAVE_TLS) SET(SSL_SOURCES "${CC_SOURCE_DIR}/libmariadb/secure/schannel.c" - "${CC_SOURCE_DIR}/libmariadb/secure/ma_schannel.c") + "${CC_SOURCE_DIR}/libmariadb/secure/ma_schannel.c" + "${CC_SOURCE_DIR}/libmariadb/secure/schannel_certs.c") INCLUDE_DIRECTORIES("${CC_SOURCE_DIR}/plugins/pvio/") SET(SSL_LIBRARIES secur32) SET(TLS_LIBRARY_VERSION "Schannel ${CMAKE_SYSTEM_VERSION}") diff --git a/libmariadb/CMakeLists.txt b/libmariadb/CMakeLists.txt index d5491a25..27e036cb 100644 --- a/libmariadb/CMakeLists.txt +++ b/libmariadb/CMakeLists.txt @@ -300,8 +300,11 @@ ${SSL_SOURCES} IF(WIN32) ADD_DEFINITIONS(-DSIZEOF_CHARP=${CMAKE_SIZEOF_VOID_P}) INCLUDE_DIRECTORIES(${CC_SOURCE_DIR}/win-iconv) - SET(LIBMARIADB_SOURCES ${LIBMARIADB_SOURCES} - ${CC_SOURCE_DIR}/win-iconv/win_iconv.c) + SET(LIBMARIADB_SOURCES + ${LIBMARIADB_SOURCES} + ${CC_SOURCE_DIR}/win-iconv/win_iconv.c + win32_errmsg.c + win32_errmsg.h) ELSE() IF(ICONV_INCLUDE_DIR) INCLUDE_DIRECTORIES(BEFORE ${ICONV_INCLUDE_DIR}) diff --git a/libmariadb/secure/ma_schannel.c b/libmariadb/secure/ma_schannel.c index b398ec3a..ffc307ca 100644 --- a/libmariadb/secure/ma_schannel.c +++ b/libmariadb/secure/ma_schannel.c @@ -20,6 +20,7 @@ *************************************************************************************/ #include "ma_schannel.h" +#include "schannel_certs.h" #include #define SC_IO_BUFFER_SIZE 0x4000 @@ -28,49 +29,13 @@ #define SCHANNEL_PAYLOAD(A) (A).cbMaximumMessage + (A).cbHeader + (A).cbTrailer void ma_schannel_set_win_error(MARIADB_PVIO *pvio, DWORD ErrorNo); -static void get_schannel_error_info(DWORD err, const char** sym, const char** text) -{ -#define ERR_ENTRY(a,b) {a,#a, b} - static struct { - DWORD code; /* Error code , e.g SEC_E_ILLEGAL_MESSAGE */ - const char* symbol; /* Error code as string, e.g "SEC_E_ILLEGAL_MESSAGE"*/ - const char* msg; /* English text message */ - } errinfo[] = - { - ERR_ENTRY(SEC_E_ILLEGAL_MESSAGE, "The message received was unexpected or badly formatted"), - ERR_ENTRY(SEC_E_UNTRUSTED_ROOT, "Untrusted root certificate"), - ERR_ENTRY(SEC_E_BUFFER_TOO_SMALL, "Buffer too small"), - ERR_ENTRY(SEC_E_CRYPTO_SYSTEM_INVALID, "Cipher is not supported"), - ERR_ENTRY(SEC_E_INSUFFICIENT_MEMORY, "Out of memory"), - ERR_ENTRY(SEC_E_OUT_OF_SEQUENCE, "Invalid message sequence"), - ERR_ENTRY(SEC_E_DECRYPT_FAILURE, "The specified data could not be decrypted"), - ERR_ENTRY(SEC_I_INCOMPLETE_CREDENTIALS, "Incomplete credentials"), - ERR_ENTRY(SEC_E_ENCRYPT_FAILURE, "The specified data could not be encrypted"), - ERR_ENTRY(SEC_I_CONTEXT_EXPIRED, "The context has expired and can no longer be used"), - ERR_ENTRY(SEC_E_ALGORITHM_MISMATCH, "no cipher match"), - ERR_ENTRY(SEC_E_NO_CREDENTIALS, "no credentials"), - ERR_ENTRY(SEC_E_INVALID_TOKEN, "The token supplied to function is invalid") - }; - for (int i = 0; i < sizeof(errinfo) / sizeof(errinfo[0]); i++) - { - if (errinfo[i].code == err) - { - *sym = errinfo[i].symbol; - *text = errinfo[i].msg; - return; - } - } - *sym = NULL; - *text = NULL; -} + /* {{{ void ma_schannel_set_sec_error */ void ma_schannel_set_sec_error(MARIADB_PVIO* pvio, DWORD ErrorNo) { MYSQL* mysql = pvio->mysql; - const char* sym; - const char* text; if (ErrorNo != SEC_E_OK) mysql->net.extension->extended_errno = ErrorNo; if (ErrorNo == SEC_E_INTERNAL_ERROR && GetLastError()) @@ -78,517 +43,22 @@ void ma_schannel_set_sec_error(MARIADB_PVIO* pvio, DWORD ErrorNo) ma_schannel_set_win_error(pvio, GetLastError()); return; } - get_schannel_error_info(ErrorNo, &sym, &text); - if (sym) - { - pvio->set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, - "SSL connection error: %s (%s , 0x%08x)", text, sym, ErrorNo); - } - else - { - ma_schannel_set_win_error(pvio, ErrorNo); - } + ma_schannel_set_win_error(pvio, ErrorNo); } /* }}} */ +#include "win32_errmsg.h" /* {{{ void ma_schnnel_set_win_error */ void ma_schannel_set_win_error(MARIADB_PVIO *pvio, DWORD ErrorNo) { - ulong ssl_errno= ErrorNo ? ErrorNo : GetLastError(); - char *ssl_error_reason= NULL; - char *p; char buffer[256]; - if (!ssl_errno) - { - pvio->set_error(pvio->mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, "Unknown SSL error"); - return; - } - - 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 ); - for (p = ssl_error_reason; *p; p++) - if (*p == '\n' || *p == '\r') - *p = 0; - snprintf(buffer, sizeof(buffer), "SSL connection error: %s. Windows error %lu / 0x%08lx",ssl_error_reason, - ErrorNo, ErrorNo); + ma_format_win32_error(buffer, sizeof(buffer), ErrorNo, "SSL connection error: "); pvio->set_error(pvio->mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, buffer); - if (ssl_error_reason) - LocalFree(ssl_error_reason); return; } /* }}} */ -/* {{{ LPBYTE ma_schannel_load_pem(const char *PemFileName, DWORD *buffer_len) */ -/* - Load a pem or clr file and convert it to a binary DER object - SYNOPSIS - ma_schannel_load_pem() - PemFileName name of the pem file (in) - buffer_len length of the converted DER binary - - DESCRIPTION - Loads a X509 file (ca, certification, key or clr) into memory and converts - it to a DER binary object. This object can be decoded and loaded into - a schannel crypto context. - If the function failed, error can be retrieved by GetLastError() - The returned binary object must be freed by caller. - - RETURN VALUE - NULL if the conversion failed or file was not found - LPBYTE * a pointer to a binary der object - buffer_len will contain the length of binary der object -*/ -static LPBYTE ma_schannel_load_pem(MARIADB_PVIO *pvio, const char *PemFileName, DWORD *buffer_len) -{ - HANDLE hfile= 0; - char *buffer= NULL; - DWORD dwBytesRead= 0; - LPBYTE der_buffer= NULL; - DWORD der_buffer_length= 0; - - if (buffer_len == NULL) - return NULL; - - - if ((hfile= CreateFile(PemFileName, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, - FILE_ATTRIBUTE_NORMAL, NULL )) == INVALID_HANDLE_VALUE) - { - ma_schannel_set_win_error(pvio, 0); - 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, 0); - 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(pvio, 0); - goto end; - } - /* allocate DER binary buffer */ - if (!(der_buffer= (LPBYTE)LocalAlloc(0, der_buffer_length))) - { - 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_buffer_length, NULL, NULL)) - { - ma_schannel_set_win_error(pvio, 0); - goto end; - } - - *buffer_len= der_buffer_length; - free(buffer); - - return der_buffer; - -end: - if (hfile != INVALID_HANDLE_VALUE) - CloseHandle(hfile); - if (buffer) - LocalFree(buffer); - if (der_buffer) - LocalFree(der_buffer); - *buffer_len= 0; - return NULL; -} -/* }}} */ - -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, 0); - 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, 0); - 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, 0); - 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, 0); - 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, 0); - 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 - - SYNOPSIS - ma_schannel_create_cert_context() - pvio pvio object - pem_file name of certificate or ca file - - DESCRIPTION - Loads a PEM file (certificate authority or certificate) creates a certification - context and loads the binary representation into context. - The returned context must be freed by caller. - If the function failed, error can be retrieved by GetLastError(). - - RETURNS - 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(MARIADB_PVIO *pvio, const char *pem_file) -{ - DWORD der_buffer_length; - LPBYTE der_buffer= NULL; - - CERT_CONTEXT *ctx= NULL; - - /* create DER binary object from ca/certification file */ - if (!(der_buffer= ma_schannel_load_pem(pvio, pem_file, (DWORD *)&der_buffer_length))) - goto end; - if (!(ctx= (CERT_CONTEXT *)CertCreateCertificateContext(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, - der_buffer, der_buffer_length))) - ma_schannel_set_win_error(pvio, 0); - -end: - if (der_buffer) - LocalFree(der_buffer); - return ctx; -} -/* }}} */ - -/* {{{ PCCRL_CONTEXT ma_schannel_create_crl_context(MARIADB_PVIO *pvio, const char *pem_file) */ -/* - Create a crl context from crlfile - - SYNOPSIS - ma_schannel_create_crl_context() - pem_file name of certificate or ca file - - DESCRIPTION - Loads a certification revocation list file, creates a certification - context and loads the binary representation into crl context. - The returned context must be freed by caller. - If the function failed, error can be retrieved by GetLastError(). - - RETURNS - 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(MARIADB_PVIO *pvio, const char *pem_file) -{ - DWORD der_buffer_length; - LPBYTE der_buffer= NULL; - - PCCRL_CONTEXT ctx= NULL; - - /* load ca pem file into memory */ - if (!(der_buffer= ma_schannel_load_pem(pvio, pem_file, (DWORD *)&der_buffer_length))) - goto end; - if (!(ctx= CertCreateCRLContext(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, - der_buffer, der_buffer_length))) - ma_schannel_set_win_error(pvio, 0); -end: - if (der_buffer) - free(der_buffer); - return ctx; -} -/* }}} */ - -/* {{{ my_bool ma_schannel_load_private_key(MARIADB_PVIO *pvio, CERT_CONTEXT *ctx, char *key_file) */ -/* - Load private key into context - - SYNOPSIS - ma_schannel_load_private_key() - ctx pointer to a certification context - pem_file name of certificate or ca file - - DESCRIPTION - Loads a certification revocation list file, creates a certification - context and loads the binary representation into crl context. - The returned context must be freed by caller. - If the function failed, error can be retrieved by GetLastError(). - - RETURNS - NULL If loading of the file or creating context failed - PCCRL_CONTEXT A pointer to a certification context structure -*/ - -my_bool ma_schannel_load_private_key(MARIADB_PVIO *pvio, SC_CTX *ctx) -{ - DWORD priv_key_len= 0; - LPBYTE priv_key= NULL; - HCRYPTPROV crypt_prov= 0; - HCRYPTKEY crypt_key= 0; - CERT_KEY_CONTEXT kpi={ 0 }; - my_bool rc= 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, - ctx->der_key->der_buffer, ctx->der_key->der_length, - 0, NULL, - NULL, &priv_key_len)) - { - ma_schannel_set_win_error(pvio, 0); - goto end; - } - - /* allocate buffer for decoded private key */ - if (!(priv_key= malloc(priv_key_len))) - { - pvio->set_error(pvio->mysql, CR_OUT_OF_MEMORY, SQLSTATE_UNKNOWN, NULL); - goto end; - } - - if (!CryptDecodeObjectEx(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, - 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, 0); - goto end; - } - - /* Acquire context */ - if (!CryptAcquireContext(&crypt_prov, NULL, MS_ENHANCED_PROV, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)) - { - ma_schannel_set_win_error(pvio, 0); - goto end; - } - /* ... and import the private key */ - if (!CryptImportKey(crypt_prov, priv_key, priv_key_len, 0, 0, (HCRYPTKEY *)&crypt_key)) - { - ma_schannel_set_win_error(pvio, 0); - goto end; - } - - kpi.hCryptProv= crypt_prov; - kpi.dwKeySpec = AT_KEYEXCHANGE; - kpi.cbSize= sizeof(kpi); - - /* assign private key to certificate context */ - if (CertSetCertificateContextProperty(ctx->client_cert_ctx, CERT_KEY_CONTEXT_PROP_ID, 0, &kpi)) - rc= 1; - else - ma_schannel_set_win_error(pvio, 0); - -end: - 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); - free(priv_key); - if (!rc) - if (crypt_prov) - CryptReleaseContext(crypt_prov, 0); - } - return rc; -} /* }}} */ /* {{{ SECURITY_STATUS ma_schannel_handshake_loop(MARIADB_PVIO *pvio, my_bool InitialRead, SecBuffer *pExtraData) */ @@ -688,7 +158,7 @@ SECURITY_STATUS ma_schannel_handshake_loop(MARIADB_PVIO *pvio, my_bool InitialRe rc = InitializeSecurityContextA(&sctx->CredHdl, - &sctx->ctxt, + &sctx->hCtxt, NULL, dwSSPIFlags, 0, @@ -711,7 +181,7 @@ SECURITY_STATUS ma_schannel_handshake_loop(MARIADB_PVIO *pvio, my_bool InitialRe if(nbytes <= 0) { FreeContextBuffer(OutBuffers.pvBuffer); - DeleteSecurityContext(&sctx->ctxt); + DeleteSecurityContext(&sctx->hCtxt); return SEC_E_INTERNAL_ERROR; } cbData= (DWORD)nbytes; @@ -773,7 +243,7 @@ loopend: if (FAILED(rc)) { ma_schannel_set_sec_error(pvio, rc); - DeleteSecurityContext(&sctx->ctxt); + DeleteSecurityContext(&sctx->hCtxt); } LocalFree(IoBuffer); @@ -837,7 +307,7 @@ SECURITY_STATUS ma_schannel_client_handshake(MARIADB_TLS *ctls) SECURITY_NATIVE_DREP, NULL, 0, - &sctx->ctxt, + &sctx->hCtxt, &BufferOut, &OutFlags, NULL); @@ -864,7 +334,7 @@ SECURITY_STATUS ma_schannel_client_handshake(MARIADB_TLS *ctls) /* allocate IO-Buffer for write operations: After handshake was successful, we are able now to calculate payload */ - if ((sRet = QueryContextAttributes(&sctx->ctxt, SECPKG_ATTR_STREAM_SIZES, &sctx->Sizes ))) + if ((sRet = QueryContextAttributes(&sctx->hCtxt, SECPKG_ATTR_STREAM_SIZES, &sctx->Sizes ))) goto end; sctx->IoBufferSize= SCHANNEL_PAYLOAD(sctx->Sizes); @@ -876,12 +346,8 @@ SECURITY_STATUS ma_schannel_client_handshake(MARIADB_TLS *ctls) return sRet; end: - LocalFree(sctx->IoBuffer); - sctx->IoBufferSize= 0; if (BuffersOut.pvBuffer) FreeContextBuffer(BuffersOut.pvBuffer); - - DeleteSecurityContext(&sctx->ctxt); return sRet; } /* }}} */ @@ -909,7 +375,6 @@ end: */ SECURITY_STATUS ma_schannel_read_decrypt(MARIADB_PVIO *pvio, - PCredHandle phCreds, CtxtHandle * phContext, DWORD *DecryptLength, uchar *ReadBuffer, @@ -1018,85 +483,58 @@ SECURITY_STATUS ma_schannel_read_decrypt(MARIADB_PVIO *pvio, } } /* }}} */ - -my_bool ma_schannel_verify_certs(MARIADB_TLS *ctls) +#include "win32_errmsg.h" +my_bool ma_schannel_verify_certs(MARIADB_TLS *ctls, BOOL verify_server_name) { - SECURITY_STATUS sRet; - + SECURITY_STATUS status; + MARIADB_PVIO *pvio= ctls->pvio; MYSQL *mysql= pvio->mysql; SC_CTX *sctx = (SC_CTX *)ctls->ssl; - const char *ca_file= mysql->options.ssl_ca; + const char* ca_path = mysql->options.ssl_capath; const char *crl_file= mysql->options.extension ? mysql->options.extension->ssl_crl : NULL; + const char* crl_path = mysql->options.extension ? mysql->options.extension->ssl_crlpath : NULL; PCCERT_CONTEXT pServerCert= NULL; - CRL_CONTEXT *crl_ctx= NULL; - CERT_CONTEXT *ca_ctx= NULL; + char errmsg[256]; + HCERTSTORE store= NULL; int ret= 0; - if (!ca_file && !crl_file) - return 1; - - if (ca_file && !(ca_ctx = ma_schannel_create_cert_context(pvio, ca_file))) + status = schannel_create_store(ca_file, ca_path, crl_file, crl_path, &store, errmsg, sizeof(errmsg)); + if(status) goto end; - if (crl_file && !(crl_ctx= (CRL_CONTEXT *)ma_schannel_create_crl_context(pvio, mysql->options.extension->ssl_crl))) - goto end; - - if ((sRet= QueryContextAttributes(&sctx->ctxt, SECPKG_ATTR_REMOTE_CERT_CONTEXT, (PVOID)&pServerCert)) != SEC_E_OK) + status = QueryContextAttributesA(&sctx->hCtxt, SECPKG_ATTR_REMOTE_CERT_CONTEXT, (PVOID)&pServerCert); + if (status) { - ma_schannel_set_sec_error(pvio, sRet); + ma_format_win32_error(errmsg, sizeof(errmsg), GetLastError(), + "QueryContextAttributes(SECPKG_ATTR_REMOTE_CERT_CONTEXT) failed."); goto end; } - if (ca_ctx) - { - DWORD flags = CERT_STORE_SIGNATURE_FLAG | CERT_STORE_TIME_VALIDITY_FLAG; - if (!CertVerifySubjectCertificateContext(pServerCert, ca_ctx, &flags)) - { - ma_schannel_set_win_error(pvio, 0); - goto end; - } + status = schannel_verify_server_certificate( + pServerCert, + store, + crl_file != 0 || crl_path != 0, + mysql->host, + verify_server_name, + errmsg, sizeof(errmsg)); - if (flags) - { - if ((flags & CERT_STORE_SIGNATURE_FLAG) != 0) - pvio->set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, "SSL connection error: Certificate signature check failed"); - else if ((flags & CERT_STORE_REVOCATION_FLAG) != 0) - pvio->set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, "SSL connection error: certificate was revoked"); - else if ((flags & CERT_STORE_TIME_VALIDITY_FLAG) != 0) - pvio->set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, "SSL connection error: certificate has expired"); - else - pvio->set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, "SSL connection error: Unknown error during certificate validation"); - goto end; - } - } + if (status) + goto end; - - /* Check certificates in the certificate chain have been revoked. */ - if (crl_ctx) - { - if (!CertVerifyCRLRevocation(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, pServerCert->pCertInfo, 1, &crl_ctx->pCrlInfo)) - { - pvio->set_error(pvio->mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, "SSL connection error: CRL Revocation test failed"); - goto end; - } - } ret= 1; end: - if (crl_ctx) + if (!ret) { - CertFreeCRLContext(crl_ctx); - } - if (ca_ctx) - { - CertFreeCertificateContext(ca_ctx); + pvio->set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, + "SSL connection error: %s", errmsg); } if (pServerCert) - { CertFreeCertificateContext(pServerCert); - } + if(store) + schannel_free_store(store); return ret; } @@ -1159,7 +597,7 @@ ssize_t ma_schannel_write_encrypt(MARIADB_PVIO *pvio, Message.ulVersion = SECBUFFER_VERSION; Message.cBuffers = 4; Message.pBuffers = Buffers; - if ((scRet = EncryptMessage(&sctx->ctxt, 0, &Message, 0))!= SEC_E_OK) + if ((scRet = EncryptMessage(&sctx->hCtxt, 0, &Message, 0))!= SEC_E_OK) return -1; write_size = Buffers[0].cbBuffer + Buffers[1].cbBuffer + Buffers[2].cbBuffer; nbytes = pvio->methods->write(pvio, sctx->IoBuffer, write_size); @@ -1179,7 +617,7 @@ int ma_tls_get_protocol_version(MARIADB_TLS *ctls) sctx= (SC_CTX *)ctls->ssl; - if (QueryContextAttributes(&sctx->ctxt, SECPKG_ATTR_CONNECTION_INFO, &ConnectionInfo) != SEC_E_OK) + if (QueryContextAttributes(&sctx->hCtxt, SECPKG_ATTR_CONNECTION_INFO, &ConnectionInfo) != SEC_E_OK) return -1; switch(ConnectionInfo.dwProtocol) diff --git a/libmariadb/secure/ma_schannel.h b/libmariadb/secure/ma_schannel.h index 93d347f8..af7fc602 100644 --- a/libmariadb/secure/ma_schannel.h +++ b/libmariadb/secure/ma_schannel.h @@ -52,15 +52,11 @@ struct st_DER { }; struct st_schannel { - HCERTSTORE cert_store; - const CERT_CONTEXT *client_cert_ctx; - struct st_DER *der_key; CredHandle CredHdl; - my_bool FreeCredHdl; PUCHAR IoBuffer; DWORD IoBufferSize; SecPkgContext_StreamSizes Sizes; - CtxtHandle ctxt; + CtxtHandle hCtxt; /* Cached data from the last read/decrypt call.*/ SecBuffer extraBuf; /* encrypted data read from server. */ @@ -73,20 +69,16 @@ typedef struct st_schannel SC_CTX; extern HCERTSTORE ca_CertStore, crl_CertStore; extern my_bool ca_Check, crl_Check; -DWORD ma_schannel_load_certs_and_keys(MARIADB_PVIO* pvio, const char* PemFileName, SC_CTX* ctx); -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, 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); + +my_bool ma_schannel_verify_certs(MARIADB_TLS *ctls, BOOL verify_server_name); ssize_t ma_schannel_write_encrypt(MARIADB_PVIO *pvio, uchar *WriteBuffer, size_t WriteBufferSize); - SECURITY_STATUS ma_schannel_read_decrypt(MARIADB_PVIO *pvio, - PCredHandle phCreds, - CtxtHandle * phContext, +SECURITY_STATUS ma_schannel_read_decrypt(MARIADB_PVIO *pvio, + CtxtHandle* phContext, DWORD *DecryptLength, uchar *ReadBuffer, DWORD ReadBufferSize); diff --git a/libmariadb/secure/schannel.c b/libmariadb/secure/schannel.c index b4661cc8..1258f539 100644 --- a/libmariadb/secure/schannel.c +++ b/libmariadb/secure/schannel.c @@ -18,6 +18,7 @@ *************************************************************************************/ #include "ma_schannel.h" +#include "schannel_certs.h" #pragma comment (lib, "crypt32.lib") #pragma comment (lib, "secur32.lib") @@ -195,16 +196,14 @@ void ma_tls_end() } /* {{{ static int ma_tls_set_client_certs(MARIADB_TLS *ctls) */ -static int ma_tls_set_client_certs(MARIADB_TLS *ctls) +static int ma_tls_set_client_certs(MARIADB_TLS *ctls,const CERT_CONTEXT **cert_ctx) { MYSQL *mysql= ctls->pvio->mysql; char *certfile= mysql->options.ssl_cert, *keyfile= mysql->options.ssl_key; SC_CTX *sctx= (SC_CTX *)ctls->ssl; MARIADB_PVIO *pvio= ctls->pvio; - - sctx->client_cert_ctx= NULL; - sctx->der_key = NULL; + char errmsg[256]; if (!certfile && keyfile) certfile= keyfile; @@ -214,23 +213,10 @@ static int ma_tls_set_client_certs(MARIADB_TLS *ctls) if (!certfile) return 0; - if (certfile && ma_schannel_load_certs_and_keys(pvio, certfile, sctx)) - return 1; - - if (keyfile && ma_schannel_load_certs_and_keys(pvio, keyfile, sctx)) - return 1; - - if (sctx->client_cert_ctx) + *cert_ctx = schannel_create_cert_context(certfile, keyfile, errmsg, sizeof(errmsg)); + if (!*cert_ctx) { - 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"); + pvio->set_error(pvio->mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, "SSL connection error: %s", errmsg); return 1; } @@ -241,9 +227,12 @@ static int ma_tls_set_client_certs(MARIADB_TLS *ctls) /* {{{ void *ma_tls_init(MARIADB_TLS *ctls, MYSQL *mysql) */ void *ma_tls_init(MYSQL *mysql) { - SC_CTX *sctx= NULL; - if ((sctx= (SC_CTX *)LocalAlloc(0, sizeof(SC_CTX)))) - ZeroMemory(sctx, sizeof(SC_CTX)); + SC_CTX *sctx = (SC_CTX *)LocalAlloc(LMEM_ZEROINIT, sizeof(SC_CTX)); + if (sctx) + { + SecInvalidateHandle(&sctx->CredHdl); + SecInvalidateHandle(&sctx->hCtxt); + } return sctx; } /* }}} */ @@ -297,7 +286,7 @@ static size_t set_cipher(char * cipher_str, DWORD protocol, ALG_ID *arr , size_t my_bool ma_tls_connect(MARIADB_TLS *ctls) { MYSQL *mysql; - SCHANNEL_CRED Cred; + SCHANNEL_CRED Cred = {0}; MARIADB_PVIO *pvio; my_bool rc= 1; SC_CTX *sctx; @@ -305,20 +294,20 @@ my_bool ma_tls_connect(MARIADB_TLS *ctls) ALG_ID AlgId[MAX_ALG_ID]; size_t i; DWORD protocol = 0; + int verify_certs; + CERT_CONTEXT* cert_context = NULL; + + if (!ctls) + return 1; - - if (!ctls || !ctls->pvio) - return 1;; - pvio= ctls->pvio; sctx= (SC_CTX *)ctls->ssl; + if (!pvio || !sctx) + return 1; mysql= pvio->mysql; - - if (ma_tls_set_client_certs(ctls)) - goto end; - - ZeroMemory(&Cred, sizeof(SCHANNEL_CRED)); + if (!mysql) + return 1; /* Set cipher */ if (mysql->options.ssl_cipher) @@ -350,11 +339,6 @@ my_bool ma_tls_connect(MARIADB_TLS *ctls) 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; - } if (mysql->options.extension && mysql->options.extension->tls_version) { if (strstr(mysql->options.extension->tls_version, "TLSv1.0")) @@ -367,30 +351,40 @@ my_bool ma_tls_connect(MARIADB_TLS *ctls) if (!Cred.grbitEnabledProtocols) Cred.grbitEnabledProtocols = SP_PROT_TLS1_0_CLIENT | SP_PROT_TLS1_1_CLIENT | SP_PROT_TLS1_2_CLIENT; - if ((sRet= AcquireCredentialsHandleA(NULL, UNISP_NAME_A, SECPKG_CRED_OUTBOUND, - NULL, &Cred, NULL, NULL, &sctx->CredHdl, NULL)) != SEC_E_OK) + + if (ma_tls_set_client_certs(ctls, &cert_context)) + goto end; + + if (cert_context) + { + Cred.cCreds = 1; + Cred.paCred = &cert_context; + } + sRet= AcquireCredentialsHandleA(NULL, UNISP_NAME_A, SECPKG_CRED_OUTBOUND, + NULL, &Cred, NULL, NULL, &sctx->CredHdl, NULL); + if (sRet) { ma_schannel_set_sec_error(pvio, sRet); goto end; } - sctx->FreeCredHdl= 1; - if (ma_schannel_client_handshake(ctls) != SEC_E_OK) goto end; - - if (!ma_schannel_verify_certs(ctls)) - goto end; - - return 0; + + verify_certs = mysql->options.ssl_ca || mysql->options.ssl_capath || + (mysql->client_flag & CLIENT_SSL_VERIFY_SERVER_CERT); + + if (verify_certs) + { + if (!ma_schannel_verify_certs(ctls, (mysql->client_flag & CLIENT_SSL_VERIFY_SERVER_CERT))) + goto end; + } + + rc = 0; end: - if (rc && sctx->IoBufferSize) - LocalFree(sctx->IoBuffer); - sctx->IoBufferSize= 0; - if (sctx->client_cert_ctx) - CertFreeCertificateContext(sctx->client_cert_ctx); - sctx->client_cert_ctx= 0; - return 1; + if (cert_context) + schannel_free_cert_context(cert_context); + return rc; } ssize_t ma_tls_read(MARIADB_TLS *ctls, const uchar* buffer, size_t length) @@ -398,7 +392,7 @@ ssize_t ma_tls_read(MARIADB_TLS *ctls, const uchar* buffer, size_t length) SC_CTX *sctx= (SC_CTX *)ctls->ssl; MARIADB_PVIO *pvio= ctls->pvio; DWORD dlength= 0; - SECURITY_STATUS status = ma_schannel_read_decrypt(pvio, &sctx->CredHdl, &sctx->ctxt, &dlength, (uchar *)buffer, (DWORD)length); + SECURITY_STATUS status = ma_schannel_read_decrypt(pvio, &sctx->hCtxt, &dlength, (uchar *)buffer, (DWORD)length); if (status == SEC_I_CONTEXT_EXPIRED) return 0; /* other side shut down the connection. */ if (status == SEC_I_RENEGOTIATE) @@ -430,12 +424,13 @@ my_bool ma_tls_close(MARIADB_TLS *ctls) if (sctx) { - if (sctx->IoBufferSize) - LocalFree(sctx->IoBuffer); - if (sctx->client_cert_ctx) - CertFreeCertificateContext(sctx->client_cert_ctx); - FreeCredentialHandle(&sctx->CredHdl); - DeleteSecurityContext(&sctx->ctxt); + LocalFree(sctx->IoBuffer); + + if (SecIsValidHandle(&sctx->CredHdl)) + FreeCredentialHandle(&sctx->CredHdl); + + if (SecIsValidHandle(&sctx->hCtxt)) + DeleteSecurityContext(&sctx->hCtxt); } LocalFree(sctx); return 0; @@ -444,87 +439,8 @@ my_bool ma_tls_close(MARIADB_TLS *ctls) int ma_tls_verify_server_cert(MARIADB_TLS *ctls) { - SC_CTX *sctx= (SC_CTX *)ctls->ssl; - MARIADB_PVIO *pvio= ctls->pvio; - int rc= 1; - char *szName= NULL; - char *pszServerName= pvio->mysql->host; - PCCERT_CONTEXT pServerCert= NULL; - - /* check server name */ - if (pszServerName && (ctls->pvio->mysql->client_flag & CLIENT_SSL_VERIFY_SERVER_CERT)) - { - DWORD NameSize= 0; - char *p1; - SECURITY_STATUS sRet; - - if ((sRet= QueryContextAttributes(&sctx->ctxt, SECPKG_ATTR_REMOTE_CERT_CONTEXT, (PVOID)&pServerCert)) != SEC_E_OK) - { - ma_schannel_set_sec_error(pvio, sRet); - goto end; - } - - if (!(NameSize= CertGetNameString(pServerCert, - CERT_NAME_DNS_TYPE, - CERT_NAME_SEARCH_ALL_NAMES_FLAG, - NULL, NULL, 0))) - { - pvio->set_error(ctls->pvio->mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, "SSL connection error: Can't retrieve name of server certificate"); - goto end; - } - - if (!(szName= (char *)LocalAlloc(0, NameSize + 1))) - { - pvio->set_error(ctls->pvio->mysql, CR_OUT_OF_MEMORY, SQLSTATE_UNKNOWN, NULL); - goto end; - } - - if (!CertGetNameString(pServerCert, - CERT_NAME_DNS_TYPE, - CERT_NAME_SEARCH_ALL_NAMES_FLAG, - NULL, szName, NameSize)) - - { - pvio->set_error(ctls->pvio->mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, "SSL connection error: Can't retrieve name of server certificate"); - goto end; - } - - /* szName may contain multiple names: Each name is zero terminated, the last name is - double zero terminated */ - - - p1 = szName; - while (p1 && *p1 != 0) - { - size_t len = strlen(p1); - /* check if given name contains wildcard */ - if (len && *p1 == '*') - { - size_t hostlen = strlen(pszServerName); - if (hostlen < len) - break; - if (!stricmp(pszServerName + hostlen - len + 1, p1 + 1)) - { - rc = 0; - goto end; - } - } - else if (!stricmp(pszServerName, p1)) - { - rc = 0; - goto end; - } - p1 += (len + 1); - } - pvio->set_error(pvio->mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, - "SSL connection error: Name of server certificate didn't match"); - } -end: - if (szName) - LocalFree(szName); - if (pServerCert) - CertFreeCertificateContext(pServerCert); - return rc; + /* Done elsewhere */ + return 0; } static const char *cipher_name(const SecPkgContext_CipherInfo *CipherInfo) @@ -549,7 +465,7 @@ const char *ma_tls_get_cipher(MARIADB_TLS *ctls) return NULL; sctx= (SC_CTX *)ctls->ssl; - sRet= QueryContextAttributesA(&sctx->ctxt, SECPKG_ATTR_CIPHER_INFO, (PVOID)&CipherInfo); + sRet= QueryContextAttributesA(&sctx->hCtxt, SECPKG_ATTR_CIPHER_INFO, (PVOID)&CipherInfo); if (sRet != SEC_E_OK) return NULL; @@ -561,7 +477,7 @@ unsigned int ma_tls_get_finger_print(MARIADB_TLS *ctls, char *fp, unsigned int l { SC_CTX *sctx= (SC_CTX *)ctls->ssl; PCCERT_CONTEXT pRemoteCertContext = NULL; - if (QueryContextAttributes(&sctx->ctxt, SECPKG_ATTR_REMOTE_CERT_CONTEXT, (PVOID)&pRemoteCertContext) != SEC_E_OK) + if (QueryContextAttributes(&sctx->hCtxt, SECPKG_ATTR_REMOTE_CERT_CONTEXT, (PVOID)&pRemoteCertContext) != SEC_E_OK) return 0; CertGetCertificateContextProperty(pRemoteCertContext, CERT_HASH_PROP_ID, fp, (DWORD *)&len); CertFreeCertificateContext(pRemoteCertContext); diff --git a/libmariadb/secure/schannel_certs.c b/libmariadb/secure/schannel_certs.c new file mode 100644 index 00000000..79d4bfeb --- /dev/null +++ b/libmariadb/secure/schannel_certs.c @@ -0,0 +1,892 @@ +/************************************************************************************ + Copyright (C) 2019 MariaDB + + 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 + + *************************************************************************************/ + + /* + This module contain X509 certificate handling on Windows. + PEM parsing, loading client certificate and key, server certificate validation + */ + + /* + CERT_CHAIN_ENGINE_CONFIG has additional members in Windows 8.1 + To allow client to be work on pre-8.1 Windows, compile + with corresponding _WIN32_WINNT + */ +#ifdef _WIN32_WINNT +#undef _WIN32_WINNT +#define _WIN32_WINNT 0x0601 +#endif + +#include "schannel_certs.h" +#include +#include +#include +#include +#include +#include +#include +#include "win32_errmsg.h" + + /* + Return GetLastError(), or, if this unexpectedly gives success, + return ERROR_INTERNAL_ERROR. + + Background - in several cases in this module we return GetLastError() + after an Windows function fails. However, we do not want the function to + return success, even if GetLastError() was suddenly 0. + */ +static DWORD get_last_error() +{ + DWORD ret = GetLastError(); + if (ret) + return ret; + + // We generally expect last error to be set API fails. + // thus the debug assertion- + assert(0); + return ERROR_INTERNAL_ERROR; +} +/* + Load file into memory. Add null terminator at the end, so it will be a valid C string. +*/ +static char* pem_file_to_string(const char* file, char* errmsg, size_t errmsg_len) +{ + LARGE_INTEGER file_size; + size_t file_bufsize = 0; + size_t total_bytes_read = 0; + char* file_buffer = NULL; + + HANDLE file_handle = CreateFile(file, GENERIC_READ, FILE_SHARE_READ, NULL, + OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + if (file_handle == INVALID_HANDLE_VALUE) + { + ma_format_win32_error(errmsg, errmsg_len, GetLastError(), "failed to open file '%s'", file); + goto cleanup; + } + + if (!GetFileSizeEx(file_handle, &file_size)) + { + ma_format_win32_error(errmsg, errmsg_len, get_last_error(), "GetFileSizeEx failed on '%s'", file); + goto cleanup; + } + + if (file_size.QuadPart > ULONG_MAX - 1) + { + snprintf(errmsg, errmsg_len, "file '%s' too large", file); + goto cleanup; + } + + file_bufsize = (size_t)file_size.QuadPart; + file_buffer = (char*)malloc(file_bufsize + 1); + if (!file_buffer) + { + snprintf(errmsg, errmsg_len, "malloc(%zu) failed, out of memory", file_bufsize + 1); + goto cleanup; + } + + while (total_bytes_read < file_bufsize) + { + DWORD bytes_to_read = (DWORD)(file_bufsize - total_bytes_read); + DWORD bytes_read = 0; + + if (!ReadFile(file_handle, file_buffer + total_bytes_read, + bytes_to_read, &bytes_read, NULL)) + { + ma_format_win32_error(errmsg, errmsg_len, GetLastError(), + "ReadFile() failed to read file '%s'", file); + goto cleanup; + } + if (bytes_read == 0) + { + /* Premature EOF -- adjust the bufsize to the new value */ + file_bufsize = total_bytes_read; + } + else + { + total_bytes_read += bytes_read; + } + } + + /* Null terminate the buffer */ + file_buffer[file_bufsize] = '\0'; +cleanup: + if (file_handle != INVALID_HANDLE_VALUE) + CloseHandle(file_handle); + return file_buffer; +} + + +// Structure for parsing BEGIN/END sections inside pem. +typedef struct _pem_type_desc +{ + const char* begin_tag; + size_t begin_tag_len; + const char* end_tag; + size_t end_tag_len; +} pem_type_desc; + +#define BEGIN_TAG(x) "-----BEGIN " x "-----" +#define END_TAG(x) "\n-----END " x "-----" +#define PEM_SECTION(tag) {BEGIN_TAG(tag), sizeof(BEGIN_TAG(tag))-1, END_TAG(tag), sizeof(END_TAG(tag))-1} + +typedef enum { + PEM_TYPE_CERTIFICATE = 0, + PEM_TYPE_X509_CRL, + PEM_TYPE_RSA_PRIVATE_KEY, + PEM_TYPE_PRIVATE_KEY +} PEM_TYPE; + +static const pem_type_desc pem_sections[] = { + PEM_SECTION("CERTIFICATE"), + PEM_SECTION("X509 CRL"), + PEM_SECTION("RSA PRIVATE KEY"), + PEM_SECTION("PRIVATE KEY") +}; + +/* + Locate a substring in pem for given type, + e.g section between BEGIN CERTIFICATE and END CERTIFICATE + in PEMs base64 format, with header and footer. + + output parameters 'begin' and 'end' are set upon return. + it is possible that functions returns 'begin' != NULL but + 'end' = NULL. This is generally a format error, meaning that + the end tag was not found +*/ +void pem_locate(char* pem_str, + PEM_TYPE type, + char** begin, + char** end) +{ + *begin = NULL; + *end = NULL; + char c; + + const pem_type_desc* desc = &pem_sections[type]; + *begin = strstr(pem_str, desc->begin_tag); + if (!(*begin)) + return; + + // We expect newline after the + // begin tag, LF or CRLF + c = (*begin)[desc->begin_tag_len]; + + if (c != '\r' && c != '\n') + { + *begin = NULL; + return; + } + + *end = strstr(*begin + desc->begin_tag_len + 1, desc->end_tag); + if (!*end) + return; // error, end marker not found + + (*end) += desc->end_tag_len; + return; +} + + +/* + Add certificates, or CRLs from a PEM file to Wincrypt store +*/ +static SECURITY_STATUS add_certs_to_store( + HCERTSTORE trust_store, + const char* file, + PEM_TYPE type, + char* errmsg, + size_t errmsg_len) +{ + char* file_buffer = NULL; + char* cur = NULL; + SECURITY_STATUS status = SEC_E_INTERNAL_ERROR; + char* begin; + char* end; + + file_buffer = pem_file_to_string(file, errmsg, errmsg_len); + if (!file_buffer) + goto cleanup; + + for (cur = file_buffer; ; cur = end) + { + pem_locate(cur, type, &begin, &end); + + if (!begin) + break; + + if (!end) + { + snprintf(errmsg, errmsg_len, "Invalid PEM file '%s'," + "missing end marker corresponding to begin marker '%s' at offset %zu", + file, pem_sections[type].begin_tag, (size_t)(begin - file_buffer)); + goto cleanup; + } + CERT_BLOB cert_blob; + void* context = NULL; + int add_cert_result = FALSE; + DWORD actual_content_type = 0; + + cert_blob.pbData = (BYTE*)begin; + cert_blob.cbData = (DWORD)(end - begin); + if (!CryptQueryObject( + CERT_QUERY_OBJECT_BLOB, &cert_blob, + CERT_QUERY_CONTENT_FLAG_CERT | CERT_QUERY_CONTENT_FLAG_CRL, + CERT_QUERY_FORMAT_FLAG_ALL, 0, NULL, &actual_content_type, + NULL, NULL, NULL, (const void**)&context)) + { + ma_format_win32_error(errmsg, errmsg_len, GetLastError(), + "failed to extract certificate from PEM file '%s'", + file); + goto cleanup; + } + + if (!context) + { + ma_format_win32_error(errmsg, errmsg_len, 0, + "unexpected result from CryptQueryObject(),cert_context is NULL" + " after successful completion, file '%s'", + file); + goto cleanup; + } + + if (actual_content_type == CERT_QUERY_CONTENT_CERT) + { + CERT_CONTEXT* cert_context = (CERT_CONTEXT*)context; + BOOL ok = CertAddCertificateContextToStore( + trust_store, cert_context, + CERT_STORE_ADD_ALWAYS, NULL); + if (!ok) + status = get_last_error(); + CertFreeCertificateContext(cert_context); + if (!ok) + { + status = get_last_error(); + ma_format_win32_error(errmsg, errmsg_len, get_last_error(), + "CertAddCertificateContextToStore failed"); + goto cleanup; + } + } + else if (actual_content_type == CERT_QUERY_CONTENT_CRL) + { + CRL_CONTEXT* crl_context = (CRL_CONTEXT*)context; + BOOL ok = CertAddCRLContextToStore( + trust_store, crl_context, + CERT_STORE_ADD_ALWAYS, NULL); + if (!ok) + status = get_last_error(); + CertFreeCRLContext(crl_context); + if (!ok) + { + ma_format_win32_error(errmsg, errmsg_len, status, "CertAddCRLContextToStore() failed"); + goto cleanup; + } + } + } + status = SEC_E_OK; + +cleanup: + free(file_buffer); + return status; +} + +/* +Add a directory to store, i.e try to load all files. +(extract certificates and add them to store) + +@return 0 on success, error only if directory is invalid. +*/ +SECURITY_STATUS add_dir_to_store(HCERTSTORE trust_store, const char* dir, + PEM_TYPE type, char* errmsg, size_t errmsg_len) +{ + WIN32_FIND_DATAA ffd; + char path[MAX_PATH]; + char pattern[MAX_PATH]; + DWORD dwAttr; + HANDLE hFind; + SECURITY_STATUS status = SEC_E_INTERNAL_ERROR; + + if ((dwAttr = GetFileAttributes(dir)) == INVALID_FILE_ATTRIBUTES) + { + ma_format_win32_error(errmsg, errmsg_len, 0, "invalid directory '%s'", dir); + return status; + } + if (!(dwAttr & FILE_ATTRIBUTE_DIRECTORY)) + { + ma_format_win32_error(errmsg, errmsg_len, 0, "'%s' is not a directory", dir); + return status; + } + snprintf(pattern, sizeof(pattern), "%s\\*", dir); + hFind = FindFirstFile(pattern, &ffd); + if (hFind == INVALID_HANDLE_VALUE) + { + ma_format_win32_error(errmsg, errmsg_len, GetLastError(), "FindFirstFile(%s) failed", pattern); + return status; + } + do + { + if (ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) + continue; + snprintf(path, sizeof(path), "%s\\%s", dir, ffd.cFileName); + + // ignore error from add_certs_to_store(), not all file + // maybe PEM. + add_certs_to_store(trust_store, path, type, errmsg, + errmsg_len); + } while (FindNextFile(hFind, &ffd) != 0); + FindClose(hFind); + return status; +} + +/* Count certificates in store. */ +static int count_certificates(HCERTSTORE store) +{ + int num_certs = 0; + PCCERT_CONTEXT c = NULL; + + while (c = CertEnumCertificatesInStore(store, c)) + num_certs++; + + return num_certs; +} + +/** + Creates certificate store with user defined CA chain and/or CRL. + Loads PEM certificate from files or directories. + + If only CRLFile/CRLPath is defined, the "system" store is duplicated, + and new CRLs are added to it. + + If CAFile/CAPAth is defined, then new empty store is created, and CAs + (and CRLs, if defined), are added to it. + + The function throws an error, if none of the files in CAFile/CAPath have a valid certificate. + It is also an error if CRLFile does not exist. +*/ +SECURITY_STATUS schannel_create_store( + const char* CAFile, + const char* CAPath, + const char* CRLFile, + const char* CRLPath, + HCERTSTORE* out_store, + char* errmsg, + size_t errmsg_len) +{ + + HCERTSTORE store = NULL; + HCERTSTORE system_store = NULL; + int status = SEC_E_INTERNAL_ERROR; + + *out_store = NULL; + if (!CAFile && !CAPath && !CRLFile && !CRLPath) + { + /* Nothing to do, caller will use default store*/ + *out_store = NULL; + return SEC_E_OK; + } + if (CAFile || CAPath) + { + /* Open the certificate store */ + store = CertOpenStore(CERT_STORE_PROV_MEMORY, 0, (HCRYPTPROV)NULL, + CERT_STORE_CREATE_NEW_FLAG, NULL); + if (!store) + { + status = get_last_error(); + ma_format_win32_error(errmsg, errmsg_len, status, "failed to create certificate store"); + goto cleanup; + } + } + else if (CRLFile || CRLPath) + { + /* Only CRL was provided, copy system store, add revocation list to + * it. */ + system_store = + CertOpenStore(CERT_STORE_PROV_SYSTEM, 0, (HCRYPTPROV_LEGACY)NULL, + CERT_SYSTEM_STORE_CURRENT_USER, L"MY"); + if (!system_store) + { + status = get_last_error(); + ma_format_win32_error(errmsg, errmsg_len, status, + "failed to open system certificate store"); + goto cleanup; + } + + store = CertDuplicateStore(system_store); + if (!store) + { + status = get_last_error(); + ma_format_win32_error(errmsg, errmsg_len, status, + "failed to duplicate system certificate store"); + goto cleanup; + } + } + + if (CAFile) + { + status = add_certs_to_store(store, CAFile, + PEM_TYPE_CERTIFICATE, errmsg, errmsg_len); + if (status) + goto cleanup; + } + if (CAPath) + { + status = add_dir_to_store(store, CAPath, + PEM_TYPE_CERTIFICATE, errmsg, errmsg_len); + if (status) + goto cleanup; + } + + if ((CAFile || CAPath) && store && !count_certificates(store)) + { + snprintf(errmsg, errmsg_len, + "no valid certificates were found, CAFile='%s', CAPath='%s'", + CAFile ? CAFile : "", CAPath ? CAPath : ""); + goto cleanup; + } + + if (CRLFile) + { + status = add_certs_to_store(store, CRLFile, PEM_TYPE_X509_CRL, + errmsg, errmsg_len); + } + if (CRLPath) + { + status = add_dir_to_store(store, CRLPath, PEM_TYPE_X509_CRL, + errmsg, errmsg_len); + } + status = SEC_E_OK; + +cleanup: + if (system_store) + CertCloseStore(system_store, 0); + if (status && store) + { + CertCloseStore(store, 0); + store = NULL; + } + *out_store = store; + return status; +} + +/* + The main verification logic. + Taken almost completely from Windows 2003 Platform SDK 2003 + (Samples\Security\SSPI\SSL\WebClient.c) + + The only difference here is is usage of custom store + and chain engine. +*/ +static SECURITY_STATUS VerifyServerCertificate( + PCCERT_CONTEXT pServerCert, + HCERTSTORE hStore, + LPWSTR pwszServerName, + DWORD dwRevocationCheckFlags, + DWORD dwVerifyFlags, + LPSTR pErrMessage, + size_t cbErrMessage) +{ + SSL_EXTRA_CERT_CHAIN_POLICY_PARA polExtra; + CERT_CHAIN_POLICY_PARA PolicyPara; + CERT_CHAIN_POLICY_STATUS PolicyStatus; + CERT_CHAIN_PARA ChainPara; + HCERTCHAINENGINE hChainEngine = NULL; + PCCERT_CHAIN_CONTEXT pChainContext = NULL; + LPSTR rgszUsages[] = { szOID_PKIX_KP_SERVER_AUTH, + szOID_SERVER_GATED_CRYPTO, + szOID_SGC_NETSCAPE }; + DWORD cUsages = sizeof(rgszUsages) / sizeof(LPSTR); + SECURITY_STATUS Status = SEC_E_INTERNAL_ERROR; + + if (pServerCert == NULL) + { + snprintf(pErrMessage, cbErrMessage, "Invalid parameter pServerCert passed to VerifyServerCertificate"); + Status = SEC_E_WRONG_PRINCIPAL; + goto cleanup; + } + + ZeroMemory(&ChainPara, sizeof(ChainPara)); + ChainPara.cbSize = sizeof(ChainPara); + ChainPara.RequestedUsage.dwType = USAGE_MATCH_TYPE_OR; + ChainPara.RequestedUsage.Usage.cUsageIdentifier = cUsages; + ChainPara.RequestedUsage.Usage.rgpszUsageIdentifier = rgszUsages; + + if (hStore) + { + CERT_CHAIN_ENGINE_CONFIG EngineConfig = { 0 }; + EngineConfig.cbSize = sizeof(EngineConfig); + EngineConfig.hExclusiveRoot = hStore; + if (!CertCreateCertificateChainEngine(&EngineConfig, &hChainEngine)) + { + Status = get_last_error(); + ma_format_win32_error(pErrMessage, cbErrMessage, Status, + "CertCreateCertificateChainEngine failed"); + goto cleanup; + } + } + + if (!CertGetCertificateChain( + hChainEngine, + pServerCert, + NULL, + pServerCert->hCertStore, + &ChainPara, + dwRevocationCheckFlags, + NULL, + &pChainContext)) + { + Status = get_last_error(); + ma_format_win32_error(pErrMessage, cbErrMessage, Status, "CertGetCertificateChain failed"); + goto cleanup; + } + + // Validate certificate chain. + ZeroMemory(&polExtra, sizeof(SSL_EXTRA_CERT_CHAIN_POLICY_PARA)); + polExtra.cbStruct = sizeof(SSL_EXTRA_CERT_CHAIN_POLICY_PARA); + polExtra.dwAuthType = AUTHTYPE_SERVER; + polExtra.fdwChecks = dwVerifyFlags; + polExtra.pwszServerName = pwszServerName; + + memset(&PolicyPara, 0, sizeof(PolicyPara)); + PolicyPara.cbSize = sizeof(PolicyPara); + PolicyPara.pvExtraPolicyPara = &polExtra; + + memset(&PolicyStatus, 0, sizeof(PolicyStatus)); + PolicyStatus.cbSize = sizeof(PolicyStatus); + + if (!CertVerifyCertificateChainPolicy( + CERT_CHAIN_POLICY_SSL, + pChainContext, + &PolicyPara, + &PolicyStatus)) + { + Status = get_last_error(); + ma_format_win32_error(pErrMessage, cbErrMessage, Status, "CertVerifyCertificateChainPolicy failed"); + goto cleanup; + } + + if (PolicyStatus.dwError) + { + Status = PolicyStatus.dwError; + ma_format_win32_error(pErrMessage, cbErrMessage, Status, "Server certificate validation failed"); + goto cleanup; + } + Status = SEC_E_OK; + +cleanup: + if (hChainEngine) + { + CertFreeCertificateChainEngine(hChainEngine); + } + if (pChainContext) + { + CertFreeCertificateChain(pChainContext); + } + return Status; +} + + +void schannel_free_store(HCERTSTORE store) +{ + if (store) + CertCloseStore(store, 0); +} + + +/* +Verify server certificate against a wincrypt store +@return 0 - success, otherwise error occured. +*/ +SECURITY_STATUS schannel_verify_server_certificate( + const CERT_CONTEXT* cert, + HCERTSTORE store, + BOOL check_revocation, + const char* server_name, + BOOL check_server_name, + char* errmsg, + size_t errmsg_len) +{ + SECURITY_STATUS status = SEC_E_INTERNAL_ERROR; + wchar_t* wserver_name = NULL; + DWORD dwVerifyFlags; + DWORD dwRevocationFlags; + + if (check_server_name) + { + int cchServerName = (int)strlen(server_name) + 1; + wserver_name = (wchar_t*)malloc(sizeof(wchar_t) * cchServerName); + if (!wserver_name) + { + ma_format_win32_error(errmsg, errmsg_len, ERROR_OUTOFMEMORY, "malloc() failed"); + goto cleanup; + } + if (MultiByteToWideChar(CP_UTF8, 0, server_name, cchServerName, wserver_name, cchServerName) < 0) + { + ma_format_win32_error(errmsg, errmsg_len, GetLastError(), "MultiByteToWideChar() failed"); + goto cleanup; + } + } + + dwVerifyFlags = 0; + dwRevocationFlags = 0; + if (check_revocation) + dwRevocationFlags |= CERT_CHAIN_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT | CERT_CHAIN_REVOCATION_CHECK_CACHE_ONLY; + if (!check_server_name) + dwVerifyFlags |= SECURITY_FLAG_IGNORE_CERT_CN_INVALID; + + status = VerifyServerCertificate(cert, store, wserver_name ? wserver_name : L"SERVER_NAME", + dwRevocationFlags, dwVerifyFlags, errmsg, errmsg_len); + +cleanup: + free(wserver_name); + return status; +} + + +/* Attach private key (in PEM format) to client certificate */ +static SECURITY_STATUS load_private_key(CERT_CONTEXT* cert, char* private_key_str, size_t len, char* errmsg, size_t errmsg_len) +{ + DWORD derlen = (DWORD)len; + BYTE* derbuf = NULL; + DWORD keyblob_len = 0; + BYTE* keyblob = NULL; + HCRYPTPROV hProv = 0; + HCRYPTKEY hKey = 0; + CERT_KEY_CONTEXT cert_key_context = { 0 }; + PCRYPT_PRIVATE_KEY_INFO pki = NULL; + DWORD pki_len = 0; + SECURITY_STATUS status = SEC_E_INTERNAL_ERROR; + + derbuf = LocalAlloc(0, derlen); + if (!derbuf) + { + status = get_last_error(); + ma_format_win32_error(errmsg, errmsg_len, status, "Memory allocation failed"); + goto cleanup; + } + + if (!CryptStringToBinaryA(private_key_str, (DWORD)len, CRYPT_STRING_BASE64HEADER, derbuf, &derlen, NULL, NULL)) + { + status = get_last_error(); + ma_format_win32_error(errmsg, errmsg_len, status, "Failed to convert BASE64 private key"); + goto cleanup; + } + + /* + To accomodate for both "BEGIN PRIVATE KEY" vs "BEGIN RSA PRIVATE KEY" + sections in PEM, we try to decode with PKCS_PRIVATE_KEY_INFO first, + and, if it fails, with PKCS_RSA_PRIVATE_KEY flag. + */ + if (CryptDecodeObjectEx( + X509_ASN_ENCODING, + PKCS_PRIVATE_KEY_INFO, + derbuf, derlen, + CRYPT_DECODE_ALLOC_FLAG, + NULL, &pki, &pki_len)) + { + // convert private key info to RSA private key blob + if (!CryptDecodeObjectEx( + X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, + PKCS_RSA_PRIVATE_KEY, + pki->PrivateKey.pbData, + pki->PrivateKey.cbData, + CRYPT_DECODE_ALLOC_FLAG, + NULL, &keyblob, &keyblob_len)) + { + status = get_last_error(); + ma_format_win32_error(errmsg, errmsg_len, status, + "Failed to parse private key"); + goto cleanup; + } + } + else if (!CryptDecodeObjectEx( + X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, + PKCS_RSA_PRIVATE_KEY, + derbuf, derlen, + CRYPT_DECODE_ALLOC_FLAG, NULL, + &keyblob, &keyblob_len)) + { + status = get_last_error(); + ma_format_win32_error(errmsg, errmsg_len, status, + "Failed to parse private key"); + goto cleanup; + } + + if (!CryptAcquireContext(&hProv, NULL, MS_ENHANCED_PROV, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)) + { + status = get_last_error(); + ma_format_win32_error(errmsg, errmsg_len, status, "CryptAcquireContext failed"); + goto cleanup; + } + + if (!CryptImportKey(hProv, keyblob, keyblob_len, 0, 0, (HCRYPTKEY*)&hKey)) + { + status = get_last_error(); + ma_format_win32_error(errmsg, errmsg_len, status, "CryptImportKey failed"); + goto cleanup; + } + cert_key_context.hCryptProv = hProv; + cert_key_context.dwKeySpec = AT_KEYEXCHANGE; + cert_key_context.cbSize = sizeof(cert_key_context); + + /* assign private key to certificate context */ + if (!CertSetCertificateContextProperty(cert, CERT_KEY_CONTEXT_PROP_ID, 0, &cert_key_context)) + { + status = get_last_error(); + ma_format_win32_error(errmsg, errmsg_len, get_last_error(), "Can't assign private key to certificate context"); + } + status = SEC_E_OK; + +cleanup: + LocalFree(derbuf); + LocalFree(keyblob); + LocalFree(pki); + if (hKey) + CryptDestroyKey(hKey); + if (status) + { + if (hProv) + CryptReleaseContext(hProv, 0); + } + return status; +} + +/* + Given PEM strings for certificate and private key, + create a client certificate* +*/ +static CERT_CONTEXT* create_client_certificate_mem( + char* cert_file_content, + char* key_file_content, + char* errmsg, + size_t errmsg_len) +{ + CERT_CONTEXT* ctx = NULL; + char* begin; + char* end; + CERT_BLOB cert_blob; + DWORD actual_content_type = 0; + int ok = 0; + + /* Parse certificate */ + pem_locate(cert_file_content, PEM_TYPE_CERTIFICATE, + &begin, &end); + + if (!begin || !end) + { + snprintf(errmsg, errmsg_len, "Client certificate not found in PEM file"); + goto cleanup; + } + + cert_blob.pbData = (BYTE*)begin; + cert_blob.cbData = (DWORD)(end - begin); + if (!CryptQueryObject( + CERT_QUERY_OBJECT_BLOB, &cert_blob, + CERT_QUERY_CONTENT_FLAG_CERT, + CERT_QUERY_FORMAT_FLAG_ALL, 0, NULL, &actual_content_type, + NULL, NULL, NULL, (const void**)&ctx)) + { + ma_format_win32_error(errmsg, errmsg_len, GetLastError(), + "Can't parse client certficate"); + goto cleanup; + } + + /* Parse key */ + PEM_TYPE types[] = { PEM_TYPE_RSA_PRIVATE_KEY, PEM_TYPE_PRIVATE_KEY }; + for (int i = 0; i < sizeof(types) / sizeof(types[0]); i++) + { + pem_locate(key_file_content, types[i], &begin, &end); + if (begin && end) + { + /* Assign key to certificate.*/ + ok = !load_private_key(ctx, begin, (end - begin), errmsg, errmsg_len); + goto cleanup; + } + } + + if (!begin || !end) + snprintf(errmsg, errmsg_len, "Client private key not found in PEM"); + +cleanup: + if (!ok) + { + if (ctx) + { + CertFreeCertificateContext(ctx); + ctx = NULL; + } + } + return ctx; +} + + +/* Given cert and key, as PEM file names, create a client certificate */ +CERT_CONTEXT* schannel_create_cert_context(char* cert_file, char* key_file, char* errmsg, size_t errmsg_len) +{ + CERT_CONTEXT* ctx = NULL; + char* key_file_content = NULL; + char* cert_file_content = NULL; + + cert_file_content = pem_file_to_string(cert_file, errmsg, errmsg_len); + + if (!cert_file_content) + goto cleanup; + + if (cert_file == key_file) + { + key_file_content = cert_file_content; + } + else + { + key_file_content = pem_file_to_string(key_file, errmsg, errmsg_len); + if (!key_file_content) + goto cleanup; + } + + ctx = create_client_certificate_mem(cert_file_content, key_file_content, errmsg, errmsg_len); + +cleanup: + free(cert_file_content); + if (cert_file != key_file) + free(key_file_content); + + return ctx; +} + +/* + Free certificate, and all resources, created by schannel_create_cert_context() +*/ +void schannel_free_cert_context(const CERT_CONTEXT* cert) +{ + /* release provider handle which was acquires in load_private_key() */ + CERT_KEY_CONTEXT cert_key_context = { 0 }; + cert_key_context.cbSize = sizeof(cert_key_context); + DWORD cbData = sizeof(CERT_KEY_CONTEXT); + + if (CertGetCertificateContextProperty(cert, CERT_KEY_CONTEXT_PROP_ID, &cert_key_context, &cbData)) + { + CryptReleaseContext(cert_key_context.hCryptProv, 0); + } + else + { + /* + At this point, there are serious doubts the context was created by + channel_create_cert_context(). + */ + assert(0); + } + CertFreeCertificateContext(cert); +} diff --git a/libmariadb/secure/schannel_certs.h b/libmariadb/secure/schannel_certs.h new file mode 100644 index 00000000..54170836 --- /dev/null +++ b/libmariadb/secure/schannel_certs.h @@ -0,0 +1,53 @@ +/************************************************************************************ + Copyright (C) 2019 MariaDB Corporation 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 + + *************************************************************************************/ + +#pragma once +#include +#include + +extern SECURITY_STATUS schannel_create_store( + const char* CAFile, + const char* CAPath, + const char* CRLFile, + const char* CRLPath, + HCERTSTORE* store, + char* errmsg, + size_t errmsg_len +); + +extern SECURITY_STATUS schannel_verify_server_certificate( + const CERT_CONTEXT* cert, + HCERTSTORE store, + BOOL īcheck_revocation, + const char* server_name, + BOOL check_server_name, + char* errmsg, + size_t errmsg_len); + +extern void schannel_free_store(HCERTSTORE store); + +extern CERT_CONTEXT* schannel_create_cert_context( + char* cert_file, + char* key_file, + char* errmsg, + size_t errmsg_len); + +extern void schannel_free_cert_context(const CERT_CONTEXT* cert); + diff --git a/libmariadb/win32_errmsg.c b/libmariadb/win32_errmsg.c new file mode 100644 index 00000000..530b3114 --- /dev/null +++ b/libmariadb/win32_errmsg.c @@ -0,0 +1,131 @@ +/************************************************************************************ + Copyright (C) 2019 MariaDB + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not see + or write to the Free Software Foundation, Inc., + 51 Franklin St., Fifth Floor, Boston, MA 02110, USA + + *************************************************************************************/ + +#include +#include +#include + +/* + Format Windows error, with optional text. + + For "known" errors we also output their symbolic error constant, e.g + CERT_E_CN_NO_MATCH in addition to numeric value. + + We also try to output English text for all error messages, as not to mess up + with encodings. +*/ +void ma_format_win32_error(char* buf, size_t buflen, DWORD code, _Printf_format_string_ const char* fmt, ...) +{ + char* cur = buf; + char* end = cur + buflen; + *cur = 0; + if (fmt) + { + va_list vargs; + va_start(vargs, fmt); + cur += vsnprintf(cur, end - cur, fmt, vargs); + va_end(vargs); + } + + if (code == 0) + return; + + static struct map_entry + { + DWORD code; + const char* sym; + const char* msg; + } + map[] = + { +#define ENTRY(x, y) {x,#x, y} + ENTRY(SEC_E_WRONG_PRINCIPAL, "The target principal name is incorrect"), + ENTRY(CERT_E_CN_NO_MATCH,"The certificate's CN name does not match the passed value"), + ENTRY(SEC_E_UNTRUSTED_ROOT,"The certificate chain was issued by an authority that is not trusted"), + ENTRY(TRUST_E_CERT_SIGNATURE,"The signature of the certificate cannot be verified"), + ENTRY(SEC_E_CERT_EXPIRED,"The received certificate has expired"), + ENTRY(CERT_E_EXPIRED,"A required certificate is not within its validity period when verifying against the current system clock or the timestamp in the signed file"), + ENTRY(CRYPT_E_NO_REVOCATION_CHECK, "The revocation function was unable to check revocation for the certificate"), + ENTRY(CRYPT_E_REVOCATION_OFFLINE,"The revocation function was unable to check revocation because the revocation server was offline"), + ENTRY(CRYPT_E_REVOKED,"The certificate is revoked"), + ENTRY(SEC_E_CERT_UNKNOWN,"An unknown error occurred while processing the certificate"), + ENTRY(CERT_E_ROLE," A certificate that can only be used as an end-entity is being used as a CA or vice versa"), + ENTRY(CERT_E_WRONG_USAGE,"The certificate is not valid for the requested usage"), + ENTRY(SEC_E_ILLEGAL_MESSAGE, "The message received was unexpected or badly formatted"), + ENTRY(CERT_E_VALIDITYPERIODNESTING,"The validity periods of the certification chain do not nest correctly"), + ENTRY(CERT_E_PATHLENCONST,"A path length constraint in the certification chain has been violated"), + ENTRY(CERT_E_CRITICAL,"A certificate contains an unknown extension that is marked 'critical'"), + ENTRY(CERT_E_PURPOSE,"A certificate being used for a purpose other than the ones specified by its CA"), + ENTRY(CERT_E_ISSUERCHAINING,"A parent of a given certificate in fact did not issue that child certificate"), + ENTRY(CERT_E_MALFORMED, "A certificate is missing or has an empty value for an important field, such as a subject or issuer name"), + ENTRY(CERT_E_CHAINING,"A certificate chain could not be built to a trusted root authority"), + ENTRY(TRUST_E_FAIL," Generic trust failure"), + ENTRY(CERT_E_UNTRUSTEDTESTROOT,"The certification path terminates with the test root which is not trusted with the current policy settings"), + ENTRY(CERT_E_UNTRUSTEDROOT,"A certificate chain processed, but terminated in a root certificate which is not trusted by the trust provider"), + ENTRY(CERT_E_REVOCATION_FAILURE,"The revocation process could not continue - the certificate(s) could not be checked"), + ENTRY(SEC_E_ILLEGAL_MESSAGE, "The message received was unexpected or badly formatted"), + ENTRY(SEC_E_UNTRUSTED_ROOT, "Untrusted root certificate"), + ENTRY(SEC_E_BUFFER_TOO_SMALL, "Buffer too small"), + ENTRY(SEC_E_CRYPTO_SYSTEM_INVALID, "Cipher is not supported"), + ENTRY(SEC_E_INSUFFICIENT_MEMORY, "Out of memory"), + ENTRY(SEC_E_OUT_OF_SEQUENCE, "Invalid message sequence"), + ENTRY(SEC_E_DECRYPT_FAILURE, "The specified data could not be decrypted"), + ENTRY(SEC_I_INCOMPLETE_CREDENTIALS, "Incomplete credentials"), + ENTRY(SEC_E_ENCRYPT_FAILURE, "The specified data could not be encrypted"), + ENTRY(SEC_I_CONTEXT_EXPIRED, "The context has expired and can no longer be used"), + ENTRY(SEC_E_ALGORITHM_MISMATCH, "no cipher match"), + ENTRY(SEC_E_NO_CREDENTIALS, "no credentials"), + ENTRY(SEC_E_INVALID_TOKEN, "The token supplied to function is invalid") + }; + + struct map_entry* entry = NULL; + strncpy(cur,". ",end-cur); + cur += 2; + + for (size_t i = 0; i < sizeof(map) / sizeof(map[0]); i++) + { + if (code == map[i].code) + { + entry = &map[i]; + break; + } + } + if (cur > end - 20) + return; + if (entry) + { + cur += snprintf(cur, end - cur, "%s. Error 0x%08lX(%s)", entry->msg, code, entry->sym); + } + else + { + cur += FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, code, MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), + cur, (DWORD)(end - cur), NULL); + while (cur > buf && (*cur == '\0' || *cur == '\n' || *cur == '\r' || *cur == '.')) + cur--; + if (*cur) + { + cur++; + *cur = 0; + } + cur += snprintf(cur, end - cur, ". Error %lu/0x%08lX", code, code); + } +} + diff --git a/libmariadb/win32_errmsg.h b/libmariadb/win32_errmsg.h new file mode 100644 index 00000000..865d9928 --- /dev/null +++ b/libmariadb/win32_errmsg.h @@ -0,0 +1,2 @@ +#include +void ma_format_win32_error(char* buf, size_t buflen, DWORD code, _Printf_format_string_ const char* fmt, ...);