1
0
mirror of https://github.com/mariadb-corporation/mariadb-connector-c.git synced 2025-08-08 14:02:17 +03:00

CONC-447 ERROR 2026 (HY000): SSL connection error: Certificate signature check failed

Implement proper verification for server certificate chain,
with refactoring of the certificate stuff.

If custom CA and CRL certs are given, load them into in-memory store, and
use CertVerifyCertificateChainPolicy() to verify the certificate chain.

There are minor errors fixed, such as
- now there is a support for private keys encoded as BEGIN/END PRIVATE KEY
in PEM, instead of only BEGIN/END RSA PRIVATE KEY
- memory leak around CryptAcquireContext() is fixed i.e when client loads
private key, it previously did never released it, not even when connection
ended.

The handling of certificates moved into schannel_certs.c from various places
This commit is contained in:
Vladislav Vaintroub
2019-12-05 14:39:28 +01:00
parent c8833751cf
commit 63df45ce3d
9 changed files with 1191 additions and 763 deletions

View File

@@ -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}")

View File

@@ -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})

View File

@@ -20,6 +20,7 @@
*************************************************************************************/
#include "ma_schannel.h"
#include "schannel_certs.h"
#include <assert.h>
#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)

View File

@@ -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);

View File

@@ -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 || !ctls->pvio)
return 1;;
if (!ctls)
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;
verify_certs = mysql->options.ssl_ca || mysql->options.ssl_capath ||
(mysql->client_flag & CLIENT_SSL_VERIFY_SERVER_CERT);
return 0;
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);

View File

@@ -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 <http://www.gnu.org/licenses>
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 <malloc.h>
#include <stdio.h>
#include <string.h>
#include <winsock2.h>
#include <ws2tcpip.h>
#include <winhttp.h>
#include <assert.h>
#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 : "<not set>", CAPath ? CAPath : "<not set>");
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);
}

View File

@@ -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 <http://www.gnu.org/licenses>
or write to the Free Software Foundation, Inc.,
51 Franklin St., Fifth Floor, Boston, MA 02110, USA
*************************************************************************************/
#pragma once
#include <windows.h>
#include <wincrypt.h>
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 <EFBFBD>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);

131
libmariadb/win32_errmsg.c Normal file
View File

@@ -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 <http://www.gnu.org/licenses>
or write to the Free Software Foundation, Inc.,
51 Franklin St., Fifth Floor, Boston, MA 02110, USA
*************************************************************************************/
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
/*
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);
}
}

View File

@@ -0,0 +1,2 @@
#include <windows.h>
void ma_format_win32_error(char* buf, size_t buflen, DWORD code, _Printf_format_string_ const char* fmt, ...);