/*! @file
@ id $ Id $
*/
// 1 2 3 4 5 6 7 8
// 45678901234567890123456789012345678901234567890123456789012345678901234567890
# ifndef __OPENSSL_HXX__
# define __OPENSSL_HXX__
# include <openssl/pkcs12.h>
# include <openssl/x509.h>
# include "openssl/bio.h"
# include "openssl/ssl.h"
# include <openssl/err.h>
# include <vector>
# include <openssl/x509v3.h> // BASIC_CONSTRAINTS
# include <cryptaux.hxx>
# include <cstdio>
/*! @defgroup gopenssl C++ Wrapper around OpenSSL API */
//@{
//! @defgroup openssllib OpenSSL C++ Library
//! @defgroup opensslexceptions OpenSSL Exceptions
//! @see gopenssl
namespace openssl {
//============================================================================
//! @addtogroup opensslexceptions
//@{
//----------------------------------------------------------------------------
class exception : public std : : exception {
public :
exception ( const std : : string & reason ) throw ( ) :
_what ( " openssl: " + reason ) {
}
~ exception ( ) throw ( ) { }
const char * what ( ) const throw ( ) {
return _what . c_str ( ) ;
}
private :
std : : string _what ;
} ;
//----------------------------------------------------------------------------
class openssl_error : public exception {
public :
openssl_error ( const std : : string & reason ) throw ( ) :
exception ( reason + ' \n ' + ERR_error_string ( ERR_get_error ( ) , 0 ) ) {
}
} ;
//----------------------------------------------------------------------------
class pkcs12_error : public openssl_error {
public :
pkcs12_error ( const std : : string & reason ) throw ( ) :
openssl_error ( " pkcs12: " + reason ) {
}
} ;
//----------------------------------------------------------------------------
class x509_error : public openssl_error {
public :
x509_error ( const std : : string & reason ) throw ( ) :
openssl_error ( " x509: " + reason ) {
}
} ;
//----------------------------------------------------------------------------
class bio_error : public openssl_error {
public :
bio_error ( const std : : string & reason ) throw ( ) :
openssl_error ( " bio: " + reason ) {
}
} ;
//----------------------------------------------------------------------------
class ssl_error : public openssl_error {
public :
ssl_error ( const std : : string & reason ) throw ( ) :
openssl_error ( " ssl: " + reason ) {
}
} ;
//----------------------------------------------------------------------------
class allocation_failed : public x509_error {
public :
allocation_failed ( ) throw ( ) :
x509_error ( " memory allocation failed " ) {
}
} ;
//----------------------------------------------------------------------------
class x509_decoding_failed : public x509_error {
public :
x509_decoding_failed ( const std : : string & der ) throw ( ) :
x509_error ( " certificate decoding failed: \n " + crypto : : readable ( der ) ) {
}
} ;
//----------------------------------------------------------------------------
class undefined_certificate : public x509_error {
public :
undefined_certificate ( ) throw ( ) :
x509_error ( " certificate must not be 0 " ) {
}
} ;
//----------------------------------------------------------------------------
class x509_parsing_failed : public x509_error {
public :
x509_parsing_failed ( ) throw ( ) :
x509_error ( " parsing DER encoded certificate failed " ) {
}
} ;
//----------------------------------------------------------------------------
class x509_copy_failed : public x509_error {
public :
x509_copy_failed ( ) throw ( ) :
x509_error ( " certificate object copy failed " ) {
}
} ;
//----------------------------------------------------------------------------
class pkcs12_reading_failed : public pkcs12_error {
public :
pkcs12_reading_failed ( const std : : string & file ) throw ( ) :
pkcs12_error ( " reading DER encoded p12 file failed: " + file ) {
}
} ;
//----------------------------------------------------------------------------
class pkcs12_parsing_failed : public pkcs12_error {
public :
pkcs12_parsing_failed ( const std : : string & file ) throw ( ) :
pkcs12_error ( " parsing DER encoded p12 file failed: " + file ) {
}
} ;
//----------------------------------------------------------------------------
class pkcs12_no_private_key : public pkcs12_error {
public :
pkcs12_no_private_key ( ) throw ( ) : pkcs12_error ( " no private key " ) { }
} ;
//----------------------------------------------------------------------------
class pkcs12_no_x509 : public pkcs12_error {
public :
pkcs12_no_x509 ( ) throw ( ) : pkcs12_error ( " no x509 certificate " ) { }
} ;
//----------------------------------------------------------------------------
class cannot_open_file : public exception {
public :
cannot_open_file ( const std : : string & file ) throw ( ) :
exception ( " cannot open file: " + file ) {
}
} ;
//----------------------------------------------------------------------------
class bio_connection_failed : public bio_error {
public :
bio_connection_failed ( const std : : string & hostPort ) throw ( ) :
bio_error ( " connection failed to: " + hostPort ) {
}
} ;
//----------------------------------------------------------------------------
class bio_closed_connection : public bio_error {
public :
bio_closed_connection ( ) throw ( ) : bio_error ( " closed connection " ) { }
} ;
//----------------------------------------------------------------------------
class bio_read_error : public bio_error {
public :
bio_read_error ( ) throw ( ) : bio_error ( " read error " ) { }
} ;
//----------------------------------------------------------------------------
class bio_write_error : public bio_error {
public :
bio_write_error ( ) throw ( ) : bio_error ( " write error " ) { }
} ;
//----------------------------------------------------------------------------
class ssl_cannot_create_context : public ssl_error {
public :
ssl_cannot_create_context ( ) throw ( ) : ssl_error ( " cannot create context " ) { }
} ;
//----------------------------------------------------------------------------
class ssl_certificate_file_not_loaded : public ssl_error {
public :
ssl_certificate_file_not_loaded ( const std : : string & file ) throw ( ) :
ssl_error ( " certificate file not loaded: " + file ) {
}
} ;
//----------------------------------------------------------------------------
class ssl_certificate_folder_not_loaded : public ssl_error {
public :
ssl_certificate_folder_not_loaded ( const std : : string & path ) throw ( ) :
ssl_error ( " certificate folder not loaded: " + path ) {
}
} ;
//@}
//! @addtogroup openssllib
//@{
//============================================================================
class Init {
public :
Init ( ) {
SSL_load_error_strings ( ) ;
ERR_load_BIO_strings ( ) ;
OpenSSL_add_all_algorithms ( ) ;
}
} ;
//============================================================================
class X509 {
public :
//! Construct empty certificate.
X509 ( ) : _x509 ( X509_new ( ) ) {
if ( ! _x509 ) throw allocation_failed ( ) ;
}
//! Initialize from DER encoded cerificate.
X509 ( const std : : string & der ) : _x509 ( 0 ) {
const unsigned char * c ( ( const unsigned char * ) der . begin ( ) . operator - > ( ) ) ;
if ( ! ( _x509 = d2i_X509 ( 0 , & c , der . size ( ) ) ) | |
( const char * ) c ! = der . begin ( ) . operator - > ( ) + der . size ( ) )
throw x509_decoding_failed ( der ) ;
}
X509 ( const X509 & o ) : _x509 ( 0 ) {
unsigned char * d ( 0 ) ;
int len ( i2d_X509 ( o . _x509 , & d ) ) ;
if ( ! len ) throw x509_copy_failed ( ) ;
const unsigned char * d2 ( d ) ;
_x509 = d2i_X509 ( 0 , & d2 , len ) ;
OPENSSL_free ( d ) ;
if ( ! _x509 ) throw x509_copy_failed ( ) ;
}
//! Take over OpenSSL allocated certificate.
X509 ( : : X509 * x509 ) : _x509 ( x509 ) {
if ( ! _x509 ) throw undefined_certificate ( ) ;
}
~ X509 ( ) {
X509_free ( _x509 ) ;
}
X509 & operator = ( const X509 & o ) {
X509_free ( _x509 ) ;
_x509 = 0 ;
unsigned char * d ( 0 ) ;
int len ( i2d_X509 ( o . _x509 , & d ) ) ;
if ( ! len ) throw x509_copy_failed ( ) ;
const unsigned char * d2 ( d ) ;
_x509 = d2i_X509 ( 0 , & d2 , len ) ;
OPENSSL_free ( d ) ;
if ( ! _x509 ) throw x509_copy_failed ( ) ;
}
//! Get DER encoded subject.
std : : string subjectDER ( ) const {
unsigned char * c ( 0 ) ;
int len ( i2d_X509_NAME ( X509_get_subject_name ( _x509 ) , & c ) ) ;
std : : string res ( ( char * ) c , len ) ;
OPENSSL_free ( c ) ;
return res ;
}
//! Get DER encoded issuer.
std : : string issuerDER ( ) const {
unsigned char * c ( 0 ) ;
int len ( i2d_X509_NAME ( X509_get_issuer_name ( _x509 ) , & c ) ) ;
std : : string res ( ( char * ) c , len ) ;
OPENSSL_free ( c ) ;
return res ;
}
//! Get DER encoded value.
std : : string valueDER ( ) const {
unsigned char * c ( 0 ) ;
int len ( i2d_X509 ( _x509 , & c ) ) ;
std : : string res ( ( char * ) c , len ) ;
OPENSSL_free ( c ) ;
return res ;
}
//! Get serial number.
std : : string serial ( ) const {
/* @bug http://albistechnologies.com reports: «could be a
failure in openSSL : len too short by 1 if serial number
starts with 00 ASN1_INTEGER * ser =
X509_get_serialNumber ( _x509 ) ; »
@ code
ASN1_INTEGER * ser ( X509_get_serialNumber ( _x509 ) ) ;
return std : : string ( ( char * ) ser - > data , ser - > length ) ;
@ endcode
- requires memory free ?
- ser - > type ? ! ? http : //albistechnologies.com prepends
tag and length in the first two char - fields . */
unsigned char * c ( 0 ) ;
int len ( i2d_X509 ( _x509 , & c ) ) ;
std : : string res ( ( char * ) c + 15 , c [ 14 ] ) ;
OPENSSL_free ( c ) ;
return res ;
}
//! Get id.
std : : string id ( ) const {
unsigned char c [ SHA_DIGEST_LENGTH ] ;
SHA1 ( _x509 - > cert_info - > key - > public_key - > data ,
_x509 - > cert_info - > key - > public_key - > length ,
c ) ;
return std : : string ( ( char * ) c , SHA_DIGEST_LENGTH ) ;
}
//! Get common name.
std : : string commonName ( ) const {
X509_NAME * name ( X509_get_subject_name ( _x509 ) ) ;
ASN1_STRING * cn
( X509_NAME_ENTRY_get_data
( X509_NAME_get_entry
( name , X509_NAME_get_index_by_NID ( name , NID_commonName , - 1 ) ) ) ) ;
return std : : string ( ( char * ) M_ASN1_STRING_data ( cn ) ,
M_ASN1_STRING_length ( cn ) ) ;
}
//! Get country name.
std : : string countryName ( ) const {
X509_NAME * name ( X509_get_subject_name ( _x509 ) ) ;
ASN1_STRING * cn
( X509_NAME_ENTRY_get_data
( X509_NAME_get_entry
( name , X509_NAME_get_index_by_NID ( name , NID_countryName , - 1 ) ) ) ) ;
return std : : string ( ( char * ) M_ASN1_STRING_data ( cn ) ,
M_ASN1_STRING_length ( cn ) ) ;
}
//! Get locality name.
std : : string localityName ( ) const {
X509_NAME * name ( X509_get_subject_name ( _x509 ) ) ;
ASN1_STRING * cn
( X509_NAME_ENTRY_get_data
( X509_NAME_get_entry
( name , X509_NAME_get_index_by_NID ( name , NID_localityName , - 1 ) ) ) ) ;
return std : : string ( ( char * ) M_ASN1_STRING_data ( cn ) ,
M_ASN1_STRING_length ( cn ) ) ;
}
//! Get state or province name.
std : : string stateOrProvinceName ( ) const {
X509_NAME * name ( X509_get_subject_name ( _x509 ) ) ;
ASN1_STRING * cn
( X509_NAME_ENTRY_get_data
( X509_NAME_get_entry
( name , X509_NAME_get_index_by_NID
( name , NID_stateOrProvinceName , - 1 ) ) ) ) ;
return std : : string ( ( char * ) M_ASN1_STRING_data ( cn ) ,
M_ASN1_STRING_length ( cn ) ) ;
}
//! Get organization name.
std : : string organizationName ( ) const {
X509_NAME * name ( X509_get_subject_name ( _x509 ) ) ;
ASN1_STRING * cn
( X509_NAME_ENTRY_get_data
( X509_NAME_get_entry
( name , X509_NAME_get_index_by_NID
( name , NID_organizationName , - 1 ) ) ) ) ;
return std : : string ( ( char * ) M_ASN1_STRING_data ( cn ) ,
M_ASN1_STRING_length ( cn ) ) ;
}
//! Check whether it's a CA certificate.
bool isCa ( ) {
static BASIC_CONSTRAINTS * bc ( 0 ) ;
if ( ! bc ) {
int pos ( X509_get_ext_by_NID ( _x509 , NID_basic_constraints , - 1 ) ) ;
if ( pos > = 0 )
bc = ( BASIC_CONSTRAINTS * ) X509V3_EXT_d2i ( X509_get_ext ( _x509 , pos ) ) ;
}
return bc & & bc - > ca ;
}
//! Get organizational unit name.
std : : string organizationalUnitName ( ) const {
X509_NAME * name ( X509_get_subject_name ( _x509 ) ) ;
ASN1_STRING * cn
( X509_NAME_ENTRY_get_data
( X509_NAME_get_entry
( name , X509_NAME_get_index_by_NID
( name , NID_organizationalUnitName , - 1 ) ) ) ) ;
return std : : string ( ( char * ) M_ASN1_STRING_data ( cn ) ,
M_ASN1_STRING_length ( cn ) ) ;
}
//! Get key usage flags.
std : : string keyUsageFlags ( ) const {
int pos ( X509_get_ext_by_NID ( _x509 , NID_key_usage , - 1 ) ) ;
if ( pos > = 0 ) {
ASN1_BIT_STRING * ku ( ( ASN1_BIT_STRING * ) X509V3_EXT_d2i
( X509_get_ext ( _x509 , pos ) ) ) ;
return std : : string ( ( char * ) M_ASN1_STRING_data ( ku ) ,
M_ASN1_STRING_length ( ku ) ) ;
} else return std : : string ( ) ; //! @todo better throw exception?
}
private :
: : X509 * _x509 ;
} ;
//============================================================================
class PrivateKey {
public :
PrivateKey ( EVP_PKEY * ) {
}
} ;
//============================================================================
class PKCS12 {
//...............................................................typedefs
public :
typedef std : : vector < X509 * > X509List ;
//................................................................methods
public :
//! Read from a PKCS#12 (.p12) file.
PKCS12 ( std : : string filename , std : : string password ) :
_key ( 0 ) , _cert ( 0 ) {
FILE * file ( fopen ( filename . c_str ( ) , " rb " ) ) ;
if ( ! file ) throw cannot_open_file ( filename ) ;
: : PKCS12 * p12 ( d2i_PKCS12_fp ( file , 0 ) ) ;
fclose ( file ) ;
if ( ! p12 ) throw pkcs12_reading_failed ( filename ) ;
try {
EVP_PKEY * pkey ( 0 ) ;
: : X509 * cert ( 0 ) ;
STACK_OF ( X509 ) * ca ( 0 ) ;
if ( ! PKCS12_parse ( p12 , password . c_str ( ) , & pkey , & cert , & ca ) )
throw pkcs12_parsing_failed ( filename ) ;
if ( pkey ) _key = new PrivateKey ( pkey ) ;
if ( cert ) _cert = new X509 ( cert ) ;
for ( int i ( sk_num ( ca ) ) ; i > 0 ; - - i )
_ca . push_back ( new X509 ( ( : : X509 * ) sk_pop ( ca ) ) ) ;
PKCS12_free ( p12 ) ;
} catch ( . . . ) {
PKCS12_free ( p12 ) ;
throw ;
}
}
~ PKCS12 ( ) {
delete _key ;
delete _cert ;
for ( X509List : : iterator it ( _ca . begin ( ) ) ; it ! = _ca . end ( ) ; + + it )
delete * it ;
}
bool hasPrivateKey ( ) {
return _key ;
}
bool hasCert ( ) {
return _cert ;
}
const PrivateKey & privateKey ( ) {
if ( ! _key ) throw pkcs12_no_private_key ( ) ;
return * _key ;
} ;
const X509 & x509 ( ) {
if ( ! _cert ) throw pkcs12_no_x509 ( ) ;
return * _cert ;
} ;
const X509List & ca ( ) const {
return _ca ;
}
private :
PrivateKey * _key ;
X509 * _cert ;
X509List _ca ;
} ;
//============================================================================
class BIO {
private :
BIO ( const BIO & ) ;
BIO & operator = ( const BIO & ) ;
public :
BIO ( ) : _bio ( 0 ) { }
~ BIO ( ) {
try {
close ( ) ;
} catch ( . . . ) {
if ( ! std : : uncaught_exception ( ) ) throw ;
}
}
BIO & connect ( const std : : string & hostPort ) {
close ( ) ;
if ( ! ( _bio = BIO_new_connect ( const_cast < char * > ( hostPort . c_str ( ) ) ) ) | |
BIO_do_connect ( _bio ) < = 0 )
throw bio_connection_failed ( hostPort ) ;
return * this ;
}
BIO & operator > > ( std : : string & s ) {
s + = read ( ) ;
return * this ;
}
BIO & operator < < ( const std : : string & s ) {
return write ( s ) ;
}
/*! @todo How can I find out, whether there's still data
available , or whether a response has been finished and
server is waiting for next request , but connection is still
open ? */
std : : string read ( ) {
if ( ! _bio ) throw bio_closed_connection ( ) ;
const int BUFF_SZ ( 1024 ) ;
char buff [ BUFF_SZ ] ;
int x ( BIO_read ( _bio , buff , BUFF_SZ ) ) ;
if ( x < = 0 )
if ( BIO_should_retry ( _bio ) ) return read ( ) ;
else throw bio_read_error ( ) ;
return std : : string ( buff , x ) ;
}
BIO & write ( const std : : string & s ) {
int x ( BIO_write ( _bio , s . begin ( ) . operator - > ( ) , s . size ( ) ) ) ;
if ( x < = 0 )
if ( BIO_should_retry ( _bio ) ) return write ( s ) ;
else throw bio_write_error ( ) ;
else
if ( x < s . size ( ) ) return write ( s . substr ( x ) ) ;
return * this ;
}
void close ( ) {
BIO_free_all ( _bio ) ;
}
private :
friend class SSL ;
: : BIO * _bio ;
} ;
//! Read authorized certificate from a single pem file.
class TrustStore {
public :
TrustStore ( const std : : string & pathToPemFile ) :
_file ( pathToPemFile ) {
}
const std : : string & file ( ) const {
return _file ;
}
private :
std : : string _file ;
} ;
class CertificateFolder {
public :
CertificateFolder ( const std : : string & certificateFolder ) :
_path ( certificateFolder ) {
}
const std : : string & path ( ) const {
return _path ;
}
private :
std : : string _path ;
} ;
class SSL {
private :
SSL ( ) ;
SSL ( const SSL & ) ;
SSL & operator = ( const SSL & ) ;
public :
SSL ( const TrustStore & file ) :
_ctx ( SSL_CTX_new ( SSLv23_client_method ( ) ) ) ,
_ssl ( 0 ) {
if ( ! _ctx ) throw ssl_cannot_create_context ( ) ;
if ( ! SSL_CTX_load_verify_locations ( _ctx , file . file ( ) . c_str ( ) , 0 ) )
throw ssl_certificate_file_not_loaded ( file . file ( ) ) ;
}
SSL ( const CertificateFolder & folder ) :
_ctx ( SSL_CTX_new ( SSLv23_client_method ( ) ) ) ,
_ssl ( 0 ) {
if ( ! _ctx ) throw ssl_cannot_create_context ( ) ;
if ( ! SSL_CTX_load_verify_locations ( _ctx , 0 , folder . path ( ) . c_str ( ) ) )
throw ssl_certificate_folder_not_loaded ( folder . path ( ) ) ;
}
~ SSL ( ) {
close ( ) ;
SSL_CTX_free ( _ctx ) ;
}
BIO & connect ( const std : : string & hostPort ) {
close ( ) ;
if ( ! ( _bio . _bio = BIO_new_ssl_connect ( _ctx ) ) )
throw bio_connection_failed ( hostPort ) ;
BIO_get_ssl ( _bio . _bio , & _ssl ) ;
if ( ! _ssl )
SSL_set_mode ( _ssl , SSL_MODE_AUTO_RETRY ) ;
BIO_set_conn_hostname ( _bio . _bio , const_cast < char * > ( hostPort . c_str ( ) ) ) ;
if ( BIO_do_connect ( _bio . _bio ) < = 0 ) throw bio_connection_failed ( hostPort ) ;
return _bio ;
}
SSL & close ( ) {
_bio . close ( ) ;
_ssl = 0 ; //! @todo is this correct? <--
return * this ;
}
bool verifyResult ( ) {
return _ssl & & SSL_get_verify_result ( _ssl ) = = X509_V_OK ;
}
private :
SSL_CTX * _ctx ;
: : SSL * _ssl ;
BIO _bio ;
} ;
//@}
}
//@}
# endif