blob: 2fe6c01bc91eb3d828082860e1f99147e291276c [file]
// 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 "config.h"
#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 <filesystem>
#include <format>
#include <fstream>
#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()};
libhoth_error rv = libhoth_dbus_open(&opts, &hoth_device);
if (rv != LIBHOTH_OK)
{
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;
libhoth_error ret_code = libhoth_key_rotation_chunk_type_count(
hoth_device, chunk_typecode, &chunk_count);
if (ret_code != LIBHOTH_OK)
{
LOG(flashupdate::LogLevel::Error, "Get {} chunk count failed: {}",
rotConfigChunkName(chunk_typecode), 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 signature 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 signature: {}",
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)
{
libhoth_error 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 != LIBHOTH_OK)
{
LOG(flashupdate::LogLevel::Error, "Read {}_{} failed: ({})",
rotConfigChunkName(chunk_typecode), chunk_index, 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;
}
void writeBiosKeyMetricFiles(std::string_view validation_method,
uint32_t image_family, uint32_t version,
uint64_t validation_key_data)
{
std::string dir = BIOS_KEY_METRICS_DIR;
std::string tmp_dir = dir + ".tmp";
std::error_code ec;
// 1. Create and write to a .tmp directory first. We use the
// write-to-tmp-and-rename pattern to ensure that the metrics update is
// atomic. This prevents a telemetry reader from experiencing race
// conditions, such as reading partially-written files (partial writes) or a
// mix of old and new files (group inconsistency).
std::filesystem::create_directories(tmp_dir, ec);
if (ec)
{
LOG(flashupdate::LogLevel::Error, "Failed to create directory {}: {}",
tmp_dir, ec.message());
return;
}
auto write_file = [](const std::filesystem::path& path,
const std::string_view content) {
std::ofstream file(path, std::ios::out | std::ios::trunc);
if (!file.is_open())
{
LOG(flashupdate::LogLevel::Error,
"Failed to open file for writing: {}", path.string());
return;
}
file << content << "\n";
};
std::filesystem::path tmp_dir_path(tmp_dir);
write_file(tmp_dir_path / "bios_key_validation_method", validation_method);
write_file(tmp_dir_path / "bios_key_image_family",
std::to_string(image_family));
write_file(tmp_dir_path / "bios_key_version", std::to_string(version));
write_file(tmp_dir_path / "bios_key_validation_key_data",
std::format("0x{:016x}", validation_key_data));
// 2. Delete the old target directory
std::filesystem::remove_all(dir, ec);
if (ec && ec != std::errc::no_such_file_or_directory)
{
LOG(flashupdate::LogLevel::Error,
"Failed to remove old directory {}: {}", dir, ec.message());
return;
}
// 3. Rename tmp directory to target directory
std::filesystem::rename(tmp_dir, dir, ec);
if (ec)
{
LOG(flashupdate::LogLevel::Error,
"Failed to rename directory {} to {}: {}", tmp_dir, dir,
ec.message());
return;
}
LOG(flashupdate::LogLevel::Notice,
"Write bios key metric files: {} {} {} {}", validation_method,
image_family, version, std::format("0x{:016x}", validation_key_data));
}
void clearBiosKeyMetricFiles()
{
std::string dir = BIOS_KEY_METRICS_DIR;
std::error_code ec;
std::filesystem::remove_all(dir, ec);
if (ec && ec != std::errc::no_such_file_or_directory)
{
LOG(flashupdate::LogLevel::Error,
"Failed to clear metrics directory {}: {}", dir, ec.message());
}
}
const uint8_t* getModulusFromCr51Signature(enum signature_scheme scheme,
const void* cr51_signature)
{
if (!cr51_signature)
{
return nullptr;
}
switch (scheme)
{
case SIGNATURE_RSA2048_PKCS15:
return reinterpret_cast<const struct signature_rsa2048_pkcs15*>(
cr51_signature)
->modulus;
case SIGNATURE_RSA3072_PKCS15:
return reinterpret_cast<const struct signature_rsa3072_pkcs15*>(
cr51_signature)
->modulus;
case SIGNATURE_RSA4096_PKCS15:
case SIGNATURE_RSA4096_PKCS15_SHA512:
return reinterpret_cast<const struct signature_rsa4096_pkcs15*>(
cr51_signature)
->modulus;
default:
return nullptr;
}
}
} // 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)
{
writeBiosKeyMetricFiles("EMBEDDED_KEY", 0, 0, 0);
return false;
}
LOG(flashupdate::LogLevel::Notice, "Hoth RoT config defined {} {}",
trusted_bios_key_count,
rotConfigChunkName(KEY_ROTATION_CHUNK_TYPE_CODE_BKEY));
struct libcr51sign_ctx* cr51signCtx =
reinterpret_cast<struct libcr51sign_ctx*>(ctx);
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))
{
uint32_t imageFamily = cr51signCtx->descriptor.image_family;
if (imageFamily != trusted_bios_key->image_family &&
imageFamily != IMAGE_FAMILY_ALL &&
trusted_bios_key->image_family != IMAGE_FAMILY_ALL)
{
LOG(flashupdate::LogLevel::Warning,
"Match trusted bios key finger print in {}_{}, but mismatch IMAGE_FAMILY {} vs. {}",
rotConfigChunkName(KEY_ROTATION_CHUNK_TYPE_CODE_BKEY),
chunk_index, imageFamily, trusted_bios_key->image_family);
continue;
}
LOG(flashupdate::LogLevel::Notice,
"Match trusted bios key finger print in {}_{}",
rotConfigChunkName(KEY_ROTATION_CHUNK_TYPE_CODE_BKEY),
chunk_index);
uint32_t version = 0;
struct hoth_response_key_rotation_record_version record_version;
libhoth_error version_err =
libhoth_key_rotation_get_version(hoth_device, &record_version);
if (version_err == LIBHOTH_OK)
{
version = record_version.version;
}
else
{
LOG(flashupdate::LogLevel::Error,
"Get key rotation version failed: {}", version_err);
}
uint64_t val_key_data = 0;
const uint8_t* modulus =
getModulusFromCr51Signature(scheme, cr51_signature);
if (modulus)
{
for (size_t i = 0; i < 8; ++i)
{
val_key_data = (val_key_data << 8) | modulus[i];
}
}
writeBiosKeyMetricFiles("PAYLOAD_KEY",
trusted_bios_key->image_family, version,
val_key_data);
return true;
}
}
LOG(flashupdate::LogLevel::Notice, "Not match any trusted bios key");
writeBiosKeyMetricFiles("EMBEDDED_KEY", 0, 0, 0);
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();
// Clear any stale metric files written by previous runs.
clearBiosKeyMetricFiles();
if (hoth_device && chunkCountInHothRoTConfig(
hoth_device, KEY_ROTATION_CHUNK_TYPE_CODE_BKEY) > 0)
{
return true;
}
// If rotation is not supported, we immediately record that we are
// using the EMBEDDED_KEY
writeBiosKeyMetricFiles("EMBEDDED_KEY", 0, 0, 0);
return false;
}
} // namespace cr51
} // namespace google