1
0
mirror of https://github.com/VCMP-SqMod/SqMod.git synced 2025-08-03 14:41:48 +02:00

Add MariaDB Connector/C as a built-in alternative (v3.2.3).

This commit is contained in:
Sandu Liviu Catalin
2021-09-21 20:59:01 +03:00
parent f192767853
commit b4bf96ce4b
372 changed files with 90819 additions and 11 deletions

1463
vendor/MDBC/libmariadb/secure/gnutls.c vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,77 @@
/*
Copyright (C) 2018 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
*/
#include <ma_crypt.h>
#include <gnutls/gnutls.h>
#include <gnutls/crypto.h>
static gnutls_digest_algorithm_t ma_hash_get_algorithm(unsigned int alg)
{
switch(alg)
{
case MA_HASH_MD5:
return GNUTLS_DIG_MD5;
case MA_HASH_SHA1:
return GNUTLS_DIG_SHA1;
case MA_HASH_SHA256:
return GNUTLS_DIG_SHA256;
case MA_HASH_SHA384:
return GNUTLS_DIG_SHA384;
case MA_HASH_SHA512:
return GNUTLS_DIG_SHA512;
case MA_HASH_RIPEMD160:
return GNUTLS_DIG_RMD160;
default:
return GNUTLS_DIG_UNKNOWN;
}
}
MA_HASH_CTX *ma_hash_new(unsigned int algorithm, MA_HASH_CTX *unused_ctx __attribute__((unused)))
{
gnutls_hash_hd_t ctx= NULL;
gnutls_digest_algorithm_t hash_alg= ma_hash_get_algorithm(algorithm);
/* unknown or unsupported hash algorithm */
if (hash_alg == GNUTLS_DIG_UNKNOWN)
return NULL;
if (gnutls_hash_init(&ctx, hash_alg) < 0)
return NULL;
return (MA_HASH_CTX *)ctx;
}
void ma_hash_free(MA_HASH_CTX *ctx)
{
if (ctx)
gnutls_hash_deinit((gnutls_hash_hd_t)ctx, NULL);
}
void ma_hash_input(MA_HASH_CTX *ctx,
const unsigned char *buffer,
size_t len)
{
gnutls_hash((gnutls_hash_hd_t)ctx, (const void *)buffer, len);
}
void ma_hash_result(MA_HASH_CTX *ctx, unsigned char *digest)
{
gnutls_hash_output((gnutls_hash_hd_t)ctx, digest);
}

View File

