DPE: Add boot data processing
Signed-off-by: Jamie Fox <jamie.fox@arm.com>
Change-Id: I505570a28ea0accf1bdcdaecb26580e558917ebb
diff --git a/partitions/dice_protection_environment/dpe_boot_data.c b/partitions/dice_protection_environment/dpe_boot_data.c
new file mode 100644
index 0000000..bdeaea9
--- /dev/null
+++ b/partitions/dice_protection_environment/dpe_boot_data.c
@@ -0,0 +1,373 @@
+/*
+ * Copyright (c) 2023, Arm Limited. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ */
+
+#include "dpe_boot_data.h"
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <string.h>
+
+#include "boot_hal.h"
+#include "boot_measurement.h"
+#include "dpe_context_mngr.h"
+#include "service_api.h"
+#include "tfm_boot_status.h"
+#include "tfm_plat_otp.h"
+
+/* Maximum measurement size is size of SHA-512 hash */
+#define MEASUREMENT_VALUE_MAX_SIZE 64
+
+/* 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_x 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;
+
+/**
+ * \brief Get the current DICE mode based on HW state.
+ *
+ * \return DICE mode.
+ */
+static DiceMode get_dice_mode(void)
+{
+ enum tfm_plat_err_t err;
+ enum plat_otp_lcs_t otp_lcs;
+
+ err = tfm_plat_otp_read(PLAT_OTP_ID_LCS, sizeof(otp_lcs),
+ (uint8_t *)&otp_lcs);
+ if (err != TFM_PLAT_ERR_SUCCESS) {
+ return kDiceModeNotInitialized;
+ }
+
+ /* FIXME consider DCU state as well */
+ switch (otp_lcs) {
+ case PLAT_OTP_LCS_SECURED:
+ return kDiceModeNormal;
+ case PLAT_OTP_LCS_DECOMMISSIONED:
+ return kDiceModeMaintenance;
+ default:
+ return kDiceModeNotInitialized;
+ }
+}
+
+/**
+ * \brief Convert boot measurement data to DICE input values.
+ *
+ * \param[in] metadata Boot measurement metadata.
+ * \param[in] measurement Boot measurement.
+ * \param[in] measurement_len Length of measurement input.
+ * \param[out] dice_inputs DICE input values.
+ *
+ * \return Returns 0 on success, -1 on failure.
+ */
+static int measurement_to_dice_inputs(const struct boot_measurement_metadata *metadata,
+ const uint8_t *measurement,
+ size_t measurement_len,
+ DiceInputValues *dice_inputs)
+{
+ uint8_t *cfg_p;
+
+ if (measurement_len > sizeof(dice_inputs->code_hash) ||
+ metadata->signer_id_size > sizeof(metadata->signer_id) ||
+ metadata->signer_id_size > sizeof(dice_inputs->authority_hash)) {
+ return -1;
+ }
+
+ /* Zero DICE inputs to ensure unused values are zero */
+ memset(dice_inputs, 0, sizeof(*dice_inputs));
+
+ /* Code hash */
+ memcpy(dice_inputs->code_hash, measurement, measurement_len);
+
+ /* Code descriptor */
+ dice_inputs->code_descriptor = (uint8_t *)&metadata->measurement_type;
+ dice_inputs->code_descriptor_size = sizeof(metadata->measurement_type);
+
+ /* Config value */
+ dice_inputs->config_type = kDiceConfigTypeInline;
+ cfg_p = dice_inputs->config_value;
+
+ memcpy(cfg_p, &metadata->sw_version.build_num,
+ sizeof(metadata->sw_version.build_num));
+ cfg_p += sizeof(metadata->sw_version.build_num);
+
+ memcpy(cfg_p, &metadata->sw_version.revision,
+ sizeof(metadata->sw_version.revision));
+ cfg_p += sizeof(metadata->sw_version.revision);
+
+ memcpy(cfg_p, &metadata->sw_version.minor,
+ sizeof(metadata->sw_version.minor));
+ cfg_p += sizeof(metadata->sw_version.minor);
+
+ memcpy(cfg_p, &metadata->sw_version.major,
+ sizeof(metadata->sw_version.major));
+
+ /* Authority hash */
+ memcpy(dice_inputs->authority_hash, metadata->signer_id,
+ metadata->signer_id_size);
+
+ /* Mode */
+ dice_inputs->mode = get_dice_mode();
+
+ return 0;
+}
+
+/**
+ * \brief Function pointer type that indicates whether slot meets condition.
+ */
+typedef bool (*slot_cond_t)(uint8_t slot);
+
+/**
+ * \brief Iteratively get measurements whose slot values meet the condition of
+ * the supplied condition function.
+ *
+ * \param[in] slot_cond Slot condition function.
+ * \param[in,out] itr Pointer to iterator. If the pointed-to value is
+ * NULL then the function searches from the
+ * beginning of the data area. The iterator value is
+ * updated by the function and should be supplied to
+ * subsequent calls to continue the search.
+ * \param[out] dice_inputs DICE input values.
+ *
+ * \return Returns integer error code.
+ * \retval -1 Failure.
+ * \retval 0 Success, reached end of data area without finding a component.
+ * \retval 1 Success, component found.
+ */
+static int get_measurement_for_slot_cond(slot_cond_t slot_cond,
+ void **itr,
+ DiceInputValues *dice_inputs)
+{
+ struct boot_measurement_metadata *metadata;
+ uint8_t *measurement;
+ size_t measurement_len;
+ struct shared_data_tlv_entry tlv_entry;
+ uint8_t *tlv_curr;
+ uint8_t *tlv_end;
+ uint8_t slot;
+ uint8_t claim;
+
+ if (boot_measurements.header.tlv_magic != SHARED_DATA_TLV_INFO_MAGIC) {
+ /* Boot measurement information is malformed. */
+ return -1;
+ }
+
+ /* Get the boundaries of TLV section where to lookup. */
+ tlv_curr = (*itr == NULL) ? boot_measurements.data : (uint8_t *)*itr;
+ 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. */
+ memcpy(&tlv_entry, tlv_curr, SHARED_DATA_ENTRY_HEADER_SIZE);
+ slot = GET_MBS_SLOT(tlv_entry.tlv_type);
+
+ if ((*slot_cond)(slot)) {
+ 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. */
+ return -1;
+ }
+
+ metadata = (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);
+ 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. */
+ return -1;
+ }
+
+ measurement = tlv_curr + SHARED_DATA_ENTRY_HEADER_SIZE;
+ measurement_len = tlv_entry.tlv_len;
+
+ if (measurement_to_dice_inputs(metadata, measurement,
+ measurement_len, dice_inputs) != 0) {
+ return -1;
+ }
+
+ /* Set iterator to point to next TLV entry */
+ *itr = tlv_curr + SHARED_DATA_ENTRY_HEADER_SIZE + tlv_entry.tlv_len;
+
+ return 1;
+ }
+
+ /* Move to the next TLV entry. */
+ tlv_curr += (SHARED_DATA_ENTRY_HEADER_SIZE + tlv_entry.tlv_len);
+ }
+
+ return 0;
+}
+
+dpe_error_t initialise_boot_data(void)
+{
+ psa_status_t status;
+
+ /* 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 DPE_INTERNAL_ERROR;
+ }
+
+ return DPE_NO_ERROR;
+}
+
+static bool bl1_2_cond(uint8_t slot)
+{
+ return slot == BOOT_MEASUREMENT_SLOT_BL1_2;
+}
+
+static bool bl2_cond(uint8_t slot)
+{
+ return slot == BOOT_MEASUREMENT_SLOT_BL2;
+}
+
+static bool plat_cond(uint8_t slot)
+{
+ return slot >= BOOT_MEASUREMENT_SLOT_RT_0 &&
+ slot <= BOOT_MEASUREMENT_SLOT_MAX &&
+ slot != BOOT_MEASUREMENT_SLOT_RT_2;
+}
+
+static bool ap_cond(uint8_t slot)
+{
+ return slot == BOOT_MEASUREMENT_SLOT_RT_2; /* FIXME: This may vary */
+}
+
+dpe_error_t derive_boot_data_contexts(int rot_ctx_handle,
+ int *child_ctx_handle)
+{
+ int ret;
+ dpe_error_t err;
+ void *itr;
+ DiceInputValues dice_inputs;
+ int plat_ctx_handle;
+ int invalid_ctx_handle;
+
+ /* Only the BL1_2 measurement is included in the RoT layer */
+ itr = NULL;
+ ret = get_measurement_for_slot_cond(&bl1_2_cond, &itr, &dice_inputs);
+ if (ret != 1) {
+ /* RoT layer measurement is either malformed or missing, fatal error */
+ return DPE_INTERNAL_ERROR;
+ }
+
+ /* Derive RoT layer */
+ err = derive_child_request(rot_ctx_handle,
+ false,
+ true,
+ true, /* create certificate */
+ &dice_inputs,
+ 0,
+ &plat_ctx_handle,
+ &invalid_ctx_handle);
+ if (err != DPE_NO_ERROR) {
+ return err;
+ }
+
+ /* Get BL2 measurement */
+ itr = NULL;
+ ret = get_measurement_for_slot_cond(&bl2_cond, &itr, &dice_inputs);
+ if (ret != 1) {
+ /* BL2 measurement is either malformed or missing, fatal error */
+ return DPE_INTERNAL_ERROR;
+ }
+
+ /* Derive BL2 context */
+ err = derive_child_request(plat_ctx_handle,
+ false, /* close parent context */
+ true, /* allow BL2 child to derive further */
+ false,
+ &dice_inputs,
+ 0,
+ &plat_ctx_handle,
+ &invalid_ctx_handle);
+ if (err != DPE_NO_ERROR) {
+ return err;
+ }
+
+ /* Get measurements for the rest of platform layer, except AP */
+ itr = NULL;
+ while ((ret = get_measurement_for_slot_cond(&plat_cond, &itr,
+ &dice_inputs)) == 1) {
+ /* Derive rest of platform contexts from retained BL2 context */
+ err = derive_child_request(plat_ctx_handle,
+ true, /* retain parent context */
+ false, /* do not allow child to derive */
+ false,
+ &dice_inputs,
+ 0,
+ &invalid_ctx_handle,
+ &plat_ctx_handle);
+ if (err != DPE_NO_ERROR) {
+ return err;
+ }
+ }
+
+ /* Check termination was due to reaching end of boot data area */
+ if (ret != 0) {
+ return DPE_INTERNAL_ERROR;
+ }
+
+ /* Get AP measurement */
+ itr = NULL;
+ ret = get_measurement_for_slot_cond(&ap_cond, &itr, &dice_inputs);
+ if (ret != 1) {
+ /* AP measurement is either malformed or missing, fatal error */
+ return DPE_INTERNAL_ERROR;
+ }
+
+ /* Derive AP context, with the new child context handle returned to the
+ * caller in the child_ctx_handle output parameter.
+ */
+ return derive_child_request(plat_ctx_handle,
+ false, /* close parent context */
+ true, /* allow AP to derive */
+ false,
+ &dice_inputs,
+ 0,
+ child_ctx_handle,
+ &invalid_ctx_handle);
+}