/* ========================================================================== * 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 /* snprintf(3) */ #include /* EINVAL EAGAIN EPIPE */ #include /* memset(3), strrchr(3) strerror(3) strcmp(3) */ #include /* assert(3) */ #include /* GetLastError SetLastError */ #include /* MAX */ #include /* SLIST */ #include /* struct timeval timersub gettimeofday(2) */ #include #include #include #include #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 /* size_t */ #include /* 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 */