@@ -0,0 +1,637 @@
/************************************************************************************
Copyright (C) 2014 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
Author: Georg Richter
*************************************************************************************/
#include "ma_schannel.h"
#include "schannel_certs.h"
#include <assert.h>
#define SC_IO_BUFFER_SIZE 0x4000
#define MAX_SSL_ERR_LEN 100
#define SCHANNEL_PAYLOAD(A) ((A).cbMaximumMessage + (A).cbHeader + (A).cbTrailer)
void ma_schannel_set_win_error(MARIADB_PVIO *pvio, DWORD ErrorNo);
/* {{{ void ma_schannel_set_sec_error */
void ma_schannel_set_sec_error(MARIADB_PVIO* pvio, DWORD ErrorNo)
{
MYSQL* mysql = pvio->mysql;
if (ErrorNo != SEC_E_OK)
mysql->net.extension->extended_errno = ErrorNo;
if (ErrorNo == SEC_E_INTERNAL_ERROR && GetLastError())
{
ma_schannel_set_win_error(pvio, GetLastError());
return;
}
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)
{
char buffer[256];
ma_format_win32_error(buffer, sizeof(buffer), ErrorNo, "SSL connection error: ");
pvio->set_error(pvio->mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, buffer);
return;
}
/* }}} */
/* }}} */
/* {{{ SECURITY_STATUS ma_schannel_handshake_loop(MARIADB_PVIO *pvio, my_bool InitialRead, SecBuffer *pExtraData) */
/*
perform handshake loop
SYNOPSIS
ma_schannel_handshake_loop()
pvio Pointer to an Communication/IO structure
InitialRead TRUE if it's the very first read
ExtraData Pointer to an SecBuffer which contains extra data (sent by application)
*/
SECURITY_STATUS ma_schannel_handshake_loop(MARIADB_PVIO *pvio, my_bool InitialRead, SecBuffer *pExtraData)
{
SecBufferDesc OutBuffer, InBuffer;
SecBuffer InBuffers[2], OutBuffers;
DWORD dwSSPIFlags, dwSSPIOutFlags, cbData, cbIoBuffer;
TimeStamp tsExpiry;
SECURITY_STATUS rc;
PUCHAR IoBuffer;
BOOL fDoRead;
MARIADB_TLS *ctls= pvio->ctls;
SC_CTX *sctx= (SC_CTX *)ctls->ssl;
dwSSPIFlags = ISC_REQ_SEQUENCE_DETECT |
ISC_REQ_REPLAY_DETECT |
ISC_REQ_CONFIDENTIALITY |
ISC_RET_EXTENDED_ERROR |
ISC_REQ_ALLOCATE_MEMORY |
ISC_REQ_STREAM;
/* Allocate data buffer */
if (!(IoBuffer = LocalAlloc(LMEM_FIXED, SC_IO_BUFFER_SIZE)))
return SEC_E_INSUFFICIENT_MEMORY;
cbIoBuffer = 0;
fDoRead = InitialRead;
/* handshake loop: We will leave if handshake is finished
or an error occurs */
rc = SEC_I_CONTINUE_NEEDED;
while (rc == SEC_I_CONTINUE_NEEDED ||
rc == SEC_E_INCOMPLETE_MESSAGE ||
rc == SEC_I_INCOMPLETE_CREDENTIALS )
{
/* Read data */
if (rc == SEC_E_INCOMPLETE_MESSAGE ||
!cbIoBuffer)
{
if(fDoRead)
{
ssize_t nbytes = pvio->methods->read(pvio, IoBuffer + cbIoBuffer, (size_t)(SC_IO_BUFFER_SIZE - cbIoBuffer));
if (nbytes <= 0)
{
rc = SEC_E_INTERNAL_ERROR;
break;
}
cbData = (DWORD)nbytes;
cbIoBuffer += cbData;
}
else
fDoRead = TRUE;
}
/* input buffers
First buffer stores data received from server. leftover data
will be stored in second buffer with BufferType SECBUFFER_EXTRA */
InBuffers[0].pvBuffer = IoBuffer;
InBuffers[0].cbBuffer = cbIoBuffer;
InBuffers[0].BufferType = SECBUFFER_TOKEN;
InBuffers[1].pvBuffer = NULL;
InBuffers[1].cbBuffer = 0;
InBuffers[1].BufferType = SECBUFFER_EMPTY;
InBuffer.cBuffers = 2;
InBuffer.pBuffers = InBuffers;
InBuffer.ulVersion = SECBUFFER_VERSION;
/* output buffer */
OutBuffers.pvBuffer = NULL;
OutBuffers.BufferType= SECBUFFER_TOKEN;
OutBuffers.cbBuffer = 0;
OutBuffer.cBuffers = 1;
OutBuffer.pBuffers = &OutBuffers;
OutBuffer.ulVersion = SECBUFFER_VERSION;
rc = InitializeSecurityContextA(&sctx->CredHdl,
&sctx->hCtxt,
NULL,
dwSSPIFlags,
0,
SECURITY_NATIVE_DREP,
&InBuffer,
0,
NULL,
&OutBuffer,
&dwSSPIOutFlags,
&tsExpiry );
if (rc == SEC_E_OK ||
rc == SEC_I_CONTINUE_NEEDED ||
(FAILED(rc) && (dwSSPIOutFlags & ISC_RET_EXTENDED_ERROR)))
{
if(OutBuffers.cbBuffer && OutBuffers.pvBuffer)
{
ssize_t nbytes = pvio->methods->write(pvio, (uchar *)OutBuffers.pvBuffer, (size_t)OutBuffers.cbBuffer);
if(nbytes <= 0)
{
FreeContextBuffer(OutBuffers.pvBuffer);
DeleteSecurityContext(&sctx->hCtxt);
return SEC_E_INTERNAL_ERROR;
}
cbData= (DWORD)nbytes;
/* Free output context buffer */
FreeContextBuffer(OutBuffers.pvBuffer);
OutBuffers.pvBuffer = NULL;
}
}
/* check if we need to read more data */
switch (rc) {
case SEC_E_INCOMPLETE_MESSAGE:
/* we didn't receive all data, so just continue loop */
continue;
break;
case SEC_E_OK:
/* handshake completed, but we need to check if extra
data was sent (which contains encrypted application data) */
if (InBuffers[1].BufferType == SECBUFFER_EXTRA)
{
if (!(pExtraData->pvBuffer= LocalAlloc(0, InBuffers[1].cbBuffer)))
return SEC_E_INSUFFICIENT_MEMORY;
MoveMemory(pExtraData->pvBuffer, IoBuffer + (cbIoBuffer - InBuffers[1].cbBuffer), InBuffers[1].cbBuffer );
pExtraData->BufferType = SECBUFFER_TOKEN;
pExtraData->cbBuffer = InBuffers[1].cbBuffer;
}
else
{
pExtraData->BufferType= SECBUFFER_EMPTY;
pExtraData->pvBuffer= NULL;
pExtraData->cbBuffer= 0;
}
break;
case SEC_I_INCOMPLETE_CREDENTIALS:
/* Provided credentials didn't contain a valid client certificate.
We will try to connect anonymously, using current credentials */
fDoRead= FALSE;
rc= SEC_I_CONTINUE_NEEDED;
continue;
break;
default:
if (FAILED(rc))
{
goto loopend;
}
break;
}
if ( InBuffers[1].BufferType == SECBUFFER_EXTRA )
{
MoveMemory( IoBuffer, IoBuffer + (cbIoBuffer - InBuffers[1].cbBuffer), InBuffers[1].cbBuffer );
cbIoBuffer = InBuffers[1].cbBuffer;
}
else
cbIoBuffer = 0;
}
loopend:
if (FAILED(rc))
{
ma_schannel_set_sec_error(pvio, rc);
DeleteSecurityContext(&sctx->hCtxt);
}
LocalFree(IoBuffer);
return rc;
}
/* }}} */
/* {{{ SECURITY_STATUS ma_schannel_client_handshake(MARIADB_TLS *ctls) */
/*
performs client side handshake
SYNOPSIS
ma_schannel_client_handshake()
ctls Pointer to a MARIADB_TLS structure
DESCRIPTION
initiates a client/server handshake. This function can be used
by clients only
RETURN
SEC_E_OK on success
*/
SECURITY_STATUS ma_schannel_client_handshake(MARIADB_TLS *ctls)
{
MARIADB_PVIO *pvio;
SECURITY_STATUS sRet;
DWORD OutFlags;
DWORD r;
SC_CTX *sctx;
SecBuffer ExtraData;
DWORD SFlags= ISC_REQ_SEQUENCE_DETECT | ISC_REQ_REPLAY_DETECT |
ISC_REQ_CONFIDENTIALITY | ISC_RET_EXTENDED_ERROR |
ISC_REQ_USE_SUPPLIED_CREDS |
ISC_REQ_ALLOCATE_MEMORY | ISC_REQ_STREAM;
SecBufferDesc BufferOut;
SecBuffer BuffersOut;
if (!ctls || !ctls->pvio)
return 1;
pvio= ctls->pvio;
sctx= (SC_CTX *)ctls->ssl;
/* Initialie securifty context */
BuffersOut.BufferType= SECBUFFER_TOKEN;
BuffersOut.cbBuffer= 0;
BuffersOut.pvBuffer= NULL;
BufferOut.cBuffers= 1;
BufferOut.pBuffers= &BuffersOut;
BufferOut.ulVersion= SECBUFFER_VERSION;
sRet = InitializeSecurityContext(&sctx->CredHdl,
NULL,
pvio->mysql->host,
SFlags,
0,
SECURITY_NATIVE_DREP,
NULL,
0,
&sctx->hCtxt,
&BufferOut,
&OutFlags,
NULL);
if(sRet != SEC_I_CONTINUE_NEEDED)
{
ma_schannel_set_sec_error(pvio, sRet);
return sRet;
}
/* send client hello packaet */
if(BuffersOut.cbBuffer != 0 && BuffersOut.pvBuffer != NULL)
{
ssize_t nbytes = (DWORD)pvio->methods->write(pvio, (uchar *)BuffersOut.pvBuffer, (size_t)BuffersOut.cbBuffer);
if (nbytes <= 0)
{
sRet= SEC_E_INTERNAL_ERROR;
goto end;
}
r = (DWORD)nbytes;
}
sRet= ma_schannel_handshake_loop(pvio, TRUE, &ExtraData);
/* allocate IO-Buffer for write operations: After handshake
was successful, we are able now to calculate payload */
if ((sRet = QueryContextAttributes(&sctx->hCtxt, SECPKG_ATTR_STREAM_SIZES, &sctx->Sizes )))
goto end;
sctx->IoBufferSize= SCHANNEL_PAYLOAD(sctx->Sizes);
if (!(sctx->IoBuffer= (PUCHAR)LocalAlloc(0, sctx->IoBufferSize)))
{
sRet= SEC_E_INSUFFICIENT_MEMORY;
goto end;
}
return sRet;
end:
if (BuffersOut.pvBuffer)
FreeContextBuffer(BuffersOut.pvBuffer);
return sRet;
}
/* }}} */
/* {{{ SECURITY_STATUS ma_schannel_read_decrypt(MARIADB_PVIO *pvio, PCredHandle phCreds, CtxtHandle * phContext,
DWORD DecryptLength, uchar *ReadBuffer, DWORD ReadBufferSize) */
/*
Reads encrypted data from a SSL stream and decrypts it.
SYNOPSIS
ma_schannel_read
pvio pointer to Communication IO structure
phContext a context handle
DecryptLength size of decrypted buffer
ReadBuffer Buffer for decrypted data
ReadBufferSize size of ReadBuffer
DESCRIPTION
Reads decrypted data from a SSL stream and encrypts it.
RETURN
SEC_E_OK on success
SEC_E_* if an error occurred
*/
SECURITY_STATUS ma_schannel_read_decrypt(MARIADB_PVIO *pvio,
CtxtHandle * phContext,
DWORD *DecryptLength,
uchar *ReadBuffer,
DWORD ReadBufferSize)
{
ssize_t nbytes = 0;
DWORD dwOffset = 0;
SC_CTX *sctx;
SECURITY_STATUS sRet = 0;
SecBufferDesc Msg;
SecBuffer Buffers[4];
int i;
if (!pvio || !pvio->methods || !pvio->methods->read || !pvio->ctls || !DecryptLength)
return SEC_E_INTERNAL_ERROR;
sctx = (SC_CTX *)pvio->ctls->ssl;
*DecryptLength = 0;
if (sctx->dataBuf.cbBuffer)
{
/* Have unread decrypted data from the last time, copy. */
nbytes = MIN(ReadBufferSize, sctx->dataBuf.cbBuffer);
memcpy(ReadBuffer, sctx->dataBuf.pvBuffer, nbytes);
sctx->dataBuf.pvBuffer = (char *)(sctx->dataBuf.pvBuffer) + nbytes;
sctx->dataBuf.cbBuffer -= (DWORD)nbytes;
*DecryptLength = (DWORD)nbytes;
return SEC_E_OK;
}
while (1)
{
/* Check for any encrypted data returned by last DecryptMessage() in SECBUFFER_EXTRA buffer. */
if (sctx->extraBuf.cbBuffer)
{
memmove(sctx->IoBuffer, sctx->extraBuf.pvBuffer, sctx->extraBuf.cbBuffer);
dwOffset = sctx->extraBuf.cbBuffer;
sctx->extraBuf.cbBuffer = 0;
}
do {
assert(sctx->IoBufferSize > dwOffset);
if (dwOffset == 0 || sRet == SEC_E_INCOMPLETE_MESSAGE)
{
nbytes = pvio->methods->read(pvio, sctx->IoBuffer + dwOffset, (size_t)(sctx->IoBufferSize - dwOffset));
if (nbytes <= 0)
{
/* server closed connection, or an error */
// todo: error
return SEC_E_INVALID_HANDLE;
}
dwOffset += (DWORD)nbytes;
}
ZeroMemory(Buffers, sizeof(SecBuffer) * 4);
Buffers[0].pvBuffer = sctx->IoBuffer;
Buffers[0].cbBuffer = dwOffset;
Buffers[0].BufferType = SECBUFFER_DATA;
Buffers[1].BufferType = SECBUFFER_EMPTY;
Buffers[2].BufferType = SECBUFFER_EMPTY;
Buffers[3].BufferType = SECBUFFER_EMPTY;
Msg.ulVersion = SECBUFFER_VERSION; // Version number
Msg.cBuffers = 4;
Msg.pBuffers = Buffers;
sRet = DecryptMessage(phContext, &Msg, 0, NULL);
} while (sRet == SEC_E_INCOMPLETE_MESSAGE); /* Continue reading until full message arrives */
if (sRet != SEC_E_OK)
{
ma_schannel_set_sec_error(pvio, sRet);
return sRet;
}
sctx->extraBuf.cbBuffer = 0;
sctx->dataBuf.cbBuffer = 0;
for (i = 0; i < 4; i++)
{
if (Buffers[i].BufferType == SECBUFFER_DATA)
sctx->dataBuf = Buffers[i];
if (Buffers[i].BufferType == SECBUFFER_EXTRA)
sctx->extraBuf = Buffers[i];
}
if (sctx->dataBuf.cbBuffer)
{
assert(sctx->dataBuf.pvBuffer);
/*
Copy at most ReadBufferSize bytes to output.
Store the rest (if any) to be processed next time.
*/
nbytes = MIN(sctx->dataBuf.cbBuffer, ReadBufferSize);
memcpy((char *)ReadBuffer, sctx->dataBuf.pvBuffer, nbytes);
sctx->dataBuf.cbBuffer -= (unsigned long)nbytes;
sctx->dataBuf.pvBuffer = (char *)sctx->dataBuf.pvBuffer + nbytes;
*DecryptLength = (DWORD)nbytes;
return SEC_E_OK;
}
// No data buffer, loop
}
}
/* }}} */
#include "win32_errmsg.h"
my_bool ma_schannel_verify_certs(MARIADB_TLS *ctls, BOOL verify_server_name)
{
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;
char errmsg[256];
HCERTSTORE store= NULL;
int ret= 0;
status = schannel_create_store(ca_file, ca_path, crl_file, crl_path, &store, errmsg, sizeof(errmsg));
if(status)
goto end;
status = QueryContextAttributesA(&sctx->hCtxt, SECPKG_ATTR_REMOTE_CERT_CONTEXT, (PVOID)&pServerCert);
if (status)
{
ma_format_win32_error(errmsg, sizeof(errmsg), GetLastError(),
"QueryContextAttributes(SECPKG_ATTR_REMOTE_CERT_CONTEXT) failed.");
goto end;
}
status = schannel_verify_server_certificate(
pServerCert,
store,
crl_file != 0 || crl_path != 0,
mysql->host,
verify_server_name,
errmsg, sizeof(errmsg));
if (status)
goto end;
ret= 1;
end:
if (!ret)
{
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;
}
/* {{{ size_t ma_schannel_write_encrypt(MARIADB_PVIO *pvio, PCredHandle phCreds, CtxtHandle * phContext) */
/*
Decrypts data and write to SSL stream
SYNOPSIS
ma_schannel_write_decrypt
pvio pointer to Communication IO structure
phContext a context handle
DecryptLength size of decrypted buffer
ReadBuffer Buffer for decrypted data
ReadBufferSize size of ReadBuffer
DESCRIPTION
Write encrypted data to SSL stream.
RETURN
SEC_E_OK on success
SEC_E_* if an error occurred
*/
ssize_t ma_schannel_write_encrypt(MARIADB_PVIO *pvio,
uchar *WriteBuffer,
size_t WriteBufferSize)
{
SECURITY_STATUS scRet;
SecBufferDesc Message;
SecBuffer Buffers[4];
DWORD cbMessage;
PBYTE pbMessage;
SC_CTX *sctx= (SC_CTX *)pvio->ctls->ssl;
size_t payload;
ssize_t nbytes;
DWORD write_size;
payload= MIN(WriteBufferSize, sctx->Sizes.cbMaximumMessage);
memcpy(&sctx->IoBuffer[sctx->Sizes.cbHeader], WriteBuffer, payload);
pbMessage = sctx->IoBuffer + sctx->Sizes.cbHeader;
cbMessage = (DWORD)payload;
Buffers[0].pvBuffer = sctx->IoBuffer;
Buffers[0].cbBuffer = sctx->Sizes.cbHeader;
Buffers[0].BufferType = SECBUFFER_STREAM_HEADER; // Type of the buffer
Buffers[1].pvBuffer = &sctx->IoBuffer[sctx->Sizes.cbHeader];
Buffers[1].cbBuffer = (DWORD)payload;
Buffers[1].BufferType = SECBUFFER_DATA;
Buffers[2].pvBuffer = &sctx->IoBuffer[sctx->Sizes.cbHeader] + payload;
Buffers[2].cbBuffer = sctx->Sizes.cbTrailer;
Buffers[2].BufferType = SECBUFFER_STREAM_TRAILER;
Buffers[3].pvBuffer = SECBUFFER_EMPTY; // Pointer to buffer 4
Buffers[3].cbBuffer = SECBUFFER_EMPTY; // length of buffer 4
Buffers[3].BufferType = SECBUFFER_EMPTY; // Type of the buffer 4
Message.ulVersion = SECBUFFER_VERSION;
Message.cBuffers = 4;
Message.pBuffers = Buffers;
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);
return nbytes == write_size ? payload : -1;
}
/* }}} */
extern char *ssl_protocol_version[5];
/* {{{ ma_tls_get_protocol_version(MARIADB_TLS *ctls) */
int ma_tls_get_protocol_version(MARIADB_TLS *ctls)
{
SC_CTX *sctx;
SecPkgContext_ConnectionInfo ConnectionInfo;
if (!ctls->ssl)
return 1;
sctx= (SC_CTX *)ctls->ssl;
if (QueryContextAttributes(&sctx->hCtxt, SECPKG_ATTR_CONNECTION_INFO, &ConnectionInfo) != SEC_E_OK)
return -1;
switch(ConnectionInfo.dwProtocol)
{
case SP_PROT_SSL3_CLIENT:
return PROTOCOL_SSLV3;
case SP_PROT_TLS1_CLIENT:
return PROTOCOL_TLS_1_0;
case SP_PROT_TLS1_1_CLIENT:
return PROTOCOL_TLS_1_1;
case SP_PROT_TLS1_2_CLIENT:
return PROTOCOL_TLS_1_2;
default:
return -1;
}
}
/* }}} */

