From 7623e0f0850e8fbd498cab3ae34ae43b93706f51 Mon Sep 17 00:00:00 2001 From: Jon Shallow Date: Tue, 16 Aug 2022 17:14:09 +0100 Subject: [PATCH] OSCORE: Add in interopability tests See https://core-wg.github.io/oscore/test-spec5.html Add in new test server oscore-interop-server to provide the necessary functionality. Add in script and configuration files to run the tests locally. --- .gitignore | 1 + CMakeLists.txt | 7 +- examples/Makefile.am | 20 +- examples/interop/a_client.conf | 8 + examples/interop/b_server.conf | 8 + examples/interop/c_client.conf | 9 + examples/interop/d_server.conf | 9 + examples/interop/e_client.conf | 8 + examples/interop/f_client.conf | 10 + examples/interop/g_client.conf | 10 + examples/oscore-interop-server.c | 786 +++++++++++++++++++++++++++++++ examples/oscore_testcases.sh | 259 ++++++++++ 12 files changed, 1132 insertions(+), 3 deletions(-) create mode 100644 examples/interop/a_client.conf create mode 100644 examples/interop/b_server.conf create mode 100644 examples/interop/c_client.conf create mode 100644 examples/interop/d_server.conf create mode 100644 examples/interop/e_client.conf create mode 100644 examples/interop/f_client.conf create mode 100644 examples/interop/g_client.conf create mode 100644 examples/oscore-interop-server.c create mode 100755 examples/oscore_testcases.sh diff --git a/.gitignore b/.gitignore index 2ee6667175..c29fa05dd6 100644 --- a/.gitignore +++ b/.gitignore @@ -86,6 +86,7 @@ examples/coap-rd-* examples/coap-server examples/coap-server-* examples/coap-tiny +examples/oscore-interop-server examples/*.exe # the include/ folder diff --git a/CMakeLists.txt b/CMakeLists.txt index 432fb9ce05..28e8ec2757 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -649,6 +649,11 @@ if(ENABLE_EXAMPLES) add_executable(tiny ${CMAKE_CURRENT_LIST_DIR}/examples/tiny.c) target_link_libraries(tiny PUBLIC ${PROJECT_NAME}::${COAP_LIBRARY_NAME}) + + add_executable(oscore-interop-server + ${CMAKE_CURRENT_LIST_DIR}/examples/oscore-interop-server.c) + target_link_libraries(oscore-interop-server + PUBLIC ${PROJECT_NAME}::${COAP_LIBRARY_NAME}) endif() endif() @@ -750,7 +755,7 @@ if(ENABLE_EXAMPLES) COMPONENT dev) if(NOT WIN32) install( - TARGETS etsi_iot_01 tiny + TARGETS etsi_iot_01 tiny oscore-interop-server DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT dev) endif() diff --git a/examples/Makefile.am b/examples/Makefile.am index b18b8a7abf..f297d4d206 100644 --- a/examples/Makefile.am +++ b/examples/Makefile.am @@ -8,7 +8,19 @@ # This file is part of the CoAP C library libcoap. Please see README and # COPYING for terms of use. -EXTRA_DIST = share.libcoap.examples.Makefile share.libcoap.examples.README +EXTRA_DIST = \ + share.libcoap.examples.Makefile \ + share.libcoap.examples.README \ + coap_list.h \ + getopt.c \ + interop/a_client.conf \ + interop/b_server.conf \ + interop/c_client.conf \ + interop/d_server.conf \ + interop/e_client.conf \ + interop/f_client.conf \ + interop/g_client.conf \ + oscore_testcases.sh # just do nothing if 'BUILD_EXAMPLES' isn't defined if BUILD_EXAMPLES @@ -38,7 +50,7 @@ if HAVE_SERVER_SUPPORT bin_PROGRAMS += coap-server@LIBCOAP_DTLS_LIB_EXTENSION_NAME@ \ coap-rd@LIBCOAP_DTLS_LIB_EXTENSION_NAME@ -check_PROGRAMS += coap-etsi_iot_01 +check_PROGRAMS += coap-etsi_iot_01 oscore-interop-server if BUILD_ADD_DEFAULT_NAMES noinst_PROGRAMS += coap-server coap-rd @@ -79,6 +91,10 @@ coap_etsi_iot_01_SOURCES = etsi_iot_01.c coap_etsi_iot_01_LDADD = $(DTLS_LIBS) \ $(top_builddir)/.libs/libcoap-$(LIBCOAP_NAME_SUFFIX).la +oscore_interop_server_SOURCES = oscore-interop-server.c +oscore_interop_server_LDADD = $(DTLS_LIBS) \ + $(top_builddir)/.libs/libcoap-$(LIBCOAP_NAME_SUFFIX).la + coap_tiny_SOURCES = tiny.c coap_tiny_LDADD = $(DTLS_LIBS) \ $(top_builddir)/.libs/libcoap-$(LIBCOAP_NAME_SUFFIX).la diff --git a/examples/interop/a_client.conf b/examples/interop/a_client.conf new file mode 100644 index 0000000000..cd8ec40d36 --- /dev/null +++ b/examples/interop/a_client.conf @@ -0,0 +1,8 @@ +master_secret,hex,"0102030405060708090a0b0c0d0e0f10" +master_salt,hex,"9e7ca92223786340" +sender_id,hex,"" +recipient_id,hex,"01" +replay_window,integer,30 +aead_alg,integer,10 +hkdf_alg,integer,-10 +ssn_freq,integer,4 diff --git a/examples/interop/b_server.conf b/examples/interop/b_server.conf new file mode 100644 index 0000000000..a0a5982a59 --- /dev/null +++ b/examples/interop/b_server.conf @@ -0,0 +1,8 @@ +master_secret,hex,"0102030405060708090a0b0c0d0e0f10" +master_salt,hex,"9e7ca92223786340" +sender_id,hex,"01" +recipient_id,hex,"" +replay_window,integer,30 +aead_alg,integer,10 +hkdf_alg,integer,-10 +ssn_freq,integer,4 diff --git a/examples/interop/c_client.conf b/examples/interop/c_client.conf new file mode 100644 index 0000000000..fc92133542 --- /dev/null +++ b/examples/interop/c_client.conf @@ -0,0 +1,9 @@ +master_secret,hex,"0102030405060708090a0b0c0d0e0f10" +master_salt,hex,"9e7ca92223786340" +id_context,hex,"37cbf3210017a2d3" +sender_id,hex,"" +recipient_id,hex,"01" +replay_window,integer,30 +aead_alg,integer,10 +hkdf_alg,integer,-10 +ssn_freq,integer,4 diff --git a/examples/interop/d_server.conf b/examples/interop/d_server.conf new file mode 100644 index 0000000000..93f86368ea --- /dev/null +++ b/examples/interop/d_server.conf @@ -0,0 +1,9 @@ +master_secret,hex,"0102030405060708090a0b0c0d0e0f10" +master_salt,hex,"9e7ca92223786340" +id_context,hex,"37cbf3210017a2d3" +sender_id,hex,"01" +recipient_id,hex,"" +replay_window,integer,30 +aead_alg,integer,10 +hkdf_alg,integer,-10 +ssn_freq,integer,4 diff --git a/examples/interop/e_client.conf b/examples/interop/e_client.conf new file mode 100644 index 0000000000..dade02f50c --- /dev/null +++ b/examples/interop/e_client.conf @@ -0,0 +1,8 @@ +master_secret,hex,"0102030405060708090a0b0c0d0e0f10" +master_salt,hex,"9e7ca92223786340" +sender_id,hex,"010101" +recipient_id,hex,"01" +replay_window,integer,30 +aead_alg,integer,10 +hkdf_alg,integer,-10 +ssn_freq,integer,4 diff --git a/examples/interop/f_client.conf b/examples/interop/f_client.conf new file mode 100644 index 0000000000..a56d57f6fa --- /dev/null +++ b/examples/interop/f_client.conf @@ -0,0 +1,10 @@ +master_secret,hex,"0102030405060708090a0b0c0d0e0f10" +master_salt,hex,"9e7ca92223786340" +sender_id,hex,"" +recipient_id,hex,"01" +replay_window,integer,30 +aead_alg,integer,10 +hkdf_alg,integer,-10 +ssn_freq,integer,4 + +break_sender_key,bool,true diff --git a/examples/interop/g_client.conf b/examples/interop/g_client.conf new file mode 100644 index 0000000000..536772f6aa --- /dev/null +++ b/examples/interop/g_client.conf @@ -0,0 +1,10 @@ +master_secret,hex,"0102030405060708090a0b0c0d0e0f10" +master_salt,hex,"9e7ca92223786340" +sender_id,hex,"" +recipient_id,hex,"01" +replay_window,integer,30 +aead_alg,integer,10 +hkdf_alg,integer,-10 +ssn_freq,integer,4 + +break_recipient_key,bool,true diff --git a/examples/oscore-interop-server.c b/examples/oscore-interop-server.c new file mode 100644 index 0000000000..8782415306 --- /dev/null +++ b/examples/oscore-interop-server.c @@ -0,0 +1,786 @@ +/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ + +/* oscore-interop-server + * + * A server for use in the RFC 8613 OSCORE interop testing. + * https://core-wg.github.io/oscore/test-spec5.html + * + * Copyright (C) 2022-2023 Olaf Bergmann and others + * + * SPDX-License-Identifier: BSD-2-Clause + * + * This file is part of the CoAP library libcoap. Please see README for terms + * of use. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef _WIN32 +#define strcasecmp _stricmp +#define strncasecmp _strnicmp +#include "getopt.c" +#if !defined(S_ISDIR) +#define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR) +#endif +#ifndef R_OK +#define R_OK 4 +#endif +static char* +strndup(const char* s1, size_t n) { + char* copy = (char*)malloc(n + 1); + if (copy) { + memcpy(copy, s1, n); + copy[n] = 0; + } + return copy; +}; +#include +#define access _access +#define fileno _fileno +#else +#include +#include +#include +#include +#include +#include +#include +#endif + +#include + +#ifndef min +#define min(a,b) ((a) < (b) ? (a) : (b)) +#endif + +static coap_oscore_conf_t *oscore_conf; +static int doing_oscore = 0; + +/* set to 1 to request clean server shutdown */ +static int quit = 0; + +static coap_resource_t *r_observe_1; +static coap_resource_t *r_observe_2; + +static int resource_flags = COAP_RESOURCE_FLAGS_NOTIFY_CON; + +static uint32_t block_mode = COAP_BLOCK_USE_LIBCOAP; +static uint32_t csm_max_message_size = 0; + +/* SIGINT handler: set quit to 1 for graceful termination */ +static void +handle_sigint(int signum COAP_UNUSED) { + quit = 1; +} + +#define INDEX "This is a OSCORE test server made with libcoap " \ + "(see https://libcoap.net)\n" \ + "Copyright (C) 2023 Olaf Bergmann " \ + "and others\n\n" + +static void +hnd_get_index(coap_resource_t *resource, + coap_session_t *session, + const coap_pdu_t *request, + const coap_string_t *query COAP_UNUSED, + coap_pdu_t *response) { + + coap_pdu_set_code(response, COAP_RESPONSE_CODE_CONTENT); + coap_add_data_large_response(resource, session, request, response, + query, COAP_MEDIATYPE_TEXT_PLAIN, + 0x2ffff, 0, strlen(INDEX), + (const uint8_t *)INDEX, NULL, NULL); +} + +#define HELLO_WORLD "Hello World!" + +static void +hnd_get_hello_coap(coap_resource_t *resource, + coap_session_t *session, + const coap_pdu_t *request, + const coap_string_t *query, + coap_pdu_t *response) { + coap_pdu_set_code(response, COAP_RESPONSE_CODE_CONTENT); + coap_add_data_large_response(resource, session, request, response, + query, COAP_MEDIATYPE_TEXT_PLAIN, + -1, 0, strlen(HELLO_WORLD), + (const uint8_t *)HELLO_WORLD, NULL, NULL); +} + +static void +hnd_get_hello_1(coap_resource_t *resource, + coap_session_t *session, + const coap_pdu_t *request, + const coap_string_t *query, + coap_pdu_t *response) { + coap_pdu_set_code(response, COAP_RESPONSE_CODE_CONTENT); + coap_add_data_large_response(resource, session, request, response, + query, COAP_MEDIATYPE_TEXT_PLAIN, + -1, 0, strlen(HELLO_WORLD), + (const uint8_t *)HELLO_WORLD, NULL, NULL); +} + +static void +hnd_get_hello_2(coap_resource_t *resource, + coap_session_t *session, + const coap_pdu_t *request, + const coap_string_t *query, + coap_pdu_t *response) { + coap_pdu_set_code(response, COAP_RESPONSE_CODE_CONTENT); + coap_add_data_large_response(resource, session, request, response, + query, COAP_MEDIATYPE_TEXT_PLAIN, + -1, 0x2b, strlen(HELLO_WORLD), + (const uint8_t *)HELLO_WORLD, NULL, NULL); +} + +static void +hnd_get_hello_3(coap_resource_t *resource, + coap_session_t *session, + const coap_pdu_t *request, + const coap_string_t *query, + coap_pdu_t *response) { + coap_pdu_set_code(response, COAP_RESPONSE_CODE_CONTENT); + coap_add_data_large_response(resource, session, request, response, + query, COAP_MEDIATYPE_TEXT_PLAIN, + 5, 0, strlen(HELLO_WORLD), + (const uint8_t *)HELLO_WORLD, NULL, NULL); +} + +static void +hnd_post_hello_6(coap_resource_t *resource, + coap_session_t *session, + const coap_pdu_t *request, + const coap_string_t *query, + coap_pdu_t *response) { + size_t size; + const uint8_t *data; + + (void)coap_get_data(request, &size, &data); + coap_pdu_set_code(response, COAP_RESPONSE_CODE_CHANGED); + coap_add_data_large_response(resource, session, request, response, + query, COAP_MEDIATYPE_TEXT_PLAIN, + -1, 0, size, + data, NULL, NULL); +} + +static void +hnd_put_hello_7(coap_resource_t *resource, + coap_session_t *session, + const coap_pdu_t *request, + const coap_string_t *query, + coap_pdu_t *response) { + size_t size; + const uint8_t *data; + coap_opt_iterator_t opt_iter; + coap_opt_t *option; + uint64_t etag; + + + if ((option = coap_check_option(request, COAP_OPTION_IF_MATCH, + &opt_iter)) != NULL) { + etag = coap_decode_var_bytes8 (coap_opt_value (option), + coap_opt_length (option)); + if (etag != 0x7b) { + coap_pdu_set_code(response, COAP_RESPONSE_CODE_PRECONDITION_FAILED); + return; + } + } + + coap_pdu_set_code(response, COAP_RESPONSE_CODE_CHANGED); + (void)coap_get_data(request, &size, &data); + coap_add_data_large_response(resource, session, request, response, + query, COAP_MEDIATYPE_TEXT_PLAIN, + -1, 0x7b, size, + data, NULL, NULL); +} + +static void +hnd_get_observe1(coap_resource_t *resource, + coap_session_t *session, + const coap_pdu_t *request, + const coap_string_t *query, + coap_pdu_t *response) { + static int count = 0; + + count++; + switch (count) { + case 1: + coap_pdu_set_code(response, COAP_RESPONSE_CODE_CONTENT); + coap_add_data_large_response(resource, session, request, response, + query, COAP_MEDIATYPE_TEXT_PLAIN, + 1, 0, strlen("one"), + (const uint8_t *)"one", NULL, NULL); + break; + case 2: + coap_pdu_set_code(response, COAP_RESPONSE_CODE_CONTENT); + coap_add_data_large_response(resource, session, request, response, + query, COAP_MEDIATYPE_TEXT_PLAIN, + 1, 0, strlen("two"), + (const uint8_t *)"two", NULL, NULL); + break; + default: + coap_pdu_set_code(response, COAP_RESPONSE_CODE_INTERNAL_ERROR); + coap_add_data_large_response(resource, session, request, response, + query, COAP_MEDIATYPE_TEXT_PLAIN, + -1, 0, strlen("Terminate Observe"), + (const uint8_t *)"Terminate Observe", + NULL, NULL); + r_observe_1 = NULL; + } +} + +static void +hnd_get_observe2(coap_resource_t *resource, + coap_session_t *session, + const coap_pdu_t *request, + const coap_string_t *query, + coap_pdu_t *response) { + static int count = 0; + + count++; + switch (count) { + case 1: + coap_pdu_set_code(response, COAP_RESPONSE_CODE_CONTENT); + coap_add_data_large_response(resource, session, request, response, + query, COAP_MEDIATYPE_TEXT_PLAIN, + 1, 0, strlen("one"), + (const uint8_t *)"one", NULL, NULL); + break; + case 2: + default: + coap_pdu_set_code(response, COAP_RESPONSE_CODE_CONTENT); + coap_add_data_large_response(resource, session, request, response, + query, COAP_MEDIATYPE_TEXT_PLAIN, + 1, 0, strlen("two"), + (const uint8_t *)"two", NULL, NULL); + r_observe_2 = NULL; + break; + } +} + +static void +hnd_del_test(coap_resource_t *resource COAP_UNUSED, + coap_session_t *session COAP_UNUSED, + const coap_pdu_t *request COAP_UNUSED, + const coap_string_t *query COAP_UNUSED, + coap_pdu_t *response) { + coap_pdu_set_code(response, COAP_RESPONSE_CODE_DELETED); +} + +static void +init_resources(coap_context_t *ctx) { + coap_resource_t *r; + + r = coap_resource_init(NULL, COAP_RESOURCE_FLAGS_HAS_MCAST_SUPPORT); + coap_register_request_handler(r, COAP_REQUEST_GET, hnd_get_index); + + coap_add_attr(r, coap_make_str_const("ct"), coap_make_str_const("0"), 0); + coap_add_attr(r, coap_make_str_const("title"), + coap_make_str_const("\"General Info\""), 0); + coap_add_resource(ctx, r); + + r = coap_resource_init(coap_make_str_const("oscore/hello/coap"), + resource_flags); + coap_register_request_handler(r, COAP_REQUEST_GET, hnd_get_hello_coap); + coap_add_resource(ctx, r); + + r = coap_resource_init(coap_make_str_const("oscore/hello/1"), + resource_flags | COAP_RESOURCE_FLAGS_OSCORE_ONLY); + coap_register_request_handler(r, COAP_REQUEST_GET, hnd_get_hello_1); + coap_add_resource(ctx, r); + + r = coap_resource_init(coap_make_str_const("oscore/hello/2"), + resource_flags | COAP_RESOURCE_FLAGS_OSCORE_ONLY); + coap_register_request_handler(r, COAP_REQUEST_GET, hnd_get_hello_2); + coap_add_resource(ctx, r); + + r = coap_resource_init(coap_make_str_const("oscore/hello/3"), + resource_flags | COAP_RESOURCE_FLAGS_OSCORE_ONLY); + coap_register_request_handler(r, COAP_REQUEST_GET, hnd_get_hello_3); + coap_add_resource(ctx, r); + + r = coap_resource_init(coap_make_str_const("oscore/hello/6"), + resource_flags | COAP_RESOURCE_FLAGS_OSCORE_ONLY); + coap_register_request_handler(r, COAP_REQUEST_POST, hnd_post_hello_6); + coap_add_resource(ctx, r); + + r = coap_resource_init(coap_make_str_const("oscore/hello/7"), + resource_flags | COAP_RESOURCE_FLAGS_OSCORE_ONLY); + coap_register_request_handler(r, COAP_REQUEST_PUT, hnd_put_hello_7); + coap_add_resource(ctx, r); + + r = coap_resource_init(coap_make_str_const("oscore/observe1"), + resource_flags | COAP_RESOURCE_FLAGS_OSCORE_ONLY); + coap_register_request_handler(r, COAP_REQUEST_GET, hnd_get_observe1); + coap_resource_set_get_observable(r, 1); + coap_add_resource(ctx, r); + r_observe_1 = r; + + r = coap_resource_init(coap_make_str_const("oscore/observe2"), + resource_flags | COAP_RESOURCE_FLAGS_OSCORE_ONLY); + coap_register_request_handler(r, COAP_REQUEST_GET, hnd_get_observe2); + coap_resource_set_get_observable(r, 1); + coap_add_resource(ctx, r); + r_observe_2 = r; + + r = coap_resource_init(coap_make_str_const("oscore/test"), + resource_flags | COAP_RESOURCE_FLAGS_OSCORE_ONLY); + coap_register_request_handler(r, COAP_REQUEST_DELETE, hnd_del_test); + coap_add_resource(ctx, r); +} + +static uint8_t * +read_file_mem(const char* file, size_t *length) { + FILE *f; + uint8_t *buf; + struct stat statbuf; + + *length = 0; + if (!file || !(f = fopen(file, "r"))) + return NULL; + + if (fstat(fileno(f), &statbuf) == -1) { + fclose(f); + return NULL; + } + + buf = coap_malloc(statbuf.st_size+1); + if (!buf) { + fclose(f); + return NULL; + } + + if (fread(buf, 1, statbuf.st_size, f) != (size_t)statbuf.st_size) { + fclose(f); + coap_free(buf); + return NULL; + } + buf[statbuf.st_size] = '\000'; + *length = (size_t)(statbuf.st_size + 1); + fclose(f); + return buf; +} + +static void +usage( const char *program, const char *version) { + const char *p; + char buffer[120]; + const char *lib_build = coap_package_build(); + + p = strrchr( program, '/' ); + if ( p ) + program = ++p; + + fprintf( stderr, "%s v%s -- OSCORE interop implementation\n" + "(c) 2023 Olaf Bergmann and others\n\n" + "Build: %s\n" + "%s\n" + , program, version, lib_build, + coap_string_tls_version(buffer, sizeof(buffer))); + fprintf(stderr, "%s\n", coap_string_tls_support(buffer, sizeof(buffer))); + fprintf(stderr, "\n" + "Usage: %s [-d max] [-g group] [-l loss] [-p port] [-r] [-v num]\n" + "\t\t[-A address] [-E oscore_conf_file[,seq_file]] [-G group_if]\n" + "\t\t[-L value] [-N] [-P scheme://address[:port],[name1[,name2..]]]\n" + "\t\t[-X size]\n" + "General Options\n" + "\t-d max \t\tAllow dynamic creation of up to a total of max\n" + "\t \t\tresources. If max is reached, a 4.06 code is returned\n" + "\t \t\tuntil one of the dynamic resources has been deleted\n" + "\t-g group\tJoin the given multicast group\n" + "\t \t\tNote: DTLS over multicast is not currently supported\n" + "\t-l list\t\tFail to send some datagrams specified by a comma\n" + "\t \t\tseparated list of numbers or number ranges\n" + "\t \t\t(for debugging only)\n" + "\t-l loss%%\tRandomly fail to send datagrams with the specified\n" + "\t \t\tprobability - 100%% all datagrams, 0%% no datagrams\n" + "\t \t\t(for debugging only)\n" + "\t-p port\t\tListen on specified port for UDP and TCP. If (D)TLS is\n" + "\t \t\tenabled, then the coap-server will also listen on\n" + "\t \t\t 'port'+1 for DTLS and TLS. The default port is 5683\n" + "\t-r \t\tEnable multicast per resource support. If enabled,\n" + "\t \t\tonly '/', '/async' and '/.well-known/core' are enabled\n" + "\t \t\tfor multicast requests support, otherwise all\n" + "\t \t\tresources are enabled\n" + "\t-v num \t\tVerbosity level (default 3, maximum is 9). Above 7,\n" + "\t \t\tthere is increased verbosity in GnuTLS and OpenSSL\n" + "\t \t\tlogging\n" + "\t-A address\tInterface address to bind to\n" + "\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-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" + "\t-L value\tSum of one or more COAP_BLOCK_* flag valuess for block\n" + "\t \t\thandling methods. Default is 1 (COAP_BLOCK_USE_LIBCOAP)\n" + "\t \t\t(Sum of one or more of 1,2 and 4)\n" + "\t-N \t\tMake \"observe\" responses NON-confirmable. Even if set\n" + "\t \t\tevery fifth response will still be sent as a confirmable\n" + "\t \t\tresponse (RFC 7641 requirement)\n" + , program); +} + +static coap_context_t * +get_context(const char *node, const char *port) { + coap_context_t *ctx = NULL; + int s; + struct addrinfo hints; + struct addrinfo *result, *rp; + + ctx = coap_new_context(NULL); + if (!ctx) { + return NULL; + } + + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_family = AF_UNSPEC; /* Allow IPv4 or IPv6 */ + hints.ai_socktype = SOCK_DGRAM; /* Coap uses UDP */ + hints.ai_flags = AI_PASSIVE | AI_NUMERICHOST; + + s = getaddrinfo(node, port, &hints, &result); + if ( s != 0 ) { + fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(s)); + coap_free_context(ctx); + return NULL; + } + + /* iterate through results until success */ + for (rp = result; rp != NULL; rp = rp->ai_next) { + coap_address_t addr; + coap_endpoint_t *ep_udp = NULL; + + if (rp->ai_addrlen <= (socklen_t)sizeof(addr.addr)) { + coap_address_init(&addr); + addr.size = (socklen_t)rp->ai_addrlen; + memcpy(&addr.addr, rp->ai_addr, rp->ai_addrlen); + + ep_udp = coap_new_endpoint(ctx, &addr, COAP_PROTO_UDP); + if (!ep_udp) { + coap_log_crit("cannot create UDP endpoint\n"); + continue; + } + if (coap_tcp_is_supported()) { + coap_endpoint_t *ep_tcp; + ep_tcp = coap_new_endpoint(ctx, &addr, COAP_PROTO_TCP); + if (!ep_tcp) { + coap_log_crit("cannot create TCP endpoint\n"); + } + } + if (ep_udp) + goto finish; + } + } + + fprintf(stderr, "no context available for interface '%s'\n", node); + coap_free_context(ctx); + ctx = NULL; + +finish: + freeaddrinfo(result); + return ctx; +} + +static FILE *oscore_seq_num_fp = NULL; +static const char* oscore_conf_file = NULL; +static const char* oscore_seq_save_file = NULL; + +static int +oscore_save_seq_num(uint64_t sender_seq_num, void *param COAP_UNUSED) { + if (oscore_seq_num_fp) { + rewind(oscore_seq_num_fp); + fprintf(oscore_seq_num_fp, "%ju\n", sender_seq_num); + fflush(oscore_seq_num_fp); + } + return 1; +} + +static coap_oscore_conf_t * +get_oscore_conf(coap_context_t *context) { + uint8_t *buf; + size_t length; + coap_str_const_t file_mem; + uint64_t start_seq_num = 0; + + buf = read_file_mem(oscore_conf_file, &length); + if (buf == NULL) { + fprintf(stderr, "OSCORE configuraton file error: %s\n", oscore_conf_file); + return NULL; + } + file_mem.s = buf; + file_mem.length = length; + if (oscore_seq_save_file) { + oscore_seq_num_fp = fopen(oscore_seq_save_file, "r+"); + if (oscore_seq_num_fp == NULL) { + /* Try creating it */ + oscore_seq_num_fp = fopen(oscore_seq_save_file, "w+"); + if (oscore_seq_num_fp == NULL) { + fprintf(stderr, "OSCORE save restart info file error: %s\n", + oscore_seq_save_file); + return NULL; + } + } + if (fscanf(oscore_seq_num_fp, "%ju", &start_seq_num) != 1) { + /* Must be empty */ + start_seq_num = 0; + } + } + oscore_conf = coap_new_oscore_conf(file_mem, + oscore_save_seq_num, + NULL, start_seq_num); + coap_free(buf); + if (oscore_conf == NULL) { + fprintf(stderr, "OSCORE configuraton file error: %s\n", oscore_conf_file); + return NULL; + } + coap_context_oscore_server(context, oscore_conf); + return oscore_conf; +} + +static int +cmdline_oscore(char *arg) { + if (coap_oscore_is_supported()) { + char *sep = strchr(arg, ','); + + if (sep) + *sep = '\000'; + oscore_conf_file = arg; + + if (sep) { + sep++; + oscore_seq_save_file = sep; + } + doing_oscore = 1; + return 1; + } + fprintf(stderr, "OSCORE support not enabled\n"); + return 0; +} + +int +main(int argc, char **argv) { + coap_context_t *ctx; + char *group = NULL; + char *group_if = NULL; + coap_tick_t now; + char addr_str[NI_MAXHOST] = "::"; + char port_str[NI_MAXSERV] = "5683"; + int opt; + int mcast_per_resource = 0; + coap_log_t log_level = COAP_LOG_WARN; + unsigned wait_ms; + coap_time_t t_last = 0; + int coap_fd; + fd_set m_readfds; + int nfds = 0; + uint16_t cache_ignore_options[] = { COAP_OPTION_BLOCK1, + COAP_OPTION_BLOCK2, + /* See https://rfc-editor.org/rfc/rfc7959#section-2.10 */ + COAP_OPTION_MAXAGE, + /* See https://rfc-editor.org/rfc/rfc7959#section-2.10 */ + COAP_OPTION_IF_NONE_MATCH }; +#ifndef _WIN32 + struct sigaction sa; +#endif + + while ((opt = getopt(argc, argv, "g:G:l:p:rv:A:E:L:NX:")) != -1) { + switch (opt) { + case 'A' : + strncpy(addr_str, optarg, NI_MAXHOST-1); + addr_str[NI_MAXHOST - 1] = '\0'; + break; + case 'E': + if (!cmdline_oscore(optarg)) { + exit(1); + } + break; + case 'g' : + group = optarg; + break; + case 'G' : + group_if = optarg; + break; + case 'l': + if (!coap_debug_set_packet_loss(optarg)) { + usage(argv[0], LIBCOAP_PACKAGE_VERSION); + exit(1); + } + break; + case 'L': + block_mode = strtoul(optarg, NULL, 0); + if (!(block_mode & COAP_BLOCK_USE_LIBCOAP)) { + fprintf(stderr, "Block mode must include COAP_BLOCK_USE_LIBCOAP (1)\n"); + exit(-1); + } + break; + case 'N': + resource_flags = COAP_RESOURCE_FLAGS_NOTIFY_NON; + break; + case 'p' : + strncpy(port_str, optarg, NI_MAXSERV-1); + port_str[NI_MAXSERV - 1] = '\0'; + break; + case 'r' : + mcast_per_resource = 1; + break; + case 'v' : + log_level = strtol(optarg, NULL, 10); + break; + case 'X': + csm_max_message_size = strtol(optarg, NULL, 10); + break; + default: + usage( argv[0], LIBCOAP_PACKAGE_VERSION ); + exit( 1 ); + } + } + +#ifdef _WIN32 + signal(SIGINT, handle_sigint); +#else + memset (&sa, 0, sizeof(sa)); + sigemptyset(&sa.sa_mask); + sa.sa_handler = handle_sigint; + sa.sa_flags = 0; + sigaction (SIGINT, &sa, NULL); + sigaction (SIGTERM, &sa, NULL); + /* So we do not exit on a SIGPIPE */ + sa.sa_handler = SIG_IGN; + sigaction (SIGPIPE, &sa, NULL); +#endif + + coap_startup(); + coap_dtls_set_log_level(log_level); + coap_set_log_level(log_level); + + ctx = get_context(addr_str, port_str); + if (!ctx) + return -1; + + init_resources(ctx); + if (mcast_per_resource) + coap_mcast_per_resource(ctx); + coap_context_set_block_mode(ctx, block_mode); + if (csm_max_message_size) + coap_context_set_csm_max_message_size(ctx, csm_max_message_size); + if (doing_oscore) { + if (get_oscore_conf(ctx) == NULL) + goto finish; + } + + /* Define the options to ignore when setting up cache-keys */ + coap_cache_ignore_options(ctx, cache_ignore_options, + sizeof(cache_ignore_options)/sizeof(cache_ignore_options[0])); + /* join multicast group if requested at command line */ + if (group) + coap_join_mcast_group_intf(ctx, group, group_if); + + coap_fd = coap_context_get_coap_fd(ctx); + if (coap_fd != -1) { + /* if coap_fd is -1, then epoll is not supported within libcoap */ + FD_ZERO(&m_readfds); + FD_SET(coap_fd, &m_readfds); + nfds = coap_fd + 1; + } + + wait_ms = COAP_RESOURCE_CHECK_TIME * 1000; + + while ( !quit ) { + int result; + + if (coap_fd != -1) { + /* + * Using epoll. It is more usual to call coap_io_process() with wait_ms + * (as in the non-epoll branch), but doing it this way gives the + * flexibility of potentially working with other file descriptors that + * are not a part of libcoap. + */ + fd_set readfds = m_readfds; + struct timeval tv; + coap_tick_t begin, end; + + coap_ticks(&begin); + + tv.tv_sec = wait_ms / 1000; + tv.tv_usec = (wait_ms % 1000) * 1000; + /* Wait until any i/o takes place or timeout */ + result = select (nfds, &readfds, NULL, NULL, &tv); + if (result == -1) { + if (errno != EAGAIN) { + coap_log_debug("select: %s (%d)\n", coap_socket_strerror(), + errno); + break; + } + } + if (result > 0) { + if (FD_ISSET(coap_fd, &readfds)) { + result = coap_io_process(ctx, COAP_IO_NO_WAIT); + } + } + if (result >= 0) { + coap_ticks(&end); + /* Track the overall time spent in select() and coap_io_process() */ + result = (int)(end - begin); + } + } else { + /* + * epoll is not supported within libcoap + * + * result is time spent in coap_io_process() + */ + result = coap_io_process( ctx, wait_ms ); + } + if ( result < 0 ) { + break; + } else if ( result && (unsigned)result < wait_ms ) { + /* decrement if there is a result wait time returned */ + wait_ms -= result; + } else { + /* + * result == 0, or result >= wait_ms + * (wait_ms could have decremented to a small value, below + * the granularity of the timer in coap_io_process() and hence + * result == 0) + */ + wait_ms = COAP_RESOURCE_CHECK_TIME * 1000; + } + if (r_observe_1 || r_observe_2) { + coap_time_t t_now; + unsigned int next_sec_ms; + + coap_ticks(&now); + t_now = coap_ticks_to_rt(now); + if (t_last != t_now) { + /* Happens once per second */ + t_last = t_now; + if (r_observe_1) + coap_resource_notify_observers(r_observe_1, NULL); + if (r_observe_2) + coap_resource_notify_observers(r_observe_2, NULL); + } + /* need to wait until next second starts if wait_ms is too large */ + next_sec_ms = 1000 - (now % COAP_TICKS_PER_SECOND) * + 1000 / COAP_TICKS_PER_SECOND; + if (next_sec_ms && next_sec_ms < wait_ms) + wait_ms = next_sec_ms; + } + } + +finish: + + if (oscore_seq_num_fp) + fclose(oscore_seq_num_fp); + + coap_free_context(ctx); + coap_cleanup(); + + return 0; +} diff --git a/examples/oscore_testcases.sh b/examples/oscore_testcases.sh new file mode 100755 index 0000000000..be12ca474b --- /dev/null +++ b/examples/oscore_testcases.sh @@ -0,0 +1,259 @@ +#!/bin/bash + +# +# This script is used to run the oscore interop tests as specified in +# https://core-wg.github.io/oscore/test-spec5.html +# +# By default, this script should be run in the examples directory. +# +# Run as +# ./oscore_testcases.sh [-h remote-target-IP] [-B port-B-OSCORE] \ +# [-D port-D-OSCORE] [-N port-NO-OSCORE] \ +# [-s executable-for-interop-server] \ +# [-c executable-for-client] \ +# [-P] [-F] +# +# -h remote-target-IP +# Remote server hosting interop tests if not running the interop server on this host. +# +# -B port-B-OSCORE +# Port that the server listening on providing B OSCORE security profile +# +# -D port-D-OSCORE +# Port that the server listening on providing D OSCORE security profile +# +# -N port-N-OSCORE +# Port that the server listening on providing no security profile +# +# -s executable-for-interop-server +# Exectuable to use for the interop server if not the default of ./oscore-interop-server. +# +# -c executable-for-client +# Exectuable to use for the coap client if not the default of ./coap-client. +# +# -P +# Output partial client logs +# +# -F +# Output full client logs +# + +INDIR=`dirname $0` + +# Defaults + +# host running oscore interop server +TARGET_IP=127.0.0.1 +# Server with B OSCORE Security +S_PORT_B=5683 +# Server with D OSCORE Security +S_PORT_D=5685 +# Server with no Security +S_PORT_N=5687 +# Client app +CLIENT=$INDIR/coap-client +# SERVER app +SERVER=$INDIR/oscore-interop-server +# Partial Logs +PARTIAL_LOGS=no +# Full Logs +FULL_LOGS=no + +while getopts "c:h:s:B:D:FN:P" OPTION; do + case $OPTION in + c) + CLIENT="$OPTARG" + ;; + h) + TARGET_IP="$OPTARG" + ;; + s) + SERVER="$OPTARG" + ;; + B) + S_PORT_B="$OPTARG" + ;; + D) + S_PORT_D="$OPTARG" + ;; + F) + FULL_LOGS=yes + ;; + N) + S_PORT_N="$OPTARG" + ;; + P) + PARTIAL_LOGS=yes + ;; + *) + echo Error in options detected + echo Run as + echo "$0 [-h remote-target-IP] [-B port-B-OSCORE]" + echo " [-D port-D-OSCORE] [-N port-NO-OSCORE]" + echo " [-s executable-for-interop-server]" + echo " [-c executable-for-client]" + echo " [-P] [-F]" + exit 1 + esac +done + +timecheck () { + timeout $* + if [ $? = 124 ] ; then + echo "****** Timed Out ******" + fi +} + +NO_PASS=0 +NO_FAIL=0 +# passfail count egrep-expression +passfail () { + PASS=`cat /tmp/client_out | egrep "$2" | wc -l` + if [ "$PASS" = "$1" ] ; then + echo Pass + let "NO_PASS=$NO_PASS+1" + else + echo Fail + let "NO_FAIL=$NO_FAIL+1" + fi + if [ "$FULL_LOGS" = yes ] ; then + cat /tmp/client_out + elif [ "$PARTIAL_LOGS" = yes ] ; then + cat /tmp/client_out | egrep -v " DEBG | OSC " + fi +} + +$SERVER -E $INDIR/interop/b_server.conf -v8 -p $S_PORT_B > /tmp/server_b 2>&1 & +$SERVER -E $INDIR/interop/d_server.conf -v8 -p $S_PORT_D > /tmp/server_d 2>&1 & +$SERVER -v8 -p $S_PORT_N > /tmp/server_n 2>&1 & + +# Reset sequence number counters +rm -f /tmp/client_a +rm -f /tmp/client_c + +# Test 0 General checkout +echo -n "Test 0 - " +timecheck 10 $CLIENT -w -v8 coap://$TARGET_IP:$S_PORT_B/oscore/hello/coap 2>&1 | egrep -v " DEBG | OSC " > /tmp/client_out +passfail 1 "^Hello World" + +# Test 1 +echo -n "Test 1 - " +timeout 10 $CLIENT -w -v8 -E $INDIR/interop/a_client.conf,/tmp/client_a coap://$TARGET_IP:$S_PORT_B/oscore/hello/1 > /tmp/client_out 2>&1 +passfail 1 "^Hello World" + +# Test 2 +echo -n "Test 2 - " +timeout 10 $CLIENT -w -v8 -E $INDIR/interop/c_client.conf,/tmp/client_c coap://$TARGET_IP:$S_PORT_D/oscore/hello/1 > /tmp/client_out 2>&1 +passfail 1 "^Hello World" + +# Test 3 +echo -n "Test 3 - " +timeout 10 $CLIENT -w -v8 -E $INDIR/interop/a_client.conf,/tmp/client_a coap://$TARGET_IP:$S_PORT_B/oscore/hello/2?first=1 > /tmp/client_out 2>&1 +passfail 1 "^Hello World" + +# Test 4 +echo -n "Test 4 - " +timeout 10 $CLIENT -w -v8 -E $INDIR/interop/a_client.conf,/tmp/client_a -A 0 coap://$TARGET_IP:$S_PORT_B/oscore/hello/3 > /tmp/client_out 2>&1 +passfail 1 "^Hello World" + +# Test 5 +echo -n "Test 5 - " +timeout 10 $CLIENT -w -v8 -E $INDIR/interop/a_client.conf,/tmp/client_a -s 2 coap://$TARGET_IP:$S_PORT_B/oscore/hello/1 > /tmp/client_out 2>&1 +passfail 1 "^Hello World" + +# Test 6 +echo -n "Test 6 - " +timeout 10 $CLIENT -w -v8 -E $INDIR/interop/a_client.conf,/tmp/client_a -s 4 coap://$TARGET_IP:$S_PORT_B/oscore/observe1 > /tmp/client_out > /tmp/client_out 2>&1 +passfail 3 "^one|^two|^5.00 Terminate Observe" + +# Test 7 +echo -n "Test 7 - " +timeout 10 $CLIENT -w -v8 -E $INDIR/interop/a_client.conf,/tmp/client_a -s 2 coap://$TARGET_IP:$S_PORT_B/oscore/observe2 > /tmp/client_out 2>&1 +passfail 3 "^one|^two" + +# Test 8 +echo -n "Test 8 - " +timeout 10 $CLIENT -w -v8 -E $INDIR/interop/a_client.conf,/tmp/client_a -m post -e "%4a" -t 0 coap://$TARGET_IP:$S_PORT_B/oscore/hello/6 > /tmp/client_out 2>&1 +passfail 1 "^J$" + +# Test 9 +echo -n "Test 9 - " +timeout 10 $CLIENT -w -v8 -E $INDIR/interop/a_client.conf,/tmp/client_a -m put -e "%7a" -t 0 -O 1,0x7b coap://$TARGET_IP:$S_PORT_B/oscore/hello/7 > /tmp/client_out 2>&1 +passfail 1 "^z" + +# Test 10 +echo -n "Test 10 - " +timeout 10 $CLIENT -w -v8 -E $INDIR/interop/a_client.conf,/tmp/client_a -m put -e "%8a" -t 0 -O 5 coap://$TARGET_IP:$S_PORT_B/oscore/hello/7 > /tmp/client_out 2>&1 +passfail 1 "^4.12 Precondition Failed" + +# Test 11 +if [ "$SUPPRESS" = no ] ; then + echo +fi +echo -n "Test 11 - " +timeout 10 $CLIENT -w -v8 -E $INDIR/interop/a_client.conf,/tmp/client_a -m delete coap://$TARGET_IP:$S_PORT_B/oscore/test > /tmp/client_out 2>&1 +passfail 1 "^v:1 t:CON c:2.02 i:" + +# Test 12 +if [ "$SUPPRESS" = no ] ; then + echo +fi +echo -n "Test 12 - " +timeout 10 $CLIENT -w -v8 -E $INDIR/interop/e_client.conf,/tmp/client_a coap://$TARGET_IP:$S_PORT_B/oscore/hello/1 > /tmp/client_out 2>&1 +passfail 1 "^4.01 Security context not found" + +# Test 13 +if [ "$SUPPRESS" = no ] ; then + echo +fi +echo -n "Test 13 - " +timeout 10 $CLIENT -w -v8 -E $INDIR/interop/f_client.conf,/tmp/client_a coap://$TARGET_IP:$S_PORT_B/oscore/hello/1 > /tmp/client_out 2>&1 +passfail 1 "^4.00 Decryption failed" + +# Test 14 +if [ "$SUPPRESS" = no ] ; then + echo +fi +echo -n "Test 14 - " +timeout 10 $CLIENT -w -v8 -E $INDIR/interop/g_client.conf,/tmp/client_a coap://$TARGET_IP:$S_PORT_B/oscore/hello/1 > /tmp/client_out 2>&1 +passfail 1 "WARN OSCORE: Decryption Failure, result code: -5" + +# Test 15 +if [ "$SUPPRESS" = no ] ; then + echo +fi +echo -n "Test 15 - " +timeout 10 $CLIENT -w -v8 -E $INDIR/interop/a_client.conf coap://$TARGET_IP:$S_PORT_B/oscore/hello/1 > /tmp/client_out 2>&1 +passfail 1 "^4.01 Replay detected" + +# Test 16 +if [ "$SUPPRESS" = no ] ; then + echo +fi +echo -n "Test 16 - " +timeout 10 $CLIENT -w -v8 -E $INDIR/interop/e_client.conf,/tmp/client_a coap://$TARGET_IP:$S_PORT_N/oscore/hello/coap > /tmp/client_out 2>&1 +passfail 1 "^4.02 Bad Option" + +# Test 17 +if [ "$SUPPRESS" = no ] ; then + echo +fi +echo -n "Test 17 - " +timeout 10 $CLIENT -w -v8 coap://$TARGET_IP:$S_PORT_N/oscore/hello/1 > /tmp/client_out 2>&1 +passfail 1 "^4.01 Unauthorized" + +KILL_SERVER=`basename $SERVER` +if [ ! -z "$KILL_SERVER" ] ; then + killall $KILL_SERVER +fi + +echo +echo =============== +echo Pass: $NO_PASS +echo Fail: $NO_FAIL +#Starts with test 0 +echo Total: 18 + +if [ "$NO_FAIL" != 0 ] ; then + exit 1 +fi