diff options
Diffstat (limited to 'libmariadb/libmariadb/secure/ma_schannel.c')
-rw-r--r-- | libmariadb/libmariadb/secure/ma_schannel.c | 629 |
1 files changed, 629 insertions, 0 deletions
diff --git a/libmariadb/libmariadb/secure/ma_schannel.c b/libmariadb/libmariadb/secure/ma_schannel.c new file mode 100644 index 00000000..be4148c7 --- /dev/null +++ b/libmariadb/libmariadb/secure/ma_schannel.c @@ -0,0 +1,629 @@ +/************************************************************************************ + Copyright (C) 2014 MariaDB Corporation Ab + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not see <http://www.gnu.org/licenses> + or write to the Free Software Foundation, Inc., + 51 Franklin St., Fifth Floor, Boston, MA 02110, USA + + Author: Georg Richter + + *************************************************************************************/ +#include "ma_schannel.h" +#include "schannel_certs.h" +#include <assert.h> + +#define SC_IO_BUFFER_SIZE 0x4000 +#define MAX_SSL_ERR_LEN 100 + +#define SCHANNEL_PAYLOAD(A) ((A).cbMaximumMessage + (A).cbHeader + (A).cbTrailer) +void ma_schannel_set_win_error(MARIADB_PVIO *pvio, DWORD ErrorNo); + + + + +/* {{{ void ma_schannel_set_sec_error */ +void ma_schannel_set_sec_error(MARIADB_PVIO* pvio, DWORD ErrorNo) +{ + MYSQL* mysql = pvio->mysql; + if (ErrorNo != SEC_E_OK) + mysql->net.extension->extended_errno = ErrorNo; + if (ErrorNo == SEC_E_INTERNAL_ERROR && GetLastError()) + { + ma_schannel_set_win_error(pvio, GetLastError()); + return; + } + ma_schannel_set_win_error(pvio, ErrorNo); +} +/* }}} */ + +#include "win32_errmsg.h" +/* {{{ void ma_schnnel_set_win_error */ +void ma_schannel_set_win_error(MARIADB_PVIO *pvio, DWORD ErrorNo) +{ + char buffer[256]; + ma_format_win32_error(buffer, sizeof(buffer), ErrorNo, "TLS/SSL error: "); + pvio->set_error(pvio->mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, buffer); + return; +} +/* }}} */ + + +/* }}} */ + +/* {{{ SECURITY_STATUS ma_schannel_handshake_loop(MARIADB_PVIO *pvio, my_bool InitialRead, SecBuffer *pExtraData) */ +/* + perform handshake loop + + SYNOPSIS + ma_schannel_handshake_loop() + pvio Pointer to an Communication/IO structure + InitialRead TRUE if it's the very first read + ExtraData Pointer to an SecBuffer which contains extra data (sent by application) + + +*/ + +SECURITY_STATUS ma_schannel_handshake_loop(MARIADB_PVIO *pvio, my_bool InitialRead, SecBuffer *pExtraData) +{ + SecBufferDesc OutBuffer, InBuffer; + SecBuffer InBuffers[2], OutBuffers; + DWORD dwSSPIFlags, dwSSPIOutFlags, cbData, cbIoBuffer; + TimeStamp tsExpiry; + SECURITY_STATUS rc; + PUCHAR IoBuffer; + BOOL fDoRead; + MARIADB_TLS *ctls= pvio->ctls; + SC_CTX *sctx= (SC_CTX *)ctls->ssl; + + + dwSSPIFlags = ISC_REQ_SEQUENCE_DETECT | + ISC_REQ_REPLAY_DETECT | + ISC_REQ_CONFIDENTIALITY | + ISC_RET_EXTENDED_ERROR | + ISC_REQ_ALLOCATE_MEMORY | + ISC_REQ_STREAM; + + + /* Allocate data buffer */ + if (!(IoBuffer = malloc(SC_IO_BUFFER_SIZE))) + return SEC_E_INSUFFICIENT_MEMORY; + + cbIoBuffer = 0; + fDoRead = InitialRead; + + /* handshake loop: We will leave if handshake is finished + or an error occurs */ + + rc = SEC_I_CONTINUE_NEEDED; + + while (rc == SEC_I_CONTINUE_NEEDED || + rc == SEC_E_INCOMPLETE_MESSAGE || + rc == SEC_I_INCOMPLETE_CREDENTIALS ) + { + /* Read data */ + if (rc == SEC_E_INCOMPLETE_MESSAGE || + !cbIoBuffer) + { + if(fDoRead) + { + ssize_t nbytes = pvio->methods->read(pvio, IoBuffer + cbIoBuffer, (size_t)(SC_IO_BUFFER_SIZE - cbIoBuffer)); + if (nbytes <= 0) + { + rc = SEC_E_INTERNAL_ERROR; + break; + } + cbData = (DWORD)nbytes; + cbIoBuffer += cbData; + } + else + fDoRead = TRUE; + } + + /* input buffers + First buffer stores data received from server. leftover data + will be stored in second buffer with BufferType SECBUFFER_EXTRA */ + + InBuffers[0].pvBuffer = IoBuffer; + InBuffers[0].cbBuffer = cbIoBuffer; + InBuffers[0].BufferType = SECBUFFER_TOKEN; + + InBuffers[1].pvBuffer = NULL; + InBuffers[1].cbBuffer = 0; + InBuffers[1].BufferType = SECBUFFER_EMPTY; + + InBuffer.cBuffers = 2; + InBuffer.pBuffers = InBuffers; + InBuffer.ulVersion = SECBUFFER_VERSION; + + + /* output buffer */ + OutBuffers.pvBuffer = NULL; + OutBuffers.BufferType= SECBUFFER_TOKEN; + OutBuffers.cbBuffer = 0; + + OutBuffer.cBuffers = 1; + OutBuffer.pBuffers = &OutBuffers; + OutBuffer.ulVersion = SECBUFFER_VERSION; + + + rc = InitializeSecurityContextA(&sctx->CredHdl, + &sctx->hCtxt, + NULL, + dwSSPIFlags, + 0, + SECURITY_NATIVE_DREP, + &InBuffer, + 0, + NULL, + &OutBuffer, + &dwSSPIOutFlags, + &tsExpiry ); + + + if (rc == SEC_E_OK || + rc == SEC_I_CONTINUE_NEEDED || + (FAILED(rc) && (dwSSPIOutFlags & ISC_RET_EXTENDED_ERROR))) + { + if(OutBuffers.cbBuffer && OutBuffers.pvBuffer) + { + ssize_t nbytes = pvio->methods->write(pvio, (uchar *)OutBuffers.pvBuffer, (size_t)OutBuffers.cbBuffer); + if(nbytes <= 0) + { + FreeContextBuffer(OutBuffers.pvBuffer); + DeleteSecurityContext(&sctx->hCtxt); + return SEC_E_INTERNAL_ERROR; + } + cbData= (DWORD)nbytes; + /* Free output context buffer */ + FreeContextBuffer(OutBuffers.pvBuffer); + OutBuffers.pvBuffer = NULL; + } + } + /* check if we need to read more data */ + switch (rc) { + case SEC_E_INCOMPLETE_MESSAGE: + /* we didn't receive all data, so just continue loop */ + continue; + break; + case SEC_E_OK: + /* handshake completed, but we need to check if extra + data was sent (which contains encrypted application data) */ + if (InBuffers[1].BufferType == SECBUFFER_EXTRA) + { + if (!(pExtraData->pvBuffer= LocalAlloc(0, InBuffers[1].cbBuffer))) + return SEC_E_INSUFFICIENT_MEMORY; + + MoveMemory(pExtraData->pvBuffer, IoBuffer + (cbIoBuffer - InBuffers[1].cbBuffer), InBuffers[1].cbBuffer ); + pExtraData->BufferType = SECBUFFER_TOKEN; + pExtraData->cbBuffer = InBuffers[1].cbBuffer; + } + else + { + pExtraData->BufferType= SECBUFFER_EMPTY; + pExtraData->pvBuffer= NULL; + pExtraData->cbBuffer= 0; + } + break; + + case SEC_I_INCOMPLETE_CREDENTIALS: + /* Provided credentials didn't contain a valid client certificate. + We will try to connect anonymously, using current credentials */ + fDoRead= FALSE; + rc= SEC_I_CONTINUE_NEEDED; + continue; + break; + default: + if (FAILED(rc)) + { + goto loopend; + } + break; + } + + if ( InBuffers[1].BufferType == SECBUFFER_EXTRA ) + { + MoveMemory( IoBuffer, IoBuffer + (cbIoBuffer - InBuffers[1].cbBuffer), InBuffers[1].cbBuffer ); + cbIoBuffer = InBuffers[1].cbBuffer; + } + else + cbIoBuffer = 0; + } +loopend: + if (FAILED(rc)) + { + ma_schannel_set_sec_error(pvio, rc); + DeleteSecurityContext(&sctx->hCtxt); + } + free(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; + SC_CTX *sctx; + SecBuffer ExtraData; + DWORD SFlags= ISC_REQ_SEQUENCE_DETECT | ISC_REQ_REPLAY_DETECT | + ISC_REQ_CONFIDENTIALITY | ISC_RET_EXTENDED_ERROR | + ISC_REQ_USE_SUPPLIED_CREDS | + ISC_REQ_ALLOCATE_MEMORY | ISC_REQ_STREAM; + + SecBufferDesc BufferOut; + SecBuffer BuffersOut; + + if (!ctls || !ctls->pvio) + return 1; + + pvio= ctls->pvio; + sctx= (SC_CTX *)ctls->ssl; + + /* Initialie securifty context */ + BuffersOut.BufferType= SECBUFFER_TOKEN; + BuffersOut.cbBuffer= 0; + BuffersOut.pvBuffer= NULL; + + + BufferOut.cBuffers= 1; + BufferOut.pBuffers= &BuffersOut; + BufferOut.ulVersion= SECBUFFER_VERSION; + + sRet = InitializeSecurityContext(&sctx->CredHdl, + NULL, + pvio->mysql->host, + SFlags, + 0, + SECURITY_NATIVE_DREP, + NULL, + 0, + &sctx->hCtxt, + &BufferOut, + &OutFlags, + NULL); + + if(sRet != SEC_I_CONTINUE_NEEDED) + { + ma_schannel_set_sec_error(pvio, sRet); + return sRet; + } + + /* send client hello */ + 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; + } + } + sRet= ma_schannel_handshake_loop(pvio, TRUE, &ExtraData); + + /* allocate IO-Buffer for write operations: After handshake + was successful, we are able now to calculate payload */ + if ((sRet = QueryContextAttributes(&sctx->hCtxt, SECPKG_ATTR_STREAM_SIZES, &sctx->Sizes ))) + goto end; + + sctx->IoBufferSize= SCHANNEL_PAYLOAD(sctx->Sizes); + if (!(sctx->IoBuffer= (PUCHAR)LocalAlloc(0, sctx->IoBufferSize))) + { + sRet= SEC_E_INSUFFICIENT_MEMORY; + goto end; + } + + return sRet; +end: + if (BuffersOut.pvBuffer) + FreeContextBuffer(BuffersOut.pvBuffer); + return sRet; +} +/* }}} */ + +/* {{{ SECURITY_STATUS ma_schannel_read_decrypt(MARIADB_PVIO *pvio, PCredHandle phCreds, CtxtHandle * phContext, + DWORD DecryptLength, uchar *ReadBuffer, DWORD ReadBufferSize) */ +/* + Reads encrypted data from a SSL stream and decrypts it. + + SYNOPSIS + ma_schannel_read + pvio pointer to Communication IO structure + phContext a context handle + DecryptLength size of decrypted buffer + ReadBuffer Buffer for decrypted data + ReadBufferSize size of ReadBuffer + + + DESCRIPTION + Reads decrypted data from a SSL stream and encrypts it. + + RETURN + SEC_E_OK on success + SEC_E_* if an error occurred +*/ + +SECURITY_STATUS ma_schannel_read_decrypt(MARIADB_PVIO *pvio, + CtxtHandle * phContext, + DWORD *DecryptLength, + uchar *ReadBuffer, + DWORD ReadBufferSize) +{ + ssize_t nbytes = 0; + DWORD dwOffset = 0; + SC_CTX *sctx; + SECURITY_STATUS sRet = 0; + SecBufferDesc Msg; + SecBuffer Buffers[4]; + int i; + + if (!pvio || !pvio->methods || !pvio->methods->read || !pvio->ctls || !DecryptLength) + return SEC_E_INTERNAL_ERROR; + + sctx = (SC_CTX *)pvio->ctls->ssl; + *DecryptLength = 0; + + if (sctx->dataBuf.cbBuffer) + { + /* Have unread decrypted data from the last time, copy. */ + nbytes = MIN(ReadBufferSize, sctx->dataBuf.cbBuffer); + memcpy(ReadBuffer, sctx->dataBuf.pvBuffer, nbytes); + sctx->dataBuf.pvBuffer = (char *)(sctx->dataBuf.pvBuffer) + nbytes; + sctx->dataBuf.cbBuffer -= (DWORD)nbytes; + *DecryptLength = (DWORD)nbytes; + return SEC_E_OK; + } + + + while (1) + { + /* Check for any encrypted data returned by last DecryptMessage() in SECBUFFER_EXTRA buffer. */ + if (sctx->extraBuf.cbBuffer) + { + memmove(sctx->IoBuffer, sctx->extraBuf.pvBuffer, sctx->extraBuf.cbBuffer); + dwOffset = sctx->extraBuf.cbBuffer; + sctx->extraBuf.cbBuffer = 0; + } + + do { + assert(sctx->IoBufferSize > dwOffset); + if (dwOffset == 0 || sRet == SEC_E_INCOMPLETE_MESSAGE) + { + nbytes = pvio->methods->read(pvio, sctx->IoBuffer + dwOffset, (size_t)(sctx->IoBufferSize - dwOffset)); + if (nbytes <= 0) + { + /* server closed connection, or an error */ + // todo: error + return SEC_E_INVALID_HANDLE; + } + dwOffset += (DWORD)nbytes; + } + ZeroMemory(Buffers, sizeof(SecBuffer) * 4); + Buffers[0].pvBuffer = sctx->IoBuffer; + Buffers[0].cbBuffer = dwOffset; + + Buffers[0].BufferType = SECBUFFER_DATA; + Buffers[1].BufferType = SECBUFFER_EMPTY; + Buffers[2].BufferType = SECBUFFER_EMPTY; + Buffers[3].BufferType = SECBUFFER_EMPTY; + + Msg.ulVersion = SECBUFFER_VERSION; // Version number + Msg.cBuffers = 4; + Msg.pBuffers = Buffers; + + sRet = DecryptMessage(phContext, &Msg, 0, NULL); + + } while (sRet == SEC_E_INCOMPLETE_MESSAGE); /* Continue reading until full message arrives */ + + + if (sRet != SEC_E_OK) + { + ma_schannel_set_sec_error(pvio, sRet); + return sRet; + } + + sctx->extraBuf.cbBuffer = 0; + sctx->dataBuf.cbBuffer = 0; + for (i = 0; i < 4; i++) + { + if (Buffers[i].BufferType == SECBUFFER_DATA) + sctx->dataBuf = Buffers[i]; + if (Buffers[i].BufferType == SECBUFFER_EXTRA) + sctx->extraBuf = Buffers[i]; + } + + + if (sctx->dataBuf.cbBuffer) + { + assert(sctx->dataBuf.pvBuffer); + /* + Copy at most ReadBufferSize bytes to output. + Store the rest (if any) to be processed next time. + */ + nbytes = MIN(sctx->dataBuf.cbBuffer, ReadBufferSize); + memcpy((char *)ReadBuffer, sctx->dataBuf.pvBuffer, nbytes); + sctx->dataBuf.cbBuffer -= (unsigned long)nbytes; + sctx->dataBuf.pvBuffer = (char *)sctx->dataBuf.pvBuffer + nbytes; + + *DecryptLength = (DWORD)nbytes; + return SEC_E_OK; + } + // No data buffer, loop + } +} +/* }}} */ +#include "win32_errmsg.h" +my_bool ma_schannel_verify_certs(MARIADB_TLS *ctls, BOOL verify_server_name) +{ + SECURITY_STATUS status; + + MARIADB_PVIO *pvio= ctls->pvio; + MYSQL *mysql= pvio->mysql; + SC_CTX *sctx = (SC_CTX *)ctls->ssl; + const char *ca_file= mysql->options.ssl_ca; + const char* ca_path = mysql->options.ssl_capath; + const char *crl_file= mysql->options.extension ? mysql->options.extension->ssl_crl : NULL; + const char* crl_path = mysql->options.extension ? mysql->options.extension->ssl_crlpath : NULL; + PCCERT_CONTEXT pServerCert= NULL; + char errmsg[256]; + HCERTSTORE store= NULL; + int ret= 0; + + status = schannel_create_store(ca_file, ca_path, crl_file, crl_path, &store, errmsg, sizeof(errmsg)); + if(status) + goto end; + + status = QueryContextAttributesA(&sctx->hCtxt, SECPKG_ATTR_REMOTE_CERT_CONTEXT, (PVOID)&pServerCert); + if (status) + { + ma_format_win32_error(errmsg, sizeof(errmsg), GetLastError(), + "QueryContextAttributes(SECPKG_ATTR_REMOTE_CERT_CONTEXT) failed."); + goto end; + } + + status = schannel_verify_server_certificate( + pServerCert, + store, + crl_file != 0 || crl_path != 0, + mysql->host, + verify_server_name, + errmsg, sizeof(errmsg)); + + if (status) + goto end; + + ret= 1; + +end: + if (!ret) + { + pvio->set_error(mysql, CR_SSL_CONNECTION_ERROR, SQLSTATE_UNKNOWN, 0, errmsg); + } + if (pServerCert) + CertFreeCertificateContext(pServerCert); + if(store) + schannel_free_store(store); + return ret; +} + + +/* {{{ size_t ma_schannel_write_encrypt(MARIADB_PVIO *pvio, PCredHandle phCreds, CtxtHandle * phContext) */ +/* + Decrypts data and write to SSL stream + SYNOPSIS + ma_schannel_write_decrypt + pvio pointer to Communication IO structure + phContext a context handle + DecryptLength size of decrypted buffer + ReadBuffer Buffer for decrypted data + ReadBufferSize size of ReadBuffer + + DESCRIPTION + Write encrypted data to SSL stream. + + RETURN + SEC_E_OK on success + SEC_E_* if an error occurred +*/ +ssize_t ma_schannel_write_encrypt(MARIADB_PVIO *pvio, + uchar *WriteBuffer, + size_t WriteBufferSize) +{ + SECURITY_STATUS scRet; + SecBufferDesc Message; + SecBuffer Buffers[4]; + 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); + + 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; + Buffers[3].cbBuffer = SECBUFFER_EMPTY; + Buffers[3].BufferType = SECBUFFER_EMPTY; + + Message.ulVersion = SECBUFFER_VERSION; + Message.cBuffers = 4; + Message.pBuffers = Buffers; + if ((scRet = EncryptMessage(&sctx->hCtxt, 0, &Message, 0))!= SEC_E_OK) + return -1; + write_size = Buffers[0].cbBuffer + Buffers[1].cbBuffer + Buffers[2].cbBuffer; + nbytes = pvio->methods->write(pvio, sctx->IoBuffer, write_size); + return nbytes == write_size ? payload : -1; +} +/* }}} */ + +extern char *ssl_protocol_version[5]; + +/* {{{ ma_tls_get_protocol_version(MARIADB_TLS *ctls) */ +int ma_tls_get_protocol_version(MARIADB_TLS *ctls) +{ + SC_CTX *sctx; + SecPkgContext_ConnectionInfo ConnectionInfo; + if (!ctls->ssl) + return 1; + + sctx= (SC_CTX *)ctls->ssl; + + if (QueryContextAttributes(&sctx->hCtxt, SECPKG_ATTR_CONNECTION_INFO, &ConnectionInfo) != SEC_E_OK) + return -1; + + switch(ConnectionInfo.dwProtocol) + { + case SP_PROT_SSL3_CLIENT: + return PROTOCOL_SSLV3; + case SP_PROT_TLS1_CLIENT: + return PROTOCOL_TLS_1_0; + case SP_PROT_TLS1_1_CLIENT: + return PROTOCOL_TLS_1_1; + case SP_PROT_TLS1_2_CLIENT: + return PROTOCOL_TLS_1_2; + default: + return -1; + } +} +/* }}} */ |