core/manifest/manifest_flash.c (802 lines of code) (raw):
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include "manifest.h"
#include "manifest_flash.h"
#include "platform_api.h"
#include "common/buffer_util.h"
#include "common/common_math.h"
#include "crypto/ecc.h"
#include "crypto/rsa.h"
#include "flash/flash_common.h"
#include "flash/flash_util.h"
/**
* Initialize the common handling for manifests stored on flash. Only version 1 style manifests
* will be supported.
*
* @param manifest The manifest to initialize.
* @param state Variable context for the manifest. This must be uninitialized.
* @param flash The flash device that contains the manifest.
* @param base_addr The starting address in flash of the manifest.
* @param magic_num The magic number that identifies the manifest.
*
* @return 0 if the manifest was initialized successfully or an error code.
*/
int manifest_flash_init (struct manifest_flash *manifest, struct manifest_flash_state *state,
const struct flash *flash, uint32_t base_addr, uint16_t magic_num)
{
return manifest_flash_v2_init (manifest, state, flash, NULL, base_addr, magic_num,
MANIFEST_NOT_SUPPORTED, NULL, 0, NULL, 0);
}
/**
* Initialize the common handling for manifests stored on flash. Both version 1 and version 2 style
* manifests can be supported.
*
* @param manifest The manifest to initialize.
* @param state Variable context for the manifest. This must be uninitialized.
* @param flash The flash device that contains the manifest.
* @param hash A hash engine to use for validating run-time access of manifest elements.
* @param base_addr The starting address in flash of the manifest.
* @param magic_num_v1 The magic number that identifies version 1 of the manifest.
* @param magic_num_v2 The magic number that identifies version 2 of the manifest.
* @param signature_cache Buffer to hold the manifest signature.
* @param max_signature The maximum supported length for a manifest signature.
* @param platform_id_cache Buffer to hold the manifest platform ID.
* @param max_platform_id The maximum platform ID length supported, including the NULL terminator.
*
* @return 0 if the manifest was initialized successfully or an error code.
*/
int manifest_flash_v2_init (struct manifest_flash *manifest, struct manifest_flash_state *state,
const struct flash *flash, const struct hash_engine *hash, uint32_t base_addr,
uint16_t magic_num_v1, uint16_t magic_num_v2, uint8_t *signature_cache, size_t max_signature,
uint8_t *platform_id_cache, size_t max_platform_id)
{
uint32_t block;
int status;
if ((manifest == NULL) || (state == NULL) || (flash == NULL)) {
return MANIFEST_INVALID_ARGUMENT;
}
if ((magic_num_v2 != MANIFEST_NOT_SUPPORTED) &&
((hash == NULL) || (platform_id_cache == NULL) || (signature_cache == NULL))) {
return MANIFEST_INVALID_ARGUMENT;
}
status = flash->get_block_size (flash, &block);
if (status != 0) {
return status;
}
if (FLASH_REGION_OFFSET (base_addr, block) != 0) {
return MANIFEST_STORAGE_NOT_ALIGNED;
}
memset (manifest, 0, sizeof (struct manifest_flash));
if (signature_cache == NULL) {
/* This can only be true if v2 manifests are not supported. */
max_signature = RSA_KEY_LENGTH_2K;
signature_cache = platform_malloc (max_signature);
if (signature_cache == NULL) {
return MANIFEST_NO_MEMORY;
}
manifest->free_signature = true;
}
manifest->state = state;
manifest->flash = flash;
manifest->hash = hash;
manifest->addr = base_addr;
manifest->magic_num_v1 = magic_num_v1;
manifest->magic_num_v2 = magic_num_v2;
manifest->signature = signature_cache;
manifest->max_signature = max_signature;
manifest->platform_id = (char*) platform_id_cache;
manifest->max_platform_id = max_platform_id - 1;
memset (manifest->state, 0, sizeof (*manifest->state));
return 0;
}
/**
* Initialize only the variable state for a manifest on flash. The rest of the handler is assumed
* to have already been initialized.
*
* This would generally be used with a statically initialized instance.
*
* @param manifest The manifest that contains the state to initialize.
*
* @return 0 if the state was successfully initialized or an error code.
*/
int manifest_flash_init_state (const struct manifest_flash *manifest)
{
uint32_t block;
int status;
if ((manifest == NULL) || (manifest->state == NULL) || (manifest->flash == NULL)) {
return MANIFEST_INVALID_ARGUMENT;
}
if ((manifest->magic_num_v2 != MANIFEST_NOT_SUPPORTED) &&
((manifest->hash == NULL) || (manifest->platform_id == NULL) ||
(manifest->signature == NULL))) {
return MANIFEST_INVALID_ARGUMENT;
}
memset (manifest->state, 0, sizeof (*manifest->state));
status = manifest->flash->get_block_size (manifest->flash, &block);
if (status != 0) {
return status;
}
if (FLASH_REGION_OFFSET (manifest->addr, block) != 0) {
return MANIFEST_STORAGE_NOT_ALIGNED;
}
return 0;
}
/**
* Release the common manifest components.
*
* @param manifest The manifest to release.
*/
void manifest_flash_release (const struct manifest_flash *manifest)
{
if (manifest && manifest->free_signature) {
platform_free (manifest->signature);
}
}
/**
* Read the manifest header and run validity checking on the contents:
* - Check the magic number.
* - Verify that signature length is valid relative to the total length.
*
* @param manifest The manifest for the header to read.
* @param header Output for the header data.
*
* @return 0 if the header was successfully read and checked or an error code.
*/
int manifest_flash_read_header (const struct manifest_flash *manifest,
struct manifest_header *header)
{
int status;
if ((manifest == NULL) || (header == NULL)) {
return MANIFEST_INVALID_ARGUMENT;
}
status = manifest->flash->read (manifest->flash, manifest->addr, (uint8_t*) header,
sizeof (*header));
if (status != 0) {
return status;
}
if ((header->magic == MANIFEST_NOT_SUPPORTED) ||
((header->magic != manifest->magic_num_v1) && (header->magic != manifest->magic_num_v2))) {
return MANIFEST_BAD_MAGIC_NUMBER;
}
if ((header->length < sizeof (struct manifest_header)) ||
(header->sig_length > (header->length - sizeof (struct manifest_header)))) {
return MANIFEST_BAD_LENGTH;
}
return 0;
}
/**
* Validate and parse the header on a manifest.
*
* @param manifest The manifest that will be verified.
* @param hash The hash engine to use for validation.
* @param verification The module to use for signature verification.
* @param sig_hash Output for the type of hash used to generate the signature.
* @param hash_out Optional buffer to hold the manifest hash calculated during verification. The
* hash output will be valid even if the signature verification fails. This can be set to null to
* not save the hash value.
* @param hash_length Length of hash output buffer.
*
* @return 0 if the header was is valid or an error code.
*/
static int manifest_flash_parse_header (const struct manifest_flash *manifest,
const struct hash_engine *hash, const struct signature_verification *verification,
enum hash_type *sig_hash, uint8_t *hash_out, size_t hash_length)
{
struct manifest_header *header;
int status;
if ((manifest == NULL) || (hash == NULL) || (verification == NULL)) {
return MANIFEST_INVALID_ARGUMENT;
}
if ((hash_out != NULL) && (hash_length < SHA256_HASH_LENGTH)) {
return MANIFEST_HASH_BUFFER_TOO_SMALL;
}
header = &manifest->state->header;
manifest->state->manifest_valid = false;
manifest->state->cache_valid = false;
if (hash_out != NULL) {
/* Clear the output hash buffer to indicate no hash was calculated. */
memset (hash_out, 0, hash_length);
}
status = manifest_flash_read_header (manifest, header);
if (status != 0) {
return status;
}
if (header->sig_length > manifest->max_signature) {
return MANIFEST_SIG_BUFFER_TOO_SMALL;
}
switch (manifest_get_hash_type (header->sig_type)) {
case MANIFEST_HASH_SHA256:
*sig_hash = HASH_TYPE_SHA256;
manifest->state->hash_length = SHA256_HASH_LENGTH;
break;
case MANIFEST_HASH_SHA384:
*sig_hash = HASH_TYPE_SHA384;
manifest->state->hash_length = SHA384_HASH_LENGTH;
break;
case MANIFEST_HASH_SHA512:
*sig_hash = HASH_TYPE_SHA512;
manifest->state->hash_length = SHA512_HASH_LENGTH;
break;
default:
return MANIFEST_SIG_UNKNOWN_HASH_TYPE;
}
if (hash_out != NULL) {
if (hash_length < manifest->state->hash_length) {
return MANIFEST_HASH_BUFFER_TOO_SMALL;
}
}
return manifest->flash->read (manifest->flash,
manifest->addr + header->length - header->sig_length, manifest->signature,
header->sig_length);
}
/**
* Validate the signature on a version 1 manifest.
*
* @param manifest The manifest that will be verified.
* @param hash The hash engine to use for validation.
* @param verification The module to use for signature verification.
* @param sig_hash The type of hash used to generate the signature.
* @param hash_out Optional output buffer for the manifest hash.
*
* @return 0 if the manifest is valid or an error code.
*/
static int manifest_flash_verify_v1 (const struct manifest_flash *manifest,
const struct hash_engine *hash, const struct signature_verification *verification,
enum hash_type sig_hash, uint8_t *hash_out)
{
int status;
status = flash_contents_verification (manifest->flash, manifest->addr,
manifest->state->header.length - manifest->state->header.sig_length, hash, sig_hash,
verification, manifest->signature, manifest->state->header.sig_length,
manifest->state->hash_cache, sizeof (manifest->state->hash_cache));
if ((status == 0) || (status == SIG_VERIFICATION_BAD_SIGNATURE)) {
manifest->state->cache_valid = true;
if (hash_out) {
memcpy (hash_out, manifest->state->hash_cache, manifest->state->hash_length);
}
}
return status;
}
/**
* Validate the signature on a version 2 manifest.
*
* @param manifest The manifest that will be verified.
* @param hash The hash engine to use for validation.
* @param verification The module to use for signature verification.
* @param sig_hash The type of hash used to generate the signature.
* @param hash_out Optional output buffer for the manifest hash.
*
* @return 0 if the manifest is valid or an error code.
*/
static int manifest_flash_verify_v2 (const struct manifest_flash *manifest,
const struct hash_engine *hash, const struct signature_verification *verification,
enum hash_type sig_hash, uint8_t *hash_out)
{
struct manifest_toc_header *toc_header;
struct manifest_toc_entry entry;
struct manifest_platform_id plat_id_header;
uint32_t next_addr;
uint32_t toc_end;
uint32_t sig_addr =
manifest->addr + manifest->state->header.length - manifest->state->header.sig_length;
int i;
int status;
/* Hash the header data that has already been read in. */
status = hash_start_new_hash (hash, sig_hash);
if (status != 0) {
return status;
}
status = hash->update (hash, (uint8_t*) &manifest->state->header,
sizeof (manifest->state->header));
if (status != 0) {
goto error;
}
/* Read and hash the table of contents header. */
toc_header = &manifest->state->toc_header;
next_addr = manifest->addr + sizeof (manifest->state->header);
status = manifest->flash->read (manifest->flash, next_addr, (uint8_t*) toc_header,
sizeof (*toc_header));
if (status != 0) {
goto error;
}
switch (toc_header->hash_type) {
case MANIFEST_HASH_SHA256:
manifest->state->toc_hash_type = HASH_TYPE_SHA256;
manifest->state->toc_hash_length = SHA256_HASH_LENGTH;
break;
#ifdef HASH_ENABLE_SHA384
case MANIFEST_HASH_SHA384:
manifest->state->toc_hash_type = HASH_TYPE_SHA384;
manifest->state->toc_hash_length = SHA384_HASH_LENGTH;
break;
#endif
#ifdef HASH_ENABLE_SHA512
case MANIFEST_HASH_SHA512:
manifest->state->toc_hash_type = HASH_TYPE_SHA512;
manifest->state->toc_hash_length = SHA512_HASH_LENGTH;
break;
#endif
default:
status = MANIFEST_TOC_UNKNOWN_HASH_TYPE;
goto error;
}
status = hash->update (hash, (uint8_t*) toc_header, sizeof (*toc_header));
if (status != 0) {
goto error;
}
/* Find the platform ID element, hashing each entry as it is read in. */
next_addr += sizeof (*toc_header);
i = 0;
do {
status = manifest->flash->read (manifest->flash, next_addr, (uint8_t*) &entry,
sizeof (entry));
if (status != 0) {
goto error;
}
status = hash->update (hash, (uint8_t*) &entry, sizeof (entry));
if (status != 0) {
goto error;
}
next_addr += sizeof (entry);
i++;
} while ((entry.type_id != MANIFEST_PLATFORM_ID) && (i < toc_header->entry_count));
if (entry.type_id != MANIFEST_PLATFORM_ID) {
status = MANIFEST_NO_PLATFORM_ID;
goto error;
}
/* Hash the flash contents for the rest of the table of contents. */
toc_end = manifest->addr + sizeof (manifest->state->header) + sizeof (*toc_header) +
(toc_header->entry_count * sizeof (entry)) +
(toc_header->hash_count * manifest->state->toc_hash_length);
status = flash_hash_update_contents (manifest->flash, next_addr, toc_end - next_addr, hash);
if (status != 0) {
goto error;
}
/* Read and hash the table of contents hash. */
next_addr = toc_end;
status = manifest->flash->read (manifest->flash, next_addr, manifest->state->toc_hash,
manifest->state->toc_hash_length);
if (status != 0) {
goto error;
}
status = hash->update (hash, manifest->state->toc_hash, manifest->state->toc_hash_length);
if (status != 0) {
goto error;
}
/* Hash the flash contents until the platform ID element. */
next_addr += manifest->state->toc_hash_length;
status = flash_hash_update_contents (manifest->flash, next_addr,
manifest->addr + entry.offset - next_addr, hash);
if (status != 0) {
goto error;
}
/* Read and hash the platform ID element header. */
next_addr = manifest->addr + entry.offset;
status = manifest->flash->read (manifest->flash, next_addr, (uint8_t*) &plat_id_header,
sizeof (plat_id_header));
if (status != 0) {
goto error;
}
if (plat_id_header.id_length > manifest->max_platform_id) {
status = MANIFEST_PLAT_ID_BUFFER_TOO_SMALL;
goto error;
}
status = hash->update (hash, (uint8_t*) &plat_id_header, sizeof (plat_id_header));
if (status != 0) {
goto error;
}
/* Read and hash the platform ID string. */
next_addr += sizeof (plat_id_header);
status = manifest->flash->read (manifest->flash, next_addr, (uint8_t*) manifest->platform_id,
plat_id_header.id_length);
if (status != 0) {
goto error;
}
manifest->platform_id[plat_id_header.id_length] = '\0';
status = hash->update (hash, (uint8_t*) manifest->platform_id, plat_id_header.id_length);
if (status != 0) {
goto error;
}
/* Hash the remaining manifest flash contents. */
next_addr += plat_id_header.id_length;
status = flash_hash_update_contents (manifest->flash, next_addr, sig_addr - next_addr, hash);
if (status != 0) {
goto error;
}
/* Verify the signature of the overall manifest data. */
status = signature_verification_verify_hash_and_finish_save_digest (verification, hash, NULL, 0,
manifest->signature, manifest->state->header.sig_length, manifest->state->hash_cache,
sizeof (manifest->state->hash_cache), &manifest->state->cache_valid);
if (manifest->state->cache_valid && hash_out) {
memcpy (hash_out, manifest->state->hash_cache, manifest->state->hash_length);
}
return status;
error:
hash->cancel (hash);
return status;
}
/**
* Verify if the manifest is valid.
*
* @param manifest The manifest that will be verified.
* @param hash The hash engine to use for validation.
* @param verification The module to use for signature verification.
* @param hash_out Optional buffer to hold the manifest hash calculated during verification. The
* hash output will be valid even if the signature verification fails. This can be set to null to
* not save the hash value.
* @param hash_length Length of hash output buffer.
*
* @return 0 if the manifest is valid or an error code.
*/
int manifest_flash_verify (const struct manifest_flash *manifest, const struct hash_engine *hash,
const struct signature_verification *verification, uint8_t *hash_out, size_t hash_length)
{
enum hash_type sig_hash;
int status;
status = manifest_flash_parse_header (manifest, hash, verification, &sig_hash, hash_out,
hash_length);
if (status != 0) {
return status;
}
if (manifest->state->header.magic == manifest->magic_num_v1) {
status = manifest_flash_verify_v1 (manifest, hash, verification, sig_hash, hash_out);
}
else {
status = manifest_flash_verify_v2 (manifest, hash, verification, sig_hash, hash_out);
}
if (status == 0) {
manifest->state->manifest_valid = true;
}
return status;
}
/**
* Verify if the manifest is valid. Only version 2 style manifests will be supported, regardless of
* the manifest instance configuration.
*
* @param manifest The manifest that will be verified.
* @param hash The hash engine to use for validation.
* @param verification The module to use for signature verification.
* @param hash_out Optional buffer to hold the manifest hash calculated during verification. The
* hash output will be valid even if the signature verification fails. This can be set to null to
* not save the hash value.
* @param hash_length Length of hash output buffer.
*
* @return 0 if the manifest is valid or an error code.
*/
int manifest_flash_v2_verify (const struct manifest_flash *manifest, const struct hash_engine *hash,
const struct signature_verification *verification, uint8_t *hash_out, size_t hash_length)
{
enum hash_type sig_hash;
int status;
status = manifest_flash_parse_header (manifest, hash, verification, &sig_hash, hash_out,
hash_length);
if (status != 0) {
return status;
}
if (manifest->state->header.magic == manifest->magic_num_v2) {
status = manifest_flash_verify_v2 (manifest, hash, verification, sig_hash, hash_out);
}
else {
status = MANIFEST_BAD_MAGIC_NUMBER;
}
if (status == 0) {
manifest->state->manifest_valid = true;
}
return status;
}
/**
* Get the ID of the manifest.
*
* @param manifest The manifest to query.
* @param id The buffer to hold the manifest ID.
*
* @return 0 if the ID was successfully retrieved or an error code.
*/
int manifest_flash_get_id (const struct manifest_flash *manifest, uint32_t *id)
{
int status = 0;
if ((manifest == NULL) || (id == NULL)) {
return MANIFEST_INVALID_ARGUMENT;
}
if (manifest->state->manifest_valid) {
buffer_unaligned_copy32 (id, &manifest->state->header.id);
}
else {
status = MANIFEST_NO_MANIFEST;
}
return status;
}
/**
* Get the platform identifier from the manifest.
*
* @param manifest The manifest to query.
* @param id Pointer to the output buffer for the platform identifier. The buffer pointer
* cannot be null, but if the buffer itself is null, the output will directly reference the internal
* static buffer holding the platform identifier.
* @param length Length of the output buffer if the buffer is static (i.e. not null). This argument
* is ignored when using dynamic allocation.
*
* @return 0 if the platform ID was retrieved successfully or an error code.
*/
int manifest_flash_get_platform_id (const struct manifest_flash *manifest, char **id, size_t length)
{
if ((manifest == NULL) || (id == NULL)) {
return MANIFEST_INVALID_ARGUMENT;
}
if (!manifest->state->manifest_valid) {
return MANIFEST_NO_MANIFEST;
}
if (*id != NULL) {
strncpy (*id, manifest->platform_id, length);
if ((*id)[length - 1] != '\0') {
return MANIFEST_PLAT_ID_BUFFER_TOO_SMALL;
}
}
else {
*id = manifest->platform_id;
}
return 0;
}
/**
* Get the hash for the manifest. The hash will be the hash last calculated for manifest
* verification. If no verification has been previously performed or there was an error during the
* last verification, the hash will be calculated from flash.
*
* @param manifest The manifest to hash.
* @param hash The hash engine to use to calculate the hash.
* @param hash_out Output buffer for the manifest hash.
* @param hash_length Length of the hash output buffer.
*
* @return Length of the hash if it was calculated successfully or an error code. Use
* ROT_IS_ERROR to check the return value.
*/
int manifest_flash_get_hash (const struct manifest_flash *manifest, const struct hash_engine *hash,
uint8_t *hash_out, size_t hash_length)
{
struct manifest_header header;
enum hash_type sig_hash;
int status;
if ((manifest == NULL) || (hash == NULL) || (hash_out == NULL)) {
return MANIFEST_INVALID_ARGUMENT;
}
if (hash_length < SHA256_HASH_LENGTH) {
return MANIFEST_HASH_BUFFER_TOO_SMALL;
}
if (manifest->state->cache_valid) {
if (hash_length < manifest->state->hash_length) {
return MANIFEST_HASH_BUFFER_TOO_SMALL;
}
memcpy (hash_out, manifest->state->hash_cache, manifest->state->hash_length);
}
else {
status = manifest_flash_read_header (manifest, &header);
if (status != 0) {
return status;
}
switch (manifest_get_hash_type (header.sig_type)) {
case MANIFEST_HASH_SHA256:
sig_hash = HASH_TYPE_SHA256;
manifest->state->hash_length = SHA256_HASH_LENGTH;
break;
case MANIFEST_HASH_SHA384:
sig_hash = HASH_TYPE_SHA384;
manifest->state->hash_length = SHA384_HASH_LENGTH;
break;
case MANIFEST_HASH_SHA512:
sig_hash = HASH_TYPE_SHA512;
manifest->state->hash_length = SHA512_HASH_LENGTH;
break;
default:
return MANIFEST_SIG_UNKNOWN_HASH_TYPE;
}
if (hash_length < manifest->state->hash_length) {
return MANIFEST_HASH_BUFFER_TOO_SMALL;
}
status = flash_hash_contents (manifest->flash, manifest->addr,
header.length - header.sig_length, hash, sig_hash, hash_out, hash_length);
if (status != 0) {
return status;
}
}
return manifest->state->hash_length;
}
/**
* Get the signature for the manifest.
*
* @param manifest The manifest to query.
* @param signature Output buffer for the manifest signature.
* @param length Length of the signature output buffer.
*
* @return Length of the signature if it was successfully retrieved or an error code. Use
* ROT_IS_ERROR to check the return value.
*/
int manifest_flash_get_signature (const struct manifest_flash *manifest, uint8_t *signature,
size_t length)
{
if ((manifest == NULL) || (signature == NULL)) {
return MANIFEST_INVALID_ARGUMENT;
}
if (manifest->state->manifest_valid) {
if (length < manifest->state->header.sig_length) {
return MANIFEST_SIG_BUFFER_TOO_SMALL;
}
memcpy (signature, manifest->signature, manifest->state->header.sig_length);
return manifest->state->header.sig_length;
}
else {
struct manifest_header header;
int status;
status = manifest_flash_read_header (manifest, &header);
if (status != 0) {
return status;
}
if (length < header.sig_length) {
return MANIFEST_SIG_BUFFER_TOO_SMALL;
}
status = manifest->flash->read (manifest->flash,
manifest->addr + header.length - header.sig_length, signature, header.sig_length);
if (status != 0) {
return status;
}
return header.sig_length;
}
}
/**
* Find the first element of a specified type in the manifest and read the element data.
* Everything about the operation will be validated, as appropriate. This includes table of
* contents and entry data hashing.
*
* @param manifest The manifest to read.
* @param hash The hash engine to use for element validation.
* @param type Identifier for the type of element to find.
* @param start Index of the table of contents entry to start searching for the element.
* @param parent_type Identifier for the type of the parent element. If the element has no parent,
* MANIFEST_NO_PARENT must be provided.
* @param read_offset Offset into the element data to start reading. The entire element is still
* validated, but the buffer will only contain element data starting at the offset.
* @param found Optional output indicating which TOC entry was used for the element.
* @param format Optional output for the format version of the element data.
* @param total_len Optional output for the total length of the element data.
* @param element Optional pointer to the output buffer for the element data. If the output buffer
* is null, a buffer will by dynamically allocated to fit the entire element. This buffer must be
* freed by the caller. If the pointer is null, no element data will be read.
* @param length Length of the element output buffer, if the buffer is not null. If the actual
* element data is longer than the specified length, only the specified length will be read back and
* no error is generated. This parameter is ignored when the output buffer is dynamically
* allocated.
*
* @return The amount of element data read or an error code. Use ROT_IS_ERROR to check the return
* value.
*/
int manifest_flash_read_element_data (const struct manifest_flash *manifest,
const struct hash_engine *hash, uint8_t type, int start, uint8_t parent_type,
uint32_t read_offset, uint8_t *found, uint8_t *format, size_t *total_len, uint8_t **element,
size_t length)
{
struct manifest_toc_entry entry;
uint8_t entry_hash[SHA512_HASH_LENGTH];
uint8_t validate_hash[SHA512_HASH_LENGTH];
uint32_t entry_addr;
uint32_t hash_addr;
uint32_t toc_end;
int i;
int status;
if ((manifest == NULL) || (hash == NULL)) {
return MANIFEST_INVALID_ARGUMENT;
}
if (!manifest->state->manifest_valid) {
return MANIFEST_NO_MANIFEST;
}
if (start >= manifest->state->toc_header.entry_count) {
return (parent_type == MANIFEST_NO_PARENT) ?
MANIFEST_ELEMENT_NOT_FOUND : MANIFEST_CHILD_NOT_FOUND;
}
entry_addr =
manifest->addr + sizeof (struct manifest_header) + sizeof (struct manifest_toc_header);
hash_addr = entry_addr + (sizeof (entry) * manifest->state->toc_header.entry_count);
toc_end =
hash_addr + (manifest->state->toc_hash_length * manifest->state->toc_header.hash_count);
/* Start hashing to verify the TOC contents. */
status = hash_start_new_hash (hash, manifest->state->toc_hash_type);
if (status != 0) {
return status;
}
status = hash->update (hash, (uint8_t*) &manifest->state->toc_header,
sizeof (manifest->state->toc_header));
if (status != 0) {
goto error;
}
/* Hash the TOC data before the first entry that will be read. */
status = flash_hash_update_contents (manifest->flash, entry_addr, sizeof (entry) * start, hash);
if (status != 0) {
goto error;
}
/* Find the TOC entry for the requested element. */
entry_addr += sizeof (entry) * start;
i = start;
do {
status = manifest->flash->read (manifest->flash, entry_addr, (uint8_t*) &entry,
sizeof (entry));
if (status != 0) {
goto error;
}
/* As soon as we see an element that is not a child, we fail because we have left the
* context of the expected parent. */
if ((parent_type != MANIFEST_NO_PARENT) && (entry.parent == MANIFEST_NO_PARENT)) {
status = MANIFEST_CHILD_NOT_FOUND;
goto error;
}
status = hash->update (hash, (uint8_t*) &entry, sizeof (entry));
if (status != 0) {
goto error;
}
i++;
entry_addr += sizeof (entry);
} while ((entry.type_id != type) && (i < manifest->state->toc_header.entry_count));
if (entry.type_id != type) {
status = (parent_type == MANIFEST_NO_PARENT) ?
MANIFEST_ELEMENT_NOT_FOUND : MANIFEST_CHILD_NOT_FOUND;
goto error;
}
if (entry.hash_id < manifest->state->toc_header.hash_count) {
/* Find the address of the entry hash. */
hash_addr += (manifest->state->toc_hash_length * entry.hash_id);
/* Hash the unneeded TOC data until the entry hash. */
status = flash_hash_update_contents (manifest->flash, entry_addr, hash_addr - entry_addr,
hash);
if (status != 0) {
goto error;
}
/* Read the entry hash for element validation. */
status = manifest->flash->read (manifest->flash, hash_addr, entry_hash,
manifest->state->toc_hash_length);
if (status != 0) {
goto error;
}
status = hash->update (hash, entry_hash, manifest->state->toc_hash_length);
if (status != 0) {
goto error;
}
/* Hash the remaining TOC data. */
hash_addr += manifest->state->toc_hash_length;
status = flash_hash_update_contents (manifest->flash, hash_addr, toc_end - hash_addr, hash);
if (status != 0) {
goto error;
}
}
else {
status = flash_hash_update_contents (manifest->flash, entry_addr, toc_end - entry_addr,
hash);
if (status != 0) {
goto error;
}
}
/* Validate the TOC. */
status = hash->finish (hash, validate_hash, sizeof (validate_hash));
if (status != 0) {
goto error;
}
if (buffer_compare (validate_hash, manifest->state->toc_hash,
manifest->state->toc_hash_length) != 0) {
return MANIFEST_TOC_INVALID;
}
/* Read the element data. */
if ((entry.parent != MANIFEST_NO_PARENT) && (entry.parent != parent_type)) {
return MANIFEST_WRONG_PARENT;
}
if (found) {
*found = i - 1;
}
if (format) {
*format = entry.format;
}
if (total_len) {
*total_len = entry.length;
}
if ((element == NULL) || (read_offset >= entry.length)) {
length = 0;
}
else if (*element == NULL) {
/* This value doesn't matter. It's just set to pass the check for reading element data. */
length = 1;
}
if (length != 0) {
entry.length -= read_offset;
if (*element == NULL) {
*element = platform_malloc (entry.length);
if (*element == NULL) {
return MANIFEST_NO_MEMORY;
}
length = entry.length;
}
if (entry.hash_id < manifest->state->toc_header.hash_count) {
/* Hash the element data to validate the contents. */
status = hash_start_new_hash (hash, manifest->state->toc_hash_type);
if (status != 0) {
return status;
}
status = flash_hash_update_contents (manifest->flash, manifest->addr + entry.offset,
read_offset, hash);
if (status != 0) {
goto error;
}
}
entry.offset += read_offset;
length = min (length, entry.length);
status = manifest->flash->read (manifest->flash, manifest->addr + entry.offset, *element,
length);
if (status != 0) {
goto error;
}
if (entry.hash_id < manifest->state->toc_header.hash_count) {
status = hash->update (hash, *element, length);
if (status != 0) {
goto error;
}
if (length < entry.length) {
status = flash_hash_update_contents (manifest->flash,
manifest->addr + entry.offset + length, entry.length - length, hash);
if (status != 0) {
goto error;
}
}
status = hash->finish (hash, validate_hash, sizeof (validate_hash));
if (status != 0) {
goto error;
}
if (buffer_compare (validate_hash, entry_hash, manifest->state->toc_hash_length) != 0) {
return MANIFEST_ELEMENT_INVALID;
}
}
}
return length;
error:
hash->cancel (hash);
return status;
}
/**
* Get requested information of child elements or requested entry.
*
* Get Number of Child Elements: Use child_count to find the number of direct child elements of
* specified type of requested element. If element has nested children, they are not counted.
*
* Get Total Length of Child Elements: Use child_len to find the total length of direct child
* elements of specified type of requested element. If element has nested children, they are not
* counted.
*
* Get Entry ID of First Child: Use first_entry to get first child entry of requested type.
*
* @param manifest The manifest to read.
* @param hash The hash engine to use for element validation.
* @param entry Starting table of contents entry to start processing.
* @param type Type of requested parent element.
* @param parent_type Type of parent to requested parent element.
* @param child_type Type of child element to get count of.
* @param child_len Optional output buffer with total length of child elements.
* @param child_count Optional output buffer with number of child elements found.
* @param first_entry Optional output buffer with entry of first child.
*
* @return 0 if request completed successfully or an error code.
*/
int manifest_flash_get_child_elements_info (const struct manifest_flash *manifest,
const struct hash_engine *hash, int entry, uint8_t type, uint8_t parent_type,
uint8_t child_type, size_t *child_len, int *child_count, int *first_entry)
{
uint8_t validate_hash[SHA512_HASH_LENGTH];
struct manifest_toc_entry toc_entry;
uint32_t entry_addr;
uint32_t hash_addr;
bool only_entry = ((child_len == NULL) && (child_count == NULL));
int status;
if ((manifest == NULL) || (hash == NULL) || (only_entry && (first_entry == NULL))) {
return MANIFEST_INVALID_ARGUMENT;
}
if (!manifest->state->manifest_valid) {
return MANIFEST_NO_MANIFEST;
}
if (child_len != NULL) {
*child_len = 0;
}
if (child_count != NULL) {
*child_count = 0;
}
if (first_entry != NULL) {
*first_entry = 0;
}
if (entry >= manifest->state->toc_header.entry_count) {
return 0;
}
entry_addr = manifest->addr + sizeof (struct manifest_header) +
sizeof (struct manifest_toc_header);
hash_addr = entry_addr +
((sizeof (struct manifest_toc_entry) + manifest->state->toc_hash_length) *
manifest->state->toc_header.entry_count);
/* Start hashing to verify the TOC contents. */
status = hash_start_new_hash (hash, manifest->state->toc_hash_type);
if (status != 0) {
return status;
}
status = hash->update (hash, (uint8_t*) &manifest->state->toc_header,
sizeof (struct manifest_toc_header));
if (status != 0) {
goto error;
}
/* Hash the TOC data before the first entry that will be read. */
status = flash_hash_update_contents (manifest->flash, entry_addr,
sizeof (struct manifest_toc_entry) * entry, hash);
if (status != 0) {
goto error;
}
entry_addr += (sizeof (struct manifest_toc_entry) * entry);
for (; entry < manifest->state->toc_header.entry_count;
++entry, entry_addr += sizeof (struct manifest_toc_entry)) {
status = manifest->flash->read (manifest->flash, entry_addr, (uint8_t*) &toc_entry,
sizeof (struct manifest_toc_entry));
if (status != 0) {
goto error;
}
status = hash->update (hash, (uint8_t*) &toc_entry, sizeof (struct manifest_toc_entry));
if (status != 0) {
goto error;
}
if ((toc_entry.parent == parent_type) || (toc_entry.type_id == parent_type)) {
if (only_entry) {
status = MANIFEST_CHILD_NOT_FOUND;
goto error;
}
entry_addr += sizeof (struct manifest_toc_entry);
break;
}
if ((toc_entry.parent == type) && (toc_entry.type_id == child_type)) {
if ((first_entry != NULL) && (*first_entry == 0)) {
*first_entry = entry;
if (only_entry) {
entry_addr += sizeof (struct manifest_toc_entry);
break;
}
}
if (child_count != NULL) {
*child_count = *child_count + 1;
}
if (child_len != NULL) {
*child_len = *child_len + toc_entry.length;
}
}
}
if (only_entry && (*first_entry == 0)) {
status = MANIFEST_CHILD_NOT_FOUND;
goto error;
}
/* Hash the unneeded TOC data until the entry hash. */
status = flash_hash_update_contents (manifest->flash, entry_addr, hash_addr - entry_addr, hash);
if (status != 0) {
goto error;
}
/* Validate the TOC. */
status = hash->finish (hash, validate_hash, sizeof (validate_hash));
if (status != 0) {
goto error;
}
if (buffer_compare (validate_hash, manifest->state->toc_hash,
manifest->state->toc_hash_length) != 0) {
return MANIFEST_TOC_INVALID;
}
return 0;
error:
hash->cancel (hash);
return status;
}
/**
* Get the starting flash address of the manifest.
*
* @param manifest The manifest to query.
*
* @return The manifest base flash address.
*/
uint32_t manifest_flash_get_addr (const struct manifest_flash *manifest)
{
if (manifest) {
return manifest->addr;
}
else {
return 0;
}
}
/**
* Get the flash device that is used to store the manifest.
*
* @param manifest The manifest to query.
*
* @return The flash device for the manifest.
*/
const struct flash* manifest_flash_get_flash (const struct manifest_flash *manifest)
{
if (manifest) {
return manifest->flash;
}
else {
return NULL;
}
}
/**
* Compare the IDs of two manifests to check for a valid manifest ID. If the first manifest is not
* valid, the second will always report a higher ID as long as it is a valid manifest. If the
* second manifest is not valid, an error will be returned.
*
* @param manifest1 The first manifest for comparison.
* @param manifest2 The second manifest for comparison.
*
* @return 0 if second manifest has a higher ID than the first, 1 if not, or an error code.
*/
int manifest_flash_compare_id (const struct manifest_flash *manifest1,
const struct manifest_flash *manifest2)
{
if (manifest2 == NULL) {
return MANIFEST_INVALID_ARGUMENT;
}
if (!manifest2->state->manifest_valid) {
return MANIFEST_NO_MANIFEST;
}
else if ((manifest1 == NULL) || !manifest1->state->manifest_valid) {
return 0;
}
if (manifest1->state->header.id < manifest2->state->header.id) {
return 0;
}
else {
return 1;
}
}
/**
* Compare the platform IDs of two manifests. If either manifest is invalid, an error will be
* returned.
*
* @param manifest1 The active manifest for comparison.
* @param manifest2 The pending manifest for comparison.
* @param sku_upgrade_permitted Manifest permitted to upgrade from generic to SKU-specific.
*
* @return 0 if both manifests have the same platform ID, 1 if not, or an error code.
*/
int manifest_flash_compare_platform_id (const struct manifest_flash *manifest1,
const struct manifest_flash *manifest2, bool sku_upgrade_permitted)
{
if ((manifest1 == NULL) || (manifest2 == NULL)) {
return MANIFEST_INVALID_ARGUMENT;
}
if (!manifest1->state->manifest_valid || !manifest2->state->manifest_valid) {
return MANIFEST_NO_MANIFEST;
}
if (sku_upgrade_permitted) {
return (strncmp (manifest1->platform_id, manifest2->platform_id,
strlen (manifest1->platform_id)) != 0);
}
else {
return (strcmp (manifest1->platform_id, manifest2->platform_id) != 0);
}
}