socat/xio-openssl.c

1660 lines
53 KiB
C

/* source: xio-openssl.c */
/* Copyright Gerhard Rieger */
/* 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 <openssl/conf.h>
#include <openssl/x509v3.h>
#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,
const char *peername,
bool opt_ver,
int level);
static int xioSSL_set_fd(struct single *xfd, int level);
static int xioSSL_connect(struct single *xfd, const char *opt_commonname, bool opt_ver, int level);
static int openssl_delete_cert_info(void);
/* 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 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 OPENSSL_VERSION_NUMBER >= 0x00908000L
const struct optdesc opt_openssl_compress = { "openssl-compress", "compress", OPT_OPENSSL_COMPRESS, GROUP_OPENSSL, PH_SPEC, TYPE_STRING, OFUNC_SPEC };
#endif
#if WITH_FIPS
const struct optdesc opt_openssl_fips = { "openssl-fips", "fips", OPT_OPENSSL_FIPS, GROUP_OPENSSL, PH_SPEC, TYPE_BOOL, OFUNC_SPEC };
#endif
const struct optdesc opt_openssl_commonname = { "openssl-commonname", "cn", OPT_OPENSSL_COMMONNAME, GROUP_OPENSSL, PH_SPEC, TYPE_STRING, OFUNC_SPEC };
const struct optdesc opt_openssl_snihost = { "openssl-snihost", "snihost", OPT_OPENSSL_SNIHOST, GROUP_OPENSSL, PH_SPEC, TYPE_STRING, OFUNC_SPEC };
/* 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
static void openssl_conn_loginfo(SSL *ssl) {
Notice1("SSL connection using %s", SSL_get_cipher(ssl));
#if OPENSSL_VERSION_NUMBER >= 0x00908000L
{
const COMP_METHOD *comp, *expansion;
comp = sycSSL_get_current_compression(ssl);
expansion = sycSSL_get_current_expansion(ssl);
Notice1("SSL connection compression \"%s\"",
comp?sycSSL_COMP_get_name(comp):"none");
Notice1("SSL connection expansion \"%s\"",
expansion?sycSSL_COMP_get_name(expansion):"none");
}
#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 */
const char *opt_commonname = NULL; /* for checking peer certificate */
const char *opt_snihost = NULL; /* for sni host */
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];
if (hostname[0] == '\0') {
/* we catch this explicitely because empty commonname (peername) disables
commonName check of peer certificate */
Error1("%s: empty host name", argv[0]);
return STAT_NORETRY;
}
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);
retropt_string(opts, OPT_OPENSSL_COMMONNAME, (char **)&opt_commonname);
retropt_string(opts, OPT_OPENSSL_SNIHOST, (char **)&opt_snihost);
if (opt_commonname == NULL) {
opt_commonname = hostname;
}
/* could do this, but might not be desired?
if (opt_snihost == NULL) {
opt_snihost = hostname;
} */
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,
&needbind, &lowport, socktype);
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, uslen,
(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, opt_commonname, opt_snihost, 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;
}
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 */
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 */
openssl_conn_loginfo(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,
const char *opt_commonname,
const char *opt_snihost,
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;
}
if (opt_snihost && !SSL_set_tlsext_host_name(ssl, opt_snihost)) {
sycSSL_free(xfd->para.openssl.ssl);
xfd->para.openssl.ssl = NULL;
return STAT_NORETRY;
}
result = xioSSL_connect(xfd, opt_commonname, 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_commonname,
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 */
const char *opt_commonname = NULL; /* for checking peer 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\"");
}
retropt_string(opts, OPT_OPENSSL_COMMONNAME, (char **)&opt_commonname);
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],
us, &uslen, socktype)
!= 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, opt_commonname, 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;
}
openssl_conn_loginfo(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,
const char *opt_commonname,
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_accept(): %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_commonname, opt_ver, E_ERROR/*!*/) < 0) {
return STAT_NORETRY;
}
return STAT_OK;
}
#endif /* WITH_LISTEN */
#if OPENSSL_VERSION_NUMBER >= 0x00908000L
/* In OpenSSL 0.9.7 compression methods could be added using
* SSL_COMP_add_compression_method(3), but the implemntation is not compatible
* with the standard (RFC3749).
*/
static int openssl_setup_compression(SSL_CTX *ctx, char *method)
{
STACK_OF(SSL_COMP)* comp_methods;
assert(method);
/* Getting the stack of compression methods has the intended side-effect of
* initializing the SSL library's compression part.
*/
comp_methods = SSL_COMP_get_compression_methods();
if (!comp_methods) {
Info("OpenSSL built without compression support");
return STAT_OK;
}
if (strcasecmp(method, "auto") == 0) {
Info("Using default OpenSSL compression");
return STAT_OK;
}
if (strcasecmp(method, "none") == 0) {
/* Disable compression */
#ifdef SSL_OP_NO_COMPRESSION
Info("Disabling OpenSSL compression");
SSL_CTX_set_options(ctx, SSL_OP_NO_COMPRESSION);
#else
/* SSL_OP_NO_COMPRESSION was only introduced in OpenSSL 0.9.9 (released
* as 1.0.0). Removing all compression methods is a work-around for
* earlier versions of OpenSSL, but it affects all SSL connections.
*/
Info("Disabling OpenSSL compression globally");
sk_SSL_COMP_zero(comp_methods);
#endif
return STAT_OK;
}
/* zlib compression in OpenSSL before version 0.9.8e-beta1 uses the libc's
* default malloc/free instead of the ones passed to OpenSSL. Should socat
* ever use custom malloc/free functions for OpenSSL, this must be taken
* into consideration. See OpenSSL bug #1468.
*/
Error1("openssl-compress=\"%s\": unknown compression method", method);
return STAT_NORETRY;
}
#endif
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;
const SSL_METHOD *method = NULL;
char *me_str = NULL; /* method string */
char *ci_str = "HIGH:-NULL:-PSK:-aNULL"; /* 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 */
#if OPENSSL_VERSION_NUMBER >= 0x00908000L
char *opt_compress = NULL; /* compression method */
#endif
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 OPENSSL_VERSION_NUMBER >= 0x00908000L
retropt_string(opts, OPT_OPENSSL_COMPRESS, &opt_compress);
#endif
#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_delete_cert_info();
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 != NULL) {
if (false) {
; /* for canonical reasons */
#if HAVE_SSLv2_client_method
} else if (!strcasecmp(me_str, "SSL2")) {
method = sycSSLv2_client_method();
#endif
#if HAVE_SSLv3_client_method
} else if (!strcasecmp(me_str, "SSL3")) {
method = sycSSLv3_client_method();
#endif
#if HAVE_SSLv23_client_method
} else if (!strcasecmp(me_str, "SSL23")) {
method = sycSSLv23_client_method();
#endif
#if HAVE_TLSv1_client_method
} else if (!strcasecmp(me_str, "TLS1") || !strcasecmp(me_str, "TLS1.0")) {
method = sycTLSv1_client_method();
#endif
#if HAVE_TLSv1_1_client_method
} else if (!strcasecmp(me_str, "TLS1.1")) {
method = sycTLSv1_1_client_method();
#endif
#if HAVE_TLSv1_2_client_method
} else if (!strcasecmp(me_str, "TLS1.2")) {
method = sycTLSv1_2_client_method();
#endif
#if HAVE_DTLSv1_client_method
} else if (!strcasecmp(me_str, "DTLS") || !strcasecmp(me_str, "DTLS1")) {
method = sycDTLSv1_client_method();
#endif
} else {
Error1("openssl-method=\"%s\": method unknown or not provided by library", me_str);
}
} else {
#if HAVE_SSLv23_client_method
method = sycSSLv23_client_method();
#elif HAVE_TLSv1_2_client_method
method = sycTLSv1_2_client_method();
#elif HAVE_TLSv1_1_client_method
method = sycTLSv1_1_client_method();
#elif HAVE_TLSv1_client_method
method = sycTLSv1_client_method();
#elif HAVE_SSLv3_client_method
method = sycSSLv3_client_method();
#elif HAVE_SSLv2_client_method
method = sycSSLv2_client_method();
#else
# error "OpenSSL does not seem to provide client methods"
#endif
}
} else /* server */ {
if (me_str != 0) {
if (false) {
; /* for canonical reasons */
#if HAVE_SSLv2_server_method
} else if (!strcasecmp(me_str, "SSL2")) {
method = sycSSLv2_server_method();
#endif
#if HAVE_SSLv3_server_method
} else if (!strcasecmp(me_str, "SSL3")) {
method = sycSSLv3_server_method();
#endif
#if HAVE_SSLv23_server_method
} else if (!strcasecmp(me_str, "SSL23")) {
method = sycSSLv23_server_method();
#endif
#if HAVE_TLSv1_server_method
} else if (!strcasecmp(me_str, "TLS1") || !strcasecmp(me_str, "TLS1.0")) {
method = sycTLSv1_server_method();
#endif
#if HAVE_TLSv1_1_server_method
} else if (!strcasecmp(me_str, "TLS1.1")) {
method = sycTLSv1_1_server_method();
#endif
#if HAVE_TLSv1_2_server_method
} else if (!strcasecmp(me_str, "TLS1.2")) {
method = sycTLSv1_2_server_method();
#endif
#if HAVE_DTLSv1_server_method
} else if (!strcasecmp(me_str, "DTLS") || !strcasecmp(me_str, "DTLS1")) {
method = sycDTLSv1_server_method();
#endif
} else {
Error1("openssl-method=\"%s\": method unknown or not provided by library", me_str);
}
} else {
#if HAVE_SSLv23_server_method
method = sycSSLv23_server_method();
#elif HAVE_TLSv1_2_server_method
method = sycTLSv1_2_server_method();
#elif HAVE_TLSv1_1_server_method
method = sycTLSv1_1_server_method();
#elif HAVE_TLSv1_server_method
method = sycTLSv1_server_method();
#elif HAVE_SSLv3_server_method
method = sycSSLv3_server_method();
#elif HAVE_SSLv2_server_method
method = sycSSLv2_server_method();
#else
# error "OpenSSL does not seem to provide client methods"
#endif
}
}
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 dh1024_p[] = {
0xCC,0x17,0xF2,0xDC,0x96,0xDF,0x59,0xA4,0x46,0xC5,0x3E,0x0E,
0xB8,0x26,0x55,0x0C,0xE3,0x88,0xC1,0xCE,0xA7,0xBC,0xB3,0xBF,
0x16,0x94,0xD8,0xA9,0x45,0xA2,0xCE,0xA9,0x5B,0x22,0x25,0x5F,
0x92,0x59,0x94,0x1C,0x22,0xBF,0xCB,0xC8,0xC8,0x57,0xCB,0xBF,
0xBC,0x0E,0xE8,0x40,0xF9,0x87,0x03,0xBF,0x60,0x9B,0x08,0xC6,
0x8E,0x99,0xC6,0x05,0xFC,0x00,0xD6,0x6D,0x90,0xA8,0xF5,0xF8,
0xD3,0x8D,0x43,0xC8,0x8F,0x7A,0xBD,0xBB,0x28,0xAC,0x04,0x69,
0x4A,0x0B,0x86,0x73,0x37,0xF0,0x6D,0x4F,0x04,0xF6,0xF5,0xAF,
0xBF,0xAB,0x8E,0xCE,0x75,0x53,0x4D,0x7F,0x7D,0x17,0x78,0x0E,
0x12,0x46,0x4A,0xAF,0x95,0x99,0xEF,0xBC,0xA6,0xC5,0x41,0x77,
0x43,0x7A,0xB9,0xEC,0x8E,0x07,0x3C,0x6D,
};
static unsigned char dh1024_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(dh1024_p, sizeof(dh1024_p), NULL);
dh->g = BN_bin2bn(dh1024_g, sizeof(dh1024_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 (sycSSL_CTX_set_tmp_dh(*ctx, dh) <= 0) {
while (err = ERR_get_error()) {
Warn3("SSL_CTX_set_tmp_dh(%p, %p): %s", *ctx, dh,
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 defined(EC_KEY) /* not on Openindiana 5.11 */
{
/* see http://openssl.6102.n7.nabble.com/Problem-with-cipher-suite-ECDHE-ECDSA-AES256-SHA384-td42229.html */
int nid;
EC_KEY *ecdh;
#if 0
nid = OBJ_sn2nid(ECDHE_CURVE);
if (nid == NID_undef) {
Error("openssl: failed to set ECDHE parameters");
return -1;
}
#endif
nid = NID_X9_62_prime256v1;
ecdh = EC_KEY_new_by_curve_name(nid);
if (NULL == ecdh) {
Error("openssl: failed to set ECDHE parameters");
return -1;
}
SSL_CTX_set_tmp_ecdh(*ctx, ecdh);
}
#endif /* !defined(EC_KEY) */
#if OPENSSL_VERSION_NUMBER >= 0x00908000L
if (opt_compress) {
int result;
result = openssl_setup_compression(*ctx, opt_compress);
if (result != STAT_OK) {
return result;
}
}
#endif
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;
}
}
#ifdef HAVE_SSL_CTX_set_default_verify_paths
} else {
SSL_CTX_set_default_verify_paths(*ctx);
#endif
}
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) {
while (err = ERR_get_error()) {
Warn3("SSL_CTX_set_tmp_dh(%p, %p): %s", *ctx, dh,
ERR_error_string(err, NULL));
}
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" */
int stat = STAT_OK;
while (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\"");
stat = STAT_NORETRY;
} else {
Msg2(level, "%s(): %s", funcname, ERR_error_string(e, buf));
stat = level==E_ERROR ? STAT_NORETRY : STAT_RETRYLATER;
}
}
return stat;
}
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",
} ;
/* delete all environment variables whose name begins with SOCAT_OPENSSL_
resp. <progname>_OPENSSL_ */
static int openssl_delete_cert_info(void) {
# define XIO_ENVNAMELEN 256
const char *progname;
char envprefix[XIO_ENVNAMELEN];
char envname[XIO_ENVNAMELEN];
size_t i, l;
const char **entry;
progname = diag_get_string('p');
envprefix[0] = '\0'; strncat(envprefix, progname, XIO_ENVNAMELEN-1);
l = strlen(envprefix);
for (i = 0; i < l; ++i) envprefix[i] = toupper(envprefix[i]);
strncat(envprefix+l, "_OPENSSL_", XIO_ENVNAMELEN-l-1);
#if HAVE_VAR_ENVIRON
entry = (const char **)environ;
while (*entry != NULL) {
if (!strncmp(*entry, envprefix, strlen(envprefix))) {
const char *eq = strchr(*entry, '=');
if (eq == NULL) eq = *entry + strlen(*entry);
envname[0] = '\0'; strncat(envname, *entry, eq-*entry);
Unsetenv(envname);
} else {
++entry;
}
}
#endif /* HAVE_VAR_ENVIRON */
return 0;
}
/* read in the "name" information (from field "issuer" or "subject") and
create environment variable with complete info, eg:
SOCAT_OPENSSL_X509_SUBJECT */
static int openssl_setenv_cert_name(const char *field, X509_NAME *name) {
BIO *bio = BIO_new(BIO_s_mem());
char *buf = NULL, *str;
size_t len;
X509_NAME_print_ex(bio, name, 0, XN_FLAG_ONELINE&~ASN1_STRFLGS_ESC_MSB); /* rc not documented */
len = BIO_get_mem_data (bio, &buf);
if ((str = Malloc(len+1)) == NULL) {
BIO_free(bio);
return -1;
}
memcpy(str, buf, len);
str[len] = '\0';
Info2("SSL peer cert %s: \"%s\"", field, buf);
xiosetenv2("OPENSSL_X509", field, str, 1, NULL);
free(str);
BIO_free(bio);
return 0;
}
/* read in the "name" information (from field "issuer" or "subject") and
create environment variables with the fields, eg:
SOCAT_OPENSSL_X509_COMMONNAME
*/
static int openssl_setenv_cert_fields(const char *field, X509_NAME *name) {
int n, i;
n = X509_NAME_entry_count(name);
/* extract fields of cert name */
for (i = 0; i < n; ++i) {
X509_NAME_ENTRY *entry;
ASN1_OBJECT *obj;
ASN1_STRING *data;
unsigned char *text;
int nid;
entry = X509_NAME_get_entry(name, i);
obj = X509_NAME_ENTRY_get_object(entry);
data = X509_NAME_ENTRY_get_data(entry);
nid = OBJ_obj2nid(obj);
text = ASN1_STRING_data(data);
Debug3("SSL peer cert %s entry: %s=\"%s\"", (field[0]?field:"subject"), OBJ_nid2ln(nid), text);
if (field != NULL && field[0] != '\0') {
xiosetenv3("OPENSSL_X509", field, OBJ_nid2ln(nid), (const char *)text, 2, " // ");
} else {
xiosetenv2("OPENSSL_X509", OBJ_nid2ln(nid), (const char *)text, 2, " // ");
}
}
return 0;
}
/* compares the peername used/provided by the client to cn as extracted from
the peer certificate.
supports wildcard cn like *.domain which matches domain and
host.domain
returns true on match */
static bool openssl_check_name(const char *cn, const char *peername) {
const char *dotp;
if (peername == NULL) {
Info1("commonName \"%s\": no peername", cn);
return false;
} else if (peername[0] == '\0') {
Info1("commonName \"%s\": matched by empty peername", cn);
return true;
}
if (! (cn[0] == '*' && cn[1] == '.')) {
/* normal server name - this is simple */
Debug1("commonName \"%s\" has no wildcard", cn);
if (strcmp(cn, peername) == 0) {
Debug2("commonName \"%s\" matches peername \"%s\"", cn, peername);
return true;
} else {
Info2("commonName \"%s\" does not match peername \"%s\"", cn, peername);
return false;
}
}
/* wildcard cert */
Debug1("commonName \"%s\" is a wildcard name", cn);
/* case: just the base domain */
if (strcmp(cn+2, peername) == 0) {
Debug2("wildcard commonName \"%s\" matches base domain \"%s\"", cn, peername);
return true;
}
/* case: subdomain; only one level! */
dotp = strchr(peername, '.');
if (dotp == NULL) {
Info2("peername \"%s\" is not a subdomain, thus is not matched by wildcard commonName \"%s\"",
peername, cn);
return false;
}
if (strcmp(cn+1, dotp) != 0) {
Info2("commonName \"%s\" does not match subdomain peername \"%s\"", cn, peername);
return false;
}
Debug2("commonName \"%s\" matches subdomain peername \"%s\"", cn, peername);
return true;
}
/* retrieves the commonName field and compares it to the peername
returns true on match, false otherwise */
static bool openssl_check_peername(X509_NAME *name, const char *peername) {
int ind = -1;
X509_NAME_ENTRY *entry;
ASN1_STRING *data;
unsigned char *text;
ind = X509_NAME_get_index_by_NID(name, NID_commonName, -1);
if (ind < 0) {
Info("no COMMONNAME field in peer certificate");
return false;
}
entry = X509_NAME_get_entry(name, ind);
data = X509_NAME_ENTRY_get_data(entry);
text = ASN1_STRING_data(data);
return openssl_check_name((const char *)text, peername);
}
/* retrieves certificate provided by peer, sets env vars containing
certificates field values, and checks peername if provided by
calling function */
/* parts of this code were copied from Gene Spaffords C/C++ Secure Programming at Etutorials.org:
http://etutorials.org/Programming/secure+programming/Chapter+10.+Public+Key+Infrastructure/10.8+Adding+Hostname+Checking+to+Certificate+Verification/
The code examples in this tutorial do not seem to have explicit license restrictions.
*/
static int openssl_handle_peer_certificate(struct single *xfd,
const char *peername,
bool opt_ver, int level) {
X509 *peer_cert;
X509_NAME *subjectname, *issuername;
/*ASN1_TIME not_before, not_after;*/
int extcount, i, ok = 0;
int status;
if ((peer_cert = SSL_get_peer_certificate(xfd->para.openssl.ssl)) == NULL) {
if (opt_ver) {
Msg(level, "no peer certificate");
status = STAT_RETRYLATER;
} else {
Notice("no peer certificate and no check");
status = STAT_OK;
}
return status;
}
/* verify peer certificate (trust, signature, validity dates) */
if (opt_ver) {
long verify_result;
if ((verify_result = sycSSL_get_verify_result(xfd->para.openssl.ssl)) != X509_V_OK) {
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;
X509_free(peer_cert);
return STAT_RETRYLATER;
}
Info("peer certificate is trusted");
}
/* set env vars from cert's subject and issuer values */
if ((subjectname = X509_get_subject_name(peer_cert)) != NULL) {
openssl_setenv_cert_name("subject", subjectname);
openssl_setenv_cert_fields("", subjectname);
/*! I'd like to provide dates too; see
http://markmail.org/message/yi4vspp7aeu3xwtu#query:+page:1+mid:jhnl4wklif3pgzqf+state:results */
}
if ((issuername = X509_get_issuer_name(peer_cert)) != NULL) {
openssl_setenv_cert_name("issuer", issuername);
}
/* check peername against cert's subjectAltName DNS entries */
/* this code is based on example from Gerhard Gappmeier in
http://openssl.6102.n7.nabble.com/How-to-extract-subjectAltName-td17236.html
*/
if ((extcount = X509_get_ext_count(peer_cert)) > 0) {
for (i = 0; !ok && i < extcount; ++i) {
const char *extstr;
X509_EXTENSION *ext;
const X509V3_EXT_METHOD *meth;
ext = X509_get_ext(peer_cert, i);
extstr = OBJ_nid2sn(OBJ_obj2nid(X509_EXTENSION_get_object(ext)));
if (!strcasecmp(extstr, "subjectAltName")) {
void *names;
if (!(meth = X509V3_EXT_get(ext))) break;
names = X509_get_ext_d2i(peer_cert, NID_subject_alt_name, NULL, NULL);
if (names) {
int numalts;
int i;
/* get amount of alternatives, RFC2459 claims there MUST be at least one, but we don't depend on it... */
numalts = sk_GENERAL_NAME_num ( names );
/* loop through all alternatives */
for ( i=0; ( i<numalts ); i++ ) {
/* get a handle to alternative name number i */
const GENERAL_NAME *pName = sk_GENERAL_NAME_value (names, i );
unsigned char *pBuffer;
switch ( pName->type ) {
case GEN_DNS:
ASN1_STRING_to_UTF8(&pBuffer,
pName->d.ia5);
xiosetenv("OPENSSL_X509V3_SUBJECTALTNAME_DNS", (char *)pBuffer, 2, " // ");
if (peername != NULL &&
openssl_check_name((char *)pBuffer, /*const char*/peername)) {
ok = 1;
}
OPENSSL_free(pBuffer);
break;
default: continue;
}
}
}
}
}
}
if (!opt_ver) {
Notice("option openssl-verify disabled, no check of certificate");
X509_free(peer_cert);
return STAT_OK;
}
if (peername == NULL || peername[0] == '\0') {
Notice("trusting certificate, no check of commonName");
X509_free(peer_cert);
return STAT_OK;
}
if (ok) {
Notice("trusting certificate, commonName matches");
X509_free(peer_cert);
return STAT_OK;
}
/* here: all envs set; opt_ver, cert verified, no subjAltName match -> check subject CN */
if (!openssl_check_peername(/*X509_NAME*/subjectname, /*const char*/peername)) {
Error("certificate is valid but its commonName does not match hostname");
status = STAT_NORETRY;
} else {
Notice("trusting certificate, commonName matches");
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, const char *opt_commonname,
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_commonname, 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");
break;
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 */