Device key implementation

pull/6642/head
Yossi Levy 2018-02-21 11:48:13 +02:00 committed by David Saada
parent f895392374
commit efc19040ea
9 changed files with 1123 additions and 1 deletions

View File

@ -0,0 +1,46 @@
## DeviceKey
DeviceKey is a mechanism that implements key derivation from a root of trust key. The DeviceKey mechanism generates symmetric keys that security features need. You can use these keys for encryption, authentication and more. The DeviceKey API allows key derivation without exposing the actual root of trust, to reduce the possibility of accidental exposure of the root of trust outside the device.
We have implemented DeviceKey according to NIST SP 800-108, section "KDF in Counter Mode", with AES-CMAC as the pseudorandom function.
### Root of Trust
The root of trust key, which DeviceKey uses to derive additional keys, is generated using the hardware random generator if it exists, or using a key injected to the device in the production process.
The characteristics required by this root of trust are:
- It must be unique per device.
- It must be difficult to guess.
- It must be at least 128 bits.
- It must be kept secret.
The DeviceKey feature keeps the root of trust key in internal storage, using the NVStore component. Internal storage provides protection from external physical attacks to the device.
The root of trust is generated at the first use of DeviceKey if the true random number generator is available in the device. If no true random number generator is available, you must pass the injected root of trust key to the DeviceKey before you call the key derivation API.
### Key derivation API
`generate_derived_key`: This API generates a new key based on a string (salt) the caller provides. The same key is generated for the same salt. Generated keys can be 128 or 256 bits in length.
#### Root of Trust Injection API
`device_inject_root_of_trust`: You must call this API once in the lifecycle of the device, before any call to key derivation, if the device does not support True Random Number Generator (`DEVICE_TRNG` is not defined).
#### Using DeviceKey
DeviceKey is a singleton class, meaning that the system can have only a single instance of it.
To instantiate DeviceKey, you need to call its `get_instance` member function as following:
```c++
DeviceKey &deviceKey = DeviceKey::get_instance();
```
#### Testing DeviceKey
Run the DeviceKey functionality test with the `mbed` command as follows:
```
```mbed test -n features-device_key-tests-device_key-functionality```
```

View File

