blob: 96369771803170fe28d39a3f8e3d1f7d9cf8573b [file] [log] [blame]
// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "flashupdate/validator/key_rotate_helper.hpp"
#include "flashupdate/info.hpp"
#include "flashupdate/logging.hpp"
#include <libcr51sign/cr51_image_descriptor.h>
#include <libcr51sign/libcr51sign.h>
#include <libhoth/protocol/key_rotation.h>
#include <libhoth/transports/libhoth_dbus.h>
#include <cstdint>
#include <format>
#include <string_view>
namespace
{
std::string HothId{};
struct libhoth_device* hothDevice()
{
static struct libhoth_device* hoth_device = nullptr;
if (hoth_device)
{
return hoth_device;
}
struct libhoth_dbus_device_init_options opts = {.hoth_id = HothId.c_str()};
int rv = libhoth_dbus_open(&opts, &hoth_device);
if (rv)
{
LOG(flashupdate::LogLevel::Error, "libhoth_dbus_open failed {}", rv);
return nullptr;
}
return hoth_device;
}
inline std::string rotConfigChunkName(uint32_t chunk_typecode)
{
switch (chunk_typecode)
{
case KEY_ROTATION_CHUNK_TYPE_CODE_PKEY:
return std::string{"KEY_ROTATION_CHUNK_TYPE_CODE_PKEY"};
case KEY_ROTATION_CHUNK_TYPE_CODE_HASH:
return std::string{"KEY_ROTATION_CHUNK_TYPE_CODE_HASH"};
case KEY_ROTATION_CHUNK_TYPE_CODE_BKEY:
return std::string{"KEY_ROTATION_CHUNK_TYPE_CODE_BKEY"};
case KEY_ROTATION_CHUNK_TYPE_CODE_BASH:
return std::string{"KEY_ROTATION_CHUNK_TYPE_CODE_BASH"};
default:
return std::format("Unknown {:X}", chunk_typecode);
}
}
__attribute__((nonnull)) int chunkCountInHothRoTConfig(
struct libhoth_device* hoth_device, uint32_t chunk_typecode)
{
uint16_t chunk_count = 0;
enum key_rotation_err ret_code = libhoth_key_rotation_chunk_type_count(
hoth_device, chunk_typecode, &chunk_count);
if (ret_code)
{
LOG(flashupdate::LogLevel::Error, "Get {} chunk count failed: {}",
rotConfigChunkName(chunk_typecode), std::to_underlying(ret_code));
return -1;
}
return chunk_count;
}
struct BiosKeyInfo
{
const uint8_t* e_bytes;
size_t e_size;
const uint8_t* n_bytes;
size_t n_size;
};
template <typename T>
BiosKeyInfo
biosKeyInfoFromCr51Signature(T* cr51_signature, size_t cr51_signature_size)
{
size_t expected_signature_size = sizeof(T);
if (expected_signature_size != cr51_signature_size)
{
LOG(flashupdate::LogLevel::Error,
"signture structure has invalid size({}), expect ({})",
cr51_signature_size, expected_signature_size);
return {nullptr, 0, nullptr, 0};
}
return {reinterpret_cast<const uint8_t*>(&cr51_signature->exponent),
sizeof(cr51_signature->exponent),
reinterpret_cast<const uint8_t*>(&cr51_signature->modulus),
sizeof(cr51_signature->modulus)};
}
std::string_view cr51SignSchemeName(enum signature_scheme scheme)
{
switch (scheme)
{
case SIGNATURE_RSA2048_PKCS15:
return "SIGNATURE_RSA2048_PKCS15";
case SIGNATURE_RSA3072_PKCS15:
return "SIGNATURE_RSA3072_PKCS15";
case SIGNATURE_RSA4096_PKCS15:
return "SIGNATURE_RSA4096_PKCS15";
case SIGNATURE_RSA4096_PKCS15_SHA512:
return "SIGNATURE_RSA4096_PKCS15_SHA512";
default:
return "UNKNOWN";
}
}
__attribute__((nonnull)) bool fingerPrintOfKeyInCr51Signature(
void* ctx, enum signature_scheme scheme, const void* cr51_signature,
size_t cr51_signature_size, sha256& finger_print)
{
BiosKeyInfo bios_key_info{};
LOG(flashupdate::LogLevel::Notice,
"Calculating fingerprint of key in CR51 singature structure with scheme({}), size({})",
cr51SignSchemeName(scheme), cr51_signature_size);
switch (scheme)
{
case SIGNATURE_RSA2048_PKCS15:
{
bios_key_info = biosKeyInfoFromCr51Signature(
reinterpret_cast<const struct signature_rsa2048_pkcs15*>(
cr51_signature),
cr51_signature_size);
}
break;
case SIGNATURE_RSA3072_PKCS15:
{
bios_key_info = biosKeyInfoFromCr51Signature(
reinterpret_cast<const struct signature_rsa3072_pkcs15*>(
cr51_signature),
cr51_signature_size);
}
break;
case SIGNATURE_RSA4096_PKCS15:
case SIGNATURE_RSA4096_PKCS15_SHA512:
{
bios_key_info = biosKeyInfoFromCr51Signature(
reinterpret_cast<const struct signature_rsa4096_pkcs15*>(
cr51_signature),
cr51_signature_size);
}
break;
default:
{
LOG(flashupdate::LogLevel::Error, "Not supported scheme({})",
static_cast<int>(scheme));
}
return false;
}
// BIOS Key finger print is sha2_256(e || n)
int ec = hash_init(ctx, HASH_SHA2_256);
if (ec)
{
LOG(flashupdate::LogLevel::Error, "CR51 Hash init error: ({})", ec);
return false;
}
ec = hash_update(ctx, bios_key_info.e_bytes, bios_key_info.e_size);
if (ec)
{
LOG(flashupdate::LogLevel::Error,
"CR51 Hash update e_bytes error: ({})", ec);
return false;
}
ec = hash_update(ctx, bios_key_info.n_bytes, bios_key_info.n_size);
if (ec)
{
LOG(flashupdate::LogLevel::Error,
"CR51 Hash update n_bytes error: ({})", ec);
return false;
}
ec = hash_final(ctx, finger_print);
if (ec)
{
LOG(flashupdate::LogLevel::Error, "CR51 Hash final error: ({})", ec);
return false;
}
LOG(flashupdate::LogLevel::Notice,
"fingerprint of key in CR51 singature: {}",
flashupdate::info::bytesToHex(finger_print));
return true;
}
inline bool sha256Equal(const sha256& left, const sha256& right)
{
return (memcmp(&left, &right, sizeof(sha256)) == 0);
}
__attribute__((nonnull)) bool readRoTConfigChunk(
struct libhoth_device* hoth_device, uint32_t chunk_typecode,
int chunk_index,
struct hoth_response_key_rotation_record_read* read_response,
size_t expected_data_size, uint16_t* response_size)
{
enum key_rotation_err ret_read = libhoth_key_rotation_read_chunk_type(
hoth_device, chunk_typecode, chunk_index,
sizeof(key_rotation_chunk_header) /* skip the trunk header */,
0 /* read whole chunk data */, read_response, response_size);
if (ret_read)
{
LOG(flashupdate::LogLevel::Error, "Read {}_{} failed: ({})",
rotConfigChunkName(chunk_typecode), chunk_index,
static_cast<int>(ret_read));
return false;
}
// To be backward compatible allow in the future appending new fields in
// the Chunk, here only make sure fetched enough data the code understand
// also we need support variable length Chunk like bios_allowed_list
if (*response_size < expected_data_size)
{
LOG(flashupdate::LogLevel::Warning,
"Ignore {}_{}, as size ({}) is less than expected ({})",
rotConfigChunkName(chunk_typecode), chunk_index, *response_size,
expected_data_size);
return false;
}
return true;
}
} // namespace
namespace google
{
namespace cr51
{
void setHothId(std::string_view hoth_id)
{
HothId = hoth_id;
}
bool trustDescriptorHash(const void*, const uint8_t* descriptor_hash,
size_t hash_size)
{
// Currently all platforms BIOS updated or validatored by BMC are using
// sha256
if (hash_size != sizeof(sha256))
{
LOG(flashupdate::LogLevel::Notice,
"All trusted descriptor hash are sha256, "
"the input descriptor hash is not sha256 "
"based on hash size {}, so not trust it",
hash_size);
return false;
}
LOG(flashupdate::LogLevel::Notice, "check cr51hash: {}",
flashupdate::info::bytesToHex(
std::span<const uint8_t>(descriptor_hash, hash_size)));
struct libhoth_device* hoth_device = hothDevice();
if (!hoth_device)
{
return false;
}
int trusted_bios_hash_chunk_count = chunkCountInHothRoTConfig(
hoth_device, KEY_ROTATION_CHUNK_TYPE_CODE_BASH);
if (trusted_bios_hash_chunk_count < 0)
{
return false;
}
LOG(flashupdate::LogLevel::Notice, "Hoth RoT config defined {} {}",
trusted_bios_hash_chunk_count,
rotConfigChunkName(KEY_ROTATION_CHUNK_TYPE_CODE_BASH));
for (int chunk_index = 0; chunk_index < trusted_bios_hash_chunk_count;
++chunk_index)
{
uint16_t response_size = 0;
struct hoth_response_key_rotation_record_read read_response;
if (!readRoTConfigChunk(hoth_device, KEY_ROTATION_CHUNK_TYPE_CODE_BASH,
chunk_index, &read_response,
sizeof(struct bios_allowed_hash_list),
&response_size))
{
continue;
}
const struct bios_allowed_hash_list* trusted_bios_hash_list =
reinterpret_cast<const struct bios_allowed_hash_list*>(
&read_response.data);
uint16_t expected_data_size =
sizeof(struct bios_allowed_hash_list) +
sizeof(sha256) * trusted_bios_hash_list->hash_count;
if (response_size < expected_data_size)
{
LOG(flashupdate::LogLevel::Warning,
"Ignore variable length {}_{}, as size ({}) is less than expected ({})",
rotConfigChunkName(KEY_ROTATION_CHUNK_TYPE_CODE_BASH),
chunk_index, response_size, expected_data_size);
}
for (uint32_t hash_index = 0;
hash_index < trusted_bios_hash_list->hash_count; ++hash_index)
{
LOG(flashupdate::LogLevel::Notice, "allowed hash[{}] = {{ {} }}",
hash_index,
flashupdate::info::bytesToHex(
trusted_bios_hash_list->hash_list[hash_index]));
if (sha256Equal(trusted_bios_hash_list->hash_list[hash_index],
*reinterpret_cast<const sha256*>(descriptor_hash)))
{
LOG(flashupdate::LogLevel::Notice,
"Match allowed hash #{} in {}_{}", hash_index,
rotConfigChunkName(KEY_ROTATION_CHUNK_TYPE_CODE_BKEY),
chunk_index);
return true;
}
}
}
LOG(flashupdate::LogLevel::Notice,
"Not match any trusted bios allowed hash");
return false;
}
bool trustKeyInCr51Signature(void* ctx, enum signature_scheme scheme,
const void* cr51_signature,
size_t cr51_signature_size)
{
if (!ctx || !cr51_signature)
{
LOG(flashupdate::LogLevel::Alert,
"input ctx or cr51_signature is NULL");
return false;
}
sha256 key_fingerprint;
if (!fingerPrintOfKeyInCr51Signature(ctx, scheme, cr51_signature,
cr51_signature_size, key_fingerprint))
{
return false;
}
struct libhoth_device* hoth_device = hothDevice();
if (!hoth_device)
{
return false;
}
int trusted_bios_key_count = chunkCountInHothRoTConfig(
hoth_device, KEY_ROTATION_CHUNK_TYPE_CODE_BKEY);
if (trusted_bios_key_count < 0)
{
return false;
}
LOG(flashupdate::LogLevel::Notice, "Hoth RoT config defined {} {}",
trusted_bios_key_count,
rotConfigChunkName(KEY_ROTATION_CHUNK_TYPE_CODE_BKEY));
for (int chunk_index = 0; chunk_index < trusted_bios_key_count;
++chunk_index)
{
uint16_t response_size = 0;
struct hoth_response_key_rotation_record_read read_response;
if (!readRoTConfigChunk(hoth_device, KEY_ROTATION_CHUNK_TYPE_CODE_BKEY,
chunk_index, &read_response,
sizeof(struct bios_verifiction_key_fingerprint),
&response_size))
{
continue;
}
const struct bios_verifiction_key_fingerprint* trusted_bios_key =
reinterpret_cast<const struct bios_verifiction_key_fingerprint*>(
&read_response.data);
LOG(flashupdate::LogLevel::Notice,
"trusted bios key finger print in {}_{}: {}",
rotConfigChunkName(KEY_ROTATION_CHUNK_TYPE_CODE_BKEY), chunk_index,
flashupdate::info::bytesToHex(trusted_bios_key->key_fingerprint));
if (sha256Equal(trusted_bios_key->key_fingerprint, key_fingerprint))
{
LOG(flashupdate::LogLevel::Notice,
"Match trusted bios key finger print in {}_{}",
rotConfigChunkName(KEY_ROTATION_CHUNK_TYPE_CODE_BKEY),
chunk_index);
return true;
}
}
LOG(flashupdate::LogLevel::Notice, "Not match any trusted bios key");
return false;
}
// For Testing or debug
bool alwaysTrustDescriptorHash(const void* ctx, const uint8_t* descriptor_hash,
size_t hash_size)
{
bool trust_by_rot = trustDescriptorHash(ctx, descriptor_hash, hash_size);
LOG(flashupdate::LogLevel::Notice,
"Override RoT decision {} to always trust", trust_by_rot);
return true;
}
bool alwaysTrustKeyInCr51Signature(void* ctx, enum signature_scheme scheme,
const void* cr51_signature,
size_t cr51_signature_size)
{
bool trust_by_rot = trustKeyInCr51Signature(ctx, scheme, cr51_signature,
cr51_signature_size);
LOG(flashupdate::LogLevel::Notice,
"Override RoT decision {} to always trust", trust_by_rot);
return true;
}
bool neverTrustDescriptorHash(const void* ctx, const uint8_t* descriptor_hash,
size_t hash_size)
{
bool trust_by_rot = trustDescriptorHash(ctx, descriptor_hash, hash_size);
LOG(flashupdate::LogLevel::Notice,
"Override RoT decision {} to never trust", trust_by_rot);
return false;
}
bool neverTrustKeyInCr51Signature(void* ctx, enum signature_scheme scheme,
const void* cr51_signature,
size_t cr51_signature_size)
{
bool trust_by_rot = trustKeyInCr51Signature(ctx, scheme, cr51_signature,
cr51_signature_size);
LOG(flashupdate::LogLevel::Notice,
"Override RoT decision {} to never trust", trust_by_rot);
return false;
}
bool isBiosKeyRotationSupport()
{
struct libhoth_device* hoth_device = hothDevice();
if (!hoth_device)
{
return false;
}
return chunkCountInHothRoTConfig(hoth_device,
KEY_ROTATION_CHUNK_TYPE_CODE_BKEY) > 0;
}
} // namespace cr51
} // namespace google