//*****************************************************************************
// RCF - Remote Call Framework
// Copyright (c) 2005. All rights reserved.
// Developed by Jarl Lindrud.
// Contact: jlindrud@hotmail.com .
//*****************************************************************************
#include <RCF/OpenSslEncryptionFilter.hpp>
#include <openssl/bio.h>
#include <openssl/crypto.h>
#include <openssl/engine.h>
#include <openssl/err.h>
#include <openssl/evp.h>
#include <openssl/rand.h>
#include <openssl/ssl.h>
#include <openssl/x509.h>
#include <openssl/x509v3.h>
#include <RCF/Tools.hpp>
#include <RCF/UsingOpenSsl.hpp>
namespace RCF {
class OpenSslEncryptionFilterImpl
{
public:
OpenSslEncryptionFilterImpl(
OpenSslEncryptionFilter &openSslEncryptionFilter,
SslRole sslRole,
const std::string &certificateFile,
const std::string &certificateFilePassword,
unsigned int bioBufferSize);
void read(char *buffer, std::size_t bufferLen);
void write(const char *buffer, std::size_t bufferLen);
void onReadWriteCompleted(std::size_t bytesTransferred, int error);
private:
void readWrite(char *buffer, std::size_t bufferLen);
void transferData();
void onDataTransferred(std::size_t bytesTransferred);
void retryReadWrite();
void init();
bool loadCertificate(boost::shared_ptr<SSL_CTX> ctx, const std::string &file, const std::string &password);
enum IoState
{
Ready,
Reading,
Writing
};
// server or client
SslRole sslRole;
// certificate
std::string certificateFile;
std::string certificateFilePassword;
// input state
IoState preState;
// output state
IoState postState;
// retry state
bool retry;
// input parameters
char *preBuffer;
char *preBufferOrig;
std::size_t preBufferLen;
std::size_t preBufferLenOrig;
// output parameters
char *postBuffer;
std::size_t postBufferLen;
std::size_t postBufferRequested;
int err;
// OpenSSL members
// NB: using shared_ptr instead of auto_ptr, since we need custom deleters
boost::shared_ptr<SSL_CTX> ssl_ctx;
boost::shared_ptr<SSL> ssl;
boost::shared_ptr<BIO> bio;
boost::shared_ptr<BIO> io_bio;
boost::shared_ptr<BIO> ssl_bio;
unsigned int bioBufferSize;
OpenSslEncryptionFilter &openSslEncryptionFilter;
};
FilterDescription OpenSslEncryptionFilter::sGetFilterDescription()
{
return FilterDescription("OpenSSL encryption filter", RCF_FILTER_OPENSSL_ENCRYPTION);
}
#ifdef _MSC_VER
#pragma warning( push )
#pragma warning( disable : 4355 ) // warning C4355: 'this' : used in base member initializer list
#endif
OpenSslEncryptionFilter::OpenSslEncryptionFilter(
SslRole sslRole,
const std::string &certificateFile,
const std::string &certificateFilePassword,
unsigned int bioBufferSize/* = 2048*/) :
implPtr( new OpenSslEncryptionFilterImpl( *this, sslRole, certificateFile, certificateFilePassword, bioBufferSize) )
{}
#ifdef _MSC_VER
#pragma warning( pop )
#endif
void OpenSslEncryptionFilter::read(char *buffer, std::size_t bufferLen)
{
implPtr->read(buffer, bufferLen);
}
void OpenSslEncryptionFilter::write(const char *buffer, std::size_t bufferLen)
{
implPtr->write(buffer, bufferLen);
}
void OpenSslEncryptionFilter::onReadWriteCompleted(std::size_t bytesTransferred, int error)
{
implPtr->onReadWriteCompleted(bytesTransferred, error);
}
FilterDescription OpenSslEncryptionFilter::getFilterDescription() const
{
return sGetFilterDescription();
}
OpenSslEncryptionFilterImpl::OpenSslEncryptionFilterImpl(
OpenSslEncryptionFilter &openSslEncryptionFilter,
SslRole sslRole,
const std::string &certificateFile,
const std::string &certificateFilePassword,
unsigned int bioBufferSize) :
sslRole(sslRole),
certificateFile(certificateFile),
certificateFilePassword(certificateFilePassword),
preBuffer(),
preBufferOrig(),
preBufferLen(),
preBufferLenOrig(),
postBuffer(),
postBufferLen(),
postBufferRequested(),
preState(Ready),
postState(Ready),
bioBufferSize(bioBufferSize),
retry(),
err(),
openSslEncryptionFilter(openSslEncryptionFilter)
{
init();
}
void OpenSslEncryptionFilterImpl::read(char *buffer, std::size_t bufferLen)
{
RCF_ASSERT(preState == Ready);
preState = Reading;
readWrite(buffer, bufferLen);
}
void OpenSslEncryptionFilterImpl::write(const char *buffer, std::size_t bufferLen)
{
RCF_ASSERT(preState == Ready);
preState = Writing;
readWrite(const_cast<char *>(buffer), bufferLen);
}
void OpenSslEncryptionFilterImpl::onReadWriteCompleted(std::size_t bytesTransferred, int error)
{
// check for error
if (error == -1)
{
preState = Ready;
err = -1;
openSslEncryptionFilter.mReadWriteCompletionCallback(0, -1);
}
else
{
// complete the data transfer
onDataTransferred(bytesTransferred);
if (retry)
{
retryReadWrite();
}
else
{
if (preState == Writing && BIO_ctrl_pending(io_bio.get()) > 0)
{
transferData();
}
else
{
preState = Ready;
openSslEncryptionFilter.mReadWriteCompletionCallback(preBufferLenOrig - preBufferLen, 0);
}
}
}
}
void OpenSslEncryptionFilterImpl::readWrite(char *buffer, std::size_t bufferLen)
{
// set input parameters
retry = true;
err = 0;
preBuffer = const_cast<char *>(buffer);
preBufferOrig = const_cast<char *>(buffer);
preBufferLen = bufferLen;
preBufferLenOrig = bufferLen;
retryReadWrite();
}
void OpenSslEncryptionFilterImpl::transferData()
{
if (BIO_ctrl_pending(io_bio.get()) == 0)
{
// move data from network to the io bio
postState = Reading;
postBufferRequested = static_cast<int>(BIO_ctrl_get_read_request(io_bio.get()));
postBufferLen = BIO_nwrite0(io_bio.get(), &postBuffer);
RCF_ASSERT(postBufferRequested <= postBufferLen);
// NB: completion routine will call BIO_nwrite(io_bio, len)
//readFunction(postBuffer, postBufferRequested, completionKey);
openSslEncryptionFilter.mReadFunction(postBuffer, postBufferRequested);
}
else
{
// move data from io_bio to network
postState = Writing;
postBufferRequested = static_cast<int>(BIO_ctrl_pending(io_bio.get()));
postBufferLen = BIO_nread0(io_bio.get(), &postBuffer);
// NB: completion routine will call BIO_nread(io_bio, postBufferLen)
//writeFunction(postBuffer, postBufferLen, completionKey);
openSslEncryptionFilter.mWriteFunction(postBuffer, postBufferLen);
}
}
void OpenSslEncryptionFilterImpl::onDataTransferred(std::size_t bytesTransferred)
{
// complete a data transfer, in the direction that was requested
RCF_ASSERT(bytesTransferred > 0);
RCF_ASSERT(
(postState == Reading && bytesTransferred <= postBufferRequested) ||
(postState == Writing && bytesTransferred <= postBufferLen));
if (postState == Reading)
{
BIO_nwrite(io_bio.get(), &postBuffer, static_cast<int>(bytesTransferred)); // return value not documented
postBuffer = 0;
postBufferLen = 0;
postState = Ready;
}
else if (postState == Writing)
{
BIO_nread(io_bio.get(), &postBuffer, static_cast<int>(bytesTransferred)); // return value not documented
postBuffer = 0;
postBufferLen = 0;
postState = Ready;
}
}
void OpenSslEncryptionFilterImpl::retryReadWrite()
{
RCF_ASSERT(preState == Reading || preState == Writing);
int bioRet = preState == Reading ?
BIO_read(ssl_bio.get(), preBuffer, static_cast<int>(preBufferLen)) :
BIO_write(ssl_bio.get(), preBuffer, static_cast<int>(preBufferLen));
RCF_ASSERT(-1 <= bioRet && bioRet <= static_cast<int>(preBufferLen));
if (bioRet == -1 && BIO_should_retry(ssl_bio))
{
retry = true;
transferData(); // initiates io requests on underlying filters
}
else if (0 < bioRet && bioRet <= static_cast<int>(preBufferLen))
{
retry = false;
preBuffer += bioRet;
preBufferLen -= bioRet;
if (preState == Writing && BIO_ctrl_pending(io_bio.get()) > 0)
{
transferData();
}
else
{
preState = Ready;
openSslEncryptionFilter.mReadWriteCompletionCallback(preBufferLenOrig - preBufferLen, 0);
}
}
else
{
err = -1;
}
}
void OpenSslEncryptionFilterImpl::init()
{
ssl_ctx = boost::shared_ptr<SSL_CTX>(
SSL_CTX_new(SSLv23_method()),
SSL_CTX_free);
RCF_ASSERT(sslRole == SslServer || sslRole == SslClient);
loadCertificate(ssl_ctx, certificateFile, certificateFilePassword);
ssl = boost::shared_ptr<SSL>(
SSL_new(ssl_ctx.get()),
SSL_free);
BIO *my_bio = NULL;
BIO *my_io_bio = NULL;
BIO_new_bio_pair(&my_bio, bioBufferSize, &my_io_bio, bioBufferSize);
bio = boost::shared_ptr<BIO>(
my_bio,
BIO_free);
io_bio = boost::shared_ptr<BIO>(
my_io_bio,
BIO_free);
ssl_bio = boost::shared_ptr<BIO>(
BIO_new(BIO_f_ssl()),
BIO_free);
RCF_ASSERT(sslRole == SslServer || sslRole == SslClient);
sslRole == SslServer ?
SSL_set_accept_state(ssl.get()) :
SSL_set_connect_state(ssl.get());
SSL_set_bio(ssl.get(), bio.get(), bio.get());
BIO_set_ssl(ssl_bio.get(), ssl.get(), BIO_NOCLOSE);
if (
ssl_ctx.get() == NULL ||
ssl.get() == NULL ||
bio.get() == NULL ||
io_bio.get() == NULL)
{
RCF_THROW(ServiceException, "ssl filter failed to initialize")(getOpenSslErrors())(certificateFile);
}
}
bool OpenSslEncryptionFilterImpl::loadCertificate(boost::shared_ptr<SSL_CTX> ctx, const std::string &file, const std::string &password)
{
RCF_ASSERT(ctx.get());
if (1 == SSL_CTX_use_certificate_chain_file(ctx.get(), file.c_str()))
{
boost::shared_ptr<BIO> bio(
BIO_new( BIO_s_file() ),
BIO_free );
if (bio.get())
{
if (1 == BIO_read_filename(bio.get(), file.c_str()))
{
boost::shared_ptr<EVP_PKEY> evp(
PEM_read_bio_PrivateKey(bio.get(), NULL, NULL, (void *) password.c_str()),
EVP_PKEY_free );
if (evp.get())
{
if (1 == SSL_CTX_use_PrivateKey(ctx.get(), evp.get()))
{
return true;
}
}
}
}
}
RCF_THROW(ServiceException, "failed to load certificate")(getOpenSslErrors())(file)(password);
return false;
}
OpenSslEncryptionFilterFactory::OpenSslEncryptionFilterFactory(
const std::string &certificateFile,
const std::string &certificateFilePassword,
bool serverRole/* = true*/) :
mCertificateFile(certificateFile),
mCertificateFilePassword(certificateFilePassword),
mRole(serverRole ? SslServer : SslClient)
{}
FilterPtr OpenSslEncryptionFilterFactory::createFilter()
{
return FilterPtr( new OpenSslEncryptionFilter(mRole, mCertificateFile, mCertificateFilePassword));
}
FilterDescription OpenSslEncryptionFilterFactory::getFilterDescription()
{
return OpenSslEncryptionFilter::sGetFilterDescription();
}
} // namespace RCF