2008-01-28 16:37:16 -05:00
/* source: xio-openssl.c */
2008-02-01 17:15:14 -05:00
/* Copyright Gerhard Rieger 2002-2008 */
2008-01-27 07:00:08 -05:00
/* Published under the GNU General Public License V.2, see file COPYING */
/* this file contains the implementation of the openssl addresses */
# include "xiosysincludes.h"
# if WITH_OPENSSL /* make this address configure dependend */
# include "xioopen.h"
# include "xio-fd.h"
# include "xio-socket.h" /* _xioopen_connect() */
# include "xio-listen.h"
# include "xio-ipapp.h"
# include "xio-openssl.h"
/* the openssl library requires a file descriptor for external communications.
so our best effort is to provide any possible kind of un * x file descriptor
( not only tcp , but also pipes , stdin , files . . . )
for tcp we want to provide support for socks and proxy .
read and write functions must use the openssl crypt versions .
but currently only plain tcp4 is implemented .
*/
/* Linux: "man 3 ssl" */
/* generate a simple openssl server for testing:
1 ) generate a private key
openssl genrsa - out server . key 1024
2 ) generate a self signed cert
openssl req - new - key server . key - x509 - days 3653 - out server . crt
enter fields . . .
3 ) generate the pem file
cat server . key server . crt > server . pem
openssl s_server ( listens on 4433 / tcp )
*/
/* static declaration of ssl's open function */
static int xioopen_openssl_connect ( int argc , const char * argv [ ] , struct opt * opts ,
int xioflags , xiofile_t * fd , unsigned groups ,
int dummy1 , int dummy2 , int dummy3 ) ;
/* static declaration of ssl's open function */
static int xioopen_openssl_listen ( int argc , const char * argv [ ] , struct opt * opts ,
int xioflags , xiofile_t * fd , unsigned groups ,
int dummy1 , int dummy2 , int dummy3 ) ;
static int openssl_SSL_ERROR_SSL ( int level , const char * funcname ) ;
static int openssl_handle_peer_certificate ( struct single * xfd , bool opt_ver ,
int level ) ;
static int xioSSL_set_fd ( struct single * xfd , int level ) ;
static int xioSSL_connect ( struct single * xfd , bool opt_ver , int level ) ;
/* description record for ssl connect */
const struct addrdesc addr_openssl = {
" openssl " , /* keyword for selecting this address type in xioopen calls
( canonical or main name ) */
3 , /* data flow directions this address supports on API layer:
1. . read , 2. . write , 3. . both */
xioopen_openssl_connect , /* a function pointer used to "open" these addresses.*/
GROUP_FD | GROUP_SOCKET | GROUP_SOCK_IP4 | GROUP_SOCK_IP6 | GROUP_IP_TCP | GROUP_CHILD | GROUP_OPENSSL | GROUP_RETRY , /* bitwise OR of address groups this address belongs to.
You might have to specify a new group in xioopts . h */
0 , /* an integer passed to xioopen_openssl; makes it possible to
use the same xioopen_openssl function for slightly different
address types . */
0 , /* like previous argument */
0 /* like previous arguments, but pointer type.
No trailing comma or semicolon ! */
HELP ( " :<host>:<port> " ) /* a text displayed from xio help function.
No trailing comma or semicolon !
only generates this text if WITH_HELP is ! = 0 */
} ;
# if WITH_LISTEN
/* description record for ssl listen */
const struct addrdesc addr_openssl_listen = {
" openssl-listen " , /* keyword for selecting this address type in xioopen calls
( canonical or main name ) */
3 , /* data flow directions this address supports on API layer:
1. . read , 2. . write , 3. . both */
xioopen_openssl_listen , /* a function pointer used to "open" these addresses.*/
GROUP_FD | GROUP_SOCKET | GROUP_SOCK_IP4 | GROUP_SOCK_IP6 | GROUP_IP_TCP | GROUP_LISTEN | GROUP_CHILD | GROUP_RANGE | GROUP_OPENSSL | GROUP_RETRY , /* bitwise OR of address groups this address belongs to.
You might have to specify a new group in xioopts . h */
0 , /* an integer passed to xioopen_openssl_listen; makes it possible to
use the same xioopen_openssl_listen function for slightly different
address types . */
0 , /* like previous argument */
0 /* like previous arguments, but pointer type.
No trailing comma or semicolon ! */
HELP ( " :<port> " ) /* a text displayed from xio help function.
No trailing comma or semicolon !
only generates this text if WITH_HELP is ! = 0 */
} ;
# endif /* WITH_LISTEN */
/* both client and server */
const struct optdesc opt_openssl_cipherlist = { " openssl-cipherlist " , " ciphers " , OPT_OPENSSL_CIPHERLIST , GROUP_OPENSSL , PH_SPEC , TYPE_STRING , OFUNC_SPEC } ;
const struct optdesc opt_openssl_method = { " openssl-method " , " method " , OPT_OPENSSL_METHOD , GROUP_OPENSSL , PH_SPEC , TYPE_STRING , OFUNC_SPEC } ;
const struct optdesc opt_openssl_verify = { " openssl-verify " , " verify " , OPT_OPENSSL_VERIFY , GROUP_OPENSSL , PH_SPEC , TYPE_BOOL , OFUNC_SPEC } ;
const struct optdesc opt_openssl_certificate = { " openssl-certificate " , " cert " , OPT_OPENSSL_CERTIFICATE , GROUP_OPENSSL , PH_SPEC , TYPE_FILENAME , OFUNC_SPEC } ;
const struct optdesc opt_openssl_key = { " openssl-key " , " key " , OPT_OPENSSL_KEY , GROUP_OPENSSL , PH_SPEC , TYPE_FILENAME , OFUNC_SPEC } ;
const struct optdesc o pt_openssl_dhparam = { " openssl-dhparam " , " dh " , OPT_OPENSSL_DHPARAM , GROUP_OPENSSL , PH_SPEC , TYPE_FILENAME , OFUNC_SPEC } ;
const struct optdesc opt_openssl_cafile = { " openssl-cafile " , " cafile " , OPT_OPENSSL_CAFILE , GROUP_OPENSSL , PH_SPEC , TYPE_FILENAME , OFUNC_SPEC } ;
const struct optdesc opt_openssl_capath = { " openssl-capath " , " capath " , OPT_OPENSSL_CAPATH , GROUP_OPENSSL , PH_SPEC , TYPE_FILENAME , OFUNC_SPEC } ;
const struct optdesc opt_openssl_egd = { " openssl-egd " , " egd " , OPT_OPENSSL_EGD , GROUP_OPENSSL , PH_SPEC , TYPE_FILENAME , OFUNC_SPEC } ;
const struct optdesc opt_openssl_pseudo = { " openssl-pseudo " , " pseudo " , OPT_OPENSSL_PSEUDO , GROUP_OPENSSL , PH_SPEC , TYPE_BOOL , OFUNC_SPEC } ;
# if WITH_FIPS
const struct optdesc opt_openssl_fips = { " openssl-fips " , " fips " , OPT_OPENSSL_FIPS , GROUP_OPENSSL , PH_SPEC , TYPE_BOOL , OFUNC_SPEC } ;
# endif
/* If FIPS is compiled in, we need to track if the user asked for FIPS mode.
* On forks , the FIPS mode must be reset by a disable , then enable since
* FIPS tracks the process ID that initializes things .
* If FIPS is not compiled in , no tracking variable is needed
* and we make the reset code compile out . This keeps the
* rest of the code below free of FIPS related # ifs
*/
# if WITH_FIPS
static bool xio_openssl_fips = false ;
int xio_reset_fips_mode ( void ) {
if ( xio_openssl_fips ) {
if ( ! sycFIPS_mode_set ( 0 ) | | ! sycFIPS_mode_set ( 1 ) ) {
ERR_load_crypto_strings ( ) ;
ERR_print_errors ( BIO_new_fp ( stderr , BIO_NOCLOSE ) ) ;
Error ( " Failed to reset OpenSSL FIPS mode " ) ;
xio_openssl_fips = false ;
return - 1 ;
}
}
return 0 ;
}
# else
# define xio_reset_fips_mode() 0
# endif
/* the open function for OpenSSL client */
static int
xioopen_openssl_connect ( int argc ,
const char * argv [ ] , /* the arguments in the address string */
struct opt * opts ,
int xioflags , /* is the open meant for reading (0),
writing ( 1 ) , or both ( 2 ) ? */
xiofile_t * xxfd , /* a xio file descriptor structure,
already allocated */
unsigned groups , /* the matching address groups... */
int dummy1 , /* first transparent integer value from
addr_openssl */
int dummy2 , /* second transparent integer value from
addr_openssl */
int dummy3 ) /* transparent pointer value from
addr_openssl */
{
struct single * xfd = & xxfd - > stream ;
struct opt * opts0 = NULL ;
const char * hostname , * portname ;
int pf = PF_UNSPEC ;
int ipproto = IPPROTO_TCP ;
int socktype = SOCK_STREAM ;
bool dofork = false ;
union sockaddr_union us_sa , * us = & us_sa ;
union sockaddr_union them_sa , * them = & them_sa ;
socklen_t uslen = sizeof ( us_sa ) ;
socklen_t themlen = sizeof ( them_sa ) ;
bool needbind = false ;
bool lowport = false ;
int level ;
SSL_CTX * ctx ;
bool opt_ver = true ; /* verify peer certificate */
char * opt_cert = NULL ; /* file name of client certificate */
int result ;
if ( ! ( xioflags & XIO_MAYCONVERT ) ) {
Error ( " address with data processing not allowed here " ) ;
return STAT_NORETRY ;
}
xfd - > flags | = XIO_DOESCONVERT ;
if ( argc ! = 3 ) {
Error1 ( " %s: 2 parameters required " , argv [ 0 ] ) ;
return STAT_NORETRY ;
}
hostname = argv [ 1 ] ;
portname = argv [ 2 ] ;
xfd - > howtoend = END_SHUTDOWN ;
if ( applyopts_single ( xfd , opts , PH_INIT ) < 0 ) return - 1 ;
applyopts ( - 1 , opts , PH_INIT ) ;
retropt_bool ( opts , OPT_FORK , & dofork ) ;
retropt_string ( opts , OPT_OPENSSL_CERTIFICATE , & opt_cert ) ;
result =
_xioopen_openssl_prepare ( opts , xfd , false , & opt_ver , opt_cert , & ctx ) ;
if ( result ! = STAT_OK ) return STAT_NORETRY ;
result =
_xioopen_ipapp_prepare ( opts , & opts0 , hostname , portname , & pf , ipproto ,
xfd - > para . socket . ip . res_opts [ 1 ] ,
xfd - > para . socket . ip . res_opts [ 0 ] ,
them , & themlen , us , & uslen ,
2008-09-20 17:47:06 -04:00
& needbind , & lowport , socktype ) ;
2008-01-27 07:00:08 -05:00
if ( result ! = STAT_OK ) return STAT_NORETRY ;
if ( xioopts . logopt = = ' m ' ) {
Info ( " starting connect loop, switching to syslog " ) ;
diag_set ( ' y ' , xioopts . syslogfac ) ; xioopts . logopt = ' y ' ;
} else {
Info ( " starting connect loop " ) ;
}
do { /* loop over failed connect and SSL handshake attempts */
# if WITH_RETRY
if ( xfd - > forever | | xfd - > retry ) {
level = E_INFO ;
} else
# endif /* WITH_RETRY */
level = E_ERROR ;
/* this cannot fork because we retrieved fork option above */
result =
_xioopen_connect ( xfd ,
needbind ? ( struct sockaddr * ) us : NULL , sizeof ( * us ) ,
( struct sockaddr * ) them , themlen ,
opts , pf , socktype , ipproto , lowport , level ) ;
switch ( result ) {
case STAT_OK : break ;
# if WITH_RETRY
case STAT_RETRYLATER :
case STAT_RETRYNOW :
if ( xfd - > forever | | xfd - > retry ) {
dropopts ( opts , PH_ALL ) ; opts = copyopts ( opts0 , GROUP_ALL ) ;
if ( result = = STAT_RETRYLATER ) {
Nanosleep ( & xfd - > intervall , NULL ) ;
}
- - xfd - > retry ;
continue ;
}
return STAT_NORETRY ;
# endif /* WITH_RETRY */
default :
return result ;
}
/*! isn't this too early? */
if ( ( result = _xio_openlate ( xfd , opts ) ) < 0 ) {
return result ;
}
result = _xioopen_openssl_connect ( xfd , opt_ver , ctx , level ) ;
switch ( result ) {
case STAT_OK : break ;
# if WITH_RETRY
case STAT_RETRYLATER :
case STAT_RETRYNOW :
if ( xfd - > forever | | xfd - > retry ) {
Close ( xfd - > fd ) ;
dropopts ( opts , PH_ALL ) ; opts = copyopts ( opts0 , GROUP_ALL ) ;
if ( result = = STAT_RETRYLATER ) {
Nanosleep ( & xfd - > intervall , NULL ) ;
}
- - xfd - > retry ;
continue ;
}
# endif /* WITH_RETRY */
default : return STAT_NORETRY ;
}
2008-02-01 17:15:14 -05:00
if ( dofork ) {
xiosetchilddied ( ) ; /* set SIGCHLD handler */
}
2008-01-27 07:00:08 -05:00
# if WITH_RETRY
if ( dofork ) {
pid_t pid ;
2008-09-22 16:17:55 -04:00
int level = E_ERROR ;
if ( xfd - > forever | | xfd - > retry ) {
level = E_WARN ;
}
while ( ( pid = xio_fork ( false , level ) ) < 0 ) {
if ( xfd - > forever | | - - xfd - > retry ) {
Nanosleep ( & xfd - > intervall , NULL ) ; continue ;
2008-01-27 07:00:08 -05:00
}
return STAT_RETRYLATER ;
}
2008-09-22 16:17:55 -04:00
if ( pid = = 0 ) { /* child process */
xfd - > forever = false ; xfd - > retry = 0 ;
2008-01-27 07:00:08 -05:00
break ;
}
2008-09-22 16:17:55 -04:00
2008-01-27 07:00:08 -05:00
/* parent process */
Close ( xfd - > fd ) ;
sycSSL_free ( xfd - > para . openssl . ssl ) ;
xfd - > para . openssl . ssl = NULL ;
/* with and without retry */
Nanosleep ( & xfd - > intervall , NULL ) ;
dropopts ( opts , PH_ALL ) ; opts = copyopts ( opts0 , GROUP_ALL ) ;
continue ; /* with next socket() bind() connect() */
}
# endif /* WITH_RETRY */
break ;
} while ( true ) ; /* drop out on success */
Notice1 ( " SSL connection using %s " , SSL_get_cipher ( xfd - > para . openssl . ssl ) ) ;
/* fill in the fd structure */
return STAT_OK ;
}
/* this function is typically called within the OpenSSL client fork/retry loop.
xfd must be of type DATA_OPENSSL , and its fd must be set with a valid file
descriptor . this function then performs all SSL related step to make a valid
SSL connection from an FD and a CTX . */
int _xioopen_openssl_connect ( struct single * xfd ,
bool opt_ver ,
SSL_CTX * ctx ,
int level ) {
SSL * ssl ;
unsigned long err ;
int result ;
/* create a SSL object */
if ( ( ssl = sycSSL_new ( ctx ) ) = = NULL ) {
if ( ERR_peek_error ( ) = = 0 ) Msg ( level , " SSL_new() failed " ) ;
while ( err = ERR_get_error ( ) ) {
Msg1 ( level , " SSL_new(): %s " , ERR_error_string ( err , NULL ) ) ;
}
/*Error("SSL_new()");*/
return STAT_RETRYLATER ;
}
xfd - > para . openssl . ssl = ssl ;
result = xioSSL_set_fd ( xfd , level ) ;
if ( result ! = STAT_OK ) {
sycSSL_free ( xfd - > para . openssl . ssl ) ;
xfd - > para . openssl . ssl = NULL ;
return result ;
}
result = xioSSL_connect ( xfd , opt_ver , level ) ;
if ( result ! = STAT_OK ) {
sycSSL_free ( xfd - > para . openssl . ssl ) ;
xfd - > para . openssl . ssl = NULL ;
return result ;
}
result = openssl_handle_peer_certificate ( xfd , opt_ver , level ) ;
if ( result ! = STAT_OK ) {
sycSSL_free ( xfd - > para . openssl . ssl ) ;
xfd - > para . openssl . ssl = NULL ;
return result ;
}
return STAT_OK ;
}
# if WITH_LISTEN
static int
xioopen_openssl_listen ( int argc ,
const char * argv [ ] , /* the arguments in the address string */
struct opt * opts ,
int xioflags , /* is the open meant for reading (0),
writing ( 1 ) , or both ( 2 ) ? */
xiofile_t * xxfd , /* a xio file descriptor structure,
already allocated */
unsigned groups , /* the matching address groups... */
int dummy1 , /* first transparent integer value from
addr_openssl */
int dummy2 , /* second transparent integer value from
addr_openssl */
int dummy3 ) /* transparent pointer value from
addr_openssl */
{
struct single * xfd = & xxfd - > stream ;
const char * portname ;
struct opt * opts0 = NULL ;
union sockaddr_union us_sa , * us = & us_sa ;
socklen_t uslen = sizeof ( us_sa ) ;
int pf ;
int socktype = SOCK_STREAM ;
int ipproto = IPPROTO_TCP ;
/*! lowport? */
int level ;
SSL_CTX * ctx ;
bool opt_ver = true ; /* verify peer certificate - changed with 1.6.0 */
char * opt_cert = NULL ; /* file name of server certificate */
int result ;
if ( ! ( xioflags & XIO_MAYCONVERT ) ) {
Error ( " address with data processing not allowed here " ) ;
return STAT_NORETRY ;
}
xfd - > flags | = XIO_DOESCONVERT ;
if ( argc ! = 2 ) {
Error1 ( " %s: 1 parameter required " , argv [ 0 ] ) ;
return STAT_NORETRY ;
}
# if WITH_IP4 && WITH_IP6
pf = xioopts . default_ip = = ' 6 ' ? PF_INET6 : PF_INET ;
# elif WITH_IP6
pf = PF_INET6 ;
# else
pf = PF_INET ;
# endif
portname = argv [ 1 ] ;
xfd - > howtoend = END_SHUTDOWN ;
if ( applyopts_single ( xfd , opts , PH_INIT ) < 0 ) return - 1 ;
applyopts ( - 1 , opts , PH_INIT ) ;
retropt_string ( opts , OPT_OPENSSL_CERTIFICATE , & opt_cert ) ;
if ( opt_cert = = NULL ) {
Warn ( " no certificate given; consider option \" cert \" " ) ;
}
applyopts ( - 1 , opts , PH_EARLY ) ;
result =
_xioopen_openssl_prepare ( opts , xfd , true , & opt_ver , opt_cert , & ctx ) ;
if ( result ! = STAT_OK ) return STAT_NORETRY ;
if ( _xioopen_ipapp_listen_prepare ( opts , & opts0 , portname , & pf , ipproto ,
xfd - > para . socket . ip . res_opts [ 1 ] ,
xfd - > para . socket . ip . res_opts [ 0 ] ,
2008-09-20 17:47:06 -04:00
us , & uslen , socktype )
2008-01-27 07:00:08 -05:00
! = STAT_OK ) {
return STAT_NORETRY ;
}
xfd - > addr = & addr_openssl_listen ;
xfd - > dtype = XIODATA_OPENSSL ;
while ( true ) { /* loop over failed attempts */
# if WITH_RETRY
if ( xfd - > forever | | xfd - > retry ) {
level = E_INFO ;
} else
# endif /* WITH_RETRY */
level = E_ERROR ;
/* tcp listen; this can fork() for us; it only returns on error or on
successful establishment of tcp connection */
result = _xioopen_listen ( xfd , xioflags ,
( struct sockaddr * ) us , uslen ,
opts , pf , socktype , IPPROTO_TCP ,
# if WITH_RETRY
( xfd - > retry | | xfd - > forever ) ? E_INFO : E_ERROR
# else
E_ERROR
# endif /* WITH_RETRY */
) ;
/*! not sure if we should try again on retry/forever */
switch ( result ) {
case STAT_OK : break ;
# if WITH_RETRY
case STAT_RETRYLATER :
case STAT_RETRYNOW :
if ( xfd - > forever | | xfd - > retry ) {
dropopts ( opts , PH_ALL ) ; opts = copyopts ( opts0 , GROUP_ALL ) ;
if ( result = = STAT_RETRYLATER ) {
Nanosleep ( & xfd - > intervall , NULL ) ;
}
dropopts ( opts , PH_ALL ) ; opts = copyopts ( opts0 , GROUP_ALL ) ;
- - xfd - > retry ;
continue ;
}
return STAT_NORETRY ;
# endif /* WITH_RETRY */
default :
return result ;
}
result = _xioopen_openssl_listen ( xfd , opt_ver , ctx , level ) ;
switch ( result ) {
case STAT_OK : break ;
# if WITH_RETRY
case STAT_RETRYLATER :
case STAT_RETRYNOW :
if ( xfd - > forever | | xfd - > retry ) {
dropopts ( opts , PH_ALL ) ; opts = copyopts ( opts0 , GROUP_ALL ) ;
if ( result = = STAT_RETRYLATER ) {
Nanosleep ( & xfd - > intervall , NULL ) ;
}
dropopts ( opts , PH_ALL ) ; opts = copyopts ( opts0 , GROUP_ALL ) ;
- - xfd - > retry ;
continue ;
}
return STAT_NORETRY ;
# endif /* WITH_RETRY */
default :
return result ;
}
Notice1 ( " SSL connection using %s " ,
SSL_get_cipher ( xfd - > para . openssl . ssl ) ) ;
break ;
} /* drop out on success */
/* fill in the fd structure */
return STAT_OK ;
}
int _xioopen_openssl_listen ( struct single * xfd ,
bool opt_ver ,
SSL_CTX * ctx ,
int level ) {
char error_string [ 120 ] ;
unsigned long err ;
int errint , ret ;
/* create an SSL object */
if ( ( xfd - > para . openssl . ssl = sycSSL_new ( ctx ) ) = = NULL ) {
if ( ERR_peek_error ( ) = = 0 ) Msg ( level , " SSL_new() failed " ) ;
while ( err = ERR_get_error ( ) ) {
Msg1 ( level , " SSL_new(): %s " , ERR_error_string ( err , NULL ) ) ;
}
/*Error("SSL_new()");*/
return STAT_NORETRY ;
}
/* assign the network connection to the SSL object */
if ( sycSSL_set_fd ( xfd - > para . openssl . ssl , xfd - > fd ) < = 0 ) {
if ( ERR_peek_error ( ) = = 0 ) Msg ( level , " SSL_set_fd() failed " ) ;
while ( err = ERR_get_error ( ) ) {
Msg2 ( level , " SSL_set_fd(, %d): %s " ,
xfd - > fd , ERR_error_string ( err , NULL ) ) ;
}
}
# if WITH_DEBUG
{
int i = 0 ;
const char * ciphers = NULL ;
Debug ( " available ciphers: " ) ;
do {
ciphers = SSL_get_cipher_list ( xfd - > para . openssl . ssl , i ) ;
if ( ciphers = = NULL ) break ;
Debug2 ( " CIPHERS pri=%d: %s " , i , ciphers ) ;
+ + i ;
} while ( 1 ) ;
}
# endif /* WITH_DEBUG */
/* connect via SSL by performing handshake */
if ( ( ret = sycSSL_accept ( xfd - > para . openssl . ssl ) ) < = 0 ) {
/*if (ERR_peek_error() == 0) Msg(level, "SSL_accept() failed");*/
errint = SSL_get_error ( xfd - > para . openssl . ssl , ret ) ;
switch ( errint ) {
case SSL_ERROR_NONE :
Msg ( level , " ok " ) ; break ;
case SSL_ERROR_ZERO_RETURN :
Msg ( level , " connection closed (wrong version number?) " ) ; break ;
case SSL_ERROR_WANT_READ : case SSL_ERROR_WANT_WRITE :
case SSL_ERROR_WANT_CONNECT :
case SSL_ERROR_WANT_X509_LOOKUP :
Msg ( level , " nonblocking operation did not complete " ) ; break ; /*!*/
case SSL_ERROR_SYSCALL :
if ( ERR_peek_error ( ) = = 0 ) {
if ( ret = = 0 ) {
Msg ( level , " SSL_accept(): socket closed by peer " ) ;
} else if ( ret = = - 1 ) {
Msg1 ( level , " SSL_accept(): %s " , strerror ( errno ) ) ;
}
} else {
Msg ( level , " I/O error " ) ; /*!*/
while ( err = ERR_get_error ( ) ) {
ERR_error_string_n ( err , error_string , sizeof ( error_string ) ) ;
Msg4 ( level , " SSL_accept(): %s / %s / %s / %s " , error_string ,
ERR_lib_error_string ( err ) , ERR_func_error_string ( err ) ,
ERR_reason_error_string ( err ) ) ;
}
/* Msg1(level, "SSL_connect(): %s", ERR_error_string(e, buf));*/
}
break ;
case SSL_ERROR_SSL :
/*ERR_print_errors_fp(stderr);*/
openssl_SSL_ERROR_SSL ( level , " SSL_accept " ) ;
break ;
default :
Msg ( level , " unknown error " ) ;
}
return STAT_RETRYLATER ;
}
if ( openssl_handle_peer_certificate ( xfd , opt_ver , E_ERROR /*!*/ ) < 0 ) {
return STAT_NORETRY ;
}
return STAT_OK ;
}
# endif /* WITH_LISTEN */
int
_xioopen_openssl_prepare ( struct opt * opts ,
struct single * xfd , /* a xio file descriptor
structure , already allocated
*/
bool server , /* SSL client: false */
bool * opt_ver ,
const char * opt_cert ,
SSL_CTX * * ctx )
{
bool opt_fips = false ;
SSL_METHOD * method ;
char * me_str = NULL ; /* method string */
char * ci_str = NULL ; /* cipher string */
char * opt_key = NULL ; /* file name of client private key */
char * opt_dhparam = NULL ; /* file name of DH params */
char * opt_cafile = NULL ; /* certificate authority file */
char * opt_capath = NULL ; /* certificate authority directory */
char * opt_egd = NULL ; /* entropy gathering daemon socket path */
bool opt_pseudo = false ; /* use pseudo entropy if nothing else */
unsigned long err ;
int result ;
xfd - > addr = & addr_openssl ;
xfd - > dtype = XIODATA_OPENSSL ;
retropt_bool ( opts , OPT_OPENSSL_FIPS , & opt_fips ) ;
retropt_string ( opts , OPT_OPENSSL_METHOD , & me_str ) ;
retropt_string ( opts , OPT_OPENSSL_CIPHERLIST , & ci_str ) ;
retropt_bool ( opts , OPT_OPENSSL_VERIFY , opt_ver ) ;
retropt_string ( opts , OPT_OPENSSL_CAFILE , & opt_cafile ) ;
retropt_string ( opts , OPT_OPENSSL_CAPATH , & opt_capath ) ;
retropt_string ( opts , OPT_OPENSSL_KEY , & opt_key ) ;
retropt_string ( opts , OPT_OPENSSL_DHPARAM , & opt_dhparam ) ;
retropt_string ( opts , OPT_OPENSSL_EGD , & opt_egd ) ;
retropt_bool ( opts , OPT_OPENSSL_PSEUDO , & opt_pseudo ) ;
# if WITH_FIPS
if ( opt_fips ) {
if ( ! sycFIPS_mode_set ( 1 ) ) {
ERR_load_crypto_strings ( ) ;
ERR_print_errors ( BIO_new_fp ( stderr , BIO_NOCLOSE ) ) ;
Error ( " Failed to set FIPS mode " ) ;
} else {
xio_openssl_fips = true ;
}
}
# endif
OpenSSL_add_all_algorithms ( ) ;
OpenSSL_add_all_ciphers ( ) ;
OpenSSL_add_all_digests ( ) ;
sycSSL_load_error_strings ( ) ;
/* OpenSSL preparation */
sycSSL_library_init ( ) ;
/*! actions_to_seed_PRNG();*/
if ( ! server ) {
if ( me_str ! = 0 ) {
if ( ! strcasecmp ( me_str , " SSLv2 " ) | | ! strcasecmp ( me_str , " SSL2 " ) ) {
method = sycSSLv2_client_method ( ) ;
} else if ( ! strcasecmp ( me_str , " SSLv3 " ) | | ! strcasecmp ( me_str , " SSL3 " ) ) {
method = sycSSLv3_client_method ( ) ;
} else if ( ! strcasecmp ( me_str , " SSLv23 " ) | | ! strcasecmp ( me_str , " SSL23 " ) | |
! strcasecmp ( me_str , " SSL " ) ) {
method = sycSSLv23_client_method ( ) ;
} else if ( ! strcasecmp ( me_str , " TLSv1 " ) | | ! strcasecmp ( me_str , " TLS1 " ) | |
! strcasecmp ( me_str , " TLS " ) ) {
method = sycTLSv1_client_method ( ) ;
} else {
Error1 ( " openssl-method= \" %s \" : unknown method " , me_str ) ;
method = sycSSLv23_client_method ( ) /*!*/ ;
}
} else {
method = sycSSLv23_client_method ( ) /*!*/ ;
}
} else /* server */ {
if ( me_str ! = 0 ) {
if ( ! strcasecmp ( me_str , " SSLv2 " ) | | ! strcasecmp ( me_str , " SSL2 " ) ) {
method = sycSSLv2_server_method ( ) ;
} else if ( ! strcasecmp ( me_str , " SSLv3 " ) | | ! strcasecmp ( me_str , " SSL3 " ) ) {
method = sycSSLv3_server_method ( ) ;
} else if ( ! strcasecmp ( me_str , " SSLv23 " ) | | ! strcasecmp ( me_str , " SSL23 " ) | |
! strcasecmp ( me_str , " SSL " ) ) {
method = sycSSLv23_server_method ( ) ;
} else if ( ! strcasecmp ( me_str , " TLSv1 " ) | | ! strcasecmp ( me_str , " TLS1 " ) | |
! strcasecmp ( me_str , " TLS " ) ) {
method = sycTLSv1_server_method ( ) ;
} else {
Error1 ( " openssl-method= \" %s \" : unknown method " , me_str ) ;
method = sycSSLv23_server_method ( ) /*!*/ ;
}
} else {
method = sycSSLv23_server_method ( ) /*!*/ ;
}
}
if ( opt_egd ) {
sycRAND_egd ( opt_egd ) ;
}
if ( opt_pseudo ) {
long int randdata ;
/* initialize libc random from actual microseconds */
struct timeval tv ;
struct timezone tz ;
tz . tz_minuteswest = 0 ;
tz . tz_dsttime = 0 ;
if ( ( result = Gettimeofday ( & tv , & tz ) ) < 0 ) {
Warn2 ( " gettimeofday(%p, {0,0}): %s " , & tv , strerror ( errno ) ) ;
}
srandom ( tv . tv_sec * 1000000 + tv . tv_usec ) ;
while ( ! RAND_status ( ) ) {
randdata = random ( ) ;
Debug2 ( " RAND_seed(0x{%lx}, " F_Zu " ) " ,
randdata , sizeof ( randdata ) ) ;
RAND_seed ( & randdata , sizeof ( randdata ) ) ;
}
}
if ( ( * ctx = sycSSL_CTX_new ( method ) ) = = NULL ) {
if ( ERR_peek_error ( ) = = 0 ) Error ( " SSL_CTX_new() " ) ;
while ( err = ERR_get_error ( ) ) {
Error1 ( " SSL_CTX_new(): %s " , ERR_error_string ( err , NULL ) ) ;
}
/*ERR_clear_error;*/
return STAT_RETRYLATER ;
}
if ( opt_cafile ! = NULL | | opt_capath ! = NULL ) {
if ( sycSSL_CTX_load_verify_locations ( * ctx , opt_cafile , opt_capath ) ! = 1 ) {
int result ;
if ( ( result =
openssl_SSL_ERROR_SSL ( E_ERROR , " SSL_CTX_load_verify_locations " ) )
! = STAT_OK ) {
/*! free ctx */
return STAT_RETRYLATER ;
}
}
}
if ( opt_cert ) {
BIO * bio ;
DH * dh ;
if ( sycSSL_CTX_use_certificate_chain_file ( * ctx , opt_cert ) < = 0 ) {
/*! trace functions */
/*0 ERR_print_errors_fp(stderr);*/
if ( ERR_peek_error ( ) = = 0 )
Error2 ( " SSL_CTX_use_certificate_file(%p, \" %s \" , SSL_FILETYPE_PEM) failed " ,
* ctx , opt_cert ) ;
while ( err = ERR_get_error ( ) ) {
Error1 ( " SSL_CTX_use_certificate_file(): %s " ,
ERR_error_string ( err , NULL ) ) ;
}
return STAT_RETRYLATER ;
}
if ( sycSSL_CTX_use_PrivateKey_file ( * ctx , opt_key ? opt_key : opt_cert , SSL_FILETYPE_PEM ) < = 0 ) {
/*ERR_print_errors_fp(stderr);*/
openssl_SSL_ERROR_SSL ( E_ERROR /*!*/ , " SSL_CTX_use_PrivateKey_file " ) ;
return STAT_RETRYLATER ;
}
if ( opt_dhparam = = NULL ) {
opt_dhparam = ( char * ) opt_cert ;
}
if ( ( bio = sycBIO_new_file ( opt_dhparam , " r " ) ) = = NULL ) {
Warn2 ( " BIO_new_file( \" %s \" , \" r \" ): %s " ,
opt_dhparam , strerror ( errno ) ) ;
} else {
if ( ( dh = sycPEM_read_bio_DHparams ( bio , NULL , NULL , NULL ) ) = = NULL ) {
Info1 ( " PEM_read_bio_DHparams(%p, NULL, NULL, NULL): error " , bio ) ;
} else {
BIO_free ( bio ) ;
if ( sycSSL_CTX_set_tmp_dh ( * ctx , dh ) = = 0 ) {
Error2 ( " SSL_CTX_set_tmp_dh(%p, %p): error " , ctx , dh ) ;
}
}
}
}
/* set pre ssl-connect options */
/* SSL_CIPHERS */
if ( ci_str ! = NULL ) {
if ( sycSSL_CTX_set_cipher_list ( * ctx , ci_str ) < = 0 ) {
if ( ERR_peek_error ( ) = = 0 )
Error1 ( " SSL_set_cipher_list(, \" %s \" ) failed " , ci_str ) ;
while ( err = ERR_get_error ( ) ) {
Error2 ( " SSL_set_cipher_list(, \" %s \" ): %s " ,
ci_str , ERR_error_string ( err , NULL ) ) ;
}
/*Error("SSL_new()");*/
return STAT_RETRYLATER ;
}
}
if ( * opt_ver ) {
sycSSL_CTX_set_verify ( * ctx ,
SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT ,
NULL ) ;
} else {
sycSSL_CTX_set_verify ( * ctx ,
SSL_VERIFY_NONE ,
NULL ) ;
}
return STAT_OK ;
}
/* analyses an OpenSSL error condition, prints the appropriate messages with
severity ' level ' and returns one of STAT_OK , STAT_RETRYLATER , or
STAT_NORETRY */
static int openssl_SSL_ERROR_SSL ( int level , const char * funcname ) {
unsigned long e ;
char buf [ 120 ] ; /* this value demanded by "man ERR_error_string" */
e = ERR_get_error ( ) ;
Debug1 ( " ERR_get_error(): %lx " , e ) ;
if ( e = = ( ( ERR_LIB_RAND < < 24 ) |
( RAND_F_SSLEAY_RAND_BYTES < < 12 ) |
( RAND_R_PRNG_NOT_SEEDED ) ) /*0x24064064*/ ) {
Error ( " too few entropy; use options \" egd \" or \" pseudo \" " ) ;
return STAT_NORETRY ;
} else {
Msg2 ( level , " %s(): %s " , funcname , ERR_error_string ( e , buf ) ) ;
return level = = E_ERROR ? STAT_NORETRY : STAT_RETRYLATER ;
}
return STAT_OK ;
}
static const char * openssl_verify_messages [ ] = {
/* 0 */ " ok " ,
/* 1 */ NULL ,
/* 2 */ " unable to get issuer certificate " ,
/* 3 */ " unable to get certificate CRL " ,
/* 4 */ " unable to decrypt certificate's signature " ,
/* 5 */ " unable to decrypt CRL's signature " ,
/* 6 */ " unable to decode issuer public key " ,
/* 7 */ " certificate signature failure " ,
/* 8 */ " CRL signature failure " ,
/* 9 */ " certificate is not yet valid " ,
/* 10 */ " certificate has expired " ,
/* 11 */ " CRL is not yet valid " ,
/* 12 */ " CRL has expired " ,
/* 13 */ " format error in certificate's notBefore field " ,
/* 14 */ " format error in certificate's notAfter field " ,
/* 15 */ " format error in CRL's lastUpdate field " ,
/* 16 */ " format error in CRL's nextUpdate field " ,
/* 17 */ " out of memory " ,
/* 18 */ " self signed certificate " ,
/* 19 */ " self signed certificate in certificate chain " ,
/* 20 */ " unable to get local issuer certificate " ,
/* 21 */ " unable to verify the first certificate " ,
/* 22 */ " certificate chain too long " ,
/* 23 */ " certificate revoked " ,
/* 24 */ " invalid CA certificate " ,
/* 25 */ " path length constraint exceeded " ,
/* 26 */ " unsupported certificate purpose " ,
/* 27 */ " certificate not trusted " ,
/* 28 */ " certificate rejected " ,
/* 29 */ " subject issuer mismatch " ,
/* 30 */ " authority and subject key identifier mismatch " ,
/* 31 */ " authority and issuer serial number mismatch " ,
/* 32 */ " key usage does not include certificate signing " ,
/* 33 */ NULL ,
/* 34 */ NULL ,
/* 35 */ NULL ,
/* 36 */ NULL ,
/* 37 */ NULL ,
/* 38 */ NULL ,
/* 39 */ NULL ,
/* 40 */ NULL ,
/* 41 */ NULL ,
/* 42 */ NULL ,
/* 43 */ NULL ,
/* 44 */ NULL ,
/* 45 */ NULL ,
/* 46 */ NULL ,
/* 47 */ NULL ,
/* 48 */ NULL ,
/* 49 */ NULL ,
/* 50 */ " application verification failure " ,
} ;
static int openssl_handle_peer_certificate ( struct single * xfd ,
bool opt_ver , int level ) {
X509 * peer_cert ;
char * str ;
char buff [ 2048 ] ; /* hold peer certificate */
int status ;
/* SSL_CTX_add_extra_chain_cert
SSL_get_verify_result
*/
if ( ( peer_cert = SSL_get_peer_certificate ( xfd - > para . openssl . ssl ) ) ! = NULL ) {
Debug ( " peer certificate: " ) ;
if ( ( str = X509_NAME_oneline ( X509_get_subject_name ( peer_cert ) , buff , sizeof ( buff ) ) ) ! = NULL )
Debug1 ( " \t subject: %s " , str ) ; /*free (str); SIGSEGV*/
if ( ( str = X509_NAME_oneline ( X509_get_issuer_name ( peer_cert ) , buff , sizeof ( buff ) ) ) ! = NULL )
Debug1 ( " \t issuer: %s " , str ) ; /*free (str); SIGSEGV*/
}
if ( peer_cert ) {
if ( opt_ver ) {
long verify_result ;
if ( ( verify_result = sycSSL_get_verify_result ( xfd - > para . openssl . ssl ) ) = = X509_V_OK ) {
Info ( " accepted peer certificate " ) ;
status = STAT_OK ;
} else {
const char * message = NULL ;
if ( verify_result > = 0 & &
( size_t ) verify_result <
sizeof ( openssl_verify_messages ) / sizeof ( char * ) )
{
message = openssl_verify_messages [ verify_result ] ;
}
if ( message ) {
Msg1 ( level , " %s " , message ) ;
} else {
Msg1 ( level , " rejected peer certificate with error %ld " , verify_result ) ;
}
status = STAT_RETRYLATER ;
}
} else {
Notice ( " no check of certificate " ) ;
status = STAT_OK ;
}
} else {
if ( opt_ver ) {
Msg ( level , " no peer certificate " ) ;
status = STAT_RETRYLATER ;
} else {
Notice ( " no peer certificate and no check " ) ;
status = STAT_OK ;
}
}
X509_free ( peer_cert ) ;
return status ;
}
static int xioSSL_set_fd ( struct single * xfd , int level ) {
unsigned long err ;
/* assign a network connection to the SSL object */
if ( sycSSL_set_fd ( xfd - > para . openssl . ssl , xfd - > fd ) < = 0 ) {
Msg ( level , " SSL_set_fd() failed " ) ;
while ( err = ERR_get_error ( ) ) {
Msg2 ( level , " SSL_set_fd(, %d): %s " ,
xfd - > fd , ERR_error_string ( err , NULL ) ) ;
}
return STAT_RETRYLATER ;
}
return STAT_OK ;
}
/* ...
in case of an error condition , this function check forever and retry
options and ev . sleeps an interval . It returns NORETRY when the caller
should not retry for any reason . */
static int xioSSL_connect ( struct single * xfd , bool opt_ver , int level ) {
char error_string [ 120 ] ;
int errint , status , ret ;
unsigned long err ;
/* connect via SSL by performing handshake */
if ( ( ret = sycSSL_connect ( xfd - > para . openssl . ssl ) ) < = 0 ) {
/*if (ERR_peek_error() == 0) Msg(level, "SSL_connect() failed");*/
errint = SSL_get_error ( xfd - > para . openssl . ssl , ret ) ;
switch ( errint ) {
case SSL_ERROR_NONE :
/* this is not an error, but I dare not continue for security reasons*/
Msg ( level , " ok " ) ;
status = STAT_RETRYLATER ;
case SSL_ERROR_ZERO_RETURN :
Msg ( level , " connection closed (wrong version number?) " ) ;
status = STAT_RETRYLATER ;
break ;
case SSL_ERROR_WANT_READ :
case SSL_ERROR_WANT_WRITE :
case SSL_ERROR_WANT_CONNECT :
case SSL_ERROR_WANT_X509_LOOKUP :
Msg ( level , " nonblocking operation did not complete " ) ;
status = STAT_RETRYLATER ;
break ; /*!*/
case SSL_ERROR_SYSCALL :
if ( ERR_peek_error ( ) = = 0 ) {
if ( ret = = 0 ) {
Msg ( level , " SSL_connect(): socket closed by peer " ) ;
} else if ( ret = = - 1 ) {
Msg1 ( level , " SSL_connect(): %s " , strerror ( errno ) ) ;
}
} else {
Msg ( level , " I/O error " ) ; /*!*/
while ( err = ERR_get_error ( ) ) {
ERR_error_string_n ( err , error_string , sizeof ( error_string ) ) ;
Msg4 ( level , " SSL_connect(): %s / %s / %s / %s " , error_string ,
ERR_lib_error_string ( err ) , ERR_func_error_string ( err ) ,
ERR_reason_error_string ( err ) ) ;
}
}
status = STAT_RETRYLATER ;
break ;
case SSL_ERROR_SSL :
status = openssl_SSL_ERROR_SSL ( level , " SSL_connect " ) ;
if ( openssl_handle_peer_certificate ( xfd , opt_ver , level /*!*/ ) < 0 ) {
return STAT_RETRYLATER ;
}
break ;
default :
Msg ( level , " unknown error " ) ;
status = STAT_RETRYLATER ;
break ;
}
return status ;
}
return STAT_OK ;
}
/* on result < 0: errno is set (at least to EIO) */
ssize_t xioread_openssl ( struct single * pipe , void * buff , size_t bufsiz ) {
unsigned long err ;
char error_string [ 120 ] ;
int _errno = EIO ; /* if we have no better idea about nature of error */
int errint , ret ;
ret = sycSSL_read ( pipe - > para . openssl . ssl , buff , bufsiz ) ;
if ( ret < 0 ) {
errint = SSL_get_error ( pipe - > para . openssl . ssl , ret ) ;
switch ( errint ) {
case SSL_ERROR_NONE :
/* this is not an error, but I dare not continue for security reasons*/
Error ( " ok " ) ;
case SSL_ERROR_ZERO_RETURN :
Error ( " connection closed by peer " ) ;
break ;
case SSL_ERROR_WANT_READ :
case SSL_ERROR_WANT_WRITE :
case SSL_ERROR_WANT_CONNECT :
case SSL_ERROR_WANT_X509_LOOKUP :
Error ( " nonblocking operation did not complete " ) ;
break ; /*!*/
case SSL_ERROR_SYSCALL :
if ( ERR_peek_error ( ) = = 0 ) {
if ( ret = = 0 ) {
Error ( " SSL_read(): socket closed by peer " ) ;
} else if ( ret = = - 1 ) {
_errno = errno ;
Error1 ( " SSL_read(): %s " , strerror ( errno ) ) ;
}
} else {
Error ( " I/O error " ) ; /*!*/
while ( err = ERR_get_error ( ) ) {
ERR_error_string_n ( err , error_string , sizeof ( error_string ) ) ;
Error4 ( " SSL_read(): %s / %s / %s / %s " , error_string ,
ERR_lib_error_string ( err ) , ERR_func_error_string ( err ) ,
ERR_reason_error_string ( err ) ) ;
}
}
break ;
case SSL_ERROR_SSL :
openssl_SSL_ERROR_SSL ( E_ERROR , " SSL_connect " ) ;
break ;
default :
Error ( " unknown error " ) ;
break ;
}
errno = _errno ;
return - 1 ;
}
return ret ;
}
ssize_t xiopending_openssl ( struct single * pipe ) {
int bytes = sycSSL_pending ( pipe - > para . openssl . ssl ) ;
return bytes ;
}
/* on result < 0: errno is set (at least to EIO) */
ssize_t xiowrite_openssl ( struct single * pipe , const void * buff , size_t bufsiz ) {
unsigned long err ;
char error_string [ 120 ] ;
int _errno = EIO ; /* if we have no better idea about nature of error */
int errint , ret ;
ret = sycSSL_write ( pipe - > para . openssl . ssl , buff , bufsiz ) ;
if ( ret < 0 ) {
errint = SSL_get_error ( pipe - > para . openssl . ssl , ret ) ;
switch ( errint ) {
case SSL_ERROR_NONE :
/* this is not an error, but I dare not continue for security reasons*/
Error ( " ok " ) ;
case SSL_ERROR_ZERO_RETURN :
Error ( " connection closed by peer " ) ;
break ;
case SSL_ERROR_WANT_READ :
case SSL_ERROR_WANT_WRITE :
case SSL_ERROR_WANT_CONNECT :
case SSL_ERROR_WANT_X509_LOOKUP :
Error ( " nonblocking operation did not complete " ) ;
break ; /*!*/
case SSL_ERROR_SYSCALL :
if ( ERR_peek_error ( ) = = 0 ) {
if ( ret = = 0 ) {
Error ( " SSL_write(): socket closed by peer " ) ;
} else if ( ret = = - 1 ) {
_errno = errno ;
Error1 ( " SSL_write(): %s " , strerror ( errno ) ) ;
}
} else {
Error ( " I/O error " ) ; /*!*/
while ( err = ERR_get_error ( ) ) {
ERR_error_string_n ( err , error_string , sizeof ( error_string ) ) ;
Error4 ( " SSL_write(): %s / %s / %s / %s " , error_string ,
ERR_lib_error_string ( err ) , ERR_func_error_string ( err ) ,
ERR_reason_error_string ( err ) ) ;
}
}
break ;
case SSL_ERROR_SSL :
openssl_SSL_ERROR_SSL ( E_ERROR , " SSL_connect " ) ;
break ;
default :
Error ( " unknown error " ) ;
break ;
}
errno = _errno ;
return - 1 ;
}
return ret ;
}
# endif /* WITH_OPENSSL */