| // 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 |