blob: 6f4daf9f3303ee4e36b6025ebe076cddf9cfbe85 [file] [log] [blame]
/*
* SPDX-FileCopyrightText: Copyright The TrustedFirmware-M Contributors
*
* SPDX-License-Identifier: BSD-3-Clause
*
*/
#include "measured_boot.h"
#include "measured_boot_utils.h"
#include "measured_boot_api.h"
#include "psa/crypto.h"
#include "tfm_boot_status.h"
#include "boot_hal.h"
#include "service_api.h"
#include "tfm_strnlen.h"
#include "tfm_log_unpriv.h"
#include <stdint.h>
#include <string.h>
#include <stdbool.h>
#define TEMP_BUFFER_SIZE (MEASUREMENT_VALUE_SIZE + MEASUREMENT_VALUE_MAX_SIZE)
#ifdef CONFIG_TFM_BOOT_STORE_MEASUREMENTS
/* Size of 1 complete measurement (value + metadata) in TLV format. */
#define SHARED_BOOT_MEASUREMENT_SIZE \
((2 * SHARED_DATA_ENTRY_HEADER_SIZE) \
+ sizeof(struct boot_measurement_metadata) \
+ MEASUREMENT_VALUE_MAX_SIZE)
/* 2 measurements from the BL1 stages and 1 measurement per image from BL2. */
#define MAX_SHARED_BOOT_DATA_LENGTH ((2 + MCUBOOT_IMAGE_NUMBER) \
* SHARED_BOOT_MEASUREMENT_SIZE)
/*!
* \struct boot_measurement_data
*
* \brief Contains all the measurement and related metadata (from BL1 and BL2).
*
* \details This is a redefinition of \ref tfm_boot_data to allocate the
* appropriate, service dependent size of \ref boot_data.
*/
struct boot_measurement_data {
struct shared_data_tlv_header header;
uint8_t data[MAX_SHARED_BOOT_DATA_LENGTH];
};
/*!
* \var boot_measurements
*
* \brief Store the boot measurements in the service's memory.
*
* \details Boot measurements come from the BL1 and BL2 boot stages and stored
* on a memory area which is shared between the bootloaders and SPM.
* SPM provides the \ref tfm_core_get_boot_data() API to retrieve
* the service related data from shared area.
*/
__attribute__ ((aligned(4)))
static struct boot_measurement_data boot_measurements;
#endif /* CONFIG_TFM_BOOT_STORE_MEASUREMENTS */
struct measurement_metadata_t {
uint8_t signer_id[SIGNER_ID_MAX_SIZE];
size_t signer_id_size;
uint8_t version[VERSION_MAX_SIZE];
uint8_t version_size;
uint32_t measurement_algo;
uint8_t sw_type[SW_TYPE_MAX_SIZE];
uint8_t sw_type_size;
};
struct measurement_value_t {
uint8_t hash_buf[MEASUREMENT_VALUE_MAX_SIZE];
uint8_t hash_buf_size;
};
struct measurement_t {
struct measurement_value_t value; /* measurement value */
struct measurement_metadata_t metadata; /* metadata */
};
struct measured_boot_slot_t {
bool is_locked;
bool is_populated;
bool is_common;
struct measurement_t measurement;
};
static struct measured_boot_slot_t measurement_slot[NUM_OF_MEASUREMENT_SLOTS];
static bool is_signer_id_different(uint8_t index, const uint8_t *signer_id)
{
uint8_t *stored_signer_id =
&measurement_slot[index].measurement.metadata.signer_id[0];
uint8_t stored_signer_id_size =
measurement_slot[index].measurement.metadata.signer_id_size;
if (memcmp(signer_id, stored_signer_id, stored_signer_id_size) != 0) {
return true;
}
return false;
}
/* TODO: Access control strategy to be updated */
static bool is_slot_access_prohibited(uint8_t slot_index,
const uint8_t *signer_id,
uint32_t measurement_algo)
{
if (measurement_algo != measurement_slot[slot_index].measurement.metadata.measurement_algo) {
/* The client hash algorithm is different from the current slot hash algorithm */
return true;
}
if (is_signer_id_different(slot_index, signer_id)) {
/* The client signer id is different from the current slot signer id */
if (!measurement_slot[slot_index].is_common) {
/* This slot does NOT hold common measurements */
return true;
}
}
/* The client signer id and hash algo is same as the current slot signer id
* and hash algo respectively; hence it must be allowed full access
*/
return false;
}
/* TODO: Implement updates for access control strategy here. */
static bool is_read_access_prohibited(uint8_t slot_index)
{
return false;
}
static inline bool is_measurement_slot_populated(const uint8_t index)
{
/* Extension is required if any previous measurement value already exists
* in this slot
*/
return (measurement_slot[index].is_populated);
}
psa_status_t measured_boot_read_measurement(uint8_t index,
uint8_t *signer_id,
size_t signer_id_size,
size_t *signer_id_len,
uint8_t *version,
size_t version_size,
uint8_t *version_len,
uint32_t *measurement_algo,
uint8_t *sw_type,
size_t sw_type_size,
uint8_t *sw_type_len,
uint8_t *measurement_value,
size_t measurement_value_size,
size_t *measurement_value_len,
uint8_t *is_locked)
{
struct measurement_t *src_measurement =
&measurement_slot[index].measurement;
if (is_read_access_prohibited(index)) {
return PSA_ERROR_NOT_PERMITTED;
}
if (is_measurement_slot_populated(index)) {
if ((version_size < src_measurement->metadata.version_size) ||
(sw_type_size < src_measurement->metadata.sw_type_size) ||
(signer_id_size < src_measurement->metadata.signer_id_size) ||
(measurement_value_size < src_measurement->value.hash_buf_size)) {
/* The size of one of the arguments is incorrect */
return PSA_ERROR_INVALID_ARGUMENT;
}
*signer_id_len = src_measurement->metadata.signer_id_size;
memcpy(signer_id, src_measurement->metadata.signer_id, *signer_id_len);
*version_len = src_measurement->metadata.version_size;
memcpy(version, src_measurement->metadata.version, *version_len);
*sw_type_len = src_measurement->metadata.sw_type_size;
memcpy(sw_type, src_measurement->metadata.sw_type, *sw_type_len);
*measurement_algo = src_measurement->metadata.measurement_algo;
*measurement_value_len = src_measurement->value.hash_buf_size;
memcpy(measurement_value, src_measurement->value.hash_buf,
*measurement_value_len);
*is_locked = measurement_slot[index].is_locked;
} else {
/* Measurement slot is not populated. */
return PSA_ERROR_DOES_NOT_EXIST;
}
return PSA_SUCCESS;
}
static void update_metadata(const uint8_t index,
const uint8_t *signer_id,
const size_t signer_id_size,
const uint8_t *version,
const size_t version_size,
const uint32_t measurement_algo,
const uint8_t *sw_type,
const size_t sw_type_size)
{
struct measurement_metadata_t *dest_metadata =
&measurement_slot[index].measurement.metadata;
/* Copy metadata for corresponding measurement slot */
dest_metadata->signer_id_size = signer_id_size;
dest_metadata->version_size = version_size;
dest_metadata->sw_type_size = sw_type_size;
memcpy(dest_metadata->signer_id, signer_id, signer_id_size);
memcpy(dest_metadata->version, version, version_size);
dest_metadata->measurement_algo = measurement_algo;
memcpy(dest_metadata->sw_type, sw_type, sw_type_size);
}
static void extend_metadata(const uint8_t index)
{
struct measurement_metadata_t *dest_metadata =
&measurement_slot[index].measurement.metadata;
/* Do not update Signer ID as it should be the same */
/* Do not update Measurement algo as it should be the same */
/* Clear Version info and Software component description */
dest_metadata->version_size = 0;
dest_metadata->sw_type_size = 0;
(void)memset(dest_metadata->version, 0, VERSION_MAX_SIZE);
(void)memset(dest_metadata->sw_type, 0, SW_TYPE_MAX_SIZE);
}
static void store_measurement_value(const uint8_t index,
const uint8_t *src_value,
const size_t measurement_size)
{
memcpy(&measurement_slot[index].measurement.value.hash_buf[0], src_value,
measurement_size);
measurement_slot[index].measurement.value.hash_buf_size = measurement_size;
}
static inline void lock_measurement_slot(const uint8_t index)
{
measurement_slot[index].is_locked = true;
}
static inline void get_stored_measurement_value(const uint8_t index,
uint8_t *output_buffer,
const size_t measurement_size)
{
memcpy(output_buffer,
&measurement_slot[index].measurement.value.hash_buf[0],
MEASUREMENT_VALUE_SIZE);
}
static psa_status_t extend_measurement_value(const uint8_t index,
const uint8_t *measurement,
const size_t measurement_size,
uint8_t *hash_result,
size_t *hash_len)
{
uint8_t temp_buffer[TEMP_BUFFER_SIZE];
size_t total_size;
/* Read previous measurement */
get_stored_measurement_value(index, temp_buffer, MEASUREMENT_VALUE_SIZE);
/* concatenate new measurement */
memcpy(&temp_buffer[MEASUREMENT_VALUE_SIZE], measurement, measurement_size);
total_size = measurement_size + MEASUREMENT_VALUE_SIZE;
/* Perform hash calculation */
return psa_hash_compute(TFM_MEASURED_BOOT_HASH_ALG, temp_buffer,
total_size, hash_result, MEASUREMENT_VALUE_SIZE,
hash_len);
}
static inline bool is_measurement_slot_locked(const uint8_t index)
{
return measurement_slot[index].is_locked;
}
static inline void mark_slot_as_occupied(const uint8_t index)
{
measurement_slot[index].is_populated = true;
}
/* This API is used to extend and store measurement and the corresponding
* metadata / boot-record (if provided).
*/
psa_status_t measured_boot_extend_measurement(uint8_t index,
const uint8_t *signer_id,
size_t signer_id_size,
const uint8_t *version,
uint8_t version_size,
uint32_t measurement_algo,
const uint8_t *sw_type,
uint8_t sw_type_size,
const uint8_t *measurement_value,
size_t measurement_value_size,
uint8_t lock_measurement)
{
psa_status_t status = PSA_SUCCESS;
uint8_t hash_result[MEASUREMENT_VALUE_SIZE] = {0};
size_t hash_len;
log_extend_measurement(index,
signer_id, signer_id_size,
version, version_size,
measurement_algo,
sw_type, sw_type_size,
measurement_value, measurement_value_size,
lock_measurement);
if (is_measurement_slot_locked(index)) {
/* Cannot write to measurement slot once locked */
status = PSA_ERROR_BAD_STATE;
goto error;
}
/* Check how metadata needs updating for the requested slot */
if (is_measurement_slot_populated(index)) {
if (is_slot_access_prohibited(index, signer_id, measurement_algo)) {
status = PSA_ERROR_NOT_PERMITTED;
goto error;
}
/* Extend metadata */
extend_metadata(index);
} else {
/* Store the corresponding metadata */
update_metadata(index,
signer_id, signer_id_size,
version, version_size,
measurement_algo,
sw_type, sw_type_size);
/* Indicate that the slot is not empty anymore */
mark_slot_as_occupied(index);
}
/* Extend current measurement with new measured value */
status = extend_measurement_value(index,
measurement_value,
measurement_value_size,
hash_result, &hash_len);
if (status != PSA_SUCCESS) {
goto error;
}
/* Store calculated extended value */
store_measurement_value(index, hash_result, hash_len);
if (lock_measurement) {
/* lock measurement slot if requested */
lock_measurement_slot(index);
}
error:
if (status != PSA_SUCCESS) {
VERBOSE_UNPRIV_RAW("Measured Boot : measurement extension failed.\n");
} else {
VERBOSE_UNPRIV_RAW("Measured Boot : measurement extended successfully.\n");
}
return status;
}
void initialise_all_measurements(void)
{
uint32_t i;
for (i = 0; i < NUM_OF_MEASUREMENT_SLOTS; i++) {
measurement_slot[i].is_locked = false;
measurement_slot[i].is_populated = false;
/* By default, mark all slots as "not common" to avoid accidental extend
* and write by a different signer id
*/
measurement_slot[i].is_common = false;
/* Clear all metadata for corresponding measurement slot */
(void)memset(&measurement_slot[i].measurement.metadata, 0,
sizeof(struct measurement_metadata_t));
/* Initialise measurement values to default pattern */
(void)memset(&measurement_slot[i].measurement.value.hash_buf[0],
MEASUREMENT_VALUE_INIT_PATTERN,
sizeof(measurement_slot[i].measurement.value.hash_buf));
}
}
#ifdef CONFIG_TFM_BOOT_STORE_MEASUREMENTS
/**
* \brief Format an unsigned integer value into the pointed-to string.
*
* \param[out] str Pointer to string
* \param[in] end Pointer to end of string
* \param[in] val Unsigned integer value
*
* \return Number of characters written (or would be written).
*/
static size_t format_uint(uint8_t **str, const uint8_t *const end, uint32_t val)
{
size_t num = 0;
do {
if (*str < end) {
*(*str)++ = '0' + val % 10;
}
val /= 10;
num++;
} while (val > 0);
return num;
}
/**
* \brief Format the version into a string "major.minor.revision+build".
*
* \param[out] str Version string
* \param[in] size Size of version string
* \param[in] major Major version
* \param[in] minor Minor version
* \param[in] revision Revison version
* \param[in] build Build number
*
* \return Number of characters written (or would be written, if size is too
* small), excluding the NULL terminator.
*/
static size_t format_version_string(uint8_t *str, size_t size,
uint8_t major, uint8_t minor,
uint16_t revision, uint32_t build)
{
const uint8_t *const end = str + size;
size_t num = 0;
num += format_uint(&str, end, major);
if (str < end) {
*str++ = '.';
}
num++;
num += format_uint(&str, end, minor);
if (str < end) {
*str++ = '.';
}
num++;
num += format_uint(&str, end, revision);
if (str < end) {
*str++ = '+';
}
num++;
num += format_uint(&str, end, build);
if (str < end) {
*str = '\0';
} else if (size > 0) {
str[size - 1] = '\0';
}
return num;
}
psa_status_t collect_shared_measurements(void)
{
struct shared_data_tlv_entry tlv_entry;
struct boot_measurement_metadata *metadata_ptr;
uint8_t *tlv_end;
uint8_t *tlv_curr;
uint8_t claim;
psa_status_t status = PSA_ERROR_GENERIC_ERROR;
uint8_t version[VERSION_MAX_SIZE];
size_t version_size;
/* Collect the measurements from the shared data area and store them. */
status = tfm_core_get_boot_data(TLV_MAJOR_MBS,
(struct tfm_boot_data *)&boot_measurements,
sizeof(boot_measurements));
if (status != PSA_SUCCESS) {
return PSA_ERROR_GENERIC_ERROR;
}
if (boot_measurements.header.tlv_magic != SHARED_DATA_TLV_INFO_MAGIC) {
/* Boot measurement information is malformed. */
return PSA_ERROR_GENERIC_ERROR;
}
/* Get the boundaries of TLV section where to lookup. */
tlv_curr = boot_measurements.data;
tlv_end = (uint8_t *)&boot_measurements
+ boot_measurements.header.tlv_tot_len;
while (tlv_curr < tlv_end) {
/* Copy TLV entry header - the measurement metadata must come first. */
(void)memcpy(&tlv_entry, tlv_curr, SHARED_DATA_ENTRY_HEADER_SIZE);
if ((GET_MBS_CLAIM(tlv_entry.tlv_type) != SW_MEASURE_METADATA) ||
(tlv_entry.tlv_len != sizeof(struct boot_measurement_metadata))) {
/* Boot measurement information is malformed. */
status = PSA_ERROR_GENERIC_ERROR;
break;
}
metadata_ptr = (struct boot_measurement_metadata *)
(tlv_curr + SHARED_DATA_ENTRY_HEADER_SIZE);
/* Copy next TLV entry header - it must belong to the measurement. */
tlv_curr += (SHARED_DATA_ENTRY_HEADER_SIZE + tlv_entry.tlv_len);
(void)memcpy(&tlv_entry, tlv_curr, SHARED_DATA_ENTRY_HEADER_SIZE);
claim = GET_MBS_CLAIM(tlv_entry.tlv_type);
if ((claim != SW_MEASURE_VALUE) &&
(claim != SW_MEASURE_VALUE_NON_EXTENDABLE)) {
/* Boot measurement information is malformed. */
status = PSA_ERROR_GENERIC_ERROR;
break;
} else {
/* Validate size limits of metadata items (if applicable) and
* the size limits of measurement value before storing it.
*/
if ((metadata_ptr->signer_id_size < SIGNER_ID_MIN_SIZE) ||
(metadata_ptr->signer_id_size > SIGNER_ID_MAX_SIZE) ||
(tlv_entry.tlv_len < MEASUREMENT_VALUE_MIN_SIZE) ||
(tlv_entry.tlv_len > MEASUREMENT_VALUE_MAX_SIZE)) {
status = PSA_ERROR_GENERIC_ERROR;
break;
}
version_size = format_version_string(version, sizeof(version),
metadata_ptr->sw_version.major,
metadata_ptr->sw_version.minor,
metadata_ptr->sw_version.revision,
metadata_ptr->sw_version.build_num);
if (version_size >= sizeof(version)) {
status = PSA_ERROR_GENERIC_ERROR;
break;
}
/* Store the measurement and associated metadata. */
status = measured_boot_extend_measurement(
(uint8_t)GET_MBS_SLOT(tlv_entry.tlv_type),
metadata_ptr->signer_id,
metadata_ptr->signer_id_size,
version,
version_size,
metadata_ptr->measurement_type,
(const uint8_t*)metadata_ptr->sw_type,
tfm_strnlen(metadata_ptr->sw_type,
sizeof(metadata_ptr->sw_type)),
tlv_curr + SHARED_DATA_ENTRY_HEADER_SIZE,
tlv_entry.tlv_len,
(claim == SW_MEASURE_VALUE_NON_EXTENDABLE) ? true : false);
if (status != PSA_SUCCESS) {
/* Failed to store measurement. */
break;
}
}
/* Move to the next TLV entry. */
tlv_curr += (SHARED_DATA_ENTRY_HEADER_SIZE + tlv_entry.tlv_len);
}
return status;
}
#endif /* CONFIG_TFM_BOOT_STORE_MEASUREMENTS */