View File

@@ -0,0 +1,87 @@
/************************************************************************************
Copyright (C) 2014 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
Author: Georg Richter
*************************************************************************************/
#ifndef _ma_schannel_h_
#define _ma_schannel_h_
#define SECURITY_WIN32
#include <ma_global.h>
#include <ma_sys.h>
#include <ma_common.h>
#include <ma_pvio.h>
#include <errmsg.h>
#include <wincrypt.h>
#include <wintrust.h>
#include <security.h>
#include <schnlsp.h>
#undef SECURITY_WIN32
#include <windows.h>
#include <sspi.h>
#define SC_IO_BUFFER_SIZE 0x4000
#include <ma_pthread.h>
struct st_DER {
char* der_buffer;
DWORD der_length;
};
struct st_schannel {
CredHandle CredHdl;
PUCHAR IoBuffer;
DWORD IoBufferSize;
SecPkgContext_StreamSizes Sizes;
CtxtHandle hCtxt;
/* Cached data from the last read/decrypt call.*/
SecBuffer extraBuf; /* encrypted data read from server. */
SecBuffer dataBuf; /* decrypted but still unread data from server.*/
};
typedef struct st_schannel SC_CTX;
extern HCERTSTORE ca_CertStore, crl_CertStore;
extern my_bool ca_Check, crl_Check;
;
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_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,
CtxtHandle* phContext,
DWORD *DecryptLength,
uchar *ReadBuffer,
DWORD ReadBufferSize);
#endif /* _ma_schannel_h_ */

797
vendor/MDBC/libmariadb/secure/openssl.c vendored Normal file
View File

