aboutsummaryrefslogtreecommitdiff
path: root/mysql/libmariadb/secure
diff options
context:
space:
mode:
Diffstat (limited to 'mysql/libmariadb/secure')
-rw-r--r--mysql/libmariadb/secure/ma_schannel.c1008
-rw-r--r--mysql/libmariadb/secure/ma_schannel.h87
-rw-r--r--mysql/libmariadb/secure/schannel.c586
3 files changed, 1681 insertions, 0 deletions
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 <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 <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);
+
+/* {{{ 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 <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_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 <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"
+
+#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;
+}