From 02178a8b5843a2c5a59fb7b104e4f9f5df1ff5ee Mon Sep 17 00:00:00 2001 From: Garrett D'Amore Date: Thu, 9 Nov 2017 14:09:14 -0800 Subject: fixes #3 TLS transport This introduces a new transport (compatible with the TLS transport from mangos), using TLS v1.2. To use the new transport, you must have the mbed TLS library available on your system (Xenial libmbedtls-dev). You can use version 2.x or newer -- 1.3.x and PolarSSL versions are not supported. You enable the TLS transport with -DNNG_TRANSPORT_TLS=ON in the CMake configuration. You must configure the server certificate by default, and this can only be done using nng options. See the nng_tls man page for details. This work is experimental, and was made possible by Capitar IT Group BV, and Staysail Systems, Inc. --- src/CMakeLists.txt | 13 +- src/core/transport.c | 4 + src/supplemental/README.adoc | 5 + src/supplemental/mbedtls/CMakeLists.txt | 54 ++ src/supplemental/mbedtls/tls.c | 1022 +++++++++++++++++++++++++++++ src/supplemental/tls.h | 84 +++ src/transport/inproc/inproc.h | 2 +- src/transport/ipc/ipc.h | 2 +- src/transport/tcp/tcp.h | 2 +- src/transport/tls/CMakeLists.txt | 19 + src/transport/tls/tls.c | 1081 +++++++++++++++++++++++++++++++ src/transport/tls/tls.h | 62 ++ src/transport/zerotier/zerotier.h | 14 +- 13 files changed, 2344 insertions(+), 20 deletions(-) create mode 100644 src/supplemental/README.adoc create mode 100644 src/supplemental/mbedtls/CMakeLists.txt create mode 100644 src/supplemental/mbedtls/tls.c create mode 100644 src/supplemental/tls.h create mode 100644 src/transport/tls/CMakeLists.txt create mode 100644 src/transport/tls/tls.c create mode 100644 src/transport/tls/tls.h (limited to 'src') diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 4901789b..291769f0 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -74,7 +74,7 @@ set (NNG_SOURCES core/timer.h core/transport.c core/transport.h -) + ) if (NNG_PLATFORM_POSIX) set (NNG_SOURCES ${NNG_SOURCES} @@ -119,9 +119,7 @@ if (NNG_PLATFORM_WINDOWS) ) endif() -install(FILES transport/inproc/inproc.h - DESTINATION include/nng/transport/inproc) - +add_subdirectory(supplemental/mbedtls) add_subdirectory(protocol/bus0) add_subdirectory(protocol/pair0) @@ -134,6 +132,7 @@ add_subdirectory(protocol/survey0) add_subdirectory(transport/inproc) add_subdirectory(transport/ipc) add_subdirectory(transport/tcp) +add_subdirectory(transport/tls) add_subdirectory(transport/zerotier) include_directories(AFTER SYSTEM ${PROJECT_SOURCE_DIR}/src @@ -183,11 +182,6 @@ if (CMAKE_THREAD_LIBS_INIT) endif() # pkg-config file -if (NNG_REQUIRED_LIBRARIES) - foreach (lib ${NNG_REQUIRED_LIBRARIES}) - set (NNG_REQUIRED_LFLAGS "${NNG_REQUIRED_LFLAGS} -l${lib}") - endforeach() -endif() #configure_file (pkgconfig.in ${PROJECT_NAME}.pc @ONLY) #install ( # FILES ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}.pc @@ -200,4 +194,3 @@ install (TARGETS ${PROJECT_NAME} ${PROJECT_NAME}_static # Promote settings to parent set(NNG_REQUIRED_LIBRARIES ${NNG_REQUIRED_LIBRARIES} PARENT_SCOPE) -set(NNG_REQUIRED_LFLAGS ${NNG_REQUIRED_LFLAGS} PARENT_SCOPE) \ No newline at end of file diff --git a/src/core/transport.c b/src/core/transport.c index af9c93fb..359b03fd 100644 --- a/src/core/transport.c +++ b/src/core/transport.c @@ -12,6 +12,7 @@ #include "transport/inproc/inproc.h" #include "transport/ipc/ipc.h" #include "transport/tcp/tcp.h" +#include "transport/tls/tls.h" #include "transport/zerotier/zerotier.h" #include @@ -150,6 +151,9 @@ static nni_tran_ctor nni_tran_ctors[] = { #ifdef NNG_HAVE_TCP nng_tcp_register, #endif +#ifdef NNG_HAVE_TLS + nng_tls_register, +#endif #ifdef NNI_HAVE_ZEROTIER nng_zt_register, #endif diff --git a/src/supplemental/README.adoc b/src/supplemental/README.adoc new file mode 100644 index 00000000..05483ae7 --- /dev/null +++ b/src/supplemental/README.adoc @@ -0,0 +1,5 @@ += supplemental sources + +This is for code that is not intrinsic to nng, but which may be used +by multiple other subsystems. For example, common code for TLS support +used by multiple transports is located here. diff --git a/src/supplemental/mbedtls/CMakeLists.txt b/src/supplemental/mbedtls/CMakeLists.txt new file mode 100644 index 00000000..5c2de10b --- /dev/null +++ b/src/supplemental/mbedtls/CMakeLists.txt @@ -0,0 +1,54 @@ +# +# Copyright 2017 Garrett D'Amore +# Copyright 2017 Capitar IT Group BV +# +# This software is supplied under the terms of the MIT License, a +# copy of which should be located in the distribution where this +# file was obtained (LICENSE.txt). A copy of the license may also be +# found online at https://opensource.org/licenses/MIT. +# + +# MBEDTLS library + +# This requires the mbedTLS library be installed somewhere. You can +# point this at a suitable installation of mbedTLS by setting +# MBEDTLS_ROOT_DIR to point at the root of the installation (prefix). + +# It is possible to minimize the mbedTLS library quite a bit. We do +# not require legacy algorithms, the net_sockets layer, the filesystem +# I/O, as well as various other tidbits. We provide an entropy source, +# so you can disable that in mbedTLS too. You may disable fallback support, +# as we only support TLS v1.2 at present. (You may also therefore remove +# code to support older versions of TLS/SSL.) You may also remove DTLS, +# since we're not using it now (nor are we likely to in the near feature). +# Also you may remove support for ZLIB compression, we don't use it either +# (and it would be insecure to do so.) PEM and X509 writing (encoding) +# is not needed (but parse support is!) You may also remove session support, +# as we don't use that either. +# +# (Look for a sample config.h in this directory, if you want to build +# a minimized version just for nng.) + +# What we do require is support for TLSv1.2 + +if (NNG_MBEDTLS_ENABLE) + set(SUPP_SOURCES supplemental/mbedtls/tls.c supplemental/tls.h) + Find_Package(mbedTLS REQUIRED) + + # If it isn't already in the link list, add the TLS libraries there. + # or something, so we take care not to duplicate it). + list(FIND NNG_REQUIRED_LIBRARIES ${MBEDTLS_TLS_LIBRARY} _index) + if (_index EQUAL -1) + set(NNG_REQUIRED_LIBRARIES ${NNG_REQUIRED_LIBRARIES} ${MBEDTLS_LIBRARIES}) + set(NNG_REQUIRED_LIBRARIES ${NNG_REQUIRED_LIBRARIES} PARENT_SCOPE) + endif() + + # Likewise for the include search path. + list(FIND NNG_REQUIRED_INCLUDES ${MBEDTLS_INCLUDE_DIR} _index) + if (_index EQUAL -1) + set(NNG_REQUIRED_INCLUDES ${NNG_REQUIRED_INCLUDES} ${MBEDTLS_INCLUDE_DIR}) + set(NNG_REQUIRED_INCLUDES ${NNG_REQUIRED_INCLUDES} PARENT_SCOPE) + endif() +endif() + +set(NNG_SOURCES ${NNG_SOURCES} ${SUPP_SOURCES} PARENT_SCOPE) diff --git a/src/supplemental/mbedtls/tls.c b/src/supplemental/mbedtls/tls.c new file mode 100644 index 00000000..80660469 --- /dev/null +++ b/src/supplemental/mbedtls/tls.c @@ -0,0 +1,1022 @@ +// +// Copyright 2017 Staysail Systems, Inc. +// Copyright 2017 Capitar IT Group BV +// +// This software is supplied under the terms of the MIT License, a +// copy of which should be located in the distribution where this +// file was obtained (LICENSE.txt). A copy of the license may also be +// found online at https://opensource.org/licenses/MIT. +// + +#ifdef NNG_MBEDTLS_ENABLE +#include +#include +#include +#include + +#include "mbedtls/error.h" +#include "mbedtls/net.h" +#include "mbedtls/ssl.h" + +#include "core/nng_impl.h" + +#include "supplemental/tls.h" + +// Implementation note. This implementation buffers data between the TLS +// encryption layer (mbedTLS) and the underlying TCP socket. As a result, +// there may be some additional latency caused by buffer draining and +// refilling. In the future we might want to investigate some kind of +// double buffer policy to allow data to flow without entering a true +// empty state. + +// NNG_TLS_MAX_SEND_SIZE limits the amount of data we will buffer for sending, +// exerting backpressure if this size is exceeded. The 16K is aligned to the +// maximum TLS record size. +#ifndef NNG_TLS_MAX_SEND_SIZE +#define NNG_TLS_MAX_SEND_SIZE 16384 +#endif + +// NNG_TLS_MAX_RECV_SIZE limits the amount of data we will receive in a single +// operation. As we have to buffer data, this drives the size of our +// intermediary buffer. The 16K is aligned to the maximum TLS record size. +#ifndef NNG_TLX_MAX_RECV_SIZE +#define NNG_TLS_MAX_RECV_SIZE 16384 +#endif + +typedef struct nni_tls_certkey { + char * pass; + uint8_t * crt; + uint8_t * key; + size_t crtlen; + size_t keylen; + mbedtls_x509_crt mcrt; + mbedtls_pk_context mpk; + nni_list_node node; +} nni_tls_certkey; + +struct nni_tls { + nni_plat_tcp_pipe * tcp; + mbedtls_ssl_context ctx; + nni_mtx lk; + nni_aio * tcp_send; + nni_aio * tcp_recv; + bool sending; + bool recving; + bool closed; + bool hsdone; + bool tls_closed; // upper TLS layer closed + bool tcp_closed; // underlying TCP buffer closed + uint8_t * sendbuf; // send buffer + int sendlen; // amount of data in send buffer + int sendoff; // offset of start of send data + uint8_t * recvbuf; // recv buffer + int recvlen; // amount of data in recv buffer + int recvoff; // offset of start of recv data + nni_list sends; // upper side sends + nni_list recvs; // upper recv aios + nni_aio * handshake; // handshake aio (upper) +}; + +struct nni_tls_config { + mbedtls_ssl_config cfg_ctx; + nni_mtx lk; + bool active; + char * server_name; +#ifdef NNG_TLS_USE_CTR_DRBG + mbedtls_ctr_drbg_context rng_ctx; + nni_mtx rng_lk; +#endif + mbedtls_x509_crt ca_certs; + mbedtls_x509_crl crl; + bool have_ca_certs; + bool have_crl; + + nni_list certkeys; +}; + +static void nni_tls_send_cb(void *); +static void nni_tls_recv_cb(void *); + +static void nni_tls_do_send(nni_tls *); +static void nni_tls_do_recv(nni_tls *); +static void nni_tls_do_handshake(nni_tls *); + +static int nni_tls_net_send(void *, const unsigned char *, size_t); +static int nni_tls_net_recv(void *, unsigned char *, size_t); + +static void +nni_tls_dbg(void *ctx, int level, const char *file, int line, const char *s) +{ + char buf[128]; + snprintf(buf, sizeof(buf), "%s:%04d: %s", file, line, s); + nni_plat_println(buf); +} + +static int +nni_tls_get_entropy(void *arg, unsigned char *buf, size_t len) +{ + NNI_ARG_UNUSED(arg); + while (len) { + uint32_t x = nni_random(); + size_t n; + + n = len < sizeof(x) ? len : sizeof(x); + memcpy(buf, &x, n); + len -= n; + buf += n; + } + return (0); +} + +static int +nni_tls_random(void *arg, unsigned char *buf, size_t sz) +{ +#ifdef NNG_TLS_USE_CTR_DRBG + int rv; + nni_tls_config *cfg = arg; + NNI_ARG_UNUSED(arg); + + nni_mtx_lock(&cfg->rng_lk); + rv = mbedtls_ctr_drbg_random(&cfg->rng_ctx, buf, sz); + nni_mtx_unlock(&cfg->rng_lk); + return (rv); +#else + return (nni_tls_get_entropy(arg, buf, sz)); +#endif +} + +void +nni_tls_config_fini(nni_tls_config *cfg) +{ + nni_tls_certkey *ck; + + mbedtls_ssl_config_free(&cfg->cfg_ctx); +#ifdef NNG_TLS_USE_CTR_DRBG + mbedtls_ctr_drbg_free(&cfg->rng_ctx); +#endif + mbedtls_x509_crt_free(&cfg->ca_certs); + mbedtls_x509_crl_free(&cfg->crl); + if (cfg->server_name) { + nni_strfree(cfg->server_name); + } + while ((ck = nni_list_first(&cfg->certkeys))) { + nni_list_remove(&cfg->certkeys, ck); + if (ck->pass) { + nni_strfree(ck->pass); + } + if (ck->crt) { + nni_free(ck->crt, ck->crtlen); + } + if (ck->key) { + nni_free(ck->key, ck->keylen); + } + mbedtls_x509_crt_free(&ck->mcrt); + mbedtls_pk_free(&ck->mpk); + + NNI_FREE_STRUCT(ck); + } + nni_mtx_fini(&cfg->lk); + NNI_FREE_STRUCT(cfg); +} + +int +nni_tls_config_init(nni_tls_config **cpp, int mode) +{ + nni_tls_config *cfg; + int rv; + int sslmode; + int authmode; + + if ((cfg = NNI_ALLOC_STRUCT(cfg)) == NULL) { + return (NNG_ENOMEM); + } + nni_mtx_init(&cfg->lk); + if (mode == NNI_TLS_CONFIG_SERVER) { + sslmode = MBEDTLS_SSL_IS_SERVER; + authmode = MBEDTLS_SSL_VERIFY_NONE; + } else { + sslmode = MBEDTLS_SSL_IS_CLIENT; + authmode = MBEDTLS_SSL_VERIFY_REQUIRED; + } + + NNI_LIST_INIT(&cfg->certkeys, nni_tls_certkey, node); + mbedtls_ssl_config_init(&cfg->cfg_ctx); + mbedtls_x509_crt_init(&cfg->ca_certs); + mbedtls_x509_crl_init(&cfg->crl); + + rv = mbedtls_ssl_config_defaults(&cfg->cfg_ctx, sslmode, + MBEDTLS_SSL_TRANSPORT_STREAM, MBEDTLS_SSL_PRESET_DEFAULT); + if (rv != 0) { + nni_tls_config_fini(cfg); + return (rv); + } + + mbedtls_ssl_conf_authmode(&cfg->cfg_ctx, authmode); + + // We *require* TLS v1.2 or newer, which is also known as SSL v3.3. + mbedtls_ssl_conf_min_version(&cfg->cfg_ctx, + MBEDTLS_SSL_MAJOR_VERSION_3, MBEDTLS_SSL_MINOR_VERSION_3); + +#ifdef NNG_TLS_USE_CTR_DRBG + mbedtls_ctr_drbg_init(&cfg->rng_ctx); + rv = mbedtls_ctr_drbg_seed( + &cfg->rng_ctx, nni_tls_get_entropy, NULL, NULL, 0); + if (rv != 0) { + nni_tls_config_fini(cfg); + return (rv); + } +#endif + mbedtls_ssl_conf_rng(&cfg->cfg_ctx, nni_tls_random, cfg); + + mbedtls_ssl_conf_dbg(&cfg->cfg_ctx, nni_tls_dbg, cfg); + + *cpp = cfg; + return (0); +} + +void +nni_tls_fini(nni_tls *tp) +{ + // Shut it all down first. + if (tp->tcp) { + nni_plat_tcp_pipe_close(tp->tcp); + } + nni_aio_stop(tp->tcp_send); + nni_aio_stop(tp->tcp_recv); + + // And finalize / free everything. + if (tp->tcp) { + nni_plat_tcp_pipe_fini(tp->tcp); + } + nni_aio_fini(tp->tcp_send); + nni_aio_fini(tp->tcp_recv); + mbedtls_ssl_free(&tp->ctx); + nni_mtx_fini(&tp->lk); + nni_free(tp->recvbuf, NNG_TLS_MAX_RECV_SIZE); + nni_free(tp->sendbuf, NNG_TLS_MAX_RECV_SIZE); + NNI_FREE_STRUCT(tp); +} + +void +nni_tls_strerror(int errnum, char *buf, size_t sz) +{ + if (errnum & NNG_ETRANERR) { + errnum &= ~NNG_ETRANERR; + errnum = -errnum; + + mbedtls_strerror(errnum, buf, sz); + } else { + (void) snprintf(buf, sz, "%s", nng_strerror(errnum)); + } +} + +// nni_tls_mkerr converts an mbed error to an NNG error. In all cases +// we just encode with NNG_ETRANERR. +static int +nni_tls_mkerr(int err) +{ + err = -err; + err |= NNG_ETRANERR; + return (err); +} + +int +nni_tls_init(nni_tls **tpp, nni_tls_config *cfg, nni_plat_tcp_pipe *tcp) +{ + nni_tls *tp; + int rv; + + if ((tp = NNI_ALLOC_STRUCT(tp)) == NULL) { + return (NNG_ENOMEM); + } + if ((tp->recvbuf = nni_alloc(NNG_TLS_MAX_RECV_SIZE)) == NULL) { + NNI_FREE_STRUCT(tp); + return (NNG_ENOMEM); + } + if ((tp->sendbuf = nni_alloc(NNG_TLS_MAX_SEND_SIZE)) == NULL) { + nni_free(tp->sendbuf, NNG_TLS_MAX_RECV_SIZE); + NNI_FREE_STRUCT(tp); + return (NNG_ENOMEM); + } + + nni_mtx_lock(&cfg->lk); + // No more changes allowed to config. + if (cfg->active == false) { + nni_tls_certkey *ck; + + rv = 0; + + if (cfg->have_ca_certs || cfg->have_crl) { + mbedtls_ssl_conf_ca_chain( + &cfg->cfg_ctx, &cfg->ca_certs, &cfg->crl); + } + NNI_LIST_FOREACH (&cfg->certkeys, ck) { + if (rv != 0) { + break; + } + if (rv == 0) { + rv = mbedtls_x509_crt_parse( + &ck->mcrt, ck->crt, ck->crtlen); + } + if (rv == 0) { + rv = mbedtls_pk_parse_key(&ck->mpk, ck->key, + ck->keylen, (uint8_t *) ck->pass, + ck->pass != NULL ? strlen(ck->pass) : 0); + } + if (rv == 0) { + rv = mbedtls_ssl_conf_own_cert( + &cfg->cfg_ctx, &ck->mcrt, &ck->mpk); + } + + if (rv != 0) { + break; + } + } + if (rv != 0) { + nni_mtx_unlock(&cfg->lk); + nni_tls_fini(tp); + return (nni_tls_mkerr(rv)); + } + cfg->active = true; + } + nni_mtx_unlock(&cfg->lk); + + nni_aio_list_init(&tp->sends); + nni_aio_list_init(&tp->recvs); + nni_mtx_init(&tp->lk); + mbedtls_ssl_init(&tp->ctx); + mbedtls_ssl_set_bio( + &tp->ctx, tp, nni_tls_net_send, nni_tls_net_recv, NULL); + + if ((rv = mbedtls_ssl_setup(&tp->ctx, &cfg->cfg_ctx)) != 0) { + rv = nni_tls_mkerr(rv); + nni_tls_fini(tp); + return (rv); + } + + if (cfg->server_name) { + mbedtls_ssl_set_hostname(&tp->ctx, cfg->server_name); + } + + tp->tcp = tcp; + + if (((rv = nni_aio_init(&tp->tcp_send, nni_tls_send_cb, tp)) != 0) || + ((rv = nni_aio_init(&tp->tcp_recv, nni_tls_recv_cb, tp)) != 0)) { + nni_tls_fini(tp); + return (rv); + } + + nni_mtx_lock(&tp->lk); + // Kick off a handshake operation. + nni_tls_do_handshake(tp); + nni_mtx_unlock(&tp->lk); + + *tpp = tp; + return (0); +} + +static void +nni_tls_cancel(nni_aio *aio, int rv) +{ + nni_tls *tp = aio->a_prov_data; + nni_mtx_lock(&tp->lk); + if (nni_aio_list_active(aio)) { + nni_aio_list_remove(aio); + nni_aio_finish_error(aio, rv); + } + nni_mtx_unlock(&tp->lk); +} + +// nni_tls_send_cb is called when the underlying TCP send completes. +static void +nni_tls_send_cb(void *ctx) +{ + nni_tls *tp = ctx; + nni_aio *aio = tp->tcp_send; + + nni_mtx_lock(&tp->lk); + if (nni_aio_result(aio) != 0) { + nni_plat_tcp_pipe_close(tp->tcp); + tp->tcp_closed = true; + } else { + size_t n = nni_aio_count(aio); + NNI_ASSERT(tp->sendlen <= n); + tp->sendlen -= n; + if (tp->sendlen) { + tp->sendoff += n; + + aio->a_niov = 1; + aio->a_iov[0].iov_buf = tp->sendbuf + tp->sendoff; + aio->a_iov[0].iov_len = tp->sendlen; + nni_aio_set_timeout(aio, -1); // No timeout. + nni_plat_tcp_pipe_send(tp->tcp, aio); + nni_mtx_unlock(&tp->lk); + return; + } + tp->sendoff = 0; + tp->sending = false; + } + if (!tp->hsdone) { + nni_tls_do_handshake(tp); + } + if (tp->hsdone) { + nni_tls_do_send(tp); + nni_tls_do_recv(tp); + } + nni_mtx_unlock(&tp->lk); +} + +static void +nni_tls_recv_start(nni_tls *tp) +{ + nni_aio *aio; + + if (tp->recving || tp->tcp_closed) { + return; + } + // If we already have data, wait for that to be consumed before + // doing another read. + if (tp->recvlen != 0) { + return; + } + + tp->recving = 1; + tp->recvoff = 0; + aio = tp->tcp_recv; + aio->a_niov = 1; + aio->a_iov[0].iov_buf = tp->recvbuf; + aio->a_iov[0].iov_len = NNG_TLS_MAX_RECV_SIZE; + nni_aio_set_timeout(tp->tcp_recv, -1); // No timeout. + nni_plat_tcp_pipe_recv(tp->tcp, aio); +} + +static void +nni_tls_recv_cb(void *ctx) +{ + nni_tls *tp = ctx; + nni_aio *aio = tp->tcp_recv; + + nni_mtx_lock(&tp->lk); + tp->recving = false; + if (nni_aio_result(aio) != 0) { + // Close the underlying TCP channel, but permit data we + // already received to continue to be received. + nni_plat_tcp_pipe_close(tp->tcp); + tp->tcp_closed = true; + } else { + NNI_ASSERT(tp->recvlen == 0); + NNI_ASSERT(tp->recvoff == 0); + tp->recvlen = nni_aio_count(aio); + } + + // If we were closed (above), the upper layer will detect and + // react properly. Otherwise the upper layer will consume + // data. + if (!tp->hsdone) { + nni_tls_do_handshake(tp); + } + if (tp->hsdone) { + nni_tls_do_recv(tp); + nni_tls_do_send(tp); + } + + nni_mtx_unlock(&tp->lk); +} + +// This handles the bottom half send (i.e. sending over TCP). +// We always accept a chunk of data, to a limit, if the bottom +// sender is not busy. Then we handle that in the background. +// If the sender *is* busy, we return MBEDTLS_ERR_SSL_WANT_WRITE. +// The chunk size we accept is 64k at a time, which prevents +// ridiculous over queueing. This is always called with the pipe +// lock held, and never blocks. +int +nni_tls_net_send(void *ctx, const unsigned char *buf, size_t len) +{ + nni_tls *tp = ctx; + + if (len > NNG_TLS_MAX_SEND_SIZE) { + len = NNG_TLS_MAX_SEND_SIZE; + } + + // We should already be running with the pipe lock held, + // as we are running in that context. + + if (tp->sending) { + return (MBEDTLS_ERR_SSL_WANT_WRITE); + } + if (tp->tcp_closed) { + return (MBEDTLS_ERR_NET_SEND_FAILED); + } + + tp->sending = 1; + tp->sendlen = len; + tp->sendoff = 0; + memcpy(tp->sendbuf, buf, len); + + tp->tcp_send->a_niov = 1; + tp->tcp_send->a_iov[0].iov_buf = tp->sendbuf; + tp->tcp_send->a_iov[0].iov_len = len; + nni_aio_set_timeout(tp->tcp_send, -1); // No timeout. + nni_plat_tcp_pipe_send(tp->tcp, tp->tcp_send); + return (len); +} + +static int +nni_tls_net_recv(void *ctx, unsigned char *buf, size_t len) +{ + nni_tls *tp = ctx; + + // We should already be running with the pipe lock held, + // as we are running in that context. + if (tp->tcp_closed && tp->recvlen == 0) { + return (MBEDTLS_ERR_NET_RECV_FAILED); + } + + if (tp->recvlen == 0) { + len = MBEDTLS_ERR_SSL_WANT_READ; + } else { + if (len > tp->recvlen) { + len = tp->recvlen; + } + memcpy(buf, tp->recvbuf + tp->recvoff, len); + tp->recvoff += len; + tp->recvlen -= len; + } + + nni_tls_recv_start(tp); + return ((int) len); +} + +// nni_tls_send is the exported send function. It has a similar +// calling convention as the platform TCP pipe. +void +nni_tls_send(nni_tls *tp, nni_aio *aio) +{ + nni_mtx_lock(&tp->lk); + if (nni_aio_start(aio, nni_tls_cancel, tp) != 0) { + nni_mtx_unlock(&tp->lk); + return; + } + if (tp->tls_closed) { + nni_mtx_unlock(&tp->lk); + nni_aio_finish_error(aio, NNG_ECLOSED); + return; + } + nni_list_append(&tp->sends, aio); + nni_tls_do_send(tp); + nni_mtx_unlock(&tp->lk); +} + +void +nni_tls_recv(nni_tls *tp, nni_aio *aio) +{ + nni_mtx_lock(&tp->lk); + if (nni_aio_start(aio, nni_tls_cancel, tp) != 0) { + nni_mtx_unlock(&tp->lk); + return; + } + if (tp->tls_closed) { + nni_mtx_unlock(&tp->lk); + nni_aio_finish_error(aio, NNG_ECLOSED); + return; + } + nni_list_append(&tp->recvs, aio); + nni_tls_do_recv(tp); + nni_mtx_unlock(&tp->lk); +} + +void +nni_tls_do_handshake(nni_tls *tp) +{ + int rv; + + if (tp->tls_closed) { + return; + } + rv = mbedtls_ssl_handshake(&tp->ctx); + switch (rv) { + case MBEDTLS_ERR_SSL_WANT_WRITE: + case MBEDTLS_ERR_SSL_WANT_READ: + // We have underlying I/O to complete first. We will + // be called again by a callback later. + return; + case 0: + // The handshake is done, yay! + tp->hsdone = true; + return; + + default: + // Some other error occurred... would be nice to be + // able to diagnose it better. + tp->tls_closed = true; + nni_plat_tcp_pipe_close(tp->tcp); + tp->tcp_closed = true; + } +} + +// nni_tls_do_send is called to try to send more data if we have not +// yet completed the I/O. It also completes any transactions that +// *have* completed. It must be called with the lock held. +static void +nni_tls_do_send(nni_tls *tp) +{ + nni_aio *aio; + + while ((aio = nni_list_first(&tp->sends)) != NULL) { + int n; + uint8_t *buf = NULL; + size_t len = 0; + + for (int i = 0; i < aio->a_niov; i++) { + if (aio->a_iov[i].iov_len != 0) { + buf = aio->a_iov[i].iov_buf; + len = aio->a_iov[i].iov_len; + break; + } + } + if (len == 0 || buf == NULL) { + nni_aio_list_remove(aio); + nni_aio_finish_error(aio, NNG_EINVAL); + continue; + } + + n = mbedtls_ssl_write(&tp->ctx, buf, len); + + if ((n == MBEDTLS_ERR_SSL_WANT_WRITE) || + (n == MBEDTLS_ERR_SSL_WANT_READ)) { + // Cannot send any more data right now, wait + // for callback. + return; + } + // Some other error occurred... this is not good. + // Want better diagnostics. + nni_aio_list_remove(aio); + if (n < 0) { + nni_aio_finish_error(aio, nni_tls_mkerr(n)); + } else { + nni_aio_finish(aio, 0, n); + } + } +} + +static void +nni_tls_do_recv(nni_tls *tp) +{ + nni_aio *aio; + + while ((aio = nni_list_first(&tp->recvs)) != NULL) { + int n; + uint8_t *buf = NULL; + size_t len = 0; + + for (int i = 0; i < aio->a_niov; i++) { + if (aio->a_iov[i].iov_len != 0) { + buf = aio->a_iov[i].iov_buf; + len = aio->a_iov[i].iov_len; + break; + } + } + if (len == 0 || buf == NULL) { + nni_aio_list_remove(aio); + nni_aio_finish_error(aio, NNG_EINVAL); + continue; + } + n = mbedtls_ssl_read(&tp->ctx, buf, len); + + if ((n == MBEDTLS_ERR_SSL_WANT_READ) || + (n == MBEDTLS_ERR_SSL_WANT_WRITE)) { + // Cannot receive any more data right now, wait + // for callback. + return; + } + + nni_aio_list_remove(aio); + + if (n < 0) { + nni_aio_finish_error(aio, nni_tls_mkerr(n)); + } else { + nni_aio_finish(aio, 0, n); + } + } +} + +void +nni_tls_close(nni_tls *tp) +{ + nni_aio *aio; + + nni_mtx_lock(&tp->lk); + tp->tls_closed = true; + + while ((aio = nni_list_first(&tp->sends)) != NULL) { + nni_aio_list_remove(aio); + nni_aio_finish_error(aio, NNG_ECLOSED); + } + + while ((aio = nni_list_first(&tp->recvs)) != NULL) { + nni_aio_list_remove(aio); + nni_aio_finish_error(aio, NNG_ECLOSED); + } + + if (tp->hsdone) { + // This may succeed, or it may fail. Either way we + // don't care. Implementations that depend on + // close-notify to mean anything are broken by design, + // just like RFC. Note that we do *NOT* close the TCP + // connection at this point. + (void) mbedtls_ssl_close_notify(&tp->ctx); + } else { + nni_plat_tcp_pipe_close(tp->tcp); + tp->tcp_closed = true; + } + nni_mtx_unlock(&tp->lk); +} + +const char * +nni_tls_ciphersuite_name(nni_tls *tp) +{ + return (mbedtls_ssl_get_ciphersuite(&tp->ctx)); +} + +int +nni_tls_verified(nni_tls *tp) +{ + int rv; + + rv = mbedtls_ssl_get_verify_result(&tp->ctx); + return (rv ? 1 : 0); +} + +int +nni_tls_config_server_name(nni_tls_config *cfg, const char *name) +{ + int rv; + nni_mtx_lock(&cfg->lk); + if (cfg->active) { + nni_mtx_unlock(&cfg->lk); + return (NNG_ESTATE); + } + if (cfg->server_name) { + nni_strfree(cfg->server_name); + } + cfg->server_name = nni_strdup(name); + rv = cfg->server_name == NULL ? NNG_ENOMEM : 0; + nni_mtx_unlock(&cfg->lk); + return (rv); +} + +int +nni_tls_config_auth_mode(nni_tls_config *cfg, int mode) +{ + nni_mtx_lock(&cfg->lk); + if (cfg->active) { + nni_mtx_unlock(&cfg->lk); + return (NNG_ESTATE); + } + switch (mode) { + case NNI_TLS_CONFIG_AUTH_MODE_NONE: + mbedtls_ssl_conf_authmode( + &cfg->cfg_ctx, MBEDTLS_SSL_VERIFY_NONE); + break; + case NNI_TLS_CONFIG_AUTH_MODE_OPTIONAL: + mbedtls_ssl_conf_authmode( + &cfg->cfg_ctx, MBEDTLS_SSL_VERIFY_OPTIONAL); + break; + case NNI_TLS_CONFIG_AUTH_MODE_REQUIRED: + mbedtls_ssl_conf_authmode( + &cfg->cfg_ctx, MBEDTLS_SSL_VERIFY_REQUIRED); + break; + default: + nni_mtx_unlock(&cfg->lk); + return (NNG_EINVAL); + } + nni_mtx_unlock(&cfg->lk); + return (0); +} + +#define PEMSTART "-----BEGIN " + +// nni_tls_copy_key_cert_material copies either PEM or DER encoded +// key material. It allocates an extra byte for a NUL terminator +// required by mbed TLS if the data is PEM and missing the terminator. +// It is required that the key material passed in begins with the +// PEM delimiter if it is actually PEM. +static int +nni_tls_copy_key_cert_material( + uint8_t **dstp, size_t *szp, const uint8_t *src, size_t sz) +{ + bool addz = false; + uint8_t *dst; + + if ((sz > strlen(PEMSTART)) && + (strncmp((const char *) src, PEMSTART, strlen(PEMSTART)) == 0) && + (src[sz - 1] != '\0')) { + addz = true; + } + + if (addz) { + if ((dst = nni_alloc(sz + 1)) != NULL) { + memcpy(dst, src, sz); + dst[sz] = '\0'; + sz++; + } + } else { + if ((dst = nni_alloc(sz)) != NULL) { + memcpy(dst, src, sz); + } + } + if (dst == NULL) { + return (NNG_ENOMEM); + } + *dstp = dst; + *szp = sz; + return (0); +} + +int +nni_tls_config_cert(nni_tls_config *cfg, const uint8_t *key, size_t sz) +{ + int rv = 0; + nni_tls_certkey *ck; + bool cknew; + + if (sz < 1) { + return (NNG_EINVAL); + } + + nni_mtx_lock(&cfg->lk); + if (cfg->active) { + rv = NNG_ESTATE; + goto err; + } + cknew = false; + if (((ck = nni_list_last(&cfg->certkeys)) == NULL) || + (ck->crt != NULL)) { + if ((ck = NNI_ALLOC_STRUCT(ck)) == NULL) { + rv = NNG_ENOMEM; + goto err; + } + mbedtls_pk_init(&ck->mpk); + mbedtls_x509_crt_init(&ck->mcrt); + cknew = true; + } + + rv = nni_tls_copy_key_cert_material(&ck->crt, &ck->crtlen, key, sz); + if (rv != 0) { + goto err; + } + if (cknew) { + nni_list_append(&cfg->certkeys, ck); + } +err: + nni_mtx_unlock(&cfg->lk); + + return (rv); +} + +int +nni_tls_config_key(nni_tls_config *cfg, const uint8_t *key, size_t sz) +{ + int rv = 0; + nni_tls_certkey *ck; + bool cknew; + + if (sz < 1) { + return (NNG_EINVAL); + } + + nni_mtx_lock(&cfg->lk); + if (cfg->active) { + rv = NNG_ESTATE; + goto err; + } + cknew = false; + if (((ck = nni_list_last(&cfg->certkeys)) == NULL) || + (ck->key != NULL)) { + if ((ck = NNI_ALLOC_STRUCT(ck)) == NULL) { + rv = NNG_ENOMEM; + goto err; + } + cknew = true; + } + + rv = nni_tls_copy_key_cert_material(&ck->key, &ck->keylen, key, sz); + if (rv != 0) { + goto err; + } + if (cknew) { + nni_list_append(&cfg->certkeys, ck); + } +err: + nni_mtx_unlock(&cfg->lk); + + return (rv); +} + +int +nni_tls_config_pass(nni_tls_config *cfg, const char *pass) +{ + int rv = 0; + nni_tls_certkey *ck; + bool cknew; + + if (pass == NULL) { + return (NNG_EINVAL); + } + + nni_mtx_lock(&cfg->lk); + if (cfg->active) { + rv = NNG_ESTATE; + goto err; + } + cknew = false; + if (((ck = nni_list_last(&cfg->certkeys)) == NULL) || + (ck->pass != NULL)) { + if ((ck = NNI_ALLOC_STRUCT(ck)) == NULL) { + rv = NNG_ENOMEM; + goto err; + } + cknew = true; + } + + if ((ck->pass = nni_strdup(pass)) != NULL) { + rv = NNG_ENOMEM; + goto err; + } + rv = 0; + if (cknew) { + nni_list_append(&cfg->certkeys, ck); + } +err: + nni_mtx_unlock(&cfg->lk); + + return (rv); +} + +int +nni_tls_config_ca_cert(nni_tls_config *cfg, const uint8_t *data, size_t sz) +{ + uint8_t *tmp; + size_t len = sz; + int rv = 0; + + if (sz < 1) { + return (NNG_EINVAL); + } + + if ((rv = nni_tls_copy_key_cert_material(&tmp, &len, data, sz)) != 0) { + return (NNG_ENOMEM); + } + + nni_mtx_lock(&cfg->lk); + if (cfg->active) { + rv = NNG_ESTATE; + goto err; + } + if ((rv = mbedtls_x509_crt_parse(&cfg->ca_certs, tmp, len)) != 0) { + rv = nni_tls_mkerr(rv); + } else { + cfg->have_ca_certs = true; + } +err: + nni_mtx_unlock(&cfg->lk); + nni_free(tmp, len); + if (rv != 0) { + nni_panic("panic:"); + } + return (rv); +} + +int +nni_tls_config_crl(nni_tls_config *cfg, const uint8_t *data, size_t sz) +{ + int rv; + uint8_t *tmp; + size_t len; + + if (sz < 1) { + return (NNG_EINVAL); + } + + if ((rv = nni_tls_copy_key_cert_material(&tmp, &len, data, sz)) != 0) { + return (NNG_ENOMEM); + } + + nni_mtx_lock(&cfg->lk); + if (cfg->active) { + rv = NNG_ESTATE; + goto err; + } + + if ((rv = mbedtls_x509_crl_parse(&cfg->crl, tmp, len)) != 0) { + rv = nni_tls_mkerr(rv); + } else { + cfg->have_crl = true; + } +err: + nni_mtx_unlock(&cfg->lk); + nni_free(tmp, len); + return (rv); +} +#endif // NNG_MBEDTLS_ENABLE \ No newline at end of file diff --git a/src/supplemental/tls.h b/src/supplemental/tls.h new file mode 100644 index 00000000..da2fe8cd --- /dev/null +++ b/src/supplemental/tls.h @@ -0,0 +1,84 @@ +// +// Copyright 2017 Staysail Systems, Inc. +// Copyright 2017 Capitar IT Group BV +// +// This software is supplied under the terms of the MIT License, a +// copy of which should be located in the distribution where this +// file was obtained (LICENSE.txt). A copy of the license may also be +// found online at https://opensource.org/licenses/MIT. +// + +#ifndef NNG_SUPPLEMENTAL_TLS_H +#define NNG_SUPPLEMENTAL_TLS_H + +// nni_tls represents the context for a single TLS stream. +typedef struct nni_tls nni_tls; + +// nni_tls_config is the context for full TLS configuration, normally +// associated with an endpoint, for example. +typedef struct nni_tls_config nni_tls_config; + +#define NNI_TLS_CONFIG_SERVER 1 +#define NNI_TLS_CONFIG_CLIENT 0 + +extern int nni_tls_config_init(nni_tls_config **, int); +extern void nni_tls_config_fini(nni_tls_config *); + +// nni_tls_config_server_name is used by clients to set the server name +// that they expect to be talking to. This may also support the SNI +// extension for virtual hosting. +extern int nni_tls_config_server_name(nni_tls_config *, const char *); + +// nni_tls_config_ca_cert configures one or more CAs used for validation +// of peer certificates. Multiple CAs (and their chains) may be configured +// by either calling this multiple times, or by specifying a list of +// certificates as concatenated data. The certs may be in PEM or DER +// format. +extern int nni_tls_config_ca_cert(nni_tls_config *, const uint8_t *, size_t); + +// nni_tls_config_clr loads a certificate revocation list. Again, these +// are in X.509 format (either PEM or DER). +extern int nni_tls_config_crl(nni_tls_config *, const uint8_t *, size_t); + +// nni_tls_config_cert is used to load our own certificate. For servers, +// this may be called more than once to configure multiple different keys, +// for example with different algorithms depending on what the peer supports. +// On the client, only a single option is available. +extern int nni_tls_config_cert(nni_tls_config *, const uint8_t *crt, size_t); +extern int nni_tls_config_key(nni_tls_config *, const uint8_t *, size_t); +extern int nni_tls_config_pass(nni_tls_config *, const char *); + +// nni_tls_config_validate_peer is used to enable validation of the peer +// and it's certificate. If disabled, the peer's certificate will still +// be available, but may not be valid. +extern int nni_tls_config_validate_peer(nni_tls_config *, bool); + +// nni_tls_config_auth_mode is a read-ony option that is used to configure +// the authentication mode use. The default is that servers have this off +// (i.e. no client authentication) and clients have it on (they verify +// the server), which matches typical practice. +extern int nni_tls_config_auth_mode(nni_tls_config *, int); +#define NNI_TLS_CONFIG_AUTH_MODE_NONE 0 // No verification is performed +#define NNI_TLS_CONFIG_AUTH_MODE_OPTIONAL 1 // Verify cert if presented +#define NNI_TLS_CONFIG_AUTH_MODE_REQUIRED 2 // Verify cert, close if invalid + +extern int nni_tls_init(nni_tls **, nni_tls_config *, nni_plat_tcp_pipe *); +extern void nni_tls_close(nni_tls *); +extern void nni_tls_fini(nni_tls *); +extern void nni_tls_send(nni_tls *, nni_aio *); +extern void nni_tls_recv(nni_tls *, nni_aio *); + +// nni_tls_verified returns true if the peer, or false if the peer did not +// verify. (During the handshake phase, the peer is not verified, so this +// might return false if executed too soon. The verification status will +// be accurate once the handshake is finished, however. +extern int nni_tls_verified(nni_tls *); + +// nni_tls_ciphersuite_name returns the name of the ciphersuite in use. +extern const char *nni_tls_ciphersuite_name(nni_tls *); + +// TBD: getting additional peer certificate information... + +extern void nni_tls_strerror(int, char *, size_t); // review this + +#endif // NNG_SUPPLEMENTAL_TLS_H diff --git a/src/transport/inproc/inproc.h b/src/transport/inproc/inproc.h index 9538b06f..bfd6e1ca 100644 --- a/src/transport/inproc/inproc.h +++ b/src/transport/inproc/inproc.h @@ -13,6 +13,6 @@ // inproc transport. This is used for intra-process communication. -extern int nng_inproc_register(void); +NNG_DECL int nng_inproc_register(void); #endif // NNG_TRANSPORT_INPROC_INPROC_H diff --git a/src/transport/ipc/ipc.h b/src/transport/ipc/ipc.h index f19762c7..4c4c5708 100644 --- a/src/transport/ipc/ipc.h +++ b/src/transport/ipc/ipc.h @@ -14,6 +14,6 @@ // ipc transport. This is used for inter-process communication on // the same host computer. -extern int nng_ipc_register(void); +NNG_DECL int nng_ipc_register(void); #endif // NNG_TRANSPORT_IPC_IPC_H diff --git a/src/transport/tcp/tcp.h b/src/transport/tcp/tcp.h index b4c79461..6975109f 100644 --- a/src/transport/tcp/tcp.h +++ b/src/transport/tcp/tcp.h @@ -13,6 +13,6 @@ // TCP transport. This is used for communication over TCP/IP. -extern int nng_tcp_register(void); +NNG_DECL int nng_tcp_register(void); #endif // NNG_TRANSPORT_TCP_TCP_H diff --git a/src/transport/tls/CMakeLists.txt b/src/transport/tls/CMakeLists.txt new file mode 100644 index 00000000..59ff3f5e --- /dev/null +++ b/src/transport/tls/CMakeLists.txt @@ -0,0 +1,19 @@ +# +# Copyright 2017 Staysail Systems, Inc. +# Copyright 2017 Capitar IT Group BV +# +# This software is supplied under the terms of the MIT License, a +# copy of which should be located in the distribution where this +# file was obtained (LICENSE.txt). A copy of the license may also be +# found online at https://opensource.org/licenses/MIT. +# + +# TLS transport + +if (NNG_TRANSPORT_TLS) + set(TLS_SOURCES transport/tls/tls.c transport/tls/tls.h) + install(FILES tls.h DESTINATION include/nng/transport/tls) + +endif() + +set(NNG_SOURCES ${NNG_SOURCES} ${TLS_SOURCES} PARENT_SCOPE) diff --git a/src/transport/tls/tls.c b/src/transport/tls/tls.c new file mode 100644 index 00000000..1bd83971 --- /dev/null +++ b/src/transport/tls/tls.c @@ -0,0 +1,1081 @@ +// +// Copyright 2017 Staysail Systems, Inc. +// Copyright 2017 Capitar IT Group BV +// +// This software is supplied under the terms of the MIT License, a +// copy of which should be located in the distribution where this +// file was obtained (LICENSE.txt). A copy of the license may also be +// found online at https://opensource.org/licenses/MIT. +// + +#include +#include +#include +#include + +#include "core/nng_impl.h" + +#include "supplemental/tls.h" +#include "tls.h" + +// TLS over TCP transport. Platform specific TCP operations must be +// supplied as well, and uses the supplemental TLS v1.2 code. It is not +// an accident that this very closely resembles the TCP transport itself. + +typedef struct nni_tls_pipe nni_tls_pipe; +typedef struct nni_tls_ep nni_tls_ep; + +// nni_tls_pipe is one end of a TLS connection. +struct nni_tls_pipe { + const char * addr; + nni_plat_tcp_pipe *tcp; + uint16_t peer; + uint16_t proto; + size_t rcvmax; + + nni_aio *user_txaio; + nni_aio *user_rxaio; + nni_aio *user_negaio; + + uint8_t txlen[sizeof(uint64_t)]; + uint8_t rxlen[sizeof(uint64_t)]; + size_t gottxhead; + size_t gotrxhead; + size_t wanttxhead; + size_t wantrxhead; + nni_aio *txaio; + nni_aio *rxaio; + nni_aio *negaio; + nni_msg *rxmsg; + nni_mtx mtx; + nni_tls *tls; +}; + +struct nni_tls_ep { + char addr[NNG_MAXADDRLEN + 1]; + nni_plat_tcp_ep *tep; + uint16_t proto; + size_t rcvmax; + nni_duration linger; + int ipv4only; + int authmode; + nni_aio * aio; + nni_aio * user_aio; + nni_mtx mtx; + nni_tls_config * cfg; +}; + +static void nni_tls_pipe_send_cb(void *); +static void nni_tls_pipe_recv_cb(void *); +static void nni_tls_pipe_nego_cb(void *); +static void nni_tls_ep_cb(void *arg); + +static int +nni_tls_tran_init(void) +{ + return (0); +} + +static void +nni_tls_tran_fini(void) +{ +} + +static void +nni_tls_pipe_close(void *arg) +{ + nni_tls_pipe *p = arg; + + nni_tls_close(p->tls); +} + +static void +nni_tls_pipe_fini(void *arg) +{ + nni_tls_pipe *p = arg; + + nni_aio_stop(p->rxaio); + nni_aio_stop(p->txaio); + nni_aio_stop(p->negaio); + + nni_aio_fini(p->rxaio); + nni_aio_fini(p->txaio); + nni_aio_fini(p->negaio); + + if (p->tls != NULL) { + nni_tls_fini(p->tls); + } + if (p->rxmsg) { + nni_msg_free(p->rxmsg); + } + NNI_FREE_STRUCT(p); +} + +static int +nni_tls_pipe_init(nni_tls_pipe **pipep, nni_tls_ep *ep, void *tpp) +{ + nni_tls_pipe * p; + nni_plat_tcp_pipe *tcp = tpp; + int rv; + + if ((p = NNI_ALLOC_STRUCT(p)) == NULL) { + return (NNG_ENOMEM); + } + nni_mtx_init(&p->mtx); + + if (((rv = nni_tls_init(&p->tls, ep->cfg, tcp)) != 0) || + ((rv = nni_aio_init(&p->txaio, nni_tls_pipe_send_cb, p)) != 0) || + ((rv = nni_aio_init(&p->rxaio, nni_tls_pipe_recv_cb, p)) != 0) || + ((rv = nni_aio_init(&p->negaio, nni_tls_pipe_nego_cb, p)) != 0)) { + nni_tls_pipe_fini(p); + return (rv); + } + + p->proto = ep->proto; + p->rcvmax = ep->rcvmax; + p->tcp = tcp; + p->addr = ep->addr; + + *pipep = p; + return (0); +} + +static void +nni_tls_cancel_nego(nni_aio *aio, int rv) +{ + nni_tls_pipe *p = aio->a_prov_data; + + nni_mtx_lock(&p->mtx); + if (p->user_negaio != aio) { + nni_mtx_unlock(&p->mtx); + return; + } + p->user_negaio = NULL; + nni_mtx_unlock(&p->mtx); + + nni_aio_cancel(p->negaio, rv); + nni_aio_finish_error(aio, rv); +} + +static void +nni_tls_pipe_nego_cb(void *arg) +{ + nni_tls_pipe *p = arg; + nni_aio * aio = p->negaio; + int rv; + + nni_mtx_lock(&p->mtx); + if ((rv = nni_aio_result(aio)) != 0) { + goto done; + } + + // We start transmitting before we receive. + if (p->gottxhead < p->wanttxhead) { + p->gottxhead += nni_aio_count(aio); + } else if (p->gotrxhead < p->wantrxhead) { + p->gotrxhead += nni_aio_count(aio); + } + + if (p->gottxhead < p->wanttxhead) { + aio->a_niov = 1; + aio->a_iov[0].iov_len = p->wanttxhead - p->gottxhead; + aio->a_iov[0].iov_buf = &p->txlen[p->gottxhead]; + // send it down... + nni_tls_send(p->tls, aio); + nni_mtx_unlock(&p->mtx); + return; + } + if (p->gotrxhead < p->wantrxhead) { + aio->a_niov = 1; + aio->a_iov[0].iov_len = p->wantrxhead - p->gotrxhead; + aio->a_iov[0].iov_buf = &p->rxlen[p->gotrxhead]; + nni_tls_recv(p->tls, aio); + nni_mtx_unlock(&p->mtx); + return; + } + // We have both sent and received the headers. Lets check the + // receive side header. + if ((p->rxlen[0] != 0) || (p->rxlen[1] != 'S') || + (p->rxlen[2] != 'P') || (p->rxlen[3] != 0) || (p->rxlen[6] != 0) || + (p->rxlen[7] != 0)) { + rv = NNG_EPROTO; + goto done; + } + + NNI_GET16(&p->rxlen[4], p->peer); + +done: + if ((aio = p->user_negaio) != NULL) { + p->user_negaio = NULL; + nni_aio_finish(aio, rv, 0); + } + nni_mtx_unlock(&p->mtx); +} + +static void +nni_tls_pipe_send_cb(void *arg) +{ + nni_tls_pipe *p = arg; + int rv; + nni_aio * aio; + size_t n; + nni_msg * msg; + nni_aio * txaio = p->txaio; + + nni_mtx_lock(&p->mtx); + if ((aio = p->user_txaio) == NULL) { + nni_mtx_unlock(&p->mtx); + return; + } + + if ((rv = nni_aio_result(txaio)) != 0) { + p->user_txaio = NULL; + nni_mtx_unlock(&p->mtx); + msg = nni_aio_get_msg(aio); + nni_aio_set_msg(aio, NULL); + nni_msg_free(msg); + nni_aio_finish_error(aio, rv); + return; + } + + n = nni_aio_count(txaio); + while (n) { + NNI_ASSERT(txaio->a_niov != 0); + if (txaio->a_iov[0].iov_len > n) { + txaio->a_iov[0].iov_len -= n; + txaio->a_iov[0].iov_buf += n; + break; + } + n -= txaio->a_iov[0].iov_len; + for (int i = 0; i < txaio->a_niov; i++) { + txaio->a_iov[i] = txaio->a_iov[i + 1]; + } + txaio->a_niov--; + } + if ((txaio->a_niov != 0) && (txaio->a_iov[0].iov_len != 0)) { + nni_tls_send(p->tls, txaio); + nni_mtx_unlock(&p->mtx); + return; + } + + nni_mtx_unlock(&p->mtx); + msg = nni_aio_get_msg(aio); + n = nni_msg_len(msg); + nni_aio_set_msg(aio, NULL); + nni_aio_finish(aio, 0, n); +} + +static void +nni_tls_pipe_recv_cb(void *arg) +{ + nni_tls_pipe *p = arg; + nni_aio * aio; + int rv; + size_t n; + nni_msg * msg; + nni_aio * rxaio = p->rxaio; + + nni_mtx_lock(&p->mtx); + + if ((aio = p->user_rxaio) == NULL) { + // Canceled. + nni_mtx_unlock(&p->mtx); + return; + } + + if ((rv = nni_aio_result(p->rxaio)) != 0) { + goto recv_error; + } + + n = nni_aio_count(p->rxaio); + while (n) { + NNI_ASSERT(rxaio->a_niov != 0); + if (rxaio->a_iov[0].iov_len > n) { + rxaio->a_iov[0].iov_len -= n; + rxaio->a_iov[0].iov_buf += n; + break; + } + n -= rxaio->a_iov[0].iov_len; + rxaio->a_niov--; + for (int i = 0; i < rxaio->a_niov; i++) { + rxaio->a_iov[i] = rxaio->a_iov[i + 1]; + } + } + // Was this a partial read? If so then resubmit for the rest. + if ((rxaio->a_niov != 0) && (rxaio->a_iov[0].iov_len != 0)) { + nni_tls_recv(p->tls, rxaio); + nni_mtx_unlock(&p->mtx); + return; + } + + // If we don't have a message yet, we were reading the TCP message + // header, which is just the length. This tells us the size of the + // message to allocate and how much more to expect. + if (p->rxmsg == NULL) { + uint64_t len; + // We should have gotten a message header. + NNI_GET64(p->rxlen, len); + + // Make sure the message payload is not too big. If it is + // the caller will shut down the pipe. + if (len > p->rcvmax) { + rv = NNG_EMSGSIZE; + goto recv_error; + } + + if ((rv = nng_msg_alloc(&p->rxmsg, (size_t) len)) != 0) { + goto recv_error; + } + + // Submit the rest of the data for a read -- we want to + // read the entire message now. + if (len != 0) { + rxaio->a_iov[0].iov_buf = nni_msg_body(p->rxmsg); + rxaio->a_iov[0].iov_len = (size_t) len; + rxaio->a_niov = 1; + + nni_tls_recv(p->tls, rxaio); + nni_mtx_unlock(&p->mtx); + return; + } + } + + // We read a message completely. Let the user know the good news. + p->user_rxaio = NULL; + msg = p->rxmsg; + p->rxmsg = NULL; + nni_mtx_unlock(&p->mtx); + nni_aio_finish_msg(aio, msg); + return; + +recv_error: + p->user_rxaio = NULL; + msg = p->rxmsg; + p->rxmsg = NULL; + nni_mtx_unlock(&p->mtx); + nni_msg_free(msg); + nni_aio_finish_error(aio, rv); +} + +static void +nni_tls_cancel_tx(nni_aio *aio, int rv) +{ + nni_tls_pipe *p = aio->a_prov_data; + + nni_mtx_lock(&p->mtx); + if (p->user_txaio != aio) { + nni_mtx_unlock(&p->mtx); + return; + } + p->user_txaio = NULL; + nni_mtx_unlock(&p->mtx); + + // cancel the underlying operation. + nni_aio_cancel(p->txaio, rv); + nni_aio_finish_error(aio, rv); +} + +static void +nni_tls_pipe_send(void *arg, nni_aio *aio) +{ + nni_tls_pipe *p = arg; + nni_msg * msg = nni_aio_get_msg(aio); + uint64_t len; + nni_aio * txaio; + int niov; + + len = nni_msg_len(msg) + nni_msg_header_len(msg); + + nni_mtx_lock(&p->mtx); + + if (nni_aio_start(aio, nni_tls_cancel_tx, p) != 0) { + nni_mtx_unlock(&p->mtx); + return; + } + + p->user_txaio = aio; + + NNI_PUT64(p->txlen, len); + + niov = 0; + txaio = p->txaio; + txaio->a_iov[niov].iov_buf = p->txlen; + txaio->a_iov[niov].iov_len = sizeof(p->txlen); + niov++; + if (nni_msg_header_len(msg) > 0) { + txaio->a_iov[niov].iov_buf = nni_msg_header(msg); + txaio->a_iov[niov].iov_len = nni_msg_header_len(msg); + niov++; + } + if (nni_msg_len(msg) > 0) { + txaio->a_iov[niov].iov_buf = nni_msg_body(msg); + txaio->a_iov[niov].iov_len = nni_msg_len(msg); + niov++; + } + txaio->a_niov = niov; + + nni_tls_send(p->tls, txaio); + nni_mtx_unlock(&p->mtx); +} + +static void +nni_tls_cancel_rx(nni_aio *aio, int rv) +{ + nni_tls_pipe *p = aio->a_prov_data; + + nni_mtx_lock(&p->mtx); + if (p->user_rxaio != aio) { + nni_mtx_unlock(&p->mtx); + return; + } + p->user_rxaio = NULL; + nni_mtx_unlock(&p->mtx); + + // cancel the underlying operation. + nni_aio_cancel(p->rxaio, rv); + nni_aio_finish_error(aio, rv); +} + +static void +nni_tls_pipe_recv(void *arg, nni_aio *aio) +{ + nni_tls_pipe *p = arg; + nni_aio * rxaio; + + nni_mtx_lock(&p->mtx); + + if (nni_aio_start(aio, nni_tls_cancel_rx, p) != 0) { + nni_mtx_unlock(&p->mtx); + return; + } + p->user_rxaio = aio; + + NNI_ASSERT(p->rxmsg == NULL); + + // Schedule a read of the TCP header. + rxaio = p->rxaio; + rxaio->a_iov[0].iov_buf = p->rxlen; + rxaio->a_iov[0].iov_len = sizeof(p->rxlen); + rxaio->a_niov = 1; + + nni_tls_recv(p->tls, rxaio); + nni_mtx_unlock(&p->mtx); +} + +static uint16_t +nni_tls_pipe_peer(void *arg) +{ + nni_tls_pipe *p = arg; + + return (p->peer); +} + +static int +nni_tls_pipe_getopt_locaddr(void *arg, void *v, size_t *szp) +{ + nni_tls_pipe *p = arg; + int rv; + nng_sockaddr sa; + + memset(&sa, 0, sizeof(sa)); + if ((rv = nni_plat_tcp_pipe_sockname(p->tcp, &sa)) == 0) { + rv = nni_getopt_sockaddr(&sa, v, szp); + } + return (rv); +} + +static int +nni_tls_pipe_getopt_remaddr(void *arg, void *v, size_t *szp) +{ + nni_tls_pipe *p = arg; + int rv; + nng_sockaddr sa; + + memset(&sa, 0, sizeof(sa)); + if ((rv = nni_plat_tcp_pipe_peername(p->tcp, &sa)) == 0) { + rv = nni_getopt_sockaddr(&sa, v, szp); + } + return (rv); +} + +static int +nni_tls_parse_pair(char *pair, char **hostp, char **servp) +{ + char *host, *serv, *end; + + if (pair[0] == '[') { + host = pair + 1; + // IP address enclosed ... for IPv6 usually. + if ((end = strchr(host, ']')) == NULL) { + return (NNG_EADDRINVAL); + } + *end = '\0'; + serv = end + 1; + if (*serv == ':') { + serv++; + } else if (*serv != '\0') { + return (NNG_EADDRINVAL); + } + } else { + host = pair; + serv = strchr(host, ':'); + if (serv != NULL) { + *serv = '\0'; + serv++; + } + } + if ((strlen(host) == 0) || (strcmp(host, "*") == 0)) { + *hostp = NULL; + } else { + *hostp = host; + } + if ((serv == NULL) || (strlen(serv) == 0)) { + *servp = NULL; + } else { + *servp = serv; + } + // Stash the port in big endian (network) byte order. + return (0); +} + +// Note that the url *must* be in a modifiable buffer. +int +nni_tls_parse_url(char *url, char **lhost, char **lserv, char **rhost, + char **rserv, int mode) +{ + char *h1; + int rv; + + if (strncmp(url, "tls://", strlen("tls://")) != 0) { + return (NNG_EADDRINVAL); + } + url += strlen("tls://"); + if ((mode == NNI_EP_MODE_DIAL) && ((h1 = strchr(url, ';')) != 0)) { + // The local address is the first part, the remote address + // is the second part. + *h1 = '\0'; + h1++; + if (((rv = nni_tls_parse_pair(h1, rhost, rserv)) != 0) || + ((rv = nni_tls_parse_pair(url, lhost, lserv)) != 0)) { + return (rv); + } + if ((*rserv == NULL) || (*rhost == NULL)) { + // We have to know where to connect to! + return (NNG_EADDRINVAL); + } + } else if (mode == NNI_EP_MODE_DIAL) { + *lhost = NULL; + *lserv = NULL; + if ((rv = nni_tls_parse_pair(url, rhost, rserv)) != 0) { + return (rv); + } + if ((*rserv == NULL) || (*rhost == NULL)) { + // We have to know where to connect to! + return (NNG_EADDRINVAL); + } + } else { + NNI_ASSERT(mode == NNI_EP_MODE_LISTEN); + *rhost = NULL; + *rserv = NULL; + if ((rv = nni_tls_parse_pair(url, lhost, lserv)) != 0) { + return (rv); + } + // We have to have a port to listen on! + if (*lserv == NULL) { + return (NNG_EADDRINVAL); + } + } + return (0); +} + +static void +nni_tls_pipe_start(void *arg, nni_aio *aio) +{ + nni_tls_pipe *p = arg; + nni_aio * negaio; + + nni_mtx_lock(&p->mtx); + p->txlen[0] = 0; + p->txlen[1] = 'S'; + p->txlen[2] = 'P'; + p->txlen[3] = 0; + NNI_PUT16(&p->txlen[4], p->proto); + NNI_PUT16(&p->txlen[6], 0); + + p->user_negaio = aio; + p->gotrxhead = 0; + p->gottxhead = 0; + p->wantrxhead = 8; + p->wanttxhead = 8; + negaio = p->negaio; + negaio->a_niov = 1; + negaio->a_iov[0].iov_len = 8; + negaio->a_iov[0].iov_buf = &p->txlen[0]; + if (nni_aio_start(aio, nni_tls_cancel_nego, p) != 0) { + nni_mtx_unlock(&p->mtx); + return; + } + nni_tls_send(p->tls, negaio); + nni_mtx_unlock(&p->mtx); +} + +static void +nni_tls_ep_fini(void *arg) +{ + nni_tls_ep *ep = arg; + + nni_aio_stop(ep->aio); + if (ep->tep != NULL) { + nni_plat_tcp_ep_fini(ep->tep); + } + if (ep->cfg) { + nni_tls_config_fini(ep->cfg); + } + nni_aio_fini(ep->aio); + nni_mtx_fini(&ep->mtx); + NNI_FREE_STRUCT(ep); +} + +static int +nni_tls_ep_init(void **epp, const char *url, nni_sock *sock, int mode) +{ + nni_tls_ep * ep; + int rv; + char buf[NNG_MAXADDRLEN + 1]; + char * rhost; + char * rserv; + char * lhost; + char * lserv; + nni_sockaddr rsa, lsa; + nni_aio * aio; + int passive; + int tlsmode; + int authmode; + + // Make a copy of the url (to allow for destructive operations) + if (nni_strlcpy(buf, url, sizeof(buf)) >= sizeof(buf)) { + return (NNG_EADDRINVAL); + } + + // Parse the URLs first. + rv = nni_tls_parse_url(buf, &lhost, &lserv, &rhost, &rserv, mode); + if (rv != 0) { + return (rv); + } + if (mode == NNI_EP_MODE_DIAL) { + passive = 0; + tlsmode = NNI_TLS_CONFIG_CLIENT; + authmode = NNI_TLS_CONFIG_AUTH_MODE_REQUIRED; + } else { + passive = 1; + tlsmode = NNI_TLS_CONFIG_SERVER; + authmode = NNI_TLS_CONFIG_AUTH_MODE_NONE; + } + + if ((rv = nni_aio_init(&aio, NULL, NULL)) != 0) { + return (rv); + } + + // XXX: arguably we could defer this part to the point we do a bind + // or connect! + + if ((rhost != NULL) || (rserv != NULL)) { + aio->a_addr = &rsa; + nni_plat_tcp_resolv(rhost, rserv, NNG_AF_UNSPEC, passive, aio); + nni_aio_wait(aio); + if ((rv = nni_aio_result(aio)) != 0) { + nni_aio_fini(aio); + return (rv); + } + } else { + rsa.s_un.s_family = NNG_AF_UNSPEC; + } + + if ((lhost != NULL) || (lserv != NULL)) { + aio->a_addr = &lsa; + nni_plat_tcp_resolv(lhost, lserv, NNG_AF_UNSPEC, passive, aio); + nni_aio_wait(aio); + if ((rv = nni_aio_result(aio)) != 0) { + nni_aio_fini(aio); + return (rv); + } + } else { + lsa.s_un.s_family = NNG_AF_UNSPEC; + } + nni_aio_fini(aio); + + if ((ep = NNI_ALLOC_STRUCT(ep)) == NULL) { + return (NNG_ENOMEM); + } + nni_mtx_init(&ep->mtx); + + if (nni_strlcpy(ep->addr, url, sizeof(ep->addr)) >= sizeof(ep->addr)) { + NNI_FREE_STRUCT(ep); + return (NNG_EADDRINVAL); + } + + if (((rv = nni_plat_tcp_ep_init(&ep->tep, &lsa, &rsa, mode)) != 0) || + ((rv = nni_tls_config_init(&ep->cfg, tlsmode)) != 0) || + ((rv = nni_tls_config_auth_mode(ep->cfg, authmode)) != 0) || + ((rv = nni_aio_init(&ep->aio, nni_tls_ep_cb, ep)) != 0)) { + nni_tls_ep_fini(ep); + return (rv); + } + if ((tlsmode == NNI_TLS_CONFIG_CLIENT) && (rhost != NULL)) { + if ((rv = nni_tls_config_server_name(ep->cfg, rhost)) != 0) { + nni_tls_ep_fini(ep); + return (rv); + } + } + ep->proto = nni_sock_proto(sock); + ep->authmode = authmode; + + *epp = ep; + return (0); +} + +static void +nni_tls_ep_close(void *arg) +{ + nni_tls_ep *ep = arg; + + nni_mtx_lock(&ep->mtx); + nni_plat_tcp_ep_close(ep->tep); + nni_mtx_unlock(&ep->mtx); + + nni_aio_stop(ep->aio); +} + +static int +nni_tls_ep_bind(void *arg) +{ + nni_tls_ep *ep = arg; + int rv; + + nni_mtx_lock(&ep->mtx); + rv = nni_plat_tcp_ep_listen(ep->tep); + nni_mtx_unlock(&ep->mtx); + + return (rv); +} + +static void +nni_tls_ep_finish(nni_tls_ep *ep) +{ + nni_aio * aio; + int rv; + nni_tls_pipe *pipe = NULL; + + if ((rv = nni_aio_result(ep->aio)) != 0) { + goto done; + } + NNI_ASSERT(nni_aio_get_pipe(ep->aio) != NULL); + + // Attempt to allocate the parent pipe. If this fails we'll + // drop the connection (ENOMEM probably). + rv = nni_tls_pipe_init(&pipe, ep, nni_aio_get_pipe(ep->aio)); + +done: + nni_aio_set_pipe(ep->aio, NULL); + aio = ep->user_aio; + ep->user_aio = NULL; + + if ((aio != NULL) && (rv == 0)) { + nni_aio_finish_pipe(aio, pipe); + return; + } + if (pipe != NULL) { + nni_tls_pipe_fini(pipe); + } + if (aio != NULL) { + NNI_ASSERT(rv != 0); + nni_aio_finish_error(aio, rv); + } +} + +static void +nni_tls_ep_cb(void *arg) +{ + nni_tls_ep *ep = arg; + + nni_mtx_lock(&ep->mtx); + nni_tls_ep_finish(ep); + nni_mtx_unlock(&ep->mtx); +} + +static void +nni_tls_cancel_ep(nni_aio *aio, int rv) +{ + nni_tls_ep *ep = aio->a_prov_data; + + nni_mtx_lock(&ep->mtx); + if (ep->user_aio != aio) { + nni_mtx_unlock(&ep->mtx); + return; + } + ep->user_aio = NULL; + nni_mtx_unlock(&ep->mtx); + + nni_aio_cancel(ep->aio, rv); + nni_aio_finish_error(aio, rv); +} + +static void +nni_tls_ep_accept(void *arg, nni_aio *aio) +{ + nni_tls_ep *ep = arg; + int rv; + + nni_mtx_lock(&ep->mtx); + NNI_ASSERT(ep->user_aio == NULL); + + if ((rv = nni_aio_start(aio, nni_tls_cancel_ep, ep)) != 0) { + nni_mtx_unlock(&ep->mtx); + return; + } + + ep->user_aio = aio; + + nni_plat_tcp_ep_accept(ep->tep, ep->aio); + nni_mtx_unlock(&ep->mtx); +} + +static void +nni_tls_ep_connect(void *arg, nni_aio *aio) +{ + nni_tls_ep *ep = arg; + int rv; + + nni_mtx_lock(&ep->mtx); + NNI_ASSERT(ep->user_aio == NULL); + + // If we can't start, then its dying and we can't report either. + if ((rv = nni_aio_start(aio, nni_tls_cancel_ep, ep)) != 0) { + nni_mtx_unlock(&ep->mtx); + return; + } + + ep->user_aio = aio; + + nni_plat_tcp_ep_connect(ep->tep, ep->aio); + nni_mtx_unlock(&ep->mtx); +} + +static int +nni_tls_ep_setopt_recvmaxsz(void *arg, const void *v, size_t sz) +{ + nni_tls_ep *ep = arg; + if (ep == NULL) { + return (nni_chkopt_size(v, sz, 0, NNI_MAXSZ)); + } + return (nni_setopt_size(&ep->rcvmax, v, sz, 0, NNI_MAXSZ)); +} + +static int +nni_tls_ep_getopt_recvmaxsz(void *arg, void *v, size_t *szp) +{ + nni_tls_ep *ep = arg; + return (nni_getopt_size(ep->rcvmax, v, szp)); +} + +static int +nni_tls_ep_setopt_linger(void *arg, const void *v, size_t sz) +{ + nni_tls_ep *ep = arg; + if (ep == NULL) { + return (nni_chkopt_ms(v, sz)); + } + return (nni_setopt_ms(&ep->linger, v, sz)); +} + +static int +nni_tls_ep_getopt_linger(void *arg, void *v, size_t *szp) +{ + nni_tls_ep *ep = arg; + return (nni_getopt_ms(ep->linger, v, szp)); +} + +static int +tls_setopt_ca_cert(void *arg, const void *data, size_t sz) +{ + nni_tls_ep *ep = arg; + + if (ep == NULL) { + return (0); + } + return (nni_tls_config_ca_cert(ep->cfg, data, sz)); +} + +static int +tls_setopt_cert(void *arg, const void *data, size_t sz) +{ + nni_tls_ep *ep = arg; + + if (ep == NULL) { + return (0); + } + return (nni_tls_config_cert(ep->cfg, data, sz)); +} + +static int +tls_setopt_private_key(void *arg, const void *data, size_t sz) +{ + nni_tls_ep *ep = arg; + + if (ep == NULL) { + return (0); + } + return (nni_tls_config_key(ep->cfg, data, sz)); +} + +static int +tls_setopt_pass(void *arg, const void *data, size_t sz) +{ + nni_tls_ep *ep = arg; + size_t len; + + len = nni_strnlen(data, sz); + if (len >= sz) { + return (NNG_EINVAL); + } + + if (ep == NULL) { + return (0); + } + return (nni_tls_config_pass(ep->cfg, data)); +} + +int nng_tls_auth_mode_none = NNI_TLS_CONFIG_AUTH_MODE_NONE; +int nng_tls_auth_mode_required = NNI_TLS_CONFIG_AUTH_MODE_REQUIRED; +int nng_tls_auth_mode_optional = NNI_TLS_CONFIG_AUTH_MODE_OPTIONAL; + +static int +tls_getopt_auth_mode(void *arg, void *v, size_t *szp) +{ + nni_tls_ep *ep = arg; + return (nni_getopt_int(ep->authmode, v, szp)); +} + +static int +tls_setopt_auth_mode(void *arg, const void *data, size_t sz) +{ + nni_tls_ep *ep = arg; + int mode; + int rv; + + rv = nni_setopt_int(&mode, data, sz, -100, 100); + if (rv == 0) { + switch (mode) { + case NNI_TLS_CONFIG_AUTH_MODE_NONE: + case NNI_TLS_CONFIG_AUTH_MODE_OPTIONAL: + case NNI_TLS_CONFIG_AUTH_MODE_REQUIRED: + break; + default: + rv = NNG_EINVAL; + break; + } + } + + if ((ep == NULL) || (rv != 0)) { + return (rv); + } + + if ((rv = nni_tls_config_auth_mode(ep->cfg, mode)) == 0) { + ep->authmode = mode; + } + return (rv); +} + +static int +tls_getopt_verified(void *arg, void *v, size_t *szp) +{ + nni_tls_pipe *p = arg; + int verified; + + verified = nni_tls_verified(p->tls); + return (nni_getopt_int(verified, v, szp)); +} + +static nni_tran_pipe_option nni_tls_pipe_options[] = { + { NNG_OPT_LOCADDR, nni_tls_pipe_getopt_locaddr }, + { NNG_OPT_REMADDR, nni_tls_pipe_getopt_remaddr }, + { NNG_OPT_TLS_AUTH_VERIFIED, tls_getopt_verified }, + // terminate list + { NULL, NULL } +}; + +static nni_tran_pipe nni_tls_pipe_ops = { + .p_fini = nni_tls_pipe_fini, + .p_start = nni_tls_pipe_start, + .p_send = nni_tls_pipe_send, + .p_recv = nni_tls_pipe_recv, + .p_close = nni_tls_pipe_close, + .p_peer = nni_tls_pipe_peer, + .p_options = nni_tls_pipe_options, +}; + +static nni_tran_ep_option nni_tls_ep_options[] = { + { + .eo_name = NNG_OPT_RECVMAXSZ, + .eo_getopt = nni_tls_ep_getopt_recvmaxsz, + .eo_setopt = nni_tls_ep_setopt_recvmaxsz, + }, + { + .eo_name = NNG_OPT_LINGER, + .eo_getopt = nni_tls_ep_getopt_linger, + .eo_setopt = nni_tls_ep_setopt_linger, + }, + { + .eo_name = NNG_OPT_TLS_CA_CERT, + .eo_getopt = NULL, + .eo_setopt = tls_setopt_ca_cert, + }, + { + .eo_name = NNG_OPT_TLS_CERT, + .eo_getopt = NULL, + .eo_setopt = tls_setopt_cert, + }, + { + .eo_name = NNG_OPT_TLS_PRIVATE_KEY, + .eo_getopt = NULL, + .eo_setopt = tls_setopt_private_key, + }, + { + .eo_name = NNG_OPT_TLS_PRIVATE_KEY_PASSWORD, + .eo_getopt = NULL, + .eo_setopt = tls_setopt_pass, + }, + { + .eo_name = NNG_OPT_TLS_AUTH_MODE, + .eo_getopt = tls_getopt_auth_mode, + .eo_setopt = tls_setopt_auth_mode, + }, + + // terminate list + { NULL, NULL, NULL }, +}; + +static nni_tran_ep nni_tls_ep_ops = { + .ep_init = nni_tls_ep_init, + .ep_fini = nni_tls_ep_fini, + .ep_connect = nni_tls_ep_connect, + .ep_bind = nni_tls_ep_bind, + .ep_accept = nni_tls_ep_accept, + .ep_close = nni_tls_ep_close, + .ep_options = nni_tls_ep_options, +}; + +static nni_tran nni_tls_tran = { + .tran_version = NNI_TRANSPORT_VERSION, + .tran_scheme = "tls", + .tran_ep = &nni_tls_ep_ops, + .tran_pipe = &nni_tls_pipe_ops, + .tran_init = nni_tls_tran_init, + .tran_fini = nni_tls_tran_fini, +}; + +int +nng_tls_register(void) +{ + return (nni_tran_register(&nni_tls_tran)); +} diff --git a/src/transport/tls/tls.h b/src/transport/tls/tls.h new file mode 100644 index 00000000..4317ae55 --- /dev/null +++ b/src/transport/tls/tls.h @@ -0,0 +1,62 @@ +// +// Copyright 2017 Staysail Systems, Inc. +// Copyright 2017 Capitar IT Group BV +// +// This software is supplied under the terms of the MIT License, a +// copy of which should be located in the distribution where this +// file was obtained (LICENSE.txt). A copy of the license may also be +// found online at https://opensource.org/licenses/MIT. +// + +#ifndef NNG_TRANSPORT_TLS_TLS_H +#define NNG_TRANSPORT_TLS_TLS_H + +// TLS transport. This is used for communication via TLS v1.2 over TCP/IP. + +NNG_DECL int nng_tls_register(void); + +// TLS options. Note that these can only be set *before* the endpoint is +// started. Once started, it is no longer possible to alter the TLS +// configuration. + +// NNG_OPT_TLS_CA_CERT is a string with one or more X.509 certificates, +// representing the entire CA chain. The content may be either PEM or DER +// encoded. +#define NNG_OPT_TLS_CA_CERT "tls:ca-cert" + +// NNG_OPT_TLS_CRL is a PEM encoded CRL (revocation list). Multiple lists +// may be loaded by using this option multiple times. +#define NNG_OPT_TLS_CRL "tls:crl" + +// NNG_OPT_TLS_CERT is used to specify our own certificate. At present +// only one certificate may be supplied. (In the future it may be +// possible to call this multiple times, for servers that select different +// certificates depending upon client capabilities.) +#define NNG_OPT_TLS_CERT "tls:cert" + +// NNG_OPT_TLS_PRIVATE_KEY is used to specify the private key used +// with the given certificate. This should be called after setting +// the certificate. The private key may be in PEM or DER format. +// If in PEM encoded, a terminating ZERO byte should be included. +#define NNG_OPT_TLS_PRIVATE_KEY "tls:private-key" + +// NNG_OPT_TLS_PRIVATE_KEY_PASSWORD is used to specify a password +// used for the private key. The value is an ASCIIZ string. +#define NNG_OPT_TLS_PRIVATE_KEY_PASSWORD "tls:private-key-password" + +// NNG_OPT_TLS_AUTH_MODE is an integer indicating whether our +// peer should be verified or not. It is required on clients/dialers, +// and off on servers/listeners, by default. +#define NNG_OPT_TLS_AUTH_MODE "tls:auth-mode" + +extern int nng_tls_auth_mode_required; +extern int nng_tls_auth_mode_none; +extern int nng_tls_auth_mode_optional; + +// NNG_OPT_TLS_AUTH_VERIFIED is a boolean that can be read on pipes, +// indicating whether the peer certificate is verified. +#define NNG_OPT_TLS_AUTH_VERIFIED "tls:auth-verified" + +// XXX: TBD: Ciphersuite selection and reporting. Session reuse? + +#endif // NNG_TRANSPORT_TLS_TLS_H diff --git a/src/transport/zerotier/zerotier.h b/src/transport/zerotier/zerotier.h index 4f10f9be..87325f34 100644 --- a/src/transport/zerotier/zerotier.h +++ b/src/transport/zerotier/zerotier.h @@ -110,13 +110,13 @@ // return values from zt_opt_status. We avoid hard coding them as defines, // to keep applications from baking in values that may change if the // underlying ZeroTier transport changes. -extern int nng_zt_network_status_configuring; -extern int nng_zt_network_status_ok; -extern int nng_zt_network_status_denied; -extern int nng_zt_network_status_notfound; -extern int nng_zt_network_status_error; -extern int nng_zt_network_status_obsolete; +NNG_DECL int nng_zt_network_status_configuring; +NNG_DECL int nng_zt_network_status_ok; +NNG_DECL int nng_zt_network_status_denied; +NNG_DECL int nng_zt_network_status_notfound; +NNG_DECL int nng_zt_network_status_error; +NNG_DECL int nng_zt_network_status_obsolete; -extern int nng_zt_register(void); +NNG_DECL int nng_zt_register(void); #endif // NNG_TRANSPORT_ZEROTIER_ZEROTIER_H -- cgit v1.2.3-70-g09d2