diff --git a/Makefile b/Makefile index 2c7ba73..dc1893d 100644 --- a/Makefile +++ b/Makefile @@ -6,11 +6,11 @@ # Master version number VER_MAJOR := 0 -VER_MINOR := 3 +VER_MINOR := 4 VER_PATCH := 0 PROJECT := re -VERSION := 0.3.0 +VERSION := 0.4.0 MK := mk/re.mk diff --git a/include/re_tls.h b/include/re_tls.h index b2c6867..d53a6ca 100644 --- a/include/re_tls.h +++ b/include/re_tls.h @@ -7,11 +7,26 @@ struct tls; struct tls_conn; +struct tls_sock; +struct tcp_conn; +struct udp_sock; -int tls_alloc(struct tls **tlsp, const char *keyfile, const char *pwd); +/** Defines the TLS method */ +enum tls_method { + TLS_METHOD_SSLV23, + TLS_METHOD_DTLSV1, +}; + + +int tls_alloc(struct tls **tlsp, enum tls_method method, const char *keyfile, + const char *pwd); int tls_add_ca(struct tls *tls, const char *capath); int tls_verify_cert(struct tls_conn *tc, char *cn, size_t cn_size); int tls_start_tcp(struct tls_conn **ptc, struct tls *tls, - struct tcp_conn *tcp); + struct tcp_conn *tcp, int layer); +int tls_start_udp(struct tls_sock **tsp, struct tls *tls, + struct udp_sock *us, int layer, uint32_t bsize); +struct tls_conn *tls_udp_conn(const struct tls_sock *ts, + const struct sa *peer); diff --git a/rpm/re.spec b/rpm/re.spec index b84fcf3..c4b8d56 100644 --- a/rpm/re.spec +++ b/rpm/re.spec @@ -1,6 +1,6 @@ %define name re -%define ver 0.3.0 -%define rel 2 +%define ver 0.4.0 +%define rel 1 Summary: Generic library for real-time communications with async IO support Name: %name diff --git a/src/bfcp/sock.c b/src/bfcp/sock.c index fd307e8..ef7be6e 100644 --- a/src/bfcp/sock.c +++ b/src/bfcp/sock.c @@ -184,7 +184,7 @@ static void tcp_conn_handler(const struct sa *addr, void *arg) #ifdef USE_TLS if (bs->transp == BFCP_TRANSP_TLS) { - if (tls_start_tcp(&conn->sc, bs->tls, conn->tc)) + if (tls_start_tcp(&conn->sc, bs->tls, conn->tc, 0)) goto error; } #endif @@ -318,7 +318,7 @@ int bfcp_send(struct bfcp_sock *sock, const struct sa *dst, struct mbuf *mb) if (sock->transp == BFCP_TRANSP_TLS) { err = tls_start_tcp(&conn->sc, sock->tls, - conn->tc); + conn->tc, 0); if (err) goto out; } diff --git a/src/sip/transp.c b/src/sip/transp.c index e9b1dfa..a38bdf2 100644 --- a/src/sip/transp.c +++ b/src/sip/transp.c @@ -510,7 +510,7 @@ static void tcp_connect_handler(const struct sa *paddr, void *arg) #ifdef USE_TLS if (transp->tls) { - err = tls_start_tcp(&conn->sc, transp->tls, conn->tc); + err = tls_start_tcp(&conn->sc, transp->tls, conn->tc, 0); if (err) goto out; } @@ -570,7 +570,7 @@ static int conn_send(struct sip_connqent **qentp, struct sip *sip, bool secure, goto out; } - err = tls_start_tcp(&conn->sc, transp->tls, conn->tc); + err = tls_start_tcp(&conn->sc, transp->tls, conn->tc, 0); if (err) goto out; } diff --git a/src/tls/mod.mk b/src/tls/mod.mk index c0dbdbe..299b6f6 100644 --- a/src/tls/mod.mk +++ b/src/tls/mod.mk @@ -7,4 +7,14 @@ ifneq ($(USE_OPENSSL),) SRCS += tls/openssl/tls.c SRCS += tls/openssl/tls_tcp.c + +USE_OPENSSL_DTLS := $(shell [ -f $(SYSROOT)/include/openssl/dtls1.h ] || \ + [ -f $(SYSROOT)/local/include/openssl/dtls1.h ] || \ + [ -f $(SYSROOT_ALT)/include/openssl/dtls1.h ] && echo "yes") + +ifneq ($(USE_OPENSSL_DTLS),) +CFLAGS += -DUSE_OPENSSL_DTLS=1 +SRCS += tls/openssl/tls_udp.c +endif + endif diff --git a/src/tls/openssl/tls.c b/src/tls/openssl/tls.c index a40dbfc..19696d1 100644 --- a/src/tls/openssl/tls.c +++ b/src/tls/openssl/tls.c @@ -49,7 +49,10 @@ static void destructor(void *data) { struct tls *tls = data; - SSL_CTX_free(tls->ctx); + if (tls->ctx) + SSL_CTX_free(tls->ctx); + + mem_deref(tls->pass); if (--tlsg.tlsc == 0) { DEBUG_INFO("error strings freed\n"); @@ -76,10 +79,11 @@ static int password_cb(char *buf, int size, int rwflag, void *userdata) } -int tls_alloc(struct tls **tlsp, const char *keyfile, const char *pwd) +int tls_alloc(struct tls **tlsp, enum tls_method method, const char *keyfile, + const char *pwd) { - int r, err = ENOMEM; struct tls *tls; + int r, err; if (!tlsp) return EINVAL; @@ -103,20 +107,44 @@ int tls_alloc(struct tls **tlsp, const char *keyfile, const char *pwd) SSL_load_error_strings(); } - tls->ctx = SSL_CTX_new(SSLv23_method()); - if (!tls->ctx) + switch (method) { + + case TLS_METHOD_SSLV23: + tls->ctx = SSL_CTX_new(SSLv23_method()); + break; + +#ifdef USE_OPENSSL_DTLS + case TLS_METHOD_DTLSV1: + tls->ctx = SSL_CTX_new(DTLSv1_method()); + break; +#endif + + default: + DEBUG_WARNING("tls method %d not supported\n", method); + err = ENOSYS; goto out; + } + + if (!tls->ctx) { + err = ENOMEM; + goto out; + } #if (OPENSSL_VERSION_NUMBER < 0x00905100L) SSL_CTX_set_verify_depth(tls->ctx, 1); #endif + if (method == TLS_METHOD_DTLSV1) { + SSL_CTX_set_read_ahead(tls->ctx, 1); + } + /* Load our keys and certificates */ if (keyfile) { - err = EINVAL; - if (pwd) { - tls->pass = pwd; + err = str_dup(&tls->pass, pwd); + if (err) + goto out; + SSL_CTX_set_default_passwd_cb(tls->ctx, password_cb); SSL_CTX_set_default_passwd_cb_userdata(tls->ctx, tls); } @@ -125,6 +153,7 @@ int tls_alloc(struct tls **tlsp, const char *keyfile, const char *pwd) if (r <= 0) { DEBUG_WARNING("Can't read certificate file: %s (%d)\n", keyfile, r); + err = EINVAL; goto out; } @@ -133,6 +162,7 @@ int tls_alloc(struct tls **tlsp, const char *keyfile, const char *pwd) if (r <= 0) { DEBUG_WARNING("Can't read key file: %s (%d)\n", keyfile, r); + err = EINVAL; goto out; } } diff --git a/src/tls/openssl/tls.h b/src/tls/openssl/tls.h index f1e8b8d..4ad44fc 100644 --- a/src/tls/openssl/tls.h +++ b/src/tls/openssl/tls.h @@ -7,5 +7,5 @@ struct tls { SSL_CTX *ctx; - const char *pass; /* password for private key */ + char *pass; /* password for private key */ }; diff --git a/src/tls/openssl/tls_tcp.c b/src/tls/openssl/tls_tcp.c index 158cf1e..f9ceeb7 100644 --- a/src/tls/openssl/tls_tcp.c +++ b/src/tls/openssl/tls_tcp.c @@ -276,10 +276,10 @@ static bool send_handler(int *err, struct mbuf *mb, void *arg) } -int tls_start_tcp(struct tls_conn **ptc, struct tls *tls, struct tcp_conn *tcp) +int tls_start_tcp(struct tls_conn **ptc, struct tls *tls, struct tcp_conn *tcp, + int layer) { struct tls_conn *tc; - const int layer = 0; int err; if (!ptc || !tls || !tcp) diff --git a/src/tls/openssl/tls_udp.c b/src/tls/openssl/tls_udp.c new file mode 100644 index 0000000..b9ed517 --- /dev/null +++ b/src/tls/openssl/tls_udp.c @@ -0,0 +1,377 @@ +/** + * @file openssl/tls_udp.c DTLS/UDP backend using OpenSSL + * + * Copyright (C) 2010 Creytiv.com + */ +#define OPENSSL_NO_KRB5 1 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "tls.h" + + +#define DEBUG_MODULE "tls_udp" +#define DEBUG_LEVEL 5 +#include + + +struct tls_sock { + struct udp_helper *uh; + struct udp_sock *us; + struct hash *ht_conn; + struct tls *tls; +}; + +struct tls_conn { + SSL *ssl; /* inheritance */ + BIO *sbio_out; + BIO *sbio_in; + struct le he; + struct sa peer; + struct tmr tmr; + struct tls_sock *ts; +}; + + +static void check_timer(struct tls_conn *conn); + + +static int bio_create(BIO *b) +{ + b->init = 1; + b->num = 0; + b->ptr = NULL; + b->flags = 0; + + return 1; +} + + +static int bio_destroy(BIO *b) +{ + if (!b) + return 0; + + b->ptr = NULL; + b->init = 0; + b->flags = 0; + + return 1; +} + + +static int bio_write(BIO *b, const char *buf, int len) +{ + struct tls_conn *tc = b->ptr; + struct mbuf mb; + int err; + + mb.buf = (void *)buf; + mb.pos = 0; + mb.end = mb.size = len; + + err = udp_send_helper(tc->ts->us, &tc->peer, &mb, tc->ts->uh); + if (err) + return -1; + + return len; +} + + +static long bio_ctrl(BIO *b, int cmd, long num, void *ptr) +{ + (void)b; + (void)num; + (void)ptr; + + if (cmd == BIO_CTRL_FLUSH) { + /* The OpenSSL library needs this */ + return 1; + } + + return 0; +} + + +static struct bio_method_st bio_udp_send = { + BIO_TYPE_SOURCE_SINK, + "udp_send", + bio_write, + 0, + 0, + 0, + bio_ctrl, + bio_create, + bio_destroy, + 0 +}; + + +static void timeout(void *arg) +{ + struct tls_conn *tc = arg; + + DTLSv1_handle_timeout(tc->ssl); + check_timer(tc); +} + + +static void check_timer(struct tls_conn *tc) +{ + struct timeval tv = {0, 0}; + + if (DTLSv1_get_timeout(tc->ssl, &tv)) { + tmr_start(&tc->tmr, tv.tv_sec * 1000 + tv.tv_usec / 1000, + timeout, tc); + } +} + + +static void destructor(void *arg) +{ + struct tls_sock *ts = arg; + + hash_flush(ts->ht_conn); + mem_deref(ts->ht_conn); + mem_deref(ts->uh); + mem_deref(ts->us); + mem_deref(ts->tls); +} + + +static void conn_destructor(void *arg) +{ + struct tls_conn *tc = arg; + + hash_unlink(&tc->he); + tmr_cancel(&tc->tmr); + + if (tc->ssl) { + (void)SSL_shutdown(tc->ssl); + SSL_free(tc->ssl); + } +} + + +static bool hash_cmp_handler(struct le *le, void *arg) +{ + const struct tls_conn *tc = le->data; + + return sa_cmp(&tc->peer, arg, SA_ALL); +} + + +static struct tls_conn *conn_alloc(struct tls_sock *ts, const struct sa *peer) +{ + struct tls_conn *tc; + + tc = mem_zalloc(sizeof(*tc), conn_destructor); + if (!tc) + return NULL; + + tc->ssl = SSL_new(ts->tls->ctx); + if (!tc->ssl) + goto error; + + tc->sbio_in = BIO_new(BIO_s_mem()); + if (!tc->sbio_in) + goto error; + + tc->sbio_out = BIO_new(&bio_udp_send); + if (!tc->sbio_out) { + BIO_free(tc->sbio_in); + goto error; + } + tc->sbio_out->ptr = tc; + + SSL_set_bio(tc->ssl, tc->sbio_in, tc->sbio_out); + + tmr_init(&tc->tmr); + + tc->peer = *peer; + tc->ts = ts; + + hash_append(ts->ht_conn, sa_hash(peer, SA_ALL), &tc->he, tc); + + return tc; + + error: + return mem_deref(tc); +} + + +static bool send_handler(int *err, struct sa *dst, struct mbuf *mb, void *arg) +{ + struct tls_sock *ts = arg; + struct tls_conn *tc; + int r; + + tc = tls_udp_conn(ts, dst); + if (!tc) { + + /* No connection found, assuming Client role */ + + tc = conn_alloc(ts, dst); + if (!tc) { + *err = ENOMEM; + return true; + } + + SSL_set_connect_state(tc->ssl); + + check_timer(tc); + } + + r = SSL_write(tc->ssl, mbuf_buf(mb), (int)mbuf_get_left(mb)); + if (r < 0) { + + switch (SSL_get_error(tc->ssl, r)) { + + case SSL_ERROR_WANT_READ: + break; + + default: + DEBUG_WARNING("SSL_write: %d\n", + SSL_get_error(tc->ssl, r)); + *err = EPROTO; + return true; + } + } + + return true; +} + + +static bool recv_handler(struct sa *src, struct mbuf *mb, void *arg) +{ + struct tls_sock *ts = arg; + struct tls_conn *tc; + int r; + + tc = tls_udp_conn(ts, src); + if (!tc) { + + /* No connection found, assuming Server role */ + + tc = conn_alloc(ts, src); + if (!tc) + return true; + + SSL_set_verify(tc->ssl, 0, 0); + SSL_set_accept_state(tc->ssl); + } + + /* feed SSL data to the BIO */ + r = BIO_write(tc->sbio_in, mbuf_buf(mb), (int)mbuf_get_left(mb)); + if (r <= 0) + return true; + + check_timer(tc); + + mbuf_set_pos(mb, 0); + + for (;;) { + int n; + + if (mbuf_get_space(mb) < 4096) { + if (mbuf_resize(mb, mb->size + 8192)) + return true; + } + + n = SSL_read(tc->ssl, mbuf_buf(mb), (int)mbuf_get_space(mb)); + if (n < 0) { + const int ssl_err = SSL_get_error(tc->ssl, n); + + switch (ssl_err) { + + case SSL_ERROR_WANT_READ: + break; + + default: + return true; + } + + break; + } + else if (n == 0) + break; + + mb->pos += n; + } + + if (!mb->pos) + return true; + + mbuf_set_end(mb, mb->pos); + mbuf_set_pos(mb, 0); + + return false; +} + + +/** + * Start TLS on a UDP socket (aka DTLS). The UDP socket can act as a + * client or a server, and multiple DTLS connections can be established to + * multiple peers, all from the same UDP socket. + * + * @param tsp Pointer to allocated TLS socket + * @param tls TLS context + * @param us UDP socket + * @param layer Protocol stack layer + * @param bsize Bucket size for hash table (0 for default) + * + * @return 0 if success, otherwise errorcode + */ +int tls_start_udp(struct tls_sock **tsp, struct tls *tls, struct udp_sock *us, + int layer, uint32_t bsize) +{ + struct tls_sock *ts; + int err; + + if (!tsp || !tls || !us) + return EINVAL; + + ts = mem_zalloc(sizeof(*ts), destructor); + if (!ts) + return ENOMEM; + + err = hash_alloc(&ts->ht_conn, bsize ? bsize : 4); + if (err) + goto out; + + err = udp_register_helper(&ts->uh, us, layer, send_handler, + recv_handler, ts); + if (err) + goto out; + + ts->us = mem_ref(us); + ts->tls = mem_ref(tls); + + out: + if (err) + mem_deref(ts); + else + *tsp = ts; + + return err; +} + + +struct tls_conn *tls_udp_conn(const struct tls_sock *ts, const struct sa *peer) +{ + if (!ts) + return NULL; + + return list_ledata(hash_lookup(ts->ht_conn, sa_hash(peer, SA_ALL), + hash_cmp_handler, (void *)peer)); +}