aboutsummaryrefslogtreecommitdiff
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
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.
-rw-r--r--docs/man/nng_tls.7.adoc1
-rw-r--r--docs/man/nng_tls_options.5.adoc6
-rw-r--r--docs/man/nng_ws.7.adoc5
-rw-r--r--docs/ref/api/http.md20
-rw-r--r--docs/ref/migrate/nng1.md9
-rw-r--r--include/nng/http.h4
-rw-r--r--include/nng/nng.h38
-rw-r--r--src/core/defs.h2
-rw-r--r--src/core/pipe.c9
-rw-r--r--src/core/pipe.h3
-rw-r--r--src/core/sockfd.h2
-rw-r--r--src/core/stream.c20
-rw-r--r--src/core/stream.h1
-rw-r--r--src/core/tcp.h2
-rw-r--r--src/nng.c16
-rw-r--r--src/sp/transport.h4
-rw-r--r--src/sp/transport/dtls/dtls.c49
-rw-r--r--src/sp/transport/dtls/dtls_tran_test.c81
-rw-r--r--src/sp/transport/tcp/tcp.c2
-rw-r--r--src/sp/transport/tcp/tcp_test.c6
-rw-r--r--src/sp/transport/tls/tls.c27
-rw-r--r--src/sp/transport/tls/tls_tran_test.c30
-rw-r--r--src/sp/transport/ws/websocket.c32
-rw-r--r--src/sp/transport/ws/wss_test.c76
-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
37 files changed, 1169 insertions, 189 deletions
diff --git a/docs/man/nng_tls.7.adoc b/docs/man/nng_tls.7.adoc
index a6ad395c..1430e537 100644
--- a/docs/man/nng_tls.7.adoc
+++ b/docs/man/nng_tls.7.adoc
@@ -107,7 +107,6 @@ Note that setting these must be done before the transport is started.
* xref:nng_tcp_options.5.adoc#NNG_OPT_TCP_NODELAY[`NNG_OPT_TCP_NODELAY`]
* xref:nng_tls_options.5.adoc#NNG_OPT_TLS_VERIFIED[`NNG_OPT_TLS_VERIFIED_`]
* xref:nng_tls_options.5.adoc#NNG_OPT_TLS_PEER_CN[`NNG_OPT_TLS_PEER_CN`]
-* xref:nng_tls_options.5.adoc#NNG_OPT_TLS_PEER_ALT_NAMES[`NNG_OPT_TLS_PEER_ALT_NAMES`]
* xref:nng_options.5.adoc#NNG_OPT_URL[`NNG_OPT_URL`]
== SEE ALSO
diff --git a/docs/man/nng_tls_options.5.adoc b/docs/man/nng_tls_options.5.adoc
index 5921246f..f7cce90a 100644
--- a/docs/man/nng_tls_options.5.adoc
+++ b/docs/man/nng_tls_options.5.adoc
@@ -22,7 +22,6 @@ nng_tls_options - TLS-specific options
#define NNG_OPT_TLS_VERIFIED "tls-verified"
#define NNG_OPT_TLS_PEER_CN "tls-peer-cn"
-#define NNG_OPT_TLS_PEER_ALT_NAMES "tls-peer-alt-names"
----
== DESCRIPTION
@@ -66,11 +65,6 @@ May return incorrect results if peer authentication is disabled.
This read-only option returns the common name of the peer certificate.
May return incorrect results if peer authentication is disabled.
-[[NNG_OPT_TLS_PEER_ALT_NAMES]]((`NNG_OPT_TLS_PEER_ALT_NAMES`))::
-(string)
-This read-only option returns string list with the subject alternative names of the
-peer certificate. May return incorrect results if peer authentication is disabled.
-
=== Inherited Options
Generally, the following option values are also available for TLS objects,
diff --git a/docs/man/nng_ws.7.adoc b/docs/man/nng_ws.7.adoc
index 49783920..f7034987 100644
--- a/docs/man/nng_ws.7.adoc
+++ b/docs/man/nng_ws.7.adoc
@@ -159,11 +159,6 @@ May return incorrect results if peer authentication is disabled.
(string) This read-only option returns the common name of the peer certificate.
May return incorrect results if peer authentication is disabled.
-`NNG_OPT_TLS_PEER_ALT_NAMES`::
-(string list) returns string list with the subject alternative names of the
-peer certificate. May return incorrect results if peer authentication
-is disabled.
-
// We should also look at a hook mechanism for listeners. Probably this could
// look like NNG_OPT_WS_LISTEN_HOOK_FUNC which would take a function pointer
// along the lines of int hook(void *, char *req_headers, char **res_headers),
diff --git a/docs/ref/api/http.md b/docs/ref/api/http.md
index 04c3ee4a..bbe33c24 100644
--- a/docs/ref/api/http.md
+++ b/docs/ref/api/http.md
@@ -349,6 +349,26 @@ This function is most useful when called from a handler function.
> This function is intended to facilitate uses cases that involve changing the protocol from HTTP, such as WebSocket.
> Most applications will never need to use this function.
+### Obtaining TLS Connection Details
+
+```c
+nng_err nng_http_peer_cert(nng_http_conn *conn, nng_tls_cert **certp);
+```
+
+TODO: We need to document the cert API.
+
+The {{i:`nng_http_peer_cert`}} function will obtain the TLS certificate object for the peer, if one is available.
+This can then be used for additional authentication or identity specific logic.
+
+The certificate must be released with [`nng_tls_cert_free`] when no longer in use.
+See [`nng_tls_cert`] for more information about working with TLS certificates.
+
+> [!NOTE]
+> While it should be obvious that this function is only available when using HTTPS,
+> it also requires that peer authentication is in use, and may require that the underlying
+> TLS engine support peer certificate colleciton. (Some minimal configurations elide this
+> to save space in embedded environments.)
+
## Client API
The NNG client API consists of an API for creating connections, and an API for performing
diff --git a/docs/ref/migrate/nng1.md b/docs/ref/migrate/nng1.md
index 5b85c41d..df062efd 100644
--- a/docs/ref/migrate/nng1.md
+++ b/docs/ref/migrate/nng1.md
@@ -156,6 +156,15 @@ The ability to configure multiple keys and certificates for a given TLS configur
The intended purpose was to support alternative cryptographic algorithms, but this is not necessary, was never
used, and was error prone.
+## TLS Peer Certificate APIs Replaced
+
+The `NNG_OPT_TLS_PEER_CN` and `NNG_OPT_TLS_PEER_ALT_NAMES` properties have been removed.
+They are replaced with functions like [`nng_pipe_peer_cert`], [`nng_stream_peer_cert`],
+and [`nng_http_peer_cert`] which return a new `nng_tls_cert` object.
+
+This object supports methods to get additional information about the certificate, as well
+as to obtain the raw DER content so that it can be imported for use in other APIs.
+
## Support for Local Addresses in Dial URLs Removed
NNG 1.x had an undocumented ability to specify the local address to bind
diff --git a/include/nng/http.h b/include/nng/http.h
index 08099cbe..f8bfb679 100644
--- a/include/nng/http.h
+++ b/include/nng/http.h
@@ -207,6 +207,10 @@ NNG_DECL void nng_http_set_body(nng_http *, void *, size_t);
// makes a local copy. It can fail due to NNG_ENOMEM.
NNG_DECL nng_err nng_http_copy_body(nng_http *, const void *, size_t);
+// nng_http_peer_cert returns the HTTP peer cert, if the one is available.
+// Only available for HTTPS connections.
+NNG_DECL nng_err nng_http_peer_cert(nng_http *, nng_tls_cert **);
+
// nng_http_handler is a handler used on the server side to handle HTTP
// requests coming into a specific URL.
//
diff --git a/include/nng/nng.h b/include/nng/nng.h
index fb61b0de..69c5ee89 100644
--- a/include/nng/nng.h
+++ b/include/nng/nng.h
@@ -24,6 +24,7 @@ extern "C" {
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
+#include <time.h>
// NNG_DECL is used on declarations to deal with scope.
// For building Windows DLLs, it should be the appropriate __declspec().
@@ -176,6 +177,10 @@ typedef struct nng_url nng_url;
// and so forth. A TLS configuration cannot be changed once it is in use.
typedef struct nng_tls_config nng_tls_config;
+// This is a representation of X.509 certificate as used in TLS transports.
+// Internal details are opaque.
+typedef struct nng_tls_cert_s nng_tls_cert;
+
// Initializers.
// clang-format off
#define NNG_PIPE_INITIALIZER { 0 }
@@ -767,6 +772,7 @@ NNG_DECL nng_err nng_pipe_get_ms(nng_pipe, const char *, nng_duration *);
NNG_DECL nng_err nng_pipe_get_size(nng_pipe, const char *, size_t *);
NNG_DECL nng_err nng_pipe_get_string(nng_pipe, const char *, char **);
NNG_DECL nng_err nng_pipe_get_addr(nng_pipe, const char *, nng_sockaddr *);
+NNG_DECL nng_err nng_pipe_peer_cert(nng_pipe, nng_tls_cert **);
NNG_DECL nng_err nng_pipe_close(nng_pipe);
NNG_DECL int nng_pipe_id(nng_pipe);
@@ -803,13 +809,6 @@ NNG_DECL nng_listener nng_pipe_listener(nng_pipe);
// peer authentication is disabled with `NNG_TLS_AUTH_MODE_NONE`.
#define NNG_OPT_TLS_PEER_CN "tls-peer-cn"
-// NNG_OPT_TLS_PEER_ALT_NAMES returns string list with the
-// subject alternative names of the peer certificate. Typically this is
-// read-only and only available for pipes. This option may return
-// incorrect results if peer authentication is disabled with
-// `NNG_TLS_AUTH_MODE_NONE`.
-#define NNG_OPT_TLS_PEER_ALT_NAMES "tls-peer-alt-names"
-
// TCP options. These may be supported on various transports that use
// TCP underneath such as TLS, or not.
@@ -1145,6 +1144,7 @@ NNG_DECL nng_err nng_stream_get_uint64(nng_stream *, const char *, uint64_t *);
NNG_DECL nng_err nng_stream_get_string(nng_stream *, const char *, char **);
NNG_DECL nng_err nng_stream_get_addr(
nng_stream *, const char *, nng_sockaddr *);
+NNG_DECL nng_err nng_stream_peer_cert(nng_stream *, nng_tls_cert **);
NNG_DECL nng_err nng_stream_dialer_alloc(nng_stream_dialer **, const char *);
NNG_DECL nng_err nng_stream_dialer_alloc_url(
@@ -1624,6 +1624,30 @@ NNG_DECL const char *nng_tls_engine_description(void);
// nng_tls_engine_fips_mode returns true if the engine is in FIPS 140 mode.
NNG_DECL bool nng_tls_engine_fips_mode(void);
+// nng_tls_cert_parse parses PEM content to obtain an object suitable for
+// use with TLS APIs.
+NNG_DECL nng_err nng_tls_cert_parse_pem(nng_tls_cert **, const char *, size_t);
+
+// nng_tls_cert_parse_der parses a DER (distinguished encoding rules) format
+// certificate.
+NNG_DECL nng_err nng_tls_cert_parse_der(
+ nng_tls_cert **, const uint8_t *, size_t);
+
+// nng_tls_cert_der extracts the certificate as DER content. This can be
+// useful for importing into other APIs such as OpenSSL or mbedTLS directly.
+NNG_DECL void nng_tls_cert_der(nng_tls_cert *cert, uint8_t *, size_t *);
+
+// nng_tls_cert_free releases the certificate from memory.
+NNG_DECL void nng_tls_cert_free(nng_tls_cert *);
+
+NNG_DECL nng_err nng_tls_cert_subject(nng_tls_cert *, char **);
+NNG_DECL nng_err nng_tls_cert_issuer(nng_tls_cert *, char **);
+NNG_DECL nng_err nng_tls_cert_serial_number(nng_tls_cert *, char **);
+NNG_DECL nng_err nng_tls_cert_subject_cn(nng_tls_cert *, char **);
+NNG_DECL nng_err nng_tls_cert_next_alt(nng_tls_cert *, char **);
+NNG_DECL nng_err nng_tls_cert_not_before(nng_tls_cert *, struct tm *);
+NNG_DECL nng_err nng_tls_cert_not_after(nng_tls_cert *, struct tm *);
+
// Public ID map support.
typedef struct nng_id_map_s nng_id_map;
diff --git a/src/core/defs.h b/src/core/defs.h
index 64a43a04..23c17d8b 100644
--- a/src/core/defs.h
+++ b/src/core/defs.h
@@ -14,7 +14,7 @@
#include <stdbool.h>
#include <stdint.h>
-#include <nng/nng.h>
+#include "nng/nng.h"
// C compilers may get unhappy when named arguments are not used. While
// there are things like __attribute__((unused)) which are arguably
diff --git a/src/core/pipe.c b/src/core/pipe.c
index bfc272b3..db2c4d41 100644
--- a/src/core/pipe.c
+++ b/src/core/pipe.c
@@ -430,3 +430,12 @@ nni_pipe_peer_addr(nni_pipe *p, char buf[NNG_MAXADDRSTRLEN])
nng_str_sockaddr(&sa, buf, NNG_MAXADDRSTRLEN);
return (buf);
}
+
+nng_err
+nni_pipe_peer_cert(nni_pipe *p, nng_tls_cert **certp)
+{
+ if (p->p_tran_ops.p_peer_cert == NULL) {
+ return (NNG_ENOTSUP);
+ }
+ return (p->p_tran_ops.p_peer_cert(p->p_tran_data, certp));
+}
diff --git a/src/core/pipe.h b/src/core/pipe.h
index b9a13a68..4ed61660 100644
--- a/src/core/pipe.h
+++ b/src/core/pipe.h
@@ -39,6 +39,9 @@ extern uint16_t nni_pipe_peer(nni_pipe *);
extern nng_err nni_pipe_getopt(
nni_pipe *, const char *, void *, size_t *, nni_opt_type);
+// nni_pipe_peer_cert obtains the peer TLS certificate, if available.
+extern nng_err nni_pipe_peer_cert(nni_pipe *, nng_tls_cert **);
+
// nni_pipe_find finds a pipe given its ID. It places a hold on the
// pipe, which must be released by the caller when it is done.
extern nng_err nni_pipe_find(nni_pipe **, uint32_t);
diff --git a/src/core/sockfd.h b/src/core/sockfd.h
index 8985c009..3b39ee60 100644
--- a/src/core/sockfd.h
+++ b/src/core/sockfd.h
@@ -10,7 +10,7 @@
#ifndef CORE_FDC_H
#define CORE_FDC_H
-#include "core/nng_impl.h"
+#include "nng/nng.h"
// the nni_sfd_conn struct is provided by platform code to wrap
// an arbitrary byte stream file descriptor (UNIX) or handle (Windows)
diff --git a/src/core/stream.c b/src/core/stream.c
index e0da3582..61a8a3ba 100644
--- a/src/core/stream.c
+++ b/src/core/stream.c
@@ -12,12 +12,13 @@
#include <string.h>
-#include "core/nng_impl.h"
+#include "nng_impl.h"
-#include "core/sockfd.h"
-#include "core/tcp.h"
-#include "supplemental/tls/tls_api.h"
-#include "supplemental/websocket/websocket.h"
+#include "sockfd.h"
+#include "tcp.h"
+
+#include "../supplemental/tls/tls_api.h"
+#include "../supplemental/websocket/websocket.h"
static struct {
const char *scheme;
@@ -385,6 +386,15 @@ nng_stream_get_addr(nng_stream *s, const char *n, nng_sockaddr *v)
}
nng_err
+nng_stream_peer_cert(nng_stream *s, nng_tls_cert **certp)
+{
+ if (s->s_peer_cert == NULL) {
+ return (NNG_ENOTSUP);
+ }
+ return (s->s_peer_cert(s, certp));
+}
+
+nng_err
nng_stream_dialer_get_int(nng_stream_dialer *d, const char *n, int *v)
{
return (nni_stream_dialer_get(d, n, v, NULL, NNI_TYPE_INT32));
diff --git a/src/core/stream.h b/src/core/stream.h
index 83c121cd..a9a17ec1 100644
--- a/src/core/stream.h
+++ b/src/core/stream.h
@@ -50,6 +50,7 @@ struct nng_stream {
void (*s_send)(void *, nng_aio *);
nng_err (*s_get)(void *, const char *, void *, size_t *, nni_type);
nng_err (*s_set)(void *, const char *, const void *, size_t, nni_type);
+ nng_err (*s_peer_cert)(void *, nng_tls_cert **);
};
// Dialer implementation. Stream dialers create streams.
diff --git a/src/core/tcp.h b/src/core/tcp.h
index cc41dfac..58cac45a 100644
--- a/src/core/tcp.h
+++ b/src/core/tcp.h
@@ -10,7 +10,7 @@
#ifndef CORE_TCP_H
#define CORE_TCP_H
-#include "core/nng_impl.h"
+#include "nng/nng.h"
// These are interfaces we use for TCP internally. These are not exposed
// to the public API.
diff --git a/src/nng.c b/src/nng.c
index 9b155599..39da87d4 100644
--- a/src/nng.c
+++ b/src/nng.c
@@ -670,7 +670,7 @@ void
nng_dialer_start_aio(nng_dialer did, int flags, nng_aio *aio)
{
nni_dialer *d;
- int rv;
+ int rv;
if (aio != NULL) {
nni_aio_reset(aio);
@@ -1375,6 +1375,20 @@ nng_pipe_get_addr(nng_pipe id, const char *n, nng_sockaddr *v)
return (pipe_get(id, n, v, NULL, NNI_TYPE_SOCKADDR));
}
+nng_err
+nng_pipe_peer_cert(nng_pipe p, nng_tls_cert **certp)
+{
+ nng_err rv;
+ nni_pipe *pipe;
+
+ if ((rv = nni_pipe_find(&pipe, p.id)) != 0) {
+ return (rv);
+ }
+ rv = nni_pipe_peer_cert(pipe, certp);
+ nni_pipe_rele(pipe);
+ return (rv);
+}
+
nng_socket
nng_pipe_socket(nng_pipe p)
{
diff --git a/src/sp/transport.h b/src/sp/transport.h
index 36ade021..11a9ac01 100644
--- a/src/sp/transport.h
+++ b/src/sp/transport.h
@@ -188,6 +188,10 @@ struct nni_sp_pipe_ops {
// p_getopt is used to obtain an option. Pipes don't implement
// option setting.
nng_err (*p_getopt)(void *, const char *, void *, size_t *, nni_type);
+
+ // p_peer_cert is used to obtain a peer cert for transports that
+ // implement TLS.
+ nng_err (*p_peer_cert)(void *, nng_tls_cert **);
};
// Transport implementation details. Transports must implement the
diff --git a/src/sp/transport/dtls/dtls.c b/src/sp/transport/dtls/dtls.c
index e95560de..400709a1 100644
--- a/src/sp/transport/dtls/dtls.c
+++ b/src/sp/transport/dtls/dtls.c
@@ -6,18 +6,18 @@
// found online at https://opensource.org/licenses/MIT.
//
-#include "core/aio.h"
-#include "core/defs.h"
-#include "core/idhash.h"
-#include "core/message.h"
-#include "core/nng_impl.h"
-#include "core/options.h"
-#include "core/pipe.h"
-#include "core/platform.h"
-#include "core/socket.h"
-#include "core/stats.h"
+#include "../../../core/aio.h"
+#include "../../../core/defs.h"
+#include "../../../core/idhash.h"
+#include "../../../core/message.h"
+#include "../../../core/nng_impl.h"
+#include "../../../core/options.h"
+#include "../../../core/pipe.h"
+#include "../../../core/platform.h"
+#include "../../../core/socket.h"
+#include "../../../core/stats.h"
+#include "../../../supplemental/tls/tls_common.h"
#include "nng/nng.h"
-#include "supplemental/tls/tls_common.h"
#include <string.h>
@@ -1070,6 +1070,14 @@ dtls_pipe_getopt(
return (nni_getopt(dtls_pipe_options, name, p, buf, szp, t));
}
+static nng_err
+dtls_pipe_peer_cert(void *arg, nng_tls_cert **certp)
+{
+ dtls_pipe *p = arg;
+
+ return (nni_tls_peer_cert(&p->tls, certp));
+}
+
static void
dtls_ep_fini(void *arg)
{
@@ -1676,15 +1684,16 @@ dtls_ep_accept(void *arg, nni_aio *aio)
}
static nni_sp_pipe_ops dtls_pipe_ops = {
- .p_size = dtls_pipe_size,
- .p_init = dtls_pipe_init,
- .p_fini = dtls_pipe_fini,
- .p_stop = dtls_pipe_stop,
- .p_send = dtls_pipe_send,
- .p_recv = dtls_pipe_recv,
- .p_close = dtls_pipe_close,
- .p_peer = dtls_pipe_peer,
- .p_getopt = dtls_pipe_getopt,
+ .p_size = dtls_pipe_size,
+ .p_init = dtls_pipe_init,
+ .p_fini = dtls_pipe_fini,
+ .p_stop = dtls_pipe_stop,
+ .p_send = dtls_pipe_send,
+ .p_recv = dtls_pipe_recv,
+ .p_close = dtls_pipe_close,
+ .p_peer = dtls_pipe_peer,
+ .p_getopt = dtls_pipe_getopt,
+ .p_peer_cert = dtls_pipe_peer_cert,
};
static const nni_option dtls_ep_opts[] = {
diff --git a/src/sp/transport/dtls/dtls_tran_test.c b/src/sp/transport/dtls/dtls_tran_test.c
index 774c967a..7e604f30 100644
--- a/src/sp/transport/dtls/dtls_tran_test.c
+++ b/src/sp/transport/dtls/dtls_tran_test.c
@@ -513,6 +513,86 @@ test_dtls_psk(void)
#endif
}
+void
+test_dtls_pipe_details(void)
+{
+ nng_socket s1;
+ nng_socket s2;
+ nng_tls_config *c1, *c2;
+ nng_sockaddr sa;
+ nng_listener l;
+ nng_dialer d;
+ nng_msg *msg;
+ nng_pipe p;
+ const nng_url *url;
+
+ 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));
+ NUTS_PASS(nng_tls_config_ca_chain(c1, nuts_ecdsa_server_crt, NULL));
+ NUTS_PASS(nng_tls_config_ca_chain(c2, nuts_ecdsa_server_crt, NULL));
+ NUTS_PASS(nng_listener_create(&l, s1, "dtls://127.0.0.1:0"));
+ NUTS_PASS(nng_listener_set_tls(l, c1));
+ NUTS_PASS(nng_listener_start(l, 0));
+ NUTS_PASS(nng_listener_get_url(l, &url));
+ NUTS_MATCH(nng_url_scheme(url), "dtls");
+ NUTS_PASS(nng_listener_get_addr(l, NNG_OPT_LOCADDR, &sa));
+ NUTS_TRUE(sa.s_in.sa_family == NNG_AF_INET);
+ NUTS_TRUE(sa.s_in.sa_port != 0);
+ NUTS_TRUE(sa.s_in.sa_addr = nuts_be32(0x7f000001));
+ NUTS_PASS(nng_dialer_create_url(&d, s2, url));
+ NUTS_PASS(nng_dialer_set_tls(d, c2));
+ NUTS_PASS(nng_dialer_start(d, 0));
+ nng_msleep(50);
+ NUTS_SEND(s1, "text");
+ NUTS_PASS(nng_recvmsg(s2, &msg, 0));
+ p = nng_msg_get_pipe(msg);
+ NUTS_TRUE(nng_pipe_id(p) >= 0);
+#if !defined(NNG_TLS_ENGINE_WOLFSSL) || defined(NNG_WOLFSSL_HAVE_PEER_CERT)
+ // TOOD: maybe implement this -- although I think we want to move away
+ // from it.
+ //
+ // char *cn; NUTS_PASS(nng_pipe_get_string(p,
+ // NNG_OPT_TLS_PEER_CN, &cn)); NUTS_ASSERT(cn != NULL); NUTS_MATCH(cn,
+ // "127.0.0.1"); nng_strfree(cn);
+
+ nng_tls_cert *cert;
+ char *name;
+ NUTS_PASS(nng_pipe_peer_cert(p, &cert));
+ NUTS_PASS(nng_tls_cert_subject(cert, &name));
+ NUTS_ASSERT(name != NULL);
+ nng_log_debug(NULL, "SUBJECT: %s", name);
+ NUTS_PASS(nng_tls_cert_issuer(cert, &name));
+ NUTS_ASSERT(name != NULL);
+ nng_log_debug(NULL, "ISSUER: %s", name);
+ NUTS_PASS(nng_tls_cert_serial_number(cert, &name));
+ NUTS_ASSERT(name != NULL);
+ nng_log_debug(NULL, "SERIAL: %s", name);
+ NUTS_PASS(nng_tls_cert_subject_cn(cert, &name));
+ NUTS_MATCH(name, "127.0.0.1");
+ NUTS_PASS(nng_tls_cert_next_alt(cert, &name));
+ nng_log_debug(NULL, "FIRST ALT: %s", name);
+ NUTS_MATCH(name, "localhost");
+ NUTS_FAIL(nng_tls_cert_next_alt(cert, &name), NNG_ENOENT);
+ struct tm when;
+ NUTS_PASS(nng_tls_cert_not_before(cert, &when));
+ nng_log_debug(NULL, "BEGINS: %s", asctime(&when));
+ NUTS_PASS(nng_tls_cert_not_after(cert, &when));
+ nng_log_debug(NULL, "EXPIRES: %s", asctime(&when));
+
+ nng_tls_cert_free(cert);
+#endif
+ nng_msg_free(msg);
+ NUTS_CLOSE(s2);
+ NUTS_CLOSE(s1);
+ nng_tls_config_free(c1);
+ nng_tls_config_free(c2);
+}
+
NUTS_TESTS = {
{ "dtls port zero bind", test_dtls_port_zero_bind },
@@ -525,5 +605,6 @@ NUTS_TESTS = {
{ "dtls pre-shared key", test_dtls_psk },
{ "dtls bad cert mutual", test_dtls_bad_cert_mutual },
{ "dtls cert mutual", test_dtls_cert_mutual },
+ { "dtls pipe details", test_dtls_pipe_details },
{ NULL, NULL },
};
diff --git a/src/sp/transport/tcp/tcp.c b/src/sp/transport/tcp/tcp.c
index 096d2e24..c2e9093b 100644
--- a/src/sp/transport/tcp/tcp.c
+++ b/src/sp/transport/tcp/tcp.c
@@ -12,7 +12,7 @@
#include <stdlib.h>
#include <string.h>
-#include "core/nng_impl.h"
+#include "../../../core/nng_impl.h"
#include "nng/nng.h"
// TCP transport. Platform specific TCP operations must be
diff --git a/src/sp/transport/tcp/tcp_test.c b/src/sp/transport/tcp/tcp_test.c
index a7ff5e96..31379db6 100644
--- a/src/sp/transport/tcp/tcp_test.c
+++ b/src/sp/transport/tcp/tcp_test.c
@@ -11,7 +11,8 @@
//
#include "nng/nng.h"
-#include <nuts.h>
+
+#include "../../../testing/nuts.h"
// TCP tests.
@@ -231,6 +232,9 @@ check_props_v4(nng_msg *msg)
NUTS_PASS(nng_pipe_get_bool(p, NNG_OPT_TCP_NODELAY, &b));
NUTS_TRUE(b); // default
+
+ nng_tls_cert *cert;
+ NUTS_FAIL(nng_pipe_peer_cert(p, &cert), NNG_ENOTSUP);
}
void
diff --git a/src/sp/transport/tls/tls.c b/src/sp/transport/tls/tls.c
index 0bd4c284..528b05ec 100644
--- a/src/sp/transport/tls/tls.c
+++ b/src/sp/transport/tls/tls.c
@@ -952,6 +952,14 @@ tlstran_pipe_getopt(
return (rv);
}
+static nng_err
+tlstran_pipe_peer_cert(void *arg, nng_tls_cert **certp)
+{
+ tlstran_pipe *p = arg;
+
+ return (nng_stream_peer_cert(p->tls, certp));
+}
+
static size_t
tlstran_pipe_size(void)
{
@@ -959,15 +967,16 @@ tlstran_pipe_size(void)
}
static nni_sp_pipe_ops tlstran_pipe_ops = {
- .p_size = tlstran_pipe_size,
- .p_init = tlstran_pipe_init,
- .p_fini = tlstran_pipe_fini,
- .p_stop = tlstran_pipe_stop,
- .p_send = tlstran_pipe_send,
- .p_recv = tlstran_pipe_recv,
- .p_close = tlstran_pipe_close,
- .p_peer = tlstran_pipe_peer,
- .p_getopt = tlstran_pipe_getopt,
+ .p_size = tlstran_pipe_size,
+ .p_init = tlstran_pipe_init,
+ .p_fini = tlstran_pipe_fini,
+ .p_stop = tlstran_pipe_stop,
+ .p_send = tlstran_pipe_send,
+ .p_recv = tlstran_pipe_recv,
+ .p_close = tlstran_pipe_close,
+ .p_peer = tlstran_pipe_peer,
+ .p_getopt = tlstran_pipe_getopt,
+ .p_peer_cert = tlstran_pipe_peer_cert,
};
static nni_option tlstran_ep_options[] = {
diff --git a/src/sp/transport/tls/tls_tran_test.c b/src/sp/transport/tls/tls_tran_test.c
index 73c299c8..1b69c65f 100644
--- a/src/sp/transport/tls/tls_tran_test.c
+++ b/src/sp/transport/tls/tls_tran_test.c
@@ -227,11 +227,31 @@ test_tls_pipe_details(void)
p = nng_msg_get_pipe(msg);
NUTS_TRUE(nng_pipe_id(p) >= 0);
#if !defined(NNG_TLS_ENGINE_WOLFSSL) || defined(NNG_WOLFSSL_HAVE_PEER_CERT)
- char *cn;
- NUTS_PASS(nng_pipe_get_string(p, NNG_OPT_TLS_PEER_CN, &cn));
- NUTS_ASSERT(cn != NULL);
- NUTS_MATCH(cn, "127.0.0.1");
- nng_strfree(cn);
+ nng_tls_cert *cert;
+ char *name;
+ NUTS_PASS(nng_pipe_peer_cert(p, &cert));
+ NUTS_PASS(nng_tls_cert_subject(cert, &name));
+ NUTS_ASSERT(name != NULL);
+ nng_log_debug(NULL, "SUBJECT: %s", name);
+ NUTS_PASS(nng_tls_cert_issuer(cert, &name));
+ NUTS_ASSERT(name != NULL);
+ nng_log_debug(NULL, "ISSUER: %s", name);
+ NUTS_PASS(nng_tls_cert_serial_number(cert, &name));
+ NUTS_ASSERT(name != NULL);
+ nng_log_debug(NULL, "SERIAL: %s", name);
+ NUTS_PASS(nng_tls_cert_subject_cn(cert, &name));
+ NUTS_MATCH(name, "127.0.0.1");
+ NUTS_PASS(nng_tls_cert_next_alt(cert, &name));
+ nng_log_debug(NULL, "FIRST ALT: %s", name);
+ NUTS_MATCH(name, "localhost");
+ NUTS_FAIL(nng_tls_cert_next_alt(cert, &name), NNG_ENOENT);
+ struct tm when;
+ NUTS_PASS(nng_tls_cert_not_before(cert, &when));
+ nng_log_debug(NULL, "BEGINS: %s", asctime(&when));
+ NUTS_PASS(nng_tls_cert_not_after(cert, &when));
+ nng_log_debug(NULL, "EXPIRES: %s", asctime(&when));
+
+ nng_tls_cert_free(cert);
#endif
nng_msg_free(msg);
NUTS_CLOSE(s2);
diff --git a/src/sp/transport/ws/websocket.c b/src/sp/transport/ws/websocket.c
index 515f7b65..f9c13afe 100644
--- a/src/sp/transport/ws/websocket.c
+++ b/src/sp/transport/ws/websocket.c
@@ -13,8 +13,9 @@
#include <stdio.h>
#include <string.h>
-#include "core/nng_impl.h"
-#include "supplemental/websocket/websocket.h"
+#include "../../../core/nng_impl.h"
+#include "../../../supplemental/websocket/websocket.h"
+#include "nng/nng.h"
typedef struct ws_dialer ws_dialer;
typedef struct ws_listener ws_listener;
@@ -328,6 +329,14 @@ wstran_pipe_getopt(
return (rv);
}
+static nng_err
+wstran_pipe_peer_cert(void *arg, nng_tls_cert **certp)
+{
+ ws_pipe *p = arg;
+
+ return (nng_stream_peer_cert(p->ws, certp));
+}
+
static size_t
wstran_pipe_size(void)
{
@@ -335,15 +344,16 @@ wstran_pipe_size(void)
}
static nni_sp_pipe_ops ws_pipe_ops = {
- .p_size = wstran_pipe_size,
- .p_init = wstran_pipe_init,
- .p_fini = wstran_pipe_fini,
- .p_stop = wstran_pipe_stop,
- .p_send = wstran_pipe_send,
- .p_recv = wstran_pipe_recv,
- .p_close = wstran_pipe_close,
- .p_peer = wstran_pipe_peer,
- .p_getopt = wstran_pipe_getopt,
+ .p_size = wstran_pipe_size,
+ .p_init = wstran_pipe_init,
+ .p_fini = wstran_pipe_fini,
+ .p_stop = wstran_pipe_stop,
+ .p_send = wstran_pipe_send,
+ .p_recv = wstran_pipe_recv,
+ .p_close = wstran_pipe_close,
+ .p_peer = wstran_pipe_peer,
+ .p_getopt = wstran_pipe_getopt,
+ .p_peer_cert = wstran_pipe_peer_cert,
};
static void
diff --git a/src/sp/transport/ws/wss_test.c b/src/sp/transport/ws/wss_test.c
index 7c8ceeec..1686090b 100644
--- a/src/sp/transport/ws/wss_test.c
+++ b/src/sp/transport/ws/wss_test.c
@@ -14,7 +14,7 @@
#include <nng/nng.h>
-#include <nuts.h>
+#include "../../../testing/nuts.h"
static nng_tls_config *
wss_server_config(void)
@@ -381,6 +381,79 @@ test_wss_psk(void)
#endif
}
+void
+test_wss_pipe_details(void)
+{
+ nng_socket s1;
+ nng_socket s2;
+ nng_tls_config *c1, *c2;
+ nng_sockaddr sa;
+ nng_listener l;
+ nng_dialer d;
+ nng_msg *msg;
+ nng_pipe p;
+ const nng_url *url;
+
+ c1 = wss_server_config_ecdsa();
+ c2 = wss_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));
+ NUTS_PASS(nng_tls_config_ca_chain(c1, nuts_ecdsa_server_crt, NULL));
+ NUTS_PASS(nng_tls_config_ca_chain(c2, nuts_ecdsa_server_crt, NULL));
+ NUTS_PASS(nng_listener_create(&l, s1, "wss://127.0.0.1:0/test"));
+ NUTS_PASS(nng_listener_set_tls(l, c1));
+ NUTS_PASS(nng_listener_start(l, 0));
+ NUTS_PASS(nng_listener_get_url(l, &url));
+ NUTS_MATCH(nng_url_scheme(url), "wss");
+ NUTS_PASS(nng_listener_get_addr(l, NNG_OPT_LOCADDR, &sa));
+ NUTS_TRUE(sa.s_in.sa_family == NNG_AF_INET);
+ NUTS_TRUE(sa.s_in.sa_port != 0);
+ NUTS_TRUE(sa.s_in.sa_addr = nuts_be32(0x7f000001));
+ NUTS_PASS(nng_dialer_create_url(&d, s2, url));
+ NUTS_PASS(nng_dialer_set_tls(d, c2));
+ NUTS_PASS(nng_dialer_start(d, 0));
+ nng_msleep(50);
+ NUTS_SEND(s1, "text");
+ NUTS_PASS(nng_recvmsg(s2, &msg, 0));
+ p = nng_msg_get_pipe(msg);
+ NUTS_TRUE(nng_pipe_id(p) >= 0);
+#if !defined(NNG_TLS_ENGINE_WOLFSSL) || defined(NNG_WOLFSSL_HAVE_PEER_CERT)
+ nng_tls_cert *cert;
+ char *name;
+ NUTS_PASS(nng_pipe_peer_cert(p, &cert));
+ NUTS_PASS(nng_tls_cert_subject(cert, &name));
+ NUTS_ASSERT(name != NULL);
+ nng_log_debug(NULL, "SUBJECT: %s", name);
+ NUTS_PASS(nng_tls_cert_issuer(cert, &name));
+ NUTS_ASSERT(name != NULL);
+ nng_log_debug(NULL, "ISSUER: %s", name);
+ NUTS_PASS(nng_tls_cert_serial_number(cert, &name));
+ NUTS_ASSERT(name != NULL);
+ nng_log_debug(NULL, "SERIAL: %s", name);
+ NUTS_PASS(nng_tls_cert_subject_cn(cert, &name));
+ NUTS_MATCH(name, "127.0.0.1");
+ NUTS_PASS(nng_tls_cert_next_alt(cert, &name));
+ nng_log_debug(NULL, "FIRST ALT: %s", name);
+ NUTS_MATCH(name, "localhost");
+ NUTS_FAIL(nng_tls_cert_next_alt(cert, &name), NNG_ENOENT);
+ struct tm when;
+ NUTS_PASS(nng_tls_cert_not_before(cert, &when));
+ nng_log_debug(NULL, "BEGINS: %s", asctime(&when));
+ NUTS_PASS(nng_tls_cert_not_after(cert, &when));
+ nng_log_debug(NULL, "EXPIRES: %s", asctime(&when));
+
+ nng_tls_cert_free(cert);
+#endif
+ nng_msg_free(msg);
+ NUTS_CLOSE(s2);
+ NUTS_CLOSE(s1);
+ nng_tls_config_free(c1);
+ nng_tls_config_free(c2);
+}
+
NUTS_TESTS = {
{ "wss port zero bind", test_wss_port_zero_bind },
{ "wss malformed address", test_wss_malformed_address },
@@ -390,6 +463,7 @@ NUTS_TESTS = {
{ "wss pre-shared key", test_wss_psk },
{ "wss bad cert mutual", test_wss_bad_cert_mutual },
{ "wss cert mutual", test_wss_cert_mutual },
+ { "wss pipe details", test_wss_pipe_details },
{ NULL, NULL },
};
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));
+}