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/supplemental/tls | |
| 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/supplemental/tls')
| -rw-r--r-- | src/supplemental/tls/mbedtls/mbedtls.c | 326 | ||||
| -rw-r--r-- | src/supplemental/tls/tls_common.c | 109 | ||||
| -rw-r--r-- | src/supplemental/tls/tls_common.h | 3 | ||||
| -rw-r--r-- | src/supplemental/tls/tls_dialer.c | 2 | ||||
| -rw-r--r-- | src/supplemental/tls/tls_engine.h | 58 | ||||
| -rw-r--r-- | src/supplemental/tls/tls_listener.c | 2 | ||||
| -rw-r--r-- | src/supplemental/tls/tls_stream.c | 22 | ||||
| -rw-r--r-- | src/supplemental/tls/wolfssl/CMakeLists.txt | 8 | ||||
| -rw-r--r-- | src/supplemental/tls/wolfssl/wolfssl.c | 328 |
9 files changed, 751 insertions, 107 deletions
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, |
