Skip to content

Commit

Permalink
tfm: Move TF-M attestation data to provisioned OTP region
Browse files Browse the repository at this point in the history
Optional fields to TF-M attestation were previously stored in
tfm_otp_nv_counters region, which we were not able to provision.
This moves the psa_certification_reference to the provisioned
OTP-region and adds support for accessing the variable data in
bl_storage.h.

Verification service URL and profile may change with device
upgrades, for this reason they are added as Kconfigs.

Note that we still need to keep the tfm_otp_nv_counters region
when TFM_PARTITION_PROTECTED_STORAGE and
TFM_PS_ROLLBACK_PROTECTION are enabled. TF-M will increase
monotonic counters every time new data is written and given the
limited size of our OTP-region it would not support many updates.

NCSDK-17932

Signed-off-by: Markus Lassila <[email protected]>
  • Loading branch information
MarkusLassila committed Nov 12, 2024
1 parent db0b80c commit ca635e2
Show file tree
Hide file tree
Showing 13 changed files with 406 additions and 103 deletions.
6 changes: 6 additions & 0 deletions cmake/sysbuild/provision_hex.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,10 @@ function(provision application prefix_name)
set(mcuboot_counters_slots --mcuboot-counters-slots ${SB_CONFIG_MCUBOOT_HW_DOWNGRADE_PREVENTION_COUNTER_SLOTS})
endif()

if(SB_CONFIG_TFM_OTP_PSA_CERTIFICATE_REFERENCE AND SB_CONFIG_TFM_PSA_CERTIFICATE_REFERENCE_VALUE)
set(psa_certificate_reference --psa-certificate-reference ${SB_CONFIG_TFM_PSA_CERTIFICATE_REFERENCE_VALUE})
endif()