@@ -0,0 +1,797 @@
/************************************************************************************
Copyright (C) 2012 Monty Program 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
*************************************************************************************/
#include <ma_global.h>
#include <ma_sys.h>
#include <ma_common.h>
#include <ma_pvio.h>
#include <errmsg.h>
#include <string.h>
#include <mysql/client_plugin.h>
#include <string.h>
#include <openssl/ssl.h> /* SSL and SSL_CTX */
#include <openssl/err.h> /* error reporting */
#include <openssl/conf.h>
#include <openssl/md4.h>
#if defined(_WIN32) && !defined(_OPENSSL_Applink) && defined(HAVE_OPENSSL_APPLINK_C)
#include <openssl/applink.c>
#endif
#if OPENSSL_VERSION_NUMBER >= 0x10002000L && !defined(LIBRESSL_VERSION_NUMBER)
#include <openssl/x509v3.h>
#define HAVE_OPENSSL_CHECK_HOST 1
#endif
#if OPENSSL_VERSION_NUMBER >= 0x10100000L && !defined(LIBRESSL_VERSION_NUMBER)
#define HAVE_OPENSSL_1_1_API
#endif
#if OPENSSL_VERSION_NUMBER < 0x10000000L
#define SSL_OP_NO_TLSv1_1 0L
#define SSL_OP_NO_TLSv1_2 0L
#define CRYPTO_THREADID_set_callback CRYPTO_set_id_callback
#define CRYPTO_THREADID_get_callback CRYPTO_get_id_callback
#endif
#if defined(OPENSSL_USE_BIOMETHOD)
#undef OPENSSL_USE_BIOMETHOD
#endif
#ifndef HAVE_OPENSSL_DEFAULT
#include <memory.h>
#define ma_malloc(A,B) malloc((A))
#undef ma_free
#define ma_free(A) free((A))
#define ma_snprintf snprintf
#define ma_vsnprintf vsnprintf
#undef SAFE_MUTEX
#endif
#include <ma_pthread.h>
#include <mariadb_async.h>
#include <ma_context.h>
extern my_bool ma_tls_initialized;
extern unsigned int mariadb_deinitialize_ssl;
#define MAX_SSL_ERR_LEN 100
char tls_library_version[TLS_VERSION_LENGTH];
static pthread_mutex_t LOCK_openssl_config;
#ifndef HAVE_OPENSSL_1_1_API
static pthread_mutex_t *LOCK_crypto= NULL;
#endif
#if defined(OPENSSL_USE_BIOMETHOD)
static int ma_bio_read(BIO *h, char *buf, int size);
static int ma_bio_write(BIO *h, const char *buf, int size);
static BIO_METHOD ma_BIO_method;
#endif
static long ma_tls_version_options(const char *version)
{
long protocol_options,
disable_all_protocols;
protocol_options= disable_all_protocols=
SSL_OP_NO_SSLv2 |
SSL_OP_NO_SSLv3 |
SSL_OP_NO_TLSv1 |
SSL_OP_NO_TLSv1_1 |
SSL_OP_NO_TLSv1_2
#ifdef TLS1_3_VERSION
| SSL_OP_NO_TLSv1_3
#endif
;
if (!version)
return 0;
if (strstr(version, "TLSv1.0"))
protocol_options&= ~SSL_OP_NO_TLSv1;
if (strstr(version, "TLSv1.1"))
protocol_options&= ~SSL_OP_NO_TLSv1_1;
if (strstr(version, "TLSv1.2"))
protocol_options&= ~SSL_OP_NO_TLSv1_2;
#ifdef TLS1_3_VERSION
if (strstr(version, "TLSv1.3"))
protocol_options&= ~SSL_OP_NO_TLSv1_3;
#endif
if (protocol_options != disable_all_protocols)
return protocol_options;
return 0;
}
static void ma_tls_set_error(MYSQL *mysql)
{
ulong ssl_errno= ERR_get_error();
char ssl_error[MAX_SSL_ERR_LEN];
const char *ssl_error_reason;
MARIADB_PVIO *pvio= mysql->net.pvio;
if (!ssl_errno)
{
pvio->set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, "Unknown SSL error");
return;
}
if ((ssl_error_reason= ERR_reason_error_string(ssl_errno)))
{
pvio->set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN,
0, ssl_error_reason);
return;
}
snprintf(ssl_error, MAX_SSL_ERR_LEN, "SSL errno=%lu", ssl_errno);
pvio->set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, 0, ssl_error);
return;
}
#ifndef HAVE_OPENSSL_1_1_API
/*
thread safe callbacks for OpenSSL
Crypto call back functions will be
set during ssl_initialization
*/
#if OPENSSL_VERSION_NUMBER < 0x10000000L
static unsigned long my_cb_threadid(void)
{
/* cast pthread_t to unsigned long */
return (unsigned long) pthread_self();
}
#else
static void my_cb_threadid(CRYPTO_THREADID *id)
{
CRYPTO_THREADID_set_numeric(id, (unsigned long)pthread_self());
}
#endif
#endif
#ifndef HAVE_OPENSSL_1_1_API
static void my_cb_locking(int mode, int n,
const char *file __attribute__((unused)),
int line __attribute__((unused)))
{
if (mode & CRYPTO_LOCK)
pthread_mutex_lock(&LOCK_crypto[n]);
else
pthread_mutex_unlock(&LOCK_crypto[n]);
}
static int ssl_thread_init()
{
if (!CRYPTO_THREADID_get_callback()
#ifndef OPENSSL_NO_DEPRECATED
&& !CRYPTO_get_id_callback()
#endif
)
{
int i, max= CRYPTO_num_locks();
if (LOCK_crypto == NULL)
{
if (!(LOCK_crypto=
(pthread_mutex_t *)ma_malloc(sizeof(pthread_mutex_t) * max, MYF(0))))
return 1;
for (i=0; i < max; i++)
pthread_mutex_init(&LOCK_crypto[i], NULL);
}
CRYPTO_set_locking_callback(my_cb_locking);
CRYPTO_THREADID_set_callback(my_cb_threadid);
}
return 0;
}
#endif
#if defined(_WIN32) || !defined(DISABLE_SIGPIPE)
#define disable_sigpipe()
#else
#include <signal.h>
static void ma_sigpipe_handler()
{
}
static void disable_sigpipe()
{
struct sigaction old_handler, new_handler={NULL};
if (!sigaction (SIGPIPE, NULL, &old_handler) &&
!old_handler.sa_handler)
{
new_handler.sa_handler= ma_sigpipe_handler;
new_handler.sa_flags= 0;
if (!sigemptyset(&new_handler.sa_mask))
sigaction(SIGPIPE, &new_handler, NULL);
}
}
#endif
/*
Initializes SSL
SYNOPSIS
my_ssl_start
mysql connection handle
RETURN VALUES
0 success
1 error
*/
int ma_tls_start(char *errmsg __attribute__((unused)), size_t errmsg_len __attribute__((unused)))
{
int rc= 1;
char *p;
if (ma_tls_initialized)
return 0;
/* lock mutex to prevent multiple initialization */
pthread_mutex_init(&LOCK_openssl_config, NULL);
pthread_mutex_lock(&LOCK_openssl_config);
#ifdef HAVE_OPENSSL_1_1_API
if (!OPENSSL_init_ssl(OPENSSL_INIT_LOAD_CONFIG, NULL))
goto end;
#else
if (ssl_thread_init())
{
strncpy(errmsg, "Not enough memory", errmsg_len);
goto end;
}
SSL_library_init();
#if SSLEAY_VERSION_NUMBER >= 0x00907000L
OPENSSL_config(NULL);
#endif
#endif
#ifndef HAVE_OPENSSL_1_1_API
/* load errors */
SSL_load_error_strings();
/* digests and ciphers */
OpenSSL_add_all_algorithms();
#endif
disable_sigpipe();
#ifdef OPENSSL_USE_BIOMETHOD
memcpy(&ma_BIO_method, BIO_s_socket(), sizeof(BIO_METHOD));
ma_BIO_method.bread= ma_bio_read;
ma_BIO_method.bwrite= ma_bio_write;
#endif
snprintf(tls_library_version, TLS_VERSION_LENGTH - 1, "%s",
#if defined(LIBRESSL_VERSION_NUMBER) || !defined(HAVE_OPENSSL_1_1_API)
SSLeay_version(SSLEAY_VERSION));
#else
OpenSSL_version(OPENSSL_VERSION));
#endif
/* remove date from version */
if ((p= strstr(tls_library_version, " ")))
*p= 0;
rc= 0;
ma_tls_initialized= TRUE;
end:
pthread_mutex_unlock(&LOCK_openssl_config);
return rc;
}
/*
Release SSL and free resources
Will be automatically executed by
mysql_server_end() function
SYNOPSIS
my_ssl_end()
void
RETURN VALUES
void
*/
void ma_tls_end()
{
if (ma_tls_initialized)
{
pthread_mutex_lock(&LOCK_openssl_config);
#ifndef HAVE_OPENSSL_1_1_API
if (LOCK_crypto)
{
int i;
CRYPTO_set_locking_callback(NULL);
CRYPTO_THREADID_set_callback(NULL);
for (i=0; i < CRYPTO_num_locks(); i++)
pthread_mutex_destroy(&LOCK_crypto[i]);
ma_free((gptr)LOCK_crypto);
LOCK_crypto= NULL;
}
#endif
if (mariadb_deinitialize_ssl)
{
#ifndef HAVE_OPENSSL_1_1_API
ERR_remove_thread_state(NULL);
EVP_cleanup();
CRYPTO_cleanup_all_ex_data();
ERR_free_strings();
CONF_modules_free();
CONF_modules_unload(1);
#endif
}
ma_tls_initialized= FALSE;
pthread_mutex_unlock(&LOCK_openssl_config);
pthread_mutex_destroy(&LOCK_openssl_config);
}
return;
}
int ma_tls_get_password(char *buf, int size,
int rwflag __attribute__((unused)),
void *userdata)
{
memset(buf, 0, size);
if (userdata)
strncpy(buf, (char *)userdata, size);
return (int)strlen(buf);
}
static int ma_tls_set_certs(MYSQL *mysql, SSL_CTX *ctx)
{
char *certfile= mysql->options.ssl_cert,
*keyfile= mysql->options.ssl_key;
char *pw= (mysql->options.extension) ?
mysql->options.extension->tls_pw : NULL;
/* add cipher */
if ((mysql->options.ssl_cipher &&
mysql->options.ssl_cipher[0] != 0))
{
if(
#ifdef TLS1_3_VERSION
SSL_CTX_set_ciphersuites(ctx, mysql->options.ssl_cipher) == 0 &&
#endif
SSL_CTX_set_cipher_list(ctx, mysql->options.ssl_cipher) == 0)
goto error;
}
/* ca_file and ca_path */
if (!SSL_CTX_load_verify_locations(ctx,
mysql->options.ssl_ca,
mysql->options.ssl_capath))
{
if (mysql->options.ssl_ca || mysql->options.ssl_capath)
goto error;
if (SSL_CTX_set_default_verify_paths(ctx) == 0)
goto error;
}
if (mysql->options.extension &&
(mysql->options.extension->ssl_crl || mysql->options.extension->ssl_crlpath))
{
X509_STORE *certstore;
if ((certstore= SSL_CTX_get_cert_store(ctx)))
{
if (X509_STORE_load_locations(certstore, mysql->options.extension->ssl_crl,
mysql->options.extension->ssl_crlpath) == 0)
goto error;
if (X509_STORE_set_flags(certstore, X509_V_FLAG_CRL_CHECK | X509_V_FLAG_CRL_CHECK_ALL) == 0)
goto error;
}
}
if (keyfile && !certfile)
certfile= keyfile;
if (certfile && !keyfile)
keyfile= certfile;
/* set cert */
if (certfile && certfile[0] != 0)
{
if (SSL_CTX_use_certificate_chain_file(ctx, certfile) != 1)
{
goto error;
}
}
if (keyfile && keyfile[0])
{
FILE *fp;
if ((fp= fopen(keyfile, "rb")))
{
EVP_PKEY *key= EVP_PKEY_new();
PEM_read_PrivateKey(fp, &key, NULL, pw);
fclose(fp);
if (SSL_CTX_use_PrivateKey(ctx, key) != 1)
{
unsigned long err= ERR_peek_error();
EVP_PKEY_free(key);
if (!(ERR_GET_LIB(err) == ERR_LIB_X509 &&
ERR_GET_REASON(err) == X509_R_CERT_ALREADY_IN_HASH_TABLE))
goto error;
}
EVP_PKEY_free(key);
} else {
my_set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN,
CER(CR_FILE_NOT_FOUND), keyfile);
return 1;
}
}
/* verify key */
if (certfile && SSL_CTX_check_private_key(ctx) != 1)
goto error;
SSL_CTX_set_verify(ctx, (mysql->options.ssl_ca || mysql->options.ssl_capath) ?
SSL_VERIFY_PEER : SSL_VERIFY_NONE, NULL);
return 0;
error:
ma_tls_set_error(mysql);
return 1;
}
void *ma_tls_init(MYSQL *mysql)
{
SSL *ssl= NULL;
SSL_CTX *ctx= NULL;
long default_options= SSL_OP_ALL |
SSL_OP_NO_SSLv2 |
SSL_OP_NO_SSLv3;
long options= 0;
pthread_mutex_lock(&LOCK_openssl_config);
#if OPENSSL_VERSION_NUMBER >= 0x10100000L
if (!(ctx= SSL_CTX_new(TLS_client_method())))
#else
if (!(ctx= SSL_CTX_new(SSLv23_client_method())))
#endif
goto error;
if (mysql->options.extension)
options= ma_tls_version_options(mysql->options.extension->tls_version);
SSL_CTX_set_options(ctx, options ? options : default_options);
if (ma_tls_set_certs(mysql, ctx))
{
goto error;
}
if (!(ssl= SSL_new(ctx)))
goto error;
if (!SSL_set_app_data(ssl, mysql))
goto error;
pthread_mutex_unlock(&LOCK_openssl_config);
return (void *)ssl;
error:
pthread_mutex_unlock(&LOCK_openssl_config);
if (ctx)
SSL_CTX_free(ctx);
if (ssl)
SSL_free(ssl);
return NULL;
}
my_bool ma_tls_connect(MARIADB_TLS *ctls)
{
SSL *ssl = (SSL *)ctls->ssl;
my_bool blocking, try_connect= 1;
MYSQL *mysql;
MARIADB_PVIO *pvio;
int rc;
#ifdef OPENSSL_USE_BIOMETHOD
BIO_METHOD *bio_method= NULL;
BIO *bio;
#endif
mysql= (MYSQL *)SSL_get_app_data(ssl);
pvio= mysql->net.pvio;
/* Set socket to non blocking if not already set */
if (!(blocking= pvio->methods->is_blocking(pvio)))
pvio->methods->blocking(pvio, FALSE, 0);
SSL_clear(ssl);
#ifdef OPENSSL_USE_BIOMETHOD
bio= BIO_new(&ma_BIO_method);
bio->ptr= pvio;
SSL_set_bio(ssl, bio, bio);
BIO_set_fd(bio, mysql_get_socket(mysql), BIO_NOCLOSE);
#else
SSL_set_fd(ssl, (int)mysql_get_socket(mysql));
#endif
while (try_connect && (rc= SSL_connect(ssl)) == -1)
{
switch((SSL_get_error(ssl, rc))) {
case SSL_ERROR_WANT_READ:
if (pvio->methods->wait_io_or_timeout(pvio, TRUE, mysql->options.connect_timeout) < 1)
try_connect= 0;
break;
case SSL_ERROR_WANT_WRITE:
if (pvio->methods->wait_io_or_timeout(pvio, TRUE, mysql->options.connect_timeout) < 1)
try_connect= 0;
break;
default:
try_connect= 0;
}
}
/* In case handshake failed or if a root certificate (ca) was specified,
we need to check the result code of X509 verification. A detailed check
of the peer certificate (hostname checking will follow later) */
if (rc != 1 ||
(mysql->client_flag & CLIENT_SSL_VERIFY_SERVER_CERT) ||
(mysql->options.ssl_ca || mysql->options.ssl_capath))
{
long x509_err= SSL_get_verify_result(ssl);
if (x509_err != X509_V_OK)
{
my_set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN,
ER(CR_SSL_CONNECTION_ERROR), X509_verify_cert_error_string(x509_err));
/* restore blocking mode */
if (!blocking)
pvio->methods->blocking(pvio, FALSE, 0);
return 1;
} else if (rc != 1) {
ma_tls_set_error(mysql);
return 1;
}
}
pvio->ctls->ssl= ctls->ssl= (void *)ssl;
return 0;
}
static my_bool
ma_tls_async_check_result(int res, struct mysql_async_context *b, SSL *ssl)
{
int ssl_err;
b->events_to_wait_for= 0;
if (res >= 0)
return 1;
ssl_err= SSL_get_error(ssl, res);
if (ssl_err == SSL_ERROR_WANT_READ)
b->events_to_wait_for|= MYSQL_WAIT_READ;
else if (ssl_err == SSL_ERROR_WANT_WRITE)
b->events_to_wait_for|= MYSQL_WAIT_WRITE;
else
return 1;
if (b->suspend_resume_hook)
(*b->suspend_resume_hook)(TRUE, b->suspend_resume_hook_user_data);
my_context_yield(&b->async_context);
if (b->suspend_resume_hook)
(*b->suspend_resume_hook)(FALSE, b->suspend_resume_hook_user_data);
return 0;
}
ssize_t ma_tls_read_async(MARIADB_PVIO *pvio,
const unsigned char *buffer,
size_t length)
{
int res;
struct mysql_async_context *b= pvio->mysql->options.extension->async_context;
MARIADB_TLS *ctls= pvio->ctls;
for (;;)
{
res= SSL_read((SSL *)ctls->ssl, (void *)buffer, (int)length);
if (ma_tls_async_check_result(res, b, (SSL *)ctls->ssl))
return res;
}
}
ssize_t ma_tls_write_async(MARIADB_PVIO *pvio,
const unsigned char *buffer,
size_t length)
{
int res;
struct mysql_async_context *b= pvio->mysql->options.extension->async_context;
MARIADB_TLS *ctls= pvio->ctls;
for (;;)
{
res= SSL_write((SSL *)ctls->ssl, (void *)buffer, (int)length);
if (ma_tls_async_check_result(res, b, (SSL *)ctls->ssl))
return res;
}
}
ssize_t ma_tls_read(MARIADB_TLS *ctls, const uchar* buffer, size_t length)
{
int rc;
MARIADB_PVIO *pvio= ctls->pvio;
while ((rc= SSL_read((SSL *)ctls->ssl, (void *)buffer, (int)length)) < 0)
{
int error= SSL_get_error((SSL *)ctls->ssl, rc);
if (error != SSL_ERROR_WANT_READ)
return rc;
if (pvio->methods->wait_io_or_timeout(pvio, TRUE, pvio->mysql->options.read_timeout) < 1)
return rc;
}
return rc;
}
ssize_t ma_tls_write(MARIADB_TLS *ctls, const uchar* buffer, size_t length)
{
int rc;
MARIADB_PVIO *pvio= ctls->pvio;
while ((rc= SSL_write((SSL *)ctls->ssl, (void *)buffer, (int)length)) <= 0)
{
int error= SSL_get_error((SSL *)ctls->ssl, rc);
if (error != SSL_ERROR_WANT_WRITE)
return rc;
if (pvio->methods->wait_io_or_timeout(pvio, TRUE, pvio->mysql->options.write_timeout) < 1)
return rc;
}
return rc;
}
my_bool ma_tls_close(MARIADB_TLS *ctls)
{
int i, rc;
SSL *ssl;
SSL_CTX *ctx= NULL;
if (!ctls || !ctls->ssl)
return 1;
ssl= (SSL *)ctls->ssl;
ctx= SSL_get_SSL_CTX(ssl);
if (ctx)
SSL_CTX_free(ctx);
SSL_set_quiet_shutdown(ssl, 1);
/* 2 x pending + 2 * data = 4 */
for (i=0; i < 4; i++)
if ((rc= SSL_shutdown(ssl)))
break;
/* Since we transferred ownership of BIO to ssl, BIO will
automatically freed - no need for an explicit BIO_free_all */
SSL_free(ssl);
ctls->ssl= NULL;
return rc;
}
int ma_tls_verify_server_cert(MARIADB_TLS *ctls)
{
X509 *cert;
MYSQL *mysql;
SSL *ssl;
MARIADB_PVIO *pvio;
#if !defined(HAVE_OPENSSL_CHECK_HOST)
X509_NAME *x509sn;
int cn_pos;
X509_NAME_ENTRY *cn_entry;
ASN1_STRING *cn_asn1;
const char *cn_str;
#endif
if (!ctls || !ctls->ssl)
return 1;
ssl= (SSL *)ctls->ssl;
mysql= (MYSQL *)SSL_get_app_data(ssl);
pvio= mysql->net.pvio;
if (!mysql->host)
{
pvio->set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN,
ER(CR_SSL_CONNECTION_ERROR), "Invalid (empty) hostname");
return 1;
}
if (!(cert= SSL_get_peer_certificate(ssl)))
{
pvio->set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN,
ER(CR_SSL_CONNECTION_ERROR), "Unable to get server certificate");
return 1;
}
#ifdef HAVE_OPENSSL_CHECK_HOST
if (X509_check_host(cert, mysql->host, 0, 0, 0) != 1
&& X509_check_ip_asc(cert, mysql->host, 0) != 1)
goto error;
#else
x509sn= X509_get_subject_name(cert);
if ((cn_pos= X509_NAME_get_index_by_NID(x509sn, NID_commonName, -1)) < 0)
goto error;
if (!(cn_entry= X509_NAME_get_entry(x509sn, cn_pos)))
goto error;
if (!(cn_asn1 = X509_NAME_ENTRY_get_data(cn_entry)))
goto error;
cn_str = (char *)ASN1_STRING_data(cn_asn1);
/* Make sure there is no embedded \0 in the CN */
if ((size_t)ASN1_STRING_length(cn_asn1) != strlen(cn_str))
goto error;
if (strcmp(cn_str, mysql->host))
goto error;
#endif
X509_free(cert);
return 0;
error:
X509_free(cert);
pvio->set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN,
ER(CR_SSL_CONNECTION_ERROR), "Validation of SSL server certificate failed");
return 1;
}
const char *ma_tls_get_cipher(MARIADB_TLS *ctls)
{
if (!ctls || !ctls->ssl)
return NULL;
return SSL_get_cipher_name(ctls->ssl);
}
unsigned int ma_tls_get_finger_print(MARIADB_TLS *ctls, char *fp, unsigned int len)
{
X509 *cert= NULL;
MYSQL *mysql;
unsigned int fp_len;
if (!ctls || !ctls->ssl)
return 0;
mysql= SSL_get_app_data(ctls->ssl);
if (!(cert= SSL_get_peer_certificate(ctls->ssl)))
{
my_set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN,
ER(CR_SSL_CONNECTION_ERROR),
"Unable to get server certificate");
goto end;
}
if (len < EVP_MAX_MD_SIZE)
{
my_set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN,
ER(CR_SSL_CONNECTION_ERROR),
"Finger print buffer too small");
goto end;
}
if (!X509_digest(cert, EVP_sha1(), (unsigned char *)fp, &fp_len))
{
my_set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN,
ER(CR_SSL_CONNECTION_ERROR),
"invalid finger print of server certificate");
goto end;
}
X509_free(cert);
return (fp_len);
end:
X509_free(cert);
return 0;
}
int ma_tls_get_protocol_version(MARIADB_TLS *ctls)
{
if (!ctls || !ctls->ssl)
return -1;
return SSL_version(ctls->ssl) & 0xFF;
}

