aboutsummaryrefslogtreecommitdiff
path: root/src/supplemental/tls
diff options
context:
space:
mode:
authorGarrett D'Amore <garrett@damore.org>2020-02-08 12:46:47 -0800
committerGarrett D'Amore <garrett@damore.org>2020-02-23 17:06:58 -0800
commitee0b44406d2b658886760ea08c0af12781ab7e3a (patch)
tree674d2d31df7a62c367c161261c942e96f7909166 /src/supplemental/tls
parent56bcc0310c4710bb21802719566926c2ccd2262a (diff)
downloadnng-ee0b44406d2b658886760ea08c0af12781ab7e3a.tar.gz
nng-ee0b44406d2b658886760ea08c0af12781ab7e3a.tar.bz2
nng-ee0b44406d2b658886760ea08c0af12781ab7e3a.zip
fixes #1005 TLS 1.3 support
This introduces support for an external wolfSSL plugin, and generally creates the framework for pluggable TLS implementations. The wolfSSL engine is provided via an external module (git submodule), available either under a GPLv3 license or a commercial license.
Diffstat (limited to 'src/supplemental/tls')
-rw-r--r--src/supplemental/tls/CMakeLists.txt57
-rw-r--r--src/supplemental/tls/mbedtls/CMakeLists.txt36
-rw-r--r--src/supplemental/tls/mbedtls/tls.c1069
-rw-r--r--src/supplemental/tls/none/tls.c154
-rw-r--r--src/supplemental/tls/tls_api.h32
-rw-r--r--src/supplemental/tls/tls_common.c1216
-rw-r--r--src/supplemental/tls/tls_test.c59
-rw-r--r--src/supplemental/tls/wolfssl/CMakeLists.txt26
8 files changed, 1580 insertions, 1069 deletions
diff --git a/src/supplemental/tls/CMakeLists.txt b/src/supplemental/tls/CMakeLists.txt
index dfeb6526..61d9f2fb 100644
--- a/src/supplemental/tls/CMakeLists.txt
+++ b/src/supplemental/tls/CMakeLists.txt
@@ -1,6 +1,6 @@
#
+# Copyright 2020 Staysail Systems, Inc. <info@staysail.tech>
# Copyright 2018 Capitar IT Group BV <info@capitar.com>
-# Copyright 2018 Staysail Systems, Inc. <info@staysail.tech>
# Copyright 2019 Devolutions <info@devolutions.net>
#
@@ -11,38 +11,29 @@
# found online at https://opensource.org/licenses/MIT.
#
-set(_SRCS ${PROJECT_SOURCE_DIR}/include/nng/supplemental/tls/tls.h)
-
-if (NNG_SUPP_TLS)
- set(NNG_SUPP_TLS_MBEDTLS ON)
- set(_DEFS -DNNG_SUPP_TLS)
-
- list(APPEND _SRCS supplemental/tls/tls_common.c)
-endif()
-
-# For now we only support the ARM mbedTLS library.
-if (NNG_SUPP_TLS_MBEDTLS)
-
- message(WARNING "
- ************************************************************
- Linking against mbedTLS changes license terms (Apache 2.0).
- Consult a lawyer and the license files for details.
- ************************************************************")
-
- # If mbedTLS was added by a consuming project, then we should use that
- # instance of it, instead of configuring our own.
- if (TARGET mbedtls)
- set(_LIBS mbedtls)
- else()
- find_package(mbedTLS REQUIRED)
- set(_LIBS ${MBEDTLS_LIBRARIES})
- set(_INCS ${MBEDTLS_INCLUDE_DIR})
- endif()
- list(APPEND _SRCS supplemental/tls/mbedtls/tls.c)
-
-else()
- list(APPEND _SRCS supplemental/tls/none/tls.c)
-endif()
+
+if (NNG_ENABLE_TLS)
+ set(NNG_TLS_ENGINES mbed wolf none)
+ # We assume Mbed for now. (Someday replaced perhaps with Bear.)
+ set(NNG_TLS_ENGINE mbed CACHE STRING "TLS engine to use.")
+ set_property(CACHE NNG_TLS_ENGINE PROPERTY STRINGS ${NNG_TLS_ENGINES})
+else ()
+ set(NNG_TLS_ENGINE none)
+endif ()
+
+# default TLS implementation for now is Mbed.
+nng_headers(nng/supplemental/tls/tls.h)
+nng_headers(nng/supplemental/tls/engine.h)
+
+if (NOT NNG_TLS_ENGINE STREQUAL "none")
+ nng_test(tls_test)
+endif ()
+
+add_subdirectory(mbedtls)
+add_subdirectory(wolfssl)
+
+nng_sources(tls_common.c)
+nng_sources(tls_api.h)
list(APPEND NNG_DEFS ${_DEFS})
list(APPEND NNG_SRCS ${_SRCS})
diff --git a/src/supplemental/tls/mbedtls/CMakeLists.txt b/src/supplemental/tls/mbedtls/CMakeLists.txt
new file mode 100644
index 00000000..22c8e1c6
--- /dev/null
+++ b/src/supplemental/tls/mbedtls/CMakeLists.txt
@@ -0,0 +1,36 @@
+#
+# Copyright 2020 Staysail Systems, Inc. <info@staysail.tech>
+#
+# 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.
+#
+
+if (NNG_TLS_ENGINE STREQUAL "mbed")
+ message(WARNING "
+ ************************************************************
+ Linking against Mbed TLS changes license terms (Apache 2.0).
+ Consult a lawyer and the license files for details.
+ ************************************************************")
+ nng_sources(tls.c)
+ nng_defines(NNG_TLS_ENGINE_INIT=nng_tls_engine_init_mbed)
+ nng_defines(NNG_TLS_ENGINE_FINI=nng_tls_engine_fini_mbed)
+ nng_defines(NNG_SUPP_TLS)
+
+ # If Mbed TLS was added by a consuming project, then we should use that
+ # instance of it, instead of configuring our own.
+ if (TARGET mbedtls)
+ set(_LIBS mbedtls)
+ else()
+ find_package(mbedTLS REQUIRED)
+ set(_LIBS ${MBEDTLS_LIBRARIES})
+ set(_INCS ${MBEDTLS_INCLUDE_DIR})
+ endif()
+
+ list(APPEND NNG_LIBS ${_LIBS})
+ list(APPEND NNG_INCS ${_INCS})
+
+ set(NNG_LIBS ${NNG_LIBS} PARENT_SCOPE)
+ set(NNG_INCS ${NNG_INCS} PARENT_SCOPE)
+endif()
diff --git a/src/supplemental/tls/mbedtls/tls.c b/src/supplemental/tls/mbedtls/tls.c
index b0ac6ec5..e378d304 100644
--- a/src/supplemental/tls/mbedtls/tls.c
+++ b/src/supplemental/tls/mbedtls/tls.c
@@ -28,88 +28,35 @@
#include "mbedtls/ssl.h"
#include "core/nng_impl.h"
-#include "core/tcp.h"
-#include "supplemental/tls/tls_api.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
+#include <nng/supplemental/tls/engine.h>
-typedef struct nni_tls_certkey {
+// pair holds a private key and the associated certificate.
+typedef struct {
mbedtls_x509_crt crt;
mbedtls_pk_context key;
nni_list_node node;
-} nni_tls_certkey;
+} pair;
-typedef struct {
- nni_tls_common com;
- nng_stream * tcp;
- mbedtls_ssl_context ctx;
- nng_tls_config * cfg; // kept so we can release it
- 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
- size_t sendlen; // amount of data in send buffer
- size_t sendoff; // offset of start of send data
- uint8_t * recvbuf; // recv buffer
- size_t recvlen; // amount of data in recv buffer
- size_t 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)
- nni_reap_item reap;
-} tls;
-
-struct nng_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;
+// Use a global RNG if we're going to override the builtin.
+static mbedtls_ctr_drbg_context rng_ctx;
+static nni_mtx rng_lock;
#endif
- mbedtls_x509_crt ca_certs;
- mbedtls_x509_crl crl;
- nni_atomic_u64 refcnt;
-
- nni_list certkeys;
+struct nng_tls_engine_conn {
+ void * tls; // parent conn
+ mbedtls_ssl_context ctx;
};
-static void tls_send_cb(void *);
-static void tls_recv_cb(void *);
-
-static void tls_do_send(tls *);
-static void tls_do_recv(tls *);
-static void tls_do_handshake(tls *);
-
-static int tls_net_send(void *, const unsigned char *, size_t);
-static int tls_net_recv(void *, unsigned char *, size_t);
+struct nng_tls_engine_config {
+ mbedtls_ssl_config cfg_ctx;
+ char * server_name;
+ mbedtls_x509_crt ca_certs;
+ mbedtls_x509_crl crl;
+ int min_ver;
+ int max_ver;
+ nni_list pairs;
+};
static void
tls_dbg(void *ctx, int level, const char *file, int line, const char *s)
@@ -141,115 +88,18 @@ static int
tls_random(void *arg, unsigned char *buf, size_t sz)
{
#ifdef NNG_TLS_USE_CTR_DRBG
- int rv;
- nng_tls_config *cfg = arg;
- NNI_ARG_UNUSED(arg);
+ int rv;
- nni_mtx_lock(&cfg->rng_lk);
- rv = mbedtls_ctr_drbg_random(&cfg->rng_ctx, buf, sz);
- nni_mtx_unlock(&cfg->rng_lk);
+ nni_mtx_lock(&rng_lock);
+ rv = mbedtls_ctr_drbg_random(&rng_ctx, buf, sz);
+ nni_mtx_unlock(&rng_lock);
return (rv);
#else
return (tls_get_entropy(arg, buf, sz));
#endif
}
-void
-nni_tls_config_fini(nng_tls_config *cfg)
-{
- nni_tls_certkey *ck;
-
- if (cfg != NULL) {
- if (nni_atomic_dec64_nv(&cfg->refcnt) != 0) {
- return;
- }
-
- 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);
- mbedtls_x509_crt_free(&ck->crt);
- mbedtls_pk_free(&ck->key);
-
- NNI_FREE_STRUCT(ck);
- }
- nni_mtx_fini(&cfg->lk);
- NNI_FREE_STRUCT(cfg);
- }
-}
-
-int
-nni_tls_config_init(nng_tls_config **cpp, enum nng_tls_mode mode)
-{
- nng_tls_config *cfg;
- int rv;
- int sslmode;
- int authmode;
-
- if ((cfg = NNI_ALLOC_STRUCT(cfg)) == NULL) {
- return (NNG_ENOMEM);
- }
- nni_atomic_init64(&cfg->refcnt);
- nni_atomic_inc64(&cfg->refcnt);
- nni_mtx_init(&cfg->lk);
- if (mode == NNG_TLS_MODE_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, tls_get_entropy, NULL, NULL, 0);
- if (rv != 0) {
- nni_tls_config_fini(cfg);
- return (rv);
- }
-#endif
- mbedtls_ssl_conf_rng(&cfg->cfg_ctx, tls_random, cfg);
-
- mbedtls_ssl_conf_dbg(&cfg->cfg_ctx, tls_dbg, cfg);
-
- *cpp = cfg;
- return (0);
-}
-
-void
-nni_tls_config_hold(nng_tls_config *cfg)
-{
- nni_atomic_inc64(&cfg->refcnt);
-}
-
-// tls_mkerr converts an mbed error to an NNG error. In all cases
-// we just encode with NNG_ETRANERR.
+// tls_mk_err converts an mbed error to an NNG error.
static struct {
int tls;
int nng;
@@ -267,7 +117,7 @@ static struct {
};
static int
-tls_mkerr(int err)
+tls_mk_err(int err)
{
for (int i = 0; tls_errs[i].tls != 0; i++) {
if (tls_errs[i].tls == err) {
@@ -277,762 +127,433 @@ tls_mkerr(int err)
return (NNG_ECRYPTO);
}
-// The common code should call this only after it has released
-// it's upper layer stuff.
-static void
-tls_reap(void *arg)
+static int
+net_send(void *tls, const unsigned char *buf, size_t len)
{
- tls *tls = arg;
+ size_t sz = len;
+ int rv;
- // Shut it all down first.
- if (tls->tcp != NULL) {
- nng_stream_close(tls->tcp);
- }
- nni_aio_stop(tls->tcp_send);
- nni_aio_stop(tls->tcp_recv);
- nni_aio_free(tls->com.aio);
-
- // And finalize / free everything.
- nng_stream_free(tls->tcp);
- nni_aio_free(tls->tcp_send);
- nni_aio_free(tls->tcp_recv);
- mbedtls_ssl_free(&tls->ctx);
- nng_tls_config_free(tls->com.cfg);
-
- if (tls->recvbuf != NULL) {
- nni_free(tls->recvbuf, NNG_TLS_MAX_RECV_SIZE);
- }
- if (tls->sendbuf != NULL) {
- nni_free(tls->sendbuf, NNG_TLS_MAX_RECV_SIZE);
+ rv = nng_tls_engine_send(tls, buf, &sz);
+ switch (rv) {
+ case 0:
+ return ((int) sz);
+ case NNG_EAGAIN:
+ return (MBEDTLS_ERR_SSL_WANT_WRITE);
+ default:
+ return (MBEDTLS_ERR_NET_SEND_FAILED);
}
- nni_mtx_fini(&tls->lk);
- memset(tls, 0xff, sizeof(*tls));
- NNI_FREE_STRUCT(tls);
-}
-
-static void
-tls_free(void *arg)
-{
- tls *tls = arg;
- nni_reap(&tls->reap, tls_reap, tls);
}
-int
-nni_tls_start(nng_stream *arg, nng_stream *tcp)
+static int
+net_recv(void *tls, unsigned char *buf, size_t len)
{
- tls * tp = (void *) arg;
- nng_tls_config *cfg = tp->com.cfg;
- int rv;
-
- if ((tp->recvbuf = nni_zalloc(NNG_TLS_MAX_RECV_SIZE)) == NULL) {
- return (NNG_ENOMEM);
- }
- if ((tp->sendbuf = nni_zalloc(NNG_TLS_MAX_SEND_SIZE)) == NULL) {
- return (NNG_ENOMEM);
- }
-
- nni_mtx_lock(&cfg->lk);
- // No more changes allowed to config.
- cfg->active = true;
- nni_mtx_unlock(&cfg->lk);
-
- mbedtls_ssl_init(&tp->ctx);
- mbedtls_ssl_set_bio(&tp->ctx, tp, tls_net_send, tls_net_recv, NULL);
-
- if ((rv = mbedtls_ssl_setup(&tp->ctx, &cfg->cfg_ctx)) != 0) {
- return (tls_mkerr(rv));
- }
-
- if (cfg->server_name) {
- mbedtls_ssl_set_hostname(&tp->ctx, cfg->server_name);
- }
-
- tp->tcp = tcp;
+ size_t sz = len;
+ int rv;
- if (((rv = nni_aio_alloc(&tp->tcp_send, tls_send_cb, tp)) != 0) ||
- ((rv = nni_aio_alloc(&tp->tcp_recv, tls_recv_cb, tp)) != 0)) {
- return (rv);
+ rv = nng_tls_engine_recv(tls, buf, &sz);
+ switch (rv) {
+ case 0:
+ return ((int) sz);
+ case NNG_EAGAIN:
+ return (MBEDTLS_ERR_SSL_WANT_READ);
+ default:
+ return (MBEDTLS_ERR_NET_RECV_FAILED);
}
-
- nni_mtx_lock(&tp->lk);
- // Kick off a handshake operation.
- tls_do_handshake(tp);
- nni_mtx_unlock(&tp->lk);
-
- return (0);
}
static void
-tls_cancel(nni_aio *aio, void *arg, int rv)
+conn_fini(nng_tls_engine_conn *ec)
{
- tls *tp = arg;
- 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);
+ mbedtls_ssl_free(&ec->ctx);
}
-// tls_send_cb is called when the underlying TCP send completes.
-static void
-tls_send_cb(void *ctx)
-{
- tls * tp = ctx;
- nni_aio *aio = tp->tcp_send;
-
- nni_mtx_lock(&tp->lk);
- if (nni_aio_result(aio) != 0) {
- nng_stream_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) {
- nni_iov iov;
- tp->sendoff += n;
- iov.iov_buf = tp->sendbuf + tp->sendoff;
- iov.iov_len = tp->sendlen;
- nni_aio_set_iov(aio, 1, &iov);
- nni_aio_set_timeout(aio, NNG_DURATION_INFINITE);
- nng_stream_send(tp->tcp, aio);
- nni_mtx_unlock(&tp->lk);
- return;
- }
- tp->sendoff = 0;
- tp->sending = false;
- }
-
- tls_do_handshake(tp);
- if (tp->hsdone) {
- tls_do_send(tp);
- tls_do_recv(tp);
- }
- nni_mtx_unlock(&tp->lk);
-}
-
-static void
-tls_recv_start(tls *tp)
+// The common code should call this only after it has released
+// it's upper layer stuff.
+int
+conn_init(nng_tls_engine_conn *ec, void *tls, nng_tls_engine_config *cfg)
{
- nni_aio *aio;
- nni_iov iov;
+ int rv;
- 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;
- }
+ ec->tls = tls;
- tp->recving = 1;
- tp->recvoff = 0;
- aio = tp->tcp_recv;
- iov.iov_buf = tp->recvbuf;
- iov.iov_len = NNG_TLS_MAX_RECV_SIZE;
- nni_aio_set_iov(aio, 1, &iov);
- nni_aio_set_timeout(tp->tcp_recv, NNG_DURATION_INFINITE);
- nng_stream_recv(tp->tcp, aio);
-}
+ mbedtls_ssl_init(&ec->ctx);
+ mbedtls_ssl_set_bio(&ec->ctx, tls, net_send, net_recv, NULL);
-static void
-tls_recv_cb(void *ctx)
-{
- 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.
- nng_stream_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 ((rv = mbedtls_ssl_setup(&ec->ctx, &cfg->cfg_ctx)) != 0) {
+ return (tls_mk_err(rv));
}
- // If we were closed (above), the upper layer will detect and
- // react properly. Otherwise the upper layer will consume
- // data.
- tls_do_handshake(tp);
- if (tp->hsdone) {
- tls_do_recv(tp);
- tls_do_send(tp);
+ if (cfg->server_name != NULL) {
+ mbedtls_ssl_set_hostname(&ec->ctx, cfg->server_name);
}
- nni_mtx_unlock(&tp->lk);
+ return (0);
}
-// 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.
-static int
-tls_net_send(void *ctx, const unsigned char *buf, size_t len)
+static void
+conn_close(nng_tls_engine_conn *ec)
{
- tls * tp = ctx;
- nni_iov iov;
-
- 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);
- iov.iov_buf = tp->sendbuf;
- iov.iov_len = len;
- nni_aio_set_iov(tp->tcp_send, 1, &iov);
- nni_aio_set_timeout(tp->tcp_send, NNG_DURATION_INFINITE);
- nng_stream_send(tp->tcp, tp->tcp_send);
- return (len);
+ // 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(&ec->ctx);
}
static int
-tls_net_recv(void *ctx, unsigned char *buf, size_t len)
+conn_recv(nng_tls_engine_conn *ec, uint8_t *buf, size_t *szp)
{
- tls *tp = ctx;
-
- // We should already be running with the pipe lock held,
- // as we are running in that context.
-
- if (tp->recvlen == 0) {
- if (tp->tcp_closed) {
- // The underlying TCP transport has closed, and we
- // have no more data in our receive buffer.
- return (MBEDTLS_ERR_NET_RECV_FAILED);
- }
- len = MBEDTLS_ERR_SSL_WANT_READ;
- } else {
- if (len > tp->recvlen) {
- len = tp->recvlen;
+ int rv;
+ if ((rv = mbedtls_ssl_read(&ec->ctx, buf, *szp)) < 0) {
+ switch (rv) {
+ case MBEDTLS_ERR_SSL_WANT_READ:
+ case MBEDTLS_ERR_SSL_WANT_WRITE:
+ return (NNG_EAGAIN);
+ default:
+ return (tls_mk_err(rv));
}
- memcpy(buf, tp->recvbuf + tp->recvoff, len);
- tp->recvoff += len;
- tp->recvlen -= len;
- }
-
- tls_recv_start(tp);
- return ((int) len);
-}
-
-static void
-tls_send(void *arg, nni_aio *aio)
-{
- int rv;
- tls *tp = arg;
-
- if (nni_aio_begin(aio) != 0) {
- return;
- }
- nni_mtx_lock(&tp->lk);
- if (tp->tls_closed) {
- nni_mtx_unlock(&tp->lk);
- nni_aio_finish_error(aio, NNG_ECLOSED);
- return;
- }
- if ((rv = nni_aio_schedule(aio, tls_cancel, tp)) != 0) {
- nni_mtx_unlock(&tp->lk);
- nni_aio_finish_error(aio, rv);
- return;
}
- nni_list_append(&tp->sends, aio);
- tls_do_send(tp);
- nni_mtx_unlock(&tp->lk);
-}
-
-static void
-tls_recv(void *arg, nni_aio *aio)
-{
- int rv;
- tls *tp = arg;
-
- if (nni_aio_begin(aio) != 0) {
- return;
- }
- nni_mtx_lock(&tp->lk);
- if (tp->tls_closed) {
- nni_mtx_unlock(&tp->lk);
- nni_aio_finish_error(aio, NNG_ECLOSED);
- return;
- }
- if ((rv = nni_aio_schedule(aio, tls_cancel, tp)) != 0) {
- nni_mtx_unlock(&tp->lk);
- nni_aio_finish_error(aio, rv);
- return;
- }
-
- nni_list_append(&tp->recvs, aio);
- tls_do_recv(tp);
- nni_mtx_unlock(&tp->lk);
-}
-
-static int
-tls_get_verified(void *arg, void *buf, size_t *szp, nni_type t)
-{
- tls *tp = arg;
- bool v = (mbedtls_ssl_get_verify_result(&tp->ctx) == 0);
-
- return (nni_copyout_bool(v, buf, szp, t));
+ *szp = (size_t) rv;
+ return (0);
}
-static const nni_option tls_options[] = {
- {
- .o_name = NNG_OPT_TLS_VERIFIED,
- .o_get = tls_get_verified,
- },
- {
- .o_name = NULL,
- },
-};
-
static int
-tls_setx(void *arg, const char *name, const void *buf, size_t sz, nni_type t)
+conn_send(nng_tls_engine_conn *ec, const uint8_t *buf, size_t *szp)
{
- tls * tp = arg;
- int rv;
- nng_stream *tcp;
-
- tcp = (tp != NULL) ? tp->tcp : NULL;
+ int rv;
- if ((rv = nni_stream_setx(tcp, name, buf, sz, t)) != NNG_ENOTSUP) {
- return (rv);
+ if ((rv = mbedtls_ssl_write(&ec->ctx, buf, *szp)) < 0) {
+ switch (rv) {
+ case MBEDTLS_ERR_SSL_WANT_READ:
+ case MBEDTLS_ERR_SSL_WANT_WRITE:
+ return (NNG_EAGAIN);
+ default:
+ return (tls_mk_err(rv));
+ }
}
- return (nni_setopt(tls_options, name, tp, buf, sz, t));
+ *szp = (size_t) rv;
+ return (0);
}
static int
-tls_getx(void *arg, const char *name, void *buf, size_t *szp, nni_type t)
-{
- tls *tp = arg;
- int rv;
-
- if ((rv = nni_stream_getx(tp->tcp, name, buf, szp, t)) !=
- NNG_ENOTSUP) {
- return (rv);
- }
- return (nni_getopt(tls_options, name, tp, buf, szp, t));
-}
-
-static void
-tls_do_handshake(tls *tp)
+conn_handshake(nng_tls_engine_conn *ec)
{
- int rv;
- nni_aio *aio;
+ int rv;
- if (tp->hsdone || tp->tls_closed) {
- return;
- }
- rv = mbedtls_ssl_handshake(&tp->ctx);
+ rv = mbedtls_ssl_handshake(&ec->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;
+ return (NNG_EAGAIN);
case 0:
// The handshake is done, yay!
- tp->hsdone = true;
- return;
+ return (0);
default:
- // some other error occurred, this causes us to tear it down
- nng_stream_close(tp->tcp);
- tp->tls_closed = true;
- tp->tcp_closed = true;
- rv = tls_mkerr(rv);
-
- while (((aio = nni_list_first(&tp->recvs)) != NULL) ||
- ((aio = nni_list_first(&tp->sends)) != NULL)) {
- nni_aio_list_remove(aio);
- nni_aio_finish_error(aio, rv);
- }
+ return (tls_mk_err(rv));
}
}
-// 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
-tls_do_send(tls *tp)
+static bool
+conn_verified(nng_tls_engine_conn *ec)
{
- nni_aio *aio;
-
- while ((aio = nni_list_first(&tp->sends)) != NULL) {
- int n;
- uint8_t *buf = NULL;
- size_t len = 0;
- nni_iov *iov;
- unsigned niov;
-
- nni_aio_get_iov(aio, &niov, &iov);
-
- for (unsigned i = 0; i < niov; i++) {
- if (iov[i].iov_len != 0) {
- buf = iov[i].iov_buf;
- len = 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, tls_mkerr(n));
- } else {
- nni_aio_finish(aio, 0, n);
- }
- }
+ return (mbedtls_ssl_get_verify_result(&ec->ctx) == 0);
}
static void
-tls_do_recv(tls *tp)
+config_fini(nng_tls_engine_config *cfg)
{
- nni_aio *aio;
-
- while ((aio = nni_list_first(&tp->recvs)) != NULL) {
- int n;
- uint8_t *buf = NULL;
- size_t len = 0;
- nni_iov *iov;
- unsigned niov;
-
- nni_aio_get_iov(aio, &niov, &iov);
-
- for (unsigned i = 0; i < niov; i++) {
- if (iov[i].iov_len != 0) {
- buf = iov[i].iov_buf;
- len = 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;
- }
+ pair *p;
- nni_aio_list_remove(aio);
+ 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 ((p = nni_list_first(&cfg->pairs))) {
+ nni_list_remove(&cfg->pairs, p);
+ mbedtls_x509_crt_free(&p->crt);
+ mbedtls_pk_free(&p->key);
- if (n < 0) {
- nni_aio_finish_error(aio, tls_mkerr(n));
- } else {
- nni_aio_finish(aio, 0, n);
- }
+ NNI_FREE_STRUCT(p);
}
}
-static void
-tls_close(void *arg)
+static int
+config_init(nng_tls_engine_config *cfg, enum nng_tls_mode mode)
{
- tls * tp = arg;
- nni_aio *aio;
+ int rv;
+ int ssl_mode;
+ int auth_mode;
- nni_aio_close(tp->tcp_send);
- nni_aio_close(tp->tcp_recv);
+ if (mode == NNG_TLS_MODE_SERVER) {
+ ssl_mode = MBEDTLS_SSL_IS_SERVER;
+ auth_mode = MBEDTLS_SSL_VERIFY_NONE;
+ } else {
+ ssl_mode = MBEDTLS_SSL_IS_CLIENT;
+ auth_mode = MBEDTLS_SSL_VERIFY_REQUIRED;
+ }
- nni_mtx_lock(&tp->lk);
- tp->tls_closed = true;
+ NNI_LIST_INIT(&cfg->pairs, pair, node);
+ mbedtls_ssl_config_init(&cfg->cfg_ctx);
+ mbedtls_x509_crt_init(&cfg->ca_certs);
+ mbedtls_x509_crl_init(&cfg->crl);
- while (((aio = nni_list_first(&tp->sends)) != NULL) ||
- ((aio = nni_list_first(&tp->recvs)) != NULL)) {
- nni_aio_list_remove(aio);
- nni_aio_finish_error(aio, NNG_ECLOSED);
+ rv = mbedtls_ssl_config_defaults(&cfg->cfg_ctx, ssl_mode,
+ MBEDTLS_SSL_TRANSPORT_STREAM, MBEDTLS_SSL_PRESET_DEFAULT);
+ if (rv != 0) {
+ config_fini(cfg);
+ return (rv);
}
- 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 {
- nng_stream_close(tp->tcp);
- }
- nni_mtx_unlock(&tp->lk);
-}
+ mbedtls_ssl_conf_authmode(&cfg->cfg_ctx, auth_mode);
-// This allocates a TLS structure, that can be used by the caller.
-// The reason we have this API is so that the base structure can be
-// embedded in the parent structure.
-int
-nni_tls_alloc(nng_stream **tlsp)
-{
- tls *tp;
+ // Default: we *require* TLS v1.2 or newer, which is also known as
+ // SSL v3.3. As of this writing, Mbed TLS still does not support
+ // version 1.3, and we would want to test it before enabling it here.
+ cfg->min_ver = MBEDTLS_SSL_MINOR_VERSION_3;
+ cfg->max_ver = MBEDTLS_SSL_MINOR_VERSION_3;
+
+ mbedtls_ssl_conf_min_version(
+ &cfg->cfg_ctx, MBEDTLS_SSL_MAJOR_VERSION_3, cfg->min_ver);
+ mbedtls_ssl_conf_max_version(
+ &cfg->cfg_ctx, MBEDTLS_SSL_MAJOR_VERSION_3, cfg->max_ver);
+
+ mbedtls_ssl_conf_rng(&cfg->cfg_ctx, tls_random, cfg);
+ mbedtls_ssl_conf_dbg(&cfg->cfg_ctx, tls_dbg, cfg);
- if ((tp = NNI_ALLOC_STRUCT(tp)) == NULL) {
- return (NNG_ENOMEM);
- }
- nni_aio_list_init(&tp->sends);
- nni_aio_list_init(&tp->recvs);
- nni_mtx_init(&tp->lk);
- tp->com.ops.s_close = tls_close;
- tp->com.ops.s_free = tls_free;
- tp->com.ops.s_send = tls_send;
- tp->com.ops.s_recv = tls_recv;
- tp->com.ops.s_getx = tls_getx;
- tp->com.ops.s_setx = tls_setx;
-
- *tlsp = (void *) tp;
return (0);
}
-int
-nng_tls_config_server_name(nng_tls_config *cfg, const char *name)
+static int
+config_server_name(nng_tls_engine_config *cfg, const char *name)
{
- int rv;
- nni_mtx_lock(&cfg->lk);
- if (cfg->active) {
- nni_mtx_unlock(&cfg->lk);
- return (NNG_ESTATE);
+ char *dup;
+ if ((dup = strdup(name)) == NULL) {
+ return (NNG_ENOMEM);
}
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);
+ cfg->server_name = dup;
+ return (0);
}
-int
-nng_tls_config_auth_mode(nng_tls_config *cfg, nng_tls_auth_mode mode)
+static int
+config_auth_mode(nng_tls_engine_config *cfg, nng_tls_auth_mode mode)
{
- nni_mtx_lock(&cfg->lk);
- if (cfg->active) {
- nni_mtx_unlock(&cfg->lk);
- return (NNG_ESTATE);
- }
switch (mode) {
case NNG_TLS_AUTH_MODE_NONE:
mbedtls_ssl_conf_authmode(
&cfg->cfg_ctx, MBEDTLS_SSL_VERIFY_NONE);
- break;
+ return (0);
case NNG_TLS_AUTH_MODE_OPTIONAL:
mbedtls_ssl_conf_authmode(
&cfg->cfg_ctx, MBEDTLS_SSL_VERIFY_OPTIONAL);
- break;
+ return (0);
case NNG_TLS_AUTH_MODE_REQUIRED:
mbedtls_ssl_conf_authmode(
&cfg->cfg_ctx, MBEDTLS_SSL_VERIFY_REQUIRED);
- break;
- default:
- nni_mtx_unlock(&cfg->lk);
- return (NNG_EINVAL);
+ return (0);
}
- nni_mtx_unlock(&cfg->lk);
- return (0);
+ return (NNG_EINVAL);
}
-int
-nng_tls_config_ca_chain(
- nng_tls_config *cfg, const char *certs, const char *crl)
+static int
+config_ca_chain(nng_tls_engine_config *cfg, const char *certs, const char *crl)
{
size_t len;
const uint8_t *pem;
int rv;
// Certs and CRL are in PEM data, with terminating NUL byte.
- nni_mtx_lock(&cfg->lk);
- if (cfg->active) {
- rv = NNG_ESTATE;
- goto err;
- }
pem = (const uint8_t *) certs;
len = strlen(certs) + 1;
if ((rv = mbedtls_x509_crt_parse(&cfg->ca_certs, pem, len)) != 0) {
- rv = tls_mkerr(rv);
- goto err;
+ return (tls_mk_err(rv));
}
if (crl != NULL) {
pem = (const uint8_t *) crl;
len = strlen(crl) + 1;
if ((rv = mbedtls_x509_crl_parse(&cfg->crl, pem, len)) != 0) {
- rv = tls_mkerr(rv);
- goto err;
+ return (tls_mk_err(rv));
}
}
mbedtls_ssl_conf_ca_chain(&cfg->cfg_ctx, &cfg->ca_certs, &cfg->crl);
-
-err:
- nni_mtx_unlock(&cfg->lk);
- return (rv);
+ return (0);
}
-int
-nng_tls_config_own_cert(
- nng_tls_config *cfg, const char *cert, const char *key, const char *pass)
+static int
+config_own_cert(nng_tls_engine_config *cfg, const char *cert, const char *key,
+ const char *pass)
{
- size_t len;
- const uint8_t * pem;
- nni_tls_certkey *ck;
- int rv;
+ size_t len;
+ const uint8_t *pem;
+ pair * p;
+ int rv;
- if ((ck = NNI_ALLOC_STRUCT(ck)) == NULL) {
+ if ((p = NNI_ALLOC_STRUCT(p)) == NULL) {
return (NNG_ENOMEM);
}
- mbedtls_x509_crt_init(&ck->crt);
- mbedtls_pk_init(&ck->key);
+ mbedtls_x509_crt_init(&p->crt);
+ mbedtls_pk_init(&p->key);
pem = (const uint8_t *) cert;
len = strlen(cert) + 1;
- if ((rv = mbedtls_x509_crt_parse(&ck->crt, pem, len)) != 0) {
- rv = tls_mkerr(rv);
+ if ((rv = mbedtls_x509_crt_parse(&p->crt, pem, len)) != 0) {
+ rv = tls_mk_err(rv);
goto err;
}
pem = (const uint8_t *) key;
len = strlen(key) + 1;
- rv = mbedtls_pk_parse_key(&ck->key, pem, len, (const uint8_t *) pass,
+ rv = mbedtls_pk_parse_key(&p->key, pem, len, (const uint8_t *) pass,
pass != NULL ? strlen(pass) : 0);
if (rv != 0) {
- rv = tls_mkerr(rv);
+ rv = tls_mk_err(rv);
goto err;
}
- nni_mtx_lock(&cfg->lk);
- if (cfg->active) {
- nni_mtx_unlock(&cfg->lk);
- rv = NNG_ESTATE;
- goto err;
- }
- rv = mbedtls_ssl_conf_own_cert(&cfg->cfg_ctx, &ck->crt, &ck->key);
+ rv = mbedtls_ssl_conf_own_cert(&cfg->cfg_ctx, &p->crt, &p->key);
if (rv != 0) {
- nni_mtx_unlock(&cfg->lk);
- rv = tls_mkerr(rv);
+ rv = tls_mk_err(rv);
goto err;
}
// Save this structure so we can free it with the context.
- nni_list_append(&cfg->certkeys, ck);
- nni_mtx_unlock(&cfg->lk);
+ nni_list_append(&cfg->pairs, p);
return (0);
err:
- mbedtls_x509_crt_free(&ck->crt);
- mbedtls_pk_free(&ck->key);
- NNI_FREE_STRUCT(ck);
+ mbedtls_x509_crt_free(&p->crt);
+ mbedtls_pk_free(&p->key);
+ NNI_FREE_STRUCT(p);
return (rv);
}
-int
-nng_tls_config_ca_file(nng_tls_config *cfg, const char *path)
+static int
+config_version(nng_tls_engine_config *cfg, nng_tls_version min_ver,
+ nng_tls_version max_ver)
{
- int rv;
- void * fdata;
- size_t fsize;
- char * pem;
- // Note that while mbedTLS supports its own file methods, we want
- // to avoid depending on that because it might not have been
- // included, so we use our own. We have to read the file, and
- // then allocate a buffer that has an extra byte so we can
- // ensure NUL termination. The file named by path may contain
- // both a ca chain, and crl chain, or just a ca chain.
- if ((rv = nni_file_get(path, &fdata, &fsize)) != 0) {
- return (rv);
+ int v1, v2;
+ int maj = MBEDTLS_SSL_MAJOR_VERSION_3;
+
+ if (min_ver > max_ver) {
+ return (NNG_ENOTSUP);
}
- if ((pem = nni_zalloc(fsize + 1)) == NULL) {
- nni_free(fdata, fsize);
- return (NNG_ENOMEM);
+ switch (min_ver) {
+ case NNG_TLS_1_0:
+ v1 = MBEDTLS_SSL_MINOR_VERSION_1;
+ break;
+ case NNG_TLS_1_1:
+ v1 = MBEDTLS_SSL_MINOR_VERSION_2;
+ break;
+ case NNG_TLS_1_2:
+ v1 = MBEDTLS_SSL_MINOR_VERSION_3;
+ break;
+ default:
+ return (NNG_ENOTSUP);
}
- memcpy(pem, fdata, fsize);
- nni_free(fdata, fsize);
- if (strstr(pem, "-----BEGIN X509 CRL-----") != NULL) {
- rv = nng_tls_config_ca_chain(cfg, pem, pem);
- } else {
- rv = nng_tls_config_ca_chain(cfg, pem, NULL);
+
+ switch (max_ver) {
+ case NNG_TLS_1_0:
+ v2 = MBEDTLS_SSL_MINOR_VERSION_1;
+ break;
+ case NNG_TLS_1_1:
+ v2 = MBEDTLS_SSL_MINOR_VERSION_2;
+ break;
+ case NNG_TLS_1_2:
+ case NNG_TLS_1_3: // We lack support for 1.3, so treat as 1.2.
+ v2 = MBEDTLS_SSL_MINOR_VERSION_3;
+ break;
+ default:
+ // Note that this means that if we ever TLS 1.4 or 2.0,
+ // then this will break. That's sufficiently far out
+ // to justify not worrying about it.
+ return (NNG_ENOTSUP);
}
- nni_free(pem, fsize + 1);
- return (rv);
+
+ cfg->min_ver = v1;
+ cfg->max_ver = v2;
+ mbedtls_ssl_conf_min_version(&cfg->cfg_ctx, maj, cfg->min_ver);
+ mbedtls_ssl_conf_max_version(&cfg->cfg_ctx, maj, cfg->max_ver);
+ return (0);
}
+static nng_tls_engine_config_ops config_ops = {
+ .init = config_init,
+ .fini = config_fini,
+ .size = sizeof(nng_tls_engine_config),
+ .auth = config_auth_mode,
+ .ca_chain = config_ca_chain,
+ .own_cert = config_own_cert,
+ .server = config_server_name,
+ .version = config_version,
+};
+
+static nng_tls_engine_conn_ops conn_ops = {
+ .size = sizeof(nng_tls_engine_conn),
+ .init = conn_init,
+ .fini = conn_fini,
+ .close = conn_close,
+ .recv = conn_recv,
+ .send = conn_send,
+ .handshake = conn_handshake,
+ .verified = conn_verified,
+};
+
+static nng_tls_engine tls_engine_mbed = {
+ .version = NNG_TLS_ENGINE_VERSION,
+ .config_ops = &config_ops,
+ .conn_ops = &conn_ops,
+ .name = "mbed",
+ .description = MBEDTLS_VERSION_STRING_FULL,
+ .fips_mode = false,
+};
+
int
-nng_tls_config_cert_key_file(
- nng_tls_config *cfg, const char *path, const char *pass)
+nng_tls_engine_init_mbed(void)
{
- int rv;
- void * fdata;
- size_t fsize;
- char * pem;
-
- // Note that while mbedTLS supports its own file methods, we want
- // to avoid depending on that because it might not have been
- // included, so we use our own. We have to read the file, and
- // then allocate a buffer that has an extra byte so we can
- // ensure NUL termination. The file named by path must contain
- // both our certificate, and our private key. The password
- // may be NULL if the key is not encrypted.
- if ((rv = nni_file_get(path, &fdata, &fsize)) != 0) {
+ int rv;
+
+#ifdef NNG_TLS_USE_CTR_DRBG
+ nni_mtx_init(&rng_lock);
+
+ mbedtls_ctr_drbg_init(&cfg->rng_ctx);
+ rv = mbedtls_ctr_drbg_seed(&rng_ctx, tls_get_entropy, NULL, NULL, 0);
+ if (rv != 0) {
+ nni_mtx_fini(&rng_lock);
return (rv);
}
- if ((pem = nni_zalloc(fsize + 1)) == NULL) {
- nni_free(fdata, fsize);
- return (NNG_ENOMEM);
- }
- memcpy(pem, fdata, fsize);
- nni_free(fdata, fsize);
- rv = nng_tls_config_own_cert(cfg, pem, pem, pass);
- nni_free(pem, fsize + 1);
- return (rv);
-}
+#endif
+ // Uncomment the following to have noisy debug from mbedTLS.
+ // This may be useful when trying to debug failures.
+ // mbedtls_debug_set_threshold(3);
-int
-nng_tls_config_alloc(nng_tls_config **cfgp, nng_tls_mode mode)
-{
- return (nni_tls_config_init(cfgp, mode));
-}
+ rv = nng_tls_engine_register(&tls_engine_mbed);
-void
-nng_tls_config_free(nng_tls_config *cfg)
-{
- nni_tls_config_fini(cfg);
+#ifdef NNG_TLS_USE_CTR_DRBG
+ if (rv != 0) {
+ nni_mtx_fini(&rng_lock);
+ }
+#endif
+
+ return (rv);
}
void
-nng_tls_config_hold(nng_tls_config *cfg)
+nng_tls_engine_fini_mbed(void)
{
- nni_tls_config_hold(cfg);
-}
+#ifdef NNG_TLS_USE_CTR_DRBG
+ mbedtls_ctr_drbg_free(&rng_ctx);
+ nni_mtx_fini(&rng_lock);
+#endif
+} \ No newline at end of file
diff --git a/src/supplemental/tls/none/tls.c b/src/supplemental/tls/none/tls.c
deleted file mode 100644
index a1e70b73..00000000
--- a/src/supplemental/tls/none/tls.c
+++ /dev/null
@@ -1,154 +0,0 @@
-//
-// Copyright 2019 Staysail Systems, Inc. <info@staysail.tech>
-// Copyright 2018 Capitar IT Group BV <info@capitar.com>
-// Copyright 2018 Devolutions <info@devolutions.net>
-//
-// 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 <stdbool.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-
-// This file is only used when TLS support is not build into the library.
-// We provide stub functions only to satisfy linkage.
-
-#include "core/nng_impl.h"
-#include "supplemental/tls/tls_api.h"
-
-void
-nni_tls_config_fini(nng_tls_config *cfg)
-{
- NNI_ARG_UNUSED(cfg);
-}
-
-int
-nni_tls_config_init(nng_tls_config **cpp, enum nng_tls_mode mode)
-{
- NNI_ARG_UNUSED(cpp);
- NNI_ARG_UNUSED(mode);
- return (NNG_ENOTSUP);
-}
-
-void
-nni_tls_config_hold(nng_tls_config *cfg)
-{
- NNI_ARG_UNUSED(cfg);
-}
-
-int
-nng_tls_config_server_name(nng_tls_config *cfg, const char *name)
-{
- NNI_ARG_UNUSED(cfg);
- NNI_ARG_UNUSED(name);
- return (NNG_ENOTSUP);
-}
-
-int
-nng_tls_config_auth_mode(nng_tls_config *cfg, nng_tls_auth_mode mode)
-{
- NNI_ARG_UNUSED(cfg);
- NNI_ARG_UNUSED(mode);
- return (NNG_ENOTSUP);
-}
-
-int
-nng_tls_config_ca_chain(
- nng_tls_config *cfg, const char *certs, const char *crl)
-{
- NNI_ARG_UNUSED(cfg);
- NNI_ARG_UNUSED(certs);
- NNI_ARG_UNUSED(crl);
- return (NNG_ENOTSUP);
-}
-
-int
-nng_tls_config_own_cert(
- nng_tls_config *cfg, const char *cert, const char *key, const char *pass)
-{
- NNI_ARG_UNUSED(cfg);
- NNI_ARG_UNUSED(cert);
- NNI_ARG_UNUSED(key);
- NNI_ARG_UNUSED(pass);
- return (NNG_ENOTSUP);
-}
-
-int
-nng_tls_config_ca_file(nng_tls_config *cfg, const char *path)
-{
- NNI_ARG_UNUSED(cfg);
- NNI_ARG_UNUSED(path);
- return (NNG_ENOTSUP);
-}
-
-int
-nng_tls_config_cert_key_file(
- nng_tls_config *cfg, const char *path, const char *pass)
-{
- NNI_ARG_UNUSED(cfg);
- NNI_ARG_UNUSED(path);
- NNI_ARG_UNUSED(pass);
- return (NNG_ENOTSUP);
-}
-
-int
-nng_tls_config_key(nng_tls_config *cfg, const uint8_t *key, size_t size)
-{
- NNI_ARG_UNUSED(cfg);
- NNI_ARG_UNUSED(key);
- NNI_ARG_UNUSED(size);
- return (NNG_ENOTSUP);
-}
-
-int
-nng_tls_config_pass(nng_tls_config *cfg, const char *pass)
-{
- NNI_ARG_UNUSED(cfg);
- NNI_ARG_UNUSED(pass);
- return (NNG_ENOTSUP);
-}
-
-int
-nng_tls_config_alloc(nng_tls_config **cfgp, nng_tls_mode mode)
-{
-
- NNI_ARG_UNUSED(cfgp);
- NNI_ARG_UNUSED(mode);
- return (NNG_ENOTSUP);
-}
-
-void
-nng_tls_config_free(nng_tls_config *cfg)
-{
- NNI_ARG_UNUSED(cfg);
-}
-
-int
-nni_tls_dialer_alloc(nng_stream_dialer **dp, const nng_url *url)
-{
- NNI_ARG_UNUSED(dp);
- NNI_ARG_UNUSED(url);
- return (NNG_ENOTSUP);
-}
-
-int
-nni_tls_listener_alloc(nng_stream_listener **lp, const nng_url *url)
-{
- NNI_ARG_UNUSED(lp);
- NNI_ARG_UNUSED(url);
- return (NNG_ENOTSUP);
-}
-
-int
-nni_tls_checkopt(const char *nm, const void *buf, size_t sz, nni_type t)
-{
- NNI_ARG_UNUSED(nm);
- NNI_ARG_UNUSED(buf);
- NNI_ARG_UNUSED(sz);
- NNI_ARG_UNUSED(t);
- return (NNG_ENOTSUP);
-} \ No newline at end of file
diff --git a/src/supplemental/tls/tls_api.h b/src/supplemental/tls/tls_api.h
index 4e6146b1..642b9072 100644
--- a/src/supplemental/tls/tls_api.h
+++ b/src/supplemental/tls/tls_api.h
@@ -1,5 +1,5 @@
//
-// Copyright 2019 Staysail Systems, Inc. <info@staysail.tech>
+// Copyright 2020 Staysail Systems, Inc. <info@staysail.tech>
// Copyright 2018 Capitar IT Group BV <info@capitar.com>
// Copyright 2019 Devolutions <info@devolutions.net>
//
@@ -14,40 +14,10 @@
#include <nng/supplemental/tls/tls.h>
-// This nni_tls_common structure represents the "base" structure for
-// an implementation to extend. One of these must be the first member
-// of the implementation specific TLS stream struct.
-typedef struct {
- nng_stream ops;
- nni_aio * aio; // system aio for connect/accept
- nni_aio * uaio; // user aio for connect/accept
- nng_tls_config *cfg;
-} nni_tls_common;
-
// The implementation supplies this function to create the TLS connection
// object. All fields will be zeroed.
-extern int nni_tls_alloc(nng_stream **);
extern int nni_tls_dialer_alloc(nng_stream_dialer **, const nng_url *);
extern int nni_tls_listener_alloc(nng_stream_listener **, const nng_url *);
extern int nni_tls_checkopt(const char *, const void *, size_t, nni_type);
-// nni_tls_start is called by the common TLS dialer/listener completions
-// to start the TLS stream activity. This may also do allocations, etc.
-extern int nni_tls_start(nng_stream *, nng_stream *);
-
-// nni_tls_config_init creates a new TLS configuration object.
-// The object is created with a reference count of one.
-extern int nni_tls_config_init(nng_tls_config **, nng_tls_mode);
-
-// nni_tls_config_fini drops the reference on the configuration
-// object, deallocating if this was the last reference.
-extern void nni_tls_config_fini(nng_tls_config *);
-
-// nni_tls_config_hold is used to get a hold on the config
-// object, preventing it from being released inadvertently.
-// The hold is released with a call to nng_tls_config_fini().
-// Note that a hold need not be acquired at creation, since
-// the configuration object is created with a hold on it.
-extern void nni_tls_config_hold(nng_tls_config *);
-
#endif // NNG_SUPPLEMENTAL_TLS_TLS_API_H
diff --git a/src/supplemental/tls/tls_common.c b/src/supplemental/tls/tls_common.c
index 7cccc1e9..7d89ea21 100644
--- a/src/supplemental/tls/tls_common.c
+++ b/src/supplemental/tls/tls_common.c
@@ -9,22 +9,90 @@
// found online at https://opensource.org/licenses/MIT.
//
-#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "core/nng_impl.h"
-#include "core/tcp.h"
-#include "supplemental/tls/tls_api.h"
+#include <nng/supplemental/tls/engine.h>
#include <nng/supplemental/tls/tls.h>
+// NNG_TLS_MAX_SEND_SIZE limits the amount of data we will buffer for sending,
+// exerting back-pressure 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
+
// This file contains common code for TLS, and is only compiled if we
// have TLS configured in the system. In particular, this provides the
// parts of TLS support that are invariant relative to different TLS
// libraries, such as dialer and listener support.
+#ifdef NNG_SUPP_TLS
+
+static const nng_tls_engine *tls_engine;
+static nni_mtx tls_engine_lock;
+
+struct nng_tls_config {
+ nng_tls_engine_config_ops ops;
+ const nng_tls_engine * engine; // store this so we can verify
+ nni_mtx lock;
+ int ref;
+ int busy;
+ size_t size;
+
+ // ... engine config data follows
+};
+
+typedef struct {
+ nng_stream stream;
+ nng_tls_engine_conn_ops ops;
+ nng_tls_config * cfg;
+ const nng_tls_engine * engine;
+ size_t size;
+ nni_aio * user_aio; // user's aio for connect/accept
+ nni_aio conn_aio; // system aio for connect/accept
+ nni_mtx lock;
+ bool closed;
+ bool hs_done;
+ nni_list send_queue;
+ nni_list recv_queue;
+ nng_stream * tcp; // lower level stream
+ nni_aio tcp_send; // lower level send pending
+ nni_aio tcp_recv; // lower level recv pending
+ uint8_t * tcp_send_buf;
+ uint8_t * tcp_recv_buf;
+ size_t tcp_recv_len;
+ size_t tcp_recv_off;
+ bool tcp_recv_pend;
+ bool tcp_send_active;
+ size_t tcp_send_len;
+ size_t tcp_send_head;
+ size_t tcp_send_tail;
+ struct nni_reap_item reap;
+
+ // ... engine connection data follows
+} tls_conn;
+
+static void tls_tcp_send_cb(void *arg);
+static void tls_tcp_recv_cb(void *arg);
+static void tls_do_send(tls_conn *);
+static void tls_do_recv(tls_conn *);
+static void tls_tcp_send_start(tls_conn *);
+static void tls_free(void *);
+static int tls_alloc(tls_conn **, nng_tls_config *, nng_aio *);
+static int tls_start(tls_conn *, nng_stream *);
+static void tls_tcp_error(tls_conn *, int);
+
typedef struct {
nng_stream_dialer ops;
nng_stream_dialer *d; // underlying TCP dialer
@@ -57,28 +125,26 @@ tls_dialer_free(void *arg)
static void
tls_conn_cb(void *arg)
{
- nng_stream * tls = arg;
- nni_tls_common *com = arg;
- nng_stream * tcp;
- int rv;
+ tls_conn * conn = arg;
+ nng_stream *tcp;
+ int rv;
- if ((rv = nni_aio_result(com->aio)) != 0) {
- nni_aio_finish_error(com->uaio, rv);
- nng_stream_free(tls);
+ if ((rv = nni_aio_result(&conn->conn_aio)) != 0) {
+ nni_aio_finish_error(conn->user_aio, rv);
+ nng_stream_free(&conn->stream);
return;
}
- tcp = nni_aio_get_output(com->aio, 0);
+ tcp = nni_aio_get_output(&conn->conn_aio, 0);
- if ((rv = nni_tls_start(tls, tcp)) != 0) {
- nni_aio_finish_error(com->uaio, rv);
- nng_stream_free(tcp);
- nng_stream_free(tls);
+ if ((rv = tls_start(conn, tcp)) != 0) {
+ nni_aio_finish_error(conn->user_aio, rv);
+ nng_stream_free(&conn->stream);
return;
}
- nni_aio_set_output(com->uaio, 0, tls);
- nni_aio_finish(com->uaio, 0, 0);
+ nni_aio_set_output(conn->user_aio, 0, &conn->stream);
+ nni_aio_finish(conn->user_aio, 0, 0);
}
// Dialer cancel is called when the user has indicated that they no longer
@@ -86,52 +152,35 @@ tls_conn_cb(void *arg)
static void
tls_conn_cancel(nni_aio *aio, void *arg, int rv)
{
- nni_tls_common *com = arg;
- NNI_ASSERT(com->uaio == aio);
+ tls_conn *conn = arg;
+ NNI_ASSERT(conn->user_aio == aio);
// Just pass this down. If the connection is already done, this
// will have no effect.
- nni_aio_abort(com->aio, rv);
+ nni_aio_abort(&conn->conn_aio, rv);
}
static void
tls_dialer_dial(void *arg, nng_aio *aio)
{
- tls_dialer * d = arg;
- int rv;
- nng_stream * tls;
- nni_tls_common *com;
+ tls_dialer *d = arg;
+ int rv;
+ tls_conn * conn;
if (nni_aio_begin(aio) != 0) {
return;
}
- if ((rv = nni_tls_alloc(&tls)) != 0) {
+ if ((rv = tls_alloc(&conn, d->cfg, aio)) != 0) {
nni_aio_finish_error(aio, rv);
return;
}
- com = (void *) tls;
- if ((rv = nni_aio_alloc(&com->aio, tls_conn_cb, tls)) != 0) {
+ if ((rv = nni_aio_schedule(aio, tls_conn_cancel, conn)) != 0) {
nni_aio_finish_error(aio, rv);
- nng_stream_free(tls);
+ tls_free(conn);
return;
}
- com->uaio = aio;
- // Save a copy of the TLS configuration. This way we don't have
- // to ensure that the dialer outlives the connection, because the
- // only shared data is the configuration which is reference counted.
- nni_mtx_lock(&d->lk);
- com->cfg = d->cfg;
- nng_tls_config_hold(com->cfg);
- nni_mtx_unlock(&d->lk);
-
- if ((rv = nni_aio_schedule(aio, tls_conn_cancel, tls)) != 0) {
- nni_aio_finish_error(aio, rv);
- nng_stream_free(tls);
- return;
- }
-
- nng_stream_dialer_dial(d->d, com->aio);
+ nng_stream_dialer_dial(d->d, &conn->conn_aio);
}
static int
@@ -307,10 +356,12 @@ nni_tls_dialer_alloc(nng_stream_dialer **dp, const nng_url *url)
{
tls_dialer *d;
int rv;
- nng_url myurl;
+ nng_url my_url;
- memcpy(&myurl, url, sizeof(myurl));
- myurl.u_scheme = url->u_scheme + strlen("tls+");
+ memcpy(&my_url, url, sizeof(my_url));
+ if (strncmp(my_url.u_scheme, "tls+", 4) == 0) {
+ my_url.u_scheme += 4;
+ }
if ((rv = nni_init()) != 0) {
return (rv);
@@ -320,7 +371,7 @@ nni_tls_dialer_alloc(nng_stream_dialer **dp, const nng_url *url)
}
nni_mtx_init(&d->lk);
- if ((rv = nng_stream_dialer_alloc_url(&d->d, &myurl)) != 0) {
+ if ((rv = nng_stream_dialer_alloc_url(&d->d, &my_url)) != 0) {
nni_mtx_fini(&d->lk);
NNI_FREE_STRUCT(d);
return (rv);
@@ -381,41 +432,25 @@ tls_listener_listen(void *arg)
static void
tls_listener_accept(void *arg, nng_aio *aio)
{
- tls_listener * l = arg;
- int rv;
- nng_stream * tls;
- nni_tls_common *com;
+ tls_listener *l = arg;
+ int rv;
+ tls_conn * conn;
if (nni_aio_begin(aio) != 0) {
return;
}
- if ((rv = nni_tls_alloc(&tls)) != 0) {
+ if ((rv = tls_alloc(&conn, l->cfg, aio)) != 0) {
nni_aio_finish_error(aio, rv);
return;
}
- com = (void *) tls;
- if ((rv = nni_aio_alloc(&com->aio, tls_conn_cb, tls)) != 0) {
- nni_aio_finish_error(aio, rv);
- nng_stream_free(tls);
- return;
- }
- com->uaio = aio;
-
- // Save a copy of the TLS configuration. This way we don't have
- // to ensure that the dialer outlives the connection, because the
- // only shared data is the configuration which is reference counted.
- nni_mtx_lock(&l->lk);
- com->cfg = l->cfg;
- nng_tls_config_hold(com->cfg);
- nni_mtx_unlock(&l->lk);
- if ((rv = nni_aio_schedule(aio, tls_conn_cancel, tls)) != 0) {
+ if ((rv = nni_aio_schedule(aio, tls_conn_cancel, conn)) != 0) {
nni_aio_finish_error(aio, rv);
- nng_stream_free(tls);
+ tls_free(conn);
return;
}
- nng_stream_listener_accept(l->l, com->aio);
+ nng_stream_listener_accept(l->l, &conn->conn_aio);
}
static int
@@ -581,10 +616,13 @@ nni_tls_listener_alloc(nng_stream_listener **lp, const nng_url *url)
{
tls_listener *l;
int rv;
- nng_url myurl;
+ nng_url my_url;
- memcpy(&myurl, url, sizeof(myurl));
- myurl.u_scheme = url->u_scheme + strlen("tls+");
+ memcpy(&my_url, url, sizeof(my_url));
+
+ if (strncmp(my_url.u_scheme, "tls+", 4) == 0) {
+ my_url.u_scheme += 4;
+ }
if ((rv = nni_init()) != 0) {
return (rv);
@@ -594,7 +632,7 @@ nni_tls_listener_alloc(nng_stream_listener **lp, const nng_url *url)
}
nni_mtx_init(&l->lk);
- if ((rv = nng_stream_listener_alloc_url(&l->l, &myurl)) != 0) {
+ if ((rv = nng_stream_listener_alloc_url(&l->l, &my_url)) != 0) {
nni_mtx_fini(&l->lk);
NNI_FREE_STRUCT(l);
return (rv);
@@ -645,7 +683,7 @@ tls_check_auth_mode(const void *buf, size_t sz, nni_type t)
return (rv);
}
-static const nni_chkoption tls_chkopts[] = {
+static const nni_chkoption tls_check_opts[] = {
{
.o_name = NNG_OPT_TLS_CONFIG,
.o_check = tls_check_config,
@@ -676,9 +714,1033 @@ nni_tls_checkopt(const char *name, const void *data, size_t sz, nni_type t)
{
int rv;
- rv = nni_chkopt(tls_chkopts, name, data, sz, t);
+ rv = nni_chkopt(tls_check_opts, name, data, sz, t);
if (rv == NNG_ENOTSUP) {
rv = nni_stream_checkopt("tcp", name, data, sz, t);
}
return (rv);
}
+
+static void
+tls_cancel(nni_aio *aio, void *arg, int rv)
+{
+ tls_conn *conn = arg;
+ nni_mtx_lock(&conn->lock);
+ if (aio == nni_list_first(&conn->recv_queue)) {
+ nni_aio_abort(&conn->tcp_recv, rv);
+ } else if (aio == nni_list_first(&conn->send_queue)) {
+ nni_aio_abort(&conn->tcp_send, rv);
+ } else if (nni_aio_list_active(aio)) {
+ nni_aio_list_remove(aio);
+ nni_aio_finish_error(aio, rv);
+ }
+ nni_mtx_unlock(&conn->lock);
+}
+
+// tls_send implements the upper layer stream send operation.
+static void
+tls_send(void *arg, nni_aio *aio)
+{
+ int rv;
+ tls_conn *conn = arg;
+
+ if (nni_aio_begin(aio) != 0) {
+ return;
+ }
+ nni_mtx_lock(&conn->lock);
+ if (conn->closed) {
+ nni_mtx_unlock(&conn->lock);
+ nni_aio_finish_error(aio, NNG_ECLOSED);
+ return;
+ }
+ if ((rv = nni_aio_schedule(aio, tls_cancel, conn)) != 0) {
+ nni_mtx_unlock(&conn->lock);
+ nni_aio_finish_error(aio, rv);
+ return;
+ }
+ nni_list_append(&conn->send_queue, aio);
+ tls_do_send(conn);
+ nni_mtx_unlock(&conn->lock);
+}
+
+static void
+tls_recv(void *arg, nni_aio *aio)
+{
+ int rv;
+ tls_conn *conn = arg;
+
+ if (nni_aio_begin(aio) != 0) {
+ return;
+ }
+ nni_mtx_lock(&conn->lock);
+ if (conn->closed) {
+ nni_mtx_unlock(&conn->lock);
+ nni_aio_finish_error(aio, NNG_ECLOSED);
+ return;
+ }
+ if ((rv = nni_aio_schedule(aio, tls_cancel, conn)) != 0) {
+ nni_mtx_unlock(&conn->lock);
+ nni_aio_finish_error(aio, rv);
+ return;
+ }
+
+ nni_list_append(&conn->recv_queue, aio);
+ tls_do_recv(conn);
+ nni_mtx_unlock(&conn->lock);
+}
+
+static void
+tls_close(void *arg)
+{
+ tls_conn *conn = arg;
+
+ nni_mtx_lock(&conn->lock);
+ conn->ops.close((void *) (conn + 1));
+ tls_tcp_error(conn, NNG_ECLOSED);
+ nni_mtx_unlock(&conn->lock);
+ nng_stream_close(conn->tcp);
+}
+
+static int
+tls_get_verified(void *arg, void *buf, size_t *szp, nni_type t)
+{
+ tls_conn *conn = arg;
+ bool v;
+
+ nni_mtx_lock(&conn->lock);
+ v = conn->ops.verified((void *) (conn + 1));
+ nni_mtx_unlock(&conn->lock);
+ return (nni_copyout_bool(v, buf, szp, t));
+}
+
+static const nni_option tls_options[] = {
+ {
+ .o_name = NNG_OPT_TLS_VERIFIED,
+ .o_get = tls_get_verified,
+ },
+ {
+ .o_name = NULL,
+ },
+};
+
+static int
+tls_setx(void *arg, const char *name, const void *buf, size_t sz, nni_type t)
+{
+ tls_conn * conn = arg;
+ int rv;
+ nng_stream *tcp;
+
+ tcp = (conn != NULL) ? conn->tcp : NULL;
+
+ if ((rv = nni_stream_setx(tcp, name, buf, sz, t)) != NNG_ENOTSUP) {
+ return (rv);
+ }
+ return (nni_setopt(tls_options, name, conn, buf, sz, t));
+}
+
+static int
+tls_getx(void *arg, const char *name, void *buf, size_t *szp, nni_type t)
+{
+ tls_conn *conn = arg;
+ int rv;
+
+ if ((rv = nni_stream_getx(conn->tcp, name, buf, szp, t)) !=
+ NNG_ENOTSUP) {
+ return (rv);
+ }
+ return (nni_getopt(tls_options, name, conn, buf, szp, t));
+}
+
+static int
+tls_alloc(tls_conn **conn_p, nng_tls_config *cfg, nng_aio *user_aio)
+{
+ tls_conn * conn;
+ const nng_tls_engine *eng;
+ size_t size;
+
+ eng = cfg->engine;
+
+ size = NNI_ALIGN_UP(sizeof(*conn)) + eng->conn_ops->size;
+
+ if ((conn = nni_zalloc(size)) == NULL) {
+ return (NNG_ENOMEM);
+ }
+ if (((conn->tcp_send_buf = nni_alloc(NNG_TLS_MAX_SEND_SIZE)) ==
+ NULL) ||
+ ((conn->tcp_recv_buf = nni_alloc(NNG_TLS_MAX_RECV_SIZE)) ==
+ NULL)) {
+ tls_free(conn);
+ return (NNG_ENOMEM);
+ }
+ conn->size = size;
+ conn->ops = *eng->conn_ops;
+ conn->engine = eng;
+ conn->user_aio = user_aio;
+ conn->cfg = cfg;
+
+ nni_aio_init(&conn->conn_aio, tls_conn_cb, conn);
+ nni_aio_init(&conn->tcp_recv, tls_tcp_recv_cb, conn);
+ nni_aio_init(&conn->tcp_send, tls_tcp_send_cb, conn);
+ nni_aio_list_init(&conn->send_queue);
+ nni_aio_list_init(&conn->recv_queue);
+ nni_mtx_init(&conn->lock);
+ nni_aio_set_timeout(&conn->tcp_send, NNG_DURATION_INFINITE);
+ nni_aio_set_timeout(&conn->tcp_recv, NNG_DURATION_INFINITE);
+
+ conn->stream.s_close = tls_close;
+ conn->stream.s_free = tls_free;
+ conn->stream.s_send = tls_send;
+ conn->stream.s_recv = tls_recv;
+ conn->stream.s_getx = tls_getx;
+ conn->stream.s_setx = tls_setx;
+
+ nng_tls_config_hold(cfg);
+ *conn_p = conn;
+ return (0);
+}
+
+static void
+tls_reap(void *arg)
+{
+ tls_conn *conn = arg;
+
+ // Shut it all down first. We should be freed.
+ if (conn->tcp != NULL) {
+ nng_stream_close(conn->tcp);
+ }
+ nni_aio_stop(&conn->conn_aio);
+ nni_aio_stop(&conn->tcp_send);
+ nni_aio_stop(&conn->tcp_recv);
+
+ conn->ops.fini((void *) (conn + 1));
+ nni_aio_fini(&conn->conn_aio);
+ nni_aio_fini(&conn->tcp_send);
+ nni_aio_fini(&conn->tcp_recv);
+ nng_stream_free(conn->tcp);
+ if (conn->cfg != NULL) {
+ nng_tls_config_free(conn->cfg); // this drops our hold on it
+ }
+ if (conn->tcp_send_buf != NULL) {
+ nni_free(conn->tcp_send_buf, NNG_TLS_MAX_SEND_SIZE);
+ }
+ if (conn->tcp_recv_buf != NULL) {
+ nni_free(conn->tcp_recv_buf, NNG_TLS_MAX_RECV_SIZE);
+ }
+ NNI_FREE_STRUCT(conn);
+}
+
+static void
+tls_free(void *arg)
+{
+ tls_conn *conn = arg;
+
+ nni_reap(&conn->reap, tls_reap, conn);
+}
+
+static int
+tls_start(tls_conn *conn, nng_stream *tcp)
+{
+ int rv;
+
+ conn->tcp = tcp;
+ rv = conn->ops.init(
+ (void *) (conn + 1), conn, (void *) (conn->cfg + 1));
+ return (rv);
+}
+
+static void
+tls_tcp_error(tls_conn *conn, int rv)
+{
+ // An error here is fatal. Shut it all down.
+ nni_aio *aio;
+ nng_stream_close(conn->tcp);
+ nni_aio_close(&conn->tcp_send);
+ nni_aio_close(&conn->tcp_recv);
+ while (((aio = nni_list_first(&conn->send_queue)) != NULL) ||
+ ((aio = nni_list_first(&conn->recv_queue)) != NULL)) {
+ nni_aio_list_remove(aio);
+ nni_aio_finish_error(aio, rv);
+ }
+}
+
+static bool
+tls_do_handshake(tls_conn *conn)
+{
+ int rv;
+ if (conn->hs_done) {
+ return (true);
+ }
+ rv = conn->ops.handshake((void *) (conn + 1));
+ if (rv == NNG_EAGAIN) {
+ // We need more data.
+ return (false);
+ }
+ if (rv == 0) {
+ conn->hs_done = true;
+ return (true);
+ }
+ tls_tcp_error(conn, rv);
+ return (true);
+}
+
+static void
+tls_do_recv(tls_conn *conn)
+{
+ nni_aio *aio;
+
+ while ((aio = nni_list_first(&conn->recv_queue)) != NULL) {
+ uint8_t *buf = NULL;
+ size_t len = 0;
+ nni_iov *iov;
+ unsigned nio;
+ int rv;
+
+ nni_aio_get_iov(aio, &nio, &iov);
+
+ for (unsigned i = 0; i < nio; i++) {
+ if (iov[i].iov_len != 0) {
+ buf = iov[i].iov_buf;
+ len = iov[i].iov_len;
+ break;
+ }
+ }
+ if (len == 0 || buf == NULL) {
+ // Caller has asked to receive "nothing".
+ nni_aio_list_remove(aio);
+ nni_aio_finish_error(aio, NNG_EINVAL);
+ continue;
+ }
+
+ rv = conn->ops.recv((void *) (conn + 1), buf, &len);
+ if (rv == NNG_EAGAIN) {
+ // Nothing more we can do, the engine doesn't
+ // have anything else for us (yet).
+ return;
+ }
+
+ // Unlike the send side, we want to return back to the
+ // caller as *soon* as we have some data.
+ nni_aio_list_remove(aio);
+
+ if (rv != 0) {
+ nni_aio_finish_error(aio, rv);
+ } else {
+ nni_aio_finish(aio, 0, len);
+ }
+ }
+}
+
+// tls_do_send attempts to send user data.
+static void
+tls_do_send(tls_conn *conn)
+{
+ nni_aio *aio;
+
+ while ((aio = nni_list_first(&conn->send_queue)) != NULL) {
+ uint8_t *buf = NULL;
+ size_t len = 0;
+ nni_iov *iov;
+ unsigned nio;
+ int rv;
+
+ nni_aio_get_iov(aio, &nio, &iov);
+
+ for (unsigned i = 0; i < nio; i++) {
+ if (iov[i].iov_len != 0) {
+ buf = iov[i].iov_buf;
+ len = iov[i].iov_len;
+ break;
+ }
+ }
+ if (len == 0 || buf == NULL) {
+ nni_aio_list_remove(aio);
+ // Presumably this means we've completed this
+ // one, lets preserve the count, and move to the
+ // next.
+ nni_aio_finish(aio, 0, nni_aio_count(aio));
+ continue;
+ }
+
+ // Ask the engine to send.
+ rv = conn->ops.send((void *) (conn + 1), buf, &len);
+ if (rv == NNG_EAGAIN) {
+ // Can't send any more, wait for callback.
+ return;
+ }
+
+ if (rv != 0) {
+ nni_aio_list_remove(aio);
+ nni_aio_finish_error(aio, rv);
+ } else {
+ nni_aio_list_remove(aio);
+ nni_aio_finish(aio, 0, len);
+ }
+ }
+}
+
+static void
+tls_tcp_send_cb(void *arg)
+{
+ tls_conn *conn = arg;
+ nng_aio * aio = &conn->tcp_send;
+ int rv;
+ size_t count;
+
+ nni_mtx_lock(&conn->lock);
+ conn->tcp_send_active = false;
+
+ if ((rv = nni_aio_result(aio)) != 0) {
+ tls_tcp_error(conn, rv);
+ nni_mtx_unlock(&conn->lock);
+ return;
+ }
+
+ count = nni_aio_count(aio);
+ NNI_ASSERT(count <= conn->tcp_send_len);
+ conn->tcp_send_len -= count;
+ tls_tcp_send_start(conn);
+
+ if (tls_do_handshake(conn)) {
+ tls_do_send(conn);
+ tls_do_recv(conn);
+ }
+
+ nni_mtx_unlock(&conn->lock);
+}
+
+static void
+tls_tcp_recv_cb(void *arg)
+{
+ tls_conn *conn = arg;
+ nni_aio * aio = &conn->tcp_recv;
+ int rv;
+
+ nni_mtx_lock(&conn->lock);
+
+ conn->tcp_recv_pend = false;
+ if ((rv = nni_aio_result(aio)) != 0) {
+ tls_tcp_error(conn, rv);
+ nni_mtx_unlock(&conn->lock);
+ return;
+ }
+
+ NNI_ASSERT(conn->tcp_recv_len == 0);
+ NNI_ASSERT(conn->tcp_recv_off == 0);
+ conn->tcp_recv_len = nni_aio_count(aio);
+
+ if (tls_do_handshake(conn)) {
+ tls_do_recv(conn);
+ tls_do_send(conn);
+ }
+
+ nni_mtx_unlock(&conn->lock);
+}
+
+static void
+tls_tcp_recv_start(tls_conn *conn)
+{
+ nng_iov iov;
+
+ if (conn->tcp_recv_len != 0) {
+ // We already have data in the buffer.
+ return;
+ }
+ if (conn->tcp_recv_pend) {
+ // Already have a receive in flight.
+ return;
+ }
+ conn->tcp_recv_off = 0;
+ iov.iov_len = NNG_TLS_MAX_RECV_SIZE;
+ iov.iov_buf = conn->tcp_recv_buf;
+
+ conn->tcp_recv_pend = true;
+ nng_aio_set_iov(&conn->tcp_recv, 1, &iov);
+
+ nng_stream_recv(conn->tcp, &conn->tcp_recv);
+}
+
+static void
+tls_tcp_send_start(tls_conn *conn)
+{
+ nni_iov iov[2];
+ unsigned nio = 0;
+ size_t len;
+ size_t tail;
+ size_t head;
+
+ if (conn->tcp_send_active) {
+ return;
+ }
+ if (conn->tcp_send_len == 0) {
+ return;
+ }
+ len = conn->tcp_send_len;
+ head = conn->tcp_send_head;
+ tail = conn->tcp_send_tail;
+
+ while (len > 0) {
+ size_t cnt;
+ if (tail < head) {
+ cnt = head - tail;
+ } else {
+ cnt = NNG_TLS_MAX_SEND_SIZE - tail;
+ }
+ if (cnt > len) {
+ cnt = len;
+ }
+ iov[nio].iov_buf = conn->tcp_send_buf + tail;
+ iov[nio].iov_len = cnt;
+ len -= cnt;
+ tail += cnt;
+ tail %= NNG_TLS_MAX_SEND_SIZE;
+ nio++;
+ }
+ conn->tcp_send_active = true;
+ conn->tcp_send_tail = tail;
+ nni_aio_set_iov(&conn->tcp_send, nio, iov);
+ nng_stream_send(conn->tcp, &conn->tcp_send);
+}
+
+int
+nng_tls_engine_send(void *arg, const uint8_t *buf, size_t *szp)
+{
+ tls_conn *conn = arg;
+ size_t len = *szp;
+ size_t head = conn->tcp_send_head;
+ size_t tail = conn->tcp_send_tail;
+ size_t space;
+ size_t cnt;
+
+ space = NNG_TLS_MAX_SEND_SIZE - conn->tcp_send_len;
+
+ if (space == 0) {
+ return (NNG_EAGAIN);
+ }
+
+ if (conn->closed) {
+ return (NNG_ECLOSED);
+ }
+
+ if (len > space) {
+ len = space;
+ }
+
+ // We are committed at this point to sending out len bytes.
+ // Update this now, so that we can use len to update.
+ *szp = len;
+
+ while (len > 0) {
+ if (head >= tail) {
+ cnt = NNG_TLS_MAX_SEND_SIZE - head;
+ } else {
+ cnt = tail - head;
+ }
+ if (cnt > len) {
+ cnt = len;
+ }
+
+ memcpy(conn->tcp_send_buf + head, buf, cnt);
+ buf += cnt;
+ head += cnt;
+ head %= NNG_TLS_MAX_SEND_SIZE;
+ conn->tcp_send_len += cnt;
+ conn->tcp_send_head = head;
+ len -= cnt;
+ }
+
+ tls_tcp_send_start(conn);
+ return (0);
+}
+
+int
+nng_tls_engine_recv(void *arg, uint8_t *buf, size_t *szp)
+{
+ tls_conn *conn = arg;
+ size_t len = *szp;
+
+ if (conn->closed) {
+ return (NNG_ECLOSED);
+ }
+ if (conn->tcp_recv_len == 0) {
+ tls_tcp_recv_start(conn);
+ return (NNG_EAGAIN);
+ }
+ if (len > conn->tcp_recv_len) {
+ len = conn->tcp_recv_len;
+ }
+ memcpy(buf, conn->tcp_recv_buf + conn->tcp_recv_off, len);
+ conn->tcp_recv_off += len;
+ conn->tcp_recv_len -= len;
+
+ // If we still have data left in the buffer, then the following
+ // call is a no-op.
+ tls_tcp_recv_start(conn);
+
+ *szp = len;
+ return (0);
+}
+
+int
+nng_tls_config_cert_key_file(
+ nng_tls_config *cfg, const char *path, const char *pass)
+{
+ int rv;
+ void * data;
+ size_t size;
+ char * pem;
+
+ if ((rv = nni_file_get(path, &data, &size)) != 0) {
+ return (rv);
+ }
+ if ((pem = nni_zalloc(size + 1)) == NULL) {
+ nni_free(data, size);
+ return (NNG_ENOMEM);
+ }
+ memcpy(pem, data, size);
+ nni_free(data, size);
+ rv = nng_tls_config_own_cert(cfg, pem, pem, pass);
+ nni_free(pem, size + 1);
+ return (rv);
+}
+
+int
+nng_tls_config_ca_file(nng_tls_config *cfg, const char *path)
+{
+ int rv;
+ void * data;
+ size_t size;
+ char * pem;
+
+ if ((rv = nni_file_get(path, &data, &size)) != 0) {
+ return (rv);
+ }
+ if ((pem = nni_zalloc(size + 1)) == NULL) {
+ nni_free(data, size);
+ return (NNG_ENOMEM);
+ }
+ memcpy(pem, data, size);
+ nni_free(data, size);
+ if (strstr(pem, "-----BEGIN X509 CRL-----") != NULL) {
+ rv = nng_tls_config_ca_chain(cfg, pem, pem);
+ } else {
+ rv = nng_tls_config_ca_chain(cfg, pem, NULL);
+ }
+ nni_free(pem, size + 1);
+ return (rv);
+}
+
+int
+nng_tls_config_version(
+ nng_tls_config *cfg, nng_tls_version min_ver, nng_tls_version max_ver)
+{
+ int rv;
+
+ nni_mtx_lock(&cfg->lock);
+ if (cfg->busy != 0) {
+ rv = NNG_EBUSY;
+ } else {
+ rv = cfg->ops.version((void *) (cfg + 1), min_ver, max_ver);
+ }
+ nni_mtx_unlock(&cfg->lock);
+ return (rv);
+}
+
+int
+nng_tls_config_server_name(nng_tls_config *cfg, const char *name)
+{
+ int rv;
+
+ nni_mtx_lock(&cfg->lock);
+ if (cfg->busy != 0) {
+ rv = NNG_EBUSY;
+ } else {
+ rv = cfg->ops.server((void *) (cfg + 1), name);
+ }
+ nni_mtx_unlock(&cfg->lock);
+ return (rv);
+}
+
+int
+nng_tls_config_ca_chain(
+ nng_tls_config *cfg, const char *certs, const char *crl)
+{
+ int rv;
+
+ nni_mtx_lock(&cfg->lock);
+ if (cfg->busy != 0) {
+ rv = NNG_EBUSY;
+ } else {
+ rv = cfg->ops.ca_chain((void *) (cfg + 1), certs, crl);
+ }
+ nni_mtx_unlock(&cfg->lock);
+ return (rv);
+}
+
+int
+nng_tls_config_own_cert(
+ nng_tls_config *cfg, const char *cert, const char *key, const char *pass)
+{
+ int rv;
+ nni_mtx_lock(&cfg->lock);
+ if (cfg->busy != 0) {
+ rv = NNG_EBUSY;
+ } else {
+ rv = cfg->ops.own_cert((void *) (cfg + 1), cert, key, pass);
+ }
+ nni_mtx_unlock(&cfg->lock);
+ return (rv);
+}
+
+int
+nng_tls_config_auth_mode(nng_tls_config *cfg, nng_tls_auth_mode mode)
+{
+ int rv;
+
+ nni_mtx_lock(&cfg->lock);
+ if (cfg->busy != 0) {
+ rv = NNG_EBUSY;
+ } else {
+ rv = cfg->ops.auth((void *) (cfg + 1), mode);
+ }
+ nni_mtx_unlock(&cfg->lock);
+ return (rv);
+}
+
+int
+nng_tls_config_alloc(nng_tls_config **cfg_p, nng_tls_mode mode)
+{
+ nng_tls_config * cfg;
+ const nng_tls_engine *eng;
+ size_t size;
+ int rv;
+
+ if ((rv = nni_init()) != 0) {
+ return (rv);
+ }
+
+ nni_mtx_lock(&tls_engine_lock);
+ eng = tls_engine;
+ nni_mtx_unlock(&tls_engine_lock);
+
+ if (eng == NULL) {
+ return (NNG_ENOTSUP);
+ }
+
+ size = NNI_ALIGN_UP(sizeof(*cfg) + eng->config_ops->size);
+
+ if ((cfg = nni_zalloc(size)) == NULL) {
+ return (NNG_ENOMEM);
+ }
+
+ cfg->ops = *eng->config_ops;
+ cfg->size = size;
+ cfg->engine = eng;
+ cfg->ref = 1;
+ cfg->busy = 0;
+ nni_mtx_init(&cfg->lock);
+
+ if ((rv = cfg->ops.init((void *) (cfg + 1), mode)) != 0) {
+ nni_free(cfg, cfg->size);
+ return (rv);
+ }
+ *cfg_p = cfg;
+ return (0);
+}
+
+void
+nng_tls_config_free(nng_tls_config *cfg)
+{
+ nni_mtx_lock(&cfg->lock);
+ cfg->ref--;
+ if (cfg->ref != 0) {
+ nni_mtx_unlock(&cfg->lock);
+ return;
+ }
+ nni_mtx_unlock(&cfg->lock);
+ nni_mtx_fini(&cfg->lock);
+ cfg->ops.fini((void *) (cfg + 1));
+ nni_free(cfg, cfg->size);
+}
+
+void
+nng_tls_config_hold(nng_tls_config *cfg)
+{
+ nni_mtx_lock(&cfg->lock);
+ cfg->ref++;
+ nni_mtx_unlock(&cfg->lock);
+}
+
+const char *
+nng_tls_engine_name(void)
+{
+ const nng_tls_engine *eng;
+
+ nni_init();
+ nni_mtx_lock(&tls_engine_lock);
+ eng = tls_engine;
+ nni_mtx_unlock(&tls_engine_lock);
+
+ return (eng == NULL ? "none" : eng->name);
+}
+
+const char *
+nng_tls_engine_description(void)
+{
+ const nng_tls_engine *eng;
+
+ nni_init();
+ nni_mtx_lock(&tls_engine_lock);
+ eng = tls_engine;
+ nni_mtx_unlock(&tls_engine_lock);
+
+ return (eng == NULL ? "" : eng->description);
+}
+
+bool
+nng_tls_engine_fips_mode(void)
+{
+ const nng_tls_engine *eng;
+
+ nni_init();
+ nni_mtx_lock(&tls_engine_lock);
+ eng = tls_engine;
+ nni_mtx_unlock(&tls_engine_lock);
+
+ return (eng == NULL ? false : eng->fips_mode);
+}
+
+int
+nng_tls_engine_register(const nng_tls_engine *engine)
+{
+ if (engine->version != NNG_TLS_ENGINE_VERSION) {
+ return (NNG_ENOTSUP);
+ }
+ nni_mtx_lock(&tls_engine_lock);
+ tls_engine = engine;
+ nni_mtx_unlock(&tls_engine_lock);
+ return (0);
+}
+
+#ifdef NNG_TLS_ENGINE_INIT
+extern int NNG_TLS_ENGINE_INIT(void);
+#else
+static int
+NNI_TLS_ENGINE_INIT(void)
+{
+ return (0);
+}
+#endif
+
+#ifdef NNG_TLS_ENGINE_FINI
+extern void NNG_TLS_ENGINE_FINI(void);
+#else
+static void
+NNG_TLS_ENGINE_FINI(void)
+{
+}
+#endif
+
+int
+nni_tls_sys_init(void)
+{
+ int rv;
+ nni_mtx_init(&tls_engine_lock);
+ tls_engine = NULL;
+
+ rv = NNG_TLS_ENGINE_INIT();
+ if (rv != 0) {
+ nni_mtx_fini(&tls_engine_lock);
+ return (rv);
+ }
+ return (0);
+}
+
+void
+nni_tls_sys_fini(void)
+{
+ NNG_TLS_ENGINE_FINI();
+}
+
+#else // NNG_SUPP_TLS
+
+// Provide stubs for the case where TLS is not enabled.
+void
+nni_tls_config_fini(nng_tls_config *cfg)
+{
+ NNI_ARG_UNUSED(cfg);
+}
+
+int
+nni_tls_config_init(nng_tls_config **cpp, enum nng_tls_mode mode)
+{
+ NNI_ARG_UNUSED(cpp);
+ NNI_ARG_UNUSED(mode);
+ return (NNG_ENOTSUP);
+}
+
+void
+nni_tls_config_hold(nng_tls_config *cfg)
+{
+ NNI_ARG_UNUSED(cfg);
+}
+
+int
+nng_tls_config_server_name(nng_tls_config *cfg, const char *name)
+{
+ NNI_ARG_UNUSED(cfg);
+ NNI_ARG_UNUSED(name);
+ return (NNG_ENOTSUP);
+}
+
+int
+nng_tls_config_auth_mode(nng_tls_config *cfg, nng_tls_auth_mode mode)
+{
+ NNI_ARG_UNUSED(cfg);
+ NNI_ARG_UNUSED(mode);
+ return (NNG_ENOTSUP);
+}
+
+int
+nng_tls_config_ca_chain(
+ nng_tls_config *cfg, const char *certs, const char *crl)
+{
+ NNI_ARG_UNUSED(cfg);
+ NNI_ARG_UNUSED(certs);
+ NNI_ARG_UNUSED(crl);
+ return (NNG_ENOTSUP);
+}
+
+int
+nng_tls_config_own_cert(
+ nng_tls_config *cfg, const char *cert, const char *key, const char *pass)
+{
+ NNI_ARG_UNUSED(cfg);
+ NNI_ARG_UNUSED(cert);
+ NNI_ARG_UNUSED(key);
+ NNI_ARG_UNUSED(pass);
+ return (NNG_ENOTSUP);
+}
+
+int
+nng_tls_config_ca_file(nng_tls_config *cfg, const char *path)
+{
+ NNI_ARG_UNUSED(cfg);
+ NNI_ARG_UNUSED(path);
+ return (NNG_ENOTSUP);
+}
+
+int
+nng_tls_config_cert_key_file(
+ nng_tls_config *cfg, const char *path, const char *pass)
+{
+ NNI_ARG_UNUSED(cfg);
+ NNI_ARG_UNUSED(path);
+ NNI_ARG_UNUSED(pass);
+ return (NNG_ENOTSUP);
+}
+
+int
+nng_tls_config_key(nng_tls_config *cfg, const uint8_t *key, size_t size)
+{
+ NNI_ARG_UNUSED(cfg);
+ NNI_ARG_UNUSED(key);
+ NNI_ARG_UNUSED(size);
+ return (NNG_ENOTSUP);
+}
+
+int
+nng_tls_config_pass(nng_tls_config *cfg, const char *pass)
+{
+ NNI_ARG_UNUSED(cfg);
+ NNI_ARG_UNUSED(pass);
+ return (NNG_ENOTSUP);
+}
+
+int
+nng_tls_config_alloc(nng_tls_config **cfgp, nng_tls_mode mode)
+{
+
+ NNI_ARG_UNUSED(cfgp);
+ NNI_ARG_UNUSED(mode);
+ return (NNG_ENOTSUP);
+}
+
+void
+nng_tls_config_free(nng_tls_config *cfg)
+{
+ NNI_ARG_UNUSED(cfg);
+}
+
+int
+nng_tls_config_version(
+ nng_tls_config *cfg, nng_tls_version min_ver, nng_tls_version max_ver)
+{
+ NNI_ARG_UNUSED(cfg);
+ NNI_ARG_UNUSED(min_ver);
+ NNI_ARG_UNUSED(max_ver);
+ return (NNG_ENOTSUP);
+}
+
+int
+nni_tls_dialer_alloc(nng_stream_dialer **dp, const nng_url *url)
+{
+ NNI_ARG_UNUSED(dp);
+ NNI_ARG_UNUSED(url);
+ return (NNG_ENOTSUP);
+}
+
+int
+nni_tls_listener_alloc(nng_stream_listener **lp, const nng_url *url)
+{
+ NNI_ARG_UNUSED(lp);
+ NNI_ARG_UNUSED(url);
+ return (NNG_ENOTSUP);
+}
+
+int
+nni_tls_checkopt(const char *nm, const void *buf, size_t sz, nni_type t)
+{
+ NNI_ARG_UNUSED(nm);
+ NNI_ARG_UNUSED(buf);
+ NNI_ARG_UNUSED(sz);
+ NNI_ARG_UNUSED(t);
+ return (NNG_ENOTSUP);
+}
+
+const char *
+nng_tls_engine_name(void)
+{
+ return ("none");
+}
+
+const char *
+nng_tls_engine_description(void)
+{
+ return ("");
+}
+
+bool
+nng_tls_engine_fips_mode(void)
+{
+ return (false);
+}
+
+int
+nng_tls_engine_register(const nng_tls_engine *engine)
+{
+ NNI_ARG_UNUSED(engine);
+ return (NNG_ENOTSUP);
+}
+
+int
+nni_tls_sys_init(void)
+{
+ return (0);
+}
+
+void
+nni_tls_sys_fini(void)
+{
+}
+
+#endif // !NNG_SUPP_TLS \ No newline at end of file
diff --git a/src/supplemental/tls/tls_test.c b/src/supplemental/tls/tls_test.c
new file mode 100644
index 00000000..bd96ae08
--- /dev/null
+++ b/src/supplemental/tls/tls_test.c
@@ -0,0 +1,59 @@
+//
+// Copyright 2020 Staysail Systems, Inc. <info@staysail.tech>
+//
+// 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 <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <nng/nng.h>
+#include <nng/supplemental/tls/tls.h>
+#include <testutil.h>
+
+#include <acutest.h>
+
+void
+test_tls_config_version(void)
+{
+ nng_tls_config *cfg;
+
+ TEST_NNG_PASS(nng_tls_config_alloc(&cfg, NNG_TLS_MODE_SERVER));
+
+ // Verify that min ver < max ver
+ TEST_NNG_FAIL(nng_tls_config_version(cfg, NNG_TLS_1_3, NNG_TLS_1_0),
+ NNG_ENOTSUP);
+
+ // Verify that we cannot configure SSL 3.0 or older.
+ TEST_NNG_FAIL(
+ nng_tls_config_version(cfg, NNG_TLS_1_0 - 1, NNG_TLS_1_0),
+ NNG_ENOTSUP);
+
+ // Verify that we cannot configure TLS > 1.3.
+ TEST_NNG_FAIL(
+ nng_tls_config_version(cfg, NNG_TLS_1_0, NNG_TLS_1_3 + 1),
+ NNG_ENOTSUP);
+
+ // Verify that we *can* configure some various ranges.
+ TEST_NNG_PASS(nng_tls_config_version(cfg, NNG_TLS_1_0, NNG_TLS_1_0));
+ TEST_NNG_PASS(nng_tls_config_version(cfg, NNG_TLS_1_0, NNG_TLS_1_1));
+ TEST_NNG_PASS(nng_tls_config_version(cfg, NNG_TLS_1_0, NNG_TLS_1_2));
+ TEST_NNG_PASS(nng_tls_config_version(cfg, NNG_TLS_1_0, NNG_TLS_1_3));
+ TEST_NNG_PASS(nng_tls_config_version(cfg, NNG_TLS_1_1, NNG_TLS_1_1));
+ TEST_NNG_PASS(nng_tls_config_version(cfg, NNG_TLS_1_1, NNG_TLS_1_2));
+ TEST_NNG_PASS(nng_tls_config_version(cfg, NNG_TLS_1_1, NNG_TLS_1_3));
+ TEST_NNG_PASS(nng_tls_config_version(cfg, NNG_TLS_1_2, NNG_TLS_1_2));
+ TEST_NNG_PASS(nng_tls_config_version(cfg, NNG_TLS_1_2, NNG_TLS_1_3));
+
+ nng_tls_config_free(cfg);
+}
+
+TEST_LIST = {
+ { "tls config version", test_tls_config_version },
+ { NULL, NULL },
+}; \ No newline at end of file
diff --git a/src/supplemental/tls/wolfssl/CMakeLists.txt b/src/supplemental/tls/wolfssl/CMakeLists.txt
new file mode 100644
index 00000000..f0b48d9a
--- /dev/null
+++ b/src/supplemental/tls/wolfssl/CMakeLists.txt
@@ -0,0 +1,26 @@
+#
+# Copyright 2020 Staysail Systems, Inc. <info@staysail.tech>
+#
+# 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.
+#
+
+# Enabling wolfSSL requires that the extern/nng-wolfssl submodule
+# be populated. Note also that this action changes the licensing
+# restrictions around the resulting library!
+
+if (NNG_TLS_ENGINE STREQUAL "wolf")
+ add_subdirectory(${PROJECT_SOURCE_DIR}/extern/nng-wolfssl nng-wolfssl)
+ target_include_directories(nng-wolfssl PRIVATE )
+ target_link_libraries(nng PRIVATE nng-wolfssl)
+ if (TARGET nng_testlib)
+ target_link_libraries(nng_testlib PRIVATE nng-wolfssl)
+ endif ()
+
+ nng_defines(NNG_TLS_ENGINE_INIT=nng_tls_engine_init_wolf)
+ nng_defines(NNG_TLS_ENGINE_FINI=nng_tls_engine_fini_wolf)
+ nng_defines(NNG_SUPP_TLS)
+
+endif ()