From 9a8a4f62092f310ad35aa09472b9ca98cff2e91d Mon Sep 17 00:00:00 2001 From: Jon Shallow Date: Sun, 10 Oct 2021 20:06:07 +0100 Subject: [PATCH] RFC8613: Add in OSCORE support Based on work done in https://gitlab.informatik.uni-bremen.de/obergman/libcoap New directories with files include/oscore src/oscore New files src/coap_oscore.c include/oscore/coap_oscore.h include/oscore/coap_crypto_internal.h include/oscore/coap_oscore_internal.h man/coap-oscore-conf.txt.in man/coap_oscore.txt.in Supported by new option (enabled by default) ./configure --enable-oscore cmake .. -DENABLE_OSCORE=ON Requires a TLS library configured to do the OSCORE encryption and hashing. --- CMakeLists.txt | 28 +- LICENSE | 30 + Makefile.am | 32 + Makefile.libcoap | 2 +- README.md | 2 + cmake_coap_config.h.in | 3 + coap_config.h.windows | 4 + coap_config.h.windows.in | 4 + configure.ac | 34 + doc/Doxyfile.in | 2 + doc/Makefile.am | 10 +- doc/main.md | 2 + examples/coap-client.c | 210 ++- examples/coap-rd.c | 2 +- examples/coap-server.c | 151 +- examples/lwip/Makefile | 2 +- include/coap3/coap.h.in | 6 + include/coap3/coap.h.windows | 1 + include/coap3/coap.h.windows.in | 1 + include/coap3/coap_crypto_internal.h | 155 ++ include/coap3/coap_dtls.h | 2 +- include/coap3/coap_forward_decls.h | 8 + include/coap3/coap_internal.h | 2 + include/coap3/coap_net_internal.h | 3 + include/coap3/coap_oscore.h | 187 +++ include/coap3/coap_oscore_internal.h | 127 ++ include/coap3/coap_pdu_internal.h | 5 + include/coap3/coap_session_internal.h | 10 +- include/coap3/mem.h | 9 +- include/coap3/pdu.h | 7 + include/coap3/uri.h | 5 +- include/oscore/oscore.h | 130 ++ include/oscore/oscore_cbor.h | 144 ++ include/oscore/oscore_context.h | 269 ++++ include/oscore/oscore_cose.h | 389 +++++ include/oscore/oscore_crypto.h | 95 ++ libcoap-3.map | 8 + libcoap-3.sym | 8 + man/Makefile.am | 5 +- man/coap-client.txt.in | 17 +- man/coap-oscore-conf.txt.in | 178 +++ man/coap-server.txt.in | 27 +- man/coap_oscore.txt.in | 364 +++++ man/coap_pdu_setup.txt.in | 2 + src/block.c | 40 +- src/coap_debug.c | 77 +- src/coap_gnutls.c | 227 +++ src/coap_mbedtls.c | 305 ++++ src/coap_notls.c | 111 ++ src/coap_openssl.c | 466 +++++- src/coap_oscore.c | 2074 +++++++++++++++++++++++++ src/coap_session.c | 3 + src/coap_tinydtls.c | 211 +++ src/net.c | 168 +- src/oscore/oscore.c | 587 +++++++ src/oscore/oscore_cbor.c | 428 +++++ src/oscore/oscore_context.c | 697 +++++++++ src/oscore/oscore_cose.c | 524 +++++++ src/oscore/oscore_crypto.c | 230 +++ src/resource.c | 10 +- src/uri.c | 9 + tests/Makefile.am | 3 +- tests/test_oscore.c | 963 ++++++++++++ tests/test_oscore.h | 13 + tests/testdriver.c | 4 + win32/libcoap.props | 1 + win32/libcoap.vcxproj | 14 + win32/libcoap.vcxproj.filters | 42 + 68 files changed, 9763 insertions(+), 126 deletions(-) create mode 100644 include/coap3/coap_crypto_internal.h create mode 100644 include/coap3/coap_oscore.h create mode 100644 include/coap3/coap_oscore_internal.h create mode 100644 include/oscore/oscore.h create mode 100644 include/oscore/oscore_cbor.h create mode 100644 include/oscore/oscore_context.h create mode 100644 include/oscore/oscore_cose.h create mode 100644 include/oscore/oscore_crypto.h create mode 100644 man/coap-oscore-conf.txt.in create mode 100644 man/coap_oscore.txt.in create mode 100644 src/coap_oscore.c create mode 100644 src/oscore/oscore.c create mode 100644 src/oscore/oscore_cbor.c create mode 100644 src/oscore/oscore_context.c create mode 100644 src/oscore/oscore_cose.c create mode 100644 src/oscore/oscore_crypto.c create mode 100644 tests/test_oscore.c create mode 100644 tests/test_oscore.h diff --git a/CMakeLists.txt b/CMakeLists.txt index dfb3fac888..4d50f4d0f4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -24,6 +24,7 @@ option( OFF) add_library(${COAP_LIBRARY_NAME}) +set_property(TARGET ${COAP_LIBRARY_NAME} PROPERTY C_STANDARD 99) # # options to tweak the library @@ -65,6 +66,10 @@ option( ENABLE_SERVER_MODE "compile with support for server mode code" ON) +option( + ENABLE_OSCORE + "compile with support for OSCORE" + ON) option( WITH_EPOLL "compile with epoll support" @@ -186,6 +191,13 @@ else() message(STATUS "compiling without server support") endif() +if(${ENABLE_OSCORE}) + set(HAVE_OSCORE "1") + message(STATUS "compiling with OSCORE support") +else() + message(STATUS "compiling without OSCORE support") +endif() + if(${WITH_EPOLL} AND ${HAVE_EPOLL_H} AND ${HAVE_TIMERFD_H}) @@ -383,6 +395,7 @@ message(STATUS "ENABLE_DTLS:.....................${ENABLE_DTLS}") message(STATUS "ENABLE_TCP:......................${ENABLE_TCP}") message(STATUS "ENABLE_CLIENT_MODE:..............${ENABLE_CLIENT_MODE}") message(STATUS "ENABLE_SERVER_MODE:..............${ENABLE_SERVER_MODE}") +message(STATUS "ENABLE_OSCORE:...................${ENABLE_OSCORE}") message(STATUS "ENABLE_DOCS:.....................${ENABLE_DOCS}") message(STATUS "ENABLE_EXAMPLES:.................${ENABLE_EXAMPLES}") message(STATUS "DTLS_BACKEND:....................${DTLS_BACKEND}") @@ -453,6 +466,7 @@ target_sources( ${CMAKE_CURRENT_LIST_DIR}/src/coap_hashkey.c ${CMAKE_CURRENT_LIST_DIR}/src/coap_io.c ${CMAKE_CURRENT_LIST_DIR}/src/coap_notls.c + ${CMAKE_CURRENT_LIST_DIR}/src/coap_oscore.c ${CMAKE_CURRENT_LIST_DIR}/src/coap_prng.c ${CMAKE_CURRENT_LIST_DIR}/src/coap_session.c ${CMAKE_CURRENT_LIST_DIR}/src/coap_tcp.c @@ -471,6 +485,12 @@ target_sources( $<$:${CMAKE_CURRENT_LIST_DIR}/src/coap_tinydtls.c> $<$:${CMAKE_CURRENT_LIST_DIR}/src/coap_gnutls.c> $<$:${CMAKE_CURRENT_LIST_DIR}/src/coap_mbedtls.c> + # needed for OSCORE is enabled + $<$:${CMAKE_CURRENT_LIST_DIR}/src/oscore/oscore.c> + $<$:${CMAKE_CURRENT_LIST_DIR}/src/oscore/oscore_cbor.c> + $<$:${CMAKE_CURRENT_LIST_DIR}/src/oscore/oscore_context.c> + $<$:${CMAKE_CURRENT_LIST_DIR}/src/oscore/oscore_cose.c> + $<$:${CMAKE_CURRENT_LIST_DIR}/src/oscore/oscore_crypto.c> # headers ${CMAKE_CURRENT_LIST_DIR}/include/coap${LIBCOAP_API_VERSION}/address.h ${CMAKE_CURRENT_LIST_DIR}/include/coap${LIBCOAP_API_VERSION}/async.h @@ -480,7 +500,6 @@ target_sources( ${CMAKE_CURRENT_LIST_DIR}/include/coap${LIBCOAP_API_VERSION}/coap_dtls.h ${CMAKE_CURRENT_LIST_DIR}/include/coap${LIBCOAP_API_VERSION}/coap_event.h ${CMAKE_CURRENT_LIST_DIR}/include/coap${LIBCOAP_API_VERSION}/coap.h - ${CMAKE_CURRENT_LIST_DIR}/include/coap${LIBCOAP_API_VERSION}/coap_hashkey.h ${CMAKE_CURRENT_LIST_DIR}/include/coap${LIBCOAP_API_VERSION}/coap_io.h ${CMAKE_CURRENT_LIST_DIR}/include/coap${LIBCOAP_API_VERSION}/coap_session.h ${CMAKE_CURRENT_LIST_DIR}/include/coap${LIBCOAP_API_VERSION}/coap_time.h @@ -560,6 +579,8 @@ if(ENABLE_TESTS) ${CMAKE_CURRENT_LIST_DIR}/tests/test_error_response.h ${CMAKE_CURRENT_LIST_DIR}/tests/test_options.c ${CMAKE_CURRENT_LIST_DIR}/tests/test_options.h + ${CMAKE_CURRENT_LIST_DIR}/tests/test_oscore.c + ${CMAKE_CURRENT_LIST_DIR}/tests/test_oscore.h ${CMAKE_CURRENT_LIST_DIR}/tests/test_pdu.c ${CMAKE_CURRENT_LIST_DIR}/tests/test_pdu.h ${CMAKE_CURRENT_LIST_DIR}/tests/test_sendqueue.c @@ -692,7 +713,10 @@ install( PATTERN "lwippools.h" EXCLUDE PATTERN "utlist.h" EXCLUDE PATTERN "uthash.h" EXCLUDE - PATTERN "*_internal.h" EXCLUDE) + PATTERN "coap_hashkey.h" EXCLUDE + PATTERN "coap_mutex.h" EXCLUDE + PATTERN "*_internal.h" EXCLUDE + PATTERN "oscore*" EXCLUDE) install( DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/include/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} diff --git a/LICENSE b/LICENSE index 663d472517..22f7d4484b 100644 --- a/LICENSE +++ b/LICENSE @@ -53,6 +53,36 @@ libcoap uses uthash.h and utlist.h from Troy D. Hanson BSD license (BSD-1-Clause license) as included in these two source files. +======================================================================== +oscore cose + +Copyright (c) 2018, SICS, RISE AB +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +3. Neither the name of the Institute nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +SUCH DAMAGE. + ======================================================================== OpenSSL diff --git a/Makefile.am b/Makefile.am index 7cb01183ff..1a14b16bb6 100644 --- a/Makefile.am +++ b/Makefile.am @@ -40,9 +40,11 @@ EXTRA_DIST = \ include/coap$(LIBCOAP_API_VERSION)/coap_async_internal.h \ include/coap$(LIBCOAP_API_VERSION)/coap_block_internal.h \ include/coap$(LIBCOAP_API_VERSION)/coap_cache_internal.h \ + include/coap$(LIBCOAP_API_VERSION)/coap_crypto_internal.h \ include/coap$(LIBCOAP_API_VERSION)/coap_dtls_internal.h \ include/coap$(LIBCOAP_API_VERSION)/coap_io_internal.h \ include/coap$(LIBCOAP_API_VERSION)/coap_net_internal.h \ + include/coap$(LIBCOAP_API_VERSION)/coap_oscore_internal.h \ include/coap$(LIBCOAP_API_VERSION)/coap_pdu_internal.h \ include/coap$(LIBCOAP_API_VERSION)/coap_resource_internal.h \ include/coap$(LIBCOAP_API_VERSION)/coap_session_internal.h \ @@ -54,10 +56,16 @@ EXTRA_DIST = \ include/coap$(LIBCOAP_API_VERSION)/lwippools.h \ include/coap$(LIBCOAP_API_VERSION)/uthash.h \ include/coap$(LIBCOAP_API_VERSION)/utlist.h \ + include/oscore/oscore_cbor.h \ + include/oscore/oscore_context.h \ + include/oscore/oscore_cose.h \ + include/oscore/oscore_crypto.h \ + include/oscore/oscore.h \ src/coap_io_riot.c \ tests/test_error_response.h \ tests/test_encode.h \ tests/test_options.h \ + tests/test_oscore.h \ tests/test_pdu.h \ tests/test_sendqueue.h \ tests/test_session.h \ @@ -77,6 +85,18 @@ EXTRA_DIST = \ win32/testdriver/testdriver.vcxproj.filters \ win32/testdriver/testdriver.vcxproj.user +# This is a mirror of files depending on HAVE_OSCORE included in src as per +# libcoap_@LIBCOAP_NAME_SUFFIX@_la_SOURCES +if !HAVE_OSCORE +EXTRA_DIST += \ + src/oscore/oscore.c \ + src/oscore/oscore_cbor.c \ + src/oscore/oscore_context.c \ + src/oscore/oscore_cose.c \ + src/oscore/oscore_crypto.c + +endif # !HAVE_OSCORE + AM_CFLAGS = -I$(top_builddir)/include -I$(top_srcdir)/include $(WARNING_CFLAGS) $(DTLS_CFLAGS) -std=c99 $(EXTRA_CFLAGS) SUBDIRS = $(subdirs) . man doc tests examples @@ -111,6 +131,7 @@ libcoap_@LIBCOAP_NAME_SUFFIX@_la_SOURCES = \ src/coap_mbedtls.c \ src/coap_notls.c \ src/coap_openssl.c \ + src/coap_oscore.c \ src/coap_prng.c \ src/coap_session.c \ src/coap_tcp.c \ @@ -126,6 +147,16 @@ libcoap_@LIBCOAP_NAME_SUFFIX@_la_SOURCES = \ src/subscribe.c \ src/uri.c +if HAVE_OSCORE +libcoap_@LIBCOAP_NAME_SUFFIX@_la_SOURCES += \ + src/oscore/oscore.c \ + src/oscore/oscore_cbor.c \ + src/oscore/oscore_context.c \ + src/oscore/oscore_cose.c \ + src/oscore/oscore_crypto.c + +endif # HAVE_OSCORE + ## Define the list of public header files and their install location. ## The API version is appended to the install folder to being able to ## co-install various versions of libcoap. @@ -150,6 +181,7 @@ libcoap_include_HEADERS = \ $(top_srcdir)/include/coap$(LIBCOAP_API_VERSION)/coap_hashkey.h \ $(top_srcdir)/include/coap$(LIBCOAP_API_VERSION)/coap_io.h \ $(top_srcdir)/include/coap$(LIBCOAP_API_VERSION)/coap_mutex.h \ + $(top_srcdir)/include/coap$(LIBCOAP_API_VERSION)/coap_oscore.h \ $(top_srcdir)/include/coap$(LIBCOAP_API_VERSION)/coap_session.h \ $(top_srcdir)/include/coap$(LIBCOAP_API_VERSION)/coap_time.h \ $(top_srcdir)/include/coap$(LIBCOAP_API_VERSION)/encode.h \ diff --git a/Makefile.libcoap b/Makefile.libcoap index ea868df682..5c97006f1c 100644 --- a/Makefile.libcoap +++ b/Makefile.libcoap @@ -1,4 +1,4 @@ -libcoap_src = pdu.c net.c coap_cache.c coap_debug.c encode.c uri.c subscribe.c resource.c str.c option.c async.c block.c mem.c coap_io.c coap_session.c coap_notls.c coap_hashkey.c address.c coap_tcp.c +libcoap_src = pdu.c net.c coap_cache.c coap_debug.c encode.c uri.c subscribe.c resource.c str.c option.c async.c block.c mem.c coap_io.c coap_session.c coap_notls.c coap_hashkey.c address.c coap_tcp.c coap_oscore.c libcoap_dir := $(filter %libcoap,$(APPDS)) vpath %c $(libcoap_dir)/src diff --git a/README.md b/README.md index 712d66e8d5..4a1d738453 100644 --- a/README.md +++ b/README.md @@ -49,6 +49,8 @@ The following RFCs are supported * RFC8323: CoAP (Constrained Application Protocol) over TCP, TLS, and WebSockets [No WebSockets support] +* RFC8613: Object Security for Constrained RESTful Environments (OSCORE) + * RFC8768: Constrained Application Protocol (CoAP) Hop-Limit Option There is (D)TLS support for the following libraries diff --git a/cmake_coap_config.h.in b/cmake_coap_config.h.in index b7c2ed0b14..b2f4fe23b7 100644 --- a/cmake_coap_config.h.in +++ b/cmake_coap_config.h.in @@ -30,6 +30,9 @@ /* Define if the system has epoll support */ #cmakedefine COAP_EPOLL_SUPPORT "@COAP_EPOLL_SUPPORT@" +/* Define if the library has OSCORE support */ +#cmakedefine HAVE_OSCORE @HAVE_OSCORE@ + /* Define to 1 if you have the header file. */ #cmakedefine HAVE_ARPA_INET_H "@HAVE_ARPA_INET_H@" diff --git a/coap_config.h.windows b/coap_config.h.windows index 6a81518aa6..e27d178ed9 100644 --- a/coap_config.h.windows +++ b/coap_config.h.windows @@ -71,6 +71,10 @@ #define COAP_DISABLE_TCP 0 #endif +#ifndef HAVE_OSCORE +#define HAVE_OSCORE 1 +#endif + /* Define to the address where bug reports for this package should be sent. */ #define PACKAGE_BUGREPORT "libcoap-developers@lists.sourceforge.net" diff --git a/coap_config.h.windows.in b/coap_config.h.windows.in index 84ef458cff..0c5049414e 100644 --- a/coap_config.h.windows.in +++ b/coap_config.h.windows.in @@ -71,6 +71,10 @@ #define COAP_DISABLE_TCP 0 #endif +#ifndef HAVE_OSCORE +#define HAVE_OSCORE 1 +#endif + /* Define to the address where bug reports for this package should be sent. */ #define PACKAGE_BUGREPORT "@PACKAGE_BUGREPORT@" diff --git a/configure.ac b/configure.ac index 918ccddc48..72aef7c63b 100644 --- a/configure.ac +++ b/configure.ac @@ -575,7 +575,9 @@ elif test "x$with_tinydtls" = "xyes"; then LIBCOAP_DTLS_LIB_EXTENSION_NAME=-tinydtls else LIBCOAP_DTLS_LIB_EXTENSION_NAME=-notls + AC_DEFINE(HAVE_NOTLS, [1], [Define if libcoap has no tls library support]) fi +AM_CONDITIONAL(HAVE_NOTLS, [test "x$LIBCOAP_DTLS_LIB_EXTENSION_NAME" = "x-notls"]) LIBCOAP_NAME_SUFFIX="$LIBCOAP_API_VERSION$LIBCOAP_DTLS_LIB_EXTENSION_NAME" @@ -583,6 +585,26 @@ AC_SUBST(LIBCOAP_NAME_SUFFIX) AC_SUBST(LIBCOAP_DTLS_LIB_EXTENSION_NAME) AC_SUBST([DOLLAR_SIGN],[$]) +# configure options +# __OSCORE__ +# Support for Object Security according to RFC 8613. +AC_ARG_ENABLE([oscore], + [AS_HELP_STRING([--enable-oscore], + [Enable building with OSCORE support [default=yes]])], + [build_oscore="$enableval"], + [build_oscore="yes"]) + +if test "x$build_oscore" = "xyes"; then + if test "x$LIBCOAP_DTLS_LIB_EXTENSION_NAME" = "x-notls"; then + AC_MSG_WARN([==> --enable-oscore requires crypto support from TLS library or OS]) + fi +fi + +if test "x$build_oscore" = "xyes"; then + AC_DEFINE(HAVE_OSCORE, [1], [Define to build with OSCORE support]) +fi +AM_CONDITIONAL(HAVE_OSCORE, [test "x$build_oscore" = "xyes"]) + # configure options # __tests__ AC_ARG_ENABLE([tests], @@ -903,6 +925,7 @@ man/coap_io.txt man/coap_keepalive.txt man/coap_logging.txt man/coap_observe.txt +man/coap_oscore.txt man/coap_pdu_access.txt man/coap_pdu_setup.txt man/coap_recovery.txt @@ -911,6 +934,7 @@ man/coap_session.txt man/coap_string.txt man/coap_tls_library.txt man/coap-client.txt +man/coap-oscore-conf.txt man/coap-server.txt man/coap-rd.txt man/Makefile @@ -985,6 +1009,16 @@ if test "x$build_async" != "xno"; then else AC_MSG_RESULT([ enable separate responses: "no"]) fi +if test "x$build_oscore" != "xno"; then + AC_MSG_RESULT([ enable OSCORE support : "yes"]) +else + AC_MSG_RESULT([ enable OSCORE support : "no"]) +fi +if test "x$build_oscore_group" != "xno"; then + AC_MSG_RESULT([ enable Group OSCORE : "yes"]) +else + AC_MSG_RESULT([ enable Group OSCORE : "no"]) +fi if test "x$build_doxygen" = "xyes"; then AC_MSG_RESULT([ build doxygen pages : "yes"]) AC_MSG_RESULT([ --> Doxygen around : "yes" ($DOXYGEN $doxygen_version)]) diff --git a/doc/Doxyfile.in b/doc/Doxyfile.in index 782410d88c..49c712324f 100644 --- a/doc/Doxyfile.in +++ b/doc/Doxyfile.in @@ -857,7 +857,9 @@ WARN_LOGFILE = INPUT = @top_srcdir@/doc/main.md \ @top_srcdir@/doc/module_api_wrap.h \ @top_srcdir@/src \ + @top_srcdir@/src/oscore \ @top_srcdir@/include/coap@LIBCOAP_API_VERSION@ \ + @top_srcdir@/include/oscore \ @top_builddir@/doc/man_tmp # This tag can be used to specify the character encoding of the source files diff --git a/doc/Makefile.am b/doc/Makefile.am index 4f537ebb5e..a2a94bd96c 100644 --- a/doc/Makefile.am +++ b/doc/Makefile.am @@ -17,7 +17,8 @@ CLEANFILES = \ EXTRA_DIST = \ docbook.local.css \ upgrade_4.2.1_4.3.0.txt \ - main.md + main.md \ + module_api_wrap.h if HAVE_DOXYGEN @@ -55,6 +56,9 @@ man-page-build: upg-page-build man-page-start BASE=`basename $${FILE} | cut -d. -f 1` ;\ MANUAL=`egrep -B 1 "^====" $${FILE} | head -1` ;\ SUMMARY=`egrep -B 2 "^SYNOPSIS" $${FILE} | sed 's/coap-//g' | cut -d\- -f2 | cut -c2- | head -1` ;\ + if [ -z "$${SUMMARY}" ] ; then \ + SUMMARY=`egrep -B 2 "^DESCRIPTION" $${FILE} | sed 's/coap-//g' | cut -d\- -f2 | cut -c2- | head -1` ;\ + fi ;\ ## Build the manual insert page echo "/// @page man_$${BASE} $${MANUAL}" > $(top_builddir)/doc/man_tmp/$${MANUAL}.dox ;\ echo "/// @htmlinclude $${BASE}.html $${MANUAL}" >> $(top_builddir)/doc/man_tmp/$${MANUAL}.dox ;\ @@ -64,7 +68,7 @@ man-page-build: upg-page-build man-page-start echo " " >> $(top_builddir)/doc/man_tmp/manpage.dox ;\ echo " @ref man_$${BASE} $${MANUAL} - $${SUMMARY}" >> $(top_builddir)/doc/man_tmp/manpage.dox ;\ echo " " >> $(top_builddir)/doc/man_tmp/manpage.dox ;\ - if [ -z $${ROW_EVEN} ] ; then \ + if [ -z "$${ROW_EVEN}" ] ; then \ ROW_EVEN=" class=\"even\"" ;\ else \ ROW_EVEN= ;\ @@ -92,7 +96,7 @@ man-page-build: upg-page-build man-page-start echo " " >> $(top_builddir)/doc/man_tmp/upgrading.dox ;\ echo " @ref upg_$${CUPGRADE} $${SUMMARY}" >> $(top_builddir)/doc/man_tmp/upgrading.dox ;\ echo " " >> $(top_builddir)/doc/man_tmp/upgrading.dox ;\ - if [ -z $${ROW_EVEN} ] ; then \ + if [ -z "$${ROW_EVEN}" ] ; then \ ROW_EVEN=" class=\"even\"" ;\ else \ ROW_EVEN= ;\ diff --git a/doc/main.md b/doc/main.md index 3ffe3d1b94..7112484994 100644 --- a/doc/main.md +++ b/doc/main.md @@ -34,6 +34,8 @@ The following RFCs are supported * RFC8323: CoAP (Constrained Application Protocol) over TCP, TLS, and WebSockets [No WebSockets support] +* RFC8613: Object Security for Constrained RESTful Environments (OSCORE) + * RFC8768: Constrained Application Protocol (CoAP) Hop-Limit Option There is (D)TLS support for the following libraries diff --git a/examples/coap-client.c b/examples/coap-client.c index 890909b16a..86046d9770 100644 --- a/examples/coap-client.c +++ b/examples/coap-client.c @@ -142,6 +142,9 @@ int doing_observe = 0; #define min(a,b) ((a) < (b) ? (a) : (b)) #endif +static coap_oscore_context_t *oscore_ctx = NULL; +static int doing_oscore = 0; + static int quit = 0; /* SIGINT handler: set quit to 1 for graceful termination */ @@ -485,7 +488,7 @@ message_handler(coap_session_t *session COAP_UNUSED, static void usage( const char *program, const char *version) { const char *p; - char buffer[72]; + char buffer[120]; const char *lib_version = coap_package_version(); p = strrchr( program, '/' ); @@ -502,8 +505,9 @@ usage( const char *program, const char *version) { fprintf(stderr, "\n" "Usage: %s [-a addr] [-b [num,]size] [-e text] [-f file] [-l loss]\n" "\t\t[-m method] [-o file] [-p port] [-r] [-s duration] [-t type]\n" - "\t\t[-v num] [-w] [-A type] [-B seconds] [G count] [-H hoplimit]\n" - "\t\t[-K interval] [-N] [-O num,text] [-P scheme://address[:port]]\n" + "\t\t[-v num] [-w] [-A type] [-B seconds]\n" + "\t\t[-E oscore_conf_file[,seq_file]] [-G count] [-H hoplimit]\n" + "\t\t[-K interval] [-N] [-O num,text] [-P scheme://address[:port]\n" "\t\t[-T token] [-U]\n" "\t\t[[-h match_hint_file] [-k key] [-u user]]\n" "\t\t[[-c certfile] [-j keyfile] [-n] [-C cafile]\n" @@ -538,6 +542,11 @@ usage( const char *program, const char *version) { "\t-A type\t\tAccepted media type\n" "\t-B seconds\tBreak operation after waiting given seconds\n" "\t \t\t(default is %d)\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 count\tRepeat the Request 'count' times. Must have a value\n" "\t \t\tbetween 1 and 255 inclusive. Default is '1'\n" "\t-H hoplimit\tSet the Hop Limit count to hoplimit for proxies. Must\n" @@ -551,9 +560,10 @@ usage( const char *program, const char *version) { "\t-O num,text\tAdd option num with contents text to request. If the\n" "\t \t\ttext begins with 0x, then the hex text (two [0-9a-f] per\n" "\t \t\tbyte) is converted to binary data\n" - "\t-P scheme://address[:port]\tScheme, address and optional port to\n" - "\t \t\tdefine how to connect to a CoAP proxy (automatically adds\n" - "\t \t\tProxy-Uri option to request) to forward the request to.\n" + "\t-P scheme://address[:port]\n" + "\t \t\tScheme, address and optional port to define how to\n" + "\t \t\tconnect to a CoAP proxy (automatically adds Proxy-Uri\n" + "\t \t\toption to request) to forward the request to.\n" "\t \t\tScheme is one of coap, coaps, coap+tcp and coaps+tcp\n" "\t-T token\tDefine the initial starting token\n" "\t-U \t\tNever include Uri-Host or Uri-Port options\n" @@ -597,7 +607,8 @@ usage( const char *program, const char *version) { "\t \t\tcontains both PUBLIC KEY and PRIVATE KEY or just\n" "\t \t\tEC PRIVATE KEY. (GnuTLS and TinyDTLS(PEM) support only).\n" "\t \t\t'-C cafile' or '-R trust_casfile' are not required\n" - "\t-R trust_casfile\tPEM file containing the set of trusted root CAs\n" + "\t-R trust_casfile\n" + "\t \t\tPEM file containing the set of trusted root CAs\n" "\t \t\tthat are to be used to validate the server certificate.\n" "\t \t\tAlternatively, this can point to a directory containing\n" "\t \t\ta set of CA PEM files.\n" @@ -691,6 +702,110 @@ cmdline_hop_limit(char *arg) { return 1; } +static uint8_t *read_file_mem(const char* filename, size_t *length) { + FILE *f; + uint8_t *buf; + struct stat statbuf; + + *length = 0; + if (!filename || !(f = fopen(filename, "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 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_context_t * +get_oscore_context(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; + } + } + fscanf(oscore_seq_num_fp, "%ju", &start_seq_num); + } + oscore_ctx = coap_new_oscore_context(context, file_mem, + oscore_save_seq_num, + NULL, start_seq_num); + coap_free(buf); + if (oscore_ctx == NULL) { + fprintf(stderr, "OSCORE configuraton file error: %s\n", oscore_conf_file); + return NULL; + } + return oscore_ctx; +} + +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; +} static uint16_t get_default_port(const coap_uri_t *u) { @@ -1194,37 +1309,6 @@ static int cmdline_read_hint_check(const char *arg) { return valid_ihs.count > 0; } -static uint8_t *read_file_mem(const char* filename, size_t *length) { - FILE *f; - uint8_t *buf; - struct stat statbuf; - - *length = 0; - if (!filename || !(f = fopen(filename, "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 int verify_cn_callback(const char *cn, const uint8_t *asn1_public_cert COAP_UNUSED, @@ -1407,24 +1491,49 @@ open_session( if (root_ca_file || ca_file || cert_file) { /* Setup PKI session */ coap_dtls_pki_t *dtls_pki = setup_pki(ctx); - session = coap_new_client_session_pki(ctx, bind_addr, dst, proto, dtls_pki); + if (doing_oscore) { + session = coap_new_client_session_oscore_pki(ctx, bind_addr, dst, + proto, dtls_pki, + oscore_ctx); + } + else + session = coap_new_client_session_pki(ctx, bind_addr, dst, proto, + dtls_pki); } else if (identity || key) { /* Setup PSK session */ coap_dtls_cpsk_t *dtls_psk = setup_psk(identity, identity_len, key, key_len); - session = coap_new_client_session_psk2(ctx, bind_addr, dst, proto, + if (doing_oscore) { + session = coap_new_client_session_oscore_psk(ctx, bind_addr, dst, + proto, dtls_psk, + oscore_ctx); + } + else + session = coap_new_client_session_psk2(ctx, bind_addr, dst, proto, dtls_psk); } else { /* No PKI or PSK defined, as encrypted, use PKI */ coap_dtls_pki_t *dtls_pki = setup_pki(ctx); - session = coap_new_client_session_pki(ctx, bind_addr, dst, proto, dtls_pki); + if (doing_oscore) { + session = coap_new_client_session_oscore_pki(ctx, bind_addr, dst, + proto, dtls_pki, + oscore_ctx); + } + else + session = coap_new_client_session_pki(ctx, bind_addr, dst, proto, + dtls_pki); } } else { /* Non-encrypted session */ - session = coap_new_client_session(ctx, bind_addr, dst, proto); + if (doing_oscore) { + session = coap_new_client_session_oscore(ctx, bind_addr, dst, proto, + oscore_ctx); + } + else + session = coap_new_client_session(ctx, bind_addr, dst, proto); } return session; } @@ -1504,7 +1613,7 @@ main(int argc, char **argv) { struct sigaction sa; #endif - while ((opt = getopt(argc, argv, "a:b:c:e:f:h:j:k:l:m:no:p:rs:t:u:v:wA:B:C:G:H:J:K:L:M:NO:P:R:T:U")) != -1) { + while ((opt = getopt(argc, argv, "a:b:c:e:f:h:j:k:l:m:no:p:rs:t:u:v:wA:B:C:E:G:H:J:K:L:M:NO:P:R:T:U")) != -1) { switch (opt) { case 'a': strncpy(node_str, optarg, NI_MAXHOST - 1); @@ -1640,6 +1749,11 @@ main(int argc, char **argv) { repeat_count = 1; } break; + case 'E': + if (!cmdline_oscore(optarg)) { + exit(1); + } + break; default: usage( argv[0], LIBCOAP_PACKAGE_VERSION ); exit( 1 ); @@ -1688,6 +1802,12 @@ main(int argc, char **argv) { goto finish; } + if (doing_oscore) { + oscore_ctx = get_oscore_context(ctx); + if (oscore_ctx == NULL) + goto finish; + } + coap_context_set_keepalive(ctx, ping_seconds); coap_context_set_block_mode(ctx, block_mode); @@ -1845,7 +1965,9 @@ main(int argc, char **argv) { } free(tracked_tokens); coap_delete_optlist(optlist); - coap_session_release( session ); + if (oscore_seq_num_fp) + fclose(oscore_seq_num_fp); + coap_session_release(session); coap_free_context( ctx ); coap_cleanup(); close_output(); diff --git a/examples/coap-rd.c b/examples/coap-rd.c index ad93f41802..dea186f591 100644 --- a/examples/coap-rd.c +++ b/examples/coap-rd.c @@ -567,7 +567,7 @@ init_resources(coap_context_t *ctx) { static void usage( const char *program, const char *version) { const char *p; - char buffer[72]; + char buffer[120]; const char *lib_version = coap_package_version(); p = strrchr( program, '/' ); diff --git a/examples/coap-server.c b/examples/coap-server.c index 959d1491da..236ddfb6e0 100644 --- a/examples/coap-server.c +++ b/examples/coap-server.c @@ -68,6 +68,9 @@ static char* strndup(const char* s1, size_t n) #define min(a,b) ((a) < (b) ? (a) : (b)) #endif +static coap_oscore_context_t *oscore_ctx; +static int doing_oscore = 0; + /* temporary storage for dynamic resource representations */ static int quit = 0; @@ -758,6 +761,7 @@ verify_proxy_scheme_supported(coap_uri_scheme_t scheme) { return 0; } break; + case COAP_URI_SCHEME_LAST: default: coap_log(LOG_WARNING, "%d URI scheme not supported\n", scheme); @@ -970,6 +974,7 @@ get_ongoing_proxy_session(coap_session_t *session, break; case COAP_URI_SCHEME_HTTP: case COAP_URI_SCHEME_HTTPS: + case COAP_URI_SCHEME_LAST: default: assert(0); break; @@ -1104,6 +1109,8 @@ hnd_proxy_uri(coap_resource_t *resource COAP_UNUSED, ongoing = get_ongoing_proxy_session(session, response, &token, query, &uri); + if (!ongoing) + goto cleanup; /* * Build up the ongoing PDU that we are going to send */ @@ -2122,7 +2129,7 @@ fill_keystore(coap_context_t *ctx) { static void usage( const char *program, const char *version) { const char *p; - char buffer[72]; + char buffer[120]; const char *lib_version = coap_package_version(); p = strrchr( program, '/' ); @@ -2137,9 +2144,9 @@ usage( const char *program, const char *version) { 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] [-e] [-g group] [-G group_if] [-l loss] [-p port]\n" - "\t\t[-v num] [-A address] [-L value] [-N]\n" - "\t\t[-P scheme://address[:port],name1[,name2..]]\n" + "Usage: %s [-d max] [-e] [-g group] [-l loss] [-p port] [-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[[-h hint] [-i match_identity_file] [-k key]\n" "\t\t[-s match_psk_sni_file] [-u user]]\n" "\t\t[[-c certfile] [-j keyfile] [-m] [-n] [-C cafile]\n" @@ -2152,9 +2159,6 @@ usage( const char *program, const char *version) { "\t-e \t\tEcho back the data sent with a PUT\n" "\t-g group\tJoin the given multicast group\n" "\t \t\tNote: DTLS over multicast is not currently supported\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 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" @@ -2168,6 +2172,14 @@ usage( const char *program, const char *version) { "\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" @@ -2176,15 +2188,15 @@ usage( const char *program, const char *version) { "\t \t\tresponse (RFC 7641 requirement)\n" , program); fprintf( stderr, - "\t-P scheme://address[:port],name1[,name2[,name3..]]\tScheme, address,\n" - "\t \t\toptional port of how to connect to the next proxy server\n" - "\t \t\tand one or more names (comma separated) that this proxy\n" - "\t \t\tserver is known by. If the hostname of the incoming proxy\n" - "\t \t\trequest matches one of these names, then this server is\n" - "\t \t\tconsidered to be the final endpoint. If\n" - "\t \t\tscheme://address[:port] is not defined before the leading\n" - "\t \t\t, (comma) of the first name, then the ongoing connection\n" - "\t \t\twill be a direct connection.\n" + "\t-P scheme://address[:port],name1[,name2[,name3..]]\n" + "\t \t\tScheme, address, optional port of how to connect to the\n" + "\t \t\tnext proxy server and one or more names (comma\n" + "\t \t\tseparated) that this proxy server is known by. If the\n" + "\t \t\thostname of the incoming proxy request matches one of\n" + "\t \t\tthese names, then this server is considered to be the\n" + "\t \t\tfinal endpoint. If scheme://address[:port] is not\n" + "\t \t\tdefined before the leading , (comma) of the first name,\n" + "\t \t\tthen the ongoing connection will be a direct connection.\n" "\t \t\tScheme is one of coap, coaps, coap+tcp and coaps+tcp\n" "PSK Options (if supported by underlying (D)TLS library)\n" "\t-h hint\t\tIdentity Hint to send. Default is CoAP. Zero length is\n" @@ -2253,7 +2265,8 @@ usage( const char *program, const char *version) { "\t \t\tcontains both PUBLIC KEY and PRIVATE KEY or just\n" "\t \t\tEC PRIVATE KEY. (GnuTLS and TinyDTLS(PEM) support only).\n" "\t \t\t'-C cafile' or '-R trust_casfile' are not required\n" - "\t-R trust_casfile\tPEM file containing the set of trusted root CAs\n" + "\t-R trust_casfile\n" + "\t \t\tPEM file containing the set of trusted root CAs\n" "\t \t\tthat are to be used to validate the client certificate.\n" "\t \t\tAlternatively, this can point to a directory containing\n" "\t \t\ta set of CA PEM files.\n" @@ -2410,6 +2423,80 @@ cmdline_read_user(char *arg, unsigned char **buf, size_t maxlen) { } #endif /* SERVER_CAN_PROXY */ +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_context_t * +get_oscore_context(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; + } + } + fscanf(oscore_seq_num_fp, "%ju", &start_seq_num); + } + oscore_ctx = coap_new_oscore_context(context, file_mem, + oscore_save_seq_num, + NULL, start_seq_num); + coap_free(buf); + if (oscore_ctx == NULL) { + fprintf(stderr, "OSCORE configuraton file error: %s\n", oscore_conf_file); + return NULL; + } + return oscore_ctx; +} + +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; +} + static ssize_t cmdline_read_key(char *arg, unsigned char **buf, size_t maxlen) { size_t len = strnlen(arg, maxlen); @@ -2608,7 +2695,7 @@ main(int argc, char **argv) { clock_offset = time(NULL); - while ((opt = getopt(argc, argv, "c:d:eg:G:h:i:j:J:k:l:mnp:s:u:v:A:C:L:M:NP:R:S:")) != -1) { + while ((opt = getopt(argc, argv, "c:d:eg:G:h:i:j:J:k:l:mnp:s:u:v:A:C:E:L:M:NP:R:S:")) != -1) { switch (opt) { case 'A' : strncpy(addr_str, optarg, NI_MAXHOST-1); @@ -2626,6 +2713,11 @@ main(int argc, char **argv) { case 'e': echo_back = 1; break; + case 'E': + if (!cmdline_oscore(optarg)) { + exit(1); + } + break; case 'g' : group = optarg; break; @@ -2688,14 +2780,17 @@ main(int argc, char **argv) { strncpy(port_str, optarg, NI_MAXSERV-1); port_str[NI_MAXSERV - 1] = '\0'; break; -#if SERVER_CAN_PROXY case 'P': +#if SERVER_CAN_PROXY if (!cmdline_proxy(optarg)) { fprintf(stderr, "error specifying proxy address or host names\n"); exit(-1); } +#else /* ! SERVER_CAN_PROXY */ + fprintf(stderr, "Proxy support not available as no Client mode code\n"); + exit(-1); +#endif /* ! SERVER_CAN_PROXY */ break; -#endif /* SERVER_CAN_PROXY */ case 'R' : root_ca_file = optarg; break; @@ -2711,11 +2806,14 @@ main(int argc, char **argv) { exit(1); } break; -#if SERVER_CAN_PROXY case 'u': +#if SERVER_CAN_PROXY user_length = cmdline_read_user(optarg, &user, MAX_USER); +#else /* ! SERVER_CAN_PROXY */ + fprintf(stderr, "Proxy support not available as no Client mode code\n"); + exit(-1); +#endif /* ! SERVER_CAN_PROXY */ break; -#endif /* SERVER_CAN_PROXY */ case 'v' : log_level = strtol(optarg, NULL, 10); break; @@ -2735,6 +2833,11 @@ main(int argc, char **argv) { init_resources(ctx); coap_context_set_block_mode(ctx, block_mode); + if (doing_oscore) { + oscore_ctx = get_oscore_context(ctx); + if (oscore_ctx == NULL) + goto finish; + } /* Define the options to ignore when setting up cache-keys */ coap_cache_ignore_options(ctx, cache_ignore_options, @@ -2845,6 +2948,8 @@ main(int argc, char **argv) { } } +finish: + coap_free(ca_mem); coap_free(cert_mem); coap_free(key_mem); @@ -2895,6 +3000,8 @@ main(int argc, char **argv) { #endif coap_free(proxy_host_name_list); #endif /* SERVER_CAN_PROXY */ + if (oscore_seq_num_fp) + fclose(oscore_seq_num_fp); coap_free_context(ctx); coap_cleanup(); diff --git a/examples/lwip/Makefile b/examples/lwip/Makefile index 39a26a62bf..69989e746a 100644 --- a/examples/lwip/Makefile +++ b/examples/lwip/Makefile @@ -45,7 +45,7 @@ CFLAGS += -I$(top_srcdir)/include -I$(coap_include_dir) vpath %.c $(top_srcdir)/src -COAPOBJS = net.o coap_cache.o coap_debug.o option.o resource.o pdu.o encode.o subscribe.o coap_io_lwip.o block.o uri.o str.o coap_session.o coap_notls.o coap_hashkey.o address.o coap_tcp.o async.o +COAPOBJS = net.o coap_cache.o coap_debug.o option.o resource.o pdu.o encode.o subscribe.o coap_io_lwip.o block.o uri.o str.o coap_session.o coap_notls.o coap_hashkey.o address.o coap_tcp.o async.o coap_oscore.o CFLAGS += -g3 -Wall -Wextra -pedantic -O0 # not sorted out yet diff --git a/include/coap3/coap.h.in b/include/coap3/coap.h.in index 77e234a95a..1a81a0eec4 100644 --- a/include/coap3/coap.h.in +++ b/include/coap3/coap.h.in @@ -10,6 +10,11 @@ * of use. */ +/** + * @file coap.h + * @brief Primary include file + */ + #ifndef COAP_H_ #define COAP_H_ @@ -46,6 +51,7 @@ extern "C" { #include "coap@LIBCOAP_API_VERSION@/coap_dtls.h" #include "coap@LIBCOAP_API_VERSION@/coap_event.h" #include "coap@LIBCOAP_API_VERSION@/coap_io.h" +#include "coap@LIBCOAP_API_VERSION@/coap_oscore.h" #include "coap@LIBCOAP_API_VERSION@/coap_prng.h" #include "coap@LIBCOAP_API_VERSION@/coap_time.h" #include "coap@LIBCOAP_API_VERSION@/encode.h" diff --git a/include/coap3/coap.h.windows b/include/coap3/coap.h.windows index 70e6979b22..905adc2565 100644 --- a/include/coap3/coap.h.windows +++ b/include/coap3/coap.h.windows @@ -46,6 +46,7 @@ extern "C" { #include "coap3/coap_dtls.h" #include "coap3/coap_event.h" #include "coap3/coap_io.h" +#include "coap3/coap_oscore.h" #include "coap3/coap_prng.h" #include "coap3/coap_time.h" #include "coap3/encode.h" diff --git a/include/coap3/coap.h.windows.in b/include/coap3/coap.h.windows.in index 77e234a95a..eabbeaf265 100644 --- a/include/coap3/coap.h.windows.in +++ b/include/coap3/coap.h.windows.in @@ -46,6 +46,7 @@ extern "C" { #include "coap@LIBCOAP_API_VERSION@/coap_dtls.h" #include "coap@LIBCOAP_API_VERSION@/coap_event.h" #include "coap@LIBCOAP_API_VERSION@/coap_io.h" +#include "coap@LIBCOAP_API_VERSION@/coap_oscore.h" #include "coap@LIBCOAP_API_VERSION@/coap_prng.h" #include "coap@LIBCOAP_API_VERSION@/coap_time.h" #include "coap@LIBCOAP_API_VERSION@/encode.h" diff --git a/include/coap3/coap_crypto_internal.h b/include/coap3/coap_crypto_internal.h new file mode 100644 index 0000000000..a4896a98bf --- /dev/null +++ b/include/coap3/coap_crypto_internal.h @@ -0,0 +1,155 @@ +/* + * coap_crypto_internal.h -- Structures, Enums & Functions that are not + * exposed to application programming + * + * Copyright (C) 2017-2021 Olaf Bergmann + * Copyright (C) 2021 Jon Shallow + * + * SPDX-License-Identifier: BSD-2-Clause + * + * This file is part of the CoAP library libcoap. Please see README for terms + * of use. + */ + +/** + * @file coap_crypto_internal.h + * @brief COAP crypto internal information + */ + +#ifndef COAP_CRYPTO_INTERNAL_H_ +#define COAP_CRYPTO_INTERNAL_H_ + +/** + * @ingroup internal_api + * @defgroup crypto_internal OSCORE Crypto Support + * Internal API for interfacing with Crypto libraries + * @{ + */ + +#ifndef COAP_CRYPTO_MAX_KEY_SIZE +#define COAP_CRYPTO_MAX_KEY_SIZE (32) +#endif /* COAP_CRYPTO_MAX_KEY_SIZE */ + +#ifndef COAP_OSCORE_DEFAULT_REPLAY_WINDOW +#define COAP_OSCORE_DEFAULT_REPLAY_WINDOW 32 +#endif /* COAP_OSCORE_DEFAULT_REPLAY_WINDOW */ + +typedef enum { + COAP_OSCORE_MODE_SINGLE = 0, /**< Vanilla RFC8613 support */ + COAP_OSCORE_MODE_GROUP, /**< TODO draft-ietf-core-oscore-groupcomm */ + COAP_OSCORE_MODE_PAIRWISE /**< TODO draft-ietf-core-oscore-groupcomm */ +} coap_oscore_mode_t; + +/* See https://www.iana.org/assignments/cose/cose.xhtml#algorithms */ +typedef enum { + COAP_COSE_ALG_ECDH_SS_HKDF_256 = -27, /**< COSE ECDH-SS + HKDF-256 */ + COAP_COSE_ALG_EdDSA = -8, /**< COSE EdDSA */ + COAP_COSE_ALG_HMAC256_256 = 5, /**< COSE HMAC 256/256 */ + COAP_COSE_ALG_AES_CCM_16_64_128 = 10, /**< COSE AES-CCM-16-64-128 */ + COAP_COSE_ALG_AES_CCM_16_64_256 = 11 /**< COSE AES-CCM-16-64-256 */ +} coap_cose_alg_t; + +typedef enum { +/* cose curves */ + COAP_COSE_Elliptic_Curve_Ed25519 = 6 /**< COSE_Elliptic_Curve_Ed25519 + used with EdDSA only */ +} coap_cose_curve_t; + +/** + * The structure that holds the Crypto Key. + */ +typedef coap_bin_const_t coap_crypto_key_t; + +/** + * The structure that holds the AES Crypto information + */ +typedef struct coap_crypto_aes_ccm_t { + coap_crypto_key_t key; /**< The Key to use */ + const uint8_t *nonce; /**< must be exactly 15 - l bytes */ + size_t tag_len; /**< The size of the Tag */ + size_t l; /**< The number of bytes in the length field */ +} coap_crypto_aes_ccm_t; + +/** + * The common structure that holds the Crypto information + */ +typedef struct coap_crypto_param_t { + coap_cose_alg_t alg; /**< The COSE algorith to use */ + union { + coap_crypto_aes_ccm_t aes; /**< Used if AES type encryption */ + coap_crypto_key_t key; /**< The key to use */ + } params; +} coap_crypto_param_t; + +/** + * Check whether the defined cipher algorithm is supported by the underlying + * crypto library. + * + * @param alg The COSE algorithm to check. + * + * @return @c 1 if there is support, else @c 0. + */ +int coap_crypto_check_cipher_alg(coap_cose_alg_t alg); + +/** + * Check whether the defined hkdf algorithm is supported by the underlying + * crypto library. + * + * @param alg The COSE algorithm to check. + * + * @return @c 1 if there is support, else @c 0. + */ +int coap_crypto_check_hkdf_alg(coap_cose_alg_t alg); + +/** + * Encrypt the provided plaintext data + * + * @param params The Encrypt/Decrypt/Hash paramaters. + * @param data The data to encrypt. + * @param aad The additional AAD information. + * @param result Where to put the encrypted data. + * @param max_result_len The maximum size for @p result + * (updated with actual size). + * + * @return @c 1 if the data was successfully encrypted, else @c 0. + */ +int coap_crypto_aead_encrypt(const coap_crypto_param_t *params, + coap_bin_const_t *data, + coap_bin_const_t *aad, + uint8_t *result, size_t *max_result_len); + +/** + * Decrypt the provided encrypted data into plaintext. + * + * @param params The Encrypt/Decrypt/Hash paramaters. + * @param data The data to decrypt. + * @param aad The additional AAD information. + * @param result Where to put the decrypted data. + * @param max_result_len The maximum size for @p result + * (updated with actual size). + * + * @return @c 1 if the data was successfully decrypted, else @c 0. + */ +int coap_crypto_aead_decrypt(const coap_crypto_param_t *params, + coap_bin_const_t *data, + coap_bin_const_t *aad, + uint8_t *result, size_t *max_result_len); + +/** + * Create a hash of the provided data. + * + * @param params The Encrypt/Decrypt/Hash paramaters. + * @param data The data to hash. + * @param result Where to put the hash value. + * @param max_result_len The maximum size for @p result + * (updated with actual size). + * + * @return @c 1 if the data was successfully hashed, else @c 0. + */ +int coap_crypto_hmac(const coap_crypto_param_t *params, + coap_bin_const_t *data, + uint8_t *result, size_t *max_result_len); + +/** @} */ + +#endif /* COAP_CRYPTO_INTERNAL_H_ */ diff --git a/include/coap3/coap_dtls.h b/include/coap3/coap_dtls.h index 95ca1ec05e..fac55f198a 100644 --- a/include/coap3/coap_dtls.h +++ b/include/coap3/coap_dtls.h @@ -278,7 +278,7 @@ struct coap_dtls_pki_t { future compatibility */ /* Size of 3 chosen to align to next * parameter, so if newly defined option - * it can use one of the reserverd slot so + * it can use one of the reserved slots so * no need to change * COAP_DTLS_PKI_SETUP_VERSION and just * decrement the reserved[] count. diff --git a/include/coap3/coap_forward_decls.h b/include/coap3/coap_forward_decls.h index 2ad9c4978c..b152385f27 100644 --- a/include/coap3/coap_forward_decls.h +++ b/include/coap3/coap_forward_decls.h @@ -73,6 +73,14 @@ typedef struct coap_socket_t coap_socket_t; typedef struct coap_context_t coap_context_t; typedef struct coap_queue_t coap_queue_t; +/* ************* coap_oscore_internal.h ***************** */ + +/* + * OSCORE information. + */ +typedef struct oscore_ctx_t coap_oscore_context_t; +typedef struct coap_oscore_conf_t coap_oscore_conf_t; + /* ************* coap_pdu_internal.h ***************** */ /** diff --git a/include/coap3/coap_internal.h b/include/coap3/coap_internal.h index 2951f73133..6995f1eca1 100644 --- a/include/coap3/coap_internal.h +++ b/include/coap3/coap_internal.h @@ -67,9 +67,11 @@ #include "coap_async_internal.h" #include "coap_block_internal.h" #include "coap_cache_internal.h" +#include "coap_crypto_internal.h" #include "coap_dtls_internal.h" #include "coap_io_internal.h" #include "coap_net_internal.h" +#include "coap_oscore_internal.h" #include "coap_pdu_internal.h" #include "coap_session_internal.h" #include "coap_resource_internal.h" diff --git a/include/coap3/coap_net_internal.h b/include/coap3/coap_net_internal.h index 9e58510105..3896b7410c 100644 --- a/include/coap3/coap_net_internal.h +++ b/include/coap3/coap_net_internal.h @@ -88,6 +88,9 @@ struct coap_context_t { * scheduled using lwIP timers for this * context, otherwise 0. */ #endif /* WITH_LWIP */ +#ifdef HAVE_OSCORE + struct oscore_ctx_t *osc_ctx; /**< oscore context */ +#endif /* HAVE_OSCORE */ #if COAP_CLIENT_SUPPORT coap_response_handler_t response_handler; diff --git a/include/coap3/coap_oscore.h b/include/coap3/coap_oscore.h new file mode 100644 index 0000000000..807f9b3fb7 --- /dev/null +++ b/include/coap3/coap_oscore.h @@ -0,0 +1,187 @@ +/* + * coap_oscore.h -- Object Security for Constrained RESTful Environments + * (OSCORE) support for libcoap + * + * Copyright (C) 2019-2021 Olaf Bergmann + * Copyright (C) 2021 Jon Shallow + * + * SPDX-License-Identifier: BSD-2-Clause + * + * This file is part of the CoAP library libcoap. Please see README for terms + * of use. + */ + +/** + * @file coap_oscore.h + * @brief CoAP OSCORE support + */ + +#ifndef COAP_OSCORE_H_ +#define COAP_OSCORE_H_ + +/** + * @ingroup application_api + * @defgroup oscore OSCORE Support + * API functions for interfacing with OSCORE (RFC8613) + * @{ + */ + +/** + * Check whether OSCORE is available. + * + * @return @c 1 if support for OSCORE is enabled, or @c 0 otherwise. + */ +int coap_oscore_is_supported(void); + +/** + * Creates a new client session to the designated server, protecting the data + * using OSCORE. + * + * @param ctx The CoAP context. + * @param local_if Address of local interface. It is recommended to use NULL + * to let the operating system choose a suitable local + * interface. If an address is specified, the port number + * should be zero, which means that a free port is + * automatically selected. + * @param server The server's address. If the port number is zero, the default + * port for the protocol will be used. + * @param proto CoAP Protocol. + * @param oscore_context OSCORE context to use. + * + * @return A new CoAP session or NULL if failed. Call coap_session_release() + * to free. + */ +coap_session_t *coap_new_client_session_oscore( + coap_context_t *ctx, + const coap_address_t *local_if, + const coap_address_t *server, + coap_proto_t proto, + coap_oscore_context_t *oscore_context +); + +/** + * Creates a new client session to the designated server with PSK credentials + * as well as protecting the data using OSCORE. + * + * @param ctx The CoAP context. + * @param local_if Address of local interface. It is recommended to use NULL to + * let the operating system choose a suitable local interface. + * If an address is specified, the port number should be zero, + * which means that a free port is automatically selected. + * @param server The server's address. If the port number is zero, the default + * port for the protocol will be used. + * @param proto CoAP Protocol. + * @param psk_data PSK parameters. + * @param oscore_context OSCORE context to use. + * + * @return A new CoAP session or NULL if failed. Call coap_session_release() + * to free. + */ +coap_session_t *coap_new_client_session_oscore_psk( + coap_context_t *ctx, + const coap_address_t *local_if, + const coap_address_t *server, + coap_proto_t proto, + coap_dtls_cpsk_t *psk_data, + coap_oscore_context_t *oscore_context +); + +/** + * Creates a new client session to the designated server with PKI credentials + * as well as protecting the data using OSCORE. + * + * @param ctx The CoAP context. + * @param local_if Address of local interface. It is recommended to use NULL to + * let the operating system choose a suitable local interface. + * If an address is specified, the port number should be zero, + * which means that a free port is automatically selected. + * @param server The server's address. If the port number is zero, the default + * port for the protocol will be used. + * @param proto CoAP Protocol. + * @param pki_data PKI parameters. + * @param oscore_context OSCORE context to use. + * + * @return A new CoAP session or NULL if failed. Call coap_session_release() + * to free. + */ +coap_session_t *coap_new_client_session_oscore_pki( + coap_context_t *ctx, + const coap_address_t *local_if, + const coap_address_t *server, + coap_proto_t proto, + coap_dtls_pki_t *pki_data, + coap_oscore_context_t *oscore_context +); + +/** + * Definition of the function used to save the current Sender Sequence Number + * + * @param sender_seq_no The Sender Sequence Number to save in non-volatile + * memory. + * @param param The save_seq_num_func_param provided to + * coap_new_oscore_context(). + * + * @return @c 1 if success, else @c 0 if a failure of some sort. + */ +typedef int (*coap_oscore_save_seq_num_t)(uint64_t sender_seq_num, void *param); + +/** + * Parse an OSCORE configuration (held in memory) and populate a OSCORE + * context structure. + * + * @param context The CoAP context to associate this OSCORE context with. + * @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. + * @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. + * + * @return The new OSCORE context. NULL if failed. It needs to be freed off + * with coap_delete_oscore_conf() when no longer required, otherwise + * it is freed off when coap_free_context() is called. + */ +coap_oscore_context_t *coap_new_oscore_context(coap_context_t *context, + coap_str_const_t conf_mem, + coap_oscore_save_seq_num_t save_seq_num_func, + void *save_seq_num_func_param, + uint64_t start_seq_num); + +/** + * Release all the information associated with the OSCORE context (and hence + * stop any further OSCORE protection for this OSCORE context). + * + * @param context The CoAP context associated with this OSCORE context. + * @param oscore_context The OSCORE context structure to release. + * + * @return @c 1 Successfully removed, else @c 0 not found. + */ +int coap_delete_oscore_context(coap_context_t *context, + coap_oscore_context_t *oscore_context); + +/** + * Add in the specific Recipient ID into the OSCORE context. + * + * @param oscore_context The OSCORE context to add the recipient_id to. + * @param recipient_id The Recipient ID to add. + * + * @return @c 1 Successfully added, else @c 0 there is an issue. + */ +int coap_new_oscore_recipient(coap_oscore_context_t *oscore_context, + coap_bin_const_t *recipient_id); + +/** + * Release all the information associated for the specific Recipient ID + * (and hence and stop any further OSCORE protection for this Recipient). + * + * @param oscore_context The OSCORE context containing the recipient_id. + * @param recipient_id The Recipient ID to remove. + * + * @return @c 1 Successfully removed, else @c 0 not found. + */ +int coap_delete_oscore_recipient(coap_oscore_context_t *oscore_context, + coap_bin_const_t *recipient_id); +/** @} */ + +#endif /* COAP_OSCORE_H */ diff --git a/include/coap3/coap_oscore_internal.h b/include/coap3/coap_oscore_internal.h new file mode 100644 index 0000000000..04d7879686 --- /dev/null +++ b/include/coap3/coap_oscore_internal.h @@ -0,0 +1,127 @@ +/* + * coap_oscore_internal.h - Object Security for Constrained RESTful Environments + * (OSCORE) support for libcoap + * + * Copyright (C) 2019-2021 Olaf Bergmann + * Copyright (C) 2021 Jon Shallow + * + * SPDX-License-Identifier: BSD-2-Clause + * + * This file is part of the CoAP library libcoap. Please see README for terms + * of use. + */ + +/** + * @file coap_oscore_internal.h + * @brief CoAP OSCORE internal information + */ + + +#ifndef COAP_OSCORE_INTERNAL_H_ +#define COAP_OSCORE_INTERNAL_H_ + +#include "oscore/oscore_context.h" + +/** + * @ingroup internal_api + * @defgroup oscore_internal OSCORE Support + * Internal API for interfacing with OSCORE (RFC8613) + * @{ + */ + +/** + * The structure used to hold the configuration information + */ +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_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 */ + uint32_t replay_window; /**< Replay window size + Use COAP_OSCORE_DEFAULT_REPLAY_WINDOW */ + coap_cose_alg_t aead_alg; /**< Set to one of COAP_COSE_ALG_AES* */ + coap_cose_alg_t hkdf_alg; /**< Set to one of COAP_COSE_ALG_HMAC* */ + coap_oscore_mode_t mode; /**< Set to one of COAP_OSCORE_MODE_* */ + coap_cose_alg_t hkdf; /**< Set to one of COAP_COSE_ALG_HMAC* */ + // pub_key_enc; /**< TODO */ + uint32_t ssn_freq; /**< Sender Seq Num update frequency */ + int group_mode; /**< 1 if group mode else 0 */ + coap_cose_alg_t sign_enc_alg; /**< Set to one of COAP_COSE_ALG_AES* */ + coap_cose_alg_t sign_alg; /**< Set to one of COAP_COSE_ALG_AES* */ + int pairwise_mode; /**< 1 if pairwise mode else 0 */ + coap_cose_alg_t alg; /**< Set to one of COAP_COSE_ALG_AES* */ + coap_cose_alg_t ecdh_alg; /**< Set to one of COAP_COSE_ALG_AES* */ + coap_bin_const_t *gm_public_key; /**< Group Manager Public Key */ + coap_bin_const_t *sender_public_key; /**< Sender Public Key (i.e. local our + Key) */ + coap_bin_const_t *sender_private_key; /**< Private Key for + sender_public_key */ + coap_bin_const_t **recipient_public_key; /**< Recipient Public Key (i.e. + remote peer Key + Array of recipient_public_key */ + uint32_t recipient_public_key_count; /**< Number of recipient_public_key + entries */ +}; + +/** + * Encrypts the specified @p pdu when OSCORE encryption is required + * on @p session. This function returns the encrypted PDU or @c NULL + * on error. + * + * @param session The session that will handle the transport of the + * specified @p pdu. + * @param pdu The PDU to encrypt if necessary. + * @param echo_value Optional Echo option to add in or NULL. + * @param send_partial_iv @c 1 if partial_iv is always to be added, else @c 0 + * if not to be added for a response if not required.. + * + * @return The OSCORE encrypted version of @p pdu, or @c NULL on error. + */ +coap_pdu_t *coap_oscore_new_pdu_encrypted(coap_session_t *session, + coap_pdu_t *pdu, + coap_bin_const_t *echo_value, + int send_partial_iv); + +/** + * Decrypts the OSCORE-encrypted parts of @p pdu when OSCORE is used. + * This function returns the decrypted PDU or @c NULL on error. + * + * @param session The session that will handle the transport of the + * specified @p pdu. + * @param pdu The PDU to decrypt if necessary. + * + * @return The decrypted @p pdu, or @c NULL on error. + */ +struct coap_pdu_t *coap_oscore_decrypt_pdu(coap_session_t *session, + coap_pdu_t *pdu); + +/** + * Cleanup all allocated OSCORE information. + * + * @param context The context that the OSCORE information is associated with. + */ +void coap_delete_all_oscore(coap_context_t *context); + +/** + * Cleanup all allocated OSCORE association information. + * + * @param session The session that the OSCORE associations are associated with. + */ +void coap_delete_oscore_associations(coap_session_t *session); + +/** + * Determine the additional data size requirements for adding in OSCORE. + * + * @param session The session that the OSCORE associations are associated with. + * @param pdu The non OSCORE protected PDU that is going to be used. + * + * @return The OSCORE packet size overhead. + */ +size_t coap_oscore_overhead(coap_session_t *session, coap_pdu_t *pdu); + +/** @} */ + +#endif /* COAP_OSCORE_INTERNAL_H */ diff --git a/include/coap3/coap_pdu_internal.h b/include/coap3/coap_pdu_internal.h index dfac81ddaf..7fbf9e91e3 100644 --- a/include/coap3/coap_pdu_internal.h +++ b/include/coap3/coap_pdu_internal.h @@ -44,6 +44,11 @@ #define COAP_MAX_MESSAGE_SIZE_TCP8 (COAP_MESSAGE_SIZE_OFFSET_TCP16-1) /* 268 */ #define COAP_MAX_MESSAGE_SIZE_TCP16 (COAP_MESSAGE_SIZE_OFFSET_TCP32-1) /* 65804 */ #define COAP_MAX_MESSAGE_SIZE_TCP32 (COAP_MESSAGE_SIZE_OFFSET_TCP32+0xFFFFFFFF) +#ifdef HAVE_OSCORE +/* for oscore encryption */ +#define COAP_MAX_CHUNK_SIZE COAP_DEFAULT_MAX_PDU_RX_SIZE +#define OSCORE_CRYPTO_BUFFER_SIZE COAP_MAX_CHUNK_SIZE+16 +#endif /* HAVE_OSCORE */ #ifndef COAP_DEBUG_BUF_SIZE #if defined(WITH_CONTIKI) || defined(WITH_LWIP) diff --git a/include/coap3/coap_session_internal.h b/include/coap3/coap_session_internal.h index 72fd0f9728..cda1ce6348 100644 --- a/include/coap3/coap_session_internal.h +++ b/include/coap3/coap_session_internal.h @@ -131,8 +131,16 @@ struct coap_session_t { 1.5) */ unsigned int dtls_timeout_count; /**< dtls setup retry counter */ int dtls_event; /**< Tracking any (D)TLS events on this - sesison */ + session */ uint8_t block_mode; /**< Zero or more COAP_BLOCK_ or'd options */ + uint8_t doing_first; /**< Set if doing client's first request */ +#ifdef HAVE_OSCORE + uint8_t oscore_encryption; /**< OSCORE is used for this session */ + struct oscore_recipient_ctx_t *recipient_ctx; /**< OSCORE recipient context + for session */ + struct oscore_association_t *associations; /**< OSCORE set of response + associations */ +#endif /*HAVE_OSCORE */ uint64_t tx_token; /**< Next token number to use */ }; diff --git a/include/coap3/mem.h b/include/coap3/mem.h index cf47d6148b..de921715ff 100644 --- a/include/coap3/mem.h +++ b/include/coap3/mem.h @@ -45,9 +45,7 @@ typedef enum { COAP_PDU_BUF, COAP_RESOURCE, COAP_RESOURCEATTR, -#ifdef HAVE_LIBTINYDTLS COAP_DTLS_SESSION, -#endif COAP_SESSION, COAP_OPTLIST, COAP_CACHE_KEY, @@ -55,6 +53,13 @@ typedef enum { COAP_LG_XMIT, COAP_LG_CRCV, COAP_LG_SRCV, + COAP_OSCORE_COM, + COAP_OSCORE_SEN, + COAP_OSCORE_REC, + COAP_OSCORE_EX, + COAP_OSCORE_EP, + COAP_OSCORE_BUF, + COAP_COSE, } coap_memory_tag_t; #ifndef WITH_LWIP diff --git a/include/coap3/pdu.h b/include/coap3/pdu.h index 5bcf2a3da5..b36f9b145a 100644 --- a/include/coap3/pdu.h +++ b/include/coap3/pdu.h @@ -130,6 +130,10 @@ typedef enum coap_request_t { #define COAP_OPTION_SIZE1 60 /* __N_E_U, uint, 0-4 B, RFC7252 */ #define COAP_OPTION_NORESPONSE 258 /* _U-_E_U, uint, 0-1 B, RFC7967 */ +/* selected option types from draft-ietf-core-echo-request-tag */ +#define COAP_OPTION_ECHO 252 /* __N_E_U, opaque, 0-40 B, RFC?? */ +#define COAP_OPTION_RTAG 292 /* ___RE_U, opaque, 0-8 B, RFC?? */ + #define COAP_MAX_OPT 65535 /**< the highest option number we know */ /* CoAP result codes (HTTP-Code / 100 * 40 + HTTP-Code % 100) */ @@ -221,6 +225,9 @@ typedef enum coap_pdu_signaling_proto_t { /* Content formats from RFC 8782 */ #define COAP_MEDIATYPE_APPLICATION_DOTS_CBOR 271 /* application/dots+cbor */ +/* Content formats from RFC 8613 */ +#define COAP_MEDIATYPE_APPLICATION_OSCORE 10001 /* application/oscore */ + /* Note that identifiers for registered media types are in the range 0-65535. We * use an unallocated type here and hope for the best. */ #define COAP_MEDIATYPE_ANY 0xff /* any media type */ diff --git a/include/coap3/uri.h b/include/coap3/uri.h index 02136afb8b..3fb1697e96 100644 --- a/include/coap3/uri.h +++ b/include/coap3/uri.h @@ -31,9 +31,12 @@ typedef enum coap_uri_scheme_t { COAP_URI_SCHEME_COAP_TCP, /* 2 */ COAP_URI_SCHEME_COAPS_TCP, /* 3 */ COAP_URI_SCHEME_HTTP, /* 4 Proxy-Uri only */ - COAP_URI_SCHEME_HTTPS /* 5 Proxy-Uri only */ + COAP_URI_SCHEME_HTTPS, /* 5 Proxy-Uri only */ + COAP_URI_SCHEME_LAST /* 6 Size of scheme */ } coap_uri_scheme_t; +extern const char *coap_uri_scheme[COAP_URI_SCHEME_LAST]; + /** This mask can be used to check if a parsed URI scheme is secure. */ #define COAP_URI_SCHEME_SECURE_MASK 0x01 diff --git a/include/oscore/oscore.h b/include/oscore/oscore.h new file mode 100644 index 0000000000..1018ff7edc --- /dev/null +++ b/include/oscore/oscore.h @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2018, SICS, RISE AB + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + */ + +/** + * @file oscore.h + * @brief An implementation of the Object Security for Constrained RESTful Enviornments (RFC 8613). + * \author + * Martin Gunnarsson + * major rewrite for libcoap + * Peter van der Stok + * on request of Fairhair alliance + * adapted for libcoap integration + * Jon Shallow + * + */ + +#ifndef _OSCORE_H +#define _OSCORE_H + +#include +#include "oscore_cose.h" +#include "oscore_context.h" + +/** + * @ingroup internal_api + * @addtogroup oscore_internal + * @{ + */ + +/* Estimate your header size, especially when using Proxy-Uri. */ +#define COAP_MAX_HEADER_SIZE 70 + +/* OSCORE error messages (to be moved elsewhere */ +#define OSCORE_DECRYPTION_ERROR 100 +#define PACKET_SERIALIZATION_ERROR 102 + +/* oscore_cs_params + * returns cbor array [[param_type], [paramtype, param]] + */ +uint8_t * +oscore_cs_params(int8_t param, int8_t param_type, size_t *len); + +/* oscore_cs_key_params + * returns cbor array [paramtype, param] + */ +uint8_t * +oscore_cs_key_params(int8_t param, int8_t param_type, size_t *len); + +// +// oscore_encode_option_value +// +size_t +oscore_encode_option_value(uint8_t *option_buffer, cose_encrypt0_t *cose, uint8_t group); + +/* + * Decodes the OSCORE option value and places decoded values into the provided + * cose structure */ +int +oscore_decode_option_value(const uint8_t *option_value, size_t option_len, + cose_encrypt0_t *cose); + + +/* Sets alg and keys in COSE SIGN */ +void +oscore_populate_sign(cose_sign1_t *sign, oscore_ctx_t *ctx, + coap_bin_const_t *public_key, + coap_bin_const_t *private_key); + +// +// oscore_prepare_sig_structure +// creates and sets structure to be signed +size_t +oscore_prepare_sig_structure(uint8_t *sigptr, size_t sig_size, + const uint8_t *e_aad_buffer, uint16_t e_aad_len, + const uint8_t *text, uint16_t text_len); + +/* Creates AAD, creates External AAD and serializes it into the complete AAD structure. Returns serialized size. */ +size_t +oscore_prepare_aad(const uint8_t *external_aad_buffer, size_t external_aad_len, + uint8_t *aad_buffer, size_t aad_size); + +size_t +oscore_prepare_e_aad(oscore_ctx_t *ctx, cose_encrypt0_t *cose, + const uint8_t *oscore_option, size_t oscore_option_len, + coap_bin_const_t *sender_public_key, + uint8_t *external_aad_ptr, size_t external_aad_size); + +/* Creates Nonce */ +void +oscore_generate_nonce(cose_encrypt0_t *ptr, oscore_ctx_t *ctx, uint8_t *buffer, uint8_t size); + +/*Return 1 if OK, Error code otherwise */ +uint8_t oscore_validate_sender_seq(oscore_recipient_ctx_t *ctx, cose_encrypt0_t *cose); + +/* Return 0 if SEQ MAX, return 1 if OK */ +uint8_t oscore_increment_sender_seq(oscore_ctx_t *ctx); + +/* Restore the sequence number and replay-window to the previous state. This is to be used when decryption fail. */ +void oscore_roll_back_seq(oscore_recipient_ctx_t *ctx); + +/** @} */ + +#endif /* _OSCORE_H */ diff --git a/include/oscore/oscore_cbor.h b/include/oscore/oscore_cbor.h new file mode 100644 index 0000000000..0eee1990ef --- /dev/null +++ b/include/oscore/oscore_cbor.h @@ -0,0 +1,144 @@ +/* + * Copyright (c) 2018, SICS, RISE AB + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + */ + +/** + * @file oscore_cbor.h + * @brief An implementation of the Concise Binary Object Representation (RFC). + * + * \author + * Martin Gunnarsson + * extended for libcoap by: + * Peter van der Stok + * on request of Fairhair alliance + * adapted for libcoap integration + * Jon Shallow + * + */ + + +#ifndef _OSCORE_CBOR_H +#define _OSCORE_CBOR_H +#include +#include + +/** + * @ingroup internal_api + * @defgroup oscore_cbor_internal OSCORE CBOR Support + * Internal API for interfacing with OSCORE CBOR + * @{ + */ + +// CBOR major types +#define CBOR_UNSIGNED_INTEGER 0 +#define CBOR_NEGATIVE_INTEGER 1 +#define CBOR_BYTE_STRING 2 +#define CBOR_TEXT_STRING 3 +#define CBOR_ARRAY 4 +#define CBOR_MAP 5 +#define CBOR_TAG 6 +#define CBOR_SIMPLE_VALUE 7 +#define CBOR_FLOATING_POINT 7 + +#define CBOR_FALSE 20 +#define CBOR_TRUE 21 + + +size_t oscore_cbor_put_nil(uint8_t **buffer, size_t *buf_size); + +size_t oscore_cbor_put_true(uint8_t **buffer, size_t *buf_size); + +size_t oscore_cbor_put_false(uint8_t **buffer, size_t *buf_size); + +size_t oscore_cbor_put_text(uint8_t **buffer, size_t *buf_size, + const char *text, uint64_t text_len); + +size_t oscore_cbor_put_array(uint8_t **buffer, size_t *buf_size, + uint64_t elements); + +size_t oscore_cbor_put_bytes(uint8_t **buffer, size_t *buf_size, + const uint8_t *bytes, uint64_t bytes_len); + +size_t oscore_cbor_put_map(uint8_t **buffer, size_t *buf_size, + uint64_t elements); + +size_t oscore_cbor_put_number(uint8_t **buffer, size_t *buf_size, + int64_t value); + +size_t oscore_cbor_put_simple_value(uint8_t **buffer, size_t *buf_size, + uint8_t value); + +size_t oscore_cbor_put_unsigned(uint8_t **buffer, size_t *buf_size, + uint64_t value); + +size_t oscore_cbor_put_tag(uint8_t **buffer, size_t *buf_size, uint64_t value); + +size_t oscore_cbor_put_negative(uint8_t **buffer, size_t *buf_size, + int64_t value); + +uint8_t oscore_cbor_get_next_element(const uint8_t **buffer); + +uint64_t oscore_cbor_get_element_size(const uint8_t **buffer); + +uint8_t oscore_cbor_elem_contained(const uint8_t *data, uint8_t *end); + +uint8_t +oscore_cbor_get_number(const uint8_t **data, int64_t *value); + +uint8_t +oscore_cbor_get_simple_value(const uint8_t **data, uint8_t *value); + +int64_t +oscore_cbor_get_negative_integer(const uint8_t **buffer); + +uint64_t +oscore_cbor_get_unsigned_integer(const uint8_t **buffer); + +void +oscore_cbor_get_string(const uint8_t **buffer, char *str, uint64_t size); + +void +oscore_cbor_get_array(const uint8_t **buffer, uint8_t *arr, uint64_t size); + +/* oscore_cbor_get_string_array + * fills the the size and the array from the cbor element + */ +uint8_t +oscore_cbor_get_string_array(const uint8_t **data, uint8_t **result, size_t *len); + +/* oscore_cbor_strip value + * strips the value of the cbor element into result + * and returns size + */ +uint8_t +oscore_cbor_strip_value(const uint8_t **data, uint8_t **result, size_t *len); + +/** @} */ + +#endif /* _OSCORE_CBOR_H */ diff --git a/include/oscore/oscore_context.h b/include/oscore/oscore_context.h new file mode 100644 index 0000000000..8a259cd100 --- /dev/null +++ b/include/oscore/oscore_context.h @@ -0,0 +1,269 @@ +/* + * Copyright (c) 2018, SICS, RISE AB + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + */ + +/** + * @file oscore_context.h + * @brief An implementation of the Object Security for Constrained RESTful Enviornments (RFC 8613). + * + * \author + * Martin Gunnarsson + * adapted to libcoap; added group communication + * Peter van der Stok + * on request of Fairhair alliance + * adapted for libcoap integration + * Jon Shallow + * + */ + +#ifndef _OSCORE_CONTEXT_H +#define _OSCORE_CONTEXT_H + +#include +#include +#include + +/** + * @ingroup internal_api + * @addtogroup oscore_internal + * @{ + */ + +#define CONTEXT_KEY_LEN 16 +#define COAP_TOKEN_LEN 8 // added +#define TOKEN_SEQ_NUM 2 // to be set by application +#define EP_CTX_NUM 10 // to be set by application +#define CONTEXT_INIT_VECT_LEN 13 +#define CONTEXT_SEQ_LEN sizeof(uint64_t) +#define Ed25519_PRIVATE_KEY_LEN 64 +#define Ed25519_PUBLIC_KEY_LEN 32 +#define Ed25519_SEED_LEN 32 +#define Ed25519_SIGNATURE_LEN 64 + +#define OSCORE_SEQ_MAX (((uint64_t)1 << 40) - 1) + +typedef struct oscore_sender_ctx_t oscore_sender_ctx_t; +typedef struct oscore_recipient_ctx_t oscore_recipient_ctx_t; +typedef struct oscore_ctx_t oscore_ctx_t; +typedef struct oscore_association_t oscore_association_t; + +struct oscore_ctx_t { + struct oscore_ctx_t *next; + coap_bin_const_t *master_secret; + coap_bin_const_t *master_salt; + coap_bin_const_t *common_iv; /**< Derived from Master Secret, + Master Salt, and ID Context */ + coap_bin_const_t *id_context; /* contains GID in case of group */ + oscore_sender_ctx_t *sender_context; + oscore_recipient_ctx_t *recipient_chain; + coap_cose_alg_t aead_alg; + coap_cose_alg_t hkdf_alg; + coap_oscore_mode_t mode; + uint32_t ssn_freq; /**< Sender Seq Num update frequency */ + uint32_t replay_window_size; + coap_oscore_save_seq_num_t save_seq_num_func; /**< Called every seq num + change */ + void *save_seq_num_func_param; /**< Passed to save_seq_num_func() */ +#ifdef HAVE_OSCORE_GROUP + coap_bin_const_t *gm_public_key; + coap_bin_const_t *sign_params; /* binary CBOR array */ + coap_cose_alg_t sign_enc_alg; + coap_cose_alg_t sign_alg; + coap_bin_const_t *group_enc_key; + coap_cose_alg_t pairwise_agree_alg; +#endif /* HAVE_OSCORE_GROUP */ + +}; + +#define coap_oscore_context_t oscore_ctx_t + +struct oscore_sender_ctx_t { + uint64_t seq; + uint64_t next_seq; /**< Used for ssn_freq updating */ + coap_bin_const_t *sender_key; + coap_bin_const_t *sender_id; +#ifdef HAVE_OSCORE_GROUP + /* addition for group communication */ + coap_bin_const_t *public_key; + coap_bin_const_t *private_key; + /* addition for pairwise communication */ + coap_bin_const_t *pairwise_sender_key; +#endif /* HAVE_OSCORE_GROUP */ +}; + +struct oscore_recipient_ctx_t { + oscore_recipient_ctx_t *next_recipient; + /* This field allows recipient chaining */ + oscore_ctx_t *common_ctx; + uint64_t last_seq; +// uint64_t highest_seq; + uint64_t sliding_window; + uint64_t rollback_sliding_window; + uint64_t rollback_last_seq; + coap_bin_const_t *recipient_key; + coap_bin_const_t *recipient_id; + uint8_t echo_value[8]; + uint8_t initial_state; +#ifdef HAVE_OSCORE_GROUP + /* addition for group communication */ + coap_bin_const_t *public_key; + /* addition for pairwise communication */ + coap_bin_const_t *pairwise_recipient_key; +#endif /* HAVE_OSCORE_GROUP */ +}; + +#define OSCORE_ASSOCIATIONS_ADD(r, obj) \ + HASH_ADD(hh, (r), token->s[0], (obj)->token->length, (obj)) + +#define OSCORE_ASSOCIATIONS_DELETE(r, obj) \ + HASH_DELETE(hh, (r), (obj)) + +#define OSCORE_ASSOCIATIONS_ITER(r,tmp) \ + oscore_associations_t *tmp, *rtmp; \ + HASH_ITER(hh, (r), tmp, rtmp) + +#define OSCORE_ASSOCIATIONS_ITER_SAFE(e, el, rtmp) \ +for ((el) = (e); (el) && ((rtmp) = (el)->hh.next, 1); (el) = (rtmp)) + +#define OSCORE_ASSOCIATIONS_FIND(r, k, res) { \ + HASH_FIND(hh, (r), (k)->s, (k)->length, (res)); \ + } + +struct oscore_association_t { + UT_hash_handle hh; + oscore_recipient_ctx_t *recipient_ctx; + coap_bin_const_t *token; + coap_bin_const_t *aad; + coap_bin_const_t *nonce; + coap_bin_const_t *partial_iv; + coap_tick_t last_seen; + uint8_t is_observe; +}; + +void +oscore_enter_context(coap_context_t *c_context, oscore_ctx_t *osc_ctx); + +oscore_ctx_t * +oscore_derive_ctx(coap_bin_const_t *master_secret, + coap_bin_const_t *master_salt, + coap_cose_alg_t cipher_alg, coap_cose_alg_t hamc_alg, + coap_bin_const_t *sid, + coap_bin_const_t *rid, + coap_bin_const_t *id_context, + uint32_t replay_window, uint32_t ssn_freq); + +void +oscore_free_contexts(coap_context_t *c_context); + +int +oscore_remove_context(coap_context_t *c_context, oscore_ctx_t *osc_ctx); + +oscore_recipient_ctx_t * +oscore_add_recipient(oscore_ctx_t *ctx, + coap_bin_const_t *rid); + +int +oscore_delete_recipient(oscore_ctx_t *osc_ctx, + coap_bin_const_t *rid); + +void +oscore_add_pair_keys(oscore_ctx_t *ctx, + oscore_recipient_ctx_t *recipient_ctx, + uint8_t *pairwise_recipient_key, + uint8_t pairwise_recipient_key_len, + uint8_t *pairwise_sender_key, + uint8_t pairwise_sender_key_len); + + +void +oscore_add_group_keys(oscore_ctx_t *ctx, + oscore_recipient_ctx_t *recipient_ctx, + coap_bin_const_t *snd_public_key, + coap_bin_const_t *snd_private_key, + coap_bin_const_t *rcp_public_key); + +void +oscore_add_group_algorithm(oscore_ctx_t *ctx, + coap_cose_alg_t counter_signature_enc_algorithm, + coap_cose_alg_t counter_signature_algorithm, + uint8_t *counter_signature_parameters, + uint8_t counter_signature_parameters_len); + +int _strcmp(const char *a, const char *b); + +uint8_t +oscore_bytes_equal(uint8_t *a_ptr, uint8_t a_len, uint8_t *b_ptr, uint8_t b_len); + +void oscore_convert_to_hex(const uint8_t *src, size_t src_len, + char *dest, size_t dst_len); + +void +oscore_log_hex_value(coap_log_t level, const char *name, + coap_bin_const_t *value); + +void +oscore_log_int_value(coap_log_t level, const char *name, int value); + +// +// oscore_find_context +// finds context for received send_id, reciever_id, or context_id +// that is stored in cose->key_id +// used by client interface +oscore_ctx_t * +oscore_find_context(coap_context_t *c_context, + coap_bin_const_t sndkey_id, + coap_bin_const_t rcpkey_id, + coap_bin_const_t ctxkey_id, + oscore_recipient_ctx_t **recipient_ctx); + +void oscore_free_association(oscore_association_t *association); + +int oscore_new_association(coap_session_t *session, + coap_bin_const_t *token, + oscore_recipient_ctx_t *recipient_ctx, + coap_bin_const_t *aad, coap_bin_const_t *nonce, + coap_bin_const_t *partial_iv, int is_observe); + +oscore_association_t * oscore_find_association(coap_session_t *session, + coap_bin_const_t *token); + +int oscore_delete_association(coap_session_t *session, + oscore_association_t *association); + +void oscore_delete_server_associations(coap_session_t *session); + +int oscore_derive_keystream(oscore_ctx_t *osc_ctx, cose_encrypt0_t *code, + uint8_t coap_request, + coap_bin_const_t *sender_key, + coap_bin_const_t *id_context, size_t cs_size, + uint8_t *keystream, size_t keystream_size); + +/** @} */ + +#endif /* _OSCORE_CONTEXT_H */ diff --git a/include/oscore/oscore_cose.h b/include/oscore/oscore_cose.h new file mode 100644 index 0000000000..2a6ca3496f --- /dev/null +++ b/include/oscore/oscore_cose.h @@ -0,0 +1,389 @@ +/* + * Copyright (c) 2018, SICS, RISE AB + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + */ + +/** + * @file oscore_cose.h + * @brief An implementation of the CBOR Object Signing and Encryption (RFC). + * + * \author + * Martin Gunnarsson + * adapted with sign1 function for libcoap + * Peter van der Stok + * on request of Fairhair alliance + * adapted for libcoap integration + * Jon Shallow + */ + +#ifndef _OSCORE_COSE_H +#define _OSCORE_COSE_H + +#include +#include + +/** + * @ingroup internal_api + * @defgroup oscore_cose_internal OSCORE COSE Support + * Internal API for interfacing with OSCORE COSE + * @{ + */ + +/* cose curves */ + +#define COSE_Elliptic_Curve_Ed25519 6 /* used with EdDSA only */ +#define COSE_curve_P_256 1 /* with ECDSA known as secp256r1 */ +#define COSE_curve_X25519 4 /* used with ECDH only */ + +#define COSE_KEY_EC2 2 + +/* key, tag, and signature lengths */ +#define COSE_ALGORITHM_Ed25519_SIG_LEN 64 +#define COSE_ALGORITHM_Ed25519_PRIV_KEY_LEN 32 +#define COSE_ALGORITHM_Ed25519_PUB_KEY_LEN 32 + +#define COSE_algorithm_AES_CCM_64_64_128_KEY_LEN 16 +#define COSE_algorithm_AES_CCM_64_64_128_IV_LEN 7 +#define COSE_algorithm_AES_CCM_64_64_128_TAG_LEN 8 + +#define COSE_algorithm_AES_CCM_16_64_128_KEY_LEN 16 +#define COSE_algorithm_AES_CCM_16_64_128_IV_LEN 13 +#define COSE_algorithm_AES_CCM_16_64_128_TAG_LEN 8 + +#define COSE_algorithm_AES_CCM_64_128_128_KEY_LEN 16 +#define COSE_algorithm_AES_CCM_64_128_128_IV_LEN 7 +#define COSE_algorithm_AES_CCM_64_128_128_TAG_LEN 16 + +#define COSE_algorithm_AES_CCM_16_128_128_KEY_LEN 16 +#define COSE_algorithm_AES_CCM_16_128_128_IV_LEN 13 +#define COSE_algorithm_AES_CCM_16_128_128_TAG_LEN 16 + +#define COSE_ALGORITHM_ES256_PRIV_KEY_LEN 24 +#define COSE_ALGORITHM_ES256_PUB_KEY_LEN 32 +#define COSE_ALGORITHM_ES256_SIGNATURE_LEN 64 +#define COSE_ALGORITHM_ES256_HASH_LEN 32 + +#define COSE_ALGORITHM_ES384_PRIV_KEY_LEN 24 +#define COSE_ALGORITHM_ES384_PUB_KEY_LEN 32 +#define COSE_ALGORITHM_ES384_SIGNATURE_LEN 64 +#define COSE_ALGORITHM_ES384_HASH_LEN 48 + +#define COSE_ALGORITHM_ES512_PRIV_KEY_LEN 24 +#define COSE_ALGORITHM_ES512_PUB_KEY_LEN 32 +#define COSE_ALGORITHM_ES512_SIGNATURE_LEN 64 +#define COSE_ALGORITHM_ES512_HASH_LEN 64 + +#define COSE_ALGORITHM_ECDH_PRIV_KEY_LEN 32 +#define COSE_ALGORITHM_ECDH_PUB_KEY_LEN 32 + +#define COSE_ALGORITHM_SHA_512_256_LEN 32 +#define COSE_ALGORITHM_SHA_256_256_LEN 32 +#define COSE_ALGORITHM_SHA_256_64_LEN 8 + +#define COSE_Algorithm_HMAC256_64_HASH_LEN 16 +#define COSE_Algorithm_HMAC256_256_HASH_LEN 32 +#define COSE_Algorithm_HMAC384_384_HASH_LEN 48 +#define COSE_Algorithm_HMAC512_512_HASH_LEN 64 + +/* cose algorithms */ +#define COSE_Algorithm_EdDSA -8 +#define COSE_Algorithm_HKDF_SHA_256 -10 +#define COSE_ALGORITHM_ES256 -7 /* with ECC known as secp256r1 */ +#define COSE_ALGORITHM_ES512 -36 /* with ECDSA */ +#define COSE_ALGORITHM_ES384 -35 /* with ECDSA */ +#define COSE_ALGORITHM_ES256K -47 /* with ECC known as secp256k1 */ +#define COSE_ALGORITHM_SHA_512_256 -17 +#define COSE_ALGORITHM_SHA_256_256 -16 +#define COSE_ALGORITHM_SHA_256_64 -15 +#define COSE_ALGORITHM_SHA_1 -14 +#define COSE_Algorithm_AES_CCM_16_64_128 10 +#define COSE_Algorithm_AES_CCM_16_64_256 11 +#define COSE_Algorithm_AES_CCM_64_64_128 12 +#define COSE_Algorithm_AES_CCM_64_64_256 13 +#define COSE_Algorithm_AES_CCM_16_128_128 30 +#define COSE_Algorithm_AES_CCM_16_128_256 31 +#define COSE_Algorithm_AES_CCM_64_128_128 32 +#define COSE_Algorithm_AES_CCM_64_128_256 33 +#define COSE_Algorithm_HMAC256_64 4 /* truncated to 64 bits */ +#define COSE_Algorithm_HMAC256_256 5 +#define COSE_Algorithm_HMAC384_384 6 +#define COSE_Algorithm_HMAC512_512 7 + +#define UNDEFINED_TAG 0xff + +/* COSE OSCORE Security tags */ +#define OSCORE_CONTEXT_MS 1 +#define OSCORE_CONTEXT_CLIENTID 2 +#define OSCORE_CONTEXT_SERVERID 3 +#define OSCORE_CONTEXT_HKDF 4 +#define OSCORE_CONTEXT_ALG 5 +#define OSCORE_CONTEXT_SALT 6 +#define OSCORE_CONTEXT_CONTEXTID 7 +#define OSCORE_CONTEXT_RPL 8 +#define OSCORE_CONTEXT_CSALG 9 +#define OSCORE_CONTEXT_CSPARAMS 10 +#define OSCORE_CONTEXT_CSKEYPARAMS 11 +#define OSCORE_CONTEXT_CSKEYENC 12 + +/* COSE Web Token claims */ +#define CWT_CLAIM_ISS 1 +#define CWT_CLAIM_SUB 2 +#define CWT_CLAIM_AUD 3 +#define CWT_CLAIM_EXP 4 +#define CWT_CLAIM_NBF 5 +#define CWT_CLAIM_IAT 6 +#define CWT_CLAIM_CTI 7 +#define CWT_CLAIM_CNF 8 +#define CWT_CLAIM_SCOPE 9 +#define CWT_CLAIM_PROFILE 38 +#define CWT_CLAIM_CNONCE 39 +#define CWT_CLAIM_EXI 40 + +#define CWT_OSCORE_SECURITY_CONTEXT 4 + +/* COSE CWT COSE keys */ +#define CWT_KEY_COSE_KEY 1 +#define CWT_KEY_ENCRYPTED_COSE_KEY 2 +#define CWT_KEY_KID 3 +#define CWT_LABEL_k -1 + +/* OAUTH Claims */ +#define OAUTH_CLAIM_ACCESSTOKEN 1 +#define OAUTH_CLAIM_REQCNF 4 +#define OAUTH_CLAIM_GRANTTYPE 33 +#define OAUTH_CLAIM_RSCNF 41 +#define OAUTH_CLAIM_KEYINFO 105 +#define OAUTH_CLAIM_RSNONCE 126 + +/* COSE header parameters */ +#define COSE_HP_ALG 1 +#define COSE_HP_CRIT 2 +#define COSE_HP_CT 3 +#define COSE_HP_KID 4 +#define COSE_HP_IV 5 +#define COSE_HP_CS 7 +#define COSE_HP_CS0 9 +#define COSE_HP_X5BAG 32 +#define COSE_HP_X5CHAIN 33 +#define COSE_HP_X5T 33 +#define COSE_HP_X5U 34 + +/* COSE Key Types */ +#define COSE_KTY_OKP 1 +#define COSE_KTY_EC2 2 +#define COSE_KTY_RSA 3 +#define COSE_KTY_SYMMETRIC 4 + +/* COSE Key Operations */ +#define COSE_KOP_SIGN 1 +#define COSE_KOP_VERIFY 2 +#define COSE_KOP_ENCRYPT 3 +#define COSE_KOP_DECRYPT 4 + +/* COSE Key Common Parameters */ +#define COSE_KCP_KTY 1 +#define COSE_KCP_KID 2 +#define COSE_KCP_ALG 3 +#define COSE_KCP_KEYOPS 4 +#define COSE_KCP_BASE_IV 113 /* temporary */ + +/* COSE Key Type Parameters KEY Type OKP */ +#define COSE_KTP_CRV -1 +#define COSE_KTP_X -2 +#define COSE_KTP_Y -3 + +/* COSE CBOR Tag values */ +#define CBOR_TAG_COSE_SIGN 98 +#define CBOR_TAG_COSE_SIGN1 18 +#define CBOR_TAG_COSE_ENCRYPT 96 +#define CBOR_TAG_COSE_ENCRYPT0 16 +#define CBOR_TAG_COSE_MAC 97 +#define CBOR_TAG_COSE_MAC0 17 + + +// cose_get_tag +// returns tag value from ACE defined tags in CBOR maps +int16_t +cose_get_tag(const uint8_t **data); + +/* parameter value functions */ + +/* return tag length belonging to cose algorithm */ +int +cose_tag_len(coap_cose_alg_t cose_alg); + +/* return hash length belonging to cose algorithm */ +int +cose_hash_len(coap_cose_alg_t cose_alg); + +/* return nonce length belonging to cose algorithm */ +int +cose_nonce_len(coap_cose_alg_t cose_alg); + +/* return key length belonging to cose algorithm */ +int +cose_key_len(coap_cose_alg_t cose_alg); + +/* COSE Encrypt0 Struct */ +typedef struct cose_encrypt0_t { + + coap_cose_alg_t alg; + + coap_bin_const_t key; + + uint8_t partial_iv_data[8]; + /* partial_iv.s will point back to partial_iv_data if set */ + coap_bin_const_t partial_iv; + + coap_bin_const_t key_id; + + coap_bin_const_t kid_context; + + coap_bin_const_t oscore_option; + + coap_bin_const_t nonce; + + coap_bin_const_t aad; + + coap_bin_const_t plaintext; + + coap_bin_const_t ciphertext; + +#ifdef HAVE_OSCORE_GROUP + int group_flag; +#endif /* HAVE_OSCORE_GROUP */ +} cose_encrypt0_t; + +/* COSE Sign1 Struct */ +typedef struct cose_sign1_t { + + coap_cose_alg_t alg; + coap_cose_curve_t alg_param; + int alg_kty; + + coap_bin_const_t private_key; + + coap_bin_const_t public_key; + + coap_bin_const_t ciphertext; + + coap_bin_const_t sigstructure; + + coap_binary_t signature; +} cose_sign1_t; + + + +/* Return length */ +size_t cose_encrypt0_encode(cose_encrypt0_t *ptr, uint8_t *buffer, size_t size); + +/*Return status */ +int cose_encrypt0_decode(cose_encrypt0_t *ptr, uint8_t *buffer, size_t size); + +/* Initiate a new COSE Encrypt0 object. */ +void cose_encrypt0_init(cose_encrypt0_t *ptr); + +void cose_encrypt0_set_alg(cose_encrypt0_t *ptr, uint8_t alg); + +void cose_encrypt0_set_plaintext(cose_encrypt0_t *ptr, uint8_t *buffer, + size_t size); + +void cose_encrypt0_set_ciphertext(cose_encrypt0_t *ptr, uint8_t *buffer, + size_t size); + +/* Return length */ +int cose_encrypt0_get_plaintext(cose_encrypt0_t *ptr, uint8_t **buffer); + +void cose_encrypt0_set_partial_iv(cose_encrypt0_t *ptr, + coap_bin_const_t *partial_iv); + +coap_bin_const_t cose_encrypt0_get_partial_iv(cose_encrypt0_t *ptr); + +void cose_encrypt0_set_key_id(cose_encrypt0_t *ptr, + coap_bin_const_t *key_id); + +/* Return length */ +size_t cose_encrypt0_get_key_id(cose_encrypt0_t *ptr, + const uint8_t **buffer); + +void cose_encrypt0_set_aad(cose_encrypt0_t *ptr, + coap_bin_const_t *aad); + +/* Return length */ +size_t cose_encrypt0_get_kid_context(cose_encrypt0_t *ptr, + const uint8_t **buffer); + +void cose_encrypt0_set_kid_context(cose_encrypt0_t *ptr, + coap_bin_const_t *kid_context); + +/* Returns 1 if successfull, 0 if key is of incorrect length. */ +int cose_encrypt0_set_key(cose_encrypt0_t *ptr, + coap_bin_const_t *key); + +void cose_encrypt0_set_nonce(cose_encrypt0_t *ptr, + coap_bin_const_t *nonce); + +int cose_encrypt0_encrypt(cose_encrypt0_t *ptr, + uint8_t *ciphertext_buffer, size_t ciphertext_len); + +int cose_encrypt0_decrypt(cose_encrypt0_t *ptr, + uint8_t *plaintext_buffer, size_t plaintext_len); + +/* ed25519 signature functions */ + +void cose_sign1_init(cose_sign1_t *ptr); + +void cose_sign1_set_alg(cose_sign1_t *ptr, int alg, + int alg_param, int alg_kty); + +void cose_sign1_set_ciphertext(cose_sign1_t *ptr, + uint8_t *buffer, size_t size); + +void cose_sign1_set_public_key(cose_sign1_t *ptr, + coap_bin_const_t *buffer); + +void cose_sign1_set_private_key(cose_sign1_t *ptr, + coap_bin_const_t *buffer); + +/* Return length */ +int cose_sign1_get_signature(cose_sign1_t *ptr, + const uint8_t **buffer); + +void cose_sign1_set_signature(cose_sign1_t *ptr, + uint8_t *buffer); + +int cose_sign1_sign(cose_sign1_t *ptr); + +void cose_sign1_set_sigstructure(cose_sign1_t *ptr, + uint8_t *buffer, size_t size); + +int cose_sign1_verify(cose_sign1_t *ptr); + +/** @} */ + +#endif /* _OSCORE_COSE_H */ diff --git a/include/oscore/oscore_crypto.h b/include/oscore/oscore_crypto.h new file mode 100644 index 0000000000..3b209f0287 --- /dev/null +++ b/include/oscore/oscore_crypto.h @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2018, SICS, RISE AB + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + */ + +/** + * @file oscore_crypto.h + * @brief An implementation of the Hash Based Key Derivation Function (RFC) and wrappers for AES-CCM*. + * + * \author + * Martin Gunnarsson + * + * adapted to libcoap + * Peter van der Stok + * on request of Fairhair alliance + * adapted for libcoap integration + * Jon Shallow + */ + +#ifndef _OSCORE_CRYPTO_H +#define _OSCORE_CRYPTO_H + +#include + +/** + * @ingroup internal_api + * @addtogroup oscore_internal + * @{ + */ + +#define HKDF_INFO_MAXLEN 25 +#define HKDF_OUTPUT_MAXLEN 25 +#define AES_CCM_TAG 8 + +/* Plaintext Maxlen and Tag Maxlen is quite generous. */ +#define AEAD_PLAINTEXT_MAXLEN COAP_MAX_CHUNK_SIZE +#define AEAD_TAG_MAXLEN COAP_MAX_CHUNK_SIZE + +void +oscore_hmac_shaX(coap_cose_alg_t alg, coap_bin_const_t *key, + coap_bin_const_t *data, uint8_t *hmac); + +int +oscore_hkdf_extract(coap_cose_alg_t alg, coap_bin_const_t *salt, + coap_bin_const_t *ikm, uint8_t *prk_buffer); + +int +oscore_hkdf_expand(coap_cose_alg_t alg, uint8_t *prk, uint8_t *info, size_t info_len, + uint8_t *okm, size_t okm_len); + +/* Return 0 if signing failure. Signatue length otherwise, signature length and key length are derived fron ed25519 values. No check is done to ensure that buffers are of the correct length. */ + +int +oscore_edDSA_sign(coap_cose_alg_t alg, coap_cose_curve_t alg_param, + coap_binary_t *signature, coap_bin_const_t *ciphertext, + coap_bin_const_t *seed, coap_bin_const_t *public_key); + +/* Return 0 if signing failure. Signatue length otherwise, signature length and key length are derived fron ed25519 values. No check is done to ensure that buffers are of the correct length. */ + +int +oscore_edDSA_verify(coap_cose_alg_t alg, coap_cose_curve_t alg_param, + coap_binary_t *signature, coap_bin_const_t *plaintext, + coap_bin_const_t *public_key); + +int oscore_hkdf(coap_cose_alg_t alg, coap_bin_const_t *salt, coap_bin_const_t *ikm, + uint8_t *info, size_t info_len, uint8_t *okm, size_t okm_len); + +/** @} */ + +#endif /* _OSCORE_CRYPTO_H */ diff --git a/libcoap-3.map b/libcoap-3.map index 8cbdadf9a7..615cccc021 100644 --- a/libcoap-3.map +++ b/libcoap-3.map @@ -60,6 +60,8 @@ global: coap_delete_cache_entry; coap_delete_cache_key; coap_delete_optlist; + coap_delete_oscore_context; + coap_delete_oscore_recipient; coap_delete_pdu; coap_delete_resource; coap_delete_str_const; @@ -108,6 +110,9 @@ global: coap_new_bin_const; coap_new_cache_entry; coap_new_client_session; + coap_new_client_session_oscore; + coap_new_client_session_oscore_pki; + coap_new_client_session_oscore_psk; coap_new_client_session_pki; coap_new_client_session_psk; coap_new_client_session_psk2; @@ -116,6 +121,8 @@ global: coap_new_error_response; coap_new_message_id; coap_new_optlist; + coap_new_oscore_context; + coap_new_oscore_recipient; coap_new_pdu; coap_new_str_const; coap_new_string; @@ -134,6 +141,7 @@ global: coap_opt_setheader; coap_opt_size; coap_opt_value; + coap_oscore_is_supported; coap_package_name; coap_package_version; coap_pdu_duplicate; diff --git a/libcoap-3.sym b/libcoap-3.sym index 42eb8c0142..628420ccbd 100644 --- a/libcoap-3.sym +++ b/libcoap-3.sym @@ -58,6 +58,8 @@ coap_delete_bin_const coap_delete_cache_entry coap_delete_cache_key coap_delete_optlist +coap_delete_oscore_context +coap_delete_oscore_recipient coap_delete_pdu coap_delete_resource coap_delete_str_const @@ -106,6 +108,9 @@ coap_new_binary coap_new_bin_const coap_new_cache_entry coap_new_client_session +coap_new_client_session_oscore +coap_new_client_session_oscore_pki +coap_new_client_session_oscore_psk coap_new_client_session_pki coap_new_client_session_psk coap_new_client_session_psk2 @@ -114,6 +119,8 @@ coap_new_endpoint coap_new_error_response coap_new_message_id coap_new_optlist +coap_new_oscore_context +coap_new_oscore_recipient coap_new_pdu coap_new_str_const coap_new_string @@ -132,6 +139,7 @@ coap_opt_parse coap_opt_setheader coap_opt_size coap_opt_value +coap_oscore_is_supported coap_package_name coap_package_version coap_pdu_duplicate diff --git a/man/Makefile.am b/man/Makefile.am index c4080b817e..2e5a53704c 100644 --- a/man/Makefile.am +++ b/man/Makefile.am @@ -31,6 +31,7 @@ TXT3 = coap_async.txt \ coap_keepalive.txt \ coap_logging.txt \ coap_observe.txt \ + coap_oscore.txt \ coap_pdu_access.txt \ coap_pdu_setup.txt \ coap_recovery.txt \ @@ -45,7 +46,8 @@ man3_MANS = $(MAN3) TXT5 = coap-client.txt \ coap-rd.txt \ - coap-server.txt + coap-server.txt \ + coap-oscore-conf.txt MAN5 = $(TXT5:%.txt=%.5) @@ -94,6 +96,7 @@ install-man: install-man3 install-man5 install-man7 @echo ".so man3/coap_context.3" > coap_context_get_csm_timeout.3 @echo ".so man3/coap_logging.3" > coap_endpoint_str.3 @echo ".so man3/coap_logging.3" > coap_session_str.3 + @echo ".so man3/coap_oscore.3" > coap_new_client_session_oscore_psk.3 @echo ".so man3/coap_pdu_access.3" > coap_option_filter_set.3 @echo ".so man3/coap_pdu_access.3" > coap_option_filter_unset.3 @echo ".so man3/coap_pdu_access.3" > coap_option_iterator_init.3 diff --git a/man/coap-client.txt.in b/man/coap-client.txt.in index f603b21f35..903767de51 100644 --- a/man/coap-client.txt.in +++ b/man/coap-client.txt.in @@ -22,8 +22,9 @@ SYNOPSIS *coap-client* [*-a* addr] [*-b* [num,]size] [*-e* text] [*-f* file] [*-l* loss] [*-m* method] [*-o* file] [*-p* port] [*-r*] [*-s duration*] [*-t* type] [*-v* num] [*-w*] [*-A* type] [*-B* seconds] - [*-G* count] [*-H* hoplimit] [*-K* interval] [*-L* value] [*-N*] - [*-O* num,text] [*-P* scheme://addr[:port]] [*-T* token] [*-U*] + [*-E* oscore_conf_file[,seq_file]] [*-G* count] [*-H* hoplimit] + [*-K* interval] [*-L* value] [*-N*] [*-O* num,text] + [*-P* scheme://addr[:port]] [*-T* token] [*-U*] [[*-h* match_hint_file] [*-k* key] [*-u* user]] [[*-c* certfile] [*-j* keyfile] [-n] [*-C* cafile] [*-J* pkcs11_pin] [*-M* rpk_file] [*-R* trust_casfile]] URI @@ -126,6 +127,13 @@ OPTIONS - General *-B* seconds:: Break operation after waiting given seconds (default is 90). +*-E* oscore_conf_file[,seq_file]:: + 'oscore_conf_file' contains OSCORE configuration. See *coap-oscore-conf*(5) + for definitions. Optional 'seq_file' (which is created if needed) is used to + save the current transmit sequence number, so on client restart sequence + numbers continue to increase and are not reset to prevent anti-replay + mechanisms being triggered. + *-G* count :: Repeat the Request 'count' times. Must have a value between 1 and 255 inclusive. Default is '1'. @@ -283,6 +291,11 @@ EXIT STATUS Failure (syntax or usage error; configuration error; document processing failure; unexpected error) +SEE ALSO +-------- + +*coap-server*(5) and *coap-oscore-conf*(5) + BUGS ----- Please report bugs on the mailing list for libcoap: diff --git a/man/coap-oscore-conf.txt.in b/man/coap-oscore-conf.txt.in new file mode 100644 index 0000000000..6c12258e66 --- /dev/null +++ b/man/coap-oscore-conf.txt.in @@ -0,0 +1,178 @@ +// -*- mode:doc; -*- +// vim: set syntax=asciidoc,tw=0: + +coap-oscore-conf(5) +=================== +:doctype: manpage +:man source: coap-oscore-conf +:man version: @PACKAGE_VERSION@ +:man manual: Coap OSCORE configuration file format + +NAME +----- +coap-oscore-conf +- CoAP OSCORE configuration file format + +DESCRIPTION +----------- +The OSCORE configuration file is read in when using the _*-E* oscore_conf_file_ +option for the *coap-client*(5) or *coap-server*(5) executables. This then +allows a client or server to use OSCORE to protect the CoAP information +between endpoints (RFC8613). + +This configuration file can be a configuration held in memory, the formatting +of the memory region is same as that for a file as if the file was mapped +into memory. The *coap_new_oscore_context*(3) function uses the memory +version of the file. + +The configuration file complises of a set of keywords, the value of the +keyword encoding type and the keyword value, one per line, comma separated. + +keyword,encoding,value + +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. + +The possible encodings are: + +*ascii* :: + The value is encoded as a binary representation of the ascii string. This + string can optionally be enclosed in _"_. + +*hex* :: + The value is encoded as a binary representation of the hex string. This + string can optionally be enclosed in _"_. + +*integer* :: + The value is encoded as an integer number. + +The valid keywords are: + +*master_secret* :: + (*hex* or *ascii*) (*Required*) (No default) + + RFC8613 Section 3.1 Master Secret. Variable length. Must be the same for + both client and server. + +*master_salt* :: + (*hex* or *ascii*) (*Optional*) (No default) + + RFC8613 Section 3.1 Master Salt. Variable length. Must be the same for + both client and server. + +*id_context* :: + (*hex* or *ascii*) (*Optional*) (No default) + + RFC8613 Section 3.1 ID Context. Variable length. Must be the same for + both client and server. + +*sender_id* :: + (*hex* or *ascii*) (*Required*) (No default) + + RFC8613 Section 3.1. Sender ID. This is the local application ID. + Maximum length is determined by the AEAD Algorithm (typically 7). + +*recipient_id* :: + (*hex* or *ascii*) (*Required*) (No default) + + RFC8613 Section 3.1. Recipient ID. This is the remote peer application ID. + Maximum length is determined by the AEAD Algorithm (typically 7). + For servers, there can be multiple (unique) recipient_ids. + For clients, there should only be one recipient_id (only the first is used). + +*replay_window* :: + (*integer*) (*Optional*) (Default is 32) + + RFC8613 Section 3.1. Recipient Replay Window (Server Only). Supported values + are 1 - 63. + +*aead_alg* :: + (*integer*) (*Optional*) (Default is 10) + + RFC8613 Section 3.1. AEAD Algorithm. Only the mandatory and a small subset + of the algorithms are supported. + +*hkdf_alg* :: + (*integer*) (*Optional*) (Default is 5) + + RFC8613 Section 3.1. HDKF Algorithm. Only the mandatory and a small subset + of the algorithms are supported. + +*ssn_freq* :: + (*integer*) (*Optional*) (Default is 1) + + RFC8613 Appendix B.1.1. Sender Sequence Number frequency non-volatile + storage update rate. Has to be a positive number. + +EXAMPLE SERVER CONFIGURATION FILE +--------------------------------- + +[source, c] +---- + +# Master Secret (same for both client and server) +master_secret,hex,"0102030405060708090a0b0c0d0e0f10" + +# Master Salt (same for both client and server) +master_salt,hex,"9e7ca92223786340" + +# Sender ID +sender_id,ascii,"server" + +# Recipient ID +recipient_id,ascii,"client" + +# Replay Window (usually 30) +replay_window,integer,30 + +# AEAD COSE Cipher Algorithm (usually 10) +aead_alg,integer,10 + +# HKDF COSE hash Algorithm (usually 5) +hkdf_alg,integer,5 + +---- + +EXAMPLE CLIENT CONFIGURATION FILE +--------------------------------- + +[source, c] +---- + +# Master Secret (same for both client and server) +master_secret,hex,"0102030405060708090a0b0c0d0e0f10" + +# Master Salt (same for both client and server) +master_salt,hex,"9e7ca92223786340" + +# Sender ID (This is the client who is the Sender) +sender_id,ascii,"client" + +# Recipient ID (It is the server that is remote) +recipient_id,ascii,"server" + +# Replay Window (usually 30) +replay_window,integer,30 + +# AEAD COSE Cipher Algorithm (usually 10) +aead_alg,integer,10 + +# HKDF COSE hash Algorithm (usually 5) +hkdf_alg,integer,5 + +---- + +SEE ALSO +-------- + +*coap-client*(5), *coap-server*(5) and *coap_new_oscore_context*(3) + +FURTHER INFORMATION +------------------- +See + +"RFC8613: Object Security for Constrained RESTful Environments (OSCORE)" + +for further information. + +BUGS +----- +Please report bugs on the mailing list for libcoap: +libcoap-developers@lists.sourceforge.net or raise an issue on GitHub at +https://github.com/obgm/libcoap/issues + +AUTHORS +------- +The libcoap project diff --git a/man/coap-server.txt.in b/man/coap-server.txt.in index 10bdb871a4..ad8dbe57cd 100644 --- a/man/coap-server.txt.in +++ b/man/coap-server.txt.in @@ -19,9 +19,10 @@ coap-server-notls SYNOPSIS -------- -*coap-server* [*-d* max] [*-e*] [*-g* group] [*-G* group_if] [*-l* loss] - [*-p* port] [*-v* num] [*-A* address] [*-L* value] [*-N*] - [*-P* scheme://addr[:port],name1[,name2..]] +*coap-server* [*-d* max] [*-e*] [*-g* group] [*-l* loss] [*-p* port] + [*-v* num] [*-A* address] [*-E* oscore_conf_file[,seq_file]] + [*-G* group_if] [*-L* value] [*-N*] + [*-P* scheme://addr[:port],name1[,name2..] [[*-h* hint] [*-i* match_identity_file] [*-k* key] [*-s* match_psk_sni_file] [*-u* user]] [[*-c* certfile] [*-j* keyfile] [*-n*] [*-C* cafile] @@ -52,10 +53,6 @@ OPTIONS - General Join specified multicast 'group' on start up. *Note:* DTLS over multicast is not currently supported. -*-G* group_if:: - Use this interface for listening for the multicast group. This can be - different from the implied interface if the *-A* option is used. - *-l* list:: Fail to send some datagrams specified by a comma separated list of numbers or number ranges (debugging only). @@ -77,6 +74,17 @@ OPTIONS - General *-A* address:: The local address of the interface which the server has to listen on. +*-E* oscore_conf_file[,seq_file]:: + 'oscore_conf_file' contains OSCORE configuration. See *coap-oscore-conf*(5) + for definitions. Optional 'seq_file' (which is created if needed) is used to + save the current transmit sequence number, so on server restart sequence + numbers continue to increase and are not reset to prevent anti-replay + mechanisms being triggered. + +*-G group_if:: + Use this interface for listening for the multicast group. This can be + different from the implied interface if the *-A* option is used. + *-L* value:: Sum of one or more COAP_BLOCK_* flag values for different block handling methods. Default is 1 (COAP_BLOCK_USE_LIBCOAP). @@ -248,6 +256,11 @@ EXIT STATUS Failure (syntax or usage error; configuration error; document processing failure; unexpected error) +SEE ALSO +-------- + +*coap-client*(5) and *coap-oscore-conf*(5) + BUGS ----- Please report bugs on the mailing list for libcoap: diff --git a/man/coap_oscore.txt.in b/man/coap_oscore.txt.in new file mode 100644 index 0000000000..000de7b35d --- /dev/null +++ b/man/coap_oscore.txt.in @@ -0,0 +1,364 @@ +// -*- mode:doc; -*- +// vim: set syntax=asciidoc,tw=0: + +coap_oscore(3) +============== +:doctype: manpage +:man source: coap_oscore +:man version: @PACKAGE_VERSION@ +:man manual: libcoap Manual + +NAME +---- +coap_oscore, +coap_oscore_is_supported, +coap_new_oscore_context, +coap_delete_oscore_context, +coap_new_oscore_recipient, +coap_delete_oscore_recipient, +coap_new_client_session_oscore, +coap_new_client_session_oscore_pki, +coap_new_client_session_oscore_psk, +- Configuring and using OSCORE + +SYNOPSIS +-------- +*#include * + +*int coap_oscore_is_supported(void);* + +*coap_oscore_context_t *coap_new_oscore_context(coap_context_t *_context_, +coap_str_const_t _conf_mem_, coap_oscore_save_seq_num_t _save_seq_num_func_, +void *_save_seq_num_func_param_, uint64_t _start_seq_num_);* + +*int coap_delete_oscore_context(coap_context_t *_context_, +coap_oscore_context_t *_oscore_context_);* + +*int coap_new_oscore_recipient(coap_oscore_context_t *_oscore_context_, +coap_bin_const_t *_recipient_id_);* + +*int coap_delete_oscore_recipient(coap_oscore_context_t *_oscore_context_, +coap_bin_const_t *_recipient_id_);* + +*coap_session_t *coap_new_client_session_oscore(coap_context_t *_context_, +const coap_address_t *_local_if_, const coap_address_t *_server_, +coap_proto_t _proto_, coap_oscore_context_t *_oscore_context_);* + +*coap_session_t *coap_new_client_session_oscore_psk(coap_context_t *_context_, +const coap_address_t *_local_if_, const coap_address_t *_server_, +coap_proto_t _proto_, coap_dtls_cpsk_t *_psk_data_, +coap_oscore_context_t *_oscore_context_);* + +*coap_session_t *coap_new_client_session_oscore_pki(coap_context_t *_context_, +const coap_address_t *_local_if_, const coap_address_t *_server_, +coap_proto_t _proto_, coap_dtls_pki_t *_pki_data_, +coap_oscore_context_t *_oscore_context_);* + +For specific (D)TLS library support, link with +*-lcoap-@LIBCOAP_API_VERSION@-notls*, *-lcoap-@LIBCOAP_API_VERSION@-gnutls*, +*-lcoap-@LIBCOAP_API_VERSION@-openssl*, *-lcoap-@LIBCOAP_API_VERSION@-mbedtls* +or *-lcoap-@LIBCOAP_API_VERSION@-tinydtls*. Otherwise, link with +*-lcoap-@LIBCOAP_API_VERSION@* to get the default (D)TLS library support. + +DESCRIPTION +----------- +This describes libcoap's support for using OSCORE as defined in RFC 8613. + +OSCORE provides end-to-end protection between endpoints communicating using +CoAP. (D)TLS can only protect HOP by HOP traffic which allows proxies to +manipulate information. + +The *coap_oscore_is_supported*() function returns 1 if OSCORE is supported, +otherwise 0. + +The coap_oscore_save_seq_num_t method handler function prototype is defined as: +[source, c] +---- +typedef int (*coap_oscore_save_seq_num_t)(uint64_t sender_seq_num, void *param); +---- +and returns 0 on failure, 1 on succes. + +The *coap_new_oscore_context*() function is used to build a new OSCORE context +which is associated with _context_. It parses the provided OSCORE configuration +in _conf_mem_. The format of the keywords, encoding types and values is +documented in *coap-oscore-conf*(5). It also sets an optional function +_save_seq_num_func_ (which gets _save_seq_num_func_param_ passed in) that is +called to store the next Sender Sequence Number (SSN) in non-volatile +storage. The latest next SSN from non-volatile storage (or 0) is then put in +_start_seq_num_. SSN are used so that anti-replay mechanisms do not kick in +following application restarts. The rate of calling _save_seq_num_func_ can +be controlled by the _ssn_freq_ parameter as defined in *coap-oscore-conf*(5). + +This OSCORE context is then used in the client setup functions. + +This OSCORE context is already added to the CoAP context, so nothing extra +needs to be done for servers. It is possible for there to be multiple OSCORE +context, but for servers, there should be no duplication of recipient_id. + +If the server is proxy enabled and the new incoming session is OSCORE encoded +with the Outer CoAP ProxyScheme and Host Options set, then the libcoap logic +will determine whether the server is the final endpoint of the session, or +whether the proxy will be forwarding the session off to another server, based +on how *coap_resource_proxy_uri_init*(3) configured the proxy logic. If the +session is going to forwarded, then the OSCORE protection will not be removed. + +If *coap_new_oscore_context*() is not called and the proxy logic (if set) +indicates the session will get forwarded, then the OSCORE protection is +untouched, otherwise the session will get dropped with an unknown critical +option error. + +The *coap_new_oscore_recipient*() is used to add a new _recipient_id_ to the +OSCORE context _oscore_context_. The new _recipient_id_ should be unique +and this function should only be used for server based appliocations as the +client will only ever use the first defined _recipient_id_. + +The *coap_delete_oscore_recipient*() is used to remove the _recipient_id_ from +the OSCORE context _oscore_context_. Traffic continuing to use _recipient_id_ +will then fail. + +The *coap_delete_oscore_context*() function is used to free off the +coap_oscore_context_t structure _oscore_context_ as returned by +*coap_new_oscore_context*() and removes it from _context_ so it can no longer +be used for OSCORE protection. _oscore_context_ is also deleted by +coap_free_context(3). + +The *coap_new_client_session_oscore*() is basically the same as +*coap_new_client_session*(3), but has an additional parameter _oscore_context_ +that is provided by *coap_new_oscore_context*(). This function creates a +client endpoint for a specific _context_ and initiates a new client session +to the specified _server_ using the CoAP protocol _proto_ and OSCORE +protected by the _oscore_context_ definition. If the port is set to 0 in +_server_, then the default CoAP port is used. Normally _local_if_ would be +set to NULL, but by specifying _local_if_ the source of the network session +can be bound to a specific IP address or port. The session will initially +have a reference count of 1. + +The *coap_new_client_session_oscore_psk*() is basically the same as +*coap_new_client_session_psk2*(3), but has an additional parameter +_oscore_context_ that is provided by *coap_new_oscore_context*(). This +function, for a specific _context_, is used to configure the TLS context +using the _setup_data_ variables as defined in the coap_dtls_cpsk_t structure +in the newly created endpoint session - see *coap_encryption*(3), +as well as OSCORE protected by the _oscore_context_ definition. The connection +is to the specified _server_ using the CoAP protocol _proto_. If the port is +set to 0 in _server_, then the default CoAP port is used. Normally _local_if_ +would be set to NULL, but by specifying _local_if_ the source of the network +session can be bound to a specific IP address or port. The session will +initially have a reference count of 1. + +The *coap_new_client_session_oscore_pki*() is basically the same as +*coap_new_client_session_pki*(3), but has an additional parameter +_oscore_context_ that is provided by *coap_new_oscore_context*(). This +function, for a specific _context_, is used to configure the TLS context using +the _setup_data_ variables as defined in the coap_dtls_pki_t structure in the +newly created endpoint session - see *coap_encryption*(3), as well as OSCORE +protected by the _oscore_context_ definition. The connection is to the specified +_server_ using the CoAP protocol _proto_. If the port is set to 0 in +_server_, then the default CoAP port is used. Normally _local_if_ would be +set to NULL, but by specifying _local_if_ the source of the network session +can be bound to a specific IP address or port. The session will initially have +a reference count of 1. + +RETURN VALUES +------------- +The *coap_new_client_session_oscore*(), *coap_new_client_session_oscore_psk*() +and *coap_new_client_session_oscore_pki*() functions return a newly created +client session or NULL if there is a malloc or parameter failure. + +The *coap_new_oscore_context*() function returns a _coap_oscore_context_t +or NULL on failure. + +The *coap_oscore_is_supported*(), *coap_delete_oscore_context*(), +*coap_add_oscore_recipient*() and *coap_delete_oscore_context*() functions +return 0 on failure, 1 on success. + +EXAMPLES +-------- +*Client Setup* + +[source, c] +---- +#include + +#include +#include + +static uint8_t oscore_config[] = + "master_secret,hex,\"0102030405060708090a0b0c0d0e0f10\"\n" + "master_salt,hex,\"9e7ca92223786340\"\n" + "server_id,ascii,\"client\"\n" + "recipient_id,ascii,\"server\"\n" + "replay_window,integer,30\n" + "aead_alg,integer,10\n" + "hkdf_alg,integer,5\n" +; +static FILE *oscore_seq_num_fp = NULL; +/* Not a particularily safe place to keep next Sender Sequence NUmber ... */ +static const char* oscore_seq_save_file = "/tmp/client.seq"; + +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, "%lu\n", sender_seq_num); + fflush(oscore_seq_num_fp); + } + return 1; +} + +static coap_session_t * +setup_client_session (struct in_addr ip_address) { + coap_session_t *session; + coap_address_t server; + /* See coap_context(3) */ + coap_context_t *context = coap_new_context(NULL); + + if (!context) + return NULL; + + /* See coap_block(3) */ + coap_context_set_block_mode(context, + COAP_BLOCK_USE_LIBCOAP | COAP_BLOCK_SINGLE_BODY); + + + coap_address_init(&server); + server.addr.sa.sa_family = AF_INET; + server.addr.sin.sin_addr = ip_address; + server.addr.sin.sin_port = htons (5683); + + if (coap_oscore_is_supported()) { + coap_str_const_t config = { sizeof (oscore_config), oscore_config }; + uint64_t start_seq_num = 0; + + 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; + } + } + fscanf(oscore_seq_num_fp, "%lu", &start_seq_num); + } + coap_oscore_context_t *oscore_context = coap_new_oscore_context(context, + config, oscore_save_seq_num, + NULL, start_seq_num); + if (!oscore_context) { + coap_free_context(context); + return NULL; + } + session = coap_new_client_session_oscore(context, NULL, &server, + COAP_PROTO_UDP, oscore_context); + } + else { + session = coap_new_client_session(context, NULL, &server, COAP_PROTO_UDP); + } + if (!session) { + coap_free_context(context); + return NULL; + } + /* The context is in session->context */ + return session; +} +---- + +*Server Setup* + +[source, c] +---- +#include +#include + +static uint8_t oscore_config[] = + "master_secret,hex,\"0102030405060708090a0b0c0d0e0f10\"\n" + "master_salt,hex,\"9e7ca92223786340\"\n" + "sender_id,ascii,\"server\"\n" + "recipient_id,ascii,\"client\"\n" + "replay_window,integer,30\n" + "aead_alg,integer,10\n" + "hkdf_alg,integer,5\n" +; +static FILE *oscore_seq_num_fp = NULL; +/* Not a particularily safe place to keep next Sender Sequence NUmber ... */ +static const char* oscore_seq_save_file = "/tmp/server.seq"; + +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, "%lu\n", sender_seq_num); + fflush(oscore_seq_num_fp); + } + return 1; +} + +static int +setup_context (void) { + /* See coap_context(3) */ + coap_context_t *context = coap_new_context(NULL); + + if (!context) + return 0; + + /* See coap_block(3) */ + coap_context_set_block_mode(context, + COAP_BLOCK_USE_LIBCOAP | COAP_BLOCK_SINGLE_BODY); + + if (coap_oscore_is_supported()) { + coap_str_const_t config = { sizeof (oscore_config), oscore_config }; + uint64_t start_seq_num = 0; + + 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 0; + } + } + fscanf(oscore_seq_num_fp, "%lu", &start_seq_num); + } + coap_oscore_context_t *oscore_context = coap_new_oscore_context(context, + config, oscore_save_seq_num, + NULL, start_seq_num); + if (!oscore_context) { + coap_free_context(context); + return 0; + } + } + return 1; +} + +---- + +SEE ALSO +-------- +*coap_endpoint_client*(3), *coap_endpoint_server*(3) and *coap-oscore-conf*(5) + +FURTHER INFORMATION +------------------- +See + +"RFC7252: The Constrained Application Protocol (CoAP)" + +"RFC8613: Object Security for Constrained RESTful Environments (OSCORE)" + +for further information. + +BUGS +---- +Please report bugs on the mailing list for libcoap: +libcoap-developers@lists.sourceforge.net or raise an issue on GitHub at +https://github.com/obgm/libcoap/issues + +AUTHORS +------- +The libcoap project diff --git a/man/coap_pdu_setup.txt.in b/man/coap_pdu_setup.txt.in index 308ad128cf..894eef82d6 100644 --- a/man/coap_pdu_setup.txt.in +++ b/man/coap_pdu_setup.txt.in @@ -570,6 +570,8 @@ See "RFC7959: Block-Wise Transfers in the Constrained Application Protocol (CoAP)" +"RFC8613: Object Security for Constrained RESTful Environments (OSCORE)" + for further information. See https://www.iana.org/assignments/core-parameters/core-parameters.xhtml#option-numbers diff --git a/src/block.c b/src/block.c index 7c8f92df52..bb425d14ea 100644 --- a/src/block.c +++ b/src/block.c @@ -374,7 +374,7 @@ coap_add_data_large_internal(coap_session_t *session, "Size of large buffer restricted to 0x%x bytes\n", MAX_BLK_LEN); length = MAX_BLK_LEN; } - /* Determine the block size to use, adding in sensible options if needed */ + if (COAP_PDU_IS_REQUEST(pdu)) { coap_lg_xmit_t *q; @@ -415,7 +415,11 @@ coap_add_data_large_internal(coap_session_t *session, } } + /* Determine the block size to use, adding in sensible options if needed */ avail = pdu->max_size - pdu->used_size - pdu->hdr_size; +#ifdef HAVE_OSCORE + avail -= coap_oscore_overhead(session, pdu); +#endif /* HAVE_OSCORE */ /* May need token of length 8, so account for this */ avail -= (pdu->token_length <= 8) ? pdu->token_length <= 8 : 0; blk_size = coap_flsll((long long)avail) - 4 - 1; @@ -575,6 +579,9 @@ coap_add_data_large_internal(coap_session_t *session, avail = pdu->max_size - pdu->used_size - pdu->hdr_size; /* May need token of length 8, so account for this */ avail -= (pdu->token_length <= 8) ? pdu->token_length <= 8 : 0; +#ifdef HAVE_OSCORE + avail -= coap_oscore_overhead(session, pdu); +#endif /* HAVE_OSCORE */ if (avail < (ssize_t)chunk) { /* chunk size change down */ if (avail < 16) { @@ -638,8 +645,8 @@ coap_add_data_large_request(coap_session_t *session, const uint8_t *data, coap_release_large_data_t release_func, void *app_ptr) { - return coap_add_data_large_internal(session, pdu, NULL, NULL, -1, - 0, length, data, release_func, app_ptr); + return coap_add_data_large_internal(session, pdu, NULL, NULL, -1, 0, + length, data, release_func, app_ptr); } #endif /* ! COAP_CLIENT_SUPPORT */ @@ -1940,6 +1947,33 @@ coap_handle_response_get_block(coap_context_t *context, } } } +#ifdef HAVE_OSCORE + else if (rcvd->code == COAP_RESPONSE_CODE(401)) { + /* Check for authorization failure and Echo option for freshness */ + coap_opt_t *opt = coap_check_option(rcvd, COAP_OPTION_ECHO, &opt_iter); + if (opt) { + /* Need to retransmit original request with ECHO added */ + coap_bin_const_t echo_value; + coap_pdu_t *osc_pdu; + int oscore_encryption = session->oscore_encryption; + coap_mid_t mid; + + echo_value.s = coap_opt_value(opt); + echo_value.length = coap_opt_length(opt); + osc_pdu = coap_oscore_new_pdu_encrypted(session, &p->pdu, &echo_value, + 0); + if (!osc_pdu) + goto fail_resp; + osc_pdu->mid = coap_new_message_id(session); + session->oscore_encryption = 0; + mid = coap_send_internal(session, osc_pdu); + session->oscore_encryption = oscore_encryption; + if (mid == COAP_INVALID_MID) + goto fail_resp; + goto skip_app_handler; + } + } +#endif /* HAVE_OSCORE */ if (!block.m && !p->observe_set) { fail_resp: /* lg_crcv no longer required - cache it */ diff --git a/src/coap_debug.c b/src/coap_debug.c index 1b5aa19559..49bceb14d8 100644 --- a/src/coap_debug.c +++ b/src/coap_debug.c @@ -343,6 +343,7 @@ msg_option_string(uint8_t code, uint16_t option_type) { { COAP_OPTION_OBSERVE, "Observe" }, { COAP_OPTION_URI_PORT, "Uri-Port" }, { COAP_OPTION_LOCATION_PATH, "Location-Path" }, + { COAP_OPTION_OSCORE, "Oscore" }, { COAP_OPTION_URI_PATH, "Uri-Path" }, { COAP_OPTION_CONTENT_FORMAT, "Content-Format" }, { COAP_OPTION_MAXAGE, "Max-Age" }, @@ -356,7 +357,9 @@ msg_option_string(uint8_t code, uint16_t option_type) { { COAP_OPTION_PROXY_URI, "Proxy-Uri" }, { COAP_OPTION_PROXY_SCHEME, "Proxy-Scheme" }, { COAP_OPTION_SIZE1, "Size1" }, - { COAP_OPTION_NORESPONSE, "No-Response" } + { COAP_OPTION_NORESPONSE, "No-Response" }, + { COAP_OPTION_ECHO, "Echo" }, + { COAP_OPTION_RTAG, "RTag" } }; static struct option_desc_t options_csm[] = { @@ -452,6 +455,7 @@ print_content_format(unsigned int format_type, { COAP_MEDIATYPE_APPLICATION_SENML_XML, "application/senml+xml" }, { COAP_MEDIATYPE_APPLICATION_SENSML_XML, "application/sensml+xml" }, { COAP_MEDIATYPE_APPLICATION_DOTS_CBOR, "application/dots+cbor" }, + { COAP_MEDIATYPE_APPLICATION_OSCORE, "application/oscore"}, { 75, "application/dcaf+cbor" } }; @@ -530,6 +534,7 @@ coap_show_pdu(coap_log_t level, const coap_pdu_t *pdu) { uint32_t opt_len; const uint8_t* opt_val; size_t outbuflen = 0; + int is_oscore_payload = 0; /* Save time if not needed */ if (level > coap_get_log_level()) @@ -620,6 +625,69 @@ coap_show_pdu(coap_log_t level, const coap_pdu_t *pdu) { break; + case COAP_OPTION_OSCORE: + opt_len = coap_opt_length(option); + buf[0] = '\000'; + if (opt_len) { + size_t ofs = 1; + size_t cnt; + + opt_val = coap_opt_value(option); + if (opt_val[0] & 0x20) { + /* Group Flag */ + snprintf((char *)buf, sizeof(buf), "grp"); + } + if (opt_val[0] & 0x07) { + /* Partial IV */ + cnt = opt_val[0] & 0x07; + if (cnt > opt_len - ofs) + goto no_more; + buf_len = strlen((char *)buf); + snprintf((char *)&buf[buf_len], sizeof(buf)-buf_len, "%spIV=0x", + buf_len ? "," : ""); + for (i = 0; (uint32_t)i < cnt; i++) { + buf_len = strlen((char *)buf); + snprintf((char *)&buf[buf_len], sizeof(buf)-buf_len, + "%02x", opt_val[ofs + i]); + } + ofs += cnt; + } + if (opt_val[0] & 0x10) { + /* kid context */ + cnt = opt_val[ofs]; + if (cnt > opt_len - ofs - 1) + goto no_more; + ofs++; + buf_len = strlen((char *)buf); + snprintf((char *)&buf[buf_len], sizeof(buf)-buf_len, "%skc=0x", + buf_len ? "," : ""); + for (i = 0; (uint32_t)i < cnt; i++) { + buf_len = strlen((char *)buf); + snprintf((char *)&buf[buf_len], sizeof(buf)-buf_len, + "%02x", opt_val[ofs + i]); + } + ofs += cnt; + } + if (opt_val[0] & 0x08) { + /* kid */ + cnt = opt_len - ofs; + if (cnt > opt_len - ofs) + goto no_more; + buf_len = strlen((char *)buf); + snprintf((char *)&buf[buf_len], sizeof(buf)-buf_len, "%skid=0x", + buf_len ? "," : ""); + for (i = 0; (uint32_t)i < cnt; i++) { + buf_len = strlen((char *)buf); + snprintf((char *)&buf[buf_len], sizeof(buf)-buf_len, + "%02x", opt_val[ofs + i]); + } + } + } +no_more: + buf_len = strlen((char *)buf); + is_oscore_payload = 1; + break; + case COAP_OPTION_URI_PORT: case COAP_OPTION_MAXAGE: case COAP_OPTION_OBSERVE: @@ -634,6 +702,9 @@ coap_show_pdu(coap_log_t level, const coap_pdu_t *pdu) { case COAP_OPTION_IF_MATCH: case COAP_OPTION_ETAG: + case COAP_OPTION_NORESPONSE: + case COAP_OPTION_ECHO: + case COAP_OPTION_RTAG: opt_len = coap_opt_length(option); opt_val = coap_opt_value(option); snprintf((char *)buf, sizeof(buf), "0x"); @@ -677,7 +748,7 @@ coap_show_pdu(coap_log_t level, const coap_pdu_t *pdu) { outbuflen = strlen(outbuf); snprintf(&outbuf[outbuflen], sizeof(outbuf)-outbuflen, " :: "); - if (is_binary(content_format) || !isprint(data[0])) { + if (is_binary(content_format) || !isprint(data[0]) || is_oscore_payload) { size_t keep_data_len = data_len; const uint8_t *keep_data = data; @@ -856,7 +927,7 @@ char *coap_string_tls_support(char *buffer, size_t bufsize) if (have_dtls == 0 && have_tls == 0) { snprintf(buffer, bufsize, "(No DTLS or TLS support)"); return buffer; - } + } switch (tls_version->type) { case COAP_TLS_LIBRARY_NOTLS: snprintf(buffer, bufsize, "(No DTLS or TLS support)"); diff --git a/src/coap_gnutls.c b/src/coap_gnutls.c index 97b4906988..bd6d845795 100644 --- a/src/coap_gnutls.c +++ b/src/coap_gnutls.c @@ -2938,6 +2938,233 @@ coap_digest_final(coap_digest_ctx_t *digest_ctx, } #endif /* COAP_SERVER_SUPPORT */ +#if defined(HAVE_OSCORE) + +int +coap_oscore_is_supported(void) { + return 1; +} + +/* + * The struct cipher_algs and the function get_cipher_alg() are used to + * determine which cipher type to use for creating the required cipher + * suite object. + */ +static struct cipher_algs { + coap_cose_alg_t alg; + gnutls_cipher_algorithm_t cipher_type; +} ciphers[] = { + { COAP_COSE_ALG_AES_CCM_16_64_128, GNUTLS_CIPHER_AES_128_CCM_8 }, + { COAP_COSE_ALG_AES_CCM_16_64_256, GNUTLS_CIPHER_AES_256_CCM_8 } +}; + +static gnutls_cipher_algorithm_t +get_cipher_alg(coap_cose_alg_t alg) { + size_t idx; + + for (idx = 0; idx < sizeof(ciphers)/sizeof(struct cipher_algs); idx++) { + if (ciphers[idx].alg == alg) + return ciphers[idx].cipher_type; + } + coap_log(LOG_DEBUG, "get_cipher_alg: COSE cipher %d not supported\n", alg); + return 0; +} + +/* + * The struct hmac_algs and the function get_hmac_alg() are used to + * determine which hmac type to use for creating the required hmac + * suite object. + */ +static struct hmac_algs { + coap_cose_alg_t alg; + gnutls_mac_algorithm_t hmac_type; +} hmacs[] = { + { COAP_COSE_ALG_HMAC256_256, GNUTLS_MAC_SHA256 }, +}; + +static gnutls_mac_algorithm_t +get_hmac_alg(coap_cose_alg_t alg) { + size_t idx; + + for (idx = 0; idx < sizeof(hmacs)/sizeof(struct hmac_algs); idx++) { + if (hmacs[idx].alg == alg) + return hmacs[idx].hmac_type; + } + coap_log(LOG_DEBUG, "get_hmac_alg: COSE hkdf %d not supported\n", alg); + return 0; +} + +int +coap_crypto_check_cipher_alg(coap_cose_alg_t alg) { + return get_cipher_alg(alg); +} + +int +coap_crypto_check_hkdf_alg(coap_cose_alg_t alg) { + return get_hmac_alg(alg); +} + +int +coap_crypto_aead_encrypt(const coap_crypto_param_t *params, + coap_bin_const_t *data, + coap_bin_const_t *aad, + uint8_t *result, size_t *max_result_len) { + gnutls_aead_cipher_hd_t ctx; + gnutls_datum_t key; + const coap_crypto_aes_ccm_t *ccm; + int ret = 0; + size_t result_len = *max_result_len; + gnutls_cipher_algorithm_t algo; + unsigned tag_size; + uint8_t *key_data_rw; + coap_bin_const_t laad; + + if (data == NULL) + return 0; + + assert(params != NULL); + if (!params) { + return 0; + } + if ((algo = get_cipher_alg(params->alg)) == 0) { + coap_log(LOG_DEBUG, + "coap_crypto_encrypt: algorithm %d not supported\n", + params->alg); + return 0; + } + tag_size = gnutls_cipher_get_tag_size(algo); + ccm = ¶ms->params.aes; + + /* Get a RW copy of data */ + memcpy(&key_data_rw, &ccm->key.s, sizeof(key_data_rw)); + key.data = key_data_rw; + key.size = ccm->key.length; + + if (aad) { + laad = *aad; + } + else { + laad.s = NULL; + laad.length = 0; + } + + G_CHECK(gnutls_aead_cipher_init(&ctx, algo, &key), "gnutls_aead_cipher_init"); + + G_CHECK(gnutls_aead_cipher_encrypt(ctx, + ccm->nonce, 15 - ccm->l, /* iv */ + laad.s, laad.length, /* ad */ + tag_size, + data->s, data->length, /* input */ + result, &result_len), /* output */ + "gnutls_aead_cipher_encrypt"); + *max_result_len = result_len; + ret = 1; + fail: + gnutls_aead_cipher_deinit(ctx); + return ret; +} + +int +coap_crypto_aead_decrypt(const coap_crypto_param_t *params, + coap_bin_const_t *data, + coap_bin_const_t *aad, + uint8_t *result, size_t *max_result_len) { + gnutls_aead_cipher_hd_t ctx; + gnutls_datum_t key; + const coap_crypto_aes_ccm_t *ccm; + int ret = 0; + size_t result_len = *max_result_len; + gnutls_cipher_algorithm_t algo; + unsigned tag_size; + uint8_t *key_data_rw; + coap_bin_const_t laad; + + if (data == NULL) + return 0; + + assert(params != NULL); + + if (!params) { + return 0; + } + if ((algo = get_cipher_alg(params->alg)) == 0) { + coap_log(LOG_DEBUG, + "coap_crypto_decrypt: algorithm %d not supported\n", + params->alg); + return 0; + } + tag_size = gnutls_cipher_get_tag_size(algo); + ccm = ¶ms->params.aes; + + /* Get a RW copy of data */ + memcpy(&key_data_rw, &ccm->key.s, sizeof(key_data_rw)); + key.data = key_data_rw; + key.size = ccm->key.length; + + if (aad) { + laad = *aad; + } + else { + laad.s = NULL; + laad.length = 0; + } + + G_CHECK(gnutls_aead_cipher_init(&ctx, algo, &key), "gnutls_aead_cipher_init"); + + G_CHECK(gnutls_aead_cipher_decrypt(ctx, + ccm->nonce, 15 - ccm->l, /* iv */ + laad.s, laad.length, /* ad */ + tag_size, + data->s, data->length, /* input */ + result, &result_len), /* output */ + "gnutls_aead_cipher_encrypt"); + *max_result_len = result_len; + ret = 1; + fail: + gnutls_aead_cipher_deinit(ctx); + return ret == 1 ? 1 : 0; +} + +int +coap_crypto_hmac(const coap_crypto_param_t *params, + coap_bin_const_t *data, + uint8_t *result, size_t *max_result_len) { + gnutls_hmac_hd_t ctx; + int ret = 0; + unsigned len; + gnutls_mac_algorithm_t mac_algo; + + if (data == NULL) + return 0; + + if ((mac_algo = get_hmac_alg(params->alg)) == 0) { + coap_log(LOG_DEBUG, + "coap_crypto_hmac: algorithm %d not supported\n", + params->alg); + return 0; + } + len = gnutls_hmac_get_len(mac_algo); + if (*max_result_len < len) { + coap_log(LOG_ERR, "coap_hmac: output buffer too small (%zu < %u\n", + *max_result_len, len); + return 0; + } + + G_CHECK(gnutls_hmac_init(&ctx, mac_algo, + params->params.key.s, params->params.key.length), + "gnutls_hmac_init"); + G_CHECK(gnutls_hmac(ctx, data->s, data->length), "gnutls_hmac"); + gnutls_hmac_output(ctx, result); + + *max_result_len = len; + ret = 1; +fail: + gnutls_hmac_deinit(ctx, NULL); + return ret; +} + +#endif /* HAVE_OSCORE */ + #else /* !HAVE_LIBGNUTLS */ #ifdef __clang__ diff --git a/src/coap_mbedtls.c b/src/coap_mbedtls.c index d3dfa72024..8254e7488f 100644 --- a/src/coap_mbedtls.c +++ b/src/coap_mbedtls.c @@ -2402,6 +2402,311 @@ coap_digest_final(coap_digest_ctx_t *digest_ctx, } #endif /* COAP_SERVER_SUPPORT */ +#if defined(HAVE_OSCORE) + +int +coap_oscore_is_supported(void) { + return 1; +} + +#include +#include + +#ifndef MBEDTLS_CIPHER_MODE_AEAD +#error need MBEDTLS_CIPHER_MODE_AEAD, please enable MBEDTLS_CCM_C +#endif /* MBEDTLS_CIPHER_MODE_AEAD */ + +#ifdef MBEDTLS_ERROR_C +#include +#endif /* MBEDTLS_ERROR_C */ + +/* + * The struct cipher_algs and the function get_cipher_alg() are used to + * determine which cipher type to use for creating the required cipher + * suite object. + */ +static struct cipher_algs { + coap_cose_alg_t alg; + mbedtls_cipher_type_t cipher_type; +} ciphers[] = { + { COAP_COSE_ALG_AES_CCM_16_64_128, MBEDTLS_CIPHER_AES_128_CCM }, + { COAP_COSE_ALG_AES_CCM_16_64_256, MBEDTLS_CIPHER_AES_256_CCM } +}; + +static mbedtls_cipher_type_t +get_cipher_alg(coap_cose_alg_t alg) { + size_t idx; + + for (idx = 0; idx < sizeof(ciphers)/sizeof(struct cipher_algs); idx++) { + if (ciphers[idx].alg == alg) + return ciphers[idx].cipher_type; + } + coap_log(LOG_DEBUG, "get_cipher_alg: COSE cipher %d not supported\n", alg); + return 0; +} + +/* + * The struct hmac_algs and the function get_hmac_alg() are used to + * determine which hmac type to use for creating the required hmac + * suite object. + */ +static struct hmac_algs { + coap_cose_alg_t alg; + mbedtls_md_type_t hmac_type; +} hmacs[] = { + { COAP_COSE_ALG_HMAC256_256, MBEDTLS_MD_SHA256 }, +}; + +static mbedtls_md_type_t +get_hmac_alg(coap_cose_alg_t alg) { + size_t idx; + + for (idx = 0; idx < sizeof(hmacs)/sizeof(struct hmac_algs); idx++) { + if (hmacs[idx].alg == alg) + return hmacs[idx].hmac_type; + } + coap_log(LOG_DEBUG, "get_hmac_alg: COSE hkdf %d not supported\n", alg); + return 0; +} + +int +coap_crypto_check_cipher_alg(coap_cose_alg_t alg) { + return get_cipher_alg(alg); +} + +int +coap_crypto_check_hkdf_alg(coap_cose_alg_t alg) { + return get_hmac_alg(alg); +} + +#ifdef MBEDTLS_ERROR_C +#define C(Func) do { \ + int c_tmp = (int)(Func); \ + if (c_tmp != 0) { \ + char error_buf[32]; \ + mbedtls_strerror(c_tmp, error_buf, sizeof(error_buf)); \ + coap_log(LOG_ERR, "mbedtls: %s\n", error_buf); \ + goto error; \ + } \ +} while(0); +#else /* !MBEDTLS_ERROR_C */ +#define C(Func) do { \ + int c_tmp = (int)(Func); \ + if (c_tmp != 0) { \ + coap_log(LOG_ERR, "mbedtls: %d\n", tmp); \ + goto error; \ + } \ +} while(0); +#endif /* !MBEDTLS_ERROR_C */ + +/** + * Initializes the cipher context @p ctx. On success, this function + * returns true and @p ctx must be released by the caller using + * mbedtls_ciper_free(). */ +static int +setup_cipher_context(mbedtls_cipher_context_t *ctx, + coap_cose_alg_t coap_alg, + const uint8_t *key_data, size_t key_length, + mbedtls_operation_t mode) { + const mbedtls_cipher_info_t *cipher_info; + mbedtls_cipher_type_t cipher_type; + uint8_t key[COAP_CRYPTO_MAX_KEY_SIZE]; /* buffer for normalizing the key according to its key length */ + int klen; + memset(key, 0, sizeof(key)); + + if ((cipher_type = get_cipher_alg(coap_alg)) == 0) { + coap_log(LOG_DEBUG, + "coap_crypto_encrypt: algorithm %d not supported\n", + coap_alg); + return 0; + } + cipher_info = mbedtls_cipher_info_from_type(cipher_type); + if (!cipher_info) { + coap_log(LOG_CRIT, "coap_crypto_encrypt: cannot get cipher info\n"); + return 0; + } + + mbedtls_cipher_init(ctx); + + C(mbedtls_cipher_setup(ctx, cipher_info)); + klen = mbedtls_cipher_get_key_bitlen(ctx); + if ((klen > (int)(sizeof(key) * 8)) || (key_length > sizeof(key))) { + coap_log(LOG_CRIT, "coap_crypto: cannot set key\n"); + goto error; + } + memcpy(key, key_data, key_length); + C(mbedtls_cipher_setkey(ctx, key, klen, mode)); + + /* On success, the cipher context is released by the caller. */ + return 1; +error: + mbedtls_cipher_free(ctx); + return 0; +} + +int +coap_crypto_aead_encrypt(const coap_crypto_param_t *params, + coap_bin_const_t *data, + coap_bin_const_t *aad, + uint8_t *result, size_t *max_result_len) { + mbedtls_cipher_context_t ctx; + const coap_crypto_aes_ccm_t *ccm; + unsigned char tag[16]; + int ret = 0; + size_t result_len = *max_result_len; + coap_bin_const_t laad; + + if (data == NULL) + return 0; + + assert(params != NULL); + + if (!params) { + return 0; + } + ccm = ¶ms->params.aes; + + if (!setup_cipher_context(&ctx, params->alg, ccm->key.s, ccm->key.length, + MBEDTLS_ENCRYPT)) { + return 0; + } + + if (aad) { + laad = *aad; + } + else { + laad.s = NULL; + laad.length = 0; + } + + C(mbedtls_cipher_auth_encrypt(&ctx, + ccm->nonce, 15 - ccm->l, /* iv */ + laad.s, laad.length, /* ad */ + data->s, data->length, /* input */ + result, &result_len, /* output */ + tag, ccm->tag_len /* tag */ + )); + + /* check if buffer is sufficient to hold tag */ + if ((result_len + ccm->tag_len) > *max_result_len) { + coap_log(LOG_ERR, "coap_encrypt: buffer too small\n"); + goto error; + } + /* append tag to result */ + memcpy(result + result_len, tag, ccm->tag_len); + *max_result_len = result_len + ccm->tag_len; + ret = 1; + error: + mbedtls_cipher_free(&ctx); + return ret; +} + +int +coap_crypto_aead_decrypt(const coap_crypto_param_t *params, + coap_bin_const_t *data, + coap_bin_const_t *aad, + uint8_t *result, size_t *max_result_len) { + mbedtls_cipher_context_t ctx; + const coap_crypto_aes_ccm_t *ccm; + const unsigned char *tag; + int ret = 0; + size_t result_len = *max_result_len; + coap_bin_const_t laad; + + if (data == NULL) + return 0; + + assert(params != NULL); + + if (!params) { + return 0; + } + + ccm = ¶ms->params.aes; + + if (!setup_cipher_context(&ctx, params->alg, ccm->key.s, ccm->key.length, + MBEDTLS_DECRYPT)) { + return 0; + } + + if (data->length < ccm->tag_len) { + coap_log(LOG_ERR, "coap_decrypt: invalid tag length\n"); + goto error; + } + + if (aad) { + laad = *aad; + } + else { + laad.s = NULL; + laad.length = 0; + } + + tag = data->s + data->length - ccm->tag_len; + C(mbedtls_cipher_auth_decrypt(&ctx, + ccm->nonce, 15 - ccm->l, /* iv */ + laad.s, laad.length, /* ad */ + data->s, data->length - ccm->tag_len, /* input */ + result, &result_len, /* output */ + tag, ccm->tag_len /* tag */ + )); + + *max_result_len = result_len; + ret = 1; + error: + mbedtls_cipher_free(&ctx); + return ret; +} + +int +coap_crypto_hmac(const coap_crypto_param_t *params, + coap_bin_const_t *data, + uint8_t *result, size_t *max_result_len) { + mbedtls_md_context_t ctx; + int ret = 0; + const int use_hmac = 1; + const mbedtls_md_info_t *md_info; + mbedtls_md_type_t mac_algo; + + if (data == NULL) + return 0; + + assert(params != NULL); + + if (!params) { + return 0; + } + + if ((mac_algo = get_hmac_alg(params->alg)) == 0) { + coap_log(LOG_DEBUG, + "coap_crypto_hmac: algorithm %d not supported\n", + params->alg); + return 0; + } + md_info = mbedtls_md_info_from_type(mac_algo); + + if (*max_result_len < (size_t)mbedtls_md_get_size(md_info)) { + coap_log(LOG_ERR, "coap_hmac: output buffer too small\n"); + return 0; + } + + mbedtls_md_init(&ctx); + C(mbedtls_md_setup(&ctx, md_info, use_hmac)); + + C(mbedtls_md_hmac_starts(&ctx, params->params.key.s, + params->params.key.length)); + C(mbedtls_md_hmac_update(&ctx, (const unsigned char *)data->s, data->length)); + C(mbedtls_md_hmac_finish(&ctx, result)); + + *max_result_len = (size_t)mbedtls_md_get_size(md_info); + ret = 1; + error: + mbedtls_md_free(&ctx); + return ret; +} + +#endif /* HAVE_OSCORE */ + #else /* !HAVE_MBEDTLS */ #ifdef __clang__ diff --git a/src/coap_notls.c b/src/coap_notls.c index 1ed0b02f1e..e21c8cd031 100644 --- a/src/coap_notls.c +++ b/src/coap_notls.c @@ -250,6 +250,117 @@ coap_digest_final(coap_digest_ctx_t *digest_ctx, } #endif /* COAP_SERVER_SUPPORT */ +#if defined(HAVE_OSCORE) + +int +coap_oscore_is_supported(void) { + return 0; +} + +/* + * These are currently just stub functions as no crypto support is + * provided. + * TODO Add in RIOT OS support etc. + */ + +/* + * The struct cipher_algs and the function get_cipher_alg() are used to + * determine which cipher type to use for creating the required cipher + * suite object. + */ +static struct cipher_algs { + coap_cose_alg_t alg; + u_int cipher_type; +} ciphers[] = { + { COAP_COSE_ALG_AES_CCM_16_64_128, 1 }, +}; + +static u_int +get_cipher_alg(coap_cose_alg_t alg) { + size_t idx; + + for (idx = 0; idx < sizeof(ciphers)/sizeof(struct cipher_algs); idx++) { + if (ciphers[idx].alg == alg) + return ciphers[idx].cipher_type; + } + coap_log(LOG_DEBUG, "get_cipher_alg: COSE cipher %d not supported\n", alg); + return 0; +} + +/* + * The struct hmac_algs and the function get_hmac_alg() are used to + * determine which hmac type to use for creating the required hmac + * suite object. + */ +static struct hmac_algs { + coap_cose_alg_t alg; + u_int hmac_type; +} hmacs[] = { + { COAP_COSE_ALG_HMAC256_256, 1 }, +}; + +static u_int +get_hmac_alg(coap_cose_alg_t alg) { + size_t idx; + + for (idx = 0; idx < sizeof(hmacs)/sizeof(struct hmac_algs); idx++) { + if (hmacs[idx].alg == alg) + return hmacs[idx].hmac_type; + } + coap_log(LOG_DEBUG, "get_hmac_alg: COSE hkdf %d not supported\n", alg); + return 0; +} + +int +coap_crypto_check_cipher_alg(coap_cose_alg_t alg) { + return 0; + return get_cipher_alg(alg); +} + +int +coap_crypto_check_hkdf_alg(coap_cose_alg_t alg) { + return 0; + return get_hmac_alg(alg); +} + +int +coap_crypto_aead_encrypt(const coap_crypto_param_t *params, + coap_bin_const_t *data, + coap_bin_const_t *aad, + uint8_t *result, size_t *max_result_len) { + (void)params; + (void)data; + (void)aad; + (void)result; + *max_result_len = 0; + return 0; +} + +int +coap_crypto_aead_decrypt(const coap_crypto_param_t *params, + coap_bin_const_t *data, + coap_bin_const_t *aad, + uint8_t *result, size_t *max_result_len) { + (void)params; + (void)data; + (void)aad; + (void)result; + *max_result_len = 0; + return 0; +} + +int +coap_crypto_hmac(const coap_crypto_param_t *params, + coap_bin_const_t *data, + uint8_t *result, size_t *max_result_len) { + (void)params; + (void)data; + (void)result; + *max_result_len = 0; + return 0; +} +#endif /* HAVE_OSCORE */ + #else /* !HAVE_LIBTINYDTLS && !HAVE_OPENSSL && !HAVE_LIBGNUTLS */ #ifdef __clang__ diff --git a/src/coap_openssl.c b/src/coap_openssl.c index e9296d8c59..1746760269 100644 --- a/src/coap_openssl.c +++ b/src/coap_openssl.c @@ -491,7 +491,7 @@ coap_dtls_psk_client_callback( } else { /* Reduce to match */ - max_identity_len = psk_identity->length; + max_identity_len = (unsigned int)psk_identity->length; } memcpy(identity, psk_identity->s, max_identity_len); identity[max_identity_len] = '\000'; @@ -503,7 +503,7 @@ coap_dtls_psk_client_callback( } else { /* Reduce to match */ - max_psk_len = psk_key->length; + max_psk_len = (unsigned int)psk_key->length; } memcpy(psk, psk_key->s, max_psk_len); return max_psk_len; @@ -558,7 +558,7 @@ coap_dtls_psk_server_callback( } else { /* Reduce to match */ - max_psk_len = psk_key->length; + max_psk_len = (unsigned int)psk_key->length; } memcpy(psk, psk_key->s, max_psk_len); return max_psk_len; @@ -2806,11 +2806,14 @@ void * coap_dtls_new_server_session(coap_session_t *session) { /* hint may get updated if/when handling SNI callback */ psk_hint = coap_get_session_server_psk_hint(session); if (psk_hint != NULL && psk_hint->length) { - char hint[psk_hint->length+1]; + char* hint = OPENSSL_malloc(psk_hint->length + 1); - memcpy (hint, psk_hint->s, psk_hint->length); - hint[psk_hint->length] = '\000'; - SSL_use_psk_identity_hint(ssl, hint); + if (hint) { + memcpy(hint, psk_hint->s, psk_hint->length); + hint[psk_hint->length] = '\000'; + SSL_use_psk_identity_hint(ssl, hint); + OPENSSL_free(hint); + } } r = SSL_accept(ssl); @@ -3280,11 +3283,14 @@ void *coap_tls_new_server_session(coap_session_t *session, int *connected) { psk_hint = coap_get_session_server_psk_hint(session); if (psk_hint != NULL && psk_hint->length) { - char hint[psk_hint->length+1]; + char* hint = OPENSSL_malloc(psk_hint->length + 1); - memcpy (hint, psk_hint->s, psk_hint->length); - hint[psk_hint->length] = '\000'; - SSL_use_psk_identity_hint(ssl, hint); + if (hint) { + memcpy(hint, psk_hint->s, psk_hint->length); + hint[psk_hint->length] = '\000'; + SSL_use_psk_identity_hint(ssl, hint); + OPENSSL_free(hint); + } } r = SSL_accept(ssl); @@ -3497,6 +3503,444 @@ coap_digest_final(coap_digest_ctx_t *digest_ctx, } #endif /* COAP_SERVER_SUPPORT */ +#if defined(HAVE_OSCORE) + +int +coap_oscore_is_supported(void) { + return 1; +} + +#include +#include + +/* + * The struct cipher_algs and the function get_cipher_alg() are used to + * determine which cipher type to use for creating the required cipher + * suite object. + */ +static struct cipher_algs { + coap_cose_alg_t alg; + const EVP_CIPHER *(*get_cipher)(void); +} ciphers[] = { + { COAP_COSE_ALG_AES_CCM_16_64_128, EVP_aes_128_ccm }, + { COAP_COSE_ALG_AES_CCM_16_64_256, EVP_aes_256_ccm } +}; + +static const EVP_CIPHER * +get_cipher_alg(coap_cose_alg_t alg) { + size_t idx; + + for (idx = 0; idx < sizeof(ciphers)/sizeof(struct cipher_algs); idx++) { + if (ciphers[idx].alg == alg) + return ciphers[idx].get_cipher(); + } + coap_log(LOG_DEBUG, "get_cipher_alg: COSE cipher %d not supported\n", alg); + return NULL; +} + +/* + * The struct hmac_algs and the function get_hmac_alg() are used to + * determine which hmac type to use for creating the required hmac + * suite object. + */ +static struct hmac_algs { + coap_cose_alg_t alg; + const EVP_MD *(*get_hmac)(void); +} hmacs[] = { + { COAP_COSE_ALG_HMAC256_256, EVP_sha256 }, +}; + +static const EVP_MD * +get_hmac_alg(coap_cose_alg_t alg) { + size_t idx; + + for (idx = 0; idx < sizeof(hmacs)/sizeof(struct hmac_algs); idx++) { + if (hmacs[idx].alg == alg) + return hmacs[idx].get_hmac(); + } + coap_log(LOG_DEBUG, "get_hmac_alg: COSE hkdf %d not supported\n", alg); + return NULL; +} + +int +coap_crypto_check_cipher_alg(coap_cose_alg_t alg) { + return get_cipher_alg(alg) != NULL; +} + +int +coap_crypto_check_hkdf_alg(coap_cose_alg_t alg) { + return get_hmac_alg(alg) != NULL; +} + +#define C(Func) if (1 != (Func)) { goto error; } + +static void +coap_crypto_output_errors(const char *prefix) +{ + unsigned long e; + + while ((e = ERR_get_error())) + coap_log(LOG_WARNING, "%s: %s at %s:%s\n", + prefix, + ERR_reason_error_string(e), + ERR_lib_error_string(e), ERR_func_error_string(e)); +} + +int +coap_crypto_aead_encrypt(const coap_crypto_param_t *params, + coap_bin_const_t *data, + coap_bin_const_t *aad, + uint8_t *result, size_t *max_result_len) { + const EVP_CIPHER *cipher; + const coap_crypto_aes_ccm_t *ccm; + int tmp; + int result_len = (int)(*max_result_len & INT_MAX); + + if (data == NULL) + return 0; + + assert(params != NULL); + if (!params || ((cipher = get_cipher_alg(params->alg)) == NULL)) { + return 0; + } + + /* TODO: set evp_md depending on params->alg */ + ccm = ¶ms->params.aes; + + EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); + + /* EVP_CIPHER_CTX_init(ctx); */ + C(EVP_EncryptInit_ex(ctx, cipher, NULL, NULL, NULL)); + C(EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_CCM_SET_L, (int)ccm->l, NULL)); + C(EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_IVLEN, (int)(15 - ccm->l), + NULL)); + C(EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_TAG, (int)ccm->tag_len, NULL)); + C(EVP_EncryptInit_ex(ctx, NULL, NULL, ccm->key.s, ccm->nonce)); + /* C(EVP_CIPHER_CTX_set_padding(ctx, 0)); */ + + C(EVP_EncryptUpdate(ctx, NULL, &result_len, NULL, (int)data->length)); + if (aad && aad->s && (aad->length > 0)) { + C(EVP_EncryptUpdate(ctx, NULL, &result_len, aad->s, (int)aad->length)); + } + C(EVP_EncryptUpdate(ctx, result, &result_len, data->s, (int)data->length)); + /* C(EVP_EncryptFinal_ex(ctx, result + result_len, &tmp)); */ + tmp = result_len; + C(EVP_EncryptFinal_ex(ctx, result + result_len, &tmp)); + result_len += tmp; + + /* retrieve the tag */ + C(EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_CCM_GET_TAG, (int)ccm->tag_len, + result + result_len)); + + *max_result_len = result_len + ccm->tag_len; + EVP_CIPHER_CTX_free(ctx); + return 1; + +error: + coap_crypto_output_errors("coap_crypto_aead_encrypt"); + return 0; +} + +int +coap_crypto_aead_decrypt(const coap_crypto_param_t *params, + coap_bin_const_t *data, + coap_bin_const_t *aad, + uint8_t *result, size_t *max_result_len) { + const EVP_CIPHER *cipher; + const coap_crypto_aes_ccm_t *ccm; + int tmp; + int len; + const uint8_t *tag; + uint8_t *rwtag; + + if (data == NULL) + return 0; + + assert(params != NULL); + if (!params || ((cipher = get_cipher_alg(params->alg)) == NULL)) { + return 0; + } + + ccm = ¶ms->params.aes; + + if (data->length < ccm->tag_len) { + return 0; + } else { + tag = data->s + data->length - ccm->tag_len; + data->length -= ccm->tag_len; + /* Kludge to stop compiler warning */ + memcpy(&rwtag, &tag, sizeof(rwtag)); + } + + EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); + + C(EVP_DecryptInit_ex(ctx, cipher, NULL, NULL, NULL)); + C(EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_IVLEN, (int)(15 - ccm->l), + NULL)); + C(EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_TAG, (int)ccm->tag_len, rwtag)); + C(EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_CCM_SET_L, (int)ccm->l, NULL)); + /* C(EVP_CIPHER_CTX_set_padding(ctx, 0)); */ + C(EVP_DecryptInit_ex(ctx, NULL, NULL, ccm->key.s, ccm->nonce)); + + C(EVP_DecryptUpdate(ctx, NULL, &len, NULL, (int)data->length)); + if (aad && aad->s && (aad->length > 0)) { + C(EVP_DecryptUpdate(ctx, NULL, &len, aad->s, (int)aad->length)); + } + tmp = EVP_DecryptUpdate(ctx, result, &len, data->s, (int)data->length); + EVP_CIPHER_CTX_free(ctx); + if (tmp <= 0) { + *max_result_len = 0; + return 0; + } + *max_result_len = len; + return 1; + +error: + coap_crypto_output_errors("coap_crypto_aead_decrypt"); + return 0; +} + +int +coap_crypto_hmac(const coap_crypto_param_t *params, + coap_bin_const_t *data, + uint8_t *result, size_t *max_result_len) { + unsigned int result_len; + const EVP_MD *evp_md; + + assert(params); + assert(data); + assert(result); + assert(max_result_len); + + if ((evp_md = get_hmac_alg(params->alg)) == 0) { + coap_log(LOG_DEBUG, + "coap_crypto_hmac: algorithm %d not supported\n", + params->alg); + return 0; + } + result_len = (unsigned int)*max_result_len; + if (HMAC(evp_md, params->params.key.s, + (int)params->params.key.length, data->s, (int)data->length, result, + &result_len)) { + *max_result_len = result_len; + return 1; + } + + coap_crypto_output_errors("coap_crypto_hmac"); + return 0; +} + +#if defined(HAVE_OSCORE_GROUP) + +#if OPENSSL_VERSION_NUMBER >= 0x10101000L + +int +coap_crypto_read_pem_private_key(const char *filename, uint8_t *priv, + size_t *len) +{ + BIO *in; + char *rw_var = NULL; + EVP_PKEY *priv_key = NULL; + + in = BIO_new(BIO_s_file()); + if (in == NULL) + goto error; + + /* Need to do this to not get a compiler warning about const parameters */ + memcpy(&rw_var, &filename, sizeof (rw_var)); + if (BIO_read_filename(in, rw_var) != 1) { + goto error; + } + + if ((priv_key = PEM_read_bio_PrivateKey(in, NULL, NULL, NULL)) == NULL) + goto error; + + if (EVP_PKEY_get_raw_private_key(priv_key, priv, len) != 1) + goto error; + + EVP_PKEY_free(priv_key); + BIO_free(in); + return 1; + +error: + if (in) + BIO_free(in); + + coap_crypto_output_errors("coap_crypto_read_pem_private_key"); + return 0; +} + +int +coap_crypto_read_pem_public_key(const char *filename, uint8_t *pub, + size_t *len) +{ + BIO *in; + char *rw_var = NULL; + EVP_PKEY *pub_key = NULL; + + in = BIO_new(BIO_s_file()); + if (in == NULL) + goto error; + + /* Need to do this to not get a compiler warning about const parameters */ + memcpy(&rw_var, &filename, sizeof (rw_var)); + if (BIO_read_filename(in, rw_var) != 1) { + goto error; + } + + if ((pub_key = PEM_read_bio_PUBKEY(in, NULL, NULL, NULL)) == NULL) { + + if (BIO_reset(in) != 0) + goto error; + + if ((pub_key = PEM_read_bio_PrivateKey(in, NULL, NULL, NULL)) == NULL) + goto error; + } + + if (EVP_PKEY_get_raw_public_key(pub_key, pub, len) != 1) + goto error; + + EVP_PKEY_free(pub_key); + BIO_free(in); + return 1; + +error: + if (in) + BIO_free(in); + + coap_crypto_output_errors("coap_crypto_read_pem_private_key"); + return 0; +} + +int +coap_crypto_ed25519_sign(coap_binary_t *signature, + coap_bin_const_t *ciphertext, + coap_bin_const_t *private_key, + coap_bin_const_t *public_key) +{ + EVP_PKEY *ed_key = EVP_PKEY_new_raw_private_key(EVP_PKEY_ED25519, NULL, + private_key->s, + private_key->length); + unsigned char *sig = NULL; + EVP_MD_CTX *md_ctx = NULL; + + (void)public_key; + if (ed_key == NULL) + goto error; + + md_ctx = EVP_MD_CTX_new(); + if (EVP_DigestSignInit(md_ctx, NULL, NULL, NULL, ed_key) != 1) + goto error; + /* Calculate the requires size for the signature by passing a NULL buffer */ + if (EVP_DigestSign(md_ctx, NULL, &signature->length, ciphertext->s, + ciphertext->length) != 1) + goto error; + if ((sig = OPENSSL_zalloc(signature->length)) == NULL) + goto error; + + if (EVP_DigestSign(md_ctx, signature->s, &signature->length, ciphertext->s, + ciphertext->length) != 1) + goto error; + OPENSSL_free(sig); + EVP_MD_CTX_free(md_ctx); + EVP_PKEY_free(ed_key); + return 1; + +error: + coap_crypto_output_errors("coap_crypto_ed25519_sign"); + if (sig) + OPENSSL_free(sig); + if (md_ctx) + EVP_MD_CTX_free(md_ctx); + if (ed_key) + EVP_PKEY_free(ed_key); + return 0; +} + +int +coap_crypto_ed25519_verify(coap_binary_t *signature, + coap_bin_const_t *plaintext, + coap_bin_const_t *public_key) +{ + EVP_PKEY *ed_key = EVP_PKEY_new_raw_public_key(EVP_PKEY_ED25519, NULL, + public_key->s, + public_key->length); + unsigned char *sig = NULL; + EVP_MD_CTX *md_ctx = NULL; + + if (ed_key == NULL) + goto error; + + md_ctx = EVP_MD_CTX_new(); + if (EVP_DigestVerifyInit(md_ctx, NULL, NULL, NULL, ed_key) != 1) + goto error; + if (EVP_DigestVerify(md_ctx, signature->s, signature->length, plaintext->s, + plaintext->length) != 1) + goto error; + OPENSSL_free(sig); + EVP_MD_CTX_free(md_ctx); + EVP_PKEY_free(ed_key); + return 1; + +error: + coap_crypto_output_errors("coap_crypto_ed25519_verify"); + if (md_ctx) + EVP_MD_CTX_free(md_ctx); + if (ed_key) + EVP_PKEY_free(ed_key); + return 0; +} + +#else /* OPENSSL_VERSION_NUMBER < 0x10101000L */ + +int +coap_crypto_read_pem_private_key(const char *filename, uint8_t *priv, + size_t *len) +{ + (void)filename; + (void)priv; + (void)len; + return 0; +} + +int +coap_crypto_read_pem_public_key(const char *filename, uint8_t *pub, + size_t *len) +{ + (void)filename; + (void)pub; + (void)len; + return 0; +} + +int +coap_crypto_ed25519_sign(coap_binary_t *signature, + coap_bin_const_t *ciphertext, + coap_bin_const_t *private_key, + coap_bin_const_t *public_key) +{ + (void)signature; + (void)ciphertext; + (void)private_key; + (void)public_key; + return 0; +} + +int +coap_crypto_ed25519_verify(coap_binary_t *signature, + coap_bin_const_t *plaintext, + coap_bin_const_t *public_key) +{ + (void)signature; + (void)plaintext; + (void)public_key; + return 0; +} +#endif /* OPENSSL_VERSION_NUMBER < 0x10101000L */ + +#endif /* HAVE_OSCORE_GROUP */ +#endif /* HAVE_OSCORE */ + #else /* !HAVE_OPENSSL */ #ifdef __clang__ diff --git a/src/coap_oscore.c b/src/coap_oscore.c new file mode 100644 index 0000000000..bd21f11b85 --- /dev/null +++ b/src/coap_oscore.c @@ -0,0 +1,2074 @@ +/* + * coap_oscore.c -- Object Security for Constrained RESTful Environments + * (OSCORE) support for libcoap + * + * Copyright (C) 2019-2021 Olaf Bergmann + * Copyright (C) 2021 Jon Shallow + * + * SPDX-License-Identifier: BSD-2-Clause + * + * This file is part of the CoAP library libcoap. Please see README for terms + * of use. + */ + +/** + * @file coap_oscore.c + * @brief CoAP OSCORE handling + */ + +#include "coap3/coap_internal.h" + +#ifdef HAVE_OSCORE +#include "oscore/oscore.h" +#include "oscore/oscore_crypto.h" +#include + +#define AAD_BUF_LEN 120 /* length of aad_buffer */ + +#if COAP_CLIENT_SUPPORT +coap_session_t * +coap_new_client_session_oscore(coap_context_t *ctx, + const coap_address_t *local_if, + const coap_address_t *server, + coap_proto_t proto, + coap_oscore_context_t *oscore_context) +{ + coap_session_t *session = coap_new_client_session(ctx, local_if, server, + proto); + + if (!session) + return NULL; + + if (oscore_context) { + if (oscore_context->recipient_chain == NULL) { + coap_log(LOG_WARNING, + "OSCORE: Recipient ID must be defined for a client\n"); + coap_session_release(session); + return NULL; + } + session->recipient_ctx = oscore_context->recipient_chain; + session->oscore_encryption = 1; + } + return session; +} + +coap_session_t * +coap_new_client_session_oscore_psk(coap_context_t *ctx, + const coap_address_t *local_if, + const coap_address_t *server, + coap_proto_t proto, + coap_dtls_cpsk_t *psk_data, + coap_oscore_context_t *oscore_context) +{ + coap_session_t *session = coap_new_client_session_psk2(ctx, local_if, server, + proto, psk_data); + + if (!session) + return NULL; + + if (oscore_context) { + if (oscore_context->recipient_chain == NULL) { + coap_log(LOG_WARNING, + "OSCORE: Recipient ID must be defined for a client\n"); + coap_session_release(session); + return NULL; + } + session->recipient_ctx = oscore_context->recipient_chain; + session->oscore_encryption = 1; + } + return session; +} + +coap_session_t * +coap_new_client_session_oscore_pki(coap_context_t *ctx, + const coap_address_t *local_if, + const coap_address_t *server, + coap_proto_t proto, + coap_dtls_pki_t *pki_data, + coap_oscore_context_t *oscore_context) +{ + coap_session_t *session = coap_new_client_session_pki(ctx, local_if, server, + proto, pki_data); + + if (!session) + return NULL; + + if (oscore_context) { + if (oscore_context->recipient_chain == NULL) { + coap_log(LOG_WARNING, + "OSCORE: Recipient ID must be defined for a client\n"); + coap_session_release(session); + return NULL; + } + session->recipient_ctx = oscore_context->recipient_chain; + session->oscore_encryption = 1; + } + return session; +} +#endif /* ! COAP_CLIENT_SUPPORT */ + +static void +dump_cose(cose_encrypt0_t *cose, const char *message) +{ + if (coap_get_log_level() >= COAP_LOG_CIPHERS) { + coap_log(COAP_LOG_CIPHERS, "%s Cose information\n", message); + oscore_log_int_value(COAP_LOG_CIPHERS, "alg", cose->alg); + oscore_log_hex_value(COAP_LOG_CIPHERS, "key", &cose->key); + oscore_log_hex_value(COAP_LOG_CIPHERS, "partial_iv", &cose->partial_iv); + oscore_log_hex_value(COAP_LOG_CIPHERS, "key_id", &cose->key_id); + oscore_log_hex_value(COAP_LOG_CIPHERS, "kid_context", &cose->kid_context); + oscore_log_hex_value(COAP_LOG_CIPHERS, "oscore_option", &cose->oscore_option); + oscore_log_hex_value(COAP_LOG_CIPHERS, "nonce", &cose->nonce); + oscore_log_hex_value(COAP_LOG_CIPHERS, "aad", &cose->aad); +#ifdef HAVE_OSCORE_GROUP + oscore_log_int_value(COAP_LOG_CIPHERS, "group_flag", cose->group_flag); +#endif /* HAVE_OSCORE_GROUP */ + } +} + +#define MAX_IV_LEN 10 /* maximum length of iv buffer */ +/* + * Take current PDU, create a new one approriately separated as per RFC8613 + * and then encrypt / integrity check the OSCORE data + */ +coap_pdu_t * +coap_oscore_new_pdu_encrypted(coap_session_t *session, coap_pdu_t *pdu, + coap_bin_const_t *echo_value, int send_partial_iv) +{ + uint8_t coap_request = COAP_PDU_IS_REQUEST(pdu); + coap_pdu_code_t code = coap_request ? COAP_REQUEST_CODE_POST : + COAP_RESPONSE_CODE(204); + coap_pdu_t *osc_pdu; + coap_pdu_t *plain_pdu = NULL; + coap_bin_const_t pdu_token; + coap_opt_iterator_t opt_iter; + coap_opt_t *option; + uint8_t pdu_code = pdu->code; + size_t length; + const uint8_t *data; + uint8_t option_value_buffer[15]; + uint8_t *ciphertext_buffer = NULL; + size_t ciphertext_len = 0; + uint8_t aad_buffer[AAD_BUF_LEN]; + uint8_t nonce_buffer[13]; + coap_bin_const_t aad; + coap_bin_const_t nonce; + oscore_recipient_ctx_t *rcp_ctx = session->recipient_ctx; + oscore_ctx_t *osc_ctx = rcp_ctx ? rcp_ctx->common_ctx : NULL; + cose_encrypt0_t cose[1]; +#ifdef HAVE_OSCORE_GROUP + cose_sign1_t sign[1]; +#endif /* HAVE_OSCORE_GROUP */ + uint8_t group_flag = 0; + coap_uri_t uri; + int show_pdu = 0; + int doing_observe = 0; + uint32_t observe_value = 0; + oscore_association_t *association = NULL; + uint8_t partial_iv_buffer[MAX_IV_LEN]; + size_t partial_iv_len; + oscore_sender_ctx_t *snd_ctx = osc_ctx->sender_context; + uint8_t external_aad_buffer[100]; + size_t external_aad_len = 0; + uint8_t oscore_option[20]; + size_t oscore_option_len; + + if (osc_ctx == NULL) + return NULL; + + /* Check that OSCORE has not already been done */ + if (coap_check_option(pdu, COAP_OPTION_OSCORE, &opt_iter)) + return NULL; + + if (coap_check_option(pdu, COAP_OPTION_OBSERVE, &opt_iter)) + doing_observe = 1; + + coap_show_pdu(LOG_DEBUG, pdu); + osc_pdu = coap_pdu_init(pdu->type, code, pdu->mid, + coap_session_max_pdu_size(session)); + if (osc_pdu == NULL) + return NULL; + + cose_encrypt0_init(cose); /* clears cose memory */ + pdu_token = coap_pdu_get_token(pdu); + if (coap_request) { + /* + * RFC8613 8.1 Step 1. Protecting the client's request + * Get the Sender Context + */ + rcp_ctx = session->recipient_ctx; + if (rcp_ctx == NULL) + goto error; + osc_ctx = rcp_ctx->common_ctx; + snd_ctx = osc_ctx->sender_context; + } + else { + /* + * RFC8613 8.3 Step 1. Protecting the server's response + * Get the Sender Context + */ + association = oscore_find_association(session, &pdu_token); + if (association == NULL) + goto error; + + rcp_ctx = association->recipient_ctx; + osc_ctx = rcp_ctx->common_ctx; + snd_ctx = osc_ctx->sender_context; + cose_encrypt0_set_partial_iv(cose, association->partial_iv); + cose_encrypt0_set_aad(cose, association->aad); + } + +#ifdef HAVE_OSCORE_GROUP + cose_sign1_init(sign); /* clear sign memory */ + if (osc_ctx->mode == COAP_OSCORE_MODE_GROUP) + group_flag = 1; + + if (osc_ctx->mode != COAP_OSCORE_MODE_SINGLE && coap_request) + cose_encrypt0_set_alg(cose, osc_ctx->sign_enc_alg); + else +#endif /* HAVE_OSCORE_GROUP */ + cose_encrypt0_set_alg(cose, osc_ctx->aead_alg); + + if (coap_request || doing_observe || send_partial_iv) { + coap_bin_const_t partial_iv; + partial_iv_len = coap_encode_var_safe8(partial_iv_buffer, + sizeof(partial_iv_buffer), + snd_ctx->seq); + if (snd_ctx->seq == 0) { + /* Need to special case */ + partial_iv_buffer[0] = '\000'; + partial_iv_len = 1; + } + partial_iv.s = partial_iv_buffer; + partial_iv.length = partial_iv_len; + cose_encrypt0_set_partial_iv(cose, &partial_iv); + } + + cose_encrypt0_set_kid_context(cose, osc_ctx->id_context); + + cose_encrypt0_set_key_id(cose, snd_ctx->sender_id); + + /* nonce (needs to have sender information correctly set up) */ + + if (coap_request || doing_observe || send_partial_iv) { + /* + * 8.1 Step 3 or RFC8613 8.3.1 Step A + * Compose the AEAD nonce + * + * Requires in COSE object as appropriate + * key_id (kid) (sender) + * partial_iv (sender) + * common_iv (already in osc_ctx) + */ + nonce.s = nonce_buffer; + nonce.length = 13; + oscore_generate_nonce(cose, osc_ctx, nonce_buffer, 13); + cose_encrypt0_set_nonce(cose, &nonce); + if (!oscore_increment_sender_seq(osc_ctx)) + goto error; + if (osc_ctx->save_seq_num_func) { + if (osc_ctx->sender_context->seq > osc_ctx->sender_context->next_seq) { + /* Only update at ssn_freq rate */ + osc_ctx->sender_context->next_seq += osc_ctx->ssn_freq; + osc_ctx->save_seq_num_func(osc_ctx->sender_context->next_seq, + osc_ctx->save_seq_num_func_param); + } + } + } + else { + /* + * 8.3 Step 3. + * Use nonce from request + */ + cose_encrypt0_set_nonce(cose, association->nonce); + } + + /* OSCORE_option (needs to be before AAD as included in AAD if group) */ + + /* cose is modified for encode option in response message */ + if (!coap_request) { + /* no kid on response */ + cose_encrypt0_set_key_id(cose, NULL); + if (!doing_observe && !send_partial_iv) + cose_encrypt0_set_partial_iv(cose, NULL); + } + oscore_option_len = oscore_encode_option_value(oscore_option, cose, + group_flag); + if (!coap_request) { + /* Reset what was just unset as appropriate for AAD */ + cose_encrypt0_set_key_id(cose, rcp_ctx->recipient_id); + cose_encrypt0_set_partial_iv(cose, association->partial_iv); + } + + /* + * RFC8613 8.1/8.3 Step 2(a) (5.4). + * Compose the External AAD and then AAD + * + * OSCORE_option requires + * partial_iv (cose partial_iv) + * kid_context (cose kid_context) + * key_id (cose key_id) + * group_flag + * + * Non Group (based on osc_tx->mode) requires the following + * alg_aead (osc_ctx) + * request_kid (request key_id using cose) + * request_piv (request partial_iv using cose) + * options (none at present) + * Group (based on osc_tx->mode) requires the following + * alg_aead (osc_ctx) (pairwise mode) + * alg_signature_enc (osc_ctx) (group mode) + * alg_signature (osc_ctx) (group mode) + * alg_pairwise_key_agreement (osc_ctx) (pairwise mode) + * request_kid (request key_id using cose) + * request_piv (request partial_iv using cose) + * options (none at present) + * request_kid_context (osc_ctx id_context) + * OSCORE_option (parameter) + * sender_public_key (osc_ctx sender_context public_key) + * gm_public_key (osc_ctx gm_public_key) + * + * Note: No I options at present + */ + + + if (coap_request || osc_ctx->mode != COAP_OSCORE_MODE_SINGLE || + send_partial_iv) { + /* External AAD */ + external_aad_len = oscore_prepare_e_aad(osc_ctx, cose, +#ifdef HAVE_OSCORE_GROUP + oscore_option, + oscore_option_len, + osc_ctx->sender_context->public_key, +#else /* HAVE_OSCORE_GROUP */ + NULL, + 0, + NULL, +#endif /* HAVE_OSCORE_GROUP */ + external_aad_buffer, + sizeof(external_aad_buffer)); + + /* AAD */ + aad.s = aad_buffer; + aad.length = oscore_prepare_aad(external_aad_buffer, external_aad_len, + aad_buffer, sizeof(aad_buffer)); + assert(aad.length < AAD_BUF_LEN); + cose_encrypt0_set_aad(cose, &aad); + } + + /* + * RFC8613 8.1/8.3 Step 2(b) (5.3). + * + * Set up temp plaintext pdu, the data including token, options and + * optional payload will get encrypted as COSE ciphertext. + */ + plain_pdu = coap_pdu_init(pdu->type, pdu->code, pdu->mid, + coap_session_max_pdu_size(session)); + if (plain_pdu == NULL) + goto error; + + coap_add_token(osc_pdu, pdu_token.length, pdu_token.s); + + /* First byte of plain is real CoAP code. Pretend it is token */ + coap_add_token(plain_pdu, 1, &pdu_code); + + /* Copy across the Outer/Inner Options to respective PDUs */ + coap_option_iterator_init(pdu, &opt_iter, COAP_OPT_ALL); + while ((option = coap_option_next(&opt_iter))) { + switch (opt_iter.number) { + case COAP_OPTION_URI_HOST: + case COAP_OPTION_URI_PORT: + case COAP_OPTION_PROXY_SCHEME: + case COAP_OPTION_HOP_LIMIT: + /* Outer only */ + if (!coap_insert_option(osc_pdu, opt_iter.number, + coap_opt_length(option), + coap_opt_value(option))) + goto error; + break; + case COAP_OPTION_OBSERVE: + /* Make as Outer option as-is */ + if (!coap_insert_option(osc_pdu, opt_iter.number, + coap_opt_length(option), + coap_opt_value(option))) + goto error; + if (coap_request) { + /* Make as Inner option (unchanged) */ + if (!coap_insert_option(plain_pdu, opt_iter.number, + coap_opt_length(option), + coap_opt_value(option))) + goto error; + osc_pdu->code = COAP_REQUEST_CODE_FETCH; + } + else { + /* Make as Inner option but empty */ + if (!coap_insert_option(plain_pdu, opt_iter.number, + 0, NULL)) + goto error; + osc_pdu->code = COAP_RESPONSE_CODE(205); + } + show_pdu = 1; + doing_observe = 1; + observe_value = coap_decode_var_bytes(coap_opt_value(option), + coap_opt_length(option)); + break; + case COAP_OPTION_PROXY_URI: + /* Need to break down into the component parts RFC8613 4.1.3.3 */ + memset(&uri, 0, sizeof(uri)); + if (coap_split_proxy_uri(coap_opt_value(option), coap_opt_length(option), + &uri) < 0) { + coap_log(LOG_WARNING, "Proxy URI '%.*s' not decodable\n", + coap_opt_length(option), + (const char*)coap_opt_value(option)); + goto error; + } + /* Outer options */ + if (!coap_insert_option(osc_pdu, COAP_OPTION_URI_HOST, uri.host.length, + uri.host.s)) + goto error; + if (uri.port != (coap_uri_scheme_is_secure(&uri) ? COAPS_DEFAULT_PORT : + COAP_DEFAULT_PORT) && + !coap_insert_option(osc_pdu, COAP_OPTION_URI_PORT, + coap_encode_var_safe(option_value_buffer, + sizeof(option_value_buffer), + uri.port & 0xffff), + option_value_buffer)) + goto error; + if (uri.scheme >= COAP_URI_SCHEME_LAST || + !coap_insert_option(osc_pdu, COAP_OPTION_PROXY_SCHEME, + strlen(coap_uri_scheme[uri.scheme]), + (const uint8_t *)coap_uri_scheme[uri.scheme])) + goto error; + /* Inner options */ + if (uri.path.length) { + uint8_t *buf; + size_t buflen = uri.path.length+1; + int res; + + buf = coap_malloc(uri.path.length + 1); + if (buf) { + res = coap_split_path(uri.path.s, uri.path.length, buf, &buflen); + while (res--) { + if (!coap_insert_option(plain_pdu, COAP_OPTION_URI_PATH, + coap_opt_length(buf), + coap_opt_value(buf))) { + coap_free(buf); + goto error; + } + buf += coap_opt_size(buf); + } + } + coap_free(buf); + } + if (uri.query.length) { + uint8_t *buf; + size_t buflen = uri.query.length+1; + int res; + + buf = coap_malloc(uri.query.length + 1); + if (buf) { + res = coap_split_query(uri.query.s, uri.query.length, buf, &buflen); + while (res--) { + if (!coap_insert_option(plain_pdu, COAP_OPTION_URI_QUERY, + coap_opt_length(buf), + coap_opt_value(buf))) { + coap_free(buf); + goto error; + } + + buf += coap_opt_size(buf); + } + coap_free(buf); + } + } + show_pdu = 1; + break; + default: + /* Make as Inner option */ + if (!coap_insert_option(plain_pdu, opt_iter.number, + coap_opt_length(option), + coap_opt_value(option))) + goto error; + break; + } + } + if (echo_value) { + /* Add in Inner Echo option response */ + if (!coap_insert_option(plain_pdu, COAP_OPTION_ECHO, + echo_value->length, + echo_value->s)) + goto error; + show_pdu = 1; + } + /* Add in data to plain */ + if (coap_get_data(pdu, &length, &data)) { + if (!coap_add_data(plain_pdu, length, data)) + goto error; + } + if (show_pdu) { + coap_log(COAP_LOG_CIPHERS, "OSCORE payload\n"); + coap_show_pdu(COAP_LOG_CIPHERS, plain_pdu); + } + + /* + * 8.1/8.3 Step 4. + * Encrypt the COSE object. + * + * Requires in COSE object as appropriate + * alg (already set) + * key (sender key) + * nonce (already set) + * aad (already set) + * plaintext + */ + cose_encrypt0_set_key(cose, snd_ctx->sender_key); + cose_encrypt0_set_plaintext(cose, plain_pdu->token, plain_pdu->used_size); + dump_cose(cose, "Pre encrypt"); + ciphertext_buffer = coap_malloc_type(COAP_OSCORE_BUF, + OSCORE_CRYPTO_BUFFER_SIZE); + ciphertext_len = cose_encrypt0_encrypt(cose, + ciphertext_buffer, + plain_pdu->used_size + AES_CCM_TAG); + assert(ciphertext_len < OSCORE_CRYPTO_BUFFER_SIZE); + +#ifdef HAVE_OSCORE_GROUP + if (osc_ctx->mode != COAP_OSCORE_MODE_SINGLE /* && coap_request */) { + /* sign request message */ + uint8_t *sig_buffer = NULL; + size_t sig_len = external_aad_len + ciphertext_len + 30; + int sign_res; + uint8_t keystream[Ed25519_SIGNATURE_LEN]; + uint8_t *buffer = ciphertext_buffer + ciphertext_len; + + sig_buffer = coap_malloc(sig_len); + oscore_populate_sign(sign, osc_ctx, snd_ctx->public_key, + snd_ctx->private_key); + sig_len = oscore_prepare_sig_structure(sig_buffer, sig_len, + external_aad_buffer, + external_aad_len, + ciphertext_buffer, + ciphertext_len); + cose_sign1_set_signature(sign, ciphertext_buffer + ciphertext_len); + cose_sign1_set_ciphertext(sign, sig_buffer, sig_len); + sign_res = cose_sign1_sign(sign); + coap_free(sig_buffer); + if (sign_res ==0 ){ + coap_log(LOG_WARNING,"OSCORE: Signature Failure \n"); + goto error; + } + /* signature at end of encrypted text */ + ciphertext_len += Ed25519_SIGNATURE_LEN; + assert(ciphertext_len + Ed25519_SIGNATURE_LEN < OSCORE_CRYPTO_BUFFER_SIZE); + + /* ENC_SIGNATURE = SIGNATURE XOR KEYSTREAM */ + oscore_derive_keystream(osc_ctx, cose, coap_request, + snd_ctx->sender_id, + osc_ctx->id_context, Ed25519_SIGNATURE_LEN, + keystream, sizeof(keystream)); + for(int i = 0; i < Ed25519_SIGNATURE_LEN; i++) { + buffer[i] = buffer[i] ^ (uint8_t)keystream[i]; + } + } +#endif /* HAVE_OSCORE_GROUP */ + + /* Add in OSCORE option (previously computed) */ + if (!coap_insert_option(osc_pdu, COAP_OPTION_OSCORE, oscore_option_len, + oscore_option)) + goto error; + + /* Add now encrypted payload */ + if (!coap_add_data(osc_pdu, ciphertext_len, ciphertext_buffer)) + goto error; + + coap_free_type(COAP_OSCORE_BUF, ciphertext_buffer); + ciphertext_buffer = NULL; + + coap_delete_pdu(plain_pdu); + plain_pdu = NULL; + + if (association && association->is_observe == 0) + oscore_delete_association(session, association); + association = NULL; + + if (!coap_pdu_encode_header(osc_pdu, session->proto)) { + goto error; + } + + /* + * Set up an association for handling a response if this is a request + */ + if (coap_request) { + association = oscore_find_association(session, &pdu_token); + if (association) { + if (doing_observe && observe_value == 1) { + association->is_observe = 0; + } + /* Refresh the association */ + coap_delete_bin_const(association->nonce); + association->nonce = coap_new_bin_const(cose->nonce.s, + cose->nonce.length); + if (association->nonce == NULL) + goto error; + coap_delete_bin_const(association->aad); + association->aad = coap_new_bin_const(cose->aad.s, cose->aad.length); + if (association->aad == NULL) + goto error; + coap_delete_bin_const(association->partial_iv); + association->partial_iv = coap_new_bin_const(cose->partial_iv.s, + cose->partial_iv.length); + if (association->partial_iv == NULL) + goto error; + association->recipient_ctx = rcp_ctx; + } + else if (!oscore_new_association(session, &pdu_token, + rcp_ctx, &cose->aad, &cose->nonce, + &cose->partial_iv, doing_observe)) { + goto error; + } + } + return osc_pdu; + +error: + if (ciphertext_buffer) coap_free_type(COAP_OSCORE_BUF, ciphertext_buffer); + coap_delete_pdu(osc_pdu); + coap_delete_pdu(plain_pdu); + return NULL; +} + +static void +build_and_send_error_pdu(coap_session_t *session, coap_pdu_t *rcvd, + coap_pdu_code_t code, const char *diagnostic, + uint8_t *echo_data, int encrypt_oscore) +{ + coap_pdu_t *err_pdu; + coap_bin_const_t token; + int oscore_encryption = session->oscore_encryption; + coap_mid_t mid = COAP_INVALID_MID; + unsigned char buf[4]; + + err_pdu = coap_pdu_init(rcvd->type == COAP_MESSAGE_NON ? + COAP_MESSAGE_NON : COAP_MESSAGE_ACK, + code, rcvd->mid, coap_session_max_pdu_size(session)); + if (!err_pdu) + return; + token = coap_pdu_get_token(rcvd); + coap_add_token(err_pdu, token.length, token.s); + if (echo_data) { + coap_add_option(err_pdu, COAP_OPTION_ECHO, 8, echo_data); + } + else { + coap_add_option(err_pdu, COAP_OPTION_MAXAGE, + coap_encode_var_safe(buf, sizeof(buf), 0), buf); + } + if (diagnostic) + coap_add_data(err_pdu, strlen(diagnostic), + (const uint8_t *)diagnostic); + session->oscore_encryption = encrypt_oscore; + + if (echo_data && encrypt_oscore) { + coap_pdu_t *osc_pdu; + + osc_pdu = coap_oscore_new_pdu_encrypted(session, err_pdu, NULL, 1); + if (!osc_pdu) + goto fail_resp; + session->oscore_encryption = 0; + mid = coap_send_internal(session, osc_pdu); + } + else { + mid = coap_send_internal(session, err_pdu); + } +fail_resp: + session->oscore_encryption = oscore_encryption; + if (mid == COAP_INVALID_MID) + return; + return; +} + +/* pdu contains incoming message with encrypted COSE ciphertext payload + * function returns decrypted message + * and verifies signature, if present + * returns NULL when decryption,verification fails + */ +coap_pdu_t * +coap_oscore_decrypt_pdu(coap_session_t *session, coap_pdu_t *pdu) +{ + coap_pdu_t *decrypt_pdu = NULL; + coap_pdu_t *plain_pdu = NULL; + const uint8_t *osc_value; /* value of OSCORE option */ + uint8_t osc_size; /* size of OSCORE OPTION */ + coap_opt_iterator_t opt_iter; + coap_opt_t *opt = NULL; +#ifdef HAVE_OSCORE_GROUP + uint8_t group_message = 0; + cose_sign1_t sign[1]; +#endif /* HAVE_OSCORE_GROUP */ + cose_encrypt0_t cose[1]; + oscore_ctx_t *osc_ctx = NULL; + uint8_t aad_buffer[AAD_BUF_LEN]; + uint8_t nonce_buffer[13]; + coap_bin_const_t aad; + coap_bin_const_t nonce; + int pltxt_size = 0; + uint8_t coap_request = COAP_PDU_IS_REQUEST(pdu); + coap_bin_const_t pdu_token; + uint8_t *st_encrypt; + size_t encrypt_len; + size_t tag_len; + oscore_recipient_ctx_t *rcp_ctx = NULL; + oscore_association_t *association = NULL; + uint8_t external_aad_buffer[100]; + size_t external_aad_len = 0; + oscore_sender_ctx_t *snd_ctx = NULL; + + opt = coap_check_option(pdu, COAP_OPTION_OSCORE, &opt_iter); + assert(opt); + if (opt == NULL) + return NULL; + + coap_show_pdu(LOG_DEBUG, pdu); + if (session->context->osc_ctx == NULL) { + coap_log(LOG_WARNING,"OSCORE: Not enabled\n"); + return NULL; + } + + if (pdu->data == NULL) { + coap_log(LOG_WARNING,"OSCORE: No protected payload\n"); + return NULL; + } + + osc_size = coap_opt_length(opt); + osc_value = coap_opt_value(opt); + + cose_encrypt0_init(cose); /* clear cose memory */ +#ifdef HAVE_OSCORE_GROUP + cose_sign1_init(sign); /* clear sign memory */ +#endif /* HAVE_OSCORE_GROUP */ + + /* PDU code will be filled in after decryption */ + decrypt_pdu = coap_pdu_init(pdu->type, 0, pdu->mid, + coap_session_max_pdu_size(session)); + if (decrypt_pdu == NULL) + goto error; + + /* Copy across the Token */ + pdu_token = coap_pdu_get_token(pdu); + coap_add_token(decrypt_pdu, pdu_token.length, pdu_token.s); + + /* + * 8.2/8.4 Step 1. + * Copy outer options across, except E and OSCORE options + */ + coap_option_iterator_init(pdu, &opt_iter, COAP_OPT_ALL); + while ((opt = coap_option_next(&opt_iter))) { + switch (opt_iter.number) { + /* 'E' options skipped */ + case COAP_OPTION_IF_MATCH: + case COAP_OPTION_ETAG: + case COAP_OPTION_IF_NONE_MATCH: + case COAP_OPTION_OBSERVE: + case COAP_OPTION_LOCATION_PATH: + case COAP_OPTION_URI_PATH: + case COAP_OPTION_CONTENT_FORMAT: + case COAP_OPTION_MAXAGE: + case COAP_OPTION_URI_QUERY: + case COAP_OPTION_ACCEPT: + case COAP_OPTION_LOCATION_QUERY: + case COAP_OPTION_BLOCK2: + case COAP_OPTION_BLOCK1: + case COAP_OPTION_SIZE2: + case COAP_OPTION_SIZE1: + case COAP_OPTION_NORESPONSE: + case COAP_OPTION_ECHO: + case COAP_OPTION_RTAG: + /* OSCORE does not get copied across */ + case COAP_OPTION_OSCORE: + break; + default: + if (!coap_add_option(decrypt_pdu, opt_iter.number, + coap_opt_length(opt), + coap_opt_value(opt))) + goto error; + break; + } + } + + if (coap_request) { + uint64_t incoming_seq; + coap_bin_const_t empty = { 0, NULL}; + /* + * 8.2 Step 2 + * Decompress COSE object + * Get Recipient Context based on kid and optional kid_context + */ + if (oscore_decode_option_value(osc_value, osc_size, cose) == 0) { + coap_log(LOG_WARNING,"OSCORE: OSCORE Option cannot be decoded.\n"); + build_and_send_error_pdu(session, pdu, COAP_RESPONSE_CODE(402), + "Failed to decode COSE", NULL, 0); + goto error_no_ack; + } + osc_ctx = oscore_find_context(session->context, + empty, + cose->key_id, + cose->kid_context, + &rcp_ctx); + if (!osc_ctx) { + coap_log(LOG_CRIT,"OSCORE: Security Context not found\n"); + build_and_send_error_pdu(session, pdu, COAP_RESPONSE_CODE(401), + "Security context not found", NULL, 0); + goto error_no_ack; + } + /* to be used for encryption of returned response later */ + session->recipient_ctx = rcp_ctx; + snd_ctx = osc_ctx->sender_context; + + /* + * 8.2 Step 3. + * Verify Partial IV is not duplicated. + * + * Requires in COSE object as appropriate + * partial_iv (as received) + */ + if (rcp_ctx->initial_state == 0 && + !oscore_validate_sender_seq(rcp_ctx, cose)) { + coap_log(LOG_WARNING,"OSCORE: Replayed or old message\n"); + build_and_send_error_pdu(session, pdu, COAP_RESPONSE_CODE(401), + "Replay detected", NULL, 0); + goto error_no_ack; + } + + incoming_seq = coap_decode_var_bytes8(cose->partial_iv.s, + cose->partial_iv.length); + rcp_ctx->last_seq = incoming_seq; + } + else /* !coap_request */ { + /* + * 8.4 Step 2 + * Decompress COSE object + * Get Recipient Context based on token + */ + if (oscore_decode_option_value(osc_value, osc_size, cose) == 0) { + coap_log(LOG_WARNING,"OSCORE: OSCORE Option cannot be decoded.\n"); + goto error; + } + association = oscore_find_association(session, &pdu_token); + if (association) { + rcp_ctx = association->recipient_ctx; + osc_ctx = rcp_ctx->common_ctx; + snd_ctx = osc_ctx->sender_context; + } + else { + coap_log(LOG_CRIT,"OSCORE: Security Context association not found\n"); + goto error; + } + } + +#ifdef HAVE_OSCORE_GROUP + group_message = osc_ctx->mode != COAP_OSCORE_MODE_SINGLE; + if ((cose->group_flag == 1 && + osc_ctx->mode != COAP_OSCORE_MODE_GROUP) || + (cose->group_flag == 0 && + osc_ctx->mode == COAP_OSCORE_MODE_GROUP)) { + /* mode cannot be treated according to oscore context */ + coap_log(LOG_WARNING,"OSCORE: Unsupported mode\n"); + goto error; + } + + if (osc_ctx->mode != COAP_OSCORE_MODE_SINGLE && coap_request) + cose_encrypt0_set_alg(cose, osc_ctx->sign_enc_alg); + else +#endif /* HAVE_OSCORE_GROUP */ + cose_encrypt0_set_alg(cose, osc_ctx->aead_alg); + + if (coap_request) { + /* + * RFC8613 8.2 Step 4. + * Compose the External AAD and then AAD + * + * Non Group (based on osc_tx->mode) requires the following + * alg_aead (osc_ctx) + * request_kid (request key_id using cose) + * request_piv (request partial_iv using cose) + * options (none at present) + * Group (based on osc_tx->mode) requires the following + * alg_aead (osc_ctx) (pairwise mode) + * alg_signature_enc (osc_ctx) (group mode) + * alg_signature (osc_ctx) (group mode) + * alg_pairwise_key_agreement (osc_ctx) (pairwise mode) + * request_kid (request key_id using cose) + * request_piv (request partial_iv using cose) + * options (none at present) + * request_kid_context (osc_ctx id_context) + * OSCORE_option (as received in request) + * sender_public_key (recipient public key) + * gm_public_key (osc_ctx gm_public_key) + * + * Note: No I options at present + */ + + /* External AAD */ + external_aad_len = oscore_prepare_e_aad(osc_ctx, cose, osc_value, + osc_size, +#ifdef HAVE_OSCORE_GROUP + rcp_ctx->public_key, +#else /* HAVE_OSCORE_GROUP */ + NULL, +#endif /* HAVE_OSCORE_GROUP */ + external_aad_buffer, + sizeof(external_aad_buffer)); + + /* AAD */ + aad.s = aad_buffer; + aad.length = oscore_prepare_aad(external_aad_buffer, external_aad_len, + aad_buffer, sizeof(aad_buffer)); + assert(aad.length < AAD_BUF_LEN); + cose_encrypt0_set_aad(cose, &aad); + + /* + *RFC8613 8.2 Step 5. + * Compute the AEAD nonce. + * + * Requires in COSE object as appropriate + * key_id (kid) (Recipient ID) + * partial_iv (as received in request) + * common_iv (already in osc_ctx) + */ + nonce.s = nonce_buffer; + nonce.length = 13; + oscore_generate_nonce(cose, osc_ctx, nonce_buffer, 13); + cose_encrypt0_set_nonce(cose, &nonce); + /* + * Set up an association for use in the response + */ + association = oscore_find_association(session, &pdu_token); + if (association) { + /* Refresh the association */ + coap_delete_bin_const(association->nonce); + association->nonce = coap_new_bin_const(cose->nonce.s, + cose->nonce.length); + if (association->nonce == NULL) + goto error; + coap_delete_bin_const(association->partial_iv); + association->partial_iv = coap_new_bin_const(cose->partial_iv.s, + cose->partial_iv.length); + if (association->partial_iv == NULL) + goto error; + coap_delete_bin_const(association->aad); + association->aad = coap_new_bin_const(cose->aad.s, + cose->aad.length); + if (association->aad == NULL) + goto error; + association->recipient_ctx = rcp_ctx; + } + else if (!oscore_new_association(session, &pdu_token, + rcp_ctx, &cose->aad, &cose->nonce, + &cose->partial_iv, 0)) { + goto error; + } + /* So association is not released when handling decrypt */ + association = NULL; + } + else /* ! coap_request */ { + /* Need to do nonce before AAD because of different partial_iv */ + /* + * 8.4 Step 4. + * Compose the AEAD nonce. + */ + cose_encrypt0_set_key_id(cose, rcp_ctx->recipient_id); + if (cose->partial_iv.length == 0) { + cose_encrypt0_set_partial_iv(cose, association->partial_iv); + cose_encrypt0_set_nonce(cose, association->nonce); + } + else { + /* + * Requires in COSE object as appropriate + * kid (set above) + * partial_iv (as received) + * common_iv (already in osc_ctx) + */ + oscore_generate_nonce(cose, osc_ctx, nonce_buffer, 13); + nonce.s = nonce_buffer; + nonce.length = 13; + cose_encrypt0_set_nonce(cose, &nonce); + } +#ifdef OSCORE_EXTRA_DEBUG + dump_cose(cose, "!req post nonce"); +#endif /* OSCORE_EXTRA_DEBUG */ + /* + * 8.4 Step 3. + * Compose the External AAD and then AAD (same as request non-group (5.4) + * + * Non Group (based on osc_tx->mode) requires the following + * alg_aead (osc_ctx) + * request_kid (request key_id using cose) + * request_piv (request partial_iv using cose) + * options (none at present) + * Group (based on osc_tx->mode) requires the following + * alg_aead (osc_ctx) (pairwise mode) + * alg_signature_enc (osc_ctx) (group mode) + * alg_signature (osc_ctx) (group mode) + * alg_pairwise_key_agreement (osc_ctx) (pairwise mode) + * request_kid (request key_id using cose) + * request_piv (request partial_iv using cose) + * options (none at present) + * request_kid_context (osc_ctx id_context) + * OSCORE_option (as received in request) + * sender_public_key (recipient public key) + * gm_public_key (osc_ctx gm_public_key) + * + * Note: No I options at present + */ + + /* External AAD */ + cose_encrypt0_set_key_id(cose, snd_ctx->sender_id); + cose_encrypt0_set_partial_iv(cose, association->partial_iv); +#ifdef OSCORE_EXTRA_DEBUG + dump_cose(cose, "!req pre aad"); +#endif /* OSCORE_EXTRA_DEBUG */ + external_aad_len = oscore_prepare_e_aad(osc_ctx, cose, +#ifdef HAVE_OSCORE_GROUP + osc_value, + osc_size, + rcp_ctx->public_key, +#else /* HAVE_OSCORE_GROUP */ + NULL, + 0, + NULL, +#endif /* HAVE_OSCORE_GROUP */ + external_aad_buffer, + sizeof(external_aad_buffer)); + + /* AAD */ + aad.s = aad_buffer; + aad.length = oscore_prepare_aad(external_aad_buffer, external_aad_len, + aad_buffer, sizeof(aad_buffer)); + assert(aad.length < AAD_BUF_LEN); + cose_encrypt0_set_aad(cose, &aad); +#ifdef OSCORE_EXTRA_DEBUG + dump_cose(cose, "!req pre nonce"); +#endif /* OSCORE_EXTRA_DEBUG */ + } + + /* + * 8.2 Step 6 / 8.4 Step 5. + * Decrypt the COSE object. + * + * Requires in COSE object as appropriate + * alg (already set) + * key + * nonce (already set) + * aad (already set) + * ciphertext + */ + st_encrypt = pdu->data; + encrypt_len = pdu->used_size - (pdu->data - pdu->token); +#ifdef HAVE_OSCORE_GROUP + if (group_message == 1) + encrypt_len = encrypt_len - Ed25519_SIGNATURE_LEN; +#endif /* HAVE_OSCORE_GROUP */ + if (encrypt_len <= 0) { + coap_log(LOG_WARNING,"OSCORE: No protected payload\n"); + goto error; + } + cose_encrypt0_set_key(cose, rcp_ctx->recipient_key); + cose_encrypt0_set_ciphertext(cose, st_encrypt, encrypt_len); + + tag_len = cose_tag_len(cose->alg); + /* Decrypt into plain_pdu, so code (token), options and data are in place */ + plain_pdu = coap_pdu_init(0, 0, 0, encrypt_len /* - tag_len */); + if (plain_pdu == NULL) + goto error; + + /* need the tag_len on the end for TinyDTLS to do its work - yuk */ + if (!coap_pdu_resize(plain_pdu, encrypt_len /* - tag_len */)) + goto error; + + /* Account for 1 byte 'code' used as token */ + plain_pdu->token_length = 1; + /* Account for the decrypted data */ + plain_pdu->used_size = encrypt_len - tag_len; + +#ifdef HAVE_OSCORE_GROUP + if (group_message == 1) { + /* verify signature */ + uint8_t *st_signature = st_encrypt + encrypt_len; + uint8_t *sig_buffer = NULL; + size_t sig_len = external_aad_len + encrypt_len + 30; + int sign_res; + uint8_t keystream[Ed25519_SIGNATURE_LEN]; + + sig_buffer = coap_malloc(sig_len); + oscore_populate_sign(sign, osc_ctx, rcp_ctx->public_key, NULL); + sig_len = oscore_prepare_sig_structure(sig_buffer, sig_len, + external_aad_buffer, external_aad_len, + st_encrypt, encrypt_len); + assert(external_aad_len + encrypt_len + 30 > sig_len); + cose_sign1_set_signature(sign, st_signature); + cose_sign1_set_ciphertext(sign, sig_buffer, sig_len); + + /* SIGNATURE = ENC_SIGNATURE XOR KEYSTREAM */ + oscore_derive_keystream(osc_ctx, cose, coap_request, + rcp_ctx->recipient_id, + osc_ctx->id_context, Ed25519_SIGNATURE_LEN, + keystream, sizeof(keystream)); + for(int i = 0; i < Ed25519_SIGNATURE_LEN; i++) { + st_signature[i] = st_signature[i] ^ (uint8_t)keystream[i]; + } + + sign_res = cose_sign1_verify(sign); + coap_free(sig_buffer); + if (sign_res == 0) { + coap_log(LOG_WARNING, + "OSCORE: Signature verification Failure \n"); + build_and_send_error_pdu(session, pdu, COAP_RESPONSE_CODE(400), + "Decryption failed", NULL, 0); + goto error_no_ack; + } + } +#endif /* HAVE_OSCORE_GROUP */ + dump_cose(cose, "Pre decrypt"); + pltxt_size = cose_encrypt0_decrypt(cose, plain_pdu->token, + encrypt_len - tag_len); + if (pltxt_size <= 0) { + coap_log(LOG_WARNING,"OSCORE: Decryption Failure, result code: %d \n", + (int)pltxt_size); + if (coap_request) { + build_and_send_error_pdu(session, pdu, COAP_RESPONSE_CODE(400), + "Decryption failed", NULL, 0); + oscore_roll_back_seq(rcp_ctx); + goto error_no_ack; + } + goto error; + } + + assert((size_t)pltxt_size < pdu->alloc_size + pdu->max_hdr_size ); + + /* Appendix B.1.2 Trap */ + if (coap_request) { + if (rcp_ctx->initial_state == 1) { + opt = coap_check_option(plain_pdu, COAP_OPTION_ECHO, &opt_iter); + if (opt) { + /* Verify Client is genuine */ + if (coap_opt_length(opt) == 8 && + memcmp(coap_opt_value(opt), rcp_ctx->echo_value, 8) == 0) { + if (!oscore_validate_sender_seq(rcp_ctx, cose)) { + coap_log(LOG_WARNING,"OSCORE: Replayed or old message\n"); + build_and_send_error_pdu(session, pdu, COAP_RESPONSE_CODE(401), + "Replay detected", NULL, 0); + goto error_no_ack; + } + } + else + goto error; + } + else { + /* RFC 8163 Appendix B.1.2 */ + coap_prng(rcp_ctx->echo_value, sizeof(rcp_ctx->echo_value)); + build_and_send_error_pdu(session, pdu, COAP_RESPONSE_CODE(401), + NULL, rcp_ctx->echo_value, 1); + goto error_no_ack; + } + } + } + + /* + * 8.2 Step 7 / 8.4 Step 6. + * Add decrypted Code, options and payload + * [OSCORE option not copied across previously] + */ + + /* PDU code is pseudo plain_pdu token */ + decrypt_pdu->code = plain_pdu->token[0]; + + /* Copy inner decrypted options across */ + coap_option_iterator_init(plain_pdu, &opt_iter, COAP_OPT_ALL); + while ((opt = coap_option_next(&opt_iter))) { + size_t len; + size_t bias; + + switch (opt_iter.number) { + case COAP_OPTION_OSCORE: + break; + case COAP_OPTION_OBSERVE: + if (!coap_request) { + bias = cose->partial_iv.length > 3 ? cose->partial_iv.length - 3 : 0; + len = cose->partial_iv.length > 3 ? 3 : cose->partial_iv.length; + /* Make Observe option reflect last 3 bytes of partial_iv */ + if (!coap_add_option(decrypt_pdu, opt_iter.number, + len, + cose->partial_iv.s ? &cose->partial_iv.s[bias] : NULL)) + goto error; + break; + } + association = oscore_find_association(session, &pdu_token); + if (association) { + association->is_observe = 1; + association = NULL; + } + /* Fall Through */ + default: + if (!coap_insert_option(decrypt_pdu, opt_iter.number, + coap_opt_length(opt), + coap_opt_value(opt))) + goto error; + break; + } + } + /* Need to copy across any data */ + if (opt_iter.length > 0 && opt_iter.next_option && + opt_iter.next_option[0] == COAP_PAYLOAD_START) { + plain_pdu->data = &opt_iter.next_option[1]; + if (!coap_add_data(decrypt_pdu, plain_pdu->used_size - + (plain_pdu->data - plain_pdu->token), plain_pdu->data)) + goto error; + } + coap_delete_pdu(plain_pdu); + plain_pdu = NULL; + + /* Make sure headers are correctly set up */ + if (!coap_pdu_encode_header(decrypt_pdu, session->proto)) { + goto error; + } + if (association && association->is_observe == 0) + oscore_delete_association(session, association); + + return decrypt_pdu; + +error: + coap_send_ack(session, pdu); +error_no_ack: + if (association && association->is_observe == 0) + oscore_delete_association(session, association); + coap_delete_pdu(decrypt_pdu); + coap_delete_pdu(plain_pdu); + return NULL; +} + +typedef enum { + COAP_ENC_ASCII = 0x01, + COAP_ENC_HEX = 0x02, +#ifdef HAVE_OSCORE_GROUP + COAP_ENC_FILE_PEM = 0x04, +#endif /* HAVE_OSCORE_GROUP */ + COAP_ENC_INTEGER = 0x08, + COAP_ENC_TEXT = 0x10, + COAP_ENC_BOOL = 0x20, + COAP_ENC_LAST +} coap_oscore_coding_t; + +static struct coap_oscore_encoding_t { + const char *name; + coap_oscore_coding_t encoding; +} oscore_encoding[] = { + { "ascii", COAP_ENC_ASCII }, + { "hex", COAP_ENC_HEX }, +#ifdef HAVE_OSCORE_GROUP + { "file_pem", COAP_ENC_FILE_PEM }, +#endif /* HAVE_OSCORE_GROUP */ + { "integer", COAP_ENC_INTEGER }, + { "text", COAP_ENC_TEXT }, + { "bool", COAP_ENC_BOOL } +}; + +typedef struct { + coap_oscore_coding_t encoding; + union { + int value_int; + coap_bin_const_t *value_bin; + coap_str_const_t value_str; + } u; +} oscore_value_t; + +static uint8_t +hex2char(char c) { + assert(isxdigit(c)); + if ('a' <= c && c <= 'f') + return c - 'a' + 10; + else if ('A' <= c && c <= 'F') + return c - 'A' + 10; + else + return c - '0'; +} + +/* + * Break up each OSCORE Configuration line entry into the 3 parts which + * are comma separated + * + * keyword,encoding,value + */ +static int +get_split_entry(const char **start, size_t size, coap_str_const_t *keyword, + oscore_value_t *value) +{ + const char *begin = *start; + const char *end; + const char *split; + size_t i; +#ifdef HAVE_OSCORE_GROUP + size_t suffix_len; + coap_string_t *file_name; +#endif /* HAVE_OSCORE_GROUP */ + +retry: + end = memchr(begin, '\n', size); + if (end == NULL) + return 0; + + /* Track beginning of next line */ + *start = end + 1; + if (end > begin && end[-1] == '\r') + end--; + + if (begin[0] == '#' || (end - begin) == 0) { + /* Skip comment / blank line */ + size -= end - begin + 1; + begin = *start; + goto retry; + } + + /* Get in the keyword */ + split = memchr(begin, ',', end - begin); + if (split == NULL) + goto bad_entry; + + keyword->s = (const uint8_t *)begin; + keyword->length = split - begin; + + begin = split + 1; + if ((end - begin) == 0) + goto bad_entry; + /* Get in the encoding */ + split = memchr(begin, ',', end - begin); + if (split == NULL) + goto bad_entry; + + for (i = 0; i < COAP_ENC_LAST; i++) { + if (memcmp(begin, oscore_encoding[i].name, split-begin) == 0) { + value->encoding = oscore_encoding[i].encoding; + break; + } + } + if (i == COAP_ENC_LAST) + goto bad_entry; + + begin = split + 1; + if ((end - begin) == 0) + goto bad_entry; + /* Get in the keyword's value */ + if (begin[0] == '"') { + split = memchr(&begin[1], '"', end - split - 1); + if (split == NULL) + goto bad_entry; + end = split; + begin++; + } + switch (value->encoding) { + case COAP_ENC_ASCII: + value->u.value_bin = coap_new_bin_const((const uint8_t *)begin, + end - begin); + break; + case COAP_ENC_HEX: + /* Parse the hex into binary */ + if ((end - begin) % 2 != 0) + goto bad_entry; + coap_binary_t * hex = coap_new_binary((end - begin) / 2); + for (i = 0; (i < (size_t)(end - begin)) && isxdigit(begin[i]) && + isxdigit(begin[i+1]); i+=2) { + hex->s[i/2] = (hex2char(begin[i]) << 4) + hex2char(begin[i+1]); + } + if (i != (size_t)(end - begin)) + goto bad_entry; + value->u.value_bin = (coap_bin_const_t *)hex; + break; +#ifdef HAVE_OSCORE_GROUP + case COAP_ENC_FILE_PEM: + /* Need NULL terminated file name */ + file_name = coap_new_string(end - begin); + if (file_name == NULL) + goto bad_file; + memcpy(file_name->s, begin, end - begin); + + coap_binary_t *key = coap_new_binary(32); + suffix_len = sizeof("_private_key") - 1; + if (keyword->length > suffix_len) { + if (memcmp("_private_key", &keyword->s[keyword->length - suffix_len], + suffix_len) == 0) { + if (coap_crypto_read_pem_private_key((const char *)file_name->s, + key->s, &key->length) == 0) { + coap_delete_binary(key); + goto bad_file; + } + value->u.value_bin = (coap_bin_const_t *)key; + coap_delete_string(file_name); + break; + } + } + suffix_len = sizeof("_public_key") - 1; + if (keyword->length > suffix_len) { + if (memcmp("_public_key", &keyword->s[keyword->length - suffix_len], + suffix_len) == 0) { + if (coap_crypto_read_pem_public_key((const char *)file_name->s, + key->s, &key->length) == 0) { + coap_delete_binary(key); + goto bad_file; + } + value->u.value_bin = (coap_bin_const_t *)key; + coap_delete_string(file_name); + break; + } + } + goto bad_file; + break; +#endif /* HAVE_OSCORE_GROUP */ + case COAP_ENC_INTEGER: + value->u.value_int = atoi(begin); + break; + case COAP_ENC_TEXT: + value->u.value_str.s = (const uint8_t *)begin; + value->u.value_str.length = end - begin; + break; + case COAP_ENC_BOOL: + if (memcmp("true", begin, end - begin) == 0) + value->u.value_int = 1; + else if (memcmp("false", begin, end - begin) == 0) + value->u.value_int = 0; + else + goto bad_entry; + break; + case COAP_ENC_LAST: + default: + goto bad_entry; + } + return 1; + +bad_entry: + coap_log(LOG_WARNING, + "oscore_conf: Unrecognized configuration entry '%.*s'\n", + (int)(end - begin - 1), begin); + return 0; + +#ifdef HAVE_OSCORE_GROUP +bad_file: + coap_log(LOG_WARNING, + "oscore_conf: Bad configuration file_pem entry '%.*s'\n", + (int)file_name->length, file_name->s); + coap_delete_string(file_name); + return 0; +#endif /* HAVE_OSCORE_GROUP */ +} + +#undef CONFIG_ENTRY +#define CONFIG_ENTRY(n,e,t) { #n, e, offsetof(coap_oscore_conf_t, n), t } + +typedef struct oscore_text_mapping_t { + const char *text; + int value; +} oscore_text_mapping_t; + +/* Naming as per https://www.iana.org/assignments/cose/cose.xhtml#algorithms */ +static oscore_text_mapping_t text_aead_alg[] = { + { "AES-CCM-16-64-128", COAP_COSE_ALG_AES_CCM_16_64_128 }, + { "AES-CCM-16-64-256", COAP_COSE_ALG_AES_CCM_16_64_256 }, + { NULL, 0 } +}; + +static oscore_text_mapping_t text_hkdf_alg[] = { + { "HMAC 256/256", COAP_COSE_ALG_HMAC256_256 }, + { NULL, 0 } +}; + +static oscore_text_mapping_t text_mode[] = { + { "single", COAP_OSCORE_MODE_SINGLE }, +#ifdef HAVE_OSCORE_GROUP + { "group", COAP_OSCORE_MODE_GROUP }, + { "pairwise", COAP_OSCORE_MODE_PAIRWISE }, +#endif /* HAVE_OSCORE_GROUP */ + { NULL, 0 } +}; + +#ifdef HAVE_OSCORE_GROUP +static oscore_text_mapping_t text_sign_alg[] = { + { "EdDSA", COAP_COSE_ALG_EdDSA }, + { NULL, 0 } +}; + +static oscore_text_mapping_t text_ecdh_alg[] = { + { "ECDH-SS + HKDF-256", COAP_COSE_ALG_ECDH_SS_HKDF_256 }, + { NULL, 0 } +}; +#endif /* HAVE_OSCORE_GROUP */ + +static struct oscore_config_t { + const char *keyword; + coap_oscore_coding_t encoding; + size_t offset; + oscore_text_mapping_t *text_mapping; +} oscore_config[] = { + CONFIG_ENTRY(master_secret, COAP_ENC_HEX | COAP_ENC_ASCII, NULL), + CONFIG_ENTRY(master_salt, COAP_ENC_HEX | COAP_ENC_ASCII, NULL), + CONFIG_ENTRY(sender_id, COAP_ENC_HEX | COAP_ENC_ASCII, NULL), + CONFIG_ENTRY(recipient_id, COAP_ENC_HEX | COAP_ENC_ASCII, NULL), + CONFIG_ENTRY(id_context, COAP_ENC_HEX | COAP_ENC_ASCII, NULL), + CONFIG_ENTRY(replay_window, COAP_ENC_INTEGER, NULL), + CONFIG_ENTRY(ssn_freq, COAP_ENC_INTEGER, NULL), + CONFIG_ENTRY(aead_alg, COAP_ENC_INTEGER | COAP_ENC_TEXT, text_aead_alg), + CONFIG_ENTRY(hkdf_alg, COAP_ENC_INTEGER | COAP_ENC_TEXT, text_hkdf_alg), + CONFIG_ENTRY(mode, COAP_ENC_TEXT, text_mode), +#ifdef HAVE_OSCORE_GROUP + /* As per draft-ietf-ace-oscore-gm-admin 3.1.1 to provide group support */ + CONFIG_ENTRY(hkdf, COAP_ENC_INTEGER | COAP_ENC_TEXT, text_hkdf_alg), +/*CONFIG_ENTRY(pub_key_enc, COAP_ENC_INTEGER, NULL), */ + CONFIG_ENTRY(group_mode, COAP_ENC_BOOL, NULL), + CONFIG_ENTRY(sign_enc_alg, COAP_ENC_INTEGER | COAP_ENC_TEXT, text_aead_alg), + CONFIG_ENTRY(sign_alg, COAP_ENC_INTEGER | COAP_ENC_TEXT, text_sign_alg), +/*CONFIG_ENTRY(sign_params, COAP_ENC_INTEGER | COAP_ENC_TEXT, text_sign_alg),*/ + CONFIG_ENTRY(pairwise_mode, COAP_ENC_BOOL, NULL), + CONFIG_ENTRY(alg, COAP_ENC_INTEGER | COAP_ENC_TEXT, text_aead_alg), + CONFIG_ENTRY(ecdh_alg, COAP_ENC_INTEGER | COAP_ENC_TEXT, text_ecdh_alg), +/*CONFIG_ENTRY(alg, COAP_ENC_INTEGER | COAP_ENC_TEXT, text_aead_alg),*/ + CONFIG_ENTRY(gm_public_key, COAP_ENC_HEX | COAP_ENC_FILE_PEM, NULL), + CONFIG_ENTRY(sender_public_key, COAP_ENC_HEX | COAP_ENC_FILE_PEM, NULL), + CONFIG_ENTRY(sender_private_key, COAP_ENC_HEX | COAP_ENC_FILE_PEM, NULL), + CONFIG_ENTRY(recipient_public_key, COAP_ENC_HEX | COAP_ENC_FILE_PEM, NULL), +#endif /* HAVE_OSCORE_GROUP */ +}; + +static void +coap_free_oscore_conf(coap_oscore_conf_t *oscore_conf) { + uint32_t i; + + if (oscore_conf == NULL) + return; + + 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_free(oscore_conf->recipient_id); +#ifdef HAVE_OSCORE_GROUP + coap_delete_bin_const(oscore_conf->gm_public_key); + coap_delete_bin_const(oscore_conf->sender_public_key); + coap_delete_bin_const(oscore_conf->sender_private_key); + for (i = 0; i < oscore_conf->recipient_public_key_count; i++) { + coap_delete_bin_const(oscore_conf->recipient_public_key[i]); + } + coap_free(oscore_conf->recipient_public_key); +#endif /* HAVE_OSCORE_GROUP */ + + coap_free(oscore_conf); +} + +static coap_oscore_conf_t * +coap_parse_oscore_conf_mem(coap_str_const_t conf_mem) +{ + const char *start = (const char *)conf_mem.s; + const char *end = start + conf_mem.length; + coap_str_const_t keyword; + oscore_value_t value; + coap_oscore_conf_t *oscore_conf; + + oscore_conf = coap_malloc(sizeof(coap_oscore_conf_t)); + if (oscore_conf == NULL) + return NULL; + memset(oscore_conf, 0, sizeof(coap_oscore_conf_t)); + + memset(&value, 0, sizeof(value)); + /* Preset with defaults */ + oscore_conf->replay_window = COAP_OSCORE_DEFAULT_REPLAY_WINDOW; + oscore_conf->ssn_freq = 1; + oscore_conf->aead_alg = COAP_COSE_ALG_AES_CCM_16_64_128; + oscore_conf->hkdf_alg = COAP_COSE_ALG_HMAC256_256; + oscore_conf->mode = COAP_OSCORE_MODE_SINGLE; +#ifdef HAVE_OSCORE_GROUP + oscore_conf->sign_alg = COAP_COSE_ALG_EdDSA; + oscore_conf->sign_enc_alg = COAP_COSE_ALG_AES_CCM_16_64_128; +#endif /* HAVE_OSCORE_GROUP */ + + while (end > start && + get_split_entry(&start, end - start, &keyword, &value)) { + size_t i; + size_t j; + + for (i = 0; i < sizeof(oscore_config)/sizeof(oscore_config[0]); i++) { + if (memcmp(oscore_config[i].keyword, keyword.s, keyword.length) == 0 && + value.encoding & oscore_config[i].encoding) { + if (memcmp(keyword.s, "recipient_id", keyword.length) == 0) { + if (value.u.value_bin->length > 7) { + coap_log(LOG_WARNING, + "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; + } + oscore_conf->recipient_id[oscore_conf->recipient_id_count++] = + value.u.value_bin; + } + else if (memcmp(keyword.s, "recipient_public_key", + keyword.length) == 0) { + /* Special case as there are potentially multiple entries */ + oscore_conf->recipient_public_key = + coap_realloc_type(COAP_STRING, oscore_conf->recipient_public_key, + sizeof(oscore_conf->recipient_public_key[0]) * + (oscore_conf->recipient_public_key_count + 1)); + if (oscore_conf->recipient_public_key == NULL) + goto error_free_value_bin; + oscore_conf->recipient_public_key[oscore_conf->recipient_public_key_count++] = + value.u.value_bin; + } + else { + coap_bin_const_t *unused_check; + + switch(value.encoding) { + case COAP_ENC_HEX: + case COAP_ENC_ASCII: +#ifdef HAVE_OSCORE_GROUP + case COAP_ENC_FILE_PEM: +#endif /* HAVE_OSCORE_GROUP */ + memcpy(&unused_check, + &(((char*)oscore_conf)[oscore_config[i].offset]), + sizeof(unused_check)); + if (unused_check != NULL) { + coap_log(LOG_WARNING, "oscore_conf: Keyword '%.*s' duplicated\n", + (int)keyword.length, (const char *)keyword.s); + goto error; + } + memcpy(&(((char*)oscore_conf)[oscore_config[i].offset]), + &value.u.value_bin, sizeof(value.u.value_bin)); + break; + case COAP_ENC_INTEGER: + case COAP_ENC_BOOL: + memcpy(&(((char*)oscore_conf)[oscore_config[i].offset]), + &value.u.value_int, sizeof(value.u.value_int)); + break; + case COAP_ENC_TEXT: + for (j = 0; oscore_config[i].text_mapping[j].text != NULL; j++) { + if (memcmp(value.u.value_str.s, + oscore_config[i].text_mapping[j].text, + value.u.value_str.length) == 0) { + memcpy(&(((char*)oscore_conf)[oscore_config[i].offset]), + &oscore_config[i].text_mapping[j].value, + sizeof(oscore_config[i].text_mapping[j].value)); + break; + } + } + if (oscore_config[i].text_mapping[j].text == NULL) { + coap_log(LOG_WARNING, + "oscore_conf: Keyword '%.*s': value '%.*s' unknown\n", + (int)keyword.length, (const char *)keyword.s, + (int)value.u.value_str.length, + (const char *)value.u.value_str.s); + goto error; + } + break; + case COAP_ENC_LAST: + default: + assert(0); + break; + } + } + break; + } + } + if (i == sizeof(oscore_config)/sizeof(oscore_config[0])) { + coap_log(LOG_WARNING, "oscore_conf: Keyword '%.*s', type %d unknown\n", + (int)keyword.length, (const char *)keyword.s, value.encoding); + if (value.encoding == COAP_ENC_HEX || value.encoding == COAP_ENC_ASCII) + coap_delete_bin_const(value.u.value_bin); + goto error; + } + } + if (!oscore_conf->master_secret || !oscore_conf->sender_id || + !oscore_conf->recipient_id) { + coap_log(LOG_WARNING, + "oscore_conf: One or more of master_secret," + " sender_id, recipient_id not defined\n"); + goto error; + } + if (oscore_conf->sender_id->length > 7) { + coap_log(LOG_WARNING, + "oscore_conf: Maximum size of sender_id is 7 bytes\n"); + goto error; + } +#ifdef HAVE_OSCORE_GROUP + if (oscore_conf->group_mode && (!oscore_conf->id_context || + !oscore_conf->sender_public_key || !oscore_conf->sender_private_key || + !oscore_conf->recipient_public_key)) { + coap_log(LOG_WARNING, + "oscore_conf: group: One or more of id_context," + " sender_public_key, sender_private_key and recipient_public_key" + " not defined\n"); + goto error; + } + if (oscore_conf->pairwise_mode && (!oscore_conf->id_context)) { + coap_log(LOG_WARNING, + "oscore_conf: group: id_context not defined\n"); + goto error; + } +#endif /* HAVE_OSCORE_GROUP */ + return oscore_conf; + +error_free_value_bin: + coap_delete_bin_const(value.u.value_bin); +error: + coap_free_oscore_conf(oscore_conf); + return NULL; +} + +static coap_oscore_context_t * +coap_oscore_init(coap_context_t *c_context, coap_oscore_conf_t *oscore_conf) +{ + coap_oscore_context_t *osc_ctx; + uint32_t i; + uint32_t replay_window = oscore_conf->replay_window ? + oscore_conf->replay_window : + COAP_OSCORE_DEFAULT_REPLAY_WINDOW; + uint32_t ssn_freq = oscore_conf->ssn_freq ? + oscore_conf->ssn_freq : 1; + + if (!coap_crypto_check_cipher_alg(oscore_conf->aead_alg)) { + coap_log(LOG_WARNING, "COSE: Cipher Algorithm %d not supported\n", + oscore_conf->aead_alg); + goto error; + } + if (!coap_crypto_check_hkdf_alg(oscore_conf->hkdf_alg)) { + coap_log(LOG_WARNING, "COSE: HMAC Algorithm %d not supported\n", + oscore_conf->hkdf_alg); + goto error; + } +#ifdef HAVE_OSCORE_GROUP + /* TODO Check other algs */ +#endif /* HAVE_OSCORE_GROUP */ + +#ifndef HAVE_OSCORE_GROUP + if (oscore_conf->mode != COAP_OSCORE_MODE_SINGLE) { + coap_log(LOG_WARNING, "OSCORE: group not enabled\n"); + goto error; + } +#endif /* !HAVE_OSCORE_GROUP */ + +#ifdef HAVE_OSCORE_GROUP + if (oscore_conf->group_mode) { + /* Set up Group operation */ + if (oscore_conf->recipient_id_count != + oscore_conf->recipient_public_key_count) { + coap_log(LOG_WARNING, "OSCORE: recipient_id count (%d) does not match" + " recipient_public_key count (%d)\n", + oscore_conf->recipient_id_count, + oscore_conf->recipient_public_key_count); + goto error; + } + } +#endif /* HAVE_OSCORE_GROUP */ + + osc_ctx = oscore_derive_ctx(oscore_conf->master_secret, + oscore_conf->master_salt, + oscore_conf->aead_alg, + oscore_conf->hkdf_alg, + oscore_conf->sender_id, + oscore_conf->recipient_id[0], + oscore_conf->id_context, + replay_window, ssn_freq); + if (!osc_ctx) { + coap_log(LOG_CRIT, "OSCORE: Could not create Security Context!\n"); + goto error; + } + for (i = 1; i < oscore_conf->recipient_id_count; i++) { + if (oscore_add_recipient(osc_ctx, oscore_conf->recipient_id[i]) == NULL) { + coap_log(LOG_WARNING, "OSCORE: Failed to add Client ID\n"); + goto error; + } + } + /* Free off the recipient_id array */ + coap_free(oscore_conf->recipient_id); + oscore_conf->recipient_id = NULL; + +#ifdef HAVE_OSCORE_GROUP + if (oscore_conf->group_mode) { + oscore_recipient_ctx_t *rcp_ctx = osc_ctx->recipient_chain; + + oscore_add_group_keys(osc_ctx, + rcp_ctx, + oscore_conf->sender_public_key, + oscore_conf->sender_private_key, + oscore_conf->recipient_public_key[0]); + for (i = 1; i < oscore_conf->recipient_public_key_count; i++) { + /* Add in Client Public Keys */ + rcp_ctx = rcp_ctx->next_recipient; + if (rcp_ctx) { + coap_delete_bin_const(rcp_ctx->public_key); + rcp_ctx->public_key = oscore_conf->recipient_public_key[i]; + if (coap_get_log_level() >= COAP_LOG_CIPHERS) { + if (rcp_ctx->public_key != NULL) + oscore_log_hex_value(COAP_LOG_CIPHERS, "Rcpt Pub Key", + rcp_ctx->public_key); + } + } + } + /* Free off the recipient_public_key array */ + coap_free(oscore_conf->recipient_public_key); + + size_t counter_signature_parameters_len = 0; + uint8_t *counter_signature_parameters = + oscore_cs_key_params(COSE_Elliptic_Curve_Ed25519, + COSE_KTY_OKP, + &counter_signature_parameters_len); + oscore_add_group_algorithm(osc_ctx, + oscore_conf->sign_enc_alg, + oscore_conf->sign_alg, + counter_signature_parameters, + counter_signature_parameters_len); + coap_free(counter_signature_parameters); + } + + if (oscore_conf->pairwise_mode) { + /* Set up Pairwise operation */ +#if 0 + oscore_add_pair_keys(os_ctx, + oscore_recipient_ctx_t *rcp_ctx, + uint8_t *pairwise_recipient_key, + uint8_t pairwise_recipient_key_len, + uint8_t *pairwise_sender_key, + uint8_t pairwise_sender_key_len) +#endif + } +#endif /* HAVE_OSCORE_GROUP */ + + /* As all is stored in osc_ctx, oscore_conf is no longer needed */ + coap_free(oscore_conf); + + /* Add to linked chain */ + oscore_enter_context(c_context, osc_ctx); + + /* return default first context */ + return osc_ctx; + +error: + coap_free_oscore_conf(oscore_conf); + return NULL; +} + +void +coap_delete_all_oscore(coap_context_t *c_context) { + oscore_free_contexts(c_context); +} + +void +coap_delete_oscore_associations(coap_session_t *session) +{ + oscore_delete_server_associations(session); +} + + +coap_oscore_context_t * +coap_new_oscore_context(coap_context_t *context, + coap_str_const_t conf_mem, + coap_oscore_save_seq_num_t save_seq_num_func, + void *save_seq_num_func_param, + uint64_t start_seq_num) +{ + coap_oscore_conf_t *oscore_conf = coap_parse_oscore_conf_mem(conf_mem); + coap_oscore_context_t *osc_ctx; + + if (oscore_conf == NULL) + return NULL; + + osc_ctx = coap_oscore_init(context, oscore_conf); + if (osc_ctx) { + osc_ctx->save_seq_num_func = save_seq_num_func; + osc_ctx->save_seq_num_func_param = save_seq_num_func_param; + osc_ctx->sender_context->seq = start_seq_num; + /* + * Need to set the last Sender Seq Num based on ssn_freq + * The value should only change if there is a change to ssn_freq + * and (potentially) be lower than seq, then save_seq_num_func() is + * immediately called on next SSN update. + */ + osc_ctx->sender_context->next_seq = + start_seq_num - (start_seq_num % osc_ctx->ssn_freq); + } + return osc_ctx; +} + +/* + * Compute the size of the potential OSCORE overhead + */ +size_t +coap_oscore_overhead(coap_session_t *session, coap_pdu_t *pdu) +{ + size_t overhead = 0; + oscore_recipient_ctx_t *rcp_ctx = session->recipient_ctx; + oscore_ctx_t *osc_ctx = rcp_ctx ? rcp_ctx->common_ctx : NULL; + coap_opt_iterator_t opt_iter; + coap_opt_t *option; + + if (osc_ctx == NULL) + return 0; + + /* Protected code held inside */ + overhead += 1; + + /* Observe option (creates inner and outer */ + option = coap_check_option(pdu, COAP_OPTION_OBSERVE, &opt_iter); + if (option) { + /* Assume delta is small */ + overhead += 1 + coap_opt_length(option); + } + + /* Proxy URI option Split */ + + /* Echo option */ + + /* OSCORE option */ + /* Option header */ + overhead += 1 + + /* Partial IV (64 bits max)*/ + 8 + + /* kid context */ + (osc_ctx->id_context ? osc_ctx->id_context->length : 0) + + /* kid */ + osc_ctx->sender_context->sender_id->length; + + /* AAD overhead */ + overhead += AES_CCM_TAG; + + /* Signing Overhead */ +#ifdef HAVE_OSCORE_GROUP + if (osc_ctx && osc_ctx->mode != COAP_OSCORE_MODE_SINGLE) + overhead += Ed25519_SIGNATURE_LEN; +#endif /* HAVE_OSCORE_GROUP */ + + /* End of options marker */ + overhead += 1; + + return overhead; +} + +int +coap_delete_oscore_context(coap_context_t *context, + coap_oscore_context_t *oscore_context) +{ + return oscore_remove_context(context, oscore_context); +} + +int coap_new_oscore_recipient(coap_oscore_context_t *oscore_context, + coap_bin_const_t *recipient_id) +{ + if (oscore_add_recipient(oscore_context, recipient_id) == NULL) + return 0; + return 1; +} + +int coap_delete_oscore_recipient(coap_oscore_context_t *oscore_context, + coap_bin_const_t *recipient_id) +{ + return oscore_delete_recipient(oscore_context, recipient_id); +} + +/** @} */ + +#else /* !HAVE_OSCORE */ +int +coap_oscore_is_supported(void) { + return 0; +} + +int +coap_oscore_group_is_supported(void) { + return 0; +} + +coap_session_t * +coap_new_client_session_oscore(coap_context_t *ctx, + const coap_address_t *local_if, + const coap_address_t *server, + coap_proto_t proto, + coap_oscore_context_t *oscore_context) { + (void)ctx; + (void)local_if; + (void)server; + (void)proto; + (void)oscore_context; + return NULL; +} + +coap_session_t * +coap_new_client_session_oscore_psk(coap_context_t *ctx, + const coap_address_t *local_if, + const coap_address_t *server, + coap_proto_t proto, + coap_dtls_cpsk_t *psk_data, + coap_oscore_context_t *oscore_context) { + (void)ctx; + (void)local_if; + (void)server; + (void)proto; + (void)psk_data; + (void)oscore_context; + return NULL; +} + +coap_session_t * +coap_new_client_session_oscore_pki(coap_context_t *ctx, + const coap_address_t *local_if, + const coap_address_t *server, + coap_proto_t proto, + coap_dtls_pki_t *pki_data, + coap_oscore_context_t *oscore_context) { + (void)ctx; + (void)local_if; + (void)server; + (void)proto; + (void)pki_data; + (void)oscore_context; + return NULL; +} + +coap_oscore_context_t * +coap_new_oscore_context(coap_context_t *context, + coap_str_const_t conf_mem, + coap_oscore_save_seq_num_t save_seq_num_func, + void *save_seq_num_func_param, + uint64_t start_seq_num) +{ + (void)context; + (void)conf_mem; + (void)save_seq_num_func; + (void)save_seq_num_func_param; + (void)start_seq_num; + return NULL; +} + +int +coap_delete_oscore_context(coap_context_t *context, + coap_oscore_context_t *oscore_context) +{ + (void)context; + (void)oscore_context; + return 0; +} + +int coap_new_oscore_recipient(coap_oscore_context_t *osc_ctx, + coap_bin_const_t *recipient_id) +{ + (void)osc_ctx; + (void)recipient_id; + return 0; +} + +int coap_delete_oscore_recipient(coap_oscore_context_t *osc_ctx, + coap_bin_const_t *recipient_id) +{ + (void)osc_ctx; + (void)recipient_id; + return 0; +} + +#endif /* !HAVE_OSCORE */ diff --git a/src/coap_session.c b/src/coap_session.c index 891e3a8d31..1f35fa056e 100644 --- a/src/coap_session.c +++ b/src/coap_session.c @@ -238,6 +238,9 @@ void coap_session_mfree(coap_session_t *session) { coap_block_delete_lg_srcv(session, sq); } #endif /* COAP_SERVER_SUPPORT */ +#ifdef HAVE_OSCORE + coap_delete_oscore_associations(session); +#endif /* HAVE_OSCORE */ } void coap_session_free(coap_session_t *session) { diff --git a/src/coap_tinydtls.c b/src/coap_tinydtls.c index 0438d79854..6c61373df9 100644 --- a/src/coap_tinydtls.c +++ b/src/coap_tinydtls.c @@ -1292,6 +1292,217 @@ coap_digest_final(coap_digest_ctx_t *digest_ctx, } #endif /* COAP_SERVER_SUPPORT */ +#if defined(HAVE_OSCORE) + +int +coap_oscore_is_supported(void) { + return 1; +} + +/* + * The struct cipher_algs and the function get_cipher_alg() are used to + * determine which cipher type to use for creating the required cipher + * suite object. + */ +static struct cipher_algs { + coap_cose_alg_t alg; + u_int cipher_type; +} ciphers[] = { + { COAP_COSE_ALG_AES_CCM_16_64_128, 1 }, +}; + +static u_int +get_cipher_alg(coap_cose_alg_t alg) { + size_t idx; + + for (idx = 0; idx < sizeof(ciphers)/sizeof(struct cipher_algs); idx++) { + if (ciphers[idx].alg == alg) + return ciphers[idx].cipher_type; + } + coap_log(LOG_DEBUG, "get_cipher_alg: COSE cipher %d not supported\n", alg); + return 0; +} + +/* + * The struct hmac_algs and the function get_hmac_alg() are used to + * determine which hmac type to use for creating the required hmac + * suite object. + */ +static struct hmac_algs { + coap_cose_alg_t alg; + u_int hmac_type; +} hmacs[] = { + { COAP_COSE_ALG_HMAC256_256, 1 }, +}; + +static u_int +get_hmac_alg(coap_cose_alg_t alg) { + size_t idx; + + for (idx = 0; idx < sizeof(hmacs)/sizeof(struct hmac_algs); idx++) { + if (hmacs[idx].alg == alg) + return hmacs[idx].hmac_type; + } + coap_log(LOG_DEBUG, "get_hmac_alg: COSE hkdf %d not supported\n", alg); + return 0; +} + +int +coap_crypto_check_cipher_alg(coap_cose_alg_t alg) { + return get_cipher_alg(alg); +} + +int +coap_crypto_check_hkdf_alg(coap_cose_alg_t alg) { + return get_hmac_alg(alg); +} + +int +coap_crypto_aead_encrypt(const coap_crypto_param_t *params, + coap_bin_const_t *data, + coap_bin_const_t *aad, + uint8_t *result, size_t *max_result_len) { + int num_bytes; + const coap_crypto_aes_ccm_t *ccm; + dtls_ccm_params_t dtls_params; + coap_bin_const_t laad; + + if (data == NULL) + return 0; + + assert(params); + + if (get_cipher_alg(params->alg) == 0) { + coap_log(LOG_DEBUG, + "coap_crypto_encrypt: algorithm %d not supported\n", + params->alg); + return 0; + } + + ccm = ¶ms->params.aes; + if (*max_result_len < (data->length + ccm->tag_len)) { + coap_log(LOG_WARNING, + "coap_encrypt: result buffer too small\n"); + return 0; + } + + dtls_params.nonce = ccm->nonce; + dtls_params.tag_length = ccm->tag_len; + dtls_params.l = ccm->l; + + if (aad) { + laad = *aad; + } + else { + laad.s = NULL; + laad.length = 0; + } + + num_bytes = dtls_encrypt_params(&dtls_params, + data->s, data->length, + result, + ccm->key.s, ccm->key.length, + laad.s, laad.length); + if (num_bytes < 0) { + return 0; + } + *max_result_len = num_bytes; + return 1; +} + +int +coap_crypto_aead_decrypt(const coap_crypto_param_t *params, + coap_bin_const_t *data, + coap_bin_const_t *aad, + uint8_t *result, size_t *max_result_len) { + int num_bytes; + const coap_crypto_aes_ccm_t *ccm; + dtls_ccm_params_t dtls_params; + coap_bin_const_t laad; + + if (data == NULL) + return 0; + + assert(params); + + if (get_cipher_alg(params->alg) == 0) { + coap_log(LOG_DEBUG, + "coap_crypto_decrypt: algorithm %d not supported\n", + params->alg); + return 0; + } + + ccm = ¶ms->params.aes; + + if ((*max_result_len + ccm->tag_len) < data->length) { + coap_log(LOG_WARNING, + "coap_decrypt: result buffer too small\n"); + return 0; + } + + dtls_params.nonce = ccm->nonce; + dtls_params.tag_length = ccm->tag_len; + dtls_params.l = ccm->l; + + if (aad) { + laad = *aad; + } + else { + laad.s = NULL; + laad.length = 0; + } + + num_bytes = dtls_decrypt_params(&dtls_params, + data->s, data->length, + result, + ccm->key.s, ccm->key.length, + laad.s, laad.length); + if (num_bytes < 0) { + return 0; + } + *max_result_len = num_bytes; + return 1; +} + +int +coap_crypto_hmac(const coap_crypto_param_t *params, + coap_bin_const_t *data, + uint8_t *result, size_t *max_result_len) { + dtls_hmac_context_t hmac_context; + const coap_crypto_key_t *key; + int num_bytes; + + if (data == NULL) + return 0; + + assert(params); + + if (get_hmac_alg(params->alg) == 0) { + coap_log(LOG_DEBUG, + "coap_crypto_hmac: algorithm %d not supported\n", + params->alg); + return 0; + } + + key = ¶ms->params.key; + + if (*max_result_len < DTLS_SHA256_DIGEST_LENGTH) { + coap_log(LOG_WARNING, + "coap_hmac: result buffer too small\n"); + return 0; + } + dtls_hmac_init(&hmac_context, key->s, key->length); + dtls_hmac_update(&hmac_context, data->s, data->length); + num_bytes = dtls_hmac_finalize(&hmac_context, result); + + if (num_bytes != DTLS_SHA256_DIGEST_LENGTH) { + return 0; + } + *max_result_len = num_bytes; + return 1; +} +#endif /* HAVE_OSCORE */ + #else /* !HAVE_LIBTINYDTLS */ #ifdef __clang__ diff --git a/src/net.c b/src/net.c index 357f0b0a81..1ce62b0ad2 100644 --- a/src/net.c +++ b/src/net.c @@ -614,6 +614,11 @@ coap_free_context(coap_context_t *context) { #ifndef WITHOUT_ASYNC coap_delete_all_async(context); #endif /* WITHOUT_ASYNC */ + +#ifdef HAVE_OSCORE + coap_delete_all_oscore(context); +#endif + #if COAP_SERVER_SUPPORT coap_cache_entry_t *cp, *ctmp; @@ -704,6 +709,17 @@ coap_option_check_critical(coap_context_t *ctx, case COAP_OPTION_BLOCK2: case COAP_OPTION_BLOCK1: break; + case COAP_OPTION_OSCORE: + /* Valid critical if doing OSCORE or are a proxy */ +#if HAVE_OSCORE + if (ctx->osc_ctx) + break; +#endif /* HAVE_OSCORE */ +#if COAP_SERVER_SUPPORT + if (ctx->proxy_uri_resource) + break; +#endif /* COAP_SERVER_SUPPORT */ + /* Fall Through */ default: if (coap_option_filter_get(&ctx->known_options, opt_iter.number) <= 0) { coap_log(LOG_DEBUG, "unknown critical option %d\n", opt_iter.number); @@ -773,9 +789,7 @@ coap_session_send_pdu(coap_session_t *session, coap_pdu_t *pdu) { static ssize_t coap_send_pdu(coap_session_t *session, coap_pdu_t *pdu, coap_queue_t *node) { ssize_t bytes_written; - #ifdef WITH_LWIP - coap_socket_t *sock = &session->sock; if (sock->flags == COAP_SOCKET_EMPTY) { assert(session->endpoint != NULL); @@ -792,7 +806,7 @@ coap_send_pdu(coap_session_t *session, coap_pdu_t *pdu, coap_queue_t *node) { } coap_ticks(&session->last_rx_tx); -#else +#else /* ! WITH_LWIP */ if (session->state == COAP_SESSION_STATE_NONE) { #if ! COAP_CLIENT_SUPPORT @@ -866,14 +880,11 @@ coap_send_pdu(coap_session_t *session, coap_pdu_t *pdu, coap_queue_t *node) { if ((session->sock.flags & COAP_SOCKET_NOT_EMPTY) && (session->sock.flags & COAP_SOCKET_WANT_WRITE)) return coap_session_delay_pdu(session, pdu, node); - bytes_written = coap_session_send_pdu(session, pdu); if (bytes_written >= 0 && pdu->type == COAP_MESSAGE_CON && COAP_PROTO_NOT_RELIABLE(session->proto)) session->con_active++; - -#endif /* WITH_LWIP */ - +#endif /* ! WITH_LWIP */ return bytes_written; } @@ -1045,6 +1056,56 @@ coap_send(coap_session_t *session, coap_pdu_t *pdu) { int have_block1 = 0; coap_opt_t *opt; +#ifndef WITH_LWIP + /* + * If this is not the first client request and are waiting for a response + * to the first client request, then delay sending out this next request + * untill all is properly established. + */ + if (session->type == COAP_SESSION_TYPE_CLIENT && session->doing_first) { + unsigned ref; + int timeout_ms = 5000; + + /* TODO for LwIP */ + /* + * Need to wait for first request to get out and response back before + * continuing.. Response handler has to clear doing_first if not an error. + */ + ref = ++session->ref; + while (*(&session->doing_first) != 0) { + int result = coap_io_process(session->context, 1000); + + if (result < 0) { + coap_session_release(session); + return COAP_INVALID_MID; + } + if (result <= timeout_ms) { + timeout_ms -= result; + } + else { + if (*(&session->doing_first) == 1 && ref > session->ref) { + /* Timeout failure of some sort with first request */ +#ifdef HAVE_OSCORE +#endif /* HAVE_OSCORE */ + } + session->doing_first = 0; + } + } + coap_session_release(session); + } +#endif /* ! WITH_LWIP */ +#ifdef HAVE_OSCORE + if (session->oscore_encryption && + session->recipient_ctx->initial_state == 1) { + /* + * Not sure if remote supports OSCORE, or is going to send us a + * "4.01 + ECHO" etc. so need to hold off future coap_send()s until all + * is OK. + */ + session->doing_first = 1; + } +#endif /* HAVE_OSCORE */ + if (!(session->block_mode & COAP_BLOCK_USE_LIBCOAP)) { return coap_send_internal(session, pdu); } @@ -1066,9 +1127,13 @@ coap_send(coap_session_t *session, coap_pdu_t *pdu) { /* * If type is CON and protocol is not reliable, there is no need to set up * lg_crcv here as it can be built up based on sent PDU if there is a - * Block2 in the response. However, still need it for observe and block1. + * Block2 in the response. However, still need it for observe, oscore and + * block1. */ if (observe_action != -1 || have_block1 || +#ifdef HAVE_OSCORE + session->oscore_encryption || +#endif /* HAVE_OSCORE */ ((pdu->type == COAP_MESSAGE_NON || COAP_PROTO_RELIABLE(session->proto)) && COAP_PDU_IS_REQUEST(pdu) && pdu->code != COAP_REQUEST_CODE_DELETE)) { /* See if this token is already in use for large body responses */ @@ -1268,13 +1333,28 @@ coap_send_internal(coap_session_t *session, coap_pdu_t *pdu) { } #endif /* !COAP_DISABLE_TCP */ - bytes_written = coap_send_pdu( session, pdu, NULL ); +#ifdef HAVE_OSCORE + if (session->oscore_encryption && + !(pdu->type == COAP_MESSAGE_ACK && pdu->code == COAP_EMPTY_CODE)) { + /* Refactor PDU as appropriate RFC8613 */ + coap_pdu_t *osc_pdu = coap_oscore_new_pdu_encrypted(session, pdu, NULL, 0); + + if (osc_pdu == NULL) { + coap_log(LOG_WARNING, "OSCORE: PDU could not be encrypted\n"); + goto error; + } + bytes_written = coap_send_pdu(session, osc_pdu, NULL); + coap_delete_pdu(pdu); + pdu = osc_pdu; + } + else +#endif /* HAVE_OSCORE */ + bytes_written = coap_send_pdu(session, pdu, NULL); if (bytes_written == COAP_PDU_DELAYED) { /* do not free pdu as it is stored with session for later use */ return pdu->mid; } - if (bytes_written < 0) { coap_delete_pdu(pdu); return (coap_mid_t)bytes_written; @@ -1299,7 +1379,6 @@ coap_send_internal(coap_session_t *session, coap_pdu_t *pdu) { coap_delete_pdu(pdu); return id; } - coap_queue_t *node = coap_new_node(); if (!node) { coap_log(LOG_DEBUG, "coap_wait_ack: insufficient memory\n"); @@ -1408,16 +1487,14 @@ void coap_io_do_io(coap_context_t *ctx, coap_tick_t now) { return; } -#else /* WITH_LWIP */ +#else /* ! WITH_LWIP */ static int coap_handle_dgram_for_proto(coap_context_t *ctx, coap_session_t *session, coap_packet_t *packet) { uint8_t *data; size_t data_len; int result = -1; - coap_packet_get_memmapped(packet, &data, &data_len); - if (session->proto == COAP_PROTO_DTLS) { #if COAP_SERVER_SUPPORT if (session->type == COAP_SESSION_TYPE_HELLO) @@ -1677,7 +1754,6 @@ coap_read_endpoint(coap_context_t *ctx, coap_endpoint_t *endpoint, coap_tick_t n coap_packet_t e_packet; #endif /* ! COAP_CONSTRAINED_STACK */ coap_packet_t *packet = &e_packet; - assert(COAP_PROTO_NOT_RELIABLE(endpoint->proto)); assert(endpoint->sock.flags & COAP_SOCKET_BOUND); @@ -1690,7 +1766,6 @@ coap_read_endpoint(coap_context_t *ctx, coap_endpoint_t *endpoint, coap_tick_t n coap_address_init(&packet->addr_info.remote); coap_address_copy(&packet->addr_info.local, &endpoint->bind_addr); bytes_read = ctx->network_read(&endpoint->sock, packet); - if (bytes_read < 0) { coap_log(LOG_WARNING, "* %s: read failed\n", coap_endpoint_str(endpoint)); } else if (bytes_read > 0) { @@ -2925,6 +3000,8 @@ handle_response(coap_context_t *context, coap_session_t *session, return; } } + if (session->doing_first) + session->doing_first = 0; /* Call application-specific response handler when available. */ if (context->response_handler) { @@ -2984,12 +3061,66 @@ handle_signaling(coap_context_t *context, coap_session_t *session, void coap_dispatch(coap_context_t *context, coap_session_t *session, - coap_pdu_t *pdu) { + coap_pdu_t *pdu) { coap_queue_t *sent = NULL; coap_pdu_t *response; coap_opt_filter_t opt_filter; int is_ping_rst; +#ifdef HAVE_OSCORE + coap_opt_iterator_t opt_iter; + coap_pdu_t *dec_pdu = NULL; + + if (coap_check_option(pdu, COAP_OPTION_OSCORE, &opt_iter) != NULL) { + int decrypt = 1; +#if COAP_SERVER_SUPPORT + coap_opt_t *opt; + coap_resource_t *resource; + coap_uri_t uri; +#endif /* COAP_SERVER_SUPPORT */ + if (COAP_PDU_IS_RESPONSE(pdu) && !session->oscore_encryption) + decrypt = 0; + +#if COAP_SERVER_SUPPORT + if (COAP_PDU_IS_REQUEST(pdu) && + coap_check_option(pdu, COAP_OPTION_PROXY_SCHEME, &opt_iter) != NULL && + (opt = coap_check_option(pdu, COAP_OPTION_URI_HOST, &opt_iter)) + != NULL) { + /* Need to check whether this is a direct or proxy session */ + memset(&uri, 0, sizeof(uri)); + uri.host.length = coap_opt_length(opt); + uri.host.s = coap_opt_value(opt); + resource = context->proxy_uri_resource; + if (uri.host.length && resource->proxy_name_count && + resource->proxy_name_list) { + size_t i; + for (i = 0; i < resource->proxy_name_count; i++) { + if (coap_string_equal(&uri.host, resource->proxy_name_list[i])) { + break; + } + } + if (i == resource->proxy_name_count) { + /* This server is not hosting the proxy connection endpoint */ + decrypt = 0; + } + } + } +#endif /* COAP_SERVER_SUPPORT */ + if (decrypt) { + if ((dec_pdu = coap_oscore_decrypt_pdu(session, pdu)) == NULL) { + if (session->recipient_ctx == 0 || + session->recipient_ctx->initial_state == 0) { + coap_log(LOG_WARNING, "OSCORE: PDU could not be decrypted\n"); + } + return; + } + else { + session->oscore_encryption = 1; + pdu = dec_pdu; + } + } + } +#endif /* HAVE_OSCORE */ if (LOG_DEBUG <= coap_get_log_level()) { /* FIXME: get debug to work again ** unsigned char addr[INET6_ADDRSTRLEN+8], localaddr[INET6_ADDRSTRLEN+8]; @@ -3173,6 +3304,9 @@ coap_dispatch(coap_context_t *context, coap_session_t *session, cleanup: coap_delete_node(sent); +#ifdef HAVE_OSCORE + coap_delete_pdu(dec_pdu); +#endif /* HAVE_OSCORE */ } int diff --git a/src/oscore/oscore.c b/src/oscore/oscore.c new file mode 100644 index 0000000000..fe7fc43ae0 --- /dev/null +++ b/src/oscore/oscore.c @@ -0,0 +1,587 @@ +/* + * Copyright (c) 2018, SICS, RISE AB + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + */ + +/** + * @file oscore.c + * @brief An implementation of the Object Security for Constrained RESTful Enviornments (RFC 8613). + * \author + * Martin Gunnarsson + * adapted to libcoap and major rewrite + * Peter van der Stok + * on request of Fairhair alliance + * adapted for libcoap integration + * Jon Shallow + * + */ + +#include "coap3/coap_internal.h" +#include "oscore/oscore.h" +#include "oscore/oscore_context.h" +#include "oscore/oscore_crypto.h" +#include "oscore/oscore_cbor.h" +#include "stdio.h" +#include +#ifdef HAVE_OSCORE_GROUP +#include "oscore_group_crypto/ed25519.h" +#endif /* HAVE_OSCORE_GROUP */ + +#define AAD_BUF_LEN 60 /* length of aad_buffer */ +#define MAX_IV_LEN 10 /* maximum length of iv buffer */ + +/* oscore_cs_params + * returns cbor array [[param_type], [paramtype, param]] + */ +uint8_t * +oscore_cs_params(int8_t param, int8_t param_type, size_t *len) +{ + uint8_t buf[50]; + size_t rem_size = sizeof(buf); + uint8_t *pt = buf; + + *len = 0; + *len += oscore_cbor_put_array(&pt, &rem_size, 2); + *len += oscore_cbor_put_array(&pt, &rem_size, 1); + *len += oscore_cbor_put_number(&pt, &rem_size, param_type); + *len += oscore_cbor_put_array(&pt, &rem_size, 2); + *len += oscore_cbor_put_number(&pt, &rem_size, param_type); + *len += oscore_cbor_put_number(&pt, &rem_size, param); + uint8_t *result = coap_malloc(*len); + memcpy(result, buf, *len); + return result; +} + +/* oscore_cs_key_params + * returns cbor array [paramtype, param] + */ +uint8_t * +oscore_cs_key_params(int8_t param, int8_t param_type, size_t *len) +{ + uint8_t buf[50]; + size_t rem_size = sizeof(buf); + uint8_t *pt = buf; + + *len = 0; + *len += oscore_cbor_put_array(&pt, &rem_size, 2); + *len += oscore_cbor_put_number(&pt, &rem_size, param_type); + *len += oscore_cbor_put_number(&pt, &rem_size, param); + uint8_t *result = coap_malloc(*len); + memcpy(result, buf, *len); + return result; +} + +#ifdef HAVE_OSCORE_GROUP +/* extract_param + * extract algorithm paramater from [type, param] + */ +static int +extract_param(const uint8_t *oscore_cbor_array){ + int64_t mm = 0; + uint8_t elem = oscore_cbor_get_next_element(&oscore_cbor_array); + if (elem == CBOR_ARRAY){ + uint64_t arr_size = oscore_cbor_get_element_size(&oscore_cbor_array); + if (arr_size != 2) return 0; + for (uint16_t i=0; i < arr_size; i++){ + int8_t ok = oscore_cbor_get_number(&oscore_cbor_array, &mm); + if (ok != 0)return 0; + } + return (int)mm; + } + return 0; +} + + +/* extract_type + * extract algorithm paramater from [type, param] + */ +static int +extract_type(const uint8_t *oscore_cbor_array){ + int64_t mm = 0; + uint8_t elem = oscore_cbor_get_next_element(&oscore_cbor_array); + if (elem == CBOR_ARRAY){ + uint64_t arr_size = oscore_cbor_get_element_size(&oscore_cbor_array); + if (arr_size != 2) return 0; + if(oscore_cbor_get_number(&oscore_cbor_array, &mm) == 1) return 0; + return (int)mm; + } + return 0; +} +#endif /* HAVE_OSCORE_GROUP */ + +/* + * Build the CBOR for external_aad + * + * external_aad = bstr .cbor aad_array + * + * No group mode + * aad_array = [ + * oscore_version : uint, + * algorithms : [ alg_aead : int / tstr ], + * request_kid : bstr, + * request_piv : bstr, + * options : bstr, + * ] + * + * Group mode + * aad_array = [ + * oscore_version : uint, + * algorithms : [alg_aead : int / tstr / null, + * alg_signature_enc : int / tstr / null, + * alg_signature : int / tstr / null, + * alg_pairwise_key_agreement : int / tstr / null], + * request_kid : bstr, + * request_piv : bstr, + * options : bstr, + * request_kid_context : bstr, + * OSCORE_option: bstr, + * sender_public_key: bstr, (initiator's key) + * gm_public_key: bstr / null + * ] + */ +size_t +oscore_prepare_e_aad(oscore_ctx_t *ctx, cose_encrypt0_t *cose, + const uint8_t *oscore_option, size_t oscore_option_len, + coap_bin_const_t *sender_public_key, + uint8_t *external_aad_ptr, size_t external_aad_size) +{ + size_t external_aad_len = 0; + size_t rem_size = external_aad_size; + +#ifndef HAVE_OSCORE_GROUP + (void)oscore_option; + (void)oscore_option_len; + (void)sender_public_key; +#endif /* ! HAVE_OSCORE_GROUP */ + + if (ctx->mode != COAP_OSCORE_MODE_SINGLE) + external_aad_len += oscore_cbor_put_array(&external_aad_ptr, &rem_size, 9); + else + external_aad_len += oscore_cbor_put_array(&external_aad_ptr, &rem_size, 5); + + + /* oscore_version, always "1" */ + external_aad_len += oscore_cbor_put_unsigned(&external_aad_ptr, &rem_size, 1); + + if (ctx->mode == COAP_OSCORE_MODE_SINGLE) { + /* Algoritms array with one item*/ + external_aad_len += oscore_cbor_put_array(&external_aad_ptr, &rem_size, 1); + /* Encryption Algorithm */ + external_aad_len += oscore_cbor_put_number(&external_aad_ptr, &rem_size, + ctx->aead_alg); + } +#ifdef HAVE_OSCORE_GROUP + else { + /* Algoritms array with 4 items */ + external_aad_len += oscore_cbor_put_array(&external_aad_ptr, &rem_size, 4); + + if (ctx->mode == COAP_OSCORE_MODE_PAIRWISE) { + /* alg_aead */ + external_aad_len += oscore_cbor_put_number(&external_aad_ptr, &rem_size, + ctx->aead_alg); + /* alg_signature_enc */ + external_aad_len += oscore_cbor_put_nil(&external_aad_ptr, &rem_size); + /* alg_signature */ + external_aad_len += oscore_cbor_put_nil(&external_aad_ptr, &rem_size); + /* alg_pairwise_key_agreement */ + external_aad_len += oscore_cbor_put_number(&external_aad_ptr, &rem_size, + ctx->pairwise_agree_alg); + } + else { + /* alg_aead */ + external_aad_len += oscore_cbor_put_nil(&external_aad_ptr, &rem_size); + /* alg_signature_enc */ + external_aad_len += oscore_cbor_put_number(&external_aad_ptr, &rem_size, + ctx->sign_enc_alg); + /* alg_signature */ + external_aad_len += oscore_cbor_put_number(&external_aad_ptr, &rem_size, + ctx->sign_alg); + /* alg_pairwise_key_agreement */ + external_aad_len += oscore_cbor_put_nil(&external_aad_ptr, &rem_size); + } + } +#endif /* HAVE_OSCORE_GROUP */ + /* request_kid */ + external_aad_len += oscore_cbor_put_bytes(&external_aad_ptr, &rem_size, + cose->key_id.s, cose->key_id.length); + /* request_piv */ + external_aad_len += oscore_cbor_put_bytes(&external_aad_ptr, &rem_size, + cose->partial_iv.s, cose->partial_iv.length); + /* options */ + /* Put integrity protected options, at present there are none. */ + external_aad_len += oscore_cbor_put_bytes(&external_aad_ptr, &rem_size, + NULL, 0); + +#ifdef HAVE_OSCORE_GROUP + if (ctx->mode != COAP_OSCORE_MODE_SINGLE) { + /* request_kid_context */ + external_aad_len += oscore_cbor_put_bytes(&external_aad_ptr, &rem_size, + ctx->id_context->s, + ctx->id_context->length); + /* OSCORE_option */ + external_aad_len += oscore_cbor_put_bytes(&external_aad_ptr, &rem_size, + oscore_option, oscore_option_len); + /* sender_public_key */ + external_aad_len += oscore_cbor_put_bytes(&external_aad_ptr, &rem_size, + sender_public_key->s, + sender_public_key->length); + /* gm_public_key */ + if (ctx->gm_public_key) + external_aad_len += oscore_cbor_put_bytes(&external_aad_ptr, &rem_size, + ctx->gm_public_key->s, + ctx->gm_public_key->length); + else + external_aad_len += oscore_cbor_put_nil(&external_aad_ptr, &rem_size); + } +#endif /* HAVE_OSCORE_GROUP */ + return external_aad_len; +} + +// +// oscore_encode_option_value +// +size_t +oscore_encode_option_value(uint8_t *option_buffer, cose_encrypt0_t *cose, + uint8_t group_flag) +{ + size_t offset = 1; + +#ifndef HAVE_OSCORE_GROUP + (void)group_flag; +#endif /* ! HAVE_OSCORE_GROUP */ + if(cose->partial_iv.length > 5){ + return 0; + } +#ifdef HAVE_OSCORE_GROUP + if (group_flag == 1) { + option_buffer[0] = 0x20; + cose->group_flag = 1; + } + else +#endif /* HAVE_OSCORE_GROUP */ + option_buffer[0] = 0; + + if (cose->partial_iv.length > 0 && cose->partial_iv.length <= 5 && + cose->partial_iv.s != NULL) { + option_buffer[0] |= (0x07 & cose->partial_iv.length); + memcpy(&(option_buffer[offset]), cose->partial_iv.s, + cose->partial_iv.length); + offset += cose->partial_iv.length; + } + + if(cose->kid_context.length > 0 && cose->kid_context.s != NULL) { + option_buffer[0] |= 0x10; + option_buffer[offset] = (uint8_t)cose->kid_context.length; + offset++; + memcpy(&(option_buffer[offset]), cose->kid_context.s, + (uint8_t)cose->kid_context.length); + offset += cose->kid_context.length; + } + + if (cose->key_id.s != NULL) { + option_buffer[0] |= 0x08; + if (cose->key_id.length) { + memcpy(&(option_buffer[offset]), cose->key_id.s, cose->key_id.length); + offset += cose->key_id.length; + } + } + + if(offset == 1 && option_buffer[0] == 0) { + /* If option_value is 0x00 it should be empty. */ + offset = 0; + } + cose->oscore_option.s = option_buffer; + cose->oscore_option.length = offset; + return offset; +} + + +/* + * oscore_decode_option_value + * error: return 0 + * OK: return 1 + * + * Basic assupmption is that all is preset to 0 or NULL on entry + */ +int +oscore_decode_option_value(const uint8_t *opt_value, size_t option_len, + cose_encrypt0_t *cose) +{ + uint8_t partial_iv_len = (opt_value[0] & 0x07); + size_t offset = 1; + + cose->oscore_option.s = opt_value; + cose->oscore_option.length = option_len; + + if (option_len == 0) + return 1; /* empty option */ + + if (option_len > 255 || partial_iv_len == 6 || + partial_iv_len == 7 || (opt_value[0] & 0xC0) != 0) { + return 0; + } + + if ((opt_value[0] & 0x20) != 0) { +#ifdef HAVE_OSCORE_GROUP + cose->group_flag = 1; + } + else { + cose->group_flag = 0; +#else /* ! HAVE_OSCORE_GROUP */ + return 0; +#endif /* ! HAVE_OSCORE_GROUP */ + } + + if(partial_iv_len != 0) { + coap_bin_const_t partial_iv; + if( offset + partial_iv_len > option_len) { + return 0; + } + partial_iv.s = &(opt_value[offset]); + partial_iv.length = partial_iv_len; + cose_encrypt0_set_partial_iv(cose, &partial_iv); + offset += partial_iv_len; + } + + if((opt_value[0] & 0x10) != 0) { + coap_bin_const_t kid_context; + + kid_context.length = opt_value[offset]; + offset++; + if (offset + kid_context.length > option_len) { + return 0; + } + kid_context.s = &(opt_value[offset]); + cose_encrypt0_set_kid_context(cose, &kid_context); + offset = offset + kid_context.length; + } + + if((opt_value[0] & 0x08) != 0) { + coap_bin_const_t key_id; + + key_id.length = option_len - offset; + if ((int)key_id.length < 0) { + return 0; + } + key_id.s = &(opt_value[offset]); + cose_encrypt0_set_key_id(cose, &key_id); + } + return 1; +} + +#ifdef HAVE_OSCORE_GROUP +/* Sets alg and keys in COSE SIGN */ +void +oscore_populate_sign(cose_sign1_t *sign, oscore_ctx_t *ctx, + coap_bin_const_t *public_key, + coap_bin_const_t *private_key) +{ + cose_sign1_set_alg(sign, ctx->sign_alg, + extract_param(ctx->sign_params->s), + extract_type(ctx->sign_params->s)); + + if (private_key) + cose_sign1_set_private_key(sign, private_key); + + cose_sign1_set_public_key(sign, public_key); +} + +// +// oscore_prepare_sig_structure +// creates and sets structure to be signed +size_t +oscore_prepare_sig_structure(uint8_t *sig_ptr, size_t sig_size, + const uint8_t *e_aad_buffer, uint16_t e_aad_len, + const uint8_t *text, uint16_t text_len) +{ + size_t sig_len = 0; + size_t rem_size = sig_size; + char countersig0[] = "CounterSignature0"; + + (void)sig_size; + sig_len += oscore_cbor_put_array(&sig_ptr, &rem_size, 5); + /* 1. "CounterSignature0" */ + sig_len += oscore_cbor_put_text(&sig_ptr, &rem_size, + countersig0, strlen(countersig0)); + /* 2. Protected attributes from target structure */ + sig_len += oscore_cbor_put_bytes(&sig_ptr, &rem_size, NULL, 0); + /* 3. Protected attributes from signer structure */ + sig_len += oscore_cbor_put_bytes(&sig_ptr, &rem_size, NULL, 0); + /* 4. External AAD */ + sig_len += oscore_cbor_put_bytes(&sig_ptr, &rem_size, + e_aad_buffer, e_aad_len); + /* 5. Payload */ + sig_len += oscore_cbor_put_bytes(&sig_ptr, &rem_size, text, text_len); + return sig_len; +} +#endif /* HAVE_OSCORE_GROUP */ + +// +// oscore_prepare_aad +/* Creates and sets External AAD for encryption */ +size_t +oscore_prepare_aad(const uint8_t *external_aad_buffer, size_t external_aad_len, + uint8_t *aad_buffer, size_t aad_size) +{ + size_t ret = 0; + size_t rem_size = aad_size; + char encrypt0[] = "Encrypt0"; + + (void)aad_size; /* TODO */ + /* Creating the AAD */ + ret += oscore_cbor_put_array(&aad_buffer, &rem_size, 3); + /* 1. "Encrypt0" */ + ret += oscore_cbor_put_text(&aad_buffer, &rem_size, + encrypt0, strlen(encrypt0)); + /* 2. Empty h'' entry */ + ret += oscore_cbor_put_bytes(&aad_buffer, &rem_size, NULL, 0); + /* 3. External AAD */ + ret += oscore_cbor_put_bytes(&aad_buffer, &rem_size, + external_aad_buffer, external_aad_len); + + return ret; +} + +// +// oscore_generate_nonce +/* Creates Nonce */ +void +oscore_generate_nonce(cose_encrypt0_t *ptr, oscore_ctx_t *ctx, + uint8_t *buffer, uint8_t size) +{ + memset(buffer, 0, size); + buffer[0] = (uint8_t)(ptr->key_id.length); + memcpy(&(buffer[((size - 5) - ptr->key_id.length)]), ptr->key_id.s, + ptr->key_id.length); + memcpy(&(buffer[size - ptr->partial_iv.length]), ptr->partial_iv.s, + ptr->partial_iv.length); + for(int i = 0; i < size; i++) { + buffer[i] = buffer[i] ^ (uint8_t)ctx->common_iv->s[i]; + } +} + + +// +// oscore_validate_sender_seq +// +/*Return 1 if OK, 0 otherwise */ +uint8_t +oscore_validate_sender_seq(oscore_recipient_ctx_t *ctx, cose_encrypt0_t *cose) +{ + uint64_t incoming_seq = coap_decode_var_bytes8(cose->partial_iv.s, + cose->partial_iv.length); + + if (incoming_seq >= OSCORE_SEQ_MAX) { + coap_log(LOG_WARNING, + "OSCORE Replay protection, SEQ larger than SEQ_MAX.\n"); + return 0; + } + + ctx->rollback_last_seq = ctx->last_seq; + ctx->rollback_sliding_window = ctx->sliding_window; + + /* Special case since we do not use unisgned int for seq */ + if (ctx->initial_state == 1) { + ctx->initial_state = 0; + /* bitfield. B0 biggest seq seen. B1 seq-1 seen, B2 seq-2 seen etc. */ + ctx->sliding_window = 1; + ctx->last_seq = incoming_seq; + } + else if (incoming_seq > ctx->last_seq) { + /* Update the replay window */ + size_t shift = incoming_seq - ctx->last_seq; + ctx->sliding_window = ctx->sliding_window << shift; + /* bitfield. B0 biggest seq seen. B1 seq-1 seen, B2 seq-2 seen etc. */ + ctx->sliding_window |= 1; + ctx->last_seq = incoming_seq; + } + else if (incoming_seq == ctx->last_seq) { + coap_log(LOG_WARNING, "OSCORE: Replay protextion, replayed SEQ (%lu)\n", + incoming_seq); + return 0; + } + else { /* incoming_seq < last_seq */ + size_t shift = ctx->last_seq - incoming_seq - 1; + uint64_t pattern; + + if (shift > ctx->common_ctx->replay_window_size || shift > 63 ) { + coap_log(LOG_WARNING, + "OSCORE: Replay protection, SEQ outside of replay window (%lu %lu)\n", + ctx->last_seq, incoming_seq); + return 0; + } + /* seq + replay_window_size > last_seq */ + pattern = 1ULL << shift; + if (ctx->sliding_window & pattern) { + coap_log(LOG_WARNING,"OSCORE: Replay protection, replayed SEQ (%lu)\n", + incoming_seq); + return 0; + } + /* bitfield. B0 biggest seq seen. B1 seq-1 seen, B2 seq-2 seen etc. */ + ctx->sliding_window |= pattern; + } + coap_log(COAP_LOG_CIPHERS, "OSCORE: window 0x%lx seq-B0 %lu SEQ %lu\n", + ctx->sliding_window, ctx->last_seq, incoming_seq); + return 1; +} + +// +// oscore_increment_sender_seq +// +/* Return 0 if SEQ MAX, return 1 if OK */ +uint8_t +oscore_increment_sender_seq(oscore_ctx_t *ctx) +{ + ctx->sender_context->seq++; + + if(ctx->sender_context->seq >= OSCORE_SEQ_MAX) { + return 0; + } else { + return 1; + } +} + +// +// oscore_roll_back_seq +/* Restore the sequence number and replay-window to the previous state. This + * is to be used when decryption fail. */ +void +oscore_roll_back_seq(oscore_recipient_ctx_t *ctx) +{ + + if(ctx->rollback_sliding_window != 0) { + ctx->sliding_window = ctx->rollback_sliding_window; + ctx->rollback_sliding_window = 0; + } + if(ctx->rollback_last_seq != 0) { + ctx->last_seq = ctx->rollback_last_seq; + ctx->rollback_last_seq = 0; + } +} + + diff --git a/src/oscore/oscore_cbor.c b/src/oscore/oscore_cbor.c new file mode 100644 index 0000000000..b70f1414e6 --- /dev/null +++ b/src/oscore/oscore_cbor.c @@ -0,0 +1,428 @@ +/* + * Copyright (c) 2018, SICS, RISE AB + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + */ + +/** + * @file oscore_cbor.c + * @brief An implementation of the Concise Binary Object Representation (RFC). + * \author + * Martin Gunnarsson + * extended for libcoap by Peter van der Stok + * + * on request of Fairhair alliance. + * adapted for libcoap integration + * Jon Shallow + * + */ + +#include "coap3/coap_internal.h" +#include "oscore/oscore_cbor.h" +#include +#include +#include + + +size_t +oscore_cbor_put_nil(uint8_t **buffer, size_t *buf_size){ + assert(*buf_size >= 1); + (*buf_size)--; + **buffer = 0xF6; + (*buffer)++; + return 1; +} + +size_t +oscore_cbor_put_true(uint8_t **buffer, size_t *buf_size){ + assert(*buf_size >= 1); + (*buf_size)--; + **buffer = 0xF5; + (*buffer)++; + return 1; +} + +size_t +oscore_cbor_put_false(uint8_t **buffer, size_t *buf_size){ + assert(*buf_size >= 1); + (*buf_size)--; + **buffer = 0xF4; + (*buffer)++; + return 1; +} + +size_t +oscore_cbor_put_text(uint8_t **buffer, size_t *buf_size, + const char *text, uint64_t text_len) +{ + uint8_t *pt = *buffer; + size_t nb = oscore_cbor_put_unsigned(buffer, buf_size, text_len); + assert(*buf_size >= text_len); + (*buf_size) -= text_len; + *pt = (*pt | 0x60); + memcpy(*buffer, text, text_len); + (*buffer) += text_len; + return nb + text_len; +} + +size_t +oscore_cbor_put_array(uint8_t **buffer, size_t *buf_size, uint64_t elements) +{ + uint8_t *pt = *buffer; + size_t nb = oscore_cbor_put_unsigned(buffer, buf_size, elements); + *pt = (*pt | 0x80); + return nb; +} + +size_t +oscore_cbor_put_bytes(uint8_t **buffer, size_t *buf_size, const uint8_t *bytes, uint64_t bytes_len) +{ + uint8_t *pt = *buffer; + size_t nb = oscore_cbor_put_unsigned(buffer, buf_size, bytes_len); + assert(*buf_size >= bytes_len); + (*buf_size) -= bytes_len; + *pt = (*pt | 0x40); + memcpy(*buffer, bytes, bytes_len); + (*buffer) += bytes_len; + return nb + bytes_len; +} + +size_t +oscore_cbor_put_map(uint8_t **buffer, size_t *buf_size, uint64_t elements) +{ + uint8_t *pt = *buffer; + size_t nb = oscore_cbor_put_unsigned(buffer, buf_size, elements); + *pt = (*pt | 0xa0); + return nb; +} + +size_t +oscore_cbor_put_number(uint8_t **buffer, size_t *buf_size, int64_t value) +{ + if (value < 0) + return oscore_cbor_put_negative(buffer, buf_size, -value); + else + return oscore_cbor_put_unsigned(buffer, buf_size, value); +} + +size_t oscore_cbor_put_simple_value(uint8_t **buffer, size_t *buf_size, + uint8_t value) +{ + uint8_t *pt = *buffer; + size_t nb = oscore_cbor_put_unsigned(buffer, buf_size, value); + *pt = (*pt | 0xe0); + return nb; +} + +size_t +oscore_cbor_put_tag(uint8_t **buffer, size_t *buf_size, uint64_t value) +{ + uint8_t *pt = *buffer; + size_t nb = oscore_cbor_put_unsigned(buffer, buf_size, value); + *pt = (*pt | 0xc0); + return nb; +} + + +size_t +oscore_cbor_put_negative(uint8_t **buffer, size_t *buf_size, int64_t value) +{ + value--; + uint8_t *pt = *buffer; + size_t nb = oscore_cbor_put_unsigned(buffer, buf_size, value); + *pt = (*pt | 0x20); + return nb; +} + +static void +put_b_f(uint8_t **buffer, uint64_t value, uint8_t nr) +{ + uint8_t *pt = *buffer-1; + uint64_t vv = value; + for (int q = nr; q > -1; q--){ + (*pt--) = (uint8_t)( vv & 0xff); + vv = (vv >>8); + } +} + +size_t +oscore_cbor_put_unsigned(uint8_t **buffer, size_t *buf_size, uint64_t value) +{ + if (value < 0x18 ) { /* small value half a byte */ + assert(*buf_size >= 1); + (*buf_size)--; + (**buffer) = (uint8_t)value; + (*buffer)++; + return 1; + } else if ((value > 0x17) && (value < 0x100)) { +/* one byte uint8_t */ + assert(*buf_size >= 2); + (*buf_size) -= 2; + (**buffer) = (0x18); + *buffer = (*buffer) + 2; + put_b_f(buffer, value, 0); + return 2; + } else if((value > 0xff) && (value < 0x10000)){ +/* 2 bytes uint16_t */ + assert(*buf_size >= 3); + (*buf_size) -= 3; + (**buffer) = (0x19); + *buffer = (*buffer) + 3; + put_b_f(buffer, value, 1); + return 3; + } else if((value > 0xffff) && (value < 0x100000000)){ +/* 4 bytes uint32_t */ + assert(*buf_size >= 5); + (*buf_size) -= 5; + (**buffer) = (0x1a); + *buffer = (*buffer) + 5; + put_b_f(buffer, value, 3); + return 5; + } else /*if(value > 0xffffffff)*/{ +/* 8 bytes uint64_t */ + assert(*buf_size >= 9); + (*buf_size) -= 9; + (**buffer) = (0x1b); + *buffer = (*buffer) + 9; + put_b_f(buffer, value, 7); + return 9; + } +} + + +/* temporary routine to read ascii hex dump */ +static uint8_t +gethex_byte(const uint8_t *buffer){ +/* + uint8_t temp1 = *buffer; + if ('0' <= temp1 && temp1 <= '9') temp1 = temp1 - '0'; + else if ('A' <= temp1 && temp1 <= 'F') + temp1 = 10 + temp1 - 'A'; + else if ('a' <= temp1 && temp1 <= 'f') + temp1 = 10 + temp1 - 'a'; + else return 255; + uint8_t temp2 = *buffer; + if ('0' <= temp2 && temp2 <= '9') temp2 = temp2 - '0'; + else if ('A' <= temp2 && temp2 <= 'F') + temp2 = 10 + temp2 - 'A'; + else if ('a' <= temp2 && temp2 <= 'f') + temp2 = 10 + temp2 - 'a'; + else return 255; + return (temp1<<4) + temp2; +*/ + return *buffer; +} + +uint8_t +oscore_cbor_get_next_element(const uint8_t **buffer){ + uint8_t element = gethex_byte(*buffer); + return element>>5; +} + +/* oscore_cbor_get_element_size returns + * - size of byte strings of character strings + * - size of array + * - size of map + * - value of unsigned integer + */ + +uint64_t +oscore_cbor_get_element_size(const uint8_t **buffer){ + uint8_t control = gethex_byte((*buffer)) & 0x1f; + uint64_t size = gethex_byte((*buffer)++); + if (control < 0x18) size = (uint64_t)control; + else { + control = control & 0x3; + int num = 1 << control; + size = 0; + uint64_t getal; + for (int i = 0; i < num; i++){ + getal = (uint64_t)gethex_byte((*buffer)++); + size = (size<<8) + getal; + } + } + return size; +} + +uint8_t +oscore_cbor_elem_contained(const uint8_t *data, uint8_t *end){ + const uint8_t *buf = data; + const uint8_t *last = data + oscore_cbor_get_element_size(&buf); + if (last > end){ + fprintf(stderr,"oscore_cbor_elem_contained returns 1 \n"); + return 1; + } + else return 0; +} + +int64_t +oscore_cbor_get_negative_integer(const uint8_t **buffer){ + return -(int64_t)(oscore_cbor_get_element_size(buffer) -1); +} + +uint64_t +oscore_cbor_get_unsigned_integer(const uint8_t **buffer){ + return oscore_cbor_get_element_size(buffer); +} + + +// oscore_cbor_get_number +// gets a negative or positive number from data +// OK: return 0 ; NOK: return 1 +uint8_t +oscore_cbor_get_number(const uint8_t **data, int64_t *value){ + uint8_t elem = oscore_cbor_get_next_element(data); + if (elem == CBOR_UNSIGNED_INTEGER){ + *value = oscore_cbor_get_unsigned_integer(data); + return 0; + } + else if (elem == CBOR_NEGATIVE_INTEGER){ + *value = oscore_cbor_get_negative_integer(data); + return 0; + } + else return 1; +} + +// oscore_cbor_get_simple_value +// gets a simple value from data +// OK: return 0 ; NOK: return 1 +uint8_t +oscore_cbor_get_simple_value(const uint8_t **data, uint8_t *value){ + uint8_t elem = oscore_cbor_get_next_element(data); + if (elem == CBOR_SIMPLE_VALUE){ + *value = gethex_byte((*data)++) & 0x1f; + return 0; + } + else return 1; +} + +void +oscore_cbor_get_string(const uint8_t **buffer, char *str, uint64_t size){ + for( uint64_t i=0; i < size; i++){ + *str++ = (char)gethex_byte((*buffer)++); + } +} + +void +oscore_cbor_get_array(const uint8_t **buffer, uint8_t *arr, uint64_t size){ + for( uint64_t i=0; i < size; i++){ + *arr++ = gethex_byte((*buffer)++); + } +} + +/* oscore_cbor_get_string_array + * fills the the size and the array from the cbor element + */ +uint8_t +oscore_cbor_get_string_array(const uint8_t **data, uint8_t **result, size_t *len){ + + uint8_t elem = oscore_cbor_get_next_element(data); + *len = oscore_cbor_get_element_size(data); + *result = NULL; + void *rs = coap_malloc( *len); + *result = (uint8_t *) rs; + if (elem == CBOR_TEXT_STRING){ + oscore_cbor_get_string(data, (char *)*result, *len); + return 0; + } + else if (elem == CBOR_BYTE_STRING){ + oscore_cbor_get_array(data, *result, *len); + return 0; /* all is well */ + } + else { + free(*result); + *result = NULL; + return 1; /* failure */ + } +} + +/* oscore_cbor_skip value + * returns number of CBOR bytes + */ +static size_t +oscore_cbor_skip_value(const uint8_t **data){ + uint8_t elem = oscore_cbor_get_next_element(data); + uint8_t control = gethex_byte((*data)) & 0x1f; + size_t nb = 0; /* number of elements in array or map */ + size_t num = 0; /* number of bytes of length or number */ + size_t size = 0; /* size of value to be skipped */ + if (control < 0x18) num = 1; + else { + control = control & 0x3; + num = 1 + (1 << control); + } + switch (elem){ + case CBOR_UNSIGNED_INTEGER: + case CBOR_NEGATIVE_INTEGER: + *data = *data + num; + size = num; + break; + case CBOR_BYTE_STRING: + case CBOR_TEXT_STRING: + size = num; + size += oscore_cbor_get_element_size(data); + (*data) = (*data) + size - num; + break; + case CBOR_ARRAY: + nb = oscore_cbor_get_element_size(data); + size = num; + for (uint16_t qq = 0; qq < nb; qq++) + size += oscore_cbor_skip_value(data); + break; + case CBOR_MAP: + nb = oscore_cbor_get_element_size(data); + size = num; + for (uint16_t qq = 0; qq < nb; qq++){ + size += oscore_cbor_skip_value(data); + size += oscore_cbor_skip_value(data); + } + break; + case CBOR_TAG: + (*data)++; + size = 1; + break; + default: + return 0; + break; + } /* switch */ + return size; +} + +/* oscore_cbor_strip value + * strips the value of the cbor element into result + * and returns size + */ +uint8_t +oscore_cbor_strip_value(const uint8_t **data, uint8_t **result, size_t *len){ + const uint8_t *st_data = *data; + size_t size = oscore_cbor_skip_value(data); + *result = coap_malloc(size); + for (uint16_t qq = 0; qq < size; qq++)(*result)[qq] = st_data[qq]; + *len = size; + return 0; +} + diff --git a/src/oscore/oscore_context.c b/src/oscore/oscore_context.c new file mode 100644 index 0000000000..eed0e2756e --- /dev/null +++ b/src/oscore/oscore_context.c @@ -0,0 +1,697 @@ +/* + * Copyright (c) 2018, SICS, RISE AB + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + */ + +/** + * @file oscore_context.c + * @brief An implementation of the Object Security for Constrained RESTful Enviornments (RFC 8613). + * \author + * Martin Gunnarsson + * adapted for libcoap + * Peter van der Stok + * on request of Fairhair alliance + * adapted for libcoap integration + * Jon Shallow + */ + +#include "coap3/coap_internal.h" +#include "oscore/oscore_context.h" +#include +#include +#include "oscore/oscore_cbor.h" +#include +#include "oscore/oscore_crypto.h" +#include "oscore/oscore.h" + +#include + +static size_t +compose_info(uint8_t *buffer, size_t buf_size, uint8_t alg, + coap_bin_const_t *id, coap_bin_const_t *id_context, + coap_str_const_t *type, size_t out_len) +{ + size_t ret = 0; + size_t rem_size = buf_size; + + ret += oscore_cbor_put_array(&buffer, &rem_size, 5); + ret += oscore_cbor_put_bytes(&buffer, &rem_size, + id ? id->s : NULL, id ? id->length : 0); + if (id_context && id_context->length + 12 > 30){ + coap_log(LOG_WARNING,"compose_info buffer overflow.\n"); + return 0; + } + if (id_context != NULL && id_context->length > 0){ + ret += oscore_cbor_put_bytes(&buffer, &rem_size, + id_context->s, id_context->length); + } else { + ret += oscore_cbor_put_nil(&buffer, &rem_size); + } + ret += oscore_cbor_put_unsigned(&buffer, &rem_size, alg); + ret += oscore_cbor_put_text(&buffer, &rem_size, + (const char *)type->s, type->length); + ret += oscore_cbor_put_unsigned(&buffer, &rem_size, out_len); + return ret; +} + +#ifdef HAVE_OSCORE_GROUP +int +oscore_derive_keystream(oscore_ctx_t *osc_ctx, cose_encrypt0_t *cose, + uint8_t coap_request, + coap_bin_const_t *sender_id, + coap_bin_const_t *id_context, size_t cs_size, + uint8_t *keystream, size_t keystream_size) +{ + uint8_t info_buffer[30]; + uint8_t *buffer = info_buffer; + size_t info_len = 0; + size_t rem_size = sizeof(info_buffer);; + + info_len += oscore_cbor_put_array(&buffer, &rem_size, 4); + /* 1. id */ + info_len += oscore_cbor_put_bytes(&buffer, &rem_size, + sender_id->s, sender_id->length); + /* 2. id_context */ + info_len += oscore_cbor_put_bytes(&buffer, &rem_size, + id_context->s, id_context->length); + /* 3. type */ + if (coap_request) + info_len += oscore_cbor_put_true(&buffer, &rem_size); + else + info_len += oscore_cbor_put_false(&buffer, &rem_size); + /* 4. L */ + info_len += oscore_cbor_put_unsigned(&buffer, &rem_size, cs_size); + + oscore_hkdf(osc_ctx->hkdf_alg, &cose->partial_iv, osc_ctx->group_enc_key, + info_buffer, info_len, + keystream, keystream_size); + return 1; +} +#endif /* HAVE_OSCORE_GROUP */ + +uint8_t +oscore_bytes_equal(uint8_t *a_ptr, uint8_t a_len, uint8_t *b_ptr, uint8_t b_len) +{ + if(a_len != b_len) { + return 0; + } + + if(memcmp(a_ptr, b_ptr, a_len) == 0) { + return 1; + } else { + return 0; + } +} + +void +oscore_enter_context(coap_context_t *c_context, oscore_ctx_t *osc_ctx) +{ + osc_ctx->next = c_context->osc_ctx; + c_context->osc_ctx = osc_ctx; +} + +static void +oscore_free_recipient(oscore_recipient_ctx_t *recipient) +{ + coap_delete_bin_const(recipient->recipient_id); + coap_delete_bin_const(recipient->recipient_key); +#ifdef HAVE_OSCORE_GROUP + coap_delete_bin_const(recipient->public_key); +#endif /* HAVE_OSCORE_GROUP */ + coap_free_type(COAP_OSCORE_REC, recipient); +} + +static void +oscore_free_context(oscore_ctx_t *osc_ctx) +{ + coap_delete_bin_const(osc_ctx->sender_context->sender_id); + coap_delete_bin_const(osc_ctx->sender_context->sender_key); +#ifdef HAVE_OSCORE_GROUP + coap_delete_bin_const(osc_ctx->sender_context->private_key); + coap_delete_bin_const(osc_ctx->sender_context->public_key); +#endif /* HAVE_OSCORE_GROUP */ + coap_free_type(COAP_OSCORE_SEN, osc_ctx->sender_context); + + 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_delete_bin_const(osc_ctx->master_secret); + coap_delete_bin_const(osc_ctx->master_salt); + coap_delete_bin_const(osc_ctx->id_context); + coap_delete_bin_const(osc_ctx->common_iv); +#ifdef HAVE_OSCORE_GROUP + coap_delete_bin_const(osc_ctx->sign_params); + coap_delete_bin_const(osc_ctx->group_enc_key); +#endif /* HAVE_OSCORE_GROUP */ + coap_free_type(COAP_OSCORE_COM, osc_ctx); +} + +void +oscore_free_contexts(coap_context_t *c_context) { + while(c_context->osc_ctx) { + oscore_ctx_t *osc_ctx = c_context->osc_ctx; + + c_context->osc_ctx = osc_ctx->next; + + oscore_free_context(osc_ctx); + } +} + +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->osc_ctx; + while (next) { + if (next == osc_ctx) { + if (prev != NULL) + prev->next = next->next; + else + c_context->osc_ctx = next->next; + oscore_free_context(next); + return 1; + } + next = next->next; + } + return 0; +} + +/* + * oscore_find_context + * finds context for received send_id, reciever_id, or context_id + * any of the arguments may be NULL + */ +oscore_ctx_t * +oscore_find_context(coap_context_t *c_context, + coap_bin_const_t sndkey_id, + coap_bin_const_t rcpkey_id, + coap_bin_const_t ctxkey_id, + oscore_recipient_ctx_t **recipient_ctx) +{ + oscore_ctx_t * pt = c_context->osc_ctx; + + *recipient_ctx = NULL; + while (pt != NULL){ + int ok = 0; + oscore_sender_ctx_t *spt = pt->sender_context; + oscore_recipient_ctx_t *rpt = pt->recipient_chain; + if (sndkey_id.length) { + if ((sndkey_id.length == spt->sender_id->length) && + (ctxkey_id.length == pt->id_context->length)){ + if (sndkey_id.s != NULL) + ok = strncmp((const char *)spt->sender_id->s, + (const char *)sndkey_id.s, sndkey_id.length); + if (ctxkey_id.s != NULL) + ok = ok + strncmp((const char *)pt->id_context->s, + (const char *)ctxkey_id.s, ctxkey_id.length); + if (ok == 0){ /* context and sender id are the same */ + if (rcpkey_id.s == NULL) return pt; /* context found */ + while (rpt != NULL){ + if (rcpkey_id.length == rpt->recipient_id->length){ + if (strncmp((const char *)rpt->recipient_id->s, + (const char *)rcpkey_id.s, rcpkey_id.length)==0){ + *recipient_ctx = rpt; + return pt; + } + } /* if rcpkey_id.length */ + rpt = rpt->next_recipient; + } /* while rpt */ + } /* if sender_id */ + } /* large if */ + } + while (rpt) { + ok = 0; + if ((rcpkey_id.length == rpt->recipient_id->length) && + (ctxkey_id.length == (pt->id_context ? + pt->id_context->length : 0))) { + if (rcpkey_id.s != NULL) + ok = strncmp((const char *)rpt->recipient_id->s, + (const char *)rcpkey_id.s, rcpkey_id.length); + if (ctxkey_id.s != NULL && pt->id_context != NULL) + ok = ok + strncmp((const char *)pt->id_context->s, + (const char *)ctxkey_id.s, ctxkey_id.length); + if (ok == 0) { /* context and recipient id are the same */ + *recipient_ctx = rpt; + return pt; /* context found */ + } + } + rpt = rpt->next_recipient; + } /* while rpt */ + pt= pt->next; + } /* end while */ + return NULL; +} + +#define OSCORE_LOG_SIZE 16 +void +oscore_log_hex_value(coap_log_t level, const char *name, + coap_bin_const_t *value) +{ + size_t i; + + if (value == NULL || value->length == 0) { + coap_log(level, " %-16s\n", name); + return; + } + if (coap_get_log_level() >= level) { + for (i = 0; i < value->length; i += OSCORE_LOG_SIZE) { + char number[3*OSCORE_LOG_SIZE+4]; + + oscore_convert_to_hex(&value->s[i], value->length - i > OSCORE_LOG_SIZE ? + OSCORE_LOG_SIZE : value->length - i, + number, sizeof(number)); + coap_log(level, " %-16s %s\n", i == 0 ? name : "", number); + } + } +} + +void +oscore_log_int_value(coap_log_t level, const char *name, int value) +{ + coap_log(level, " %-16s %2d\n", name, value); +} + +void +oscore_convert_to_hex(const uint8_t *src, size_t src_len, + char *dest, size_t dst_len) +{ + /* + * Last output character will be '\000' + * (If output undersized, add trailing ... to indicate this. + */ + size_t space = (dst_len - 4)/3; + uint32_t qq; + + for (qq = 0; qq < src_len && qq < space; qq++) { + char tmp = src[qq]>>4; + if (tmp > 9) + tmp = tmp + 0x61 - 10; + else + tmp = tmp + 0x30; + dest[qq*3]= tmp; + tmp = src[qq] & 0xf; + if (tmp > 9) + tmp = tmp +0x61 - 10; + else + tmp = tmp + 0x30; + dest[qq*3+1] = tmp; + dest[qq*3+2] = 0x20; + } + if (qq != src_len) { + dest[qq*3]= '.'; + dest[qq*3+1]= '.'; + dest[qq*3+2]= '.'; + qq++; + } + dest[qq*3] = 0; +} + +oscore_ctx_t * +oscore_derive_ctx(coap_bin_const_t *master_secret, + coap_bin_const_t *master_salt, + coap_cose_alg_t aead_alg, coap_cose_alg_t hkdf_alg, + coap_bin_const_t *sid, + coap_bin_const_t *rid, + coap_bin_const_t *id_context, + uint32_t replay_window, uint32_t ssn_freq) +{ + oscore_ctx_t *common_ctx = NULL; + oscore_sender_ctx_t *sender_ctx = NULL; + uint8_t info_buffer[40]; + size_t info_len; + uint8_t hkdf_tmp[CONTEXT_KEY_LEN > CONTEXT_INIT_VECT_LEN ? + CONTEXT_KEY_LEN : CONTEXT_INIT_VECT_LEN]; + + common_ctx = coap_malloc_type(COAP_OSCORE_COM, sizeof(oscore_ctx_t)); + if (common_ctx == NULL) + goto error; + memset(common_ctx, 0, sizeof(oscore_ctx_t)); + + sender_ctx = coap_malloc_type(COAP_OSCORE_SEN, sizeof(oscore_sender_ctx_t)); + if (sender_ctx == NULL) + goto error; + memset(sender_ctx, 0, sizeof(oscore_sender_ctx_t)); + +#ifdef HAVE_OSCORE_GROUP + common_ctx->mode = COAP_OSCORE_MODE_SINGLE; +#endif /* HAVE_OSCORE_GROUP */ + + /* sender_ key */ + info_len = compose_info(info_buffer, sizeof(info_buffer), aead_alg, sid, + id_context, coap_make_str_const("Key"), + CONTEXT_KEY_LEN); + if (info_len == 0) + goto error; + + oscore_hkdf(hkdf_alg, master_salt, master_secret, info_buffer, info_len, + hkdf_tmp, CONTEXT_KEY_LEN); + sender_ctx->sender_key = coap_new_bin_const(hkdf_tmp, CONTEXT_KEY_LEN); + + /* common IV */ + info_len = compose_info(info_buffer, sizeof(info_buffer), aead_alg, NULL, + id_context, coap_make_str_const("IV"), + CONTEXT_INIT_VECT_LEN); + if (info_len == 0) + goto error; + oscore_hkdf(hkdf_alg, master_salt, master_secret, info_buffer, info_len, + hkdf_tmp, CONTEXT_INIT_VECT_LEN); + common_ctx->common_iv = coap_new_bin_const(hkdf_tmp, CONTEXT_INIT_VECT_LEN); + +#ifdef HAVE_OSCORE_GROUP + /* Group Encryption Key */ + info_len = compose_info(info_buffer, sizeof(info_buffer), aead_alg, NULL, + id_context, + coap_make_str_const("Group Encryption Key"), + CONTEXT_KEY_LEN); + if (info_len == 0) + goto error; + oscore_hkdf(hkdf_alg, master_salt, master_secret, info_buffer, info_len, + hkdf_tmp, CONTEXT_KEY_LEN); + common_ctx->group_enc_key = coap_new_bin_const(hkdf_tmp, CONTEXT_KEY_LEN); +#endif /* HAVE_OSCORE_GROUP */ + + if (coap_get_log_level() >= COAP_LOG_CIPHERS){ + coap_log(COAP_LOG_CIPHERS, "Common context \n"); + oscore_log_hex_value(COAP_LOG_CIPHERS, "ID Context", id_context); + oscore_log_hex_value(COAP_LOG_CIPHERS, "Master Secret", master_secret); + oscore_log_hex_value(COAP_LOG_CIPHERS, "Master Salt", master_salt); + oscore_log_hex_value(COAP_LOG_CIPHERS, "Common IV", + common_ctx->common_iv); +#ifdef HAVE_OSCORE_GROUP + oscore_log_hex_value(COAP_LOG_CIPHERS, "Group Enc Key", + common_ctx->group_enc_key); +#endif /* HAVE_OSCORE_GROUP */ + oscore_log_hex_value(COAP_LOG_CIPHERS, "Sender ID", + sid); + oscore_log_hex_value(COAP_LOG_CIPHERS, "Sender Key", + sender_ctx->sender_key); + } + + common_ctx->master_secret = master_secret; + common_ctx->master_salt = master_salt; + common_ctx->aead_alg = aead_alg; + common_ctx->hkdf_alg = hkdf_alg; + common_ctx->id_context = id_context; + common_ctx->ssn_freq = ssn_freq; + common_ctx->replay_window_size = replay_window; + + common_ctx->sender_context = sender_ctx; + + sender_ctx->sender_id = sid; + + if (oscore_add_recipient(common_ctx, rid) == NULL) + goto error; + + return common_ctx; + +error: + coap_free_type(COAP_OSCORE_COM, common_ctx); + coap_free_type(COAP_OSCORE_SEN, sender_ctx); + return NULL; +} + +oscore_recipient_ctx_t * +oscore_add_recipient(oscore_ctx_t *osc_ctx, + coap_bin_const_t *rid) +{ + uint8_t info_buffer[30]; + size_t info_len; + uint8_t hkdf_tmp[CONTEXT_KEY_LEN]; + oscore_recipient_ctx_t *rcp_ctx = osc_ctx->recipient_chain; + oscore_recipient_ctx_t *recipient_ctx = NULL; + + /* Check this is not a duplicate recipient id */ + while (rcp_ctx) { + if (rcp_ctx->recipient_id->length == rid->length && + memcmp(rcp_ctx->recipient_id->s, rid->s, rid->length) == 0) { + return 0; + } + rcp_ctx = rcp_ctx->next_recipient; + } + recipient_ctx = + (oscore_recipient_ctx_t *)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)); + + info_len = compose_info(info_buffer, sizeof(info_buffer), osc_ctx->aead_alg, + rid, osc_ctx->id_context, coap_make_str_const("Key"), + CONTEXT_KEY_LEN); + if (info_len == 0) + return NULL; + oscore_hkdf(osc_ctx->hkdf_alg, osc_ctx->master_salt, osc_ctx->master_secret, + info_buffer, info_len, hkdf_tmp, + CONTEXT_KEY_LEN); + recipient_ctx->recipient_key = coap_new_bin_const(hkdf_tmp, CONTEXT_KEY_LEN); + + if (coap_get_log_level() >= COAP_LOG_CIPHERS){ + oscore_log_hex_value(COAP_LOG_CIPHERS, "Recipient ID", + rid); + oscore_log_hex_value(COAP_LOG_CIPHERS, "Recipient Key", + recipient_ctx->recipient_key); + } + + recipient_ctx->recipient_id = rid; + recipient_ctx->initial_state = 1; + recipient_ctx->common_ctx = osc_ctx; + + rcp_ctx = osc_ctx->recipient_chain; + recipient_ctx->next_recipient = rcp_ctx; + osc_ctx->recipient_chain = recipient_ctx; + return recipient_ctx; +} + +int +oscore_delete_recipient(oscore_ctx_t *osc_ctx, + coap_bin_const_t *rid) +{ + oscore_recipient_ctx_t *prev = NULL; + oscore_recipient_ctx_t *next = osc_ctx->recipient_chain; + while (next) { + if (next->recipient_id->length == rid->length && + memcmp(next->recipient_id->s, rid->s, rid->length) == 0) { + if (prev != NULL) + prev->next_recipient = next->next_recipient; + else + osc_ctx->recipient_chain = next->next_recipient; + oscore_free_recipient(next); + return 1; + } + next = next->next_recipient; + } + return 0; +} + +void +oscore_free_association(oscore_association_t *association) +{ + if (association) { + coap_delete_bin_const(association->token); + coap_delete_bin_const(association->aad); + coap_delete_bin_const(association->nonce); + coap_delete_bin_const(association->partial_iv); + coap_free(association); + } +} + +int +oscore_new_association(coap_session_t *session, + coap_bin_const_t *token, + oscore_recipient_ctx_t *recipient_ctx, + coap_bin_const_t *aad, coap_bin_const_t *nonce, + coap_bin_const_t *partial_iv, int is_observe) +{ + oscore_association_t *association; + + association = coap_malloc(sizeof(oscore_association_t)); + if (association == NULL) + return 0; + + memset(association, 0, sizeof(oscore_association_t)); + association->recipient_ctx = recipient_ctx; + association->is_observe = is_observe; + + association->token = coap_new_bin_const(token->s, token->length); + if (association->token == NULL) + goto error; + + if (aad) { + association->aad = coap_new_bin_const(aad->s, aad->length); + if (association->aad == NULL) + goto error; + } + + if (nonce) { + association->nonce = coap_new_bin_const(nonce->s, nonce->length); + if (association->nonce == NULL) + goto error; + } + + if (partial_iv) { + association->partial_iv = coap_new_bin_const(partial_iv->s, + partial_iv->length); + if (association->partial_iv == NULL) + goto error; + } + + OSCORE_ASSOCIATIONS_ADD(session->associations, association); + return 1; + +error: + oscore_free_association(association); + return 0; +} + +oscore_association_t * +oscore_find_association(coap_session_t *session, coap_bin_const_t *token) +{ + oscore_association_t *association; + + OSCORE_ASSOCIATIONS_FIND(session->associations, token, association); + return association; +} + +int +oscore_delete_association(coap_session_t *session, + oscore_association_t *association) +{ + if (association) { + OSCORE_ASSOCIATIONS_DELETE(session->associations, association); + oscore_free_association(association); + return 1; + } + return 0; +} + +void +oscore_delete_server_associations(coap_session_t *session) +{ + if (session) { + oscore_association_t *association; + oscore_association_t *tmp; + + OSCORE_ASSOCIATIONS_ITER_SAFE(session->associations, association, tmp) { + OSCORE_ASSOCIATIONS_DELETE(session->associations, association); + oscore_free_association(association); + } + session->associations = NULL; + } +} + +#ifdef HAVE_OSCORE_GROUP +void +oscore_add_pair_keys(oscore_ctx_t *ctx, + oscore_recipient_ctx_t *recipient_ctx, + uint8_t *pairwise_recipient_key, + uint8_t pairwise_recipient_key_len, + uint8_t *pairwise_sender_key, + uint8_t pairwise_sender_key_len) +{ + ctx->mode = COAP_OSCORE_MODE_PAIRWISE; + if (pairwise_recipient_key != NULL){ + recipient_ctx->pairwise_recipient_key = + coap_new_bin_const(pairwise_recipient_key, + pairwise_recipient_key_len); + } + if (pairwise_sender_key != NULL){ + ctx->sender_context->pairwise_sender_key = + coap_new_bin_const(pairwise_sender_key, + pairwise_sender_key_len); + } + if (coap_get_log_level() >= COAP_LOG_CIPHERS){ + oscore_log_hex_value(COAP_LOG_CIPHERS, "Sender Pairwise", + ctx->sender_context->pairwise_sender_key); + oscore_log_hex_value(COAP_LOG_CIPHERS, "Recipient Pair", + recipient_ctx->pairwise_recipient_key); + } +} + +void +oscore_add_group_keys(oscore_ctx_t *ctx, + oscore_recipient_ctx_t *recipient_ctx, + coap_bin_const_t *snd_public_key, + coap_bin_const_t *snd_private_key, + coap_bin_const_t *rcp_public_key) +{ + if (recipient_ctx == NULL) + return; + + ctx->mode = COAP_OSCORE_MODE_GROUP; + + coap_delete_bin_const(ctx->sender_context->private_key); + ctx->sender_context->private_key = snd_private_key; + coap_delete_bin_const(ctx->sender_context->public_key); + ctx->sender_context->public_key = snd_public_key; + + coap_delete_bin_const(recipient_ctx->public_key); + recipient_ctx->public_key = rcp_public_key; + + if (coap_get_log_level() >= COAP_LOG_CIPHERS){ + oscore_log_hex_value(COAP_LOG_CIPHERS, "Sender Priv Key", + ctx->sender_context->private_key); + oscore_log_hex_value(COAP_LOG_CIPHERS, "Sender Pub Key", + ctx->sender_context->public_key); + oscore_log_hex_value(COAP_LOG_CIPHERS, "Rcpt Pub Key", + recipient_ctx->public_key); + } +} + +void +oscore_add_group_algorithm(oscore_ctx_t *ctx, + coap_cose_alg_t counter_signature_enc_algorithm, + coap_cose_alg_t counter_signature_algorithm, + uint8_t *counter_signature_parameters, + uint8_t counter_signature_parameters_len) +{ + ctx->sign_enc_alg = counter_signature_enc_algorithm; + ctx->sign_alg = counter_signature_algorithm; + ctx->sign_params = coap_new_bin_const(counter_signature_parameters, + counter_signature_parameters_len); + if (coap_get_log_level() >= COAP_LOG_CIPHERS){ + oscore_log_hex_value(COAP_LOG_CIPHERS, "Sign Params", + ctx->sign_params); + } +} +#endif /* HAVE_OSCORE_GROUP */ + +int _strcmp(const char *a, const char *b){ + if( a == NULL && b != NULL){ + return -1; + } else if ( a != NULL && b == NULL) { + return 1; + } else if ( a == NULL && b == NULL) { + return 0; + } + return strcmp(a,b); +} + diff --git a/src/oscore/oscore_cose.c b/src/oscore/oscore_cose.c new file mode 100644 index 0000000000..bbe3756a51 --- /dev/null +++ b/src/oscore/oscore_cose.c @@ -0,0 +1,524 @@ +/* + * Copyright (c) 2018, SICS, RISE AB + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + */ + +/** + * @file oscore_cose.c + * @brief An implementation of the CBOR Object Signing and Encryption (RFC). + * + * \author + * Martin Gunnarsson + * added sign1 addition for coaplib + * Peter van der Stok + * adapted for libcoap integration + * Jon Shallow + * + */ + +#include "coap3/coap_internal.h" +#include "stdio.h" +#include "oscore/oscore_cose.h" +#include "oscore/oscore_cbor.h" +#include "oscore/oscore_crypto.h" +#include "oscore/oscore_context.h" + +/* return tag length belonging to cose algorithm */ +int +cose_tag_len(int cose_alg){ + switch (cose_alg){ + case COSE_Algorithm_AES_CCM_16_64_128: + return COSE_algorithm_AES_CCM_16_64_128_TAG_LEN; + break; + case COSE_Algorithm_AES_CCM_64_64_128: + return COSE_algorithm_AES_CCM_64_64_128_TAG_LEN; + break; + case COSE_Algorithm_AES_CCM_16_128_128: + return COSE_algorithm_AES_CCM_16_128_128_TAG_LEN; + break; + case COSE_Algorithm_AES_CCM_64_128_128: + return COSE_algorithm_AES_CCM_64_128_128_TAG_LEN; + break; + default: + return 0; + break; + } +} + + +/* return hash length belonging to cose algorithm */ +int +cose_hash_len(int cose_alg){ + switch (cose_alg){ + case COSE_ALGORITHM_ES256: + return COSE_Algorithm_HMAC256_256_HASH_LEN; + break; + case COSE_ALGORITHM_ES512: + return COSE_ALGORITHM_ES512_HASH_LEN; + break; + case COSE_ALGORITHM_ES384: + return COSE_ALGORITHM_ES384_HASH_LEN; + break; + case COSE_Algorithm_HMAC256_64: + return COSE_Algorithm_HMAC256_64_HASH_LEN; + break; + case COSE_Algorithm_HMAC256_256: + return COSE_Algorithm_HMAC256_256_HASH_LEN; + break; + case COSE_Algorithm_HMAC384_384: + return COSE_Algorithm_HMAC384_384_HASH_LEN; + break; + case COSE_Algorithm_HMAC512_512: + return COSE_Algorithm_HMAC512_512_HASH_LEN; + break; + case COSE_ALGORITHM_SHA_256_64: + return COSE_ALGORITHM_SHA_256_64_LEN; + break; + case COSE_ALGORITHM_SHA_256_256: + return COSE_ALGORITHM_SHA_256_256_LEN; + break; + case COSE_ALGORITHM_SHA_512_256: + return COSE_ALGORITHM_SHA_512_256_LEN; + break; + default: + return 0; + break; + } +} + +/* return nonce length belonging to cose algorithm */ +int +cose_nonce_len(int cose_alg){ + switch (cose_alg){ + case COSE_Algorithm_AES_CCM_16_64_128: + return COSE_algorithm_AES_CCM_16_64_128_IV_LEN; + break; + case COSE_Algorithm_AES_CCM_64_64_128: + return COSE_algorithm_AES_CCM_64_64_128_IV_LEN; + break; + case COSE_Algorithm_AES_CCM_16_128_128: + return COSE_algorithm_AES_CCM_16_128_128_IV_LEN; + break; + case COSE_Algorithm_AES_CCM_64_128_128: + return COSE_algorithm_AES_CCM_64_128_128_IV_LEN; + break; + default: + return 0; + break; + } +} + +/* return key length belonging to cose algorithm */ +int +cose_key_len(int cose_alg){ + switch (cose_alg){ + case COSE_Algorithm_AES_CCM_16_64_128: + return COSE_algorithm_AES_CCM_16_64_128_KEY_LEN; + break; + case COSE_Algorithm_AES_CCM_64_64_128: + return COSE_algorithm_AES_CCM_64_64_128_KEY_LEN; + break; + case COSE_Algorithm_AES_CCM_16_128_128: + return COSE_algorithm_AES_CCM_16_128_128_KEY_LEN; + break; + case COSE_Algorithm_AES_CCM_64_128_128: + return COSE_algorithm_AES_CCM_64_128_128_KEY_LEN; + break; + default: + return 0; + break; + } +} + +struct CWT_tag_t{ + int16_t tag_value; + const char *tag_name; +}; + +#define NR_OF_TAGS 36 +static struct CWT_tag_t cwt_tags[NR_OF_TAGS] = +/* oscore_context tags */ +{ +{OSCORE_CONTEXT_MS,"ms"}, +{OSCORE_CONTEXT_CLIENTID,"clientId"}, +{OSCORE_CONTEXT_SERVERID,"serverId"}, +{OSCORE_CONTEXT_HKDF,"hkdf"}, +{OSCORE_CONTEXT_ALG,"alg"}, +{OSCORE_CONTEXT_SALT,"salt"}, +{OSCORE_CONTEXT_CONTEXTID,"contextId"}, +{OSCORE_CONTEXT_RPL,"rpl"}, +{OSCORE_CONTEXT_CSALG, "cs_alg"}, +{OSCORE_CONTEXT_CSPARAMS, "cs_params"}, +{OSCORE_CONTEXT_CSKEYPARAMS, "cs_key_params"}, + +/* CWT - cnf tag */ +{CWT_OSCORE_SECURITY_CONTEXT,"OSCORE_Security_Context"}, +{CWT_KEY_COSE_KEY,"COSE_Key"}, +{CWT_KEY_ENCRYPTED_COSE_KEY,"Encrypted_COSE_Key"}, +{CWT_KEY_KID,"CWT_kid"}, + +/* CWT tags */ +{CWT_CLAIM_ISS,"iss"}, +{CWT_CLAIM_SUB,"sub"}, +{CWT_CLAIM_AUD,"aud"}, +{CWT_CLAIM_EXP,"exp"}, +{CWT_CLAIM_NBF,"nbf"}, +{CWT_CLAIM_IAT,"iat"}, +{CWT_CLAIM_CTI,"cti"}, +{CWT_CLAIM_CNF,"cnf"}, +{CWT_KEY_KID,"kid"}, +/* OAUTH-AUTHZ claims */ +{CWT_CLAIM_SCOPE,"scope"}, +{CWT_CLAIM_PROFILE,"profile"}, +{CWT_CLAIM_CNONCE,"cnonce"}, +{OAUTH_CLAIM_GRANTTYPE, "grant_type"}, +{OAUTH_CLAIM_REQCNF, "req_cnf"}, +{OAUTH_CLAIM_ACCESSTOKEN, "access_token"}, +{OAUTH_CLAIM_RSCNF, "rs_cnf"}, +{OAUTH_CLAIM_KEYINFO, "key_info"}, +/* group-comm tags*/ +{COSE_KCP_KTY, "kty"}, +{COSE_KTP_CRV,"crv"}, +{COSE_KCP_KEYOPS, "key_ops"}, +{COSE_KCP_BASE_IV, "iv"}, +}; + +// cose_get_tag +// returns tag value from ACE defined CBOR array of maps +int16_t +cose_get_tag(const uint8_t **data) +{ + uint8_t elem = oscore_cbor_get_next_element(data); + if (elem == CBOR_UNSIGNED_INTEGER) + return (int16_t)oscore_cbor_get_unsigned_integer(data); + if (elem == CBOR_NEGATIVE_INTEGER) + return (int16_t)oscore_cbor_get_negative_integer(data); + if ((elem == CBOR_BYTE_STRING) | (elem == CBOR_TEXT_STRING)){ + size_t len = oscore_cbor_get_element_size(data); + uint8_t *ident = NULL; + ident = realloc(ident, len); + oscore_cbor_get_array(data, ident,(uint64_t)len); + +/* verify that string a valid string and find tag value */ + for (int k=0; k < NR_OF_TAGS; k++){ + if ( + (strncmp((char *)ident, cwt_tags[k].tag_name, len) == 0) + && (len == strlen(cwt_tags[k].tag_name))) + { + free(ident); + return cwt_tags[k].tag_value; + } /* if */ + } /* for NR_OF_TAGS */ + free(ident); + return UNDEFINED_TAG; + } /* if BYTE_STRING */ + return UNDEFINED_TAG; +} + + + +/* Return length */ +size_t +cose_encrypt0_encode(cose_encrypt0_t *ptr, uint8_t *buffer, size_t buf_len) +{ + size_t ret = 0; + size_t rem_size = buf_len; + + ret += oscore_cbor_put_array(&buffer, &rem_size, 3); + ret += oscore_cbor_put_bytes(&buffer, &rem_size, NULL, 0); + /* ret += cose encode attributyes */ + ret += oscore_cbor_put_bytes(&buffer, &rem_size, + ptr->ciphertext.s, ptr->ciphertext.length); + return ret; +} + +/*Return status */ +int cose_encrypt0_decode(cose_encrypt0_t *ptr, uint8_t *buffer, size_t size); + +/* Initiate a new COSE Encrypt0 object. */ +void +cose_encrypt0_init(cose_encrypt0_t *ptr) +{ + memset( ptr, 0, sizeof(cose_encrypt0_t)); +} + +void +cose_encrypt0_set_alg(cose_encrypt0_t *ptr, uint8_t alg) +{ + ptr->alg = alg; +} + +void +cose_encrypt0_set_ciphertext(cose_encrypt0_t *ptr, uint8_t *buffer, + size_t size) +{ + ptr->ciphertext.s = buffer; + ptr->ciphertext.length = size; +} + +void +cose_encrypt0_set_plaintext(cose_encrypt0_t *ptr, uint8_t *buffer, size_t size) +{ + ptr->plaintext.s = buffer; + ptr->plaintext.length = size; +} +/* Return length */ +int cose_encrypt0_get_plaintext(cose_encrypt0_t *ptr, uint8_t **buffer); + +void +cose_encrypt0_set_partial_iv(cose_encrypt0_t *ptr, + coap_bin_const_t *partial_iv) +{ + if (partial_iv == NULL || partial_iv->length == 0) { + ptr->partial_iv.s = NULL; + ptr->partial_iv.length = 0; + } + else { + if (partial_iv->length > (int)sizeof(ptr->partial_iv_data)) + partial_iv->length = sizeof(ptr->partial_iv_data); + memcpy(ptr->partial_iv_data, partial_iv->s, partial_iv->length); + ptr->partial_iv.s = ptr->partial_iv_data; + ptr->partial_iv.length = partial_iv->length; + } +} + +/* Return length */ +coap_bin_const_t +cose_encrypt0_get_partial_iv(cose_encrypt0_t *ptr) +{ + return ptr->partial_iv; +} + +void +cose_encrypt0_set_key_id(cose_encrypt0_t *ptr, coap_bin_const_t *key_id) +{ + if (key_id) { + ptr->key_id = *key_id; + } + else { + ptr->key_id.length = 0; + ptr->key_id.s = NULL; + } +} +/* Return length */ +size_t +cose_encrypt0_get_key_id(cose_encrypt0_t *ptr, const uint8_t **buffer) +{ + *buffer = ptr->key_id.s; + return ptr->key_id.length; +} + +size_t cose_encrypt0_get_kid_context(cose_encrypt0_t *ptr, + const uint8_t **buffer) +{ + *buffer = ptr->kid_context.s; + return ptr->kid_context.length; +} + +void cose_encrypt0_set_kid_context(cose_encrypt0_t *ptr, + coap_bin_const_t *kid_context) +{ + if (kid_context) { + ptr->kid_context = *kid_context; + } + else { + ptr->kid_context.length = 0; + ptr->kid_context.s = NULL; + } +} + +void +cose_encrypt0_set_aad(cose_encrypt0_t *ptr, coap_bin_const_t *aad) +{ + if (aad) { + ptr->aad = *aad; + } + else { + ptr->aad.length = 0; + ptr->aad.s = NULL; + } +} + +/* Returns 1 if successfull, 0 if key is of incorrect length. */ +int +cose_encrypt0_set_key(cose_encrypt0_t *ptr, coap_bin_const_t *key) +{ + if (key == NULL || key->length != 16) { + return 0; + } + + ptr->key = *key; + return 1; +} + +void +cose_encrypt0_set_nonce(cose_encrypt0_t *ptr, coap_bin_const_t *nonce) +{ + if (nonce) { + ptr->nonce = *nonce; + } + else { + ptr->nonce.length = 0; + ptr->nonce.s = NULL; + } +} + +int +cose_encrypt0_encrypt(cose_encrypt0_t *ptr, uint8_t *ciphertext_buffer, + size_t ciphertext_len) +{ + coap_crypto_param_t params; + size_t tag_len = cose_tag_len(ptr->alg); + size_t max_result_len = ptr->plaintext.length + tag_len; + + if (ptr->key.s == NULL || ptr->key.length != (size_t)cose_key_len(ptr->alg)) { + return -1; + } + if (ptr->nonce.s == NULL || + ptr->nonce.length != (size_t)cose_nonce_len(ptr->alg)) { + return -2; + } + if( ptr->aad.s == NULL || ptr->aad.length == 0) { + return -3; + } + if (ptr->plaintext.s == NULL || + (ptr->plaintext.length + tag_len) > ciphertext_len) { + return -4; + } + + memset(¶ms, 0, sizeof(params)); + params.alg = ptr->alg; + params.params.aes.key = ptr->key; + params.params.aes.nonce = ptr->nonce.s; + params.params.aes.tag_len = tag_len; + params.params.aes.l = 15 - ptr->nonce.length; + if (!coap_crypto_aead_encrypt(¶ms, &ptr->plaintext, &ptr->aad, + ciphertext_buffer, &max_result_len)) { + return -5; + } + return (int)max_result_len; +} + +int +cose_encrypt0_decrypt(cose_encrypt0_t *ptr, uint8_t *plaintext_buffer, + size_t plaintext_len) +{ + int ret_len = 0; + coap_crypto_param_t params; + size_t tag_len = cose_tag_len(ptr->alg); + size_t max_result_len = ptr->ciphertext.length - tag_len; + + if(ptr->key.s == NULL || ptr->key.length != (size_t)cose_key_len(ptr->alg)) { + return -1; + } + if (ptr->nonce.s == NULL || + ptr->nonce.length != (size_t)cose_nonce_len(ptr->alg)) { + return -2; + } + if (ptr->aad.s == NULL || ptr->aad.length == 0) { + return -3; + } + if (ptr->ciphertext.s == NULL || + ptr->ciphertext.length > (plaintext_len + tag_len)) { + return -4; + } + + memset(¶ms, 0, sizeof(params)); + params.alg = ptr->alg; + params.params.aes.key = ptr->key; + params.params.aes.nonce = ptr->nonce.s; + params.params.aes.tag_len = tag_len; + params.params.aes.l = 15 - ptr->nonce.length; + if (!coap_crypto_aead_decrypt(¶ms, &ptr->ciphertext, &ptr->aad, + plaintext_buffer, &max_result_len)) { + return -5; + } + ret_len =(int) max_result_len; + return ret_len; +} + +#ifdef HAVE_OSCORE_GROUP +/* ed25519 signature functions */ + +void cose_sign1_init(cose_sign1_t *ptr){ + memset( ptr, 0, sizeof(cose_sign1_t)); +} + +void cose_sign1_set_alg(cose_sign1_t *ptr, int alg, + int alg_param, int alg_kty){ + ptr->alg = alg; + ptr->alg_param = alg_param; + ptr->alg_kty = alg_kty; +} + +void cose_sign1_set_ciphertext(cose_sign1_t *ptr, uint8_t *buffer, + size_t size){ + ptr->ciphertext.s = buffer; + ptr->ciphertext.length = size; +} + +/* Return length */ +int cose_sign1_get_signature(cose_sign1_t *ptr, const uint8_t **buffer){ + *buffer = ptr->signature.s; + return ptr->signature.length; +} + +void cose_sign1_set_signature(cose_sign1_t *ptr, uint8_t *buffer){ + ptr->signature.s = buffer; + ptr->signature.length = Ed25519_SIGNATURE_LEN; +} + +void cose_sign1_set_sigstructure(cose_sign1_t *ptr, uint8_t *buffer, + size_t size){ + ptr->sigstructure.s = buffer; + ptr->sigstructure.length = size; +} + +void cose_sign1_set_public_key(cose_sign1_t *ptr, coap_bin_const_t *buffer) { + ptr->public_key = *buffer; +} + +void cose_sign1_set_private_key(cose_sign1_t *ptr, coap_bin_const_t *buffer) { + ptr->private_key = *buffer; +} + +int cose_sign1_sign(cose_sign1_t *ptr){ + return oscore_edDSA_sign(ptr->alg, ptr->alg_param, &ptr->signature, + &ptr->ciphertext, &ptr->private_key, + &ptr->public_key); +} + +int +cose_sign1_verify(cose_sign1_t *ptr) +{ + return oscore_edDSA_verify(ptr->alg, ptr->alg_param, &ptr->signature, + &ptr->ciphertext, &ptr->public_key); +} +#endif /* HAVE_OSCORE_GROUP */ + + diff --git a/src/oscore/oscore_crypto.c b/src/oscore/oscore_crypto.c new file mode 100644 index 0000000000..01ca0447f3 --- /dev/null +++ b/src/oscore/oscore_crypto.c @@ -0,0 +1,230 @@ +/* + * Copyright (c) 2018, SICS, RISE AB + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + */ + +/** + * @file oscore_crypto.c + * @brief An implementation of the Hash Based Key Derivation Function (RFC) and wrappers for AES-CCM*. + * + * \author + * Martin Gunnarsson + * extended for libcoap + * Peter van der Stok + * on request of Fairhair alliance + * adapted for libcoap integration + * Jon Shallow + */ + +#include "coap3/coap_internal.h" +#include "oscore/oscore_crypto.h" +#include +#include "oscore/oscore.h" +#include "oscore/oscore_cose.h" + +#include +#ifdef HAVE_OSCORE_GROUP +#include "oscore_group_crypto/ed25519.h" +#endif /* HAVE_OSCORE_GROUP */ +#include "oscore/oscore_context.h" + +void +oscore_hmac_shaX(coap_cose_alg_t hkdf_alg, coap_bin_const_t *key, + coap_bin_const_t *data, uint8_t *hmac) +{ + coap_crypto_param_t params; + size_t max_result_len = 32; + + memset(¶ms, 0, sizeof(params)); + params.alg = hkdf_alg; + params.params.key = *key; + if (!coap_crypto_hmac(¶ms, data, hmac, &max_result_len)) { + coap_log(LOG_WARNING, "hmac_shaX: Failed hmac\n"); + } +} + +int +oscore_hkdf_extract(coap_cose_alg_t hkdf_alg, coap_bin_const_t *salt, + coap_bin_const_t *ikm, uint8_t *prk_buffer) +{ + assert(ikm); + if (salt == NULL || salt->s == NULL || salt->length == 0) { + uint8_t zeroes_data[32]; + coap_bin_const_t zeroes; + + memset(zeroes_data, 0, 32); + zeroes.s = zeroes_data; + zeroes.length = 32; + + oscore_hmac_shaX(hkdf_alg, &zeroes, ikm, prk_buffer); + } else { + oscore_hmac_shaX(hkdf_alg, salt, ikm, prk_buffer); + } + return 0; +} + +int +oscore_hkdf_expand(coap_cose_alg_t hkdf_alg, uint8_t *prk, uint8_t *info, + size_t info_len, uint8_t *okm, size_t okm_len) +{ + size_t N = (okm_len + 32 - 1) / 32; /* ceil(okm_len/32) */ + uint8_t *aggregate_buffer = coap_malloc(32 + info_len +1); + uint8_t *out_buffer = coap_malloc((N+1)*32); /* 32 extra bytes to fit the last block */ + size_t i; + coap_bin_const_t prk_info; + coap_bin_const_t data; + + prk_info.s = prk; + prk_info.length = 32; + + /* Compose T(1) */ + memcpy(aggregate_buffer, info, info_len); + aggregate_buffer[info_len] = 0x01; + + data.s = aggregate_buffer; + data.length = info_len + 1; + oscore_hmac_shaX(hkdf_alg, &prk_info, &data, &(out_buffer[0])); + + /* Compose T(2) -> T(N) */ + memcpy(aggregate_buffer, &(out_buffer[0]), 32); + for(i = 1; i < N; i++) { + memcpy(&(aggregate_buffer[32]), info, info_len); + aggregate_buffer[32 + info_len] = (uint8_t)(i + 1); + data.s = aggregate_buffer; + data.length = 32 + info_len + 1; + oscore_hmac_shaX(hkdf_alg, &prk_info, &data, &(out_buffer[i * 32])); + memcpy(aggregate_buffer, &(out_buffer[i * 32]), 32); + } + memcpy(okm, out_buffer, okm_len); + coap_free(out_buffer); + coap_free(aggregate_buffer); + return 0; +} + +int +oscore_hkdf(coap_cose_alg_t hkdf_alg, coap_bin_const_t *salt, + coap_bin_const_t *ikm, uint8_t *info, size_t info_len, + uint8_t *okm, size_t okm_len) +{ + + uint8_t prk_buffer[32]; + oscore_hkdf_extract(hkdf_alg, salt, ikm, prk_buffer); + oscore_hkdf_expand(hkdf_alg, prk_buffer, info, info_len, okm, okm_len); + return 0; +} + +#ifdef HAVE_OSCORE_GROUP +/* Return 0 if key pair generation failure. Key lengths are derived fron ed25519 values. No check is done to ensure that buffers are of the correct length. */ + +#if 0 +int +oscore_edDSA_keypair(coap_cose_alg_t alg, coap_cose_curve_t alg_param, + uint8_t *private_key, + uint8_t *public_key, uint8_t *ed25519_seed) +{ + if (alg != COSE_Algorithm_EdDSA || alg_param != COSE_Elliptic_Curve_Ed25519) { + return 0; + } + ed25519_create_keypair(public_key, private_key, ed25519_seed); + + if (coap_get_log_level() >= COAP_LOG_CIPHERS){ + coap_log(COAP_LOG_CIPHERS, "Key Pair\n"); + oscore_log_hex_value(COAP_LOG_CIPHERS, "Public Key", public_key); + oscore_log_hex_value(COAP_LOG_CIPHERS, "Private Key", private_key); + oscore_log_hex_value(COAP_LOG_CIPHERS, "Seed", ed25519_seed); + } + + return 1; +} +#endif + +/* Return 0 if signing failure. Signatue length otherwise, signature length and key length are derived fron ed25519 values. No check is done to ensure that buffers are of the correct length. */ + +int +oscore_edDSA_sign(coap_cose_alg_t alg, coap_cose_curve_t alg_param, + coap_binary_t *signature, coap_bin_const_t *ciphertext, + coap_bin_const_t *private_key, coap_bin_const_t *public_key) +{ + if (alg != COSE_Algorithm_EdDSA || alg_param != COSE_Elliptic_Curve_Ed25519) { + return 0; + } + +#ifdef HAVE_OPENSSL + coap_crypto_ed25519_sign(signature, ciphertext, + private_key, public_key); +#else /* ! HAVE_OPENSSL */ + ed25519_sign(signature->s, ciphertext->s, ciphertext->length, public_key->s, + private_key->s); +#endif /* ! HAVE_OPENSSL */ + + if (coap_get_log_level() >= COAP_LOG_CIPHERS){ + coap_log(COAP_LOG_CIPHERS, "Sign\n"); + oscore_log_hex_value(COAP_LOG_CIPHERS, "Signature", + (coap_bin_const_t*)signature); + oscore_log_hex_value(COAP_LOG_CIPHERS, "Ciphertext", ciphertext); + oscore_log_hex_value(COAP_LOG_CIPHERS, "Private Key", private_key); + oscore_log_hex_value(COAP_LOG_CIPHERS, "Public Key", public_key); + } + + return 1; +} + +/* Return 0 if signing failure. Signatue length otherwise, signature length and key length are derived fron ed25519 values. No check is done to ensure that buffers are of the correct length. */ + +int +oscore_edDSA_verify(coap_cose_alg_t alg, coap_cose_curve_t alg_param, + coap_binary_t *signature, coap_bin_const_t *plaintext, + coap_bin_const_t *public_key) +{ + int res; + + if(alg != COSE_Algorithm_EdDSA || alg_param != COSE_Elliptic_Curve_Ed25519) { + return 0; + } + + if (coap_get_log_level() >= COAP_LOG_CIPHERS){ + coap_log(COAP_LOG_CIPHERS, "Verify\n"); + oscore_log_hex_value(COAP_LOG_CIPHERS, "Signature", (coap_bin_const_t*)signature); + oscore_log_hex_value(COAP_LOG_CIPHERS, "Plaintext", plaintext); + oscore_log_hex_value(COAP_LOG_CIPHERS, "Public Key", public_key); + } + +#ifdef HAVE_OPENSSL + res = coap_crypto_ed25519_verify(signature, plaintext, public_key); +#else /* ! HAVE_OPENSSL */ + res = ed25519_verify(signature->s, plaintext->s, plaintext->length, + public_key->s); +#endif /* ! HAVE_OPENSSL */ + return res; +} + +#endif /* HAVE_OSCORE_GROUP */ + + + + diff --git a/src/resource.c b/src/resource.c index 70076d4255..03c20cc6b1 100644 --- a/src/resource.c +++ b/src/resource.c @@ -419,7 +419,6 @@ coap_add_attr(coap_resource_t *resource, if (!resource || !name) return NULL; - attr = (coap_attr_t *)coap_malloc_type(COAP_RESOURCEATTR, sizeof(coap_attr_t)); if (attr) { @@ -665,6 +664,12 @@ coap_print_link(const coap_resource_t *resource, COPY_COND_WITH_OFFSET(p, bufend, *offset, ";obs", 4, *len); } +#ifdef HAVE_OSCORE + /* If oscore is enabled */ + if (resource->context && resource->context->osc_ctx) + COPY_COND_WITH_OFFSET(p, bufend, *offset, ";osc", 4, *len); +#endif /* HAVE_OSCORE */ + output_length = p - buf; if (output_length > COAP_PRINT_STATUS_MAX) { @@ -734,7 +739,8 @@ coap_add_observer(coap_resource_t *resource, size_t len; const uint8_t *data; /* https://tools.ietf.org/html/rfc7641#section-3.6 */ -static const uint16_t cache_ignore_options[] = { COAP_OPTION_ETAG }; +static const uint16_t cache_ignore_options[] = { COAP_OPTION_ETAG, + COAP_OPTION_OSCORE }; assert( session ); diff --git a/src/uri.c b/src/uri.c index b7bef0f630..b619125a4c 100644 --- a/src/uri.c +++ b/src/uri.c @@ -51,6 +51,15 @@ typedef enum coap_uri_check_t { COAP_URI_CHECK_PROXY } coap_uri_check_t; +const char *coap_uri_scheme[] = { + "coap", /* COAP_URI_SCHEME_COAP */ + "coaps", /* COAP_URI_SCHEME_COAPS */ + "coap+tcp", /* COAP_URI_SCHEME_COAP_TCP */ + "coaps+tcp", /* COAP_URI_SCHEME_COAPS_TCP */ + "http", /* COAP_URI_SCHEME_HTTP */ + "https" /* COAP_URI_SCHEME_HTTPS */ +}; + static int coap_split_uri_sub(const uint8_t *str_var, size_t len, diff --git a/tests/Makefile.am b/tests/Makefile.am index bec6dfaf23..193d7cf5c6 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -26,7 +26,8 @@ testdriver_SOURCES = \ test_session.c \ test_uri.c \ test_wellknown.c \ - test_tls.c + test_tls.c \ + test_oscore.c # The .a file is uses instead of .la so that testdriver can always access the # internal functions that are not globaly exposed in a .so file. diff --git a/tests/test_oscore.c b/tests/test_oscore.c new file mode 100644 index 0000000000..e0745d9ae5 --- /dev/null +++ b/tests/test_oscore.c @@ -0,0 +1,963 @@ +/* libcoap unit tests + * + * Copyright (C) 2021 Jon Shallow + * + * SPDX-License-Identifier: BSD-2-Clause + * + * This file is part of the CoAP library libcoap. Please see + * README for terms of use. + */ + +#include "test_common.h" +#include "test_oscore.h" +#include "oscore/oscore.h" +#include "oscore/oscore_context.h" + +#include +#include +#include + +#ifdef HAVE_OSCORE +#define CHECK_SAME(a,b) \ + (sizeof((a)) == (b)->length && memcmp((a), (b)->s, (b)->length) == 0) + +/************************************************************************ + ** RFC8613 tests + ************************************************************************/ + +static void +t_oscore_c_1_1(void) { + static const char conf_data[] = + "master_secret,hex,\"0102030405060708090a0b0c0d0e0f10\"\n" + "master_salt,hex,\"9e7ca92223786340\"\n" + "sender_id,hex,\"\"\n" + "recipient_id,hex,\"01\"\n"; + static const uint8_t sender_key[] = + { 0xf0, 0x91, 0x0e, 0xd7, 0x29, 0x5e, 0x6a, 0xd4, + 0xb5, 0x4f, 0xc7, 0x93, 0x15, 0x43, 0x02, 0xff }; + static const uint8_t recipient_key[] = + { 0xff, 0xb1, 0x4e, 0x09, 0x3c, 0x94, 0xc9, 0xca, + 0xc9, 0x47, 0x16, 0x48, 0xb4, 0xf9, 0x87, 0x10 }; + static const uint8_t common_iv[] = + { 0x46, 0x22, 0xd4, 0xdd, 0x6d, 0x94, 0x41, 0x68, + 0xee, 0xfb, 0x54, 0x98, 0x7c }; + static const uint8_t sender_nonce[] = + { 0x46, 0x22, 0xd4, 0xdd, 0x6d, 0x94, 0x41, 0x68, + 0xee, 0xfb, 0x54, 0x98, 0x7c }; + static const uint8_t recipient_nonce[] = + { 0x47, 0x22, 0xd4, 0xdd, 0x6d, 0x94, 0x41, 0x69, + 0xee, 0xfb, 0x54, 0x98, 0x7c }; + const coap_str_const_t conf = { sizeof(conf_data)-1, + (const uint8_t *)conf_data }; + coap_context_t ctx[1]; + coap_oscore_context_t *oscore_ctx; + cose_encrypt0_t cose[1]; + uint8_t nonce_buffer[13]; + coap_bin_const_t nonce = { 13, nonce_buffer }; + + memset(&ctx, 0, sizeof(ctx)); + oscore_ctx = coap_new_oscore_context(ctx, conf, NULL, NULL, 0); + CU_ASSERT_PTR_NOT_NULL(oscore_ctx); + + CU_ASSERT(CHECK_SAME(sender_key, oscore_ctx->sender_context->sender_key)); + CU_ASSERT(CHECK_SAME(recipient_key, + oscore_ctx->recipient_chain->recipient_key)); + CU_ASSERT(CHECK_SAME(common_iv, oscore_ctx->common_iv)); + + cose_encrypt0_init(cose); + cose_encrypt0_set_key_id(cose, oscore_ctx->recipient_chain->recipient_id); + cose_encrypt0_set_partial_iv(cose, NULL); + oscore_generate_nonce(cose, oscore_ctx, nonce_buffer, 13); + CU_ASSERT(CHECK_SAME(recipient_nonce, &nonce)); + + cose_encrypt0_init(cose); + cose_encrypt0_set_key_id(cose, oscore_ctx->sender_context->sender_id); + cose_encrypt0_set_partial_iv(cose, NULL); + oscore_generate_nonce(cose, oscore_ctx, nonce_buffer, 13); + CU_ASSERT(CHECK_SAME(sender_nonce, &nonce)); + + oscore_free_contexts(ctx); +} + +static void +t_oscore_c_1_2(void) { + static const char conf_data[] = + "master_secret,hex,\"0102030405060708090a0b0c0d0e0f10\"\n" + "master_salt,hex,\"9e7ca92223786340\"\n" + "sender_id,hex,\"01\"\n" + "recipient_id,hex,\"\"\n"; + static const uint8_t sender_key[] = + { 0xff, 0xb1, 0x4e, 0x09, 0x3c, 0x94, 0xc9, 0xca, + 0xc9, 0x47, 0x16, 0x48, 0xb4, 0xf9, 0x87, 0x10 }; + static const uint8_t recipient_key[] = + { 0xf0, 0x91, 0x0e, 0xd7, 0x29, 0x5e, 0x6a, 0xd4, + 0xb5, 0x4f, 0xc7, 0x93, 0x15, 0x43, 0x02, 0xff }; + static const uint8_t common_iv[] = + { 0x46, 0x22, 0xd4, 0xdd, 0x6d, 0x94, 0x41, 0x68, + 0xee, 0xfb, 0x54, 0x98, 0x7c }; + static const uint8_t sender_nonce[] = + { 0x47, 0x22, 0xd4, 0xdd, 0x6d, 0x94, 0x41, 0x69, + 0xee, 0xfb, 0x54, 0x98, 0x7c }; + static const uint8_t recipient_nonce[] = + { 0x46, 0x22, 0xd4, 0xdd, 0x6d, 0x94, 0x41, 0x68, + 0xee, 0xfb, 0x54, 0x98, 0x7c }; + const coap_str_const_t conf = { sizeof(conf_data)-1, + (const uint8_t *)conf_data }; + coap_context_t ctx[1]; + oscore_ctx_t *oscore_ctx; + cose_encrypt0_t cose[1]; + uint8_t nonce_buffer[13]; + coap_bin_const_t nonce = { 13, nonce_buffer }; + + memset(&ctx, 0, sizeof(ctx)); + oscore_ctx = coap_new_oscore_context(ctx, conf, NULL, NULL, 0); + CU_ASSERT_PTR_NOT_NULL(oscore_ctx); + + CU_ASSERT(CHECK_SAME(sender_key, oscore_ctx->sender_context->sender_key)); + CU_ASSERT(CHECK_SAME(recipient_key, + oscore_ctx->recipient_chain->recipient_key)); + CU_ASSERT(CHECK_SAME(common_iv, oscore_ctx->common_iv)); + + cose_encrypt0_init(cose); + cose_encrypt0_set_key_id(cose, oscore_ctx->recipient_chain->recipient_id); + cose_encrypt0_set_partial_iv(cose, NULL); + oscore_generate_nonce(cose, oscore_ctx, nonce_buffer, 13); + CU_ASSERT(CHECK_SAME(recipient_nonce, &nonce)); + + cose_encrypt0_init(cose); + cose_encrypt0_set_key_id(cose, oscore_ctx->sender_context->sender_id); + cose_encrypt0_set_partial_iv(cose, NULL); + oscore_generate_nonce(cose, oscore_ctx, nonce_buffer, 13); + CU_ASSERT(CHECK_SAME(sender_nonce, &nonce)); + + oscore_free_contexts(ctx); +} + +static void +t_oscore_c_2_1(void) { + static const char conf_data[] = + "master_secret,hex,\"0102030405060708090a0b0c0d0e0f10\"\n" + "sender_id,hex,\"00\"\n" + "recipient_id,hex,\"01\"\n"; + static const uint8_t sender_key[] = + { 0x32, 0x1b, 0x26, 0x94, 0x32, 0x53, 0xc7, 0xff, + 0xb6, 0x00, 0x3b, 0x0b, 0x64, 0xd7, 0x40, 0x41 }; + static const uint8_t recipient_key[] = + { 0xe5, 0x7b, 0x56, 0x35, 0x81, 0x51, 0x77, 0xcd, + 0x67, 0x9a, 0xb4, 0xbc, 0xec, 0x9d, 0x7d, 0xda }; + static const uint8_t common_iv[] = + { 0xbe, 0x35, 0xae, 0x29, 0x7d, 0x2d, 0xac, 0xe9, + 0x10, 0xc5, 0x2e, 0x99, 0xf9 }; + static const uint8_t sender_nonce[] = + { 0xbf, 0x35, 0xae, 0x29, 0x7d, 0x2d, 0xac, 0xe9, + 0x10, 0xc5, 0x2e, 0x99, 0xf9 }; + static const uint8_t recipient_nonce[] = + { 0xbf, 0x35, 0xae, 0x29, 0x7d, 0x2d, 0xac, 0xe8, + 0x10, 0xc5, 0x2e, 0x99, 0xf9 }; + const coap_str_const_t conf = { sizeof(conf_data)-1, + (const uint8_t *)conf_data }; + coap_context_t ctx[1]; + oscore_ctx_t *oscore_ctx; + cose_encrypt0_t cose[1]; + uint8_t nonce_buffer[13]; + coap_bin_const_t nonce = { 13, nonce_buffer }; + + memset(&ctx, 0, sizeof(ctx)); + oscore_ctx = coap_new_oscore_context(ctx, conf, NULL, NULL, 0); + CU_ASSERT_PTR_NOT_NULL(oscore_ctx); + + CU_ASSERT(CHECK_SAME(sender_key, oscore_ctx->sender_context->sender_key)); + CU_ASSERT(CHECK_SAME(recipient_key, + oscore_ctx->recipient_chain->recipient_key)); + CU_ASSERT(CHECK_SAME(common_iv, oscore_ctx->common_iv)); + + cose_encrypt0_init(cose); + cose_encrypt0_set_key_id(cose, oscore_ctx->recipient_chain->recipient_id); + cose_encrypt0_set_partial_iv(cose, NULL); + oscore_generate_nonce(cose, oscore_ctx, nonce_buffer, 13); + CU_ASSERT(CHECK_SAME(recipient_nonce, &nonce)); + + cose_encrypt0_init(cose); + cose_encrypt0_set_key_id(cose, oscore_ctx->sender_context->sender_id); + cose_encrypt0_set_partial_iv(cose, NULL); + oscore_generate_nonce(cose, oscore_ctx, nonce_buffer, 13); + CU_ASSERT(CHECK_SAME(sender_nonce, &nonce)); + + oscore_free_contexts(ctx); +} + +static void +t_oscore_c_2_2(void) { + static const char conf_data[] = + "master_secret,hex,\"0102030405060708090a0b0c0d0e0f10\"\n" + "sender_id,hex,\"01\"\n" + "recipient_id,hex,\"00\"\n"; + static const uint8_t sender_key[] = + { 0xe5, 0x7b, 0x56, 0x35, 0x81, 0x51, 0x77, 0xcd, + 0x67, 0x9a, 0xb4, 0xbc, 0xec, 0x9d, 0x7d, 0xda }; + static const uint8_t recipient_key[] = + { 0x32, 0x1b, 0x26, 0x94, 0x32, 0x53, 0xc7, 0xff, + 0xb6, 0x00, 0x3b, 0x0b, 0x64, 0xd7, 0x40, 0x41 }; + static const uint8_t common_iv[] = + { 0xbe, 0x35, 0xae, 0x29, 0x7d, 0x2d, 0xac, 0xe9, + 0x10, 0xc5, 0x2e, 0x99, 0xf9 }; + static const uint8_t sender_nonce[] = + { 0xbf, 0x35, 0xae, 0x29, 0x7d, 0x2d, 0xac, 0xe8, + 0x10, 0xc5, 0x2e, 0x99, 0xf9 }; + static const uint8_t recipient_nonce[] = + { 0xbf, 0x35, 0xae, 0x29, 0x7d, 0x2d, 0xac, 0xe9, + 0x10, 0xc5, 0x2e, 0x99, 0xf9 }; + const coap_str_const_t conf = { sizeof(conf_data)-1, + (const uint8_t *)conf_data }; + coap_context_t ctx[1]; + oscore_ctx_t *oscore_ctx; + cose_encrypt0_t cose[1]; + uint8_t nonce_buffer[13]; + coap_bin_const_t nonce = { 13, nonce_buffer }; + + memset(&ctx, 0, sizeof(ctx)); + oscore_ctx = coap_new_oscore_context(ctx, conf, NULL, NULL, 0); + CU_ASSERT_PTR_NOT_NULL(oscore_ctx); + + CU_ASSERT(CHECK_SAME(sender_key, oscore_ctx->sender_context->sender_key)); + CU_ASSERT(CHECK_SAME(recipient_key, + oscore_ctx->recipient_chain->recipient_key)); + CU_ASSERT(CHECK_SAME(common_iv, oscore_ctx->common_iv)); + + cose_encrypt0_init(cose); + cose_encrypt0_set_key_id(cose, oscore_ctx->recipient_chain->recipient_id); + cose_encrypt0_set_partial_iv(cose, NULL); + oscore_generate_nonce(cose, oscore_ctx, nonce_buffer, 13); + CU_ASSERT(CHECK_SAME(recipient_nonce, &nonce)); + + cose_encrypt0_init(cose); + cose_encrypt0_set_key_id(cose, oscore_ctx->sender_context->sender_id); + cose_encrypt0_set_partial_iv(cose, NULL); + oscore_generate_nonce(cose, oscore_ctx, nonce_buffer, 13); + CU_ASSERT(CHECK_SAME(sender_nonce, &nonce)); + + oscore_free_contexts(ctx); +} + +static void +t_oscore_c_3_1(void) { + static const char conf_data[] = + "master_secret,hex,\"0102030405060708090a0b0c0d0e0f10\"\n" + "master_salt,hex,\"9e7ca92223786340\"\n" + "id_context,hex,\"37cbf3210017a2d3\"\n" + "sender_id,hex,\"\"\n" + "recipient_id,hex,\"01\"\n"; + static const uint8_t sender_key[] = + { 0xaf, 0x2a, 0x13, 0x00, 0xa5, 0xe9, 0x57, 0x88, + 0xb3, 0x56, 0x33, 0x6e, 0xee, 0xcd, 0x2b, 0x92 }; + static const uint8_t recipient_key[] = + { 0xe3, 0x9a, 0x0c, 0x7c, 0x77, 0xb4, 0x3f, 0x03, + 0xb4, 0xb3, 0x9a, 0xb9, 0xa2, 0x68, 0x69, 0x9f }; + static const uint8_t common_iv[] = + { 0x2c, 0xa5, 0x8f, 0xb8, 0x5f, 0xf1, 0xb8, 0x1c, + 0x0b, 0x71, 0x81, 0xb8, 0x5e }; + static const uint8_t sender_nonce[] = + { 0x2c, 0xa5, 0x8f, 0xb8, 0x5f, 0xf1, 0xb8, 0x1c, + 0x0b, 0x71, 0x81, 0xb8, 0x5e }; + static const uint8_t recipient_nonce[] = + { 0x2d, 0xa5, 0x8f, 0xb8, 0x5f, 0xf1, 0xb8, 0x1d, + 0x0b, 0x71, 0x81, 0xb8, 0x5e }; + const coap_str_const_t conf = { sizeof(conf_data)-1, + (const uint8_t *)conf_data }; + coap_context_t ctx[1]; + oscore_ctx_t *oscore_ctx; + cose_encrypt0_t cose[1]; + uint8_t nonce_buffer[13]; + coap_bin_const_t nonce = { 13, nonce_buffer }; + + memset(&ctx, 0, sizeof(ctx)); + oscore_ctx = coap_new_oscore_context(ctx, conf, NULL, NULL, 0); + CU_ASSERT_PTR_NOT_NULL(oscore_ctx); + + CU_ASSERT(CHECK_SAME(sender_key, oscore_ctx->sender_context->sender_key)); + CU_ASSERT(CHECK_SAME(recipient_key, + oscore_ctx->recipient_chain->recipient_key)); + CU_ASSERT(CHECK_SAME(common_iv, oscore_ctx->common_iv)); + + cose_encrypt0_init(cose); + cose_encrypt0_set_key_id(cose, oscore_ctx->recipient_chain->recipient_id); + cose_encrypt0_set_partial_iv(cose, NULL); + oscore_generate_nonce(cose, oscore_ctx, nonce_buffer, 13); + CU_ASSERT(CHECK_SAME(recipient_nonce, &nonce)); + + cose_encrypt0_init(cose); + cose_encrypt0_set_key_id(cose, oscore_ctx->sender_context->sender_id); + cose_encrypt0_set_partial_iv(cose, NULL); + oscore_generate_nonce(cose, oscore_ctx, nonce_buffer, 13); + CU_ASSERT(CHECK_SAME(sender_nonce, &nonce)); + + oscore_free_contexts(ctx); +} + +static void +t_oscore_c_3_2(void) { + static const char conf_data[] = + "master_secret,hex,\"0102030405060708090a0b0c0d0e0f10\"\n" + "master_salt,hex,\"9e7ca92223786340\"\n" + "id_context,hex,\"37cbf3210017a2d3\"\n" + "sender_id,hex,\"01\"\n" + "recipient_id,hex,\"\"\n"; + static const uint8_t sender_key[] = + { 0xe3, 0x9a, 0x0c, 0x7c, 0x77, 0xb4, 0x3f, 0x03, + 0xb4, 0xb3, 0x9a, 0xb9, 0xa2, 0x68, 0x69, 0x9f }; + static const uint8_t recipient_key[] = + { 0xaf, 0x2a, 0x13, 0x00, 0xa5, 0xe9, 0x57, 0x88, + 0xb3, 0x56, 0x33, 0x6e, 0xee, 0xcd, 0x2b, 0x92 }; + static const uint8_t common_iv[] = + { 0x2c, 0xa5, 0x8f, 0xb8, 0x5f, 0xf1, 0xb8, 0x1c, + 0x0b, 0x71, 0x81, 0xb8, 0x5e }; + static const uint8_t sender_nonce[] = + { 0x2d, 0xa5, 0x8f, 0xb8, 0x5f, 0xf1, 0xb8, 0x1d, + 0x0b, 0x71, 0x81, 0xb8, 0x5e }; + static const uint8_t recipient_nonce[] = + { 0x2c, 0xa5, 0x8f, 0xb8, 0x5f, 0xf1, 0xb8, 0x1c, + 0x0b, 0x71, 0x81, 0xb8, 0x5e }; + const coap_str_const_t conf = { sizeof(conf_data)-1, + (const uint8_t *)conf_data }; + coap_context_t ctx[1]; + oscore_ctx_t *oscore_ctx; + cose_encrypt0_t cose[1]; + uint8_t nonce_buffer[13]; + coap_bin_const_t nonce = { 13, nonce_buffer }; + + memset(&ctx, 0, sizeof(ctx)); + oscore_ctx = coap_new_oscore_context(ctx, conf, NULL, NULL, 0); + CU_ASSERT_PTR_NOT_NULL(oscore_ctx); + + CU_ASSERT(CHECK_SAME(sender_key, oscore_ctx->sender_context->sender_key)); + CU_ASSERT(CHECK_SAME(recipient_key, + oscore_ctx->recipient_chain->recipient_key)); + CU_ASSERT(CHECK_SAME(common_iv, oscore_ctx->common_iv)); + + cose_encrypt0_init(cose); + cose_encrypt0_set_key_id(cose, oscore_ctx->recipient_chain->recipient_id); + cose_encrypt0_set_partial_iv(cose, NULL); + oscore_generate_nonce(cose, oscore_ctx, nonce_buffer, 13); + CU_ASSERT(CHECK_SAME(recipient_nonce, &nonce)); + + cose_encrypt0_init(cose); + cose_encrypt0_set_key_id(cose, oscore_ctx->sender_context->sender_id); + cose_encrypt0_set_partial_iv(cose, NULL); + oscore_generate_nonce(cose, oscore_ctx, nonce_buffer, 13); + CU_ASSERT(CHECK_SAME(sender_nonce, &nonce)); + + oscore_free_contexts(ctx); +} + +static void +t_oscore_c_4(void) { + static const char conf_data[] = + "master_secret,hex,\"0102030405060708090a0b0c0d0e0f10\"\n" + "master_salt,hex,\"9e7ca92223786340\"\n" + "sender_id,hex,\"\"\n" + "recipient_id,hex,\"01\"\n"; + static const uint8_t unprotected_coap_request[] = + { 0x44, 0x01, 0x5d, 0x1f, 0x00, 0x00, 0x39, 0x74, + 0x39, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x68, 0x6f, + 0x73, 0x74, 0x83, 0x74, 0x76, 0x31 }; + static const uint8_t protected[] = + { 0x44, 0x02, 0x5d, 0x1f, 0x00, 0x00, 0x39, 0x74, + 0x39, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x68, 0x6f, + 0x73, 0x74, 0x62, 0x09, 0x14, 0xff, 0x61, 0x2f, + 0x10, 0x92, 0xf1, 0x77, 0x6f, 0x1c, 0x16, 0x68, + 0xb3, 0x82, 0x5e }; + const coap_str_const_t conf = { sizeof(conf_data)-1, + (const uint8_t *)conf_data }; + coap_context_t ctx[1]; + oscore_ctx_t *oscore_ctx; + int result; + coap_pdu_t *pdu = coap_pdu_init(0, 0, 0, COAP_DEFAULT_MTU); + coap_pdu_t *osc_pdu; + coap_session_t *session; + + memset(&ctx, 0, sizeof(ctx)); + oscore_ctx = coap_new_oscore_context(ctx, conf, NULL, NULL, 20); + CU_ASSERT_PTR_NOT_NULL(oscore_ctx); + CU_ASSERT_PTR_NOT_NULL(pdu); + + result = coap_pdu_parse(COAP_PROTO_UDP, unprotected_coap_request, + sizeof(unprotected_coap_request), pdu); + CU_ASSERT(result > 0); + + session = coap_malloc_type(COAP_SESSION, sizeof(coap_session_t)); + CU_ASSERT_PTR_NOT_NULL(session); + memset(session, 0, sizeof(coap_session_t)); + session->proto = COAP_PROTO_UDP; + session->type = COAP_SESSION_TYPE_CLIENT; + session->recipient_ctx = oscore_ctx->recipient_chain; + + osc_pdu = coap_oscore_new_pdu_encrypted(session, pdu, NULL, 0); + CU_ASSERT_PTR_NOT_NULL(osc_pdu); + + result = coap_pdu_encode_header(osc_pdu, session->proto); + CU_ASSERT(result != 0); + CU_ASSERT(osc_pdu->hdr_size + osc_pdu->used_size == sizeof(protected)); + result = memcmp(&osc_pdu->token[-osc_pdu->hdr_size], protected, + osc_pdu->hdr_size + osc_pdu->used_size); + CU_ASSERT(result == 0); + + oscore_free_contexts(ctx); + coap_delete_pdu(pdu); + coap_delete_pdu(osc_pdu); + oscore_delete_server_associations(session); + coap_free(session); +} + +static void +t_oscore_c_5(void) { + static const char conf_data[] = + "master_secret,hex,\"0102030405060708090a0b0c0d0e0f10\"\n" + "sender_id,hex,\"00\"\n" + "recipient_id,hex,\"01\"\n"; + static const uint8_t unprotected_coap_request[] = + { 0x44, 0x01, 0x71, 0xc3, 0x00, 0x00, 0xb9, 0x32, + 0x39, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x68, 0x6f, + 0x73, 0x74, 0x83, 0x74, 0x76, 0x31 }; + static const uint8_t protected[] = + { 0x44, 0x02, 0x71, 0xc3, 0x00, 0x00, 0xb9, 0x32, + 0x39, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x68, 0x6f, + 0x73, 0x74, 0x63, 0x09, 0x14, 0x00, 0xff, 0x4e, + 0xd3, 0x39, 0xa5, 0xa3, 0x79, 0xb0, 0xb8, 0xbc, + 0x73, 0x1f, 0xff, 0xb0 }; + const coap_str_const_t conf = { sizeof(conf_data)-1, + (const uint8_t *)conf_data }; + coap_context_t ctx[1]; + oscore_ctx_t *oscore_ctx; + int result; + coap_pdu_t *pdu = coap_pdu_init(0, 0, 0, COAP_DEFAULT_MTU); + coap_pdu_t *osc_pdu; + coap_session_t *session; + + memset(&ctx, 0, sizeof(ctx)); + oscore_ctx = coap_new_oscore_context(ctx, conf, NULL, NULL, 20); + CU_ASSERT_PTR_NOT_NULL(oscore_ctx); + CU_ASSERT_PTR_NOT_NULL(pdu); + + result = coap_pdu_parse(COAP_PROTO_UDP, unprotected_coap_request, + sizeof(unprotected_coap_request), pdu); + CU_ASSERT(result > 0); + + session = coap_malloc_type(COAP_SESSION, sizeof(coap_session_t)); + CU_ASSERT_PTR_NOT_NULL(session); + memset(session, 0, sizeof(coap_session_t)); + session->proto = COAP_PROTO_UDP; + session->type = COAP_SESSION_TYPE_CLIENT; + session->recipient_ctx = oscore_ctx->recipient_chain; + + osc_pdu = coap_oscore_new_pdu_encrypted(session, pdu, NULL, 0); + CU_ASSERT_PTR_NOT_NULL(osc_pdu); + + result = coap_pdu_encode_header(osc_pdu, session->proto); + CU_ASSERT(result != 0); + CU_ASSERT(osc_pdu->hdr_size + osc_pdu->used_size == sizeof(protected)); + result = memcmp(&osc_pdu->token[-osc_pdu->hdr_size], protected, + osc_pdu->hdr_size + osc_pdu->used_size); + CU_ASSERT(result == 0); + + oscore_free_contexts(ctx); + coap_delete_pdu(pdu); + coap_delete_pdu(osc_pdu); + oscore_delete_server_associations(session); + coap_free(session); +} + +static void +t_oscore_c_6(void) { + static const char conf_data[] = + "master_secret,hex,\"0102030405060708090a0b0c0d0e0f10\"\n" + "master_salt,hex,\"9e7ca92223786340\"\n" + "id_context,hex,\"37cbf3210017a2d3\"\n" + "sender_id,hex,\"\"\n" + "recipient_id,hex,\"01\"\n"; + static const uint8_t unprotected_coap_request[] = + { 0x44, 0x01, 0x2f, 0x8e, 0xef, 0x9b, 0xbf, 0x7a, + 0x39, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x68, 0x6f, + 0x73, 0x74, 0x83, 0x74, 0x76, 0x31 }; + static const uint8_t protected[] = + { 0x44, 0x02, 0x2f, 0x8e, 0xef, 0x9b, 0xbf, 0x7a, + 0x39, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x68, 0x6f, + 0x73, 0x74, 0x6b, 0x19, 0x14, 0x08, 0x37, 0xcb, + 0xf3, 0x21, 0x00, 0x17, 0xa2, 0xd3, 0xff, 0x72, + 0xcd, 0x72, 0x73, 0xfd, 0x33, 0x1a, 0xc4, 0x5c, + 0xff, 0xbe, 0x55, 0xc3 }; + const coap_str_const_t conf = { sizeof(conf_data)-1, + (const uint8_t *)conf_data }; + coap_context_t ctx[1]; + oscore_ctx_t *oscore_ctx; + int result; + coap_pdu_t *pdu = coap_pdu_init(0, 0, 0, COAP_DEFAULT_MTU); + coap_pdu_t *osc_pdu; + coap_session_t *session; + + memset(&ctx, 0, sizeof(ctx)); + oscore_ctx = coap_new_oscore_context(ctx, conf, NULL, NULL, 20); + CU_ASSERT_PTR_NOT_NULL(oscore_ctx); + CU_ASSERT_PTR_NOT_NULL(pdu); + + result = coap_pdu_parse(COAP_PROTO_UDP, unprotected_coap_request, + sizeof(unprotected_coap_request), pdu); + CU_ASSERT(result > 0); + + session = coap_malloc_type(COAP_SESSION, sizeof(coap_session_t)); + CU_ASSERT_PTR_NOT_NULL(session); + memset(session, 0, sizeof(coap_session_t)); + session->proto = COAP_PROTO_UDP; + session->type = COAP_SESSION_TYPE_CLIENT; + session->recipient_ctx = oscore_ctx->recipient_chain; + + osc_pdu = coap_oscore_new_pdu_encrypted(session, pdu, NULL, 0); + CU_ASSERT_PTR_NOT_NULL(osc_pdu); + + result = coap_pdu_encode_header(osc_pdu, session->proto); + CU_ASSERT(result != 0); + CU_ASSERT(osc_pdu->hdr_size + osc_pdu->used_size == sizeof(protected)); + result = memcmp(&osc_pdu->token[-osc_pdu->hdr_size], protected, + osc_pdu->hdr_size + osc_pdu->used_size); + CU_ASSERT(result == 0); + + oscore_free_contexts(ctx); + coap_delete_pdu(pdu); + coap_delete_pdu(osc_pdu); + oscore_delete_server_associations(session); + coap_free(session); +} + +static void +t_oscore_c_7(void) { + static const char conf_data[] = + "master_secret,hex,\"0102030405060708090a0b0c0d0e0f10\"\n" + "master_salt,hex,\"9e7ca92223786340\"\n" + "sender_id,hex,\"01\"\n" + "recipient_id,hex,\"\"\n"; + static const uint8_t protected_coap_request[] = + { 0x44, 0x02, 0x5d, 0x1f, 0x00, 0x00, 0x39, 0x74, + 0x39, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x68, 0x6f, + 0x73, 0x74, 0x62, 0x09, 0x14, 0xff, 0x61, 0x2f, + 0x10, 0x92, 0xf1, 0x77, 0x6f, 0x1c, 0x16, 0x68, + 0xb3, 0x82, 0x5e }; + static const uint8_t unprotected_coap_request[] = + { 0x44, 0x01, 0x5d, 0x1f, 0x00, 0x00, 0x39, 0x74, + 0x39, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x68, 0x6f, + 0x73, 0x74, 0x83, 0x74, 0x76, 0x31 }; + static const uint8_t unprotected_coap_response[] = + { 0x64, 0x45, 0x5d, 0x1f, 0x00, 0x00, 0x39, 0x74, + 0xff, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x57, + 0x6f, 0x72, 0x6c, 0x64, 0x21 }; + static const uint8_t protected_coap_response[] = + { 0x64, 0x44, 0x5d, 0x1f, 0x00, 0x00, 0x39, 0x74, + 0x90, 0xff, 0xdb, 0xaa, 0xd1, 0xe9, 0xa7, 0xe7, + 0xb2, 0xa8, 0x13, 0xd3, 0xc3, 0x15, 0x24, 0x37, + 0x83, 0x03, 0xcd, 0xaf, 0xae, 0x11, 0x91, 0x06 }; + const coap_str_const_t conf = { sizeof(conf_data)-1, + (const uint8_t *)conf_data }; + coap_context_t ctx[1]; + oscore_ctx_t *oscore_ctx; + int result; + coap_pdu_t *incoming_pdu = coap_pdu_init(0, 0, 0, COAP_DEFAULT_MTU); + coap_pdu_t *pdu = coap_pdu_init(0, 0, 0, COAP_DEFAULT_MTU); + coap_pdu_t *osc_pdu; + coap_session_t *session; + + memset(&ctx, 0, sizeof(ctx)); + oscore_ctx = coap_new_oscore_context(ctx, conf, NULL, NULL, 0); + CU_ASSERT_PTR_NOT_NULL(oscore_ctx); + CU_ASSERT_PTR_NOT_NULL(incoming_pdu); + CU_ASSERT_PTR_NOT_NULL(pdu); + + result = coap_pdu_parse(COAP_PROTO_UDP, protected_coap_request, + sizeof(protected_coap_request), incoming_pdu); + CU_ASSERT(result > 0); + + result = coap_pdu_parse(COAP_PROTO_UDP, unprotected_coap_response, + sizeof(unprotected_coap_response), pdu); + CU_ASSERT(result > 0); + + session = coap_malloc_type(COAP_SESSION, sizeof(coap_session_t)); + CU_ASSERT_PTR_NOT_NULL(session); + memset(session, 0, sizeof(coap_session_t)); + session->proto = COAP_PROTO_UDP; + session->type = COAP_SESSION_TYPE_SERVER; + session->recipient_ctx = oscore_ctx->recipient_chain; + session->recipient_ctx->initial_state = 0; + session->context = ctx; + + /* First, decrypt incoming request to set up all variables for + sending response */ + osc_pdu = coap_oscore_decrypt_pdu(session, incoming_pdu); + CU_ASSERT_PTR_NOT_NULL(osc_pdu); + result = coap_pdu_encode_header(osc_pdu, session->proto); + CU_ASSERT(result != 0); + CU_ASSERT(osc_pdu->hdr_size + osc_pdu->used_size == + sizeof(unprotected_coap_request)); + result = memcmp(&osc_pdu->token[-osc_pdu->hdr_size], unprotected_coap_request, + osc_pdu->hdr_size + osc_pdu->used_size); + CU_ASSERT(result == 0); + coap_delete_pdu(osc_pdu); + coap_delete_pdu(incoming_pdu); + + /* Now encrypt the server's response */ + osc_pdu = coap_oscore_new_pdu_encrypted(session, pdu, NULL, 0); + CU_ASSERT_PTR_NOT_NULL(osc_pdu); + + result = coap_pdu_encode_header(osc_pdu, session->proto); + CU_ASSERT(result != 0); + CU_ASSERT(osc_pdu->hdr_size + osc_pdu->used_size == + sizeof(protected_coap_response)); + result = memcmp(&osc_pdu->token[-osc_pdu->hdr_size], protected_coap_response, + osc_pdu->hdr_size + osc_pdu->used_size); + CU_ASSERT(result == 0); + + oscore_free_contexts(ctx); + coap_delete_pdu(pdu); + coap_delete_pdu(osc_pdu); + oscore_delete_server_associations(session); + coap_free(session); +} + +/* + * Decrypt the encrypted response from C.7 and check it matches input + */ +static void +t_oscore_c_7_2(void) { + static const char conf_data[] = + "master_secret,hex,\"0102030405060708090a0b0c0d0e0f10\"\n" + "master_salt,hex,\"9e7ca92223786340\"\n" + "sender_id,hex,\"\"\n" + "recipient_id,hex,\"01\"\n"; + static const uint8_t unprotected_coap_request[] = + { 0x44, 0x01, 0x5d, 0x1f, 0x00, 0x00, 0x39, 0x74, + 0x39, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x68, 0x6f, + 0x73, 0x74, 0x83, 0x74, 0x76, 0x31 }; + static const uint8_t protected_coap_request[] = + { 0x44, 0x02, 0x5d, 0x1f, 0x00, 0x00, 0x39, 0x74, + 0x39, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x68, 0x6f, + 0x73, 0x74, 0x62, 0x09, 0x14, 0xff, 0x61, 0x2f, + 0x10, 0x92, 0xf1, 0x77, 0x6f, 0x1c, 0x16, 0x68, + 0xb3, 0x82, 0x5e }; + static const uint8_t unprotected_coap_response[] = + { 0x64, 0x45, 0x5d, 0x1f, 0x00, 0x00, 0x39, 0x74, + 0xff, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x57, + 0x6f, 0x72, 0x6c, 0x64, 0x21 }; + static const uint8_t protected_coap_response[] = + { 0x64, 0x44, 0x5d, 0x1f, 0x00, 0x00, 0x39, 0x74, + 0x90, 0xff, 0xdb, 0xaa, 0xd1, 0xe9, 0xa7, 0xe7, + 0xb2, 0xa8, 0x13, 0xd3, 0xc3, 0x15, 0x24, 0x37, + 0x83, 0x03, 0xcd, 0xaf, 0xae, 0x11, 0x91, 0x06 }; + const coap_str_const_t conf = { sizeof(conf_data)-1, + (const uint8_t *)conf_data }; + coap_context_t ctx[1]; + oscore_ctx_t *oscore_ctx; + int result; + coap_pdu_t *outgoing_pdu = coap_pdu_init(0, 0, 0, COAP_DEFAULT_MTU); + coap_pdu_t *incoming_pdu = coap_pdu_init(0, 0, 0, COAP_DEFAULT_MTU); + coap_pdu_t *osc_pdu; + coap_session_t *session; + + CU_ASSERT_PTR_NOT_NULL(outgoing_pdu); + CU_ASSERT_PTR_NOT_NULL(incoming_pdu); + + memset(&ctx, 0, sizeof(ctx)); + oscore_ctx = coap_new_oscore_context(ctx, conf, NULL, NULL, 20); + CU_ASSERT_PTR_NOT_NULL(oscore_ctx); + + result = coap_pdu_parse(COAP_PROTO_UDP, unprotected_coap_request, + sizeof(unprotected_coap_request), outgoing_pdu); + CU_ASSERT(result > 0); + result = coap_pdu_parse(COAP_PROTO_UDP, protected_coap_response, + sizeof(protected_coap_response), incoming_pdu); + CU_ASSERT(result > 0); + + session = coap_malloc_type(COAP_SESSION, sizeof(coap_session_t)); + CU_ASSERT_PTR_NOT_NULL(session); + memset(session, 0, sizeof(coap_session_t)); + session->proto = COAP_PROTO_UDP; + session->type = COAP_SESSION_TYPE_CLIENT; + session->recipient_ctx = oscore_ctx->recipient_chain; + session->recipient_ctx->initial_state = 0; + session->context = ctx; + + /* Send request, so that all associations etc. are correctly set up */ + + osc_pdu = coap_oscore_new_pdu_encrypted(session, outgoing_pdu, NULL, 0); + CU_ASSERT_PTR_NOT_NULL(osc_pdu); + + result = coap_pdu_encode_header(osc_pdu, session->proto); + CU_ASSERT(result != 0); + CU_ASSERT(osc_pdu->hdr_size + osc_pdu->used_size == + sizeof(protected_coap_request)); + result = memcmp(&osc_pdu->token[-osc_pdu->hdr_size], protected_coap_request, + osc_pdu->hdr_size + osc_pdu->used_size); + CU_ASSERT(result == 0); + coap_delete_pdu(outgoing_pdu); + coap_delete_pdu(osc_pdu); + + /* Decrypt the encrypted response */ + + osc_pdu = coap_oscore_decrypt_pdu(session, incoming_pdu); + CU_ASSERT_PTR_NOT_NULL(osc_pdu); + + result = coap_pdu_encode_header(osc_pdu, session->proto); + CU_ASSERT(result != 0); + CU_ASSERT(osc_pdu->hdr_size + osc_pdu->used_size == + sizeof(unprotected_coap_response)); + result = memcmp(&osc_pdu->token[-osc_pdu->hdr_size], + unprotected_coap_response, + osc_pdu->hdr_size + osc_pdu->used_size); + CU_ASSERT(result == 0); + + oscore_free_contexts(ctx); + coap_delete_pdu(incoming_pdu); + coap_delete_pdu(osc_pdu); + coap_free(session); +} + +static void +t_oscore_c_8(void) { + static const char conf_data[] = + "master_secret,hex,\"0102030405060708090a0b0c0d0e0f10\"\n" + "master_salt,hex,\"9e7ca92223786340\"\n" + "sender_id,hex,\"01\"\n" + "recipient_id,hex,\"\"\n"; + static const uint8_t protected_coap_request[] = + { 0x44, 0x02, 0x5d, 0x1f, 0x00, 0x00, 0x39, 0x74, + 0x39, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x68, 0x6f, + 0x73, 0x74, 0x62, 0x09, 0x14, 0xff, 0x61, 0x2f, + 0x10, 0x92, 0xf1, 0x77, 0x6f, 0x1c, 0x16, 0x68, + 0xb3, 0x82, 0x5e }; + static const uint8_t unprotected_coap_request[] = + { 0x44, 0x01, 0x5d, 0x1f, 0x00, 0x00, 0x39, 0x74, + 0x39, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x68, 0x6f, + 0x73, 0x74, 0x83, 0x74, 0x76, 0x31 }; + static const uint8_t unprotected_coap_response[] = + { 0x64, 0x45, 0x5d, 0x1f, 0x00, 0x00, 0x39, 0x74, + 0xff, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x57, + 0x6f, 0x72, 0x6c, 0x64, 0x21 }; + static const uint8_t protected_coap_response[] = + { 0x64, 0x44, 0x5d, 0x1f, 0x00, 0x00, 0x39, 0x74, + 0x92, 0x01, 0x00, 0xff, 0x4d, 0x4c, 0x13, 0x66, + 0x93, 0x84, 0xb6, 0x73, 0x54, 0xb2, 0xb6, 0x17, + 0x5f, 0xf4, 0xb8, 0x65, 0x8c, 0x66, 0x6a, 0x6c, + 0xf8, 0x8e }; + const coap_str_const_t conf = { sizeof(conf_data)-1, + (const uint8_t *)conf_data }; + coap_context_t ctx[1]; + oscore_ctx_t *oscore_ctx; + int result; + coap_pdu_t *incoming_pdu = coap_pdu_init(0, 0, 0, COAP_DEFAULT_MTU); + coap_pdu_t *pdu = coap_pdu_init(0, 0, 0, COAP_DEFAULT_MTU); + coap_pdu_t *osc_pdu; + coap_session_t *session; + + memset(&ctx, 0, sizeof(ctx)); + oscore_ctx = coap_new_oscore_context(ctx, conf, NULL, NULL, 0); + CU_ASSERT_PTR_NOT_NULL(oscore_ctx); + CU_ASSERT_PTR_NOT_NULL(incoming_pdu); + CU_ASSERT_PTR_NOT_NULL(pdu); + + result = coap_pdu_parse(COAP_PROTO_UDP, protected_coap_request, + sizeof(protected_coap_request), incoming_pdu); + CU_ASSERT(result > 0); + + result = coap_pdu_parse(COAP_PROTO_UDP, unprotected_coap_response, + sizeof(unprotected_coap_response), pdu); + CU_ASSERT(result > 0); + + session = coap_malloc_type(COAP_SESSION, sizeof(coap_session_t)); + CU_ASSERT_PTR_NOT_NULL(session); + memset(session, 0, sizeof(coap_session_t)); + session->proto = COAP_PROTO_UDP; + session->type = COAP_SESSION_TYPE_SERVER; + session->recipient_ctx = oscore_ctx->recipient_chain; + session->recipient_ctx->initial_state = 0; + session->context = ctx; + + /* First, decrypt incoming request to set up all variables for + sending response */ + osc_pdu = coap_oscore_decrypt_pdu(session, incoming_pdu); + CU_ASSERT_PTR_NOT_NULL(osc_pdu); + result = coap_pdu_encode_header(osc_pdu, session->proto); + CU_ASSERT(result != 0); + CU_ASSERT(osc_pdu->hdr_size + osc_pdu->used_size == + sizeof(unprotected_coap_request)); + result = memcmp(&osc_pdu->token[-osc_pdu->hdr_size], unprotected_coap_request, + osc_pdu->hdr_size + osc_pdu->used_size); + CU_ASSERT(result == 0); + coap_delete_pdu(osc_pdu); + coap_delete_pdu(incoming_pdu); + + /* Now encrypt the server's response */ + osc_pdu = coap_oscore_new_pdu_encrypted(session, pdu, NULL, 1); + CU_ASSERT_PTR_NOT_NULL(osc_pdu); + + result = coap_pdu_encode_header(osc_pdu, session->proto); + CU_ASSERT(result != 0); + CU_ASSERT(osc_pdu->hdr_size + osc_pdu->used_size == + sizeof(protected_coap_response)); + result = memcmp(&osc_pdu->token[-osc_pdu->hdr_size], protected_coap_response, + osc_pdu->hdr_size + osc_pdu->used_size); + CU_ASSERT(result == 0); + + oscore_free_contexts(ctx); + coap_delete_pdu(pdu); + coap_delete_pdu(osc_pdu); + oscore_delete_server_associations(session); + coap_free(session); +} + +/* + * Decrypt the encrypted response from C.8 and check it matches input + */ +static void +t_oscore_c_8_2(void) { + static const char conf_data[] = + "master_secret,hex,\"0102030405060708090a0b0c0d0e0f10\"\n" + "master_salt,hex,\"9e7ca92223786340\"\n" + "sender_id,hex,\"\"\n" + "recipient_id,hex,\"01\"\n"; + static const uint8_t unprotected_coap_request[] = + { 0x44, 0x01, 0x5d, 0x1f, 0x00, 0x00, 0x39, 0x74, + 0x39, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x68, 0x6f, + 0x73, 0x74, 0x83, 0x74, 0x76, 0x31 }; + static const uint8_t protected_coap_request[] = + { 0x44, 0x02, 0x5d, 0x1f, 0x00, 0x00, 0x39, 0x74, + 0x39, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x68, 0x6f, + 0x73, 0x74, 0x62, 0x09, 0x14, 0xff, 0x61, 0x2f, + 0x10, 0x92, 0xf1, 0x77, 0x6f, 0x1c, 0x16, 0x68, + 0xb3, 0x82, 0x5e }; + static const uint8_t unprotected_coap_response[] = + { 0x64, 0x45, 0x5d, 0x1f, 0x00, 0x00, 0x39, 0x74, + 0xff, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x57, + 0x6f, 0x72, 0x6c, 0x64, 0x21 }; + static const uint8_t protected_coap_response[] = + { 0x64, 0x44, 0x5d, 0x1f, 0x00, 0x00, 0x39, 0x74, + 0x92, 0x01, 0x00, 0xff, 0x4d, 0x4c, 0x13, 0x66, + 0x93, 0x84, 0xb6, 0x73, 0x54, 0xb2, 0xb6, 0x17, + 0x5f, 0xf4, 0xb8, 0x65, 0x8c, 0x66, 0x6a, 0x6c, + 0xf8, 0x8e }; + const coap_str_const_t conf = { sizeof(conf_data)-1, + (const uint8_t *)conf_data }; + coap_context_t ctx[1]; + oscore_ctx_t *oscore_ctx; + int result; + coap_pdu_t *outgoing_pdu = coap_pdu_init(0, 0, 0, COAP_DEFAULT_MTU); + coap_pdu_t *incoming_pdu = coap_pdu_init(0, 0, 0, COAP_DEFAULT_MTU); + coap_pdu_t *osc_pdu; + coap_session_t *session; + + CU_ASSERT_PTR_NOT_NULL(outgoing_pdu); + CU_ASSERT_PTR_NOT_NULL(incoming_pdu); + + memset(&ctx, 0, sizeof(ctx)); + oscore_ctx = coap_new_oscore_context(ctx, conf, NULL, NULL, 20); + CU_ASSERT_PTR_NOT_NULL(oscore_ctx); + + result = coap_pdu_parse(COAP_PROTO_UDP, unprotected_coap_request, + sizeof(unprotected_coap_request), outgoing_pdu); + CU_ASSERT(result > 0); + result = coap_pdu_parse(COAP_PROTO_UDP, protected_coap_response, + sizeof(protected_coap_response), incoming_pdu); + CU_ASSERT(result > 0); + + session = coap_malloc_type(COAP_SESSION, sizeof(coap_session_t)); + CU_ASSERT_PTR_NOT_NULL(session); + memset(session, 0, sizeof(coap_session_t)); + session->proto = COAP_PROTO_UDP; + session->type = COAP_SESSION_TYPE_CLIENT; + session->recipient_ctx = oscore_ctx->recipient_chain; + session->recipient_ctx->initial_state = 0; + session->context = ctx; + + /* Send request, so that all associations etc. are correctly set up */ + + osc_pdu = coap_oscore_new_pdu_encrypted(session, outgoing_pdu, NULL, 0); + CU_ASSERT_PTR_NOT_NULL(osc_pdu); + + result = coap_pdu_encode_header(osc_pdu, session->proto); + CU_ASSERT(result != 0); + CU_ASSERT(osc_pdu->hdr_size + osc_pdu->used_size == + sizeof(protected_coap_request)); + result = memcmp(&osc_pdu->token[-osc_pdu->hdr_size], protected_coap_request, + osc_pdu->hdr_size + osc_pdu->used_size); + CU_ASSERT(result == 0); + coap_delete_pdu(outgoing_pdu); + coap_delete_pdu(osc_pdu); + + /* Decrypt the encrypted response */ + + osc_pdu = coap_oscore_decrypt_pdu(session, incoming_pdu); + CU_ASSERT_PTR_NOT_NULL(osc_pdu); + + result = coap_pdu_encode_header(osc_pdu, session->proto); + CU_ASSERT(result != 0); + CU_ASSERT(osc_pdu->hdr_size + osc_pdu->used_size == + sizeof(unprotected_coap_response)); + result = memcmp(&osc_pdu->token[-osc_pdu->hdr_size], + unprotected_coap_response, + osc_pdu->hdr_size + osc_pdu->used_size); + CU_ASSERT(result == 0); + + oscore_free_contexts(ctx); + coap_delete_pdu(incoming_pdu); + coap_delete_pdu(osc_pdu); + coap_free(session); +} + + +/************************************************************************ + ** initialization + ************************************************************************/ + +CU_pSuite +t_init_oscore_tests(void) { + CU_pSuite suite[5]; + + suite[0] = CU_add_suite("RFC8613 Appendix C OSCORE tests", NULL, NULL); + if (!suite[0]) { /* signal error */ + fprintf(stderr, "W: cannot add OSCORE test suite (%s)\n", + CU_get_error_msg()); + + return NULL; + } + +#define OSCORE_TEST(n) \ + if (!CU_add_test(suite[0], #n, n)) { \ + fprintf(stderr, "W: cannot add OSCORE test (%s)\n", \ + CU_get_error_msg()); \ + } + + if (coap_oscore_is_supported()) { + OSCORE_TEST(t_oscore_c_1_1); + OSCORE_TEST(t_oscore_c_1_2); + OSCORE_TEST(t_oscore_c_2_1); + OSCORE_TEST(t_oscore_c_2_2); + OSCORE_TEST(t_oscore_c_3_1); + OSCORE_TEST(t_oscore_c_3_2); + OSCORE_TEST(t_oscore_c_4); + OSCORE_TEST(t_oscore_c_5); + OSCORE_TEST(t_oscore_c_6); + OSCORE_TEST(t_oscore_c_7); + OSCORE_TEST(t_oscore_c_7_2); + OSCORE_TEST(t_oscore_c_8); + OSCORE_TEST(t_oscore_c_8_2); + } + + return suite[0]; +} + +#else /* HAVE_OSCORE */ + +#ifdef __clang__ +/* Make compilers happy that do not like empty modules. As this function is + * never used, we ignore -Wunused-function at the end of compiling this file + */ +#pragma GCC diagnostic ignored "-Wunused-function" +#endif +static inline void dummy(void) { +} + +#endif /* HAVE_OSCORE */ diff --git a/tests/test_oscore.h b/tests/test_oscore.h new file mode 100644 index 0000000000..168962dea9 --- /dev/null +++ b/tests/test_oscore.h @@ -0,0 +1,13 @@ +/* libcoap unit tests + * + * Copyright (C) 2021 Jon Shallow + * + * SPDX-License-Identifier: BSD-2-Clause + * + * This file is part of the CoAP library libcoap. Please see + * README for terms of use. + */ + +#include + +CU_pSuite t_init_oscore_tests(void); diff --git a/tests/testdriver.c b/tests/testdriver.c index 3ead615450..c0883ca06b 100644 --- a/tests/testdriver.c +++ b/tests/testdriver.c @@ -23,6 +23,7 @@ #include "test_sendqueue.h" #include "test_wellknown.h" #include "test_tls.h" +#include "test_oscore.h" int main(int argc COAP_UNUSED, char **argv COAP_UNUSED) { @@ -48,6 +49,9 @@ main(int argc COAP_UNUSED, char **argv COAP_UNUSED) { t_init_wellknown_tests(); #endif /* COAP_SERVER_SUPPORT && COAP_CLIENT_SUPPORT */ t_init_tls_tests(); +#ifdef HAVE_OSCORE + t_init_oscore_tests(); +#endif /* HAVE_OSCORE */ CU_basic_set_mode(run_mode); result = CU_basic_run_tests(); diff --git a/win32/libcoap.props b/win32/libcoap.props index 27c9a36c90..e9246d55d2 100644 --- a/win32/libcoap.props +++ b/win32/libcoap.props @@ -18,6 +18,7 @@ $(CUnitRootDir)lib $(CUnitRootDirDbg)lib include\coap3 + include\oscore diff --git a/win32/libcoap.vcxproj b/win32/libcoap.vcxproj index e4468a2f38..672d36838d 100644 --- a/win32/libcoap.vcxproj +++ b/win32/libcoap.vcxproj @@ -51,6 +51,7 @@ + @@ -65,6 +66,11 @@ + + + + + @@ -75,6 +81,7 @@ + @@ -83,6 +90,8 @@ + + @@ -102,6 +111,11 @@ + + + + + diff --git a/win32/libcoap.vcxproj.filters b/win32/libcoap.vcxproj.filters index 995f8a2b25..7f4404d40d 100644 --- a/win32/libcoap.vcxproj.filters +++ b/win32/libcoap.vcxproj.filters @@ -51,6 +51,9 @@ Source Files + + Source Files + Source Files @@ -90,6 +93,21 @@ Source Files + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + @@ -116,6 +134,9 @@ Header Files + + Header Files + Header Files @@ -140,6 +161,12 @@ Header Files + + Header Files + + + Header Files + Header Files @@ -197,6 +224,21 @@ Header Files + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files +