@ -0,0 +1,491 @@
/* 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"
#if !NVSTORE_ENABLED
#error [NOT_SUPPORTED] NVSTORE needs to be enabled for this test
#endif
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 generate_derived_key_consistency_16_byte_key_reset_test(char *key);
void generate_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)];
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 generate_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) {
generate_derived_key_consistency_16_byte_key_reset_test(key);
return generate_derived_key_reset_test();
}
if (strcmp(key, MSG_KEY_DEVICE_TEST_STEP3) == 0 || strcmp(key, MSG_KEY_DEVICE_TEST_STEP4) == 0) {
return generate_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 generate_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.generate_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.generate_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 generate_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.generate_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.generate_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 generate_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.generate_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.generate_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 generate_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.generate_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.generate_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 generate_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.generate_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 generate_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.generate_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 generate_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.generate_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", generate_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", generate_derived_key_consistency_16_byte_key_test, greentea_failure_handler),
Case("Device Key - derived key consistency 32 byte key", generate_derived_key_consistency_32_byte_key_test, greentea_failure_handler),
Case("Device Key - derived key key type 16", generate_derived_key_key_type_16_test, greentea_failure_handler),
Case("Device Key - derived key key type 32", generate_derived_key_key_type_32_test, greentea_failure_handler),
Case("Device Key - derived key wrong key type", generate_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;
}

View File

@ -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

View File

@ -0,0 +1,3 @@
{
"name": "device_key"
}

View File

@ -0,0 +1,287 @@
/* 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 "mbedtls/config.h"
#include "mbedtls/cmac.h"
#include "nvstore.h"
#include "trng_api.h"
#include "mbed_wait_api.h"
#include "stdlib.h"
#if !defined(MBEDTLS_CMAC_C)
#error [NOT_SUPPORTED] MBEDTLS_CMAC_C needs to be enabled for this driver
#else
#if NVSTORE_ENABLED
namespace mbed {
#define DEVKEY_WRITE_UINT32_LE( dst, src ) \
do \
{ \
(dst)[0] = ( (src) >> 0 ) & 0xFF; \
(dst)[1] = ( (src) >> 8 ) & 0xFF; \
(dst)[2] = ( (src) >> 16 ) & 0xFF; \
(dst)[3] = ( (src) >> 24 ) & 0xFF; \
} while( 0 )
#define DEVKEY_WRITE_UINT8_LE( dst, src ) \
do \
{ \
(dst)[0] = (src) & 0xFF; \
} while( 0 )
DeviceKey::DeviceKey()
{
return;
}
DeviceKey::~DeviceKey()
{
return;
}
int DeviceKey::generate_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_UNPREDICTED_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_UNPREDICTED_ERROR;
}
size = out_size;
return DEVICEKEY_SUCCESS;
}
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)
{
//KDF in counter mode implementation as described in Section 5.1
//of NIST SP 800-108, Recommendation for Key Derivation Using Pseudorandom Functions
int ret;
size_t counter = 0;
char separator = 0x00;
mbedtls_cipher_context_t ctx;
unsigned char output_len_enc[ 4 ] = {0};
unsigned char counter_enc[ 1 ] = {0};
DEVKEY_WRITE_UINT32_LE(output_len_enc, ikey_type);
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;
}
do {
ret = mbedtls_cipher_cmac_starts(&ctx, (unsigned char *)ikey_buff, ikey_size * 8);
if (ret != 0) {
goto finish;
}
DEVKEY_WRITE_UINT8_LE(counter_enc, (counter+1));
ret = mbedtls_cipher_cmac_update(&ctx, (unsigned char *)counter_enc, sizeof(counter_enc));
if (ret != 0) {
goto finish;
}
ret = mbedtls_cipher_cmac_update(&ctx, isalt, isalt_size);
if (ret != 0) {
goto finish;
}
ret = mbedtls_cipher_cmac_update(&ctx, (unsigned char *)&separator, sizeof(char));
if (ret != 0) {
goto finish;
}
ret = mbedtls_cipher_cmac_update(&ctx, (unsigned char *)&output_len_enc, sizeof(output_len_enc));
if (ret != 0) {
goto finish;
}
ret = mbedtls_cipher_cmac_finish(&ctx, output + (DEVICE_KEY_16BYTE * (counter)));
if (ret != 0) {
goto finish;
}
counter++;
} while (DEVICE_KEY_16BYTE * counter < ikey_type);
finish:
mbedtls_cipher_free( &ctx );
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;
size_t ongoing_size;
size_t final_size;
trng_t trng_obj;
int ret = DEVICEKEY_SUCCESS;
unsigned char *pBuffer = (unsigned char *)output;
memset(output, 0, size);
if (DEVICE_KEY_16BYTE > size) {
return DEVICEKEY_BUFFER_TOO_SMALL;
} else if (DEVICE_KEY_16BYTE <= size && DEVICE_KEY_32BYTE > size) {
size = DEVICE_KEY_16BYTE;
} else {
size = DEVICE_KEY_32BYTE;
}
trng_init(&trng_obj);
final_size = 0;
in_size = size;
while (final_size < size) {
ongoing_size = 0;
ret = trng_get_bytes(&trng_obj, (unsigned char *)pBuffer, in_size, &ongoing_size);
final_size += ongoing_size;
if (0 != ret) {
ret = DEVICEKEY_TRNG_ERROR;
goto finish;
}
pBuffer += ongoing_size;
in_size -= ongoing_size;
}
ret = DEVICEKEY_SUCCESS;
finish:
trng_free(&trng_obj);
size = final_size;
return ret;
#else
return DEVICEKEY_NO_KEY_INJECTED;
#endif
}
} // namespace mbed
#endif //NVSTORE_ENABLED
#endif

View File

@ -0,0 +1,142 @@
/* 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"
#if (NVSTORE_ENABLED)
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_UNPREDICTED_ERROR = -7,
DEVICEKEY_ERR_CMAC_GENERIC_FAILURE = -8,
DEVICEKEY_BUFFER_TOO_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.
*
* @note Synchronization level: Thread safe
* @ingroup drivers
*/
class DeviceKey : private mbed::NonCopyable<DeviceKey> {
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;
}
~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 generate_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();
/** 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. The methods implements
* Section 5.1 in NIST SP 800-108, Recommendation for Key Derivation Using Pseudorandom Functions
* @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 //NVSTORE_ENABLED
#endif

View File

@ -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

View File

@ -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

View File

@ -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;