blob: 134424d425423f91aaa9b7fe043986bfb67dac89 [file] [edit]
/*
* SPDX-FileCopyrightText: Copyright (c) 2023-2024 NVIDIA CORPORATION &
* AFFILIATES. All rights reserved. SPDX-License-Identifier: Apache-2.0
*
* 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 the .cpp directly so that its anonymous-namespace `commands`
// vector and all class definitions are in this translation unit.
#include "firmware-utils.h"
#include <cstdio>
#include <cstring>
#include <fstream>
#include "../nsm_firmware_cmd.cpp"
#include <gtest/gtest.h>
// Firmware commands (after registerCommand) in order:
// [0] GetRotInformation
// [1] IrreversibleConfig
// [2] QueryCodeAuthKeyPerm (registered as QueryFWCodeAuthKey)
// [3] UpdateCodeAuthKeyPerm
// [4] QueryFirmwareSecurityVersion
// [5] UpdateMinSecurityVersion
// [6] SetRoTProperty
// [7] DotCAKInstall
// [8] DotCAKBypass
// [9] ImageCopyControl
// [10] DotLock
// [11] DotCAKRotate
// [12] DotUnlockChallenge
// [13] DotUnlock
// [14] DotGetInfo
// [15] DotGetStatus
// [16] DotDisable
// [17] DotOverride
// [18] DotRecovery
namespace nsmtool::firmware
{
// ---- Helpers ----------------------------------------------------------------
static void setupFirmwareCommands(CLI::App& app)
{
commands.clear();
registerCommand(app);
}
static void parseSubcmdArgs(CLI::App& app, const std::string& cmdName,
std::vector<std::string> extraArgs = {})
{
auto* fwSub = app.get_subcommand("firmware");
if (!fwSub)
return;
auto* leafSub = fwSub->get_subcommand(cmdName);
if (!leafSub)
return;
std::vector<std::string> args = {"-m", "1"};
args.insert(args.end(), extraArgs.begin(), extraArgs.end());
std::reverse(args.begin(), args.end());
try
{
leafSub->parse(args);
}
catch (...)
{}
}
static std::string createTempBinFile(const std::string& path, size_t size)
{
std::ofstream f(path, std::ios::binary | std::ios::trunc);
std::vector<uint8_t> zeros(size, 0);
f.write(reinterpret_cast<const char*>(zeros.data()),
static_cast<std::streamsize>(size));
f.close();
return path;
}
static const std::string kEcdsaKeyFile = "/tmp/fwb_test_ecdsa_96b.bin";
static const std::string kLmsKeyFile = "/tmp/fwb_test_lms_48b.bin";
static const std::string kSignatureFile = "/tmp/fwb_test_sig_1840b.bin";
static const std::string kDotBlobFile = "/tmp/fwb_test_dotblob_1024b.bin";
static const std::string kChallengeFile = "/tmp/fwb_test_challenge_32b.bin";
static const std::string kOutputFile = "/tmp/fwb_test_output.bin";
class FwBranchTest : public ::testing::Test
{
protected:
void SetUp() override
{
createTempBinFile(kEcdsaKeyFile, 96);
createTempBinFile(kLmsKeyFile, 48);
createTempBinFile(kSignatureFile, 1840);
createTempBinFile(kDotBlobFile, 1024);
createTempBinFile(kChallengeFile, 32);
}
void TearDown() override
{
std::remove(kEcdsaKeyFile.c_str());
std::remove(kLmsKeyFile.c_str());
std::remove(kSignatureFile.c_str());
std::remove(kDotBlobFile.c_str());
std::remove(kChallengeFile.c_str());
std::remove(kOutputFile.c_str());
}
};
// ===========================================================================
// [0] GetRotInformation — mapEnumToString branches
// ===========================================================================
// Success with 2 firmware slots (exercises loop body and slot_info branches)
TEST_F(FwBranchTest, GetRotInformation_TwoSlots)
{
CLI::App app;
setupFirmwareCommands(app);
parseSubcmdArgs(app, "GetRotInformation",
{"-c", "1", "-i", "0", "-d", "0"});
// Allocate enough for header + full query response + 2 slot entries
nsm_firmware_erot_state_info_resp fw_info = {};
fw_info.fq_resp_hdr.firmware_slot_count = 2;
fw_info.fq_resp_hdr.background_copy_policy = 0; // Disabled
fw_info.fq_resp_hdr.active_slot = 0;
fw_info.fq_resp_hdr.active_keyset = 0;
fw_info.fq_resp_hdr.global_failover_policy =
NSM_ROT_GLOBAL_FAILOVER_POLICY_AUTOMATIC_FAILOVER;
nsm_firmware_slot_info slots[2] = {};
// Slot 0: exercise known enum values
slots[0].slot_id = 0;
strncpy(reinterpret_cast<char*>(slots[0].firmware_version_string), "v1.0",
sizeof(slots[0].firmware_version_string));
slots[0].build_type = 0; // Development
slots[0].signing_type = 1; // Production
slots[0].write_protect_state = 1; // Enabled
slots[0].firmware_state = 1; // Activated
// Slot 1: exercise "Not Defined" branches (unknown values)
slots[1].slot_id = 1;
strncpy(reinterpret_cast<char*>(slots[1].firmware_version_string), "v2.0",
sizeof(slots[1].firmware_version_string));
slots[1].build_type = 99; // Not Defined
slots[1].signing_type = 99; // Not Defined
slots[1].write_protect_state = 99; // Not Defined
slots[1].firmware_state = 99; // Not Defined
fw_info.slot_info = slots;
std::vector<uint8_t> buf(2048, 0);
auto* msg = reinterpret_cast<nsm_msg*>(buf.data());
encode_nsm_query_get_erot_state_parameters_resp(0, NSM_SUCCESS, ERR_NULL,
&fw_info, msg);
EXPECT_NO_THROW(commands[0]->parseResponseMsg(msg, buf.size()));
}
// bgCopyPolicy=1 (Enabled), globalFailoverPolicy values
TEST_F(FwBranchTest, GetRotInformation_BgCopyEnabled)
{
CLI::App app;
setupFirmwareCommands(app);
parseSubcmdArgs(app, "GetRotInformation",
{"-c", "1", "-i", "0", "-d", "0"});
nsm_firmware_erot_state_info_resp fw_info = {};
fw_info.fq_resp_hdr.firmware_slot_count = 0;
fw_info.fq_resp_hdr.background_copy_policy = 1; // Enabled
fw_info.fq_resp_hdr.global_failover_policy =
NSM_ROT_GLOBAL_FAILOVER_POLICY_NO_FAILOVER;
fw_info.slot_info = nullptr;
std::vector<uint8_t> buf(512, 0);
auto* msg = reinterpret_cast<nsm_msg*>(buf.data());
encode_nsm_query_get_erot_state_parameters_resp(0, NSM_SUCCESS, ERR_NULL,
&fw_info, msg);
EXPECT_NO_THROW(commands[0]->parseResponseMsg(msg, buf.size()));
}
// globalFailoverPolicy=Not Applicable
TEST_F(FwBranchTest, GetRotInformation_FailoverNotApplicable)
{
CLI::App app;
setupFirmwareCommands(app);
parseSubcmdArgs(app, "GetRotInformation",
{"-c", "1", "-i", "0", "-d", "0"});
nsm_firmware_erot_state_info_resp fw_info = {};
fw_info.fq_resp_hdr.firmware_slot_count = 0;
fw_info.fq_resp_hdr.global_failover_policy =
NSM_ROT_GLOBAL_FAILOVER_POLICY_NOT_APPLICABLE;
fw_info.slot_info = nullptr;
std::vector<uint8_t> buf(512, 0);
auto* msg = reinterpret_cast<nsm_msg*>(buf.data());
encode_nsm_query_get_erot_state_parameters_resp(0, NSM_SUCCESS, ERR_NULL,
&fw_info, msg);
EXPECT_NO_THROW(commands[0]->parseResponseMsg(msg, buf.size()));
}
// globalFailoverPolicy=Not Defined (unknown value)
TEST_F(FwBranchTest, GetRotInformation_FailoverNotDefined)
{
CLI::App app;
setupFirmwareCommands(app);
parseSubcmdArgs(app, "GetRotInformation",
{"-c", "1", "-i", "0", "-d", "0"});
nsm_firmware_erot_state_info_resp fw_info = {};
fw_info.fq_resp_hdr.firmware_slot_count = 0;
fw_info.fq_resp_hdr.global_failover_policy = 99; // Not Defined
fw_info.fq_resp_hdr.background_copy_policy = 99; // Not Defined
fw_info.slot_info = nullptr;
std::vector<uint8_t> buf(512, 0);
auto* msg = reinterpret_cast<nsm_msg*>(buf.data());
encode_nsm_query_get_erot_state_parameters_resp(0, NSM_SUCCESS, ERR_NULL,
&fw_info, msg);
EXPECT_NO_THROW(commands[0]->parseResponseMsg(msg, buf.size()));
}
// All firmware_state enum values
TEST_F(FwBranchTest, GetRotInformation_AllFirmwareStates)
{
// Test firmware states 0-9 (each in a separate slot)
for (uint8_t state = 0; state <= 9; ++state)
{
CLI::App app;
setupFirmwareCommands(app);
parseSubcmdArgs(app, "GetRotInformation",
{"-c", "1", "-i", "0", "-d", "0"});
nsm_firmware_erot_state_info_resp fw_info = {};
fw_info.fq_resp_hdr.firmware_slot_count = 1;
nsm_firmware_slot_info slot = {};
slot.firmware_state = state;
fw_info.slot_info = &slot;
std::vector<uint8_t> buf(2048, 0);
auto* msg = reinterpret_cast<nsm_msg*>(buf.data());
encode_nsm_query_get_erot_state_parameters_resp(
0, NSM_SUCCESS, ERR_NULL, &fw_info, msg);
EXPECT_NO_THROW(commands[0]->parseResponseMsg(msg, buf.size()));
}
}
// All signing_type enum values
TEST_F(FwBranchTest, GetRotInformation_AllSigningTypes)
{
for (uint8_t sigType : {0, 1, 2, 4})
{
CLI::App app;
setupFirmwareCommands(app);
parseSubcmdArgs(app, "GetRotInformation",
{"-c", "1", "-i", "0", "-d", "0"});
nsm_firmware_erot_state_info_resp fw_info = {};
fw_info.fq_resp_hdr.firmware_slot_count = 1;
nsm_firmware_slot_info slot = {};
slot.signing_type = sigType;
fw_info.slot_info = &slot;
std::vector<uint8_t> buf(2048, 0);
auto* msg = reinterpret_cast<nsm_msg*>(buf.data());
encode_nsm_query_get_erot_state_parameters_resp(
0, NSM_SUCCESS, ERR_NULL, &fw_info, msg);
EXPECT_NO_THROW(commands[0]->parseResponseMsg(msg, buf.size()));
}
}
// ===========================================================================
// [3] UpdateCodeAuthKeyPerm — updateMethod bit branches
// ===========================================================================
TEST_F(FwBranchTest, UpdateCodeAuthKeyPerm_AllUpdateBits)
{
CLI::App app;
setupFirmwareCommands(app);
parseSubcmdArgs(app, "UpdateCodeAuthKeyPerm",
{"-r", "0", "-c", "1", "-i", "0", "-d", "0", "-n", "0"});
// Set all relevant bits: bit0-5, bit16-18
uint32_t updateMethod = (1u << 0) | (1u << 1) | (1u << 2) | (1u << 3) |
(1u << 4) | (1u << 5) | (1u << 16) | (1u << 17) |
(1u << 18);
std::vector<uint8_t> buf(sizeof(nsm_msg_hdr) +
sizeof(nsm_code_auth_key_perm_update_resp));
auto* msg = reinterpret_cast<nsm_msg*>(buf.data());
encode_nsm_code_auth_key_perm_update_resp(0, NSM_SUCCESS, ERR_NULL,
updateMethod, msg);
EXPECT_NO_THROW(commands[3]->parseResponseMsg(msg, buf.size()));
}
TEST_F(FwBranchTest, UpdateCodeAuthKeyPerm_NoBits)
{
CLI::App app;
setupFirmwareCommands(app);
parseSubcmdArgs(app, "UpdateCodeAuthKeyPerm",
{"-r", "0", "-c", "1", "-i", "0", "-d", "0", "-n", "0"});
std::vector<uint8_t> buf(sizeof(nsm_msg_hdr) +
sizeof(nsm_code_auth_key_perm_update_resp));
auto* msg = reinterpret_cast<nsm_msg*>(buf.data());
encode_nsm_code_auth_key_perm_update_resp(0, NSM_SUCCESS, ERR_NULL, 0, msg);
EXPECT_NO_THROW(commands[3]->parseResponseMsg(msg, buf.size()));
}
TEST_F(FwBranchTest, UpdateCodeAuthKeyPerm_Error)
{
CLI::App app;
setupFirmwareCommands(app);
parseSubcmdArgs(app, "UpdateCodeAuthKeyPerm",
{"-r", "0", "-c", "1", "-i", "0", "-d", "0", "-n", "0"});
std::vector<uint8_t> buf(sizeof(nsm_msg_hdr) + NSM_RESPONSE_ERROR_LEN);
auto* msg = reinterpret_cast<nsm_msg*>(buf.data());
// Set cc to non-zero (error) so decode returns early before any OOB read.
msg->payload[1] = 1;
EXPECT_NO_THROW(commands[3]->parseResponseMsg(msg, buf.size()));
}
// ===========================================================================
// [5] UpdateMinSecurityVersion — updateMethod bit branches (same pattern)
// ===========================================================================
TEST_F(FwBranchTest, UpdateMinSecurityVersion_AllUpdateBits)
{
CLI::App app;
setupFirmwareCommands(app);
parseSubcmdArgs(app, "UpdateMinSecurityVersion",
{"-r", "0", "-n", "0", "-c", "1", "-i", "0", "-d", "0"});
nsm_firmware_update_min_sec_ver_resp sec_info{};
sec_info.update_methods = (1u << 0) | (1u << 1) | (1u << 2) | (1u << 3) |
(1u << 4) | (1u << 5) | (1u << 16) | (1u << 17);
std::vector<uint8_t> buf(
sizeof(nsm_msg_hdr) +
sizeof(nsm_firmware_update_min_sec_ver_resp_command));
auto* msg = reinterpret_cast<nsm_msg*>(buf.data());
encode_nsm_firmware_update_sec_ver_resp(0, NSM_SUCCESS, ERR_NULL, &sec_info,
msg);
EXPECT_NO_THROW(commands[5]->parseResponseMsg(msg, buf.size()));
}
TEST_F(FwBranchTest, UpdateMinSecurityVersion_Error)
{
CLI::App app;
setupFirmwareCommands(app);
parseSubcmdArgs(app, "UpdateMinSecurityVersion",
{"-r", "0", "-n", "0", "-c", "1", "-i", "0", "-d", "0"});
std::vector<uint8_t> buf(sizeof(nsm_msg_hdr) + NSM_RESPONSE_ERROR_LEN);
auto* msg = reinterpret_cast<nsm_msg*>(buf.data());
// Set cc to non-zero (error) so decode returns early before any OOB read.
msg->payload[1] = 1;
EXPECT_NO_THROW(commands[5]->parseResponseMsg(msg, buf.size()));
}
// ===========================================================================
// [1] IrreversibleConfig — switch(requestType) branches
// ===========================================================================
// requestType=0 (QUERY) — error path
TEST_F(FwBranchTest, IrreversibleConfig_Query_Error)
{
CLI::App app;
setupFirmwareCommands(app);
parseSubcmdArgs(app, "IrreversibleConfig", {"-r", "0"});
std::vector<uint8_t> buf(sizeof(nsm_msg_hdr) + NSM_RESPONSE_ERROR_LEN);
auto* msg = reinterpret_cast<nsm_msg*>(buf.data());
// Set completion_code to non-zero so decode returns early before
// memcpy of irreversible_cfg_query (which would be out-of-bounds in
// this small error-response buffer).
msg->payload[1] = 1;
EXPECT_NO_THROW(commands[1]->parseResponseMsg(msg, buf.size()));
}
// requestType=1 (DISABLE) — success
TEST_F(FwBranchTest, IrreversibleConfig_Disable_Success)
{
CLI::App app;
setupFirmwareCommands(app);
parseSubcmdArgs(app, "IrreversibleConfig", {"-r", "1"});
std::vector<uint8_t> buf(sizeof(nsm_msg_hdr) + sizeof(nsm_common_resp));
auto* msg = reinterpret_cast<nsm_msg*>(buf.data());
encode_nsm_firmware_irreversible_config_request_1_resp(0, NSM_SUCCESS,
ERR_NULL, msg);
EXPECT_NO_THROW(commands[1]->parseResponseMsg(msg, buf.size()));
}
// requestType=1 (DISABLE) — error
TEST_F(FwBranchTest, IrreversibleConfig_Disable_Error)
{
CLI::App app;
setupFirmwareCommands(app);
parseSubcmdArgs(app, "IrreversibleConfig", {"-r", "1"});
std::vector<uint8_t> buf(sizeof(nsm_msg_hdr) + NSM_RESPONSE_ERROR_LEN);
auto* msg = reinterpret_cast<nsm_msg*>(buf.data());
// Set cc to non-zero (error) so decode returns early before any OOB read.
msg->payload[1] = 1;
EXPECT_NO_THROW(commands[1]->parseResponseMsg(msg, buf.size()));
}
// requestType=2 (ENABLE) — error
TEST_F(FwBranchTest, IrreversibleConfig_Enable_Error)
{
CLI::App app;
setupFirmwareCommands(app);
parseSubcmdArgs(app, "IrreversibleConfig", {"-r", "2"});
std::vector<uint8_t> buf(sizeof(nsm_msg_hdr) + NSM_RESPONSE_ERROR_LEN);
auto* msg = reinterpret_cast<nsm_msg*>(buf.data());
// Set cc to non-zero (error) so decode returns early before any OOB read.
msg->payload[1] = 1;
EXPECT_NO_THROW(commands[1]->parseResponseMsg(msg, buf.size()));
}
// requestType=99 (default branch)
TEST_F(FwBranchTest, IrreversibleConfig_Default)
{
CLI::App app;
setupFirmwareCommands(app);
parseSubcmdArgs(app, "IrreversibleConfig", {"-r", "99"});
std::vector<uint8_t> buf(sizeof(nsm_msg_hdr) + sizeof(nsm_common_resp));
auto* msg = reinterpret_cast<nsm_msg*>(buf.data());
EXPECT_NO_THROW(commands[1]->parseResponseMsg(msg, buf.size()));
}
// ===========================================================================
// [2] QueryCodeAuthKeyPerm — success with bitmap data
// ===========================================================================
TEST_F(FwBranchTest, QueryCodeAuthKeyPerm_Error)
{
CLI::App app;
setupFirmwareCommands(app);
parseSubcmdArgs(app, "QueryFWCodeAuthKey",
{"-c", "1", "-i", "0", "-d", "0"});
std::vector<uint8_t> buf(sizeof(nsm_msg_hdr) + NSM_RESPONSE_ERROR_LEN);
auto* msg = reinterpret_cast<nsm_msg*>(buf.data());
// Set completion_code to non-zero so decode_reason_code_and_cc returns
// early before reading permission_bitmap_length (which would be
// out-of-bounds in this small error-response buffer).
msg->payload[1] = 1;
EXPECT_NO_THROW(commands[2]->parseResponseMsg(msg, buf.size()));
}
// ===========================================================================
// [4] QueryFirmwareSecurityVersion
// ===========================================================================
TEST_F(FwBranchTest, QueryFirmwareSecurityVersion_Error)
{
CLI::App app;
setupFirmwareCommands(app);
parseSubcmdArgs(app, "QueryFirmwareSecurityVersion",
{"-c", "1", "-i", "0", "-d", "0"});
std::vector<uint8_t> buf(sizeof(nsm_msg_hdr) + NSM_RESPONSE_ERROR_LEN);
auto* msg = reinterpret_cast<nsm_msg*>(buf.data());
// Set cc to non-zero (error) so decode returns early before any OOB read.
msg->payload[1] = 1;
EXPECT_NO_THROW(commands[4]->parseResponseMsg(msg, buf.size()));
}
// ===========================================================================
// [6] SetRoTProperty
// ===========================================================================
TEST_F(FwBranchTest, SetRoTProperty_Error)
{
CLI::App app;
setupFirmwareCommands(app);
parseSubcmdArgs(app, "SetRoTProperty",
{"-p", "0", "-c", "1", "-i", "0", "-d", "0"});
std::vector<uint8_t> buf(sizeof(nsm_msg_hdr) + NSM_RESPONSE_ERROR_LEN);
auto* msg = reinterpret_cast<nsm_msg*>(buf.data());
// Set cc to non-zero (error) so decode returns early before any OOB read.
msg->payload[1] = 1;
EXPECT_NO_THROW(commands[6]->parseResponseMsg(msg, buf.size()));
}
// ===========================================================================
// [7] DotCAKInstall — parseResponseMsg branches
// ===========================================================================
TEST_F(FwBranchTest, DotCAKInstall_NullPtr)
{
CLI::App app;
setupFirmwareCommands(app);
EXPECT_NO_THROW(commands[7]->parseResponseMsg(nullptr, 0));
}
TEST_F(FwBranchTest, DotCAKInstall_PayloadTooSmall)
{
CLI::App app;
setupFirmwareCommands(app);
std::vector<uint8_t> buf(sizeof(nsm_msg_hdr) + 1, 0);
auto* msg = reinterpret_cast<nsm_msg*>(buf.data());
EXPECT_NO_THROW(
commands[7]->parseResponseMsg(msg, 1)); // payloadLength < min
}
TEST_F(FwBranchTest, DotCAKInstall_DecodeError)
{
CLI::App app;
setupFirmwareCommands(app);
std::vector<uint8_t> buf(sizeof(nsm_msg_hdr) + NSM_RESPONSE_ERROR_LEN);
auto* msg = reinterpret_cast<nsm_msg*>(buf.data());
// Set cc to non-zero (error) so decode returns early before any OOB read.
msg->payload[1] = 1;
EXPECT_NO_THROW(commands[7]->parseResponseMsg(msg, buf.size()));
}
TEST_F(FwBranchTest, DotCAKInstall_Success)
{
CLI::App app;
setupFirmwareCommands(app);
parseSubcmdArgs(app, "DotCAKInstall",
{"--cak_key_auth_scheme", "0", "--cak_ecdsa_key",
kEcdsaKeyFile, "--lak_key_auth_scheme", "0",
"--lak_ecdsa_key", kEcdsaKeyFile});
std::vector<uint8_t> buf(sizeof(nsm_msg_hdr) + sizeof(nsm_common_resp));
auto* msg = reinterpret_cast<nsm_msg*>(buf.data());
auto* resp = reinterpret_cast<nsm_common_resp*>(msg->payload);
resp->command = NSM_FW_DOT_CAK_INSTALL;
resp->completion_code = NSM_SUCCESS;
resp->reserved = 0;
encode_nsm_dot_cak_install_resp(0, NSM_SUCCESS, ERR_NULL, msg);
EXPECT_NO_THROW(commands[7]->parseResponseMsg(msg, buf.size()));
}
// DotCAKInstall with wrong command code in response
TEST_F(FwBranchTest, DotCAKInstall_WrongCommand)
{
CLI::App app;
setupFirmwareCommands(app);
parseSubcmdArgs(app, "DotCAKInstall",
{"--cak_key_auth_scheme", "0", "--cak_ecdsa_key",
kEcdsaKeyFile, "--lak_key_auth_scheme", "0",
"--lak_ecdsa_key", kEcdsaKeyFile});
std::vector<uint8_t> buf(sizeof(nsm_msg_hdr) + sizeof(nsm_common_resp));
auto* msg = reinterpret_cast<nsm_msg*>(buf.data());
encode_nsm_dot_cak_install_resp(0, NSM_SUCCESS, ERR_NULL, msg);
// Overwrite command code to trigger the warning branch
auto* resp = reinterpret_cast<nsm_common_resp*>(msg->payload);
resp->command = 0xFF;
EXPECT_NO_THROW(commands[7]->parseResponseMsg(msg, buf.size()));
}
// ===========================================================================
// [8] DotCAKBypass — branches: payloadTooShort, decodeError, cc!=SUCCESS,
// success
// ===========================================================================
TEST_F(FwBranchTest, DotCAKBypass_PayloadTooShort)
{
CLI::App app;
setupFirmwareCommands(app);
std::vector<uint8_t> buf(sizeof(nsm_msg_hdr) + 1, 0);
auto* msg = reinterpret_cast<nsm_msg*>(buf.data());
EXPECT_NO_THROW(commands[8]->parseResponseMsg(msg, 1));
}
TEST_F(FwBranchTest, DotCAKBypass_DecodeError)
{
CLI::App app;
setupFirmwareCommands(app);
std::vector<uint8_t> buf(sizeof(nsm_msg_hdr) + NSM_RESPONSE_ERROR_LEN);
auto* msg = reinterpret_cast<nsm_msg*>(buf.data());
// Set cc to non-zero (error) so decode returns early before any OOB read.
msg->payload[1] = 1;
EXPECT_NO_THROW(commands[8]->parseResponseMsg(msg, buf.size()));
}
TEST_F(FwBranchTest, DotCAKBypass_NonSuccessCC)
{
CLI::App app;
setupFirmwareCommands(app);
std::vector<uint8_t> buf(sizeof(nsm_msg_hdr) + sizeof(nsm_common_resp));
auto* msg = reinterpret_cast<nsm_msg*>(buf.data());
encode_nsm_dot_cak_bypass_resp(0, NSM_ERROR, 0x1234, msg);
EXPECT_NO_THROW(commands[8]->parseResponseMsg(msg, buf.size()));
}
TEST_F(FwBranchTest, DotCAKBypass_Success)
{
CLI::App app;
setupFirmwareCommands(app);
std::vector<uint8_t> buf(sizeof(nsm_msg_hdr) + sizeof(nsm_common_resp));
auto* msg = reinterpret_cast<nsm_msg*>(buf.data());
encode_nsm_dot_cak_bypass_resp(0, NSM_SUCCESS, ERR_NULL, msg);
EXPECT_NO_THROW(commands[8]->parseResponseMsg(msg, buf.size()));
}
// ===========================================================================
// [9] ImageCopyControl — switch(requestType) branches
// ===========================================================================
// ImageCopyControl tests are extensively covered in nsm_firmware_cmd_parse_test
// ===========================================================================
// [10] DotLock — branches: nullptr, error, success with/without outputFile
// ===========================================================================
TEST_F(FwBranchTest, DotLock_NullPtr)
{
CLI::App app;
setupFirmwareCommands(app);
EXPECT_NO_THROW(commands[10]->parseResponseMsg(nullptr, 0));
}
TEST_F(FwBranchTest, DotLock_Error)
{
CLI::App app;
setupFirmwareCommands(app);
parseSubcmdArgs(app, "DotLock",
{"--cak_key_auth_scheme", "0", "--cak_ecdsa_key",
kEcdsaKeyFile, "--lak_key_auth_scheme", "0",
"--lak_ecdsa_key", kEcdsaKeyFile, "--unlock_method", "0",
"--lock_signature_auth_scheme", "0", "--signature",
kSignatureFile});
std::vector<uint8_t> buf(sizeof(nsm_msg_hdr) + NSM_RESPONSE_ERROR_LEN);
auto* msg = reinterpret_cast<nsm_msg*>(buf.data());
// Set cc to non-zero (error) so decode returns early before any OOB read.
msg->payload[1] = 1;
EXPECT_NO_THROW(commands[10]->parseResponseMsg(msg, buf.size()));
}
TEST_F(FwBranchTest, DotLock_SuccessNoOutput)
{
CLI::App app;
setupFirmwareCommands(app);
parseSubcmdArgs(app, "DotLock",
{"--cak_key_auth_scheme", "0", "--cak_ecdsa_key",
kEcdsaKeyFile, "--lak_key_auth_scheme", "0",
"--lak_ecdsa_key", kEcdsaKeyFile, "--unlock_method", "0",
"--lock_signature_auth_scheme", "0", "--signature",
kSignatureFile});
std::vector<uint8_t> buf(sizeof(nsm_msg_hdr) + sizeof(nsm_dot_lock_resp));
auto* msg = reinterpret_cast<nsm_msg*>(buf.data());
std::vector<uint8_t> dotBlob(DOT_BLOB_SIZE, 0xAA);
encode_nsm_dot_lock_resp(0, NSM_SUCCESS, ERR_NULL, dotBlob.data(), msg);
EXPECT_NO_THROW(commands[10]->parseResponseMsg(msg, buf.size()));
}
TEST_F(FwBranchTest, DotLock_SuccessWithOutput)
{
CLI::App app;
setupFirmwareCommands(app);
parseSubcmdArgs(app, "DotLock",
{"--cak_key_auth_scheme", "0", "--cak_ecdsa_key",
kEcdsaKeyFile, "--lak_key_auth_scheme", "0",
"--lak_ecdsa_key", kEcdsaKeyFile, "--unlock_method", "0",
"--lock_signature_auth_scheme", "0", "--signature",
kSignatureFile, "--output", kOutputFile});
std::vector<uint8_t> buf(sizeof(nsm_msg_hdr) + sizeof(nsm_dot_lock_resp));
auto* msg = reinterpret_cast<nsm_msg*>(buf.data());
std::vector<uint8_t> dotBlob(DOT_BLOB_SIZE, 0xBB);
encode_nsm_dot_lock_resp(0, NSM_SUCCESS, ERR_NULL, dotBlob.data(), msg);
EXPECT_NO_THROW(commands[10]->parseResponseMsg(msg, buf.size()));
}
// ===========================================================================
// [11] DotCAKRotate — branches: nullptr, decode error, cc error, success
// ===========================================================================
TEST_F(FwBranchTest, DotCAKRotate_NullPtr)
{
CLI::App app;
setupFirmwareCommands(app);
EXPECT_NO_THROW(commands[11]->parseResponseMsg(nullptr, 0));
}
TEST_F(FwBranchTest, DotCAKRotate_DecodeError)
{
CLI::App app;
setupFirmwareCommands(app);
std::vector<uint8_t> buf(sizeof(nsm_msg_hdr) + NSM_RESPONSE_ERROR_LEN);
auto* msg = reinterpret_cast<nsm_msg*>(buf.data());
// Set cc to non-zero (error) so decode returns early before any OOB read.
msg->payload[1] = 1;
EXPECT_NO_THROW(commands[11]->parseResponseMsg(msg, buf.size()));
}
TEST_F(FwBranchTest, DotCAKRotate_NonSuccessCC)
{
CLI::App app;
setupFirmwareCommands(app);
parseSubcmdArgs(app, "DotCAKRotate",
{"--new_cak_key_auth_scheme", "0", "--new_cak_ecdsa_key",
kEcdsaKeyFile, "--lak_signature_auth_scheme", "0",
"--signature", kSignatureFile});
std::vector<uint8_t> buf(sizeof(nsm_msg_hdr) +
sizeof(nsm_dot_cak_rotate_resp));
auto* msg = reinterpret_cast<nsm_msg*>(buf.data());
std::vector<uint8_t> dotBlob(DOT_BLOB_SIZE, 0);
encode_nsm_dot_cak_rotate_resp(0, NSM_ERROR, 0x1234, dotBlob.data(), msg);
EXPECT_NO_THROW(commands[11]->parseResponseMsg(msg, buf.size()));
}
TEST_F(FwBranchTest, DotCAKRotate_SuccessNoOutput)
{
CLI::App app;
setupFirmwareCommands(app);
parseSubcmdArgs(app, "DotCAKRotate",
{"--new_cak_key_auth_scheme", "0", "--new_cak_ecdsa_key",
kEcdsaKeyFile, "--lak_signature_auth_scheme", "0",
"--signature", kSignatureFile});
std::vector<uint8_t> buf(sizeof(nsm_msg_hdr) +
sizeof(nsm_dot_cak_rotate_resp));
auto* msg = reinterpret_cast<nsm_msg*>(buf.data());
std::vector<uint8_t> dotBlob(DOT_BLOB_SIZE, 0xAA);
encode_nsm_dot_cak_rotate_resp(0, NSM_SUCCESS, ERR_NULL, dotBlob.data(),
msg);
EXPECT_NO_THROW(commands[11]->parseResponseMsg(msg, buf.size()));
}
TEST_F(FwBranchTest, DotCAKRotate_SuccessWithOutput)
{
CLI::App app;
setupFirmwareCommands(app);
parseSubcmdArgs(app, "DotCAKRotate",
{"--new_cak_key_auth_scheme", "0", "--new_cak_ecdsa_key",
kEcdsaKeyFile, "--lak_signature_auth_scheme", "0",
"--signature", kSignatureFile, "--output", kOutputFile});
std::vector<uint8_t> buf(sizeof(nsm_msg_hdr) +
sizeof(nsm_dot_cak_rotate_resp));
auto* msg = reinterpret_cast<nsm_msg*>(buf.data());
std::vector<uint8_t> dotBlob(DOT_BLOB_SIZE, 0xBB);
encode_nsm_dot_cak_rotate_resp(0, NSM_SUCCESS, ERR_NULL, dotBlob.data(),
msg);
EXPECT_NO_THROW(commands[11]->parseResponseMsg(msg, buf.size()));
}
// ===========================================================================
// [12] DotUnlockChallenge — branches
// ===========================================================================
TEST_F(FwBranchTest, DotUnlockChallenge_NullPtr)
{
CLI::App app;
setupFirmwareCommands(app);
EXPECT_NO_THROW(commands[12]->parseResponseMsg(nullptr, 0));
}
TEST_F(FwBranchTest, DotUnlockChallenge_Error)
{
CLI::App app;
setupFirmwareCommands(app);
parseSubcmdArgs(app, "DotUnlockChallenge",
{"--unlock_type", "1", "--output", kOutputFile});
std::vector<uint8_t> buf(sizeof(nsm_msg_hdr) + NSM_RESPONSE_ERROR_LEN);
auto* msg = reinterpret_cast<nsm_msg*>(buf.data());
// Set cc to non-zero (error) so decode returns early before any OOB read.
msg->payload[1] = 1;
EXPECT_NO_THROW(commands[12]->parseResponseMsg(msg, buf.size()));
}
TEST_F(FwBranchTest, DotUnlockChallenge_Success)
{
CLI::App app;
setupFirmwareCommands(app);
parseSubcmdArgs(app, "DotUnlockChallenge",
{"--unlock_type", "1", "--output", kOutputFile});
std::vector<uint8_t> buf(sizeof(nsm_msg_hdr) +
sizeof(nsm_dot_unlock_challenge_resp));
auto* msg = reinterpret_cast<nsm_msg*>(buf.data());
std::vector<uint8_t> challenge(STATIC_CHALLENGE_SIZE, 0x42);
encode_nsm_dot_unlock_challenge_resp(0, NSM_SUCCESS, ERR_NULL,
challenge.data(), msg);
EXPECT_NO_THROW(commands[12]->parseResponseMsg(msg, buf.size()));
}
// ===========================================================================
// [13] DotUnlock — branches
// ===========================================================================
TEST_F(FwBranchTest, DotUnlock_NullPtr)
{
CLI::App app;
setupFirmwareCommands(app);
EXPECT_NO_THROW(commands[13]->parseResponseMsg(nullptr, 0));
}
TEST_F(FwBranchTest, DotUnlock_Error)
{
CLI::App app;
setupFirmwareCommands(app);
parseSubcmdArgs(app, "DotUnlock", {"--signature", kSignatureFile});
std::vector<uint8_t> buf(sizeof(nsm_msg_hdr) + sizeof(nsm_common_resp));
auto* msg = reinterpret_cast<nsm_msg*>(buf.data());
encode_nsm_dot_unlock_resp(0, NSM_ERROR, 0x5678, msg);
EXPECT_NO_THROW(commands[13]->parseResponseMsg(msg, buf.size()));
}
TEST_F(FwBranchTest, DotUnlock_Success)
{
CLI::App app;
setupFirmwareCommands(app);
parseSubcmdArgs(app, "DotUnlock", {"--signature", kSignatureFile});
std::vector<uint8_t> buf(sizeof(nsm_msg_hdr) + sizeof(nsm_common_resp));
auto* msg = reinterpret_cast<nsm_msg*>(buf.data());
encode_nsm_dot_unlock_resp(0, NSM_SUCCESS, ERR_NULL, msg);
EXPECT_NO_THROW(commands[13]->parseResponseMsg(msg, buf.size()));
}
// ===========================================================================
// [14] DotGetInfo — branches
// ===========================================================================
TEST_F(FwBranchTest, DotGetInfo_NullPtr)
{
CLI::App app;
setupFirmwareCommands(app);
EXPECT_NO_THROW(commands[14]->parseResponseMsg(nullptr, 0));
}
TEST_F(FwBranchTest, DotGetInfo_Error)
{
CLI::App app;
setupFirmwareCommands(app);
std::vector<uint8_t> buf(sizeof(nsm_msg_hdr) + NSM_RESPONSE_ERROR_LEN);
auto* msg = reinterpret_cast<nsm_msg*>(buf.data());
// Set cc to non-zero (error) so decode returns early before any OOB read.
msg->payload[1] = 1;
EXPECT_NO_THROW(commands[14]->parseResponseMsg(msg, buf.size()));
}
TEST_F(FwBranchTest, DotGetInfo_SuccessNoOutput)
{
CLI::App app;
setupFirmwareCommands(app);
std::vector<uint8_t> buf(sizeof(nsm_msg_hdr) +
sizeof(nsm_dot_get_info_resp));
auto* msg = reinterpret_cast<nsm_msg*>(buf.data());
std::vector<uint8_t> dotBlob(DOT_BLOB_SIZE, 0xCC);
encode_nsm_dot_get_info_resp(0, NSM_SUCCESS, ERR_NULL, 1, 0, 5,
dotBlob.data(), msg);
EXPECT_NO_THROW(commands[14]->parseResponseMsg(msg, buf.size()));
}
TEST_F(FwBranchTest, DotGetInfo_SuccessWithOutput)
{
CLI::App app;
setupFirmwareCommands(app);
parseSubcmdArgs(app, "DotGetInfo", {"--output", kOutputFile});
std::vector<uint8_t> buf(sizeof(nsm_msg_hdr) +
sizeof(nsm_dot_get_info_resp));
auto* msg = reinterpret_cast<nsm_msg*>(buf.data());
std::vector<uint8_t> dotBlob(DOT_BLOB_SIZE, 0xDD);
encode_nsm_dot_get_info_resp(0, NSM_SUCCESS, ERR_NULL, 1, 0, 5,
dotBlob.data(), msg);
EXPECT_NO_THROW(commands[14]->parseResponseMsg(msg, buf.size()));
}
// ===========================================================================
// [15] DotGetStatus — branches: nullptr, error, success with status 0-3
// ===========================================================================
TEST_F(FwBranchTest, DotGetStatus_NullPtr)
{
CLI::App app;
setupFirmwareCommands(app);
EXPECT_NO_THROW(commands[15]->parseResponseMsg(nullptr, 0));
}
TEST_F(FwBranchTest, DotGetStatus_Error)
{
CLI::App app;
setupFirmwareCommands(app);
std::vector<uint8_t> buf(sizeof(nsm_msg_hdr) + NSM_RESPONSE_ERROR_LEN);
auto* msg = reinterpret_cast<nsm_msg*>(buf.data());
// Set cc to non-zero (error) so decode returns early before any OOB read.
msg->payload[1] = 1;
EXPECT_NO_THROW(commands[15]->parseResponseMsg(msg, buf.size()));
}
TEST_F(FwBranchTest, DotGetStatus_AllStatuses)
{
// status & 0x03: 0=Uninitialized, 1=Volatile, 2=Mutable Locked,
// 3=Mutable Disabled
for (uint8_t status = 0; status <= 3; ++status)
{
CLI::App app;
setupFirmwareCommands(app);
std::vector<uint8_t> buf(sizeof(nsm_msg_hdr) +
sizeof(nsm_dot_get_status_resp));
auto* msg = reinterpret_cast<nsm_msg*>(buf.data());
encode_nsm_dot_get_status_resp(0, NSM_SUCCESS, ERR_NULL, status, msg);
EXPECT_NO_THROW(commands[15]->parseResponseMsg(msg, buf.size()));
}
}
// ===========================================================================
// [16] DotDisable — branches
// ===========================================================================
TEST_F(FwBranchTest, DotDisable_NullPtr)
{
CLI::App app;
setupFirmwareCommands(app);
EXPECT_NO_THROW(commands[16]->parseResponseMsg(nullptr, 0));
}
TEST_F(FwBranchTest, DotDisable_Error)
{
CLI::App app;
setupFirmwareCommands(app);
std::vector<uint8_t> buf(sizeof(nsm_msg_hdr) + NSM_RESPONSE_ERROR_LEN);
auto* msg = reinterpret_cast<nsm_msg*>(buf.data());
// Set cc to non-zero (error) so decode returns early before any OOB read.
msg->payload[1] = 1;
EXPECT_NO_THROW(commands[16]->parseResponseMsg(msg, buf.size()));
}
TEST_F(FwBranchTest, DotDisable_SuccessNoOutput)
{
CLI::App app;
setupFirmwareCommands(app);
parseSubcmdArgs(app, "DotDisable",
{"--lak_key_auth_scheme", "0", "--lak_ecdsa_key",
kEcdsaKeyFile, "--unlock_method", "0",
"--disable_signature_auth_scheme", "0", "--signature",
kSignatureFile});
std::vector<uint8_t> buf(sizeof(nsm_msg_hdr) +
sizeof(nsm_dot_disable_resp));
auto* msg = reinterpret_cast<nsm_msg*>(buf.data());
std::vector<uint8_t> dotBlob(DOT_BLOB_SIZE, 0xEE);
encode_nsm_dot_disable_resp(0, NSM_SUCCESS, ERR_NULL, dotBlob.data(), msg);
EXPECT_NO_THROW(commands[16]->parseResponseMsg(msg, buf.size()));
}
TEST_F(FwBranchTest, DotDisable_SuccessWithOutput)
{
CLI::App app;
setupFirmwareCommands(app);
parseSubcmdArgs(app, "DotDisable",
{"--lak_key_auth_scheme", "0", "--lak_ecdsa_key",
kEcdsaKeyFile, "--unlock_method", "0",
"--disable_signature_auth_scheme", "0", "--signature",
kSignatureFile, "--output", kOutputFile});
std::vector<uint8_t> buf(sizeof(nsm_msg_hdr) +
sizeof(nsm_dot_disable_resp));
auto* msg = reinterpret_cast<nsm_msg*>(buf.data());
std::vector<uint8_t> dotBlob(DOT_BLOB_SIZE, 0xFF);
encode_nsm_dot_disable_resp(0, NSM_SUCCESS, ERR_NULL, dotBlob.data(), msg);
EXPECT_NO_THROW(commands[16]->parseResponseMsg(msg, buf.size()));
}
// ===========================================================================
// [17] DotOverride — branches
// ===========================================================================
TEST_F(FwBranchTest, DotOverride_NullPtr)
{
CLI::App app;
setupFirmwareCommands(app);
EXPECT_NO_THROW(commands[17]->parseResponseMsg(nullptr, 0));
}
TEST_F(FwBranchTest, DotOverride_Error)
{
CLI::App app;
setupFirmwareCommands(app);
parseSubcmdArgs(
app, "DotOverride",
{"--vendor_signature_auth_scheme", "0", "--signature", kSignatureFile});
std::vector<uint8_t> buf(sizeof(nsm_msg_hdr) + sizeof(nsm_common_resp));
auto* msg = reinterpret_cast<nsm_msg*>(buf.data());
encode_nsm_dot_override_resp(0, NSM_ERROR, 0x1111, msg);
EXPECT_NO_THROW(commands[17]->parseResponseMsg(msg, buf.size()));
}
TEST_F(FwBranchTest, DotOverride_Success)
{
CLI::App app;
setupFirmwareCommands(app);
parseSubcmdArgs(
app, "DotOverride",
{"--vendor_signature_auth_scheme", "0", "--signature", kSignatureFile});
std::vector<uint8_t> buf(sizeof(nsm_msg_hdr) + sizeof(nsm_common_resp));
auto* msg = reinterpret_cast<nsm_msg*>(buf.data());
encode_nsm_dot_override_resp(0, NSM_SUCCESS, ERR_NULL, msg);
EXPECT_NO_THROW(commands[17]->parseResponseMsg(msg, buf.size()));
}
// ===========================================================================
// [18] DotRecovery — branches
// ===========================================================================
TEST_F(FwBranchTest, DotRecovery_NullPtr)
{
CLI::App app;
setupFirmwareCommands(app);
EXPECT_NO_THROW(commands[18]->parseResponseMsg(nullptr, 0));
}
TEST_F(FwBranchTest, DotRecovery_Error)
{
CLI::App app;
setupFirmwareCommands(app);
parseSubcmdArgs(app, "DotRecovery", {"--dot_blob", kDotBlobFile});
std::vector<uint8_t> buf(sizeof(nsm_msg_hdr) + sizeof(nsm_common_resp));
auto* msg = reinterpret_cast<nsm_msg*>(buf.data());
encode_nsm_dot_recovery_resp(0, NSM_ERROR, 0x2222, msg);
EXPECT_NO_THROW(commands[18]->parseResponseMsg(msg, buf.size()));
}
TEST_F(FwBranchTest, DotRecovery_Success)
{
CLI::App app;
setupFirmwareCommands(app);
parseSubcmdArgs(app, "DotRecovery", {"--dot_blob", kDotBlobFile});
std::vector<uint8_t> buf(sizeof(nsm_msg_hdr) + sizeof(nsm_common_resp));
auto* msg = reinterpret_cast<nsm_msg*>(buf.data());
encode_nsm_dot_recovery_resp(0, NSM_SUCCESS, ERR_NULL, msg);
EXPECT_NO_THROW(commands[18]->parseResponseMsg(msg, buf.size()));
}
} // namespace nsmtool::firmware