From f864e30034c8b0afbe9d3ffaab6f94389ed74ecf 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 | 781 +++++++++++++++++++++++++++++++ examples/oscore_testcases.sh | 105 +++++ 12 files changed, 973 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 100644 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 c1e1499e04..673dedd542 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 f198e35b48..f4a3274f8e 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..7c667a4692 --- /dev/null +++ b/examples/oscore-interop-server.c @@ -0,0 +1,781 @@ +/* -*- 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 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) 2022 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); + 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) 2022 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(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(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) == 0) { + /* 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 = LOG_WARNING; + 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://tools.ietf.org/html/rfc7959#section-2.10 */ + COAP_OPTION_MAXAGE, + /* See https://tools.ietf.org/html/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(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 100644 index 0000000000..1a8f05707c --- /dev/null +++ b/examples/oscore_testcases.sh @@ -0,0 +1,105 @@ +#!/bin/bash + +if [ ! -z "$1" ] ; then + TARGET_IP=$1 + echo Target IP $TARGET_IP +else + TARGET_IP=127.0.0.1 +fi + +#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 + +./oscore-interop-server -E interop/b_server.conf -v8 -p $S_PORT_B > /tmp/server_b 2>&1 & +./oscore-interop-server -E interop/d_server.conf -v8 -p $S_PORT_D > /tmp/server_d 2>&1 & +./oscore-interop-server -v8 -p $S_PORT_N > /tmp/server_n 2>&1 & + +rm -f /tmp/client_a +rm -f /tmp/client_c + +# Test 0 General checkout +echo Test 0 +./coap-client -w -v8 coap://$TARGET_IP:$S_PORT_B/oscore/hello/coap 2>&1 | egrep -v " DEBG | OSC " + +# Test 1 +echo Test 1 +./coap-client -w -v8 -E interop/a_client.conf,/tmp/client_a coap://$TARGET_IP:$S_PORT_B/oscore/hello/1 2>&1 | egrep -v " DEBG | OSC " + +# Test 2 +echo Test 2 +./coap-client -w -v8 -E interop/c_client.conf,/tmp/client_c coap://$TARGET_IP:$S_PORT_D/oscore/hello/1 2>&1 | egrep -v " DEBG | OSC " + +# Test 3 +echo Test 3 +./coap-client -w -v8 -E interop/a_client.conf,/tmp/client_a coap://$TARGET_IP:$S_PORT_B/oscore/hello/2?first=1 2>&1 | egrep -v " DEBG | OSC " + +# Test 4 +echo Test 4 +./coap-client -w -v8 -E interop/a_client.conf,/tmp/client_a -A 0 coap://$TARGET_IP:$S_PORT_B/oscore/hello/3 2>&1 | egrep -v " DEBG | OSC " + +# Test 5 +echo Test 5 +./coap-client -w -v8 -E interop/a_client.conf,/tmp/client_a -s 2 coap://$TARGET_IP:$S_PORT_B/oscore/hello/1 2>&1 | egrep -v " DEBG | OSC " + +# Test 6 +echo Test 6 +./coap-client -w -v8 -E interop/a_client.conf,/tmp/client_a -s 4 coap://$TARGET_IP:$S_PORT_B/oscore/observe1 2>&1 | egrep -v " DEBG | OSC " + +# Test 7 +echo Test 7 +./coap-client -w -v8 -E interop/a_client.conf,/tmp/client_a -s 2 coap://$TARGET_IP:$S_PORT_B/oscore/observe2 2>&1 | egrep -v " DEBG | OSC " + +# Test 8 +echo Test 8 +./coap-client -w -v8 -E interop/a_client.conf,/tmp/client_a -m post -e "%4a" -t 0 coap://$TARGET_IP:$S_PORT_B/oscore/hello/6 2>&1 | egrep -v " DEBG | OSC " + +# Test 9 +echo Test 9 +./coap-client -w -v8 -E 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 2>&1 | egrep -v " DEBG | OSC " + +# Test 10 +echo Test 10 +./coap-client -w -v8 -E interop/a_client.conf,/tmp/client_a -m put -e "%8a" -t 0 -O 5 coap://$TARGET_IP:$S_PORT_B/oscore/hello/7 2>&1 | egrep -v " DEBG | OSC " + +# Test 11 +echo +echo Test 11 +./coap-client -w -v8 -E interop/a_client.conf,/tmp/client_a -m delete coap://$TARGET_IP:$S_PORT_B/oscore/test 2>&1 | egrep -v " DEBG | OSC " + +# Test 12 +echo +echo Test 12 +./coap-client -w -v8 -E interop/e_client.conf,/tmp/client_a coap://$TARGET_IP:$S_PORT_B/oscore/hello/1 2>&1 | egrep -v " DEBG | OSC " + +# Test 13 +echo +echo Test 13 +./coap-client -w -v8 -E interop/f_client.conf,/tmp/client_a coap://$TARGET_IP:$S_PORT_B/oscore/hello/1 2>&1 | egrep -v " DEBG | OSC " + +# Test 14 +echo +echo Test 14 +./coap-client -w -v8 -E interop/g_client.conf,/tmp/client_a coap://$TARGET_IP:$S_PORT_B/oscore/hello/1 2>&1 | egrep -v " DEBG | OSC " + +# Test 15 +echo +echo Test 15 +./coap-client -w -v8 -E interop/a_client.conf coap://$TARGET_IP:$S_PORT_B/oscore/hello/1 2>&1 | egrep -v " DEBG | OSC " +# ./coap-client -w -v8 -E interop/a_client.conf coap://$TARGET_IP:$S_PORT_B/oscore/hello/1 2>&1 | egrep -v " DEBG | OSC " + +# Test 16 +echo +echo Test 16 +./coap-client -w -v8 -E interop/e_client.conf,/tmp/client_a coap://$TARGET_IP:$S_PORT_N/oscore/hello/coap 2>&1 | egrep -v " DEBG | OSC " + +# Test 17 +echo +echo Test 17 +./coap-client -w -v8 coap://$TARGET_IP:$S_PORT_N/oscore/hello/1 2>&1 | egrep -v " DEBG | OSC " + + +killall oscore-interop-server