View File

@@ -0,0 +1,88 @@
/*
Copyright (C) 2018 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
*/
#include <ma_global.h>
#include <ma_crypt.h>
#include <openssl/evp.h>
static const EVP_MD *ma_hash_get_algorithm(unsigned int alg)
{
switch(alg)
{
case MA_HASH_MD5:
return EVP_md5();
case MA_HASH_SHA1:
return EVP_sha1();
case MA_HASH_SHA224:
return EVP_sha224();
case MA_HASH_SHA256:
return EVP_sha256();
case MA_HASH_SHA384:
return EVP_sha384();
case MA_HASH_SHA512:
return EVP_sha512();
case MA_HASH_RIPEMD160:
return EVP_ripemd160();
default:
return NULL;
}
}
MA_HASH_CTX *ma_hash_new(unsigned int algorithm, MA_HASH_CTX *unused __attribute__((unused)))
{
EVP_MD_CTX *ctx= NULL;
const EVP_MD *evp_md= ma_hash_get_algorithm(algorithm);
/* unknown or unsupported hash algorithm */
if (!evp_md)
return NULL;
#if OPENSSL_VERSION_NUMBER >= 0x10100000L
if (!(ctx= EVP_MD_CTX_new()))
#else
if (!(ctx= EVP_MD_CTX_create()))
#endif
return NULL;
if (!EVP_DigestInit(ctx, evp_md))
{
ma_hash_free(ctx);
return NULL;
}
return ctx;
}
void ma_hash_free(MA_HASH_CTX *ctx)
{
if (ctx)
#if OPENSSL_VERSION_NUMBER >= 0x10100000L
EVP_MD_CTX_free(ctx);
#else
EVP_MD_CTX_destroy(ctx);
#endif
}
void ma_hash_input(MA_HASH_CTX *ctx,
const unsigned char *buffer,
size_t len)
{
EVP_DigestUpdate(ctx, buffer, len);
}
void ma_hash_result(MA_HASH_CTX *ctx, unsigned char *digest)
{
EVP_DigestFinal_ex(ctx, digest, NULL);
}

562
vendor/MDBC/libmariadb/secure/schannel.c vendored Normal file
View File

