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..81c53ec398 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..43006c122c 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..3809185d32 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,82 @@ 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_oscore_rcp_conf_t *result = coap_oscore_conf_add_rcp_internal(conf, recipient_id); + 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; + + 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; + return &conf->sender_id; +} + +void +coap_oscore_conf_set_context_id(coap_oscore_conf_t *conf, + const coap_bin_const_t *context_id) { + 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; +} + /** @} */ #else /* !COAP_OSCORE_SUPPORT */ @@ -2455,4 +2705,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..ce41b1bd5b 100644 --- a/tests/test_oscore.c +++ b/tests/test_oscore.c @@ -1028,6 +1028,577 @@ 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 + */ +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 + */ +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"; + 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 + */ +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"; + 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 */ + +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 +1649,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];