From 6a50035b242b972c1d9b659ba63e037a0a8afe71 Mon Sep 17 00:00:00 2001 From: Garrett D'Amore Date: Fri, 29 Dec 2017 14:21:20 -0800 Subject: fixes #166 Websocket TLS mapping This introduces the wss:// scheme, which is available and works like the ws:// scheme if TLS is enabled in the library. The library modularization is refactored somewhat, to make it easier to use. There is now a single NNG_ENABLE_TLS that enables TLS support under the hood. This also adds a new option for the TLS transport, NNG_OPT_TLS_CONFIG (and a similar one for WSS, NNG_OPT_TLS_WSS_CONFIG) that offer access to the underlying TLS configuration object, which now has a public API to go with it as well. Note that it is also possible to use pure HTTPS using the *private* API, which will be exposed in a public form soon. --- src/CMakeLists.txt | 2 +- src/core/options.c | 13 + src/core/options.h | 3 + src/core/transport.c | 31 +- src/nng.c | 39 ++ src/nng.h | 73 +++ src/supplemental/http/client.c | 45 +- src/supplemental/http/http.c | 84 ++- src/supplemental/http/http.h | 23 +- src/supplemental/http/server.c | 115 ++-- src/supplemental/mbedtls/CMakeLists.txt | 54 -- src/supplemental/mbedtls/tls.c | 1031 ----------------------------- src/supplemental/tls.h | 84 --- src/supplemental/tls/CMakeLists.txt | 38 ++ src/supplemental/tls/mbedtls/tls.c | 1067 +++++++++++++++++++++++++++++++ src/supplemental/tls/tls.h | 45 ++ src/supplemental/websocket/websocket.c | 86 ++- src/supplemental/websocket/websocket.h | 8 +- src/transport/tls/tls.c | 107 ++-- src/transport/tls/tls.h | 6 +- src/transport/ws/websocket.c | 132 +++- src/transport/ws/websocket.h | 22 +- src/transport/zerotier/CMakeLists.txt | 1 + src/transport/zerotier/zerotier.c | 3 - 24 files changed, 1767 insertions(+), 1345 deletions(-) delete mode 100644 src/supplemental/mbedtls/CMakeLists.txt delete mode 100644 src/supplemental/mbedtls/tls.c delete mode 100644 src/supplemental/tls.h create mode 100644 src/supplemental/tls/CMakeLists.txt create mode 100644 src/supplemental/tls/mbedtls/tls.c create mode 100644 src/supplemental/tls/tls.h (limited to 'src') diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 6fae89e0..45dcb4fe 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -123,8 +123,8 @@ endif() add_subdirectory(supplemental/base64) add_subdirectory(supplemental/http) -add_subdirectory(supplemental/mbedtls) add_subdirectory(supplemental/sha1) +add_subdirectory(supplemental/tls) add_subdirectory(supplemental/websocket) add_subdirectory(protocol/bus0) diff --git a/src/core/options.c b/src/core/options.c index 1417d0b3..ef7420d6 100644 --- a/src/core/options.c +++ b/src/core/options.c @@ -186,6 +186,19 @@ nni_getopt_size(size_t u, void *val, size_t *sizep) return (0); } +int +nni_getopt_ptr(void *ptr, void *val, size_t *sizep) +{ + size_t sz = sizeof(ptr); + + if (sz > *sizep) { + sz = *sizep; + } + *sizep = sizeof(ptr); + memcpy(val, &ptr, sz); + return (0); +} + int nni_setopt_buf(nni_msgq *mq, const void *val, size_t sz) { diff --git a/src/core/options.h b/src/core/options.h index d373851f..e9aa16dd 100644 --- a/src/core/options.h +++ b/src/core/options.h @@ -58,6 +58,9 @@ extern int nni_setopt_size(size_t *, const void *, size_t, size_t, size_t); // nni_getopt_size obtains a size_t option. extern int nni_getopt_size(size_t, void *, size_t *); +// nni_getopt_ptr obtains a pointer option. +extern int nni_getopt_ptr(void *, void *, size_t *); + extern int nni_chkopt_ms(const void *, size_t); extern int nni_chkopt_int(const void *, size_t, int, int); extern int nni_chkopt_size(const void *, size_t, size_t, size_t); diff --git a/src/core/transport.c b/src/core/transport.c index 31da773f..9c129a72 100644 --- a/src/core/transport.c +++ b/src/core/transport.c @@ -56,12 +56,12 @@ nni_tran_register(const nni_tran *tran) nni_mtx_lock(&nni_tran_lk); // Check to see if the transport is already registered... NNI_LIST_FOREACH (&nni_tran_list, t) { - if (tran->tran_init == t->t_tran.tran_init) { - nni_mtx_unlock(&nni_tran_lk); - // Same transport, duplicate registration. - return (0); - } if (strcmp(tran->tran_scheme, t->t_tran.tran_scheme) == 0) { + if (tran->tran_init == t->t_tran.tran_init) { + // duplicate. + nni_mtx_unlock(&nni_tran_lk); + return (0); + } nni_mtx_unlock(&nni_tran_lk); return (NNG_ESTATE); } @@ -208,24 +208,29 @@ nni_tran_chkopt(const char *name, const void *v, size_t sz) typedef int (*nni_tran_ctor)(void); +// These are just the statically compiled in constructors. +// In the future we might want to support dynamic additions. static nni_tran_ctor nni_tran_ctors[] = { -#ifdef NNG_HAVE_INPROC +#ifdef NNG_TRANSPORT_INPROC nng_inproc_register, #endif -#ifdef NNG_HAVE_IPC +#ifdef NNG_TRANSPORT_IPC nng_ipc_register, #endif -#ifdef NNG_HAVE_TCP +#ifdef NNG_TRANSPORT_TCP nng_tcp_register, #endif -#ifdef NNG_HAVE_TLS +#ifdef NNG_TRANSPORT_TLS nng_tls_register, #endif -#ifdef NNG_HAVE_ZEROTIER - nng_zt_register, -#endif -#ifdef NNG_HAVE_WEBSOCKET +#ifdef NNG_TRANSPORT_WS nng_ws_register, +#endif +#ifdef NNG_TRANSPORT_WSS + nng_wss_register, +#endif +#ifdef NNG_TRANSPORT_ZEROTIER + nng_zt_register, #endif NULL, }; diff --git a/src/nng.c b/src/nng.c index 6cd78e1d..67eac2c4 100644 --- a/src/nng.c +++ b/src/nng.c @@ -404,6 +404,12 @@ nng_dialer_setopt_uint64(nng_dialer id, const char *name, uint64_t val) return (nng_dialer_setopt(id, name, &val, sizeof(val))); } +int +nng_dialer_setopt_ptr(nng_dialer id, const char *name, void *val) +{ + return (nng_dialer_setopt(id, name, &val, sizeof(val))); +} + int nng_dialer_getopt(nng_dialer id, const char *name, void *val, size_t *szp) { @@ -431,6 +437,13 @@ nng_dialer_getopt_uint64(nng_dialer id, const char *name, uint64_t *valp) return (nng_dialer_getopt(id, name, valp, &sz)); } +int +nng_dialer_getopt_ptr(nng_dialer id, const char *name, void **valp) +{ + size_t sz = sizeof(*valp); + return (nng_dialer_getopt(id, name, valp, &sz)); +} + int nng_dialer_getopt_ms(nng_dialer id, const char *name, nng_duration *valp) { @@ -469,6 +482,12 @@ nng_listener_setopt_uint64(nng_listener id, const char *name, uint64_t val) return (nng_listener_setopt(id, name, &val, sizeof(val))); } +int +nng_listener_setopt_ptr(nng_listener id, const char *name, void *val) +{ + return (nng_listener_setopt(id, name, &val, sizeof(val))); +} + int nng_listener_getopt(nng_listener id, const char *name, void *val, size_t *szp) { @@ -496,6 +515,13 @@ nng_listener_getopt_uint64(nng_listener id, const char *name, uint64_t *valp) return (nng_listener_getopt(id, name, valp, &sz)); } +int +nng_listener_getopt_ptr(nng_listener id, const char *name, void **valp) +{ + size_t sz = sizeof(*valp); + return (nng_listener_getopt(id, name, valp, &sz)); +} + int nng_listener_getopt_ms(nng_listener id, const char *name, nng_duration *valp) { @@ -587,6 +613,12 @@ nng_setopt_uint64(nng_socket sid, const char *name, uint64_t val) return (nng_setopt(sid, name, &val, sizeof(val))); } +int +nng_setopt_ptr(nng_socket sid, const char *name, void *val) +{ + return (nng_setopt(sid, name, &val, sizeof(val))); +} + int nng_getopt_int(nng_socket sid, const char *name, int *valp) { @@ -615,6 +647,13 @@ nng_getopt_ms(nng_socket sid, const char *name, nng_duration *valp) return (nng_getopt(sid, name, valp, &sz)); } +int +nng_getopt_ptr(nng_socket sid, const char *name, void **valp) +{ + size_t sz = sizeof(*valp); + return (nng_getopt(sid, name, valp, &sz)); +} + int nng_device(nng_socket s1, nng_socket s2) { diff --git a/src/nng.h b/src/nng.h index 6d375509..f42bc1a5 100644 --- a/src/nng.h +++ b/src/nng.h @@ -21,6 +21,7 @@ extern "C" { #endif +#include #include #include @@ -90,6 +91,7 @@ NNG_DECL int nng_getopt_int(nng_socket, const char *, int *); NNG_DECL int nng_getopt_ms(nng_socket, const char *, nng_duration *); NNG_DECL int nng_getopt_size(nng_socket, const char *, size_t *); NNG_DECL int nng_getopt_uint64(nng_socket, const char *, uint64_t *); +NNG_DECL int nng_getopt_ptr(nng_socket, const char *, void **); // nng_listen creates a listening endpoint with no special options, // and starts it listening. It is functionally equivalent to the legacy @@ -138,6 +140,7 @@ NNG_DECL int nng_dialer_setopt_int(nng_dialer, const char *, int); NNG_DECL int nng_dialer_setopt_ms(nng_dialer, const char *, nng_duration); NNG_DECL int nng_dialer_setopt_size(nng_dialer, const char *, size_t); NNG_DECL int nng_dialer_setopt_uint64(nng_dialer, const char *, uint64_t); +NNG_DECL int nng_dialer_setopt_ptr(nng_dialer, const char *, void *); // nng_dialer_getopt obtains the option for a dialer. This will // fail for options that a particular dialer is not interested in, @@ -147,6 +150,7 @@ NNG_DECL int nng_dialer_getopt_int(nng_dialer, const char *, int *); NNG_DECL int nng_dialer_getopt_ms(nng_dialer, const char *, nng_duration *); NNG_DECL int nng_dialer_getopt_size(nng_dialer, const char *, size_t *); NNG_DECL int nng_dialer_getopt_uint64(nng_dialer, const char *, uint64_t *); +NNG_DECL int nng_dialer_getopt_ptr(nng_dialer, const char *, void **); // nng_listener_setopt sets an option for a dialer. This value is // not stored in the socket. Subsequent setopts on the socket may @@ -158,6 +162,7 @@ NNG_DECL int nng_listener_setopt_int(nng_listener, const char *, int); NNG_DECL int nng_listener_setopt_ms(nng_listener, const char *, nng_duration); NNG_DECL int nng_listener_setopt_size(nng_listener, const char *, size_t); NNG_DECL int nng_listener_setopt_uint64(nng_listener, const char *, uint64_t); +NNG_DECL int nng_listener_setopt_ptr(nng_listener, const char *, void *); // nng_listener_getopt obtains the option for a listener. This will // fail for options that a particular listener is not interested in, @@ -169,6 +174,7 @@ NNG_DECL int nng_listener_getopt_ms( NNG_DECL int nng_listener_getopt_size(nng_listener, const char *, size_t *); NNG_DECL int nng_listener_getopt_uint64( nng_listener, const char *, uint64_t *); +NNG_DECL int nng_listener_getopt_ptr(nng_listener, const char *, void **); // nng_strerror returns a human readable string associated with the error // code supplied. @@ -574,6 +580,73 @@ enum nng_sockaddr_family { NNG_AF_ZT = 5, // ZeroTier }; +// For some transports, we need TLS configuration. This +// section lets us work with TLS configurations. Note +// that these symbols are only actually present at link time +// if TLS support is enabled in your build. Note also that +// a TLS configuration cannot be changed once it is in use. +typedef struct nng_tls_config nng_tls_config; + +typedef enum nng_tls_mode { + NNG_TLS_MODE_CLIENT = 0, + NNG_TLS_MODE_SERVER = 1, +} nng_tls_mode; + +typedef enum nng_tls_auth_mode { + NNG_TLS_AUTH_MODE_NONE = 0, // No verification is performed + NNG_TLS_AUTH_MODE_OPTIONAL = 1, // Verify cert if presented + NNG_TLS_AUTH_MODE_REQUIRED = 2, // Verify cert, close if invalid +} nng_tls_auth_mode; + +// nng_tls_config init creates a TLS configuration using +// reasonable defaults. This configuration can be shared +// with multiple pipes or services/servers. +NNG_DECL int nng_tls_config_init(nng_tls_config **, nng_tls_mode); + +NNG_DECL void nng_tls_config_fini(nng_tls_config *); + +// nng_tls_config_server_name sets the server name. This is +// called by clients to set the name that the server supplied +// certificate should be matched against. This can also cause +// the SNI to be sent to the server to tell it which cert to +// use if it supports more than one. +NNG_DECL int nng_tls_config_server_name(nng_tls_config *, const char *); + +// nng_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. +NNG_DECL int nng_tls_config_ca_cert(nng_tls_config *, const uint8_t *, size_t); + +// nng_tls_config_clr loads a certificate revocation list. Again, these +// are in X.509 format (either PEM or DER). +NNG_DECL int nng_tls_config_crl(nng_tls_config *, const uint8_t *, size_t); + +// nng_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. +NNG_DECL int nng_tls_config_cert(nng_tls_config *, const uint8_t *, size_t); + +// nng_tls_config_key is used to pass our own private key. +NNG_DECL int nng_tls_config_key(nng_tls_config *, const uint8_t *, size_t); + +// nng_tls_config_pass is used to pass a password used to decrypt +// private keys that are encrypted. +NNG_DECL int nng_tls_config_pass(nng_tls_config *, const char *); + +// nng_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. +NNG_DECL int nng_tls_config_validate_peer(nng_tls_config *, bool); + +// nng_tls_config_auth_mode 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. +NNG_DECL int nng_tls_config_auth_mode(nng_tls_config *, nng_tls_auth_mode); + #ifdef __cplusplus } #endif diff --git a/src/supplemental/http/client.c b/src/supplemental/http/client.c index 8c082a17..4bb517ce 100644 --- a/src/supplemental/http/client.c +++ b/src/supplemental/http/client.c @@ -14,6 +14,8 @@ #include #include "core/nng_impl.h" +#include "supplemental/tls/tls.h" + #include "http.h" struct nni_http_client { @@ -21,7 +23,7 @@ struct nni_http_client { nni_list aios; nni_mtx mtx; bool closed; - bool tls; + nng_tls_config * tls; nni_aio * connaio; nni_plat_tcp_ep *tep; }; @@ -39,7 +41,6 @@ http_conn_done(void *arg) nni_aio * aio; int rv; nni_plat_tcp_pipe *p; - nni_http_tran t; nni_http * http; nni_mtx_lock(&c->mtx); @@ -60,17 +61,13 @@ http_conn_done(void *arg) return; } - t.h_data = p; - t.h_write = (void *) nni_plat_tcp_pipe_send; - t.h_read = (void *) nni_plat_tcp_pipe_recv; - t.h_close = (void *) nni_plat_tcp_pipe_close; - t.h_sock_addr = (void *) nni_plat_tcp_pipe_sockname; - t.h_peer_addr = (void *) nni_plat_tcp_pipe_peername; - t.h_fini = (void *) nni_plat_tcp_pipe_fini; - - if ((rv = nni_http_init(&http, &t)) != 0) { + if (c->tls != NULL) { + rv = nni_http_init_tls(&http, c->tls, p); + } else { + rv = nni_http_init_tcp(&http, p); + } + if (rv != 0) { nni_aio_finish_error(aio, rv); - nni_plat_tcp_pipe_fini(p); nni_mtx_unlock(&c->mtx); return; } @@ -90,6 +87,11 @@ nni_http_client_fini(nni_http_client *c) nni_aio_fini(c->connaio); nni_plat_tcp_ep_fini(c->tep); nni_mtx_fini(&c->mtx); +#ifdef NNG_SUPP_TLS + if (c->tls != NULL) { + nng_tls_config_fini(c->tls); + } +#endif NNI_FREE_STRUCT(c); } @@ -119,6 +121,25 @@ nni_http_client_init(nni_http_client **cp, nng_sockaddr *sa) return (0); } +#ifdef NNG_SUPP_TLS +int +nni_http_client_set_tls(nni_http_client *c, nng_tls_config *tls) +{ + nng_tls_config *old; + nni_mtx_lock(&c->mtx); + old = c->tls; + c->tls = tls; + if (tls != NULL) { + nni_tls_config_hold(tls); + } + nni_mtx_unlock(&c->mtx); + if (old != NULL) { + nng_tls_config_fini(old); + } + return (0); +} +#endif + static void http_connect_cancel(nni_aio *aio, int rv) { diff --git a/src/supplemental/http/http.c b/src/supplemental/http/http.c index f414c5c7..df5c7588 100644 --- a/src/supplemental/http/http.c +++ b/src/supplemental/http/http.c @@ -12,6 +12,8 @@ #include #include "core/nng_impl.h" +#include "supplemental/tls/tls.h" + #include "http.h" // We insist that individual headers fit in 8K. @@ -33,6 +35,15 @@ enum write_flavor { HTTP_WR_RES, }; +typedef struct nni_http_tran { + void (*h_read)(void *, nni_aio *); + void (*h_write)(void *, nni_aio *); + int (*h_sock_addr)(void *, nni_sockaddr *); + int (*h_peer_addr)(void *, nni_sockaddr *); + void (*h_close)(void *); + void (*h_fini)(void *); +} nni_http_tran; + #define SET_RD_FLAVOR(aio, f) (aio)->a_prov_extra[0] = ((void *) (intptr_t)(f)) #define GET_RD_FLAVOR(aio) (int) ((intptr_t) aio->a_prov_extra[0]) #define SET_WR_FLAVOR(aio, f) (aio)->a_prov_extra[0] = ((void *) (intptr_t)(f)) @@ -76,14 +87,17 @@ http_close(nni_http *http) http->closed = true; if (nni_list_first(&http->wrq)) { nni_aio_cancel(http->wr_aio, NNG_ECLOSED); - while ((aio = nni_list_first(&http->wrq)) != NULL) { + // Abort all operations except the one in flight. + while ((aio = nni_list_last(&http->wrq)) != + nni_list_first(&http->wrq)) { nni_aio_list_remove(aio); nni_aio_finish_error(aio, NNG_ECLOSED); } } if (nni_list_first(&http->rdq)) { nni_aio_cancel(http->rd_aio, NNG_ECLOSED); - while ((aio = nni_list_first(&http->rdq)) != NULL) { + while ((aio = nni_list_last(&http->rdq)) != + nni_list_first(&http->rdq)) { nni_aio_list_remove(aio); nni_aio_finish_error(aio, NNG_ECLOSED); } @@ -591,8 +605,8 @@ nni_http_fini(nni_http *http) NNI_FREE_STRUCT(http); } -int -nni_http_init(nni_http **httpp, nni_http_tran *tran) +static int +http_init(nni_http **httpp, nni_http_tran *tran, void *data) { nni_http *http; int rv; @@ -609,21 +623,71 @@ nni_http_init(nni_http **httpp, nni_http_tran *tran) nni_aio_list_init(&http->rdq); nni_aio_list_init(&http->wrq); - if (((rv = nni_aio_init(&http->wr_aio, http_wr_cb, http)) != 0) || - ((rv = nni_aio_init(&http->rd_aio, http_rd_cb, http)) != 0)) { - nni_http_fini(http); - return (rv); - } + http->sock = data; http->rd_bufsz = HTTP_BUFSIZE; http->rd = tran->h_read; http->wr = tran->h_write; http->close = tran->h_close; http->fini = tran->h_fini; - http->sock = tran->h_data; http->sock_addr = tran->h_sock_addr; http->peer_addr = tran->h_peer_addr; + if (((rv = nni_aio_init(&http->wr_aio, http_wr_cb, http)) != 0) || + ((rv = nni_aio_init(&http->rd_aio, http_rd_cb, http)) != 0)) { + nni_http_fini(http); + return (rv); + } + *httpp = http; return (0); } + +static nni_http_tran http_tcp_ops = { + .h_read = (void *) nni_plat_tcp_pipe_recv, + .h_write = (void *) nni_plat_tcp_pipe_send, + .h_close = (void *) nni_plat_tcp_pipe_close, + .h_fini = (void *) nni_plat_tcp_pipe_fini, + .h_sock_addr = (void *) nni_plat_tcp_pipe_sockname, + .h_peer_addr = (void *) nni_plat_tcp_pipe_peername, +}; + +int +nni_http_init_tcp(nni_http **hpp, void *tcp) +{ + return (http_init(hpp, &http_tcp_ops, tcp)); +} + +#ifdef NNG_SUPP_TLS +static nni_http_tran http_tls_ops = { + .h_read = (void *) nni_tls_recv, + .h_write = (void *) nni_tls_send, + .h_close = (void *) nni_tls_close, + .h_fini = (void *) nni_tls_fini, + .h_sock_addr = (void *) nni_tls_sockname, + .h_peer_addr = (void *) nni_tls_peername, +}; + +int +nni_http_init_tls(nni_http **hpp, nng_tls_config *cfg, void *tcp) +{ + nni_tls *tls; + int rv; + + if ((rv = nni_tls_init(&tls, cfg, tcp)) != 0) { + nni_plat_tcp_pipe_fini(tcp); + return (rv); + } + + return (http_init(hpp, &http_tls_ops, tls)); +} +#else +int +nni_http_init_tls(nni_http **hpp, nng_tls_config *cfg, void *tcp) +{ + NNI_ARG_UNUSED(hpp); + NNI_ARG_UNUSED(cfg); + nni_plat_tcp_pipe_fini(tcp); + return (NNG_ENOTSUP); +} +#endif // NNG_SUPP_TLS \ No newline at end of file diff --git a/src/supplemental/http/http.h b/src/supplemental/http/http.h index 3ecce4c8..d817ba37 100644 --- a/src/supplemental/http/http.h +++ b/src/supplemental/http/http.h @@ -18,16 +18,6 @@ typedef struct nni_http_msg nni_http_msg; typedef struct nni_http_res nni_http_res; typedef struct nni_http_entity nni_http_entity; -typedef struct nni_http_tran { - void *h_data; - void (*h_read)(void *, nni_aio *); - void (*h_write)(void *, nni_aio *); - int (*h_sock_addr)(void *, nni_sockaddr *); - int (*h_peer_addr)(void *, nni_sockaddr *); - void (*h_close)(void *); - void (*h_fini)(void *); -} nni_http_tran; - typedef struct nni_http_req nni_http_req; extern int nni_http_req_init(nni_http_req **); @@ -146,7 +136,8 @@ enum { NNI_HTTP_STATUS_CONTINUE = 100, // the connection. typedef struct nni_http nni_http; -extern int nni_http_init(nni_http **, nni_http_tran *); +extern int nni_http_init_tcp(nni_http **, void *); +extern int nni_http_init_tls(nni_http **, nng_tls_config *, void *); extern void nni_http_close(nni_http *); extern void nni_http_fini(nni_http *); @@ -254,6 +245,11 @@ extern int nni_http_server_add_handler( extern void nni_http_server_del_handler(nni_http_server *, void *); +// nni_http_server_set_tls adds a TLS configuration to the server, +// and enables the use of it. This returns NNG_EBUSY if the server is +// already started. +extern int nni_http_server_set_tls(nni_http_server *, nng_tls_config *); + // nni_http_server_start starts listening on the supplied port. extern int nni_http_server_start(nni_http_server *); @@ -281,16 +277,13 @@ extern int nni_http_server_add_static(nni_http_server *, const char *host, extern int nni_http_server_add_file(nni_http_server *, const char *host, const char *ctype, const char *uri, const char *path); -// TLS will use -// extern int nni_http_server_start_tls(nni_http_server *, nng_sockaddr *, -// nni_tls_config *); - // Client stuff. typedef struct nni_http_client nni_http_client; extern int nni_http_client_init(nni_http_client **, nng_sockaddr *); extern void nni_http_client_fini(nni_http_client *); +extern int nni_http_client_set_tls(nni_http_client *, nng_tls_config *); extern void nni_http_client_connect(nni_http_client *, nni_aio *); #endif // NNG_SUPPLEMENTAL_HTTP_HTTP_H diff --git a/src/supplemental/http/server.c b/src/supplemental/http/server.c index ea7f15ce..ba74a138 100644 --- a/src/supplemental/http/server.c +++ b/src/supplemental/http/server.c @@ -14,6 +14,8 @@ #include #include "core/nng_impl.h" +#include "supplemental/tls/tls.h" + #include "http.h" static int http_server_sys_init(void); @@ -50,7 +52,6 @@ typedef struct http_sconn { nni_aio * rxaio; nni_aio * txaio; nni_aio * txdataio; - nni_http_tran tran; nni_reap_item reap; } http_sconn; @@ -64,7 +65,7 @@ struct nni_http_server { nni_mtx mtx; nni_cv cv; bool closed; - bool tls; + nng_tls_config * tls; nni_aio * accaio; nni_plat_tcp_ep *tep; }; @@ -105,28 +106,48 @@ http_sconn_fini(http_sconn *sc) } static void -http_sconn_close(http_sconn *sc) +http_sconn_close_locked(http_sconn *sc) { nni_http_server *s; s = sc->server; + nni_http *h; + if (sc->closed) { + return; + } NNI_ASSERT(!sc->finished); - nni_mtx_lock(&s->mtx); - if (!sc->closed) { - nni_http *h; - sc->closed = true; - // Close the underlying transport. - if (nni_list_node_active(&sc->node)) { - nni_list_remove(&s->conns, sc); - if (nni_list_empty(&s->conns)) { - nni_cv_wake(&s->cv); - } - } - if ((h = sc->http) != NULL) { - nni_http_close(h); + + sc->closed = true; + // Close the underlying transport. + if (nni_list_node_active(&sc->node)) { + nni_list_remove(&s->conns, sc); + if (nni_list_empty(&s->conns)) { + nni_cv_wake(&s->cv); } - http_sconn_fini(sc); } + nni_aio_cancel(sc->rxaio, NNG_ECLOSED); + nni_aio_cancel(sc->txaio, NNG_ECLOSED); + nni_aio_cancel(sc->txdataio, NNG_ECLOSED); + nni_aio_cancel(sc->cbaio, NNG_ECLOSED); + + if ((h = sc->http) != NULL) { + nni_http_close(h); + } + http_sconn_fini(sc); +} + +static void +http_sconn_close(http_sconn *sc) +{ + nni_http_server *s; + s = sc->server; + + if (sc->closed) { + return; + } + + nni_mtx_lock(&s->mtx); + http_sconn_close_locked(sc); nni_mtx_unlock(&s->mtx); } @@ -484,14 +505,16 @@ http_sconn_cbdone(void *arg) } static int -http_sconn_init(http_sconn **scp, nni_plat_tcp_pipe *tcp) +http_sconn_init(http_sconn **scp, nni_http_server *s, nni_plat_tcp_pipe *tcp) { http_sconn *sc; int rv; if ((sc = NNI_ALLOC_STRUCT(sc)) == NULL) { + nni_plat_tcp_pipe_fini(tcp); return (NNG_ENOMEM); } + if (((rv = nni_http_req_init(&sc->req)) != 0) || ((rv = nni_aio_init(&sc->rxaio, http_sconn_rxdone, sc)) != 0) || ((rv = nni_aio_init(&sc->txaio, http_sconn_txdone, sc)) != 0) || @@ -502,18 +525,13 @@ http_sconn_init(http_sconn **scp, nni_plat_tcp_pipe *tcp) http_sconn_close(sc); return (rv); } - // XXX: for HTTPS we would then try to do the TLS negotiation here. - // That would use a different set of tran values. - sc->tran.h_data = tcp; - sc->tran.h_read = (void *) nni_plat_tcp_pipe_recv; - sc->tran.h_write = (void *) nni_plat_tcp_pipe_send; - sc->tran.h_close = (void *) nni_plat_tcp_pipe_close; // close implied - sc->tran.h_fini = (void *) nni_plat_tcp_pipe_fini; - sc->tran.h_sock_addr = (void *) nni_plat_tcp_pipe_sockname; - sc->tran.h_peer_addr = (void *) nni_plat_tcp_pipe_peername; - - if ((rv = nni_http_init(&sc->http, &sc->tran)) != 0) { + if (s->tls != NULL) { + rv = nni_http_init_tls(&sc->http, s->tls, tcp); + } else { + rv = nni_http_init_tcp(&sc->http, tcp); + } + if (rv != 0) { http_sconn_close(sc); return (rv); } @@ -539,9 +557,8 @@ http_server_acccb(void *arg) return; } tcp = nni_aio_get_pipe(aio); - if (http_sconn_init(&sc, tcp) != 0) { - nni_plat_tcp_pipe_close(tcp); - nni_plat_tcp_pipe_fini(tcp); + if (http_sconn_init(&sc, s, tcp) != 0) { + // The TCP structure is already cleaned up. nni_plat_tcp_ep_accept(s->tep, s->accaio); return; @@ -590,6 +607,11 @@ http_server_fini(nni_http_server *s) http_handler_fini(h); } nni_mtx_unlock(&s->mtx); +#ifdef NNG_SUPP_TLS + if (s->tls != NULL) { + nng_tls_config_fini(s->tls); + } +#endif nni_aio_fini(s->accaio); nni_cv_fini(&s->cv); nni_mtx_fini(&s->mtx); @@ -726,11 +748,7 @@ http_server_stop(nni_http_server *s) // Stopping the server is a hard stop -- it aborts any work being // done by clients. (No graceful shutdown). NNI_LIST_FOREACH (&s->conns, sc) { - nni_list_remove(&s->conns, sc); - if (sc->http != NULL) { - nni_http_close(sc->http); - } - http_sconn_fini(sc); + http_sconn_close_locked(sc); } nni_cv_wake(&s->cv); } @@ -1073,6 +1091,29 @@ nni_http_server_add_static(nni_http_server *s, const char *host, return (0); } +#ifdef NNG_SUPP_TLS +int +nni_http_server_set_tls(nni_http_server *s, nng_tls_config *tcfg) +{ + nng_tls_config *old; + nni_mtx_lock(&s->mtx); + if (s->starts) { + nni_mtx_unlock(&s->mtx); + return (NNG_EBUSY); + } + old = s->tls; + s->tls = tcfg; + if (tcfg) { + nni_tls_config_hold(tcfg); + } + nni_mtx_unlock(&s->mtx); + if (old) { + nng_tls_config_fini(old); + } + return (0); +} +#endif + static int http_server_sys_init(void) { diff --git a/src/supplemental/mbedtls/CMakeLists.txt b/src/supplemental/mbedtls/CMakeLists.txt deleted file mode 100644 index 5c2de10b..00000000 --- a/src/supplemental/mbedtls/CMakeLists.txt +++ /dev/null @@ -1,54 +0,0 @@ -# -# 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 deleted file mode 100644 index d64447ac..00000000 --- a/src/supplemental/mbedtls/tls.c +++ /dev/null @@ -1,1031 +0,0 @@ -// -// 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/version.h" // Must be first in order to pick up version - -#include "mbedtls/error.h" - -// mbedTLS renamed this header for 2.4.0. -#if MBEDTLS_VERSION_MAJOR > 2 || MBEDTLS_VERSION_MINOR >= 4 -#include "mbedtls/net_sockets.h" -#else -#include "mbedtls/net.h" -#endif - -#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 - 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) -}; - -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, NNG_DURATION_INFINITE); - 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, NNG_DURATION_INFINITE); - 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, NNG_DURATION_INFINITE); - 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 deleted file mode 100644 index da2fe8cd..00000000 --- a/src/supplemental/tls.h +++ /dev/null @@ -1,84 +0,0 @@ -// -// 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/supplemental/tls/CMakeLists.txt b/src/supplemental/tls/CMakeLists.txt new file mode 100644 index 00000000..e78f1c13 --- /dev/null +++ b/src/supplemental/tls/CMakeLists.txt @@ -0,0 +1,38 @@ +# +# Copyright 2017 Capitar IT Group BV +# Copyright 2017 Staysail Systems, Inc. +# +# 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_SUPP_TLS) + set(NNG_SUPP_TLS_MBEDTLS ON) + set(TLS_SOURCES supplemental/tls/tls.h) +endif() + +# For now we only support the ARM mbedTLS library. +if (NNG_SUPP_TLS_MBEDTLS) + + 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() + set(TLS_SOURCES ${TLS_SOURCES} supplemental/tls/mbedtls/tls.c) +endif() + +set(NNG_SOURCES ${NNG_SOURCES} ${TLS_SOURCES} PARENT_SCOPE) diff --git a/src/supplemental/tls/mbedtls/tls.c b/src/supplemental/tls/mbedtls/tls.c new file mode 100644 index 00000000..3bbf4a33 --- /dev/null +++ b/src/supplemental/tls/mbedtls/tls.c @@ -0,0 +1,1067 @@ +// +// 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 "mbedtls/version.h" // Must be first in order to pick up version + +#include "mbedtls/error.h" + +// mbedTLS renamed this header for 2.4.0. +#if MBEDTLS_VERSION_MAJOR > 2 || MBEDTLS_VERSION_MINOR >= 4 +#include "mbedtls/net_sockets.h" +#else +#include "mbedtls/net.h" +#endif + +#include "mbedtls/ssl.h" + +#include "core/nng_impl.h" + +#include "supplemental/tls/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; + 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) +}; + +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; +#endif + mbedtls_x509_crt ca_certs; + mbedtls_x509_crl crl; + bool have_ca_certs; + bool have_crl; + + int refcnt; // servers increment the reference + + 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; + nng_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 +nng_tls_config_fini(nng_tls_config *cfg) +{ + nni_tls_certkey *ck; + + nni_mtx_lock(&cfg->lk); + cfg->refcnt--; + if (cfg->refcnt != 0) { + nni_mtx_unlock(&cfg->lk); + return; + } + nni_mtx_unlock(&cfg->lk); + + 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 +nng_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); + } + cfg->refcnt = 1; + 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) { + nng_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) { + nng_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_config_hold(nng_tls_config *cfg) +{ + nni_mtx_lock(&cfg->lk); + cfg->refcnt++; + nni_mtx_unlock(&cfg->lk); +} + +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); + if (tp->cfg != NULL) { + // release the hold we got on it + nng_tls_config_fini(tp->cfg); + } + 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, nng_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; + } + cfg->refcnt++; + tp->cfg = cfg; + 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, NNG_DURATION_INFINITE); + 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, NNG_DURATION_INFINITE); + 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, NNG_DURATION_INFINITE); + 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); +} + +int +nni_tls_peername(nni_tls *tp, nni_sockaddr *sa) +{ + return (nni_plat_tcp_pipe_peername(tp->tcp, sa)); +} + +int +nni_tls_sockname(nni_tls *tp, nni_sockaddr *sa) +{ + return (nni_plat_tcp_pipe_sockname(tp->tcp, sa)); +} + +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 +nng_tls_config_server_name(nng_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 +nng_tls_config_auth_mode(nng_tls_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; + case NNG_TLS_AUTH_MODE_OPTIONAL: + mbedtls_ssl_conf_authmode( + &cfg->cfg_ctx, MBEDTLS_SSL_VERIFY_OPTIONAL); + break; + 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); + } + 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 +nng_tls_config_cert(nng_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 +nng_tls_config_key(nng_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 +nng_tls_config_pass(nng_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 +nng_tls_config_ca_cert(nng_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 +nng_tls_config_crl(nng_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); +} diff --git a/src/supplemental/tls/tls.h b/src/supplemental/tls/tls.h new file mode 100644 index 00000000..0c9e791f --- /dev/null +++ b/src/supplemental/tls/tls.h @@ -0,0 +1,45 @@ +// +// 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_TLS_H +#define NNG_SUPPLEMENTAL_TLS_TLS_H + +// nni_tls represents the context for a single TLS stream. +typedef struct nni_tls nni_tls; + +// 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 *); + +extern int nni_tls_init(nni_tls **, nng_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 *); +extern int nni_tls_sockname(nni_tls *, nni_sockaddr *); +extern int nni_tls_peername(nni_tls *, nni_sockaddr *); + +// 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_TLS_H diff --git a/src/supplemental/websocket/websocket.c b/src/supplemental/websocket/websocket.c index 06e1c70a..c7181595 100644 --- a/src/supplemental/websocket/websocket.c +++ b/src/supplemental/websocket/websocket.c @@ -55,7 +55,6 @@ struct nni_ws { }; struct nni_ws_listener { - nni_tls_config * tls; nni_http_server * server; char * proto; char * url; @@ -82,7 +81,6 @@ struct nni_ws_listener { // completion of an earlier connection. (We don't want to establish // requests when we already have connects negotiating.) struct nni_ws_dialer { - nni_tls_config * tls; nni_http_req * req; nni_http_res * res; nni_http_client *client; @@ -1296,6 +1294,13 @@ nni_ws_listener_fini(nni_ws_listener *l) { ws_header *hdr; + nni_ws_listener_close(l); + + if (l->server != NULL) { + nni_http_server_fini(l->server); + l->server = NULL; + } + nni_mtx_fini(&l->mtx); nni_strfree(l->url); nni_strfree(l->proto); @@ -1560,6 +1565,8 @@ nni_ws_listener_init(nni_ws_listener **wslp, const char *url) { nni_ws_listener *l; int rv; + nni_aio * aio; + nni_sockaddr sa; if ((l = NNI_ALLOC_STRUCT(l)) == NULL) { return (NNG_ENOMEM); @@ -1582,6 +1589,24 @@ nni_ws_listener_init(nni_ws_listener **wslp, const char *url) l->handler.h_host = l->host; l->handler.h_cb = ws_handler; + if ((rv = nni_aio_init(&aio, NULL, NULL)) != 0) { + nni_ws_listener_fini(l); + return (rv); + } + aio->a_addr = &sa; + nni_plat_tcp_resolv(l->host, l->serv, NNG_AF_UNSPEC, true, aio); + nni_aio_wait(aio); + rv = nni_aio_result(aio); + nni_aio_fini(aio); + if (rv != 0) { + nni_ws_listener_fini(l); + return (rv); + } + if ((rv = nni_http_server_init(&l->server, &sa)) != 0) { + nni_ws_listener_fini(l); + return (rv); + } + *wslp = l; return (0); } @@ -1658,10 +1683,10 @@ nni_ws_listener_close(nni_ws_listener *l) return; } l->closed = true; - if (l->server != NULL) { + if (l->started) { nni_http_server_del_handler(l->server, l->hp); - nni_http_server_fini(l->server); - l->server = NULL; + nni_http_server_stop(l->server); + l->started = false; } NNI_LIST_FOREACH (&l->pend, ws) { nni_ws_close_error(ws, WS_CLOSE_GOING_AWAY); @@ -1675,9 +1700,7 @@ nni_ws_listener_close(nni_ws_listener *l) int nni_ws_listener_listen(nni_ws_listener *l) { - nng_sockaddr sa; - nni_aio * aio; - int rv; + int rv; nni_mtx_lock(&l->mtx); if (l->closed) { @@ -1689,25 +1712,6 @@ nni_ws_listener_listen(nni_ws_listener *l) return (NNG_ESTATE); } - if ((rv = nni_aio_init(&aio, NULL, NULL)) != 0) { - nni_mtx_unlock(&l->mtx); - return (rv); - } - aio->a_addr = &sa; - nni_plat_tcp_resolv(l->host, l->serv, NNG_AF_UNSPEC, true, aio); - nni_aio_wait(aio); - rv = nni_aio_result(aio); - nni_aio_fini(aio); - if (rv != 0) { - nni_mtx_unlock(&l->mtx); - return (rv); - } - - if ((rv = nni_http_server_init(&l->server, &sa)) != 0) { - nni_mtx_unlock(&l->mtx); - return (rv); - } - rv = nni_http_server_add_handler(&l->hp, l->server, &l->handler, l); if (rv != 0) { nni_http_server_fini(l->server); @@ -1716,12 +1720,12 @@ nni_ws_listener_listen(nni_ws_listener *l) return (rv); } - // XXX: DEAL WITH HTTPS here. - if ((rv = nni_http_server_start(l->server)) != 0) { nni_http_server_del_handler(l->server, l->hp); nni_http_server_fini(l->server); l->server = NULL; + nni_mtx_unlock(&l->mtx); + return (rv); } l->started = true; @@ -1740,11 +1744,17 @@ nni_ws_listener_hook( nni_mtx_unlock(&l->mtx); } -void -nni_ws_listener_tls(nni_ws_listener *l, nni_tls_config *tls) +#ifdef NNG_SUPP_TLS +int +nni_ws_listener_set_tls(nni_ws_listener *l, nng_tls_config *tls) { - // We need to add this later. + int rv; + nni_mtx_lock(&l->mtx); + rv = nni_http_server_set_tls(l->server, tls); + nni_mtx_unlock(&l->mtx); + return (rv); } +#endif void ws_conn_cb(void *arg) @@ -1931,6 +1941,18 @@ nni_ws_dialer_init(nni_ws_dialer **dp, const char *url) return (0); } +#ifdef NNG_SUPP_TLS +int +nni_ws_dialer_set_tls(nni_ws_dialer *d, nng_tls_config *tls) +{ + int rv; + nni_mtx_lock(&d->mtx); + rv = nni_http_client_set_tls(d->client, tls); + nni_mtx_unlock(&d->mtx); + return (rv); +} +#endif + void nni_ws_dialer_close(nni_ws_dialer *d) { diff --git a/src/supplemental/websocket/websocket.h b/src/supplemental/websocket/websocket.h index 95147a9f..ccf549df 100644 --- a/src/supplemental/websocket/websocket.h +++ b/src/supplemental/websocket/websocket.h @@ -12,9 +12,8 @@ #define NNG_SUPPLEMENTAL_WEBSOCKET_WEBSOCKET_H // Pre-defined types for some prototypes. These are from other subsystems. -typedef struct nni_tls_config nni_tls_config; -typedef struct nni_http_req nni_http_req; -typedef struct nni_http_res nni_http_res; +typedef struct nni_http_req nni_http_req; +typedef struct nni_http_res nni_http_res; typedef struct nni_ws nni_ws; typedef struct nni_ws_listener nni_ws_listener; @@ -36,7 +35,7 @@ extern int nni_ws_listener_listen(nni_ws_listener *); extern void nni_ws_listener_accept(nni_ws_listener *, nni_aio *); extern void nni_ws_listener_hook( nni_ws_listener *, nni_ws_listen_hook, void *); -extern void nni_ws_listener_tls(nni_ws_listener *, nni_tls_config *); +extern int nni_ws_listener_set_tls(nni_ws_listener *, nng_tls_config *); extern int nni_ws_dialer_init(nni_ws_dialer **, const char *); extern void nni_ws_dialer_fini(nni_ws_dialer *); @@ -44,6 +43,7 @@ extern void nni_ws_dialer_close(nni_ws_dialer *); extern int nni_ws_dialer_proto(nni_ws_dialer *, const char *); extern int nni_ws_dialer_header(nni_ws_dialer *, const char *, const char *); extern void nni_ws_dialer_dial(nni_ws_dialer *, nni_aio *); +extern int nni_ws_dialer_set_tls(nni_ws_dialer *, nng_tls_config *); // Dialer does not get a hook chance, as it can examine the request and reply // after dial is done; this is not a 3-way handshake, so the dialer does diff --git a/src/transport/tls/tls.c b/src/transport/tls/tls.c index 9c794e64..f2ca6d35 100644 --- a/src/transport/tls/tls.c +++ b/src/transport/tls/tls.c @@ -14,7 +14,7 @@ #include "core/nng_impl.h" -#include "supplemental/tls.h" +#include "supplemental/tls/tls.h" #include "tls.h" // TLS over TCP transport. Platform specific TCP operations must be @@ -61,7 +61,7 @@ struct nni_tls_ep { nni_aio * aio; nni_aio * user_aio; nni_mtx mtx; - nni_tls_config * cfg; + nng_tls_config * cfg; }; static void nni_tls_pipe_send_cb(void *); @@ -477,7 +477,7 @@ nni_tls_pipe_getopt_locaddr(void *arg, void *v, size_t *szp) nng_sockaddr sa; memset(&sa, 0, sizeof(sa)); - if ((rv = nni_plat_tcp_pipe_sockname(p->tcp, &sa)) == 0) { + if ((rv = nni_tls_sockname(p->tls, &sa)) == 0) { rv = nni_getopt_sockaddr(&sa, v, szp); } return (rv); @@ -491,7 +491,7 @@ nni_tls_pipe_getopt_remaddr(void *arg, void *v, size_t *szp) nng_sockaddr sa; memset(&sa, 0, sizeof(sa)); - if ((rv = nni_plat_tcp_pipe_peername(p->tcp, &sa)) == 0) { + if ((rv = nni_tls_peername(p->tls, &sa)) == 0) { rv = nni_getopt_sockaddr(&sa, v, szp); } return (rv); @@ -589,7 +589,7 @@ nni_tls_ep_fini(void *arg) nni_plat_tcp_ep_fini(ep->tep); } if (ep->cfg) { - nni_tls_config_fini(ep->cfg); + nng_tls_config_fini(ep->cfg); } nni_aio_fini(ep->aio); nni_mtx_fini(&ep->mtx); @@ -599,18 +599,18 @@ nni_tls_ep_fini(void *arg) 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; + 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; + nng_tls_mode tlsmode; + nng_tls_auth_mode authmode; // Make a copy of the url (to allow for destructive operations) if (nni_strlcpy(buf, url, sizeof(buf)) >= sizeof(buf)) { @@ -628,12 +628,12 @@ nni_tls_ep_init(void **epp, const char *url, nni_sock *sock, int mode) } if (mode == NNI_EP_MODE_DIAL) { passive = 0; - tlsmode = NNI_TLS_CONFIG_CLIENT; - authmode = NNI_TLS_CONFIG_AUTH_MODE_REQUIRED; + tlsmode = NNG_TLS_MODE_CLIENT; + authmode = NNG_TLS_AUTH_MODE_REQUIRED; } else { passive = 1; - tlsmode = NNI_TLS_CONFIG_SERVER; - authmode = NNI_TLS_CONFIG_AUTH_MODE_NONE; + tlsmode = NNG_TLS_MODE_SERVER; + authmode = NNG_TLS_AUTH_MODE_NONE; } // XXX: arguably we could defer this part to the point we do a bind @@ -684,15 +684,15 @@ nni_tls_ep_init(void **epp, const char *url, nni_sock *sock, int mode) } 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 = nng_tls_config_init(&ep->cfg, tlsmode)) != 0) || + ((rv = nng_tls_config_auth_mode(ep->cfg, authmode)) != 0) || ((rv = nni_aio_init(&ep->aio, nni_tls_ep_cb, ep)) != 0)) { nni_strfree(rhost); 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) { + if ((tlsmode == NNG_TLS_MODE_CLIENT) && (rhost != NULL)) { + if ((rv = nng_tls_config_server_name(ep->cfg, rhost)) != 0) { nni_strfree(rhost); nni_tls_ep_fini(ep); return (rv); @@ -867,6 +867,38 @@ nni_tls_ep_getopt_linger(void *arg, void *v, size_t *szp) return (nni_getopt_ms(ep->linger, v, szp)); } +static int +tls_setopt_config(void *arg, const void *data, size_t sz) +{ + nni_tls_ep * ep = arg; + nng_tls_config *cfg, *old; + + if (sz != sizeof(cfg)) { + return (NNG_EINVAL); + } + memcpy(&cfg, data, sz); + if (cfg == NULL) { + return (NNG_EINVAL); + } + if (ep == NULL) { + return (0); + } + old = ep->cfg; + nni_tls_config_hold(cfg); + ep->cfg = cfg; + if (old != NULL) { + nng_tls_config_fini(old); + } + return (0); +} + +static int +tls_getopt_config(void *arg, void *v, size_t *szp) +{ + nni_tls_ep *ep = arg; + return (nni_getopt_ptr(ep->cfg, v, szp)); +} + static int tls_setopt_ca_cert(void *arg, const void *data, size_t sz) { @@ -875,7 +907,7 @@ tls_setopt_ca_cert(void *arg, const void *data, size_t sz) if (ep == NULL) { return (0); } - return (nni_tls_config_ca_cert(ep->cfg, data, sz)); + return (nng_tls_config_ca_cert(ep->cfg, data, sz)); } static int @@ -886,7 +918,7 @@ tls_setopt_cert(void *arg, const void *data, size_t sz) if (ep == NULL) { return (0); } - return (nni_tls_config_cert(ep->cfg, data, sz)); + return (nng_tls_config_cert(ep->cfg, data, sz)); } static int @@ -897,7 +929,7 @@ tls_setopt_private_key(void *arg, const void *data, size_t sz) if (ep == NULL) { return (0); } - return (nni_tls_config_key(ep->cfg, data, sz)); + return (nng_tls_config_key(ep->cfg, data, sz)); } static int @@ -914,13 +946,9 @@ tls_setopt_pass(void *arg, const void *data, size_t sz) if (ep == NULL) { return (0); } - return (nni_tls_config_pass(ep->cfg, data)); + return (nng_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) { @@ -938,9 +966,9 @@ tls_setopt_auth_mode(void *arg, const void *data, size_t sz) 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: + case NNG_TLS_AUTH_MODE_NONE: + case NNG_TLS_AUTH_MODE_OPTIONAL: + case NNG_TLS_AUTH_MODE_REQUIRED: break; default: rv = NNG_EINVAL; @@ -952,7 +980,7 @@ tls_setopt_auth_mode(void *arg, const void *data, size_t sz) return (rv); } - if ((rv = nni_tls_config_auth_mode(ep->cfg, mode)) == 0) { + if ((rv = nng_tls_config_auth_mode(ep->cfg, mode)) == 0) { ep->authmode = mode; } return (rv); @@ -997,6 +1025,11 @@ static nni_tran_ep_option nni_tls_ep_options[] = { .eo_getopt = nni_tls_ep_getopt_linger, .eo_setopt = nni_tls_ep_setopt_linger, }, + { + .eo_name = NNG_OPT_TLS_CONFIG, + .eo_getopt = tls_getopt_config, + .eo_setopt = tls_setopt_config, + }, { .eo_name = NNG_OPT_TLS_CA_CERT, .eo_getopt = NULL, diff --git a/src/transport/tls/tls.h b/src/transport/tls/tls.h index 4317ae55..b36ee774 100644 --- a/src/transport/tls/tls.h +++ b/src/transport/tls/tls.h @@ -49,14 +49,12 @@ NNG_DECL int nng_tls_register(void); // 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" +#define NNG_OPT_TLS_CONFIG "tls:config" + // XXX: TBD: Ciphersuite selection and reporting. Session reuse? #endif // NNG_TRANSPORT_TLS_TLS_H diff --git a/src/transport/ws/websocket.c b/src/transport/ws/websocket.c index 4f948fb7..1162338e 100644 --- a/src/transport/ws/websocket.c +++ b/src/transport/ws/websocket.c @@ -15,6 +15,7 @@ #include "core/nng_impl.h" #include "supplemental/http/http.h" +#include "supplemental/tls/tls.h" #include "supplemental/websocket/websocket.h" #include "websocket.h" @@ -42,6 +43,7 @@ struct ws_ep { nni_ws_listener *listener; nni_ws_dialer * dialer; nni_list headers; // to send, res or req + nng_tls_config * tls; }; struct ws_pipe { @@ -368,9 +370,6 @@ ws_ep_setopt_headers(ws_ep *ep, const void *v, size_t sz) ws_hdr * h; int rv; - if (nni_strnlen(v, sz) >= sz) { - return (NNG_EINVAL); - } if (ep == NULL) { return (0); } @@ -449,7 +448,11 @@ ws_ep_setopt_reqhdrs(void *arg, const void *v, size_t sz) { ws_ep *ep = arg; - if (ep->mode == NNI_EP_MODE_LISTEN) { + if (nni_strnlen(v, sz) >= sz) { + return (NNG_EINVAL); + } + + if ((ep != NULL) && (ep->mode == NNI_EP_MODE_LISTEN)) { return (NNG_EREADONLY); } return (ws_ep_setopt_headers(ep, v, sz)); @@ -460,7 +463,11 @@ ws_ep_setopt_reshdrs(void *arg, const void *v, size_t sz) { ws_ep *ep = arg; - if (ep->mode == NNI_EP_MODE_DIAL) { + if (nni_strnlen(v, sz) >= sz) { + return (NNG_EINVAL); + } + + if ((ep != NULL) && (ep->mode == NNI_EP_MODE_DIAL)) { return (NNG_EREADONLY); } return (ws_ep_setopt_headers(ep, v, sz)); @@ -594,6 +601,11 @@ ws_ep_fini(void *arg) nni_strfree(ep->addr); nni_strfree(ep->protoname); nni_mtx_fini(&ep->mtx); +#ifdef NNG_TRANSPORT_WSS + if (ep->tls) { + nng_tls_config_fini(ep->tls); + } +#endif NNI_FREE_STRUCT(ep); } @@ -689,10 +701,21 @@ ws_ep_init(void **epp, const char *url, nni_sock *sock, int mode) if ((ep = NNI_ALLOC_STRUCT(ep)) == NULL) { return (NNG_ENOMEM); } - nni_mtx_init(&ep->mtx); NNI_LIST_INIT(&ep->headers, ws_hdr, node); +#ifdef NNG_TRANSPORT_WSS + if (strncmp(url, "wss://", 4) == 0) { + rv = nng_tls_config_init(&ep->tls, + mode == NNI_EP_MODE_DIAL ? NNG_TLS_MODE_CLIENT + : NNG_TLS_MODE_SERVER); + if (rv != 0) { + NNI_FREE_STRUCT(ep); + return (rv); + } + } +#endif + // List of pipes (server only). nni_aio_list_init(&ep->aios); @@ -758,3 +781,100 @@ nng_ws_register(void) { return (nni_tran_register(&ws_tran)); } + +#ifdef NNG_TRANSPORT_WSS + +static int +wss_ep_getopt_tlsconfig(void *arg, void *v, size_t *szp) +{ + ws_ep *ep = arg; + return (nni_getopt_ptr(ep->tls, v, szp)); +} + +static int +wss_ep_setopt_tlsconfig(void *arg, const void *v, size_t sz) +{ + ws_ep * ep = arg; + nng_tls_config *cfg; + int rv; + + if (sz != sizeof(cfg)) { + return (NNG_EINVAL); + } + memcpy(&cfg, v, sz); + if (cfg == NULL) { + // NULL is clearly invalid. + return (NNG_EINVAL); + } + if (ep == NULL) { + return (0); + } + nni_mtx_lock(&ep->mtx); + if (ep->mode == NNI_EP_MODE_LISTEN) { + rv = nni_ws_listener_set_tls(ep->listener, cfg); + } else { + rv = nni_ws_dialer_set_tls(ep->dialer, cfg); + } + if (rv == 0) { + if (ep->tls != NULL) { + nng_tls_config_fini(ep->tls); + } + nni_tls_config_hold(cfg); + ep->tls = cfg; + } + nni_mtx_unlock(&ep->mtx); + return (rv); +} + +static nni_tran_ep_option wss_ep_options[] = { + { + .eo_name = NNG_OPT_RECVMAXSZ, + .eo_getopt = ws_ep_getopt_recvmaxsz, + .eo_setopt = ws_ep_setopt_recvmaxsz, + }, + { + .eo_name = NNG_OPT_WSS_REQUEST_HEADERS, + .eo_getopt = NULL, + .eo_setopt = ws_ep_setopt_reqhdrs, + }, + { + .eo_name = NNG_OPT_WSS_RESPONSE_HEADERS, + .eo_getopt = NULL, + .eo_setopt = ws_ep_setopt_reshdrs, + }, + { + .eo_name = NNG_OPT_WSS_TLS_CONFIG, + .eo_getopt = wss_ep_getopt_tlsconfig, + .eo_setopt = wss_ep_setopt_tlsconfig, + }, + + // terminate list + { NULL, NULL, NULL }, +}; + +static nni_tran_ep wss_ep_ops = { + .ep_init = ws_ep_init, + .ep_fini = ws_ep_fini, + .ep_connect = ws_ep_connect, + .ep_bind = ws_ep_bind, + .ep_accept = ws_ep_accept, + .ep_close = ws_ep_close, + .ep_options = wss_ep_options, +}; + +static nni_tran wss_tran = { + .tran_version = NNI_TRANSPORT_VERSION, + .tran_scheme = "wss", + .tran_ep = &wss_ep_ops, + .tran_pipe = &ws_pipe_ops, + .tran_init = ws_tran_init, + .tran_fini = ws_tran_fini, +}; + +int +nng_wss_register(void) +{ + return (nni_tran_register(&wss_tran)); +} + +#endif // NNG_TRANSPORT_WSS diff --git a/src/transport/ws/websocket.h b/src/transport/ws/websocket.h index 2e86aaf0..a0d8a6cc 100644 --- a/src/transport/ws/websocket.h +++ b/src/transport/ws/websocket.h @@ -15,12 +15,30 @@ NNG_DECL int nng_ws_register(void); -// NNG_OPT_TLS_REQUEST_HEADERS is a string containing the +// NNG_OPT_WS_REQUEST_HEADERS is a string containing the // request headers, formatted as CRLF terminated lines. #define NNG_OPT_WS_REQUEST_HEADERS "ws:request-headers" -// NNG_OPT_TLS_RESPONSE_HEADERS is a string containing the +// NNG_OPT_WS_RESPONSE_HEADERS is a string containing the // response headers, formatted as CRLF terminated lines. #define NNG_OPT_WS_RESPONSE_HEADERS "ws:response-headers" +// NNG_OPT_WSS_TLS_CONFIG is a pointer to a an nng_tls_config +// object. This property is only available for wss:// style +// endpoints. Note that when configuring the object, a hold +// is placed on the TLS configuration. When retrieving the +// object, no hold is placed, and so the caller must take care +// not to use the configuration object after the endpoint it +// is associated with is removed. Furthermore, as this is a +// pointer, applications must take care to pass only valid +// data -- incorrect pointer values will lead to undefined +// behavior. +#define NNG_OPT_WSS_TLS_CONFIG "wss:tls-config" + +// These aliases are for WSS naming consistency. +#define NNG_OPT_WSS_REQUEST_HEADERS NNG_OPT_WS_REQUEST_HEADERS +#define NNG_OPT_WSS_RESPONSE_HEADERS NNG_OPT_WS_RESPONSE_HEADERS + +NNG_DECL int nng_wss_register(void); + #endif // NNG_TRANSPORT_WS_WEBSOCKET_H diff --git a/src/transport/zerotier/CMakeLists.txt b/src/transport/zerotier/CMakeLists.txt index c1bb0c35..a4271eb7 100644 --- a/src/transport/zerotier/CMakeLists.txt +++ b/src/transport/zerotier/CMakeLists.txt @@ -11,6 +11,7 @@ # ZeroTier protocol set (NNG_TRANSPORT_ZEROTIER_SOURCE "" CACHE PATH "Location of ZeroTier source tree.") +mark_as_advanced(NNG_TRANSPORT_ZEROTIER_SOURCE) if (NNG_TRANSPORT_ZEROTIER) diff --git a/src/transport/zerotier/zerotier.c b/src/transport/zerotier/zerotier.c index 3cb2871a..f6594eb3 100644 --- a/src/transport/zerotier/zerotier.c +++ b/src/transport/zerotier/zerotier.c @@ -8,7 +8,6 @@ // found online at https://opensource.org/licenses/MIT. // -#ifdef NNG_HAVE_ZEROTIER #include #include #include @@ -2804,5 +2803,3 @@ nng_zt_register(void) { return (nni_tran_register(&zt_tran)); } - -#endif -- cgit v1.2.3-70-g09d2