FreeRDP/winpr/libwinpr/sspi/Schannel/schannel_openssl.c

589 lines
14 KiB
C
Raw Normal View History

/**
* WinPR: Windows Portable Runtime
* Schannel Security Package (OpenSSL)
*
2014-06-06 06:10:08 +04:00
* Copyright 2012-2014 Marc-Andre Moreau <marcandre.moreau@gmail.com>
2014-08-18 19:22:22 +04:00
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
2015-10-06 17:56:24 +03:00
#include "schannel_openssl.h"
#ifdef WITH_OPENSSL
#include <winpr/crt.h>
#include <winpr/sspi.h>
OpenSSL thread safety freerdp/winpr had the following issues: * The non reentrant SSL_library_init() was called concurrently (crash) * Missing code/api to set the eventually required OpenSSL static and dynamic locking callbacks * Missing code/api to free the application-global or thread-local OpenSSL data and tables This commit creates two new winpr functions: BOOL winpr_InitializeSSL(DWORD flags): Use the flag WINPR_SSL_INIT_ALREADY_INITIALIZED if you want to tell winpr that your application has already initialized OpenSSL. If required use the flag WINPR_SSL_INIT_ENABLE_LOCKING to tell winpr that it should set the OpenSSL static and dynamic locking callbacks. Otherwise just call it with the flag WINPR_SSL_INIT_DEFAULT. The recommended way is that your application calls this function once before any threads are created. However, in order to support lazy OpenSSL library initialization winpr_InitializeSSL() can also safely be called multiple times and concurrently because it uses the new InitOnceExecuteOnce() function to guarantee that the initialization is only performed successfully once during the life time of the calling process. BOOL winpr_CleanupSSL(DWORD flags): If you create a thread that uses SSL you should call this function before the thread returns using the flag WINPR_SSL_CLEANUP_THREAD in order to clean up the thread-local OpenSSL data and tables. Call the function with the flag WINPR_SSL_CLEANUP_GLOBAL before terminating your application. Note: This commit only replaced the current occurences of the SSL_load_error_strings(); SSL_library_init(); pairs in the freerdp source with winpr_InitializeSSL(). None of the server or client applications has been changed according to the recommended usage described above (TBDL).
2014-07-28 23:55:57 +04:00
#include <winpr/ssl.h>
#include <winpr/print.h>
#include <winpr/crypto.h>
2015-10-06 17:56:24 +03:00
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <openssl/bio.h>
struct _SCHANNEL_OPENSSL
{
SSL* ssl;
SSL_CTX* ctx;
BOOL connected;
BIO* bioRead;
BIO* bioWrite;
BYTE* ReadBuffer;
BYTE* WriteBuffer;
};
2014-08-18 19:22:22 +04:00
#include "../../log.h"
#define TAG WINPR_TAG("sspi.schannel")
2014-08-18 19:22:22 +04:00
2014-08-18 21:34:47 +04:00
char* openssl_get_ssl_error_string(int ssl_error)
{
switch (ssl_error)
{
case SSL_ERROR_ZERO_RETURN:
return "SSL_ERROR_ZERO_RETURN";
case SSL_ERROR_WANT_READ:
return "SSL_ERROR_WANT_READ";
case SSL_ERROR_WANT_WRITE:
return "SSL_ERROR_WANT_WRITE";
case SSL_ERROR_SYSCALL:
return "SSL_ERROR_SYSCALL";
case SSL_ERROR_SSL:
return "SSL_ERROR_SSL";
}
return "SSL_ERROR_UNKNOWN";
}
2014-08-18 21:34:47 +04:00
int schannel_openssl_client_init(SCHANNEL_OPENSSL* context)
{
int status;
long options = 0;
context->ctx = SSL_CTX_new(TLSv1_client_method());
if (!context->ctx)
{
WLog_ERR(TAG, "SSL_CTX_new failed");
return -1;
}
/**
* SSL_OP_NO_COMPRESSION:
*
* The Microsoft RDP server does not advertise support
* for TLS compression, but alternative servers may support it.
* This was observed between early versions of the FreeRDP server
* and the FreeRDP client, and caused major performance issues,
* which is why we're disabling it.
*/
#ifdef SSL_OP_NO_COMPRESSION
options |= SSL_OP_NO_COMPRESSION;
#endif
/**
* SSL_OP_TLS_BLOCK_PADDING_BUG:
*
* The Microsoft RDP server does *not* support TLS padding.
* It absolutely needs to be disabled otherwise it won't work.
*/
options |= SSL_OP_TLS_BLOCK_PADDING_BUG;
/**
* SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS:
*
* Just like TLS padding, the Microsoft RDP server does not
* support empty fragments. This needs to be disabled.
*/
options |= SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS;
SSL_CTX_set_options(context->ctx, options);
context->ssl = SSL_new(context->ctx);
if (!context->ssl)
{
WLog_ERR(TAG, "SSL_new failed");
goto out_ssl_new_failed;
}
context->bioRead = BIO_new(BIO_s_mem());
if (!context->bioRead)
{
WLog_ERR(TAG, "BIO_new failed");
goto out_bio_read_failed;
return -1;
}
status = BIO_set_write_buf_size(context->bioRead, SCHANNEL_CB_MAX_TOKEN);
if (status != 1)
{
WLog_ERR(TAG, "BIO_set_write_buf_size on bioRead failed");
goto out_set_write_buf_read;
}
context->bioWrite = BIO_new(BIO_s_mem());
if (!context->bioWrite)
{
WLog_ERR(TAG, "BIO_new failed");
goto out_bio_write_failed;
}
status = BIO_set_write_buf_size(context->bioWrite, SCHANNEL_CB_MAX_TOKEN);
if (status != 1)
{
WLog_ERR(TAG, "BIO_set_write_buf_size on bioWrite failed");
goto out_set_write_buf_write;
}
status = BIO_make_bio_pair(context->bioRead, context->bioWrite);
if (status != 1)
{
WLog_ERR(TAG, "BIO_make_bio_pair failed");
goto out_bio_pair;
}
SSL_set_bio(context->ssl, context->bioRead, context->bioWrite);
2014-08-18 21:34:47 +04:00
context->ReadBuffer = (BYTE*) malloc(SCHANNEL_CB_MAX_TOKEN);
if (!context->ReadBuffer)
{
WLog_ERR(TAG, "Failed to allocate ReadBuffer");
goto out_read_alloc;
}
2014-08-18 21:34:47 +04:00
context->WriteBuffer = (BYTE*) malloc(SCHANNEL_CB_MAX_TOKEN);
if (!context->WriteBuffer)
{
WLog_ERR(TAG, "Failed to allocate ReadBuffer");
goto out_write_alloc;
}
return 0;
out_write_alloc:
free(context->ReadBuffer);
out_read_alloc:
out_bio_pair:
out_set_write_buf_write:
BIO_free(context->bioWrite);
out_bio_write_failed:
out_set_write_buf_read:
BIO_free(context->bioRead);
out_bio_read_failed:
SSL_free(context->ssl);
out_ssl_new_failed:
SSL_CTX_free(context->ctx);
return -1;
}
2014-08-18 21:34:47 +04:00
int schannel_openssl_server_init(SCHANNEL_OPENSSL* context)
{
int status;
long options = 0;
2015-10-06 17:56:24 +03:00
context->ctx = SSL_CTX_new(TLSv1_server_method());
if (!context->ctx)
{
WLog_ERR(TAG, "SSL_CTX_new failed");
return -1;
}
/*
* SSL_OP_NO_SSLv2:
*
* We only want SSLv3 and TLSv1, so disable SSLv2.
* SSLv3 is used by, eg. Microsoft RDC for Mac OS X.
*/
options |= SSL_OP_NO_SSLv2;
/**
* SSL_OP_NO_COMPRESSION:
*
* The Microsoft RDP server does not advertise support
* for TLS compression, but alternative servers may support it.
* This was observed between early versions of the FreeRDP server
* and the FreeRDP client, and caused major performance issues,
* which is why we're disabling it.
*/
#ifdef SSL_OP_NO_COMPRESSION
options |= SSL_OP_NO_COMPRESSION;
#endif
/**
* SSL_OP_TLS_BLOCK_PADDING_BUG:
*
* The Microsoft RDP server does *not* support TLS padding.
* It absolutely needs to be disabled otherwise it won't work.
*/
options |= SSL_OP_TLS_BLOCK_PADDING_BUG;
/**
* SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS:
*
* Just like TLS padding, the Microsoft RDP server does not
* support empty fragments. This needs to be disabled.
*/
options |= SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS;
SSL_CTX_set_options(context->ctx, options);
if (SSL_CTX_use_RSAPrivateKey_file(context->ctx, "/tmp/localhost.key", SSL_FILETYPE_PEM) <= 0)
{
WLog_ERR(TAG, "SSL_CTX_use_RSAPrivateKey_file failed");
goto out_rsa_key;
}
context->ssl = SSL_new(context->ctx);
if (!context->ssl)
{
WLog_ERR(TAG, "SSL_new failed");
goto out_ssl_new;
}
2013-01-05 21:35:24 +04:00
if (SSL_use_certificate_file(context->ssl, "/tmp/localhost.crt", SSL_FILETYPE_PEM) <= 0)
{
WLog_ERR(TAG, "SSL_use_certificate_file failed");
goto out_use_certificate;
}
context->bioRead = BIO_new(BIO_s_mem());
if (!context->bioRead)
{
WLog_ERR(TAG, "BIO_new failed");
goto out_bio_read;
}
status = BIO_set_write_buf_size(context->bioRead, SCHANNEL_CB_MAX_TOKEN);
if (status != 1)
{
WLog_ERR(TAG, "BIO_set_write_buf_size failed for bioRead");
goto out_set_write_buf_read;
}
context->bioWrite = BIO_new(BIO_s_mem());
if (!context->bioWrite)
{
WLog_ERR(TAG, "BIO_new failed");
goto out_bio_write;
}
status = BIO_set_write_buf_size(context->bioWrite, SCHANNEL_CB_MAX_TOKEN);
if (status != 1)
{
WLog_ERR(TAG, "BIO_set_write_buf_size failed for bioWrite");
goto out_set_write_buf_write;
}
status = BIO_make_bio_pair(context->bioRead, context->bioWrite);
if (status != 1)
{
WLog_ERR(TAG, "BIO_make_bio_pair failed");
goto out_bio_pair;
}
SSL_set_bio(context->ssl, context->bioRead, context->bioWrite);
2014-08-18 21:34:47 +04:00
context->ReadBuffer = (BYTE*) malloc(SCHANNEL_CB_MAX_TOKEN);
if (!context->ReadBuffer)
{
WLog_ERR(TAG, "Failed to allocate memory for ReadBuffer");
goto out_read_buffer;
}
2014-08-18 21:34:47 +04:00
context->WriteBuffer = (BYTE*) malloc(SCHANNEL_CB_MAX_TOKEN);
if (!context->WriteBuffer)
{
WLog_ERR(TAG, "Failed to allocate memory for WriteBuffer");
goto out_write_buffer;
}
return 0;
out_write_buffer:
free(context->ReadBuffer);
out_read_buffer:
out_bio_pair:
out_set_write_buf_write:
BIO_free(context->bioWrite);
out_bio_write:
out_set_write_buf_read:
BIO_free(context->bioRead);
out_bio_read:
out_use_certificate:
SSL_free(context->ssl);
out_ssl_new:
out_rsa_key:
SSL_CTX_free(context->ctx);
return -1;
}
2014-08-18 21:34:47 +04:00
SECURITY_STATUS schannel_openssl_client_process_tokens(SCHANNEL_OPENSSL* context, PSecBufferDesc pInput, PSecBufferDesc pOutput)
{
int status;
int ssl_error;
PSecBuffer pBuffer;
if (!context->connected)
{
if (pInput)
{
if (pInput->cBuffers < 1)
return SEC_E_INVALID_TOKEN;
pBuffer = sspi_FindSecBuffer(pInput, SECBUFFER_TOKEN);
if (!pBuffer)
return SEC_E_INVALID_TOKEN;
status = BIO_write(context->bioRead, pBuffer->pvBuffer, pBuffer->cbBuffer);
}
status = SSL_connect(context->ssl);
if (status < 0)
{
ssl_error = SSL_get_error(context->ssl, status);
WLog_ERR(TAG, "SSL_connect error: %s", openssl_get_ssl_error_string(ssl_error));
}
if (status == 1)
context->connected = TRUE;
status = BIO_read(context->bioWrite, context->ReadBuffer, SCHANNEL_CB_MAX_TOKEN);
if (pOutput->cBuffers < 1)
return SEC_E_INVALID_TOKEN;
pBuffer = sspi_FindSecBuffer(pOutput, SECBUFFER_TOKEN);
if (!pBuffer)
return SEC_E_INVALID_TOKEN;
if (status > 0)
{
2014-06-06 06:10:08 +04:00
if (pBuffer->cbBuffer < (unsigned long) status)
return SEC_E_INSUFFICIENT_MEMORY;
CopyMemory(pBuffer->pvBuffer, context->ReadBuffer, status);
pBuffer->cbBuffer = status;
return (context->connected) ? SEC_E_OK : SEC_I_CONTINUE_NEEDED;
}
else
{
pBuffer->cbBuffer = 0;
return (context->connected) ? SEC_E_OK : SEC_I_CONTINUE_NEEDED;
}
}
return SEC_E_OK;
}
2014-08-18 21:34:47 +04:00
SECURITY_STATUS schannel_openssl_server_process_tokens(SCHANNEL_OPENSSL* context, PSecBufferDesc pInput, PSecBufferDesc pOutput)
{
int status;
int ssl_error;
PSecBuffer pBuffer;
if (!context->connected)
{
if (pInput->cBuffers < 1)
return SEC_E_INVALID_TOKEN;
pBuffer = sspi_FindSecBuffer(pInput, SECBUFFER_TOKEN);
if (!pBuffer)
return SEC_E_INVALID_TOKEN;
status = BIO_write(context->bioRead, pBuffer->pvBuffer, pBuffer->cbBuffer);
status = SSL_accept(context->ssl);
if (status < 0)
{
ssl_error = SSL_get_error(context->ssl, status);
WLog_ERR(TAG, "SSL_accept error: %s", openssl_get_ssl_error_string(ssl_error));
}
if (status == 1)
context->connected = TRUE;
status = BIO_read(context->bioWrite, context->ReadBuffer, SCHANNEL_CB_MAX_TOKEN);
if (pOutput->cBuffers < 1)
return SEC_E_INVALID_TOKEN;
pBuffer = sspi_FindSecBuffer(pOutput, SECBUFFER_TOKEN);
if (!pBuffer)
return SEC_E_INVALID_TOKEN;
if (status > 0)
{
2014-06-06 06:10:08 +04:00
if (pBuffer->cbBuffer < (unsigned long) status)
return SEC_E_INSUFFICIENT_MEMORY;
CopyMemory(pBuffer->pvBuffer, context->ReadBuffer, status);
pBuffer->cbBuffer = status;
return (context->connected) ? SEC_E_OK : SEC_I_CONTINUE_NEEDED;
}
else
{
pBuffer->cbBuffer = 0;
return (context->connected) ? SEC_E_OK : SEC_I_CONTINUE_NEEDED;
}
}
return SEC_E_OK;
}
2014-08-18 21:34:47 +04:00
SECURITY_STATUS schannel_openssl_encrypt_message(SCHANNEL_OPENSSL* context, PSecBufferDesc pMessage)
{
int status;
int length;
int offset;
int ssl_error;
PSecBuffer pStreamBodyBuffer;
PSecBuffer pStreamHeaderBuffer;
PSecBuffer pStreamTrailerBuffer;
pStreamHeaderBuffer = sspi_FindSecBuffer(pMessage, SECBUFFER_STREAM_HEADER);
pStreamBodyBuffer = sspi_FindSecBuffer(pMessage, SECBUFFER_DATA);
pStreamTrailerBuffer = sspi_FindSecBuffer(pMessage, SECBUFFER_STREAM_TRAILER);
if ((!pStreamHeaderBuffer) || (!pStreamBodyBuffer) || (!pStreamTrailerBuffer))
return SEC_E_INVALID_TOKEN;
status = SSL_write(context->ssl, pStreamBodyBuffer->pvBuffer, pStreamBodyBuffer->cbBuffer);
if (status < 0)
{
ssl_error = SSL_get_error(context->ssl, status);
WLog_ERR(TAG, "SSL_write: %s", openssl_get_ssl_error_string(ssl_error));
}
status = BIO_read(context->bioWrite, context->ReadBuffer, SCHANNEL_CB_MAX_TOKEN);
if (status > 0)
{
offset = 0;
2014-06-06 06:10:08 +04:00
length = (pStreamHeaderBuffer->cbBuffer > (unsigned long) status) ? status : pStreamHeaderBuffer->cbBuffer;
CopyMemory(pStreamHeaderBuffer->pvBuffer, &context->ReadBuffer[offset], length);
status -= length;
offset += length;
2014-06-06 06:10:08 +04:00
length = (pStreamBodyBuffer->cbBuffer > (unsigned long) status) ? status : pStreamBodyBuffer->cbBuffer;
CopyMemory(pStreamBodyBuffer->pvBuffer, &context->ReadBuffer[offset], length);
status -= length;
offset += length;
2014-06-06 06:10:08 +04:00
length = (pStreamTrailerBuffer->cbBuffer > (unsigned long) status) ? status : pStreamTrailerBuffer->cbBuffer;
CopyMemory(pStreamTrailerBuffer->pvBuffer, &context->ReadBuffer[offset], length);
status -= length;
}
return SEC_E_OK;
}
2014-08-18 21:34:47 +04:00
SECURITY_STATUS schannel_openssl_decrypt_message(SCHANNEL_OPENSSL* context, PSecBufferDesc pMessage)
{
int status;
int length;
2014-08-18 21:34:47 +04:00
BYTE* buffer;
int ssl_error;
PSecBuffer pBuffer;
pBuffer = sspi_FindSecBuffer(pMessage, SECBUFFER_DATA);
if (!pBuffer)
return SEC_E_INVALID_TOKEN;
status = BIO_write(context->bioRead, pBuffer->pvBuffer, pBuffer->cbBuffer);
status = SSL_read(context->ssl, pBuffer->pvBuffer, pBuffer->cbBuffer);
if (status < 0)
{
ssl_error = SSL_get_error(context->ssl, status);
WLog_ERR(TAG, "SSL_read: %s", openssl_get_ssl_error_string(ssl_error));
}
length = status;
buffer = pBuffer->pvBuffer;
pMessage->pBuffers[0].BufferType = SECBUFFER_STREAM_HEADER;
pMessage->pBuffers[0].cbBuffer = 5;
pMessage->pBuffers[1].BufferType = SECBUFFER_DATA;
pMessage->pBuffers[1].pvBuffer = buffer;
pMessage->pBuffers[1].cbBuffer = length;
pMessage->pBuffers[2].BufferType = SECBUFFER_STREAM_TRAILER;
pMessage->pBuffers[2].cbBuffer = 36;
pMessage->pBuffers[3].BufferType = SECBUFFER_EMPTY;
pMessage->pBuffers[3].cbBuffer = 0;
return SEC_E_OK;
}
2014-08-18 21:34:47 +04:00
SCHANNEL_OPENSSL* schannel_openssl_new()
{
2014-08-18 21:34:47 +04:00
SCHANNEL_OPENSSL* context;
context = (SCHANNEL_OPENSSL*) calloc(1, sizeof(SCHANNEL_OPENSSL));
if (context != NULL)
{
OpenSSL thread safety freerdp/winpr had the following issues: * The non reentrant SSL_library_init() was called concurrently (crash) * Missing code/api to set the eventually required OpenSSL static and dynamic locking callbacks * Missing code/api to free the application-global or thread-local OpenSSL data and tables This commit creates two new winpr functions: BOOL winpr_InitializeSSL(DWORD flags): Use the flag WINPR_SSL_INIT_ALREADY_INITIALIZED if you want to tell winpr that your application has already initialized OpenSSL. If required use the flag WINPR_SSL_INIT_ENABLE_LOCKING to tell winpr that it should set the OpenSSL static and dynamic locking callbacks. Otherwise just call it with the flag WINPR_SSL_INIT_DEFAULT. The recommended way is that your application calls this function once before any threads are created. However, in order to support lazy OpenSSL library initialization winpr_InitializeSSL() can also safely be called multiple times and concurrently because it uses the new InitOnceExecuteOnce() function to guarantee that the initialization is only performed successfully once during the life time of the calling process. BOOL winpr_CleanupSSL(DWORD flags): If you create a thread that uses SSL you should call this function before the thread returns using the flag WINPR_SSL_CLEANUP_THREAD in order to clean up the thread-local OpenSSL data and tables. Call the function with the flag WINPR_SSL_CLEANUP_GLOBAL before terminating your application. Note: This commit only replaced the current occurences of the SSL_load_error_strings(); SSL_library_init(); pairs in the freerdp source with winpr_InitializeSSL(). None of the server or client applications has been changed according to the recommended usage described above (TBDL).
2014-07-28 23:55:57 +04:00
winpr_InitializeSSL(WINPR_SSL_INIT_DEFAULT);
context->connected = FALSE;
}
return context;
}
2014-08-18 21:34:47 +04:00
void schannel_openssl_free(SCHANNEL_OPENSSL* context)
{
if (context)
{
free(context->ReadBuffer);
free(context->WriteBuffer);
free(context);
}
}
2015-10-06 17:56:24 +03:00
#else
int schannel_openssl_client_init(SCHANNEL_OPENSSL* context)
{
return 0;
}
int schannel_openssl_server_init(SCHANNEL_OPENSSL* context)
{
return 0;
}
SECURITY_STATUS schannel_openssl_client_process_tokens(SCHANNEL_OPENSSL* context, PSecBufferDesc pInput, PSecBufferDesc pOutput)
{
return SEC_E_OK;
}
SECURITY_STATUS schannel_openssl_server_process_tokens(SCHANNEL_OPENSSL* context, PSecBufferDesc pInput, PSecBufferDesc pOutput)
{
return SEC_E_OK;
}
SECURITY_STATUS schannel_openssl_encrypt_message(SCHANNEL_OPENSSL* context, PSecBufferDesc pMessage)
{
return SEC_E_OK;
}
SECURITY_STATUS schannel_openssl_decrypt_message(SCHANNEL_OPENSSL* context, PSecBufferDesc pMessage)
{
return SEC_E_OK;
}
SCHANNEL_OPENSSL* schannel_openssl_new(void)
{
return NULL;
}
void schannel_openssl_free(SCHANNEL_OPENSSL* context)
{
}
#endif