// // Copyright 2019 Staysail Systems, Inc. // Copyright 2018 Capitar IT Group BV // Copyright 2019 Devolutions // // 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 "core/tcp.h" #include "supplemental/tls/tls_api.h" // Implementation note. This implementation buffers data between the TLS // encryption layer (mbedTLS) and the underlying TCP socket. As a result, // there may be some additional latency caused by buffer draining and // refilling. In the future we might want to investigate some kind of // double buffer policy to allow data to flow without entering a true // empty state. // NNG_TLS_MAX_SEND_SIZE limits the amount of data we will buffer for sending, // exerting backpressure if this size is exceeded. The 16K is aligned to the // maximum TLS record size. #ifndef NNG_TLS_MAX_SEND_SIZE #define NNG_TLS_MAX_SEND_SIZE 16384 #endif // NNG_TLS_MAX_RECV_SIZE limits the amount of data we will receive in a single // operation. As we have to buffer data, this drives the size of our // intermediary buffer. The 16K is aligned to the maximum TLS record size. #ifndef NNG_TLX_MAX_RECV_SIZE #define NNG_TLS_MAX_RECV_SIZE 16384 #endif typedef struct nni_tls_certkey { mbedtls_x509_crt crt; mbedtls_pk_context key; nni_list_node node; } nni_tls_certkey; typedef struct { nni_tls_common com; nng_stream * tcp; mbedtls_ssl_context ctx; nng_tls_config * cfg; // kept so we can release it nni_mtx lk; nni_aio * tcp_send; nni_aio * tcp_recv; bool sending; bool recving; bool closed; bool hsdone; bool tls_closed; // upper TLS layer closed bool tcp_closed; // underlying TCP buffer closed uint8_t * sendbuf; // send buffer size_t sendlen; // amount of data in send buffer size_t sendoff; // offset of start of send data uint8_t * recvbuf; // recv buffer size_t recvlen; // amount of data in recv buffer size_t recvoff; // offset of start of recv data nni_list sends; // upper side sends nni_list recvs; // upper recv aios nni_aio * handshake; // handshake aio (upper) } tls; struct nng_tls_config { mbedtls_ssl_config cfg_ctx; nni_mtx lk; bool active; char * server_name; #ifdef NNG_TLS_USE_CTR_DRBG mbedtls_ctr_drbg_context rng_ctx; nni_mtx rng_lk; #endif mbedtls_x509_crt ca_certs; mbedtls_x509_crl crl; nni_atomic_u64 refcnt; nni_list certkeys; }; static void tls_send_cb(void *); static void tls_recv_cb(void *); static void tls_do_send(tls *); static void tls_do_recv(tls *); static void tls_do_handshake(tls *); static int tls_net_send(void *, const unsigned char *, size_t); static int tls_net_recv(void *, unsigned char *, size_t); static void tls_dbg(void *ctx, int level, const char *file, int line, const char *s) { char buf[128]; NNI_ARG_UNUSED(ctx); NNI_ARG_UNUSED(level); snprintf(buf, sizeof(buf), "%s:%04d: %s", file, line, s); nni_plat_println(buf); } static int 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 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 (tls_get_entropy(arg, buf, sz)); #endif } void nni_tls_config_fini(nng_tls_config *cfg) { nni_tls_certkey *ck; if (cfg != NULL) { if (nni_atomic_dec64_nv(&cfg->refcnt) != 0) { return; } mbedtls_ssl_config_free(&cfg->cfg_ctx); #ifdef NNG_TLS_USE_CTR_DRBG mbedtls_ctr_drbg_free(&cfg->rng_ctx); #endif mbedtls_x509_crt_free(&cfg->ca_certs); mbedtls_x509_crl_free(&cfg->crl); if (cfg->server_name) { nni_strfree(cfg->server_name); } while ((ck = nni_list_first(&cfg->certkeys))) { nni_list_remove(&cfg->certkeys, ck); mbedtls_x509_crt_free(&ck->crt); mbedtls_pk_free(&ck->key); NNI_FREE_STRUCT(ck); } nni_mtx_fini(&cfg->lk); NNI_FREE_STRUCT(cfg); } } int nni_tls_config_init(nng_tls_config **cpp, enum nng_tls_mode mode) { nng_tls_config *cfg; int rv; int sslmode; int authmode; if ((cfg = NNI_ALLOC_STRUCT(cfg)) == NULL) { return (NNG_ENOMEM); } nni_atomic_init64(&cfg->refcnt); nni_atomic_inc64(&cfg->refcnt); nni_mtx_init(&cfg->lk); if (mode == NNG_TLS_MODE_SERVER) { sslmode = MBEDTLS_SSL_IS_SERVER; authmode = MBEDTLS_SSL_VERIFY_NONE; } else { sslmode = MBEDTLS_SSL_IS_CLIENT; authmode = MBEDTLS_SSL_VERIFY_REQUIRED; } NNI_LIST_INIT(&cfg->certkeys, nni_tls_certkey, node); mbedtls_ssl_config_init(&cfg->cfg_ctx); mbedtls_x509_crt_init(&cfg->ca_certs); mbedtls_x509_crl_init(&cfg->crl); rv = mbedtls_ssl_config_defaults(&cfg->cfg_ctx, sslmode, MBEDTLS_SSL_TRANSPORT_STREAM, MBEDTLS_SSL_PRESET_DEFAULT); if (rv != 0) { nni_tls_config_fini(cfg); return (rv); } mbedtls_ssl_conf_authmode(&cfg->cfg_ctx, authmode); // We *require* TLS v1.2 or newer, which is also known as SSL v3.3. mbedtls_ssl_conf_min_version(&cfg->cfg_ctx, MBEDTLS_SSL_MAJOR_VERSION_3, MBEDTLS_SSL_MINOR_VERSION_3); #ifdef NNG_TLS_USE_CTR_DRBG mbedtls_ctr_drbg_init(&cfg->rng_ctx); rv = mbedtls_ctr_drbg_seed( &cfg->rng_ctx, tls_get_entropy, NULL, NULL, 0); if (rv != 0) { nni_tls_config_fini(cfg); return (rv); } #endif mbedtls_ssl_conf_rng(&cfg->cfg_ctx, tls_random, cfg); mbedtls_ssl_conf_dbg(&cfg->cfg_ctx, tls_dbg, cfg); *cpp = cfg; return (0); } void nni_tls_config_hold(nng_tls_config *cfg) { nni_atomic_inc64(&cfg->refcnt); } // tls_mkerr converts an mbed error to an NNG error. In all cases // we just encode with NNG_ETRANERR. static struct { int tls; int nng; } tls_errs[] = { { MBEDTLS_ERR_SSL_NO_CLIENT_CERTIFICATE, NNG_EPEERAUTH }, { MBEDTLS_ERR_SSL_CA_CHAIN_REQUIRED, NNG_EPEERAUTH }, { MBEDTLS_ERR_SSL_PEER_VERIFY_FAILED, NNG_EPEERAUTH }, { MBEDTLS_ERR_SSL_NO_USABLE_CIPHERSUITE, NNG_EPEERAUTH }, { MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY, NNG_ECONNREFUSED }, { MBEDTLS_ERR_SSL_ALLOC_FAILED, NNG_ENOMEM }, { MBEDTLS_ERR_SSL_TIMEOUT, NNG_ETIMEDOUT }, { MBEDTLS_ERR_SSL_CONN_EOF, NNG_ECLOSED }, // terminator { 0, 0 }, }; static int tls_mkerr(int err) { for (int i = 0; tls_errs[i].tls != 0; i++) { if (tls_errs[i].tls == err) { return (tls_errs[i].nng); } } return (NNG_ECRYPTO); } // The common code should call this only after it has released // it's upper layer stuff. static void tls_free(void *arg) { tls *tls = arg; // Shut it all down first. if (tls != NULL) { if (tls->tcp != NULL) { nng_stream_close(tls->tcp); } nni_aio_stop(tls->tcp_send); nni_aio_stop(tls->tcp_recv); nni_aio_fini(tls->com.aio); // And finalize / free everything. nng_stream_free(tls->tcp); nni_aio_fini(tls->tcp_send); nni_aio_fini(tls->tcp_recv); mbedtls_ssl_free(&tls->ctx); nng_tls_config_free(tls->com.cfg); if (tls->recvbuf != NULL) { nni_free(tls->recvbuf, NNG_TLS_MAX_RECV_SIZE); } if (tls->sendbuf != NULL) { nni_free(tls->sendbuf, NNG_TLS_MAX_RECV_SIZE); } nni_mtx_fini(&tls->lk); memset(tls, 0xff, sizeof(*tls)); NNI_FREE_STRUCT(tls); } } int nni_tls_start(nng_stream *arg, nng_stream *tcp) { tls * tp = (void *) arg; nng_tls_config *cfg = tp->com.cfg; int rv; if ((tp->recvbuf = nni_zalloc(NNG_TLS_MAX_RECV_SIZE)) == NULL) { return (NNG_ENOMEM); } if ((tp->sendbuf = nni_zalloc(NNG_TLS_MAX_SEND_SIZE)) == NULL) { return (NNG_ENOMEM); } nni_mtx_lock(&cfg->lk); // No more changes allowed to config. cfg->active = true; nni_mtx_unlock(&cfg->lk); mbedtls_ssl_init(&tp->ctx); mbedtls_ssl_set_bio(&tp->ctx, tp, tls_net_send, tls_net_recv, NULL); if ((rv = mbedtls_ssl_setup(&tp->ctx, &cfg->cfg_ctx)) != 0) { return (tls_mkerr(rv)); } if (cfg->server_name) { mbedtls_ssl_set_hostname(&tp->ctx, cfg->server_name); } tp->tcp = tcp; if (((rv = nni_aio_init(&tp->tcp_send, tls_send_cb, tp)) != 0) || ((rv = nni_aio_init(&tp->tcp_recv, tls_recv_cb, tp)) != 0)) { return (rv); } nni_mtx_lock(&tp->lk); // Kick off a handshake operation. tls_do_handshake(tp); nni_mtx_unlock(&tp->lk); return (0); } static void tls_cancel(nni_aio *aio, void *arg, int rv) { tls *tp = arg; nni_mtx_lock(&tp->lk); if (nni_aio_list_active(aio)) { nni_aio_list_remove(aio); nni_aio_finish_error(aio, rv); } nni_mtx_unlock(&tp->lk); } // tls_send_cb is called when the underlying TCP send completes. static void tls_send_cb(void *ctx) { tls * tp = ctx; nni_aio *aio = tp->tcp_send; nni_mtx_lock(&tp->lk); if (nni_aio_result(aio) != 0) { nng_stream_close(tp->tcp); tp->tcp_closed = true; } else { size_t n = nni_aio_count(aio); NNI_ASSERT(tp->sendlen >= n); tp->sendlen -= n; if (tp->sendlen) { nni_iov iov; tp->sendoff += n; iov.iov_buf = tp->sendbuf + tp->sendoff; iov.iov_len = tp->sendlen; nni_aio_set_iov(aio, 1, &iov); nni_aio_set_timeout(aio, NNG_DURATION_INFINITE); nng_stream_send(tp->tcp, aio); nni_mtx_unlock(&tp->lk); return; } tp->sendoff = 0; tp->sending = false; } tls_do_handshake(tp); if (tp->hsdone) { tls_do_send(tp); tls_do_recv(tp); } nni_mtx_unlock(&tp->lk); } static void tls_recv_start(tls *tp) { nni_aio *aio; nni_iov iov; 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; iov.iov_buf = tp->recvbuf; iov.iov_len = NNG_TLS_MAX_RECV_SIZE; nni_aio_set_iov(aio, 1, &iov); nni_aio_set_timeout(tp->tcp_recv, NNG_DURATION_INFINITE); nng_stream_recv(tp->tcp, aio); } static void tls_recv_cb(void *ctx) { tls * tp = ctx; nni_aio *aio = tp->tcp_recv; nni_mtx_lock(&tp->lk); tp->recving = false; if (nni_aio_result(aio) != 0) { // Close the underlying TCP channel, but permit data we // already received to continue to be received. nng_stream_close(tp->tcp); tp->tcp_closed = true; } else { NNI_ASSERT(tp->recvlen == 0); NNI_ASSERT(tp->recvoff == 0); tp->recvlen = nni_aio_count(aio); } // If we were closed (above), the upper layer will detect and // react properly. Otherwise the upper layer will consume // data. tls_do_handshake(tp); if (tp->hsdone) { tls_do_recv(tp); tls_do_send(tp); } 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. static int tls_net_send(void *ctx, const unsigned char *buf, size_t len) { tls * tp = ctx; nni_iov iov; if (len > NNG_TLS_MAX_SEND_SIZE) { len = NNG_TLS_MAX_SEND_SIZE; } // We should already be running with the pipe lock held, // as we are running in that context. if (tp->sending) { return (MBEDTLS_ERR_SSL_WANT_WRITE); } if (tp->tcp_closed) { return (MBEDTLS_ERR_NET_SEND_FAILED); } tp->sending = 1; tp->sendlen = len; tp->sendoff = 0; memcpy(tp->sendbuf, buf, len); iov.iov_buf = tp->sendbuf; iov.iov_len = len; nni_aio_set_iov(tp->tcp_send, 1, &iov); nni_aio_set_timeout(tp->tcp_send, NNG_DURATION_INFINITE); nng_stream_send(tp->tcp, tp->tcp_send); return (len); } static int tls_net_recv(void *ctx, unsigned char *buf, size_t len) { tls *tp = ctx; // We should already be running with the pipe lock held, // as we are running in that context. if (tp->recvlen == 0) { if (tp->tcp_closed) { // The underlying TCP transport has closed, and we // have no more data in our receive buffer. return (MBEDTLS_ERR_NET_RECV_FAILED); } len = MBEDTLS_ERR_SSL_WANT_READ; } else { if (len > tp->recvlen) { len = tp->recvlen; } memcpy(buf, tp->recvbuf + tp->recvoff, len); tp->recvoff += len; tp->recvlen -= len; } tls_recv_start(tp); return ((int) len); } static void tls_send(void *arg, nni_aio *aio) { int rv; tls *tp = arg; if (nni_aio_begin(aio) != 0) { return; } nni_mtx_lock(&tp->lk); if (tp->tls_closed) { nni_mtx_unlock(&tp->lk); nni_aio_finish_error(aio, NNG_ECLOSED); return; } if ((rv = nni_aio_schedule(aio, tls_cancel, tp)) != 0) { nni_mtx_unlock(&tp->lk); nni_aio_finish_error(aio, rv); return; } nni_list_append(&tp->sends, aio); tls_do_send(tp); nni_mtx_unlock(&tp->lk); } static void tls_recv(void *arg, nni_aio *aio) { int rv; tls *tp = arg; if (nni_aio_begin(aio) != 0) { return; } nni_mtx_lock(&tp->lk); if (tp->tls_closed) { nni_mtx_unlock(&tp->lk); nni_aio_finish_error(aio, NNG_ECLOSED); return; } if ((rv = nni_aio_schedule(aio, tls_cancel, tp)) != 0) { nni_mtx_unlock(&tp->lk); nni_aio_finish_error(aio, rv); return; } nni_list_append(&tp->recvs, aio); tls_do_recv(tp); nni_mtx_unlock(&tp->lk); } static int tls_get_verified(void *arg, void *buf, size_t *szp, nni_type t) { tls *tp = arg; bool v = (mbedtls_ssl_get_verify_result(&tp->ctx) == 0); return (nni_copyout_bool(v, buf, szp, t)); } static const nni_option tls_options[] = { { .o_name = NNG_OPT_TLS_VERIFIED, .o_get = tls_get_verified, }, { .o_name = NULL, }, }; static int tls_setx(void *arg, const char *name, const void *buf, size_t sz, nni_type t) { tls * tp = arg; int rv; nng_stream *tcp; tcp = (tp != NULL) ? tp->tcp : NULL; if ((rv = nni_stream_setx(tcp, name, buf, sz, t)) != NNG_ENOTSUP) { return (rv); } return (nni_setopt(tls_options, name, tp, buf, sz, t)); } static int tls_getx(void *arg, const char *name, void *buf, size_t *szp, nni_type t) { tls *tp = arg; int rv; if ((rv = nni_stream_getx(tp->tcp, name, buf, szp, t)) != NNG_ENOTSUP) { return (rv); } return (nni_getopt(tls_options, name, tp, buf, szp, t)); } static void tls_do_handshake(tls *tp) { int rv; nni_aio *aio; if (tp->hsdone || 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, this causes us to tear it down nng_stream_close(tp->tcp); tp->tls_closed = true; tp->tcp_closed = true; rv = tls_mkerr(rv); while (((aio = nni_list_first(&tp->recvs)) != NULL) || ((aio = nni_list_first(&tp->sends)) != NULL)) { nni_aio_list_remove(aio); nni_aio_finish_error(aio, rv); } } } // tls_do_send is called to try to send more data if we have not // yet completed the I/O. It also completes any transactions that // *have* completed. It must be called with the lock held. static void tls_do_send(tls *tp) { nni_aio *aio; while ((aio = nni_list_first(&tp->sends)) != NULL) { int n; uint8_t *buf = NULL; size_t len = 0; nni_iov *iov; unsigned niov; nni_aio_get_iov(aio, &niov, &iov); for (unsigned i = 0; i < niov; i++) { if (iov[i].iov_len != 0) { buf = iov[i].iov_buf; len = iov[i].iov_len; break; } } if (len == 0 || buf == NULL) { nni_aio_list_remove(aio); nni_aio_finish_error(aio, NNG_EINVAL); continue; } n = mbedtls_ssl_write(&tp->ctx, buf, len); if ((n == MBEDTLS_ERR_SSL_WANT_WRITE) || (n == MBEDTLS_ERR_SSL_WANT_READ)) { // Cannot send any more data right now, wait // for callback. return; } // Some other error occurred... this is not good. // Want better diagnostics. nni_aio_list_remove(aio); if (n < 0) { nni_aio_finish_error(aio, tls_mkerr(n)); } else { nni_aio_finish(aio, 0, n); } } } static void tls_do_recv(tls *tp) { nni_aio *aio; while ((aio = nni_list_first(&tp->recvs)) != NULL) { int n; uint8_t *buf = NULL; size_t len = 0; nni_iov *iov; unsigned niov; nni_aio_get_iov(aio, &niov, &iov); for (unsigned i = 0; i < niov; i++) { if (iov[i].iov_len != 0) { buf = iov[i].iov_buf; len = iov[i].iov_len; break; } } if (len == 0 || buf == NULL) { nni_aio_list_remove(aio); nni_aio_finish_error(aio, NNG_EINVAL); continue; } n = mbedtls_ssl_read(&tp->ctx, buf, len); if ((n == MBEDTLS_ERR_SSL_WANT_READ) || (n == MBEDTLS_ERR_SSL_WANT_WRITE)) { // Cannot receive any more data right now, wait // for callback. return; } nni_aio_list_remove(aio); if (n < 0) { nni_aio_finish_error(aio, tls_mkerr(n)); } else { nni_aio_finish(aio, 0, n); } } } static void tls_close(void *arg) { tls * tp = arg; nni_aio *aio; nni_aio_close(tp->tcp_send); nni_aio_close(tp->tcp_recv); nni_mtx_lock(&tp->lk); tp->tls_closed = true; while (((aio = nni_list_first(&tp->sends)) != NULL) || ((aio = nni_list_first(&tp->recvs)) != NULL)) { nni_aio_list_remove(aio); nni_aio_finish_error(aio, NNG_ECLOSED); } if (tp->hsdone) { // This may succeed, or it may fail. Either way we // don't care. Implementations that depend on // close-notify to mean anything are broken by design, // just like RFC. Note that we do *NOT* close the TCP // connection at this point. (void) mbedtls_ssl_close_notify(&tp->ctx); } else { nng_stream_close(tp->tcp); } nni_mtx_unlock(&tp->lk); } // This allocates a TLS structure, that can be used by the caller. // The reason we have this API is so that the base structure can be // embedded in the parent structure. int nni_tls_alloc(nng_stream **tlsp) { tls *tp; if ((tp = NNI_ALLOC_STRUCT(tp)) == NULL) { return (NNG_ENOMEM); } nni_aio_list_init(&tp->sends); nni_aio_list_init(&tp->recvs); nni_mtx_init(&tp->lk); tp->com.ops.s_close = tls_close; tp->com.ops.s_free = tls_free; tp->com.ops.s_send = tls_send; tp->com.ops.s_recv = tls_recv; tp->com.ops.s_getx = tls_getx; tp->com.ops.s_setx = tls_setx; *tlsp = (void *) tp; return (0); } int nng_tls_config_server_name(nng_tls_config *cfg, const char *name) { 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); } int nng_tls_config_ca_chain( nng_tls_config *cfg, const char *certs, const char *crl) { size_t len; const uint8_t *pem; int rv; // Certs and CRL are in PEM data, with terminating NUL byte. nni_mtx_lock(&cfg->lk); if (cfg->active) { rv = NNG_ESTATE; goto err; } pem = (const uint8_t *) certs; len = strlen(certs) + 1; if ((rv = mbedtls_x509_crt_parse(&cfg->ca_certs, pem, len)) != 0) { rv = tls_mkerr(rv); goto err; } if (crl != NULL) { pem = (const uint8_t *) crl; len = strlen(crl) + 1; if ((rv = mbedtls_x509_crl_parse(&cfg->crl, pem, len)) != 0) { rv = tls_mkerr(rv); goto err; } } mbedtls_ssl_conf_ca_chain(&cfg->cfg_ctx, &cfg->ca_certs, &cfg->crl); err: nni_mtx_unlock(&cfg->lk); return (rv); } int nng_tls_config_own_cert( nng_tls_config *cfg, const char *cert, const char *key, const char *pass) { size_t len; const uint8_t * pem; nni_tls_certkey *ck; int rv; if ((ck = NNI_ALLOC_STRUCT(ck)) == NULL) { return (NNG_ENOMEM); } mbedtls_x509_crt_init(&ck->crt); mbedtls_pk_init(&ck->key); pem = (const uint8_t *) cert; len = strlen(cert) + 1; if ((rv = mbedtls_x509_crt_parse(&ck->crt, pem, len)) != 0) { rv = tls_mkerr(rv); goto err; } pem = (const uint8_t *) key; len = strlen(key) + 1; rv = mbedtls_pk_parse_key(&ck->key, pem, len, (const uint8_t *) pass, pass != NULL ? strlen(pass) : 0); if (rv != 0) { rv = tls_mkerr(rv); goto err; } nni_mtx_lock(&cfg->lk); if (cfg->active) { nni_mtx_unlock(&cfg->lk); rv = NNG_ESTATE; goto err; } rv = mbedtls_ssl_conf_own_cert(&cfg->cfg_ctx, &ck->crt, &ck->key); if (rv != 0) { nni_mtx_unlock(&cfg->lk); rv = tls_mkerr(rv); goto err; } // Save this structure so we can free it with the context. nni_list_append(&cfg->certkeys, ck); nni_mtx_unlock(&cfg->lk); return (0); err: mbedtls_x509_crt_free(&ck->crt); mbedtls_pk_free(&ck->key); NNI_FREE_STRUCT(ck); return (rv); } int nng_tls_config_ca_file(nng_tls_config *cfg, const char *path) { int rv; void * fdata; size_t fsize; char * pem; // Note that while mbedTLS supports its own file methods, we want // to avoid depending on that because it might not have been // included, so we use our own. We have to read the file, and // then allocate a buffer that has an extra byte so we can // ensure NUL termination. The file named by path may contain // both a ca chain, and crl chain, or just a ca chain. if ((rv = nni_file_get(path, &fdata, &fsize)) != 0) { return (rv); } if ((pem = nni_zalloc(fsize + 1)) == NULL) { nni_free(fdata, fsize); return (NNG_ENOMEM); } memcpy(pem, fdata, fsize); nni_free(fdata, fsize); if (strstr(pem, "-----BEGIN X509 CRL-----") != NULL) { rv = nng_tls_config_ca_chain(cfg, pem, pem); } else { rv = nng_tls_config_ca_chain(cfg, pem, NULL); } nni_free(pem, fsize + 1); return (rv); } int nng_tls_config_cert_key_file( nng_tls_config *cfg, const char *path, const char *pass) { int rv; void * fdata; size_t fsize; char * pem; // Note that while mbedTLS supports its own file methods, we want // to avoid depending on that because it might not have been // included, so we use our own. We have to read the file, and // then allocate a buffer that has an extra byte so we can // ensure NUL termination. The file named by path must contain // both our certificate, and our private key. The password // may be NULL if the key is not encrypted. if ((rv = nni_file_get(path, &fdata, &fsize)) != 0) { return (rv); } if ((pem = nni_zalloc(fsize + 1)) == NULL) { nni_free(fdata, fsize); return (NNG_ENOMEM); } memcpy(pem, fdata, fsize); nni_free(fdata, fsize); rv = nng_tls_config_own_cert(cfg, pem, pem, pass); nni_free(pem, fsize + 1); return (rv); } int nng_tls_config_alloc(nng_tls_config **cfgp, nng_tls_mode mode) { return (nni_tls_config_init(cfgp, mode)); } void nng_tls_config_free(nng_tls_config *cfg) { nni_tls_config_fini(cfg); } void nng_tls_config_hold(nng_tls_config *cfg) { nni_tls_config_hold(cfg); }