sdk-hwV1.3/lichee/brandy-2.0/u-boot-2018/common/bcc.c

522 lines
14 KiB
C

// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2022 Google LLC
*/
#include <bcc.h>
#include <malloc.h>
#include <u-boot/sha256.h>
#include <vm_instance.h>
#include <dice/android/bcc.h>
#include <dice/cbor_reader.h>
#include <dice/cbor_writer.h>
#include <dice/ops.h>
#include <dm/device.h>
#include <dm/device_compat.h>
#include <dm/uclass.h>
#include <dm/read.h>
#include <linux/ioport.h>
#include <openssl/evp.h>
#include <openssl/hkdf.h>
#define BCC_CONFIG_DESC_SIZE 64
struct boot_measurement {
const uint8_t *public_key;
size_t public_key_size;
uint8_t digest[AVB_SHA256_DIGEST_SIZE];
};
static uint8_t *bcc_handover_buffer;
static size_t bcc_handover_buffer_size;
static const DiceMode bcc_to_dice_mode[] = {
[BCC_MODE_NORMAL] = kDiceModeNormal,
[BCC_MODE_MAINTENANCE] = kDiceModeMaintenance,
[BCC_MODE_DEBUG] = kDiceModeDebug,
};
static void *find_bcc_handover(size_t *size)
{
struct udevice *dev;
struct resource res;
/* Probe drivers that provide a BCC handover buffer. */
for (uclass_first_device(UCLASS_DICE, &dev); dev; uclass_next_device(&dev)) {
if (!dev_read_resource(dev, 0, &res)) {
*size = resource_size(&res);
return (void *)res.start;
}
}
return NULL;
}
int bcc_init(void)
{
/* Idempotent initialization to allow indepedent client modules. */
if (bcc_handover_buffer && bcc_handover_buffer_size)
return 0;
/* If a BCC handover wasn't already set, look for a driver. */
bcc_handover_buffer = find_bcc_handover(&bcc_handover_buffer_size);
if (!bcc_handover_buffer || !bcc_handover_buffer_size)
return -ENOENT;
return 0;
}
void bcc_set_handover(void *handover, size_t handover_size)
{
bcc_handover_buffer = handover;
bcc_handover_buffer_size = handover_size;
}
void bcc_clear_memory(void *data, size_t size)
{
DiceClearMemory(NULL, size, data);
}
static void sha256(const void *data, size_t data_size, uint8_t digest[AVB_SHA256_DIGEST_SIZE])
{
sha256_context ctx;
sha256_starts(&ctx);
sha256_update(&ctx, data, data_size);
sha256_finish(&ctx, digest);
}
/**
* Format the VM instance data into a CBOR record.
*
* The salt is left uninitialized and a pointer to it is returned.
*
* Measurement = [
* 1: bstr, // public key
* 2: bstr, // digest
* ]
*
* InstanceData = {
* 1: bstr .size 64, // salt
* 2: Measurement, // code
* ? 3: Measurement, // config
* }
*/
static int vm_instance_format(const struct boot_measurement *code,
const struct boot_measurement *config,
size_t buffer_size, uint8_t *buffer,
size_t *formatted_size, uint8_t **salt)
{
const int64_t salt_label = 1;
const int64_t code_label = 2;
const int64_t config_label = 3;
const int64_t public_key_label = 1;
const int64_t digest_label = 2;
struct CborOut out;
if (!code || !formatted_size)
return -EINVAL;
CborOutInit(buffer, buffer_size, &out);
CborWriteMap(/*num_pairs=*/config ? 3 : 1, &out);
CborWriteInt(salt_label, &out);
*salt = CborAllocBstr(VM_INSTANCE_SALT_SIZE, &out);
CborWriteInt(code_label, &out);
CborWriteMap(/*num_pairs=*/2, &out);
CborWriteInt(public_key_label, &out);
CborWriteBstr(code->public_key_size, code->public_key, &out);
CborWriteInt(digest_label, &out);
CborWriteBstr(AVB_SHA256_DIGEST_SIZE, code->digest, &out);
if (config) {
CborWriteInt(config_label, &out);
CborWriteMap(/*num_pairs=*/2, &out);
CborWriteInt(public_key_label, &out);
CborWriteBstr(config->public_key_size, config->public_key, &out);
CborWriteInt(digest_label, &out);
CborWriteBstr(AVB_SHA256_DIGEST_SIZE, config->digest, &out);
}
*formatted_size = CborOutSize(&out);
return CborOutOverflowed(&out) ? -E2BIG : 0;
}
/**
* Verify the measurements are the same as previously recorded for the VM
* instance or create a new VM instance with the measurements.
*
* The salt that is saved with the instance data is added as a hidden input to
* bcc_ctx if the measurements don't conflict with the previously recorded
* measurements.
*/
static int vm_instance_verify(const char *iface_str, int devnum,
const char *instance_uuid, bool must_exist,
const struct boot_measurement *code,
const struct boot_measurement *config,
uint8_t out_hidden[VM_INSTANCE_SALT_SIZE])
{
const uint8_t key_info[] = {
'v', 'm', '-', 'i', 'n', 's', 't', 'a', 'n', 'c', 'e' };
uint8_t *record;
uint8_t *record_salt;
uint8_t *saved_record;
size_t record_size;
struct AvbOps *ops;
char devnum_str[3];
struct uuid uuid;
uint8_t sealing_key[32];
int ret;
if (uuid_str_to_bin(instance_uuid, (unsigned char *)&uuid,
UUID_STR_FORMAT_STD))
return -EINVAL;
/* Measure the formatted record to allocate large enough buffers. */
ret = vm_instance_format(code, config, /*buffer_size=*/0,
/*buffer=*/NULL, &record_size, &record_salt);
if (ret && ret != -E2BIG)
return ret;
record = malloc(record_size);
if (!record)
return -ENOMEM;
saved_record = malloc(record_size);
if (!saved_record) {
free(record);
return -ENOMEM;
}
/* Format the record into the buffer. */
ret = vm_instance_format(code, config, record_size, record,
&record_size, &record_salt);
if (ret)
return ret;
snprintf(devnum_str, sizeof(devnum_str), "%d", devnum);
ops = avb_ops_alloc(iface_str, devnum_str);
if (!ops) {
ret = -EINVAL;
goto out;
}
ret = bcc_get_sealing_key(key_info, sizeof(key_info),
sealing_key, sizeof(sealing_key));
if (ret)
goto out;
/* Load any previous data, failing if it's a different size. */
ret = vm_instance_load_entry(ops, &uuid,
sealing_key, sizeof(sealing_key),
saved_record, record_size);
if (ret == 0) {
ptrdiff_t salt_offset = record_salt - record;
/*
* Copy the assumed saved salt so the records will be
* byte-for-byte identical if they match.
*/
memcpy(record_salt, saved_record + salt_offset,
VM_INSTANCE_SALT_SIZE);
if (memcmp(record, saved_record, record_size) != 0) {
log_err("VM instance data mismatch.\n");
ret = -EINVAL;
goto out;
}
ret = BCC_VM_INSTANCE_FOUND;
} else if (ret == -ENOENT) {
if (must_exist) {
log_err("VM instance data not found.\n");
goto out;
}
/* No previous entry so create a fresh one. */
printf("Creating new VM instance.\n");
ret = vm_instance_new_salt(record_salt);
if (ret)
goto out;
ret = vm_instance_save_entry(ops, &uuid,
sealing_key, sizeof(sealing_key),
record, record_size);
if (ret) {
log_err("Failed to create VM instance.\n");
goto out;
}
ret = BCC_VM_INSTANCE_CREATED;
}
memcpy(out_hidden, record_salt, VM_INSTANCE_SALT_SIZE);
out:
avb_ops_free(ops);
bcc_clear_memory(sealing_key, sizeof(sealing_key));
bcc_clear_memory(record, record_size);
bcc_clear_memory(saved_record, record_size);
free(record);
free(saved_record);
return ret;
}
static int boot_measurement_from_avb_data(const AvbSlotVerifyData *data,
struct boot_measurement *measurement)
{
const char *partition_name;
size_t n;
/*
* Use the public key of the top-level vbmeta as the authority as this
* cannot change. The authority of any chained partitions will be
* captured in the vbmeta digest.
*/
if (avb_find_main_pubkey(data, &measurement->public_key,
&measurement->public_key_size)
== CMD_RET_FAILURE)
return -EINVAL;
/*
* Assuming we're only trying to load one thing, a top-level vbmeta with
* no chained partitions or a single partition that was chained from a
* top-level vbmeta.
*
* Calculate a hash that captures just the code we're interested in
* loading and will match:
*
* avbtool calculate_vbmeta_digest --image <vbmeta|partition>.img
*/
if (data->num_vbmeta_images == 1)
partition_name = data->vbmeta_images[0].partition_name;
else if (data->num_loaded_partitions == 1)
partition_name = data->loaded_partitions[0].partition_name;
else
return -EINVAL;
for (n = 0; n < data->num_vbmeta_images; ++n) {
AvbVBMetaData *vbmeta = &data->vbmeta_images[n];
if (strcmp(partition_name, vbmeta->partition_name) == 0) {
sha256(vbmeta->vbmeta_data, vbmeta->vbmeta_size,
measurement->digest);
return 0;
}
}
return -EINVAL;
}
/*
* Format a config descriptor conformaing to the BCC specification.
*
* BccConfigDescriptor = {
* ? -70002 : tstr, ; component name
* ? -70003 : int, ; component version
* ? -70004 : null, ; resettable
* ; Extension fields
* -71000: bstr .size 32; ; config digest
* }
*/
DiceResult format_config_descriptor(const char *component_name,
const uint8_t config_digest[AVB_SHA256_DIGEST_SIZE],
size_t buffer_size, uint8_t *buffer,
size_t *actual_size)
{
const int64_t component_name_label = -70002;
const int64_t config_digest_label = -71000;
struct CborOut out;
CborOutInit(buffer, buffer_size, &out);
CborWriteMap(config_digest ? 2 : 1, &out);
CborWriteInt(component_name_label, &out);
CborWriteTstr(component_name, &out);
if (config_digest) {
CborWriteInt(config_digest_label, &out);
CborWriteBstr(AVB_SHA256_DIGEST_SIZE, config_digest, &out);
}
*actual_size = CborOutSize(&out);
return CborOutOverflowed(&out) ? -E2BIG : 0;
}
int bcc_vm_instance_handover(const char *iface_str, int devnum,
const char *instance_uuid, bool must_exist,
const char *component_name,
enum bcc_mode bcc_mode,
const AvbSlotVerifyData *code_data,
const AvbSlotVerifyData *config_data,
const void *unverified_config,
size_t unverified_config_size)
{
uint8_t config_desc[BCC_CONFIG_DESC_SIZE];
struct boot_measurement code;
struct boot_measurement config = {};
uint8_t *new_handover = NULL;
DiceInputValues input_vals;
DiceResult res;
int instance_ret, ret;
/* Continue without the BCC if it wasn't found. */
ret = bcc_init();
if (ret == -ENOENT)
return 0;
if (ret)
return ret;
/* For now, only allow one config source. */
if (config_data && unverified_config)
return -EINVAL;
ret = boot_measurement_from_avb_data(code_data, &code);
if (ret)
return ret;
if (config_data) {
ret = boot_measurement_from_avb_data(config_data, &config);
if (ret)
return ret;
/* For now, require the same authority. */
if (config.public_key_size != code.public_key_size ||
memcmp(config.public_key, code.public_key,
code.public_key_size) != 0)
return -EINVAL;
} else if (unverified_config) {
sha256(unverified_config, unverified_config_size,
config.digest);
}
input_vals = (DiceInputValues){
.config_type = kDiceConfigTypeDescriptor,
.config_descriptor = config_desc,
.mode = bcc_to_dice_mode[bcc_mode],
};
memcpy(input_vals.code_hash, code.digest, AVB_SHA256_DIGEST_SIZE);
sha256(code.public_key, code.public_key_size, input_vals.authority_hash);
ret = format_config_descriptor(component_name, config.digest,
sizeof(config_desc), config_desc,
&input_vals.config_descriptor_size);
if (ret)
return ret;
instance_ret = vm_instance_verify(iface_str, devnum, instance_uuid,
must_exist, &code,
config_data ? &config : NULL,
input_vals.hidden);
if (instance_ret < 0) {
ret = instance_ret;
log_err("Failed to validate instance.\n");
goto out;
}
printf("Booting VM instance.\n");
new_handover = calloc(1, bcc_handover_buffer_size);
if (!new_handover)
return -ENOMEM;
res = BccHandoverMainFlow(/*context=*/NULL, bcc_handover_buffer,
bcc_handover_buffer_size, &input_vals,
bcc_handover_buffer_size, new_handover, NULL);
if (res != kDiceResultOk) {
ret = -EINVAL;
goto out;
}
/* Update the handover buffer with the new data. */
memcpy(bcc_handover_buffer, new_handover, bcc_handover_buffer_size);
out:
if (new_handover) {
bcc_clear_memory(new_handover, bcc_handover_buffer_size);
free(new_handover);
}
bcc_clear_memory(&input_vals, sizeof(input_vals));
return ret ? ret : instance_ret;
}
int bcc_vm_instance_avf_boot_state(bool *strict_boot, bool *new_instance)
{
const void *fdt;
int chosen, len;
if (!CONFIG_IS_ENABLED(ARM64)) {
*strict_boot = false;
*new_instance = false;
return 0;
}
fdt = (const void *)env_get_hex("fdtaddr", 0);
if (!fdt)
return -EINVAL;
chosen = fdt_path_offset(fdt, "/chosen");
if (chosen == -FDT_ERR_NOTFOUND) {
*strict_boot = false;
*new_instance = false;
return 0;
}
if (chosen < 0)
return -EINVAL;
fdt_getprop(fdt, chosen, "avf,strict-boot", &len);
if (len >= 0)
*strict_boot = true;
else if (len == -FDT_ERR_NOTFOUND)
*strict_boot = false;
else
return -EINVAL;
fdt_getprop(fdt, chosen, "avf,new-instance", &len);
if (len >= 0)
*new_instance = true;
else if (len == -FDT_ERR_NOTFOUND)
*new_instance = false;
else
return -EINVAL;
return 0;
}
int bcc_get_sealing_key(const uint8_t *info, size_t info_size,
uint8_t *out_key, size_t out_key_size)
{
const uint64_t cdi_attest_label = 1;
const uint64_t cdi_seal_label = 2;
struct CborIn in;
int64_t label;
size_t item_size;
const uint8_t *ptr;
int ret;
/* Make sure initialization is complete. */
ret = bcc_init();
if (ret)
return ret;
CborInInit(bcc_handover_buffer, bcc_handover_buffer_size, &in);
if (CborReadMap(&in, &item_size) != CBOR_READ_RESULT_OK ||
item_size < 3 ||
// Read the attestation CDI.
CborReadInt(&in, &label) != CBOR_READ_RESULT_OK ||
label != cdi_attest_label ||
CborReadBstr(&in, &item_size, &ptr) != CBOR_READ_RESULT_OK ||
item_size != DICE_CDI_SIZE ||
// Read the sealing CDI.
CborReadInt(&in, &label) != CBOR_READ_RESULT_OK ||
label != cdi_seal_label ||
CborReadBstr(&in, &item_size, &ptr) != CBOR_READ_RESULT_OK ||
item_size != DICE_CDI_SIZE)
return -EINVAL;
if (!HKDF(out_key, out_key_size, EVP_sha512(),
ptr, DICE_CDI_SIZE, /*salt=*/NULL, /*salt_len=*/0,
info, info_size)) {
return -EIO;
}
return 0;
}