/* ==========================================================================
* libevnet/src/tls.c - OpenSSL interface for libevnet.
* --------------------------------------------------------------------------
* Copyright (c) 2003 William Ahern
* Copyright (c) 2004 Barracuda Networks, Inc.
* Copyright (c) 2006 William Ahern
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
* NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
* USE OR OTHER DEALINGS IN THE SOFTWARE.
* ==========================================================================
*/
#ifdef USE_OPENSSL
#include <stdio.h> /* snprintf(3) */
#include <errno.h> /* EINVAL EAGAIN EPIPE */
#include <string.h> /* memset(3), strrchr(3) strerror(3) strcmp(3) */
#include <assert.h> /* assert(3) */
#include <windows.h> /* GetLastError SetLastError */
#include <sys/param.h> /* MAX */
#include <sys/queue.h> /* SLIST */
#include <sys/time.h> /* struct timeval timersub gettimeofday(2) */
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <event.h>
#include <arena/proto.h>
#include "tls.h"
#ifndef MARK
#define MARK fprintf(stderr, "@%s:%d\n", __FILE__, __LINE__);
#endif
#define OPENSSL_WARN do { \
unsigned long e; \
while ((e = ERR_get_error())) { \
warnx("openssl: %s", ERR_error_string(e, 0)); \
} \
} while(0)
const char *tls_errlist[] = {
[TLS_ESUCCESS] = "Success",
[TLS_ESYSTEM] = "System error",
[TLS_EOPENSSL] = "OpenSSL error",
[TLS_ETIMEDOUT] = "Operation timed out",
[TLS_ECANCELLED] = "Operation cancelled",
}; /* tls_errlist */
const int tls_nerr = sizeof tls_errlist / sizeof *tls_errlist;
const struct tls_options tls_options_initializer = {
.versions = 0,
.require_trust = 0,
.is_trusted = 0,
}; /* tls_options_initializer */
const struct tls_options tls_defaults = {
.versions = TLS_VERSION_1,
.require_trust = 0,
.is_trusted = 0,
}; /* tls_defaults */
struct stack_note {
struct tls **tp;
SLIST_ENTRY(stack_note) sle;
}; /* struct stack_note */
static const struct tls_identity {
struct tls_options opts;
const struct arena_prototype *ap;
SSL_CTX *context;
} tls_identity_initializer;
static const struct tls {
int fd;
struct tls_options opts;
struct event_base *evbase;
const struct arena_prototype *ap;
struct event event;
short event_pending;
struct timeval event_deadline;
int deadline_pending;
struct {
tls_read_cb cb;
void *arg;
} read;
struct {
tls_write_cb cb;
void *arg;
} write;
struct {
tls_accept_cb cb;
void *arg;
} accept;
/*
* Not currently used. tls_accept() is overloaded for this purpose.
*/
struct {
tls_connect_cb cb;
void *arg;
} connect;
SSL *socket;
enum tls_state state;
struct tls_info info;
enum tls_errno tls_errno;
int sys_errno;
SLIST_HEAD(, stack_note) stack;
} tls_initializer = {
.fd = -1,
.info = { .cipher = { .name = "UNKNOWN" }, .version = { .name = "UNKNOWN" } },
};
enum tls_errno tls_reset(void) {
return TLS_ESUCCESS;
} /* tls_reset() */
enum tls_errno tls_init(void) {
SSL_load_error_strings();
(void)SSL_library_init();
return TLS_ESUCCESS;
} /* tls_init() */
static void tls_options_copy(struct tls_options *dst, const struct tls_options *src) {
*dst = tls_defaults;
if (src->versions != tls_options_initializer.versions)
dst->versions = src->versions;
if (src->require_trust != tls_options_initializer.require_trust)
dst->require_trust = src->require_trust;
if (src->is_trusted != tls_options_initializer.is_trusted)
dst->is_trusted = src->is_trusted;
return /* void */;
} /* tls_options_copy() */
struct tls_identity *tls_identity_open(const struct tls_options *opts, const struct arena_prototype *ap, enum tls_errno *ep) {
struct tls_identity *c = 0;
SSL_METHOD *openssl_method = 0;
enum tls_errno tls_errno = TLS_ESYSTEM;
int sys_errno;
if (!ap)
ap = ARENA_STDLIB;
if (!(c = ap->malloc(ap, sizeof *c, 0)))
goto failed;
*c = tls_identity_initializer;
tls_options_copy(&c->opts, (opts)? opts : &tls_defaults);
c->ap = ap;
/*
* TODO: Investigate whether using the SSLv3_method() allows TLSv1
* connections.
*/
switch (c->opts.versions) {
case SSL_VERSION_2:
openssl_method = SSLv2_method();
break;
case SSL_VERSION_3:
openssl_method = SSLv3_method();
break;
case SSL_VERSION_2|SSL_VERSION_3:
openssl_method = SSLv23_method();
break;
case TLS_VERSION_1:
openssl_method = TLSv1_method();
break;
case TLS_VERSION_1|SSL_VERSION_3:
openssl_method = SSLv3_method();
break;
case TLS_VERSION_1|SSL_VERSION_2|SSL_VERSION_3:
openssl_method = SSLv23_method();
break;
default:
SetLastError(EINVAL);
goto failed;
}
if (!openssl_method) {
tls_errno = TLS_EOPENSSL;
goto failed;
}
if (!(c->context = SSL_CTX_new(openssl_method))) {
tls_errno = TLS_EOPENSSL;
goto failed;
}
return c;
failed:
sys_errno = GetLastError();
if (ep)
*ep = tls_errno;
if (c) {
if (c->context)
SSL_CTX_free(c->context);
ap->free(ap, memset(c, 0, sizeof *c));
}
SetLastError(sys_errno);
return 0;
} /* tls_identity_open() */
void tls_identity_close(struct tls_identity *c) {
const struct arena_prototype *ap;
if (c == 0)
return /* void */;
ap = c->ap;
if (c->context)
SSL_CTX_free(c->context);
ap->free(ap, memset(c, 0, sizeof *c));
return /* void */;
} /* tls_identity_close() */
enum tls_errno tls_identity_certify(struct tls_identity *c, const char *cert_path, const char *key_path) {
if (!cert_path || !key_path)
return SetLastError(EINVAL), TLS_ESYSTEM;
if (1 != SSL_CTX_use_certificate_file(c->context, cert_path, SSL_FILETYPE_PEM)
&& 1 != SSL_CTX_use_certificate_file(c->context, cert_path, SSL_FILETYPE_ASN1))
return TLS_EOPENSSL;
if (1 != SSL_CTX_use_PrivateKey_file(c->context, key_path, SSL_FILETYPE_PEM)
&& 1 != SSL_CTX_use_PrivateKey_file(c->context, key_path, SSL_FILETYPE_ASN1))
return TLS_EOPENSSL;
return 0;
} /* tls_identity_certify() */
static int tls_identity_trust_path_syntax_isdir(const char *path) {
const char *c;
if (!(c = strrchr(path, '/')))
return 0;
return (*(c + 1) == '\0' || (*(c + 1) == '.' && *(c + 2) == '\0'));
} /* tls_identity_trust_path_syntax_isdir() */
enum tls_errno tls_identity_trust(struct tls_identity *c, const char *path) {
const char *file_path = 0;
const char *dir_path = 0;
if (tls_identity_trust_path_syntax_isdir(path))
dir_path = path;
else
file_path = path;
if (1 != SSL_CTX_load_verify_locations(c->context, file_path, dir_path))
return TLS_EOPENSSL;
return 0;
} /* tls_identity_trust */
SSL_CTX *tls_identity_peek(struct tls_identity *c) {
return c->context;
} /* tls_identity_peek() */
struct tls *tls_open(int fd, struct tls_identity *c, struct event_base *evbase, const struct arena_prototype *ap, enum tls_errno *ep) {
struct tls *t = 0;
enum tls_errno tls_errno = TLS_ESYSTEM;
int sys_errno;
if (!ap)
ap = ARENA_STDLIB;
if (!(t = ap->malloc(ap, sizeof *t, 0)))
goto failed;
*t = tls_initializer;
tls_options_copy(&t->opts, &c->opts);
t->fd = fd;
t->evbase = evbase;
t->ap = ap;
SLIST_INIT(&t->stack);
if (!(t->socket = SSL_new(c->context))
|| 1 != SSL_set_fd(t->socket, fd)) {
tls_errno = TLS_EOPENSSL;
goto failed;
}
return t;
failed:
sys_errno = GetLastError();
if (ep)
*ep = tls_errno;
if (t) {
if (t->socket)
SSL_free(t->socket);
ap->free(ap, memset(t, 0, sizeof *t));
}
SetLastError(sys_errno);
return 0;
} /* tls_open() */
void tls_close(struct tls *t) {
struct stack_note *np;
if (t == 0)
return /* void */;
if (tls_pending(t)) {
struct stack_note n = { .tp = &t };
SLIST_INSERT_HEAD(&t->stack, &n, sle);
tls_cancel(t, 0);
if (t == 0)
return /* void */;
assert(SLIST_FIRST(&t->stack) == &n);
SLIST_REMOVE_HEAD(&t->stack, sle);
}
if (t->socket)
SSL_free(t->socket), t->socket = 0;
SLIST_FOREACH(np, &t->stack, sle)
*np->tp = 0;
SLIST_INIT(&t->stack);
t->ap->free(t->ap, t);
return /* void */;
} /* tls_close() */
int tls_pending(struct tls *t) {
return t->event_pending;
} /* tls_pending() */
static void tls_event_del(struct tls *t) {
if (t->event_pending) {
(void)event_del(&t->event);
t->event_pending = 0;
}
return /* void */;
} /* tls_event_del() */
static enum tls_errno tls_event_add(struct tls *t, void (*cb)(int, short, void *)) {
short events = ((t->state & TLS_S_NEED_READ)? EV_READ : 0)
| ((t->state & TLS_S_NEED_WRITE)? EV_WRITE : 0);
struct timeval now, tvbuf, *timeout = 0;
assert((t->state & (TLS_S_NEED_READ | TLS_S_NEED_WRITE)));
if (t->deadline_pending) {
if (0 != gettimeofday(&now, 0)) {
t->sys_errno = GetLastError();
t->tls_errno = TLS_ESYSTEM;
goto failed;
}
if (timercmp(&t->event_deadline, &now, <=)) {
t->tls_errno = TLS_ETIMEDOUT;
goto failed;
}
timersub(&t->event_deadline, &now, &tvbuf);
timeout = &tvbuf;
}
if (t->event_pending == events && !timeout)
return 0;
tls_event_del(t);
event_set(&t->event, t->fd, events | EV_PERSIST, cb, t);
if (t->evbase)
event_base_set(t->evbase, &t->event);
if (0 != event_add(&t->event, timeout)) {
t->sys_errno = GetLastError();
t->tls_errno = TLS_ESYSTEM;
goto failed;
}
t->event_pending = events;
return 0;
failed:
tls_event_del(t);
SetLastError(t->sys_errno);
return t->tls_errno;
} /* tls_event_add() */
void tls_cancel(struct tls *t, int notify) {
struct stack_note n = { .tp = &t };
SLIST_INSERT_HEAD(&t->stack, &n, sle);
tls_event_del(t);
if (notify) {
if (t->read.cb)
t->read.cb(t, 0, 0, TLS_ECANCELLED, t->read.arg);
if (t == 0)
return /* void */;
if (t->write.cb)
t->write.cb(t, 0, 0, TLS_ECANCELLED, t->write.arg);
if (t == 0)
return /* void */;
if (t->accept.cb)
t->accept.cb(t, TLS_ECANCELLED, t->accept.arg);
if (t == 0)
return /* void */;
if (t->connect.cb)
t->connect.cb(t, TLS_ECANCELLED, t->connect.arg);
if (t == 0)
return /* void */;
}
assert(SLIST_FIRST(&t->stack) == &n);
SLIST_REMOVE_HEAD(&t->stack, sle);
return /* void */;
} /* tls_cancel() */
SSL *tls_peek(struct tls *t) {
return t->socket;
} /* tls_peek() */
static ssize_t tls_read_try(struct tls *t, void *buf, size_t bufsiz) {
ssize_t nbytes;
t->state &= ~(TLS_S_NEED_READ | TLS_S_NEED_WRITE);
ERR_clear_error();
nbytes = SSL_read(t->socket, buf, bufsiz);
if (nbytes >= 0)
return nbytes;
switch (SSL_get_error(t->socket, nbytes)) {
case SSL_ERROR_WANT_READ:
t->state |= TLS_S_NEED_READ;
SetLastError(EAGAIN);
break;
case SSL_ERROR_WANT_WRITE:
t->state |= TLS_S_NEED_WRITE;
SetLastError(EAGAIN);
break;
case SSL_ERROR_SSL:
SetLastError(EIO);
/* FALL THROUGH */
case SSL_ERROR_SYSCALL:
/* FALL THROUGH */
default:
if (!GetLastError() || GetLastError() == EAGAIN)
SetLastError(EIO);
t->state = TLS_S_DISCONNECTED;
}
t->tls_errno = TLS_ESYSTEM;
t->sys_errno = GetLastError();
return -1;
} /* tls_read_try() */
ssize_t tls_read(struct tls *t, void *buf, size_t bufsiz, tls_read_cb cb, void *arg, struct timeval *timeout) {
assert(cb == 0); /* NOT SUPPORTED */
return tls_read_try(t, buf, bufsiz);
} /* tls_read() */
static ssize_t tls_write_try(struct tls *t, const void *buf, size_t bufsiz) {
ssize_t nbytes;
t->state &= ~(TLS_S_NEED_READ | TLS_S_NEED_WRITE);
ERR_clear_error();
nbytes = SSL_write(t->socket, buf, bufsiz);
if (nbytes > 0)
return nbytes;
switch(SSL_get_error(t->socket, nbytes)) {
case SSL_ERROR_ZERO_RETURN:
t->state = TLS_S_DISCONNECTED;
SetLastError(EPIPE);
break;
case SSL_ERROR_WANT_READ:
t->state |= TLS_S_NEED_READ;
SetLastError(EAGAIN);
break;
case SSL_ERROR_WANT_WRITE:
t->state |= TLS_S_NEED_WRITE;
SetLastError(EAGAIN);
break;
case SSL_ERROR_SSL:
SetLastError(EIO);
/* FALL THROUGH */
case SSL_ERROR_SYSCALL:
/* FALL THROUGH */
default:
if (!GetLastError() || GetLastError() == EAGAIN)
SetLastError(EIO);
t->state = TLS_S_DISCONNECTED;
}
t->tls_errno = TLS_ESYSTEM;
t->sys_errno = GetLastError();
return -1;
} /* tls_write_try() */
ssize_t tls_write(struct tls *t, const void *buf, size_t bufsiz, tls_read_cb cb, void *arg, struct timeval *timeout) {
assert(cb == 0); /* NOT SUPPORTED */
return tls_write_try(t, buf, bufsiz);
} /* tls_write() */
static enum tls_errno tls_accept_try(struct tls *t) {
X509 *peer;
int retval;
const char *cipher, *version;
t->state &= ~(TLS_S_NEED_READ | TLS_S_NEED_WRITE);
ERR_clear_error();
retval = SSL_do_handshake(t->socket);
switch (retval) {
case 1:
t->state |= TLS_S_CONNECTED;
if ((peer = SSL_get_peer_certificate(t->socket)))
X509_free(peer), peer = 0;
if (X509_V_OK == SSL_get_verify_result(t->socket)
&& (peer = SSL_get_peer_certificate(t->socket))) {
X509_free(peer), peer = 0;
t->state |= TLS_S_VERIFIED;
}
if ((cipher = SSL_get_cipher(t->socket)))
(void)snprintf(t->info.cipher.name, sizeof t->info.cipher.name, "%s", cipher);
t->info.cipher.bits = MAX(0, SSL_get_cipher_bits(t->socket, 0));
if ((version = SSL_get_version(t->socket))) {
(void)snprintf(t->info.version.name, sizeof t->info.version.name, "%s", version);
if (0 == strcmp(version, "TLSv1"))
t->info.version.id = TLS_VERSION_1;
else if (0 == strcmp(version, "SSLv3"))
t->info.version.id = SSL_VERSION_3;
else if (0 == strcmp(version, "SSLv2"))
t->info.version.id = SSL_VERSION_2;
/* TODO: Enforce specified versions. */
}
return 0;
case 0:
return t->tls_errno = TLS_EOPENSSL;
}
switch (SSL_get_error(t->socket, retval)) {
case SSL_ERROR_WANT_READ:
t->state |= TLS_S_NEED_READ;
SetLastError(EAGAIN);
break;
case SSL_ERROR_WANT_WRITE:
t->state |= TLS_S_NEED_WRITE;
SetLastError(EAGAIN);
break;
case SSL_ERROR_SSL:
SetLastError(EIO);
/* FALL THROUGH */
case SSL_ERROR_SYSCALL:
/* FALL THROUGH */
default:
if (!GetLastError() || GetLastError() == EAGAIN)
SetLastError(EIO);
t->state = TLS_S_DISCONNECTED;
}
t->tls_errno = TLS_ESYSTEM;
t->sys_errno = GetLastError();
return t->tls_errno;
} /* tls_accept_try() */
static void tls_accept_catch(int fd, short events, void *arg) {
struct tls *t = arg;
tls_accept_cb cb;
if (0 == tls_accept_try(t)) {
t->tls_errno = TLS_ESUCCESS;
goto finish;
}
if (t->tls_errno == TLS_ESYSTEM && t->sys_errno == EAGAIN) {
assert(t->state & (TLS_S_NEED_READ | TLS_S_NEED_WRITE));
if (0 != tls_event_add(t, &tls_accept_catch))
goto finish;
} else
goto finish;
return /* void */;
finish:
tls_event_del(t);
cb = t->accept.cb;
arg = t->accept.arg;
t->accept.cb = 0;
t->accept.arg = 0;
cb(t, t->tls_errno, arg);
return /* void */;
} /* tls_accept_catch() */
enum tls_errno tls_accept(struct tls *t, tls_accept_cb cb, void *arg, struct timeval *timeout) {
struct timeval now;
assert(!(t->state & TLS_S_MODE_CONNECT));
#if 0
char *cipher;
int i = 0;
while ((cipher = SSL_get_cipher_list(t->socket, i++)))
fprintf(stderr, "cipher: %s\n", cipher);
#endif
if (!(t->state & TLS_S_MODE_ACCEPT)) {
SSL_set_accept_state(t->socket);
t->state |= TLS_S_MODE_ACCEPT;
}
if (cb) {
if (timeout) {
if (0 != gettimeofday(&now, 0)) {
t->tls_errno = TLS_ESYSTEM;
t->sys_errno = GetLastError();
return t->tls_errno;
}
timeradd(&now, timeout, &t->event_deadline);
t->deadline_pending = 1;
} else
t->deadline_pending = 0;
t->accept.cb = cb;
t->accept.arg = arg;
/* Fake callback from libevent. */
tls_accept_catch(t->fd, 0, t);
return 0;
} else
return tls_accept_try(t);
} /* tls_accept() */
enum tls_errno tls_connect(struct tls *t, tls_connect_cb cb, void *arg, struct timeval *timeout) {
struct timeval now;
assert(!(t->state & TLS_S_MODE_ACCEPT));
if (!(t->state & TLS_S_MODE_CONNECT)) {
SSL_set_connect_state(t->socket);
t->state |= TLS_S_MODE_CONNECT;
}
/*
* NOTE: We overload tls_accept(). The accept-mode code does all the
* heavy lifting; we just set the connect mode up-front.
*/
if (cb) {
if (timeout) {
if (0 != gettimeofday(&now, 0)) {
t->tls_errno = TLS_ESYSTEM;
t->sys_errno = GetLastError();
return t->tls_errno;
}
timeradd(&now, timeout, &t->event_deadline);
t->deadline_pending = 1;
} else
t->deadline_pending = 0;
t->accept.cb = cb;
t->accept.arg = arg;
tls_accept_catch(t->fd, 0, t);
return 0;
} else
return tls_accept_try(t);
} /* tls_connect() */
int tls_state(struct tls *t) {
return t->state;
} /* tls_state() */
struct tls_info *tls_info(struct tls *t) {
return &t->info;
} /* tls_info() */
enum tls_errno tls_errno(struct tls *t) {
return t->tls_errno;
} /* tls_errno() */
const char *tls_strerror(struct tls *t) {
assert(t->tls_errno >= 0 && t->tls_errno < tls_nerr);
switch (t->tls_errno) {
case TLS_ESYSTEM:
return strerror(t->sys_errno);
default:
return tls_errlist[t->tls_errno];
}
/* NOT REACHED */
} /* tls_strerror() */
#else /* USE_OPENSSL */
#include <stddef.h> /* size_t */
#include <sys/time.h> /* struct timeval */
#include "tls.h"
const char *tls_errlist[1] = { 0 };
const int tls_nerr = 0;
const struct tls_options tls_options_initializer;
const struct tls_options tls_defaults;
struct tls *tls_open(int fd, struct tls_identity *c, struct event_base *evbase, const struct arena_prototype *ap, enum tls_errno *ep) {
return 0;
} /* tls_open() */
void tls_close(struct tls *t) {
return /* void */;
} /* tls_close() */
enum tls_errno tls_accept(struct tls *t, tls_accept_cb cb, void *arg, struct timeval *timeout) {
return TLS_ESYSTEM;
} /* tls_accept() */
enum tls_errno tls_connect(struct tls *t, tls_connect_cb cb, void *arg, struct timeval *timeout) {
return TLS_ESYSTEM;
} /* tls_connect() */
enum tls_errno tls_reset(void) {
return TLS_ESYSTEM;
} /* tls_reset() */
enum tls_errno tls_init(void) {
return TLS_ESYSTEM;
} /* tls_init() */
struct tls_identity *tls_identity_open(const struct tls_options *opts, const struct arena_prototype *ap, enum tls_errno *t_errno) {
*t_errno = TLS_ESYSTEM;
return 0;
} /* tls_identity_open() */
void tls_identity_close(struct tls_identity *t) {
return /* void */;
} /* tls_identity_close() */
enum tls_errno tls_identity_certify(struct tls_identity *t, const char *a, const char *b) {
return TLS_ESYSTEM;
} /* tls_identity_certify() */
enum tls_errno tls_identity_trust(struct tls_identity *t, const char *a) {
return TLS_ESYSTEM;
} /* tls_identity_trust() */
void *tls_identity_peek(struct tls_identity *t) {
return 0;
} /* tls_identity_peek() */
int tls_pending(struct tls *t) {
return 0;
} /* tls_pending() */
void tls_cancel(struct tls *t, int a) {
return /* void */;
} /* tls_cancel() */
SSL *tls_peek(struct tls *t) {
return 0;
} /* tls_peek() */
#ifdef WIN32
typedef long ssize_t;
#endif
ssize_t tls_read(struct tls *t, void *a, size_t b, tls_read_cb c, void *d, struct timeval *e) {
return -1;
} /* tls_read() */
ssize_t tls_write(struct tls *t, const void *a, size_t b, tls_read_cb c, void *d, struct timeval *e) {
return -1;
} /* tls_write() */
int tls_state(struct tls *t) {
return 0;
} /* tls_state() */
struct tls_info *tls_info(struct tls *t) {
return 0;
} /* tls_info() */
enum tls_errno tls_errno(struct tls *t) {
return TLS_ESYSTEM;
} /* tls_errno() */
const char *tls_strerror(struct tls *t) {
return "Unknown";
} /* tls_strerror() */
#endif /* USE_OPENSSL */
syntax highlighted by Code2HTML, v. 0.9.1