-
Notifications
You must be signed in to change notification settings - Fork 3k
PSA storage: Conform to "PSA 1.0.0" spec release #10847
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -28,10 +28,15 @@ | |
#include "psa/internal_trusted_storage.h" | ||
#include "psa/protected_storage.h" | ||
#include "psa/lifecycle.h" | ||
#include "KVMap.h" | ||
#include "KVStore.h" | ||
#include "kv_config.h" | ||
#include "psa_storage_common_impl.h" | ||
|
||
using namespace utest::v1; | ||
|
||
#define TEST_BUFF_SIZE 16 | ||
#define STR_EXPAND(tok) #tok | ||
|
||
typedef enum { | ||
its, | ||
|
@@ -40,20 +45,20 @@ typedef enum { | |
|
||
extern "C" psa_status_t psa_ps_reset(); | ||
|
||
static psa_status_t set_func(storage_type_t stype, psa_storage_uid_t uid, uint32_t data_length, | ||
static psa_status_t set_func(storage_type_t stype, psa_storage_uid_t uid, size_t data_length, | ||
const void *p_data, psa_storage_create_flags_t create_flags) | ||
{ | ||
return (stype == its) ? | ||
psa_its_set(uid, data_length, p_data, create_flags) : | ||
psa_ps_set(uid, data_length, p_data, create_flags); | ||
} | ||
|
||
static psa_status_t get_func(storage_type_t stype, psa_storage_uid_t uid, uint32_t data_offset, | ||
uint32_t data_length, void *p_data) | ||
static psa_status_t get_func(storage_type_t stype, psa_storage_uid_t uid, size_t data_offset, | ||
size_t data_length, void *p_data, size_t *actual_length) | ||
{ | ||
return (stype == its) ? | ||
psa_its_get(uid, data_offset, data_length, p_data) : | ||
psa_ps_get(uid, data_offset, data_length, p_data); | ||
psa_its_get(uid, data_offset, data_length, p_data, actual_length) : | ||
psa_ps_get(uid, data_offset, data_length, p_data, actual_length); | ||
} | ||
|
||
static psa_status_t get_info_func(storage_type_t stype, psa_storage_uid_t uid, | ||
|
@@ -78,6 +83,8 @@ void pits_ps_test() | |
psa_status_t status = PSA_SUCCESS; | ||
uint8_t write_buff[TEST_BUFF_SIZE] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F}; | ||
uint8_t read_buff[TEST_BUFF_SIZE] = {0}; | ||
size_t actual_size; | ||
psa_storage_create_flags_t flags; | ||
struct psa_storage_info_t info = {0, PSA_STORAGE_FLAG_WRITE_ONCE}; | ||
memset(read_buff, 0, TEST_BUFF_SIZE); | ||
|
||
|
@@ -92,15 +99,15 @@ void pits_ps_test() | |
TEST_ASSERT_EQUAL(TEST_BUFF_SIZE, info.size); | ||
TEST_ASSERT_EQUAL(0, info.flags); | ||
|
||
status = get_func(stype, 5, 0, TEST_BUFF_SIZE, read_buff); | ||
status = get_func(stype, 5, 0, TEST_BUFF_SIZE, read_buff, &actual_size); | ||
TEST_ASSERT_EQUAL(PSA_SUCCESS, status); | ||
TEST_ASSERT_EQUAL_MEMORY(write_buff, read_buff, TEST_BUFF_SIZE); | ||
|
||
memset(read_buff, 0, TEST_BUFF_SIZE); | ||
status = get_func(stype, 5, 1, TEST_BUFF_SIZE, read_buff); | ||
status = get_func(stype, 5, 1, TEST_BUFF_SIZE, read_buff, &actual_size); | ||
TEST_ASSERT_NOT_EQUAL(PSA_SUCCESS, status); | ||
|
||
status = get_func(stype, 5, 1, TEST_BUFF_SIZE - 1, read_buff); | ||
status = get_func(stype, 5, 1, TEST_BUFF_SIZE - 1, read_buff, &actual_size); | ||
TEST_ASSERT_EQUAL(PSA_SUCCESS, status); | ||
TEST_ASSERT_EQUAL_MEMORY(write_buff + 1, read_buff, TEST_BUFF_SIZE - 1); | ||
|
||
|
@@ -109,6 +116,38 @@ void pits_ps_test() | |
|
||
status = get_info_func(stype, 5, &info); | ||
TEST_ASSERT_EQUAL(PSA_ERROR_DOES_NOT_EXIST, status); | ||
|
||
if (stype == its) { | ||
return; | ||
} | ||
|
||
mbed::KVMap &kv_map = mbed::KVMap::get_instance(); | ||
mbed::KVStore *kvstore = kv_map.get_main_kv_instance(STR_EXPAND(MBED_CONF_STORAGE_DEFAULT_KV)); | ||
uint32_t kv_get_flags; | ||
|
||
flags = PSA_STORAGE_FLAG_NO_REPLAY_PROTECTION; | ||
status = set_func(stype, 6, TEST_BUFF_SIZE, write_buff, flags); | ||
TEST_ASSERT_EQUAL(PSA_SUCCESS, status); | ||
|
||
status = get_info_func(stype, 6, &info); | ||
TEST_ASSERT_EQUAL(PSA_SUCCESS, status); | ||
TEST_ASSERT_EQUAL(flags, info.flags); | ||
|
||
status = psa_storage_get_info_impl(kvstore, 1, 6, &info, &kv_get_flags); | ||
TEST_ASSERT_EQUAL(PSA_SUCCESS, status); | ||
TEST_ASSERT_EQUAL(kv_get_flags, mbed::KVStore::REQUIRE_CONFIDENTIALITY_FLAG); | ||
|
||
flags = PSA_STORAGE_FLAG_NO_REPLAY_PROTECTION | PSA_STORAGE_FLAG_NO_CONFIDENTIALITY | PSA_STORAGE_FLAG_WRITE_ONCE; | ||
status = set_func(stype, 6, TEST_BUFF_SIZE, write_buff, flags); | ||
TEST_ASSERT_EQUAL(PSA_SUCCESS, status); | ||
|
||
status = get_info_func(stype, 6, &info); | ||
TEST_ASSERT_EQUAL(PSA_SUCCESS, status); | ||
TEST_ASSERT_EQUAL(flags, info.flags); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How do we know that the KvStore flags are correct in this test? Should we also do an assert to make sure both |
||
|
||
status = psa_storage_get_info_impl(kvstore, 1, 6, &info, &kv_get_flags); | ||
TEST_ASSERT_EQUAL(PSA_SUCCESS, status); | ||
TEST_ASSERT_EQUAL(kv_get_flags, mbed::KVStore::WRITE_ONCE_FLAG); | ||
} | ||
|
||
template <storage_type_t stype> | ||
|
@@ -117,6 +156,7 @@ void pits_ps_write_once_test() | |
psa_status_t status = PSA_SUCCESS; | ||
uint8_t write_buff[TEST_BUFF_SIZE] = {0x0F, 0x0E, 0x0D, 0x0C, 0x0B, 0x0A, 0x09, 0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0x00}; | ||
uint8_t read_buff[TEST_BUFF_SIZE] = {0}; | ||
size_t actual_size; | ||
struct psa_storage_info_t info = {0, 0}; | ||
|
||
status = get_info_func(stype, 5, &info); | ||
|
@@ -132,8 +172,9 @@ void pits_ps_write_once_test() | |
TEST_ASSERT_EQUAL(TEST_BUFF_SIZE, info.size); | ||
TEST_ASSERT_EQUAL(PSA_STORAGE_FLAG_WRITE_ONCE, info.flags); | ||
|
||
status = get_func(stype, 5, 0, TEST_BUFF_SIZE, read_buff); | ||
status = get_func(stype, 5, 0, TEST_BUFF_SIZE, read_buff, &actual_size); | ||
TEST_ASSERT_EQUAL(PSA_SUCCESS, status); | ||
TEST_ASSERT_EQUAL(TEST_BUFF_SIZE, actual_size); | ||
TEST_ASSERT_EQUAL_MEMORY(write_buff, read_buff, TEST_BUFF_SIZE); | ||
|
||
status = set_func(stype, 5, TEST_BUFF_SIZE, write_buff, PSA_STORAGE_FLAG_WRITE_ONCE); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -33,8 +33,10 @@ extern "C" { | |
*/ | ||
typedef uint32_t psa_storage_create_flags_t; | ||
|
||
#define PSA_STORAGE_FLAG_NONE 0 /**< No flags to pass */ | ||
#define PSA_STORAGE_FLAG_WRITE_ONCE (1 << 0) /**< The data associated with the uid will not be able to be modified or deleted. Intended to be used to set bits in `psa_storage_create_flags_t`*/ | ||
#define PSA_STORAGE_FLAG_NONE 0 /**< No flags to pass */ | ||
#define PSA_STORAGE_FLAG_WRITE_ONCE (1 << 0) /**< The data associated with the uid will not be able to be modified or deleted. Intended to be used to set bits in `psa_storage_create_flags_t`*/ | ||
#define PSA_STORAGE_FLAG_NO_CONFIDENTIALITY (1 << 1) /**< The data associated with the uid is public and therefore does not require confidentiality. It therefore only needs to be integrity protected */ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. According to the spec linked, this should be There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please look at the up to date version of storage_common.h in master. It holds the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could we ask for a release tag in psa_trusted_storage_api? The latest release, Then, we can reference the fresh tag from this PR's commits so it's clear which version of the PSA Storage API Mbed OS implements. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Asked for it in there. |
||
#define PSA_STORAGE_FLAG_NO_REPLAY_PROTECTION (1 << 2) /**< The data associated with the uid does not require replay protection. This may permit faster storage - but it permits an attecker with physical access to revert to an earlier version of the data. */ | ||
|
||
/** \brief A type for UIDs used for identifying data | ||
*/ | ||
|
@@ -44,7 +46,8 @@ typedef uint64_t psa_storage_uid_t; | |
* \brief A container for metadata associated with a specific uid | ||
*/ | ||
struct psa_storage_info_t { | ||
uint32_t size; /**< The size of the data associated with a uid **/ | ||
size_t capacity; /**< The allocated capacity of the storage associated with a UID **/ | ||
size_t size; /**< The size of the data associated with a uid **/ | ||
psa_storage_create_flags_t flags; /**< The flags set when the uid was created **/ | ||
}; | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How do we know that the KvStore flags are correct in this test? Should we also do an assert to make sure
KVStore::REQUIRE_REPLAY_PROTECTION_FLAG
is clear inkv_info.flags
, to ensure our translation went properly?I understand the
get_info_func()
as currently implemented will do the translation from kv_info.flags to PSA flags, but if in the future thatget_info_func()
gets a bug or simply replies with previously saved PSA flag values, we would miss out on test coverage. From a defensive programming point of view, we should check directly what we want to know about what KvStore is doing; it'll be a more robust check.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, this test is a kind of a black box test, in the sense that we work with what the APIs provide us (PSA flags in that case). What you suggest here is a "white box" test, which tests the various impacts of the APIs on the internal components. Seems a bit excessive to me - this should IMO be part of the unit tests. Otherwise, one could argue that we shouldn't only check the KVStore flags, but also the key name, sizes etc.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you can think of a way to ensure that the correct flags took actual effect (for example, that storage is actually encrypted when needed, not encrypted when using internally-backed PS storage) with a black box test, that'd work too. I think whitebox would be easier, though, if you trust the underlying KvStore tests. As written now, the test is too easy to fool.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
KVStore tests (namely the SecureStore test) check that the actual encryption is performed on storage. But yet again, saying that the test is "easy to fool" can be applied on many other part of this test (for instance, checking that the stored key is what we expect, not only the flags) or other tests as well. My point here is that the KVStore flags shouldn't be "superior" in that sense.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"KVStore tests (namely the SecureStore test) check that the actual encryption is performed on storage" given that it uses the flag that tells it to do the encryption. However, we have no visibility from the PSA level that KvStore will use the right flags. We don't have any test that if PSA requests encryption, KvStore will use encryption. We have a test that PSA will be told that the encryption flag it asked for was in fact asked for and remembered, but no test that the KvStore will listen to that flag. We could get a better guarantee of this if we check the KvStore flags used for the storage backing the request from PSA.
We don't need to do integration testing to check that the storage is actually encrypted, but we do need at a minimum to unit test flag translation (
psa_ps_set()
.)Could we break out the flag translation logic from
psa_ps_set()
so that we can test PSA flags as input, and KvStore flags as output, ensuring that for all combinations of PSA input flags, we get the expected KvStore flags? The test would not ask KvStore for anything, nor would it be dependent on physical storage or any of its properties. We should do the same for the flag translation logic inpsa_ps_get_info()
, breaking it out into its own function so that we can test it in isolation from KvStore.Thanks!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, but I really have to disagree. What you are actually suggesting here is to expose the implementation of KVStore flags to the outside world. However, this is part of the internal implementation of
psa_ps_set
andpsa_ps_get_info
APIs. Exposing this implementation to the outside world gives us nothing other than the ability to test the implementation, which again is something that should be tested by unit tests. However, the real downside here is that if we decide at some point to change the implementation of these APIs (for instance, to abandon KVStore), we will have a much harder time to do that following the exposure of these flags, as it would lead to a breaking change. This is not far fetched: the initial implementation of the device key used NVStore, which was later replaced by KVStore.If you are worried about how the KVStore flags behave when calling these APIs, I can attach their print log following these actions. Don't think anything beyond this is required.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm suggesting to test the flag translation logic. This is not currently being done adequately. Exposing the KVStore flag translation functions to the outside world isn't required as only unit tests need to use it.
If KvStore is no longer used in the future, then the flag translation functions no longer need testing and the tests can be changed or removed. It's pretty common to update tests at the same time an implementation changes significantly.
The flag translation is quite tricky and deserves testing in isolation with a unit test. Crypto and many other services and applications depend on storage to provide the security guarantees they request from storage. The impact of a bug in this area is quite high, so please adequately test this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Translation logic is really not that complicated - two flags that use reverse logic in PSA Storage & KVStore. Translation of uid to KVStore key is much more complex, to give one example. Breaking the implementation to a function that exposes an internal implementation in a header file (even if only internally used by test code) seems like a total overshoot to me. That's what unit testing is for IMHO.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I'm only asking for a unit test of the flag translation logic. High risk of trouble if this translation code does something wrong, even if we don't think it's that complicated.
Requesting unit tests for flag translation logic shouldn't also require adding unit tests for uid to KVStore keys. Although you really should have unit tests for that as well, given that you've shipped that already, I'd say fixing it is out of scope of this PR. I would like to prevent more code from being added to Mbed OS that isn't sufficiently tested, however.
Where are the unit tests? Not part of Mbed OS? Someplace else?