From 8eaadb687ec0b8473f6a60dd7434823a797caeea Mon Sep 17 00:00:00 2001 From: Lucien Zuercher Date: Wed, 25 Mar 2026 10:50:07 +0100 Subject: [PATCH 1/3] oscore: enable external credential storage and safe recipient cleanup Add reference counting to recipient contexts so they can be safely removed while still in use by active sessions. Expose callback hooks for credential lookup, echo, and sequence storage to allow custom backends. Expand example server to demonstrate the external storage via a simple file based database. Expand coap_oscore_conf_t with coap_oscore_rcp_conf_t and coap_oscore_snd_conf_t with approproate setters to enable manipulation after loading the oscore credentials. Enable temporary credentials returned by the find function and are destroyed after use. --- examples/coap-server.c | 265 +++++++++++- include/coap3/coap_forward_decls.h | 2 + include/coap3/coap_net_internal.h | 6 + include/coap3/coap_oscore.h | 205 +++++++++- include/coap3/coap_oscore_internal.h | 66 ++- include/oscore/oscore_context.h | 36 +- libcoap-3.map | 7 + libcoap-3.sym | 7 + man/Makefile.am | 4 + man/coap-oscore-conf.txt.in | 20 + man/coap_oscore.txt.in | 117 ++++++ man/examples-code-check.c | 2 + src/coap_net.c | 11 +- src/coap_oscore.c | 376 +++++++++++++++-- src/coap_session.c | 1 + src/coap_subscribe.c | 2 +- src/oscore/oscore_context.c | 314 +++++++++++++-- tests/test_oscore.c | 580 +++++++++++++++++++++++++++ 18 files changed, 1928 insertions(+), 93 deletions(-) diff --git a/examples/coap-server.c b/examples/coap-server.c index 405496ef83..e8d2f08152 100644 --- a/examples/coap-server.c +++ b/examples/coap-server.c @@ -154,6 +154,11 @@ static ssize_t user_length = -1; static coap_dtls_pki_t *setup_pki(coap_context_t *ctx, coap_dtls_role_t role, char *sni); +static uint8_t *read_file_mem(const char *file, size_t *length); + +static char *oscore_make_credential_file_name(const coap_bin_const_t *rcpkey_id, + const coap_bin_const_t *ctxkey_id, int seq_file); + typedef struct psk_sni_def_t { char *sni_match; coap_bin_const_t *new_key; @@ -302,6 +307,194 @@ hnd_get_index(coap_resource_t *resource, (const uint8_t *)INDEX, NULL, NULL); } +/** Store an active echo challange, only one available at a time */ +static struct { + uint8_t recipient_id[10]; + uint8_t recipient_id_len; + uint8_t context_id[10]; + uint8_t context_id_len; + uint8_t echo_value[8]; +} active_echo_value; + +/* compare recipient_id and ctxkey_id (optional) with the active active_echo_value */ +static int +coap_oscore_active_echo_match(const coap_bin_const_t *recipient_id, + const coap_bin_const_t *ctxkey_id) { + if (recipient_id == NULL) + return 0; + + /* validate recipient id */ + if (active_echo_value.recipient_id_len != recipient_id->length || + memcmp(active_echo_value.recipient_id, recipient_id->s, recipient_id->length) != 0) { + return 0; + } + + if (ctxkey_id == NULL && active_echo_value.context_id_len == 0) + return 1; + + if (ctxkey_id == NULL || ctxkey_id->s == NULL || ctxkey_id->length == 0) + return active_echo_value.context_id_len == 0; + + return active_echo_value.context_id_len == ctxkey_id->length && + memcmp(active_echo_value.context_id, ctxkey_id->s, ctxkey_id->length) == 0; +} + +static int +update_seq_num_handler( + const coap_session_t *session, + const coap_bin_const_t rcpkey_id, + const coap_bin_const_t ctxkey_id, + uint64_t sender_seq_num, + uint64_t seq_num_window +) { + (void)session; + char *seq_file = oscore_make_credential_file_name(&rcpkey_id, &ctxkey_id, 1); + if (seq_file == NULL) + return 0; + + FILE *fp = fopen(seq_file, "w"); + if (fp == NULL) { + coap_free_type(COAP_STRING, seq_file); + return 0; + } + + int ret = fprintf(fp, "%" PRIu64 " %" PRIu64 "\n", sender_seq_num, seq_num_window); + fclose(fp); + coap_free_type(COAP_STRING, seq_file); + return ret > 0; +} + +static int +update_echo_handler( + const coap_session_t *session, + const coap_bin_const_t rcpkey_id, + const coap_bin_const_t ctxkey_id, + const uint8_t echo_value[8], + int store_value +) { + (void)session; + + if (store_value == 0) { + if (coap_oscore_active_echo_match(&rcpkey_id, &ctxkey_id)) { + /* read echo value from storage */ + memcpy(active_echo_value.echo_value, echo_value, sizeof(active_echo_value.echo_value)); + return 1; + } + return 0; + } + + if (echo_value == NULL) { + /* clear the active echo value */ + active_echo_value.recipient_id_len = 0; + active_echo_value.context_id_len = 0; + memset(active_echo_value.echo_value, 0, sizeof(active_echo_value.echo_value)); + return 1; + } + + active_echo_value.context_id_len = ctxkey_id.length ? (uint8_t)min(ctxkey_id.length, + sizeof(active_echo_value.context_id)) : 0; + if (ctxkey_id.length) { + memcpy(active_echo_value.context_id, ctxkey_id.s, active_echo_value.context_id_len); + } + + active_echo_value.recipient_id_len = (uint8_t)rcpkey_id.length; + memcpy(active_echo_value.recipient_id, rcpkey_id.s, active_echo_value.recipient_id_len); + memcpy(active_echo_value.echo_value, echo_value, sizeof(active_echo_value.echo_value)); + return 1; +} + +static int +load_oscore_storage_seq_file( + const coap_bin_const_t *rcpkey_id, + const coap_bin_const_t *ctxkey_id, + uint64_t *sender_seq_num, + uint64_t *seq_num_window +) { + char *seq_file = oscore_make_credential_file_name(rcpkey_id, ctxkey_id, 1); + if (seq_file == NULL) + return 0; + + FILE *fp = fopen(seq_file, "r"); + if (fp == NULL) { + coap_free_type(COAP_STRING, seq_file); + return 0; + } + + int ret = fscanf(fp, "%" SCNu64 " %" SCNu64 "\n", sender_seq_num, + seq_num_window); + fclose(fp); + coap_free_type(COAP_STRING, seq_file); + return ret == 2; +} + +/** + * Load the config from file system if file exists. + * Demonstrate external credential storage, this implementation is + * only for demonstration and not meant for production use. + * The approach is not efficient. + */ +static coap_oscore_conf_t * +coap_oscore_ctx_find( + const coap_session_t *session, + const coap_bin_const_t rcpkey_id, + const coap_bin_const_t ctxkey_id +) { + (void)session; + + size_t length; + uint8_t *buf = NULL; + char *cred_file = oscore_make_credential_file_name(&rcpkey_id, &ctxkey_id, 0); + char *seq_file = oscore_make_credential_file_name(&rcpkey_id, &ctxkey_id, 1); + coap_oscore_conf_t *result = NULL; + + /* load oscore context from file if available, and return recipient */ + buf = read_file_mem(cred_file, &length); + if (buf == NULL) { + coap_log_info("No credentials file '%s' found\n", cred_file); + goto exit; + } + + result = coap_new_oscore_conf((coap_str_const_t) { + length, buf + }, + NULL, NULL, 0); + if (result == NULL) { + coap_log_info("failed to load credentials file %s\n", cred_file); + goto exit; + } + + /* Ensure existing rcp conf is removed, + should the rcp with same keyid already exist. */ + coap_delete_oscore_conf_rcp(result, &rcpkey_id); + + coap_oscore_rcp_conf_t *rcp = coap_oscore_conf_add_rcp(result, &rcpkey_id); + if (rcp == NULL) { + coap_log_info("failed to add rcp for %s\n", cred_file); + goto exit; + } + + coap_log_debug("open credentials file %s\n", cred_file); + + uint64_t sender_seq_num = 0; + uint64_t seq_num_window = 0; + if (load_oscore_storage_seq_file(&rcpkey_id, &ctxkey_id, &sender_seq_num, &seq_num_window)) { + coap_oscore_rcp_conf_set_seq_num(rcp, sender_seq_num, seq_num_window); + } + + /* load echo value if available */ + if (coap_oscore_active_echo_match(&rcpkey_id, &ctxkey_id)) { + coap_oscore_rcp_conf_set_echo(rcp, active_echo_value.echo_value); + } + + coap_oscore_conf_set_context_id(result, &ctxkey_id); + +exit: + coap_free_type(COAP_STRING, cred_file); + coap_free_type(COAP_STRING, seq_file); + coap_free(buf); + return result; +} + static void hnd_get_fetch_time(coap_resource_t *resource, coap_session_t *session, @@ -1672,7 +1865,7 @@ usage(const char *program, const char *version) { "\t\t[-q tls_engine_conf_file] [-r] [-v num] [-w [port][,secure_port]]\n" "\t\t[-x] [-y rec_secs] [-z scheme://addr[:port][/resource[?query]]]\n" "\t\t[-A address] [-B resource[:check]] [-E oscore_conf_file[,seq_file]]\n" - "\t\t[-G group_if] [-L value] [-N]\n" + "\t\t[-O oscore_cred_dir] [-G group_if] [-L value] [-N]\n" "\t\t[-P scheme://address[:port],[name1[,name2..]]]\n" "\t\t[-T max_token_size] [-U type] [-V num] [-X size] [-3]\n" "\t\t[[-h hint] [-i match_identity_file] [-k key]\n" @@ -1681,6 +1874,8 @@ usage(const char *program, const char *version) { "\t\t[-J pkcs11_pin] [-M rpk_file] [-R trust_casfile]\n" "\t\t[-S match_pki_sni_file] [-Y]]\n" "General Options\n" + , program); + fprintf(stderr, "\t-a priority\tSend logging output to syslog at priority (0-7) level\n" "\t-b max_block_size\n" "\t \t\tMaximum block size server supports (16, 32, 64,\n" @@ -1743,14 +1938,27 @@ usage(const char *program, const char *version) { "\t \t\toptional check key as a query option (check=vvv). Query\n" "\t \t\tinformation in the PUT request can define the IP (ip=xxx)\n" "\t \t\tand port (port=yyy) the downstream client should be\n" - "\t \t\tspecifying to connect to in the proxy request\n" - , program); + "\t \t\tspecifying to connect to in the proxy request\n"); fprintf(stderr, "\t-E oscore_conf_file[,seq_file]\n" "\t \t\toscore_conf_file contains OSCORE configuration. See\n" "\t \t\tcoap-oscore-conf(5) for definitions.\n" "\t \t\tOptional seq_file is used to save the current transmit\n" "\t \t\tsequence number, so on restart sequence numbers continue\n" + "\t-O oscore_cred_dir\n" + "\t \t\tDirectory containing OSCORE credential files dynamically\n" + "\t \t\tloaded in format _.txt where contextid\n" + "\t \t\tand kid are hex-encoded byte strings.\n" + "\t \t\tEach file follows the coap-oscore-conf(5) format, whereby\n" + "\t \t\tcontextid and kid are ignored, the filename entries values\n" + "\t \t\tare used instead.\n" + "\t \t\tDemonstrates the use of an external OSCORE credential store.\n" + "\t \t\tA __seq.txt file contains the recipient\n" + "\t \t\tsequence counter and window. To enforce a new echo challenge\n" + "\t \t\tthe __seq.txt file can be deleted, to remove\n" + "\t \t\tthe credentials, the _.txt file can be deleted.\n" + "\t \t\tCan be combined with -E for a default context.\n"); + fprintf(stderr, "\t-G group_if\tUse this interface for listening for the multicast\n" "\t \t\tgroup. This can be different from the implied interface\n" "\t \t\tif the -A option is used\n" @@ -2175,6 +2383,7 @@ cmdline_read_user(char *arg, unsigned char **buf, size_t maxlen) { static FILE *oscore_seq_num_fp = NULL; static const char *oscore_conf_file = NULL; static const char *oscore_seq_save_file = NULL; +static const char *oscore_cred_dir = NULL; static int oscore_save_seq_num(uint64_t sender_seq_num, void *param COAP_UNUSED) { @@ -2186,6 +2395,38 @@ oscore_save_seq_num(uint64_t sender_seq_num, void *param COAP_UNUSED) { return 1; } + +/* + * Build "${oscore_cred_dir}/_[_seq].txt". + * Caller must free with coap_free(). + */ +static char * +oscore_make_credential_file_name(const coap_bin_const_t *rcpkey_id, + const coap_bin_const_t *ctxkey_id, int seq_file) { + size_t rcp_len = rcpkey_id ? rcpkey_id->length : 0; + size_t ctx_len = ctxkey_id ? ctxkey_id->length : 0; + size_t dir_len = oscore_cred_dir ? strlen(oscore_cred_dir) + 1 : 0; /* "dir/" */ + size_t buf_len = dir_len + (2 * ctx_len) + 1 + (2 * rcp_len) + + 9; /* hex + "_" + hex + "[_seq].txt\0" */ + char *result = coap_malloc_type(COAP_STRING, buf_len); + if (!result) + return NULL; + + char *p = result; + char *end = result + buf_len; + if (oscore_cred_dir) + p += snprintf(p, end - p, "%s", oscore_cred_dir); + for (size_t i = 0; i < ctx_len; i++) + p += snprintf(p, end - p, "%02x", ctxkey_id->s[i]); + *p++ = '_'; + for (size_t i = 0; i < rcp_len; i++) + p += snprintf(p, end - p, "%02x", rcpkey_id->s[i]); + if (seq_file) + p += snprintf(p, end - p, "_seq"); + snprintf(p, end - p, ".txt"); + return result; +} + static coap_oscore_conf_t * get_oscore_conf(coap_context_t *context) { uint8_t *buf; @@ -2635,7 +2876,7 @@ main(int argc, char **argv) { clock_offset = time(NULL); while ((opt = getopt(argc, argv, - "a:b:c:d:ef:g:h:i:j:k:l:mnop:q:rs:tu:v:w:xy:z:A:B:C:E:G:J:L:M:NP:R:S:T:U:V:X:Y23")) != -1) { + "a:b:c:d:ef:g:h:i:j:k:l:mnop:q:rs:tu:v:w:xy:z:A:B:C:E:G:J:L:M:NP:O:R:S:T:U:V:X:Y23")) != -1) { switch (opt) { #ifndef _WIN32 case 'a': @@ -2746,6 +2987,13 @@ main(int argc, char **argv) { case 'N': resource_flags = COAP_RESOURCE_FLAGS_NOTIFY_NON; break; + case 'O': + if (!coap_oscore_is_supported()) { + fprintf(stderr, "OSCORE support not enabled\n"); + goto failed; + } + oscore_cred_dir = optarg; + break; case 'o': shutdown_no_observe = 1; break; @@ -2908,6 +3156,15 @@ main(int argc, char **argv) { if (get_oscore_conf(ctx) == NULL) goto failed; } + if (oscore_cred_dir) { + /* register example local OSCORE credential storage */ + coap_oscore_register_external_handlers( + ctx, + coap_oscore_ctx_find, + update_seq_num_handler, + update_echo_handler + ); + } #if COAP_PROXY_SUPPORT if (reverse_proxy.entry_count) { proxy_dtls_setup(ctx, &reverse_proxy); diff --git a/include/coap3/coap_forward_decls.h b/include/coap3/coap_forward_decls.h index b9eb52a141..77d10eb088 100644 --- a/include/coap3/coap_forward_decls.h +++ b/include/coap3/coap_forward_decls.h @@ -85,6 +85,8 @@ typedef struct coap_queue_t coap_queue_t; * OSCORE information. */ typedef struct coap_oscore_conf_t coap_oscore_conf_t; +typedef struct coap_oscore_rcp_conf_t coap_oscore_rcp_conf_t; +typedef struct coap_oscore_snd_conf_t coap_oscore_snd_conf_t; /* ************* coap_pdu_internal.h ***************** */ diff --git a/include/coap3/coap_net_internal.h b/include/coap3/coap_net_internal.h index d048f11941..3a2d14b837 100644 --- a/include/coap3/coap_net_internal.h +++ b/include/coap3/coap_net_internal.h @@ -20,6 +20,7 @@ #include "coap_subscribe.h" #include "coap_resource.h" +#include "coap_oscore.h" #ifdef __cplusplus extern "C" { @@ -102,6 +103,11 @@ struct coap_context_t { #endif /* RIOT_VERSION */ #if COAP_OSCORE_SUPPORT struct oscore_ctx_t *p_osc_ctx; /**< primary oscore context */ + coap_oscore_find_handler_t oscore_find_cb; /**< Optional override for oscore_find_context() */ + coap_oscore_update_echo_handler_t + oscore_update_echo_cb; /**< Optional function to call to update echo values */ + coap_oscore_update_seq_num_handler_t + oscore_update_seq_num_cb; /**< Optional function to call to update sequence number and window values */ #endif /* COAP_OSCORE_SUPPORT */ #if COAP_CLIENT_SUPPORT diff --git a/include/coap3/coap_oscore.h b/include/coap3/coap_oscore.h index a9ab442595..a75d7df85d 100644 --- a/include/coap3/coap_oscore.h +++ b/include/coap3/coap_oscore.h @@ -32,6 +32,83 @@ extern "C" { * @{ */ +/** + * Callback function type for overriding oscore_find_context(). + * + * If set via coap_oscore_register_external_handlers(), this function is + * called before the internal oscore_find_context() to locate the OSCORE + * recipient and security context for an incoming request. + * + * The implementation of this function should be combined with + * @ref coap_oscore_update_seq_num_handler_t and @ref coap_oscore_update_echo_handler_t + * if echo challenge and sequence counter management is required. + * Otherwise, libcoap will lose those values and echo challenge + * and replay protection will not work properly. + * + * @param session The active CoAP session receiving the request from. + * @param rcpkey_id The Recipient Key ID (KID). + * @param ctxkey_id The ID Context to match. + * + * @return The OSCORE config retrieved from the custom OSCORE storage or NULL if not found. + * Will fallback to libcoap internal credential storage lookup. + */ +typedef coap_oscore_conf_t *(*coap_oscore_find_handler_t)( + const coap_session_t *session, + const coap_bin_const_t rcpkey_id, + const coap_bin_const_t ctxkey_id +); + +/** + * Callback function type for persisting the OSCORE receiver sequence number + * and anti-replay sliding window to an external storage. + * + * Called by the library whenever the Receiver Sequence Number or the + * anti-replay window is updated, giving the application the opportunity to + * store both values for external OSCORE credential management. Required + * to provide those values via the coap_oscore_find_handler_t callback, since + * the values will otherwise be lost. + * + * @param session The active CoAP session receiving the request from. + * @param rcpkey_id The Recipient ID for which the sequence number and window applies. + * @param ctxkey_id The ID Context for which the sequence number and window applies. + * @param receiver_seq_num The receiver sequence number. + * @param seq_num_window The 64-bit anti-replay sliding window bitmask. + * @return @c 1 if persisted successfully, else @c 0. + */ +typedef int (*coap_oscore_update_seq_num_handler_t)( + const coap_session_t *session, + const coap_bin_const_t rcpkey_id, + const coap_bin_const_t ctxkey_id, + uint64_t receiver_seq_num, + uint64_t seq_num_window +); + +/** + * Callback function type for storing the OSCORE Echo challenge value in + * an external storage. + * + * Called by the library when the Echo option value is set (a fresh 8-byte + * challenge is available) or cleared (NULL provided for @p echo_value ). + * The application needs to store those values to enable echo challenges + * via the coap_oscore_find_handler_t integration. + * + * @param session The active CoAP session receiving the request from. + * @param rcpkey_id The Recipient ID for which the Echo value applies. + * @param ctxkey_id The ID Context for which the Echo value applies + * (or zero-length if not used). + * @param echo_value 8-byte Echo challenge to persist, or NULL if the challenge has been cleared. + * @param store_value @c 1 if @p echo_value is a new challenge to persist, or + * @c 0 if @p echo_value is read from storage. + * @return @c 1 if the Echo value was successfully persisted or retrieved, else @c 0. + */ +typedef int (*coap_oscore_update_echo_handler_t)( + const coap_session_t *session, + const coap_bin_const_t rcpkey_id, + const coap_bin_const_t ctxkey_id, + const uint8_t echo_value[8], + int store_value +); + /** * Creates a new client session to the designated server, protecting the data * using OSCORE. @@ -238,6 +315,7 @@ COAP_API coap_session_t *coap_new_client_session_oscore_pki3(coap_context_t *ctx void *app_data, coap_app_data_free_callback_t callback, coap_str_const_t *ws_host); + /** * Set the context's default OSCORE configuration for a server. * @@ -253,8 +331,7 @@ COAP_API int coap_context_oscore_server(coap_context_t *context, /** * Definition of the function used to save the current Sender Sequence Number * - * @param sender_seq_num The Sender Sequence Number to save in non-volatile - * memory. + * @param sender_seq_num The Sender Sequence Number to save in memory. * @param param The save_seq_num_func_param provided to * coap_new_oscore_context(). * @@ -268,11 +345,11 @@ typedef int (*coap_oscore_save_seq_num_t)(uint64_t sender_seq_num, void *param); * * @param conf_mem The current configuration in memory. * @param save_seq_num_func Function to call to save Sender Sequence Number in - * non-volatile memory, or NULL. + * memory, or NULL. * @param save_seq_num_func_param Parameter to pass into * save_seq_num_func() function. * @param start_seq_num The Sender Sequence Number to start with following a - * reboot retrieved out of non-volatile menory or 0. + * reboot retrieved out of memory or 0. * * @return The new OSCORE configuration. NULL if failed. It needs to be freed * off with coap_delete_oscore_conf() when no longer required, @@ -288,17 +365,127 @@ coap_oscore_conf_t *coap_new_oscore_conf(coap_str_const_t conf_mem, * * @param oscore_conf The OSCORE configuration structure to release. * - * @return @c 1 Successfully removed, else @c 0 not found. + * @return @c 1 Successfully released, else @c 0 if not valid. */ int coap_delete_oscore_conf(coap_oscore_conf_t *oscore_conf); +/** + * Remove the specific recipient configuration from an OSCORE configuration. + * + * @param conf The OSCORE configuration structure to modify. + * @param recipient_id The Recipient ID to remove. + * + * @return @c 1 Successfully removed, else @c 0 not found. + */ +COAP_API int coap_delete_oscore_conf_rcp(coap_oscore_conf_t *conf, + const coap_bin_const_t *recipient_id); + +/** + * Add a recipient configuration entry to an OSCORE configuration. + * + * @param conf The OSCORE configuration to update. + * @param recipient_id Binary Recipient ID of the remote peer. + * + * @return Pointer to the added recipient entry, or @c NULL if already present, + * allocation fails or requiring lock during threaded access failed. + */ +COAP_API coap_oscore_rcp_conf_t *coap_oscore_conf_add_rcp(coap_oscore_conf_t *conf, + const coap_bin_const_t *recipient_id); + + +/** + * Set a preloaded echo challenge value for the provided oscore conf. + * + * @param rcp_conf The OSCORE recipient configuration to set the echo value for. + * @param echo_value The 8-byte echo challenge value to set. + */ +COAP_API void coap_oscore_rcp_conf_set_echo(coap_oscore_rcp_conf_t *rcp_conf, + uint8_t echo_value[8]); + +/** + * Set the sender sequence number and anti-replay window for the provided oscore conf. + * + * This will initialize the sliding window state for the recipient. + * + * @param rcp_conf The OSCORE recipient configuration to set the sequence number and anti-replay window for. + * @param sender_seq_num The Sender Sequence Number to set. + * @param seq_num_window The anti-replay sliding window bitmask to set. + */ +COAP_API void coap_oscore_rcp_conf_set_seq_num(coap_oscore_rcp_conf_t *rcp_conf, + uint64_t sender_seq_num, + uint64_t seq_num_window); + +/** + * Set the sender configuration for an OSCORE configuration. + * + * Stores a copy of the Sender ID into @p conf, overriding any + * sender ID previously parsed from the configuration file. Call this + * before passing @p conf to coap_context_oscore_server() or + * coap_new_client_session_oscore*(). + * + * @param conf The OSCORE configuration to update. + * @param sender_id Binary Sender ID for this endpoint which will be copied. + * + * @return Pointer to the sender configuration on success, else @c NULL. + */ +COAP_API coap_oscore_snd_conf_t *coap_oscore_conf_set_snd(coap_oscore_conf_t *conf, + const coap_bin_const_t *sender_id); + +/** + * Set the ID Context for an OSCORE configuration. + * + * Overrides the ID Context previously parsed from the configuration file. + * The context_id value is copied into @p conf; the caller retains + * ownership of @p context_id. Call this before passing @p conf to + * coap_context_oscore_server() or coap_new_client_session_oscore*(). + * + * @param conf The OSCORE configuration to update. + * @param context_id Binary ID Context to apply, or @c NULL to clear it. + */ +COAP_API void coap_oscore_conf_set_context_id(coap_oscore_conf_t *conf, + const coap_bin_const_t *context_id); + +/** + * Register external storage handlers for OSCORE session state. + * + * Allows providing a custom OSCORE credential storage for + * persistence and optimized for the needs of the application. + * Expands the built-in OSCORE context lookup and enables management + * of persistent OSCORE data (sequence numbers and Echo challenges) + * from within the application. + * + * @param context The CoAP context to configure. + * @param find_handler Inject a customized OSCORE config-lookup function + * to return temporary oscore credentials managed by + * an external credential store (see coap_oscore_find_handler_t), + * or @c NULL to only use the built-in oscore_find_context(). + * @param update_seq_num_handler Called whenever the Sender Sequence Number + * or anti-replay window changes. Use this + * to synchronize values with the external + * credential storage. @c NULL to disable. + * If find_handler is set, then it is recommended that + * update_seq_num_handler is set. + * @param update_echo_handler Called whenever the Echo challenge value is + * set or cleared. Use this to persist the + * value to non-volatile storage. @c NULL to + * disable. If find_handler is set, then it is recommended + * that update_echo_handler is set. + */ +COAP_API void coap_oscore_register_external_handlers( + coap_context_t *context, + coap_oscore_find_handler_t find_handler, + coap_oscore_update_seq_num_handler_t update_seq_num_handler, + coap_oscore_update_echo_handler_t update_echo_handler); + /** * Add in the specific Recipient ID into the OSCORE context (server only). * Note: This is only added to the OSCORE context as first defined by * coap_new_client_session_oscore*() or coap_context_oscore_server(). * - * @param context The CoAP context to add the OSCORE recipient_id to. - * @param recipient_id The Recipient ID to add. + * @param context The CoAP context to add the OSCORE recipient_id to. + * @param recipient_id The Recipient ID to add. Ownership of memory moves into the function + * and will be freed off when the context is freed or if the + * function fails. * * @return @c 1 Successfully added, else @c 0 there is an issue. */ @@ -307,11 +494,11 @@ COAP_API int coap_new_oscore_recipient(coap_context_t *context, /** * Release all the information associated for the specific Recipient ID - * (and hence and stop any further OSCORE protection for this Recipient). + * (and hence stop any further OSCORE protection for this Recipient). * Note: This is only removed from the OSCORE context as first defined by * coap_new_client_session_oscore*() or coap_context_oscore_server(). * - * @param context The CoAP context holding the OSCORE recipient_id to. + * @param context The CoAP context holding the OSCORE recipient_id to be removed. * @param recipient_id The Recipient ID to remove. * * @return @c 1 Successfully removed, else @c 0 not found. diff --git a/include/coap3/coap_oscore_internal.h b/include/coap3/coap_oscore_internal.h index bde415a12c..984709bdad 100644 --- a/include/coap3/coap_oscore_internal.h +++ b/include/coap3/coap_oscore_internal.h @@ -31,6 +31,27 @@ extern "C" { * Internal API for interfacing with OSCORE (RFC8613) * @{ */ +/** + * The structure used to hold the OSCORE Sender configuration information + */ +struct coap_oscore_snd_conf_t { + coap_bin_const_t *sender_id; /**< Sender ID (i.e. local our id) */ +}; + +/** + * The structure used to hold the OSCORE Recipient configuration + */ +struct coap_oscore_rcp_conf_t { + struct coap_oscore_rcp_conf_t *next_recipient; /**< Used to maintain + the chain */ + coap_bin_const_t *recipient_id; /**< Recipient ID (i.e. local our id) */ + + /* SSN handling for rfc8613 B.1.2 */ + uint8_t echo_value[8]; /** Inject an echo value to use for the oscore credentials */ + uint8_t window_initialized; /**< Contains if the sliding window is initialized @c 1 if initialized, @c 0 otherwise */ + uint64_t last_seq; /**< Highest sequence number used for this recipient */ + uint64_t sliding_window; /**< bitfield sequence counter window */ +}; /** * The structure used to hold the OSCORE configuration information @@ -38,11 +59,9 @@ extern "C" { struct coap_oscore_conf_t { coap_bin_const_t *master_secret; /**< Common Master Secret */ coap_bin_const_t *master_salt; /**< Common Master Salt */ - coap_bin_const_t *sender_id; /**< Sender ID (i.e. local our id) */ + coap_oscore_snd_conf_t sender_id; /**< Sender configuration */ coap_bin_const_t *id_context; /**< Common ID context */ - coap_bin_const_t **recipient_id; /**< Recipient ID (i.e. remote peer id) - Array of recipient_id */ - uint32_t recipient_id_count; /**< Number of recipient_id entries */ + coap_oscore_rcp_conf_t *recipient_id; /**< The recipients as a chain */ uint32_t replay_window; /**< Replay window size Use COAP_OSCORE_DEFAULT_REPLAY_WINDOW */ uint32_t ssn_freq; /**< Sender Seq Num update frequency */ @@ -129,6 +148,33 @@ struct coap_pdu_t *coap_oscore_decrypt_pdu(coap_session_t *session, */ void coap_delete_all_oscore(coap_context_t *context); +/** + * Attach the OSCORE recipient information to the session. + * + * @param session The session to attach the recipient information to. + * @param recipient The recipient information to attach. + */ +void coap_oscore_session_set_recipient(coap_session_t *session, + oscore_recipient_ctx_t *recipient); + +/** + * Set the recipient of an association. + * + * @param association The association to set @p recipient for. + * @param recipient For which the reference counter will be increased. + */ +void coap_oscore_association_set_recipient(oscore_association_t *association, + oscore_recipient_ctx_t *recipient); + +/** + * Verify if the OSCORE context is attached to the @p c_context . + * + * @param c_context The context to check for the OSCORE context. + * @param oscore_ctx The OSCORE context to check for. + * @return @c 1 if the OSCORE context is attached to the @p c_context , else @c 0. + */ +int coap_oscore_is_attached(coap_context_t *c_context, oscore_ctx_t *oscore_ctx); + /** * Cleanup all allocated OSCORE association information. * @@ -237,6 +283,18 @@ coap_session_t *coap_new_client_session_oscore3_lkd(coap_context_t *ctx, coap_app_data_free_callback_t callback, coap_str_const_t *ws_host); +/** + * Initializes an OSCORE context from the given configuration. + * + * @param oscore_conf The OSCORE configuration information to use to create the OSCORE context. + * Will be freed by this call. + * @return The created OSCORE context or NULL on failure. + */ +oscore_ctx_t * +coap_init_oscore_context_from_conf( + coap_oscore_conf_t *oscore_conf +); + /** * Creates a new client session to the designated server, with PKI credentials * protecting the data using OSCORE, along with app_data information (as per diff --git a/include/oscore/oscore_context.h b/include/oscore/oscore_context.h index c1011ac025..d990680e4b 100644 --- a/include/oscore/oscore_context.h +++ b/include/oscore/oscore_context.h @@ -112,6 +112,8 @@ struct oscore_sender_ctx_t { }; struct oscore_recipient_ctx_t { + /** Reference counter to keep track of linked associations / active sessions */ + unsigned ref; /* This field allows recipient chaining */ oscore_recipient_ctx_t *next_recipient; oscore_ctx_t *osc_ctx; @@ -167,6 +169,15 @@ struct oscore_association_t { oscore_ctx_t *oscore_derive_ctx(coap_context_t *c_context, coap_oscore_conf_t *oscore_conf); +/** + * oscore_derive_ctx_from_conf - derive a osc_ctx from oscore_conf information + * + * @param oscore_conf The OSCORE configuration to use. + * + * @return NULL if failure or derived OSCORE context. + */ +oscore_ctx_t *oscore_derive_ctx_from_conf(coap_oscore_conf_t *oscore_conf); + /** * oscore_duplicate_ctx - duplicate a osc_ctx * @@ -199,11 +210,21 @@ void oscore_free_contexts(coap_context_t *c_context); int oscore_remove_context(coap_context_t *c_context, oscore_ctx_t *osc_ctx); +int oscore_add_context(coap_context_t *c_context, oscore_ctx_t *osc_ctx); + +/** + * Check if oscore context is attached to a the provided context. + * + * @param osc_ctx The OSCORE context to check for being attached. + * @return @c 1 if @p osc_ctx is not attached to any context, @c 0 otherwise. + */ +int oscore_is_context_attached(const oscore_ctx_t *osc_ctx); + /** * oscore_add_recipient - add in recipient information * * @param ctx The OSCORE context to add to. - * @param rid The recipient ID. + * @param rid The recipient ID. Will be freed or attached to @p ctx. * @param break_key @c 1 if testing for broken keys, else @c 0. * * @return NULL if failure or recipient context linked onto @p ctx chain. @@ -214,6 +235,15 @@ oscore_recipient_ctx_t *oscore_add_recipient(oscore_ctx_t *ctx, int oscore_delete_recipient(oscore_ctx_t *osc_ctx, coap_bin_const_t *rid); +/** + * Cleanup recipient context, including releasing the oscore context if the + * oscore context referenced is not attached to the coap_context_t. + * @param recipient The recipient context to cleanup. Will set the pointer to NULL after cleanup. + */ +void oscore_release_recipient(oscore_recipient_ctx_t **recipient); + +void oscore_reference_recipient(oscore_recipient_ctx_t *recipient); + uint8_t oscore_bytes_equal(uint8_t *a_ptr, uint8_t a_len, uint8_t *b_ptr, @@ -236,7 +266,7 @@ void oscore_log_char_value(coap_log_t level, const char *name, /** * oscore_find_context - Locate recipient context (and hence OSCORE context) * - * @param c_context The CoAP COntext to search. + * @param session The CoAP session to search. * @param rcpkey_id The Recipient kid. * @param ctxkey_id The ID Context to match (or NULL if no check). * @param oscore_r2 Partial id_context to match against or NULL. @@ -244,7 +274,7 @@ void oscore_log_char_value(coap_log_t level, const char *name, * * return The OSCORE context and @p recipient_ctx updated, or NULL is error. */ -oscore_ctx_t *oscore_find_context(const coap_context_t *c_context, +oscore_ctx_t *oscore_find_context(const coap_session_t *session, const coap_bin_const_t rcpkey_id, const coap_bin_const_t *ctxkey_id, uint8_t *oscore_r2, diff --git a/libcoap-3.map b/libcoap-3.map index 352e226896..0997172f79 100644 --- a/libcoap-3.map +++ b/libcoap-3.map @@ -89,6 +89,7 @@ global: coap_delete_cache_key; coap_delete_optlist; coap_delete_oscore_conf; + coap_delete_oscore_conf_rcp; coap_delete_oscore_recipient; coap_delete_pdu; coap_delete_resource; @@ -203,7 +204,13 @@ global: coap_option_filter_unset; coap_option_iterator_init; coap_option_next; + coap_oscore_conf_add_rcp; + coap_oscore_conf_set_context_id; + coap_oscore_conf_set_snd; coap_oscore_is_supported; + coap_oscore_rcp_conf_set_echo; + coap_oscore_rcp_conf_set_seq_num; + coap_oscore_register_external_handlers; coap_package_build; coap_package_name; coap_package_version; diff --git a/libcoap-3.sym b/libcoap-3.sym index 63a0f822e7..18cfff9bcc 100644 --- a/libcoap-3.sym +++ b/libcoap-3.sym @@ -87,6 +87,7 @@ coap_delete_cache_entry coap_delete_cache_key coap_delete_optlist coap_delete_oscore_conf +coap_delete_oscore_conf_rcp coap_delete_oscore_recipient coap_delete_pdu coap_delete_resource @@ -201,7 +202,13 @@ coap_option_filter_set coap_option_filter_unset coap_option_iterator_init coap_option_next +coap_oscore_conf_add_rcp +coap_oscore_conf_set_context_id +coap_oscore_conf_set_snd coap_oscore_is_supported +coap_oscore_rcp_conf_set_echo +coap_oscore_rcp_conf_set_seq_num +coap_oscore_register_external_handlers coap_package_build coap_package_name coap_package_version diff --git a/man/Makefile.am b/man/Makefile.am index e7a200222e..62ed965470 100644 --- a/man/Makefile.am +++ b/man/Makefile.am @@ -134,6 +134,10 @@ install-man: install-man3 install-man5 install-man7 @echo ".so man3/coap_context.3" > coap_context_set_session_reconnect_time.3 @echo ".so man3/coap_context.3" > coap_context_set_session_reconnect_time2.3 @echo ".so man3/coap_context.3" > coap_register_option.3 + @echo ".so man3/coap_oscore.3" > coap_new_client_session_oscore3.3 + @echo ".so man3/coap_oscore.3" > coap_new_client_session_oscore_pki3.3 + @echo ".so man3/coap_oscore.3" > coap_new_client_session_oscore_psk3.3 + @echo ".so man3/coap_oscore.3" > coap_context_oscore_server.3 @echo ".so man3/coap_deprecated.3" > coap_new_client_session_oscore_psk.3 @echo ".so man3/coap_deprecated.3" > coap_new_client_session_psk.3 @echo ".so man3/coap_deprecated.3" > coap_new_client_session_psk2.3 diff --git a/man/coap-oscore-conf.txt.in b/man/coap-oscore-conf.txt.in index 753e452088..3e5040c8d1 100644 --- a/man/coap-oscore-conf.txt.in +++ b/man/coap-oscore-conf.txt.in @@ -32,6 +32,26 @@ keyword encoding type and the keyword value, one per line, comma separated. keyword,encoding,value +Additionally, the *-O oscore_cred_dir* option can be used to enable the +example external OSCORE credential store in *coap-server*(5). In this mode, +the server loads credentials dynamically per incoming (Context ID, Recipient +ID) by using the registered external handlers. + +The dynamic credential files use the following naming format: + +* `_.txt` for the OSCORE credential text in the + same format as for the *-E* option. +* `__seq.txt` for persisted recipient sequence state + (sequence counter and replay window), stored as two uint64_t values. + +* `` is the hexdecimal representation of the OSCORE ID Context. +* `` is the hexdecimal representation of the OSCORE kid or Sender ID + for the server. + +This allows a single server instance to store and load multiple OSCORE +credentials, each keyed by Context ID and Recipient ID. The *-O* option can +be combined with *-E*. + The keywords are case sensitive. If a line starts with a *#*, then it is treated as a comment line and so is ignored. Empty lines are also valid and ignored. diff --git a/man/coap_oscore.txt.in b/man/coap_oscore.txt.in index d18b24234e..e0c864381d 100644 --- a/man/coap_oscore.txt.in +++ b/man/coap_oscore.txt.in @@ -13,6 +13,10 @@ NAME coap_oscore, coap_new_oscore_conf, coap_delete_oscore_conf, +coap_oscore_conf_add_rcp, +coap_oscore_conf_set_snd, +coap_oscore_conf_set_context_id, +coap_oscore_register_external_handlers, coap_new_oscore_recipient, coap_delete_oscore_recipient, coap_new_client_session_oscore3, @@ -31,6 +35,20 @@ void *_save_seq_num_func_param_, uint64_t _start_seq_num_);* *int coap_delete_oscore_conf(coap_oscore_conf_t *_oscore_conf_);* +*coap_oscore_rcp_conf_t *coap_oscore_conf_add_rcp(coap_oscore_conf_t *_conf_, +const coap_bin_const_t *_recipient_id_);* + +*coap_oscore_snd_conf_t *coap_oscore_conf_set_snd(coap_oscore_conf_t *_conf_, +const coap_bin_const_t *_sender_id_);* + +*void coap_oscore_conf_set_context_id(coap_oscore_conf_t *_conf_, +const coap_bin_const_t *_context_id_);* + +*void coap_oscore_register_external_handlers(coap_context_t *_context_, +coap_oscore_find_handler_t _find_handler_, +coap_oscore_update_seq_num_handler_t _update_seq_num_handler_, +coap_oscore_update_echo_handler_t _update_echo_handler_);* + *int coap_new_oscore_recipient(coap_context_t *_context_, coap_bin_const_t *_recipient_id_);* @@ -76,6 +94,69 @@ manipulate information. CALLBACK HANDLER ---------------- +*Callback Type: coap_oscore_find_handler_t* + +The coap_oscore_find_handler_t method handler function prototype is defined as: +[source, c] +---- +/** + * Callback function type for overriding oscore_find_context(). + */ +typedef coap_oscore_conf_t *(*coap_oscore_find_handler_t)( + const coap_session_t *session, + const coap_bin_const_t rcpkey_id, + const coap_bin_const_t ctxkey_id +); +---- + +If this callback is registered with +*coap_oscore_register_external_handlers*(), libcoap calls it when +incoming OSCORE traffic needs a context lookup. The callback can return +temporary credentials sourced from an external store (for example a file or +database keyed by Recipient ID and Context ID). + +*Callback Type: coap_oscore_update_seq_num_handler_t* + +The coap_oscore_update_seq_num_handler_t method handler function prototype is +defined as: +[source, c] +---- +/** + * Callback function type for persisting OSCORE sequence and replay state. + */ +typedef int (*coap_oscore_update_seq_num_handler_t)( + const coap_session_t *session, + const coap_bin_const_t rcpkey_id, + const coap_bin_const_t ctxkey_id, + uint64_t receiver_seq_num, + uint64_t seq_num_window +); +---- + +This callback can be used to keep external storage synchronized whenever +sequence/replay state changes. + +*Callback Type: coap_oscore_update_echo_handler_t* + +The coap_oscore_update_echo_handler_t method handler function prototype is +defined as: +[source, c] +---- +/** + * Callback function type for persisting the OSCORE Echo challenge value. + */ +typedef int (*coap_oscore_update_echo_handler_t)( + const coap_session_t *session, + const coap_bin_const_t rcpkey_id, + const coap_bin_const_t ctxkey_id, + const uint8_t echo_value[8], + int store_value +); +---- + +This callback allows the application to save and restore Echo values in +external storage. + *Callback Type: coap_oscore_save_seq_num_t* The coap_oscore_save_seq_num_t method handler function prototype is defined as: @@ -147,6 +228,36 @@ coap_oscore_conf_t structure _oscore_conf_ as returned by *coap_new_oscore_conf*(). Normally this function never needs to be called as _oscore_conf_ is freed off by the call the client or server setup functions. +*Function: coap_oscore_conf_add_rcp()* + +The *coap_oscore_conf_add_rcp*() function appends a *recipient_id* Recipient ID +entry to _conf_. If the Recipient ID already exists, or memory allocation fails, +NULL is returned. + +*Function: coap_oscore_conf_set_snd()* + +The *coap_oscore_conf_set_snd*() function replaces the Sender ID in _conf_ +with a copy of _sender_id_. This is useful when the sender identity is +provided by the application at runtime rather than directly from the parsed +configuration text. + +*Function: coap_oscore_conf_set_context_id()* + +The *coap_oscore_conf_set_context_id*() function replaces the ID Context in +_conf_ with a copy of _context_id_. + +*Function: coap_oscore_register_external_handlers()* + +The *coap_oscore_register_external_handlers*() function registers callbacks +for custom OSCORE credential/state storage. It can be used to: + +* override context lookup (*_find_handler_*) +* persist sequence/replay state (*_update_seq_num_handler_*) +* persist Echo state (*_update_echo_handler_*) + +When no handler is required, pass NULL for that callback. If *find_handler* is +set, then *update_seq_num_handler* and *update_echo_handler* should also be defined. + *Function: coap_new_client_session_oscore3()* The *coap_new_client_session_oscore3*() is basically the same as @@ -235,6 +346,12 @@ client session or NULL if there is a malloc or parameter failure. *coap_new_oscore_conf*() returns a _coap_oscore_conf_t_ or NULL on failure. +*coap_oscore_conf_add_rcp*() returns a _coap_oscore_rcp_conf_t_ +or NULL on failure. + +*coap_oscore_conf_set_snd*() returns a pointer to the sender +configuration on success or NULL on failure. + *coap_context_oscore_server*(), *coap_delete_oscore_conf*(), *coap_new_oscore_recipient*() and *coap_delete_oscore_recipient*() return 0 on failure, 1 on success. diff --git a/man/examples-code-check.c b/man/examples-code-check.c index 40d873f1a3..ff590005a8 100644 --- a/man/examples-code-check.c +++ b/man/examples-code-check.c @@ -120,6 +120,8 @@ const char *pointer_list[] = { "coap_context_t ", "coap_fixed_point_t ", "coap_oscore_conf_t ", + "coap_oscore_rcp_conf_t ", + "coap_oscore_snd_conf_t ", "coap_optlist_t ", "coap_session_t ", "coap_string_t ", diff --git a/src/coap_net.c b/src/coap_net.c index 7a79f86346..99c799e9f5 100644 --- a/src/coap_net.c +++ b/src/coap_net.c @@ -861,10 +861,6 @@ coap_free_context_lkd(coap_context_t *context) { coap_delete_all_async(context); #endif /* COAP_ASYNC_SUPPORT */ -#if COAP_OSCORE_SUPPORT - coap_delete_all_oscore(context); -#endif /* COAP_OSCORE_SUPPORT */ - #if COAP_SERVER_SUPPORT coap_cache_entry_t *cp, *ctmp; coap_endpoint_t *ep, *tmp; @@ -889,6 +885,10 @@ coap_free_context_lkd(coap_context_t *context) { } #endif /* COAP_CLIENT_SUPPORT */ +#if COAP_OSCORE_SUPPORT + coap_delete_all_oscore(context); +#endif /* COAP_OSCORE_SUPPORT */ + if (context->dtls_context) coap_dtls_free_context(context->dtls_context); #ifdef COAP_EPOLL_SUPPORT @@ -996,7 +996,8 @@ coap_option_check_critical(coap_session_t *session, case COAP_OPTION_OSCORE: /* Valid critical if doing OSCORE */ #if COAP_OSCORE_SUPPORT - if (ctx->p_osc_ctx) + /* TODO: has coap oscore enabled helper function */ + if (ctx->p_osc_ctx || ctx->oscore_find_cb) break; #endif /* COAP_OSCORE_SUPPORT */ /* Fall Through */ diff --git a/src/coap_oscore.c b/src/coap_oscore.c index fb440f99be..891832a243 100644 --- a/src/coap_oscore.c +++ b/src/coap_oscore.c @@ -35,7 +35,7 @@ coap_oscore_initiate(coap_session_t *session, coap_oscore_conf_t *oscore_conf) { if (oscore_conf) { oscore_ctx_t *osc_ctx; - if (oscore_conf->recipient_id_count == 0) { + if (oscore_conf->recipient_id == NULL) { coap_log_warn("OSCORE: Recipient ID must be defined for a client\n"); return 0; } @@ -56,7 +56,7 @@ coap_oscore_initiate(coap_session_t *session, coap_oscore_conf_t *oscore_conf) { if (osc_ctx == NULL) { return 0; } - session->recipient_ctx = osc_ctx->recipient_chain; + coap_oscore_session_set_recipient(session, osc_ctx->recipient_chain); session->oscore_encryption = 1; } return 1; @@ -787,7 +787,7 @@ coap_oscore_new_pdu_encrypted_lkd(coap_session_t *session, coap_new_bin_const(cose->partial_iv.s, cose->partial_iv.length); if (association->partial_iv == NULL) goto error; - association->recipient_ctx = rcp_ctx; + coap_oscore_association_set_recipient(association, rcp_ctx); coap_delete_pdu_lkd(association->sent_pdu); if (session->b_2_step != COAP_OSCORE_B_2_NONE || association->just_set_up) { size_t size; @@ -882,6 +882,25 @@ build_and_send_error_pdu(coap_session_t *session, return; } +oscore_ctx_t * +coap_init_oscore_context_from_conf( + coap_oscore_conf_t *oscore_conf +) { + oscore_ctx_t *oscore_ctx = oscore_derive_ctx_from_conf(oscore_conf); + + if (oscore_ctx == NULL) { + goto error; + } + + /* As all is stored in osc_ctx, oscore_conf is no longer needed */ + coap_free_type(COAP_STRING, oscore_conf); + + return oscore_ctx; +error: + coap_delete_oscore_conf(oscore_conf); + return NULL; +} + /* pdu contains incoming message with encrypted COSE ciphertext payload * function returns decrypted message * and verifies signature, if present @@ -924,7 +943,7 @@ coap_oscore_decrypt_pdu(coap_session_t *session, if (opt == NULL) return NULL; - if (session->context->p_osc_ctx == NULL) { + if (session->context->p_osc_ctx == NULL && session->context->oscore_find_cb == NULL) { coap_log_warn("OSCORE: Not enabled\n"); if (!coap_request) coap_handle_event_lkd(session->context, @@ -1024,7 +1043,7 @@ coap_oscore_decrypt_pdu(coap_session_t *session, 0); goto error_no_ack; } - osc_ctx = oscore_find_context(session->context, + osc_ctx = oscore_find_context(session, cose->key_id, &cose->kid_context, NULL, @@ -1034,7 +1053,8 @@ coap_oscore_decrypt_pdu(coap_session_t *session, const uint8_t *ptr; size_t length; /* Appendix B.2 protocol check - Is the recipient key_id known */ - osc_ctx = oscore_find_context(session->context, + + osc_ctx = oscore_find_context(session, cose->key_id, NULL, session->oscore_r2 != 0 ? (uint8_t *)&session->oscore_r2 : NULL, @@ -1107,7 +1127,7 @@ coap_oscore_decrypt_pdu(coap_session_t *session, goto error_no_ack; } /* to be used for encryption of returned response later */ - session->recipient_ctx = rcp_ctx; + coap_oscore_session_set_recipient(session, rcp_ctx); snd_ctx = osc_ctx->sender_context; /* @@ -1133,6 +1153,15 @@ coap_oscore_decrypt_pdu(coap_session_t *session, incoming_seq = coap_decode_var_bytes8(cose->partial_iv.s, cose->partial_iv.length); rcp_ctx->last_seq = incoming_seq; + } else if (session->context->oscore_update_seq_num_cb != NULL) { + /* notify custom credential storage */ + session->context->oscore_update_seq_num_cb( + session, + *rcp_ctx->recipient_id, + *osc_ctx->id_context, + rcp_ctx->last_seq, + rcp_ctx->sliding_window + ); } } else { /* !coap_request */ /* @@ -1292,7 +1321,7 @@ coap_oscore_decrypt_pdu(coap_session_t *session, association->aad = coap_new_bin_const(cose->aad.s, cose->aad.length); if (association->aad == NULL) goto error; - association->recipient_ctx = rcp_ctx; + coap_oscore_association_set_recipient(association, rcp_ctx); } else if (!oscore_new_association(session, NULL, &pdu_token, @@ -1587,6 +1616,28 @@ coap_oscore_decrypt_pdu(coap_session_t *session, NULL, 0); goto error_no_ack; + } else { + if (session->context->oscore_update_seq_num_cb != NULL) { + /* notify echo challenge changed */ + session->context->oscore_update_seq_num_cb( + session, + *rcp_ctx->recipient_id, + *osc_ctx->id_context, + rcp_ctx->last_seq, + rcp_ctx->sliding_window + ); + } + + /* reset echo challenge value to prevent duplicate reuse */ + if (session->context->oscore_update_echo_cb != NULL) { + session->context->oscore_update_echo_cb( + session, + cose->key_id, + cose->kid_context, + NULL, + 1 + ); + } } } else goto error; @@ -1596,8 +1647,32 @@ coap_oscore_decrypt_pdu(coap_session_t *session, session->b_2_step = COAP_OSCORE_B_2_NONE; coap_log_oscore("Appendix B.2 server finished\n"); } - coap_prng_lkd(rcp_ctx->echo_value, sizeof(rcp_ctx->echo_value)); + + int ret = 0; + if (session->context->oscore_update_echo_cb != NULL) { + ret = session->context->oscore_update_echo_cb( + session, + cose->key_id, + cose->kid_context, + rcp_ctx->echo_value, + 0 + ); + } + if (ret <= 0) { + coap_prng_lkd(rcp_ctx->echo_value, sizeof(rcp_ctx->echo_value)); + + if (session->context->oscore_update_echo_cb != NULL) { + session->context->oscore_update_echo_cb( + session, + cose->key_id, + cose->kid_context, + rcp_ctx->echo_value, + 1 + ); + } + } coap_log_oscore("Appendix B.1.2 server plain response\n"); + build_and_send_error_pdu(session, pdu, COAP_RESPONSE_CODE(401), @@ -1983,20 +2058,85 @@ static struct oscore_config_t { }; int -coap_delete_oscore_conf(coap_oscore_conf_t *oscore_conf) { - uint32_t i; +coap_delete_oscore_conf_rcp(coap_oscore_conf_t *conf, + const coap_bin_const_t *recipient_id) { + coap_oscore_rcp_conf_t *current; + coap_oscore_rcp_conf_t *prev = NULL; + + if (conf == NULL || recipient_id == NULL) + return 0; + + current = conf->recipient_id; + while (current) { + if (coap_binary_equal(current->recipient_id, recipient_id)) { + if (prev) + prev->next_recipient = current->next_recipient; + else + conf->recipient_id = current->next_recipient; + coap_delete_bin_const(current->recipient_id); + coap_free_type(COAP_STRING, current); + return 1; + } + prev = current; + current = current->next_recipient; + } + return 0; +} + +static int +coap_delete_oscore_rcp_conf_entry(coap_oscore_rcp_conf_t *rcp_conf) { + if (rcp_conf == NULL) + return 0; + + coap_delete_bin_const(rcp_conf->recipient_id); + coap_free_type(COAP_STRING, rcp_conf); + return 1; +} + +static coap_oscore_rcp_conf_t * +coap_oscore_conf_add_rcp_internal(coap_oscore_conf_t *conf, + const coap_bin_const_t *recipient_id) { + if (conf == NULL || recipient_id == NULL || recipient_id->length > 7) + return NULL; + + coap_oscore_rcp_conf_t **next; + + for (next = &conf->recipient_id; *next != NULL; next = &(*next)->next_recipient) { + if (coap_binary_equal((*next)->recipient_id, recipient_id)) { + return NULL; + } + } + + coap_oscore_rcp_conf_t *rcp = coap_malloc_type(COAP_STRING, sizeof(coap_oscore_rcp_conf_t)); + + if (rcp == NULL) + return NULL; + + memset(rcp, 0, sizeof(coap_oscore_rcp_conf_t)); + rcp->recipient_id = coap_new_bin_const(recipient_id->s, recipient_id->length); + if (rcp->recipient_id == NULL) { + coap_free_type(COAP_STRING, rcp); + return NULL; + } + *next = rcp; + return rcp; +} +int +coap_delete_oscore_conf(coap_oscore_conf_t *oscore_conf) { if (oscore_conf == NULL) return 0; coap_delete_bin_const(oscore_conf->master_secret); coap_delete_bin_const(oscore_conf->master_salt); coap_delete_bin_const(oscore_conf->id_context); - coap_delete_bin_const(oscore_conf->sender_id); - for (i = 0; i < oscore_conf->recipient_id_count; i++) { - coap_delete_bin_const(oscore_conf->recipient_id[i]); + coap_delete_bin_const(oscore_conf->sender_id.sender_id); + coap_oscore_rcp_conf_t *current = oscore_conf->recipient_id; + while (current) { + coap_oscore_rcp_conf_t *next = current->next_recipient; + coap_delete_oscore_rcp_conf_entry(current); + current = next; } - coap_free_type(COAP_STRING, oscore_conf->recipient_id); coap_free_type(COAP_STRING, oscore_conf); return 1; } @@ -2035,21 +2175,18 @@ coap_parse_oscore_conf_mem(coap_str_const_t conf_mem) { if (coap_string_equal(&oscore_config[i].str_keyword, &keyword) != 0 && value.encoding & oscore_config[i].encoding) { if (coap_string_equal(coap_make_str_const("recipient_id"), &keyword)) { + coap_oscore_rcp_conf_t *rcp; + if (value.u.value_bin->length > 7) { coap_log_warn("oscore_conf: Maximum size of recipient_id is 7 bytes\n"); goto error_free_value_bin; } /* Special case as there are potentially multiple entries */ - oscore_conf->recipient_id = - coap_realloc_type(COAP_STRING, - oscore_conf->recipient_id, - sizeof(oscore_conf->recipient_id[0]) * - (oscore_conf->recipient_id_count + 1)); - if (oscore_conf->recipient_id == NULL) { - goto error_free_value_bin; + rcp = coap_oscore_conf_add_rcp_internal(oscore_conf, value.u.value_bin); + coap_delete_bin_const(value.u.value_bin); + if (rcp == NULL) { + goto error; } - oscore_conf->recipient_id[oscore_conf->recipient_id_count++] = - value.u.value_bin; } else { coap_bin_const_t *unused_check; @@ -2119,15 +2256,15 @@ coap_parse_oscore_conf_mem(coap_str_const_t conf_mem) { coap_log_warn("oscore_conf: master_secret not defined\n"); goto error; } - if (!oscore_conf->sender_id) { + if (!oscore_conf->sender_id.sender_id) { coap_log_warn("oscore_conf: sender_id not defined\n"); goto error; } - if (oscore_conf->sender_id->length > 7) { + if (oscore_conf->sender_id.sender_id->length > 7) { coap_log_warn("oscore_conf: Maximum size of sender_id is 7 bytes\n"); goto error; } - if (oscore_conf->recipient_id && oscore_conf->recipient_id[0]->length > 7) { + if (oscore_conf->recipient_id && oscore_conf->recipient_id->recipient_id->length > 7) { coap_log_warn("oscore_conf: Maximum size of recipient_id is 7 bytes\n"); goto error; } @@ -2161,10 +2298,6 @@ coap_oscore_init(coap_context_t *c_context, coap_oscore_conf_t *oscore_conf) { goto error; } - /* Free off the recipient_id array */ - coap_free_type(COAP_STRING, oscore_conf->recipient_id); - oscore_conf->recipient_id = NULL; - /* As all is stored in osc_ctx, oscore_conf is no longer needed */ coap_free_type(COAP_STRING, oscore_conf); @@ -2189,6 +2322,45 @@ coap_delete_oscore_associations(coap_session_t *session) { oscore_delete_server_associations(session); } +void +coap_oscore_session_set_recipient(coap_session_t *session, oscore_recipient_ctx_t *recipient) { + if (session == NULL) + return; + + if (session->recipient_ctx == recipient) { + /* already attached */ + return; + } + + if (session->recipient_ctx != NULL) { + oscore_release_recipient(&session->recipient_ctx); + } + + if (recipient) { + /* attach recipient */ + session->recipient_ctx = recipient; + session->recipient_ctx->ref++; + return; + } +} + +void +coap_oscore_association_set_recipient(oscore_association_t *association, + oscore_recipient_ctx_t *recipient) { + if (association == NULL) + return; + + if (association->recipient_ctx == recipient) + return; + + if (association->recipient_ctx != NULL) { + association->recipient_ctx->ref--; + } + + association->recipient_ctx = recipient; + association->recipient_ctx->ref++; +} + coap_oscore_conf_t * coap_new_oscore_conf(coap_str_const_t conf_mem, coap_oscore_save_seq_num_t save_seq_num_func, @@ -2268,8 +2440,10 @@ coap_new_oscore_recipient_lkd(coap_context_t *context, coap_lock_check_locked(); if (context->p_osc_ctx == NULL) return 0; - if (oscore_add_recipient(context->p_osc_ctx, recipient_id, 0) == NULL) + oscore_recipient_ctx_t *recipient = oscore_add_recipient(context->p_osc_ctx, recipient_id, 0); + if (recipient == NULL) return 0; + oscore_reference_recipient(recipient); return 1; } @@ -2295,6 +2469,88 @@ coap_delete_oscore_recipient_lkd(coap_context_t *context, return oscore_delete_recipient(context->p_osc_ctx, recipient_id); } +void +coap_oscore_register_external_handlers( + coap_context_t *context, + coap_oscore_find_handler_t find_handler, + coap_oscore_update_seq_num_handler_t update_seq_num_handler, + coap_oscore_update_echo_handler_t update_echo_handler) { + if (context == NULL) + return; + + coap_lock_lock(return); + context->oscore_find_cb = find_handler; + context->oscore_update_seq_num_cb = update_seq_num_handler; + context->oscore_update_echo_cb = update_echo_handler; + coap_lock_unlock(); +} + +void +coap_oscore_rcp_conf_set_echo(coap_oscore_rcp_conf_t *rcp_conf, uint8_t echo_value[8]) { + if (echo_value) { + memcpy(rcp_conf->echo_value, echo_value, sizeof(rcp_conf->echo_value)); + } else { + memset(rcp_conf->echo_value, 0, sizeof(rcp_conf->echo_value)); + } +} + +void +coap_oscore_rcp_conf_set_seq_num(coap_oscore_rcp_conf_t *rcp_conf, uint64_t seq_num, + uint64_t seq_num_window) { + rcp_conf->sliding_window = seq_num_window; + rcp_conf->last_seq = seq_num; + /* set window as initialized */ + rcp_conf->window_initialized = 1; +} + +coap_oscore_rcp_conf_t * +coap_oscore_conf_add_rcp(coap_oscore_conf_t *conf, + const coap_bin_const_t *recipient_id) { + coap_lock_lock(return NULL); + coap_oscore_rcp_conf_t *result = coap_oscore_conf_add_rcp_internal(conf, recipient_id); + coap_lock_unlock(); + return result; +} + +coap_oscore_snd_conf_t * +coap_oscore_conf_set_snd(coap_oscore_conf_t *conf, const coap_bin_const_t *sender_id) { + coap_bin_const_t *new_sender_id; + + coap_lock_lock(return NULL); + if (conf == NULL || sender_id == NULL || sender_id->length > 7) { + return NULL; + } + + new_sender_id = coap_new_bin_const(sender_id->s, sender_id->length); + if (new_sender_id == NULL) { + return NULL; + } + + if (conf->sender_id.sender_id) { + coap_delete_bin_const(conf->sender_id.sender_id); + } + conf->sender_id.sender_id = new_sender_id; + coap_lock_unlock(); + return &conf->sender_id; +} + +void +coap_oscore_conf_set_context_id(coap_oscore_conf_t *conf, + const coap_bin_const_t *context_id) { + coap_lock_lock(return); + coap_bin_const_t *id_context = coap_new_bin_const(context_id->s, context_id->length); + if (id_context == NULL) { + return; + } + + if (conf->id_context) { + coap_delete_bin_const(conf->id_context); + } + + conf->id_context = id_context; + coap_lock_unlock(); +} + /** @} */ #else /* !COAP_OSCORE_SUPPORT */ @@ -2455,4 +2711,60 @@ coap_delete_oscore_recipient(coap_context_t *context, return 0; } +void +coap_oscore_register_external_handlers( + coap_context_t *context, + coap_oscore_find_handler_t find_handler, + coap_oscore_update_seq_num_handler_t update_seq_num_handler, + coap_oscore_update_echo_handler_t update_echo_handler) { + (void)context; + (void)find_handler; + (void)update_seq_num_handler; + (void)update_echo_handler; +} + +void +coap_oscore_rcp_conf_set_echo(coap_oscore_rcp_conf_t *rcp_conf, uint8_t echo_value[8]) { + (void)rcp_conf; + (void)echo_value; +} + +void +coap_oscore_rcp_conf_set_seq_num(coap_oscore_rcp_conf_t *rcp_conf, uint64_t seq_num, + uint64_t seq_num_window) { + (void)rcp_conf; + (void)seq_num; + (void)seq_num_window; +} + +coap_oscore_rcp_conf_t * +coap_oscore_conf_add_rcp(coap_oscore_conf_t *conf, + const coap_bin_const_t *recipient_id) { + (void)conf; + (void)recipient_id; + return NULL; +} + +int +coap_delete_oscore_conf_rcp(coap_oscore_conf_t *conf, + const coap_bin_const_t *recipient_id) { + (void)conf; + (void)recipient_id; + return 0; +} + +coap_oscore_snd_conf_t * +coap_oscore_conf_set_snd(coap_oscore_conf_t *conf, const coap_bin_const_t *sender_id) { + (void)conf; + (void)sender_id; + return NULL; +} + +void +coap_oscore_conf_set_context_id(coap_oscore_conf_t *conf, + const coap_bin_const_t *context_id) { + (void)conf; + (void)context_id; +} + #endif /* !COAP_OSCORE_SUPPORT */ diff --git a/src/coap_session.c b/src/coap_session.c index 56283e8921..1411491aff 100644 --- a/src/coap_session.c +++ b/src/coap_session.c @@ -621,6 +621,7 @@ coap_session_mfree(coap_session_t *session) { #endif /* COAP_SERVER_SUPPORT */ #if COAP_OSCORE_SUPPORT coap_delete_oscore_associations(session); + oscore_release_recipient(&session->recipient_ctx); #endif /* COAP_OSCORE_SUPPORT */ #if COAP_WS_SUPPORT coap_free_type(COAP_STRING, session->ws); diff --git a/src/coap_subscribe.c b/src/coap_subscribe.c index e9d9d6ee05..b5b7774ed7 100644 --- a/src/coap_subscribe.c +++ b/src/coap_subscribe.c @@ -307,7 +307,7 @@ coap_persist_observe_add_lkd(coap_context_t *context, } else goto oscore_fail; - osc_ctx = oscore_find_context(session->context, oscore_key_id, + osc_ctx = oscore_find_context(session, oscore_key_id, have_id_context ? &id_context : NULL, NULL, &session->recipient_ctx); if (osc_ctx) { diff --git a/src/oscore/oscore_context.c b/src/oscore/oscore_context.c index c2a1c70fa0..6bec1b3a24 100644 --- a/src/oscore/oscore_context.c +++ b/src/oscore/oscore_context.c @@ -52,6 +52,8 @@ static void oscore_enter_context(coap_context_t *c_context, oscore_ctx_t *osc_ctx); +static int oscore_context_release_recipients(oscore_ctx_t *osc_ctx); + static size_t compose_info(uint8_t *buffer, size_t buf_size, @@ -104,22 +106,60 @@ oscore_bytes_equal(uint8_t *a_ptr, static void oscore_enter_context(coap_context_t *c_context, oscore_ctx_t *osc_ctx) { if (c_context->p_osc_ctx) { - oscore_ctx_t *prev = c_context->p_osc_ctx; - oscore_ctx_t *next = c_context->p_osc_ctx->next; + oscore_ctx_t *head = c_context->p_osc_ctx; + oscore_ctx_t *prev = head; + oscore_ctx_t *next = head->next; - while (next) { + /* verify if oscore context is already attached */ + if (head == osc_ctx) { + return; + } + while (next != head) { + if (next == osc_ctx) { + return; + } prev = next; next = next->next; } prev->next = osc_ctx; - } else + osc_ctx->next = head; + } else { c_context->p_osc_ctx = osc_ctx; + osc_ctx->next = osc_ctx; + } + + /* increase ref counters of all recipients */ + for (oscore_recipient_ctx_t *recipient = osc_ctx->recipient_chain; recipient != NULL; + recipient = recipient->next_recipient) { + oscore_reference_recipient(recipient); + } } static void oscore_free_recipient(oscore_recipient_ctx_t *recipient) { - coap_delete_bin_const(recipient->recipient_id); + assert(recipient->ref > 0); + if (--recipient->ref > 0) { + return; + } + + /* remove recipient from oscore context chain if attached */ + if (recipient->osc_ctx) { + if (recipient->osc_ctx->recipient_chain == recipient) { + recipient->osc_ctx->recipient_chain = recipient->next_recipient; + } else { + oscore_recipient_ctx_t *prev = recipient->osc_ctx->recipient_chain; + + while (prev && prev->next_recipient != recipient) { + prev = prev->next_recipient; + } + if (prev) { + prev->next_recipient = recipient->next_recipient; + } + } + } + coap_delete_bin_const(recipient->recipient_key); + coap_delete_bin_const(recipient->recipient_id); coap_free_type(COAP_OSCORE_REC, recipient); } @@ -152,28 +192,116 @@ oscore_free_contexts(coap_context_t *c_context) { while (c_context->p_osc_ctx) { oscore_ctx_t *osc_ctx = c_context->p_osc_ctx; - c_context->p_osc_ctx = osc_ctx->next; + if (osc_ctx->next == osc_ctx) { + c_context->p_osc_ctx = NULL; + } else { + oscore_ctx_t *tail = osc_ctx; + c_context->p_osc_ctx = osc_ctx->next; + while (tail->next != osc_ctx) { + tail = tail->next; + } + tail->next = c_context->p_osc_ctx; + } + osc_ctx->next = NULL; + + /* + * Verify if all recipients can be released, if not + * defer freeing of context until later when all recipients are released. + * The oscore context is flagged as ready to be freed by removing + * the oscore context from the coap context. + */ + if (oscore_context_release_recipients(osc_ctx) > 0) { + oscore_free_context(osc_ctx); + } + } +} - oscore_free_context(osc_ctx); +/** + * Function checks attached recipients, if any + * attached recipient has a reference counter higher then 1, + * it is referenced by another active session or association and can not be freed yet, + * otherwise will release the recipient. + * + * @param osc_ctx The OSCORE context to check attached recipients for. + * @return @c 1 if all recipients are released, + * @c 0 if some recipients are still referenced externally and can not be released yet, + */ +static int +oscore_context_release_recipients(oscore_ctx_t *osc_ctx) { + int ok = 1; + + oscore_recipient_ctx_t *prev = NULL; + oscore_recipient_ctx_t *next = osc_ctx->recipient_chain; + + while (next != NULL) { + /* if reference is 1 or lower, ready to be freed */ + if (next->ref <= 1) { + oscore_recipient_ctx_t *to_free = next; + next = next->next_recipient; + oscore_free_recipient(to_free); + continue; + } + + /* + * Recipient is still attached to an oscore association or session, + * can not be freed yet. Decrease ref counter and keep in chain. + */ + next->ref--; + ok = 0; + + /* keep recipient in chain, since still referenced by other + session or association */ + if (prev != NULL) { + prev->next_recipient = next; + } else { + osc_ctx->recipient_chain = next; + } + prev = next; + next = next->next_recipient; } + return ok; } int oscore_remove_context(coap_context_t *c_context, oscore_ctx_t *osc_ctx) { - oscore_ctx_t *prev = NULL; - oscore_ctx_t *next = c_context->p_osc_ctx; - while (next) { + oscore_ctx_t *head = c_context->p_osc_ctx; + oscore_ctx_t *prev; + oscore_ctx_t *next; + + if (head == NULL) + return 0; + + prev = head; + next = head; + do { if (next == osc_ctx) { - if (prev != NULL) + if (next->next == next) { + c_context->p_osc_ctx = NULL; + } else { + while (prev->next != next) { + prev = prev->next; + } prev->next = next->next; - else - c_context->p_osc_ctx = next->next; - oscore_free_context(next); + if (next == c_context->p_osc_ctx) + c_context->p_osc_ctx = next->next; + } + next->next = NULL; + /* + * Only free context if no recipient is still referenced by + * an association or session. Otherwise defer context to be + * freed after all references are resolved. + */ + if (oscore_context_release_recipients(osc_ctx) <= 0) { + return 2; + } + + /* all related recipient ctx attachments are released */ + osc_ctx->recipient_chain = NULL; + oscore_free_context(osc_ctx); return 1; } - prev = next; next = next->next; - } + } while (next != head); return 0; } @@ -184,16 +312,57 @@ oscore_remove_context(coap_context_t *c_context, oscore_ctx_t *osc_ctx) { * Updates recipient_ctx. */ oscore_ctx_t * -oscore_find_context(const coap_context_t *c_context, +oscore_find_context(const coap_session_t *session, const coap_bin_const_t rcpkey_id, const coap_bin_const_t *ctxkey_id, uint8_t *oscore_r2, oscore_recipient_ctx_t **recipient_ctx) { - oscore_ctx_t *pt = c_context->p_osc_ctx; + if (session->context->oscore_find_cb != NULL && ctxkey_id != NULL) { + oscore_ctx_t *tmp_ctx = NULL; + coap_oscore_conf_t *oscore_conf = session->context->oscore_find_cb(session, rcpkey_id, + *ctxkey_id); + + if (oscore_conf) { + tmp_ctx = coap_init_oscore_context_from_conf(oscore_conf); + } + + if (tmp_ctx != NULL) { + /* + * find matching recipient and remove others if multiple recipients + * have been provided. ensures unneeded memory is freed early + * to prevent memory leaks and unnecessary memory usage. + */ + if (tmp_ctx->recipient_chain != NULL && + tmp_ctx->recipient_chain->next_recipient != NULL) { + oscore_recipient_ctx_t *rcp = tmp_ctx->recipient_chain; + oscore_recipient_ctx_t *ref = NULL; + while (rcp != NULL) { + ref = rcp; + rcp = rcp->next_recipient; + if (coap_binary_equal(ref->recipient_id, &rcpkey_id)) { + ref->next_recipient = NULL; + *recipient_ctx = ref; + } else { + coap_delete_bin_const(ref->recipient_key); + coap_delete_bin_const(ref->recipient_id); + coap_free_type(COAP_OSCORE_REC, (void *)ref); + } + } + } else { + *recipient_ctx = tmp_ctx->recipient_chain; + } + tmp_ctx->recipient_chain = *recipient_ctx; + return tmp_ctx; + } + } + + oscore_ctx_t *pt = session->context->p_osc_ctx; *recipient_ctx = NULL; assert(rcpkey_id.length == 0 || rcpkey_id.s != NULL); - while (pt != NULL) { + if (pt == NULL) + return NULL; + do { int ok = 0; oscore_recipient_ctx_t *rpt = pt->recipient_chain; @@ -228,7 +397,7 @@ oscore_find_context(const coap_context_t *c_context, rpt = rpt->next_recipient; } /* while rpt */ pt = pt->next; - } /* end while */ + } while (pt != session->context->p_osc_ctx); /* end while */ return NULL; } @@ -511,11 +680,21 @@ oscore_duplicate_ctx(coap_context_t *c_context, return NULL; } +static void +oscore_conf_free_rcp_chain(coap_oscore_rcp_conf_t *rcp) { + while (rcp) { + coap_oscore_rcp_conf_t *next = rcp->next_recipient; + + coap_delete_bin_const(rcp->recipient_id); + coap_free_type(COAP_STRING, rcp); + rcp = next; + } +} + oscore_ctx_t * -oscore_derive_ctx(coap_context_t *c_context, coap_oscore_conf_t *oscore_conf) { +oscore_derive_ctx_from_conf(coap_oscore_conf_t *oscore_conf) { oscore_ctx_t *osc_ctx = NULL; oscore_sender_ctx_t *sender_ctx = NULL; - size_t i; osc_ctx = coap_malloc_type(COAP_OSCORE_COM, sizeof(oscore_ctx_t)); if (osc_ctx == NULL) @@ -547,12 +726,12 @@ oscore_derive_ctx(coap_context_t *c_context, coap_oscore_conf_t *oscore_conf) { if (oscore_conf->break_sender_key) /* Interop testing */ sender_ctx->sender_key = oscore_build_key(osc_ctx, - oscore_conf->sender_id, + oscore_conf->sender_id.sender_id, coap_make_str_const("BAD"), CONTEXT_KEY_LEN); else sender_ctx->sender_key = oscore_build_key(osc_ctx, - oscore_conf->sender_id, + oscore_conf->sender_id.sender_id, coap_make_str_const("Key"), CONTEXT_KEY_LEN); if (!sender_ctx->sender_key) @@ -576,28 +755,68 @@ oscore_derive_ctx(coap_context_t *c_context, coap_oscore_conf_t *oscore_conf) { sender_ctx->next_seq = oscore_conf->start_seq_num - (oscore_conf->start_seq_num % (oscore_conf->ssn_freq > 0 ? oscore_conf->ssn_freq : 1)); - sender_ctx->sender_id = oscore_conf->sender_id; + sender_ctx->sender_id = oscore_conf->sender_id.sender_id; sender_ctx->seq = oscore_conf->start_seq_num; - for (i = 0; i < oscore_conf->recipient_id_count; i++) { - if (oscore_add_recipient(osc_ctx, oscore_conf->recipient_id[i], - oscore_conf->break_recipient_key) == NULL) { + for (coap_oscore_rcp_conf_t *next = oscore_conf->recipient_id; next != NULL; + next = next->next_recipient) { + oscore_recipient_ctx_t *rcp_ctx = oscore_add_recipient(osc_ctx, next->recipient_id, + oscore_conf->break_recipient_key); + if (rcp_ctx == NULL) { coap_log_warn("OSCORE: Failed to add Client ID\n"); + next->recipient_id = NULL; goto error; } + next->recipient_id = NULL; + + rcp_ctx->sliding_window = next->sliding_window; + rcp_ctx->last_seq = next->last_seq; + rcp_ctx->initial_state = next->window_initialized ? 0 : 1; + memcpy(rcp_ctx->echo_value, next->echo_value, sizeof(next->echo_value)); } - oscore_log_context(osc_ctx, "Common context"); - oscore_enter_context(c_context, osc_ctx); + oscore_conf_free_rcp_chain(oscore_conf->recipient_id); + oscore_log_context(osc_ctx, "Common context"); return osc_ctx; - error: + if (osc_ctx) { + coap_delete_bin_const(osc_ctx->common_iv); + /* cleanup recipient chain */ + while (osc_ctx->recipient_chain) { + oscore_recipient_ctx_t *next = osc_ctx->recipient_chain->next_recipient; + + oscore_free_recipient(osc_ctx->recipient_chain); + osc_ctx->recipient_chain = next; + } + } coap_free_type(COAP_OSCORE_COM, osc_ctx); + if (sender_ctx) { + coap_delete_bin_const(sender_ctx->sender_key); + } + oscore_conf_free_rcp_chain(oscore_conf->recipient_id); coap_free_type(COAP_OSCORE_SEN, sender_ctx); return NULL; } +int +oscore_add_context(coap_context_t *c_context, oscore_ctx_t *osc_ctx) { + oscore_enter_context(c_context, osc_ctx); + return 1; +} + +int +oscore_is_context_attached(const oscore_ctx_t *osc_ctx) { + return osc_ctx->next != NULL; +} + +oscore_ctx_t * +oscore_derive_ctx(coap_context_t *c_context, coap_oscore_conf_t *oscore_conf) { + oscore_ctx_t *osc_ctx = oscore_derive_ctx_from_conf(oscore_conf); + oscore_enter_context(c_context, osc_ctx); + return osc_ctx; +} + oscore_recipient_ctx_t * oscore_add_recipient(oscore_ctx_t *osc_ctx, coap_bin_const_t *rid, uint32_t break_key) { @@ -606,6 +825,7 @@ oscore_add_recipient(oscore_ctx_t *osc_ctx, coap_bin_const_t *rid, if (rid->length > 7) { coap_log_warn("oscore_add_recipient: Maximum size of recipient_id is 7 bytes\n"); + coap_delete_bin_const(rid); return NULL; } /* Check this is not a duplicate recipient id */ @@ -617,9 +837,8 @@ oscore_add_recipient(oscore_ctx_t *osc_ctx, coap_bin_const_t *rid, } rcp_ctx = rcp_ctx->next_recipient; } - recipient_ctx = (oscore_recipient_ctx_t *)coap_malloc_type( - COAP_OSCORE_REC, - sizeof(oscore_recipient_ctx_t)); + recipient_ctx = coap_malloc_type(COAP_OSCORE_REC, + sizeof(oscore_recipient_ctx_t)); if (recipient_ctx == NULL) return NULL; memset(recipient_ctx, 0, sizeof(oscore_recipient_ctx_t)); @@ -672,9 +891,34 @@ oscore_delete_recipient(oscore_ctx_t *osc_ctx, coap_bin_const_t *rid) { return 0; } +void +oscore_reference_recipient(oscore_recipient_ctx_t *recipient) { + recipient->ref++; +} + +void +oscore_release_recipient(oscore_recipient_ctx_t **recipient) { + if (recipient == NULL || *recipient == NULL) + return; + + /* ensure oscore context is freed if not attached to coap context */ + oscore_ctx_t *osc_ctx = (*recipient)->osc_ctx; + oscore_free_recipient(*recipient); + /* + * Free temporary oscore context if not attached to a coap context + * and no recipients attached anymore. + */ + if (!oscore_is_context_attached(osc_ctx) && osc_ctx->recipient_chain == NULL) { + oscore_free_context(osc_ctx); + } + *recipient = NULL; +} + void oscore_free_association(oscore_association_t *association) { if (association) { + oscore_release_recipient(&association->recipient_ctx); + coap_delete_pdu_lkd(association->sent_pdu); coap_delete_bin_const(association->token); coap_delete_bin_const(association->aad); @@ -701,7 +945,7 @@ oscore_new_association(coap_session_t *session, return 0; memset(association, 0, sizeof(oscore_association_t)); - association->recipient_ctx = recipient_ctx; + coap_oscore_association_set_recipient(association, recipient_ctx); association->is_observe = is_observe; association->just_set_up = 1; diff --git a/tests/test_oscore.c b/tests/test_oscore.c index 28558de962..9ddb8e1591 100644 --- a/tests/test_oscore.c +++ b/tests/test_oscore.c @@ -1028,6 +1028,571 @@ t_oscore_c_8_2(void) { coap_free(session); } +/************************************************************************ + ** OSCORE credential storage tests + ************************************************************************/ + +static void +t_convert_1(void) { + static const char conf_data[] = + "master_secret,hex,\"0102030405060708090a0b0c0d0e0f10\"\n" + "master_salt,hex,\"9e7ca92223786340\"\n" + "sender_id,ascii,\"server\"\n" + "recipient_id,ascii,\"client\"\n" + "recipient_id,ascii,\"client1\"\n" + "recipient_id,ascii,\"client2\"\n" + "replay_window,integer,30\n" + "aead_alg,integer,10\n" + "hkdf_alg,integer,-10\n"; + const coap_str_const_t conf = { sizeof(conf_data)-1, + (const uint8_t *)conf_data + }; + coap_oscore_conf_t *oscore_conf; + oscore_ctx_t *osc_ctx; + oscore_recipient_ctx_t *rcp; + int rcp_count; + + oscore_conf = coap_new_oscore_conf(conf, NULL, NULL, 0); + FailIf_CU_ASSERT_PTR_NOT_NULL(oscore_conf); + CU_ASSERT(coap_context_oscore_server(ctx, oscore_conf) == 1); + + osc_ctx = ctx->p_osc_ctx; + FailIf_CU_ASSERT_PTR_NOT_NULL(osc_ctx); + FailIf_CU_ASSERT_PTR_NOT_NULL(osc_ctx->sender_context); + + /* sender_id == "server" */ + CU_ASSERT(osc_ctx->sender_context->sender_id->length == 6); + CU_ASSERT(memcmp(osc_ctx->sender_context->sender_id->s, "server", 6) == 0); + + /* 3 recipients in chain */ + rcp_count = 0; + for (rcp = osc_ctx->recipient_chain; rcp; rcp = rcp->next_recipient) + rcp_count++; + CU_ASSERT(rcp_count == 3); + + CU_ASSERT(osc_ctx->aead_alg == 10); + CU_ASSERT(osc_ctx->hkdf_alg == -10); + CU_ASSERT(osc_ctx->replay_window_size == 30); + +fail: + oscore_free_contexts(ctx); +} + +/* Common config for ref-counting tests */ +#define REF_CONF_DATA \ + "master_secret,hex,\"0102030405060708090a0b0c0d0e0f10\"\n" \ + "master_salt,hex,\"9e7ca92223786340\"\n" \ + "sender_id,ascii,\"server\"\n" \ + "recipient_id,ascii,\"client\"\n" + +/* + * t_ref_1: Context attached to coap_context. + * Recipient ref is managed by session attach/release. + * After all sessions release, recipient is freed but osc_ctx stays. + */ +static void +t_ref_1(void) { + static const char conf_data[] = REF_CONF_DATA; + const coap_str_const_t conf = { sizeof(conf_data)-1, + (const uint8_t *)conf_data + }; + coap_oscore_conf_t *oscore_conf; + oscore_ctx_t *osc_ctx; + oscore_recipient_ctx_t *rcp; + + oscore_conf = coap_new_oscore_conf(conf, NULL, NULL, 0); + FailIf_CU_ASSERT_PTR_NOT_NULL(oscore_conf); + CU_ASSERT(coap_context_oscore_server(ctx, oscore_conf) == 1); + + osc_ctx = ctx->p_osc_ctx; + FailIf_CU_ASSERT_PTR_NOT_NULL(osc_ctx); + + /* After enter_context, recipient ref == 1 */ + rcp = osc_ctx->recipient_chain; + FailIf_CU_ASSERT_PTR_NOT_NULL(rcp); + CU_ASSERT(rcp->ref == 1); + + /* Simulate session attaching recipient: ref becomes 2 */ + rcp->ref++; + CU_ASSERT(rcp->ref == 2); + + /* oscore_free_contexts releases the enter_context ref (ref 2 -> 1) */ + oscore_free_contexts(ctx); + + /* Context detached but recipient still alive (ref == 1) */ + CU_ASSERT_PTR_NULL(ctx->p_osc_ctx); + CU_ASSERT(rcp->ref == 1); + + /* Session releases its ref — recipient freed */ + oscore_release_recipient(&rcp); + CU_ASSERT_PTR_NULL(rcp); + return; + +fail: + oscore_free_contexts(ctx); +} + +/* + * t_ref_2: Context NOT attached to coap_context. + * When the only session releases its recipient, both the + * recipient and the oscore context should be freed. + */ +static void +t_ref_2(void) { + static const char conf_data[] = REF_CONF_DATA; + const coap_str_const_t conf = { sizeof(conf_data)-1, + (const uint8_t *)conf_data + }; + coap_oscore_conf_t *oscore_conf; + oscore_ctx_t *osc_ctx; + oscore_recipient_ctx_t *rcp; + + oscore_conf = coap_new_oscore_conf(conf, NULL, NULL, 0); + FailIf_CU_ASSERT_PTR_NOT_NULL(oscore_conf); + CU_ASSERT(coap_context_oscore_server(ctx, oscore_conf) == 1); + + osc_ctx = ctx->p_osc_ctx; + FailIf_CU_ASSERT_PTR_NOT_NULL(osc_ctx); + rcp = osc_ctx->recipient_chain; + FailIf_CU_ASSERT_PTR_NOT_NULL(rcp); + + /* Simulate session attach: ref 1 -> 2 */ + rcp->ref++; + CU_ASSERT(rcp->ref == 2); + + /* Detach context from coap_context (simulates context teardown while + * session still holds a ref). This decrements ref by 1 for each + * recipient via oscore_context_release_recipients. */ + oscore_free_contexts(ctx); + CU_ASSERT_PTR_NULL(ctx->p_osc_ctx); + CU_ASSERT(rcp->ref == 1); + + /* osc_ctx is no longer attached (next == NULL) */ + CU_ASSERT_PTR_NULL(osc_ctx->next); + + /* Session releases — recipient AND context get freed + * (oscore_release_recipient frees context when not attached and + * recipient_chain becomes empty). */ + oscore_release_recipient(&rcp); + CU_ASSERT_PTR_NULL(rcp); + /* osc_ctx is now freed — no further access. */ + return; + +fail: + oscore_free_contexts(ctx); +} + +/* + * t_ref_3: Add/remove recipient via public API + * (coap_new_oscore_recipient / coap_delete_oscore_recipient). + * Verify ref counting is consistent. + */ +static void +t_ref_3(void) { + static const char conf_data[] = REF_CONF_DATA; + const coap_str_const_t conf = { sizeof(conf_data)-1, + (const uint8_t *)conf_data + }; + coap_oscore_conf_t *oscore_conf; + oscore_ctx_t *osc_ctx; + oscore_recipient_ctx_t *rcp; + coap_bin_const_t *peer_id; + coap_bin_const_t peer_id_cmp = { 5, (const uint8_t *)"peer1" }; + int rcp_count; + + oscore_conf = coap_new_oscore_conf(conf, NULL, NULL, 0); + FailIf_CU_ASSERT_PTR_NOT_NULL(oscore_conf); + CU_ASSERT(coap_context_oscore_server(ctx, oscore_conf) == 1); + + osc_ctx = ctx->p_osc_ctx; + FailIf_CU_ASSERT_PTR_NOT_NULL(osc_ctx); + + /* Initially 1 recipient ("client") with ref == 1 */ + CU_ASSERT_PTR_NOT_NULL(osc_ctx->recipient_chain); + CU_ASSERT(osc_ctx->recipient_chain->ref == 1); + + /* Add "peer1" via public API — ownership of peer_id moves in */ + peer_id = coap_new_bin_const((const uint8_t *)"peer1", 5); + FailIf_CU_ASSERT_PTR_NOT_NULL(peer_id); + CU_ASSERT(coap_new_oscore_recipient(ctx, peer_id) == 1); + + rcp_count = 0; + for (rcp = osc_ctx->recipient_chain; rcp; rcp = rcp->next_recipient) + rcp_count++; + CU_ASSERT(rcp_count == 2); + + /* Find peer1 and verify ref */ + for (rcp = osc_ctx->recipient_chain; rcp; rcp = rcp->next_recipient) { + if (rcp->recipient_id->length == peer_id_cmp.length && + memcmp(rcp->recipient_id->s, peer_id_cmp.s, peer_id_cmp.length) == 0) + break; + } + FailIf_CU_ASSERT_PTR_NOT_NULL(rcp); + CU_ASSERT(rcp->ref == 1); + + /* Remove "peer1" via public API */ + CU_ASSERT(coap_delete_oscore_recipient(ctx, &peer_id_cmp) == 1); + + rcp_count = 0; + for (rcp = osc_ctx->recipient_chain; rcp; rcp = rcp->next_recipient) + rcp_count++; + CU_ASSERT(rcp_count == 1); + + /* Original "client" still there */ + CU_ASSERT_PTR_NOT_NULL(osc_ctx->recipient_chain); + +fail: + oscore_free_contexts(ctx); +} + +/* + * t_conf_1: Verify setter for the score_conf + */ +static void +t_conf_1(void) { + static const char conf_data[] = + "master_secret,hex,\"0102030405060708090a0b0c0d0e0f10\"\n" + "sender_id,ascii,\"snd\"\n" + "recipient_id,ascii,\"rcp1\"\n"; + const coap_str_const_t conf = { sizeof(conf_data)-1, + (const uint8_t *)conf_data + }; + coap_oscore_conf_t *oscore_conf; + coap_oscore_rcp_conf_t *rcp; + coap_oscore_snd_conf_t *snd; + coap_bin_const_t rcp2_id = { 4, (const uint8_t *)"rcp2" }; + coap_bin_const_t rcp3_id = { 4, (const uint8_t *)"rcp3" }; + coap_bin_const_t rcp1_id = { 4, (const uint8_t *)"rcp1" }; + coap_bin_const_t new_snd_id = { 6, (const uint8_t *)"newsnd" }; + coap_bin_const_t ctx_id = { 3, (const uint8_t *)"ctx" }; + uint8_t echo_val[8] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08 }; + int rcp_count; + + oscore_conf = coap_new_oscore_conf(conf, NULL, NULL, 0); + FailIf_CU_ASSERT_PTR_NOT_NULL(oscore_conf); + + /* --- coap_oscore_conf_add_rcp --- */ + + /* Initially 1 recipient ("rcp1") from config */ + CU_ASSERT_PTR_NOT_NULL(oscore_conf->recipient_id); + + /* Add "rcp2" */ + rcp = coap_oscore_conf_add_rcp(oscore_conf, &rcp2_id); + CU_ASSERT_PTR_NOT_NULL(rcp); + + /* Add "rcp3" */ + rcp = coap_oscore_conf_add_rcp(oscore_conf, &rcp3_id); + CU_ASSERT_PTR_NOT_NULL(rcp); + + /* Duplicate add returns NULL */ + CU_ASSERT_PTR_NULL(coap_oscore_conf_add_rcp(oscore_conf, &rcp2_id)); + + rcp_count = 0; + for (rcp = oscore_conf->recipient_id; rcp; rcp = rcp->next_recipient) + rcp_count++; + CU_ASSERT(rcp_count == 3); + + /* --- coap_delete_oscore_conf_rcp --- */ + + /* Remove "rcp2" */ + CU_ASSERT(coap_delete_oscore_conf_rcp(oscore_conf, &rcp2_id) == 1); + + /* Double remove returns 0 */ + CU_ASSERT(coap_delete_oscore_conf_rcp(oscore_conf, &rcp2_id) == 0); + + rcp_count = 0; + for (rcp = oscore_conf->recipient_id; rcp; rcp = rcp->next_recipient) + rcp_count++; + CU_ASSERT(rcp_count == 2); + + /* Remove "rcp1" (head of chain) */ + CU_ASSERT(coap_delete_oscore_conf_rcp(oscore_conf, &rcp1_id) == 1); + + rcp_count = 0; + for (rcp = oscore_conf->recipient_id; rcp; rcp = rcp->next_recipient) + rcp_count++; + CU_ASSERT(rcp_count == 1); + + /* Remaining is "rcp3" */ + FailIf_CU_ASSERT_PTR_NOT_NULL(oscore_conf->recipient_id); + FailIf_CU_ASSERT_PTR_NOT_NULL(oscore_conf->recipient_id->recipient_id); + CU_ASSERT(oscore_conf->recipient_id->recipient_id->length == 4); + CU_ASSERT(memcmp(oscore_conf->recipient_id->recipient_id->s, "rcp3", 4) == 0); + + /* --- coap_oscore_conf_set_snd --- */ + + /* Original sender_id is "snd" */ + CU_ASSERT_PTR_NOT_NULL(oscore_conf->sender_id.sender_id); + CU_ASSERT(oscore_conf->sender_id.sender_id->length == 3); + + /* Override with "newsnd" */ + snd = coap_oscore_conf_set_snd(oscore_conf, &new_snd_id); + CU_ASSERT_PTR_NOT_NULL(snd); + CU_ASSERT(snd == &oscore_conf->sender_id); + CU_ASSERT(oscore_conf->sender_id.sender_id->length == 6); + CU_ASSERT(memcmp(oscore_conf->sender_id.sender_id->s, "newsnd", 6) == 0); + + /* --- coap_oscore_conf_set_context_id --- */ + + /* Initially no id_context */ + CU_ASSERT_PTR_NULL(oscore_conf->id_context); + + coap_oscore_conf_set_context_id(oscore_conf, &ctx_id); + CU_ASSERT_PTR_NOT_NULL(oscore_conf->id_context); + CU_ASSERT(oscore_conf->id_context->length == 3); + CU_ASSERT(memcmp(oscore_conf->id_context->s, "ctx", 3) == 0); + + /* --- coap_oscore_rcp_conf_set_echo / coap_oscore_rcp_conf_set_seq_num --- */ + + /* Re-add a recipient to test echo and seq_num on */ + rcp = coap_oscore_conf_add_rcp(oscore_conf, &rcp1_id); + FailIf_CU_ASSERT_PTR_NOT_NULL(rcp); + + coap_oscore_rcp_conf_set_echo(rcp, echo_val); + CU_ASSERT(memcmp(rcp->echo_value, echo_val, 8) == 0); + + /* Clear echo */ + coap_oscore_rcp_conf_set_echo(rcp, NULL); + { + uint8_t zeros[8] = {0}; + CU_ASSERT(memcmp(rcp->echo_value, zeros, 8) == 0); + } + + coap_oscore_rcp_conf_set_seq_num(rcp, 42, 0xFFFF); + CU_ASSERT(rcp->last_seq == 42); + CU_ASSERT(rcp->sliding_window == 0xFFFF); + CU_ASSERT(rcp->window_initialized == 1); + +fail: + coap_delete_oscore_conf(oscore_conf); +} + +/* + * t_conf_2: Two recipients both within the 7-byte limit — expect success. + */ +static void +t_conf_2(void) { + static const char conf_data[] = + "master_secret,hex,\"0102030405060708090a0b0c0d0e0f10\"\n" + "sender_id,ascii,\"snd\"\n" + "recipient_id,ascii,\"rcp1\"\n" + "recipient_id,ascii,\"rcp2\"\n"; + const coap_str_const_t conf = { sizeof(conf_data)-1, (const uint8_t *)conf_data }; + coap_oscore_conf_t *oscore_conf; + + oscore_conf = coap_new_oscore_conf(conf, NULL, NULL, 0); + CU_ASSERT_PTR_NOT_NULL(oscore_conf); + coap_delete_oscore_conf(oscore_conf); +} + +/* + * t_conf_3: First recipient exceeds 7 bytes — expect failure. + */ +static void +t_conf_3(void) { + static const char conf_data[] = + "master_secret,hex,\"0102030405060708090a0b0c0d0e0f10\"\n" + "sender_id,ascii,\"snd\"\n" + "recipient_id,ascii,\"toolong1\"\n" + "recipient_id,ascii,\"rcp2\"\n"; + const coap_str_const_t conf = { sizeof(conf_data)-1, (const uint8_t *)conf_data }; + + CU_ASSERT_PTR_NULL(coap_new_oscore_conf(conf, NULL, NULL, 0)); +} + +/* + * t_conf_4: Second recipient exceeds 7 bytes — expect failure. + */ +static void +t_conf_4(void) { + static const char conf_data[] = + "master_secret,hex,\"0102030405060708090a0b0c0d0e0f10\"\n" + "sender_id,ascii,\"snd\"\n" + "recipient_id,ascii,\"rcp1\"\n" + "recipient_id,ascii,\"toolong2\"\n"; + const coap_str_const_t conf = { sizeof(conf_data)-1, (const uint8_t *)conf_data }; + + CU_ASSERT_PTR_NULL(coap_new_oscore_conf(conf, NULL, NULL, 0)); +} + +static int t_find_mode; /* 0 = single rcp, 1 = multi rcp, -1 = return NULL */ + +static coap_oscore_conf_t * +test_find_func(const coap_session_t *session, + const coap_bin_const_t rcpkey_id, + const coap_bin_const_t ctxkey_id) { + static const char single_conf[] = + "master_secret,hex,\"0102030405060708090a0b0c0d0e0f10\"\n" + "sender_id,ascii,\"server\"\n" + "recipient_id,ascii,\"client\"\n"; + static const char multi_conf[] = + "master_secret,hex,\"0102030405060708090a0b0c0d0e0f10\"\n" + "sender_id,ascii,\"server\"\n" + "recipient_id,ascii,\"client\"\n" + "recipient_id,ascii,\"client1\"\n" + "recipient_id,ascii,\"client2\"\n"; + coap_str_const_t conf; + + (void)session; + (void)rcpkey_id; + (void)ctxkey_id; + + if (t_find_mode < 0) + return NULL; + + if (t_find_mode > 0) { + conf.s = (const uint8_t *)multi_conf; + conf.length = sizeof(multi_conf) - 1; + } else { + conf.s = (const uint8_t *)single_conf; + conf.length = sizeof(single_conf) - 1; + } + return coap_new_oscore_conf(conf, NULL, NULL, 0); +} + +/* + * t_find_1: External find with single recipient. + * Temporary context is NOT attached to coap_context. + * Freed when recipient is released. + */ +static void +t_find_1(void) { + coap_session_t *session = NULL; + oscore_ctx_t *osc_ctx; + oscore_recipient_ctx_t *rcp_ctx = NULL; + coap_bin_const_t rcpkey_id = { 6, (const uint8_t *)"client" }; + coap_bin_const_t ctxkey_id = { 0, (const uint8_t *)"" }; + + session = coap_malloc_type(COAP_SESSION, sizeof(coap_session_t)); + FailIf_CU_ASSERT_PTR_NOT_NULL(session); + memset(session, 0, sizeof(coap_session_t)); + session->context = ctx; + session->proto = COAP_PROTO_UDP; + session->type = COAP_SESSION_TYPE_CLIENT; + + t_find_mode = 0; + ctx->oscore_find_cb = test_find_func; + + osc_ctx = oscore_find_context(session, rcpkey_id, &ctxkey_id, NULL, &rcp_ctx); + FailIf_CU_ASSERT_PTR_NOT_NULL(osc_ctx); + FailIf_CU_ASSERT_PTR_NOT_NULL(rcp_ctx); + + /* Temporary context NOT attached to coap_context */ + CU_ASSERT(!oscore_is_context_attached(osc_ctx)); + CU_ASSERT_PTR_NULL(ctx->p_osc_ctx); + + /* Single recipient returned */ + CU_ASSERT_PTR_NULL(rcp_ctx->next_recipient); + CU_ASSERT(rcp_ctx->recipient_id->length == 6); + CU_ASSERT(memcmp(rcp_ctx->recipient_id->s, "client", 6) == 0); + + /* Simulate attach (real code does ref++ on association/session) */ + rcp_ctx->ref++; + + /* Release — frees temporary context too (unattached, last recipient) */ + oscore_release_recipient(&rcp_ctx); + CU_ASSERT_PTR_NULL(rcp_ctx); + +fail: + ctx->oscore_find_cb = NULL; + coap_free(session); +} + +/* + * t_find_2: External find with multiple recipients. + * Only the matching recipient is kept; others are freed. + */ +static void +t_find_2(void) { + coap_session_t *session = NULL; + oscore_ctx_t *osc_ctx; + oscore_recipient_ctx_t *rcp_ctx = NULL; + coap_bin_const_t rcpkey_id = { 7, (const uint8_t *)"client1" }; + coap_bin_const_t ctxkey_id = { 0, (const uint8_t *)"" }; + + session = coap_malloc_type(COAP_SESSION, sizeof(coap_session_t)); + FailIf_CU_ASSERT_PTR_NOT_NULL(session); + memset(session, 0, sizeof(coap_session_t)); + session->context = ctx; + session->proto = COAP_PROTO_UDP; + session->type = COAP_SESSION_TYPE_CLIENT; + + t_find_mode = 1; + ctx->oscore_find_cb = test_find_func; + + osc_ctx = oscore_find_context(session, rcpkey_id, &ctxkey_id, NULL, &rcp_ctx); + FailIf_CU_ASSERT_PTR_NOT_NULL(osc_ctx); + FailIf_CU_ASSERT_PTR_NOT_NULL(rcp_ctx); + + /* Temporary context NOT attached */ + CU_ASSERT_PTR_NULL(osc_ctx->next); + + /* Only matching recipient "client1" kept */ + CU_ASSERT(rcp_ctx->recipient_id->length == 7); + CU_ASSERT(memcmp(rcp_ctx->recipient_id->s, "client1", 7) == 0); + CU_ASSERT_PTR_NULL(rcp_ctx->next_recipient); + CU_ASSERT(osc_ctx->recipient_chain == rcp_ctx); + + /* Simulate attach and release */ + rcp_ctx->ref++; + oscore_release_recipient(&rcp_ctx); + CU_ASSERT_PTR_NULL(rcp_ctx); + +fail: + ctx->oscore_find_cb = NULL; + coap_free(session); +} + +/* + * t_find_3: External find returns NULL — falls through to internal storage. + */ +static void +t_find_3(void) { + static const char conf_data[] = REF_CONF_DATA; + const coap_str_const_t conf = { sizeof(conf_data)-1, + (const uint8_t *)conf_data + }; + coap_oscore_conf_t *oscore_conf; + coap_session_t *session = NULL; + oscore_ctx_t *osc_ctx; + oscore_recipient_ctx_t *rcp_ctx = NULL; + coap_bin_const_t rcpkey_id = { 6, (const uint8_t *)"client" }; + coap_bin_const_t ctxkey_id = { 0, (const uint8_t *)"" }; + + oscore_conf = coap_new_oscore_conf(conf, NULL, NULL, 0); + FailIf_CU_ASSERT_PTR_NOT_NULL(oscore_conf); + CU_ASSERT(coap_context_oscore_server(ctx, oscore_conf) == 1); + FailIf_CU_ASSERT_PTR_NOT_NULL(ctx->p_osc_ctx); + + session = coap_malloc_type(COAP_SESSION, sizeof(coap_session_t)); + FailIf_CU_ASSERT_PTR_NOT_NULL(session); + memset(session, 0, sizeof(coap_session_t)); + session->context = ctx; + session->proto = COAP_PROTO_UDP; + session->type = COAP_SESSION_TYPE_CLIENT; + + t_find_mode = -1; + ctx->oscore_find_cb = test_find_func; + + osc_ctx = oscore_find_context(session, rcpkey_id, &ctxkey_id, NULL, &rcp_ctx); + FailIf_CU_ASSERT_PTR_NOT_NULL(osc_ctx); + + /* Context from internal storage — IS attached */ + CU_ASSERT(osc_ctx == ctx->p_osc_ctx); + CU_ASSERT_PTR_NOT_NULL(osc_ctx->next); + + /* Recipient found from internal storage */ + FailIf_CU_ASSERT_PTR_NOT_NULL(rcp_ctx); + FailIf_CU_ASSERT_PTR_NOT_NULL(rcp_ctx->recipient_id); + CU_ASSERT(rcp_ctx->recipient_id->length == 6); + CU_ASSERT(memcmp(rcp_ctx->recipient_id->s, "client", 6) == 0); + +fail: + ctx->oscore_find_cb = NULL; + oscore_free_contexts(ctx); + coap_free(session); +} + /************************************************************************ ** initialization ************************************************************************/ @@ -1078,6 +1643,21 @@ t_init_oscore_tests(void) { OSCORE_TEST(t_oscore_c_7_2); OSCORE_TEST(t_oscore_c_8); OSCORE_TEST(t_oscore_c_8_2); + + OSCORE_TEST(t_convert_1); + + OSCORE_TEST(t_ref_1); + OSCORE_TEST(t_ref_2); + OSCORE_TEST(t_ref_3); + + OSCORE_TEST(t_conf_1); + OSCORE_TEST(t_conf_2); + OSCORE_TEST(t_conf_3); + OSCORE_TEST(t_conf_4); + + OSCORE_TEST(t_find_1); + OSCORE_TEST(t_find_2); + OSCORE_TEST(t_find_3); } return suite[0]; From 371069a7348af168ed51f9cd2322b3561a6a3314 Mon Sep 17 00:00:00 2001 From: Lucien Zuercher Date: Mon, 6 Apr 2026 18:51:25 +0300 Subject: [PATCH 2/3] fixup! oscore: enable external credential storage and safe recipient cleanup --- src/coap_oscore.c | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/coap_oscore.c b/src/coap_oscore.c index 891832a243..3809185d32 100644 --- a/src/coap_oscore.c +++ b/src/coap_oscore.c @@ -2506,9 +2506,7 @@ coap_oscore_rcp_conf_set_seq_num(coap_oscore_rcp_conf_t *rcp_conf, uint64_t seq_ coap_oscore_rcp_conf_t * coap_oscore_conf_add_rcp(coap_oscore_conf_t *conf, const coap_bin_const_t *recipient_id) { - coap_lock_lock(return NULL); coap_oscore_rcp_conf_t *result = coap_oscore_conf_add_rcp_internal(conf, recipient_id); - coap_lock_unlock(); return result; } @@ -2516,7 +2514,6 @@ coap_oscore_snd_conf_t * coap_oscore_conf_set_snd(coap_oscore_conf_t *conf, const coap_bin_const_t *sender_id) { coap_bin_const_t *new_sender_id; - coap_lock_lock(return NULL); if (conf == NULL || sender_id == NULL || sender_id->length > 7) { return NULL; } @@ -2530,14 +2527,12 @@ coap_oscore_conf_set_snd(coap_oscore_conf_t *conf, const coap_bin_const_t *sende coap_delete_bin_const(conf->sender_id.sender_id); } conf->sender_id.sender_id = new_sender_id; - coap_lock_unlock(); return &conf->sender_id; } void coap_oscore_conf_set_context_id(coap_oscore_conf_t *conf, const coap_bin_const_t *context_id) { - coap_lock_lock(return); coap_bin_const_t *id_context = coap_new_bin_const(context_id->s, context_id->length); if (id_context == NULL) { return; @@ -2548,7 +2543,6 @@ coap_oscore_conf_set_context_id(coap_oscore_conf_t *conf, } conf->id_context = id_context; - coap_lock_unlock(); } /** @} */ From 9b9f445136ebc5046a3e0a041487956e613468c1 Mon Sep 17 00:00:00 2001 From: Lucien Zuercher Date: Mon, 6 Apr 2026 18:52:36 +0300 Subject: [PATCH 3/3] fixup! oscore: enable external credential storage and safe recipient cleanup --- man/coap-oscore-conf.txt.in | 8 ++++---- man/coap_oscore.txt.in | 12 ++++++------ tests/test_oscore.c | 26 ++++++++++++++++---------- 3 files changed, 26 insertions(+), 20 deletions(-) diff --git a/man/coap-oscore-conf.txt.in b/man/coap-oscore-conf.txt.in index 3e5040c8d1..81c53ec398 100644 --- a/man/coap-oscore-conf.txt.in +++ b/man/coap-oscore-conf.txt.in @@ -39,13 +39,13 @@ ID) by using the registered external handlers. The dynamic credential files use the following naming format: -* `_.txt` for the OSCORE credential text in the +* _.txt for the OSCORE credential text in the same format as for the *-E* option. -* `__seq.txt` for persisted recipient sequence state +* __seq.txt for persisted recipient sequence state (sequence counter and replay window), stored as two uint64_t values. -* `` is the hexdecimal representation of the OSCORE ID Context. -* `` is the hexdecimal representation of the OSCORE kid or Sender ID +* is the hexdecimal representation of the OSCORE ID Context. +* is the hexdecimal representation of the OSCORE kid or Sender ID for the server. This allows a single server instance to store and load multiple OSCORE diff --git a/man/coap_oscore.txt.in b/man/coap_oscore.txt.in index e0c864381d..43006c122c 100644 --- a/man/coap_oscore.txt.in +++ b/man/coap_oscore.txt.in @@ -230,7 +230,7 @@ as _oscore_conf_ is freed off by the call the client or server setup functions. *Function: coap_oscore_conf_add_rcp()* -The *coap_oscore_conf_add_rcp*() function appends a *recipient_id* Recipient ID +The *coap_oscore_conf_add_rcp*() function appends a _recipient_id_ Recipient ID entry to _conf_. If the Recipient ID already exists, or memory allocation fails, NULL is returned. @@ -251,12 +251,12 @@ _conf_ with a copy of _context_id_. The *coap_oscore_register_external_handlers*() function registers callbacks for custom OSCORE credential/state storage. It can be used to: -* override context lookup (*_find_handler_*) -* persist sequence/replay state (*_update_seq_num_handler_*) -* persist Echo state (*_update_echo_handler_*) +* override context lookup (_find_handler_) +* persist sequence/replay state (_update_seq_num_handler_) +* persist Echo state (_update_echo_handler_) -When no handler is required, pass NULL for that callback. If *find_handler* is -set, then *update_seq_num_handler* and *update_echo_handler* should also be defined. +When no handler is required, pass NULL for that callback. If _find_handler_ is +set, then _update_seq_num_handler_ and _update_echo_handler_ should also be defined. *Function: coap_new_client_session_oscore3()* diff --git a/tests/test_oscore.c b/tests/test_oscore.c index 9ddb8e1591..ce41b1bd5b 100644 --- a/tests/test_oscore.c +++ b/tests/test_oscore.c @@ -1123,7 +1123,7 @@ t_ref_1(void) { CU_ASSERT_PTR_NULL(ctx->p_osc_ctx); CU_ASSERT(rcp->ref == 1); - /* Session releases its ref — recipient freed */ + /* Session releases its ref - recipient freed */ oscore_release_recipient(&rcp); CU_ASSERT_PTR_NULL(rcp); return; @@ -1170,12 +1170,12 @@ t_ref_2(void) { /* osc_ctx is no longer attached (next == NULL) */ CU_ASSERT_PTR_NULL(osc_ctx->next); - /* Session releases — recipient AND context get freed + /* Session releases - recipient AND context get freed * (oscore_release_recipient frees context when not attached and * recipient_chain becomes empty). */ oscore_release_recipient(&rcp); CU_ASSERT_PTR_NULL(rcp); - /* osc_ctx is now freed — no further access. */ + /* osc_ctx is now freed - no further access. */ return; fail: @@ -1211,7 +1211,7 @@ t_ref_3(void) { CU_ASSERT_PTR_NOT_NULL(osc_ctx->recipient_chain); CU_ASSERT(osc_ctx->recipient_chain->ref == 1); - /* Add "peer1" via public API — ownership of peer_id moves in */ + /* Add "peer1" via public API - ownership of peer_id moves in */ peer_id = coap_new_bin_const((const uint8_t *)"peer1", 5); FailIf_CU_ASSERT_PTR_NOT_NULL(peer_id); CU_ASSERT(coap_new_oscore_recipient(ctx, peer_id) == 1); @@ -1368,7 +1368,7 @@ t_conf_1(void) { } /* - * t_conf_2: Two recipients both within the 7-byte limit — expect success. + * t_conf_2: Two recipients both within the 7-byte limit */ static void t_conf_2(void) { @@ -1386,7 +1386,7 @@ t_conf_2(void) { } /* - * t_conf_3: First recipient exceeds 7 bytes — expect failure. + * t_conf_3: First recipient exceeds 7 bytes */ static void t_conf_3(void) { @@ -1395,13 +1395,16 @@ t_conf_3(void) { "sender_id,ascii,\"snd\"\n" "recipient_id,ascii,\"toolong1\"\n" "recipient_id,ascii,\"rcp2\"\n"; + coap_log_t log = coap_get_log_level(); const coap_str_const_t conf = { sizeof(conf_data)-1, (const uint8_t *)conf_data }; + coap_set_log_level(COAP_LOG_CRIT); CU_ASSERT_PTR_NULL(coap_new_oscore_conf(conf, NULL, NULL, 0)); + coap_set_log_level(log); } /* - * t_conf_4: Second recipient exceeds 7 bytes — expect failure. + * t_conf_4: Second recipient exceeds 7 bytes */ static void t_conf_4(void) { @@ -1410,9 +1413,12 @@ t_conf_4(void) { "sender_id,ascii,\"snd\"\n" "recipient_id,ascii,\"rcp1\"\n" "recipient_id,ascii,\"toolong2\"\n"; + coap_log_t log = coap_get_log_level(); const coap_str_const_t conf = { sizeof(conf_data)-1, (const uint8_t *)conf_data }; + coap_set_log_level(COAP_LOG_CRIT); CU_ASSERT_PTR_NULL(coap_new_oscore_conf(conf, NULL, NULL, 0)); + coap_set_log_level(log); } static int t_find_mode; /* 0 = single rcp, 1 = multi rcp, -1 = return NULL */ @@ -1489,7 +1495,7 @@ t_find_1(void) { /* Simulate attach (real code does ref++ on association/session) */ rcp_ctx->ref++; - /* Release — frees temporary context too (unattached, last recipient) */ + /* Release - frees temporary context too (unattached, last recipient) */ oscore_release_recipient(&rcp_ctx); CU_ASSERT_PTR_NULL(rcp_ctx); @@ -1544,7 +1550,7 @@ t_find_2(void) { } /* - * t_find_3: External find returns NULL — falls through to internal storage. + * t_find_3: External find returns NULL - falls through to internal storage. */ static void t_find_3(void) { @@ -1577,7 +1583,7 @@ t_find_3(void) { osc_ctx = oscore_find_context(session, rcpkey_id, &ctxkey_id, NULL, &rcp_ctx); FailIf_CU_ASSERT_PTR_NOT_NULL(osc_ctx); - /* Context from internal storage — IS attached */ + /* Context from internal storage - IS attached */ CU_ASSERT(osc_ctx == ctx->p_osc_ctx); CU_ASSERT_PTR_NOT_NULL(osc_ctx->next);