if(CONFIG_SECURE_BOOT)
add_custom_command(
OUTPUT
Expand All @@ -103,6 +107,7 @@ function(provision application prefix_name)
${monotonic_counter_arg}
${no_verify_hashes_arg}
${mcuboot_counters_slots}
${psa_certificate_reference}
--lcs-state-size ${lcs_state_struct_size}
DEPENDS
${PROVISION_KEY_DEPENDS}
Expand All @@ -126,6 +131,7 @@ function(provision application prefix_name)
--max-size ${CONFIG_PM_PARTITION_SIZE_PROVISION}
${mcuboot_counters_num}
${mcuboot_counters_slots}
${psa_certificate_reference}
--lcs-state-size ${lcs_state_struct_size}
DEPENDS
${PROVISION_KEY_DEPENDS}
Expand Down
263 changes: 241 additions & 22 deletions include/bl_storage.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,12 @@ typedef uint32_t lcs_reserved_t;
/* We truncate the 32 byte sha256 down to 16 bytes before storing it */
#define SB_PUBLIC_KEY_HASH_LEN 16

/* Supported collection types. */
enum collection_type {
BL_COLLECTION_TYPE_MONOTONIC_COUNTERS = 1,
BL_COLLECTION_TYPE_VARIABLE_DATA = 0x9312,
};

/* Counter used by NSIB to check the firmware version */
#define BL_MONOTONIC_COUNTERS_DESC_NSIB 0x1

Expand Down Expand Up @@ -91,17 +97,45 @@ struct monotonic_counter {
counter_t counter_slots[1];
};

/** Common part for all collections. */
struct collection {
uint16_t type;
uint16_t count;
};

/** The second data structure in the provision page. It has unknown length since
* 'counters' is repeated. Note that each entry in counters also has unknown
* length, and each entry can have different length from the others, so the
* entries beyond the first cannot be accessed via array indices.
*/
struct counter_collection {
uint16_t type; /* Must be "monotonic counter". */
uint16_t num_counters; /* Number of entries in 'counters' list. */
struct collection collection; /* Type must be BL_COLLECTION_TYPE_MONOTONIC_COUNTERS */
struct monotonic_counter counters[1];
};

/* Variable data types. */
enum variable_data_type {
BL_VARIABLE_DATA_TYPE_PSA_CERTIFICATION_REFERENCE = 0x1
};
struct variable_data {
uint8_t type;
uint8_t length;
uint8_t data[1];
};

/* The third data structure in the provision page. It has unknown length since
* 'variable_data' is repeated. The collection starts immediately after the
* counter collection. As the counter collection has unknown length, the start
* of the variable data collection must be calculated dynamically. Similarly,
* the entries in the variable data collection have unknown length, so they
* cannot be accessed via array indices.
*/
struct variable_data_collection {
struct collection collection; /* Type must be BL_COLLECTION_TYPE_VARIABLE_DATA */
struct variable_data variable_data[1];
};

NRFX_STATIC_INLINE uint16_t bl_storage_otp_halfword_read(uint32_t address);
NRFX_STATIC_INLINE uint32_t bl_storage_word_read(uint32_t address);
NRFX_STATIC_INLINE uint32_t bl_storage_word_write(uint32_t address, uint32_t value);
NRFX_STATIC_INLINE counter_t bl_storage_counter_get(uint32_t address);
Expand All @@ -112,7 +146,7 @@ int get_counter(uint16_t counter_desc, counter_t *counter_value, const counter_t

/** The first data structure in the bootloader storage. It has unknown length
* since 'key_data' is repeated. This data structure is immediately followed by
* struct counter_collection.
* struct counter_collection, which is then followed by struct variable_data_collection.
*/
struct bl_storage_data {
/* NB: When placed in OTP, reads must be 4 bytes and 4 byte aligned */
Expand All @@ -125,6 +159,27 @@ struct bl_storage_data {
uint32_t valid;
uint8_t hash[SB_PUBLIC_KEY_HASH_LEN];
} key_data[1];

/* Monotonic counters:
* uint16_t type;
* uint16_t count;
* struct {
* uint16_t description;
* uint16_t num_counter_slots;
* uint16_t counter_slots[1];
* } counters[1];
*/

/* Variable data:
* uint16_t type;
* uint16_t count;
* struct {
* uint8_t type;
* uint8_t length;
* uint8_t data[1];
* } variable_data[1];
* uint8_t padding[1]; // Padding to align to 4 bytes
*/
};

#define BL_STORAGE ((const volatile struct bl_storage_data *)(PM_PROVISION_ADDRESS))
Expand All @@ -151,14 +206,7 @@ uint32_t s0_address_read(void);
uint32_t s1_address_read(void);

/**
* @brief Function for reading number of public key data slots.
*
* @return Number of public key data slots.
*/
uint32_t num_public_keys_read(void);

/**
* @brief Function for reading number of public key data slots.
* @brief Function for verifying public keys.
*
* @retval 0 if all keys are ok.
* @retval -EHASHFF if one or more keys contains an aligned 0xFFFF.
Expand Down Expand Up @@ -227,7 +275,39 @@ int get_monotonic_counter(uint16_t counter_desc, counter_t *counter_value);
* @retval -ENOMEM There are no more free counter slots (see
* @kconfig{CONFIG_SB_NUM_VER_COUNTER_SLOTS}).
*/
int set_monotonic_counter(uint16_t counter_desc, counter_t new_counter);
int set_monotonic_counter(uint16_t counter_desc, uint16_t new_counter);

/**
* @brief Function for reading number of public key data slots.
*
* @return Number of public key data slots.
*/
NRFX_STATIC_INLINE uint32_t num_public_keys_read(void)
{
return bl_storage_word_read((uint32_t)&BL_STORAGE->num_public_keys);
}

/**
* @brief Get the first collection after the public keys.
*
* @return Pointer to the first collection.
*/
NRFX_STATIC_INLINE const struct collection *get_first_collection(void)
{
return (const struct collection *)&BL_STORAGE->key_data[num_public_keys_read()];
}

/**
* @brief Get the collection type.
*
* @param[in] collection Pointer to the collection.
*
* @return Collection type.
*/
NRFX_STATIC_INLINE uint16_t get_collection_type(const struct collection *collection)
{
return bl_storage_otp_halfword_read((uint32_t)&collection->type);
}

/**
* @brief The PSA life cycle states a device can be in.
Expand All @@ -247,21 +327,41 @@ enum lcs {
* time. Writes to @p dst are done a byte at a time.
*
* @param[out] dst destination buffer.
* @param[in] src source buffer in OTP. Must be 4-byte-aligned.
* @param[in] size number of *bytes* in src to copy into dst. Must be divisible by 4.
* @param[in] src source buffer in OTP.
* @param[in] size number of *bytes* in src to copy into dst.
*/
NRFX_STATIC_INLINE void otp_copy32(uint8_t *restrict dst, uint32_t volatile * restrict src,
size_t size)
NRFX_STATIC_INLINE void otp_copy(uint8_t *restrict dst, uint8_t volatile * restrict src,
size_t size)
{
for (int i = 0; i < size / 4; i++) {
/* OTP is in UICR */
uint32_t val = bl_storage_word_read((uint32_t)(src + i));
size_t copied = 0; /* Bytes copied. */
uint8_t src_offset = (uint32_t)src % 4; /* Align the source address to 32-bits. */

uint32_t val; /* 32-bit value read from the OTP. */
uint8_t copy_size; /* Number of bytes to copy. */

while (copied < size) {

for (int j = 0; j < 4; j++) {
dst[i * 4 + j] = (val >> 8 * j) & 0xFF;
/* Read 32-bits. */
val = bl_storage_word_read((uint32_t)(src + copied - src_offset));

/* Calculate the size to copy. */
copy_size = sizeof(val) - src_offset;
if (size - copied < copy_size) {
copy_size = size - copied;
}

/* Copy the data one byte at a time. */
for (int i = 0; i < copy_size; i++) {
*dst++ = (val >> (8 * (i + src_offset))) & 0xFF;
}

/* Source address is aligned to 32-bits after the first iteration. */
src_offset = 0;

copied += copy_size;
}
}

/**
* Read the implementation id from OTP and copy it into a given buffer.
*
Expand All @@ -273,7 +373,7 @@ NRFX_STATIC_INLINE void read_implementation_id_from_otp(uint8_t *buf)
return;
}

otp_copy32(buf, (uint32_t *)&BL_STORAGE->implementation_id,
otp_copy(buf, (uint8_t *)&BL_STORAGE->implementation_id,
BL_STORAGE_IMPLEMENTATION_ID_SIZE);
}

Expand Down Expand Up @@ -467,6 +567,125 @@ NRFX_STATIC_INLINE int update_life_cycle_state(enum lcs next_lcs)
return -EINVALIDLCS;
}

#if CONFIG_BUILD_WITH_TFM

static uint32_t get_monotonic_counter_collection_size(const struct counter_collection *collection)
{
/* Add only the constant part of the counter_collection. */
uint32_t size = sizeof(struct collection);
uint16_t num_counters =
bl_storage_otp_halfword_read((uint32_t)&collection->collection.count);
const struct monotonic_counter *counter = collection->counters;

for (int i = 0; i < num_counters; i++) {
/* Add only the constant part of the monotonic_counter. */
size += sizeof(struct monotonic_counter) - sizeof(counter_t);

uint16_t num_slots =
bl_storage_otp_halfword_read((uint32_t)&counter->num_counter_slots);
size += (num_slots * sizeof(counter_t));

/* Move to the next monotonic counter. */
counter = (const struct monotonic_counter *)&counter->counter_slots[num_slots];
}

return size;
}

static const struct variable_data_collection *get_variable_data_collection(void)
{
/* We expect to find variable data after the monotonic counters. */
const struct collection *collection = get_first_collection();

if (get_collection_type(collection) == BL_COLLECTION_TYPE_MONOTONIC_COUNTERS) {

/* Advance to next collection. */
collection = (const struct collection *)((uint8_t *)collection +
get_monotonic_counter_collection_size(
(const struct counter_collection *)
collection));

/* Verify that we found variable collection. */
return get_collection_type(collection) == BL_COLLECTION_TYPE_VARIABLE_DATA
? (const struct variable_data_collection *)collection
: NULL;

} else if (get_collection_type(collection) == BL_COLLECTION_TYPE_VARIABLE_DATA) {
/* Bit of a special scenario where monotonic counters are not present. */
return (const struct variable_data_collection *)collection;
}

return NULL;
}

/**
* @brief Read variable data from OTP.
*
* Variable data starts with variable data collection ID, followed by amount of variable data
* entries and the variable data entries themselves.
* [Collection ID][Variable count][Type][Variable data length][Variable data][Type]...
* 2 bytes 2 bytes 1 byte 1 byte 0-255 bytes
*
* @note If data is not found, function does not fail. Instead, 0 length is returned.
*
* @param[in] data_type Type of the variable data to read.
* @param[out] buf Buffer to store the variable data.
* @param[in,out] buf_len On input, the size of the buffer. On output, the length of the data.
*
* @retval 0 Variable data read successfully, or not found.
* @retval -EINVAL No buffer provided.
* @retval -ENOMEM Buffer too small.
*/
int read_variable_data(enum variable_data_type data_type, uint8_t *buf, uint32_t *buf_len)
{
if (buf == NULL) {
return -EINVAL;
}

const struct variable_data_collection *collection = get_variable_data_collection();

if (collection == NULL) {
/* Variable data collection does not necessarily exist. Exit gracefully. */
*buf_len = 0;
return 0;
}

const struct variable_data *variable_data = collection->variable_data;
const uint16_t count =
bl_storage_otp_halfword_read((uint32_t)&collection->collection.count);
uint8_t type;
uint8_t length;

/* Loop through all variable data entries. */
for (int i = 0; i < count; i++) {

/* Read the type and length of the variable data. */
otp_copy((uint8_t *)&type, (uint8_t *)&variable_data->type, sizeof(type));
otp_copy((uint8_t *)&length, (uint8_t *)&variable_data->length, sizeof(length));

if (type == data_type) {
/* Found the requested variable data. */
if (*buf_len < length) {
return -ENOMEM;
}

/* Copy the variable data into the buffer. */
otp_copy(buf, (uint8_t *)&variable_data->data, length);
*buf_len = length;
return 0;
}
/* Move to the next variable data entry. */
variable_data =
(const struct variable_data *)((uint8_t *)&variable_data->data + length);
}

/* No matching variable data. */
*buf_len = 0;

return 0;
}
#endif

/** @} */

#ifdef __cplusplus
Expand Down
Loading

0 comments on commit ca635e2

Please sign in to comment.