Revision | Date | Authors | Mbed OS version | Comments |
---|---|---|---|---|
1.0 | 02 October 2018 | David Saada (@davidsaada) | 5.11+ | Initial revision |
SecureStore is a KVStore based storage solution, providing security features on the stored data, such as encryption, authentication, rollback protection and write once, over an underlying KVStore class. It references an additional KVStore class for storing the rollback protection keys.
SecureStore assumes that the underlying KVStore instances are instantiated and initialized.
SecureStore is a storage class, derived from KVStore. It adds security features to the underlying key value store. As such, it offers all KVStore APIs, with additional security options (which can be selected using the creation flags at set). These include:
When storing the data, it is stored with a preceding metadata header. Metadata includes flags and security related parameters, such as IV. The CMAC, calculated for authentication, is stored at the end of the data as it is calculated on the fly, so it can't be stored with the metadata.
Fields are:
Because the code can't construct a single buffer to store all data (including metadata and possibly encrypted data) in one shot, setting the data occurs in chunks, using the incremental set APIs. Get uses the offset argument to extract metadata, data and CMAC separately.
Rollback protection (RBP) keys are stored in the designated rollback protection storage, which is also of KVStore type. RBP keys are the same as the SecureStore keys.
Functionality, as defined by KVStore, includes the following:
SecureStore has the following header:
class SecureStore : KVStore { public: SecureStore(KVStore *underlying_kv, KVStore *rbp_kv); virtual ~SecureStore(); // Initialization and formatting virtual int init(); virtual int deinit(); virtual int reset(); // Core API virtual int set(const char *key, const void *buffer, size_t size, uint32_t create_flags); virtual int get(const char *key, void *buffer, size_t buffer_size, size_t *actual_size = NULL, size_t offset = 0); virtual int get_info(const char *key, info_t *info); virtual int remove(const char *key); // Incremental set API virtual int set_start(set_handle_t *handle, const char *key, size_t final_data_size, uint32_t create_flags); virtual int set_add_data(set_handle_t handle, const void *value_data, size_t data_size); virtual int set_finalize(set_handle_t handle); // Key iterator virtual int iterator_open(iterator_t *it, const char *prefix = NULL); virtual int iterator_next(iterator_t it, char *key, size_t key_size); virtual int iterator_close(iterator_t it); private: Mutex _mutex; KVStore *_underlying_kv; KVStore *_rbp_kv; void *_entropy; uint8_t *_scratch_buf; }
// Record header typedef struct { uint16_t metadata_size; uint16_t revision; uint32_t data_size; uint32_t create_flags; uint8_t iv[8]; } record_metadata_t; // incremental set handle typedef struct { record_metadata_t metadata; bd_size_t offset; char *key; void *encrypt_handle; void *auth_handle; KVStore::set_handle_t underlying_handle; } inc_set_handle_t; // iterator handle typedef struct { KVStore::iterator_t underlying_it; } key_iterator_handle_t;
init function
Header:
virtual int init();
Pseudo code:
_is_initialized
, return OK._mutex
._entropy
with TLS entropy APIs.DeviceKey
APIs, get the device key._scratch_buf
as a 32 byte array._is_initialized
to true._mutex
.deinit functionHeader:
virtual int deinit();
Pseudo code:
_is_initialized
, return OK._mutex
._entropy
._scratch_buf
._mutex
.reset function
Header:
virtual int reset();
Pseudo code:
_mutex
._underlying_kv
reset
API._rbp_kv
reset
API._mutex
.set function
Header:
virtual int set(const char *key, const void *buffer, size_t size, uint32_t create_flags);
Pseudo code:
set_start
with all fields and a local set_handle_t
variable.set_add_data
with buffer
and size
.set_finalize
.get function
Header:
virtual int get(const char *key, void *buffer, size_t buffer_size, size_t *actual_size = NULL, size_t offset = 0);
Pseudo code:
_is_initialized
return error._mutex
._underlying_kv
get
API with metadata
size into a metadata
local structure._rbp_kv
get
API on a local rbp_cmac
variable, key is key
, size 16.key
.auth_handle
CMAC calculation local handle with derived key.auth_handle
handle, calculate CMAC on key
and metadata
.key
.enc_handle
AES-CTR local handle with derived key and iv
field.data_size
local variable to data size in metadata.actual_size
to the minimum of buffer_size
and data_size
.current_offset
to 0.data_size
> 0:
current_offset
between offset
and actual_size
.
dest_buf
to buffer
and chunk_size
to actual_size
.dest_buf
to _scratch_buf
and chunk_size
to actual_size
._underlying_kv
get
API with dest_buf
and chunk_size
.dest_buf
, using _auth_handle
handle.dest_buf
(in place) using _enc_handle
handle.data_size
by chunk_size
._underlying_kv
get
API with on a local read_cmac
variable, size 16.cmac
variable .mbedtls_ssl_safer_memcmp
function, compare read_cmac
with cmac
. Return "data corrupt error" if no match._rbp_kv
get
API on a local rbp_cmac
variable, key is key
, size 16.rbp_cmac
doesn't match cmac
, clear buffer
and return "RBP authentication" error.auth_handle
and enc_handle
._mutex
.get_info function
Header:
virtual int get_info(const char *key, info_t *info);
Pseudo code:
_is_initialized
, return error.get
API with key
and 0 in buffer_size
parameter._underlying_kv
get
API with metadata
size and key
.info
according to metadata
.remove function
Header:
virtual int remove(const char *key);
Pseudo code:
_is_initialized
, return error._mutex
._underlying_kv
get
API with metadata
size and key
._underlying_kv
remove
API with key
._rbp_kv
remove
API with key
as key.set_start function
Header:
virtual int set_start(set_handle_t *handle, const char *key, size_t final_data_size, uint32_t create_flags);
Pseudo code:
_mutex
.inc_set_handle_t
and assign in handle._underlying_kv
get_info
API._rbp_kv
get
API with key
as key. If key exists, return "already exists" error.key
as salt (trimmed to 32 bytes with "ENC" as prefix)._entropy
handle, randomly generate iv
field.enc_handle
AES-CTR handle field with derived key and iv
field.metadata
.key
as salt (trimmed to 32 bytes with "AUTH" as prefix).auth_handle
CMAC calculation handle field with derived key.auth_handle
handle, calculate CMAC on key
and metadata
._underlying_kv
set_start
API._underlying_kv
set_add_data
API with metadata
field.set_add_data function
Header:
virtual int set_add_data(set_handle_t handle, const void *value_data, size_t data_size);
Pseudo code:
offset
+ data_size
> data size in handle, return error.value_data
field in chunks of _scratch_buf
size.
enc_handle
handle field, encrypt chunk into _scratch_buf
.auth_handle
handle field, update CMAC of _scratch_buf
._underlying_kv
set_add_data
API with _scratch_buf
.auth_handle
handle field, update CMAC of value_data
._underlying_kv
set_add_data
API with value_data
.offset
field in handle.set_finalize function
Header:
virtual int set_finalize(set_handle_t handle);
Pseudo code:
cmac
16-byte array to 0.auth_handle
handle field, generate cmac
._underlying_kv
set_add_data
API with cmac
._underlying_kv
set_finalize
._rbp_kv
set
API with key
as key and cmac
as data.auth_handle
and enc_handle
.handle
._mutex
.iterator_open function
Header:
virtual int iterator_open(iterator_t *it, const char *prefix = NULL);
Pseudo code:
key_iterator_handle_t
structure into it
._mutex
._underlying_kv
iterator_open
with underlying_it
field._mutex
.iterator_next function
Header:
virtual int iterator_next(iterator_t it, char *key, size_t key_size);
Pseudo code:
_mutex
._underlying_kv
iterator_next
with underlying_it
field._mutex
.iterator_close function
Header:
virtual int iterator_close(iterator_t it);
Pseudo code:
_mutex
._underlying_kv
iterator_close
with underlying_it
field._mutex
.it
.The following example code shows standard use of the SecureStore class:
Standard usage example
// Underlying key value store - here TDBStore (should be instantiated and initialized) extern TDBStore tdbstore; // Rollback protect store - also of TDBStore type (should be instantiated and initialized) extern TDBStore rbp_tdbstore; // Instantiate SecureStore with tdbstore as underlying key value store and rbp_tdbstore as RBP storage SecureStore secure_store(&tdbstore, &rbp_tdbstore); int res; // Initialize secure_store res = secure_store.init(); const char *val1 = "Value of key 1"; const char *val2 = "Updated value of key 1"; // Add "Key1" with encryption and authentication flags res = secure_store.set("Key1", val1, sizeof(val1), KVSTore::REQUIRE_CONFIDENTIALITY_FLAG | KVSTore::REQUIRE_INTEGRITY_FLAG); // Update value of "Key1" (flags must be the same per key) res = secure_store.set("Key1", val2, sizeof(val2), KVSTore::REQUIRE_CONFIDENTIALITY_FLAG | KVSTore::REQUIRE_INTEGRITY_FLAG); uint_8 value[32]; size_t actual_size; // Get value of "Key1". Value should return the updated value. res = secure_store.get("Key1", value, sizeof(value), &actual_size); // Remove "Key1" res = secure_store.remove("Key1"); // Incremental write, if need to generate large data with a small buffer const int data_size = 1024; char buf[8]; KVSTore::set_handle_t handle; res = secure_store.set_start(&handle, "Key2", data_size, 0); for (int i = 0; i < data_size / sizeof(buf); i++) { memset(buf, i, sizeof(buf)); res = secure_store.set_add_data(handle, buf, sizeof(buf)); } res = secure_store.set_finalize(handle); // Iterate over all keys starting with "Key" res = 0; KVSTore::iterator_t it; secure_store.iterator_open(&it, "Key*"); char key[KVSTore::KV_MAX_KEY_LENGTH]; while (!res) { res = secure_store.iterator_next(&it, key, sizeof(key)e); } res = secure_store.iterator_close(&it); // Deinitialize SecureStore res = secure_store.deinit();