mirror of
https://repo.or.cz/socat.git
synced 2024-12-22 23:42:34 +00:00
1870 lines
59 KiB
C
1870 lines
59 KiB
C
/* source: xio-openssl.c */
|
|
/* Copyright Gerhard Rieger and contributors (see file CHANGES) */
|
|
/* 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 };
|
|
#if WITH_OPENSSL_METHOD
|
|
const struct optdesc opt_openssl_method = { "openssl-method", "method", OPT_OPENSSL_METHOD, GROUP_OPENSSL, PH_SPEC, TYPE_STRING, OFUNC_SPEC };
|
|
#endif
|
|
#if HAVE_SSL_CTX_set_min_proto_version || defined(SSL_CTX_set_min_proto_version)
|
|
const struct optdesc opt_openssl_min_proto_version = { "openssl-min-proto-version", "min-version", OPT_OPENSSL_MIN_PROTO_VERSION, GROUP_OPENSSL, PH_INIT, TYPE_STRING, OFUNC_OFFSET, XIO_OFFSETOF(para.openssl.min_proto_version) };
|
|
#endif
|
|
#if HAVE_SSL_CTX_set_max_proto_version || defined(SSL_CTX_set_max_proto_version)
|
|
const struct optdesc opt_openssl_max_proto_version = { "openssl-max-proto-version", "max-version", OPT_OPENSSL_MAX_PROTO_VERSION, GROUP_OPENSSL, PH_INIT, TYPE_STRING, OFUNC_OFFSET, XIO_OFFSETOF(para.openssl.max_proto_version) };
|
|
#endif
|
|
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 && !defined(OPENSSL_NO_COMP)
|
|
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 };
|
|
|
|
|
|
/* 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) {
|
|
const char *string;
|
|
|
|
string = SSL_get_cipher_version(ssl);
|
|
Notice1("SSL proto version used: %s", string);
|
|
xiosetenv("OPENSSL_PROTO_VERSION", string, 1, NULL);
|
|
|
|
string = SSL_get_cipher(ssl);
|
|
Notice1("SSL connection using %s", string);
|
|
xiosetenv("OPENSSL_CIPHER", string, 1, NULL);
|
|
|
|
#if OPENSSL_VERSION_NUMBER >= 0x00908000L && !defined(OPENSSL_NO_COMP)
|
|
{
|
|
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 */
|
|
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);
|
|
|
|
if (opt_commonname == NULL) {
|
|
opt_commonname = 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?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, 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,
|
|
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_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
|
|
|
|
|
|
#if HAVE_CTX_SSL_set_min_proto_version || defined(SSL_CTX_set_min_proto_version) || \
|
|
HAVE_SSL_CTX_set_max_proto_version || defined(SSL_CTX_set_max_proto_version)
|
|
#define XIO_OPENSSL_VERSIONGROUP_TLS 1
|
|
#define XIO_OPENSSL_VERSIONGROUP_DTLS 2
|
|
|
|
static struct wordent _xio_openssl_versions[] = {
|
|
#ifdef DTLS1_VERSION
|
|
{ "DTLS1", (void *)DTLS1_VERSION },
|
|
{ "DTLS1.0", (void *)DTLS1_VERSION },
|
|
#endif
|
|
#ifdef DTLS1_2_VERSION
|
|
{ "DTLS1.2", (void *)DTLS1_2_VERSION },
|
|
#endif
|
|
#ifdef DTLS1_VERSION
|
|
{ "DTLSv1", (void *)DTLS1_VERSION },
|
|
{ "DTLSv1.0", (void *)DTLS1_VERSION },
|
|
#endif
|
|
#ifdef DTLS1_2_VERSION
|
|
{ "DTLSv1.2", (void *)DTLS1_2_VERSION },
|
|
#endif
|
|
#ifdef SSL2_VERSION
|
|
{ "SSL2", (void *)SSL2_VERSION },
|
|
#endif
|
|
#ifdef SSL3_VERSION
|
|
{ "SSL3", (void *)SSL3_VERSION },
|
|
#endif
|
|
#ifdef SSL2_VERSION
|
|
{ "SSLv2", (void *)SSL2_VERSION },
|
|
#endif
|
|
#ifdef SSL3_VERSION
|
|
{ "SSLv3", (void *)SSL3_VERSION },
|
|
#endif
|
|
#ifdef TLS1_VERSION
|
|
{ "TLS1", (void *)TLS1_VERSION },
|
|
{ "TLS1.0", (void *)TLS1_VERSION },
|
|
#endif
|
|
#ifdef TLS1_1_VERSION
|
|
{ "TLS1.1", (void *)TLS1_1_VERSION },
|
|
#endif
|
|
#ifdef TLS1_2_VERSION
|
|
{ "TLS1.2", (void *)TLS1_2_VERSION },
|
|
#endif
|
|
#ifdef TLS1_3_VERSION
|
|
{ "TLS1.3", (void *)TLS1_3_VERSION },
|
|
#endif
|
|
#ifdef TLS1_VERSION
|
|
{ "TLSv1", (void *)TLS1_VERSION },
|
|
{ "TLSv1.0", (void *)TLS1_VERSION },
|
|
#endif
|
|
#ifdef TLS1_1_VERSION
|
|
{ "TLSv1.1", (void *)TLS1_1_VERSION },
|
|
#endif
|
|
#ifdef TLS1_2_VERSION
|
|
{ "TLSv1.2", (void *)TLS1_2_VERSION },
|
|
#endif
|
|
#ifdef TLS1_3_VERSION
|
|
{ "TLSv1.3", (void *)TLS1_3_VERSION },
|
|
#endif
|
|
} ;
|
|
|
|
static int _xio_openssl_parse_version(const char *verstring, int vergroups) {
|
|
int sslver;
|
|
const struct wordent *we;
|
|
we = keyw(_xio_openssl_versions, verstring,
|
|
sizeof(_xio_openssl_versions)/sizeof(struct wordent));
|
|
if (we == 0) {
|
|
Error1("Unknown SSL/TLS version \"%s\"", verstring);
|
|
return -1;
|
|
}
|
|
sslver = (size_t)we->desc;
|
|
switch (sslver) {
|
|
#ifdef SSL2_VERSION
|
|
case SSL2_VERSION:
|
|
#endif
|
|
#ifdef SSL3_VERSION
|
|
case SSL3_VERSION:
|
|
#endif
|
|
#ifdef TLS1_VERSION
|
|
case TLS1_VERSION:
|
|
#endif
|
|
#ifdef TLS1_1_VERSION
|
|
case TLS1_1_VERSION:
|
|
#endif
|
|
#ifdef TLS1_2_VERSION
|
|
case TLS1_2_VERSION:
|
|
#endif
|
|
#ifdef TLS1_3_VERSION
|
|
case TLS1_3_VERSION:
|
|
#endif
|
|
if (!(vergroups & XIO_OPENSSL_VERSIONGROUP_TLS)) {
|
|
Error1("Wrong type of TLS/DTLS version \"%s\"", verstring);
|
|
return -1;
|
|
}
|
|
#ifdef DTLS1_VERSION
|
|
case DTLS1_VERSION:
|
|
#endif
|
|
#ifdef DTLS1_2_VERSION
|
|
case DTLS1_2_VERSION:
|
|
#endif
|
|
if (!(vergroups & XIO_OPENSSL_VERSIONGROUP_DTLS)) {
|
|
Error1("Wrong type of TLS/DTLS version \"%s\"", verstring);
|
|
return -1;
|
|
}
|
|
break;
|
|
}
|
|
return sslver;
|
|
}
|
|
#endif /* defined(SSL_CTX_set_min_proto_version) || defined(SSL_CTX_set_max_proto_version) */
|
|
|
|
|
|
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 **ctxp)
|
|
{
|
|
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_TLS_client_method
|
|
method = TLS_client_method();
|
|
#elif 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_TLS_server_method
|
|
method = TLS_server_method();
|
|
#elif 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) {
|
|
#if !defined(OPENSSL_NO_EGD) && HAVE_RAND_egd
|
|
sycRAND_egd(opt_egd);
|
|
#else
|
|
Debug("RAND_egd() is not available by OpenSSL");
|
|
#endif
|
|
}
|
|
|
|
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;
|
|
}
|
|
xfd->para.openssl.ctx = ctx;
|
|
*ctxp = ctx;
|
|
|
|
#if HAVE_SSL_CTX_set_min_proto_version || defined(SSL_CTX_set_min_proto_version)
|
|
if (xfd->para.openssl.min_proto_version != NULL) {
|
|
int sslver, rc;
|
|
sslver = _xio_openssl_parse_version(xfd->para.openssl.min_proto_version,
|
|
XIO_OPENSSL_VERSIONGROUP_TLS|XIO_OPENSSL_VERSIONGROUP_DTLS);
|
|
if (sslver < 0)
|
|
return STAT_NORETRY;
|
|
if ((rc = SSL_CTX_set_min_proto_version(ctx, sslver)) <= 0) {
|
|
Debug1("version: %d", SSL_CTX_get_min_proto_version(ctx));
|
|
Error3("_xioopen_openssl_prepare(): SSL_CTX_set_min_proto_version(\"%s\"->%d): failed (%d)",
|
|
xfd->para.openssl.min_proto_version, sslver, rc);
|
|
return STAT_NORETRY;
|
|
}
|
|
Debug1("version: %d", SSL_CTX_get_min_proto_version(ctx));
|
|
}
|
|
#endif /* HAVE_SSL_set_min_proto_version || defined(SSL_set_min_proto_version) */
|
|
#if HAVE_SSL_CTX_set_max_proto_version || defined(SSL_CTX_set_max_proto_version)
|
|
if (xfd->para.openssl.max_proto_version != NULL) {
|
|
int sslver;
|
|
sslver = _xio_openssl_parse_version(xfd->para.openssl.max_proto_version,
|
|
XIO_OPENSSL_VERSIONGROUP_TLS|XIO_OPENSSL_VERSIONGROUP_DTLS);
|
|
if (sslver < 0)
|
|
return STAT_NORETRY;
|
|
if (SSL_CTX_set_max_proto_version(ctx, sslver) <= 0) {
|
|
Error2("_xioopen_openssl_prepare(): SSL_CTX_set_max_proto_version(\"%s\"->%d): failed",
|
|
xfd->para.openssl.max_proto_version, sslver);
|
|
return STAT_NORETRY;
|
|
}
|
|
}
|
|
#endif /* HAVE_SSL_set_max_proto_version || defined(SSL_set_max_proto_version) */
|
|
|
|
{
|
|
static unsigned char dh2048_p[] = {
|
|
0x00,0xdc,0x21,0x64,0x56,0xbd,0x9c,0xb2,0xac,0xbe,0xc9,0x98,0xef,0x95,0x3e,
|
|
0x26,0xfa,0xb5,0x57,0xbc,0xd9,0xe6,0x75,0xc0,0x43,0xa2,0x1c,0x7a,0x85,0xdf,
|
|
0x34,0xab,0x57,0xa8,0xf6,0xbc,0xf6,0x84,0x7d,0x05,0x69,0x04,0x83,0x4c,0xd5,
|
|
0x56,0xd3,0x85,0x09,0x0a,0x08,0xff,0xb5,0x37,0xa1,0xa3,0x8a,0x37,0x04,0x46,
|
|
0xd2,0x93,0x31,0x96,0xf4,0xe4,0x0d,0x9f,0xbd,0x3e,0x7f,0x9e,0x4d,0xaf,0x08,
|
|
0xe2,0xe8,0x03,0x94,0x73,0xc4,0xdc,0x06,0x87,0xbb,0x6d,0xae,0x66,0x2d,0x18,
|
|
0x1f,0xd8,0x47,0x06,0x5c,0xcf,0x8a,0xb5,0x00,0x51,0x57,0x9b,0xea,0x1e,0xd8,
|
|
0xdb,0x8e,0x3c,0x1f,0xd3,0x2f,0xba,0x1f,0x5f,0x3d,0x15,0xc1,0x3b,0x2c,0x82,
|
|
0x42,0xc8,0x8c,0x87,0x79,0x5b,0x38,0x86,0x3a,0xeb,0xfd,0x81,0xa9,0xba,0xf7,
|
|
0x26,0x5b,0x93,0xc5,0x3e,0x03,0x30,0x4b,0x00,0x5c,0xb6,0x23,0x3e,0xea,0x94,
|
|
0xc3,0xb4,0x71,0xc7,0x6e,0x64,0x3b,0xf8,0x92,0x65,0xad,0x60,0x6c,0xd4,0x7b,
|
|
0xa9,0x67,0x26,0x04,0xa8,0x0a,0xb2,0x06,0xeb,0xe0,0x7d,0x90,0xdd,0xdd,0xf5,
|
|
0xcf,0xb4,0x11,0x7c,0xab,0xc1,0xa3,0x84,0xbe,0x27,0x77,0xc7,0xde,0x20,0x57,
|
|
0x66,0x47,0xa7,0x35,0xfe,0x0d,0x6a,0x1c,0x52,0xb8,0x58,0xbf,0x26,0x33,0x81,
|
|
0x5e,0xb7,0xa9,0xc0,0xee,0x58,0x11,0x74,0x86,0x19,0x08,0x89,0x1c,0x37,0x0d,
|
|
0x52,0x47,0x70,0x75,0x8b,0xa8,0x8b,0x30,0x11,0x71,0x36,0x62,0xf0,0x73,0x41,
|
|
0xee,0x34,0x9d,0x0a,0x2b,0x67,0x4e,0x6a,0xa3,0xe2,0x99,0x92,0x1b,0xf5,0x32,
|
|
0x73,0x63
|
|
};
|
|
static unsigned char dh2048_g[] = {
|
|
0x02,
|
|
};
|
|
DH *dh;
|
|
BIGNUM *p = NULL, *g = NULL;
|
|
unsigned long err;
|
|
|
|
dh = DH_new();
|
|
p = BN_bin2bn(dh2048_p, sizeof(dh2048_p), NULL);
|
|
g = BN_bin2bn(dh2048_g, sizeof(dh2048_g), NULL);
|
|
if (!dh || !p || !g) {
|
|
if (dh)
|
|
DH_free(dh);
|
|
if (p)
|
|
BN_free(p);
|
|
if (g)
|
|
BN_free(g);
|
|
while (err = ERR_get_error()) {
|
|
Warn1("dh2048 setup(): %s",
|
|
ERR_error_string(err, NULL));
|
|
}
|
|
Error("dh2048 setup failed");
|
|
goto cont_out;
|
|
}
|
|
#if HAVE_DH_set0_pqg
|
|
if (!DH_set0_pqg(dh, p, NULL, g)) {
|
|
DH_free(dh);
|
|
BN_free(p);
|
|
BN_free(g);
|
|
goto cont_out;
|
|
}
|
|
#else
|
|
dh->p = p;
|
|
dh->g = g;
|
|
#endif /* HAVE_DH_set0_pqg */
|
|
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);
|
|
}
|
|
/* p & g are freed by DH_free() once attached */
|
|
DH_free(dh);
|
|
cont_out:
|
|
;
|
|
}
|
|
|
|
#if HAVE_TYPE_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 /* HAVE_TYPE_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 defined(HAVE_SSL_CTX_clear_mode) || defined(SSL_CTX_clear_mode)
|
|
/* It seems that OpenSSL-1.1.1 presets the mode differently.
|
|
Without correction socat might hang in SSL_read() */
|
|
{
|
|
long mode = 0;
|
|
mode = SSL_CTX_get_mode(ctx);
|
|
if (mode & SSL_MODE_AUTO_RETRY) {
|
|
Info("SSL_CTX mode has SSL_MODE_AUTO_RETRY set. Correcting..");
|
|
Debug1("SSL_CTX_clean_mode(%p, SSL_MODE_AUTO_RETRY)", ctx);
|
|
SSL_CTX_clear_mode(ctx, SSL_MODE_AUTO_RETRY);
|
|
}
|
|
}
|
|
#endif /* defined(HAVE_SSL_CTX_clear_mode) || defined(SSL_CTX_clear_mode) */
|
|
|
|
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 openssl-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
|
|
(
|
|
#if defined(OPENSSL_IS_BORINGSSL)
|
|
0 /* BoringSSL's RNG always succeeds. */
|
|
#elif defined(HAVE_RAND_status)
|
|
ERR_GET_LIB(e) == ERR_LIB_RAND && RAND_status() != 1
|
|
#else
|
|
e == ((ERR_LIB_RAND<<24)|
|
|
#if defined(RAND_F_RAND_BYTES)
|
|
(RAND_F_RAND_BYTES<<12)|
|
|
#else
|
|
(RAND_F_SSLEAY_RAND_BYTES<<12)|
|
|
#endif
|
|
(RAND_R_PRNG_NOT_SEEDED)) /*0x24064064*/
|
|
#endif
|
|
)
|
|
{
|
|
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);
|
|
#if HAVE_UNSETENV
|
|
Unsetenv(envname);
|
|
#endif
|
|
} 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;
|
|
const 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);
|
|
#if HAVE_ASN1_STRING_get0_data
|
|
text = ASN1_STRING_get0_data(data);
|
|
#else
|
|
text = ASN1_STRING_data(data);
|
|
#endif
|
|
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;
|
|
const 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);
|
|
#if HAVE_ASN1_STRING_get0_data
|
|
text = ASN1_STRING_get0_data(data);
|
|
#else
|
|
text = ASN1_STRING_data(data);
|
|
#endif
|
|
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 */
|