aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CMakeLists.txt10
-rw-r--r--README.adoc16
-rw-r--r--cmake/FindmbedTLS.cmake80
-rw-r--r--docs/nng_tls.adoc242
-rw-r--r--src/CMakeLists.txt13
-rw-r--r--src/core/transport.c4
-rw-r--r--src/supplemental/README.adoc5
-rw-r--r--src/supplemental/mbedtls/CMakeLists.txt54
-rw-r--r--src/supplemental/mbedtls/tls.c1022
-rw-r--r--src/supplemental/tls.h84
-rw-r--r--src/transport/inproc/inproc.h2
-rw-r--r--src/transport/ipc/ipc.h2
-rw-r--r--src/transport/tcp/tcp.h2
-rw-r--r--src/transport/tls/CMakeLists.txt19
-rw-r--r--src/transport/tls/tls.c1081
-rw-r--r--src/transport/tls/tls.h62
-rw-r--r--src/transport/zerotier/zerotier.h14
-rw-r--r--tests/CMakeLists.txt1
-rw-r--r--tests/tls.c186
-rw-r--r--tests/trantest.h90
20 files changed, 2947 insertions, 42 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 6cd49b9f..27f0321e 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -36,6 +36,8 @@ include (CheckLibraryExists)
include (CheckCSourceCompiles)
include (GNUInstallDirs)
+set(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake)
+
if (POLICY CMP0042)
# Newer cmake on MacOS should use @rpath
cmake_policy (SET CMP0042 NEW)
@@ -170,6 +172,14 @@ option (NNG_TRANSPORT_ZEROTIER "Enable ZeroTier transport (requires libzerotierc
if (NNG_TRANSPORT_ZEROTIER)
add_definitions (-DNNG_HAVE_ZEROTIER)
endif ()
+
+option (NNG_TRANSPORT_TLS "Enable TLS transport (requires mbedtls)" OFF)
+if (NNG_TRANSPORT_TLS)
+ set(NNG_MBEDTLS_ENABLE ON)
+ add_definitions (-DNNG_MBEDTLS_ENABLE)
+ add_definitions (-DNNG_HAVE_TLS)
+endif()
+
# Platform checks.
if (NNG_ENABLE_COVERAGE)
diff --git a/README.adoc b/README.adoc
index fc4bbcfa..cd2fb949 100644
--- a/README.adoc
+++ b/README.adoc
@@ -7,15 +7,14 @@ image:https://img.shields.io/appveyor/ci/nanomsg/nng/master.svg?label=windows[Wi
image:https://codecov.io/gh/nanomsg/nng/branch/master/graph/badge.svg?label=coverage[Coverage,link="https://codecov.io/gh/nanomsg/nng"]
This repository represents a work in progress rewrite of the SP protocol
-library called "libnanomsg". The work is being done by Garrett D'Amore,
-and at this juncture he is not yet soliciting implementation assistance;
-that said if you want to help with this project please file an issue
-with details about what you'd like to do, and you will be contacted.
+library called "libnanomsg". This is pre-release, but at this point you
+can start using it for development, as we believe we are getting closer
+to release readiness.
Review and testing feedback are appreciated however; but please understand
that the project is still quite preliminary.
-This is a work in progress, and is *not* for suitable for product use or
+This is a work in progress, and is *not* for suitable for production use or
publication. When the library is ready for broader consumption, an
announcement will be posted on the nanomsg mailing list and website.
@@ -37,9 +36,14 @@ you can for example do:
$ make test
----
+If you want to enable the TLS transport, which is not supported by legacy
+nanomsg, use -DNNG_TRANSPORT_TLS=ON in your cmake command line. You will
+need to have the ARM mbedTLS library installed. (This is available in
+Xenial Ubuntu as libmbedtls-dev).
+
The API is not yet stable, but if you want to explore using the newer
API, pleaes have a look at the `nng.h` header file. A legacy compatible
`nng_compat.h` header is available and offers API compatibility with legacy
_nanomsg_.
- - Garrett D'Amore (Oct. 30, 2017)
+ - Garrett D'Amore (Nov. 20, 2017)
diff --git a/cmake/FindmbedTLS.cmake b/cmake/FindmbedTLS.cmake
new file mode 100644
index 00000000..e0c0aa5a
--- /dev/null
+++ b/cmake/FindmbedTLS.cmake
@@ -0,0 +1,80 @@
+#
+# Copyright 2017 Garrett D'Amore <garrett@damore.org>
+# Copyright 2017 Capitar IT Group BV <info@capitar.com>
+#
+# 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.
+#
+
+#
+# Try to find the mbed TLS libraries.
+#
+# Sets the following:
+#
+# MBEDTLS_INCLUDE_DIR - Where to find ssl.h, etc.
+# MBEDTLS_FOUND - True if we found mbedtls.
+# MBEDTLS_CRYPTO_LIBRARY - The mbedcrypto library.
+# MBEDTLS_X509_LIBRARY - The mbedx509 library.
+# MBEDTLS_TLS_LIBRARY - The mbedtls library.
+# MBEDTLS_LIBRARIES - List of all three mbedtls libraries.
+# MBEDTLS_VERSION - $major.$minor.$revision (e.g. ``2.6.0``).
+#
+# Hints:
+#
+# Set ``MBEDTLS_ROOT_DIR`` to the root directory of mbed TLS installation.
+#
+
+set(_MBEDTLS_ROOT_HINTS ${MBEDTLS_ROOT_DIR} ENV MBEDTLS_ROOT_DIR)
+
+include(FindPackageHandleStandardArgs)
+
+find_path(MBEDTLS_INCLUDE_DIR
+ NAMES mbedtls/ssl.h
+ HINTS ${_MBEDTLS_ROOT_HINTS}
+ PATHS /usr/local
+ PATH_SUFFIXES include)
+
+find_library(MBEDTLS_CRYPTO_LIBRARY
+ NAMES mbedcrypto
+ HINTS ${_MBEDTLS_ROOT_HINTS}
+ PATHS /usr/local
+ PATH_SUFFIXES lib)
+
+find_library(MBEDTLS_X509_LIBRARY
+ NAMES mbedx509
+ HINTS ${_MBEDTLS_ROOT_HINTS}
+ PATHS /usr/local
+ PATH_SUFFIXES lib)
+
+find_library(MBEDTLS_TLS_LIBRARY
+ NAMES mbedtls
+ HINTS ${_MBEDTLS_ROOT_HINTS}
+ PATHS /usr/local
+ PATH_SUFFIXES lib)
+
+set(MBEDTLS_LIBRARIES
+ ${MBEDTLS_TLS_LIBRARY}
+ ${MBEDTLS_X509_LIBRARY}
+ ${MBEDTLS_CRYPTO_LIBRARY})
+
+if (${MBEDTLS_TLS_LIBRARY-NOTFOUND})
+ message(FATAL_ERROR "Failed to find mbed TLS library")
+endif()
+
+mark_as_advanced(
+ MBEDSSL_INCLUDE_DIR
+ MBEDTLS_LIBRRIES
+ MBEDTLS_CRYPTO_LIBRARY
+ MBEDTLS_X509_LIBRARY
+ MBEDTLS_TLS_LIBRARY)
+
+# Extract the version from the header... hopefully it matches the library.
+file(STRINGS ${MBEDTLS_INCLUDE_DIR}/mbedtls/version.h _MBEDTLS_VERLINE
+ REGEX "^#define[ \t]+MBEDTLS_VERSION_STRING[\t ].*")
+string(REGEX REPLACE ".*MBEDTLS_VERSION_STRING[\t ]+\"(.*)\"" "\\1" MBEDTLS_VERSION ${_MBEDTLS_VERLINE})
+
+find_package_handle_standard_args(mbedTLS
+ REQUIRED_VARS MBEDTLS_TLS_LIBRARY MBEDTLS_CRYPTO_LIBRARY MBEDTLS_X509_LIBRARY MBEDTLS_INCLUDE_DIR VERSION_VAR MBEDTLS_VERSION)
+
diff --git a/docs/nng_tls.adoc b/docs/nng_tls.adoc
new file mode 100644
index 00000000..34221f6b
--- /dev/null
+++ b/docs/nng_tls.adoc
@@ -0,0 +1,242 @@
+nng_tls(7)
+==========
+:doctype: manpage
+:manmanual: nng
+:mansource: nng
+:icons: font
+:source-highlighter: pygments
+:copyright: Copyright 2017 Garrett D'Amore <garrett@damore.org> \
+ Copyright 2017 Staysail Systems, Inc. <info@staysail.tech> \
+ Copyright 2017 Capitar IT Group BV <info@capitar.com> \
+ 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.
+
+NAME
+----
+nng_tls - TLS transport for nng
+
+SYNOPSIS
+--------
+
+[source,c]
+----------
+#include <nng/transport/tls/tls.h>
+
+int nng_tls_register(void);
+----------
+
+DESCRIPTION
+-----------
+
+The _nng_tls_ transport provides communication support between
+_nng_ sockets across a TCP/IP network using
+https://tools.ietf.org/html/rfc5246[TLS v1.2] on top of
+https://tools.ietf.org/html/rfc793[TCP]. Both IPv4 and IPv6
+are supported when the underlying platform also supports it.
+
+The protocol details are documented in
+http://nanomsg.org/rfcs/sp-tls-v1.html[TLS Mapping for Scalability Protocols].
+
+Registration
+~~~~~~~~~~~~
+
+Depending upon how the library was built, it may be necessary to
+register the transport by calling `nng_tls_register`. This function
+returns zero on success, or an nng error value if the transport
+cannot be initialized for any reason.
+
+Availability
+~~~~~~~~~~~~
+
+The _tls_ transport depends on the use of an external library.
+As of this writing, https://tls.mbed.org/[mbed TLS] is required.
+
+TIP: Applications may need to add this library (or libraries) to
+their link line, particularly when using a statically built
+_nng_ library.
+
+NOTE: The mbed TLS library uses different licensing terms than
+_nng_ itself; as of this writing it is offered under either
+https://opensource.org/licenses/Apache-2.0[Apache License 2.0] or
+https://opensource.org/licenses/gpl-license[GNU GPL] terms.
+You are responsible for understanding and adhering to the
+license terms of any libraries you make use of.
+
+URI Format
+~~~~~~~~~~
+
+This transport uses URIs using the scheme `tls://`, followed by
+an IP address or hostname, followed by a colon and finally a
+TCP port number. For example, to contact port 4433 on the localhost
+either of the following URIs could be used: `tls://127.0.0.1:4433` or
+`tcp://localhost:4433`.
+
+When specifying IPv6 addresses, the address must be enclosed in
+square brackets (`[]`) to avoid confusion with the final colon
+separating the port.
+
+For example, the same port 4433 on the IPv6 loopback address ('::1') would
+be specified as `tcp://[::1]:4433`.
+
+NOTE: When using symbolic names, the name is resolved when the
+name is first used. _nng_ won't become aware of changes in the
+name resolution until restart,
+usually.footnote:[This is a bug and will likely be fixed in the future.]
+
+TIP: Certificate validation generally works when using names
+rather than IP addresses. This transport automatically
+uses the name supplied in the URL when validating the
+certificate supplied by the server.
+
+The special value of 0 (`INADDR_ANY`) can be used for a listener
+to indicate that it should listen on all interfaces on the host.
+A short-hand for this form is to either omit the address, or specify
+the asterisk (`*`) character. For example, the following three
+URIs are all equivalent, and could be used to listen to port 9999
+on the host:
+
+ 1. `tls://0.0.0.0:9999`
+ 2. `tls://*:9999`
+ 3. `tls://:9999`
+
+The entire URI must be less than `NNG_MAXADDRLEN` bytes long.
+
+Socket Address
+~~~~~~~~~~~~~~
+
+When using an `nng_sockaddr` structure, the actual structure is either
+of type `nng_sockaddr_in` (for IPv4) or `nng_sockaddr_in6` (for IPv6).
+These are `struct` types with the following definitions:
+
+[source,c]
+--------
+#define NNG_AF_INET 3 <1>
+#define NNG_AF_INET6 4
+#define NNG_MAXADDRLEN 128
+
+typedef struct {
+ // ... <2>
+ uint16_t sa_family; // must be NNG_AF_INET
+ uint16_t sa_port; // TCP port number
+ uint32_t sa_addr;
+ // ...
+} nng_sockaddr_in;
+
+typedef struct {
+ // ... <2>
+ uint16_t sa_family; // must be NNG_AF_INET6
+ uint16_t sa_port; // TCP port number
+ uint8_t sa_addr[16];
+ // ...
+} nng_sockaddr_in6;
+--------
+<1> The values of these macros may change, so applications
+should avoid depending upon their values and instead use them symbolically.
+<2> Other members may be present, but only those listed here
+are suitable for application use.
+
+The `sa_family` member will have the value `NNG_AF_INET` or `NNG_AF_INET6`.
+The `sa_port` and `sa_addr` are the TCP port number and address, both in
+network byte order (most significant byte is first).
+
+X.509 Formats
+~~~~~~~~~~~~~
+
+The _tls_ transport supports certificates and key material provided
+in either PEM or DER encoding. When using PEM format data, the
+encoding must be at the start of the data, with no intervening
+content. Furthermore, PEM encoded objects may have a terminating
+NUL byte, which will be ignored if present.
+
+Transport Options
+~~~~~~~~~~~~~~~~~
+
+The following transport options are available. Note that
+setting these must be done before the transport is started.
+
+`NNG_OPT_TLS_CA_CERT`::
+
+This is a write-only binay object containing a certificate
+chain, consisting of one or more X.509 certificates encoded in
+either PEM or DER format. These certificates are used to
+validate the peer. If multiple certificates are presented,
+they must be in the same format.
+
+`NNG_OPT_TLS_CRL`::
+
+This is a write-only CRL (revocation list) in X.509 format,
+specifying certificates which may not be used.
+
+`NNG_OPT_TLS_CERT`::
+
+This is an X.509 certificate containing the peers
+own public credentials. For servers, this option may be supplied
+multiple times, in order to specify multiple certificates
+in order to offer different algorithms. Clients can only
+have a single certificate.
+
+`NNG_OPT_TLS_PRIVATE_KEY`::
+
+This is an encoded private key, corresponding to the most
+recently established certificate.
+
+`NNG_OPT_TLS_PRIVATE_KEY_PASSWORD`::
+
+This is a string (NUL byte terminated) used to decrypt the
+most recently supplied private key, if the private key
+is encrypted. (If the private key is not encrypted, then
+this option need not be supplied.)
+
+`NNG_OPT_TLS_AUTH_MODE`::
+
+This is a write only integer, indicating whether the
+peer should be authenticated. It can take one of the
+following values:
++
+[cols="1,2"]
+|===
+
+| `nng_tls_auth_mode_none`
+| No authentication of the peer is performed.
+
+| `nng_tls_auth_mode_optional`
+| The peer certificate is checked if presented, but is not required to be valid or present.
+
+| `nng_tls_auth_mode_required`
+| The peer certificate must be present and valid.
+|===
++
+The default is `nng_tls_auth_mode_required` for
+clients (meaning the server must present a valid
+certificate) and `nng_tls_auth_mode_none` for
+servers (meaning any client may connect).
++
+TIP: For TLS client authentication, set this to
+`nng_auth_mode_required` and set the value
+of `NNG_OPT_TLS_CA_CERT` to a certificate corresponding
+to your own Certificate Authority.
+
+`NNG_OPT_TLS_AUTH_VERIFIED`::
+
+This is a read-only boolean option available only for
+pipes, indicating whether the peer certificate was
+valdiated or not. This is only set when the pipe
+has completed the handshake with the peer (which always
+occurs before exchanging data), and will only be set
+if the `NNG_OPT_TLS_AUTH_MODE` option is set to
+`nng_tls_auth_mode_optional` or `nng_tls_auth_mode_required`.
+
+SEE ALSO
+--------
+<<nng.adoc#,nng(7)>>
+
+COPYRIGHT
+---------
+
+Copyright 2017 mailto:info@staysail.tech[Staysail Systems, Inc.] +
+Copyright 2017 mailto:info@capitar.com[Capitar IT Group BV]
+
+This document is supplied under the terms of the
+https://opensource.org/licenses/LICENSE.txt[MIT License].
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 4901789b..291769f0 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -74,7 +74,7 @@ set (NNG_SOURCES
core/timer.h
core/transport.c
core/transport.h
-)
+ )
if (NNG_PLATFORM_POSIX)
set (NNG_SOURCES ${NNG_SOURCES}
@@ -119,9 +119,7 @@ if (NNG_PLATFORM_WINDOWS)
)
endif()
-install(FILES transport/inproc/inproc.h
- DESTINATION include/nng/transport/inproc)
-
+add_subdirectory(supplemental/mbedtls)
add_subdirectory(protocol/bus0)
add_subdirectory(protocol/pair0)
@@ -134,6 +132,7 @@ add_subdirectory(protocol/survey0)
add_subdirectory(transport/inproc)
add_subdirectory(transport/ipc)
add_subdirectory(transport/tcp)
+add_subdirectory(transport/tls)
add_subdirectory(transport/zerotier)
include_directories(AFTER SYSTEM ${PROJECT_SOURCE_DIR}/src
@@ -183,11 +182,6 @@ if (CMAKE_THREAD_LIBS_INIT)
endif()
# pkg-config file
-if (NNG_REQUIRED_LIBRARIES)
- foreach (lib ${NNG_REQUIRED_LIBRARIES})
- set (NNG_REQUIRED_LFLAGS "${NNG_REQUIRED_LFLAGS} -l${lib}")
- endforeach()
-endif()
#configure_file (pkgconfig.in ${PROJECT_NAME}.pc @ONLY)
#install (
# FILES ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}.pc
@@ -200,4 +194,3 @@ install (TARGETS ${PROJECT_NAME} ${PROJECT_NAME}_static
# Promote settings to parent
set(NNG_REQUIRED_LIBRARIES ${NNG_REQUIRED_LIBRARIES} PARENT_SCOPE)
-set(NNG_REQUIRED_LFLAGS ${NNG_REQUIRED_LFLAGS} PARENT_SCOPE) \ No newline at end of file
diff --git a/src/core/transport.c b/src/core/transport.c
index af9c93fb..359b03fd 100644
--- a/src/core/transport.c
+++ b/src/core/transport.c
@@ -12,6 +12,7 @@
#include "transport/inproc/inproc.h"
#include "transport/ipc/ipc.h"
#include "transport/tcp/tcp.h"
+#include "transport/tls/tls.h"
#include "transport/zerotier/zerotier.h"
#include <stdio.h>
@@ -150,6 +151,9 @@ static nni_tran_ctor nni_tran_ctors[] = {
#ifdef NNG_HAVE_TCP
nng_tcp_register,
#endif
+#ifdef NNG_HAVE_TLS
+ nng_tls_register,
+#endif
#ifdef NNI_HAVE_ZEROTIER
nng_zt_register,
#endif
diff --git a/src/supplemental/README.adoc b/src/supplemental/README.adoc
new file mode 100644
index 00000000..05483ae7
--- /dev/null
+++ b/src/supplemental/README.adoc
@@ -0,0 +1,5 @@
+= supplemental sources
+
+This is for code that is not intrinsic to nng, but which may be used
+by multiple other subsystems. For example, common code for TLS support
+used by multiple transports is located here.
diff --git a/src/supplemental/mbedtls/CMakeLists.txt b/src/supplemental/mbedtls/CMakeLists.txt
new file mode 100644
index 00000000..5c2de10b
--- /dev/null
+++ b/src/supplemental/mbedtls/CMakeLists.txt
@@ -0,0 +1,54 @@
+#
+# Copyright 2017 Garrett D'Amore <garrett@damore.org>
+# Copyright 2017 Capitar IT Group BV <info@capitar.com>
+#
+# This software is supplied under the terms of the MIT License, a
+# copy of which should be located in the distribution where this
+# file was obtained (LICENSE.txt). A copy of the license may also be
+# found online at https://opensource.org/licenses/MIT.
+#
+
+# MBEDTLS library
+
+# This requires the mbedTLS library be installed somewhere. You can
+# point this at a suitable installation of mbedTLS by setting
+# MBEDTLS_ROOT_DIR to point at the root of the installation (prefix).
+
+# It is possible to minimize the mbedTLS library quite a bit. We do
+# not require legacy algorithms, the net_sockets layer, the filesystem
+# I/O, as well as various other tidbits. We provide an entropy source,
+# so you can disable that in mbedTLS too. You may disable fallback support,
+# as we only support TLS v1.2 at present. (You may also therefore remove
+# code to support older versions of TLS/SSL.) You may also remove DTLS,
+# since we're not using it now (nor are we likely to in the near feature).
+# Also you may remove support for ZLIB compression, we don't use it either
+# (and it would be insecure to do so.) PEM and X509 writing (encoding)
+# is not needed (but parse support is!) You may also remove session support,
+# as we don't use that either.
+#
+# (Look for a sample config.h in this directory, if you want to build
+# a minimized version just for nng.)
+
+# What we do require is support for TLSv1.2
+
+if (NNG_MBEDTLS_ENABLE)
+ set(SUPP_SOURCES supplemental/mbedtls/tls.c supplemental/tls.h)
+ Find_Package(mbedTLS REQUIRED)
+
+ # If it isn't already in the link list, add the TLS libraries there.
+ # or something, so we take care not to duplicate it).
+ list(FIND NNG_REQUIRED_LIBRARIES ${MBEDTLS_TLS_LIBRARY} _index)
+ if (_index EQUAL -1)
+ set(NNG_REQUIRED_LIBRARIES ${NNG_REQUIRED_LIBRARIES} ${MBEDTLS_LIBRARIES})
+ set(NNG_REQUIRED_LIBRARIES ${NNG_REQUIRED_LIBRARIES} PARENT_SCOPE)
+ endif()
+
+ # Likewise for the include search path.
+ list(FIND NNG_REQUIRED_INCLUDES ${MBEDTLS_INCLUDE_DIR} _index)
+ if (_index EQUAL -1)
+ set(NNG_REQUIRED_INCLUDES ${NNG_REQUIRED_INCLUDES} ${MBEDTLS_INCLUDE_DIR})
+ set(NNG_REQUIRED_INCLUDES ${NNG_REQUIRED_INCLUDES} PARENT_SCOPE)
+ endif()
+endif()
+
+set(NNG_SOURCES ${NNG_SOURCES} ${SUPP_SOURCES} PARENT_SCOPE)
diff --git a/src/supplemental/mbedtls/tls.c b/src/supplemental/mbedtls/tls.c
new file mode 100644
index 00000000..80660469
--- /dev/null
+++ b/src/supplemental/mbedtls/tls.c
@@ -0,0 +1,1022 @@
+//
+// Copyright 2017 Staysail Systems, Inc. <info@staysail.tech>
+// Copyright 2017 Capitar IT Group BV <info@capitar.com>
+//
+// 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 <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "mbedtls/error.h"
+#include "mbedtls/net.h"
+#include "mbedtls/ssl.h"
+
+#include "core/nng_impl.h"
+
+#include "supplemental/tls.h"
+
+// Implementation note. This implementation buffers data between the TLS
+// encryption layer (mbedTLS) and the underlying TCP socket. As a result,
+// there may be some additional latency caused by buffer draining and
+// refilling. In the future we might want to investigate some kind of
+// double buffer policy to allow data to flow without entering a true
+// empty state.
+
+// NNG_TLS_MAX_SEND_SIZE limits the amount of data we will buffer for sending,
+// exerting backpressure if this size is exceeded. The 16K is aligned to the
+// maximum TLS record size.
+#ifndef NNG_TLS_MAX_SEND_SIZE
+#define NNG_TLS_MAX_SEND_SIZE 16384
+#endif
+
+// NNG_TLS_MAX_RECV_SIZE limits the amount of data we will receive in a single
+// operation. As we have to buffer data, this drives the size of our
+// intermediary buffer. The 16K is aligned to the maximum TLS record size.
+#ifndef NNG_TLX_MAX_RECV_SIZE
+#define NNG_TLS_MAX_RECV_SIZE 16384
+#endif
+
+typedef struct nni_tls_certkey {
+ char * pass;
+ uint8_t * crt;
+ uint8_t * key;
+ size_t crtlen;
+ size_t keylen;
+ mbedtls_x509_crt mcrt;
+ mbedtls_pk_context mpk;
+ nni_list_node node;
+} nni_tls_certkey;
+
+struct nni_tls {
+ nni_plat_tcp_pipe * tcp;
+ mbedtls_ssl_context ctx;
+ nni_mtx lk;
+ nni_aio * tcp_send;
+ nni_aio * tcp_recv;
+ bool sending;
+ bool recving;
+ bool closed;
+ bool hsdone;
+ bool tls_closed; // upper TLS layer closed
+ bool tcp_closed; // underlying TCP buffer closed
+ uint8_t * sendbuf; // send buffer
+ int sendlen; // amount of data in send buffer
+ int sendoff; // offset of start of send data
+ uint8_t * recvbuf; // recv buffer
+ int recvlen; // amount of data in recv buffer
+ int recvoff; // offset of start of recv data
+ nni_list sends; // upper side sends
+ nni_list recvs; // upper recv aios
+ nni_aio * handshake; // handshake aio (upper)
+};
+
+struct nni_tls_config {
+ mbedtls_ssl_config cfg_ctx;
+ nni_mtx lk;
+ bool active;
+ char * server_name;
+#ifdef NNG_TLS_USE_CTR_DRBG
+ mbedtls_ctr_drbg_context rng_ctx;
+ nni_mtx rng_lk;
+#endif
+ mbedtls_x509_crt ca_certs;
+ mbedtls_x509_crl crl;
+ bool have_ca_certs;
+ bool have_crl;
+
+ nni_list certkeys;
+};
+
+static void nni_tls_send_cb(void *);
+static void nni_tls_recv_cb(void *);
+
+static void nni_tls_do_send(nni_tls *);
+static void nni_tls_do_recv(nni_tls *);
+static void nni_tls_do_handshake(nni_tls *);
+
+static int nni_tls_net_send(void *, const unsigned char *, size_t);
+static int nni_tls_net_recv(void *, unsigned char *, size_t);
+
+static void
+nni_tls_dbg(void *ctx, int level, const char *file, int line, const char *s)
+{
+ char buf[128];
+ snprintf(buf, sizeof(buf), "%s:%04d: %s", file, line, s);
+ nni_plat_println(buf);
+}
+
+static int
+nni_tls_get_entropy(void *arg, unsigned char *buf, size_t len)
+{
+ NNI_ARG_UNUSED(arg);
+ while (len) {
+ uint32_t x = nni_random();
+ size_t n;
+
+ n = len < sizeof(x) ? len : sizeof(x);
+ memcpy(buf, &x, n);
+ len -= n;
+ buf += n;
+ }
+ return (0);
+}
+
+static int
+nni_tls_random(void *arg, unsigned char *buf, size_t sz)
+{
+#ifdef NNG_TLS_USE_CTR_DRBG
+ int rv;
+ nni_tls_config *cfg = arg;
+ NNI_ARG_UNUSED(arg);
+
+ nni_mtx_lock(&cfg->rng_lk);
+ rv = mbedtls_ctr_drbg_random(&cfg->rng_ctx, buf, sz);
+ nni_mtx_unlock(&cfg->rng_lk);
+ return (rv);
+#else
+ return (nni_tls_get_entropy(arg, buf, sz));
+#endif
+}
+
+void
+nni_tls_config_fini(nni_tls_config *cfg)
+{
+ nni_tls_certkey *ck;
+
+ mbedtls_ssl_config_free(&cfg->cfg_ctx);
+#ifdef NNG_TLS_USE_CTR_DRBG
+ mbedtls_ctr_drbg_free(&cfg->rng_ctx);
+#endif
+ mbedtls_x509_crt_free(&cfg->ca_certs);
+ mbedtls_x509_crl_free(&cfg->crl);
+ if (cfg->server_name) {
+ nni_strfree(cfg->server_name);
+ }
+ while ((ck = nni_list_first(&cfg->certkeys))) {
+ nni_list_remove(&cfg->certkeys, ck);
+ if (ck->pass) {
+ nni_strfree(ck->pass);
+ }
+ if (ck->crt) {
+ nni_free(ck->crt, ck->crtlen);
+ }
+ if (ck->key) {
+ nni_free(ck->key, ck->keylen);
+ }
+ mbedtls_x509_crt_free(&ck->mcrt);
+ mbedtls_pk_free(&ck->mpk);
+
+ NNI_FREE_STRUCT(ck);
+ }
+ nni_mtx_fini(&cfg->lk);
+ NNI_FREE_STRUCT(cfg);
+}
+
+int
+nni_tls_config_init(nni_tls_config **cpp, int mode)
+{
+ nni_tls_config *cfg;
+ int rv;
+ int sslmode;
+ int authmode;
+
+ if ((cfg = NNI_ALLOC_STRUCT(cfg)) == NULL) {
+ return (NNG_ENOMEM);
+ }
+ nni_mtx_init(&cfg->lk);
+ if (mode == NNI_TLS_CONFIG_SERVER) {
+ sslmode = MBEDTLS_SSL_IS_SERVER;
+ authmode = MBEDTLS_SSL_VERIFY_NONE;
+ } else {
+ sslmode = MBEDTLS_SSL_IS_CLIENT;
+ authmode = MBEDTLS_SSL_VERIFY_REQUIRED;
+ }
+
+ NNI_LIST_INIT(&cfg->certkeys, nni_tls_certkey, node);
+ mbedtls_ssl_config_init(&cfg->cfg_ctx);
+ mbedtls_x509_crt_init(&cfg->ca_certs);
+ mbedtls_x509_crl_init(&cfg->crl);
+
+ rv = mbedtls_ssl_config_defaults(&cfg->cfg_ctx, sslmode,
+ MBEDTLS_SSL_TRANSPORT_STREAM, MBEDTLS_SSL_PRESET_DEFAULT);
+ if (rv != 0) {
+ nni_tls_config_fini(cfg);
+ return (rv);
+ }
+
+ mbedtls_ssl_conf_authmode(&cfg->cfg_ctx, authmode);
+
+ // We *require* TLS v1.2 or newer, which is also known as SSL v3.3.
+ mbedtls_ssl_conf_min_version(&cfg->cfg_ctx,
+ MBEDTLS_SSL_MAJOR_VERSION_3, MBEDTLS_SSL_MINOR_VERSION_3);
+
+#ifdef NNG_TLS_USE_CTR_DRBG
+ mbedtls_ctr_drbg_init(&cfg->rng_ctx);
+ rv = mbedtls_ctr_drbg_seed(
+ &cfg->rng_ctx, nni_tls_get_entropy, NULL, NULL, 0);
+ if (rv != 0) {
+ nni_tls_config_fini(cfg);
+ return (rv);
+ }
+#endif
+ mbedtls_ssl_conf_rng(&cfg->cfg_ctx, nni_tls_random, cfg);
+
+ mbedtls_ssl_conf_dbg(&cfg->cfg_ctx, nni_tls_dbg, cfg);
+
+ *cpp = cfg;
+ return (0);
+}
+
+void
+nni_tls_fini(nni_tls *tp)
+{
+ // Shut it all down first.
+ if (tp->tcp) {
+ nni_plat_tcp_pipe_close(tp->tcp);
+ }
+ nni_aio_stop(tp->tcp_send);
+ nni_aio_stop(tp->tcp_recv);
+
+ // And finalize / free everything.
+ if (tp->tcp) {
+ nni_plat_tcp_pipe_fini(tp->tcp);
+ }
+ nni_aio_fini(tp->tcp_send);
+ nni_aio_fini(tp->tcp_recv);
+ mbedtls_ssl_free(&tp->ctx);
+ nni_mtx_fini(&tp->lk);
+ nni_free(tp->recvbuf, NNG_TLS_MAX_RECV_SIZE);
+ nni_free(tp->sendbuf, NNG_TLS_MAX_RECV_SIZE);
+ NNI_FREE_STRUCT(tp);
+}
+
+void
+nni_tls_strerror(int errnum, char *buf, size_t sz)
+{
+ if (errnum & NNG_ETRANERR) {
+ errnum &= ~NNG_ETRANERR;
+ errnum = -errnum;
+
+ mbedtls_strerror(errnum, buf, sz);
+ } else {
+ (void) snprintf(buf, sz, "%s", nng_strerror(errnum));
+ }
+}
+
+// nni_tls_mkerr converts an mbed error to an NNG error. In all cases
+// we just encode with NNG_ETRANERR.
+static int
+nni_tls_mkerr(int err)
+{
+ err = -err;
+ err |= NNG_ETRANERR;
+ return (err);
+}
+
+int
+nni_tls_init(nni_tls **tpp, nni_tls_config *cfg, nni_plat_tcp_pipe *tcp)
+{
+ nni_tls *tp;
+ int rv;
+
+ if ((tp = NNI_ALLOC_STRUCT(tp)) == NULL) {
+ return (NNG_ENOMEM);
+ }
+ if ((tp->recvbuf = nni_alloc(NNG_TLS_MAX_RECV_SIZE)) == NULL) {
+ NNI_FREE_STRUCT(tp);
+ return (NNG_ENOMEM);
+ }
+ if ((tp->sendbuf = nni_alloc(NNG_TLS_MAX_SEND_SIZE)) == NULL) {
+ nni_free(tp->sendbuf, NNG_TLS_MAX_RECV_SIZE);
+ NNI_FREE_STRUCT(tp);
+ return (NNG_ENOMEM);
+ }
+
+ nni_mtx_lock(&cfg->lk);
+ // No more changes allowed to config.
+ if (cfg->active == false) {
+ nni_tls_certkey *ck;
+
+ rv = 0;
+
+ if (cfg->have_ca_certs || cfg->have_crl) {
+ mbedtls_ssl_conf_ca_chain(
+ &cfg->cfg_ctx, &cfg->ca_certs, &cfg->crl);
+ }
+ NNI_LIST_FOREACH (&cfg->certkeys, ck) {
+ if (rv != 0) {
+ break;
+ }
+ if (rv == 0) {
+ rv = mbedtls_x509_crt_parse(
+ &ck->mcrt, ck->crt, ck->crtlen);
+ }
+ if (rv == 0) {
+ rv = mbedtls_pk_parse_key(&ck->mpk, ck->key,
+ ck->keylen, (uint8_t *) ck->pass,
+ ck->pass != NULL ? strlen(ck->pass) : 0);
+ }
+ if (rv == 0) {
+ rv = mbedtls_ssl_conf_own_cert(
+ &cfg->cfg_ctx, &ck->mcrt, &ck->mpk);
+ }
+
+ if (rv != 0) {
+ break;
+ }
+ }
+ if (rv != 0) {
+ nni_mtx_unlock(&cfg->lk);
+ nni_tls_fini(tp);
+ return (nni_tls_mkerr(rv));
+ }
+ cfg->active = true;
+ }
+ nni_mtx_unlock(&cfg->lk);
+
+ nni_aio_list_init(&tp->sends);
+ nni_aio_list_init(&tp->recvs);
+ nni_mtx_init(&tp->lk);
+ mbedtls_ssl_init(&tp->ctx);
+ mbedtls_ssl_set_bio(
+ &tp->ctx, tp, nni_tls_net_send, nni_tls_net_recv, NULL);
+
+ if ((rv = mbedtls_ssl_setup(&tp->ctx, &cfg->cfg_ctx)) != 0) {
+ rv = nni_tls_mkerr(rv);
+ nni_tls_fini(tp);
+ return (rv);
+ }
+
+ if (cfg->server_name) {
+ mbedtls_ssl_set_hostname(&tp->ctx, cfg->server_name);
+ }
+
+ tp->tcp = tcp;
+
+ if (((rv = nni_aio_init(&tp->tcp_send, nni_tls_send_cb, tp)) != 0) ||
+ ((rv = nni_aio_init(&tp->tcp_recv, nni_tls_recv_cb, tp)) != 0)) {
+ nni_tls_fini(tp);
+ return (rv);
+ }
+
+ nni_mtx_lock(&tp->lk);
+ // Kick off a handshake operation.
+ nni_tls_do_handshake(tp);
+ nni_mtx_unlock(&tp->lk);
+
+ *tpp = tp;
+ return (0);
+}
+
+static void
+nni_tls_cancel(nni_aio *aio, int rv)
+{
+ nni_tls *tp = aio->a_prov_data;
+ nni_mtx_lock(&tp->lk);
+ if (nni_aio_list_active(aio)) {
+ nni_aio_list_remove(aio);
+ nni_aio_finish_error(aio, rv);
+ }
+ nni_mtx_unlock(&tp->lk);
+}
+
+// nni_tls_send_cb is called when the underlying TCP send completes.
+static void
+nni_tls_send_cb(void *ctx)
+{
+ nni_tls *tp = ctx;
+ nni_aio *aio = tp->tcp_send;
+
+ nni_mtx_lock(&tp->lk);
+ if (nni_aio_result(aio) != 0) {
+ nni_plat_tcp_pipe_close(tp->tcp);
+ tp->tcp_closed = true;
+ } else {
+ size_t n = nni_aio_count(aio);
+ NNI_ASSERT(tp->sendlen <= n);
+ tp->sendlen -= n;
+ if (tp->sendlen) {
+ tp->sendoff += n;
+
+ aio->a_niov = 1;
+ aio->a_iov[0].iov_buf = tp->sendbuf + tp->sendoff;
+ aio->a_iov[0].iov_len = tp->sendlen;
+ nni_aio_set_timeout(aio, -1); // No timeout.
+ nni_plat_tcp_pipe_send(tp->tcp, aio);
+ nni_mtx_unlock(&tp->lk);
+ return;
+ }
+ tp->sendoff = 0;
+ tp->sending = false;
+ }
+ if (!tp->hsdone) {
+ nni_tls_do_handshake(tp);
+ }
+ if (tp->hsdone) {
+ nni_tls_do_send(tp);
+ nni_tls_do_recv(tp);
+ }
+ nni_mtx_unlock(&tp->lk);
+}
+
+static void
+nni_tls_recv_start(nni_tls *tp)
+{
+ nni_aio *aio;
+
+ if (tp->recving || tp->tcp_closed) {
+ return;
+ }
+ // If we already have data, wait for that to be consumed before
+ // doing another read.
+ if (tp->recvlen != 0) {
+ return;
+ }
+
+ tp->recving = 1;
+ tp->recvoff = 0;
+ aio = tp->tcp_recv;
+ aio->a_niov = 1;
+ aio->a_iov[0].iov_buf = tp->recvbuf;
+ aio->a_iov[0].iov_len = NNG_TLS_MAX_RECV_SIZE;
+ nni_aio_set_timeout(tp->tcp_recv, -1); // No timeout.
+ nni_plat_tcp_pipe_recv(tp->tcp, aio);
+}
+
+static void
+nni_tls_recv_cb(void *ctx)
+{
+ nni_tls *tp = ctx;
+ nni_aio *aio = tp->tcp_recv;
+
+ nni_mtx_lock(&tp->lk);
+ tp->recving = false;
+ if (nni_aio_result(aio) != 0) {
+ // Close the underlying TCP channel, but permit data we
+ // already received to continue to be received.
+ nni_plat_tcp_pipe_close(tp->tcp);
+ tp->tcp_closed = true;
+ } else {
+ NNI_ASSERT(tp->recvlen == 0);
+ NNI_ASSERT(tp->recvoff == 0);
+ tp->recvlen = nni_aio_count(aio);
+ }
+
+ // If we were closed (above), the upper layer will detect and
+ // react properly. Otherwise the upper layer will consume
+ // data.
+ if (!tp->hsdone) {
+ nni_tls_do_handshake(tp);
+ }
+ if (tp->hsdone) {
+ nni_tls_do_recv(tp);
+ nni_tls_do_send(tp);
+ }
+
+ nni_mtx_unlock(&tp->lk);
+}
+
+// This handles the bottom half send (i.e. sending over TCP).
+// We always accept a chunk of data, to a limit, if the bottom
+// sender is not busy. Then we handle that in the background.
+// If the sender *is* busy, we return MBEDTLS_ERR_SSL_WANT_WRITE.
+// The chunk size we accept is 64k at a time, which prevents
+// ridiculous over queueing. This is always called with the pipe
+// lock held, and never blocks.
+int
+nni_tls_net_send(void *ctx, const unsigned char *buf, size_t len)
+{
+ nni_tls *tp = ctx;
+
+ if (len > NNG_TLS_MAX_SEND_SIZE) {
+ len = NNG_TLS_MAX_SEND_SIZE;
+ }
+
+ // We should already be running with the pipe lock held,
+ // as we are running in that context.
+
+ if (tp->sending) {
+ return (MBEDTLS_ERR_SSL_WANT_WRITE);
+ }
+ if (tp->tcp_closed) {
+ return (MBEDTLS_ERR_NET_SEND_FAILED);
+ }
+
+ tp->sending = 1;
+ tp->sendlen = len;
+ tp->sendoff = 0;
+ memcpy(tp->sendbuf, buf, len);
+
+ tp->tcp_send->a_niov = 1;
+ tp->tcp_send->a_iov[0].iov_buf = tp->sendbuf;
+ tp->tcp_send->a_iov[0].iov_len = len;
+ nni_aio_set_timeout(tp->tcp_send, -1); // No timeout.
+ nni_plat_tcp_pipe_send(tp->tcp, tp->tcp_send);
+ return (len);
+}
+
+static int
+nni_tls_net_recv(void *ctx, unsigned char *buf, size_t len)
+{
+ nni_tls *tp = ctx;
+
+ // We should already be running with the pipe lock held,
+ // as we are running in that context.
+ if (tp->tcp_closed && tp->recvlen == 0) {
+ return (MBEDTLS_ERR_NET_RECV_FAILED);
+ }
+
+ if (tp->recvlen == 0) {
+ len = MBEDTLS_ERR_SSL_WANT_READ;
+ } else {
+ if (len > tp->recvlen) {
+ len = tp->recvlen;
+ }
+ memcpy(buf, tp->recvbuf + tp->recvoff, len);
+ tp->recvoff += len;
+ tp->recvlen -= len;
+ }
+
+ nni_tls_recv_start(tp);
+ return ((int) len);
+}
+
+// nni_tls_send is the exported send function. It has a similar
+// calling convention as the platform TCP pipe.
+void
+nni_tls_send(nni_tls *tp, nni_aio *aio)
+{
+ nni_mtx_lock(&tp->lk);
+ if (nni_aio_start(aio, nni_tls_cancel, tp) != 0) {
+ nni_mtx_unlock(&tp->lk);
+ return;
+ }
+ if (tp->tls_closed) {
+ nni_mtx_unlock(&tp->lk);
+ nni_aio_finish_error(aio, NNG_ECLOSED);
+ return;
+ }
+ nni_list_append(&tp->sends, aio);
+ nni_tls_do_send(tp);
+ nni_mtx_unlock(&tp->lk);
+}
+
+void
+nni_tls_recv(nni_tls *tp, nni_aio *aio)
+{
+ nni_mtx_lock(&tp->lk);
+ if (nni_aio_start(aio, nni_tls_cancel, tp) != 0) {
+ nni_mtx_unlock(&tp->lk);
+ return;
+ }
+ if (tp->tls_closed) {
+ nni_mtx_unlock(&tp->lk);
+ nni_aio_finish_error(aio, NNG_ECLOSED);
+ return;
+ }
+ nni_list_append(&tp->recvs, aio);
+ nni_tls_do_recv(tp);
+ nni_mtx_unlock(&tp->lk);
+}
+
+void
+nni_tls_do_handshake(nni_tls *tp)
+{
+ int rv;
+
+ if (tp->tls_closed) {
+ return;
+ }
+ rv = mbedtls_ssl_handshake(&tp->ctx);
+ switch (rv) {
+ case MBEDTLS_ERR_SSL_WANT_WRITE:
+ case MBEDTLS_ERR_SSL_WANT_READ:
+ // We have underlying I/O to complete first. We will
+ // be called again by a callback later.
+ return;
+ case 0:
+ // The handshake is done, yay!
+ tp->hsdone = true;
+ return;
+
+ default:
+ // Some other error occurred... would be nice to be
+ // able to diagnose it better.
+ tp->tls_closed = true;
+ nni_plat_tcp_pipe_close(tp->tcp);
+ tp->tcp_closed = true;
+ }
+}
+
+// nni_tls_do_send is called to try to send more data if we have not
+// yet completed the I/O. It also completes any transactions that
+// *have* completed. It must be called with the lock held.
+static void
+nni_tls_do_send(nni_tls *tp)
+{
+ nni_aio *aio;
+
+ while ((aio = nni_list_first(&tp->sends)) != NULL) {
+ int n;
+ uint8_t *buf = NULL;
+ size_t len = 0;
+
+ for (int i = 0; i < aio->a_niov; i++) {
+ if (aio->a_iov[i].iov_len != 0) {
+ buf = aio->a_iov[i].iov_buf;
+ len = aio->a_iov[i].iov_len;
+ break;
+ }
+ }
+ if (len == 0 || buf == NULL) {
+ nni_aio_list_remove(aio);
+ nni_aio_finish_error(aio, NNG_EINVAL);
+ continue;
+ }
+
+ n = mbedtls_ssl_write(&tp->ctx, buf, len);
+
+ if ((n == MBEDTLS_ERR_SSL_WANT_WRITE) ||
+ (n == MBEDTLS_ERR_SSL_WANT_READ)) {
+ // Cannot send any more data right now, wait
+ // for callback.
+ return;
+ }
+ // Some other error occurred... this is not good.
+ // Want better diagnostics.
+ nni_aio_list_remove(aio);
+ if (n < 0) {
+ nni_aio_finish_error(aio, nni_tls_mkerr(n));
+ } else {
+ nni_aio_finish(aio, 0, n);
+ }
+ }
+}
+
+static void
+nni_tls_do_recv(nni_tls *tp)
+{
+ nni_aio *aio;
+
+ while ((aio = nni_list_first(&tp->recvs)) != NULL) {
+ int n;
+ uint8_t *buf = NULL;
+ size_t len = 0;
+
+ for (int i = 0; i < aio->a_niov; i++) {
+ if (aio->a_iov[i].iov_len != 0) {
+ buf = aio->a_iov[i].iov_buf;
+ len = aio->a_iov[i].iov_len;
+ break;
+ }
+ }
+ if (len == 0 || buf == NULL) {
+ nni_aio_list_remove(aio);
+ nni_aio_finish_error(aio, NNG_EINVAL);
+ continue;
+ }
+ n = mbedtls_ssl_read(&tp->ctx, buf, len);
+
+ if ((n == MBEDTLS_ERR_SSL_WANT_READ) ||
+ (n == MBEDTLS_ERR_SSL_WANT_WRITE)) {
+ // Cannot receive any more data right now, wait
+ // for callback.
+ return;
+ }
+
+ nni_aio_list_remove(aio);
+
+ if (n < 0) {
+ nni_aio_finish_error(aio, nni_tls_mkerr(n));
+ } else {
+ nni_aio_finish(aio, 0, n);
+ }
+ }
+}
+
+void
+nni_tls_close(nni_tls *tp)
+{
+ nni_aio *aio;
+
+ nni_mtx_lock(&tp->lk);
+ tp->tls_closed = true;
+
+ while ((aio = nni_list_first(&tp->sends)) != NULL) {
+ nni_aio_list_remove(aio);
+ nni_aio_finish_error(aio, NNG_ECLOSED);
+ }
+
+ while ((aio = nni_list_first(&tp->recvs)) != NULL) {
+ nni_aio_list_remove(aio);
+ nni_aio_finish_error(aio, NNG_ECLOSED);
+ }
+
+ if (tp->hsdone) {
+ // This may succeed, or it may fail. Either way we
+ // don't care. Implementations that depend on
+ // close-notify to mean anything are broken by design,
+ // just like RFC. Note that we do *NOT* close the TCP
+ // connection at this point.
+ (void) mbedtls_ssl_close_notify(&tp->ctx);
+ } else {
+ nni_plat_tcp_pipe_close(tp->tcp);
+ tp->tcp_closed = true;
+ }
+ nni_mtx_unlock(&tp->lk);
+}
+
+const char *
+nni_tls_ciphersuite_name(nni_tls *tp)
+{
+ return (mbedtls_ssl_get_ciphersuite(&tp->ctx));
+}
+
+int
+nni_tls_verified(nni_tls *tp)
+{
+ int rv;
+
+ rv = mbedtls_ssl_get_verify_result(&tp->ctx);
+ return (rv ? 1 : 0);
+}
+
+int
+nni_tls_config_server_name(nni_tls_config *cfg, const char *name)
+{
+ int rv;
+ nni_mtx_lock(&cfg->lk);
+ if (cfg->active) {
+ nni_mtx_unlock(&cfg->lk);
+ return (NNG_ESTATE);
+ }
+ if (cfg->server_name) {
+ nni_strfree(cfg->server_name);
+ }
+ cfg->server_name = nni_strdup(name);
+ rv = cfg->server_name == NULL ? NNG_ENOMEM : 0;
+ nni_mtx_unlock(&cfg->lk);
+ return (rv);
+}
+
+int
+nni_tls_config_auth_mode(nni_tls_config *cfg, int mode)
+{
+ nni_mtx_lock(&cfg->lk);
+ if (cfg->active) {
+ nni_mtx_unlock(&cfg->lk);
+ return (NNG_ESTATE);
+ }
+ switch (mode) {
+ case NNI_TLS_CONFIG_AUTH_MODE_NONE:
+ mbedtls_ssl_conf_authmode(
+ &cfg->cfg_ctx, MBEDTLS_SSL_VERIFY_NONE);
+ break;
+ case NNI_TLS_CONFIG_AUTH_MODE_OPTIONAL:
+ mbedtls_ssl_conf_authmode(
+ &cfg->cfg_ctx, MBEDTLS_SSL_VERIFY_OPTIONAL);
+ break;
+ case NNI_TLS_CONFIG_AUTH_MODE_REQUIRED:
+ mbedtls_ssl_conf_authmode(
+ &cfg->cfg_ctx, MBEDTLS_SSL_VERIFY_REQUIRED);
+ break;
+ default:
+ nni_mtx_unlock(&cfg->lk);
+ return (NNG_EINVAL);
+ }
+ nni_mtx_unlock(&cfg->lk);
+ return (0);
+}
+
+#define PEMSTART "-----BEGIN "
+
+// nni_tls_copy_key_cert_material copies either PEM or DER encoded
+// key material. It allocates an extra byte for a NUL terminator
+// required by mbed TLS if the data is PEM and missing the terminator.
+// It is required that the key material passed in begins with the
+// PEM delimiter if it is actually PEM.
+static int
+nni_tls_copy_key_cert_material(
+ uint8_t **dstp, size_t *szp, const uint8_t *src, size_t sz)
+{
+ bool addz = false;
+ uint8_t *dst;
+
+ if ((sz > strlen(PEMSTART)) &&
+ (strncmp((const char *) src, PEMSTART, strlen(PEMSTART)) == 0) &&
+ (src[sz - 1] != '\0')) {
+ addz = true;
+ }
+
+ if (addz) {
+ if ((dst = nni_alloc(sz + 1)) != NULL) {
+ memcpy(dst, src, sz);
+ dst[sz] = '\0';
+ sz++;
+ }
+ } else {
+ if ((dst = nni_alloc(sz)) != NULL) {
+ memcpy(dst, src, sz);
+ }
+ }
+ if (dst == NULL) {
+ return (NNG_ENOMEM);
+ }
+ *dstp = dst;
+ *szp = sz;
+ return (0);
+}
+
+int
+nni_tls_config_cert(nni_tls_config *cfg, const uint8_t *key, size_t sz)
+{
+ int rv = 0;
+ nni_tls_certkey *ck;
+ bool cknew;
+
+ if (sz < 1) {
+ return (NNG_EINVAL);
+ }
+
+ nni_mtx_lock(&cfg->lk);
+ if (cfg->active) {
+ rv = NNG_ESTATE;
+ goto err;
+ }
+ cknew = false;
+ if (((ck = nni_list_last(&cfg->certkeys)) == NULL) ||
+ (ck->crt != NULL)) {
+ if ((ck = NNI_ALLOC_STRUCT(ck)) == NULL) {
+ rv = NNG_ENOMEM;
+ goto err;
+ }
+ mbedtls_pk_init(&ck->mpk);
+ mbedtls_x509_crt_init(&ck->mcrt);
+ cknew = true;
+ }
+
+ rv = nni_tls_copy_key_cert_material(&ck->crt, &ck->crtlen, key, sz);
+ if (rv != 0) {
+ goto err;
+ }
+ if (cknew) {
+ nni_list_append(&cfg->certkeys, ck);
+ }
+err:
+ nni_mtx_unlock(&cfg->lk);
+
+ return (rv);
+}
+
+int
+nni_tls_config_key(nni_tls_config *cfg, const uint8_t *key, size_t sz)
+{
+ int rv = 0;
+ nni_tls_certkey *ck;
+ bool cknew;
+
+ if (sz < 1) {
+ return (NNG_EINVAL);
+ }
+
+ nni_mtx_lock(&cfg->lk);
+ if (cfg->active) {
+ rv = NNG_ESTATE;
+ goto err;
+ }
+ cknew = false;
+ if (((ck = nni_list_last(&cfg->certkeys)) == NULL) ||
+ (ck->key != NULL)) {
+ if ((ck = NNI_ALLOC_STRUCT(ck)) == NULL) {
+ rv = NNG_ENOMEM;
+ goto err;
+ }
+ cknew = true;
+ }
+
+ rv = nni_tls_copy_key_cert_material(&ck->key, &ck->keylen, key, sz);
+ if (rv != 0) {
+ goto err;
+ }
+ if (cknew) {
+ nni_list_append(&cfg->certkeys, ck);
+ }
+err:
+ nni_mtx_unlock(&cfg->lk);
+
+ return (rv);
+}
+
+int
+nni_tls_config_pass(nni_tls_config *cfg, const char *pass)
+{
+ int rv = 0;
+ nni_tls_certkey *ck;
+ bool cknew;
+
+ if (pass == NULL) {
+ return (NNG_EINVAL);
+ }
+
+ nni_mtx_lock(&cfg->lk);
+ if (cfg->active) {
+ rv = NNG_ESTATE;
+ goto err;
+ }
+ cknew = false;
+ if (((ck = nni_list_last(&cfg->certkeys)) == NULL) ||
+ (ck->pass != NULL)) {
+ if ((ck = NNI_ALLOC_STRUCT(ck)) == NULL) {
+ rv = NNG_ENOMEM;
+ goto err;
+ }
+ cknew = true;
+ }
+
+ if ((ck->pass = nni_strdup(pass)) != NULL) {
+ rv = NNG_ENOMEM;
+ goto err;
+ }
+ rv = 0;
+ if (cknew) {
+ nni_list_append(&cfg->certkeys, ck);
+ }
+err:
+ nni_mtx_unlock(&cfg->lk);
+
+ return (rv);
+}
+
+int
+nni_tls_config_ca_cert(nni_tls_config *cfg, const uint8_t *data, size_t sz)
+{
+ uint8_t *tmp;
+ size_t len = sz;
+ int rv = 0;
+
+ if (sz < 1) {
+ return (NNG_EINVAL);
+ }
+
+ if ((rv = nni_tls_copy_key_cert_material(&tmp, &len, data, sz)) != 0) {
+ return (NNG_ENOMEM);
+ }
+
+ nni_mtx_lock(&cfg->lk);
+ if (cfg->active) {
+ rv = NNG_ESTATE;
+ goto err;
+ }
+ if ((rv = mbedtls_x509_crt_parse(&cfg->ca_certs, tmp, len)) != 0) {
+ rv = nni_tls_mkerr(rv);
+ } else {
+ cfg->have_ca_certs = true;
+ }
+err:
+ nni_mtx_unlock(&cfg->lk);
+ nni_free(tmp, len);
+ if (rv != 0) {
+ nni_panic("panic:");
+ }
+ return (rv);
+}
+
+int
+nni_tls_config_crl(nni_tls_config *cfg, const uint8_t *data, size_t sz)
+{
+ int rv;
+ uint8_t *tmp;
+ size_t len;
+
+ if (sz < 1) {
+ return (NNG_EINVAL);
+ }
+
+ if ((rv = nni_tls_copy_key_cert_material(&tmp, &len, data, sz)) != 0) {
+ return (NNG_ENOMEM);
+ }
+
+ nni_mtx_lock(&cfg->lk);
+ if (cfg->active) {
+ rv = NNG_ESTATE;
+ goto err;
+ }
+
+ if ((rv = mbedtls_x509_crl_parse(&cfg->crl, tmp, len)) != 0) {
+ rv = nni_tls_mkerr(rv);
+ } else {
+ cfg->have_crl = true;
+ }
+err:
+ nni_mtx_unlock(&cfg->lk);
+ nni_free(tmp, len);
+ return (rv);
+}
+#endif // NNG_MBEDTLS_ENABLE \ No newline at end of file
diff --git a/src/supplemental/tls.h b/src/supplemental/tls.h
new file mode 100644
index 00000000..da2fe8cd
--- /dev/null
+++ b/src/supplemental/tls.h
@@ -0,0 +1,84 @@
+//
+// Copyright 2017 Staysail Systems, Inc. <info@staysail.tech>
+// Copyright 2017 Capitar IT Group BV <info@capitar.com>
+//
+// This software is supplied under the terms of the MIT License, a
+// copy of which should be located in the distribution where this
+// file was obtained (LICENSE.txt). A copy of the license may also be
+// found online at https://opensource.org/licenses/MIT.
+//
+
+#ifndef NNG_SUPPLEMENTAL_TLS_H
+#define NNG_SUPPLEMENTAL_TLS_H
+
+// nni_tls represents the context for a single TLS stream.
+typedef struct nni_tls nni_tls;
+
+// nni_tls_config is the context for full TLS configuration, normally
+// associated with an endpoint, for example.
+typedef struct nni_tls_config nni_tls_config;
+
+#define NNI_TLS_CONFIG_SERVER 1
+#define NNI_TLS_CONFIG_CLIENT 0
+
+extern int nni_tls_config_init(nni_tls_config **, int);
+extern void nni_tls_config_fini(nni_tls_config *);
+
+// nni_tls_config_server_name is used by clients to set the server name
+// that they expect to be talking to. This may also support the SNI
+// extension for virtual hosting.
+extern int nni_tls_config_server_name(nni_tls_config *, const char *);
+
+// nni_tls_config_ca_cert configures one or more CAs used for validation
+// of peer certificates. Multiple CAs (and their chains) may be configured
+// by either calling this multiple times, or by specifying a list of
+// certificates as concatenated data. The certs may be in PEM or DER
+// format.
+extern int nni_tls_config_ca_cert(nni_tls_config *, const uint8_t *, size_t);
+
+// nni_tls_config_clr loads a certificate revocation list. Again, these
+// are in X.509 format (either PEM or DER).
+extern int nni_tls_config_crl(nni_tls_config *, const uint8_t *, size_t);
+
+// nni_tls_config_cert is used to load our own certificate. For servers,
+// this may be called more than once to configure multiple different keys,
+// for example with different algorithms depending on what the peer supports.
+// On the client, only a single option is available.
+extern int nni_tls_config_cert(nni_tls_config *, const uint8_t *crt, size_t);
+extern int nni_tls_config_key(nni_tls_config *, const uint8_t *, size_t);
+extern int nni_tls_config_pass(nni_tls_config *, const char *);
+
+// nni_tls_config_validate_peer is used to enable validation of the peer
+// and it's certificate. If disabled, the peer's certificate will still
+// be available, but may not be valid.
+extern int nni_tls_config_validate_peer(nni_tls_config *, bool);
+
+// nni_tls_config_auth_mode is a read-ony option that is used to configure
+// the authentication mode use. The default is that servers have this off
+// (i.e. no client authentication) and clients have it on (they verify
+// the server), which matches typical practice.
+extern int nni_tls_config_auth_mode(nni_tls_config *, int);
+#define NNI_TLS_CONFIG_AUTH_MODE_NONE 0 // No verification is performed
+#define NNI_TLS_CONFIG_AUTH_MODE_OPTIONAL 1 // Verify cert if presented
+#define NNI_TLS_CONFIG_AUTH_MODE_REQUIRED 2 // Verify cert, close if invalid
+
+extern int nni_tls_init(nni_tls **, nni_tls_config *, nni_plat_tcp_pipe *);
+extern void nni_tls_close(nni_tls *);
+extern void nni_tls_fini(nni_tls *);
+extern void nni_tls_send(nni_tls *, nni_aio *);
+extern void nni_tls_recv(nni_tls *, nni_aio *);
+
+// nni_tls_verified returns true if the peer, or false if the peer did not
+// verify. (During the handshake phase, the peer is not verified, so this
+// might return false if executed too soon. The verification status will
+// be accurate once the handshake is finished, however.
+extern int nni_tls_verified(nni_tls *);
+
+// nni_tls_ciphersuite_name returns the name of the ciphersuite in use.
+extern const char *nni_tls_ciphersuite_name(nni_tls *);
+
+// TBD: getting additional peer certificate information...
+
+extern void nni_tls_strerror(int, char *, size_t); // review this
+
+#endif // NNG_SUPPLEMENTAL_TLS_H
diff --git a/src/transport/inproc/inproc.h b/src/transport/inproc/inproc.h
index 9538b06f..bfd6e1ca 100644
--- a/src/transport/inproc/inproc.h
+++ b/src/transport/inproc/inproc.h
@@ -13,6 +13,6 @@
// inproc transport. This is used for intra-process communication.
-extern int nng_inproc_register(void);
+NNG_DECL int nng_inproc_register(void);
#endif // NNG_TRANSPORT_INPROC_INPROC_H
diff --git a/src/transport/ipc/ipc.h b/src/transport/ipc/ipc.h
index f19762c7..4c4c5708 100644
--- a/src/transport/ipc/ipc.h
+++ b/src/transport/ipc/ipc.h
@@ -14,6 +14,6 @@
// ipc transport. This is used for inter-process communication on
// the same host computer.
-extern int nng_ipc_register(void);
+NNG_DECL int nng_ipc_register(void);
#endif // NNG_TRANSPORT_IPC_IPC_H
diff --git a/src/transport/tcp/tcp.h b/src/transport/tcp/tcp.h
index b4c79461..6975109f 100644
--- a/src/transport/tcp/tcp.h
+++ b/src/transport/tcp/tcp.h
@@ -13,6 +13,6 @@
// TCP transport. This is used for communication over TCP/IP.
-extern int nng_tcp_register(void);
+NNG_DECL int nng_tcp_register(void);
#endif // NNG_TRANSPORT_TCP_TCP_H
diff --git a/src/transport/tls/CMakeLists.txt b/src/transport/tls/CMakeLists.txt
new file mode 100644
index 00000000..59ff3f5e
--- /dev/null
+++ b/src/transport/tls/CMakeLists.txt
@@ -0,0 +1,19 @@
+#
+# Copyright 2017 Staysail Systems, Inc. <info@staysail.tech>
+# Copyright 2017 Capitar IT Group BV <info@capitar.com>
+#
+# This software is supplied under the terms of the MIT License, a
+# copy of which should be located in the distribution where this
+# file was obtained (LICENSE.txt). A copy of the license may also be
+# found online at https://opensource.org/licenses/MIT.
+#
+
+# TLS transport
+
+if (NNG_TRANSPORT_TLS)
+ set(TLS_SOURCES transport/tls/tls.c transport/tls/tls.h)
+ install(FILES tls.h DESTINATION include/nng/transport/tls)
+
+endif()
+
+set(NNG_SOURCES ${NNG_SOURCES} ${TLS_SOURCES} PARENT_SCOPE)
diff --git a/src/transport/tls/tls.c b/src/transport/tls/tls.c
new file mode 100644
index 00000000..1bd83971
--- /dev/null
+++ b/src/transport/tls/tls.c
@@ -0,0 +1,1081 @@
+//
+// Copyright 2017 Staysail Systems, Inc. <info@staysail.tech>
+// Copyright 2017 Capitar IT Group BV <info@capitar.com>
+//
+// This software is supplied under the terms of the MIT License, a
+// copy of which should be located in the distribution where this
+// file was obtained (LICENSE.txt). A copy of the license may also be
+// found online at https://opensource.org/licenses/MIT.
+//
+
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "core/nng_impl.h"
+
+#include "supplemental/tls.h"
+#include "tls.h"
+
+// TLS over TCP transport. Platform specific TCP operations must be
+// supplied as well, and uses the supplemental TLS v1.2 code. It is not
+// an accident that this very closely resembles the TCP transport itself.
+
+typedef struct nni_tls_pipe nni_tls_pipe;
+typedef struct nni_tls_ep nni_tls_ep;
+
+// nni_tls_pipe is one end of a TLS connection.
+struct nni_tls_pipe {
+ const char * addr;
+ nni_plat_tcp_pipe *tcp;
+ uint16_t peer;
+ uint16_t proto;
+ size_t rcvmax;
+
+ nni_aio *user_txaio;
+ nni_aio *user_rxaio;
+ nni_aio *user_negaio;
+
+ uint8_t txlen[sizeof(uint64_t)];
+ uint8_t rxlen[sizeof(uint64_t)];
+ size_t gottxhead;
+ size_t gotrxhead;
+ size_t wanttxhead;
+ size_t wantrxhead;
+ nni_aio *txaio;
+ nni_aio *rxaio;
+ nni_aio *negaio;
+ nni_msg *rxmsg;
+ nni_mtx mtx;
+ nni_tls *tls;
+};
+
+struct nni_tls_ep {
+ char addr[NNG_MAXADDRLEN + 1];
+ nni_plat_tcp_ep *tep;
+ uint16_t proto;
+ size_t rcvmax;
+ nni_duration linger;
+ int ipv4only;
+ int authmode;
+ nni_aio * aio;
+ nni_aio * user_aio;
+ nni_mtx mtx;
+ nni_tls_config * cfg;
+};
+
+static void nni_tls_pipe_send_cb(void *);
+static void nni_tls_pipe_recv_cb(void *);
+static void nni_tls_pipe_nego_cb(void *);
+static void nni_tls_ep_cb(void *arg);
+
+static int
+nni_tls_tran_init(void)
+{
+ return (0);
+}
+
+static void
+nni_tls_tran_fini(void)
+{
+}
+
+static void
+nni_tls_pipe_close(void *arg)
+{
+ nni_tls_pipe *p = arg;
+
+ nni_tls_close(p->tls);
+}
+
+static void
+nni_tls_pipe_fini(void *arg)
+{
+ nni_tls_pipe *p = arg;
+
+ nni_aio_stop(p->rxaio);
+ nni_aio_stop(p->txaio);
+ nni_aio_stop(p->negaio);
+
+ nni_aio_fini(p->rxaio);
+ nni_aio_fini(p->txaio);
+ nni_aio_fini(p->negaio);
+
+ if (p->tls != NULL) {
+ nni_tls_fini(p->tls);
+ }
+ if (p->rxmsg) {
+ nni_msg_free(p->rxmsg);
+ }
+ NNI_FREE_STRUCT(p);
+}
+
+static int
+nni_tls_pipe_init(nni_tls_pipe **pipep, nni_tls_ep *ep, void *tpp)
+{
+ nni_tls_pipe * p;
+ nni_plat_tcp_pipe *tcp = tpp;
+ int rv;
+
+ if ((p = NNI_ALLOC_STRUCT(p)) == NULL) {
+ return (NNG_ENOMEM);
+ }
+ nni_mtx_init(&p->mtx);
+
+ if (((rv = nni_tls_init(&p->tls, ep->cfg, tcp)) != 0) ||
+ ((rv = nni_aio_init(&p->txaio, nni_tls_pipe_send_cb, p)) != 0) ||
+ ((rv = nni_aio_init(&p->rxaio, nni_tls_pipe_recv_cb, p)) != 0) ||
+ ((rv = nni_aio_init(&p->negaio, nni_tls_pipe_nego_cb, p)) != 0)) {
+ nni_tls_pipe_fini(p);
+ return (rv);
+ }
+
+ p->proto = ep->proto;
+ p->rcvmax = ep->rcvmax;
+ p->tcp = tcp;
+ p->addr = ep->addr;
+
+ *pipep = p;
+ return (0);
+}
+
+static void
+nni_tls_cancel_nego(nni_aio *aio, int rv)
+{
+ nni_tls_pipe *p = aio->a_prov_data;
+
+ nni_mtx_lock(&p->mtx);
+ if (p->user_negaio != aio) {
+ nni_mtx_unlock(&p->mtx);
+ return;
+ }
+ p->user_negaio = NULL;
+ nni_mtx_unlock(&p->mtx);
+
+ nni_aio_cancel(p->negaio, rv);
+ nni_aio_finish_error(aio, rv);
+}
+
+static void
+nni_tls_pipe_nego_cb(void *arg)
+{
+ nni_tls_pipe *p = arg;
+ nni_aio * aio = p->negaio;
+ int rv;
+
+ nni_mtx_lock(&p->mtx);
+ if ((rv = nni_aio_result(aio)) != 0) {
+ goto done;
+ }
+
+ // We start transmitting before we receive.
+ if (p->gottxhead < p->wanttxhead) {
+ p->gottxhead += nni_aio_count(aio);
+ } else if (p->gotrxhead < p->wantrxhead) {
+ p->gotrxhead += nni_aio_count(aio);
+ }
+
+ if (p->gottxhead < p->wanttxhead) {
+ aio->a_niov = 1;
+ aio->a_iov[0].iov_len = p->wanttxhead - p->gottxhead;
+ aio->a_iov[0].iov_buf = &p->txlen[p->gottxhead];
+ // send it down...
+ nni_tls_send(p->tls, aio);
+ nni_mtx_unlock(&p->mtx);
+ return;
+ }
+ if (p->gotrxhead < p->wantrxhead) {
+ aio->a_niov = 1;
+ aio->a_iov[0].iov_len = p->wantrxhead - p->gotrxhead;
+ aio->a_iov[0].iov_buf = &p->rxlen[p->gotrxhead];
+ nni_tls_recv(p->tls, aio);
+ nni_mtx_unlock(&p->mtx);
+ return;
+ }
+ // We have both sent and received the headers. Lets check the
+ // receive side header.
+ if ((p->rxlen[0] != 0) || (p->rxlen[1] != 'S') ||
+ (p->rxlen[2] != 'P') || (p->rxlen[3] != 0) || (p->rxlen[6] != 0) ||
+ (p->rxlen[7] != 0)) {
+ rv = NNG_EPROTO;
+ goto done;
+ }
+
+ NNI_GET16(&p->rxlen[4], p->peer);
+
+done:
+ if ((aio = p->user_negaio) != NULL) {
+ p->user_negaio = NULL;
+ nni_aio_finish(aio, rv, 0);
+ }
+ nni_mtx_unlock(&p->mtx);
+}
+
+static void
+nni_tls_pipe_send_cb(void *arg)
+{
+ nni_tls_pipe *p = arg;
+ int rv;
+ nni_aio * aio;
+ size_t n;
+ nni_msg * msg;
+ nni_aio * txaio = p->txaio;
+
+ nni_mtx_lock(&p->mtx);
+ if ((aio = p->user_txaio) == NULL) {
+ nni_mtx_unlock(&p->mtx);
+ return;
+ }
+
+ if ((rv = nni_aio_result(txaio)) != 0) {
+ p->user_txaio = NULL;
+ nni_mtx_unlock(&p->mtx);
+ msg = nni_aio_get_msg(aio);
+ nni_aio_set_msg(aio, NULL);
+ nni_msg_free(msg);
+ nni_aio_finish_error(aio, rv);
+ return;
+ }
+
+ n = nni_aio_count(txaio);
+ while (n) {
+ NNI_ASSERT(txaio->a_niov != 0);
+ if (txaio->a_iov[0].iov_len > n) {
+ txaio->a_iov[0].iov_len -= n;
+ txaio->a_iov[0].iov_buf += n;
+ break;
+ }
+ n -= txaio->a_iov[0].iov_len;
+ for (int i = 0; i < txaio->a_niov; i++) {
+ txaio->a_iov[i] = txaio->a_iov[i + 1];
+ }
+ txaio->a_niov--;
+ }
+ if ((txaio->a_niov != 0) && (txaio->a_iov[0].iov_len != 0)) {
+ nni_tls_send(p->tls, txaio);
+ nni_mtx_unlock(&p->mtx);
+ return;
+ }
+
+ nni_mtx_unlock(&p->mtx);
+ msg = nni_aio_get_msg(aio);
+ n = nni_msg_len(msg);
+ nni_aio_set_msg(aio, NULL);
+ nni_aio_finish(aio, 0, n);
+}
+
+static void
+nni_tls_pipe_recv_cb(void *arg)
+{
+ nni_tls_pipe *p = arg;
+ nni_aio * aio;
+ int rv;
+ size_t n;
+ nni_msg * msg;
+ nni_aio * rxaio = p->rxaio;
+
+ nni_mtx_lock(&p->mtx);
+
+ if ((aio = p->user_rxaio) == NULL) {
+ // Canceled.
+ nni_mtx_unlock(&p->mtx);
+ return;
+ }
+
+ if ((rv = nni_aio_result(p->rxaio)) != 0) {
+ goto recv_error;
+ }
+
+ n = nni_aio_count(p->rxaio);
+ while (n) {
+ NNI_ASSERT(rxaio->a_niov != 0);
+ if (rxaio->a_iov[0].iov_len > n) {
+ rxaio->a_iov[0].iov_len -= n;
+ rxaio->a_iov[0].iov_buf += n;
+ break;
+ }
+ n -= rxaio->a_iov[0].iov_len;
+ rxaio->a_niov--;
+ for (int i = 0; i < rxaio->a_niov; i++) {
+ rxaio->a_iov[i] = rxaio->a_iov[i + 1];
+ }
+ }
+ // Was this a partial read? If so then resubmit for the rest.
+ if ((rxaio->a_niov != 0) && (rxaio->a_iov[0].iov_len != 0)) {
+ nni_tls_recv(p->tls, rxaio);
+ nni_mtx_unlock(&p->mtx);
+ return;
+ }
+
+ // If we don't have a message yet, we were reading the TCP message
+ // header, which is just the length. This tells us the size of the
+ // message to allocate and how much more to expect.
+ if (p->rxmsg == NULL) {
+ uint64_t len;
+ // We should have gotten a message header.
+ NNI_GET64(p->rxlen, len);
+
+ // Make sure the message payload is not too big. If it is
+ // the caller will shut down the pipe.
+ if (len > p->rcvmax) {
+ rv = NNG_EMSGSIZE;
+ goto recv_error;
+ }
+
+ if ((rv = nng_msg_alloc(&p->rxmsg, (size_t) len)) != 0) {
+ goto recv_error;
+ }
+
+ // Submit the rest of the data for a read -- we want to
+ // read the entire message now.
+ if (len != 0) {
+ rxaio->a_iov[0].iov_buf = nni_msg_body(p->rxmsg);
+ rxaio->a_iov[0].iov_len = (size_t) len;
+ rxaio->a_niov = 1;
+
+ nni_tls_recv(p->tls, rxaio);
+ nni_mtx_unlock(&p->mtx);
+ return;
+ }
+ }
+
+ // We read a message completely. Let the user know the good news.
+ p->user_rxaio = NULL;
+ msg = p->rxmsg;
+ p->rxmsg = NULL;
+ nni_mtx_unlock(&p->mtx);
+ nni_aio_finish_msg(aio, msg);
+ return;
+
+recv_error:
+ p->user_rxaio = NULL;
+ msg = p->rxmsg;
+ p->rxmsg = NULL;
+ nni_mtx_unlock(&p->mtx);
+ nni_msg_free(msg);
+ nni_aio_finish_error(aio, rv);
+}
+
+static void
+nni_tls_cancel_tx(nni_aio *aio, int rv)
+{
+ nni_tls_pipe *p = aio->a_prov_data;
+
+ nni_mtx_lock(&p->mtx);
+ if (p->user_txaio != aio) {
+ nni_mtx_unlock(&p->mtx);
+ return;
+ }
+ p->user_txaio = NULL;
+ nni_mtx_unlock(&p->mtx);
+
+ // cancel the underlying operation.
+ nni_aio_cancel(p->txaio, rv);
+ nni_aio_finish_error(aio, rv);
+}
+
+static void
+nni_tls_pipe_send(void *arg, nni_aio *aio)
+{
+ nni_tls_pipe *p = arg;
+ nni_msg * msg = nni_aio_get_msg(aio);
+ uint64_t len;
+ nni_aio * txaio;
+ int niov;
+
+ len = nni_msg_len(msg) + nni_msg_header_len(msg);
+
+ nni_mtx_lock(&p->mtx);
+
+ if (nni_aio_start(aio, nni_tls_cancel_tx, p) != 0) {
+ nni_mtx_unlock(&p->mtx);
+ return;
+ }
+
+ p->user_txaio = aio;
+
+ NNI_PUT64(p->txlen, len);
+
+ niov = 0;
+ txaio = p->txaio;
+ txaio->a_iov[niov].iov_buf = p->txlen;
+ txaio->a_iov[niov].iov_len = sizeof(p->txlen);
+ niov++;
+ if (nni_msg_header_len(msg) > 0) {
+ txaio->a_iov[niov].iov_buf = nni_msg_header(msg);
+ txaio->a_iov[niov].iov_len = nni_msg_header_len(msg);
+ niov++;
+ }
+ if (nni_msg_len(msg) > 0) {
+ txaio->a_iov[niov].iov_buf = nni_msg_body(msg);
+ txaio->a_iov[niov].iov_len = nni_msg_len(msg);
+ niov++;
+ }
+ txaio->a_niov = niov;
+
+ nni_tls_send(p->tls, txaio);
+ nni_mtx_unlock(&p->mtx);
+}
+
+static void
+nni_tls_cancel_rx(nni_aio *aio, int rv)
+{
+ nni_tls_pipe *p = aio->a_prov_data;
+
+ nni_mtx_lock(&p->mtx);
+ if (p->user_rxaio != aio) {
+ nni_mtx_unlock(&p->mtx);
+ return;
+ }
+ p->user_rxaio = NULL;
+ nni_mtx_unlock(&p->mtx);
+
+ // cancel the underlying operation.
+ nni_aio_cancel(p->rxaio, rv);
+ nni_aio_finish_error(aio, rv);
+}
+
+static void
+nni_tls_pipe_recv(void *arg, nni_aio *aio)
+{
+ nni_tls_pipe *p = arg;
+ nni_aio * rxaio;
+
+ nni_mtx_lock(&p->mtx);
+
+ if (nni_aio_start(aio, nni_tls_cancel_rx, p) != 0) {
+ nni_mtx_unlock(&p->mtx);
+ return;
+ }
+ p->user_rxaio = aio;
+
+ NNI_ASSERT(p->rxmsg == NULL);
+
+ // Schedule a read of the TCP header.
+ rxaio = p->rxaio;
+ rxaio->a_iov[0].iov_buf = p->rxlen;
+ rxaio->a_iov[0].iov_len = sizeof(p->rxlen);
+ rxaio->a_niov = 1;
+
+ nni_tls_recv(p->tls, rxaio);
+ nni_mtx_unlock(&p->mtx);
+}
+
+static uint16_t
+nni_tls_pipe_peer(void *arg)
+{
+ nni_tls_pipe *p = arg;
+
+ return (p->peer);
+}
+
+static int
+nni_tls_pipe_getopt_locaddr(void *arg, void *v, size_t *szp)
+{
+ nni_tls_pipe *p = arg;
+ int rv;
+ nng_sockaddr sa;
+
+ memset(&sa, 0, sizeof(sa));
+ if ((rv = nni_plat_tcp_pipe_sockname(p->tcp, &sa)) == 0) {
+ rv = nni_getopt_sockaddr(&sa, v, szp);
+ }
+ return (rv);
+}
+
+static int
+nni_tls_pipe_getopt_remaddr(void *arg, void *v, size_t *szp)
+{
+ nni_tls_pipe *p = arg;
+ int rv;
+ nng_sockaddr sa;
+
+ memset(&sa, 0, sizeof(sa));
+ if ((rv = nni_plat_tcp_pipe_peername(p->tcp, &sa)) == 0) {
+ rv = nni_getopt_sockaddr(&sa, v, szp);
+ }
+ return (rv);
+}
+
+static int
+nni_tls_parse_pair(char *pair, char **hostp, char **servp)
+{
+ char *host, *serv, *end;
+
+ if (pair[0] == '[') {
+ host = pair + 1;
+ // IP address enclosed ... for IPv6 usually.
+ if ((end = strchr(host, ']')) == NULL) {
+ return (NNG_EADDRINVAL);
+ }
+ *end = '\0';
+ serv = end + 1;
+ if (*serv == ':') {
+ serv++;
+ } else if (*serv != '\0') {
+ return (NNG_EADDRINVAL);
+ }
+ } else {
+ host = pair;
+ serv = strchr(host, ':');
+ if (serv != NULL) {
+ *serv = '\0';
+ serv++;
+ }
+ }
+ if ((strlen(host) == 0) || (strcmp(host, "*") == 0)) {
+ *hostp = NULL;
+ } else {
+ *hostp = host;
+ }
+ if ((serv == NULL) || (strlen(serv) == 0)) {
+ *servp = NULL;
+ } else {
+ *servp = serv;
+ }
+ // Stash the port in big endian (network) byte order.
+ return (0);
+}
+
+// Note that the url *must* be in a modifiable buffer.
+int
+nni_tls_parse_url(char *url, char **lhost, char **lserv, char **rhost,
+ char **rserv, int mode)
+{
+ char *h1;
+ int rv;
+
+ if (strncmp(url, "tls://", strlen("tls://")) != 0) {
+ return (NNG_EADDRINVAL);
+ }
+ url += strlen("tls://");
+ if ((mode == NNI_EP_MODE_DIAL) && ((h1 = strchr(url, ';')) != 0)) {
+ // The local address is the first part, the remote address
+ // is the second part.
+ *h1 = '\0';
+ h1++;
+ if (((rv = nni_tls_parse_pair(h1, rhost, rserv)) != 0) ||
+ ((rv = nni_tls_parse_pair(url, lhost, lserv)) != 0)) {
+ return (rv);
+ }
+ if ((*rserv == NULL) || (*rhost == NULL)) {
+ // We have to know where to connect to!
+ return (NNG_EADDRINVAL);
+ }
+ } else if (mode == NNI_EP_MODE_DIAL) {
+ *lhost = NULL;
+ *lserv = NULL;
+ if ((rv = nni_tls_parse_pair(url, rhost, rserv)) != 0) {
+ return (rv);
+ }
+ if ((*rserv == NULL) || (*rhost == NULL)) {
+ // We have to know where to connect to!
+ return (NNG_EADDRINVAL);
+ }
+ } else {
+ NNI_ASSERT(mode == NNI_EP_MODE_LISTEN);
+ *rhost = NULL;
+ *rserv = NULL;
+ if ((rv = nni_tls_parse_pair(url, lhost, lserv)) != 0) {
+ return (rv);
+ }
+ // We have to have a port to listen on!
+ if (*lserv == NULL) {
+ return (NNG_EADDRINVAL);
+ }
+ }
+ return (0);
+}
+
+static void
+nni_tls_pipe_start(void *arg, nni_aio *aio)
+{
+ nni_tls_pipe *p = arg;
+ nni_aio * negaio;
+
+ nni_mtx_lock(&p->mtx);
+ p->txlen[0] = 0;
+ p->txlen[1] = 'S';
+ p->txlen[2] = 'P';
+ p->txlen[3] = 0;
+ NNI_PUT16(&p->txlen[4], p->proto);
+ NNI_PUT16(&p->txlen[6], 0);
+
+ p->user_negaio = aio;
+ p->gotrxhead = 0;
+ p->gottxhead = 0;
+ p->wantrxhead = 8;
+ p->wanttxhead = 8;
+ negaio = p->negaio;
+ negaio->a_niov = 1;
+ negaio->a_iov[0].iov_len = 8;
+ negaio->a_iov[0].iov_buf = &p->txlen[0];
+ if (nni_aio_start(aio, nni_tls_cancel_nego, p) != 0) {
+ nni_mtx_unlock(&p->mtx);
+ return;
+ }
+ nni_tls_send(p->tls, negaio);
+ nni_mtx_unlock(&p->mtx);
+}
+
+static void
+nni_tls_ep_fini(void *arg)
+{
+ nni_tls_ep *ep = arg;
+
+ nni_aio_stop(ep->aio);
+ if (ep->tep != NULL) {
+ nni_plat_tcp_ep_fini(ep->tep);
+ }
+ if (ep->cfg) {
+ nni_tls_config_fini(ep->cfg);
+ }
+ nni_aio_fini(ep->aio);
+ nni_mtx_fini(&ep->mtx);
+ NNI_FREE_STRUCT(ep);
+}
+
+static int
+nni_tls_ep_init(void **epp, const char *url, nni_sock *sock, int mode)
+{
+ nni_tls_ep * ep;
+ int rv;
+ char buf[NNG_MAXADDRLEN + 1];
+ char * rhost;
+ char * rserv;
+ char * lhost;
+ char * lserv;
+ nni_sockaddr rsa, lsa;
+ nni_aio * aio;
+ int passive;
+ int tlsmode;
+ int authmode;
+
+ // Make a copy of the url (to allow for destructive operations)
+ if (nni_strlcpy(buf, url, sizeof(buf)) >= sizeof(buf)) {
+ return (NNG_EADDRINVAL);
+ }
+
+ // Parse the URLs first.
+ rv = nni_tls_parse_url(buf, &lhost, &lserv, &rhost, &rserv, mode);
+ if (rv != 0) {
+ return (rv);
+ }
+ if (mode == NNI_EP_MODE_DIAL) {
+ passive = 0;
+ tlsmode = NNI_TLS_CONFIG_CLIENT;
+ authmode = NNI_TLS_CONFIG_AUTH_MODE_REQUIRED;
+ } else {
+ passive = 1;
+ tlsmode = NNI_TLS_CONFIG_SERVER;
+ authmode = NNI_TLS_CONFIG_AUTH_MODE_NONE;
+ }
+
+ if ((rv = nni_aio_init(&aio, NULL, NULL)) != 0) {
+ return (rv);
+ }
+
+ // XXX: arguably we could defer this part to the point we do a bind
+ // or connect!
+
+ if ((rhost != NULL) || (rserv != NULL)) {
+ aio->a_addr = &rsa;
+ nni_plat_tcp_resolv(rhost, rserv, NNG_AF_UNSPEC, passive, aio);
+ nni_aio_wait(aio);
+ if ((rv = nni_aio_result(aio)) != 0) {
+ nni_aio_fini(aio);
+ return (rv);
+ }
+ } else {
+ rsa.s_un.s_family = NNG_AF_UNSPEC;
+ }
+
+ if ((lhost != NULL) || (lserv != NULL)) {
+ aio->a_addr = &lsa;
+ nni_plat_tcp_resolv(lhost, lserv, NNG_AF_UNSPEC, passive, aio);
+ nni_aio_wait(aio);
+ if ((rv = nni_aio_result(aio)) != 0) {
+ nni_aio_fini(aio);
+ return (rv);
+ }
+ } else {
+ lsa.s_un.s_family = NNG_AF_UNSPEC;
+ }
+ nni_aio_fini(aio);
+
+ if ((ep = NNI_ALLOC_STRUCT(ep)) == NULL) {
+ return (NNG_ENOMEM);
+ }
+ nni_mtx_init(&ep->mtx);
+
+ if (nni_strlcpy(ep->addr, url, sizeof(ep->addr)) >= sizeof(ep->addr)) {
+ NNI_FREE_STRUCT(ep);
+ return (NNG_EADDRINVAL);
+ }
+
+ if (((rv = nni_plat_tcp_ep_init(&ep->tep, &lsa, &rsa, mode)) != 0) ||
+ ((rv = nni_tls_config_init(&ep->cfg, tlsmode)) != 0) ||
+ ((rv = nni_tls_config_auth_mode(ep->cfg, authmode)) != 0) ||
+ ((rv = nni_aio_init(&ep->aio, nni_tls_ep_cb, ep)) != 0)) {
+ nni_tls_ep_fini(ep);
+ return (rv);
+ }
+ if ((tlsmode == NNI_TLS_CONFIG_CLIENT) && (rhost != NULL)) {
+ if ((rv = nni_tls_config_server_name(ep->cfg, rhost)) != 0) {
+ nni_tls_ep_fini(ep);
+ return (rv);
+ }
+ }
+ ep->proto = nni_sock_proto(sock);
+ ep->authmode = authmode;
+
+ *epp = ep;
+ return (0);
+}
+
+static void
+nni_tls_ep_close(void *arg)
+{
+ nni_tls_ep *ep = arg;
+
+ nni_mtx_lock(&ep->mtx);
+ nni_plat_tcp_ep_close(ep->tep);
+ nni_mtx_unlock(&ep->mtx);
+
+ nni_aio_stop(ep->aio);
+}
+
+static int
+nni_tls_ep_bind(void *arg)
+{
+ nni_tls_ep *ep = arg;
+ int rv;
+
+ nni_mtx_lock(&ep->mtx);
+ rv = nni_plat_tcp_ep_listen(ep->tep);
+ nni_mtx_unlock(&ep->mtx);
+
+ return (rv);
+}
+
+static void
+nni_tls_ep_finish(nni_tls_ep *ep)
+{
+ nni_aio * aio;
+ int rv;
+ nni_tls_pipe *pipe = NULL;
+
+ if ((rv = nni_aio_result(ep->aio)) != 0) {
+ goto done;
+ }
+ NNI_ASSERT(nni_aio_get_pipe(ep->aio) != NULL);
+
+ // Attempt to allocate the parent pipe. If this fails we'll
+ // drop the connection (ENOMEM probably).
+ rv = nni_tls_pipe_init(&pipe, ep, nni_aio_get_pipe(ep->aio));
+
+done:
+ nni_aio_set_pipe(ep->aio, NULL);
+ aio = ep->user_aio;
+ ep->user_aio = NULL;
+
+ if ((aio != NULL) && (rv == 0)) {
+ nni_aio_finish_pipe(aio, pipe);
+ return;
+ }
+ if (pipe != NULL) {
+ nni_tls_pipe_fini(pipe);
+ }
+ if (aio != NULL) {
+ NNI_ASSERT(rv != 0);
+ nni_aio_finish_error(aio, rv);
+ }
+}
+
+static void
+nni_tls_ep_cb(void *arg)
+{
+ nni_tls_ep *ep = arg;
+
+ nni_mtx_lock(&ep->mtx);
+ nni_tls_ep_finish(ep);
+ nni_mtx_unlock(&ep->mtx);
+}
+
+static void
+nni_tls_cancel_ep(nni_aio *aio, int rv)
+{
+ nni_tls_ep *ep = aio->a_prov_data;
+
+ nni_mtx_lock(&ep->mtx);
+ if (ep->user_aio != aio) {
+ nni_mtx_unlock(&ep->mtx);
+ return;
+ }
+ ep->user_aio = NULL;
+ nni_mtx_unlock(&ep->mtx);
+
+ nni_aio_cancel(ep->aio, rv);
+ nni_aio_finish_error(aio, rv);
+}
+
+static void
+nni_tls_ep_accept(void *arg, nni_aio *aio)
+{
+ nni_tls_ep *ep = arg;
+ int rv;
+
+ nni_mtx_lock(&ep->mtx);
+ NNI_ASSERT(ep->user_aio == NULL);
+
+ if ((rv = nni_aio_start(aio, nni_tls_cancel_ep, ep)) != 0) {
+ nni_mtx_unlock(&ep->mtx);
+ return;
+ }
+
+ ep->user_aio = aio;
+
+ nni_plat_tcp_ep_accept(ep->tep, ep->aio);
+ nni_mtx_unlock(&ep->mtx);
+}
+
+static void
+nni_tls_ep_connect(void *arg, nni_aio *aio)
+{
+ nni_tls_ep *ep = arg;
+ int rv;
+
+ nni_mtx_lock(&ep->mtx);
+ NNI_ASSERT(ep->user_aio == NULL);
+
+ // If we can't start, then its dying and we can't report either.
+ if ((rv = nni_aio_start(aio, nni_tls_cancel_ep, ep)) != 0) {
+ nni_mtx_unlock(&ep->mtx);
+ return;
+ }
+
+ ep->user_aio = aio;
+
+ nni_plat_tcp_ep_connect(ep->tep, ep->aio);
+ nni_mtx_unlock(&ep->mtx);
+}
+
+static int
+nni_tls_ep_setopt_recvmaxsz(void *arg, const void *v, size_t sz)
+{
+ nni_tls_ep *ep = arg;
+ if (ep == NULL) {
+ return (nni_chkopt_size(v, sz, 0, NNI_MAXSZ));
+ }
+ return (nni_setopt_size(&ep->rcvmax, v, sz, 0, NNI_MAXSZ));
+}
+
+static int
+nni_tls_ep_getopt_recvmaxsz(void *arg, void *v, size_t *szp)
+{
+ nni_tls_ep *ep = arg;
+ return (nni_getopt_size(ep->rcvmax, v, szp));
+}
+
+static int
+nni_tls_ep_setopt_linger(void *arg, const void *v, size_t sz)
+{
+ nni_tls_ep *ep = arg;
+ if (ep == NULL) {
+ return (nni_chkopt_ms(v, sz));
+ }
+ return (nni_setopt_ms(&ep->linger, v, sz));
+}
+
+static int
+nni_tls_ep_getopt_linger(void *arg, void *v, size_t *szp)
+{
+ nni_tls_ep *ep = arg;
+ return (nni_getopt_ms(ep->linger, v, szp));
+}
+
+static int
+tls_setopt_ca_cert(void *arg, const void *data, size_t sz)
+{
+ nni_tls_ep *ep = arg;
+
+ if (ep == NULL) {
+ return (0);
+ }
+ return (nni_tls_config_ca_cert(ep->cfg, data, sz));
+}
+
+static int
+tls_setopt_cert(void *arg, const void *data, size_t sz)
+{
+ nni_tls_ep *ep = arg;
+
+ if (ep == NULL) {
+ return (0);
+ }
+ return (nni_tls_config_cert(ep->cfg, data, sz));
+}
+
+static int
+tls_setopt_private_key(void *arg, const void *data, size_t sz)
+{
+ nni_tls_ep *ep = arg;
+
+ if (ep == NULL) {
+ return (0);
+ }
+ return (nni_tls_config_key(ep->cfg, data, sz));
+}
+
+static int
+tls_setopt_pass(void *arg, const void *data, size_t sz)
+{
+ nni_tls_ep *ep = arg;
+ size_t len;
+
+ len = nni_strnlen(data, sz);
+ if (len >= sz) {
+ return (NNG_EINVAL);
+ }
+
+ if (ep == NULL) {
+ return (0);
+ }
+ return (nni_tls_config_pass(ep->cfg, data));
+}
+
+int nng_tls_auth_mode_none = NNI_TLS_CONFIG_AUTH_MODE_NONE;
+int nng_tls_auth_mode_required = NNI_TLS_CONFIG_AUTH_MODE_REQUIRED;
+int nng_tls_auth_mode_optional = NNI_TLS_CONFIG_AUTH_MODE_OPTIONAL;
+
+static int
+tls_getopt_auth_mode(void *arg, void *v, size_t *szp)
+{
+ nni_tls_ep *ep = arg;
+ return (nni_getopt_int(ep->authmode, v, szp));
+}
+
+static int
+tls_setopt_auth_mode(void *arg, const void *data, size_t sz)
+{
+ nni_tls_ep *ep = arg;
+ int mode;
+ int rv;
+
+ rv = nni_setopt_int(&mode, data, sz, -100, 100);
+ if (rv == 0) {
+ switch (mode) {
+ case NNI_TLS_CONFIG_AUTH_MODE_NONE:
+ case NNI_TLS_CONFIG_AUTH_MODE_OPTIONAL:
+ case NNI_TLS_CONFIG_AUTH_MODE_REQUIRED:
+ break;
+ default:
+ rv = NNG_EINVAL;
+ break;
+ }
+ }
+
+ if ((ep == NULL) || (rv != 0)) {
+ return (rv);
+ }
+
+ if ((rv = nni_tls_config_auth_mode(ep->cfg, mode)) == 0) {
+ ep->authmode = mode;
+ }
+ return (rv);
+}
+
+static int
+tls_getopt_verified(void *arg, void *v, size_t *szp)
+{
+ nni_tls_pipe *p = arg;
+ int verified;
+
+ verified = nni_tls_verified(p->tls);
+ return (nni_getopt_int(verified, v, szp));
+}
+
+static nni_tran_pipe_option nni_tls_pipe_options[] = {
+ { NNG_OPT_LOCADDR, nni_tls_pipe_getopt_locaddr },
+ { NNG_OPT_REMADDR, nni_tls_pipe_getopt_remaddr },
+ { NNG_OPT_TLS_AUTH_VERIFIED, tls_getopt_verified },
+ // terminate list
+ { NULL, NULL }
+};
+
+static nni_tran_pipe nni_tls_pipe_ops = {
+ .p_fini = nni_tls_pipe_fini,
+ .p_start = nni_tls_pipe_start,
+ .p_send = nni_tls_pipe_send,
+ .p_recv = nni_tls_pipe_recv,
+ .p_close = nni_tls_pipe_close,
+ .p_peer = nni_tls_pipe_peer,
+ .p_options = nni_tls_pipe_options,
+};
+
+static nni_tran_ep_option nni_tls_ep_options[] = {
+ {
+ .eo_name = NNG_OPT_RECVMAXSZ,
+ .eo_getopt = nni_tls_ep_getopt_recvmaxsz,
+ .eo_setopt = nni_tls_ep_setopt_recvmaxsz,
+ },
+ {
+ .eo_name = NNG_OPT_LINGER,
+ .eo_getopt = nni_tls_ep_getopt_linger,
+ .eo_setopt = nni_tls_ep_setopt_linger,
+ },
+ {
+ .eo_name = NNG_OPT_TLS_CA_CERT,
+ .eo_getopt = NULL,
+ .eo_setopt = tls_setopt_ca_cert,
+ },
+ {
+ .eo_name = NNG_OPT_TLS_CERT,
+ .eo_getopt = NULL,
+ .eo_setopt = tls_setopt_cert,
+ },
+ {
+ .eo_name = NNG_OPT_TLS_PRIVATE_KEY,
+ .eo_getopt = NULL,
+ .eo_setopt = tls_setopt_private_key,
+ },
+ {
+ .eo_name = NNG_OPT_TLS_PRIVATE_KEY_PASSWORD,
+ .eo_getopt = NULL,
+ .eo_setopt = tls_setopt_pass,
+ },
+ {
+ .eo_name = NNG_OPT_TLS_AUTH_MODE,
+ .eo_getopt = tls_getopt_auth_mode,
+ .eo_setopt = tls_setopt_auth_mode,
+ },
+
+ // terminate list
+ { NULL, NULL, NULL },
+};
+
+static nni_tran_ep nni_tls_ep_ops = {
+ .ep_init = nni_tls_ep_init,
+ .ep_fini = nni_tls_ep_fini,
+ .ep_connect = nni_tls_ep_connect,
+ .ep_bind = nni_tls_ep_bind,
+ .ep_accept = nni_tls_ep_accept,
+ .ep_close = nni_tls_ep_close,
+ .ep_options = nni_tls_ep_options,
+};
+
+static nni_tran nni_tls_tran = {
+ .tran_version = NNI_TRANSPORT_VERSION,
+ .tran_scheme = "tls",
+ .tran_ep = &nni_tls_ep_ops,
+ .tran_pipe = &nni_tls_pipe_ops,
+ .tran_init = nni_tls_tran_init,
+ .tran_fini = nni_tls_tran_fini,
+};
+
+int
+nng_tls_register(void)
+{
+ return (nni_tran_register(&nni_tls_tran));
+}
diff --git a/src/transport/tls/tls.h b/src/transport/tls/tls.h
new file mode 100644
index 00000000..4317ae55
--- /dev/null
+++ b/src/transport/tls/tls.h
@@ -0,0 +1,62 @@
+//
+// Copyright 2017 Staysail Systems, Inc. <info@staysail.tech>
+// Copyright 2017 Capitar IT Group BV <info@capitar.com>
+//
+// This software is supplied under the terms of the MIT License, a
+// copy of which should be located in the distribution where this
+// file was obtained (LICENSE.txt). A copy of the license may also be
+// found online at https://opensource.org/licenses/MIT.
+//
+
+#ifndef NNG_TRANSPORT_TLS_TLS_H
+#define NNG_TRANSPORT_TLS_TLS_H
+
+// TLS transport. This is used for communication via TLS v1.2 over TCP/IP.
+
+NNG_DECL int nng_tls_register(void);
+
+// TLS options. Note that these can only be set *before* the endpoint is
+// started. Once started, it is no longer possible to alter the TLS
+// configuration.
+
+// NNG_OPT_TLS_CA_CERT is a string with one or more X.509 certificates,
+// representing the entire CA chain. The content may be either PEM or DER
+// encoded.
+#define NNG_OPT_TLS_CA_CERT "tls:ca-cert"
+
+// NNG_OPT_TLS_CRL is a PEM encoded CRL (revocation list). Multiple lists
+// may be loaded by using this option multiple times.
+#define NNG_OPT_TLS_CRL "tls:crl"
+
+// NNG_OPT_TLS_CERT is used to specify our own certificate. At present
+// only one certificate may be supplied. (In the future it may be
+// possible to call this multiple times, for servers that select different
+// certificates depending upon client capabilities.)
+#define NNG_OPT_TLS_CERT "tls:cert"
+
+// NNG_OPT_TLS_PRIVATE_KEY is used to specify the private key used
+// with the given certificate. This should be called after setting
+// the certificate. The private key may be in PEM or DER format.
+// If in PEM encoded, a terminating ZERO byte should be included.
+#define NNG_OPT_TLS_PRIVATE_KEY "tls:private-key"
+
+// NNG_OPT_TLS_PRIVATE_KEY_PASSWORD is used to specify a password
+// used for the private key. The value is an ASCIIZ string.
+#define NNG_OPT_TLS_PRIVATE_KEY_PASSWORD "tls:private-key-password"
+
+// NNG_OPT_TLS_AUTH_MODE is an integer indicating whether our
+// peer should be verified or not. It is required on clients/dialers,
+// and off on servers/listeners, by default.
+#define NNG_OPT_TLS_AUTH_MODE "tls:auth-mode"
+
+extern int nng_tls_auth_mode_required;
+extern int nng_tls_auth_mode_none;
+extern int nng_tls_auth_mode_optional;
+
+// NNG_OPT_TLS_AUTH_VERIFIED is a boolean that can be read on pipes,
+// indicating whether the peer certificate is verified.
+#define NNG_OPT_TLS_AUTH_VERIFIED "tls:auth-verified"
+
+// XXX: TBD: Ciphersuite selection and reporting. Session reuse?
+
+#endif // NNG_TRANSPORT_TLS_TLS_H
diff --git a/src/transport/zerotier/zerotier.h b/src/transport/zerotier/zerotier.h
index 4f10f9be..87325f34 100644
--- a/src/transport/zerotier/zerotier.h
+++ b/src/transport/zerotier/zerotier.h
@@ -110,13 +110,13 @@
// return values from zt_opt_status. We avoid hard coding them as defines,
// to keep applications from baking in values that may change if the
// underlying ZeroTier transport changes.
-extern int nng_zt_network_status_configuring;
-extern int nng_zt_network_status_ok;
-extern int nng_zt_network_status_denied;
-extern int nng_zt_network_status_notfound;
-extern int nng_zt_network_status_error;
-extern int nng_zt_network_status_obsolete;
+NNG_DECL int nng_zt_network_status_configuring;
+NNG_DECL int nng_zt_network_status_ok;
+NNG_DECL int nng_zt_network_status_denied;
+NNG_DECL int nng_zt_network_status_notfound;
+NNG_DECL int nng_zt_network_status_error;
+NNG_DECL int nng_zt_network_status_obsolete;
-extern int nng_zt_register(void);
+NNG_DECL int nng_zt_register(void);
#endif // NNG_TRANSPORT_ZEROTIER_ZEROTIER_H
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index f6a476ab..30f76a1d 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -139,6 +139,7 @@ add_nng_test(sock 5)
add_nng_test(survey 5)
add_nng_test(synch 5)
add_nng_test(transport 5)
+add_nng_test(tls 10)
add_nng_test(tcp 5)
add_nng_test(tcp6 5)
add_nng_test(scalability 20)
diff --git a/tests/tls.c b/tests/tls.c
new file mode 100644
index 00000000..e4e430af
--- /dev/null
+++ b/tests/tls.c
@@ -0,0 +1,186 @@
+//
+// Copyright 2017 Garrett D'Amore <garrett@damore.org>
+// Copyright 2017 Capitar IT Group BV <info@capitar.com>
+//
+// 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 "convey.h"
+#include "nng.h"
+#include "protocol/pair1/pair.h"
+
+#include "transport/tls/tls.h"
+
+#include "trantest.h"
+
+#include "stubs.h"
+// TCP tests.
+
+#ifndef _WIN32
+#include <arpa/inet.h>
+#endif
+
+// These keys are for demonstration purposes ONLY. DO NOT USE.
+// The certificate is valid for 100 years, because I don't want to
+// have to regenerate it ever again. The CN is 127.0.0.1, and self-signed.
+//
+// Generated using openssl:
+//
+// % openssl ecparam -name secp521r1 -noout -genkey -out key.key
+// % openssl req -new -key key.key -out cert.csr
+// % openssl x509 -req -in cert.csr -days 36500 -out cert.crt -signkey key.key
+//
+// Relevant metadata:
+//
+// Certificate:
+// Data:
+// Version: 1 (0x0)
+// Serial Number: 9808857926806240008 (0x882010509b8f7b08)
+// Signature Algorithm: ecdsa-with-SHA1
+// Issuer: C=US, ST=CA, L=San Diego, O=nanomsg, CN=127.0.0.1
+// Validity
+// Not Before: Nov 17 20:08:06 2017 GMT
+// Not After : Oct 24 20:08:06 2117 GMT
+// Subject: C=US, ST=CA, L=San Diego, O=nanomsg, CN=127.0.0.1
+//
+static const char server_cert[] =
+ "-----BEGIN CERTIFICATE-----\n"
+ "MIICIjCCAYMCCQDaC9ARg31kIjAKBggqhkjOPQQDAjBUMQswCQYDVQQGEwJVUzEL\n"
+ "MAkGA1UECAwCQ0ExEjAQBgNVBAcMCVNhbiBEaWVnbzEQMA4GA1UECgwHbmFub21z\n"
+ "ZzESMBAGA1UEAwwJMTI3LjAuMC4xMCAXDTE3MTExNzIwMjczMloYDzIxMTcxMDI0\n"
+ "MjAyNzMyWjBUMQswCQYDVQQGEwJVUzELMAkGA1UECAwCQ0ExEjAQBgNVBAcMCVNh\n"
+ "biBEaWVnbzEQMA4GA1UECgwHbmFub21zZzESMBAGA1UEAwwJMTI3LjAuMC4xMIGb\n"
+ "MBAGByqGSM49AgEGBSuBBAAjA4GGAAQAN7vDK6GEiSguMsOuhfOvGyiVc37Sog0b\n"
+ "UkpaiS6+SagTmXFSN1Rgh9isxKFYJvcCtAko3v0I8rAVQucdhf5B3hEBMQlbBIuM\n"
+ "rMKT6ZQJ+eiwyb4O3Scgd7DoL3tc/kOqijwB/5hJ4sZdquDKP5DDFe5fAf4MNtzY\n"
+ "4C+iApWlKq/LoXkwCgYIKoZIzj0EAwIDgYwAMIGIAkIBOuJAWmNSdd6Ovmr6Ebg3\n"
+ "UF9ZrsNwARd9BfYbBk5OQhUOjCLB6d8aLi49WOm1WoRvOS5PaVvmvSfNhaw8b5nV\n"
+ "hnYCQgC+EmJ6C3bEcZrndhfbqvCaOGkc7/SrKhC6fS7mJW4wL90QUV9WjQ2Ll6X5\n"
+ "PxkSj7s0SvD6T8j7rju5LDgkdZc35A==\n"
+ "-----END CERTIFICATE-----\n";
+
+static const char server_key[] =
+ "-----BEGIN EC PRIVATE KEY-----\n"
+ "MIHcAgEBBEIB20OHMntU2UJW2yuQn2f+bLsuhTT5KRGorcocnqxatWLvxuF1cfUA\n"
+ "TjQxRRS6BIUvFt1fMIklp9qedJF00JHy4qWgBwYFK4EEACOhgYkDgYYABAA3u8Mr\n"
+ "oYSJKC4yw66F868bKJVzftKiDRtSSlqJLr5JqBOZcVI3VGCH2KzEoVgm9wK0CSje\n"
+ "/QjysBVC5x2F/kHeEQExCVsEi4yswpPplAn56LDJvg7dJyB3sOgve1z+Q6qKPAH/\n"
+ "mEnixl2q4Mo/kMMV7l8B/gw23NjgL6IClaUqr8uheQ==\n"
+ "-----END EC PRIVATE KEY-----\n";
+
+static int
+check_props_v4(nng_msg *msg, nng_listener l, nng_dialer d)
+{
+ nng_pipe p;
+ size_t z;
+ p = nng_msg_get_pipe(msg);
+ So(p > 0);
+
+ Convey("Local address property works", {
+ nng_sockaddr la;
+ z = sizeof(nng_sockaddr);
+ So(nng_pipe_getopt(p, NNG_OPT_LOCADDR, &la, &z) == 0);
+ So(z == sizeof(la));
+ So(la.s_un.s_family == NNG_AF_INET);
+ So(la.s_un.s_in.sa_port == htons(trantest_port - 1));
+ So(la.s_un.s_in.sa_port != 0);
+ So(la.s_un.s_in.sa_addr == htonl(0x7f000001));
+ });
+
+ Convey("Remote address property works", {
+ nng_sockaddr ra;
+ z = sizeof(nng_sockaddr);
+ So(nng_pipe_getopt(p, NNG_OPT_REMADDR, &ra, &z) == 0);
+ So(z == sizeof(ra));
+ So(ra.s_un.s_family == NNG_AF_INET);
+ So(ra.s_un.s_in.sa_port != 0);
+ So(ra.s_un.s_in.sa_addr == htonl(0x7f000001));
+ });
+
+ return (0);
+}
+
+static int
+init_tls(trantest *tt)
+{
+ const char *own[3];
+
+ So(nng_setopt(tt->reqsock, NNG_OPT_TLS_CA_CERT, server_cert,
+ sizeof(server_cert)) == 0);
+ own[0] = server_cert;
+ own[1] = server_key;
+ own[2] = NULL;
+ So(nng_setopt(tt->repsock, NNG_OPT_TLS_CERT, server_cert,
+ sizeof(server_cert)) == 0);
+ So(nng_setopt(tt->repsock, NNG_OPT_TLS_PRIVATE_KEY, server_key,
+ sizeof(server_key)) == 0);
+
+ return (0);
+}
+
+TestMain("TLS Transport", {
+
+ static trantest tt;
+
+ tt.init = init_tls;
+ tt.tmpl = "tls://127.0.0.1:%u";
+
+ trantest_test(&tt);
+
+ Convey("We can register the TLS transport",
+ { So(nng_tls_register() == 0); });
+
+ Convey("We cannot connect to wild cards", {
+ nng_socket s;
+ char addr[NNG_MAXADDRLEN];
+
+ So(nng_tls_register() == 0);
+ So(nng_pair_open(&s) == 0);
+ Reset({ nng_close(s); });
+ trantest_next_address(addr, "tls://*:%u");
+ So(nng_dial(s, addr, NULL, 0) == NNG_EADDRINVAL);
+ });
+
+ Convey("We can bind to wild card", {
+ nng_socket s1;
+ nng_socket s2;
+ char addr[NNG_MAXADDRLEN];
+
+ So(nng_tls_register() == 0);
+ So(nng_pair_open(&s1) == 0);
+ So(nng_pair_open(&s2) == 0);
+ Reset({
+ nng_close(s2);
+ nng_close(s1);
+ });
+ trantest_next_address(addr, "tls://*:%u");
+ So(nng_listen(s1, addr, NULL, 0) == 0);
+ // reset port back one
+ trantest_prev_address(addr, "tls://127.0.0.1:%u");
+ So(nng_dial(s2, addr, NULL, 0) == 0);
+ });
+
+ Convey("Malformed TLS addresses do not panic", {
+ nng_socket s1;
+
+ So(nng_tls_register() == 0);
+ So(nng_pair_open(&s1) == 0);
+ Reset({ nng_close(s1); });
+ So(nng_dial(s1, "tls://127.0.0.1", NULL, 0) == NNG_EADDRINVAL);
+ So(nng_dial(s1, "tls://127.0.0.1.32", NULL, 0) ==
+ NNG_EADDRINVAL);
+ So(nng_dial(s1, "tls://127.0.x.1.32", NULL, 0) ==
+ NNG_EADDRINVAL);
+ So(nng_listen(s1, "tls://127.0.0.1", NULL, 0) ==
+ NNG_EADDRINVAL);
+ So(nng_listen(s1, "tls://127.0.0.1.32", NULL, 0) ==
+ NNG_EADDRINVAL);
+ So(nng_listen(s1, "tls://127.0.x.1.32", NULL, 0) ==
+ NNG_EADDRINVAL);
+ });
+
+ nng_fini();
+})
diff --git a/tests/trantest.h b/tests/trantest.h
index d334c257..21a9c893 100644
--- a/tests/trantest.h
+++ b/tests/trantest.h
@@ -19,19 +19,29 @@
// Transport common tests. By making a common test framework for transports,
// we can avoid rewriting the same tests for each new transport. Include this
// file once in your test code. The test framework uses the REQ/REP protocol
-// for comms.
+// for messaging.
+typedef int (*trantest_proptest_t)(nng_msg *, nng_listener, nng_dialer);
-typedef struct {
- char addr[NNG_MAXADDRLEN + 1];
- nng_socket reqsock;
- nng_socket repsock;
- nni_tran * tran;
-} trantest;
+typedef struct trantest trantest;
+
+struct trantest {
+ const char * tmpl;
+ char addr[NNG_MAXADDRLEN + 1];
+ nng_socket reqsock;
+ nng_socket repsock;
+ nni_tran * tran;
+ nng_dialer dialer;
+ nng_listener listener;
+ int (*init)(struct trantest *);
+ void (*fini)(struct trantest *);
+ int (*dialer_init)(struct trantest *);
+ int (*listener_init)(struct trantest *);
+ int (*proptest)(nng_msg *, nng_listener, nng_dialer);
+ void *private; // transport specific private data
+};
unsigned trantest_port = 0;
-typedef int (*trantest_proptest_t)(nng_msg *, nng_listener, nng_dialer);
-
#ifndef NNG_HAVE_ZEROTIER
#define nng_zt_register notransport
#endif
@@ -44,6 +54,9 @@ typedef int (*trantest_proptest_t)(nng_msg *, nng_listener, nng_dialer);
#ifndef NNG_HAVE_TCP
#define nng_tcp_register notransport
#endif
+#ifndef NNG_HAVE_TLS
+#define nng_tls_register notransport
+#endif
int
notransport(void)
@@ -71,6 +84,9 @@ trantest_checktran(const char *url)
#ifndef NNG_HAVE_TCP
CHKTRAN(url, "tcp:");
#endif
+#ifndef NNG_HAVE_TLS
+ CHKTRAN(url, "tls:");
+#endif
(void) url;
}
@@ -122,6 +138,16 @@ trantest_fini(trantest *tt)
nng_close(tt->repsock);
}
+int
+trantest_dial(trantest *tt)
+{
+ So(nng_dialer_create(&tt->dialer, tt->reqsock, tt->addr) == 0);
+ if (tt->dialer_init != NULL) {
+ So(tt->dialer_init(tt) == 0);
+ }
+ return (nng_dialer_start(tt->dialer, 0));
+}
+
void
trantest_scheme(trantest *tt)
{
@@ -150,10 +176,12 @@ trantest_duplicate_listen(trantest *tt)
{
Convey("Duplicate listen rejected", {
nng_listener l;
- So(nng_listen(tt->repsock, tt->addr, &l, 0) == 0);
+ int rv;
+ rv = nng_listen(tt->repsock, tt->addr, &l, 0);
+ So(rv == 0);
So(l != 0);
l = 0;
- So(nng_listen(tt->reqsock, tt->addr, &l, 0) == NNG_EADDRINUSE);
+ So(nng_listen(tt->repsock, tt->addr, &l, 0) == NNG_EADDRINUSE);
So(l == 0);
})
}
@@ -178,7 +206,6 @@ trantest_send_recv(trantest *tt)
{
Convey("Send and recv", {
nng_listener l;
- nng_dialer d;
nng_msg * send;
nng_msg * recv;
size_t len;
@@ -188,8 +215,7 @@ trantest_send_recv(trantest *tt)
So(nng_listen(tt->repsock, tt->addr, &l, 0) == 0);
So(l != 0);
- So(nng_dial(tt->reqsock, tt->addr, &d, 0) == 0);
- So(d != 0);
+ So(trantest_dial(tt) == 0);
nng_msleep(20); // listener may be behind slightly
@@ -238,7 +264,7 @@ trantest_check_properties(trantest *tt, trantest_proptest_t f)
So(nng_dial(tt->reqsock, tt->addr, &d, 0) == 0);
So(d != 0);
- nng_msleep(20); // listener may be behind slightly
+ nng_msleep(10); // listener may be behind slightly
send = NULL;
So(nng_msg_alloc(&send, 0) == 0);
@@ -280,7 +306,7 @@ trantest_send_recv_large(trantest *tt)
So(nng_dial(tt->reqsock, tt->addr, &d, 0) == 0);
So(d != 0);
- nng_msleep(20); // listener may be behind slightly
+ nng_msleep(10); // listener may be behind slightly
send = NULL;
So(nng_msg_alloc(&send, size) == 0);
@@ -315,6 +341,7 @@ trantest_test_all(const char *addr)
{
trantest tt;
+ memset(&tt, 0, sizeof(tt));
Convey("Given transport", {
trantest_init(&tt, addr);
@@ -334,6 +361,7 @@ trantest_test_extended(const char *addr, trantest_proptest_t f)
{
trantest tt;
+ memset(&tt, 0, sizeof(tt));
Convey("Given transport", {
trantest_init(&tt, addr);
@@ -348,3 +376,33 @@ trantest_test_extended(const char *addr, trantest_proptest_t f)
trantest_check_properties(&tt, f);
})
}
+
+void
+trantest_test(trantest *tt)
+{
+ Convey("Given transport", {
+ trantest_init(tt, tt->tmpl);
+ if (tt->init != NULL) {
+ So(tt->init(tt) == 0);
+ }
+
+ Reset({
+ if (tt->fini != NULL) {
+ tt->fini(tt);
+ }
+ trantest_fini(tt);
+ });
+
+ trantest_scheme(tt);
+
+ trantest_conn_refused(tt);
+ trantest_duplicate_listen(tt);
+ trantest_listen_accept(tt);
+
+ trantest_send_recv(tt);
+ trantest_send_recv_large(tt);
+ if (tt->proptest != NULL) {
+ trantest_check_properties(tt, tt->proptest);
+ }
+ })
+}