@@ -0,0 +1,562 @@
/************************************************************************************
Copyright (C) 2014 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
*************************************************************************************/
#include "ma_schannel.h"
#include "schannel_certs.h"
#include <string.h>
extern my_bool ma_tls_initialized;
char tls_library_version[] = "Schannel";
#define PROT_SSL3 1
#define PROT_TLS1_0 2
#define PROT_TLS1_2 4
#define PROT_TLS1_3 8
static struct
{
DWORD cipher_id;
DWORD protocol;
const char *iana_name;
const char *openssl_name;
ALG_ID algs[4]; /* exchange, encryption, hash, signature */
}
cipher_map[] =
{
{
0x0002,
PROT_TLS1_0 | PROT_TLS1_2 | PROT_SSL3,
"TLS_RSA_WITH_NULL_SHA", "NULL-SHA",
{ CALG_RSA_KEYX, 0, CALG_SHA1, CALG_RSA_SIGN }
},
{
0x0004,
PROT_TLS1_0 | PROT_TLS1_2 | PROT_SSL3,
"TLS_RSA_WITH_RC4_128_MD5", "RC4-MD5",
{ CALG_RSA_KEYX, CALG_RC4, CALG_MD5, CALG_RSA_SIGN }
},
{
0x0005,
PROT_TLS1_0 | PROT_TLS1_2 | PROT_SSL3,
"TLS_RSA_WITH_RC4_128_SHA", "RC4-SHA",
{ CALG_RSA_KEYX, CALG_RC4, CALG_SHA1, CALG_RSA_SIGN }
},
{
0x000A,
PROT_SSL3,
"TLS_RSA_WITH_3DES_EDE_CBC_SHA", "DES-CBC3-SHA",
{CALG_RSA_KEYX, CALG_3DES, CALG_SHA1, CALG_DSS_SIGN}
},
{
0x0013,
PROT_TLS1_0 | PROT_TLS1_2 | PROT_SSL3,
"TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA", "EDH-DSS-DES-CBC3-SHA",
{ CALG_DH_EPHEM, CALG_3DES, CALG_SHA1, CALG_DSS_SIGN }
},
{
0x002F,
PROT_SSL3 | PROT_TLS1_0 | PROT_TLS1_2,
"TLS_RSA_WITH_AES_128_CBC_SHA", "AES128-SHA",
{ CALG_RSA_KEYX, CALG_AES_128, CALG_SHA, CALG_RSA_SIGN}
},
{
0x0032,
PROT_TLS1_0 | PROT_TLS1_2,
"TLS_DHE_DSS_WITH_AES_128_CBC_SHA", "DHE-DSS-AES128-SHA",
{ CALG_DH_EPHEM, CALG_AES_128, CALG_SHA1, CALG_RSA_SIGN }
},
{
0x0033,
PROT_TLS1_0 | PROT_TLS1_2,
"TLS_DHE_RSA_WITH_AES_128_CBC_SHA", "DHE-RSA-AES128-SHA",
{ CALG_DH_EPHEM, CALG_AES_128, CALG_SHA1, CALG_RSA_SIGN }
},
{
0x0035,
PROT_TLS1_0 | PROT_TLS1_2,
"TLS_RSA_WITH_AES_256_CBC_SHA", "AES256-SHA",
{ CALG_RSA_KEYX, CALG_AES_256, CALG_SHA1, CALG_RSA_SIGN }
},
{
0x0038,
PROT_TLS1_0 | PROT_TLS1_2,
"TLS_DHE_DSS_WITH_AES_256_CBC_SHA", "DHE-DSS-AES256-SHA",
{ CALG_DH_EPHEM, CALG_AES_256, CALG_SHA1, CALG_DSS_SIGN }
},
{
0x0039,
PROT_TLS1_0 | PROT_TLS1_2,
"TLS_DHE_RSA_WITH_AES_256_CBC_SHA", "DHE-RSA-AES256-SHA",
{ CALG_DH_EPHEM, CALG_AES_256, CALG_SHA1, CALG_RSA_SIGN }
},
{
0x003B,
PROT_TLS1_2,
"TLS_RSA_WITH_NULL_SHA256", "NULL-SHA256",
{ CALG_RSA_KEYX, 0, CALG_SHA_256, CALG_RSA_SIGN }
},
{
0x003C,
PROT_TLS1_2,
"TLS_RSA_WITH_AES_128_CBC_SHA256", "AES128-SHA256",
{ CALG_RSA_KEYX, CALG_AES_128, CALG_SHA_256, CALG_RSA_SIGN }
},
{
0x003D,
PROT_TLS1_2,
"TLS_RSA_WITH_AES_256_CBC_SHA256", "AES256-SHA256",
{ CALG_RSA_KEYX, CALG_AES_256, CALG_SHA_256, CALG_RSA_SIGN }
},
{
0x0040,
PROT_TLS1_2,
"TLS_DHE_DSS_WITH_AES_128_CBC_SHA256", "DHE-DSS-AES128-SHA256",
{ CALG_DH_EPHEM, CALG_AES_128, CALG_SHA_256, CALG_DSS_SIGN }
},
{
0x009C,
PROT_TLS1_2,
"TLS_RSA_WITH_AES_128_GCM_SHA256", "AES128-GCM-SHA256",
{ CALG_RSA_KEYX, CALG_AES_128, CALG_SHA_256, CALG_RSA_SIGN }
},
{
0x009D,
PROT_TLS1_2,
"TLS_RSA_WITH_AES_256_GCM_SHA384", "AES256-GCM-SHA384",
{ CALG_RSA_KEYX, CALG_AES_256, CALG_SHA_384, CALG_RSA_SIGN }
},
{
0x009E,
PROT_TLS1_2,
"TLS_DHE_RSA_WITH_AES_128_GCM_SHA256", "DHE-RSA-AES128-GCM-SHA256",
{ CALG_DH_EPHEM, CALG_AES_128, CALG_SHA_256, CALG_RSA_SIGN }
},
{
0x009F,
PROT_TLS1_2,
"TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", "DHE-RSA-AES256-GCM-SHA384",
{ CALG_DH_EPHEM, CALG_AES_256, CALG_SHA_384, CALG_RSA_SIGN }
},
{
0xC027,
PROT_TLS1_2,
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256", "ECDHE-RSA-AES128-SHA256",
{ CALG_ECDH, CALG_AES_128, CALG_SHA_256, CALG_RSA_SIGN }
},
{
0xC028,
PROT_TLS1_2,
"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384", "ECDHE-RSA-AES256-SHA384",
{ CALG_ECDH, CALG_AES_256, CALG_SHA_384, CALG_RSA_SIGN }
}
};
#define MAX_ALG_ID 50
extern void ma_schannel_set_sec_error(MARIADB_PVIO *pvio, DWORD ErrorNo);
/*
Initializes SSL and allocate global
context SSL_context
SYNOPSIS
ma_tls_start
RETURN VALUES
0 success
1 error
*/
int ma_tls_start(char *errmsg, size_t errmsg_len)
{
ma_tls_initialized = TRUE;
return 0;
}
/*
Release SSL and free resources
Will be automatically executed by
mysql_server_end() function
SYNOPSIS
ma_tls_end()
void
RETURN VALUES
void
*/
void ma_tls_end()
{
return;
}
/* {{{ 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;
MARIADB_PVIO *pvio= ctls->pvio;
char errmsg[256];
if (!certfile && keyfile)
certfile= keyfile;
if (!keyfile && certfile)
keyfile= certfile;
if (!certfile)
return 0;
*cert_ctx = schannel_create_cert_context(certfile, keyfile, errmsg, sizeof(errmsg));
if (!*cert_ctx)
{
pvio->set_error(pvio->mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, "SSL connection error: %s", errmsg);
return 1;
}
return 0;
}
/* }}} */
/* {{{ void *ma_tls_init(MARIADB_TLS *ctls, MYSQL *mysql) */
void *ma_tls_init(MYSQL *mysql)
{
SC_CTX *sctx = (SC_CTX *)LocalAlloc(LMEM_ZEROINIT, sizeof(SC_CTX));
if (sctx)
{
SecInvalidateHandle(&sctx->CredHdl);
SecInvalidateHandle(&sctx->hCtxt);
}
return sctx;
}
/* }}} */
/*
Maps between openssl suite names and schannel alg_ids.
Every suite has 4 algorithms (for exchange, encryption, hash and signing).
The input string is a set of suite names (openssl), separated
by ':'
The output is written into the array 'arr' of size 'arr_size'
The function returns number of elements written to the 'arr'.
*/
static struct _tls_version {
const char *tls_version;
DWORD protocol;
} tls_version[]= {
{"TLSv1.0", PROT_TLS1_0},
{"TLSv1.2", PROT_TLS1_2},
{"TLSv1.3", PROT_TLS1_3},
{"SSLv3", PROT_SSL3}
};
/* The following list was produced with OpenSSL 1.1.1j
by executing `openssl ciphers -V`. */
static struct {
DWORD dwCipherSuite;
const char *openssl_name;
} openssl_ciphers[] = {
{0x002F, "AES128-SHA"},
{0x0033, "DHE-RSA-AES128-SHA"},
{0x0035, "AES256-SHA"},
{0x0039, "DHE-RSA-AES256-SHA"},
{0x003C, "AES128-SHA256"},
{0x003D, "AES256-SHA256"},
{0x0067, "DHE-RSA-AES128-SHA256"},
{0x006B, "DHE-RSA-AES256-SHA256"},
{0x008C, "PSK-AES128-CBC-SHA"},
{0x008D, "PSK-AES256-CBC-SHA"},
{0x0090, "DHE-PSK-AES128-CBC-SHA"},
{0x0091, "DHE-PSK-AES256-CBC-SHA"},
{0x0094, "RSA-PSK-AES128-CBC-SHA"},
{0x0095, "RSA-PSK-AES256-CBC-SHA"},
{0x009C, "AES128-GCM-SHA256"},
{0x009D, "AES256-GCM-SHA384"},
{0x009E, "DHE-RSA-AES128-GCM-SHA256"},
{0x009F, "DHE-RSA-AES256-GCM-SHA384"},
{0x00A8, "PSK-AES128-GCM-SHA256"},
{0x00A9, "PSK-AES256-GCM-SHA384"},
{0x00AA, "DHE-PSK-AES128-GCM-SHA256"},
{0x00AB, "DHE-PSK-AES256-GCM-SHA384"},
{0x00AC, "RSA-PSK-AES128-GCM-SHA256"},
{0x00AD, "RSA-PSK-AES256-GCM-SHA384"},
{0x00AE, "PSK-AES128-CBC-SHA256"},
{0x00AF, "PSK-AES256-CBC-SHA384"},
{0x00B2, "DHE-PSK-AES128-CBC-SHA256"},
{0x00B3, "DHE-PSK-AES256-CBC-SHA384"},
{0x00B6, "RSA-PSK-AES128-CBC-SHA256"},
{0x00B7, "RSA-PSK-AES256-CBC-SHA384"},
{0x1301, "TLS_AES_128_GCM_SHA256"},
{0x1302, "TLS_AES_256_GCM_SHA384"},
{0x1303, "TLS_CHACHA20_POLY1305_SHA256"},
{0xC009, "ECDHE-ECDSA-AES128-SHA"},
{0xC00A, "ECDHE-ECDSA-AES256-SHA"},
{0xC013, "ECDHE-RSA-AES128-SHA"},
{0xC014, "ECDHE-RSA-AES256-SHA"},
{0xC01D, "SRP-AES-128-CBC-SHA"},
{0xC01E, "SRP-RSA-AES-128-CBC-SHA"},
{0xC020, "SRP-AES-256-CBC-SHA"},
{0xC021, "SRP-RSA-AES-256-CBC-SHA"},
{0xC023, "ECDHE-ECDSA-AES128-SHA256"},
{0xC024, "ECDHE-ECDSA-AES256-SHA384"},
{0xC027, "ECDHE-RSA-AES128-SHA256"},
{0xC028, "ECDHE-RSA-AES256-SHA384"},
{0xC02B, "ECDHE-ECDSA-AES128-GCM-SHA256"},
{0xC02C, "ECDHE-ECDSA-AES256-GCM-SHA384"},
{0xC02F, "ECDHE-RSA-AES128-GCM-SHA256"},
{0xC030, "ECDHE-RSA-AES256-GCM-SHA384"},
{0xC035, "ECDHE-PSK-AES128-CBC-SHA"},
{0xC036, "ECDHE-PSK-AES256-CBC-SHA"},
{0xC037, "ECDHE-PSK-AES128-CBC-SHA256"},
{0xC038, "ECDHE-PSK-AES256-CBC-SHA384"},
{0xCCA8, "ECDHE-RSA-CHACHA20-POLY1305"},
{0xCCA9, "ECDHE-ECDSA-CHACHA20-POLY1305"},
{0xCCAA, "DHE-RSA-CHACHA20-POLY1305"},
{0xCCAB, "PSK-CHACHA20-POLY1305"},
{0xCCAC, "ECDHE-PSK-CHACHA20-POLY1305"},
{0xCCAD, "DHE-PSK-CHACHA20-POLY1305"},
{0xCCAE, "RSA-PSK-CHACHA20-POLY1305"}
};
static size_t set_cipher(char * cipher_str, DWORD protocol, ALG_ID *arr , size_t arr_size)
{
char *token = strtok(cipher_str, ":");
size_t pos = 0;
while (token)
{
size_t i;
for(i = 0; i < sizeof(cipher_map)/sizeof(cipher_map[0]) ; i++)
{
if((pos + 4 < arr_size && strcmp(cipher_map[i].openssl_name, token) == 0) ||
(cipher_map[i].protocol <= protocol))
{
memcpy(arr + pos, cipher_map[i].algs, sizeof(ALG_ID)* 4);
pos += 4;
break;
}
}
token = strtok(NULL, ":");
}
return pos;
}
my_bool ma_tls_connect(MARIADB_TLS *ctls)
{
MYSQL *mysql;
SCHANNEL_CRED Cred = {0};
MARIADB_PVIO *pvio;
my_bool rc= 1;
SC_CTX *sctx;
SECURITY_STATUS sRet;
ALG_ID AlgId[MAX_ALG_ID];
size_t i;
DWORD protocol = 0;
int verify_certs;
const CERT_CONTEXT* cert_context = NULL;
if (!ctls)
return 1;
pvio= ctls->pvio;
sctx= (SC_CTX *)ctls->ssl;
if (!pvio || !sctx)
return 1;
mysql= pvio->mysql;
if (!mysql)
return 1;
/* Set cipher */
if (mysql->options.ssl_cipher)
{
/* check if a protocol was specified as a cipher:
* In this case don't allow cipher suites which belong to newer protocols
* Please note: There are no cipher suites for TLS1.1
*/
for (i = 0; i < sizeof(tls_version) / sizeof(tls_version[0]); i++)
{
if (!_stricmp(mysql->options.ssl_cipher, tls_version[i].tls_version))
protocol |= tls_version[i].protocol;
}
memset(AlgId, 0, sizeof(AlgId));
Cred.cSupportedAlgs = (DWORD)set_cipher(mysql->options.ssl_cipher, protocol, AlgId, MAX_ALG_ID);
if (Cred.cSupportedAlgs)
{
Cred.palgSupportedAlgs = AlgId;
}
else if (!protocol)
{
ma_schannel_set_sec_error(pvio, SEC_E_ALGORITHM_MISMATCH);
goto end;
}
}
Cred.dwVersion= SCHANNEL_CRED_VERSION;
Cred.dwFlags = SCH_CRED_NO_SERVERNAME_CHECK | SCH_CRED_NO_DEFAULT_CREDS | SCH_CRED_MANUAL_CRED_VALIDATION;
if (mysql->options.extension && mysql->options.extension->tls_version)
{
if (strstr(mysql->options.extension->tls_version, "TLSv1.0"))
Cred.grbitEnabledProtocols|= SP_PROT_TLS1_0_CLIENT;
if (strstr(mysql->options.extension->tls_version, "TLSv1.1"))
Cred.grbitEnabledProtocols|= SP_PROT_TLS1_1_CLIENT;
if (strstr(mysql->options.extension->tls_version, "TLSv1.2"))
Cred.grbitEnabledProtocols|= SP_PROT_TLS1_2_CLIENT;
}
if (!Cred.grbitEnabledProtocols)
Cred.grbitEnabledProtocols = SP_PROT_TLS1_0_CLIENT | SP_PROT_TLS1_1_CLIENT | SP_PROT_TLS1_2_CLIENT;
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;
}
if (ma_schannel_client_handshake(ctls) != SEC_E_OK)
goto end;
verify_certs = mysql->options.ssl_ca || mysql->options.ssl_capath ||
(mysql->client_flag & CLIENT_SSL_VERIFY_SERVER_CERT);
if (verify_certs)
{
if (!ma_schannel_verify_certs(ctls, (mysql->client_flag & CLIENT_SSL_VERIFY_SERVER_CERT)))
goto end;
}
rc = 0;
end:
if (cert_context)
schannel_free_cert_context(cert_context);
return rc;
}
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->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)
return -1; /* Do not handle renegotiate yet */
return (status == SEC_E_OK)? (ssize_t)dlength : -1;
}
ssize_t ma_tls_write(MARIADB_TLS *ctls, const uchar* buffer, size_t length)
{
MARIADB_PVIO *pvio= ctls->pvio;
ssize_t rc, wlength= 0;
ssize_t remain= length;
while (remain > 0)
{
if ((rc= ma_schannel_write_encrypt(pvio, (uchar *)buffer + wlength, remain)) <= 0)
return rc;
wlength+= rc;
remain-= rc;
}
return length;
}
/* {{{ my_bool ma_tls_close(MARIADB_PVIO *pvio) */
my_bool ma_tls_close(MARIADB_TLS *ctls)
{
SC_CTX *sctx= (SC_CTX *)ctls->ssl;
if (sctx)
{
LocalFree(sctx->IoBuffer);
if (SecIsValidHandle(&sctx->CredHdl))
FreeCredentialHandle(&sctx->CredHdl);
if (SecIsValidHandle(&sctx->hCtxt))
DeleteSecurityContext(&sctx->hCtxt);
}
LocalFree(sctx);
return 0;
}
/* }}} */
int ma_tls_verify_server_cert(MARIADB_TLS *ctls)
{
/* Done elsewhere */
return 0;
}
static const char *cipher_name(const SecPkgContext_CipherInfo *CipherInfo)
{
size_t i;
for(i = 0; i < sizeof(openssl_ciphers)/sizeof(openssl_ciphers[0]) ; i++)
{
if (CipherInfo->dwCipherSuite == openssl_ciphers[i].dwCipherSuite)
return openssl_ciphers[i].openssl_name;
}
return "";
};
const char *ma_tls_get_cipher(MARIADB_TLS *ctls)
{
SecPkgContext_CipherInfo CipherInfo = { SECPKGCONTEXT_CIPHERINFO_V1 };
SECURITY_STATUS sRet;
SC_CTX *sctx;
if (!ctls || !ctls->ssl)
return NULL;
sctx= (SC_CTX *)ctls->ssl;
sRet= QueryContextAttributesA(&sctx->hCtxt, SECPKG_ATTR_CIPHER_INFO, (PVOID)&CipherInfo);
if (sRet != SEC_E_OK)
return NULL;
return cipher_name(&CipherInfo);
}
unsigned int ma_tls_get_finger_print(MARIADB_TLS *ctls, char *fp, unsigned int len)
{
SC_CTX *sctx= (SC_CTX *)ctls->ssl;
PCCERT_CONTEXT pRemoteCertContext = NULL;
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);
return len;
}

