#include "ssl_iot_common.h"
#include "settings.h"
#include "logging.h"
#include <openssl/err.h>
#define LOGARGS(ssl, lvl) \
((lcbio_SOCKET*)SSL_get_app_data(ssl))->settings, "SSL", lvl, __FILE__, __LINE__
static char *global_event = "dummy event for ssl";
static void loop_run(lcb_io_opt_t io) {
lcbio_XSSL *xs = IOTSSL_FROM_IOPS(io);
IOT_START(xs->orig);
}
static void loop_stop(lcb_io_opt_t io) {
lcbio_XSSL *xs = IOTSSL_FROM_IOPS(io);
IOT_STOP(xs->orig);
}
static void *create_event(lcb_io_opt_t io) {
(void)io;
return global_event;
}
static void destroy_event(lcb_io_opt_t io, void *event) {
(void) io; (void) event;
}
static void *create_timer(lcb_io_opt_t io) {
lcbio_XSSL *xs = IOTSSL_FROM_IOPS(io);
return xs->orig->timer.create(IOT_ARG(xs->orig));
}
static int schedule_timer(lcb_io_opt_t io, void *timer, lcb_uint32_t us,
void *arg, lcb_ioE_callback callback) {
lcbio_XSSL *xs = IOTSSL_FROM_IOPS(io);
return xs->orig->timer.schedule(IOT_ARG(xs->orig), timer, us, arg, callback);
}
static void destroy_timer(lcb_io_opt_t io, void *timer) {
lcbio_XSSL *xs = IOTSSL_FROM_IOPS(io);
xs->orig->timer.destroy(IOT_ARG(xs->orig), timer);
}
static void cancel_timer(lcb_io_opt_t io, void *timer) {
lcbio_XSSL *xs = IOTSSL_FROM_IOPS(io);
xs->orig->timer.cancel(IOT_ARG(xs->orig), timer);
}
static int Eis_closed(lcb_io_opt_t io, lcb_socket_t sock, int flags) {
lcbio_XSSL *xs = IOTSSL_FROM_IOPS(io);
return xs->orig->u_io.v0.io.is_closed(IOT_ARG(xs->orig), sock, flags);
}
static int Cis_closed(lcb_io_opt_t io, lcb_sockdata_t *sd, int flags) {
lcbio_XSSL *xs = IOTSSL_FROM_IOPS(io);
return xs->orig->u_io.completion.is_closed(IOT_ARG(xs->orig), sd, flags);
}
void
iotssl_init_common(lcbio_XSSL *xs, lcbio_TABLE *orig, SSL_CTX *sctx)
{
lcbio_TABLE *base = &xs->base_;
xs->iops_dummy_ = calloc(1, sizeof(*xs->iops_dummy_));
xs->iops_dummy_->v.v0.cookie = xs;
xs->orig = orig;
base->model = xs->orig->model;
base->p = xs->iops_dummy_;
base->refcount = 1;
base->loop.start = loop_run;
base->loop.stop = loop_stop;
base->timer.create = create_timer;
base->timer.destroy = destroy_timer;
base->timer.schedule = schedule_timer;
base->timer.cancel = cancel_timer;
if (orig->model == LCB_IOMODEL_EVENT) {
base->u_io.v0.ev.create = create_event;
base->u_io.v0.ev.destroy = destroy_event;
base->u_io.v0.io.is_closed = Eis_closed;
} else {
base->u_io.completion.is_closed = Cis_closed;
}
lcbio_table_ref(xs->orig);
xs->error = 0;
xs->ssl = SSL_new(sctx);
xs->rbio = BIO_new(BIO_s_mem());
xs->wbio = BIO_new(BIO_s_mem());
SSL_set_bio(xs->ssl, xs->rbio, xs->wbio);
SSL_set_read_ahead(xs->ssl, 0);
SSL_set_connect_state(xs->ssl);
}
void
iotssl_destroy_common(lcbio_XSSL *xs)
{
free(xs->iops_dummy_);
SSL_free(xs->ssl);
lcbio_table_unref(xs->orig);
}
void
iotssl_bm_reserve(BUF_MEM *bm)
{
int oldlen;
oldlen = bm->length;
while (bm->max - bm->length < 4096) {
BUF_MEM_grow(bm, bm->max + 4096);
}
bm->length = oldlen;
}
void
iotssl_log_errors(lcbio_XSSL *xs)
{
unsigned long curerr;
while ((curerr = ERR_get_error())) {
char errbuf[4096];
ERR_error_string_n(curerr, errbuf, sizeof errbuf);
lcb_log(LOGARGS(xs->ssl, LCB_LOG_ERROR), "%s", errbuf);
if (xs->errcode != LCB_SUCCESS) {
continue;
}
if (ERR_GET_LIB(curerr) == ERR_LIB_SSL) {
switch (ERR_GET_REASON(curerr)) {
case SSL_R_CERTIFICATE_VERIFY_FAILED:
case SSL_R_MISSING_VERIFY_MESSAGE:
xs->errcode = LCB_SSL_CANTVERIFY;
break;
case SSL_R_BAD_PROTOCOL_VERSION_NUMBER:
case SSL_R_UNKNOWN_PROTOCOL:
case SSL_R_WRONG_VERSION_NUMBER:
case SSL_R_UNKNOWN_SSL_VERSION:
case SSL_R_UNSUPPORTED_SSL_VERSION:
xs->errcode = LCB_PROTOCOL_ERROR;
break;
default:
xs->errcode = LCB_SSL_ERROR;
}
}
}
}
static void
log_global_errors(lcb_settings *settings)
{
unsigned long curerr;
while ((curerr = ERR_get_error())) {
char errbuf[4096];
ERR_error_string_n(curerr, errbuf, sizeof errbuf);
lcb_log(settings, "SSL", LCB_LOG_ERROR, __FILE__, __LINE__, "SSL Error: %ld, %s", curerr, errbuf);
}
}
int
iotssl_maybe_error(lcbio_XSSL *xs, int rv)
{
assert(rv < 1);
if (rv == -1) {
int err = SSL_get_error(xs->ssl, rv);
if (err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE) {
return 0;
}
}
iotssl_log_errors(xs);
return -1;
}
static void
log_callback(const SSL *ssl, int where, int ret)
{
const char *retstr = "";
int should_log = 0;
lcbio_SOCKET *sock = SSL_get_app_data(ssl);
if (where & SSL_CB_ALERT) {
should_log = 1;
}
if (where == SSL_CB_HANDSHAKE_START || where == SSL_CB_HANDSHAKE_DONE) {
should_log = 1;
}
if ((where & SSL_CB_EXIT) && ret == 0) {
should_log = 1;
}
if (!should_log) {
return;
}
retstr = SSL_alert_type_string(ret);
lcb_log(LOGARGS(ssl, LCB_LOG_TRACE), "sock=%p: ST(0x%x). %s. R(0x%x)%s",
(void*)sock, where, SSL_state_string_long(ssl), ret, retstr);
if (where == SSL_CB_HANDSHAKE_DONE) {
lcb_log(LOGARGS(ssl, LCB_LOG_DEBUG), "sock=%p. Using SSL version %s. Cipher=%s", (void*)sock, SSL_get_version(ssl), SSL_get_cipher_name(ssl));
}
}
#if 0#endif
struct lcbio_SSLCTX {
SSL_CTX *ctx;
};
lcbio_pSSLCTX
lcbio_ssl_new(const char *cafile, int noverify, lcb_error_t *errp,
lcb_settings *settings)
{
lcb_error_t err_s;
lcbio_pSSLCTX ret;
if (!errp) {
errp = &err_s;
}
ret = calloc(1, sizeof(*ret));
if (!ret) {
*errp = LCB_CLIENT_ENOMEM;
goto GT_ERR;
}
ret->ctx = SSL_CTX_new(SSLv23_client_method());
if (!ret->ctx) {
*errp = LCB_SSL_ERROR;
goto GT_ERR;
}
SSL_CTX_set_cipher_list(ret->ctx, "DHE-RSA-AES256-SHA:DHE-DSS-AES256-SHA:AES256-SHA:EDH-RSA-DES-CBC3-SHA:EDH-DSS-DES-CBC3-SHA:DES-CBC3-SHA:DES-CBC3-MD5:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA:AES128-SHA:DHE-RSA-SEED-SHA:DHE-DSS-SEED-SHA:SEED-SHA:RC2-CBC-MD5:RC4-SHA:RC4-MD5:RC4-MD5:EDH-RSA-DES-CBC-SHA:EDH-DSS-DES-CBC-SHA:DES-CBC-SHA:DES-CBC-MD5:EXP-EDH-RSA-DES-CBC-SHA:EXP-EDH-DSS-DES-CBC-SHA:EXP-DES-CBC-SHA:EXP-RC2-CBC-MD5:EXP-RC2-CBC-MD5:EXP-RC4-MD5:EXP-RC4-MD5");
if (cafile) {
if (!SSL_CTX_load_verify_locations(ret->ctx, cafile, NULL)) {
*errp = LCB_SSL_ERROR;
goto GT_ERR;
}
}
if (noverify) {
SSL_CTX_set_verify(ret->ctx, SSL_VERIFY_NONE, NULL);
} else {
SSL_CTX_set_verify(ret->ctx, SSL_VERIFY_PEER, NULL);
}
SSL_CTX_set_info_callback(ret->ctx, log_callback);
#if 0 #endif
SSL_CTX_set_mode(ret->ctx, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
SSL_CTX_set_options(ret->ctx, SSL_OP_NO_SSLv2|SSL_OP_NO_SSLv3);
return ret;
GT_ERR:
log_global_errors(settings);
if (ret) {
if (ret->ctx) {
SSL_CTX_free(ret->ctx);
}
free(ret);
}
return NULL;
}
static void
noop_dtor(lcbio_PROTOCTX *arg) {
free(arg);
}
lcb_error_t
lcbio_ssl_apply(lcbio_SOCKET *sock, lcbio_pSSLCTX sctx)
{
lcbio_pTABLE old_iot = sock->io, new_iot;
lcbio_PROTOCTX *sproto;
if (old_iot->model == LCB_IOMODEL_EVENT) {
new_iot = lcbio_Essl_new(old_iot, sock->u.fd, sctx->ctx);
} else {
new_iot = lcbio_Cssl_new(old_iot, sock->u.sd, sctx->ctx);
}
if (new_iot) {
sproto = calloc(1, sizeof(*sproto));
sproto->id = LCBIO_PROTOCTX_SSL;
sproto->dtor = noop_dtor;
lcbio_protoctx_add(sock, sproto);
lcbio_table_unref(old_iot);
sock->io = new_iot;
SSL_set_app_data(((lcbio_XSSL *)new_iot)->ssl, sock);
return LCB_SUCCESS;
} else {
return LCB_ERROR;
}
}
int
lcbio_ssl_check(lcbio_SOCKET *sock)
{
return lcbio_protoctx_get(sock, LCBIO_PROTOCTX_SSL) != NULL;
}
lcb_error_t
lcbio_ssl_get_error(lcbio_SOCKET *sock)
{
lcbio_XSSL *xs = (lcbio_XSSL *)sock->io;
return xs->errcode;
}
void
lcbio_ssl_free(lcbio_pSSLCTX ctx)
{
SSL_CTX_free(ctx->ctx);
free(ctx);
}
#if defined(_POSIX_THREADS)
#include <pthread.h>
typedef pthread_mutex_t ossl_LOCKTYPE;
static void ossl_lock_init(ossl_LOCKTYPE *l) { pthread_mutex_init(l, NULL); }
static void ossl_lock_acquire(ossl_LOCKTYPE *l) { pthread_mutex_lock(l); }
static void ossl_lock_release(ossl_LOCKTYPE *l) { pthread_mutex_unlock(l); }
#elif defined(_WIN32)
#include <windows.h>
typedef CRITICAL_SECTION ossl_LOCKTYPE;
static void ossl_lock_init(ossl_LOCKTYPE *l) { InitializeCriticalSection(l); }
static void ossl_lock_acquire(ossl_LOCKTYPE *l) { EnterCriticalSection(l); }
static void ossl_lock_release(ossl_LOCKTYPE *l) { LeaveCriticalSection(l); }
#else
typedef char ossl_LOCKTYPE;
#define ossl_lock_init(l)
#define ossl_lock_acquire(l)
#define ossl_lock_release(l)
#endif
static ossl_LOCKTYPE *ossl_locks;
static void
ossl_lockfn(int mode, int lkid, const char *f, int line)
{
ossl_LOCKTYPE *l = ossl_locks + lkid;
if (mode & CRYPTO_LOCK) {
ossl_lock_acquire(l);
} else {
ossl_lock_release(l);
}
(void)f;
(void)line;
}
static void
ossl_init_locks(void)
{
unsigned ii, nlocks;
if (CRYPTO_get_locking_callback() != NULL) {
return;
}
nlocks = CRYPTO_num_locks();
ossl_locks = malloc(sizeof(*ossl_locks) * nlocks);
for (ii = 0; ii < nlocks; ii++) {
ossl_lock_init(ossl_locks + ii);
}
CRYPTO_set_locking_callback(ossl_lockfn);
}
static volatile int ossl_initialized = 0;
void lcbio_ssl_global_init(void)
{
if (ossl_initialized) {
return;
}
ossl_initialized = 1;
SSL_library_init();
SSL_load_error_strings();
ossl_init_locks();
}
lcb_error_t
lcbio_sslify_if_needed(lcbio_SOCKET *sock, lcb_settings *settings)
{
if (!(settings->sslopts & LCB_SSL_ENABLED)) {
return LCB_SUCCESS;
}
if (lcbio_ssl_check(sock)) {
return LCB_SUCCESS;
}
return lcbio_ssl_apply(sock, settings->ssl_ctx);
}