socat/xio-openssl.c

1349 lines
43 KiB
C

/* source: xio-openssl.c */
/* Copyright Gerhard Rieger 2002-2012 */
/* 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 inter-address ssl connect with 0 parameters */
static const struct xioaddr_inter_desc xiointer_openssl_connect0 = {
XIOADDR_INTER, /* this is an embedded address (inter module) */
"openssl-client", /* keyword for selecting this address type in xioopen calls
(canonical or main name) */
0, /* number of required parameters */
XIOBIT_ALL, /* data flow directions this address supports on API layer:
XIOBIT_RDONLY|XIOBIT_WRONLY|XIOBIT_RDWR */
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 */
XIOSHUT_OPENSSL,
XIOCLOSE_CLOSE,
xioopen_openssl_connect, /* a function pointer used to "open" these addresses.*/
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! */
XIOBIT_RDWR /* SSL is a bidirectional protocol */
HELP("") /* a text displayed from xio help function.
No trailing comma or semicolon!
only generates this text if WITH_HELP is != 0 */
} ;
/* description record for endpoint-address ssl connect with 2 parameters */
static const struct xioaddr_endpoint_desc xioendpoint_openssl_connect2 = {
XIOADDR_ENDPOINT, /* this is not an embedded address but a sys address */
"openssl-client", /* keyword for selecting this address type in xioopen calls
(canonical or main name) */
2, /* number of required parameters */
XIOBIT_ALL, /* data flow directions this address supports on API layer:
XIOBIT_RDONLY|XIOBIT_WRONLY|XIOBIT_RDWR */
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 */
XIOSHUT_OPENSSL,
XIOCLOSE_CLOSE,
xioopen_openssl_connect, /* a function pointer used to "open" these addresses.*/
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 */
} ;
/* array containing ssl connect description records */
const union xioaddr_desc *xioaddrs_openssl_connect[] = {
(union xioaddr_desc *)&xiointer_openssl_connect0,
(union xioaddr_desc *)&xioendpoint_openssl_connect2,
NULL
};
#if WITH_LISTEN
/* description record for inter-address ssl listen */
static const struct xioaddr_inter_desc xiointer_openssl_listen0 = {
XIOADDR_INTER, /* this is an embedded address (inter module) */
"openssl-server", /* keyword for selecting this address type in xioopen calls
(canonical or main name) */
0, /* number of required parameters */
XIOBIT_ALL, /* data flow directions this address supports on API layer:
XIOBIT_RDONLY|XIOBIT_WRONLY|XIOBIT_RDWR */
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 */
XIOSHUT_OPENSSL,
XIOCLOSE_CLOSE,
xioopen_openssl_listen, /* a function pointer used to "open" these addresses.*/
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! */
XIOBIT_RDWR /* SSL is a bidirectional protocol */
HELP("") /* a text displayed from xio help function.
No trailing comma or semicolon!
only generates this text if WITH_HELP is != 0 */
} ;
/* description record for enpoint-address ssl listen */
static const struct xioaddr_endpoint_desc xioendpoint_openssl_listen1 = {
XIOADDR_ENDPOINT, /* this is not an embedded module but a sys module */
"openssl-server", /* keyword for selecting this address type in xioopen calls
(canonical or main name) */
1, /* number of required parameters */
XIOBIT_ALL, /* data flow directions this address supports on API layer:
XIOBIT_RDONLY|XIOBIT_WRONLY|XIOBIT_RDWR */
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 */
XIOSHUT_OPENSSL,
XIOCLOSE_CLOSE,
xioopen_openssl_listen, /* a function pointer used to "open" these addresses.*/
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 */
} ;
/* array containing ssl listen description records */
const union xioaddr_desc *xioaddrs_openssl_listen[] = {
(union xioaddr_desc *)&xiointer_openssl_listen0,
(union xioaddr_desc *)&xioendpoint_openssl_listen1,
NULL
};
#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 opt_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;
int rw = (xioflags&XIO_ACCMODE);
struct opt *opts0 = NULL;
const char *hostname, *portname = NULL;
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;
/*0 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;
xfd->howtoshut = XIOSHUT_OPENSSL;
xfd->howtoclose = XIOCLOSE_CLOSE;
/* we support two forms of openssl-connect */
if (argc == 3) {
hostname = argv[1];
portname = argv[2];
/* a "terminal" form where we build a tcp connection to given host and
port */
applyopts_single(xfd, opts, PH_INIT);
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,
&xfd->para.openssl.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,
&needbind, &lowport, socktype);
if (result != STAT_OK) return STAT_NORETRY;
} else if (argc = 1) {
/* or a "non terminal" address without required parameters */
if (xfd->wfd < 0) {
Error("openssl-connect without hostname and port must be an embedded address");
return STAT_NORETRY;
}
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,
&xfd->para.openssl.ctx);
if (result != STAT_OK) return STAT_NORETRY;
retropt_bool(opts, OPT_FORK, &dofork);
} else {
Error1("%s: 0 or 2 parameters required", argv[0]);
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 belongs only to "old" openssl-connect form */
if (portname) {
/* 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;
}
xfd->wfd = xfd->rfd;
}
/*! isn't this too early? */
if ((result = _xio_openlate(xfd, opts)) < 0) {
return result;
}
result =
_xioopen_openssl_connect(xfd, opt_ver, xfd->para.openssl.ctx, level);
switch (result) {
case STAT_OK: break;
#if WITH_RETRY
case STAT_RETRYLATER:
case STAT_RETRYNOW:
if (xfd->forever || xfd->retry) {
Close(xfd->rfd);
Close(xfd->wfd);
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;
}
if (dofork) {
xiosetchilddied(); /* set SIGCHLD handler */
}
#if WITH_RETRY
if (dofork) {
pid_t pid;
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;
}
return STAT_RETRYLATER;
}
if (pid == 0) { /* child process */
xfd->forever = false; xfd->retry = 0;
break;
}
/* parent process */
Notice1("forked off child process "F_pid, pid);
Close(xfd->rfd);
Close(xfd->wfd);
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 = NULL;
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;
/*0 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 WITH_IP4 && WITH_IP6
pf = xioopts.default_ip=='6'?PF_INET6:PF_INET;
#elif WITH_IP6
pf = PF_INET6;
#else
pf = PF_INET;
#endif
if (argc == 2) {
portname = argv[1];
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\"");
}
result =
_xioopen_openssl_prepare(opts, xfd, true, &opt_ver, opt_cert,
&xfd->para.openssl.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],
us, &uslen, socktype)
!= STAT_OK) {
return STAT_NORETRY;
}
} else if (argc == 1) {
if (xfd->rfd < 0) {
Error("openssl-listen without port must be an embedded address");
return STAT_NORETRY;
}
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,
&xfd->para.openssl.ctx);
if (result != STAT_OK) return STAT_NORETRY;
} else {
Error1("%s: 1 parameter required", argv[0]);
return -1;
}
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;
if (portname) {
/* 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;
}
xfd->wfd = xfd->rfd;
}
result =
_xioopen_openssl_listen(xfd, opt_ver, xfd->para.openssl.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 */
ret = xioSSL_set_fd(xfd, level);
if (ret != STAT_OK) {
sycSSL_free(xfd->para.openssl.ssl);
xfd->para.openssl.ssl = NULL;
return ret;
}
#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->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;
}
{
static unsigned char dh512_p[] = {
0xDA,0x58,0x3C,0x16,0xD9,0x85,0x22,0x89,0xD0,0xE4,0xAF,0x75,
0x6F,0x4C,0xCA,0x92,0xDD,0x4B,0xE5,0x33,0xB8,0x04,0xFB,0x0F,
0xED,0x94,0xEF,0x9C,0x8A,0x44,0x03,0xED,0x57,0x46,0x50,0xD3,
0x69,0x99,0xDB,0x29,0xD7,0x76,0x27,0x6B,0xA2,0xD3,0xD4,0x12,
0xE2,0x18,0xF4,0xDD,0x1E,0x08,0x4C,0xF6,0xD8,0x00,0x3E,0x7C,
0x47,0x74,0xE8,0x33,
};
static unsigned char dh512_g[] = {
0x02,
};
DH *dh;
unsigned long err;
if ((dh = DH_new()) == NULL) {
while (err = ERR_get_error()) {
Warn1("DH_new(): %s",
ERR_error_string(err, NULL));
}
Error("DH_new() failed");
} else {
dh->p = BN_bin2bn(dh512_p, sizeof(dh512_p), NULL);
dh->g = BN_bin2bn(dh512_g, sizeof(dh512_g), NULL);
if ((dh->p == NULL) || (dh->g == NULL)) {
while (err = ERR_get_error()) {
Warn1("BN_bin2bn(): %s",
ERR_error_string(err, NULL));
}
Error("BN_bin2bn() failed");
} else {
if (SSL_CTX_set_tmp_dh(*ctx, dh) <= 0) {
while (err = ERR_get_error()) {
Warn1("SSL_CTX_set_tmp_dh(%p, %p): %s",
ERR_error_string(err, NULL));
}
Error2("SSL_CTX_set_tmp_dh(%p, %p) failed", *ctx, dh);
}
/*! OPENSSL_free(dh->p,g)? doc does not tell so */
}
DH_free(dh);
}
}
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) {
SSL_CTX_free(*ctx); *ctx = NULL;
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("\tsubject: %s", str); /*free (str); SIGSEGV*/
if ((str = X509_NAME_oneline(X509_get_issuer_name(peer_cert), buff, sizeof(buff))) != NULL)
Debug1("\tissuer: %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 (xfd->rfd == xfd->wfd) {
if (sycSSL_set_fd(xfd->para.openssl.ssl, xfd->rfd) <= 0) {
Msg(level, "SSL_set_fd() failed");
while (err = ERR_get_error()) {
Msg2(level, "SSL_set_fd(, %d): %s",
xfd->wfd, ERR_error_string(err, NULL));
}
return STAT_RETRYLATER;
}
} else {
if (xfd->rfd >= 0) {
if (sycSSL_set_rfd(xfd->para.openssl.ssl, xfd->rfd) <= 0) {
Msg(level, "SSL_set_rfd() failed");
while (err = ERR_get_error()) {
Msg2(level, "SSL_set_rfd(, %d): %s",
xfd->rfd, ERR_error_string(err, NULL));
}
return STAT_RETRYLATER;
}
}
if (xfd->wfd >= 0) {
if (sycSSL_set_wfd(xfd->para.openssl.ssl, xfd->wfd) <= 0) {
Msg(level, "SSL_set_wfd() failed");
while (err = ERR_get_error()) {
Msg2(level, "SSL_set_wfd(, %d): %s",
xfd->wfd, 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:
Info("nonblocking operation did not complete");
errno = EAGAIN;
return -1;
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 */