From 43d743e75b7b747341b9a5c36a933b490548bebb Mon Sep 17 00:00:00 2001 From: Karen Arutyunov Date: Sat, 4 Nov 2017 01:17:16 +0300 Subject: Add implementation --- mysql/libmariadb/secure/ma_schannel.c | 1008 +++++++++++++++++++++++++++++++++ mysql/libmariadb/secure/ma_schannel.h | 87 +++ mysql/libmariadb/secure/schannel.c | 586 +++++++++++++++++++ 3 files changed, 1681 insertions(+) create mode 100644 mysql/libmariadb/secure/ma_schannel.c create mode 100644 mysql/libmariadb/secure/ma_schannel.h create mode 100644 mysql/libmariadb/secure/schannel.c (limited to 'mysql/libmariadb/secure') diff --git a/mysql/libmariadb/secure/ma_schannel.c b/mysql/libmariadb/secure/ma_schannel.c new file mode 100644 index 0000000..4d8af89 --- /dev/null +++ b/mysql/libmariadb/secure/ma_schannel.c @@ -0,0 +1,1008 @@ +/************************************************************************************ + 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 + 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 + +#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); + +/* {{{ void ma_schannel_set_sec_error */ +void ma_schannel_set_sec_error(MARIADB_PVIO *pvio, DWORD ErrorNo) +{ + MYSQL *mysql= pvio->mysql; + switch(ErrorNo) { + case SEC_E_ILLEGAL_MESSAGE: + pvio->set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, "SSL connection error: The message received was unexpected or badly formatted"); + break; + case SEC_E_UNTRUSTED_ROOT: + pvio->set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, "SSL connection error: Untrusted root certificate"); + break; + case SEC_E_BUFFER_TOO_SMALL: + pvio->set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, "SSL connection error: Buffer too small"); + break; + case SEC_E_CRYPTO_SYSTEM_INVALID: + pvio->set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, "SSL connection error: Cipher is not supported"); + break; + case SEC_E_INSUFFICIENT_MEMORY: + pvio->set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, "SSL connection error: Out of memory"); + break; + case SEC_E_OUT_OF_SEQUENCE: + pvio->set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, "SSL connection error: Invalid message sequence"); + break; + case SEC_E_DECRYPT_FAILURE: + pvio->set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, "SSL connection error: An error occured during decrypting data"); + break; + case SEC_I_INCOMPLETE_CREDENTIALS: + pvio->set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, "SSL connection error: Incomplete credentials"); + break; + case SEC_E_ENCRYPT_FAILURE: + pvio->set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, "SSL connection error: An error occured during encrypting data"); + break; + case SEC_I_CONTEXT_EXPIRED: + pvio->set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, "SSL connection error: Context expired "); + break; + case SEC_E_ALGORITHM_MISMATCH: + pvio->set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, "SSL connection error: no cipher match"); + break; + case SEC_E_NO_CREDENTIALS: + pvio->set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, "SSL connection error: no credentials"); + break; + case SEC_E_OK: + break; + case SEC_E_INTERNAL_ERROR: + if (GetLastError()) + ma_schannel_set_win_error(pvio); + else + pvio->set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, "The Local Security Authority cannot be contacted"); + break; + default: + pvio->set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, "Unknown SSL error (0x%x)", ErrorNo); + } +} +/* }}} */ + +/* {{{ void ma_schnnel_set_win_error */ +void ma_schannel_set_win_error(MARIADB_PVIO *pvio) +{ + ulong ssl_errno= 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; + } + /* todo: obtain error messge */ + 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",ssl_error_reason); + 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; + char *buffer= NULL; + DWORD dwBytesRead= 0; + LPBYTE der_buffer= NULL; + DWORD der_buffer_length; + + 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); + 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= LocalAlloc(0, *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); + 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); + 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); + goto end; + } + + *buffer_len= der_buffer_length; + LocalFree(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; +} +/* }}} */ + +/* {{{ 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); + +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); +end: + if (der_buffer) + LocalFree(der_buffer); + return ctx; +} +/* }}} */ + +/* {{{ my_bool ma_schannel_load_private_key(MARIADB_PVIO *pvio, CERT_CONTEXT *ctx, char *key_file) */ +/* + Load privte 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, CERT_CONTEXT *ctx, char *key_file) +{ + DWORD der_buffer_len= 0; + LPBYTE der_buffer= NULL; + 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; + + /* load private key into der binary object */ + if (!(der_buffer= ma_schannel_load_pem(pvio, key_file, &der_buffer_len))) + return 0; + + /* determine required buffer size for decoded private key */ + if (!CryptDecodeObjectEx(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, + PKCS_RSA_PRIVATE_KEY, + der_buffer, der_buffer_len, + 0, NULL, + NULL, &priv_key_len)) + { + ma_schannel_set_win_error(pvio); + goto end; + } + + /* allocate buffer for decoded private key */ + if (!(priv_key= LocalAlloc(0, priv_key_len))) + { + pvio->set_error(pvio->mysql, CR_OUT_OF_MEMORY, SQLSTATE_UNKNOWN, NULL); + goto end; + } + + /* decode */ + if (!CryptDecodeObjectEx(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, + PKCS_RSA_PRIVATE_KEY, + der_buffer, der_buffer_len, + 0, NULL, + priv_key, &priv_key_len)) + { + ma_schannel_set_win_error(pvio); + goto end; + } + + /* Acquire context */ + if (!CryptAcquireContext(&crypt_prov, NULL, MS_ENHANCED_PROV, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)) + { + ma_schannel_set_win_error(pvio); + 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); + goto end; + } + + kpi.hCryptProv= crypt_prov; + kpi.dwKeySpec = AT_KEYEXCHANGE; + kpi.cbSize= sizeof(kpi); + + /* assign private key to certificate context */ + if (CertSetCertificateContextProperty(ctx, CERT_KEY_CONTEXT_PROP_ID, 0, &kpi)) + rc= 1; + else + ma_schannel_set_win_error(pvio); + +end: + if (der_buffer) + LocalFree(der_buffer); + if (priv_key) + { + if (crypt_key) + CryptDestroyKey(crypt_key); + LocalFree(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) */ +/* + 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->ctxt, + 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->ctxt); + 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; + } + + cbIoBuffer = 0; + } +loopend: + if (FAILED(rc)) + { + ma_schannel_set_sec_error(pvio, rc); + DeleteSecurityContext(&sctx->ctxt); + } + 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->ctxt, + &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 successfull, we are able now to calculate payload */ + if ((sRet = QueryContextAttributes(&sctx->ctxt, 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: + LocalFree(sctx->IoBuffer); + sctx->IoBufferSize= 0; + FreeContextBuffer(BuffersOut.pvBuffer); + DeleteSecurityContext(&sctx->ctxt); + 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 occured +*/ + +SECURITY_STATUS ma_schannel_read_decrypt(MARIADB_PVIO *pvio, + PCredHandle phCreds, + CtxtHandle * phContext, + DWORD *DecryptLength, + uchar *ReadBuffer, + DWORD ReadBufferSize) +{ + ssize_t nbytes= 0; + DWORD dwOffset= 0; + SC_CTX *sctx; + SECURITY_STATUS sRet= SEC_E_INCOMPLETE_MESSAGE; + SecBufferDesc Msg; + SecBuffer Buffers[4], + *pData, *pExtra; + 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; + } + + 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= + Buffers[2].BufferType= + Buffers[3].BufferType= SECBUFFER_EMPTY; + + Msg.ulVersion= SECBUFFER_VERSION; // Version number + Msg.cBuffers= 4; + Msg.pBuffers= Buffers; + + sRet = DecryptMessage(phContext, &Msg, 0, NULL); + + if (sRet == SEC_E_INCOMPLETE_MESSAGE) + continue; /* Continue reading until full message arrives */ + + if (sRet != SEC_E_OK) + { + ma_schannel_set_sec_error(pvio, sRet); + return sRet; + } + + pData= pExtra= NULL; + for (i=0; i < 4; i++) + { + if (!pData && Buffers[i].BufferType == SECBUFFER_DATA) + pData= &Buffers[i]; + if (!pExtra && Buffers[i].BufferType == SECBUFFER_EXTRA) + pExtra= &Buffers[i]; + if (pData && pExtra) + break; + } + + if (pExtra) + { + /* Save preread encrypted data, will be processed next time.*/ + sctx->extraBuf.cbBuffer = pExtra->cbBuffer; + sctx->extraBuf.pvBuffer = pExtra->pvBuffer; + } + + if (pData && pData->cbBuffer) + { + /* + Copy at most ReadBufferSize bytes to output. + Store the rest (if any) to be processed next time. + */ + nbytes=MIN(pData->cbBuffer, ReadBufferSize); + memcpy((char *)ReadBuffer, pData->pvBuffer, nbytes); + + + sctx->dataBuf.cbBuffer = pData->cbBuffer - (DWORD)nbytes; + sctx->dataBuf.pvBuffer = (char *)pData->pvBuffer + nbytes; + + *DecryptLength = (DWORD)nbytes; + return SEC_E_OK; + } + else + { + /* + DecryptMessage() did not return data buffer. + According to MSDN, this happens sometimes and is normal. + We retry the read/decrypt in this case. + */ + dwOffset = 0; + } + } +} +/* }}} */ + +my_bool ma_schannel_verify_certs(MARIADB_TLS *ctls) +{ + SECURITY_STATUS sRet; + + 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 *crl_file= mysql->options.extension ? mysql->options.extension->ssl_crl : NULL; + PCCERT_CONTEXT pServerCert= NULL; + CRL_CONTEXT *crl_ctx= NULL; + CERT_CONTEXT *ca_ctx= NULL; + int ret= 0; + + if (!ca_file && !crl_file) + return 1; + + if (ca_file && !(ca_ctx = ma_schannel_create_cert_context(pvio, ca_file))) + 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) + { + ma_schannel_set_sec_error(pvio, sRet); + 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); + goto end; + } + + if (flags) + { + if ((flags & CERT_STORE_SIGNATURE_FLAG) != 0) + pvio->set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, "SSL connection error: Certificate signature check failed"); + else if ((flags & CERT_STORE_REVOCATION_FLAG) != 0) + pvio->set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, "SSL connection error: certificate was revoked"); + else if ((flags & CERT_STORE_TIME_VALIDITY_FLAG) != 0) + pvio->set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, "SSL connection error: certificate has expired"); + else + pvio->set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, "SSL connection error: Unknown error during certificate validation"); + goto end; + } + } + + + /* 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) + { + CertFreeCRLContext(crl_ctx); + } + if (ca_ctx) + { + CertFreeCertificateContext(ca_ctx); + } + if (pServerCert) + { + CertFreeCertificateContext(pServerCert); + } + 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 occured +*/ +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->ctxt, 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->ctxt, 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; + } +} +/* }}} */ diff --git a/mysql/libmariadb/secure/ma_schannel.h b/mysql/libmariadb/secure/ma_schannel.h new file mode 100644 index 0000000..08ff3e7 --- /dev/null +++ b/mysql/libmariadb/secure/ma_schannel.h @@ -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 + 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 +#include +#include +#include +#include + + +#include +#include + + +#include + +#include +#undef SECURITY_WIN32 +#include +#include + +#define SC_IO_BUFFER_SIZE 0x4000 + + +#include + +struct st_schannel { + HCERTSTORE cert_store; + CERT_CONTEXT *client_cert_ctx; + CredHandle CredHdl; + my_bool FreeCredHdl; + PUCHAR IoBuffer; + DWORD IoBufferSize; + SecPkgContext_StreamSizes Sizes; + CtxtHandle ctxt; + + /* 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; + +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, CERT_CONTEXT *ctx, char *key_file); +PCCRL_CONTEXT ma_schannel_create_crl_context(MARIADB_PVIO *pvio, const char *pem_file); +my_bool ma_schannel_verify_certs(MARIADB_TLS *ctls); +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, + DWORD *DecryptLength, + uchar *ReadBuffer, + DWORD ReadBufferSize); + + +#endif /* _ma_schannel_h_ */ diff --git a/mysql/libmariadb/secure/schannel.c b/mysql/libmariadb/secure/schannel.c new file mode 100644 index 0000000..2725172 --- /dev/null +++ b/mysql/libmariadb/secure/schannel.c @@ -0,0 +1,586 @@ +/************************************************************************************ + 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 + or write to the Free Software Foundation, Inc., + 51 Franklin St., Fifth Floor, Boston, MA 02110, USA + + *************************************************************************************/ +#include "ma_schannel.h" + +#pragma comment (lib, "crypt32.lib") +#pragma comment (lib, "secur32.lib") + +extern my_bool ma_tls_initialized; +char tls_library_version[TLS_VERSION_LENGTH]; + +#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 } + } + +}; + +#define MAX_ALG_ID 50 + +void ma_schannel_set_sec_error(MARIADB_PVIO *pvio, DWORD ErrorNo); +void ma_schannel_set_win_error(MYSQL *mysql); + +/* + 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) +{ + DWORD size; + DWORD handle; + + if ((size= GetFileVersionInfoSize("schannel.dll", &handle))) + { + LPBYTE VersionInfo; + if ((VersionInfo = (LPBYTE)malloc(size))) + { + unsigned int len; + VS_FIXEDFILEINFO *fileinfo; + + GetFileVersionInfo("schannel.dll", 0, size, VersionInfo); + VerQueryValue(VersionInfo, "\\", (LPVOID *)&fileinfo, &len); + snprintf(tls_library_version, TLS_VERSION_LENGTH - 1, "Schannel %d.%d.%d.%d\n", + HIWORD(fileinfo->dwFileVersionMS), + LOWORD(fileinfo->dwFileVersionMS), + HIWORD(fileinfo->dwFileVersionLS), + LOWORD(fileinfo->dwFileVersionLS)); + free(VersionInfo); + goto end; + } + } + /* this shouldn't happen anyway */ + strcpy(tls_library_version, "Schannel 0.0.0.0"); +end: + 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) +{ + 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; + + if (!certfile && keyfile) + certfile= keyfile; + if (!keyfile && certfile) + keyfile= certfile; + + if (!certfile) + return 0; + + if (!(sctx->client_cert_ctx = ma_schannel_create_cert_context(ctls->pvio, certfile))) + return 1; + + if (!ma_schannel_load_private_key(pvio, sctx->client_cert_ctx, keyfile)) + { + CertFreeCertificateContext(sctx->client_cert_ctx); + sctx->client_cert_ctx= NULL; + return 1; + } + return 0; +} +/* }}} */ + +/* {{{ 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)); + 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} +}; + +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; + MARIADB_PVIO *pvio; + my_bool rc= 1; + SC_CTX *sctx; + SECURITY_STATUS sRet; + ALG_ID AlgId[MAX_ALG_ID]; + WORD validTokens = 0; + + if (!ctls || !ctls->pvio) + return 1;; + + pvio= ctls->pvio; + sctx= (SC_CTX *)ctls->ssl; + + mysql= pvio->mysql; + + if (ma_tls_set_client_certs(ctls)) + goto end; + + ZeroMemory(&Cred, sizeof(SCHANNEL_CRED)); + + /* Set cipher */ + if (mysql->options.ssl_cipher) + { + int i; + DWORD protocol = 0; + + /* 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, MAX_ALG_ID * sizeof(ALG_ID)); + 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 (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")) + 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; + + if ((sRet= AcquireCredentialsHandleA(NULL, UNISP_NAME_A, SECPKG_CRED_OUTBOUND, + NULL, &Cred, NULL, NULL, &sctx->CredHdl, NULL)) != SEC_E_OK) + { + ma_schannel_set_sec_error(pvio, sRet); + goto end; + } + sctx->FreeCredHdl= 1; + + if (ma_schannel_client_handshake(ctls) != SEC_E_OK) + goto end; + + if (!ma_schannel_verify_certs(ctls)) + goto end; + + return 0; + +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; +} + +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); + 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) +{ + SC_CTX *sctx= (SC_CTX *)ctls->ssl; + 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) + { + if (sctx->IoBufferSize) + LocalFree(sctx->IoBuffer); + if (sctx->client_cert_ctx) + CertFreeCertificateContext(sctx->client_cert_ctx); + FreeCredentialHandle(&sctx->CredHdl); + DeleteSecurityContext(&sctx->ctxt); + } + LocalFree(sctx); + return 0; +} +/* }}} */ + +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) + { + DWORD len = strlen(p1); + /* check if given name contains wildcard */ + if (len && *p1 == '*') + { + DWORD 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; +} + +static const char *cipher_name(const SecPkgContext_CipherInfo *CipherInfo) +{ + int i; + + for(i = 0; i < sizeof(cipher_map)/sizeof(cipher_map[0]) ; i++) + { + if (CipherInfo->dwCipherSuite == cipher_map[i].cipher_id) + return cipher_map[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; + DWORD i= 0; + + if (!ctls || !ctls->ssl) + return NULL; + + sctx= (SC_CTX *)ctls->ssl; + + sRet= QueryContextAttributes(&sctx->ctxt, 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->ctxt, 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; +} -- cgit v1.1