aboutsummaryrefslogtreecommitdiff
path: root/src/supplemental
diff options
context:
space:
mode:
authorGarrett D'Amore <garrett@damore.org>2025-10-05 16:51:15 -0700
committerGarrett D'Amore <garrett@damore.org>2025-10-05 20:56:39 -0700
commit06d6d80f8c92ef1d3bd7c00c919e10a411183cb3 (patch)
treeedf8d4cff9b2f595ccd9e3cb4db3cf31eb13bc02 /src/supplemental
parentd1bd64c8251171ac8e1d4e71ab8726c2a64fd55a (diff)
downloadnng-06d6d80f8c92ef1d3bd7c00c919e10a411183cb3.tar.gz
nng-06d6d80f8c92ef1d3bd7c00c919e10a411183cb3.tar.bz2
nng-06d6d80f8c92ef1d3bd7c00c919e10a411183cb3.zip
fixes #2173 New TLS cert API - replaces the properties for CN and ALTNAMES.
This will replace the NNG_OPT_TLS_PEER_ALTNAMES and NNG_OPT_TLS_PEER_CN properties, and gives a bit more access to the certificate, as well as direct access to the raw DER form, which should allow use in other APIs.
Diffstat (limited to 'src/supplemental')
-rw-r--r--src/supplemental/http/http_api.h1
-rw-r--r--src/supplemental/http/http_conn.c14
-rw-r--r--src/supplemental/http/http_public.c12
-rw-r--r--src/supplemental/tls/mbedtls/mbedtls.c326
-rw-r--r--src/supplemental/tls/tls_common.c109
-rw-r--r--src/supplemental/tls/tls_common.h3
-rw-r--r--src/supplemental/tls/tls_dialer.c2
-rw-r--r--src/supplemental/tls/tls_engine.h58
-rw-r--r--src/supplemental/tls/tls_listener.c2
-rw-r--r--src/supplemental/tls/tls_stream.c22
-rw-r--r--src/supplemental/tls/wolfssl/CMakeLists.txt8
-rw-r--r--src/supplemental/tls/wolfssl/wolfssl.c328
-rw-r--r--src/supplemental/websocket/websocket.c28
13 files changed, 800 insertions, 113 deletions
diff --git a/src/supplemental/http/http_api.h b/src/supplemental/http/http_api.h
index a8f648dc..b1a8ec84 100644
--- a/src/supplemental/http/http_api.h
+++ b/src/supplemental/http/http_api.h
@@ -99,6 +99,7 @@ extern void nni_http_conn_close(nng_http *);
extern void nni_http_conn_fini(nni_http_conn *);
extern int nni_http_conn_getopt(
nng_http *, const char *, void *, size_t *, nni_type);
+extern nng_err nni_http_conn_peer_cert(nng_http *, nng_tls_cert **);
// Reading messages -- the caller must supply a preinitialized (but otherwise
// idle) message. We recommend the caller store this in the aio's user data.
diff --git a/src/supplemental/http/http_conn.c b/src/supplemental/http/http_conn.c
index 21087474..93068512 100644
--- a/src/supplemental/http/http_conn.c
+++ b/src/supplemental/http/http_conn.c
@@ -1482,6 +1482,20 @@ nni_http_conn_getopt(
return (rv);
}
+nng_err
+nni_http_conn_peer_cert(nni_http_conn *conn, nng_tls_cert **certp)
+{
+ int rv;
+ nni_mtx_lock(&conn->mtx);
+ if (conn->closed) {
+ rv = NNG_ECLOSED;
+ } else {
+ rv = nng_stream_peer_cert(conn->sock, certp);
+ }
+ nni_mtx_unlock(&conn->mtx);
+ return (rv);
+}
+
void
nni_http_conn_fini(nni_http_conn *conn)
{
diff --git a/src/supplemental/http/http_public.c b/src/supplemental/http/http_public.c
index 9c8ded2d..5c7d8a77 100644
--- a/src/supplemental/http/http_public.c
+++ b/src/supplemental/http/http_public.c
@@ -651,3 +651,15 @@ nng_http_reset(nng_http *conn)
NNI_ARG_UNUSED(conn);
#endif
}
+
+nng_err
+nng_http_peer_cert(nng_http *conn, nng_tls_cert **certp)
+{
+#ifdef NNG_SUPP_HTTP
+ return (nni_http_conn_peer_cert(conn, certp));
+#else
+ NNI_ARG_UNUSED(conn);
+ NNI_ARG_UNUSED(certp);
+ return (NNG_ENOTSUP);
+#endif
+}
diff --git a/src/supplemental/tls/mbedtls/mbedtls.c b/src/supplemental/tls/mbedtls/mbedtls.c
index bbfc5763..87a8bd96 100644
--- a/src/supplemental/tls/mbedtls/mbedtls.c
+++ b/src/supplemental/tls/mbedtls/mbedtls.c
@@ -76,6 +76,16 @@ psk_free(psk *p)
}
}
+struct nng_tls_engine_cert {
+ mbedtls_x509_crt crt;
+ char subject[1024];
+ char issuer[1024];
+ char last_alt[256];
+ char serial[64]; // 20 bytes per RFC, x 3 for display
+ char cn[65]; // 64 + null
+ int next_alt;
+};
+
struct nng_tls_engine_conn {
void *tls; // parent conn
mbedtls_ssl_context ctx;
@@ -370,11 +380,32 @@ conn_verified(nng_tls_engine_conn *ec)
return (mbedtls_ssl_get_verify_result(&ec->ctx) == 0);
}
+static nng_err
+conn_peer_cert(nng_tls_engine_conn *ec, nng_tls_engine_cert **certp)
+{
+ nng_tls_engine_cert *cert;
+
+ const mbedtls_x509_crt *crt = mbedtls_ssl_get_peer_cert(&ec->ctx);
+
+ if (crt == NULL) {
+ return (NNG_ENOENT);
+ }
+ if ((cert = nni_zalloc(sizeof(*cert))) == NULL) {
+ return (NNG_ENOMEM);
+ }
+ // In order to ensure that we get our *own* copy that might outlive the
+ // cert buffer from the connection, we do a parse.
+ mbedtls_x509_crt_parse(&cert->crt, crt->raw.p, crt->raw.len);
+
+ *certp = cert;
+ return (NNG_OK);
+}
+
static char *
conn_peer_cn(nng_tls_engine_conn *ec)
{
const mbedtls_x509_crt *crt = mbedtls_ssl_get_peer_cert(&ec->ctx);
- if (!crt) {
+ if (crt == NULL) {
return (NULL);
}
@@ -400,47 +431,6 @@ conn_peer_cn(nng_tls_engine_conn *ec)
return (rv);
}
-static char **
-conn_peer_alt_names(nng_tls_engine_conn *ec)
-{
- const mbedtls_x509_crt *crt = mbedtls_ssl_get_peer_cert(&ec->ctx);
- if (!crt) {
- return (NULL);
- }
-
- const mbedtls_asn1_sequence *seq = &crt->subject_alt_names;
-
- // get count
- int count = 0;
- do {
- if (seq->buf.len > 0)
- ++count;
- seq = seq->next;
- } while (seq);
- if (count == 0)
- return NULL;
-
- seq = &crt->subject_alt_names;
-
- // copy strings
- char **rv = malloc((count + 1) * sizeof(char *));
- int i = 0;
- do {
- if (seq->buf.len == 0)
- continue;
-
- rv[i] = malloc(seq->buf.len + 1);
- memcpy(rv[i], seq->buf.p, seq->buf.len);
- rv[i][seq->buf.len] = 0;
- ++i;
-
- seq = seq->next;
- } while (seq);
- rv[i] = NULL;
-
- return rv;
-}
-
static void
config_fini(nng_tls_engine_config *cfg)
{
@@ -713,6 +703,223 @@ err:
return (rv);
}
+static void
+cert_fini(nng_tls_engine_cert *crt)
+{
+ mbedtls_x509_crt_free(&crt->crt);
+ nni_free(crt, sizeof(*crt));
+}
+
+static nng_err
+cert_parse_der(nng_tls_engine_cert **crtp, const uint8_t *der, size_t size)
+{
+ nng_tls_engine_cert *cert;
+ int rv;
+
+ if ((cert = nni_zalloc(sizeof(*cert))) == NULL) {
+ return (NNG_ENOMEM);
+ }
+ mbedtls_x509_crt_init(&cert->crt);
+ if ((rv = mbedtls_x509_crt_parse_der(&cert->crt, der, size)) != 0) {
+ tls_log_err(
+ "NNG-TLS-DER", "Failure parsing DER certificate", rv);
+ rv = tls_mk_err(rv);
+ cert_fini(cert);
+ return (rv);
+ }
+ *crtp = cert;
+ return (NNG_OK);
+}
+
+static nng_err
+cert_parse_pem(nng_tls_engine_cert **crtp, const char *pem, size_t size)
+{
+ nng_tls_engine_cert *cert;
+ int rv;
+
+ if ((cert = nni_zalloc(sizeof(*cert))) == NULL) {
+ return (NNG_ENOMEM);
+ }
+ mbedtls_x509_crt_init(&cert->crt);
+ if ((rv = mbedtls_x509_crt_parse(
+ &cert->crt, (const uint8_t *) pem, size)) != 0) {
+ tls_log_err(
+ "NNG-TLS-PEM", "Failure parsing PEM certificate", rv);
+ rv = tls_mk_err(rv);
+ cert_fini(cert);
+ return (rv);
+ }
+ *crtp = cert;
+ return (NNG_OK);
+}
+
+static nng_err
+cert_get_der(nng_tls_engine_cert *crt, uint8_t *buf, size_t *sizep)
+{
+ if (*sizep < crt->crt.raw.len) {
+ *sizep = crt->crt.raw.len;
+ return (NNG_ENOSPC);
+ }
+ *sizep = crt->crt.raw.len;
+ memcpy(buf, crt->crt.raw.p, *sizep);
+ return (NNG_OK);
+}
+
+static nng_err
+cert_subject(nng_tls_engine_cert *crt, char **namep)
+{
+ if (crt->subject[0] != 0) {
+ *namep = (char *) &crt->subject[0];
+ return (NNG_OK);
+ }
+ int len = mbedtls_x509_dn_gets(
+ crt->subject, sizeof(crt->subject), &crt->crt.subject);
+ if (len <= 0) {
+ return (NNG_ENOTSUP);
+ }
+ *namep = &crt->subject[0];
+ return (NNG_OK);
+}
+
+static nng_err
+cert_issuer(nng_tls_engine_cert *crt, char **namep)
+{
+ if (crt->issuer[0] != 0) {
+ *namep = (char *) &crt->issuer[0];
+ return (NNG_OK);
+ }
+ int len = mbedtls_x509_dn_gets(
+ crt->issuer, sizeof(crt->issuer), &crt->crt.issuer);
+ if (len <= 0) {
+ return (NNG_ENOTSUP);
+ }
+ *namep = &crt->issuer[0];
+ return (NNG_OK);
+}
+
+static nng_err
+cert_serial(nng_tls_engine_cert *crt, char **namep)
+{
+ if (crt->serial[0] != 0) {
+ *namep = (char *) &crt->serial[0];
+ return (NNG_OK);
+ }
+ int len = mbedtls_x509_serial_gets(
+ crt->serial, sizeof(crt->serial), &crt->crt.serial);
+ if (len < 0) {
+ return (tls_mk_err(len));
+ }
+ *namep = &crt->serial[0];
+ return (NNG_OK);
+}
+
+static nng_err
+cert_subject_cn(nng_tls_engine_cert *crt, char **namep)
+{
+ nng_err rv;
+ char *subject;
+ char *s;
+ if (crt->cn[0] != 0) {
+ *namep = (char *) &crt->cn[0];
+ return (NNG_OK);
+ }
+ if ((rv = cert_subject(crt, &subject)) != NNG_OK) {
+ return (rv);
+ }
+ if ((s = strstr(crt->subject, "CN=")) == NULL) {
+ return (NNG_ENOENT);
+ }
+ s += 3;
+ int n = 0;
+ bool esc = false;
+ while (n < 64) {
+ if (*s == 0) {
+ crt->cn[n++] = 0;
+ break;
+ }
+ if (esc) {
+ esc = false;
+ crt->cn[n++] = *s++;
+ } else if (*s == '\\') {
+ esc = true;
+ } else if (*s == ',') {
+ crt->cn[n++] = 0;
+ break;
+ } else {
+ crt->cn[n++] = *s++;
+ }
+ }
+ if ((n == 0) || (n > 64)) {
+ nng_log_warn(
+ "NNG-TLS-CN", "X.509 Subject CN length is invalid");
+ return (NNG_EINVAL);
+ }
+ *namep = (char *) &crt->cn[0];
+ return (0);
+}
+
+static nng_err
+cert_next_alt(nng_tls_engine_cert *crt, char **namep)
+{
+ const mbedtls_asn1_sequence *seq = &crt->crt.subject_alt_names;
+
+ // get count
+ int count = 0;
+ for (seq = &crt->crt.subject_alt_names; seq != NULL; seq = seq->next) {
+ if (seq->buf.len == 0) {
+ continue;
+ }
+ if (count == crt->next_alt) {
+ break;
+ }
+ count++;
+ }
+ if (seq == NULL) {
+ return (NNG_ENOENT);
+ }
+ crt->next_alt++;
+ if (seq->buf.len >= sizeof(crt->last_alt)) {
+ return (NNG_EINVAL);
+ }
+ memcpy(crt->last_alt, seq->buf.p, seq->buf.len);
+ crt->last_alt[seq->buf.len] = 0;
+ *namep = crt->last_alt;
+ return (NNG_OK);
+}
+
+static void
+mbed_time_to_tm(mbedtls_x509_time *mt, struct tm *tmp)
+{
+ memset(tmp, 0, sizeof(*tmp)); // also clears any zone offset, etc
+ if (mt->year < 100) {
+ // all dates > Y2K, relative to 1900
+ tmp->tm_year = mt->year + 100;
+ } else {
+ tmp->tm_year = mt->year - 1900;
+ }
+ tmp->tm_mon = mt->mon - 1; // month is zero based
+ tmp->tm_mday = mt->day;
+ tmp->tm_hour = mt->hour;
+ tmp->tm_min = mt->min;
+ tmp->tm_sec = mt->sec;
+ // canonicalize the time
+ (void) mktime(tmp);
+}
+
+static nng_err
+cert_not_before(nng_tls_engine_cert *cert, struct tm *tmp)
+{
+ mbed_time_to_tm(&cert->crt.valid_from, tmp);
+ return (NNG_OK);
+}
+
+static nng_err
+cert_not_after(nng_tls_engine_cert *cert, struct tm *tmp)
+{
+ mbed_time_to_tm(&cert->crt.valid_to, tmp);
+ return (NNG_OK);
+}
+
static int
config_version(nng_tls_engine_config *cfg, nng_tls_version min_ver,
nng_tls_version max_ver)
@@ -826,22 +1033,37 @@ static nng_tls_engine_config_ops config_ops = {
};
static nng_tls_engine_conn_ops conn_ops = {
- .size = sizeof(nng_tls_engine_conn),
- .init = conn_init,
- .fini = conn_fini,
- .close = conn_close,
- .recv = conn_recv,
- .send = conn_send,
- .handshake = conn_handshake,
- .verified = conn_verified,
- .peer_cn = conn_peer_cn,
- .peer_alt_names = conn_peer_alt_names,
+ .size = sizeof(nng_tls_engine_conn),
+ .init = conn_init,
+ .fini = conn_fini,
+ .close = conn_close,
+ .recv = conn_recv,
+ .send = conn_send,
+ .handshake = conn_handshake,
+ .verified = conn_verified,
+ .peer_cert = conn_peer_cert,
+ .peer_cn = conn_peer_cn,
+};
+
+static nng_tls_engine_cert_ops cert_ops = {
+ .fini = cert_fini,
+ .parse_der = cert_parse_der,
+ .parse_pem = cert_parse_pem,
+ .get_der = cert_get_der,
+ .subject = cert_subject,
+ .issuer = cert_issuer,
+ .serial_number = cert_serial,
+ .subject_cn = cert_subject_cn,
+ .next_alt_name = cert_next_alt,
+ .not_before = cert_not_before,
+ .not_after = cert_not_after,
};
nng_tls_engine nng_tls_engine_ops = {
.version = NNG_TLS_ENGINE_VERSION,
.config_ops = &config_ops,
.conn_ops = &conn_ops,
+ .cert_ops = &cert_ops,
.name = "mbed",
.description = MBEDTLS_VERSION_STRING_FULL,
.init = tls_engine_init,
diff --git a/src/supplemental/tls/tls_common.c b/src/supplemental/tls/tls_common.c
index 543b7563..75b056b3 100644
--- a/src/supplemental/tls/tls_common.c
+++ b/src/supplemental/tls/tls_common.c
@@ -45,6 +45,7 @@ static void tls_bio_send_start(nni_tls_conn *);
static void tls_bio_error(nni_tls_conn *, nng_err);
#define nni_tls_conn_ops (nng_tls_engine_ops.conn_ops)
+#define nni_tls_cert_ops (nng_tls_engine_ops.cert_ops)
#define nni_tls_cfg_ops (nng_tls_engine_ops.config_ops)
static void
@@ -134,7 +135,7 @@ nni_tls_verified(nni_tls_conn *conn)
nni_mtx_lock(&conn->lock);
result = nni_tls_conn_ops->verified((void *) (conn + 1));
nni_mtx_unlock(&conn->lock);
- return result;
+ return (result);
}
const char *
@@ -144,7 +145,14 @@ nni_tls_peer_cn(nni_tls_conn *conn)
nni_mtx_lock(&conn->lock);
result = nni_tls_conn_ops->peer_cn((void *) (conn + 1));
nni_mtx_unlock(&conn->lock);
- return result;
+ return (result);
+}
+
+nng_err
+nni_tls_peer_cert(nni_tls_conn *conn, nng_tls_cert **certp)
+{
+ return (
+ nni_tls_conn_ops->peer_cert((void *) (conn + 1), (void *) certp));
}
int
@@ -798,6 +806,103 @@ nng_tls_config_hold(nng_tls_config *cfg)
nni_mtx_unlock(&cfg->lock);
}
+void
+nng_tls_cert_free(nng_tls_cert *cert)
+{
+ nni_tls_cert_ops->fini((void *) cert);
+}
+
+nng_err
+nng_tls_cert_subject(nng_tls_cert *cert, char **namep)
+{
+ if (nni_tls_cert_ops->subject == NULL) {
+ return (NNG_ENOTSUP);
+ }
+ return (nni_tls_cert_ops->subject((void *) cert, namep));
+}
+
+nng_err
+nng_tls_cert_issuer(nng_tls_cert *cert, char **namep)
+{
+ if (nni_tls_cert_ops->issuer == NULL) {
+ return (NNG_ENOTSUP);
+ }
+ return (nni_tls_cert_ops->issuer((void *) cert, namep));
+}
+
+nng_err
+nng_tls_cert_serial_number(nng_tls_cert *cert, char **serialp)
+{
+ if (nni_tls_cert_ops->serial_number == NULL) {
+ return (NNG_ENOTSUP);
+ }
+ return (nni_tls_cert_ops->serial_number((void *) cert, serialp));
+}
+
+nng_err
+nng_tls_cert_subject_cn(nng_tls_cert *cert, char **cnp)
+{
+ if (nni_tls_cert_ops->subject_cn == NULL) {
+ return (NNG_ENOTSUP);
+ }
+ return (nni_tls_cert_ops->subject_cn((void *) cert, cnp));
+}
+
+nng_err
+nng_tls_cert_next_alt(nng_tls_cert *cert, char **alt)
+{
+ if (nni_tls_cert_ops->next_alt_name == NULL) {
+ return (NNG_ENOTSUP);
+ }
+ return (nni_tls_cert_ops->next_alt_name((void *) cert, alt));
+}
+
+nng_err
+nng_tls_cert_not_before(nng_tls_cert *cert, struct tm *tmp)
+{
+ if (nni_tls_cert_ops->not_before == NULL) {
+ return (NNG_ENOTSUP);
+ }
+ return (nni_tls_cert_ops->not_before((void *) cert, tmp));
+}
+
+nng_err
+nng_tls_cert_not_after(nng_tls_cert *cert, struct tm *tmp)
+{
+ if (nni_tls_cert_ops->not_after == NULL) {
+ return (NNG_ENOTSUP);
+ }
+ return (nni_tls_cert_ops->not_after((void *) cert, tmp));
+}
+
+void
+nng_tls_cert_der(nng_tls_cert *cert, uint8_t *buf, size_t *bufsz)
+{
+ nni_tls_cert_ops->get_der((void *) cert, buf, bufsz);
+}
+
+nng_err
+nng_tls_cert_parse_der(nng_tls_cert **certp, const uint8_t *der, size_t size)
+{
+ nng_tls_engine_cert *ecrt;
+ nng_err rv;
+ if ((rv = nni_tls_cert_ops->parse_der(&ecrt, der, size)) == NNG_OK) {
+ *certp = (void *) ecrt;
+ }
+ return (rv);
+}
+
+nng_err
+nng_tls_cert_parse_pem(nng_tls_cert **certp, const char *pem, size_t size)
+{
+ nng_tls_engine_cert *ecrt;
+ nng_err rv;
+ if ((rv = nni_tls_cert_ops->parse_pem(&ecrt, pem, size)) == NNG_OK) {
+ *certp = (void *) ecrt;
+ }
+ return (rv);
+}
+
const char *
nng_tls_engine_name(void)
{
diff --git a/src/supplemental/tls/tls_common.h b/src/supplemental/tls/tls_common.h
index 693948c2..6d163fd5 100644
--- a/src/supplemental/tls/tls_common.h
+++ b/src/supplemental/tls/tls_common.h
@@ -49,6 +49,8 @@ struct nng_tls_config {
// ... engine config data follows
};
+struct nng_tls_cert_s;
+
typedef struct nni_tls_bio_ops_s {
void (*bio_send)(void *, nng_aio *);
void (*bio_recv)(void *, nng_aio *);
@@ -99,6 +101,7 @@ extern void nni_tls_recv(nni_tls_conn *conn, nni_aio *aio);
extern void nni_tls_send(nni_tls_conn *conn, nni_aio *aio);
extern bool nni_tls_verified(nni_tls_conn *conn);
extern const char *nni_tls_peer_cn(nni_tls_conn *conn);
+extern nng_err nni_tls_peer_cert(nni_tls_conn *conn, nng_tls_cert **certp);
extern nng_err nni_tls_run(nni_tls_conn *conn);
extern size_t nni_tls_engine_conn_size(void);
diff --git a/src/supplemental/tls/tls_dialer.c b/src/supplemental/tls/tls_dialer.c
index 0b2acee7..514f03b7 100644
--- a/src/supplemental/tls/tls_dialer.c
+++ b/src/supplemental/tls/tls_dialer.c
@@ -13,7 +13,7 @@
#include <stdlib.h>
#include <string.h>
-#include "core/nng_impl.h"
+#include "../../core/nng_impl.h"
#include "tls_common.h"
#include "tls_engine.h"
diff --git a/src/supplemental/tls/tls_engine.h b/src/supplemental/tls/tls_engine.h
index 048e35a7..c0e395d5 100644
--- a/src/supplemental/tls/tls_engine.h
+++ b/src/supplemental/tls/tls_engine.h
@@ -13,6 +13,8 @@
#ifndef NNG_SUPPLEMENTAL_TLS_TLS_ENGINE_H
#define NNG_SUPPLEMENTAL_TLS_TLS_ENGINE_H
+#include <time.h>
+
#include "../../core/defs.h"
// Locking theory statement for TLS engines. The engine is assumed
@@ -35,6 +37,10 @@ typedef struct nng_tls_engine_conn nng_tls_engine_conn;
// definition locally.
typedef struct nng_tls_engine_config nng_tls_engine_config;
+// nng_tls_engine_cert represents the engine-specific representation
+// of an X.509 certificate.
+typedef struct nng_tls_engine_cert nng_tls_engine_cert;
+
typedef struct nng_tls_engine_conn_ops_s {
// size is the size of the engine's per-connection state.
// The framework will allocate this on behalf of the engine.
@@ -84,13 +90,13 @@ typedef struct nng_tls_engine_conn_ops_s {
// TLS verified, false otherwise.
bool (*verified)(nng_tls_engine_conn *);
+ // peer_cert obtains the peer certificate(s). Note that
+ // this capability might not be supported.
+ nng_err (*peer_cert)(nng_tls_engine_conn *, nng_tls_engine_cert **);
+
// peer_cn returns the common name of the peer
// The return string needs to be freed.
char *(*peer_cn)(nng_tls_engine_conn *);
-
- // peer_alt_names returns the subject alternative names.
- // The return string list and its strings need to be freed.
- char **(*peer_alt_names)(nng_tls_engine_conn *);
} nng_tls_engine_conn_ops;
typedef struct nng_tls_engine_config_ops_s {
@@ -170,12 +176,51 @@ typedef struct nng_tls_engine_config_ops_s {
nng_tls_engine_config *, nng_tls_version, nng_tls_version);
} nng_tls_engine_config_ops;
+typedef struct nng_tls_engine_cert_ops_s {
+ // fini is used to tear down the configuration object.
+ // This will only be called on objects that have been properly
+ // initialized with nte_config_init.
+ void (*fini)(nng_tls_engine_cert *);
+
+ // parse_pem parses a PEM object to obtain a certificate object.
+ nng_err (*parse_pem)(
+ nng_tls_engine_cert **certp, const char *pem, size_t pem_size);
+
+ // parse_der parses a DER object to obtain a certificate object.
+ nng_err (*parse_der)(
+ nng_tls_engine_cert **certp, const uint8_t *der, size_t der_size);
+
+ // get_der extracts the DER content from the object, which may then be
+ // used with other X509 APIs.
+ nng_err (*get_der)(
+ nng_tls_engine_cert *cert, uint8_t *dr, size_t *sizep);
+
+ // obtain the subject name
+ nng_err (*subject)(nng_tls_engine_cert *cert, char **name);
+
+ nng_err (*issuer)(nng_tls_engine_cert *cert, char **name);
+
+ nng_err (*serial_number)(nng_tls_engine_cert *cert, char **serial);
+
+ nng_err (*subject_cn)(nng_tls_engine_cert *cert, char **name);
+
+ nng_err (*next_alt_name)(nng_tls_engine_cert *cert, char **name);
+
+ // aka, valid from, the starting date of the certificate
+ nng_err (*not_before)(nng_tls_engine_cert *cert, struct tm *);
+
+ // aka, valid to, the expiration date of the certificate
+ nng_err (*not_after)(nng_tls_engine_cert *cert, struct tm *);
+
+} nng_tls_engine_cert_ops;
+
typedef enum nng_tls_engine_version_e {
NNG_TLS_ENGINE_V0 = 0,
NNG_TLS_ENGINE_V1 = 1, // adds FIPS, TLS 1.3 support
NNG_TLS_ENGINE_V2 = 2, // adds PSK support
NNG_TLS_ENGINE_V3 = 3, // refactored API
- NNG_TLS_ENGINE_VERSION = NNG_TLS_ENGINE_V3,
+ NNG_TLS_ENGINE_V4 = 4, // added cert ops
+ NNG_TLS_ENGINE_VERSION = NNG_TLS_ENGINE_V4,
} nng_tls_engine_version;
typedef struct nng_tls_engine_s {
@@ -190,6 +235,9 @@ typedef struct nng_tls_engine_s {
// conn_ops is the operations for TLS connections (stream-oriented).
const nng_tls_engine_conn_ops *conn_ops;
+ // cert_ops is the operations for TLS certificates.
+ const nng_tls_engine_cert_ops *cert_ops;
+
// name contains the name of the engine, for example "wolfSSL".
// It is acceptable to append a version number as well.
const char *name;
diff --git a/src/supplemental/tls/tls_listener.c b/src/supplemental/tls/tls_listener.c
index 5d2d8599..62dd0904 100644
--- a/src/supplemental/tls/tls_listener.c
+++ b/src/supplemental/tls/tls_listener.c
@@ -13,7 +13,7 @@
#include <stdlib.h>
#include <string.h>
-#include "core/nng_impl.h"
+#include "../../core/nng_impl.h"
#include "tls_common.h"
#include "tls_engine.h"
diff --git a/src/supplemental/tls/tls_stream.c b/src/supplemental/tls/tls_stream.c
index 4a033887..d3dd9497 100644
--- a/src/supplemental/tls/tls_stream.c
+++ b/src/supplemental/tls/tls_stream.c
@@ -15,6 +15,7 @@
#include "../../core/nng_impl.h"
+#include "nng/nng.h"
#include "tls_common.h"
#include "tls_engine.h"
#include "tls_stream.h"
@@ -142,6 +143,7 @@ tls_stream_conn_cb(void *arg)
static nng_err tls_stream_get(
void *arg, const char *name, void *buf, size_t *szp, nni_type t);
+static nng_err tls_stream_peer_cert(void *arg, nng_tls_cert **);
int
nni_tls_stream_alloc(tls_stream **tsp, nng_tls_config *cfg, nng_aio *user_aio)
@@ -160,12 +162,13 @@ nni_tls_stream_alloc(tls_stream **tsp, nng_tls_config *cfg, nng_aio *user_aio)
ts->user_aio = user_aio;
// NB: free is exposed for benefit of dialer/listener
- ts->stream.s_free = nni_tls_stream_free;
- ts->stream.s_close = tls_stream_close;
- ts->stream.s_stop = tls_stream_stop;
- ts->stream.s_send = tls_stream_send;
- ts->stream.s_recv = tls_stream_recv;
- ts->stream.s_get = tls_stream_get;
+ ts->stream.s_free = nni_tls_stream_free;
+ ts->stream.s_close = tls_stream_close;
+ ts->stream.s_stop = tls_stream_stop;
+ ts->stream.s_send = tls_stream_send;
+ ts->stream.s_recv = tls_stream_recv;
+ ts->stream.s_get = tls_stream_get;
+ ts->stream.s_peer_cert = tls_stream_peer_cert;
nni_aio_init(&ts->conn_aio, tls_stream_conn_cb, ts);
@@ -200,6 +203,13 @@ tls_get_peer_cn(void *arg, void *buf, size_t *szp, nni_type t)
return (NNG_OK);
}
+static nng_err
+tls_stream_peer_cert(void *arg, nng_tls_cert **certp)
+{
+ tls_stream *ts = arg;
+ return (nni_tls_peer_cert(&ts->conn, certp));
+}
+
static const nni_option tls_stream_options[] = {
{
.o_name = NNG_OPT_TLS_VERIFIED,
diff --git a/src/supplemental/tls/wolfssl/CMakeLists.txt b/src/supplemental/tls/wolfssl/CMakeLists.txt
index 762757a3..e580860e 100644
--- a/src/supplemental/tls/wolfssl/CMakeLists.txt
+++ b/src/supplemental/tls/wolfssl/CMakeLists.txt
@@ -60,7 +60,13 @@ if (NNG_TLS_ENGINE STREQUAL "wolf")
if (NNG_WOLFSSL_HAVE_PEER_CERT)
nng_defines(NNG_WOLFSSL_HAVE_PEER_CERT)
else ()
- message(STATUS "wolfSSL configured without peer cert chain support.")
+ message(STATUS "wolfSSL configured without full certificate APIs.")
+ message(NOTICE "
+ ************************************************************
+ WolfSSL functionality for working with certificates is missing.
+ Consider rebuilding WolfSSL for full functionality.
+ ************************************************************")
+
endif ()
if (NNG_WOLFSSL_HAVE_PSK)
diff --git a/src/supplemental/tls/wolfssl/wolfssl.c b/src/supplemental/tls/wolfssl/wolfssl.c
index a18c1605..ef3f7391 100644
--- a/src/supplemental/tls/wolfssl/wolfssl.c
+++ b/src/supplemental/tls/wolfssl/wolfssl.c
@@ -43,6 +43,13 @@ struct nng_tls_engine_conn {
int auth_mode;
};
+struct nng_tls_engine_cert {
+ WOLFSSL_X509 *crt;
+ char *subject;
+ char *issuer;
+ char serial[64]; // maximum binary serial is 20 bytes
+};
+
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,
@@ -281,53 +288,47 @@ wolf_conn_verified(nng_tls_engine_conn *ec)
}
}
-static char *
-wolf_conn_peer_cn(nng_tls_engine_conn *ec)
+static nng_err
+wolf_conn_peer_cert(nng_tls_engine_conn *ec, nng_tls_engine_cert **certp)
{
#ifdef NNG_WOLFSSL_HAVE_PEER_CERT
- WOLFSSL_X509 *cert;
- char *cn;
+ nng_tls_engine_cert *cert;
- if ((cert = wolfSSL_get_peer_certificate(ec->ssl)) == NULL) {
- return (NULL);
+ WOLFSSL_X509 *wc;
+ if ((wc = wolfSSL_get_peer_certificate(ec->ssl)) == NULL) {
+ return (NNG_ENOENT);
}
- cn = wolfSSL_X509_get_subjectCN(cert);
- if (cn != NULL) {
- cn = nng_strdup(cn);
+ if ((cert = nni_zalloc(sizeof(*cert))) == NULL) {
+ wolfSSL_X509_free(wc);
+ return (NNG_ENOMEM);
}
- return (cn);
+ cert->crt = wc;
+ *certp = cert;
+ return (NNG_OK);
#else
- return (NULL);
+ NNI_ARG_UNUSED(ec);
+ NNI_ARG_UNUSED(certp);
+ return (NNG_ENOTSUP);
#endif
}
-static char **
-wolf_conn_peer_alt_names(nng_tls_engine_conn *ec)
+static char *
+wolf_conn_peer_cn(nng_tls_engine_conn *ec)
{
#ifdef NNG_WOLFSSL_HAVE_PEER_CERT
WOLFSSL_X509 *cert;
- int num = 0;
- char **names;
+ char *cn;
if ((cert = wolfSSL_get_peer_certificate(ec->ssl)) == NULL) {
return (NULL);
}
- while (wolfSSL_X509_get_next_altname(cert) != NULL) {
- num++;
- }
- if ((names = nni_zalloc(sizeof(char *) * num)) == NULL) {
- return (NULL);
- }
- if ((cert = wolfSSL_get_peer_certificate(ec->ssl)) == NULL) {
- nni_free(names, sizeof(char *) * num);
- return (NULL);
- }
- for (int i = 0; i < num; i++) {
- names[i] = wolfSSL_X509_get_next_altname(cert);
- NNI_ASSERT(names[i] != NULL);
+ cn = wolfSSL_X509_get_subjectCN(cert);
+ if (cn != NULL) {
+ cn = nng_strdup(cn);
}
- return (names);
+ return (cn);
#else
+ NNI_ARG_UNUSED(ec);
return (NULL);
#endif
}
@@ -678,6 +679,240 @@ wolf_config_version(nng_tls_engine_config *cfg, nng_tls_version min_ver,
}
static void
+wolf_cert_free(nng_tls_engine_cert *cert)
+{
+#ifdef NNG_WOLFSSL_HAVE_PEER_CERT
+ if (cert->subject != NULL) {
+ wolfSSL_Free(cert->subject);
+ }
+ if (cert->issuer != NULL) {
+ wolfSSL_Free(cert->issuer);
+ }
+ if (cert->crt != NULL) {
+ wolfSSL_X509_free(cert->crt);
+ }
+ nni_free(cert, sizeof(*cert));
+#else
+ NNI_ARG_UNUSED(cert);
+#endif
+}
+
+// In struct nng_tls_engine_cert_ops_s
+static nng_err
+wolf_cert_get_der(nng_tls_engine_cert *cert, uint8_t *buf, size_t *sz)
+{
+#ifdef NNG_WOLFSSL_HAVE_PEER_CERT
+ const uint8_t *der;
+ int derSz;
+ der = wolfSSL_X509_get_der(cert->crt, &derSz);
+ if (*sz < (size_t) derSz) {
+ *sz = (size_t) derSz;
+ return (NNG_ENOSPC);
+ }
+ *sz = (size_t) derSz;
+ memcpy(buf, der, *sz);
+ return (NNG_OK);
+#else
+ NNI_ARG_UNUSED(cert);
+ NNI_ARG_UNUSED(buf);
+ NNI_ARG_UNUSED(sz);
+ return (NNG_ENOTSUP);
+#endif
+}
+
+static nng_err
+wolf_cert_parse_der(
+ nng_tls_engine_cert **crtp, const uint8_t *der, size_t size)
+{
+#ifdef NNG_WOLFSSL_HAVE_PEER_CERT
+ WOLFSSL_X509 *x;
+ nng_tls_engine_cert *cert;
+
+ if ((cert = nni_zalloc(sizeof(*cert))) == NULL) {
+ return (NNG_ENOMEM);
+ }
+ if ((cert->crt = wolfSSL_X509_d2i(&x, der, size)) == NULL) {
+ nni_free(cert, sizeof(*cert));
+ return (NNG_ENOMEM);
+ }
+ *crtp = cert;
+ return (NNG_OK);
+#else
+ NNI_ARG_UNUSED(crtp);
+ NNI_ARG_UNUSED(der);
+ NNI_ARG_UNUSED(size);
+ return (NNG_ENOTSUP);
+#endif
+}
+
+static nng_err
+wolf_cert_parse_pem(nng_tls_engine_cert **crtp, const char *pem, size_t size)
+{
+ nng_err rv;
+ uint8_t *derBuf;
+ int derSize;
+
+ // DER files are smaller than PEM (PEM is base64 encoded and includes
+ // headers)
+ if ((derBuf = nni_alloc(size)) == NULL) {
+ return (NNG_ENOMEM);
+ }
+
+ derSize = wc_CertPemToDer(
+ (const uint8_t *) pem, size, derBuf, size, 0 /* cert type */);
+ if (derSize < 0) {
+ nni_free(derBuf, size);
+ return (NNG_ECRYPTO);
+ }
+
+ rv = wolf_cert_parse_der(crtp, derBuf, derSize);
+ nni_free(derBuf, size);
+ return (rv);
+}
+
+static nng_err
+wolf_cert_subject(nng_tls_engine_cert *cert, char **subject)
+{
+#ifdef NNG_WOLFSSL_HAVE_PEER_CERT
+ WOLFSSL_X509_NAME *xn;
+
+ if (cert->subject != NULL) {
+ *subject = cert->subject;
+ return (NNG_OK);
+ }
+
+ xn = wolfSSL_X509_get_subject_name(cert->crt);
+ if (xn == NULL) {
+ return (NNG_ENOENT);
+ }
+ cert->subject = wolfSSL_X509_NAME_oneline(xn, NULL, 0);
+ *subject = cert->subject;
+ return (NNG_OK);
+#else
+ NNI_ARG_UNUSED(cert);
+ NNI_ARG_UNUSED(subject);
+ return (NNG_ENOTSUP);
+#endif
+}
+
+static nng_err
+wolf_cert_issuer(nng_tls_engine_cert *cert, char **issuer)
+{
+#ifdef NNG_WOLFSSL_HAVE_PEER_CERT
+ WOLFSSL_X509_NAME *xn;
+
+ if (cert->issuer != NULL) {
+ *issuer = cert->issuer;
+ return (NNG_OK);
+ }
+
+ xn = wolfSSL_X509_get_issuer_name(cert->crt);
+ if (xn == NULL) {
+ return (NNG_ENOENT);
+ }
+ cert->issuer = wolfSSL_X509_NAME_oneline(xn, NULL, 0);
+ *issuer = cert->issuer;
+ return (NNG_OK);
+#else
+ NNI_ARG_UNUSED(cert);
+ NNI_ARG_UNUSED(issuer);
+ return (NNG_ENOTSUP);
+#endif
+}
+
+static nng_err
+wolf_cert_serial(nng_tls_engine_cert *cert, char **serial)
+{
+#ifdef NNG_WOLFSSL_HAVE_PEER_CERT
+ uint8_t num[20]; // max is 20 bytes per RFC
+ char *s;
+ int len;
+
+ if (cert->serial[0] != 0) {
+ *serial = cert->serial;
+ return (NNG_OK);
+ }
+
+ len = sizeof(num);
+ wolfSSL_X509_get_serial_number(cert->crt, num, &len);
+
+ s = cert->serial;
+ for (int i = 0; i < len; i++) {
+ snprintf(s, 4, "%s%02X", i > 0 ? ":" : "", num[i]);
+ s += strlen(s);
+ }
+ *serial = cert->serial;
+ return (NNG_OK);
+#else
+ NNI_ARG_UNUSED(cert);
+ NNI_ARG_UNUSED(serial);
+ return (NNG_ENOTSUP);
+#endif
+}
+
+static nng_err
+wolf_cert_subject_cn(nng_tls_engine_cert *cert, char **cn)
+{
+#ifdef NNG_WOLFSSL_HAVE_PEER_CERT
+ *cn = wolfSSL_X509_get_subjectCN(cert->crt);
+ if (*cn == NULL) {
+ return (NNG_ENOENT);
+ }
+ return (NNG_OK);
+#else
+ NNI_ARG_UNUSED(cert);
+ NNI_ARG_UNUSED(cn);
+ return (NNG_ENOTSUP);
+#endif
+}
+
+static nng_err
+wolf_cert_next_alt(nng_tls_engine_cert *cert, char **alt)
+{
+#ifdef NNG_WOLFSSL_HAVE_PEER_CERT
+ *alt = wolfSSL_X509_get_next_altname(cert->crt);
+ if (*alt == NULL) {
+ return (NNG_ENOENT);
+ }
+ return (NNG_OK);
+#else
+ NNI_ARG_UNUSED(cert);
+ NNI_ARG_UNUSED(alt);
+ return (NNG_ENOTSUP);
+#endif
+}
+
+static nng_err
+wolf_cert_not_before(nng_tls_engine_cert *cert, struct tm *tmp)
+{
+#ifdef NNG_WOLFSSL_HAVE_PEER_CERT
+ WOLFSSL_ASN1_TIME *when;
+ when = wolfSSL_X509_get_notBefore(cert->crt);
+ wolfSSL_ASN1_TIME_to_tm(when, tmp);
+ return (NNG_OK);
+#else
+ NNI_ARG_UNUSED(cert);
+ NNI_ARG_UNUSED(tmp);
+ return (NNG_ENOTSUP);
+#endif
+}
+
+static nng_err
+wolf_cert_not_after(nng_tls_engine_cert *cert, struct tm *tmp)
+{
+#ifdef NNG_WOLFSSL_HAVE_PEER_CERT
+ WOLFSSL_ASN1_TIME *when;
+ when = wolfSSL_X509_get_notAfter(cert->crt);
+ wolfSSL_ASN1_TIME_to_tm(when, tmp);
+ return (NNG_OK);
+#else
+ NNI_ARG_UNUSED(cert);
+ NNI_ARG_UNUSED(tmp);
+ return (NNG_ENOTSUP);
+#endif
+}
+
+static void
wolf_logging_cb(const int level, const char *msg)
{
switch (level) {
@@ -742,22 +977,37 @@ static nng_tls_engine_config_ops wolf_config_ops = {
};
static nng_tls_engine_conn_ops wolf_conn_ops = {
- .size = sizeof(nng_tls_engine_conn),
- .init = wolf_conn_init,
- .fini = wolf_conn_fini,
- .close = wolf_conn_close,
- .recv = wolf_conn_recv,
- .send = wolf_conn_send,
- .handshake = wolf_conn_handshake,
- .verified = wolf_conn_verified,
- .peer_cn = wolf_conn_peer_cn,
- .peer_alt_names = wolf_conn_peer_alt_names,
+ .size = sizeof(nng_tls_engine_conn),
+ .init = wolf_conn_init,
+ .fini = wolf_conn_fini,
+ .close = wolf_conn_close,
+ .recv = wolf_conn_recv,
+ .send = wolf_conn_send,
+ .handshake = wolf_conn_handshake,
+ .verified = wolf_conn_verified,
+ .peer_cn = wolf_conn_peer_cn,
+ .peer_cert = wolf_conn_peer_cert,
+};
+
+static nng_tls_engine_cert_ops wolf_cert_ops = {
+ .fini = wolf_cert_free,
+ .get_der = wolf_cert_get_der,
+ .parse_der = wolf_cert_parse_der,
+ .parse_pem = wolf_cert_parse_pem,
+ .subject = wolf_cert_subject,
+ .issuer = wolf_cert_issuer,
+ .serial_number = wolf_cert_serial,
+ .subject_cn = wolf_cert_subject_cn,
+ .next_alt_name = wolf_cert_next_alt,
+ .not_before = wolf_cert_not_before,
+ .not_after = wolf_cert_not_after,
};
nng_tls_engine nng_tls_engine_ops = {
.version = NNG_TLS_ENGINE_VERSION,
.config_ops = &wolf_config_ops,
.conn_ops = &wolf_conn_ops,
+ .cert_ops = &wolf_cert_ops,
.name = "wolf",
.description = "wolfSSL " LIBWOLFSSL_VERSION_STRING,
.init = tls_engine_init,
diff --git a/src/supplemental/websocket/websocket.c b/src/supplemental/websocket/websocket.c
index 597efb76..15078704 100644
--- a/src/supplemental/websocket/websocket.c
+++ b/src/supplemental/websocket/websocket.c
@@ -186,6 +186,7 @@ static void ws_str_close(void *);
static void ws_str_send(void *, nng_aio *);
static void ws_str_recv(void *, nng_aio *);
static nng_err ws_str_get(void *, const char *, void *, size_t *, nni_type);
+static nng_err ws_str_peer_cert(void *, nng_tls_cert **);
static void ws_listener_close(void *);
static void ws_listener_free(void *);
@@ -1388,12 +1389,13 @@ ws_init(nni_ws **wsp)
nni_aio_set_timeout(&ws->closeaio, 100);
nni_aio_set_timeout(&ws->httpaio, 2000);
- ws->ops.s_close = ws_str_close;
- ws->ops.s_free = ws_str_free;
- ws->ops.s_stop = ws_stop;
- ws->ops.s_send = ws_str_send;
- ws->ops.s_recv = ws_str_recv;
- ws->ops.s_get = ws_str_get;
+ ws->ops.s_close = ws_str_close;
+ ws->ops.s_free = ws_str_free;
+ ws->ops.s_stop = ws_stop;
+ ws->ops.s_send = ws_str_send;
+ ws->ops.s_recv = ws_str_recv;
+ ws->ops.s_get = ws_str_get;
+ ws->ops.s_peer_cert = ws_str_peer_cert;
ws->fragsize = 1 << 20; // we won't send a frame larger than this
*wsp = ws;
@@ -2754,3 +2756,17 @@ ws_str_get(void *arg, const char *nm, void *buf, size_t *szp, nni_type t)
}
return (rv);
}
+
+static nng_err
+ws_str_peer_cert(void *arg, nng_tls_cert **certp)
+{
+ nni_ws *ws = arg;
+
+ nni_mtx_lock(&ws->mtx);
+ if (ws->closed) {
+ nni_mtx_unlock(&ws->mtx);
+ return (NNG_ECLOSED);
+ }
+ nni_mtx_unlock(&ws->mtx);
+ return (nni_http_conn_peer_cert(ws->http, certp));
+}