tlsopensslbase.cpp
/*
Copyright (c) 2009-2023 by Jakob Schröter <js@camaya.net>
This file is part of the gloox library. http://camaya.net/gloox
This software is distributed under a license. The full license
agreement can be found in the file LICENSE in this distribution.
This software may not be copied, modified, sold or distributed
other than expressed in the named license agreement.
This software is distributed without any warranty.
*/
#include "tlsopensslbase.h"
#ifdef HAVE_OPENSSL
#include <algorithm>
#include <cctype>
#include <ctime>
#include <cstdlib>
#include <openssl/err.h>
#include <openssl/x509v3.h>
#ifndef OPENSSL_NO_COMP
#include <openssl/comp.h>
#endif
#include <string.h>
namespace gloox
{
OpenSSLBase::OpenSSLBase( TLSHandler* th, const std::string& server )
: TLSBase( th, server ), m_ssl( 0 ), m_ctx( 0 ), m_buf( 0 ), m_bufsize( 17000 )
{
m_buf = static_cast<char*>( calloc( m_bufsize + 1, sizeof( char ) ) );
}
OpenSSLBase::~OpenSSLBase()
{
m_handler = 0;
free( m_buf );
SSL_CTX_free( m_ctx );
SSL_shutdown( m_ssl );
SSL_free( m_ssl );
BIO_free( m_nbio );
cleanup();
}
bool OpenSSLBase::init( const std::string& clientKey,
const std::string& clientCerts,
const StringList& cacerts )
{
#if defined OPENSSL_VERSION_NUMBER && ( OPENSSL_VERSION_NUMBER < 0x10100000 )
if( m_initLib )
SSL_library_init();
#endif // OPENSSL_VERSION_NUMBER < 0x10100000
#ifndef OPENSSL_NO_COMP
SSL_COMP_add_compression_method( 193, COMP_zlib() );
#endif // OPENSSL_NO_COMP
OpenSSL_add_all_algorithms();
if( !setType() ) //inits m_ctx
return false;
setClientCert( clientKey, clientCerts );
setCACerts( cacerts );
if( !SSL_CTX_set_cipher_list( m_ctx, "HIGH:MEDIUM:AES:@STRENGTH" ) )
return false;
m_ssl = SSL_new( m_ctx );
if( !m_ssl )
return false;
if( !BIO_new_bio_pair( &m_ibio, 0, &m_nbio, 0 ) )
return false;
SSL_set_bio( m_ssl, m_ibio, m_ibio );
SSL_set_mode( m_ssl, SSL_MODE_AUTO_RETRY | SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER | SSL_MODE_ENABLE_PARTIAL_WRITE );
ERR_load_crypto_strings();
SSL_load_error_strings();
if( !privateInit() )
return false;
m_valid = true;
return true;
}
bool OpenSSLBase::encrypt( const std::string& data )
{
m_sendBuffer += data;
if( !m_secure )
{
handshake();
return 0;
}
doTLSOperation( TLSWrite );
return true;
}
int OpenSSLBase::decrypt( const std::string& data )
{
m_recvBuffer += data;
if( !m_secure )
{
handshake();
return 0;
}
doTLSOperation( TLSRead );
return true;
}
void OpenSSLBase::setCACerts( const StringList& cacerts )
{
m_cacerts = cacerts;
if( m_cacerts.empty() )
SSL_CTX_set_default_verify_paths( m_ctx );
StringList::const_iterator it = m_cacerts.begin();
for( ; it != m_cacerts.end(); ++it )
SSL_CTX_load_verify_locations( m_ctx, (*it).c_str(), 0 );
}
void OpenSSLBase::setClientCert( const std::string& clientKey, const std::string& clientCerts )
{
m_clientKey = clientKey;
m_clientCerts = clientCerts;
if( !m_clientKey.empty() && !m_clientCerts.empty() )
{
if( SSL_CTX_use_certificate_chain_file( m_ctx, m_clientCerts.c_str() ) != 1 )
{
// FIXME
}
if( SSL_CTX_use_RSAPrivateKey_file( m_ctx, m_clientKey.c_str(), SSL_FILETYPE_PEM ) != 1 )
{
// FIXME
}
}
if ( SSL_CTX_check_private_key( m_ctx ) != 1 )
{
// FIXME
}
}
void OpenSSLBase::cleanup()
{
if( !m_mutex.trylock() )
return;
m_secure = false;
m_valid = false;
m_mutex.unlock();
}
void OpenSSLBase::doTLSOperation( TLSOperation op )
{
if( !m_handler )
return;
int ret = 0;
bool onceAgain = false;
do
{
switch( op )
{
case TLSHandshake:
ret = handshakeFunction();
break;
case TLSWrite:
ret = SSL_write( m_ssl, m_sendBuffer.c_str(), static_cast<int>( m_sendBuffer.length() ) );
break;
case TLSRead:
ret = SSL_read( m_ssl, m_buf, m_bufsize );
break;
}
switch( SSL_get_error( m_ssl, ret ) )
{
case SSL_ERROR_WANT_READ:
case SSL_ERROR_WANT_WRITE:
pushFunc();
break;
case SSL_ERROR_NONE:
if( op == TLSHandshake )
m_secure = true;
else if( op == TLSWrite )
m_sendBuffer.erase( 0, ret );
else if( op == TLSRead )
m_handler->handleDecryptedData( this, std::string( m_buf, ret ) );
pushFunc();
break;
default:
if( !m_secure )
m_handler->handleHandshakeResult( this, false, m_certInfo );
return;
break;
}
if( !onceAgain && !m_recvBuffer.length() )
onceAgain = true;
else if( onceAgain )
onceAgain = false;
}
while( ( ( onceAgain || m_recvBuffer.length() ) && ( !m_secure || op == TLSRead ) )
|| ( ( op == TLSWrite ) && ( ret > 0 ) ));
}
int OpenSSLBase::ASN1Time2UnixTime( ASN1_TIME* time )
{
struct tm t;
const char* str = reinterpret_cast<const char*>( time->data );
size_t i = 0;
memset( &t, 0, sizeof(t) );
if( time->type == V_ASN1_UTCTIME ) /* two digit year */
{
t.tm_year = ( str[i++] - '0' ) * 10;
t.tm_year += ( str[i++] - '0' );
if( t.tm_year < 70 )
t.tm_year += 100;
}
else if( time->type == V_ASN1_GENERALIZEDTIME ) /* four digit year */
{
t.tm_year = ( str[i++] - '0' ) * 1000;
t.tm_year += ( str[i++] - '0' ) * 100;
t.tm_year += ( str[i++] - '0' ) * 10;
t.tm_year += ( str[i++] - '0' );
t.tm_year -= 1900;
}
t.tm_mon = ( str[i++] - '0' ) * 10;
t.tm_mon += ( str[i++] - '0' ) - 1; // -1 since January is 0 not 1.
t.tm_mday = ( str[i++] - '0' ) * 10;
t.tm_mday += ( str[i++] - '0' );
t.tm_hour = ( str[i++] - '0' ) * 10;
t.tm_hour += ( str[i++] - '0' );
t.tm_min = ( str[i++] - '0' ) * 10;
t.tm_min += ( str[i++] - '0' );
t.tm_sec = ( str[i++] - '0' ) * 10;
t.tm_sec += ( str[i++] - '0' );
return static_cast<int>( mktime( &t ) );
}
#if defined OPENSSL_VERSION_NUMBER && ( OPENSSL_VERSION_NUMBER < 0x10100000 )
int SSL_SESSION_get_protocol_version( const SSL_SESSION* s )
{
return s->ssl_version;
}
#endif // OPENSSL_VERSION_NUMBER < 0x10100000
bool OpenSSLBase::handshake()
{
doTLSOperation( TLSHandshake );
if( !m_secure )
return true;
long res = SSL_get_verify_result( m_ssl );
if( res != X509_V_OK )
m_certInfo.status = CertInvalid;
else
m_certInfo.status = CertOk;
X509* peer = SSL_get_peer_certificate( m_ssl );
if( peer )
{
char peer_CN[256];
X509_NAME_get_text_by_NID( X509_get_issuer_name( peer ), NID_commonName, peer_CN, sizeof( peer_CN ) );
m_certInfo.issuer = peer_CN;
X509_NAME_get_text_by_NID( X509_get_subject_name( peer ), NID_commonName, peer_CN, sizeof( peer_CN ) );
m_certInfo.server = peer_CN;
m_certInfo.date_from = ASN1Time2UnixTime( X509_get_notBefore( peer ) );
m_certInfo.date_to = ASN1Time2UnixTime( X509_get_notAfter( peer ) );
std::string p( peer_CN );
std::transform( p.begin(), p.end(), p.begin(), tolower );
#if defined OPENSSL_VERSION_NUMBER && ( OPENSSL_VERSION_NUMBER >= 0x10002000 )
res = X509_check_host( peer, p.c_str(), p.length(), X509_CHECK_FLAG_MULTI_LABEL_WILDCARDS, 0 );
if( res <= 0 ) // 0: verification failed; -1: internal error; -2 input is malformed
m_certInfo.status |= CertWrongPeer;
#else
if( p != m_server )
m_certInfo.status |= CertWrongPeer;
#endif // OPENSSL_VERSION_NUMBER >= 0x10002000
if( ASN1_UTCTIME_cmp_time_t( X509_get_notBefore( peer ), time( 0 ) ) != -1 )
m_certInfo.status |= CertNotActive;
if( ASN1_UTCTIME_cmp_time_t( X509_get_notAfter( peer ), time( 0 ) ) != 1 )
m_certInfo.status |= CertExpired;
X509_free( peer );
}
else
{
m_certInfo.status = CertInvalid;
}
const char* tmp;
tmp = SSL_get_cipher_name( m_ssl );
if( tmp )
m_certInfo.cipher = tmp;
SSL_SESSION* sess = SSL_get_session( m_ssl );
if( sess )
{
switch( SSL_SESSION_get_protocol_version( sess ) )
{
case TLS1_VERSION:
m_certInfo.protocol = "TLSv1.0";
break;
case TLS1_1_VERSION:
m_certInfo.protocol = "TLSv1.1";
break;
case TLS1_2_VERSION:
m_certInfo.protocol = "TLSv1.2";
break;
case TLS1_3_VERSION:
m_certInfo.protocol = "TLSv1.3";
break;
default:
m_certInfo.protocol = "Unknown TLS version";
break;
}
}
tmp = SSL_COMP_get_name( SSL_get_current_compression( m_ssl ) );
if( tmp )
m_certInfo.compression = tmp;
m_valid = true;
m_handler->handleHandshakeResult( this, true, m_certInfo );
return true;
}
void OpenSSLBase::pushFunc()
{
int wantwrite;
size_t wantread;
long frombio;
long tobio;
while( ( wantwrite = BIO_pending( m_nbio ) ) > 0 )
{
if( wantwrite > m_bufsize )
wantwrite = m_bufsize;
if( !wantwrite )
break;
frombio = BIO_read( m_nbio, m_buf, wantwrite );
if( m_handler )
m_handler->handleEncryptedData( this, std::string( m_buf, frombio ) );
}
while( ( wantread = BIO_ctrl_get_read_request( m_nbio ) ) > 0 )
{
if( wantread > m_recvBuffer.length() )
wantread = m_recvBuffer.length();
if( !wantread )
break;
tobio = BIO_write( m_nbio, m_recvBuffer.c_str(), static_cast<int>( wantread ) );
m_recvBuffer.erase( 0, tobio );
}
}
}
#endif // HAVE_OPENSSL
gloox::OpenSSLBase::encrypt
virtual bool encrypt(const std::string &data)
Definition tlsopensslbase.cpp:97
gloox::OpenSSLBase::setCACerts
virtual void setCACerts(const StringList &cacerts)
Definition tlsopensslbase.cpp:125
gloox::OpenSSLBase::handshake
virtual bool handshake()
Definition tlsopensslbase.cpp:270
gloox::OpenSSLBase::cleanup
virtual void cleanup()
Definition tlsopensslbase.cpp:160
gloox::OpenSSLBase::setClientCert
virtual void setClientCert(const std::string &clientKey, const std::string &clientCerts)
Definition tlsopensslbase.cpp:137
gloox::OpenSSLBase::init
virtual bool init(const std::string &clientKey=EmptyString, const std::string &clientCerts=EmptyString, const StringList &cacerts=StringList())
Definition tlsopensslbase.cpp:53
gloox::OpenSSLBase::OpenSSLBase
OpenSSLBase(TLSHandler *th, const std::string &server=EmptyString)
Definition tlsopensslbase.cpp:36
gloox::OpenSSLBase::~OpenSSLBase
virtual ~OpenSSLBase()
Definition tlsopensslbase.cpp:42
gloox::OpenSSLBase::decrypt
virtual int decrypt(const std::string &data)
Definition tlsopensslbase.cpp:111
gloox::TLSBase::TLSBase
TLSBase(TLSHandler *th, const std::string server)
Definition tlsbase.h:39
gloox::TLSHandler
An interface that allows for interacting with TLS implementations derived from TLSBase.
Definition tlshandler.h:35
gloox::TLSHandler::handleDecryptedData
virtual void handleDecryptedData(const TLSBase *base, const std::string &data)=0
gloox::TLSHandler::handleEncryptedData
virtual void handleEncryptedData(const TLSBase *base, const std::string &data)=0
gloox::TLSHandler::handleHandshakeResult
virtual void handleHandshakeResult(const TLSBase *base, bool success, CertInfo &certinfo)=0
gloox
The namespace for the gloox library.
Definition adhoc.cpp:28
gloox::StringList
std::list< std::string > StringList
Definition gloox.h:1251
gloox::CertExpired
@ CertExpired
Definition gloox.h:979
gloox::CertOk
@ CertOk
Definition gloox.h:975
gloox::CertWrongPeer
@ CertWrongPeer
Definition gloox.h:981
gloox::CertInvalid
@ CertInvalid
Definition gloox.h:976
gloox::CertNotActive
@ CertNotActive
Definition gloox.h:980