From a8febd57a144cba85df7b7210b96b6df4dfc02c3 Mon Sep 17 00:00:00 2001 From: Yossi Levy Date: Wed, 21 Feb 2018 11:48:13 +0200 Subject: [PATCH] Device key implementation --- TESTS/host_tests/devicekey_reset.py | 150 ++++++ TESTS/mbed_drivers/device_key/main.cpp | 488 ++++++++++++++++++ drivers/DeviceKey.cpp | 252 +++++++++ drivers/DeviceKey.h | 148 ++++++ .../mbedtls/inc/mbedtls/config-no-entropy.h | 1 + features/mbedtls/inc/mbedtls/config.h | 2 +- features/nvstore/source/nvstore.h | 2 + 7 files changed, 1042 insertions(+), 1 deletion(-) create mode 100644 TESTS/host_tests/devicekey_reset.py create mode 100644 TESTS/mbed_drivers/device_key/main.cpp create mode 100644 drivers/DeviceKey.cpp create mode 100644 drivers/DeviceKey.h diff --git a/TESTS/host_tests/devicekey_reset.py b/TESTS/host_tests/devicekey_reset.py new file mode 100644 index 0000000000..08379739cb --- /dev/null +++ b/TESTS/host_tests/devicekey_reset.py @@ -0,0 +1,150 @@ +""" +Copyright (c) 2018 ARM Limited + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +import time +from mbed_host_tests import BaseHostTest +from mbed_host_tests.host_tests_runner.host_test_default import DefaultTestSelector + +DEFAULT_CYCLE_PERIOD = 1.0 +MSG_VALUE_DUMMY = '0' +MSG_KEY_DEVICE_READY = 'ready' +MSG_KEY_DEVICE_FINISH = 'finish' +MSG_KEY_DEVICE_TEST_STEP1 = 'check_consistency_step1' +MSG_KEY_DEVICE_TEST_STEP2 = 'check_consistency_step2' +MSG_KEY_DEVICE_TEST_STEP3 = 'check_consistency_step3' +MSG_KEY_DEVICE_TEST_STEP4 = 'check_consistency_step4' +MSG_KEY_SYNC = '__sync' +MSG_KEY_TEST_SUITE_ENDED = 'Test suite ended' + +class DeviceKeyResetTest(BaseHostTest): + """Test for the DeviceKey driver API. + """ + + def __init__(self): + super(DeviceKeyResetTest, self).__init__() + self.reset = False + self.finish = False + self.suite_ended = False + cycle_s = self.get_config_item('program_cycle_s') + self.program_cycle_s = cycle_s if cycle_s is not None else DEFAULT_CYCLE_PERIOD + self.test_steps_sequence = self.test_steps() + # Advance the coroutine to it's first yield statement. + self.test_steps_sequence.send(None) + + def setup(self): + self.register_callback(MSG_KEY_DEVICE_READY, self.cb_device_ready) + self.register_callback(MSG_KEY_DEVICE_FINISH, self.cb_device_finish) + self.register_callback(MSG_KEY_TEST_SUITE_ENDED, self.cb_device_test_suit_ended) + + def cb_device_ready(self, key, value, timestamp): + """Acknowledge device rebooted correctly and feed the test execution + """ + self.reset = True + + try: + if self.test_steps_sequence.send(value): + self.notify_complete(True) + except (StopIteration, RuntimeError) as exc: + self.notify_complete(False) + + def cb_device_finish(self, key, value, timestamp): + """Acknowledge device finished a test step correctly and feed the test execution + """ + self.finish = True + + try: + if self.test_steps_sequence.send(value): + self.notify_complete(True) + except (StopIteration, RuntimeError) as exc: + self.notify_complete(False) + + def cb_device_test_suit_ended(self, key, value, timestamp): + """Acknowledge device finished a test step correctly and feed the test execution + """ + self.suite_ended = True + + try: + if self.test_steps_sequence.send(value): + self.notify_complete(True) + except (StopIteration, RuntimeError) as exc: + self.notify_complete(False) + + def test_steps(self): + """Test step 1 (16 byte key test) + """ + wait_for_communication = yield + + self.reset = False + self.send_kv(MSG_KEY_DEVICE_TEST_STEP1, MSG_VALUE_DUMMY) + time.sleep(self.program_cycle_s) + self.send_kv(MSG_KEY_SYNC, MSG_VALUE_DUMMY) + + wait_for_communication = yield + + if self.reset == False: + raise RuntimeError('Phase 1: Platform did not reset as expected.') + + """Test step 2 (After reset) + """ + self.finish = False + self.send_kv(MSG_KEY_DEVICE_TEST_STEP2, MSG_VALUE_DUMMY) + time.sleep(self.program_cycle_s) + wait_for_communication = yield + + if self.finish == False: + raise RuntimeError('Test failed.') + + """Test Step 3 (32 byte key test) + """ + wait_for_communication = yield + + self.reset = False + self.send_kv(MSG_KEY_DEVICE_TEST_STEP3, MSG_VALUE_DUMMY) + time.sleep(self.program_cycle_s) + self.send_kv(MSG_KEY_SYNC, MSG_VALUE_DUMMY) + + wait_for_communication = yield + + if self.reset == False: + raise RuntimeError('Phase 3: Platform did not reset as expected.') + + """Test step 4 (After reset) + """ + self.finish = False + self.send_kv(MSG_KEY_DEVICE_TEST_STEP4, MSG_VALUE_DUMMY) + time.sleep(self.program_cycle_s) + + wait_for_communication = yield + + if self.finish == False: + raise RuntimeError('Test failed.') + + """Test step 4 (After reset) + """ + self.suite_ended = False + time.sleep(self.program_cycle_s) + + wait_for_communication = yield + + if self.suite_ended == False: + raise RuntimeError('Test failed.') + + # The sequence is correct -- test passed. + yield True + + + + \ No newline at end of file diff --git a/TESTS/mbed_drivers/device_key/main.cpp b/TESTS/mbed_drivers/device_key/main.cpp new file mode 100644 index 0000000000..eac7dafeb1 --- /dev/null +++ b/TESTS/mbed_drivers/device_key/main.cpp @@ -0,0 +1,488 @@ +/* mbed Microcontroller Library + * Copyright (c) 2018 ARM Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "DeviceKey.h" +#include "utest/utest.h" +#include "unity/unity.h" +#include "greentea-client/test_env.h" +#include "nvstore.h" + +using namespace utest::v1; + +#define MSG_VALUE_DUMMY "0" +#define MSG_VALUE_LEN 32 +#define MSG_KEY_LEN 32 + +#define MSG_KEY_DEVICE_READY "ready" +#define MSG_KEY_DEVICE_FINISH "finish" +#define MSG_KEY_DEVICE_TEST_STEP1 "check_consistency_step1" +#define MSG_KEY_DEVICE_TEST_STEP2 "check_consistency_step2" +#define MSG_KEY_DEVICE_TEST_STEP3 "check_consistency_step3" +#define MSG_KEY_DEVICE_TEST_STEP4 "check_consistency_step4" +#define MSG_KEY_DEVICE_TEST_SUITE_ENDED "Test suite ended" + +void device_key_derived_key_consistency_16_byte_key_reset_test(char *key); +void device_key_derived_key_consistency_32_byte_key_reset_test(char *key); + +/* + * Injection of a dummy key when there is no TRNG + */ +int inject_dummy_rot_key() +{ +#if !defined(DEVICE_TRNG) + uint32_t key[DEVICE_KEY_16BYTE / sizeof(uint32_t)]; + + memset(key, 0, DEVICE_KEY_16BYTE); + memcpy(key, "1234567812345678", DEVICE_KEY_16BYTE); + int size = DEVICE_KEY_16BYTE; + DeviceKey& devkey = DeviceKey::get_instance(); + return devkey.device_inject_root_of_trust(key, size); +#else + return DEVICEKEY_SUCCESS; +#endif +} + +void device_key_derived_key_reset_test() +{ + greentea_send_kv(MSG_KEY_DEVICE_READY, MSG_VALUE_DUMMY); + + static char key[MSG_KEY_LEN + 1] = { }; + static char value[MSG_VALUE_LEN + 1] = { }; + memset(key, 0, MSG_KEY_LEN + 1); + memset(value, 0, MSG_VALUE_LEN + 1); + + greentea_parse_kv(key, value, MSG_KEY_LEN, MSG_VALUE_LEN); + + if (strcmp(key, MSG_KEY_DEVICE_TEST_STEP1) == 0 || strcmp(key, MSG_KEY_DEVICE_TEST_STEP2) == 0) { + device_key_derived_key_consistency_16_byte_key_reset_test(key); + return device_key_derived_key_reset_test(); + } + + if (strcmp(key, MSG_KEY_DEVICE_TEST_STEP3) == 0 || strcmp(key, MSG_KEY_DEVICE_TEST_STEP4) == 0) { + return device_key_derived_key_consistency_32_byte_key_reset_test(key); + } + + TEST_ASSERT_MESSAGE(false, key); //Indicates error!!! +} + +/* + * Test the consistency of derived 16 byte key result after device reset. + */ +void device_key_derived_key_consistency_16_byte_key_reset_test(char *key) +{ + unsigned char output1[DEVICE_KEY_16BYTE]; + unsigned char output2[DEVICE_KEY_16BYTE]; + unsigned char empty_buffer[DEVICE_KEY_16BYTE]; + unsigned char salt[] = "Once upon a time, I worked for the circus and I lived in Omaha."; + int key_type = DEVICE_KEY_16BYTE; + uint16_t actual_size = 0; + DeviceKey& devkey = DeviceKey::get_instance(); + NVStore& nvstore = NVStore::get_instance(); + size_t salt_size = sizeof(salt); + + if (strcmp(key, MSG_KEY_DEVICE_TEST_STEP1) == 0) { + + //Third step: Clear NVStore, create an ROT key, derive a 16 byte + //key and store it in NVStore at index 15, At the end reset the device + int ret = nvstore.reset(); + TEST_ASSERT_EQUAL_INT(DEVICEKEY_SUCCESS, ret); + + ret = inject_dummy_rot_key(); + TEST_ASSERT_EQUAL_INT(DEVICEKEY_SUCCESS, ret); + + memset(output1, 0, sizeof(output1)); + ret = devkey.device_key_derived_key(salt, salt_size, output1, key_type); + TEST_ASSERT_EQUAL_INT32(0, ret); + bool is_empty = !memcmp(empty_buffer, output1, sizeof(output1)); + TEST_ASSERT_FALSE(is_empty); + + ret = nvstore.set(15, DEVICE_KEY_16BYTE, output1); + TEST_ASSERT_EQUAL_INT32(0, ret); + + system_reset(); + TEST_ASSERT_MESSAGE(false, "system_reset() did not reset the device as expected."); + } else if (strcmp(key, MSG_KEY_DEVICE_TEST_STEP2) == 0) { + + //Second step: Read from NVStore at index 15 there should be a derived key there. + //Now try to derive a key for 100 times and check it is the same key like before the reset. + //At the end clear NVStore. + int ret = nvstore.get(15, DEVICE_KEY_16BYTE, output1, actual_size); + TEST_ASSERT_FALSE(NVSTORE_SUCCESS != ret) + TEST_ASSERT_EQUAL_INT(DEVICE_KEY_16BYTE, actual_size); + + for (int i = 0; i < 100; i++) { + memset(output2, 0, sizeof(output2)); + ret = devkey.device_key_derived_key(salt, salt_size, output2, key_type); + TEST_ASSERT_EQUAL_INT32(0, ret); + TEST_ASSERT_EQUAL_UINT8_ARRAY(output1, output2, DEVICE_KEY_16BYTE); + } + + ret = nvstore.reset(); + TEST_ASSERT_EQUAL_INT(DEVICEKEY_SUCCESS, ret); + + greentea_send_kv(MSG_KEY_DEVICE_FINISH, MSG_VALUE_DUMMY); + } else { + TEST_ASSERT_MESSAGE(false, "Unknown test step received"); + } +} + +/* + * Test the consistency of derived 32 byte key result after device reset. + */ +void device_key_derived_key_consistency_32_byte_key_reset_test(char *key) +{ + unsigned char output1[DEVICE_KEY_32BYTE]; + unsigned char output2[DEVICE_KEY_32BYTE]; + unsigned char empty_buffer[DEVICE_KEY_32BYTE]; + unsigned char salt[] = "The quick brown fox jumps over the lazy dog"; + int key_type = DEVICE_KEY_32BYTE; + uint16_t actual_size = 0; + DeviceKey& devkey = DeviceKey::get_instance(); + NVStore& nvstore = NVStore::get_instance(); + size_t salt_size = sizeof(salt); + + if (strcmp(key, MSG_KEY_DEVICE_TEST_STEP3) == 0) { + + //Third step: Clear NVStore, create an ROT key, derive a 32 byte + //key and store it in NVStore at index 15, At the end reset the device + int ret = nvstore.reset(); + TEST_ASSERT_EQUAL_INT(DEVICEKEY_SUCCESS, ret); + + ret = inject_dummy_rot_key(); + TEST_ASSERT_EQUAL_INT(DEVICEKEY_SUCCESS, ret); + + memset(output1, 0, sizeof(output1)); + ret = devkey.device_key_derived_key(salt, salt_size, output1, key_type); + TEST_ASSERT_EQUAL_INT32(0, ret); + bool is_empty = !memcmp(empty_buffer, output1, sizeof(output1)); + TEST_ASSERT_FALSE(is_empty); + + ret = nvstore.set(15, DEVICE_KEY_32BYTE, output1); + TEST_ASSERT_EQUAL_INT32(0, ret); + + system_reset(); + TEST_ASSERT_MESSAGE(false, "system_reset() did not reset the device as expected."); + } else if (strcmp(key, MSG_KEY_DEVICE_TEST_STEP4) == 0) { + + //Fourth step: Read from NVStore at index 15 there should be a derived key there. + //Now try to derive a key for 100 times and check it is the same key like before the reset. + //At the end clear NVStore. + int ret = nvstore.get(15, DEVICE_KEY_32BYTE, output1, actual_size); + TEST_ASSERT_FALSE(NVSTORE_SUCCESS != ret) + TEST_ASSERT_EQUAL_INT(DEVICE_KEY_32BYTE, actual_size); + + for (int i = 0; i < 100; i++) { + memset(output2, 0, sizeof(output2)); + ret = devkey.device_key_derived_key(salt, salt_size, output2, key_type); + TEST_ASSERT_EQUAL_INT32(0, ret); + TEST_ASSERT_EQUAL_UINT8_ARRAY(output1, output2, DEVICE_KEY_32BYTE); + } + + ret = nvstore.reset(); + TEST_ASSERT_EQUAL_INT(DEVICEKEY_SUCCESS, ret); + + greentea_send_kv(MSG_KEY_DEVICE_FINISH, MSG_VALUE_DUMMY); + } else { + TEST_ASSERT_MESSAGE(false, "Unknown test step received"); + } +} + +/* + * Test that wrong size of key is rejected when trying to persist a key + */ +void device_inject_root_of_trust_wrong_size_test() +{ + DeviceKey& devkey = DeviceKey::get_instance(); + uint32_t key[DEVICE_KEY_32BYTE / sizeof(uint32_t)]; + + memcpy(key, "12345678123456788765432187654321", DEVICE_KEY_32BYTE); + + for (int i = 0; i < 50; i++) { + if (DEVICE_KEY_16BYTE == i || DEVICE_KEY_32BYTE == i) { + continue; + } + int ret = devkey.device_inject_root_of_trust(key, i); + TEST_ASSERT_EQUAL_INT(DEVICEKEY_INVALID_KEY_SIZE, ret); + } +} + +/* + * Test that a 16 byte size key is written to persistent storage + */ +void device_inject_root_of_trust_16_byte_size_test() +{ + DeviceKey& devkey = DeviceKey::get_instance(); + uint32_t rkey[DEVICE_KEY_16BYTE / sizeof(uint32_t)]; + uint16_t actual_size; + uint32_t key[DEVICE_KEY_16BYTE / sizeof(uint32_t)]; + NVStore& nvstore = NVStore::get_instance(); + + int ret = nvstore.reset(); + TEST_ASSERT_EQUAL_INT(DEVICEKEY_SUCCESS, ret); + + memcpy(key, "1234567812345678", sizeof(key)); + ret = devkey.device_inject_root_of_trust(key, DEVICE_KEY_16BYTE); + TEST_ASSERT_EQUAL_INT(DEVICEKEY_SUCCESS, ret); + + //Read the key from NVStore. + memset(rkey, 0, sizeof(rkey)); + ret = nvstore.get(NVSTORE_DEVICEKEY_KEY, DEVICE_KEY_16BYTE, rkey, actual_size); + TEST_ASSERT_EQUAL_INT(DEVICEKEY_SUCCESS, ret); + TEST_ASSERT_EQUAL_INT(DEVICE_KEY_16BYTE, actual_size); + TEST_ASSERT_EQUAL_INT32_ARRAY(key, rkey, actual_size / sizeof(uint32_t)); +} + +/* + * Test that a 32 byte size key is written to persistent storage + */ +void device_inject_root_of_trust_32_byte_size_test() +{ + DeviceKey& devkey = DeviceKey::get_instance(); + uint32_t rkey[DEVICE_KEY_32BYTE / sizeof(uint32_t)]; + uint16_t actual_size; + uint32_t key[DEVICE_KEY_32BYTE / sizeof(uint32_t)]; + NVStore& nvstore = NVStore::get_instance(); + + int ret = nvstore.reset(); + TEST_ASSERT_EQUAL_INT(DEVICEKEY_SUCCESS, ret); + + memcpy(key, "12345678123456788765432187654321", sizeof(key)); + ret = devkey.device_inject_root_of_trust(key, DEVICE_KEY_32BYTE); + TEST_ASSERT_EQUAL_INT(DEVICEKEY_SUCCESS, ret); + + //Read the key from NVStore. + memset(rkey, 0, sizeof(rkey)); + ret = nvstore.get(NVSTORE_DEVICEKEY_KEY, DEVICE_KEY_32BYTE, rkey, actual_size); + TEST_ASSERT_EQUAL_INT(DEVICEKEY_SUCCESS, ret); + TEST_ASSERT_EQUAL_INT(DEVICE_KEY_32BYTE, actual_size); + TEST_ASSERT_EQUAL_INT32_ARRAY(key, rkey, actual_size / sizeof(uint32_t)); +} + +/* + * Test that a key can be written to persistent storage only once. + */ +void device_inject_root_of_trust_several_times_test() +{ + DeviceKey& devkey = DeviceKey::get_instance(); + uint32_t key[DEVICE_KEY_32BYTE / sizeof(uint32_t)]; + NVStore& nvstore = NVStore::get_instance(); + + int ret = nvstore.reset(); + TEST_ASSERT_EQUAL_INT(DEVICEKEY_SUCCESS, ret); + + memcpy(key, "12345678123456788765432187654321", DEVICE_KEY_32BYTE); + ret = devkey.device_inject_root_of_trust(key, DEVICE_KEY_32BYTE); + TEST_ASSERT_EQUAL_INT(DEVICEKEY_SUCCESS, ret); + + //Trying to use the same key should fail. + ret = devkey.device_inject_root_of_trust(key, DEVICE_KEY_32BYTE); + TEST_ASSERT_EQUAL_INT(DEVICEKEY_ALREADY_EXIST, ret); + + //Trying to use different key should also fail. + ret = devkey.device_inject_root_of_trust(key, DEVICE_KEY_16BYTE); + TEST_ASSERT_EQUAL_INT(DEVICEKEY_ALREADY_EXIST, ret); +} + +/* + * Test the consistency of derived 16 byte key result. + */ +void device_key_derived_key_consistency_16_byte_key_test() +{ + unsigned char output1[DEVICE_KEY_16BYTE]; + unsigned char output2[DEVICE_KEY_16BYTE]; + unsigned char empty_buffer[DEVICE_KEY_16BYTE]; + unsigned char salt[] = "Once upon a time, I worked for the circus and I lived in Omaha."; + int key_type = DEVICE_KEY_16BYTE; + DeviceKey& devkey = DeviceKey::get_instance(); + NVStore& nvstore = NVStore::get_instance(); + + int ret = nvstore.reset(); + TEST_ASSERT_EQUAL_INT(DEVICEKEY_SUCCESS, ret); + + ret = inject_dummy_rot_key(); + TEST_ASSERT_EQUAL_INT(DEVICEKEY_SUCCESS, ret); + + size_t salt_size = sizeof(salt); + memset(output1, 0, sizeof(output1)); + ret = devkey.device_key_derived_key(salt, salt_size, output1, key_type); + TEST_ASSERT_EQUAL_INT32(0, ret); + bool is_empty = !memcmp(empty_buffer, output1, sizeof(output1)); + TEST_ASSERT_FALSE(is_empty); + + for (int i = 0; i < 100; i++) { + memset(output2, 0, sizeof(output2)); + ret = devkey.device_key_derived_key(salt, salt_size, output2, key_type); + TEST_ASSERT_EQUAL_INT32(0, ret); + TEST_ASSERT_EQUAL_UINT8_ARRAY(output1, output2, DEVICE_KEY_16BYTE); + } +} + +/* + * Test the consistency of derived 32 byte key result. + */ +void device_key_derived_key_consistency_32_byte_key_test() +{ + unsigned char output1[DEVICE_KEY_32BYTE]; + unsigned char output2[DEVICE_KEY_32BYTE]; + unsigned char empty_buffer[DEVICE_KEY_32BYTE]; + unsigned char salt[] = "The quick brown fox jumps over the lazy dog"; + int key_type = DEVICE_KEY_32BYTE; + DeviceKey& devkey = DeviceKey::get_instance(); + NVStore& nvstore = NVStore::get_instance(); + + int ret = nvstore.reset(); + TEST_ASSERT_EQUAL_INT(DEVICEKEY_SUCCESS, ret); + + ret = inject_dummy_rot_key(); + TEST_ASSERT_EQUAL_INT(DEVICEKEY_SUCCESS, ret); + + size_t salt_size = sizeof(salt); + memset(output1, 0, sizeof(output1)); + ret = devkey.device_key_derived_key(salt, salt_size, output1, key_type); + TEST_ASSERT_EQUAL_INT32(0, ret); + bool is_empty = !memcmp(empty_buffer, output1, sizeof(output1)); + TEST_ASSERT_FALSE(is_empty); + + for (int i = 0; i < 100; i++) { + memset(output2, 0, sizeof(output2)); + ret = devkey.device_key_derived_key(salt, salt_size, output2, key_type); + TEST_ASSERT_EQUAL_INT32(0, ret); + TEST_ASSERT_EQUAL_UINT8_ARRAY(output1, output2, DEVICE_KEY_32BYTE); + } +} + +/* + * Test request for 16 byte key is returning a correct key size. + */ +void device_key_derived_key_key_type_16_test() +{ + unsigned char output[DEVICE_KEY_16BYTE * 2]; + unsigned char salt[] = "The quick brown fox jumps over the lazy dog"; + unsigned char expectedString[] = "Some String"; + int key_type = DEVICE_KEY_16BYTE; + size_t salt_size = sizeof(salt); + DeviceKey& devkey = DeviceKey::get_instance(); + NVStore& nvstore = NVStore::get_instance(); + + int ret = nvstore.reset(); + TEST_ASSERT_EQUAL_INT(DEVICEKEY_SUCCESS, ret); + + ret = inject_dummy_rot_key(); + TEST_ASSERT_EQUAL_INT(DEVICEKEY_SUCCESS, ret); + + memset(output, 0, DEVICE_KEY_16BYTE * 2); + memcpy(output + DEVICE_KEY_16BYTE - sizeof(expectedString), expectedString, sizeof(expectedString)); + memcpy(output + DEVICE_KEY_16BYTE + 1, expectedString, sizeof(expectedString)); + + ret = devkey.device_key_derived_key(salt, salt_size, output, key_type); + TEST_ASSERT_EQUAL_INT32(0, ret); + //Test that we didn't override the buffer after the 16 byte size + TEST_ASSERT_EQUAL_UINT8_ARRAY(output + DEVICE_KEY_16BYTE + 1, expectedString, sizeof(expectedString)); + //Test that we did override the buffer all 16 byte + TEST_ASSERT(memcmp(output + DEVICE_KEY_16BYTE - sizeof(expectedString), expectedString, sizeof(expectedString)) != 0); +} + +/* + * Test request for 32 byte key is returning a correct key size. + */ +void device_key_derived_key_key_type_32_test() +{ + unsigned char output[DEVICE_KEY_32BYTE * 2]; + unsigned char salt[] = "The quick brown fox jumps over the lazy dog"; + int key_type = DEVICE_KEY_32BYTE; + size_t salt_size = sizeof(salt); + unsigned char expectedString[] = "Some String"; + DeviceKey& devkey = DeviceKey::get_instance(); + NVStore& nvstore = NVStore::get_instance(); + + int ret = nvstore.reset(); + TEST_ASSERT_EQUAL_INT(DEVICEKEY_SUCCESS, ret); + + ret = inject_dummy_rot_key(); + TEST_ASSERT_EQUAL_INT(DEVICEKEY_SUCCESS, ret); + + memset(output, 0, DEVICE_KEY_32BYTE * 2); + memcpy(output + DEVICE_KEY_32BYTE - sizeof(expectedString), expectedString, sizeof(expectedString)); + memcpy(output + DEVICE_KEY_32BYTE + 1, expectedString, sizeof(expectedString)); + + ret = devkey.device_key_derived_key(salt, salt_size, output, key_type); + TEST_ASSERT_EQUAL_INT32(0, ret); + //Test that we didn't override the buffer after the 32 byte size + TEST_ASSERT_EQUAL_UINT8_ARRAY(output + DEVICE_KEY_32BYTE + 1, expectedString, sizeof(expectedString)); + //Test that we did override the buffer all 32 byte + TEST_ASSERT(memcmp(output + DEVICE_KEY_32BYTE - sizeof(expectedString), expectedString, sizeof(expectedString)) != 0); +} + +/* + * Test request for unknown key size returns an error + */ +void device_key_derived_key_wrong_key_type_test() +{ + unsigned char output[DEVICE_KEY_16BYTE]; + unsigned char salt[] = "The quick brown fox jumps over the lazy dog"; + size_t salt_size = sizeof(salt); + DeviceKey& devkey = DeviceKey::get_instance(); + NVStore& nvstore = NVStore::get_instance(); + + nvstore.init(); + int ret = nvstore.reset(); + TEST_ASSERT_EQUAL_INT(DEVICEKEY_SUCCESS, ret); + + ret = inject_dummy_rot_key(); + TEST_ASSERT_EQUAL_INT(DEVICEKEY_SUCCESS, ret); + + memset(output, 0, DEVICE_KEY_32BYTE); + ret = devkey.device_key_derived_key(salt, salt_size, output, 12);//96 bit key type is not supported + TEST_ASSERT_EQUAL_INT32(DEVICEKEY_INVALID_KEY_TYPE, ret); + +} + +utest::v1::status_t greentea_failure_handler(const Case *const source, const failure_t reason) +{ + greentea_case_failure_abort_handler(source, reason); + return STATUS_CONTINUE; +} + +//Currently there can be only one test that contains reset and it has to be the first test! +Case cases[] = { + Case("Device Key - derived key reset", device_key_derived_key_reset_test, greentea_failure_handler), + Case("Device Key - inject value wrong size", device_inject_root_of_trust_wrong_size_test, greentea_failure_handler), + Case("Device Key - inject value 16 byte size", device_inject_root_of_trust_16_byte_size_test, greentea_failure_handler), + Case("Device Key - inject value 32 byte size", device_inject_root_of_trust_32_byte_size_test, greentea_failure_handler), + Case("Device Key - inject value several times", device_inject_root_of_trust_several_times_test, greentea_failure_handler), + Case("Device Key - derived key consistency 16 byte key", device_key_derived_key_consistency_16_byte_key_test, greentea_failure_handler), + Case("Device Key - derived key consistency 32 byte key", device_key_derived_key_consistency_32_byte_key_test, greentea_failure_handler), + Case("Device Key - derived key key type 16", device_key_derived_key_key_type_16_test, greentea_failure_handler), + Case("Device Key - derived key key type 32", device_key_derived_key_key_type_32_test, greentea_failure_handler), + Case("Device Key - derived key wrong key type", device_key_derived_key_wrong_key_type_test, greentea_failure_handler) +}; + +utest::v1::status_t greentea_test_setup(const size_t number_of_cases) +{ + GREENTEA_SETUP(14, "devicekey_reset"); + return greentea_test_setup_handler(number_of_cases); +} + +Specification specification(greentea_test_setup, cases, greentea_test_teardown_handler); + +int main() +{ + bool ret = Harness::run(specification); + greentea_send_kv(MSG_KEY_DEVICE_TEST_SUITE_ENDED, MSG_VALUE_DUMMY); + + return ret; +} + diff --git a/drivers/DeviceKey.cpp b/drivers/DeviceKey.cpp new file mode 100644 index 0000000000..6efa9f0265 --- /dev/null +++ b/drivers/DeviceKey.cpp @@ -0,0 +1,252 @@ +/* mbed Microcontroller Library + * Copyright (c) 2018 ARM Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "drivers/DeviceKey.h" +#include "mbedtls/config.h" +#include "mbedtls/cmac.h" +#include "nvstore.h" +#include "trng_api.h" + +#if !defined(MBEDTLS_CMAC_C) +#error [NOT_SUPPORTED] MBEDTLS_CMAC_C needs to be enabled for this driver +#else + +namespace mbed { + +DeviceKey::DeviceKey() +{ + return; +} + +DeviceKey::~DeviceKey() +{ + return; +} + +int DeviceKey::device_key_derived_key(const unsigned char *salt, size_t isalt_size, unsigned char *output, + uint16_t ikey_type) +{ + uint32_t key_buff[DEVICE_KEY_32BYTE / sizeof(uint32_t)]; + size_t actual_size = DEVICE_KEY_32BYTE; + + if (DEVICE_KEY_16BYTE != ikey_type && DEVICE_KEY_32BYTE != ikey_type) { + return DEVICEKEY_INVALID_KEY_TYPE; + } + + //First try to read the key from NVStore + int ret = read_key_from_nvstore(key_buff, actual_size); + if (DEVICEKEY_SUCCESS != ret && DEVICEKEY_NOT_FOUND != ret) { + return ret; + } + + if (DEVICE_KEY_16BYTE != actual_size && DEVICE_KEY_32BYTE != actual_size) { + return DEVICEKEY_READ_FAILED; + } + + //If the key was not found in NVStore we will create it by using TRNG and then save it to NVStore + if (DEVICEKEY_NOT_FOUND == ret) { + ret = generate_key_by_trng(key_buff, actual_size); + if (DEVICEKEY_SUCCESS != ret) { + return ret; + } + + ret = device_inject_root_of_trust(key_buff, actual_size); + if (DEVICEKEY_SUCCESS != ret) { + return ret; + } + } + + ret = get_derived_key(key_buff, actual_size, salt, isalt_size, output, ikey_type); + return ret; +} + +int DeviceKey::device_inject_root_of_trust(uint32_t *value, size_t isize) +{ + return write_key_to_nvstore(value, isize); +} + +int DeviceKey::write_key_to_nvstore(uint32_t *input, size_t isize) +{ + if (DEVICE_KEY_16BYTE != isize && DEVICE_KEY_32BYTE != isize) { + return DEVICEKEY_INVALID_KEY_SIZE; + } + + //First we read if key exist. If it is exists, we return DEVICEKEY_ALREADY_EXIST error + uint32_t read_key[DEVICE_KEY_32BYTE / sizeof(uint32_t)] = {0}; + size_t read_size = DEVICE_KEY_32BYTE; + int ret = read_key_from_nvstore(read_key, read_size); + if (DEVICEKEY_SUCCESS == ret) { + return DEVICEKEY_ALREADY_EXIST; + } + if (DEVICEKEY_NOT_FOUND != ret) { + return ret; + } + + NVStore& nvstore = NVStore::get_instance(); + ret = nvstore.set(NVSTORE_DEVICEKEY_KEY, (uint16_t)isize, input); + if (NVSTORE_WRITE_ERROR == ret || NVSTORE_BUFF_TOO_SMALL == ret) { + return DEVICEKEY_SAVE_FAILED; + } + + if (NVSTORE_SUCCESS != ret) { + return DEVICEKEY_NVSTORE_UNPREDICTABLE_ERROR; + } + + return DEVICEKEY_SUCCESS; +} + +int DeviceKey::read_key_from_nvstore(uint32_t *output, size_t& size) +{ + if (size > UINT16_MAX) { + return DEVICEKEY_INVALID_PARAM; + } + + uint16_t in_size = size; + uint16_t out_size = 0; + NVStore& nvstore = NVStore::get_instance(); + int nvStatus = nvstore.get(NVSTORE_DEVICEKEY_KEY, in_size, output, out_size); + if (NVSTORE_NOT_FOUND == nvStatus) { + return DEVICEKEY_NOT_FOUND; + } + + if (NVSTORE_READ_ERROR == nvStatus || NVSTORE_BUFF_TOO_SMALL == nvStatus) { + return DEVICEKEY_READ_FAILED; + } + + if (NVSTORE_SUCCESS != nvStatus) { + return DEVICEKEY_NVSTORE_UNPREDICTABLE_ERROR; + } + + size = out_size; + return DEVICEKEY_SUCCESS; +} + +// Calculate CMAC functions - wrapper for mbedtls start/update and finish +int DeviceKey::calc_cmac(const unsigned char *input, size_t isize, uint32_t *ikey_buff, int ikey_size, + unsigned char *output) +{ + int ret; + mbedtls_cipher_context_t ctx; + + mbedtls_cipher_type_t mbedtls_cipher_type = MBEDTLS_CIPHER_AES_128_ECB; + if (DEVICE_KEY_32BYTE == ikey_size) { + mbedtls_cipher_type = MBEDTLS_CIPHER_AES_256_ECB; + } + + const mbedtls_cipher_info_t *cipher_info = mbedtls_cipher_info_from_type(mbedtls_cipher_type); + + mbedtls_cipher_init(&ctx); + ret = mbedtls_cipher_setup(&ctx, cipher_info); + if (ret != 0) { + goto finish; + } + + ret = mbedtls_cipher_cmac_starts(&ctx, (unsigned char *)ikey_buff, ikey_size * 8); + if (ret != 0) { + goto finish; + } + + ret = mbedtls_cipher_cmac_update(&ctx, input, isize); + if (ret != 0) { + goto finish; + } + + ret = mbedtls_cipher_cmac_finish(&ctx, output); + if (ret != 0) { + goto finish; + } + + return DEVICEKEY_SUCCESS; + +finish: + mbedtls_cipher_free( &ctx ); + return ret; +} + +int DeviceKey::get_derived_key(uint32_t *ikey_buff, size_t ikey_size, const unsigned char *isalt, + size_t isalt_size, unsigned char *output, uint32_t ikey_type) +{ + int ret; + unsigned char *double_size_salt = NULL; + + if (DEVICE_KEY_16BYTE == ikey_type) { + ret = calc_cmac(isalt, isalt_size, ikey_buff, ikey_size, output); + if (DEVICEKEY_SUCCESS != ret) { + goto finish; + } + } + + if (DEVICE_KEY_32BYTE == ikey_type) { + ret = this->calc_cmac(isalt, isalt_size, ikey_buff, ikey_size, output); + if (DEVICEKEY_SUCCESS != ret) { + goto finish; + } + + //Double the salt size cause cmac always return just 16 bytes + double_size_salt = new unsigned char[isalt_size * 2]; + memcpy(double_size_salt, isalt, isalt_size); + memcpy(double_size_salt + isalt_size, isalt, isalt_size); + + ret = this->calc_cmac(double_size_salt, isalt_size * 2, ikey_buff, ikey_size, output + 16); + } + +finish: + if (double_size_salt != NULL) { + delete[] double_size_salt; + } + + if (DEVICEKEY_SUCCESS != ret) { + return DEVICEKEY_ERR_CMAC_GENERIC_FAILURE; + } + + return DEVICEKEY_SUCCESS; +} + +int DeviceKey::generate_key_by_trng(uint32_t *output, size_t& size) +{ +#if defined(DEVICE_TRNG) + size_t in_size; + trng_t trng_obj; + + memset(output, 0, size); + + if (DEVICE_KEY_16BYTE > size) { + return DEVICEKEY_BUFFER_TO_SMALL; + } else if (DEVICE_KEY_16BYTE <= size && DEVICE_KEY_32BYTE > size) { + in_size = DEVICE_KEY_16BYTE; + } else { + in_size = DEVICE_KEY_32BYTE; + } + + trng_init(&trng_obj); + + int ret = trng_get_bytes(&trng_obj, (unsigned char *)output, in_size, &size); + if (DEVICEKEY_SUCCESS != ret || in_size != size) { + return DEVICEKEY_TRNG_ERROR; + } + + trng_free(&trng_obj); + return DEVICEKEY_SUCCESS; +#else + return DEVICEKEY_NO_KEY_INJECTED; +#endif +} + +} // namespace mbed + +#endif + + diff --git a/drivers/DeviceKey.h b/drivers/DeviceKey.h new file mode 100644 index 0000000000..e04c1034cf --- /dev/null +++ b/drivers/DeviceKey.h @@ -0,0 +1,148 @@ +/* mbed Microcontroller Library + * Copyright (c) 2018 ARM Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef MBED_DEVICEKEY_H +#define MBED_DEVICEKEY_H + +#include "stddef.h" +#include "stdint.h" +#include "platform/NonCopyable.h" + +namespace mbed { +/** \addtogroup drivers */ + +#define DEVICE_KEY_16BYTE 16 +#define DEVICE_KEY_32BYTE 32 + +enum DeviceKeyStatus { + DEVICEKEY_SUCCESS = 0, + DEVICEKEY_INVALID_KEY_SIZE = -1, + DEVICEKEY_INVALID_KEY_TYPE = -2, + DEVICEKEY_SAVE_FAILED = -3, + DEVICEKEY_ALREADY_EXIST = -4, + DEVICEKEY_NOT_FOUND = -5, + DEVICEKEY_READ_FAILED = -6, + DEVICEKEY_NVSTORE_UNPREDICTABLE_ERROR = -7, + DEVICEKEY_ERR_CMAC_GENERIC_FAILURE = -8, + DEVICEKEY_BUFFER_TO_SMALL = -9, + DEVICEKEY_NO_KEY_INJECTED = -10, + DEVICEKEY_INVALID_PARAM = -11, + DEVICEKEY_TRNG_ERROR = -12, +}; + +/** Use this singleton if you need to derive a new key from the device root of trust. + * + * + * @endcode + * @ingroup drivers + */ + +class DeviceKey : private mbed::NonCopyable { +public: + + /** + * @brief As a singleton, return the single instance of the class. + * Reason for this class being a singleton is the following: + * - Ease the use for users of this class not having to coordinate instantiations. + * - Lazy instantiation of internal data (which we can't achieve with simple static classes). + * + * @returns Singleton instance reference. + */ + static DeviceKey& get_instance() + { + // Use this implementation of singleton (Meyer's) rather than the one that allocates + // the instance on the heap, as it ensures destruction at program end (preventing warnings + // from memory checking tools such as valgrind). + static DeviceKey instance; + return instance; + } + + virtual ~DeviceKey(); + + /** Derive a new key based on the salt string. key type can be with values 16 bytes and 32 bytes + * @param isalt input buffer used to create the new key. Same input will generate always the same key + * @param isalt_size size of the data in salt buffer + * @param output buffer to receive the derived key. Size must be 16 bytes or 32 bytes + * according to the ikey_type parameter + * @param ikey_type type of the required key. Type must be 16 bytes or 32 bytes. + * @return 0 on success, negative error code on failure + */ + int device_key_derived_key(const unsigned char *isalt, size_t isalt_size, unsigned char *output, uint16_t ikey_type); + + /** Set a device key into the NVStore. In case TRNG support is missing, Call this method + * before calling device_key_derived_key. This method should be called only once! + * @param value input buffer contain the key. + * @param isize size of the supplied key. Must be 16 bytes or 32 bytes. + * @return 0 on success, negative error code on failure + */ + int device_inject_root_of_trust(uint32_t *value, size_t isize); + +private: + // Private constructor, as class is a singleton + DeviceKey(); + + /** Calculate CMAC + * @param input buffer contain some string. + * @param isize size of the supplied input string. + * @param ikey_buff input buffer holding the ROT key + * @param ikey_size size of the input key. must be 16 bytes or 32 bytes. + * @param output buffer for the CMAC result. + * @return 0 on success, negative error code on failure + */ + int calc_cmac(const unsigned char *input, size_t isize, uint32_t *ikey_buff, int ikey_size, unsigned char *output); + + /** Read a device key from the NVStore + * @param output buffer for the returned key. + * @param size input: the size of the output buffer. + * output: the actual size of the written data + * @return 0 on success, negative error code on failure + */ + int read_key_from_nvstore(uint32_t *output, size_t& size); + + /** Set a device key into the NVStore + * @param input input buffer contain the key. + * @param isize the size of the input buffer. + * @return 0 on success, negative error code on failure + */ + int write_key_to_nvstore(uint32_t *input, size_t isize); + + /** Get a derived key base on a salt string + * @param ikey_buff input buffer holding the ROT key + * @param ikey_size size of the input key. Must be 16 bytes or 32 bytes. + * @param isalt input buffer contain some string. + * @param isalt_size size of the supplied input string. + * @param output buffer for the derived key result. + * @param ikey_type the requested key size. Must be 16 bytes or 32 bytes. + * @return 0 on success, negative error code on failure + */ + int get_derived_key(uint32_t *ikey_buff, size_t ikey_size, const unsigned char *isalt, size_t isalt_size, + unsigned char *output, uint32_t ikey_type); + + /** Generate a random ROT key by using TRNG + * @param output output buffer for the generated key. + * @param size input: the size of the buffer. if size is less + * then 16 bytes the method will generate an + * error. 16-31 bytes will create a 16 byte key. + * 32 or higher will generate a 32 bytes key + * output: the actual written size to the buffer + * @return 0 on success, negative error code on failure + */ + int generate_key_by_trng(uint32_t *output, size_t& size); +}; +/** @}*/ + +} + +#endif diff --git a/features/mbedtls/inc/mbedtls/config-no-entropy.h b/features/mbedtls/inc/mbedtls/config-no-entropy.h index b4a0930b9c..a0e979f0c2 100644 --- a/features/mbedtls/inc/mbedtls/config-no-entropy.h +++ b/features/mbedtls/inc/mbedtls/config-no-entropy.h @@ -82,6 +82,7 @@ #define MBEDTLS_X509_USE_C #define MBEDTLS_X509_CRT_PARSE_C #define MBEDTLS_X509_CRL_PARSE_C +#define MBEDTLS_CMAC_C /* Miscellaneous options */ #define MBEDTLS_AES_ROM_TABLES diff --git a/features/mbedtls/inc/mbedtls/config.h b/features/mbedtls/inc/mbedtls/config.h index f1a0307ad1..fe174e0c61 100644 --- a/features/mbedtls/inc/mbedtls/config.h +++ b/features/mbedtls/inc/mbedtls/config.h @@ -1861,7 +1861,7 @@ * Requires: MBEDTLS_AES_C or MBEDTLS_DES_C * */ -//#define MBEDTLS_CMAC_C +#define MBEDTLS_CMAC_C /** * \def MBEDTLS_CTR_DRBG_C diff --git a/features/nvstore/source/nvstore.h b/features/nvstore/source/nvstore.h index 91c675e057..39859d3285 100644 --- a/features/nvstore/source/nvstore.h +++ b/features/nvstore/source/nvstore.h @@ -49,6 +49,8 @@ typedef enum { // All predefined keys used for internal features should be defined here + NVSTORE_DEVICEKEY_KEY = 4, + NVSTORE_LAST_PREDEFINED_KEY = 15, NVSTORE_NUM_PREDEFINED_KEYS } nvstore_predefined_keys_e;