diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..55e342c --- /dev/null +++ b/.gitmodules @@ -0,0 +1,12 @@ +[submodule "thirdparty/NanoCBOR"] + path = thirdparty/NanoCBOR + url = https://github.com/bergzand/NanoCBOR +[submodule "thirdparty/mbedtls"] + path = thirdparty/mbedtls + url = https://github.com/Mbed-TLS/mbedtls.git +[submodule "thirdparty/libcose"] + path = thirdparty/libcose + url = https://github.com/dmochkas/libcose +[submodule "thirdparty/ascon-c"] + path = thirdparty/ascon-c + url = https://github.com/dmochkas/ascon-c diff --git a/CMakeLists.txt b/CMakeLists.txt index 096b663..ef934ee 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,11 +2,15 @@ cmake_minimum_required(VERSION 3.5) project(rmw_desert) +include(config.cmake) + # Default to C++20 if(NOT CMAKE_CXX_STANDARD) set(CMAKE_CXX_STANDARD 20) endif() +set(CMAKE_POLICY_VERSION_MINIMUM 3.5) + if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") add_compile_options(-Wall -Wextra -Wpedantic -pthread) endif() @@ -23,9 +27,6 @@ find_package(rosidl_typesupport_introspection_cpp REQUIRED) find_package(rcutils REQUIRED) find_package(rcpputils REQUIRED) -set(CBOR_ROOT cbor) -include_directories(${CBOR_ROOT}/include) - add_library(rmw_desert src/get_node_info_and_types.cpp src/init.cpp @@ -46,21 +47,109 @@ add_library(rmw_desert src/desert_classes/DesertClient.cpp src/desert_classes/DesertService.cpp src/desert_classes/DesertGuardCondition.cpp - src/desert_classes/CBorStream.cpp src/desert_classes/CStringHelper.cpp src/desert_classes/TcpDaemon.cpp src/desert_classes/Discovery.cpp src/desert_classes/TopicsConfig.cpp src/desert_classes/demangle.cpp - - ${CBOR_ROOT}/src/common.c - ${CBOR_ROOT}/src/parser.c - ${CBOR_ROOT}/src/decoder.c - ${CBOR_ROOT}/src/encoder.c - ${CBOR_ROOT}/src/helper.c - ${CBOR_ROOT}/src/ieee754.c ) +if (${CBOR_LIB} STREQUAL "libmcu_cbor") + set(CBOR_ROOT cbor) + include_directories(${CBOR_ROOT}/include) + target_sources(rmw_desert PRIVATE + ${CBOR_ROOT}/src/common.c + ${CBOR_ROOT}/src/parser.c + ${CBOR_ROOT}/src/decoder.c + ${CBOR_ROOT}/src/encoder.c + ${CBOR_ROOT}/src/helper.c + ${CBOR_ROOT}/src/ieee754.c + src/desert_classes/LibMcuCBorStream.cpp + ) + target_compile_definitions(rmw_desert PRIVATE LIBMCU_CBOR_ENABLED) +elseif (${CBOR_LIB} STREQUAL "NanoCBOR") + set(CBOR_ROOT thirdparty/NanoCBOR) + include_directories(${CBOR_ROOT}/include) + target_sources(rmw_desert PRIVATE + ${CBOR_ROOT}/src/decoder.c + ${CBOR_ROOT}/src/encoder.c + src/desert_classes/NanoCBorStream.cpp + ) + target_compile_definitions(rmw_desert PRIVATE NANOCBOR_ENABLED) +else () + message(FATAL_ERROR "At least one CBOR libraty is required") +endif () + +target_include_directories(rmw_desert PRIVATE ${CBOR_INC_DIR}) + +if (SECURE_MODE_ENABLED) + if (NOT ${CBOR_LIB} STREQUAL "NanoCBOR") + message(FATAL_ERROR "Secure mode requires NanoCBOR lib.") + endif () + + add_compile_options(-fPIC) + + set(COSE_ROOT "thirdparty/libcose") + set(COSE_LIB "libcose") + set(COSE_INC_DIR "${COSE_ROOT}/include") + add_library(${COSE_LIB} OBJECT + ${COSE_ROOT}/src/cose_common.c + ${COSE_ROOT}/src/cose_crypto.c + ${COSE_ROOT}/src/cose_encrypt.c + ${COSE_ROOT}/src/cose_hdr.c + ${COSE_ROOT}/src/cose_hkdf.c + ${COSE_ROOT}/src/cose_key.c + ${COSE_ROOT}/src/cose_recipient.c + ${COSE_ROOT}/src/cose_sign.c + ${COSE_ROOT}/src/cose_signature.c + ${COSE_ROOT}/src/crypt/keygen_symm.c + ) + target_include_directories(${COSE_LIB} PRIVATE ${COSE_INC_DIR} ${CBOR_INC_DIR}) + + if (${CRYPTO_LIB} STREQUAL "mbedtls") + add_subdirectory(thirdparty/mbedtls) + + target_sources(${COSE_LIB} PRIVATE ${COSE_ROOT}/src/crypt/mbedtls.c) + target_link_libraries(${COSE_LIB} PRIVATE mbedtls) + target_link_libraries(rmw_desert PRIVATE mbedtls) + add_compile_definitions(CRYPTO_MBEDTLS) + elseif (${CRYPTO_LIB} STREQUAL "ascon-c") + set(ASCON_LIB "ascon-c") + set(ASCON_AEAD_ROOT thirdparty/ascon-c/crypto_aead/asconaead128/opt64) + set(ASCON_HASH_ROOT thirdparty/ascon-c/crypto_hash/asconhash256/opt64) + add_library(${ASCON_LIB} OBJECT + ${ASCON_AEAD_ROOT}/aead.c + ${ASCON_AEAD_ROOT}/permutations.c + ${ASCON_HASH_ROOT}/hash.c + ) + target_compile_definitions(${ASCON_LIB} PRIVATE ASCON_INLINE_MODE=0) + + target_sources(${COSE_LIB} PRIVATE ${COSE_ROOT}/src/crypt/ascon_c.c) + target_sources(rmw_desert PRIVATE $) + add_compile_definitions(CRYPTO_ASCON) + else () + message(FATAL_ERROR "Cryptolib ${CRYPTO_LIB} is not supported") + endif () + + target_sources(rmw_desert PRIVATE + $ + src/desert_classes/security/sec_utils.cpp + src/desert_classes/security/AeadParams.cpp + src/desert_classes/security/SecurityLayer.cpp + ) + target_include_directories(rmw_desert PRIVATE ${COSE_INC_DIR}) + target_compile_definitions(rmw_desert PRIVATE SECURE_MODE_ENABLED) + target_compile_definitions(rmw_desert PRIVATE + AEAD_ALGO="${AEAD_ALGO}" + KDF_ALGO="${KDF_ALGO}" + PIV_LEN=${PIV_LEN} + SENDER_ID="${SENDER_ID}" + RECEIVER_ID="${RECEIVER_ID}") + if (COSE_STATELESS_COMP_ENABLED) + target_compile_definitions(rmw_desert PRIVATE COSE_STATELESS_COMP_ENABLED) + endif () +endif () + INCLUDE (FindPkgConfig) # specific order: dependents before dependencies diff --git a/config.cmake b/config.cmake new file mode 100644 index 0000000..262ef99 --- /dev/null +++ b/config.cmake @@ -0,0 +1,9 @@ +set(CBOR_LIB "NanoCBOR" CACHE STRING "Cbor library.") # libmcu_cbor, NanoCBOR +option(SECURE_MODE_ENABLED "Enable COSE Security Layer." ON) +option(COSE_STATELESS_COMP_ENABLED "Enable COSE stateless compression." ON) +set(CRYPTO_LIB "ascon-c" CACHE STRING "Crypto library.") # ascon-c, mbedtls +set(AEAD_ALGO "Ascon-AEAD128" CACHE STRING "Algorithm for authenticated encryption with additional data.") # Ascon-AEAD128, Ascon-AEAD128-64, Ascon-AEAD128-32 +set(KDF_ALGO "HKDF-Ascon256" CACHE STRING "Algorithm for key derivation.") +set(PIV_LEN 2 CACHE STRING "Length of Partial IV.") # Max 16 +set(SENDER_ID "0x01" CACHE STRING "Sender ID in hex.") +set(RECEIVER_ID "0x02" CACHE STRING "Receiver ID in hex.") diff --git a/src/desert_classes/CBorStream.h b/src/desert_classes/CBorStream.h index 91a79cb..a95a47e 100644 --- a/src/desert_classes/CBorStream.h +++ b/src/desert_classes/CBorStream.h @@ -52,12 +52,6 @@ /** @endcond */ -#include "cbor/encoder.h" -#include "cbor/ieee754.h" -#include "cbor/decoder.h" -#include "cbor/parser.h" -#include "cbor/helper.h" - #include "half.hpp" #define PUBLISHER_TYPE 0 @@ -225,16 +219,20 @@ class TxStream } private: + // Forward declarations to abstract the implementation + struct WRITER; + struct ERROR; + uint8_t _stream_type; std::string _stream_name; uint8_t _stream_identifier; bool _overflow; - uint8_t * _packet; - cbor_writer_t * _writer; + uint8_t * _packet; + WRITER * _writer; void new_packet(); - void handle_overrun(cbor_error_t result); + void handle_overrun(ERROR result); std::string toUTF8(const std::u16string source); @@ -317,13 +315,6 @@ class RxStream */ RxStream & operator>>(int8_t & n); - /** - * @brief Decode a generic integer - * @param n Field to decode - */ - template - RxStream & deserialize_integer(T & n); - /** * @brief Decode char * @param n Field to decode @@ -428,6 +419,9 @@ class RxStream static void interpret_packets(); private: + // Forward declarations to abstract the implementation + struct ITEM; + uint8_t _stream_type; std::string _stream_name; uint8_t _stream_identifier; @@ -455,7 +449,7 @@ class RxStream static std::mutex _rx_mutex; - static std::pair interpret_field(cbor_item_t * items, size_t i, union _cbor_value & val); + static std::pair interpret_field(ITEM * items, size_t i, union _cbor_value & val); std::u16string toUTF16(const std::string source); }; diff --git a/src/desert_classes/CBorStream.cpp b/src/desert_classes/LibMcuCBorStream.cpp similarity index 79% rename from src/desert_classes/CBorStream.cpp rename to src/desert_classes/LibMcuCBorStream.cpp index 9f43d3c..8275de6 100644 --- a/src/desert_classes/CBorStream.cpp +++ b/src/desert_classes/LibMcuCBorStream.cpp @@ -1,10 +1,27 @@ #include "CBorStream.h" +#include +#include +#include +#include +#include + namespace cbor { // TX stream +// Forward declarations to abstract the implementation +struct TxStream::WRITER +{ + cbor_writer_t libmcu_writer; +}; + +struct TxStream::ERROR +{ + cbor_error_t libmcu_error; +}; + TxStream::TxStream(uint8_t stream_type, std::string stream_name, uint8_t stream_identifier) : _stream_type(stream_type) , _stream_name(stream_name) @@ -16,9 +33,9 @@ void TxStream::new_packet() { // Initialize packet and writer _packet = new uint8_t[MAX_PACKET_LENGTH]; - _writer = new cbor_writer_t; - - cbor_writer_init(_writer, _packet, MAX_PACKET_LENGTH); + _writer = new WRITER; + + cbor_writer_init(&_writer->libmcu_writer, _packet, MAX_PACKET_LENGTH); } void TxStream::start_transmission(uint64_t sequence_id) @@ -45,11 +62,15 @@ void TxStream::start_transmission() void TxStream::end_transmission() { if (_overflow) + { + delete _packet; + delete _writer; return; + } std::vector daemon_packet; - for(size_t i=0; i < cbor_writer_len(_writer); i++) + for(size_t i=0; i < cbor_writer_len(&_writer->libmcu_writer); i++) { daemon_packet.push_back(_packet[i]); } @@ -62,8 +83,8 @@ void TxStream::end_transmission() TxStream & TxStream::operator<<(const uint64_t n) { - cbor_error_t result = cbor_encode_unsigned_integer(_writer, n); - handle_overrun(result); + cbor_error_t result = cbor_encode_unsigned_integer(&_writer->libmcu_writer, n); + handle_overrun(ERROR(result)); return *this; } @@ -90,11 +111,11 @@ TxStream & TxStream::operator<<(const int64_t n) cbor_error_t result; if (n >= 0) - result = cbor_encode_unsigned_integer(_writer, n); + result = cbor_encode_unsigned_integer(&_writer->libmcu_writer, n); else - result = cbor_encode_negative_integer(_writer, n); + result = cbor_encode_negative_integer(&_writer->libmcu_writer, n); - handle_overrun(result); + handle_overrun(ERROR(result)); return *this; } @@ -125,8 +146,8 @@ TxStream & TxStream::operator<<(const char n) TxStream & TxStream::operator<<(const float f) { - cbor_error_t result = cbor_encode_float(_writer, f); - handle_overrun(result); + cbor_error_t result = cbor_encode_float(&_writer->libmcu_writer, f); + handle_overrun(ERROR(result)); return *this; } @@ -138,8 +159,8 @@ TxStream & TxStream::operator<<(const double d) TxStream & TxStream::operator<<(const std::string s) { - cbor_error_t result = cbor_encode_text_string(_writer, s.c_str(), s.size()); - handle_overrun(result); + cbor_error_t result = cbor_encode_text_string(&_writer->libmcu_writer, s.c_str(), s.size()); + handle_overrun(ERROR(result)); return *this; } @@ -152,8 +173,8 @@ TxStream & TxStream::operator<<(const std::u16string s) TxStream & TxStream::operator<<(const bool b) { - cbor_error_t result = cbor_encode_bool(_writer, b); - handle_overrun(result); + cbor_error_t result = cbor_encode_bool(&_writer->libmcu_writer, b); + handle_overrun(ERROR(result)); return *this; } @@ -167,9 +188,9 @@ TxStream & TxStream::operator<<(const std::vector v) return *this; } -void TxStream::handle_overrun(cbor_error_t result) +void TxStream::handle_overrun(ERROR result) { - if (result == CBOR_OVERRUN) + if (result.libmcu_error == CBOR_OVERRUN) { _overflow = true; } @@ -188,6 +209,12 @@ std::string TxStream::toUTF8(const std::u16string source) // RX stream +// Forward declarations to abstract the implementation +struct RxStream::ITEM +{ + cbor_item_t libmcu_item; +}; + RxStream::RxStream(uint8_t stream_type, std::string stream_name, uint8_t stream_identifier) : _stream_type(stream_type) , _stream_name(stream_name) @@ -260,52 +287,67 @@ void RxStream::clear_buffer() RxStream & RxStream::operator>>(uint64_t & n) { - return deserialize_integer(n); + int64_t value; + *this >> value; + n = static_cast(value); + return *this; } RxStream & RxStream::operator>>(uint32_t & n) { - return deserialize_integer(n); + int64_t value; + *this >> value; + n = static_cast(value); + return *this; } RxStream & RxStream::operator>>(uint16_t & n) { - return deserialize_integer(n); + int64_t value; + *this >> value; + n = static_cast(value); + return *this; } RxStream & RxStream::operator>>(uint8_t & n) { - return deserialize_integer(n); + int64_t value; + *this >> value; + n = static_cast(value); + return *this; } RxStream & RxStream::operator>>(int64_t & n) { - return deserialize_integer(n); + if (_buffered_packet.size() > _buffered_iterator && _buffered_packet[_buffered_iterator].second == CBOR_ITEM_INTEGER) + n = *static_cast(_buffered_packet[_buffered_iterator].first); + + _buffered_iterator++; + + return *this; } RxStream & RxStream::operator>>(int32_t & n) { - return deserialize_integer(n); + int64_t value; + *this >> value; + n = static_cast(value); + return *this; } RxStream & RxStream::operator>>(int16_t & n) { - return deserialize_integer(n); + int64_t value; + *this >> value; + n = static_cast(value); + return *this; } RxStream & RxStream::operator>>(int8_t & n) { - return deserialize_integer(n); -} - -template -RxStream & RxStream::deserialize_integer(T & n) -{ - if (_buffered_packet.size() > _buffered_iterator && _buffered_packet[_buffered_iterator].second == CBOR_ITEM_INTEGER) - n = *static_cast(_buffered_packet[_buffered_iterator].first); - - _buffered_iterator++; - + int64_t value; + *this >> value; + n = static_cast(value); return *this; } @@ -413,10 +455,10 @@ void RxStream::interpret_packets() // Initialize buffer and reader uint8_t * buffer = &packet[0]; cbor_reader_t reader; - cbor_item_t items[64]; + ITEM items[64]; size_t n; - cbor_reader_init(&reader, items, sizeof(items) / sizeof(items[0])); + cbor_reader_init(&reader, &items->libmcu_item, sizeof(items) / sizeof(items[0])); cbor_parse(&reader, buffer, packet.size(), &n); uint8_t stream_type; @@ -430,7 +472,7 @@ void RxStream::interpret_packets() union _cbor_value val; memset(&val, 0, sizeof(val)); - cbor_decode(&reader, &items[i], &val, sizeof(val)); + cbor_decode(&reader, &items[i].libmcu_item, &val, sizeof(val)); if (i == 0) { @@ -459,6 +501,14 @@ void RxStream::interpret_packets() if (stream_name.empty()) { + for (auto& field : interpreted_packet) + { + if (field.first) + { + free(field.first); + field.first = nullptr; + } + } continue; } @@ -472,9 +522,9 @@ void RxStream::interpret_packets() } } -std::pair RxStream::interpret_field(cbor_item_t * items, size_t i, union _cbor_value & val) +std::pair RxStream::interpret_field(ITEM * items, size_t i, union _cbor_value & val) { - switch (items[i].type) + switch (items[i].libmcu_item.type) { case CBOR_ITEM_INTEGER: { @@ -502,7 +552,7 @@ std::pair RxStream::interpret_field(cbor_item_t * items, size_t i, } case CBOR_ITEM_STRING: { - val.str_copy[items[i].size] = '\0'; + val.str_copy[items[i].libmcu_item.size] = '\0'; std::string * str = new std::string{reinterpret_cast(val.str_copy)}; return std::make_pair(static_cast(str), CBOR_ITEM_STRING); diff --git a/src/desert_classes/NanoCBorStream.cpp b/src/desert_classes/NanoCBorStream.cpp new file mode 100644 index 0000000..e2e317a --- /dev/null +++ b/src/desert_classes/NanoCBorStream.cpp @@ -0,0 +1,643 @@ +#include "CBorStream.h" + +#include + +#ifdef SECURE_MODE_ENABLED +#include "security/SecurityLayer.h" +#endif + +namespace cbor +{ + +#ifdef SECURE_MODE_ENABLED +static security::SecurityLayer g_sec_layer{}; +#endif + +// TX stream + +// Forward declarations to abstract the implementation +struct TxStream::WRITER +{ + nanocbor_encoder_t nanocbor_encoder; +}; + +struct TxStream::ERROR +{ + nanocbor_error_t nanocbor_error; +}; + +TxStream::TxStream(uint8_t stream_type, std::string stream_name, uint8_t stream_identifier) + : _stream_type(stream_type) + , _stream_name(stream_name) + , _stream_identifier(stream_identifier) +{ +} + +void TxStream::new_packet() +{ + // Initialize packet and writer + _packet = new uint8_t[MAX_PACKET_LENGTH]; + _writer = new WRITER; + + nanocbor_encoder_init(&_writer->nanocbor_encoder, _packet, MAX_PACKET_LENGTH); +} + +void TxStream::start_transmission(uint64_t sequence_id) +{ + new_packet(); + _overflow = false; + + // Stream type, service name/identifier and sequence id + *this << _stream_type; + *this << _stream_identifier; + *this << sequence_id; +} + +void TxStream::start_transmission() +{ + new_packet(); + _overflow = false; + + // Stream type and topic name/identifier + *this << _stream_type; + *this << _stream_identifier; +} + +void TxStream::end_transmission() +{ + if (_overflow) + { + delete _packet; + delete _writer; + return; + } + + size_t encoded_len = nanocbor_encoded_len(&_writer->nanocbor_encoder); + + size_t wrapped_size = encoded_len; + uint8_t* wrapped_ptr = _packet; + +#ifdef SECURE_MODE_ENABLED + auto st = g_sec_layer.wrap(_packet, encoded_len, MAX_PACKET_LENGTH, &wrapped_ptr, &wrapped_size); + if (st != security::OK) + { + delete _packet; + delete _writer; + return; + } +#endif + + std::vector daemon_packet(wrapped_ptr, wrapped_ptr + wrapped_size); + TcpDaemon::enqueue_packet(daemon_packet); + + delete _packet; + delete _writer; +} + +TxStream & TxStream::operator<<(const uint64_t n) +{ + nanocbor_error_t result = (nanocbor_error_t) nanocbor_fmt_uint(&_writer->nanocbor_encoder, n); + handle_overrun(ERROR(result)); + return *this; +} + +TxStream & TxStream::operator<<(const uint32_t n) +{ + *this << static_cast(n); + return *this; +} + +TxStream & TxStream::operator<<(const uint16_t n) +{ + *this << static_cast(n); + return *this; +} + +TxStream & TxStream::operator<<(const uint8_t n) +{ + *this << static_cast(n); + return *this; +} + +TxStream & TxStream::operator<<(const int64_t n) +{ + nanocbor_error_t result; + + if (n >= 0) + { + result = (nanocbor_error_t) nanocbor_fmt_uint(&_writer->nanocbor_encoder, n); + } + else + { + result = (nanocbor_error_t) nanocbor_fmt_int(&_writer->nanocbor_encoder, n); + } + + handle_overrun(ERROR(result)); + + return *this; +} + +TxStream & TxStream::operator<<(const int32_t n) +{ + *this << static_cast(n); + return *this; +} + +TxStream & TxStream::operator<<(const int16_t n) +{ + *this << static_cast(n); + return *this; +} + +TxStream & TxStream::operator<<(const int8_t n) +{ + *this << static_cast(n); + return *this; +} + +TxStream & TxStream::operator<<(const char n) +{ + std::string single_char_string(1, n); + *this << single_char_string; + return *this; +} + +TxStream & TxStream::operator<<(const float f) +{ + nanocbor_error_t result = (nanocbor_error_t) nanocbor_fmt_float(&_writer->nanocbor_encoder, f); + handle_overrun(ERROR(result)); + return *this; +} + +TxStream & TxStream::operator<<(const double d) +{ + *this << static_cast(d); + return *this; +} + +TxStream & TxStream::operator<<(const std::string s) +{ + nanocbor_error_t result = (nanocbor_error_t) nanocbor_put_tstrn(&_writer->nanocbor_encoder, s.c_str(), s.size()); + handle_overrun(ERROR(result)); + return *this; +} + +TxStream & TxStream::operator<<(const std::u16string s) +{ + // Cbor does not support UTF-16 so a conversion to UTF-8 is performed + *this << toUTF8(s); + return *this; +} + +TxStream & TxStream::operator<<(const bool b) +{ + *this << static_cast(b); + return *this; +} + +TxStream & TxStream::operator<<(const std::vector v) +{ + *this << static_cast(v.size()); + for (size_t i = 0; i < v.size(); ++i) + { + *this << v[i]; + } + return *this; +} + +void TxStream::handle_overrun(ERROR result) +{ + if (result.nanocbor_error == NANOCBOR_ERR_END) + { + _overflow = true; + } +} + +std::string TxStream::toUTF8(const std::u16string source) +{ + std::string result; + + std::wstring_convert, char16_t> convertor; + result = convertor.to_bytes(source); + + return result; +} + + +// RX stream + +// Forward declarations to abstract the implementation +struct RxStream::ITEM +{ + nanocbor_value_t nanocbor_value; +}; + +RxStream::RxStream(uint8_t stream_type, std::string stream_name, uint8_t stream_identifier) + : _stream_type(stream_type) + , _stream_name(stream_name) + , _stream_identifier(stream_identifier) +{ + _listening_streams.push_back(this); +} + +RxStream::~RxStream() +{ + std::vector::iterator position = std::find(_listening_streams.begin(), _listening_streams.end(), this); + if (position != _listening_streams.end()) + _listening_streams.erase(position); +} + +const std::map RxStream::_stream_type_match_map = { + { PUBLISHER_TYPE, SUBSCRIBER_TYPE }, + { CLIENT_TYPE, SERVICE_TYPE }, + { SERVICE_TYPE, CLIENT_TYPE } +}; + +std::vector RxStream::_listening_streams; + +std::mutex RxStream::_rx_mutex; + +bool RxStream::data_available(int64_t sequence_id) +{ + // If a packet is already buffered, so do not overwrite it and wait for a clear + if (_buffered_packet.size() > 0) + return true; + + // No buffered packets, go on checking for other ones + if (_received_packets.size() == 0) + return false; + + // The received packets queue is not empty, so examine it + if (sequence_id) + { + for (size_t i = 0; i < _received_packets.size(); i++) + { + // Check for the first element in the packet, which is the sequence id + if (*static_cast(_received_packets.front().at(0).first) == sequence_id) + { + _buffered_packet = _received_packets.front(); + _received_packets.pop(); + _buffered_iterator = 1; + return true; + } + else + { + _received_packets.push(_received_packets.front()); + _received_packets.pop(); + } + } + return false; + } + else + { + _buffered_packet = _received_packets.front(); + _received_packets.pop(); + _buffered_iterator = 0; + return true; + } +} + +void RxStream::clear_buffer() +{ + _buffered_packet.clear(); +} + +RxStream & RxStream::operator>>(uint64_t & n) +{ + if (_buffered_packet.size() > _buffered_iterator && _buffered_packet[_buffered_iterator].second == NANOCBOR_TYPE_UINT) + n = *static_cast(_buffered_packet[_buffered_iterator].first); + + _buffered_iterator++; + + return *this; +} + +RxStream & RxStream::operator>>(uint32_t & n) +{ + uint64_t value; + *this >> value; + n = static_cast(value); + return *this; +} + +RxStream & RxStream::operator>>(uint16_t & n) +{ + uint64_t value; + *this >> value; + n = static_cast(value); + return *this; +} + +RxStream & RxStream::operator>>(uint8_t & n) +{ + uint64_t value; + *this >> value; + n = static_cast(value); + return *this; +} + +RxStream & RxStream::operator>>(int64_t & n) +{ + int type = _buffered_packet[_buffered_iterator].second; + if (_buffered_packet.size() > _buffered_iterator && (type == NANOCBOR_TYPE_UINT || type == NANOCBOR_TYPE_NINT)) + n = *static_cast(_buffered_packet[_buffered_iterator].first); + + _buffered_iterator++; + + return *this; +} + +RxStream & RxStream::operator>>(int32_t & n) +{ + int64_t value; + *this >> value; + n = static_cast(value); + return *this; +} + +RxStream & RxStream::operator>>(int16_t & n) +{ + int64_t value; + *this >> value; + n = static_cast(value); + return *this; +} + +RxStream & RxStream::operator>>(int8_t & n) +{ + int64_t value; + *this >> value; + n = static_cast(value); + return *this; +} + +RxStream & RxStream::operator>>(char & n) +{ + if (_buffered_packet.size() > _buffered_iterator && _buffered_packet[_buffered_iterator].second == NANOCBOR_TYPE_TSTR) + n = (*static_cast(_buffered_packet[_buffered_iterator].first))[0]; + + _buffered_iterator++; + + return *this; +} + +RxStream & RxStream::operator>>(float & f) +{ + if (_buffered_packet.size() > _buffered_iterator && _buffered_packet[_buffered_iterator].second == NANOCBOR_TYPE_FLOAT) + f = *static_cast(_buffered_packet[_buffered_iterator].first); + + _buffered_iterator++; + + return *this; +} + +RxStream & RxStream::operator>>(double & d) +{ + float value; + *this >> value; + d = static_cast(value); + return *this; +} + +RxStream & RxStream::operator>>(std::string & s) +{ + if (_buffered_packet.size() > _buffered_iterator && _buffered_packet[_buffered_iterator].second == NANOCBOR_TYPE_TSTR) + s = *static_cast(_buffered_packet[_buffered_iterator].first); + + _buffered_iterator++; + + return *this; +} + +RxStream & RxStream::operator>>(std::u16string & s) +{ + std::string str; + *this >> str; + + s = toUTF16(str); + return *this; +} + +RxStream & RxStream::operator>>(bool & b) +{ + int8_t b_; + + if (_buffered_packet.size() > _buffered_iterator && _buffered_packet[_buffered_iterator].second == NANOCBOR_TYPE_UINT) + { + b_ = *static_cast(_buffered_packet[_buffered_iterator].first); + b = b_ ? true : false; + + _buffered_iterator++; + } + + return *this; +} + +RxStream & RxStream::operator>>(std::vector & v) +{ + uint32_t size; + *this >> size; + for (size_t i = 0; i < size; ++i) + { + int b; + *this >> b; + v[i] = b ? true : false; + } + return *this; +} + +uint8_t RxStream::get_type() const +{ + return _stream_type; +} + +std::string RxStream::get_name() const +{ + return _stream_name; +} + +uint8_t RxStream::get_identifier() const +{ + return _stream_identifier; +} + +void RxStream::push_packet(std::vector> packet) +{ + _received_packets.push(packet); +} + +void RxStream::interpret_packets() +{ + std::lock_guard lock(_rx_mutex); + + std::vector packet; + for (packet = TcpDaemon::read_packet(); packet.size() != 0; packet = TcpDaemon::read_packet()) + { + // Initialize buffer and reader + uint8_t * buffer = &packet[0]; + size_t unwrapped_size = packet.size(); + +#ifdef SECURE_MODE_ENABLED +#ifdef COSE_STATELESS_COMP_ENABLED + // Need some extra space for decompression + packet.resize(packet.size() + COSE_SERIALIZATION_MAX_OVERHEAD); +#endif + auto unwrap_st = g_sec_layer.unwrap(buffer, unwrapped_size, packet.size(), &unwrapped_size); + if (unwrap_st != security::OK) + { + continue; + } +#endif + + ITEM decoder; + nanocbor_decoder_init(&decoder.nanocbor_value, buffer, unwrapped_size); + + uint8_t stream_type; + uint8_t stream_identifier; + std::string stream_name; + + std::vector> interpreted_packet; + + for (size_t i = 0; !nanocbor_at_end(&decoder.nanocbor_value); i++) + { + union _cbor_value val; + + if (i == 0) + { + if (nanocbor_get_uint8(&decoder.nanocbor_value, &stream_type) < 0) + { + break; + } + } + else if (i == 1) + { + if (nanocbor_get_uint8(&decoder.nanocbor_value, &stream_identifier) < 0) + { + break; + } + + stream_name = TopicsConfig::get_identifier_topic(stream_identifier); + + if (stream_name.empty()) + { + break; + } + } + else + { + std::pair field = interpret_field(&decoder, 0, val); + + if (field.first) + { + interpreted_packet.push_back(field); + } + } + } + + if (stream_name.empty()) + { + for (auto& field : interpreted_packet) + { + if (field.first) + { + free(field.first); + field.first = nullptr; + } + } + continue; + } + + for (RxStream * stream : _listening_streams) + { + if (stream->get_type() == _stream_type_match_map.at(stream_type) && stream->get_identifier() == stream_identifier) + { + stream->push_packet(interpreted_packet); + } + } + } +} + +std::pair RxStream::interpret_field(ITEM * cbor_value, size_t i, union _cbor_value & val) +{ + (void) i; + (void) val; + int cbor_value_type = nanocbor_get_type(&cbor_value->nanocbor_value); + switch (cbor_value_type) + { + case NANOCBOR_TYPE_NINT: + { + int64_t val; + if (nanocbor_get_int64(&cbor_value->nanocbor_value, &val) < 0) + { + break; + } + + int64_t * number = new int64_t{val}; + return std::make_pair(number, NANOCBOR_TYPE_NINT); + } + case NANOCBOR_TYPE_UINT: + { + uint64_t val; + if (nanocbor_get_uint64(&cbor_value->nanocbor_value, &val) < 0) + { + break; + } + + uint64_t * number = new uint64_t{val}; + return std::make_pair(number, NANOCBOR_TYPE_UINT); + } + case NANOCBOR_TYPE_FLOAT: + { + float val; + if (nanocbor_get_float(&cbor_value->nanocbor_value, &val) < 0) + { + break; + } + + float * f = new float{val}; + return std::make_pair(static_cast(f), NANOCBOR_TYPE_FLOAT); + } + case NANOCBOR_TYPE_TSTR: + { + const char * val; + size_t val_size; + if (nanocbor_get_tstr(&cbor_value->nanocbor_value, (const uint8_t**) &val, &val_size) < 0) + { + break; + } + + std::string * s = new std::string(val, val_size); + return std::make_pair(s, NANOCBOR_TYPE_TSTR); + } + case NANOCBOR_TYPE_BSTR: + { + const char* val; + size_t val_size; + if (nanocbor_get_bstr(&cbor_value->nanocbor_value, (const uint8_t**) &val, &val_size) < 0) + { + break; + } + + std::string * s = new std::string(val, val_size); + return std::make_pair(s, NANOCBOR_TYPE_TSTR); + } + default: + nanocbor_skip(&cbor_value->nanocbor_value); + return std::make_pair(nullptr, 0); + } + + nanocbor_skip(&cbor_value->nanocbor_value); + return std::make_pair(nullptr, 0); +} + +std::u16string RxStream::toUTF16(const std::string source) +{ + std::u16string result; + + std::wstring_convert, char16_t> convertor; + result = convertor.from_bytes(source); + + return result; +} + +} diff --git a/src/desert_classes/security/AeadParams.cpp b/src/desert_classes/security/AeadParams.cpp new file mode 100644 index 0000000..c6bf223 --- /dev/null +++ b/src/desert_classes/security/AeadParams.cpp @@ -0,0 +1,39 @@ +#include "SecurityParams.h" + +#include + +namespace security +{ + +AeadParams::AeadParams(AeadAlgorithms alg) + : alg_(alg) +{ + switch (alg_) + { + case ASCON_AEAD128: + key_size_ = 16; + nonce_size_ = 16; + tag_size_ = 16; + break; + case ASCON_AEAD128_64: + key_size_ = 16; + nonce_size_ = 16; + tag_size_ = 8; + break; + case ASCON_AEAD128_32: + key_size_ = 16; + nonce_size_ = 16; + tag_size_ = 4; + break; + case A128GCM: + key_size_ = 16; + nonce_size_ = 12; + tag_size_ = 16; + break; + default: + throw std::runtime_error("Invalid security profile"); + } +} + +} // namespace security + diff --git a/src/desert_classes/security/SecurityLayer.cpp b/src/desert_classes/security/SecurityLayer.cpp new file mode 100644 index 0000000..ee80797 --- /dev/null +++ b/src/desert_classes/security/SecurityLayer.cpp @@ -0,0 +1,441 @@ +#include +#include + +#include "SecurityLayer.h" + +#include + +#include "sec_utils.h" + +#define KDF_INFO_TYPE_KEY "Key" +#define KDF_INFO_TYPE_IV "IV" + +namespace security +{ + +static AeadAlgorithms str_to_aead_algo(const std::string& str) { + if (str == AEAD_ALGO_ASCON_128) { + return ASCON_AEAD128; + } + if (str == AEAD_ALGO_ASCON_128_64) { + return ASCON_AEAD128_64; + } + if (str == AEAD_ALGO_ASCON_128_32) { + return ASCON_AEAD128_32; + } + + return AEAD_UNKNOWN; +} + +static KdfAlgorithms str_to_kdf_algo(const std::string& str) { + if (str == KDF_ALGO_ASCON_256) { + return HKDF_ASCON; + } + + return KDF_UNKNOWN; +} + +static cose_algo_t aead_algo_to_cose_algo(AeadAlgorithms algo) +{ + switch (algo) + { + case ASCON_AEAD128: + return COSE_ALGO_ASCON_AEAD128; + case ASCON_AEAD128_64: + return COSE_ALGO_ASCON_AEAD128_64; + case ASCON_AEAD128_32: + return COSE_ALGO_ASCON_AEAD128_32; + case A128GCM: + return COSE_ALGO_A128GCM; + default: + return COSE_ALGO_NONE; + } +} + +static cose_algo_t kdf_algo_to_cose_algo(KdfAlgorithms algo) { + switch (algo) { + case HKDF_ASCON: + return COSE_ALGO_HMAC_ASCON_HASH256; + case HKDF_HMAC256: + return COSE_ALGO_HMAC256; + default: + return COSE_ALGO_NONE; + } +} + +static SecurityResult cbor_err_to_sec_err(int err) { + switch (err) { + case 0: + return OK; + case NANOCBOR_ERR_END: + return BUFFER_ERROR; + } + + return INTERNAL_ERROR; +} + +SecurityLayer::SecurityLayer() + : aead_params_(AeadParams(str_to_aead_algo(AEAD_ALGO))), + kdf_(str_to_kdf_algo(KDF_ALGO)), + piv_size_(PIV_LEN), + piv_bytes_(piv_size_), + sender_seq_number_(0) +{ + if (!decode_hex(SENDER_ID, sender_id_)) { + throw std::runtime_error("Failed to parse the sender id"); + } + + if (!decode_hex(RECEIVER_ID, receiver_id_)) { + throw std::runtime_error("Failed to parse the receiver id"); + } + + std::vector master_key{}; + if (get_master_key_env(master_key) != OK) + { + throw std::runtime_error("Failed to parse the encryption key"); + } + + std::vector master_salt{}; + if (get_master_salt_env(master_salt) != OK) + { + master_salt = std::vector(DEFAULT_MASTER_SALT_LEN, 0); + } + + auto res = derive_context(master_key, master_salt); + if (res != OK) { + throw std::runtime_error("Security context initialization error"); + } +} + +SecurityLayer::SecurityLayer(const AeadParams &aead_params, KdfAlgorithms kdf, const std::vector &master_key, + const std::vector &master_salt, size_t piv_size, size_t sender_seq_number, const std::vector &sender_id, + const std::vector &receiver_id) + : aead_params_(aead_params), + kdf_(kdf), + piv_size_(piv_size), + piv_bytes_(piv_size), + sender_seq_number_(sender_seq_number), + sender_id_(sender_id), + receiver_id_(receiver_id) +{ + auto res = derive_context(master_key, master_salt); + if (res != OK) { + throw std::runtime_error("Security context initialization error"); + } +} + +SecurityResult SecurityLayer::build_kdf_info(const std::vector& id, cose_algo_t alg, const std::string& type, size_t len, uint8_t** out, size_t* out_len) +{ + nanocbor_encoder_t info; + int st = COSE_OK; + nanocbor_encoder_init(&info, internal_buf_, sizeof(internal_buf_)); + st = nanocbor_fmt_array(&info, 4); + if (st < 0) { + return cbor_err_to_sec_err(st); + } + + st = nanocbor_put_bstr(&info, id.data(), id.size()); + if (st < 0) { + return cbor_err_to_sec_err(st); + } + + st = nanocbor_fmt_int(&info, alg); + if (st < 0) { + return cbor_err_to_sec_err(st); + } + + st = nanocbor_put_tstrn(&info, type.data(), type.size()); + if (st < 0) { + return cbor_err_to_sec_err(st); + } + + st = nanocbor_fmt_uint(&info, len); + if (st < 0) { + return cbor_err_to_sec_err(st); + } + + *out = internal_buf_; + *out_len = nanocbor_encoded_len(&info); + + return OK; +} + +SecurityResult SecurityLayer::derive_key(const std::vector& ikm, const std::vector& master_salt, const std::vector& id, cose_algo_t alg, std::vector& key_out, cose_key_t* cose_k_out) +{ + uint8_t* info_ptr; + size_t info_len; + auto info_res = build_kdf_info(id, + alg, + KDF_INFO_TYPE_KEY, + aead_params_.get_key_size(), + &info_ptr, + &info_len); + if (info_res != OK) { + return info_res; + } + + key_out.resize(aead_params_.get_key_size()); + auto kdf_res = cose_crypto_hkdf_derive(master_salt.data(), + master_salt.size(), + ikm.data(), + aead_params_.get_key_size(), + info_ptr, + info_len, + key_out.data(), + aead_params_.get_key_size(), + kdf_algo_to_cose_algo(kdf_)); + if (kdf_res != COSE_OK) { + return CONTEXT_DERIVE_ERROR; + } + + cose_key_init(cose_k_out); + cose_key_set_keys(cose_k_out, COSE_EC_NONE, alg, nullptr, nullptr, key_out.data()); + + return OK; +} + +SecurityResult SecurityLayer::derive_iv(const std::vector& ikm, const std::vector& master_salt, cose_algo_t alg, std::vector& iv_out) +{ + uint8_t* info_ptr; + size_t info_len; + auto info_res = build_kdf_info({}, + alg, + KDF_INFO_TYPE_IV, + aead_params_.get_nonce_size(), + &info_ptr, + &info_len); + if (info_res != OK) { + return info_res; + } + + iv_out.resize(aead_params_.get_nonce_size()); + auto kdf_res = cose_crypto_hkdf_derive(master_salt.data(), + master_salt.size(), + ikm.data(), + aead_params_.get_key_size(), + info_ptr, + info_len, + iv_out.data(), + aead_params_.get_nonce_size(), + kdf_algo_to_cose_algo(kdf_)); + if (kdf_res != COSE_OK) { + return CONTEXT_DERIVE_ERROR; + } + + return OK; +} + +SecurityResult SecurityLayer::get_master_key_env(std::vector& master_key) const +{ + const char* env_value = std::getenv("MASTER_SECRET_KEY"); + if (env_value == nullptr || !decode_hex(env_value, master_key)) + { + printf("CRITICAL: Master Secret Key is invalid\n"); + printf("Please fill the MASTER_SECRET_KEY environment variable with stream of hexadecimal bytes\n"); + return KEY_ENV_ERROR; + } + if (master_key.size() < aead_params_.get_key_size()) + { + printf("CRITICAL: the master secret key size does not match the required suite\n"); + printf("Please set a MASTER_SECRET_KEY of %d bytes\n", aead_params_.get_key_size()); + return KEY_SIZE_ERROR; + } + + return OK; +} + +SecurityResult SecurityLayer::get_master_salt_env(std::vector& master_salt) const +{ + const char* env_value = std::getenv("MASTER_SALT"); + if (env_value == nullptr || !decode_hex(env_value, master_salt)) + { + printf("CRITICAL: Master Salt is invalid\n"); + printf("Please fill the MASTER_SALT environment variable with stream of hexadecimal bytes\n"); + return NONCE_ENV_ERROR; + } + + return OK; +} + +/* Generating Partial Initialization Vector instead of full nonce */ +SecurityResult SecurityLayer::generate_nonce() +{ + uint32_t seq = htonl(sender_seq_number_); + uint32_t offset = MIN(sizeof(seq), piv_size_); + memcpy(piv_bytes_.data(), reinterpret_cast(&seq) + (sizeof(seq) - offset), offset); + return OK; +} + +SecurityResult SecurityLayer::derive_context(const std::vector& master_key, const std::vector& master_salt) +{ + // Master Secret and Master Salt have to be set already + cose_algo_t algo = aead_algo_to_cose_algo(aead_params_.get_alg()); + if (algo == COSE_ALGO_NONE) { + return PARAM_ERROR; + } + + // Derive Sender Key + auto res = derive_key(master_key, master_salt, sender_id_, algo, sender_key_bytes_, &sender_cose_key_); + if (res != OK) { + return res; + } + // Derive Receiver Key + res = derive_key(master_key, master_salt, receiver_id_, algo, receiver_key_bytes_, &receiver_cose_key_); + if (res != OK) { + return res; + } + + // Derive Context IV + res = derive_iv(master_key, master_salt, algo, context_iv_); + return res; +} + +SecurityResult SecurityLayer::cose_stateless_compress(uint8_t* cose, size_t cose_len, uint8_t** out, size_t* out_len) const +{ + /* + 83 # array(3) + 40 # bytes(0) + # "" + A1 # map(1) + 06 # unsigned(6) + 42 # bytes(2) + 00FE # "\u0000\xFE" + 58 18 # bytes(24) + 78AEE7DB75167291B572751B304A662925F1C71A36EED8E6 + */ + uint8_t piv_offset = 5; + const uint8_t* piv_ptr = cose + piv_offset; + const uint8_t* ciphertext_bstr = piv_ptr + piv_size_; + size_t ciphertext_bstr_len = cose + cose_len - ciphertext_bstr; + nanocbor_value_t v; + nanocbor_decoder_init(&v, ciphertext_bstr, ciphertext_bstr_len); + + const uint8_t* ciphertext; + size_t ciphertext_len; + int res = nanocbor_get_bstr(&v, &ciphertext, &ciphertext_len); + if (res < 0) { + return COMP_ERROR; + } + uint8_t* pkt_start = const_cast(ciphertext) - piv_size_; + memmove(pkt_start, piv_ptr, piv_size_); + + *out = pkt_start; + *out_len = piv_size_ + ciphertext_len; + return OK; +} + +SecurityResult SecurityLayer::cose_stateless_decompress(uint8_t* data, size_t data_len, size_t data_max_len, size_t* out_len) const +{ + static constexpr uint8_t cose_prefix[] = {0x83, 0x40, 0xA1, 0x06}; + static constexpr size_t cose_prefix_len = sizeof(cose_prefix); + + uint8_t* payload_ptr = data + piv_size_; + size_t payload_len = data_len - piv_size_; + + nanocbor_encoder_t enc; + nanocbor_encoder_init(&enc, nullptr, 0); + nanocbor_fmt_bstr(&enc, piv_size_); + size_t piv_ind_len = nanocbor_encoded_len(&enc); + + nanocbor_encoder_init(&enc, nullptr, 0); + nanocbor_fmt_bstr(&enc, payload_len); + size_t pl_ind_len = nanocbor_encoded_len(&enc); + + size_t decompressed_len = cose_prefix_len + piv_ind_len + piv_size_ + pl_ind_len + payload_len; + if (decompressed_len > data_max_len) { + return BUFFER_ERROR; + } + + memmove(payload_ptr + cose_prefix_len + piv_ind_len + pl_ind_len, payload_ptr, payload_len); + memmove(data + cose_prefix_len + piv_ind_len, data, piv_size_); + memcpy(data, cose_prefix, cose_prefix_len); + + uint8_t* piv_bstr_ptr = data + cose_prefix_len; + nanocbor_encoder_init(&enc, piv_bstr_ptr, piv_ind_len); + int res = nanocbor_fmt_bstr(&enc, piv_size_); + if (res < 0) { + return INTERNAL_ERROR; + } + + uint8_t* pl_bstr_ptr = payload_ptr + cose_prefix_len + piv_ind_len; + nanocbor_encoder_init(&enc, pl_bstr_ptr, payload_len); + res = nanocbor_fmt_bstr(&enc, payload_len); + if (res < 0) { + return INTERNAL_ERROR; + } + + *out_len = decompressed_len; + return OK; +} + +SecurityResult SecurityLayer::wrap(uint8_t* data, size_t data_len, size_t data_max_len, uint8_t** cose_ptr, size_t* cose_len) +{ + auto st = generate_nonce(); + if (st != OK) + { + return st; + } + std::vector payload(data, data + data_len); + + cose_encrypt_t crypt; + cose_encrypt_init(&crypt, COSE_FLAGS_ENCRYPT0 | COSE_FLAGS_UNTAGGED); + + cose_hdr_t piv = { + .next = nullptr, + .key = COSE_HDR_PARTIALIV, + .len = piv_size_, + .v = { + .data = piv_bytes_.data() + }, + .type = COSE_HDR_TYPE_BSTR + }; + cose_hdr_insert(&crypt.hdrs.unprot, &piv); + + cose_encrypt_add_recipient(&crypt, &sender_cose_key_); + cose_encrypt_set_payload(&crypt, payload.data(), payload.size()); + cose_encrypt_set_algo(&crypt, COSE_ALGO_DIRECT); + COSE_ssize_t len = cose_encrypt_encode(&crypt, data, data_max_len, context_iv_.data(), cose_ptr); + if (len <= 0) + { + return WRAP_ERROR; + } + *cose_len = len; +#ifdef COSE_STATELESS_COMP_ENABLED + st = cose_stateless_compress(*cose_ptr, len, cose_ptr, cose_len); + if (st != OK) { + return st; + } +#endif + sender_seq_number_++; + return OK; +} + +SecurityResult SecurityLayer::unwrap(uint8_t* data, size_t data_len, size_t data_max_len, size_t* new_data_len) +{ +#ifdef COSE_STATELESS_COMP_ENABLED + auto st = cose_stateless_decompress(data, data_len, data_max_len, &data_len); + if (st != OK) { + return st; + } +#else + (void)data_max_len; +#endif + + cose_encrypt_dec_t decrypt; + if (cose_encrypt_decode(&decrypt, data, data_len) != COSE_OK) + { + return UNWRAP_ERROR; + } + + std::vector plaintext(data_len); + if (cose_encrypt_decrypt_lw(&decrypt, nullptr, &receiver_cose_key_, internal_buf_, sizeof(internal_buf_), + plaintext.data(), new_data_len, context_iv_.data()) != COSE_OK) + { + return UNWRAP_ERROR; + } + memcpy(data, plaintext.data(), *new_data_len); + + return OK; +} + +} // namespace security diff --git a/src/desert_classes/security/SecurityLayer.h b/src/desert_classes/security/SecurityLayer.h new file mode 100644 index 0000000..58d6606 --- /dev/null +++ b/src/desert_classes/security/SecurityLayer.h @@ -0,0 +1,84 @@ +#ifndef SECURITY_LAYER_H +#define SECURITY_LAYER_H + +#include +#include +#include + +#include +#include + +#include "SecurityParams.h" + +#define DEFAULT_MASTER_SALT_LEN 16 +#define COSE_SERIALIZATION_MAX_OVERHEAD 12 +#define COSE_INTERNAL_BUF_SIZE 512 + +namespace security +{ + +enum SecurityResult +{ + OK = 0, + PARAM_ERROR = -1, + KEY_SIZE_ERROR = -2, + KEY_ENV_ERROR = -3, + NONCE_ENV_ERROR = -4, + NONCE_SIZE_ERROR = -5, + BUFFER_ERROR = -6, + CONTEXT_DERIVE_ERROR = -7, + COMP_ERROR = -8, + WRAP_ERROR = -100, + UNWRAP_ERROR = -200, + INTERNAL_ERROR = -999 +}; + +class SecurityLayer +{ + public: + SecurityLayer(); + SecurityLayer(const AeadParams &aead_params, KdfAlgorithms kdf, const std::vector &master_key, + const std::vector &master_salt, size_t piv_size, size_t sender_seq_number, + const std::vector &sender_id, const std::vector &receiver_id); + + SecurityResult wrap(uint8_t* data, size_t data_len, size_t data_max_len, uint8_t** cose_ptr, size_t* cose_len); + SecurityResult unwrap(uint8_t* data, size_t data_len, size_t data_max_len, size_t* new_data_len); + + private: + SecurityResult get_master_key_env(std::vector& master_key) const; + SecurityResult get_master_salt_env(std::vector& master_salt) const; + SecurityResult generate_nonce(); + SecurityResult build_kdf_info(const std::vector& id, cose_algo_t alg, const std::string& type, size_t len, uint8_t** out, size_t* out_len); + SecurityResult derive_key(const std::vector& ikm, const std::vector& salt, const std::vector& id, cose_algo_t algo, std::vector& key_out, cose_key_t* cose_k_out); + SecurityResult derive_iv(const std::vector& ikm, const std::vector& salt, cose_algo_t alg, std::vector& iv_out); + SecurityResult derive_context(const std::vector& master_key, const std::vector& master_salt); + SecurityResult cose_stateless_compress(uint8_t* cose, size_t cose_len, uint8_t** out, size_t* out_len) const; + SecurityResult cose_stateless_decompress(uint8_t* data, size_t data_len, size_t data_max_len, size_t* out_len) const; + + /* Algorithms to use */ + AeadParams aead_params_; + KdfAlgorithms kdf_; + /* ***************** */ + + /* Nonce-related fields */ + std::vector context_iv_{}; + size_t piv_size_; + std::vector piv_bytes_{}; + size_t sender_seq_number_; + /* ******************** */ + + /* Key-related fields */ + std::vector sender_id_{}; + std::vector sender_key_bytes_{}; + cose_key_t sender_cose_key_{}; + std::vector receiver_id_{}; + std::vector receiver_key_bytes_{}; + cose_key_t receiver_cose_key_{}; + /* ****************** */ + + uint8_t internal_buf_[COSE_INTERNAL_BUF_SIZE]{}; +}; + +} // namespace security + +#endif diff --git a/src/desert_classes/security/SecurityParams.h b/src/desert_classes/security/SecurityParams.h new file mode 100644 index 0000000..9782b69 --- /dev/null +++ b/src/desert_classes/security/SecurityParams.h @@ -0,0 +1,49 @@ +#ifndef SECURITY_CONFIG_H +#define SECURITY_CONFIG_H + +#include + +#define AEAD_ALGO_ASCON_128 "Ascon-AEAD128" +#define AEAD_ALGO_ASCON_128_64 "Ascon-AEAD128-64" +#define AEAD_ALGO_ASCON_128_32 "Ascon-AEAD128-32" + +#define KDF_ALGO_ASCON_256 "HKDF-Ascon256" + +namespace security +{ + +enum AeadAlgorithms +{ + ASCON_AEAD128, + ASCON_AEAD128_64, + ASCON_AEAD128_32, + A128GCM, + AEAD_UNKNOWN +}; + +enum KdfAlgorithms { + HKDF_ASCON, + HKDF_HMAC256, + KDF_UNKNOWN +}; + +class AeadParams +{ + public: + AeadParams(AeadAlgorithms alg); + + AeadAlgorithms get_alg() const { return alg_; } + uint16_t get_key_size() const { return key_size_; } + uint16_t get_nonce_size() const { return nonce_size_; } + uint16_t get_tag_size() const { return tag_size_; } + + private: + AeadAlgorithms alg_; + uint16_t key_size_; + uint16_t nonce_size_; + uint16_t tag_size_; +}; + +} // namespace security + +#endif diff --git a/src/desert_classes/security/sec_utils.cpp b/src/desert_classes/security/sec_utils.cpp new file mode 100644 index 0000000..2ed8c87 --- /dev/null +++ b/src/desert_classes/security/sec_utils.cpp @@ -0,0 +1,42 @@ +#include "sec_utils.h" + +static int hex_value(char c) +{ + if (c >= '0' && c <= '9') return c - '0'; + if (c >= 'a' && c <= 'f') return 10 + (c - 'a'); + if (c >= 'A' && c <= 'F') return 10 + (c - 'A'); + return -1; +} + +bool decode_hex(const std::string& hex, std::vector& out) +{ + size_t start = 0; + if (hex.size() >= 2 && hex[0] == '0' && (hex[1] == 'x' || hex[1] == 'X')) + { + start = 2; + } + + size_t hex_length = hex.size() - start; + if (hex_length % 2 != 0) + { + return false; + } + + out.clear(); + out.reserve(hex_length / 2); + + for (size_t i = start; i < hex.size(); i += 2) + { + const int hi = hex_value(hex[i]); + const int lo = hex_value(hex[i + 1]); + if (hi < 0 || lo < 0) + { + out.clear(); + return false; + } + + out.push_back(static_cast((hi << 4) | lo)); + } + + return true; +} diff --git a/src/desert_classes/security/sec_utils.h b/src/desert_classes/security/sec_utils.h new file mode 100644 index 0000000..f52cf2a --- /dev/null +++ b/src/desert_classes/security/sec_utils.h @@ -0,0 +1,12 @@ +#ifndef SEC_UTILS_H +#define SEC_UTILS_H + +#include +#include +#include + +#define MIN(a, b) ((a) > (b) ? (b) : (a)) + +bool decode_hex(const std::string& hex, std::vector& out); + +#endif diff --git a/thirdparty/NanoCBOR b/thirdparty/NanoCBOR new file mode 160000 index 0000000..3f0f042 --- /dev/null +++ b/thirdparty/NanoCBOR @@ -0,0 +1 @@ +Subproject commit 3f0f0422b86aed01c98c1f14bbcb58061012d805 diff --git a/thirdparty/ascon-c b/thirdparty/ascon-c new file mode 160000 index 0000000..677ef5e --- /dev/null +++ b/thirdparty/ascon-c @@ -0,0 +1 @@ +Subproject commit 677ef5e514495c3afbdd0baa277fc26e7d9f2e89 diff --git a/thirdparty/libcose b/thirdparty/libcose new file mode 160000 index 0000000..2c22884 --- /dev/null +++ b/thirdparty/libcose @@ -0,0 +1 @@ +Subproject commit 2c22884da2bcb80e7bb921d62bd6d8912779473e diff --git a/thirdparty/mbedtls b/thirdparty/mbedtls new file mode 160000 index 0000000..3a91dad --- /dev/null +++ b/thirdparty/mbedtls @@ -0,0 +1 @@ +Subproject commit 3a91dad9dceb484eea8b41f8941facafc4520021