aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGarrett D'Amore <garrett@damore.org>2025-10-19 10:40:26 -0700
committerGarrett D'Amore <garrett@damore.org>2025-10-19 19:16:30 -0700
commit611c4acdddab9d702d235c2bcfe3b69002e93569 (patch)
tree6a1065c07db8eb01cd4be0a436e9375ad3979b90
parent29769c0c3623a023eb12edb378b19116f64ee5b7 (diff)
downloadnng-611c4acdddab9d702d235c2bcfe3b69002e93569.tar.gz
nng-611c4acdddab9d702d235c2bcfe3b69002e93569.tar.bz2
nng-611c4acdddab9d702d235c2bcfe3b69002e93569.zip
Add support for OpenSSL v3.5 and newer.
We are *only* supporting 3.5 (or newer 3.x releases) as its the newest LTS version of OpenSSL. This supports the full set of TLS features with NNG, including DTLS, PSK, TLS 1.3, etc. Future work will explore making using of the QUIC support in OpenSSL. Note that this OpenSSL work sits on top of NNG's TCP streams, so it cannot benefit from Linux in-kernel TLS or other features such as TCP fast open at this time.
-rw-r--r--README.md6
-rw-r--r--docs/BUILD_TLS.md46
-rw-r--r--src/core/defs.h7
-rw-r--r--src/core/sockaddr.c25
-rw-r--r--src/sp/transport/tls/tls_tran_test.c11
-rw-r--r--src/supplemental/tls/CMakeLists.txt3
-rw-r--r--src/supplemental/tls/openssl/CMakeLists.txt32
-rw-r--r--src/supplemental/tls/openssl/openssl.c1115
8 files changed, 1223 insertions, 22 deletions
diff --git a/README.md b/README.md
index a8046717..5aab3f60 100644
--- a/README.md
+++ b/README.md
@@ -152,8 +152,10 @@ system (pass `-G Ninja` to CMake) when you can.
(And not just because Ninja sounds like "NNG" -- it's also
blindingly fast and has made our lives as developers measurably better.)
-If you want to build with TLS support you will also need
-[Mbed TLS](https://tls.mbed.org) or [WolfSSL](https://wolfssl.com).
+If you want to build with TLS support you will also need a supported
+TLS library. Currently [Mbed TLS](https://tls.mbed.org), [WolfSSL](https://wolfssl.com),
+and [OpenSSL](https://openssl.org) all enjoy some level support in NNG.
+
See the [build instructions](docs/BUILD_TLS.md) for details.
## Quick Start
diff --git a/docs/BUILD_TLS.md b/docs/BUILD_TLS.md
index 63ae6347..7bdea54a 100644
--- a/docs/BUILD_TLS.md
+++ b/docs/BUILD_TLS.md
@@ -3,8 +3,9 @@
If you want to include support for Transport Layer Security
(`tls+tcp://` and `wss://` URLs) you should follow these directions.
-TLS support in NNG depends on either the [Mbed TLS](https://tls.mbed.org/)
-or [WolfSSL](https://www.wolfssl.com/) library (your choice).
+TLS support in NNG depends on a suitable TLS library.
+The options are [Mbed TLS](https://tls.mbed.org/),
+[WolfSSL](https://www.wolfssl.com/), [OpenSSL](https://openssl.org).
> [!IMPORTANT]
> These libraries are licensed under different terms than NNG.
@@ -30,21 +31,52 @@ You can also build these from source; if you choose to do so,
please make sure you also _install_ it somewhere (even a temporary
staging directory).
+## Notes about Mbed TLS
+
+MbedTLS 2.28 or 3.6 are tested and known to work.
+MbedTLS 4.0 is not at present supported, but we will work to
+address that soon. Support for MbedTLS 2.28 may be dropped
+before NNG 2.0 finalizes, as it is no longer supported by
+the Mbed TLS project.
+
+## Notes about WolfSSL
+
+WolfSSL can be configured with a small subset of possible
+features, which can impair NNG's functionality. We recommend
+enabling support for peer certificates as well as the optional
+extra OpenSSL compatibility APIs.
+
+Note that the open source version of WolfSSL is GPLv3, which
+applies significant additional considerations on users. Please
+check with your lawyer if you're not planning to open source
+your work under GPLv3 as well.
+
+We have not tested NNG with the commercial version of WolfSSL.
+If you want support for that, please contact Staysail Systems
+to make support arrangements.
+
+## Notes about OpenSSL
+
+OpenSSL requires version 3.5 or newer. (As of this writing, OpenSSL
+3.5 is the most recent long term support - LTS - release of OpenSSL.)
+No effort will be made to support earlier releases.
+
## Configuring NNG with TLS
TLS support is not enabled by default, but can be enabled by configuring
with the CMake option `NNG_ENABLE_TLS=ON`.
-You can select which library to use by using `NNG_TLS_ENGINE=mbed` or
-`NNG_TLS_ENGINE=wolf`. If you specify neither, then Mbed TLS will be assumed
-by default.
+You can select which library to use by using `NNG_TLS_ENGINE=mbed`,
+`NNG_TLS_ENGINE=wolf`, or `NNG_TLS_ENGINE=openssl`.
+If you do not specify an engine, then `mbed` is assumed by default.
+(Note that the default may change in future releases.)
By default NNG searches for an installed components in `/usr/local`,
as well as the normal installation directories for libraries on your system.
-If you have installed Mbed TLS elsewhere, you can direct the NNG configuration
+If you have installed the TLS library elsewhere, you can direct the NNG configuration
to it by setting the `MBEDTLS_ROOT_DIR` or `WOLFSSL_ROOT_DIR` CMake variable
-as appropriate.
+as appropriate. For OpenSSL, see the CMake documentation for `FindOpenSSL`.
## Example
diff --git a/src/core/defs.h b/src/core/defs.h
index c94d9bd3..0ca885b3 100644
--- a/src/core/defs.h
+++ b/src/core/defs.h
@@ -261,4 +261,11 @@ extern void *nni_zalloc(size_t);
// Most implementations can just call free() here.
extern void nni_free(void *, size_t);
+// nni_inet_ntop is like inet_ntop, but for NNG_AF_INET and NNG_AF_INET6. The
+// output buffer must be able to contain at least 46 bytes. Note that
+// NNG_AF_UNSPEC is explicitly unsupported, as we do not pass the address
+// length.
+extern char *nni_inet_ntop(
+ enum nng_sockaddr_family af, const uint8_t *addr, char *buf);
+
#endif // CORE_DEFS_H
diff --git a/src/core/sockaddr.c b/src/core/sockaddr.c
index 9dcfa3bc..39e527da 100644
--- a/src/core/sockaddr.c
+++ b/src/core/sockaddr.c
@@ -24,17 +24,27 @@ str_sa_inet(const nng_sockaddr_in *sa, char *buf, size_t bufsz)
{
uint8_t *a_bytes = (uint8_t *) &sa->sa_addr;
uint8_t *p_bytes = (uint8_t *) &sa->sa_port;
+ char ipbuf[46];
- snprintf(buf, bufsz, "%u.%u.%u.%u:%u", a_bytes[0], a_bytes[1],
- a_bytes[2], a_bytes[3],
+ snprintf(buf, bufsz, "%s:%u",
+ nni_inet_ntop(NNG_AF_INET, a_bytes, ipbuf),
(((uint16_t) p_bytes[0]) << 8) + p_bytes[1]);
return (buf);
}
-// emit an IPv6 address in "short form"
-static char *
-nni_inet_ntop(const uint8_t addr[16], char buf[46])
+// emit an IP address, only NNG_AF_INET and NNG_AF_INET6 explicitly are
+// supported. (NO support for NNG_AF_UNSPEC.)
+char *
+nni_inet_ntop(enum nng_sockaddr_family af, const uint8_t *addr, char *buf)
{
+ if (af == NNG_AF_INET) {
+ snprintf(buf, 46, "%u.%u.%u.%u", addr[0], addr[1], addr[2],
+ addr[3]);
+ return (buf);
+ }
+ if (af != NNG_AF_INET6) {
+ return (NULL);
+ }
const uint8_t v4map[12] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff };
@@ -106,11 +116,12 @@ str_sa_inet6(const nng_sockaddr_in6 *sa, char *buf, size_t bufsz)
if (sa->sa_scope) {
snprintf(buf, bufsz, "[%s%%%u]:%u",
- nni_inet_ntop(sa->sa_addr, istr), sa->sa_scope,
+ nni_inet_ntop(NNG_AF_INET6, sa->sa_addr, istr),
+ sa->sa_scope,
(((uint16_t) (p_bytes[0])) << 8) + p_bytes[1]);
} else {
snprintf(buf, bufsz, "[%s]:%u",
- nni_inet_ntop(sa->sa_addr, istr),
+ nni_inet_ntop(NNG_AF_INET6, sa->sa_addr, istr),
(((uint16_t) (p_bytes[0])) << 8) + p_bytes[1]);
}
return (buf);
diff --git a/src/sp/transport/tls/tls_tran_test.c b/src/sp/transport/tls/tls_tran_test.c
index 3a5e6805..e32d4b09 100644
--- a/src/sp/transport/tls/tls_tran_test.c
+++ b/src/sp/transport/tls/tls_tran_test.c
@@ -80,8 +80,9 @@ test_tls_port_zero_bind(void)
nng_dialer d;
const nng_url *url;
- c1 = tls_server_config();
- c2 = tls_client_config();
+ NUTS_ENABLE_LOG(NNG_LOG_DEBUG);
+ c1 = tls_server_config_ecdsa();
+ c2 = tls_client_config_ecdsa();
NUTS_OPEN(s1);
NUTS_OPEN(s2);
NUTS_PASS(nng_listener_create(&l, s1, "tls+tcp://127.0.0.1:0"));
@@ -113,10 +114,10 @@ test_tls_bad_cert_mutual(void)
nng_dialer d;
const nng_url *url;
+ NUTS_ENABLE_LOG(NNG_LOG_DEBUG);
c1 = tls_server_config();
c2 = tls_client_config();
- NUTS_ENABLE_LOG(NNG_LOG_DEBUG);
NUTS_OPEN(s1);
NUTS_OPEN(s2);
NUTS_PASS(nng_tls_config_auth_mode(c1, NNG_TLS_AUTH_MODE_REQUIRED));
@@ -157,10 +158,10 @@ test_tls_cert_mutual(void)
nng_dialer d;
const nng_url *url;
+ NUTS_ENABLE_LOG(NNG_LOG_DEBUG);
c1 = tls_server_config_ecdsa();
c2 = tls_client_config_ecdsa();
- NUTS_ENABLE_LOG(NNG_LOG_DEBUG);
NUTS_OPEN(s1);
NUTS_OPEN(s2);
NUTS_PASS(nng_tls_config_auth_mode(c1, NNG_TLS_AUTH_MODE_REQUIRED));
@@ -198,10 +199,10 @@ test_tls_pipe_details(void)
nng_pipe p;
const nng_url *url;
+ NUTS_ENABLE_LOG(NNG_LOG_DEBUG);
c1 = tls_server_config_ecdsa();
c2 = tls_client_config_ecdsa();
- NUTS_ENABLE_LOG(NNG_LOG_DEBUG);
NUTS_OPEN(s1);
NUTS_OPEN(s2);
NUTS_PASS(nng_tls_config_auth_mode(c1, NNG_TLS_AUTH_MODE_REQUIRED));
diff --git a/src/supplemental/tls/CMakeLists.txt b/src/supplemental/tls/CMakeLists.txt
index 41587915..a9c3af65 100644
--- a/src/supplemental/tls/CMakeLists.txt
+++ b/src/supplemental/tls/CMakeLists.txt
@@ -14,7 +14,7 @@
if (NNG_ENABLE_TLS)
# List of TLS engines we support. TLS engines must support TLS 1.2 or better,
# and must also support DTLS. Support for PSK is preferred.
- set(NNG_TLS_ENGINES mbed wolf none)
+ set(NNG_TLS_ENGINES mbed wolf openssl none)
# We assume Mbed for now.
set(NNG_TLS_ENGINE mbed CACHE STRING "TLS engine to use.")
set_property(CACHE NNG_TLS_ENGINE PROPERTY STRINGS ${NNG_TLS_ENGINES})
@@ -28,6 +28,7 @@ endif ()
add_subdirectory(mbedtls)
add_subdirectory(wolfssl)
+add_subdirectory(openssl)
if (NNG_ENABLE_TLS)
nng_sources(tls_common.c tls_dialer.c tls_listener.c tls_stream.c)
diff --git a/src/supplemental/tls/openssl/CMakeLists.txt b/src/supplemental/tls/openssl/CMakeLists.txt
new file mode 100644
index 00000000..6cb70c03
--- /dev/null
+++ b/src/supplemental/tls/openssl/CMakeLists.txt
@@ -0,0 +1,32 @@
+#
+# Copyright 2025 Staysail Systems, Inc. <info@staysail.tech>
+#
+# This software is supplied under the terms of the MIT License, a
+# copy of which should be located in the distribution where this
+# file was obtained (LICENSE.txt). A copy of the license may also be
+# found online at https://opensource.org/licenses/MIT.
+#
+include(FindThreads)
+
+if (NNG_TLS_ENGINE STREQUAL "openssl")
+ message(NOTICE "
+ ************************************************************
+ Linking against OpenSSL may change license terms.
+ Consult a lawyer and the license files for details.
+ ************************************************************")
+ nng_sources(openssl.c)
+
+ find_package(OpenSSL 3.5 REQUIRED)
+
+ if (OPENSSL_FOUND)
+ message(STATUS "OpenSSL found: ${OPENSSL_VERSION}")
+ target_include_directories(nng PRIVATE ${OPENSSL_INCLUDE_DIR})
+ target_include_directories(nng_testing PRIVATE ${OPENSSL_INCLUDE_DIR})
+ nng_link_libraries_public(PRIVATE ${OPENSSL_LIBRARIES})
+
+ endif()
+
+ nng_defines(NNG_SUPP_TLS)
+ nng_defines(NNG_SUPP_TLS_PSK)
+ nng_defines(NNG_TLS_ENGINE_OPENSSL)
+endif ()
diff --git a/src/supplemental/tls/openssl/openssl.c b/src/supplemental/tls/openssl/openssl.c
new file mode 100644
index 00000000..df626ce9
--- /dev/null
+++ b/src/supplemental/tls/openssl/openssl.c
@@ -0,0 +1,1115 @@
+//
+// Copyright 2025 Staysail Systems, Inc. <info@staysail.tech>
+//
+// This software is supplied under the terms of the MIT License, a
+// copy of which should be located in the distribution where this
+// file was obtained (LICENSE.txt). A copy of the license may also be
+// found online at https://opensource.org/licenses/MIT.
+//
+
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <openssl/bio.h>
+#include <openssl/crypto.h>
+#include <openssl/err.h>
+#include <openssl/evp.h>
+#include <openssl/pem.h>
+#include <openssl/ssl.h>
+#include <openssl/x509.h>
+#include <openssl/x509v3.h>
+
+#include "../../../core/defs.h"
+#include "../../../core/list.h"
+#include "../../../core/strs.h"
+#include "../tls_engine.h"
+
+// library code for openssl
+static int ossl_libcode;
+
+// ex data index
+static int ossl_ex_index;
+
+// table of openssl errors
+static ERR_STRING_DATA ossl_errs[64];
+
+static BIO_METHOD *ossl_tcpm; // TCP stream method
+// static BIO_METHOD *ossl_udpm; // UDP datagram method
+
+struct nng_tls_engine_conn {
+ void *tls; // parent conn
+ SSL_CTX *ctx;
+ SSL *ssl;
+ int auth_mode;
+ nng_tls_mode mode;
+};
+
+struct nng_tls_engine_cert {
+ X509 *crt;
+ char *subject;
+ char *issuer;
+ char serial[64]; // maximum binary serial is 20 bytes
+ int next_alt;
+};
+
+typedef struct psk {
+ // NB: Technically RFC 4279 requires this be UTF-8 string, although
+ // others suggest making it opaque bytes. We treat it as a C string,
+ // so we cannot support embedded zero bytes.
+ char *identity;
+ uint8_t *key;
+ size_t keylen;
+ nni_list_node node;
+} psk;
+
+static int
+ossl_error(nng_err err)
+{
+ if ((err & NNG_ESYSERR) == NNG_ESYSERR) {
+ return NNG_ESYSERR;
+ }
+ if ((err & NNG_ETRANERR) == NNG_ETRANERR) {
+ return NNG_ETRANERR;
+ }
+ return (err);
+}
+
+static void
+psk_free(psk *p)
+{
+ if (p != NULL) {
+ NNI_ASSERT(!nni_list_node_active(&p->node));
+ if (p->identity != NULL) {
+ nni_strfree(p->identity);
+ p->identity = NULL;
+ }
+ if (p->key != NULL && p->keylen != 0) {
+ nni_free(p->key, p->keylen);
+ p->key = NULL;
+ p->keylen = 0;
+ }
+ NNI_FREE_STRUCT(p);
+ }
+}
+
+struct nng_tls_engine_config {
+ SSL_CTX *ctx;
+ nng_tls_mode mode;
+ char *pass;
+ char *server_name;
+ int auth_mode;
+ int min_ver;
+ int max_ver;
+ nni_list psks;
+};
+
+static void
+tls_log_err(const char *msgid, const char *context, int errnum)
+{
+ char errbuf[256];
+ ERR_error_string_n(errnum, errbuf, sizeof(errbuf));
+ nng_log_err(msgid, "%s: %s", context, errbuf);
+}
+
+static int
+ossl_net_send(BIO *bio, const char *buf, size_t len, size_t *lenp)
+{
+ void *ctx = BIO_get_data(bio);
+ int rv;
+
+ switch (rv = nng_tls_engine_send(ctx, (const uint8_t *) buf, &len)) {
+ case NNG_OK:
+ *lenp = len;
+ return (1);
+ case NNG_EAGAIN:
+ BIO_set_retry_write(bio);
+ return (-1);
+ default:
+ ERR_raise(ossl_libcode, ossl_error(rv));
+ return (0);
+ }
+}
+
+static int
+ossl_net_recv(BIO *bio, char *buf, size_t len, size_t *lenp)
+{
+ void *ctx = BIO_get_data(bio);
+ int rv;
+
+ switch (rv = nng_tls_engine_recv(ctx, (uint8_t *) buf, &len)) {
+ case NNG_OK:
+ *lenp = len;
+ return (1);
+ case NNG_EAGAIN:
+ BIO_set_retry_read(bio);
+ return (-1);
+ default:
+ ERR_raise(ossl_libcode, ossl_error(rv));
+ return (0);
+ }
+}
+
+static long
+ossl_bio_ctrl(BIO *bio, int cmd, long num, void *ptr)
+{
+ NNI_ARG_UNUSED(bio);
+ NNI_ARG_UNUSED(ptr);
+ switch (cmd) {
+ case BIO_CTRL_PUSH:
+ case BIO_CTRL_FLUSH:
+ case BIO_CTRL_POP:
+ return (1);
+ default:
+ nng_log_err(
+ "NNG-TLS-BIO", "Unsupported BIO CMD %d num %ld", cmd, num);
+ return (0);
+ }
+}
+
+static void
+ossl_init_nng(void)
+{
+ nng_err i;
+
+ if (ossl_libcode == 0) {
+ ossl_libcode = ERR_get_next_error_library();
+ // minus 3 to leave room for tran error, sys error, and
+ // sentinel
+ for (i = 0; i < 64 - 3; i++) {
+ const char *err = nng_strerror(i);
+ if (strncmp(err, "Unknown error",
+ strlen("Unknown error")) == 0) {
+ break;
+ }
+ ossl_errs[i].error = ERR_PACK(ossl_libcode, 0, i);
+ ossl_errs[i].string = err;
+ }
+ ossl_errs[i].error = ERR_PACK(ossl_libcode, 0, NNG_ETRANERR);
+ ossl_errs[i].string = "Transport error";
+ i++;
+ ossl_errs[i].error = ERR_PACK(ossl_libcode, 0, NNG_ESYSERR);
+ ossl_errs[i].string = "Other system error";
+ i++;
+ ossl_errs[i].error = 0;
+ ossl_errs[i].string = NULL;
+ ERR_load_strings(ossl_libcode, ossl_errs);
+ }
+
+ if (ossl_tcpm == NULL) {
+ int tcpid = BIO_get_new_index();
+ ossl_tcpm =
+ BIO_meth_new(tcpid | BIO_TYPE_SOURCE_SINK, "nng_tcp");
+ BIO_meth_set_read_ex(ossl_tcpm, ossl_net_recv);
+ BIO_meth_set_write_ex(ossl_tcpm, ossl_net_send);
+ BIO_meth_set_ctrl(ossl_tcpm, ossl_bio_ctrl);
+ }
+
+ if (ossl_ex_index == 0) {
+ ossl_ex_index = CRYPTO_get_ex_new_index(
+ CRYPTO_EX_INDEX_APP, 0, NULL, NULL, NULL, NULL);
+ }
+}
+
+static void
+ossl_conn_fini(nng_tls_engine_conn *ec)
+{
+ SSL_free(ec->ssl);
+}
+
+static int
+ossl_conn_init(nng_tls_engine_conn *ec, void *tls, nng_tls_engine_config *cfg,
+ const nng_sockaddr *sa)
+{
+ BIO *bio;
+ NNI_ARG_UNUSED(sa); // for now... revisit if we support DTLS ?
+ ec->tls = tls;
+ ec->auth_mode = cfg->auth_mode;
+ ec->mode = cfg->mode;
+
+ if ((bio = BIO_new(ossl_tcpm)) == NULL) {
+ return (NNG_ENOMEM);
+ }
+ BIO_set_data(bio, tls);
+ BIO_set_init(bio, 1);
+
+ if ((ec->ssl = SSL_new(cfg->ctx)) == NULL) {
+ BIO_free(bio);
+ return (NNG_ENOMEM); // most likely
+ }
+ switch (ec->mode) {
+ case NNG_TLS_MODE_CLIENT:
+ SSL_set_ssl_method(ec->ssl, TLS_client_method());
+ SSL_set_connect_state(ec->ssl);
+ break;
+ case NNG_TLS_MODE_SERVER:
+ SSL_set_ssl_method(ec->ssl, TLS_server_method());
+ SSL_set_accept_state(ec->ssl);
+ break;
+ }
+
+ SSL_set_bio(ec->ssl, bio, bio);
+ SSL_set_dh_auto(ec->ssl, true);
+
+ if (cfg->server_name != NULL) {
+
+ if ((!SSL_set_tlsext_host_name(ec->ssl, cfg->server_name)) ||
+ (!SSL_set1_host(ec->ssl, cfg->server_name))) {
+
+ SSL_free(ec->ssl);
+ ec->ssl = NULL;
+ return (NNG_ENOMEM);
+ }
+ }
+
+ return (NNG_OK);
+}
+
+static void
+ossl_conn_close(nng_tls_engine_conn *ec)
+{
+ (void) SSL_shutdown(ec->ssl);
+}
+
+static int
+ossl_conn_recv(nng_tls_engine_conn *ec, uint8_t *buf, size_t *szp)
+{
+ int rv;
+ size_t n = *szp;
+ if ((rv = SSL_read_ex(ec->ssl, buf, n, szp)) <= 0) {
+ rv = SSL_get_error(ec->ssl, rv);
+ switch (rv) {
+
+ case SSL_ERROR_WANT_READ:
+ case SSL_ERROR_WANT_WRITE:
+ return (NNG_EAGAIN);
+ case SSL_ERROR_ZERO_RETURN:
+ // TLS Close Notify. TCP might still be open.
+ nng_log_debug(
+ "NNG-TLS-RECV", "TLS peer closed connection");
+ return (NNG_ECONNRESET);
+ case SSL_ERROR_SSL:
+ tls_log_err("NNG-TLS-RECV", "Receive TLS error",
+ ERR_get_error());
+ return (NNG_ECRYPTO);
+ case SSL_ERROR_SYSCALL:
+ tls_log_err("NNG-TLS-RECV",
+ "Receive TLS SYSCALL error", ERR_get_error());
+ return (NNG_ESYSERR);
+ default:
+ return (NNG_EINTERNAL);
+ }
+ }
+ return (0);
+}
+
+static int
+ossl_conn_send(nng_tls_engine_conn *ec, const uint8_t *buf, size_t *szp)
+{
+ int rv;
+
+ if ((rv = SSL_write_ex(ec->ssl, buf, (*szp), szp)) <= 0) {
+ rv = SSL_get_error(ec->ssl, rv);
+ switch (rv) {
+ case SSL_ERROR_WANT_READ:
+ case SSL_ERROR_WANT_WRITE:
+ return (NNG_EAGAIN);
+ case SSL_ERROR_SSL:
+ tls_log_err(
+ "NNG-TLS-SEND", "Send TLS error", ERR_get_error());
+ return (NNG_ECRYPTO);
+ case SSL_ERROR_SYSCALL:
+ return (NNG_ESYSERR);
+ case SSL_ERROR_ZERO_RETURN:
+ // TLS Close Notify. TCP might still be open.
+ return (NNG_ECONNRESET);
+ default:
+ return (NNG_EINTERNAL);
+ }
+ }
+ return (NNG_OK);
+}
+
+static int
+ossl_conn_handshake(nng_tls_engine_conn *ec)
+{
+ int rv;
+
+ rv = SSL_do_handshake(ec->ssl);
+ if (rv == 1) {
+ nng_log_debug("NNG-TLS-HS", "TLS handshake complete");
+ return (NNG_OK);
+ }
+ rv = SSL_get_error(ec->ssl, rv);
+ switch (rv) {
+ case SSL_ERROR_WANT_WRITE:
+ case SSL_ERROR_WANT_READ:
+ return (NNG_EAGAIN);
+ case SSL_ERROR_ZERO_RETURN:
+ tls_log_err("NNG-TLS-CONN-FAIL",
+ "Failed to establish TLS connection", ERR_get_error());
+ return (NNG_ECONNRESET);
+ case SSL_ERROR_SYSCALL:
+ tls_log_err("NNG-TLS-CONN-FAIL",
+ "Failed to setup TLS connection due to system error",
+ ERR_get_error());
+ return (NNG_ESYSERR);
+ case SSL_ERROR_SSL:
+ default:
+ // This can fail if we do not have a certificate
+ // for the peer. This will manifest as a failure
+ // during nng_dialer_start typically.
+ tls_log_err("NNG-TLS-CONN-FAIL",
+ "Failed to setup TLS connection", ERR_get_error());
+ return (NNG_ECRYPTO);
+ }
+ return (0);
+}
+
+static bool
+ossl_conn_verified(nng_tls_engine_conn *ec)
+{
+ switch (ec->auth_mode) {
+ case NNG_TLS_AUTH_MODE_NONE:
+ return (false);
+ case NNG_TLS_AUTH_MODE_REQUIRED:
+ return (true);
+ case NNG_TLS_AUTH_MODE_OPTIONAL:
+ if (SSL_get_verify_result(ec->ssl) == X509_V_OK &&
+ SSL_get_peer_certificate(ec->ssl) != NULL) {
+ return (true);
+ }
+ return (false);
+ default:
+ return (false);
+ }
+}
+
+static nng_err
+ossl_conn_peer_cert(nng_tls_engine_conn *ec, nng_tls_engine_cert **certp)
+{
+ nng_tls_engine_cert *cert;
+
+ X509 *wc;
+ if ((wc = SSL_get_peer_certificate(ec->ssl)) == NULL) {
+ return (NNG_ENOENT);
+ }
+ if ((cert = nni_zalloc(sizeof(*cert))) == NULL) {
+ X509_free(wc);
+ return (NNG_ENOMEM);
+ }
+ cert->crt = wc;
+ *certp = cert;
+ return (NNG_OK);
+}
+
+static char *
+ossl_conn_peer_cn(nng_tls_engine_conn *ec)
+{
+ X509 *cert;
+ X509_NAME *xn;
+ char *cn;
+ if ((cert = SSL_get_peer_certificate(ec->ssl)) == NULL) {
+ return (NULL);
+ }
+ xn = X509_get_subject_name(cert);
+ if (xn == NULL) {
+ return (NULL);
+ }
+ int pos = -1;
+ for (;;) {
+ X509_NAME_ENTRY *entry;
+ pos = X509_NAME_get_index_by_NID(xn, NID_commonName, pos);
+ if (pos == -1) {
+ return (NULL);
+ }
+ entry = X509_NAME_get_entry(xn, pos);
+ if (entry == NULL) {
+ continue;
+ }
+ ASN1_STRING *as;
+ if ((as = X509_NAME_ENTRY_get_data(entry)) == NULL) {
+ continue;
+ }
+ unsigned char *us;
+ if (ASN1_STRING_to_UTF8(&us, as) <= 0) {
+ continue;
+ }
+ // We need to use nng_strdup so that nng_strfree works.
+ // This is probably not particularly needed for most platforms
+ // where nng_free / nng_strfree are thin wrappers around free,
+ // but let's be pedantic about it.
+ cn = nng_strdup((char *) us);
+ free(us);
+ return (cn);
+ }
+ return (NULL);
+}
+
+static void
+ossl_config_fini(nng_tls_engine_config *cfg)
+{
+ psk *psk;
+ SSL_CTX_free(cfg->ctx);
+ if (cfg->server_name != NULL) {
+ nng_strfree(cfg->server_name);
+ }
+ if (cfg->pass != NULL) {
+ nng_strfree(cfg->pass);
+ }
+
+ while ((psk = nni_list_first(&cfg->psks)) != NULL) {
+ nni_list_remove(&cfg->psks, psk);
+ psk_free(psk);
+ }
+}
+
+static int
+ossl_config_init(nng_tls_engine_config *cfg, enum nng_tls_mode mode)
+{
+ int auth_mode;
+ int nng_auth;
+ const SSL_METHOD *method;
+
+ cfg->mode = mode;
+ NNI_LIST_INIT(&cfg->psks, psk, node);
+ if (mode == NNG_TLS_MODE_SERVER) {
+ method = TLS_server_method();
+ auth_mode = SSL_VERIFY_NONE;
+ nng_auth = NNG_TLS_AUTH_MODE_NONE;
+ } else {
+ method = TLS_client_method();
+ auth_mode = SSL_VERIFY_PEER;
+ nng_auth = NNG_TLS_AUTH_MODE_REQUIRED;
+ }
+
+ cfg->min_ver = TLS1_2_VERSION;
+ cfg->max_ver = TLS1_3_VERSION;
+
+ cfg->ctx = SSL_CTX_new(method);
+ if (cfg->ctx == NULL) {
+ return (NNG_ENOMEM);
+ }
+ SSL_CTX_set_ex_data(cfg->ctx, ossl_ex_index, cfg);
+ SSL_CTX_set_dh_auto(cfg->ctx, true);
+
+ // By default we require TLS 1.2.
+ if (!SSL_CTX_set_min_proto_version(cfg->ctx, cfg->min_ver)) {
+ tls_log_err("NNG-TLS-VERSION",
+ "Failed setting min TLS version", ERR_get_error());
+ return (NNG_ECRYPTO);
+ }
+ if (!SSL_CTX_set_max_proto_version(cfg->ctx, cfg->max_ver)) {
+ tls_log_err("NNG-TLS-VERSION",
+ "Failed setting max TLS version", ERR_get_error());
+ return (NNG_ECRYPTO);
+ }
+ SSL_CTX_set_verify(cfg->ctx, auth_mode, NULL);
+
+ cfg->auth_mode = nng_auth;
+ return (NNG_OK);
+}
+
+static int
+ossl_config_server(nng_tls_engine_config *cfg, const char *name)
+{
+ char *dup;
+ if ((dup = nng_strdup(name)) == NULL) {
+ return (NNG_ENOMEM);
+ }
+ if (cfg->server_name) {
+ nng_strfree(cfg->server_name);
+ }
+ cfg->server_name = dup;
+ return (0);
+}
+
+static unsigned int
+psk_client_cb(SSL *ssl, const char *hint, char *identity,
+ unsigned int id_max_len, unsigned char *key, unsigned int max_len)
+{
+ SSL_CTX *ctx;
+ nng_tls_engine_config *cfg;
+ psk *psk;
+ NNI_ARG_UNUSED(hint);
+
+ ctx = SSL_get_SSL_CTX(ssl);
+ cfg = SSL_CTX_get_ex_data(ctx, ossl_ex_index);
+
+ // we ignore the "hint" (its not widely used, and we are electing not
+ // to support it)
+ if ((psk = nni_list_first(&cfg->psks)) != NULL) {
+ strncpy(identity, psk->identity, id_max_len);
+ if (max_len < psk->keylen) {
+ // key overrun
+ nng_log_warn(
+ "NNG-TLS-PSK-LEN", "Preshared key too long");
+ return (0);
+ }
+ memcpy(key, psk->key, psk->keylen);
+ return (psk->keylen);
+ }
+ nng_log_warn("NNG-TLS-PSK-MISSING", "Preshared key missing");
+ return (0);
+}
+
+static unsigned int
+psk_server_cb(
+ SSL *ssl, const char *identity, uint8_t *key, unsigned int max_len)
+{
+ SSL_CTX *ctx;
+ nng_tls_engine_config *cfg;
+ psk *psk;
+
+ ctx = SSL_get_SSL_CTX(ssl);
+ cfg = SSL_CTX_get_ex_data(ctx, ossl_ex_index);
+
+ // we ignore the "hint" (its not widely used, and we are electing not
+ // to support it)
+ NNI_LIST_FOREACH (&cfg->psks, psk) {
+
+ if (strcmp(psk->identity, identity) == 0) {
+ if (max_len < psk->keylen) {
+ // key overrun
+ nng_log_warn("NNG-TLS-PSK-LEN",
+ "Preshared key too long");
+ return (0);
+ }
+ nng_log_info("NNG-TLS-PSK-IDENTITY",
+ "TLS client using PSK identity %s", psk->identity);
+ memcpy(key, psk->key, psk->keylen);
+ return (psk->keylen);
+ }
+ }
+ nng_log_warn(
+ "NNG-TLS-PSK-NO-IDENTITY", "TLS client PSK identity not found");
+ return (0);
+}
+
+static int
+ossl_config_psk(nng_tls_engine_config *cfg, const char *identity,
+ const uint8_t *key, size_t key_len)
+{
+ psk *psk, *srch;
+
+ if (key_len > 64) {
+ // not exactly sure where the wolfSSL limits are, but this is
+ // enough for 512 bits of data.
+ nng_log_warn(
+ "NNG-TLS-PSK-TOO-BIG", "PSK key length too large");
+ return (NNG_ECRYPTO);
+ }
+ if (((psk = NNI_ALLOC_STRUCT(psk)) == NULL) ||
+ ((psk->identity = nni_strdup(identity)) == NULL) ||
+ ((psk->key = nni_alloc(key_len)) == NULL)) {
+ psk_free(psk);
+ return (NNG_ENOMEM);
+ }
+ memcpy(psk->key, key, key_len);
+ psk->keylen = key_len;
+
+ if (nni_list_empty(&cfg->psks)) {
+ if (cfg->mode == NNG_TLS_MODE_SERVER) {
+ SSL_CTX_set_psk_server_callback(
+ cfg->ctx, psk_server_cb);
+ } else { // client
+ SSL_CTX_set_psk_client_callback(
+ cfg->ctx, psk_client_cb);
+ }
+ }
+
+ // If the identity was previously configured, replace it.
+ // The rule here is that last one wins, so we always append.
+ NNI_LIST_FOREACH (&cfg->psks, srch) {
+ if (strcmp(srch->identity, identity) == 0) {
+ nni_list_remove(&cfg->psks, srch);
+ psk_free(srch);
+ break;
+ }
+ }
+
+ nni_list_append(&cfg->psks, psk);
+ return (0);
+}
+
+static int
+ossl_config_auth_mode(nng_tls_engine_config *cfg, nng_tls_auth_mode mode)
+{
+ cfg->auth_mode = mode;
+ switch (mode) {
+ case NNG_TLS_AUTH_MODE_NONE:
+ SSL_CTX_set_verify(cfg->ctx, SSL_VERIFY_NONE, NULL);
+ return (NNG_OK);
+ case NNG_TLS_AUTH_MODE_OPTIONAL:
+ SSL_CTX_set_verify(cfg->ctx, SSL_VERIFY_PEER, NULL);
+ return (NNG_OK);
+ case NNG_TLS_AUTH_MODE_REQUIRED:
+ SSL_CTX_set_verify(cfg->ctx,
+ SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL);
+ return (NNG_OK);
+ default:
+ return (NNG_EINVAL);
+ }
+}
+
+static int
+ossl_get_password(char *passwd, int size, int rw, void *ctx)
+{
+ NNI_ARG_UNUSED(rw);
+ nng_tls_engine_config *cfg = ctx;
+ size_t len;
+
+ if (cfg->pass == NULL) {
+ return (0);
+ }
+ len = strlen(cfg->pass); // Our "ctx" is really the password.
+ if (len > (size_t) size) {
+ len = size;
+ }
+ memcpy(passwd, cfg->pass, len);
+ return (len);
+}
+
+static int
+ossl_config_ca_chain(
+ nng_tls_engine_config *cfg, const char *certs, const char *crl)
+{
+ X509_STORE *cert_store = X509_STORE_new();
+
+ BIO *crtb = BIO_new_mem_buf(certs, -1);
+
+ // certificates first
+ X509 *cert;
+ while ((cert = PEM_read_bio_X509(crtb, NULL, 0, NULL)) != NULL) {
+ X509_STORE_add_cert(cert_store, cert);
+ X509_free(cert); // X509_STORE_add_cert takes a reference
+ }
+ BIO_free(crtb);
+
+ if (crl != NULL) {
+ BIO *crlb = BIO_new_mem_buf(crl, -1);
+ X509_CRL *xcrl = PEM_read_bio_X509_CRL(crlb, NULL, NULL, NULL);
+ if (xcrl != NULL) {
+ X509_STORE_add_crl(cert_store, xcrl);
+ }
+ BIO_free(crlb);
+ }
+ SSL_CTX_set_cert_store(cfg->ctx, cert_store);
+
+ return (NNG_OK);
+}
+
+static int
+ossl_config_own_cert(nng_tls_engine_config *cfg, const char *cert,
+ const char *key, const char *pass)
+{
+ char *dup = NULL;
+ if (pass != NULL) {
+ if ((dup = nng_strdup(pass)) == NULL) {
+ return (NNG_ENOMEM);
+ }
+ }
+ if (cfg->pass != NULL) {
+ nng_strfree(cfg->pass);
+ }
+ cfg->pass = dup;
+
+ BIO *crtb = BIO_new_mem_buf(cert, -1);
+ BIO *keyb = BIO_new_mem_buf(key, -1);
+
+ X509 *xc = PEM_read_bio_X509(crtb, NULL, 0, NULL);
+
+ if (xc == NULL) {
+ BIO_free(crtb);
+ BIO_free(keyb);
+ tls_log_err(
+ "NNG-TLS-KEY", "Failed to load own cert", ERR_get_error());
+ return (NNG_ECRYPTO);
+ }
+
+ EVP_PKEY *pkey =
+ PEM_read_bio_PrivateKey(keyb, NULL, ossl_get_password, cfg);
+ if (pkey == NULL) {
+ BIO_free(crtb);
+ BIO_free(keyb);
+ X509_free(xc);
+ tls_log_err(
+ "NNG-TLS-KEY", "Failed to load own key", ERR_get_error());
+ return (NNG_ECRYPTO);
+ }
+
+ if (SSL_CTX_use_cert_and_key(cfg->ctx, xc, pkey, NULL, 1) <= 0) {
+ BIO_free(crtb);
+ BIO_free(keyb);
+ X509_free(xc);
+ tls_log_err("NNG-TLS-KEY",
+ "Failed to configure own key and cert", ERR_get_error());
+ return (NNG_ECRYPTO);
+ }
+
+ X509_free(xc);
+ EVP_PKEY_free(pkey);
+ BIO_free(crtb);
+ BIO_free(keyb);
+ return (NNG_OK);
+}
+
+static int
+ossl_config_version(nng_tls_engine_config *cfg, nng_tls_version min_ver,
+ nng_tls_version max_ver)
+{
+ int rv;
+
+ if ((min_ver > max_ver) || (max_ver > NNG_TLS_1_3)) {
+ return (NNG_ENOTSUP);
+ }
+ switch (min_ver) {
+ case NNG_TLS_1_2:
+ rv = SSL_CTX_set_min_proto_version(cfg->ctx, TLS1_2_VERSION);
+ break;
+ case NNG_TLS_1_3:
+ rv = SSL_CTX_set_min_proto_version(cfg->ctx, TLS1_3_VERSION);
+ break;
+ default:
+ return (NNG_ENOTSUP);
+ }
+ if (!rv) {
+ return (NNG_ENOTSUP);
+ }
+
+ switch (max_ver) {
+ case NNG_TLS_1_2:
+ rv = SSL_CTX_set_max_proto_version(cfg->ctx, TLS1_2_VERSION);
+ break;
+ case NNG_TLS_1_3:
+ rv = SSL_CTX_set_max_proto_version(cfg->ctx, TLS1_3_VERSION);
+ break;
+ default:
+ return (NNG_ENOTSUP);
+ }
+
+ if (!rv) {
+ return (NNG_ENOTSUP);
+ }
+ return (NNG_OK);
+}
+
+static void
+ossl_cert_free(nng_tls_engine_cert *cert)
+{
+ if (cert->subject != NULL) {
+ OPENSSL_free(cert->subject);
+ }
+ if (cert->issuer != NULL) {
+ OPENSSL_free(cert->issuer);
+ }
+ if (cert->crt != NULL) {
+ X509_free(cert->crt);
+ }
+ nni_free(cert, sizeof(*cert));
+}
+
+static nng_err
+ossl_cert_get_der(nng_tls_engine_cert *cert, uint8_t *buf, size_t *sz)
+{
+ uint8_t *der;
+ int derSz;
+ derSz = i2d_X509(cert->crt, &der);
+ if (*sz < (size_t) derSz) {
+ *sz = (size_t) derSz;
+ return (NNG_ENOSPC);
+ }
+ *sz = (size_t) derSz;
+ memcpy(buf, der, *sz);
+ OPENSSL_free(der);
+ return (NNG_OK);
+}
+
+static nng_err
+ossl_cert_parse_der(
+ nng_tls_engine_cert **crtp, const uint8_t *der, size_t size)
+{
+ X509 *x;
+ nng_tls_engine_cert *cert;
+
+ if ((cert = nni_zalloc(sizeof(*cert))) == NULL) {
+ return (NNG_ENOMEM);
+ }
+ if ((cert->crt = d2i_X509(&x, &der, size)) == NULL) {
+ nni_free(cert, sizeof(*cert));
+ return (NNG_ENOMEM);
+ }
+ *crtp = cert;
+ return (NNG_OK);
+}
+
+static nng_err
+ossl_cert_parse_pem(nng_tls_engine_cert **crtp, const char *pem, size_t size)
+{
+ nng_tls_engine_cert *cert;
+
+ if ((cert = nni_zalloc(sizeof(*crtp))) == NULL) {
+ return (NNG_ENOMEM);
+ }
+ BIO *certb = BIO_new_mem_buf(pem, size);
+ X509 *xc = PEM_read_bio_X509(certb, NULL, NULL, NULL);
+ BIO_free(certb);
+
+ if (xc == NULL) {
+ nni_free(cert, sizeof(*cert));
+ return (NNG_ECRYPTO);
+ }
+ cert->crt = xc;
+ *crtp = cert;
+ return (NNG_OK);
+}
+
+static nng_err
+ossl_cert_subject(nng_tls_engine_cert *cert, char **subject)
+{
+ X509_NAME *xn;
+
+ if (cert->subject != NULL) {
+ *subject = cert->subject;
+ return (NNG_OK);
+ }
+
+ xn = X509_get_subject_name(cert->crt);
+ if (xn == NULL) {
+ return (NNG_ENOENT);
+ }
+ cert->subject = X509_NAME_oneline(xn, NULL, 0);
+ *subject = cert->subject;
+ return (NNG_OK);
+}
+
+static nng_err
+ossl_cert_issuer(nng_tls_engine_cert *cert, char **issuer)
+{
+ X509_NAME *xn;
+
+ if (cert->issuer != NULL) {
+ *issuer = cert->issuer;
+ return (NNG_OK);
+ }
+
+ xn = X509_get_issuer_name(cert->crt);
+ if (xn == NULL) {
+ return (NNG_ENOENT);
+ }
+ cert->issuer = X509_NAME_oneline(xn, NULL, 0);
+ *issuer = cert->issuer;
+ return (NNG_OK);
+}
+
+static nng_err
+ossl_cert_serial(nng_tls_engine_cert *cert, char **serial)
+{
+ if (cert->serial[0] != 0) {
+ *serial = cert->serial;
+ return (NNG_OK);
+ }
+
+ const ASN1_INTEGER *as = X509_get0_serialNumber(cert->crt);
+ BIGNUM *bn = ASN1_INTEGER_to_BN(as, NULL);
+ char *hex = BN_bn2hex(bn);
+
+ if ((as = X509_get0_serialNumber(cert->crt)) == NULL) {
+ return (NNG_ENOENT);
+ }
+ if ((bn = ASN1_INTEGER_to_BN(as, NULL)) == NULL) {
+ return (NNG_ENOMEM);
+ }
+ if ((hex = BN_bn2hex(bn)) == NULL) {
+ BN_free(bn);
+ return (NNG_ENOMEM);
+ }
+ snprintf(cert->serial, sizeof(cert->serial), "%s", hex);
+ BN_free(bn);
+ OPENSSL_free(hex);
+
+ *serial = cert->serial;
+ return (NNG_OK);
+}
+
+static nng_err
+ossl_cert_subject_cn(nng_tls_engine_cert *cert, char **cn)
+{
+ X509_NAME *xn = X509_get_subject_name(cert->crt);
+ int pos = -1;
+ for (;;) {
+ X509_NAME_ENTRY *entry;
+ pos = X509_NAME_get_index_by_NID(xn, NID_commonName, pos);
+ if (pos == -1) {
+ return (NNG_ENOENT);
+ }
+ entry = X509_NAME_get_entry(xn, pos);
+ if (entry == NULL) {
+ continue;
+ }
+ ASN1_STRING *as;
+ if ((as = X509_NAME_ENTRY_get_data(entry)) == NULL) {
+ continue;
+ }
+ unsigned char *us;
+ if (ASN1_STRING_to_UTF8(&us, as) <= 0) {
+ continue;
+ }
+ // We need to use nng_strdup so that nng_strfree works.
+ // This is probably not particularly needed for most platforms
+ // where nng_free / nng_strfree are thin wrappers around free,
+ // but let's be pedantic about it.
+ *cn = nng_strdup((char *) us);
+ free(us);
+ return (*cn == NULL ? NNG_ENOMEM : NNG_OK);
+ }
+ return (NNG_ENOENT);
+}
+
+static nng_err
+ossl_cert_next_alt(nng_tls_engine_cert *cert, char **alt)
+{
+ GENERAL_NAMES *names = NULL;
+ GENERAL_NAME *san;
+ int num_names;
+ nng_err rv;
+
+ names = X509_get_ext_d2i(cert->crt, NID_subject_alt_name, NULL, NULL);
+ if (names == NULL) {
+ return (NNG_ENOENT);
+ }
+ num_names = sk_GENERAL_NAME_num(names);
+ if (cert->next_alt >= num_names) {
+ sk_GENERAL_NAME_free(names);
+ return (NNG_ENOENT);
+ }
+
+ san = sk_GENERAL_NAME_value(names, cert->next_alt);
+ if (san == NULL) {
+ sk_GENERAL_NAME_free(names);
+ return (NNG_ENOENT);
+ }
+
+ cert->next_alt++;
+
+ *alt = NULL;
+ rv = NNG_OK;
+
+ char ip_str[46]; // enough for IPv6
+ enum nng_sockaddr_family af = NNG_AF_INET;
+
+ switch (san->type) {
+ case GEN_DNS:
+ if (san->d.dNSName != NULL && san->d.dNSName->data != NULL) {
+ *alt = nng_strdup((char *) san->d.dNSName->data);
+ }
+ break;
+ case GEN_IPADD:
+ if (san->d.iPAddress->length == 16) {
+ af = NNG_AF_INET6;
+ }
+ nni_inet_ntop(af, san->d.iPAddress->data, ip_str);
+ *alt = nng_strdup(ip_str);
+ break;
+ // NB: We only return DNS or IP names for now, not emails or other
+ // strings.
+ default:
+ rv = NNG_ENOENT;
+ break;
+ }
+ sk_GENERAL_NAME_free(names);
+
+ if ((*alt == NULL) && (rv == NNG_OK)) {
+ rv = NNG_ENOMEM;
+ }
+ return (rv);
+}
+
+static nng_err
+ossl_cert_not_before(nng_tls_engine_cert *cert, struct tm *tmp)
+{
+ ASN1_TIME *when;
+ when = X509_get_notBefore(cert->crt);
+ ASN1_TIME_to_tm(when, tmp);
+ return (NNG_OK);
+}
+
+static nng_err
+ossl_cert_not_after(nng_tls_engine_cert *cert, struct tm *tmp)
+{
+ ASN1_TIME *when;
+ when = X509_get_notAfter(cert->crt);
+ ASN1_TIME_to_tm(when, tmp);
+ return (NNG_OK);
+}
+
+static nng_err
+tls_engine_init(void)
+{
+ SSL_library_init();
+ ossl_init_nng();
+ return (NNG_OK);
+}
+
+static void
+tls_engine_fini(void)
+{
+}
+
+static bool
+fips_mode(void)
+{
+ return (false); // TODO: Support FIPS mode.
+}
+
+static nng_tls_engine_config_ops ossl_config_ops = {
+ .init = ossl_config_init,
+ .fini = ossl_config_fini,
+ .size = sizeof(nng_tls_engine_config),
+ .auth = ossl_config_auth_mode,
+ .ca_chain = ossl_config_ca_chain,
+ .own_cert = ossl_config_own_cert,
+ .server = ossl_config_server,
+ .psk = ossl_config_psk,
+ .version = ossl_config_version,
+};
+
+static nng_tls_engine_conn_ops ossl_conn_ops = {
+ .size = sizeof(nng_tls_engine_conn),
+ .init = ossl_conn_init,
+ .fini = ossl_conn_fini,
+ .close = ossl_conn_close,
+ .recv = ossl_conn_recv,
+ .send = ossl_conn_send,
+ .handshake = ossl_conn_handshake,
+ .verified = ossl_conn_verified,
+ .peer_cn = ossl_conn_peer_cn,
+ .peer_cert = ossl_conn_peer_cert,
+};
+
+static nng_tls_engine_cert_ops ossl_cert_ops = {
+ .fini = ossl_cert_free,
+ .get_der = ossl_cert_get_der,
+ .parse_der = ossl_cert_parse_der,
+ .parse_pem = ossl_cert_parse_pem,
+ .subject = ossl_cert_subject,
+ .issuer = ossl_cert_issuer,
+ .serial_number = ossl_cert_serial,
+ .subject_cn = ossl_cert_subject_cn,
+ .next_alt_name = ossl_cert_next_alt,
+ .not_before = ossl_cert_not_before,
+ .not_after = ossl_cert_not_after,
+};
+
+nng_tls_engine nng_tls_engine_ops = {
+ .version = NNG_TLS_ENGINE_VERSION,
+ .config_ops = &ossl_config_ops,
+ .conn_ops = &ossl_conn_ops,
+ .cert_ops = &ossl_cert_ops,
+ .name = "OpenSSL",
+ .description = OPENSSL_VERSION_TEXT,
+ .init = tls_engine_init,
+ .fini = tls_engine_fini,
+ .fips_mode = fips_mode,
+};