blob: aeedef92b5e225e9b9550b87178b6f7796a1a6e3 [file] [log] [blame]
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
#include "sw/device/silicon_creator/lib/boot_data.h"
#include <stddef.h>
#include <stdint.h>
#include "sw/device/lib/base/hardened.h"
#include "sw/device/lib/base/memory.h"
#include "sw/device/silicon_creator/lib/base/sec_mmio.h"
#include "sw/device/silicon_creator/lib/drivers/flash_ctrl.h"
#include "sw/device/silicon_creator/lib/drivers/hmac.h"
#include "sw/device/silicon_creator/lib/drivers/otp.h"
#include "sw/device/silicon_creator/lib/error.h"
#include "flash_ctrl_regs.h"
#include "hw/top_earlgrey/sw/autogen/top_earlgrey.h"
#include "otp_ctrl_regs.h"
static_assert(kBootDataEntriesPerPage ==
FLASH_CTRL_PARAM_BYTES_PER_PAGE / sizeof(boot_data_t),
"Number of boot data entries per page is incorrect");
static_assert(sizeof(boot_data_t) % FLASH_CTRL_PARAM_BYTES_PER_WORD == 0,
"Size of `boot_data_t` must be a multiple of flash word size.");
static_assert(!(FLASH_CTRL_PARAM_BYTES_PER_PAGE &
(FLASH_CTRL_PARAM_BYTES_PER_PAGE - 1)),
"Size of a flash page must be a power of two.");
static_assert(!(sizeof(boot_data_t) & (sizeof(boot_data_t) - 1)),
"Size of `boot_data_t` must be a power of two.");
OT_ASSERT_MEMBER_SIZE(boot_data_t, is_valid, FLASH_CTRL_PARAM_BYTES_PER_WORD);
static_assert(offsetof(boot_data_t, is_valid) %
FLASH_CTRL_PARAM_BYTES_PER_WORD ==
0,
"`is_valid` must be flash word aligned.");
/**
* Boot data flash info pages.
*/
static const flash_ctrl_info_page_t kPages[2] = {
kFlashCtrlInfoPageBootData0,
kFlashCtrlInfoPageBootData1,
};
/**
* Computes the SHA-256 digest of a boot data entry.
*
* The region covered by this digest starts immediately after the `identifier`
* field and ends at the end of the entry.
*
* @param boot_data A boot data entry.
* @param[out] digest Digest of the boot data entry.
* @return The result of the operation.
*/
static void boot_data_digest_compute(const void *boot_data,
hmac_digest_t *digest) {
enum {
kDigestRegionOffset = sizeof((boot_data_t){0}.digest),
kDigestRegionSize = sizeof(boot_data_t) - kDigestRegionOffset,
};
static_assert(offsetof(boot_data_t, digest) == 0,
"`digest` must be the first field of `boot_data_t`.");
hmac_sha256_init();
hmac_sha256_update((const char *)boot_data + kDigestRegionOffset,
kDigestRegionSize);
hmac_sha256_final(digest);
}
/**
* Checks whether a boot data entry is empty.
*
* @param boot_data A buffer that holds a boot data entry. Must be word aligned.
* @return Whether the entry is empty.
*/
static hardened_bool_t boot_data_is_empty(const void *boot_data) {
static_assert(kFlashCtrlErasedWord == UINT32_MAX,
"kFlashCtrlErasedWord must be UINT32_MAX");
size_t i = 0;
hardened_bool_t is_empty = kHardenedBoolTrue;
uint32_t res = kFlashCtrlErasedWord;
for (; launder32(i) < kBootDataNumWords; ++i) {
res &= read_32(boot_data);
is_empty &= read_32(boot_data);
boot_data = (char *)boot_data + sizeof(uint32_t);
}
HARDENED_CHECK_EQ(i, kBootDataNumWords);
if (launder32(res) == kFlashCtrlErasedWord) {
HARDENED_CHECK_EQ(res, kFlashCtrlErasedWord);
return is_empty;
}
return kHardenedBoolFalse;
}
/**
* Returns the `identifier` of the boot data entry at the given page and
* index after masking it with the words of its `is_valid` field.
*
* This function can be used to quickly determine if an entry can be empty or
* valid. Due to the values chosen for valid and invalid entries,
* `masked_identifier` will be `kFlashCtrlErasedWord` for entries that can be
* empty, `kBootDataIdentifier` for entries that are not invalidated, and `0`
* for invalidated entries.
*
* @param page A boot data page.
* @param index Index of the entry to read in the given page.
* @param[out] masked_identifier Identifier masked with the words of `is_valid`.
* @return The result of the operation.
*/
static rom_error_t boot_data_sniff(flash_ctrl_info_page_t page, size_t index,
uint32_t *masked_identifier) {
static_assert(kBootDataValidEntry == UINT64_MAX,
"is_valid must be UINT64_MAX for valid entries.");
static_assert(kBootDataInvalidEntry == 0,
"is_valid must be 0 for invalid entries.");
enum {
kIsValidOffset = offsetof(boot_data_t, is_valid),
kIdentifierOffset = offsetof(boot_data_t, identifier),
};
static_assert(
kIdentifierOffset - kIsValidOffset == sizeof((boot_data_t){0}.is_valid),
"is_valid and identifier must be consecutive.");
*masked_identifier = 0;
uint32_t buf[3];
const uint32_t offset = index * sizeof(boot_data_t) + kIsValidOffset;
HARDENED_RETURN_IF_ERROR(flash_ctrl_info_read(page, offset, 3, buf));
*masked_identifier = buf[0] & buf[1] & buf[2];
return kErrorOk;
}
/**
* Reads the boot data entry at the given page and index.
*
* @param page A boot data page.
* @param index Index of the entry to read in the given page.
* @param[out] boot_data A buffer that will hold the entry.
* @return The result of the operation.
*/
static rom_error_t boot_data_entry_read(flash_ctrl_info_page_t page,
size_t index, boot_data_t *boot_data) {
const uint32_t offset = index * sizeof(boot_data_t);
return flash_ctrl_info_read(page, offset, kBootDataNumWords, boot_data);
}
/**
* Populates the boot data entry at the given page and index.
*
* This function writes the new entry in two transactions skipping over the
* `is_valid` field so that the entry can be invalidated later. If `erase` is
* `kHardenedBoolTrue`, this function erases the given page before writing the
* new entry. This function also also verifies the newly written entry by
* reading it back. Reads, writes, and erases (if applicable) must be enabled
* for the given page before this function is called, see
* `boot_data_entry_write()`.
*
* @param page A boot data page.
* @param index Index of the entry to write in the given page.
* @param boot_data Entry to write.
* @param erase Whether to erase the page before writing the entry.
* @return The result of the operation.
*/
static rom_error_t boot_data_entry_write_impl(flash_ctrl_info_page_t page,
size_t index,
const boot_data_t *boot_data,
hardened_bool_t erase) {
// This function assumes the following layout for the first three fields.
OT_ASSERT_MEMBER_OFFSET(boot_data_t, digest, 0);
OT_ASSERT_MEMBER_OFFSET(boot_data_t, is_valid, 32);
OT_ASSERT_MEMBER_OFFSET(boot_data_t, identifier, 40);
if (erase == kHardenedBoolTrue) {
RETURN_IF_ERROR(flash_ctrl_info_erase(page, kFlashCtrlEraseTypePage));
}
// Write digest
const uint32_t offset = index * sizeof(boot_data_t);
RETURN_IF_ERROR(
flash_ctrl_info_write(page, offset, kHmacDigestNumWords, boot_data));
// Write the rest of the entry, skipping over `is_valid`.
enum {
kSecondWriteOffsetBytes = offsetof(boot_data_t, identifier),
kSecondWriteOffsetWords = kSecondWriteOffsetBytes / sizeof(uint32_t),
kSecondWriteNumWords = kBootDataNumWords - kSecondWriteOffsetWords,
};
RETURN_IF_ERROR(flash_ctrl_info_write(
page, offset + kSecondWriteOffsetBytes, kSecondWriteNumWords,
(const char *)boot_data + kSecondWriteOffsetBytes));
// Check.
boot_data_t written;
RETURN_IF_ERROR(
flash_ctrl_info_read(page, offset, kBootDataNumWords, &written));
if (memcmp(&written, boot_data, sizeof(boot_data_t)) != 0) {
return kErrorBootDataWriteCheck;
}
return kErrorOk;
}
/**
* Handles access permissions and populates the boot data entry at the given
* page and index.
*
* This function wraps the actual implementation to enable and disable reads,
* writes, and erases (if applicable) for the given page, see
* `boot_data_page_entry_write_impl()`.
*
* @param page A boot data page.
* @param index Index of the entry to write in the given page.
* @param boot_data Entry to write.
* @param erase Whether to erase the page before writing the entry.
* @return The result of the operation.
*/
static rom_error_t boot_data_entry_write(flash_ctrl_info_page_t page,
size_t index,
const boot_data_t *boot_data,
hardened_bool_t erase) {
flash_ctrl_info_perms_set(
page, (flash_ctrl_perms_t){
.read = kMultiBitBool4True,
.write = kMultiBitBool4True,
.erase = erase == kHardenedBoolTrue ? kMultiBitBool4True
: kMultiBitBool4False,
});
rom_error_t error = boot_data_entry_write_impl(page, index, boot_data, erase);
flash_ctrl_info_perms_set(page, (flash_ctrl_perms_t){
.read = kMultiBitBool4False,
.write = kMultiBitBool4False,
.erase = kMultiBitBool4False,
});
SEC_MMIO_WRITE_INCREMENT(2 * kFlashCtrlSecMmioInfoPermsSet);
return error;
}
/**
* Invalidates the boot data entry at the given page and index.
*
* This function handles write permissions for the given page and sets the
* `is_valid` field of the given entry to `kBootDataInvalidEntry` which will
* cause the digest checks to fail in subsequent reads.
*
* This function must be called only after the new entry is successfully
* written since writes can potentially be interrupted.
*
* @param page A boot data page.
* @param index Index of the entry to invalidate in the given page.
* @return The result of the operation.
*/
static rom_error_t boot_data_entry_invalidate(flash_ctrl_info_page_t page,
size_t index) {
// Assertions for the assumptions below.
OT_ASSERT_MEMBER_SIZE(boot_data_t, is_valid, 8);
static_assert(kBootDataInvalidEntry == 0,
"Unexpected kBootDataInvalidEntry value.");
const uint32_t offset =
index * sizeof(boot_data_t) + offsetof(boot_data_t, is_valid);
const uint32_t val[2] = {0, 0};
flash_ctrl_info_perms_set(page, (flash_ctrl_perms_t){
.read = kMultiBitBool4False,
.write = kMultiBitBool4True,
.erase = kMultiBitBool4False,
});
rom_error_t error = flash_ctrl_info_write(page, offset, 2, val);
flash_ctrl_info_perms_set(page, (flash_ctrl_perms_t){
.read = kMultiBitBool4False,
.write = kMultiBitBool4False,
.erase = kMultiBitBool4False,
});
SEC_MMIO_WRITE_INCREMENT(2 * kFlashCtrlSecMmioInfoPermsSet);
return error;
}
/**
* A struct that stores some information about the first empty and last valid
* entries in the active flash info page.
*/
typedef struct active_page_info {
/**
* Info page.
*/
flash_ctrl_info_page_t page;
/**
* Whether this page has an empty entry.
*/
hardened_bool_t has_empty_entry;
/**
* Index of the first empty entry.
*/
size_t first_empty_index;
/**
* Whether this page has a valid boot data entry.
*/
hardened_bool_t has_valid_entry;
/**
* Index of the last valid entry in the page.
*/
size_t last_valid_index;
} active_page_info_t;
/**
* Updates the given active page info struct and last valid boot data entry
* using the given page.
*
* This function performs a forward search to find the first empty boot data
* entry followed by a backward search to find the last valid boot data entry.
* If the page has an entry that is newer than the one passed in, this function
* updates `page_info` and `boot_data`. Reads must be enabled for the given page
* before this function is called, see `boot_data_page_info_get()`.
*
* @param page A boot data page.
* @param[in,out] page_info Active page info struct. Updated if the given page
* has a newer entry.
* @param[in,out] boot_data Last valid boot data entry found so far. Updated if
* the given page has a newer entry.
* @return The result of the operation.
*/
static rom_error_t boot_data_page_info_update_impl(
flash_ctrl_info_page_t page, active_page_info_t *page_info,
boot_data_t *boot_data) {
uint32_t sniff_results[kBootDataEntriesPerPage];
boot_data_t buf;
// Perform a forward search to find the first empty entry.
hardened_bool_t has_empty_entry = kHardenedBoolFalse;
size_t i = 0;
for (; launder32(i) < kBootDataEntriesPerPage; ++i) {
// Read and cache the identifier to quickly determine if an entry can be
// empty or valid.
HARDENED_RETURN_IF_ERROR(boot_data_sniff(page, i, &sniff_results[i]));
// Check all words of this entry only if it can be empty.
if (sniff_results[i] == kFlashCtrlErasedWord) {
HARDENED_RETURN_IF_ERROR(boot_data_entry_read(page, i, &buf));
has_empty_entry = boot_data_is_empty(&buf);
if (launder32(has_empty_entry) == kHardenedBoolTrue) {
HARDENED_CHECK_EQ(has_empty_entry, kHardenedBoolTrue);
break;
}
HARDENED_CHECK_EQ(has_empty_entry, kHardenedBoolFalse);
}
}
// At the end of this loop, `i` is the index of the first empty entry if any
// and `kBootDataEntriesPerPage` otherwise.
HARDENED_CHECK_LE(i, kBootDataEntriesPerPage);
size_t first_empty_index = i;
// Perform a backward search to find the last valid entry.
hardened_bool_t has_valid_entry = kHardenedBoolFalse;
i = first_empty_index - 1;
size_t j = 0;
for (; launder32(i) < first_empty_index && launder32(j) < first_empty_index;
--i, ++j) {
// Check the digest only if this entry can be valid.
if (sniff_results[i] == kBootDataIdentifier) {
HARDENED_RETURN_IF_ERROR(boot_data_entry_read(page, i, &buf));
rom_error_t is_valid = boot_data_check(&buf);
if (launder32(is_valid) == kErrorOk) {
HARDENED_CHECK_EQ(is_valid, kErrorOk);
static_assert(kErrorOk == (rom_error_t)kHardenedBoolTrue,
"kErrorOk must be equal to kHardenedBoolTrue");
has_valid_entry = (hardened_bool_t)is_valid;
break;
}
HARDENED_CHECK_EQ(is_valid, kErrorBootDataInvalid);
}
}
// At the end of this loop, `i` is the index of the last valid entry if any
// and `UINT32_MAX`, otherwise. `j` must be less than or equal to
// `first_empty_index`.
HARDENED_CHECK_LE(j, first_empty_index);
if (launder32(has_valid_entry) == kHardenedBoolTrue) {
HARDENED_CHECK_EQ(has_valid_entry, kHardenedBoolTrue);
if (launder32(page_info->has_valid_entry) == kHardenedBoolFalse) {
// Update `page_info` and `boot_data` since this is the first valid entry.
HARDENED_CHECK_EQ(page_info->has_valid_entry, kHardenedBoolFalse);
*page_info = (active_page_info_t){
.page = page,
.has_empty_entry = has_empty_entry,
.first_empty_index = first_empty_index,
.has_valid_entry = has_valid_entry,
.last_valid_index = i,
};
*boot_data = buf;
} else if (launder32(page_info->has_valid_entry) == kHardenedBoolTrue &&
launder32(buf.counter) > boot_data->counter) {
// Update `page_info` and `boot_data` since this entry is newer.
HARDENED_CHECK_EQ(page_info->has_valid_entry, kHardenedBoolTrue);
HARDENED_CHECK_GT(buf.counter, boot_data->counter);
*page_info = (active_page_info_t){
.page = page,
.has_empty_entry = has_empty_entry,
.first_empty_index = first_empty_index,
.has_valid_entry = has_valid_entry,
.last_valid_index = i,
};
*boot_data = buf;
} else {
HARDENED_CHECK_EQ(page_info->has_valid_entry, kHardenedBoolTrue);
// Counters cannot be equal if we have two valid entries since they are
// incremented at each write.
HARDENED_CHECK_LT(buf.counter, boot_data->counter);
}
}
return kErrorOk;
}
/**
* Handles read permissions and updates the active page info struct and last
* valid boot data entry using the given page.
*
* This function wraps the actual implementation to enable and disable reads for
* the given page, see `boot_data_page_info_get_impl()`.
*
* @param page A boot data page.
* @param[in,out] page_info Active page info struct. Updated if the given page
* has a newer entry.
* @param[in,out] boot_data Last valid boot data entry found so far. Updated if
* the given page has a newer entry.
* @return The result of the operation.
*/
static rom_error_t boot_data_page_info_update(flash_ctrl_info_page_t page,
active_page_info_t *page_info,
boot_data_t *boot_data) {
flash_ctrl_info_perms_set(page, (flash_ctrl_perms_t){
.read = kMultiBitBool4True,
.write = kMultiBitBool4False,
.erase = kMultiBitBool4False,
});
rom_error_t error =
boot_data_page_info_update_impl(page, page_info, boot_data);
flash_ctrl_info_perms_set(page, (flash_ctrl_perms_t){
.read = kMultiBitBool4False,
.write = kMultiBitBool4False,
.erase = kMultiBitBool4False,
});
SEC_MMIO_WRITE_INCREMENT(2 * kFlashCtrlSecMmioInfoPermsSet);
return error;
}
/**
* Finds the active info page and returns its page info struct and last boot
* data entry.
*
* The active info page is the one that has the newest valid boot data entry,
* i.e. the entry with the greatest counter value.
*
* @param[out] page_info Page info struct of the active info page.
* @param[out] boot_data Last valid boot data entry.
* @return The result of the operation.
*/
static rom_error_t boot_data_active_page_find(active_page_info_t *page_info,
boot_data_t *boot_data) {
*page_info = (active_page_info_t){
.page = (flash_ctrl_info_page_t)0,
.has_empty_entry = kHardenedBoolFalse,
.first_empty_index = kBootDataEntriesPerPage,
.has_valid_entry = kHardenedBoolFalse,
.last_valid_index = kBootDataEntriesPerPage,
};
size_t i = 0;
for (; launder32(i) < ARRAYSIZE(kPages); ++i) {
HARDENED_RETURN_IF_ERROR(
boot_data_page_info_update(kPages[i], page_info, boot_data));
}
HARDENED_CHECK_EQ(i, ARRAYSIZE(kPages));
return kErrorOk;
}
/**
* Returns the default boot data.
*
* Default boot data can be used only in TEST_UNLOCKED, DEV, and RMA life cycle
* states unless explicitly allowed by setting the
* `CREATOR_SW_CFG_DEFAULT_BOOT_DATA_IN_PROD_EN` OTP item to
* `kHardenedBoolTrue`.
*
* @param lc_state Life cycle state of the device.
* @param[out] boot_data Default boot data.
* @return The result of the operation.
*/
static rom_error_t boot_data_default_get(lifecycle_state_t lc_state,
boot_data_t *boot_data) {
uint32_t allowed_in_prod = otp_read32(
OTP_CTRL_PARAM_CREATOR_SW_CFG_DEFAULT_BOOT_DATA_IN_PROD_EN_OFFSET);
rom_error_t res = lc_state ^ launder32(kErrorBootDataNotFound);
barrier32(res);
switch (launder32(lc_state)) {
case kLcStateTest:
HARDENED_CHECK_EQ(lc_state, kLcStateTest);
res ^= kLcStateTest ^ kErrorBootDataNotFound ^ kErrorOk;
break;
case kLcStateDev:
HARDENED_CHECK_EQ(lc_state, kLcStateDev);
res ^= kLcStateDev ^ kErrorBootDataNotFound ^ kErrorOk;
break;
case kLcStateProd:
HARDENED_CHECK_EQ(lc_state, kLcStateProd);
res ^= kLcStateProd;
if (launder32(allowed_in_prod) == kHardenedBoolTrue) {
HARDENED_CHECK_EQ(allowed_in_prod, kHardenedBoolTrue);
res ^= kErrorBootDataNotFound ^ kErrorOk;
}
break;
case kLcStateProdEnd:
HARDENED_CHECK_EQ(lc_state, kLcStateProdEnd);
res ^= kLcStateProdEnd;
if (launder32(allowed_in_prod) == kHardenedBoolTrue) {
HARDENED_CHECK_EQ(allowed_in_prod, kHardenedBoolTrue);
res ^= kErrorBootDataNotFound ^ kErrorOk;
}
break;
case kLcStateRma:
HARDENED_CHECK_EQ(lc_state, kLcStateRma);
res ^= kLcStateRma ^ kErrorBootDataNotFound ^ kErrorOk;
break;
default:
HARDENED_UNREACHABLE();
}
HARDENED_RETURN_IF_ERROR(res);
boot_data->is_valid = kBootDataValidEntry;
boot_data->identifier = kBootDataIdentifier;
boot_data->counter = kBootDataDefaultCounterVal;
boot_data->min_security_version_rom_ext =
otp_read32(OTP_CTRL_PARAM_CREATOR_SW_CFG_MIN_SEC_VER_ROM_EXT_OFFSET);
boot_data->min_security_version_bl0 =
otp_read32(OTP_CTRL_PARAM_CREATOR_SW_CFG_MIN_SEC_VER_BL0_OFFSET);
// We cannot use a constant digest since some fields are read from the OTP
// and we check the digest of the cached boot data entry in rom.c
boot_data_digest_compute(boot_data, &boot_data->digest);
return res;
}
rom_error_t boot_data_read(lifecycle_state_t lc_state, boot_data_t *boot_data) {
active_page_info_t active_page;
HARDENED_RETURN_IF_ERROR(boot_data_active_page_find(&active_page, boot_data));
switch (launder32(active_page.has_valid_entry)) {
case kHardenedBoolTrue:
HARDENED_CHECK_EQ(active_page.has_valid_entry, kHardenedBoolTrue);
return kErrorOk;
case kHardenedBoolFalse:
HARDENED_CHECK_EQ(active_page.has_valid_entry, kHardenedBoolFalse);
return boot_data_default_get(lc_state, boot_data);
default:
HARDENED_UNREACHABLE();
}
}
rom_error_t boot_data_write(const boot_data_t *boot_data) {
boot_data_t new_entry = *boot_data;
new_entry.is_valid = kBootDataValidEntry;
new_entry.identifier = kBootDataIdentifier;
active_page_info_t active_page;
boot_data_t last_entry;
RETURN_IF_ERROR(boot_data_active_page_find(&active_page, &last_entry));
if (active_page.has_valid_entry == kHardenedBoolTrue) {
// Note: Not checking for wraparound since a successful write will
// invalidate the old entry.
new_entry.counter = last_entry.counter + 1;
boot_data_digest_compute(&new_entry, &new_entry.digest);
if (active_page.has_empty_entry == kHardenedBoolTrue) {
RETURN_IF_ERROR(boot_data_entry_write(active_page.page,
active_page.first_empty_index,
&new_entry, kHardenedBoolFalse));
} else {
// Erase the other page and write the new entry there if the active page
// is full.
flash_ctrl_info_page_t new_page =
active_page.page == kPages[0] ? kPages[1] : kPages[0];
RETURN_IF_ERROR(
boot_data_entry_write(new_page, 0, &new_entry, kHardenedBoolTrue));
}
// Invalidate the previous entry so that there is only one valid entry
// across both pages.
RETURN_IF_ERROR(boot_data_entry_invalidate(active_page.page,
active_page.last_valid_index));
} else {
// Erase the first page and write the entry there if the active page cannot
// be found, i.e. the storage is not initialized yet.
new_entry.counter = kBootDataDefaultCounterVal + 1;
boot_data_digest_compute(&new_entry, &new_entry.digest);
RETURN_IF_ERROR(
boot_data_entry_write(kPages[0], 0, &new_entry, kHardenedBoolTrue));
}
return kErrorOk;
}
/**
* Shares for producing the `error` value in `boot_data_check()`. First 8
* shares are generated using the `sparse-fsm-encode` script while the last
* share is `kErrorOk ^ kBootDataInvalid ^ kBootDataIdentifier ^
* kCheckShares[0] ^ ... ^ kCheckShares[7]` so that xor'ing all shares with
* the initial value of `error`, i.e. `kErrorBootDataInvalid`, and
* `kBootDataIdentifier` produces `kErrorOk`.
*
* Encoding generated with
* $ ./util/design/sparse-fsm-encode.py -d 6 -m 8 -n 32 \
* -s 49105412 --language=c
*
* Minimum Hamming distance: 12
* Maximum Hamming distance: 23
* Minimum Hamming weight: 13
* Maximum Hamming weight: 20
*/
static const uint32_t kCheckShares[kHmacDigestNumWords + 1] = {
0xe021e1a9, 0xf81e8365, 0xbf8322db, 0xc7a37080, 0x271a933f,
0xdd8ce33f, 0x7585d574, 0x951777af, 0x381dee3a,
};
/**
* Checks whether the digest of a boot data entry is valid.
*
* @param boot_data A buffer that holds a boot data entry.
* @return Whether the digest of the entry is valid.
*/
rom_error_t boot_data_check(const boot_data_t *boot_data) {
static_assert(offsetof(boot_data_t, digest) == 0,
"`digest` must be the first field of `boot_data_t`.");
rom_error_t error = kErrorBootDataInvalid;
hmac_digest_t act_digest;
boot_data_digest_compute(boot_data, &act_digest);
size_t i = 0;
for (; launder32(i) < kHmacDigestNumWords; ++i) {
error ^=
boot_data->digest.digest[i] ^ act_digest.digest[i] ^ kCheckShares[i];
}
HARDENED_CHECK_EQ(i, kHmacDigestNumWords);
error ^= boot_data->identifier ^ kCheckShares[kHmacDigestNumWords];
if (launder32(error) == kErrorOk) {
HARDENED_CHECK_EQ(error, kErrorOk);
return error;
}
return kErrorBootDataInvalid;
}