diff --git a/sys/Makefile b/sys/Makefile index 9f94ead66..058697f24 100644 --- a/sys/Makefile +++ b/sys/Makefile @@ -148,6 +148,9 @@ endif ifneq (,$(filter usbus usbus_%,$(USEMODULE))) DIRS += usb/usbus endif +ifneq (,$(filter credman,$(USEMODULE))) + DIRS += net/credman +endif DIRS += $(dir $(wildcard $(addsuffix /Makefile, $(USEMODULE)))) diff --git a/sys/include/net/credman.h b/sys/include/net/credman.h new file mode 100644 index 000000000..931bdef8c --- /dev/null +++ b/sys/include/net/credman.h @@ -0,0 +1,189 @@ +/* + * Copyright (C) 2019 HAW Hamburg + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @defgroup net_credman (D)TLS Credential Manager + * @ingroup net + * @brief Credentials management module for (D)TLS + * + * @{ + * + * @file + * @brief (D)TLS credentials management module definitions + * + * @note This module DOES NOT copy the credentials into the system. It + * just holds the pointers to the credentials given by the user. + * The user must make sure that these pointers are valid during the + * lifetime of the application. + * + * @author Aiman Ismail + */ + +#ifndef NET_CREDMAN_H +#define NET_CREDMAN_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Maximum number of credentials in credential pool + */ +#ifndef CREDMAN_MAX_CREDENTIALS +#define CREDMAN_MAX_CREDENTIALS (2) +#endif + +/** + * @brief Buffer of the credential + */ +typedef struct { + void *s; /**< Pointer to the buffer */ + size_t len; /**< Length of credman_buffer_t::s */ +} credman_buffer_t; + +/** + * @brief PSK parameters + */ +typedef struct { + credman_buffer_t key; /**< Key buffer */ + credman_buffer_t id; /**< ID buffer */ + credman_buffer_t hint; /**< Hint buffer */ +} psk_params_t; + +/** + * @brief ECDSA public keys + */ +typedef struct { + const void *x; /**< X part of the public key */ + const void *y; /**< Y part of the public key */ +} ecdsa_public_key_t; + +/** + * @brief ECDSA parameters + */ +typedef struct { + const void *private_key; /**< Pointer to the private key */ + ecdsa_public_key_t public_key; /**< Public key */ + ecdsa_public_key_t *client_keys; /**< Array of clients public keys */ + size_t client_keys_size; /**< Size of ecdsa_params_t::clients_keys */ +} ecdsa_params_t; + +/** + * @brief Tag of the credential. + */ +typedef uint16_t credman_tag_t; + +/** + * @brief Used to signal empty/no tag + */ +#define CREDMAN_TAG_EMPTY (0) + +/** + * @brief Credential types + */ +typedef enum { + CREDMAN_TYPE_EMPTY = 0, + CREDMAN_TYPE_PSK = 1, + CREDMAN_TYPE_ECDSA = 2, +} credman_type_t; + +/** + * @brief Credential information + */ +typedef struct { + credman_type_t type; /**< Type of the credential */ + credman_tag_t tag; /**< Tag of the credential */ + union { + psk_params_t psk; /**< PSK credential parameters */ + ecdsa_params_t ecdsa; /**< ECDSA credential parameters */ + } params; /**< Credential parameters */ +} credman_credential_t; + +/** + * @brief Return values + */ +enum { + CREDMAN_OK = 0, /**< No error */ + CREDMAN_EXIST = -1, /**< Credential already exist in system pool */ + CREDMAN_NO_SPACE = -2, /**< No space in system pool for new credential */ + CREDMAN_NOT_FOUND = -3, /**< Credential not found in the system pool */ + CREDMAN_INVALID = -4, /**< Invalid input parameter(s) */ + CREDMAN_TYPE_UNKNOWN = -5, /**< Unknown credential type */ + CREDMAN_ERROR = -6, /**< Other errors */ +}; + +/** + * @brief Adds a credential to the credential pool + * + * @param[in] credential Credential to add. + * + * @return CREDMAN_OK on success + * @return CREDMAN_EXIST if credential of @p tag and @p type already exist + * @return CREDMAN_NO_SPACE if credential pool is full + * @return CREDMAN_TYPE_UNKNOWN if @p credential has unknown + * credman_credential_t::type + * @return CREDMAN_INVALID if @p credential has + * @return CREDMAN_INVALID credman_credential_t::tag with the value of + * CREDMAN_TAG_EMPTY + * @return CREDMAN_INVALID credman_credential_t::type with the value of + * CREDMAN_TYPE_EMPTY + * @return CREDMAN_INVALID credman_credential_t::params with invalid credential + * parameters i.e. the key points to NULL or has a length of 0 + * @return CREDMAN_ERROR on other errors + */ +int credman_add(const credman_credential_t *credential); + +/** + * @brief Gets a credential from credential pool + * + * @param[out] credential Found credential + * @param[in] tag Tag of credential to get + * @param[in] type Type of credential to get + * + * @return CREDMAN_OK on success + * @return CREDMAN_NOT_FOUND if no credential with @p tag and @p type found + * @return CREDMAN_ERROR on other errors + */ +int credman_get(credman_credential_t *credential, credman_tag_t tag, + credman_type_t type); + +/** + * @brief Delete a credential from the credential pool. Does nothing if + * credential with credman_credential_t::tag @p tag and + * credman_credential_t::type @p type is not found. + * + * @param[in] tag Tag of the credential + * @param[in] type Type of the credential + */ +void credman_delete(credman_tag_t tag, credman_type_t type); + +/** + * @brief Gets the number of credentials currently in the credential pool + * + * Maximum number of allowed credentials is defined by CREDMAN_MAX_CREDENTIALS + * + * @return number of credentials currently in the credential pool + */ +int credman_get_used_count(void); + +#ifdef TEST_SUITES +/** + * @brief Empties the credential pool + */ +void credman_reset(void); +#endif /*TEST_SUITES */ + +#ifdef __cplusplus +} +#endif + +#endif /* NET_CREDMAN_H */ +/** @} */ diff --git a/sys/net/credman/Makefile b/sys/net/credman/Makefile new file mode 100644 index 000000000..0d87f4817 --- /dev/null +++ b/sys/net/credman/Makefile @@ -0,0 +1,2 @@ +MODULE = credman +include $(RIOTBASE)/Makefile.base diff --git a/sys/net/credman/credman.c b/sys/net/credman/credman.c new file mode 100644 index 000000000..7694d8350 --- /dev/null +++ b/sys/net/credman/credman.c @@ -0,0 +1,155 @@ +/* + * Copyright (C) 2019 HAW Hamburg + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup net_credman + * @{ + * + * @file + * @brief (D)TLS Credentials management module implementation + * + * @author Aiman Ismail + */ + +#include "net/credman.h" +#include "mutex.h" + +#include + +#define ENABLE_DEBUG (0) +#include "debug.h" + +static mutex_t _mutex = MUTEX_INIT; + +static credman_credential_t credentials[CREDMAN_MAX_CREDENTIALS]; +static unsigned used = 0; + +static int _find_credential_pos(credman_tag_t tag, credman_type_t type, + credman_credential_t **empty); + +int credman_add(const credman_credential_t *credential) +{ + credman_credential_t *entry = NULL; + assert(credential); + mutex_lock(&_mutex); + int pos = -1; + int ret = CREDMAN_ERROR; + + if ((credential->type == CREDMAN_TYPE_EMPTY) || + (credential->tag == CREDMAN_TAG_EMPTY)) { + DEBUG("credman: invalid credential type/tag\n"); + ret = CREDMAN_INVALID; + goto end; + } + switch (credential->type) { + case CREDMAN_TYPE_PSK: + if ((credential->params.psk.key.s == NULL) || + (credential->params.psk.key.len == 0)) { + DEBUG("credman: invalid PSK parameters\n"); + ret = CREDMAN_INVALID; + goto end; + } + break; + case CREDMAN_TYPE_ECDSA: + if ((credential->params.ecdsa.private_key == NULL) || + (credential->params.ecdsa.public_key.x == NULL) || + (credential->params.ecdsa.public_key.y == NULL)) { + DEBUG("credman: invalid ECDSA parameters\n"); + ret = CREDMAN_INVALID; + goto end; + } + break; + default: + ret = CREDMAN_TYPE_UNKNOWN; + goto end; + } + + pos = _find_credential_pos(credential->tag, credential->type, &entry); + if (pos >= 0) { + DEBUG("credman: credential with tag %d and type %d already exist\n", + credential->tag, credential->type); + ret = CREDMAN_EXIST; + } + else if (entry == NULL) { + DEBUG("credman: no space for new credential\n"); + ret = CREDMAN_NO_SPACE; + } + else { + *entry = *credential; + used++; + ret = CREDMAN_OK; + } +end: + mutex_unlock(&_mutex); + return ret; +} + +int credman_get(credman_credential_t *credential, credman_tag_t tag, + credman_type_t type) +{ + assert(credential); + mutex_lock(&_mutex); + int ret = CREDMAN_ERROR; + + int pos = _find_credential_pos(tag, type, NULL); + if (pos < 0) { + DEBUG("credman: credential with tag %d and type %d not found\n", + tag, type); + ret = CREDMAN_NOT_FOUND; + } + else { + memcpy(credential, &credentials[pos], sizeof(credman_credential_t)); + ret = CREDMAN_OK; + } + mutex_unlock(&_mutex); + return ret; +} + +void credman_delete(credman_tag_t tag, credman_type_t type) +{ + mutex_lock(&_mutex); + int pos = _find_credential_pos(tag, type, NULL); + if (pos >= 0) { + memset(&credentials[pos], 0, sizeof(credman_credential_t)); + used--; + } + mutex_unlock(&_mutex); +} + +int credman_get_used_count(void) +{ + return used; +} + +static int _find_credential_pos(credman_tag_t tag, credman_type_t type, + credman_credential_t **empty) +{ + for (unsigned i = 0; i < CREDMAN_MAX_CREDENTIALS; i++) { + credman_credential_t *c = &credentials[i]; + if ((c->tag == tag) && (c->type == type)) { + return i; + } + /* only check until empty position found */ + if ((empty) && (*empty == NULL) && + (c->tag == CREDMAN_TAG_EMPTY) && (c->type == CREDMAN_TYPE_EMPTY)) { + *empty = c; + } + } + return -1; +} + +#ifdef TEST_SUITES +void credman_reset(void) +{ + mutex_lock(&_mutex); + memset(credentials, 0, + sizeof(credman_credential_t) * CREDMAN_MAX_CREDENTIALS); + used = 0; + mutex_unlock(&_mutex); +} +#endif /* TEST_SUITES */ diff --git a/tests/unittests/tests-credman/Makefile b/tests/unittests/tests-credman/Makefile new file mode 100644 index 000000000..48422e909 --- /dev/null +++ b/tests/unittests/tests-credman/Makefile @@ -0,0 +1 @@ +include $(RIOTBASE)/Makefile.base diff --git a/tests/unittests/tests-credman/Makefile.include b/tests/unittests/tests-credman/Makefile.include new file mode 100644 index 000000000..58aba54c5 --- /dev/null +++ b/tests/unittests/tests-credman/Makefile.include @@ -0,0 +1 @@ +USEMODULE += credman diff --git a/tests/unittests/tests-credman/credentials.h b/tests/unittests/tests-credman/credentials.h new file mode 100644 index 000000000..6ea87090e --- /dev/null +++ b/tests/unittests/tests-credman/credentials.h @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2018 Inria + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup tests + * @{ + * + * @file + * @brief Test credentials for credman + * + * @author Raul Fuentes + * @author Aiman Ismail + * + * @} + */ + +#ifndef CREDENTIALS_H +#define CREDENTIALS_H + +#include "net/credman.h" + +#ifdef __cplusplus +extern "C" { +#endif + +static const unsigned char ecdsa_priv_key[] = { + 0x41, 0xC1, 0xCB, 0x6B, 0x51, 0x24, 0x7A, 0x14, + 0x43, 0x21, 0x43, 0x5B, 0x7A, 0x80, 0xE7, 0x14, + 0x89, 0x6A, 0x33, 0xBB, 0xAD, 0x72, 0x94, 0xCA, + 0x40, 0x14, 0x55, 0xA1, 0x94, 0xA9, 0x49, 0xFA +}; + +static const unsigned char ecdsa_pub_key_x[] = { + 0x36, 0xDF, 0xE2, 0xC6, 0xF9, 0xF2, 0xED, 0x29, + 0xDA, 0x0A, 0x9A, 0x8F, 0x62, 0x68, 0x4E, 0x91, + 0x63, 0x75, 0xBA, 0x10, 0x30, 0x0C, 0x28, 0xC5, + 0xE4, 0x7C, 0xFB, 0xF2, 0x5F, 0xA5, 0x8F, 0x52 +}; + +static const unsigned char ecdsa_pub_key_y[] = { + 0x71, 0xA0, 0xD4, 0xFC, 0xDE, 0x1A, 0xB8, 0x78, + 0x5A, 0x3C, 0x78, 0x69, 0x35, 0xA7, 0xCF, 0xAB, + 0xE9, 0x3F, 0x98, 0x72, 0x09, 0xDA, 0xED, 0x0B, + 0x4F, 0xAB, 0xC3, 0x6F, 0xC7, 0x72, 0xF8, 0x29 +}; + +#ifdef __cplusplus +} +#endif + +#endif /* CREDENTIALS_H */ diff --git a/tests/unittests/tests-credman/tests-credman.c b/tests/unittests/tests-credman/tests-credman.c new file mode 100644 index 000000000..c722f04f1 --- /dev/null +++ b/tests/unittests/tests-credman/tests-credman.c @@ -0,0 +1,251 @@ +/* + * Copyright (C) 2019 HAW Hamburg + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +#include +#include "embUnit.h" +#include "tests-credman.h" +#include "credentials.h" + +#include "net/credman.h" + +#define CREDMAN_TEST_TAG (1) + +static int _compare_credentials(const credman_credential_t *a, + const credman_credential_t *b) +{ + if ((a->tag == b->tag) && (a->type == b->type)) { + return 0; + } + return -1; +} + +static void set_up(void) +{ + /* reset credential pool before every test */ + credman_reset(); +} + +static void test_credman_add(void) +{ + int ret; + unsigned exp_count = 0; + + psk_params_t exp_psk_params = { + .id = { + .s = (void *)"RIOTer", + .len = sizeof("RIOTer") - 1, + }, + .key = { + .s = (void *)"LGPLisyourfriend", + .len = sizeof("LGPLisyourfriend") - 1, + }, + }; + + credman_credential_t credential = { + .tag = CREDMAN_TEST_TAG, + .type = CREDMAN_TYPE_PSK, + .params = { + .psk = exp_psk_params, + }, + }; + + TEST_ASSERT_EQUAL_INT(exp_count, credman_get_used_count()); + + /* add one credential */ + TEST_ASSERT_EQUAL_INT(CREDMAN_OK, credman_add(&credential)); + TEST_ASSERT_EQUAL_INT(++exp_count, credman_get_used_count()); + + /* add duplicate credential */ + ret = credman_add(&credential); + TEST_ASSERT_EQUAL_INT(CREDMAN_EXIST, ret); + TEST_ASSERT_EQUAL_INT(exp_count, credman_get_used_count()); + + /* add invalid credential params */ + memset(&credential.params.psk, 0, sizeof(psk_params_t)); + ret = credman_add(&credential); + TEST_ASSERT_EQUAL_INT(CREDMAN_INVALID, ret); + TEST_ASSERT_EQUAL_INT(exp_count, credman_get_used_count()); + + /* fill the pool */ + memcpy(&credential.params.psk, &exp_psk_params, sizeof(psk_params_t)); + while (credman_get_used_count() < CREDMAN_MAX_CREDENTIALS) { + /* increase tag number so that it is not recognized as duplicate */ + credential.tag++; + TEST_ASSERT_EQUAL_INT(CREDMAN_OK, credman_add(&credential)); + TEST_ASSERT_EQUAL_INT(++exp_count, credman_get_used_count()); + } + + /* add to full pool */ + credential.tag++; + ret = credman_add(&credential); + TEST_ASSERT_EQUAL_INT(CREDMAN_NO_SPACE, ret); + TEST_ASSERT_EQUAL_INT(exp_count, credman_get_used_count()); +} + +static void test_credman_get(void) +{ + int ret; + credman_credential_t out_credential; + credman_credential_t in_credential = { + .tag = CREDMAN_TEST_TAG, + .type = CREDMAN_TYPE_ECDSA, + .params = { + .ecdsa = { + .private_key = ecdsa_priv_key, + .public_key = { .x = ecdsa_pub_key_x, .y = ecdsa_pub_key_y }, + .client_keys = NULL, + .client_keys_size = 0, + }, + }, + }; + + /* get non-existing credential */ + ret = credman_get(&out_credential, in_credential.tag, in_credential.type); + TEST_ASSERT_EQUAL_INT(CREDMAN_NOT_FOUND, ret); + + ret = credman_add(&in_credential); + TEST_ASSERT_EQUAL_INT(CREDMAN_OK, ret); + + ret = credman_get(&out_credential, in_credential.tag, in_credential.type); + TEST_ASSERT_EQUAL_INT(CREDMAN_OK, ret); + TEST_ASSERT(!_compare_credentials(&in_credential, &out_credential)); +} + +static void test_credman_delete(void) +{ + int ret; + unsigned exp_count = 0; + credman_credential_t out_credential; + credman_credential_t in_credential = { + .tag = CREDMAN_TEST_TAG, + .type = CREDMAN_TYPE_ECDSA, + .params = { + .ecdsa = { + .private_key = ecdsa_priv_key, + .public_key = { .x = ecdsa_pub_key_x, .y = ecdsa_pub_key_y }, + .client_keys = NULL, + .client_keys_size = 0, + }, + }, + }; + + /* delete non-existing credential */ + credman_delete(in_credential.tag, in_credential.type); + TEST_ASSERT_EQUAL_INT(exp_count, credman_get_used_count()); + + /* add a credential */ + ret = credman_add(&in_credential); + TEST_ASSERT_EQUAL_INT(CREDMAN_OK, ret); + TEST_ASSERT_EQUAL_INT(++exp_count, credman_get_used_count()); + + /* delete a credential from credential pool */ + credman_delete(in_credential.tag, in_credential.type); + TEST_ASSERT_EQUAL_INT(--exp_count, credman_get_used_count()); + + /* get the deleted credential */ + ret = credman_get(&out_credential, in_credential.tag, in_credential.type); + TEST_ASSERT_EQUAL_INT(CREDMAN_NOT_FOUND, ret); + + /* delete a deleted credential */ + credman_delete(in_credential.tag, in_credential.type); + TEST_ASSERT_EQUAL_INT(exp_count, credman_get_used_count()); +} + +static void test_credman_delete_random_order(void) +{ + credman_tag_t tag1 = CREDMAN_TEST_TAG; + credman_tag_t tag2 = CREDMAN_TEST_TAG + 1; + + credman_credential_t out_credential; + credman_credential_t in_credential = { + .tag = tag1, + .type = CREDMAN_TYPE_ECDSA, + .params = { + .ecdsa = { + .private_key = ecdsa_priv_key, + .public_key = { .x = ecdsa_pub_key_x, .y = ecdsa_pub_key_y }, + .client_keys = NULL, + .client_keys_size = 0, + }, + }, + }; + TEST_ASSERT_EQUAL_INT(0, credman_get_used_count()); + + /* fill the credential pool, assume CREDMAN_MAX_CREDENTIALS is 2 */ + TEST_ASSERT_EQUAL_INT(CREDMAN_OK, credman_add(&in_credential)); + in_credential.tag = tag2; + TEST_ASSERT_EQUAL_INT(CREDMAN_OK, credman_add(&in_credential)); + TEST_ASSERT_EQUAL_INT(2, credman_get_used_count()); + + /* delete the first credential */ + credman_delete(tag1, in_credential.type); + TEST_ASSERT_EQUAL_INT(1, credman_get_used_count()); + + /* get the second credential */ + TEST_ASSERT_EQUAL_INT(CREDMAN_OK, credman_get(&out_credential, tag2, in_credential.type)); + TEST_ASSERT(!_compare_credentials(&in_credential, &out_credential)); +} + +static void test_credman_add_delete_all(void) +{ + credman_tag_t tag1 = CREDMAN_TEST_TAG; + credman_tag_t tag2 = CREDMAN_TEST_TAG + 1; + + credman_credential_t in_credential = { + .tag = tag1, + .type = CREDMAN_TYPE_ECDSA, + .params = { + .ecdsa = { + .private_key = ecdsa_priv_key, + .public_key = { .x = ecdsa_pub_key_x, .y = ecdsa_pub_key_y }, + .client_keys = NULL, + .client_keys_size = 0, + }, + }, + }; + + /* add credentials */ + TEST_ASSERT_EQUAL_INT(CREDMAN_OK, credman_add(&in_credential)); + in_credential.tag = tag2; + TEST_ASSERT_EQUAL_INT(CREDMAN_OK, credman_add(&in_credential)); + TEST_ASSERT_EQUAL_INT(2, credman_get_used_count()); + + /* delete starting from first added credential */ + credman_delete(tag1, in_credential.type); + credman_delete(tag2, in_credential.type); + TEST_ASSERT_EQUAL_INT(0, credman_get_used_count()); + + /* re-add the credentials after deletion */ + in_credential.tag = tag1; + TEST_ASSERT_EQUAL_INT(CREDMAN_OK, credman_add(&in_credential)); + in_credential.tag = tag2; + TEST_ASSERT_EQUAL_INT(CREDMAN_OK, credman_add(&in_credential)); + TEST_ASSERT_EQUAL_INT(2, credman_get_used_count()); +} + +Test *tests_credman_tests(void) +{ + EMB_UNIT_TESTFIXTURES(fixtures) { + new_TestFixture(test_credman_add), + new_TestFixture(test_credman_get), + new_TestFixture(test_credman_delete), + new_TestFixture(test_credman_delete_random_order), + new_TestFixture(test_credman_add_delete_all), + }; + + EMB_UNIT_TESTCALLER(credman_tests, + set_up, + NULL, fixtures); + + return (Test *)&credman_tests; +} + +void tests_credman(void) +{ + TESTS_RUN(tests_credman_tests()); +} diff --git a/tests/unittests/tests-credman/tests-credman.h b/tests/unittests/tests-credman/tests-credman.h new file mode 100644 index 000000000..b47755a43 --- /dev/null +++ b/tests/unittests/tests-credman/tests-credman.h @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2019 HAW Hamburg + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @addtogroup unittests + * @{ + * + * @file + * @brief Unittests for the ``credman`` module + * + * @author Aiman Ismail + */ + +#ifndef TESTS_CREDMAN_H +#define TESTS_CREDMAN_H +#include "embUnit/embUnit.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief The entry point of this test suite. + */ +void tests_credman(void); + +/** + * @brief Generates tests for credman + * + * @return embUnit tests if successful, NULL if not. + */ +Test *tests_credman_tests(void); + +#ifdef __cplusplus +} +#endif + +#endif /* TESTS_CREDMAN_H */ +/** @} */