From 760ceca7855c0eb7bdb8c0e396006b5ebaa54401 Mon Sep 17 00:00:00 2001 From: Patrick Rathje Date: Tue, 18 Aug 2020 22:46:49 +0200 Subject: [PATCH] Add platformIO files and adapt file structure --- .gitignore | 46 ++++ CMakeLists.txt | 13 - {src => include}/tls_config/user-tls.conf | 0 lib/README | 46 ++++ lib/exposure-notification/examples/tracking.c | 52 ++++ .../include}/exposure-notification.h | 0 lib/exposure-notification/library.json | 14 ++ .../src}/exposure-notification.c | 15 +- platformio.ini | 36 +++ test/README | 11 + test/test_common/test_common.cpp | 222 ++++++++++++++++++ zephyr/CMakeLists.txt | 6 + prj.conf => zephyr/prj.conf | 2 - 13 files changed, 443 insertions(+), 20 deletions(-) delete mode 100644 CMakeLists.txt rename {src => include}/tls_config/user-tls.conf (100%) create mode 100644 lib/README create mode 100644 lib/exposure-notification/examples/tracking.c rename {src => lib/exposure-notification/include}/exposure-notification.h (100%) create mode 100644 lib/exposure-notification/library.json rename {src => lib/exposure-notification/src}/exposure-notification.c (96%) create mode 100644 platformio.ini create mode 100644 test/README create mode 100644 test/test_common/test_common.cpp create mode 100644 zephyr/CMakeLists.txt rename prj.conf => zephyr/prj.conf (81%) diff --git a/.gitignore b/.gitignore index 567609b..85ad76a 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,47 @@ build/ + +# Random seed file created by test scripts and sample programs +seedfile + +# CMake build artifacts: +CMakeCache.txt +CMakeFiles +CTestTestfile.cmake +cmake_install.cmake +Testing +# CMake generates *.dir/ folders for in-tree builds (used by MSVC projects), ignore all of those: +*.dir/ +# MSVC files generated by CMake: +/*.sln +/*.vcxproj +/*.filters + +# Test coverage build artifacts: +Coverage +*.gcno +*.gcda + +# generated by scripts/memory.sh +massif-* + +# MSVC build artifacts: +*.exe +*.pdb +*.ilk +*.lib + +# Python build artifacts: +*.pyc + +# Generated documentation: +/apidoc + +# Editor navigation files: +/GPATH +/GRTAGS +/GSYMS +/GTAGS +/TAGS +/tags +.pio +.vscode \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt deleted file mode 100644 index ec9b378..0000000 --- a/CMakeLists.txt +++ /dev/null @@ -1,13 +0,0 @@ -# SPDX-License-Identifier: Apache-2.0 - -cmake_minimum_required(VERSION 3.13.1) -find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) -project(bluecovid) - -target_sources(app PRIVATE src/main.c src/contacts.c src/exposure-notification.c src/gatt-service.c src/covid.c) - -zephyr_include_directories(${APPLICATION_SOURCE_DIR}/src/tls_config) - -#use for testing only, will update keys every X seconds, default is 600 -#add_definitions(-DEN_INTERVAL_LENGTH=11) -#add_definitions(-DEN_INTERVAL_LENGTH=1) diff --git a/src/tls_config/user-tls.conf b/include/tls_config/user-tls.conf similarity index 100% rename from src/tls_config/user-tls.conf rename to include/tls_config/user-tls.conf diff --git a/lib/README b/lib/README new file mode 100644 index 0000000..6debab1 --- /dev/null +++ b/lib/README @@ -0,0 +1,46 @@ + +This directory is intended for project specific (private) libraries. +PlatformIO will compile them to static libraries and link into executable file. + +The source code of each library should be placed in a an own separate directory +("lib/your_library_name/[here are source files]"). + +For example, see a structure of the following two libraries `Foo` and `Bar`: + +|--lib +| | +| |--Bar +| | |--docs +| | |--examples +| | |--src +| | |- Bar.c +| | |- Bar.h +| | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html +| | +| |--Foo +| | |- Foo.c +| | |- Foo.h +| | +| |- README --> THIS FILE +| +|- platformio.ini +|--src + |- main.c + +and a contents of `src/main.c`: +``` +#include +#include + +int main (void) +{ + ... +} + +``` + +PlatformIO Library Dependency Finder will find automatically dependent +libraries scanning project source files. + +More information about PlatformIO Library Dependency Finder +- https://docs.platformio.org/page/librarymanager/ldf.html diff --git a/lib/exposure-notification/examples/tracking.c b/lib/exposure-notification/examples/tracking.c new file mode 100644 index 0000000..e0b7ffa --- /dev/null +++ b/lib/exposure-notification/examples/tracking.c @@ -0,0 +1,52 @@ +#include "exposure-notification.h" +#include "time.h" + + +int main(int argc, char **argv) { + + // first init everything + // TODO: More randomization, we init with NULL to let en init mbdet entropy for us + en_init(NULL); + + // get the currentTime + time_t currentTime = time(NULL); + + // The periodKey changes each EN_TEK_ROLLING_PERIOD intervals + ENIntervalNumber periodInterval = en_get_interval_number_at_period_start(currentTime); + + ENPeriodKey periodKey; + ENPeriodIdentifierKey periodIdentifierKey; + ENPeriodMetadataEncryptionKey periodMetadaEncryptionKey; + + // setup period keys at the beginning + // in theory you could let them generate automatically setting periodInterval to 0 + en_generate_and_derive_period_keys(&periodKey, &periodIdentifierKey, &periodMetadaEncryptionKey); + + char metadata[128] = ""; + + while(1) { + // we check the current time to know if we actually need to regenerate anything + currentTime = time(NULL); + ENIntervalNumber currentInterval = en_get_interval_number(currentTime); + + // we check if we need to generate new keys + if (currentInterval - periodInterval >= EN_TEK_ROLLING_PERIOD) { + periodInterval = en_get_interval_number_at_period_start(currentTime); + en_generate_and_derive_period_keys(&periodKey, &periodIdentifierKey, &periodMetadaEncryptionKey); + // TODO: Store new periodKey with periodInterval + } + + // we now generate the new interval identifier and re-encrypt the metadata + ENIntervalIdentifier intervalIdentifier; + en_derive_interval_identifier(&intervalIdentifier, &periodIdentifierKey, currentInterval); + + char encryptedMetadata[sizeof(metadata)]; + en_encrypt_interval_metadata(&periodMetadaEncryptionKey, &intervalIdentifier, metadata, encryptedMetadata, sizeof(metadata)); + + // broadcast intervalIdentifier plus encryptedMetada according to specs + // TODO: receive packets and store them + // repeat for 10-20 minutes + } + + return 0; +} \ No newline at end of file diff --git a/src/exposure-notification.h b/lib/exposure-notification/include/exposure-notification.h similarity index 100% rename from src/exposure-notification.h rename to lib/exposure-notification/include/exposure-notification.h diff --git a/lib/exposure-notification/library.json b/lib/exposure-notification/library.json new file mode 100644 index 0000000..46c0954 --- /dev/null +++ b/lib/exposure-notification/library.json @@ -0,0 +1,14 @@ +{ + "description": "exposure-notification", + "frameworks": "*", + "keywords": "exposure, notification, contact, tracking, tracing", + "name": "exposure-notification", + "platforms": "*", + "version": "0.1", + "dependencies": [ + { + "name": "mbedtls", + "version": "^2.16.4" + } + ] +} \ No newline at end of file diff --git a/src/exposure-notification.c b/lib/exposure-notification/src/exposure-notification.c similarity index 96% rename from src/exposure-notification.c rename to lib/exposure-notification/src/exposure-notification.c index ebf478a..497f14c 100644 --- a/src/exposure-notification.c +++ b/lib/exposure-notification/src/exposure-notification.c @@ -4,22 +4,27 @@ * SPDX-License-Identifier: Apache-2.0 */ + +#if EN_INCLUDE_ZEPHYR_DEPS #include #include #include #include #include +#endif + #include -#include -#include -#include -#include -#include +#include "mbedtls/entropy.h" +#include "mbedtls/ctr_drbg.h" +#include "mbedtls/hkdf.h" +#include "mbedtls/sha256.h" +#include "mbedtls/aes.h" #include "exposure-notification.h" + static mbedtls_ctr_drbg_context ctr_drbg; static ENRandomBytesCallback random_bytes_callback = NULL; diff --git a/platformio.ini b/platformio.ini new file mode 100644 index 0000000..9311773 --- /dev/null +++ b/platformio.ini @@ -0,0 +1,36 @@ +; PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; https://docs.platformio.org/page/projectconf.html + + +[env:nrf52840_dk] +platform = nordicnrf52 +board = nrf52840_dk +framework = zephyr +monitor_speed = 115200 +test_ignore = test_desktop +build_flags = + -Iinclude/tls_config + # For testing: -DUNITY_EXCLUDE_SETJMP_H=1 + -DEN_INCLUDE_ZEPHYR_DEPS=1 + -DEN_INIT_MBEDTLS_ENTROPY=0 +lib_deps = exposure-notification + + +[env:desktop] +platform = native +test_ignore = test_embedded +lib_compat_mode = off +lib_deps = mbedtls@~2 +build_flags = + -Iinclude/desktop + -Iinclude/tls_config + -Wno-nullability-completeness + -DMBEDTLS_USER_CONFIG_FILE='"user-tls.conf"' +src_filter = --<.src/> diff --git a/test/README b/test/README new file mode 100644 index 0000000..df5066e --- /dev/null +++ b/test/README @@ -0,0 +1,11 @@ + +This directory is intended for PIO Unit Testing and project tests. + +Unit Testing is a software testing method by which individual units of +source code, sets of one or more MCU program modules together with associated +control data, usage procedures, and operating procedures, are tested to +determine whether they are fit for use. Unit testing finds problems early +in the development cycle. + +More information about PIO Unit Testing: +- https://docs.platformio.org/page/plus/unit-testing.html diff --git a/test/test_common/test_common.cpp b/test/test_common/test_common.cpp new file mode 100644 index 0000000..a5ec95c --- /dev/null +++ b/test/test_common/test_common.cpp @@ -0,0 +1,222 @@ +#include + +#include + +#include "exposure-notification.h" + +#define TEST_ASSERT_EQUAL_KEY(k1, k2) TEST_ASSERT_EQUAL(0, memcmp(k1.b, k2.b, sizeof(k1.b))) +#define TEST_ASSERT_NOT_EQUAL_KEY(k1, k2) TEST_ASSERT_NOT_EQUAL(0, memcmp(k1.b, k2.b, sizeof(k1.b))) + + +void test_init(void) { + int ret = en_init(NULL); + TEST_ASSERT_EQUAL(0, ret); +} + +void test_get_interval_number(void) { + TEST_ASSERT_EQUAL(0, en_get_interval_number(0)); + TEST_ASSERT_EQUAL(0, en_get_interval_number(EN_INTERVAL_LENGTH-1)); + TEST_ASSERT_EQUAL(1, en_get_interval_number(EN_INTERVAL_LENGTH)); + TEST_ASSERT_EQUAL(42, en_get_interval_number(EN_INTERVAL_LENGTH*42)); +} + +void test_get_interval_number_at_period_start(void) { + TEST_ASSERT_EQUAL(0, en_get_interval_number_at_period_start(0)); + TEST_ASSERT_EQUAL(0, en_get_interval_number_at_period_start(EN_INTERVAL_LENGTH-1)); + TEST_ASSERT_EQUAL(0, en_get_interval_number_at_period_start(EN_INTERVAL_LENGTH)); + TEST_ASSERT_EQUAL(0, en_get_interval_number_at_period_start(EN_INTERVAL_LENGTH*42)); + TEST_ASSERT_EQUAL(EN_TEK_ROLLING_PERIOD, en_get_interval_number_at_period_start(EN_INTERVAL_LENGTH*EN_TEK_ROLLING_PERIOD)); + TEST_ASSERT_EQUAL(EN_TEK_ROLLING_PERIOD, en_get_interval_number_at_period_start(EN_INTERVAL_LENGTH*EN_TEK_ROLLING_PERIOD+1)); +} + +void test_generate_period_key(void) { + ENPeriodKey p1, p2; + en_generate_period_key(&p1); + en_generate_period_key(&p2); + // yes there is a minimal chance that both are the same ;) + TEST_ASSERT_NOT_EQUAL_KEY(p1, p2); +} + +void test_get_period_identifier_key(void) { + + ENPeriodKey pk; + en_generate_period_key(&pk); + + ENPeriodIdentifierKey k1, k2; + en_derive_period_identifier_key(&k1, &pk); + en_derive_period_identifier_key(&k2, &pk); + + // This time both should be the same key as the depent on the period key + TEST_ASSERT_EQUAL_KEY(k1, k2); + + // BUT: they should be different than the period key itself + TEST_ASSERT_NOT_EQUAL_KEY(k1, pk); +} + + +void test_get_interval_identifier(void) { + + ENPeriodKey pk; + en_generate_period_key(&pk); + + ENIntervalNumber iv = en_get_interval_number(0); + + ENIntervalIdentifier id1, id2; + en_derive_interval_identifier(&id1, &pk, iv); + en_derive_interval_identifier(&id2, &pk, iv); + + // identities should be the same for the same intervalnumber and period key + + TEST_ASSERT_EQUAL_KEY(id1, id2); + + iv = en_get_interval_number(EN_INTERVAL_LENGTH); + en_derive_interval_identifier(&id2, &pk, iv); + + // BUT: they should be different when we are using another interval number + TEST_ASSERT_NOT_EQUAL_KEY(id1, id2); + + // They should also be different for another period key but the the same interval number + en_generate_period_key(&pk); + en_derive_interval_identifier(&id1, &pk, iv); + + en_generate_period_key(&pk); + en_derive_interval_identifier(&id2, &pk, iv); + + TEST_ASSERT_NOT_EQUAL_KEY(id1, id2); +} + +void test_get_period_metadata_encryption_key(void) { + + ENPeriodKey pk; + en_generate_period_key(&pk); + ENPeriodMetadataEncryptionKey k1, k2; + en_derive_period_metadata_encryption_key(&k1, &pk); + en_derive_period_metadata_encryption_key(&k2, &pk); + + // This time both should be the same key as the depent on the period key + TEST_ASSERT_EQUAL_KEY(k1, k2); + + // BUT: they should be different than the period key itself + TEST_ASSERT_NOT_EQUAL_KEY(k1, pk); + + // and they should also be different than the periodIdentifierKey + ENPeriodIdentifierKey pik; + en_derive_period_identifier_key(&pik, &pk); + TEST_ASSERT_NOT_EQUAL_KEY(k1, pik); +} + + +void test_en_encrypt_interval_metadata(void) { + + ENPeriodKey pk; + en_generate_period_key(&pk); + ENPeriodMetadataEncryptionKey k1; + en_derive_period_metadata_encryption_key(&k1, &pk); + + ENIntervalNumber iv = en_get_interval_number(0); + ENIntervalIdentifier id; + en_derive_interval_identifier(&id, &pk, iv); + + unsigned char testData[42] = "This is some test data :)"; + unsigned char encryptedData[42] = ""; + + en_encrypt_interval_metadata(&k1, &id, testData, encryptedData, sizeof(testData)); + + // the original data should not be changed + TEST_ASSERT_EQUAL_CHAR_ARRAY("This is some test data :)", testData, sizeof("This is some test data :)")-1); + + // but the encrypted one should be changed + TEST_ASSERT_NOT_EQUAL(0, memcmp(testData, encryptedData, sizeof(testData))); + + unsigned char decryptedData[42] = ""; + en_decrypt_interval_metadata(&k1, &id, encryptedData, decryptedData, sizeof(testData)); + + // the decrypted metadata should be the same as the testData + TEST_ASSERT_EQUAL_CHAR_ARRAY(testData, decryptedData, sizeof(testData)); +} + +void test_identifier_generation_performance(void) { + + int runs = 1000000; + + for(int i = 0; i < runs; i++) { + ENPeriodKey pk; + en_generate_period_key(&pk); + ENPeriodIdentifierKey ik; + en_derive_period_identifier_key(&ik, &pk); + + for(int iv = 0; iv < EN_TEK_ROLLING_PERIOD; iv++) { + ENIntervalNumber intervalNumber = en_get_interval_number(iv); + ENIntervalIdentifier id; + en_derive_interval_identifier(&id, &ik, intervalNumber); + } + } +} + +void test_generate_and_derive_period_keys(void) { + // Test multiple generation + ENPeriodKey periodKey; + ENPeriodIdentifierKey periodIdentifierKey; + ENPeriodMetadataEncryptionKey periodMetadaEncryptionKey; + for(int i = 0; i < 10; i++) { + en_generate_and_derive_period_keys(&periodKey, &periodIdentifierKey, &periodMetadaEncryptionKey); + } +} + + +void test_against_fixtures(void) { + // First define base values + ENIntervalNumber intervalNumber = 2642976; + ENPeriodKey periodKey = {.b = {0x75, 0xc7, 0x34, 0xc6, 0xdd, 0x1a, 0x78, 0x2d, 0xe7, 0xa9, 0x65, 0xda, 0x5e, 0xb9, 0x31, 0x25}}; + unsigned char metadata[4] = {0x40, 0x08, 0x00, 0x00}; + + // define the expected values + ENPeriodIdentifierKey expectedPIK = {.b = {0x18, 0x5a, 0xd9, 0x1d, 0xb6, 0x9e, 0xc7, 0xdd, 0x04, 0x89, 0x60, 0xf1, 0xf3, 0xba, 0x61, 0x75}}; + ENPeriodMetadataEncryptionKey expectedPMEK = {.b = {0xd5, 0x7c, 0x46, 0xaf, 0x7a, 0x1d, 0x83, 0x96, 0x5b, 0x9b, 0xed, 0x8b, 0xd1, 0x52, 0x93, 0x6a}}; + + ENIntervalIdentifier expectedIntervalIdentifier = {.b = {0x8b, 0xe6, 0xcd, 0x37, 0x1c, 0x5c, 0x89, 0x16, 0x04, 0xbf, 0xbe, 0x49, 0xdf, 0x84, 0x50, 0x96}}; + unsigned char expectedEncryptedMetadata[4] = {0x72, 0x03, 0x38, 0x74}; + + + ENPeriodIdentifierKey pik; + en_derive_period_identifier_key(&pik, &periodKey); + TEST_ASSERT_EQUAL_KEY(expectedPIK, pik); + + ENPeriodMetadataEncryptionKey pmek; + en_derive_period_metadata_encryption_key(&pmek, &periodKey); + TEST_ASSERT_EQUAL_KEY(expectedPMEK, pmek); + + ENIntervalIdentifier intervalIdentifier; + en_derive_interval_identifier(&intervalIdentifier, &pik, intervalNumber); + TEST_ASSERT_EQUAL_KEY(expectedIntervalIdentifier, intervalIdentifier); + + + unsigned char encryptedMetadata[sizeof(metadata)] = {0}; + en_encrypt_interval_metadata(&pmek, &intervalIdentifier, metadata, encryptedMetadata, sizeof(metadata)); + TEST_ASSERT_EQUAL_CHAR_ARRAY(expectedEncryptedMetadata, encryptedMetadata, sizeof(expectedEncryptedMetadata)); +} + + +void test_exposure_notification() { + UNITY_BEGIN(); + RUN_TEST(test_init); + RUN_TEST(test_get_interval_number); + RUN_TEST(test_get_interval_number_at_period_start); + RUN_TEST(test_generate_period_key); + RUN_TEST(test_get_period_identifier_key); + RUN_TEST(test_get_interval_identifier); + RUN_TEST(test_get_period_metadata_encryption_key); + RUN_TEST(test_en_encrypt_interval_metadata); + RUN_TEST(test_generate_and_derive_period_keys); + RUN_TEST(test_against_fixtures); + //RUN_TEST(test_identifier_generation_performance); + UNITY_END(); +} + + + + +int main(int argc, char **argv) { + test_exposure_notification(); + return 0; +} \ No newline at end of file diff --git a/zephyr/CMakeLists.txt b/zephyr/CMakeLists.txt new file mode 100644 index 0000000..258e99a --- /dev/null +++ b/zephyr/CMakeLists.txt @@ -0,0 +1,6 @@ +cmake_minimum_required(VERSION 3.13.1) +include($ENV{ZEPHYR_BASE}/cmake/app/boilerplate.cmake NO_POLICY_SCOPE) +project(test) + +FILE(GLOB app_sources ../src/*.c*) +target_sources(app PRIVATE ${app_sources}) diff --git a/prj.conf b/zephyr/prj.conf similarity index 81% rename from prj.conf rename to zephyr/prj.conf index dc60d8a..cee8fc9 100644 --- a/prj.conf +++ b/zephyr/prj.conf @@ -8,8 +8,6 @@ CONFIG_BT_DEVICE_NAME="BlueCovid" CONFIG_ENTROPY_GENERATOR=y CONFIG_MBEDTLS=y -#CONFIG_MBEDTLS_USER_CONFIG_ENABLE=y -#CONFIG_MBEDTLS_CFG_FILE="mbedtls_config.h" CONFIG_MBEDTLS_USER_CONFIG_ENABLE=y CONFIG_MBEDTLS_USER_CONFIG_FILE="user-tls.conf"