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