View File

@@ -0,0 +1,854 @@
/************************************************************************************
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 <winsock2.h>
#include "schannel_certs.h"
#include <malloc.h>
#include <stdio.h>
#include <string.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;
}
#define FAIL(...) \
do{\
status = get_last_error();\
ma_format_win32_error(errmsg, errmsg_len, status, __VA_ARGS__);\
goto cleanup;\
} while (0)
/*
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;
SECURITY_STATUS status = SEC_E_OK;
HANDLE file_handle = CreateFile(file, GENERIC_READ, FILE_SHARE_READ, NULL,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (file_handle == INVALID_HANDLE_VALUE)
{
FAIL("failed to open file '%s'", file);
}
if (!GetFileSizeEx(file_handle, &file_size))
{
FAIL("GetFileSizeEx failed on '%s'", file);
}
if (file_size.QuadPart > ULONG_MAX - 1)
{
SetLastError(SEC_E_INVALID_PARAMETER);
FAIL("file '%s' too large", file);
}
file_bufsize = (size_t)file_size.QuadPart;
file_buffer = (char*)LocalAlloc(0,file_bufsize + 1);
if (!file_buffer)
{
FAIL("LocalAlloc(0,%zu) failed", file_bufsize + 1);
}
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))
{
FAIL("ReadFile() failed to read file '%s'", file);
}
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);
}
if (status)
{
/* Some error happened. */
LocalFree(file_buffer);
file_buffer = NULL;
}
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_OK;
CRL_CONTEXT* crl_context = NULL;
CERT_CONTEXT* cert_context = NULL;
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)
{
SetLastError(SEC_E_INVALID_PARAMETER);
FAIL("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));
}
CERT_BLOB cert_blob;
void* context = NULL;
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))
{
FAIL("failed to extract certificate from PEM file '%s'",file);
}
if (!context)
{
SetLastError(SEC_E_INTERNAL_ERROR);
FAIL("unexpected result from CryptQueryObject(),cert_context is NULL"
" after successful completion, file '%s'",
file);
}
if (actual_content_type == CERT_QUERY_CONTENT_CERT)
{
CERT_CONTEXT* cert_context = (CERT_CONTEXT*)context;
if (!CertAddCertificateContextToStore(
trust_store, cert_context,
CERT_STORE_ADD_ALWAYS, NULL))
{
FAIL("CertAddCertificateContextToStore failed");
}
}
else if (actual_content_type == CERT_QUERY_CONTENT_CRL)
{
CRL_CONTEXT* crl_context = (CRL_CONTEXT*)context;
if (!CertAddCRLContextToStore(
trust_store, crl_context,
CERT_STORE_ADD_ALWAYS, NULL))
{
FAIL("CertAddCRLContextToStore() failed");
}
}
}
cleanup:
LocalFree(file_buffer);
if (cert_context)
CertFreeCertificateContext(cert_context);
if (crl_context)
CertFreeCRLContext(crl_context);
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 = INVALID_HANDLE_VALUE;
SECURITY_STATUS status = SEC_E_OK;
if ((dwAttr = GetFileAttributes(dir)) == INVALID_FILE_ATTRIBUTES)
{
SetLastError(SEC_E_INVALID_PARAMETER);
FAIL("directory '%s' does not exist", dir);
}
if (!(dwAttr & FILE_ATTRIBUTE_DIRECTORY))
{
SetLastError(SEC_E_INVALID_PARAMETER);
FAIL("'%s' is not a directory", dir);
}
sprintf_s(pattern, sizeof(pattern), "%s\\*", dir);
hFind = FindFirstFile(pattern, &ffd);
if (hFind == INVALID_HANDLE_VALUE)
{
FAIL("FindFirstFile(%s) failed",pattern);
}
do
{
if (ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
continue;
sprintf_s(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);
cleanup:
if (hFind != INVALID_HANDLE_VALUE)
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_OK;
*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)
{
FAIL("CertOpenStore failed for memory store");
}
}
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)
{
FAIL("CertOpenStore failed for system store");
}
store = CertDuplicateStore(system_store);
if (!store)
{
FAIL("CertDuplicateStore failed");
}
}
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))
{
SetLastError(SEC_E_INVALID_PARAMETER);
FAIL("no valid certificates were found, CAFile='%s', CAPath='%s'",
CAFile ? CAFile : "<not set>", CAPath ? CAPath : "<not set>");
}
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);
}
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 errmsg,
size_t errmsg_len)
{
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_OK;
if (pServerCert == NULL)
{
SetLastError(SEC_E_WRONG_PRINCIPAL);
FAIL("Invalid parameter pServerCert passed to VerifyServerCertificate");
}
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))
{
FAIL("CertCreateCertificateChainEngine failed");
}
}
if (!CertGetCertificateChain(
hChainEngine,
pServerCert,
NULL,
pServerCert->hCertStore,
&ChainPara,
dwRevocationCheckFlags,
NULL,
&pChainContext))
{
FAIL("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))
{
FAIL("CertVerifyCertificateChainPolicy failed");
}
if (PolicyStatus.dwError)
{
SetLastError(PolicyStatus.dwError);
FAIL("Server certificate validation failed");
}
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 occurred.
*/
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_OK;
wchar_t* wserver_name = NULL;
DWORD dwVerifyFlags;
DWORD dwRevocationFlags;
if (check_server_name)
{
int cchServerName = (int)strlen(server_name) + 1;
wserver_name = (wchar_t*)LocalAlloc(0,sizeof(wchar_t) * cchServerName);
if (!wserver_name)
{
FAIL("LocalAlloc() failed");
}
if (MultiByteToWideChar(CP_UTF8, 0, server_name, cchServerName, wserver_name, cchServerName) < 0)
{
FAIL("MultiByteToWideChar() failed");
}
}
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:
LocalFree(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_OK;
derbuf = LocalAlloc(0, derlen);
if (!derbuf)
{
FAIL("LocalAlloc failed");
}
if (!CryptStringToBinaryA(private_key_str, (DWORD)len, CRYPT_STRING_BASE64HEADER, derbuf, &derlen, NULL, NULL))
{
FAIL("Failed to convert BASE64 private key");
}
/*
To accommodate 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))
{
FAIL("Failed to parse private key");
}
}
else if (!CryptDecodeObjectEx(
X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
PKCS_RSA_PRIVATE_KEY,
derbuf, derlen,
CRYPT_DECODE_ALLOC_FLAG, NULL,
&keyblob, &keyblob_len))
{
FAIL("Failed to parse private key");
}
if (!CryptAcquireContext(&hProv, NULL, MS_ENHANCED_PROV, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT))
{
FAIL("CryptAcquireContext failed");
}
if (!CryptImportKey(hProv, keyblob, keyblob_len, 0, 0, (HCRYPTKEY*)&hKey))
{
FAIL("CryptImportKey failed");
}
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,
CERT_STORE_NO_CRYPT_RELEASE_FLAG,
&cert_key_context))
{
FAIL("CertSetCertificateContextProperty failed");
}
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;
SECURITY_STATUS status = SEC_E_OK;
/* Parse certificate */
pem_locate(cert_file_content, PEM_TYPE_CERTIFICATE,
&begin, &end);
if (!begin || !end)
{
SetLastError(SEC_E_INVALID_PARAMETER);
FAIL("Client certificate not found in PEM file");
}
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))
{
FAIL("Can't parse client certficate");
}
/* 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.*/
status = load_private_key(ctx, begin, (end - begin), errmsg, errmsg_len);
goto cleanup;
}
}
if (!begin || !end)
{
SetLastError(SEC_E_INVALID_PARAMETER);
FAIL("Client private key not found in PEM");
}
cleanup:
if (status && 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:
LocalFree(cert_file_content);
if (cert_file != key_file)
LocalFree(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);
HCRYPTPROV hProv = 0;
if (CertGetCertificateContextProperty(cert, CERT_KEY_CONTEXT_PROP_ID, &cert_key_context, &cbData))
{
hProv = cert_key_context.hCryptProv;
}
CertFreeCertificateContext(cert);
if (hProv)
{
CryptReleaseContext(cert_key_context.hCryptProv, 0);
}
}

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

