flashupdate: Support key rotation If the RoT-Config support BIOS Key rotation, the Cr51Validator will use BIOS trust bundle information fetched from RoT config to validate the BIOS CR51 descriptor. The BIOS trust bundle will be: * the trusted signature verification (public) key's finger printer. * the allowed cr51 descriptor hash If the RoT-Config does not support, will fallback to use key information built-in gBMC firmware. RoT-Config support BIOS key rotation means the RoT-Config record contain at least one public key finger printer chunk. For dev signed bios, the dev signature verification key will only be built-in gBMC image. So when dev signed bios is allowed, the built-in key will be tried if image cannot be valided by keys within RoT-Config. Tested: with testing bios RoT configraution Google-Bug-Id: 421797519 Change-Id: I7ee686b94c49113ee8f406f19628851de21868b6 Signed-off-by: Dan Zhang <zhdaniel@google.com>
diff --git a/subprojects/flashupdate/include/flashupdate/validator/cr51.hpp b/subprojects/flashupdate/include/flashupdate/validator/cr51.hpp index f32ad6c..20c5e3d 100644 --- a/subprojects/flashupdate/include/flashupdate/validator/cr51.hpp +++ b/subprojects/flashupdate/include/flashupdate/validator/cr51.hpp
@@ -21,6 +21,7 @@ #include <flashupdate/config.hpp> #include <flashupdate/validator.hpp> +#include <memory> #include <optional> #include <span> #include <string> @@ -33,7 +34,7 @@ { namespace cr51 { - +constexpr std::string_view kRoTConfigKeyRing = "RoT-Config"; /** @class Cr51SignValidator * @brief Validate the firmware with CR51 signature. */ @@ -56,13 +57,25 @@ /** @brief Validate the CR51 Sign Descriptor and return the list of image * regions * - * @param[in] ctx Cr51 signature context + * @param[in] ctx Cr51 signature context + * @param[in] useRoTConfig Whether use trusted information (keys and + * allowed list) in RoT Config * * @return std::nullopt if failed to validate the descriptor, otherwise * return the validated regions */ virtual std::optional<struct libcr51sign_validated_regions> - validateDescriptor(struct libcr51sign_ctx* ctx) = 0; + validateDescriptor(struct libcr51sign_ctx* ctx, bool useRoTConfig) = 0; + + /** @brief Check if BIOS key rotation is supported by the RoT. + * @return true if supported, false otherwise. + */ + virtual bool isBiosKeyRotationSupport() = 0; + + /** @brief Check if dev signed image is allowed. + * @return true if allowed, false otherwise. + */ + virtual bool isDevSignedImageAllowed() = 0; }; class Cr51SignValidatorIpml : public Cr51SignValidator @@ -79,7 +92,11 @@ std::span<std::byte> imageDescriptor) override; std::optional<struct libcr51sign_validated_regions> - validateDescriptor(struct libcr51sign_ctx* ctx) override; + validateDescriptor(struct libcr51sign_ctx* ctx, bool useRoT) override; + + bool isBiosKeyRotationSupport() override; + + bool isDevSignedImageAllowed() override; private: bool prodToDev; @@ -103,13 +120,26 @@ class Cr51 : public Validator { public: - Cr51(Config::Cr51 config = Config::Cr51()) : + // Production constructor + explicit Cr51(Config::Cr51 config = Config::Cr51()) : imageFamily( config.imageFamily.value_or(image_family::IMAGE_FAMILY_ALL)), hash(std::vector<uint8_t>(SHA256_DIGEST_LENGTH)), - cr51Validator( - config.prodToDev, config.productionMode, - config.imageFamily.value_or(image_family::IMAGE_FAMILY_ALL)) {}; + cr51ValidatorOwner( + std::make_unique<google::cr51::Cr51SignValidatorIpml>( + config.prodToDev, config.productionMode, + config.imageFamily.value_or(image_family::IMAGE_FAMILY_ALL))), + cr51Validator(*cr51ValidatorOwner) + {} + + // Constructor for testing with a mock validator. + Cr51(google::cr51::Cr51SignValidator& validator, + Config::Cr51 config = Config::Cr51()) : + imageFamily( + config.imageFamily.value_or(image_family::IMAGE_FAMILY_ALL)), + hash(std::vector<uint8_t>(SHA256_DIGEST_LENGTH)), + cr51Validator(validator) + {} virtual ~Cr51() = default; bool validateImage(flasher::Reader& reader, @@ -142,6 +172,9 @@ } private: + std::optional<libcr51sign_validated_regions> + validateImageWithBuiltInKeys(std::string& usedKeys); + struct image_descriptor imageDescriptor(flasher::ModArgs mod, uint32_t offset, uint32_t size); @@ -160,7 +193,8 @@ std::vector<ImageRegion> persistentRegion; // CR51 Variables - google::cr51::Cr51SignValidatorIpml cr51Validator; + std::unique_ptr<google::cr51::Cr51SignValidator> cr51ValidatorOwner; + google::cr51::Cr51SignValidator& cr51Validator; static constexpr int kSignatureRsa4096Pkcs15KeyLength = 512; struct hash_ctx shaContext;
diff --git a/subprojects/flashupdate/include/flashupdate/validator/key_rotate_helper.hpp b/subprojects/flashupdate/include/flashupdate/validator/key_rotate_helper.hpp new file mode 100644 index 0000000..77d22bf --- /dev/null +++ b/subprojects/flashupdate/include/flashupdate/validator/key_rotate_helper.hpp
@@ -0,0 +1,43 @@ +// 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. + +#pragma once + +#include <libcr51sign/cr51_image_descriptor.h> + +#include <string_view> + +namespace google +{ +namespace cr51 +{ +void setHothId(std::string_view hoth_id); +bool trustDescriptorHash(const void*, const uint8_t*, size_t); +bool trustKeyInCr51Signature(void* ctx, enum signature_scheme scheme, + const void* cr51_signature, + size_t cr51_signature_size); + +bool alwaysTrustDescriptorHash(const void*, const uint8_t*, size_t); +bool alwaysTrustKeyInCr51Signature(void* ctx, enum signature_scheme scheme, + const void* cr51_signature, + size_t cr51_signature_size); + +bool neverTrustDescriptorHash(const void*, const uint8_t*, size_t); +bool neverTrustKeyInCr51Signature(void* ctx, enum signature_scheme scheme, + const void* cr51_signature, + size_t cr51_signature_size); + +bool isBiosKeyRotationSupport(); +} // namespace cr51 +} // namespace google
diff --git a/subprojects/flashupdate/src/args.cpp b/subprojects/flashupdate/src/args.cpp index 6dc72d3..7de4c8e 100644 --- a/subprojects/flashupdate/src/args.cpp +++ b/subprojects/flashupdate/src/args.cpp
@@ -97,7 +97,8 @@ Args::Args() { - validatorHelperPtr = std::make_unique<validator::cr51::Cr51>(); + config.cr51 = Config::Cr51(); + validatorHelperPtr = std::make_unique<validator::cr51::Cr51>(*config.cr51); validatorHelper = validatorHelperPtr.get(); flashHelperPtr = std::make_unique<flash::Flash>();
diff --git a/subprojects/flashupdate/src/meson.build b/subprojects/flashupdate/src/meson.build index 89150af..9319b8f 100644 --- a/subprojects/flashupdate/src/meson.build +++ b/subprojects/flashupdate/src/meson.build
@@ -35,11 +35,15 @@ stdplus_dep, ] +# Use libhoth to talk to hoth +libhoth_dep = dependency('libhoth') + libflashupdate_deps = [ flasher_dep, json_dep, stdplus_dep, libcr51sign_dep, + libhoth_dep, ] conf_data = configuration_data() @@ -71,6 +75,7 @@ 'ops/write.cpp', 'ops/verify_staging.cpp', 'validator/cr51.cpp', + 'validator/key_rotate_helper.cpp', 'logging.cpp', config_h, version: meson.project_version(),
diff --git a/subprojects/flashupdate/src/ops/write.cpp b/subprojects/flashupdate/src/ops/write.cpp index 1f127de..7e0969f 100644 --- a/subprojects/flashupdate/src/ops/write.cpp +++ b/subprojects/flashupdate/src/ops/write.cpp
@@ -270,7 +270,7 @@ else // Update the Staged version the the State to STAGED. // - // Flashing to secondary flash will save the has of CR51 image descriptor + // Flashing to secondary flash will save the hash of CR51 image descriptor // to the EEPROM for the final check when write to primary flash. { info.stage = version::Version(helper->imageVersion());
diff --git a/subprojects/flashupdate/src/validator/cr51.cpp b/subprojects/flashupdate/src/validator/cr51.cpp index bdc7d7e..8700aa3 100644 --- a/subprojects/flashupdate/src/validator/cr51.cpp +++ b/subprojects/flashupdate/src/validator/cr51.cpp
@@ -14,6 +14,8 @@ #include "config.h" +#include "flashupdate/validator/key_rotate_helper.hpp" + #include <fcntl.h> #include <libcr51sign/cr51_image_descriptor.h> #include <libcr51sign/libcr51sign.h> @@ -219,7 +221,8 @@ } std::optional<struct libcr51sign_validated_regions> - Cr51SignValidatorIpml::validateDescriptor(struct libcr51sign_ctx* ctx) + Cr51SignValidatorIpml::validateDescriptor(struct libcr51sign_ctx* ctx, + bool useRoTConfig) { // Disabled stderr for all info messages fpos_t pos; @@ -246,6 +249,15 @@ intf.read = &ReadFromFd; intf.retrieve_stored_image_mauv_data = &ReadImageMauv; intf.store_new_image_mauv_data = &WriteImageMauv; + if (useRoTConfig) + { + // use keys in RoT Config, make sure don't provide key in ctx + ctx->keyring = nullptr; + intf.trust_descriptor_hash = trustDescriptorHash; + intf.trust_key_in_signature_structure = trustKeyInCr51Signature; + intf.verify_rsa_signature_with_modulus_and_exponent = + &verify_rsa_signature_with_modulus_and_exponent; + } auto returnTrue = []() { return true; }; auto returnFalse = []() { return false; }; @@ -275,6 +287,16 @@ return imageRegions; } +bool Cr51SignValidatorIpml::isBiosKeyRotationSupport() +{ + return google::cr51::isBiosKeyRotationSupport(); +} + +bool Cr51SignValidatorIpml::isDevSignedImageAllowed() +{ + return (prodToDev || !productionMode); +} + } // namespace cr51 } // namespace google @@ -303,6 +325,24 @@ descriptor.image_subpoint); } +std::optional<libcr51sign_validated_regions> + Cr51::validateImageWithBuiltInKeys(std::string& usedKeys) +{ + std::optional<libcr51sign_validated_regions> maybeImageRegions; + for (const auto& key : keys) + { + context.keyring = key.data(); + maybeImageRegions = cr51Validator.validateDescriptor(&context, false); + if (maybeImageRegions) + { + LOG(LogLevel::Notice, "CR51 sign is valid using {}", key); + break; + } + usedKeys += key + ","; + } + return maybeImageRegions; +} + bool Cr51::validateImage(flasher::Reader& reader, const std::vector<std::string>& keys) { @@ -331,11 +371,6 @@ struct libcr51sign_validated_regions imageRegions; - if (keys.empty()) - { - throw std::runtime_error("no valid validation key available"); - } - std::unique_ptr<flasher::File> mauv = nullptr; if (std::string(CR51_MAUV_PATH) != "") { @@ -350,17 +385,35 @@ std::optional<libcr51sign_validated_regions> maybeImageRegions; std::string usedKeys; - for (const auto& key : keys) + + if (cr51Validator.isBiosKeyRotationSupport()) { - context.keyring = key.data(); - maybeImageRegions = cr51Validator.validateDescriptor(&context); + LOG(LogLevel::Notice, "RoT-Config support BIOS Key Rotation"); + usedKeys = google::cr51::kRoTConfigKeyRing; + maybeImageRegions = cr51Validator.validateDescriptor(&context, true); + if (maybeImageRegions) { - LOG(LogLevel::Notice, "CR51 sign is valid using {}", key); - break; + LOG(LogLevel::Notice, "CR51 sign is valid using {}", + google::cr51::kRoTConfigKeyRing); } - usedKeys += key + ","; + else if (cr51Validator.isDevSignedImageAllowed()) + { + // gBMC dev build will normally contain bios dev key which will not + // be in RoT-Config + LOG(LogLevel::Notice, + "CR51 sign is NOT valid using {}, but dev signed image is allowed, try built-in keys", + google::cr51::kRoTConfigKeyRing); + maybeImageRegions = validateImageWithBuiltInKeys(usedKeys); + } } + else + { + LOG(LogLevel::Notice, + "RoT-config not support BIOS Key Rotation, try built-in keys"); + maybeImageRegions = validateImageWithBuiltInKeys(usedKeys); + } + mauvManager = nullptr; if (!maybeImageRegions)
diff --git a/subprojects/flashupdate/src/validator/key_rotate_helper.cpp b/subprojects/flashupdate/src/validator/key_rotate_helper.cpp new file mode 100644 index 0000000..9636977 --- /dev/null +++ b/subprojects/flashupdate/src/validator/key_rotate_helper.cpp
@@ -0,0 +1,453 @@ +// 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
diff --git a/subprojects/flashupdate/test/meson.build b/subprojects/flashupdate/test/meson.build index 5ebd841..6ae4cb6 100644 --- a/subprojects/flashupdate/test/meson.build +++ b/subprojects/flashupdate/test/meson.build
@@ -74,21 +74,23 @@ 'Tests enabled but flashupdate_version_test cannot build') endif -gtests = [ - 'info', - 'invalidate', - 'update_state', - 'update_version', - 'hash_descriptor', - 'read', - 'write', - 'verify_staging', - 'fetch_version', - 'erase', -] +tests = { + 'info': 'ops/info.cpp', + 'invalidate': 'ops/invalidate.cpp', + 'update_state': 'ops/update_state.cpp', + 'update_version': 'ops/update_version.cpp', + 'hash_descriptor': 'ops/hash_descriptor.cpp', + 'read': 'ops/read.cpp', + 'write': 'ops/write.cpp', + 'verify_staging': 'ops/verify_staging.cpp', + 'fetch_version': 'ops/fetch_version.cpp', + 'erase': 'ops/erase.cpp', + 'key_rotate_helper': 'validator/key_rotate_helper.cpp', + 'cr51_validator': 'validator/cr51.cpp', +} -foreach t : gtests - test(t, executable(t.underscorify(), 'ops/' + t + '.cpp', +foreach name, file : tests + test(name, executable(name.underscorify(), file, build_by_default: false, implicit_include_directories: false, dependencies: [libflashupdate, stdplus_gtest_dep, gtest, gmock]))
diff --git a/subprojects/flashupdate/test/ops/write.cpp b/subprojects/flashupdate/test/ops/write.cpp index 6e7bae9..b1876f2 100644 --- a/subprojects/flashupdate/test/ops/write.cpp +++ b/subprojects/flashupdate/test/ops/write.cpp
@@ -190,7 +190,6 @@ updateInfo.stagingIndex = 0; Args args; - args.config.cr51 = Config::Cr51(); args.primary = true; args.file.emplace(createTestBin()); args.stagingIndex = 0; @@ -434,7 +433,6 @@ createFakeEeprom(args, filename); // Allow unsigned image to dev signed installs - args.config.cr51 = Config::Cr51(); args.config.cr51->unsignedToDev = true; validator::Mock validatorMockHelper;
diff --git a/subprojects/flashupdate/test/validator/cr51.cpp b/subprojects/flashupdate/test/validator/cr51.cpp new file mode 100644 index 0000000..869e0da --- /dev/null +++ b/subprojects/flashupdate/test/validator/cr51.cpp
@@ -0,0 +1,191 @@ +// 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 <flasher/file/memory.hpp> +#include <flashupdate/validator/cr51.hpp> + +#include <memory> +#include <vector> + +#include <gmock/gmock.h> +#include <gtest/gtest.h> + +using ::testing::_; +using ::testing::Invoke; +using ::testing::Return; + +namespace google::cr51 +{ + +// Mock for the Cr51SignValidator dependency +class MockCr51SignValidator : public Cr51SignValidator +{ + public: + MOCK_METHOD(std::span<const uint8_t>, hashDescriptor, + (struct libcr51sign_ctx*, std::span<std::byte>), (override)); + MOCK_METHOD(std::optional<struct libcr51sign_validated_regions>, + validateDescriptor, (struct libcr51sign_ctx*, bool), + (override)); + MOCK_METHOD(bool, isBiosKeyRotationSupport, (), (override)); + MOCK_METHOD(bool, isDevSignedImageAllowed, (), (override)); +}; + +} // namespace google::cr51 + +namespace flashupdate::validator::cr51 +{ + +class Cr51VerifyTest : public ::testing::Test +{ + protected: + Cr51VerifyTest() : + cr51ValidatorMock( + std::make_unique< + ::testing::StrictMock<google::cr51::MockCr51SignValidator>>()), + cr51(*cr51ValidatorMock) + { + // Provide some dummy data for the reader + std::vector<std::byte> imageData(1024, std::byte{0}); + reader.writeAtExact(imageData, 0); + } + + flasher::file::Memory reader; + const std::vector<std::string> fallbackKeys = {"key1.pem", "key2.pem"}; + std::unique_ptr<google::cr51::MockCr51SignValidator> cr51ValidatorMock; + Cr51 cr51; +}; + +TEST_F(Cr51VerifyTest, RoTSupported_ValidationSucceeds) +{ + EXPECT_CALL(*cr51ValidatorMock, isBiosKeyRotationSupport()) + .WillOnce(Return(true)); + + // Neither of these should be called if RoT validation succeeds. + EXPECT_CALL(*cr51ValidatorMock, isDevSignedImageAllowed()).Times(0); + EXPECT_CALL(*cr51ValidatorMock, validateDescriptor(_, false)).Times(0); + + auto successfulValidation = [](libcr51sign_ctx* ctx, bool /*useRoT*/) { + ctx->descriptor.descriptor_offset = 0; + ctx->descriptor.descriptor_area_size = 128; + ctx->descriptor.image_major = 1; + ctx->descriptor.image_minor = 2; + ctx->descriptor.image_point = 3; + ctx->descriptor.image_subpoint = 4; + ctx->descriptor.hash_type = HASH_SHA2_256; + return libcr51sign_validated_regions{}; + }; + + EXPECT_CALL(*cr51ValidatorMock, validateDescriptor(_, true)) + .WillOnce(Invoke(successfulValidation)); + EXPECT_CALL(*cr51ValidatorMock, hashDescriptor(_, _)) + .WillOnce(Return(std::span<const uint8_t>{})); + EXPECT_TRUE(cr51.validateImage(reader, fallbackKeys)); +} + +TEST_F(Cr51VerifyTest, RoTSupported_RoTValidationFails_AndDevNotAllowed) +{ + EXPECT_CALL(*cr51ValidatorMock, isBiosKeyRotationSupport()) + .WillOnce(Return(true)); + EXPECT_CALL(*cr51ValidatorMock, validateDescriptor(_, true)) + .WillOnce(Return(std::nullopt)); + EXPECT_CALL(*cr51ValidatorMock, isDevSignedImageAllowed()) + .WillOnce(Return(false)); + // Fallback validation should not be called. + EXPECT_CALL(*cr51ValidatorMock, validateDescriptor(_, false)).Times(0); + EXPECT_FALSE(cr51.validateImage(reader, fallbackKeys)); +} + +TEST_F(Cr51VerifyTest, + RoTSupported_RoTValidationFails_DevAllowed_FallbackSucceeds) +{ + EXPECT_CALL(*cr51ValidatorMock, isBiosKeyRotationSupport()) + .WillRepeatedly(Return(true)); + EXPECT_CALL(*cr51ValidatorMock, validateDescriptor(_, true)) + .WillOnce(Return(std::nullopt)); + EXPECT_CALL(*cr51ValidatorMock, isDevSignedImageAllowed()) + .WillOnce(Return(true)); + + auto successfulValidation = [](libcr51sign_ctx* ctx, bool /*useRoT*/) { + ctx->descriptor.descriptor_offset = 0; + ctx->descriptor.descriptor_area_size = 128; + ctx->descriptor.image_major = 1; + ctx->descriptor.image_minor = 2; + ctx->descriptor.image_point = 3; + ctx->descriptor.image_subpoint = 4; + ctx->descriptor.hash_type = HASH_SHA2_256; + return libcr51sign_validated_regions{}; + }; + + EXPECT_CALL(*cr51ValidatorMock, validateDescriptor(_, false)) + .WillOnce(Invoke(successfulValidation)); + EXPECT_CALL(*cr51ValidatorMock, hashDescriptor(_, _)) + .WillOnce(Return(std::span<const uint8_t>{})); + EXPECT_TRUE(cr51.validateImage(reader, fallbackKeys)); +} + +TEST_F(Cr51VerifyTest, RoTSupported_RoTValidationFails_DevAllowed_FallbackFails) +{ + EXPECT_CALL(*cr51ValidatorMock, isBiosKeyRotationSupport()) + .WillRepeatedly(Return(true)); + EXPECT_CALL(*cr51ValidatorMock, validateDescriptor(_, true)) + .WillOnce(Return(std::nullopt)); + EXPECT_CALL(*cr51ValidatorMock, isDevSignedImageAllowed()) + .WillOnce(Return(true)); + + // Fallback validation also fails. + EXPECT_CALL(*cr51ValidatorMock, validateDescriptor(_, false)) + .Times(fallbackKeys.size()) + .WillRepeatedly(Return(std::nullopt)); + + EXPECT_FALSE(cr51.validateImage(reader, fallbackKeys)); +} + +TEST_F(Cr51VerifyTest, RoTNotSupported_FallbackSucceeds) +{ + EXPECT_CALL(*cr51ValidatorMock, isBiosKeyRotationSupport()) + .WillRepeatedly(Return(false)); + EXPECT_CALL(*cr51ValidatorMock, isDevSignedImageAllowed()).Times(0); + + // The RoT path validateDescriptor(_, true) should not be called. + auto successfulValidation = [](libcr51sign_ctx* ctx, bool /*useRoT*/) { + ctx->descriptor.descriptor_offset = 0; + ctx->descriptor.descriptor_area_size = 128; + ctx->descriptor.image_major = 1; + ctx->descriptor.image_minor = 2; + ctx->descriptor.image_point = 3; + ctx->descriptor.image_subpoint = 4; + ctx->descriptor.hash_type = HASH_SHA2_256; + return libcr51sign_validated_regions{}; + }; + + EXPECT_CALL(*cr51ValidatorMock, validateDescriptor(_, false)) + .WillOnce(Invoke(successfulValidation)); + EXPECT_CALL(*cr51ValidatorMock, hashDescriptor(_, _)) + .WillOnce(Return(std::span<const uint8_t>{})); + EXPECT_TRUE(cr51.validateImage(reader, fallbackKeys)); +} + +TEST_F(Cr51VerifyTest, RoTNotSupported_FallbackFails) +{ + EXPECT_CALL(*cr51ValidatorMock, isBiosKeyRotationSupport()) + .WillRepeatedly(Return(false)); + + // The RoT path validateDescriptor(_, true) should not be called. + EXPECT_CALL(*cr51ValidatorMock, validateDescriptor(_, false)) + .Times(fallbackKeys.size()) + .WillRepeatedly(Return(std::nullopt)); + EXPECT_FALSE(cr51.validateImage(reader, fallbackKeys)); +} + +} // namespace flashupdate::validator::cr51
diff --git a/subprojects/flashupdate/test/validator/key_rotate_helper.cpp b/subprojects/flashupdate/test/validator/key_rotate_helper.cpp new file mode 100644 index 0000000..308f077 --- /dev/null +++ b/subprojects/flashupdate/test/validator/key_rotate_helper.cpp
@@ -0,0 +1,655 @@ +// 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 <libcr51sign/cr51_image_descriptor.h> +#include <libcr51sign/libcr51sign.h> +#include <libhoth/protocol/key_rotation.h> +#include <libhoth/transports/libhoth_dbus.h> + +#include <flashupdate/validator/key_rotate_helper.hpp> + +#include <memory> + +#include <gmock/gmock.h> +#include <gtest/gtest.h> + +using ::testing::_; +using ::testing::DoAll; +using ::testing::Return; +using ::testing::SetArgPointee; +using ::testing::SetArrayArgument; + +// Mocking C functions from libhoth and libcr51sign +class MockApi +{ + public: + virtual ~MockApi() = default; + MOCK_METHOD(int, libhoth_dbus_open, + (const struct libhoth_dbus_device_init_options* opts, + struct libhoth_device** hoth_device)); + MOCK_METHOD(enum key_rotation_err, libhoth_key_rotation_chunk_type_count, + (struct libhoth_device * hoth_device, uint32_t chunk_typecode, + uint16_t* chunk_count)); + MOCK_METHOD(enum key_rotation_err, libhoth_key_rotation_read_chunk_type, + (struct libhoth_device * dev, uint32_t chunk_typecode, + uint32_t chunk_index, uint16_t offset, uint16_t size, + struct hoth_response_key_rotation_record_read* read_response, + uint16_t* response_size)); + MOCK_METHOD(int, hash_init, (void* ctx, enum hash_type type)); + MOCK_METHOD(int, hash_update, (void* ctx, const uint8_t* data, size_t len)); + MOCK_METHOD(int, hash_final, (void* ctx, uint8_t* digest)); +}; + +// Global mock object +static std::unique_ptr<MockApi> mock_api; + +// C function trampolines to the mock object +extern "C" +{ +int libhoth_dbus_open(const struct libhoth_dbus_device_init_options* opts, + struct libhoth_device** hoth_device) +{ + if (mock_api) + { + return mock_api->libhoth_dbus_open(opts, hoth_device); + } + return -1; // Should not happen in tests +} + +enum key_rotation_err libhoth_key_rotation_chunk_type_count( + struct libhoth_device* hoth_device, uint32_t chunk_typecode, + uint16_t* chunk_count) +{ + if (mock_api) + { + return mock_api->libhoth_key_rotation_chunk_type_count( + hoth_device, chunk_typecode, chunk_count); + } + return KEY_ROTATION_ERR; +} + +enum key_rotation_err libhoth_key_rotation_read_chunk_type( + struct libhoth_device* dev, uint32_t chunk_typecode, uint32_t chunk_index, + uint16_t offset, uint16_t size, + struct hoth_response_key_rotation_record_read* read_response, + uint16_t* response_size) +{ + if (mock_api) + { + return mock_api->libhoth_key_rotation_read_chunk_type( + dev, chunk_typecode, chunk_index, offset, size, read_response, + response_size); + } + return KEY_ROTATION_ERR; +} + +int hash_init(void* ctx, enum hash_type type) +{ + if (mock_api) + { + return mock_api->hash_init(ctx, type); + } + return -1; +} + +int hash_update(void* ctx, const uint8_t* data, size_t len) +{ + if (mock_api) + { + return mock_api->hash_update(ctx, data, len); + } + return -1; +} + +int hash_final(void* ctx, uint8_t* digest) +{ + if (mock_api) + { + return mock_api->hash_final(ctx, digest); + } + return -1; +} +} // extern "C" + +namespace google::cr51 +{ + +class KeyRotateHelperTest : public ::testing::Test +{ + protected: + void SetUp() override + { + mock_api = std::make_unique<::testing::StrictMock<MockApi>>(); + } + + void TearDown() override + { + mock_api.reset(); + } + + // A dummy device pointer. The value doesn't matter as it's just passed + // around. + struct libhoth_device* dummy_hoth_device = + reinterpret_cast<struct libhoth_device*>(0xDEADBEEF); +}; + +TEST_F(KeyRotateHelperTest, SetHothId) +{ + std::string id = "my-hoth-device"; + setHothId(id); + + // We can't read back HothId, but we can check if it's used correctly. + // This test has to run before any other test that successfully opens a hoth + // device, due to the static variable in hothDevice(). + EXPECT_CALL( + *mock_api, + libhoth_dbus_open( + ::testing::Truly( + [&](const struct libhoth_dbus_device_init_options* opts) { + return strcmp(opts->hoth_id, id.c_str()) == 0; + }), + _)) + .WillOnce(Return(-1)); // Return error to not cache the device. + sha256 hash{}; + trustDescriptorHash(nullptr, reinterpret_cast<uint8_t*>(&hash), + sizeof(hash)); +} + +TEST_F(KeyRotateHelperTest, TrustDescriptorHashWrongHashSize) +{ + sha256 hash{}; + EXPECT_FALSE(trustDescriptorHash(nullptr, reinterpret_cast<uint8_t*>(&hash), + sizeof(hash) - 1)); +} + +TEST_F(KeyRotateHelperTest, TrustDescriptorHashHothOpenFails) +{ + sha256 hash{}; + EXPECT_CALL(*mock_api, libhoth_dbus_open(_, _)).WillOnce(Return(-1)); + EXPECT_FALSE(trustDescriptorHash(nullptr, reinterpret_cast<uint8_t*>(&hash), + sizeof(hash))); +} + +// Grouping tests that need a valid hoth device to avoid issues with static +// hoth_device in hothDevice(). +class KeyRotateHelperWithDeviceTest : public KeyRotateHelperTest +{ + protected: + // This static pointer is needed because SetUpTestSuite is static. + static struct libhoth_device* dummy_hoth_device_for_suite; + + // SetUpTestSuite is called once before any tests in this suite are run. + static void SetUpTestSuite() + { + // Create the mock API once for the whole suite. + mock_api = std::make_unique<::testing::StrictMock<MockApi>>(); + + // Expect the dbus_open call once for the entire suite. This will be + // triggered by the first test that calls hothDevice(). + EXPECT_CALL(*mock_api, libhoth_dbus_open(_, _)) + .WillOnce(DoAll(SetArgPointee<1>(dummy_hoth_device_for_suite), + Return(0))); + } + + // TearDownTestSuite is called once after all tests in this suite are run. + static void TearDownTestSuite() + { + mock_api.reset(); + } + + // Override per-test SetUp and TearDown to do nothing, preventing + // interference with the suite-level setup. + void SetUp() override {} + void TearDown() override {} +}; + +struct libhoth_device* + KeyRotateHelperWithDeviceTest::dummy_hoth_device_for_suite = + reinterpret_cast<struct libhoth_device*>(0xDEADBEEF); + +TEST_F(KeyRotateHelperWithDeviceTest, TrustDescriptorHashGetChunkCountFails) +{ + sha256 hash{}; + EXPECT_CALL(*mock_api, libhoth_key_rotation_chunk_type_count( + dummy_hoth_device_for_suite, + KEY_ROTATION_CHUNK_TYPE_CODE_BASH, _)) + .WillOnce(Return(KEY_ROTATION_ERR)); + + EXPECT_FALSE(trustDescriptorHash(nullptr, reinterpret_cast<uint8_t*>(&hash), + sizeof(hash))); +} + +TEST_F(KeyRotateHelperWithDeviceTest, TrustDescriptorHashNoChunks) +{ + sha256 hash{}; + EXPECT_CALL(*mock_api, libhoth_key_rotation_chunk_type_count( + dummy_hoth_device_for_suite, + KEY_ROTATION_CHUNK_TYPE_CODE_BASH, _)) + .WillOnce(DoAll(SetArgPointee<2>(0), Return(KEY_ROTATION_CMD_SUCCESS))); + + EXPECT_FALSE(trustDescriptorHash(nullptr, reinterpret_cast<uint8_t*>(&hash), + sizeof(hash))); +} + +TEST_F(KeyRotateHelperWithDeviceTest, TrustDescriptorHashMatch) +{ + sha256 hash{0x01, 0x02, 0x03, 0x04}; + struct hoth_response_key_rotation_record_read response = {}; + struct bios_allowed_hash_list* hash_list = + reinterpret_cast<struct bios_allowed_hash_list*>(&response.data); + hash_list->hash_count = 1; + memcpy(hash_list->hash_list[0], &hash, sizeof(hash)); + + uint16_t response_size = + sizeof(struct bios_allowed_hash_list) + sizeof(sha256); + + EXPECT_CALL(*mock_api, libhoth_key_rotation_chunk_type_count( + dummy_hoth_device_for_suite, + KEY_ROTATION_CHUNK_TYPE_CODE_BASH, _)) + .WillOnce(DoAll(SetArgPointee<2>(1), Return(KEY_ROTATION_CMD_SUCCESS))); + EXPECT_CALL(*mock_api, + libhoth_key_rotation_read_chunk_type( + dummy_hoth_device_for_suite, + KEY_ROTATION_CHUNK_TYPE_CODE_BASH, 0, _, _, _, _)) + .WillOnce( + DoAll(SetArgPointee<5>(response), SetArgPointee<6>(response_size), + Return(KEY_ROTATION_CMD_SUCCESS))); + EXPECT_TRUE(trustDescriptorHash(nullptr, reinterpret_cast<uint8_t*>(&hash), + sizeof(hash))); +} + +TEST_F(KeyRotateHelperWithDeviceTest, TrustDescriptorHashMatchOnSecondChunk) +{ + sha256 hash{0x01, 0x02, 0x03, 0x04}; + + // Setup for chunk_count = 3 + EXPECT_CALL(*mock_api, libhoth_key_rotation_chunk_type_count( + dummy_hoth_device_for_suite, + KEY_ROTATION_CHUNK_TYPE_CODE_BASH, _)) + .WillOnce(DoAll(SetArgPointee<2>(3), Return(KEY_ROTATION_CMD_SUCCESS))); + + // Setup for first chunk (non-matching) + struct hoth_response_key_rotation_record_read non_matching_response = {}; + struct bios_allowed_hash_list* non_matching_hash_list = + reinterpret_cast<struct bios_allowed_hash_list*>( + &non_matching_response.data); + non_matching_hash_list->hash_count = 1; + sha256 non_matching_hash{0xff, 0xff, 0xff, 0xff}; + memcpy(non_matching_hash_list->hash_list[0], &non_matching_hash, + sizeof(non_matching_hash)); + uint16_t non_matching_response_size = + sizeof(struct bios_allowed_hash_list) + sizeof(sha256); + + EXPECT_CALL(*mock_api, libhoth_key_rotation_read_chunk_type( + dummy_hoth_device_for_suite, + KEY_ROTATION_CHUNK_TYPE_CODE_BASH, 0, + sizeof(key_rotation_chunk_header), 0, _, _)) + .WillOnce(DoAll(SetArgPointee<5>(non_matching_response), + SetArgPointee<6>(non_matching_response_size), + Return(KEY_ROTATION_CMD_SUCCESS))); + + // Setup for second chunk (matching) + struct hoth_response_key_rotation_record_read matching_response = {}; + struct bios_allowed_hash_list* matching_hash_list = + reinterpret_cast<struct bios_allowed_hash_list*>( + &matching_response.data); + matching_hash_list->hash_count = 1; + memcpy(matching_hash_list->hash_list[0], &hash, sizeof(hash)); + uint16_t matching_response_size = + sizeof(struct bios_allowed_hash_list) + sizeof(sha256); + + EXPECT_CALL(*mock_api, libhoth_key_rotation_read_chunk_type( + dummy_hoth_device_for_suite, + KEY_ROTATION_CHUNK_TYPE_CODE_BASH, 1, + sizeof(key_rotation_chunk_header), 0, _, _)) + .WillOnce(DoAll(SetArgPointee<5>(matching_response), + SetArgPointee<6>(matching_response_size), + Return(KEY_ROTATION_CMD_SUCCESS))); + + EXPECT_TRUE(trustDescriptorHash(nullptr, reinterpret_cast<uint8_t*>(&hash), + sizeof(hash))); +} + +TEST_F(KeyRotateHelperWithDeviceTest, TrustDescriptorHashMatchOnThirdChunk) +{ + sha256 hash{0x01, 0x02, 0x03, 0x04}; + + // Setup for chunk_count = 3 + EXPECT_CALL(*mock_api, libhoth_key_rotation_chunk_type_count( + dummy_hoth_device_for_suite, + KEY_ROTATION_CHUNK_TYPE_CODE_BASH, _)) + .WillOnce(DoAll(SetArgPointee<2>(3), Return(KEY_ROTATION_CMD_SUCCESS))); + + // Setup for first and second chunks (non-matching) + for (int i = 0; i < 2; ++i) + { + struct hoth_response_key_rotation_record_read non_matching_response = + {}; + struct bios_allowed_hash_list* non_matching_hash_list = + reinterpret_cast<struct bios_allowed_hash_list*>( + &non_matching_response.data); + non_matching_hash_list->hash_count = 1; + sha256 non_matching_hash{0xff, 0xff, 0xff, (uint8_t)i}; + memcpy(non_matching_hash_list->hash_list[0], &non_matching_hash, + sizeof(non_matching_hash)); + uint16_t non_matching_response_size = + sizeof(struct bios_allowed_hash_list) + sizeof(sha256); + + EXPECT_CALL(*mock_api, + libhoth_key_rotation_read_chunk_type( + dummy_hoth_device_for_suite, + KEY_ROTATION_CHUNK_TYPE_CODE_BASH, i, _, _, _, _)) + .WillOnce(DoAll(SetArgPointee<5>(non_matching_response), + SetArgPointee<6>(non_matching_response_size), + Return(KEY_ROTATION_CMD_SUCCESS))); + } + + // Setup for third chunk (matching) + struct hoth_response_key_rotation_record_read matching_response = {}; + struct bios_allowed_hash_list* matching_hash_list = + reinterpret_cast<struct bios_allowed_hash_list*>( + &matching_response.data); + matching_hash_list->hash_count = 1; + memcpy(matching_hash_list->hash_list[0], &hash, sizeof(hash)); + uint16_t matching_response_size = + sizeof(struct bios_allowed_hash_list) + sizeof(sha256); + + EXPECT_CALL(*mock_api, libhoth_key_rotation_read_chunk_type( + dummy_hoth_device_for_suite, + KEY_ROTATION_CHUNK_TYPE_CODE_BASH, 2, + sizeof(key_rotation_chunk_header), 0, _, _)) + .WillOnce(DoAll(SetArgPointee<5>(matching_response), + SetArgPointee<6>(matching_response_size), + Return(KEY_ROTATION_CMD_SUCCESS))); + + EXPECT_TRUE(trustDescriptorHash(nullptr, reinterpret_cast<uint8_t*>(&hash), + sizeof(hash))); +} + +TEST_F(KeyRotateHelperTest, TrustKeyInCr51SignatureNullArgs) +{ + EXPECT_FALSE( + trustKeyInCr51Signature(nullptr, SIGNATURE_RSA2048_PKCS15, nullptr, 0)); + char dummy_ctx; + EXPECT_FALSE(trustKeyInCr51Signature(&dummy_ctx, SIGNATURE_RSA2048_PKCS15, + nullptr, 0)); // NOLINT + struct signature_rsa2048_pkcs15 sig; + EXPECT_FALSE(trustKeyInCr51Signature(nullptr, SIGNATURE_RSA2048_PKCS15, + &sig, sizeof(sig))); +} + +TEST_F(KeyRotateHelperTest, TrustKeyInCr51SignatureUnsupportedScheme) +{ + char dummy_ctx; + struct signature_rsa2048_pkcs15 sig{}; + EXPECT_FALSE(trustKeyInCr51Signature( + &dummy_ctx, static_cast<enum signature_scheme>(99), &sig, sizeof(sig))); +} + +TEST_F(KeyRotateHelperTest, TrustKeyInCr51SignatureHashFails) +{ + char dummy_ctx; + struct signature_rsa2048_pkcs15 sig{}; + EXPECT_CALL(*mock_api, hash_init(_, HASH_SHA2_256)).WillOnce(Return(1)); + EXPECT_FALSE(trustKeyInCr51Signature(&dummy_ctx, SIGNATURE_RSA2048_PKCS15, + &sig, sizeof(sig))); +} + +TEST_F(KeyRotateHelperWithDeviceTest, TrustKeyInCr51SignatureMatch) +{ + char dummy_ctx; + struct signature_rsa4096_pkcs15 sig = {}; + sha256 key_fingerprint{0x01, 0x02, 0x03, 0x04}; + + EXPECT_CALL(*mock_api, hash_init(_, HASH_SHA2_256)).WillOnce(Return(0)); + EXPECT_CALL(*mock_api, hash_update(_, _, _)).WillRepeatedly(Return(0)); + EXPECT_CALL(*mock_api, hash_final(_, _)) + .WillOnce(DoAll( + SetArrayArgument<1>(reinterpret_cast<uint8_t*>(&key_fingerprint), + reinterpret_cast<uint8_t*>(&key_fingerprint) + + sizeof(key_fingerprint)), + Return(0))); + + EXPECT_CALL(*mock_api, libhoth_key_rotation_chunk_type_count( + dummy_hoth_device_for_suite, + KEY_ROTATION_CHUNK_TYPE_CODE_BKEY, _)) + .WillOnce(DoAll(SetArgPointee<2>(1), Return(KEY_ROTATION_CMD_SUCCESS))); + struct hoth_response_key_rotation_record_read response = {}; + struct bios_verifiction_key_fingerprint* trusted_key = + reinterpret_cast<struct bios_verifiction_key_fingerprint*>( + &response.data); + memcpy(trusted_key->key_fingerprint, &key_fingerprint, + sizeof(key_fingerprint)); + uint16_t response_size = sizeof(struct bios_verifiction_key_fingerprint); + + EXPECT_CALL(*mock_api, + libhoth_key_rotation_read_chunk_type( + dummy_hoth_device_for_suite, + KEY_ROTATION_CHUNK_TYPE_CODE_BKEY, 0, _, _, _, _)) + .WillOnce( + DoAll(SetArgPointee<5>(response), SetArgPointee<6>(response_size), + Return(KEY_ROTATION_CMD_SUCCESS))); + EXPECT_TRUE(trustKeyInCr51Signature(&dummy_ctx, SIGNATURE_RSA4096_PKCS15, + &sig, sizeof(sig))); +} + +TEST_F(KeyRotateHelperWithDeviceTest, TrustKeyInCr51SignatureMatchOnSecondChunk) +{ + char dummy_ctx; + struct signature_rsa4096_pkcs15 sig = {}; + sha256 key_fingerprint{0x01, 0x02, 0x03, 0x04}; + + EXPECT_CALL(*mock_api, hash_init(_, HASH_SHA2_256)).WillOnce(Return(0)); + EXPECT_CALL(*mock_api, hash_update(_, _, _)).WillRepeatedly(Return(0)); + EXPECT_CALL(*mock_api, hash_final(_, _)) + .WillOnce(DoAll( + SetArrayArgument<1>(reinterpret_cast<uint8_t*>(&key_fingerprint), + reinterpret_cast<uint8_t*>(&key_fingerprint) + + sizeof(key_fingerprint)), + Return(0))); + + EXPECT_CALL(*mock_api, libhoth_key_rotation_chunk_type_count( + dummy_hoth_device_for_suite, + KEY_ROTATION_CHUNK_TYPE_CODE_BKEY, _)) + .WillOnce(DoAll(SetArgPointee<2>(3), Return(KEY_ROTATION_CMD_SUCCESS))); + + // Setup for first chunk (non-matching) + struct hoth_response_key_rotation_record_read non_matching_response = {}; + struct bios_verifiction_key_fingerprint* non_matching_key = + reinterpret_cast<struct bios_verifiction_key_fingerprint*>( + &non_matching_response.data); + sha256 non_matching_fingerprint{0xff, 0xff, 0xff, 0xff}; + memcpy(non_matching_key->key_fingerprint, &non_matching_fingerprint, + sizeof(non_matching_fingerprint)); + uint16_t non_matching_response_size = + sizeof(struct bios_verifiction_key_fingerprint); + + EXPECT_CALL(*mock_api, libhoth_key_rotation_read_chunk_type( + dummy_hoth_device_for_suite, + KEY_ROTATION_CHUNK_TYPE_CODE_BKEY, 0, + sizeof(key_rotation_chunk_header), 0, _, _)) + .WillOnce(DoAll(SetArgPointee<5>(non_matching_response), + SetArgPointee<6>(non_matching_response_size), + Return(KEY_ROTATION_CMD_SUCCESS))); + + // Setup for second chunk (matching) + struct hoth_response_key_rotation_record_read matching_response = {}; + struct bios_verifiction_key_fingerprint* matching_key = + reinterpret_cast<struct bios_verifiction_key_fingerprint*>( + &matching_response.data); + memcpy(matching_key->key_fingerprint, &key_fingerprint, + sizeof(key_fingerprint)); + uint16_t matching_response_size = + sizeof(struct bios_verifiction_key_fingerprint); + + EXPECT_CALL(*mock_api, libhoth_key_rotation_read_chunk_type( + dummy_hoth_device_for_suite, + KEY_ROTATION_CHUNK_TYPE_CODE_BKEY, 1, + sizeof(key_rotation_chunk_header), 0, _, _)) + .WillOnce(DoAll(SetArgPointee<5>(matching_response), + SetArgPointee<6>(matching_response_size), + Return(KEY_ROTATION_CMD_SUCCESS))); + + EXPECT_TRUE(trustKeyInCr51Signature(&dummy_ctx, SIGNATURE_RSA4096_PKCS15, + &sig, sizeof(sig))); +} + +TEST_F(KeyRotateHelperWithDeviceTest, TrustKeyInCr51SignatureMatchOnThirdChunk) +{ + char dummy_ctx; + struct signature_rsa4096_pkcs15 sig = {}; + sha256 key_fingerprint{0x01, 0x02, 0x03, 0x04}; + + EXPECT_CALL(*mock_api, hash_init(_, HASH_SHA2_256)).WillOnce(Return(0)); + EXPECT_CALL(*mock_api, hash_update(_, _, _)).WillRepeatedly(Return(0)); + EXPECT_CALL(*mock_api, hash_final(_, _)) + .WillOnce(DoAll( + SetArrayArgument<1>(reinterpret_cast<uint8_t*>(&key_fingerprint), + reinterpret_cast<uint8_t*>(&key_fingerprint) + + sizeof(key_fingerprint)), + Return(0))); + + EXPECT_CALL(*mock_api, libhoth_key_rotation_chunk_type_count( + dummy_hoth_device_for_suite, + KEY_ROTATION_CHUNK_TYPE_CODE_BKEY, _)) + .WillOnce(DoAll(SetArgPointee<2>(3), Return(KEY_ROTATION_CMD_SUCCESS))); + + // Setup for first and second chunks (non-matching) + for (int i = 0; i < 2; ++i) + { + struct hoth_response_key_rotation_record_read non_matching_response = + {}; + struct bios_verifiction_key_fingerprint* non_matching_key = + reinterpret_cast<struct bios_verifiction_key_fingerprint*>( + &non_matching_response.data); + sha256 non_matching_fingerprint{0xff, 0xff, 0xff, (uint8_t)i}; + memcpy(non_matching_key->key_fingerprint, &non_matching_fingerprint, + sizeof(non_matching_fingerprint)); + uint16_t non_matching_response_size = + sizeof(struct bios_verifiction_key_fingerprint); + + EXPECT_CALL(*mock_api, + libhoth_key_rotation_read_chunk_type( + dummy_hoth_device_for_suite, + KEY_ROTATION_CHUNK_TYPE_CODE_BKEY, i, _, _, _, _)) + .WillOnce(DoAll(SetArgPointee<5>(non_matching_response), + SetArgPointee<6>(non_matching_response_size), + Return(KEY_ROTATION_CMD_SUCCESS))); + } + + // Setup for third chunk (matching) + struct hoth_response_key_rotation_record_read matching_response = {}; + struct bios_verifiction_key_fingerprint* matching_key = + reinterpret_cast<struct bios_verifiction_key_fingerprint*>( + &matching_response.data); + memcpy(matching_key->key_fingerprint, &key_fingerprint, + sizeof(key_fingerprint)); + uint16_t matching_response_size = + sizeof(struct bios_verifiction_key_fingerprint); + + EXPECT_CALL(*mock_api, libhoth_key_rotation_read_chunk_type( + dummy_hoth_device_for_suite, + KEY_ROTATION_CHUNK_TYPE_CODE_BKEY, 2, + sizeof(key_rotation_chunk_header), 0, _, _)) + .WillOnce(DoAll(SetArgPointee<5>(matching_response), + SetArgPointee<6>(matching_response_size), + Return(KEY_ROTATION_CMD_SUCCESS))); + + EXPECT_TRUE(trustKeyInCr51Signature(&dummy_ctx, SIGNATURE_RSA4096_PKCS15, + &sig, sizeof(sig))); +} + +TEST_F(KeyRotateHelperTest, AlwaysTrustDescriptorHash) +{ + sha256 hash{}; + // It should return true even if the underlying function returns false. + EXPECT_CALL(*mock_api, libhoth_dbus_open(_, _)).WillOnce(Return(-1)); + EXPECT_TRUE(alwaysTrustDescriptorHash( + nullptr, reinterpret_cast<uint8_t*>(&hash), sizeof(hash))); +} + +TEST_F(KeyRotateHelperTest, AlwaysTrustKeyInCr51Signature) +{ + char dummy_ctx; + struct signature_rsa2048_pkcs15 sig{}; + // It should return true even if the underlying function returns false. + EXPECT_CALL(*mock_api, hash_init(_, _)).WillOnce(Return(1)); // make it fail + EXPECT_TRUE(alwaysTrustKeyInCr51Signature( + &dummy_ctx, SIGNATURE_RSA2048_PKCS15, &sig, sizeof(sig))); +} + +TEST_F(KeyRotateHelperWithDeviceTest, NeverTrustDescriptorHash) +{ + sha256 hash{0x01, 0x02, 0x03, 0x04}; + // It should return false even if the underlying function returns true. + struct hoth_response_key_rotation_record_read response = {}; + struct bios_allowed_hash_list* hash_list = + reinterpret_cast<struct bios_allowed_hash_list*>(&response.data); + hash_list->hash_count = 1; + memcpy(hash_list->hash_list[0], &hash, sizeof(hash)); + uint16_t response_size = + sizeof(struct bios_allowed_hash_list) + sizeof(sha256); + + EXPECT_CALL(*mock_api, libhoth_key_rotation_chunk_type_count( + dummy_hoth_device_for_suite, + KEY_ROTATION_CHUNK_TYPE_CODE_BASH, _)) + .WillOnce(DoAll(SetArgPointee<2>(1), Return(KEY_ROTATION_CMD_SUCCESS))); + EXPECT_CALL(*mock_api, + libhoth_key_rotation_read_chunk_type( + dummy_hoth_device_for_suite, + KEY_ROTATION_CHUNK_TYPE_CODE_BASH, 0, _, _, _, _)) + .WillOnce( + DoAll(SetArgPointee<5>(response), SetArgPointee<6>(response_size), + Return(KEY_ROTATION_CMD_SUCCESS))); + + EXPECT_FALSE(neverTrustDescriptorHash( + nullptr, reinterpret_cast<uint8_t*>(&hash), sizeof(hash))); +} + +TEST_F(KeyRotateHelperTest, IsBiosKeyRotationSupportHothOpenFails) +{ + // This test must run before any test that successfully opens a hoth device + // due to the static hoth_device cache in the production code. + EXPECT_CALL(*mock_api, libhoth_dbus_open(_, _)).WillOnce(Return(-1)); + EXPECT_FALSE(isBiosKeyRotationSupport()); +} + +TEST_F(KeyRotateHelperWithDeviceTest, IsBiosKeyRotationSupportChunkCountFails) +{ + EXPECT_CALL(*mock_api, libhoth_key_rotation_chunk_type_count( + dummy_hoth_device_for_suite, + KEY_ROTATION_CHUNK_TYPE_CODE_BKEY, _)) + .WillOnce(Return(KEY_ROTATION_ERR)); + EXPECT_FALSE(isBiosKeyRotationSupport()); +} + +TEST_F(KeyRotateHelperWithDeviceTest, IsBiosKeyRotationSupportNoChunks) +{ + EXPECT_CALL(*mock_api, libhoth_key_rotation_chunk_type_count( + dummy_hoth_device_for_suite, + KEY_ROTATION_CHUNK_TYPE_CODE_BKEY, _)) + .WillOnce(DoAll(SetArgPointee<2>(0), Return(KEY_ROTATION_CMD_SUCCESS))); + EXPECT_FALSE(isBiosKeyRotationSupport()); +} + +TEST_F(KeyRotateHelperWithDeviceTest, IsBiosKeyRotationSupportSuccess) +{ + EXPECT_CALL(*mock_api, libhoth_key_rotation_chunk_type_count( + dummy_hoth_device_for_suite, + KEY_ROTATION_CHUNK_TYPE_CODE_BKEY, _)) + .WillOnce(DoAll(SetArgPointee<2>(1), Return(KEY_ROTATION_CMD_SUCCESS))); + EXPECT_TRUE(isBiosKeyRotationSupport()); +} + +} // namespace google::cr51