diff options
| author | Garrett D'Amore <garrett@damore.org> | 2025-10-05 16:51:15 -0700 |
|---|---|---|
| committer | Garrett D'Amore <garrett@damore.org> | 2025-10-05 20:56:39 -0700 |
| commit | 06d6d80f8c92ef1d3bd7c00c919e10a411183cb3 (patch) | |
| tree | edf8d4cff9b2f595ccd9e3cb4db3cf31eb13bc02 /src | |
| parent | d1bd64c8251171ac8e1d4e71ab8726c2a64fd55a (diff) | |
| download | nng-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')
30 files changed, 1105 insertions, 170 deletions
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. @@ -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)); +} |
