You've already forked mariadb-connector-c
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:
@@ -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}")
|
||||
|
@@ -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})
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
||||
/* }}} */
|
||||
|
||||
#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");
|
||||
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)
|
||||
|
@@ -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);
|
||||
|
@@ -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);
|
||||
|
||||
if (SecIsValidHandle(&sctx->CredHdl))
|
||||
FreeCredentialHandle(&sctx->CredHdl);
|
||||
DeleteSecurityContext(&sctx->ctxt);
|
||||
|
||||
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);
|
||||
|
892
libmariadb/secure/schannel_certs.c
Normal file
892
libmariadb/secure/schannel_certs.c
Normal 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);
|
||||
}
|
53
libmariadb/secure/schannel_certs.h
Normal file
53
libmariadb/secure/schannel_certs.h
Normal 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
131
libmariadb/win32_errmsg.c
Normal 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);
|
||||
}
|
||||
}
|
||||
|
2
libmariadb/win32_errmsg.h
Normal file
2
libmariadb/win32_errmsg.h
Normal 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, ...);
|
Reference in New Issue
Block a user