diff --git a/sys/Makefile.dep b/sys/Makefile.dep index 4ae9d6ae4e..1233470594 100644 --- a/sys/Makefile.dep +++ b/sys/Makefile.dep @@ -713,6 +713,11 @@ ifneq (,$(filter luid,$(USEMODULE))) FEATURES_OPTIONAL += periph_cpuid endif +ifneq (,$(filter nanocoap_dtls,$(USEMODULE))) + USEMODULE += sock_dtls + USEPKG += tinydtls +endif + ifneq (,$(filter nanocoap_sock,$(USEMODULE))) USEMODULE += sock_udp USEMODULE += sock_util diff --git a/sys/include/net/coap.h b/sys/include/net/coap.h index 1480ea543d..d14a0bfe07 100644 --- a/sys/include/net/coap.h +++ b/sys/include/net/coap.h @@ -31,6 +31,11 @@ extern "C" { */ #define COAP_PORT (5683) +/** + * @brief Default CoAP DTLS port + */ +#define COAPS_PORT (5684) + #define COAP_V1 (1) /**< Identifier for CoAP version 1 (RFC 7252) */ /** diff --git a/sys/include/net/nanocoap_sock.h b/sys/include/net/nanocoap_sock.h index a1c3fecdc0..c8dc01f6bd 100644 --- a/sys/include/net/nanocoap_sock.h +++ b/sys/include/net/nanocoap_sock.h @@ -135,16 +135,57 @@ #include "net/nanocoap.h" #include "net/sock/udp.h" #include "net/sock/util.h" +#if IS_USED(MODULE_NANOCOAP_DTLS) +#include "net/credman.h" +#include "net/sock/dtls.h" +#endif #ifdef __cplusplus extern "C" { #endif /** - * @brief nanocoap socket type + * @brief Timeout for CoAP over DTLS queries in milliseconds + */ +#ifndef CONFIG_NANOCOAP_SOCK_DTLS_TIMEOUT_MS +#define CONFIG_NANOCOAP_SOCK_DTLS_TIMEOUT_MS (1000U) +#endif + +/** + * @brief Number of CoAP over DTLS handshake retries + */ +#ifndef CONFIG_NANOCOAP_SOCK_DTLS_RETRIES +#define CONFIG_NANOCOAP_SOCK_DTLS_RETRIES (2) +#endif + +/** + * @brief Credman tag used for NanoCoAP + * Tag together with the credential type (PSK) needs to be unique + */ +#ifndef CONFIG_NANOCOAP_SOCK_DTLS_TAG +#define CONFIG_NANOCOAP_SOCK_DTLS_TAG (0xc0ab) +#endif + +/** + * @brief NanoCoAP socket types + */ +typedef enum { + COAP_SOCKET_TYPE_UDP, /**< transport is plain UDP */ + COAP_SOCKET_TYPE_DTLS, /**< transport is DTLS */ +} nanocoap_socket_type_t; + +/** + * @brief NanoCoAP socket struct */ typedef struct { - sock_udp_t udp; /**< UDP socket */ + sock_udp_t udp; /**< UDP socket */ +#if IS_USED(MODULE_NANOCOAP_DTLS) || defined(DOXYGEN) + sock_dtls_t dtls; /**< DTLS socket */ + sock_dtls_session_t dtls_session; /**< Session object for the stored socket. + Used for exchanging a session between + functions. */ + nanocoap_socket_type_t type; /**< Socket type (UDP, DTLS) */ +#endif } nanocoap_sock_t; /** @@ -186,9 +227,30 @@ static inline int nanocoap_sock_connect(nanocoap_sock_t *sock, const sock_udp_ep_t *local, const sock_udp_ep_t *remote) { +#if IS_USED(MODULE_NANOCOAP_DTLS) + sock->type = COAP_SOCKET_TYPE_UDP; +#endif + return sock_udp_create(&sock->udp, local, remote, 0); } +#if IS_USED(MODULE_NANOCOAP_DTLS) || DOXYGEN +/** + * @brief Create a DTLS secured CoAP client socket + * + * @param[out] sock CoAP UDP socket + * @param[in] local Local UDP endpoint, may be NULL + * @param[in] remote remote UDP endpoint + * @param[in] tag Tag of the PSK credential to use + * Has to be added with @ref credman_add + * + * @returns 0 on success + * @returns <0 on error + */ +int nanocoap_sock_dtls_connect(nanocoap_sock_t *sock, sock_udp_ep_t *local, + const sock_udp_ep_t *remote, credman_tag_t tag); +#endif + /** * @brief Create a CoAP client socket by URL * @@ -207,6 +269,12 @@ int nanocoap_sock_url_connect(const char *url, nanocoap_sock_t *sock); */ static inline void nanocoap_sock_close(nanocoap_sock_t *sock) { +#if IS_USED(MODULE_NANOCOAP_DTLS) + if (sock->type == COAP_SOCKET_TYPE_DTLS) { + sock_dtls_session_destroy(&sock->dtls, &sock->dtls_session); + sock_dtls_close(&sock->dtls); + } +#endif sock_udp_close(&sock->udp); } diff --git a/sys/net/application_layer/nanocoap/sock.c b/sys/net/application_layer/nanocoap/sock.c index a46b73cb5d..72e176b47e 100644 --- a/sys/net/application_layer/nanocoap/sock.c +++ b/sys/net/application_layer/nanocoap/sock.c @@ -26,9 +26,11 @@ #include #include "atomic_utils.h" +#include "net/credman.h" #include "net/nanocoap_sock.h" #include "net/sock/util.h" #include "net/sock/udp.h" +#include "net/iana/portrange.h" #include "random.h" #include "sys/uio.h" #include "timex.h" @@ -37,6 +39,17 @@ #define ENABLE_DEBUG 0 #include "debug.h" +/** + * @brief Size of the buffer used for the DTLS handshake + * + * This size was found suitable for DTLS using a simple PSK in mode AES_128_CCM_8. + * DTLS places no restriction on its handshake package size therefore this might need change, + * if mode or key-size change especially if certificates instead of PSK are used. + */ +#ifndef CONFIG_NANOCOAP_DTLS_HANDSHAKE_BUF_SIZE +#define CONFIG_NANOCOAP_DTLS_HANDSHAKE_BUF_SIZE (160) +#endif + enum { STATE_REQUEST_SEND, /**< request was just sent or will be sent again */ STATE_RESPONSE_RCVD, /**< response received but might be invalid */ @@ -56,6 +69,93 @@ static uint16_t _get_id(void) return atomic_fetch_add_u16(&id, 1); } +#if IS_USED(MODULE_NANOCOAP_DTLS) +int nanocoap_sock_dtls_connect(nanocoap_sock_t *sock, sock_udp_ep_t *local, + const sock_udp_ep_t *remote, credman_tag_t tag) +{ + int res; + uint32_t timeout_ms = CONFIG_NANOCOAP_SOCK_DTLS_TIMEOUT_MS; + uint8_t retries = CONFIG_NANOCOAP_SOCK_DTLS_RETRIES; + + bool auto_port = local->port == 0; + do { + if (auto_port) { + /* choose random ephemeral port, since DTLS requires a local port */ + local->port = random_uint32_range(IANA_DYNAMIC_PORTRANGE_MIN, + IANA_DYNAMIC_PORTRANGE_MAX); + } + /* connect UDP socket */ + res = nanocoap_sock_connect(sock, local, remote); + } while (auto_port && (res == -EADDRINUSE)); + + if (res < 0) { + return res; + } + + /* create DTLS socket on to of UDP socket */ + res = sock_dtls_create(&sock->dtls, &sock->udp, tag, + SOCK_DTLS_1_2, SOCK_DTLS_CLIENT); + if (res < 0) { + DEBUG("Unable to create DTLS sock: %s\n", strerror(-res)); + nanocoap_sock_close(sock); + return res; + } + sock->type = COAP_SOCKET_TYPE_DTLS; + + while (1) { + uint8_t buf[CONFIG_NANOCOAP_DTLS_HANDSHAKE_BUF_SIZE]; + mutex_t lock = MUTEX_INIT_LOCKED; + ztimer_t timeout; + + /* unlock lock after timeout */ + ztimer_mutex_unlock(ZTIMER_MSEC, &timeout, timeout_ms, &lock); + + /* create DTLS session */ + res = sock_dtls_session_init(&sock->dtls, remote, &sock->dtls_session); + if (res >= 0) { + /* handle handshake */ + res = sock_dtls_recv(&sock->dtls, &sock->dtls_session, buf, + sizeof(buf), timeout_ms * US_PER_MS); + if (res == -SOCK_DTLS_HANDSHAKE) { + DEBUG("DTLS handshake successful\n"); + ztimer_remove(ZTIMER_MSEC, &timeout); + return 0; + } + DEBUG("Unable to establish DTLS handshake: %s\n", strerror(-res)); + + } else { + DEBUG("Unable to initialize DTLS session: %s\n", strerror(-res)); + } + + sock_dtls_session_destroy(&sock->dtls, &sock->dtls_session); + + if (retries--) { + /* wait for timeout to expire */ + mutex_lock(&lock); + } else { + ztimer_remove(ZTIMER_MSEC, &timeout); + break; + } + + /* see https://datatracker.ietf.org/doc/html/rfc6347#section-4.2.4.1 */ + timeout_ms *= 2U; + } + + nanocoap_sock_close(sock); + return res; +} +#else +int nanocoap_sock_dtls_connect(nanocoap_sock_t *sock, const sock_udp_ep_t *local, + const sock_udp_ep_t *remote, credman_tag_t tag) +{ + (void)sock; + (void)local; + (void)remote; + (void)tag; + return -ENOTSUP; +} +#endif + static int _get_error(const coap_pkt_t *pkt) { switch (coap_get_code_class(pkt)) { @@ -68,15 +168,61 @@ static int _get_error(const coap_pkt_t *pkt) } } +static inline nanocoap_socket_type_t _get_type(nanocoap_sock_t *sock) +{ +#if IS_USED(MODULE_NANOCOAP_DTLS) + return sock->type; +#else + (void)sock; + return COAP_SOCKET_TYPE_UDP; +#endif +} + +static int _sock_sendv(nanocoap_sock_t *sock, const iolist_t *snips) +{ + switch (_get_type(sock)) { + case COAP_SOCKET_TYPE_UDP: + return sock_udp_sendv(&sock->udp, snips, NULL); +#if IS_USED(MODULE_NANOCOAP_DTLS) + case COAP_SOCKET_TYPE_DTLS: + return sock_dtls_sendv(&sock->dtls, &sock->dtls_session, snips, + CONFIG_NANOCOAP_SOCK_DTLS_TIMEOUT_MS); +#endif + default: + assert(0); + return -EINVAL; + } +} + +static int _sock_recv_buf(nanocoap_sock_t *sock, void **data, void **ctx, uint32_t timeout) +{ + switch (_get_type(sock)) { + case COAP_SOCKET_TYPE_UDP: + return sock_udp_recv_buf(&sock->udp, data, ctx, timeout, NULL); +#if IS_USED(MODULE_NANOCOAP_DTLS) + case COAP_SOCKET_TYPE_DTLS: + return sock_dtls_recv_buf(&sock->dtls, &sock->dtls_session, data, ctx, timeout); +#endif + default: + assert(0); + return -EINVAL; + } +} + static int _send_ack(nanocoap_sock_t *sock, coap_pkt_t *pkt) { coap_hdr_t ack; unsigned tkl = coap_get_token_len(pkt); + const iolist_t snip = { + .iol_base = &ack, + .iol_len = sizeof(ack), + }; + coap_build_hdr(&ack, COAP_TYPE_ACK, coap_get_token(pkt), tkl, COAP_CODE_EMPTY, ntohs(pkt->hdr->id)); - return sock_udp_send(&sock->udp, &ack, sizeof(ack), NULL); + return _sock_sendv(sock, &snip); } static bool _id_or_token_missmatch(const coap_pkt_t *pkt, unsigned id, @@ -149,7 +295,7 @@ ssize_t nanocoap_sock_request_cb(nanocoap_sock_t *sock, coap_pkt_t *pkt, DEBUG("nanocoap: send %u bytes (%u tries left)\n", (unsigned)iolist_size(&head), tries_left); - res = sock_udp_sendv(&sock->udp, &head, NULL); + res = _sock_sendv(sock, &head); if (res <= 0) { DEBUG("nanocoap: error sending coap request, %d\n", (int)res); return res; @@ -170,7 +316,7 @@ ssize_t nanocoap_sock_request_cb(nanocoap_sock_t *sock, coap_pkt_t *pkt, _deadline_left_us(deadline)); } const void *old_ctx = ctx; - tmp = sock_udp_recv_buf(&sock->udp, &payload, &ctx, _deadline_left_us(deadline), NULL); + tmp = _sock_recv_buf(sock, &payload, &ctx, _deadline_left_us(deadline)); /* sock_udp_recv_buf() is supposed to return multiple packet fragments * when called multiple times with the same context. * In practise, this is not implemented and it will always return a pointer @@ -611,7 +757,14 @@ int nanocoap_sock_url_connect(const char *url, nanocoap_sock_t *sock) char hostport[CONFIG_SOCK_HOSTPORT_MAXLEN]; sock_udp_ep_t remote; - if (strncmp(url, "coap://", 7)) { + bool is_coaps = false; + + if (IS_USED(MODULE_NANOCOAP_DTLS) && !strncmp(url, "coaps://", 8)) { + DEBUG("nanocoap: CoAPS URL detected\n"); + is_coaps = true; + } + + if (!is_coaps && strncmp(url, "coap://", 7)) { DEBUG("nanocoap: URL doesn't start with \"coap://\"\n"); return -EINVAL; } @@ -627,10 +780,26 @@ int nanocoap_sock_url_connect(const char *url, nanocoap_sock_t *sock) } if (!remote.port) { - remote.port = COAP_PORT; + remote.port = is_coaps ? COAPS_PORT : COAP_PORT; } - return nanocoap_sock_connect(sock, NULL, &remote); + if (is_coaps) { + + /* tinydtls wants the interface to match */ + if (!remote.netif && + ipv6_addr_is_link_local((ipv6_addr_t *)remote.addr.ipv6)) { + netif_t *iface = netif_iter(NULL); + if (iface == NULL) { + return -ENODEV; + } + remote.netif = netif_get_id(iface); + } + + sock_udp_ep_t local = SOCK_IPV6_EP_ANY; + return nanocoap_sock_dtls_connect(sock, &local, &remote, CONFIG_NANOCOAP_SOCK_DTLS_TAG); + } else { + return nanocoap_sock_connect(sock, NULL, &remote); + } } int nanocoap_get_blockwise_url(const char *url,