Using OpenSSL with Asynchronous Sockets
By Len Holgate, October 01, 2002
OpenSSL is often implemented using a UNIX-style sockets architecture. Len presents an "asynchronous connector" for OpenSSL that makes it easier to use on the Windows platform.
October 2002/Using OpenSSL with Asynchronous Sockets
Listing 1: Pushing data into the SSL object
#include "stdafx.h"
#include "OpenSSLConnectorBase.h"
#include <openssl/err.h>
///////////////////////////////////////////////////////////////////////////////
// COpenSSLConnectorBase
///////////////////////////////////////////////////////////////////////////////
COpenSSLConnectorBase::COpenSSLConnectorBase(
SSL_CTX *pContext)
: m_readRequired(false),
m_pConnection(SSL_new(pContext)),
m_pIn(BIO_new(BIO_s_mem())),
m_pOut(BIO_new(BIO_s_mem()))
{
SSL_set_bio(m_pConnection, m_pIn, m_pOut);
}
COpenSSLConnectorBase::~COpenSSLConnectorBase()
{
SSL_free(m_pConnection);
}
size_t COpenSSLConnectorBase::DataToWrite(
BYTE *pData,
size_t dataLength)
{
size_t bytesUsed = 0;
int result = SSL_write(m_pConnection, pData, dataLength);
if (result < 0)
{
HandleError(result);
}
else
{
bytesUsed = result;
}
if (SSL_want_read(m_pConnection))
{
OutputDebugString(_T("SSL_want_read\n"));
m_readRequired = true;
}
return bytesUsed;
}
size_t COpenSSLConnectorBase::DataToRead(
BYTE *pData,
size_t dataLength)
{
m_readRequired = false;
size_t bytesUsed = BIO_write(m_pIn, pData, dataLength);
BYTE *pBuffer = 0;
size_t bufferSize = 0;
GetNextReadDataBuffer(&pBuffer, bufferSize);
int bytesOut = SSL_read(m_pConnection, (void*)pBuffer, bufferSize);
if (bytesOut > 0)
{
OnDataToRead(pBuffer, bytesOut);
}
if (bytesOut < 0)
{
HandleError(bytesOut);
}
return bytesUsed;
}
void COpenSSLConnectorBase::SendPendingData()
{
int pending;
OutputDebugString("Send pending data\n");
while ((pending = BIO_ctrl_pending(m_pOut)) > 0)
{
// If we were using BIO pairs then we'd be able to use nread() and access
// the data in place, rather than copying it out into a buffer...
// It complicates the example though.
BYTE *pBuffer = 0;
size_t bufferSize = 0;
GetNextWriteDataBuffer(&pBuffer, bufferSize);
int bytesToSend = BIO_read(m_pOut, (void*)pBuffer, bufferSize);
if (bytesToSend > 0)
{
OnDataToWrite(pBuffer, bytesToSend);
TRACE(_T("Sent %d bytes\n"), bytesToSend);
}
if (bytesToSend <= 0)
{
if (!BIO_should_retry(m_pOut))
{
HandleError(bytesToSend);
}
}
}
}
void COpenSSLConnectorBase::ExpectConnect()
{
SSL_set_connect_state(m_pConnection);
}
void COpenSSLConnectorBase::ExpectAccept()
{
SSL_set_accept_state(m_pConnection);
}
void COpenSSLConnectorBase::HandleError(int result)
{
if (result <= 0)
{
int error = SSL_get_error(m_pConnection, result);
switch(error)
{
case SSL_ERROR_ZERO_RETURN:
case SSL_ERROR_NONE:
case SSL_ERROR_WANT_READ :
break;
default :
{
char buffer[256];
while (error != 0)
{
ERR_error_string_n(error, buffer, sizeof(buffer));
TRACE(_T("Error: %d - %s\n"), error, buffer);
error = ERR_get_error();
}
// Handle errors better than this!
ASSERT(false);
}
break;
}
}
}
void COpenSSLConnectorBase::RunSSL()
{
OutputDebugString(_T("RunSSL\n"));
bool dataToWrite = false;
bool dataToRead = false;
GetPendingOperations(dataToRead, dataToWrite);
while ((!m_readRequired && dataToWrite) || dataToRead)
{
if (SSL_in_init(m_pConnection))
{
OutputDebugString("Client waiting in connect: ");
OutputDebugString(SSL_state_string_long(m_pConnection));
OutputDebugString("\n");
}
if (dataToRead)
{
PerformRead();
}
if (!m_readRequired && dataToWrite)
{
PerformWrite();
}
if (BIO_ctrl_pending(m_pOut))
{
SendPendingData();
}
GetPendingOperations(dataToRead, dataToWrite);
}
}