View File

@@ -0,0 +1,103 @@
/*
Copyright (C) 2018 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
*/
#include <windows.h>
#include <bcrypt.h>
#include <ma_crypt.h>
#include <malloc.h>
BCRYPT_ALG_HANDLE Sha256Prov= 0;
BCRYPT_ALG_HANDLE Sha512Prov= 0;
BCRYPT_ALG_HANDLE RsaProv= 0;
static LPCWSTR ma_hash_get_algorithm(unsigned int alg, BCRYPT_ALG_HANDLE *algHdl)
{
switch(alg)
{
case MA_HASH_SHA256:
*algHdl= Sha256Prov;
return BCRYPT_SHA256_ALGORITHM;
case MA_HASH_SHA512:
*algHdl= Sha512Prov;
return BCRYPT_SHA512_ALGORITHM;
default:
*algHdl= 0;
return NULL;
}
}
MA_HASH_CTX *ma_hash_new(unsigned int algorithm, MA_HASH_CTX *ctx)
{
MA_HASH_CTX *newctx= ctx;
DWORD cbObjSize, cbData;
LPCWSTR alg;
BCRYPT_ALG_HANDLE algHdl= 0;
alg= ma_hash_get_algorithm(algorithm, &algHdl);
if (!alg || !algHdl)
return NULL;
if (BCryptGetProperty(algHdl, BCRYPT_OBJECT_LENGTH,
(PBYTE)&cbObjSize, sizeof(DWORD),
&cbData, 0) < 0)
goto error;
if (!newctx)
{
newctx= (MA_HASH_CTX *)calloc(1, sizeof(MA_HASH_CTX));
newctx->free_me= 1;
}
else
memset(newctx, 0, sizeof(MA_HASH_CTX));
newctx->hashObject= (PBYTE)malloc(cbObjSize);
newctx->digest_len= (DWORD)ma_hash_digest_size(algorithm);
BCryptCreateHash(algHdl, &newctx->hHash, newctx->hashObject, cbObjSize, NULL, 0, 0);
return newctx;
error:
if (newctx && !ctx)
free(newctx);
return NULL;
}
void ma_hash_free(MA_HASH_CTX *ctx)
{
if (ctx)
{
if (ctx->hHash)
BCryptDestroyHash(ctx->hHash);
if (ctx->hashObject)
free(ctx->hashObject);
if (ctx->free_me)
free(ctx);
}
}
void ma_hash_input(MA_HASH_CTX *ctx,
const unsigned char *buffer,
size_t len)
{
BCryptHashData(ctx->hHash, (PUCHAR)buffer, (LONG)len, 0);
}
void ma_hash_result(MA_HASH_CTX *ctx, unsigned char *digest)
{
BCryptFinishHash(ctx->hHash, digest, ctx->digest_len, 0);
}