blob: 131bb26f3d84463c30fce6eebe147ca7bccea262 [file] [log] [blame] [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 "nsm_firmware_cmd.hpp"
#include "base.h"
#include "firmware-utils.h"
#include "cmd_helper.hpp"
#include "nsmDotUtils.hpp"
#include "utils.hpp"
#include <CLI/CLI.hpp>
#include <algorithm>
#include <fstream>
#include <iomanip>
#include <sstream>
namespace nsmtool::firmware
{
using namespace nsmtool::helper;
std::vector<std::unique_ptr<CommandInterface>> commands;
// DOT CAK/LAK key size constants
constexpr size_t ECDSA_KEY_SIZE =
96; // Total ECDSA key size (x + y coordinates)
constexpr size_t ECDSA_COORDINATE_SIZE = 48; // Each coordinate (x or y) size
constexpr size_t LMS_KEY_SIZE = 48; // LMS public key size
constexpr size_t AUTH_SCHEME_SIZE =
4; // Authentication scheme field size (NvU32)
constexpr size_t CRYPTO_PCP_SIZE = AUTH_SCHEME_SIZE + ECDSA_COORDINATE_SIZE +
ECDSA_COORDINATE_SIZE +
LMS_KEY_SIZE; // 148 bytes total
class GetRotInformation : public CommandInterface
{
public:
~GetRotInformation() = default;
GetRotInformation() = delete;
GetRotInformation(const GetRotInformation&) = delete;
GetRotInformation(GetRotInformation&&) = default;
GetRotInformation& operator=(const GetRotInformation&) = delete;
GetRotInformation& operator=(GetRotInformation&&) = default;
explicit GetRotInformation(const char* type, const char* name,
CLI::App* app) :
CommandInterface(type, name, app)
{
auto ccOptionGroup = app->add_option_group(
"Required",
"Get information about a particular firmware set installed on an endpoint");
ccOptionGroup
->add_option("-c,--classification", classification,
"Component classification")
->required();
ccOptionGroup
->add_option("-i,--identifier", identifier, "Component identifier")
->required();
ccOptionGroup->add_option("-d,--index", index, "Component index")
->required();
}
std::pair<int, std::vector<uint8_t>> createRequestMsg() override
{
std::vector<uint8_t> requestMsg(
sizeof(nsm_msg_hdr) + sizeof(nsm_firmware_get_erot_state_info_req));
nsm_firmware_erot_state_info_req nsm_req;
nsm_req.component_classification = classification;
nsm_req.component_classification_index = index;
nsm_req.component_identifier = identifier;
auto request = reinterpret_cast<nsm_msg*>(requestMsg.data());
auto rc = encode_nsm_query_get_erot_state_parameters_req(
instanceId, &nsm_req, request);
return std::make_pair(rc, requestMsg);
}
void parseResponseMsg(nsm_msg* responsePtr, size_t payloadLength) override
{
uint8_t cc = NSM_SUCCESS;
uint16_t reason_code = ERR_NULL;
nsm_firmware_erot_state_info_resp erot_info = {};
auto rc = decode_nsm_query_get_erot_state_parameters_resp(
responsePtr, payloadLength, &cc, &reason_code, &erot_info);
if (rc != NSM_SW_SUCCESS || cc != NSM_SUCCESS)
{
std::cerr << "Response message error: "
<< "rc=" << rc << ", cc=" << (int)cc
<< ", reasonCode=" << (int)reason_code << "\n";
free(erot_info.slot_info);
return;
}
ordered_json result;
result["Completion code"] = cc;
result["Reason code"] = reason_code;
result["Background copy policy persistent"] = mapEnumToString(
static_cast<uint32_t>(erot_info.fq_resp_hdr.background_copy_policy),
bgCopyPolicyMap);
result["Active Slot"] =
static_cast<uint32_t>(erot_info.fq_resp_hdr.active_slot);
result["Active Keyset"] =
static_cast<uint32_t>(erot_info.fq_resp_hdr.active_keyset);
result["Minimum security version"] = static_cast<uint32_t>(
erot_info.fq_resp_hdr.minimum_security_version);
result["Inband update policy persistent"] =
static_cast<uint32_t>(erot_info.fq_resp_hdr.inband_update_policy);
result["Boot status code"] =
static_cast<uint64_t>(erot_info.fq_resp_hdr.boot_status_code);
result["Firmware slot count"] =
static_cast<uint32_t>(erot_info.fq_resp_hdr.firmware_slot_count);
result["Inband update policy current"] = static_cast<uint32_t>(
erot_info.fq_resp_hdr.inband_update_policy_current);
result["Background copy policy current"] = static_cast<uint32_t>(
erot_info.fq_resp_hdr.background_copy_policy_current);
result["AP SKU ID"] =
static_cast<uint32_t>(erot_info.fq_resp_hdr.ap_sku_id);
std::vector<ordered_json> slots;
for (int i = 0; i < erot_info.fq_resp_hdr.firmware_slot_count; i++)
{
ordered_json slot_info;
slot_info["Slot ID"] =
static_cast<uint32_t>(erot_info.slot_info[i].slot_id);
slot_info["Fw version string"] =
(char*)(&(erot_info.slot_info[i].firmware_version_string[0]));
slot_info["Version comp stamp"] = static_cast<uint32_t>(
erot_info.slot_info[i].version_comparison_stamp);
slot_info["Build type"] = mapEnumToString(
static_cast<uint32_t>(erot_info.slot_info[i].build_type),
buildTypeMap);
slot_info["Signing type"] = mapEnumToString(
static_cast<uint32_t>(erot_info.slot_info[i].signing_type),
signingTypeMap);
slot_info["WR Protect State"] =
mapEnumToString(static_cast<uint32_t>(
erot_info.slot_info[i].write_protect_state),
writeProtectMap);
slot_info["Firmware state"] = mapEnumToString(
static_cast<uint32_t>(erot_info.slot_info[i].firmware_state),
firmwareStateMap);
slot_info["Security version number"] = static_cast<uint32_t>(
erot_info.slot_info[i].security_version_number);
slot_info["Signing key index"] =
static_cast<uint32_t>(erot_info.slot_info[i].signing_key_index);
slots.push_back(std::move(slot_info));
}
result["Slot information"] = std::move(slots);
DisplayInJson(result);
free(erot_info.slot_info);
}
private:
uint16_t classification{};
uint16_t identifier{};
uint8_t index{};
const std::unordered_map<uint32_t, std::string> bgCopyPolicyMap = {
{0, "Disabled"}, {1, "Enabled"}};
const std::unordered_map<uint32_t, std::string> buildTypeMap = {
{0, "Development"}, {1, "Release"}};
const std::unordered_map<uint32_t, std::string> signingTypeMap = {
{0, "Debug"}, {1, "Production"}, {2, "External"}, {4, "DOT"}};
const std::unordered_map<uint32_t, std::string> writeProtectMap = {
{0, "Disabled"}, {1, "Enabled"}};
const std::unordered_map<uint32_t, std::string> firmwareStateMap = {
{0, "Unknown"},
{1, "Activated"},
{2, "Pending Activation"},
{3, "Staged"},
{4, "Write in progress"},
{5, "Inactive"},
{6, "Failed authentication"},
{7, "Pending image copy"},
{8, "Image copy in progress"},
{9, "Failed image copy"}};
std::string mapEnumToString(
uint32_t value,
const std::unordered_map<uint32_t, std::string>& mapping) const
{
auto it = mapping.find(value);
return it != mapping.end() ? it->second : "Not Defined";
}
};
class QueryCodeAuthKeyPerm : public CommandInterface
{
public:
~QueryCodeAuthKeyPerm() = default;
QueryCodeAuthKeyPerm() = delete;
QueryCodeAuthKeyPerm(const QueryCodeAuthKeyPerm&) = delete;
QueryCodeAuthKeyPerm(QueryCodeAuthKeyPerm&&) = default;
QueryCodeAuthKeyPerm& operator=(const QueryCodeAuthKeyPerm&) = delete;
QueryCodeAuthKeyPerm& operator=(QueryCodeAuthKeyPerm&&) = default;
using CommandInterface::CommandInterface;
explicit QueryCodeAuthKeyPerm(const char* type, const char* name,
CLI::App* app) :
CommandInterface(type, name, app)
{
auto optionGroup = app->add_option_group(
"Required", "Query firmware code authentication key permissions");
optionGroup
->add_option("-c,--classification", classification,
"Component classification")
->required();
optionGroup
->add_option("-i,--identifier", identifier, "Component identifier")
->required();
optionGroup->add_option("-d,--index", index, "Component index")
->required();
}
std::pair<int, std::vector<uint8_t>> createRequestMsg() override
{
std::vector<uint8_t> requestMsg(
sizeof(nsm_msg_hdr) + sizeof(nsm_code_auth_key_perm_query_req));
auto request = reinterpret_cast<nsm_msg*>(requestMsg.data());
auto rc = encode_nsm_code_auth_key_perm_query_req(
instanceId, classification, identifier, index, request);
return std::make_pair(rc, requestMsg);
}
void parseResponseMsg(nsm_msg* responsePtr, size_t payloadLength) override
{
uint8_t cc = NSM_SUCCESS;
uint16_t reasonCode = ERR_NULL;
uint16_t activeComponentKeyIndex;
uint16_t pendingComponentKeyIndex;
uint8_t permissionBitmapLength;
auto rc = decode_nsm_code_auth_key_perm_query_resp(
responsePtr, payloadLength, &cc, &reasonCode,
&activeComponentKeyIndex, &pendingComponentKeyIndex,
&permissionBitmapLength, NULL, NULL, NULL, NULL);
if (rc != NSM_SW_SUCCESS || cc != NSM_SUCCESS)
{
std::cerr << "Response message error: "
<< "rc=" << rc << ", cc=" << (int)cc
<< ", reasonCode=" << (int)reasonCode << "\n";
return;
}
std::vector<uint8_t> activeComponentKeyPermBitmap(
permissionBitmapLength);
std::vector<uint8_t> pendingComponentKeyPermBitmap(
permissionBitmapLength);
std::vector<uint8_t> efuseKeyPermBitmap(permissionBitmapLength);
std::vector<uint8_t> pendingEfuseKeyPermBitmap(permissionBitmapLength);
rc = decode_nsm_code_auth_key_perm_query_resp(
responsePtr, payloadLength, &cc, &reasonCode,
&activeComponentKeyIndex, &pendingComponentKeyIndex,
&permissionBitmapLength, activeComponentKeyPermBitmap.data(),
pendingComponentKeyPermBitmap.data(), efuseKeyPermBitmap.data(),
pendingEfuseKeyPermBitmap.data());
if (rc != NSM_SW_SUCCESS || cc != NSM_SUCCESS)
{
std::cerr << "Response message error: "
<< "rc=" << rc << ", cc=" << (int)cc
<< ", reasonCode=" << (int)reasonCode << "\n";
return;
}
nlohmann::ordered_json result;
result["Completion code"] = cc;
result["Reason code"] = reasonCode;
result["Active component key index"] = activeComponentKeyIndex;
result["Pending component key index"] = pendingComponentKeyIndex;
result["Permission bitmap length"] = permissionBitmapLength;
auto activeComponentKeyPermIndices =
utils::bitmapToIndices(activeComponentKeyPermBitmap);
auto pendingComponentKeyPermIndices =
utils::bitmapToIndices(pendingComponentKeyPermBitmap);
auto efuseKeyPermIndices = utils::bitmapToIndices(efuseKeyPermBitmap);
auto pendingEfuseKeyPermIndices =
utils::bitmapToIndices(pendingEfuseKeyPermBitmap);
result["Active component trusted key indices"] =
std::move(activeComponentKeyPermIndices.first);
result["Active component revoked key indices"] =
std::move(activeComponentKeyPermIndices.second);
result["Pending component trusted key indices"] =
std::move(pendingComponentKeyPermIndices.first);
result["Pending component revoked key indices"] =
std::move(pendingComponentKeyPermIndices.second);
result["EFUSE trusted key indices"] =
std::move(efuseKeyPermIndices.first);
result["EFUSE revoked key indices"] =
std::move(efuseKeyPermIndices.second);
result["Pending EFUSE trusted key indices"] =
std::move(pendingEfuseKeyPermIndices.first);
result["Pending EFUSE revoked key indices"] =
std::move(pendingEfuseKeyPermIndices.second);
DisplayInJson(result);
}
private:
uint16_t classification;
uint16_t identifier;
uint8_t index;
};
class UpdateCodeAuthKeyPerm : public CommandInterface
{
public:
~UpdateCodeAuthKeyPerm() = default;
UpdateCodeAuthKeyPerm() = delete;
UpdateCodeAuthKeyPerm(const UpdateCodeAuthKeyPerm&) = delete;
UpdateCodeAuthKeyPerm(UpdateCodeAuthKeyPerm&&) = default;
UpdateCodeAuthKeyPerm& operator=(const UpdateCodeAuthKeyPerm&) = delete;
UpdateCodeAuthKeyPerm& operator=(UpdateCodeAuthKeyPerm&&) = default;
using CommandInterface::CommandInterface;
explicit UpdateCodeAuthKeyPerm(const char* type, const char* name,
CLI::App* app) :
CommandInterface(type, name, app)
{
auto optionGroup = app->add_option_group(
"Required", "Update firmware code authentication key permissions");
optionGroup
->add_option(
"-r,--requestType", requestType,
"Request type - "
"0 - most restrictive permitted value, 1 - specified value")
->required();
optionGroup
->add_option("-c,--classification", classification,
"component classification")
->required();
optionGroup
->add_option("-i,--identifier", identifier, "Component identifier")
->required();
optionGroup
->add_option("-d,--index", index, "Component classification index")
->required();
optionGroup
->add_option(
"-n,--nonce", nonce,
"Nonce obtained from Enable Irreversible Configuration command")
->required();
optionGroup->add_option(
"-k,--keys", revokedKeysString,
"Comma-separated list of indexes of keys to be revoked. "
"Cannot be used when request type is set to 0. "
"Required when request type is set to 1.");
optionGroup->add_option(
"-b,--bitmap", bitmapSize,
"Size of the permission bitmap to be sent. "
"If set to 0 or omitted, the size is calculated automatically "
"based on the provided indexes.");
}
std::pair<int, std::vector<uint8_t>> createRequestMsg() override
{
std::vector<uint8_t> indices;
if (requestType == 0 && !revokedKeysString.empty())
{
return std::make_pair(NSM_SW_ERROR, std::vector<uint8_t>());
}
if (requestType == 1)
{
std::istringstream iss{revokedKeysString};
std::string indexStr;
while (getline(iss, indexStr, ','))
{
try
{
indices.emplace_back(static_cast<uint8_t>(stoul(indexStr)));
}
catch (const std::exception&)
{
return std::make_pair(NSM_SW_ERROR, std::vector<uint8_t>());
}
}
}
std::vector<uint8_t> bitmap;
try
{
bitmap = utils::indicesToBitmap(indices, bitmapSize);
}
catch (const std::exception&)
{
return std::make_pair(NSM_SW_ERROR_LENGTH, std::vector<uint8_t>());
}
std::vector<uint8_t> requestMsg(
sizeof(nsm_msg_hdr) + sizeof(nsm_code_auth_key_perm_update_req) +
bitmap.size());
auto request = reinterpret_cast<nsm_msg*>(requestMsg.data());
auto rc = encode_nsm_code_auth_key_perm_update_req(
0, requestType, classification, identifier, index, nonce,
bitmap.size(), bitmap.data(), request);
return std::make_pair(rc, requestMsg);
}
void parseResponseMsg(nsm_msg* responsePtr, size_t payloadLength) override
{
uint8_t cc = NSM_SUCCESS;
uint16_t reasonCode = ERR_NULL;
uint32_t updateMethod = 0;
auto rc = decode_nsm_code_auth_key_perm_update_resp(
responsePtr, payloadLength, &cc, &reasonCode, &updateMethod);
if (rc != NSM_SW_SUCCESS || cc != NSM_SUCCESS)
{
std::cerr << "Response message error: "
<< "rc=" << rc << ", cc=" << (int)cc
<< ", reasonCode=" << (int)reasonCode << "\n";
return;
}
nlohmann::ordered_json result;
result["Completion code"] = cc;
result["Reason code"] = reasonCode;
// Fill update methods response
ordered_json updateMethods;
bitfield32_t updateMethodBits = {updateMethod};
if (updateMethodBits.bits.bit0)
{
updateMethods.push_back("Automatic");
}
if (updateMethodBits.bits.bit1)
{
updateMethods.push_back("Self-Contained");
}
if (updateMethodBits.bits.bit2)
{
updateMethods.push_back("Medium-specific reset");
}
if (updateMethodBits.bits.bit3)
{
updateMethods.push_back("System reboot");
}
if (updateMethodBits.bits.bit4)
{
updateMethods.push_back("DC power cycle");
}
if (updateMethodBits.bits.bit5)
{
updateMethods.push_back("AC power cycle");
}
if (updateMethodBits.bits.bit16)
{
updateMethods.push_back("Warm Reset");
}
if (updateMethodBits.bits.bit17)
{
updateMethods.push_back("Hot Reset");
}
if (updateMethodBits.bits.bit18)
{
updateMethods.push_back("Function Level Reset");
}
result["UpdateMethods"] = updateMethods;
DisplayInJson(result);
}
private:
nsm_code_auth_key_perm_request_type requestType;
uint16_t classification;
uint16_t identifier;
uint8_t index;
uint64_t nonce;
uint32_t bitmapSize;
std::string revokedKeysString;
};
class QueryFirmwareSecurityVersion : public CommandInterface
{
public:
~QueryFirmwareSecurityVersion() = default;
QueryFirmwareSecurityVersion() = delete;
QueryFirmwareSecurityVersion(const QueryFirmwareSecurityVersion&) = delete;
QueryFirmwareSecurityVersion(QueryFirmwareSecurityVersion&&) = default;
QueryFirmwareSecurityVersion&
operator=(const QueryFirmwareSecurityVersion&) = delete;
QueryFirmwareSecurityVersion&
operator=(QueryFirmwareSecurityVersion&&) = default;
explicit QueryFirmwareSecurityVersion(const char* type, const char* name,
CLI::App* app) :
CommandInterface(type, name, app)
{
auto ccOptionGroup = app->add_option_group(
"Required", "Parameters for Query Minimum Security Version");
ccOptionGroup
->add_option("-c,--classification", classification,
"Component classification")
->required();
ccOptionGroup
->add_option("-i,--identifier", identifier, "Component identifier")
->required();
ccOptionGroup->add_option("-d,--index", index, "Component index")
->required();
}
std::pair<int, std::vector<uint8_t>> createRequestMsg() override
{
printf("createRequestMsg() called");
std::vector<uint8_t> requestMsg(
sizeof(nsm_msg_hdr) +
sizeof(nsm_firmware_security_version_number_req_command));
nsm_firmware_security_version_number_req nsm_req;
nsm_req.component_classification = htole16(classification);
nsm_req.component_classification_index = index;
nsm_req.component_identifier = htole16(identifier);
auto request = reinterpret_cast<nsm_msg*>(requestMsg.data());
auto rc = encode_nsm_query_firmware_security_version_number_req(
instanceId, &nsm_req, request);
return std::make_pair(rc, requestMsg);
}
void parseResponseMsg(nsm_msg* responsePtr, size_t payloadLength) override
{
uint8_t cc = NSM_SUCCESS;
uint16_t reason_code = ERR_NULL;
struct nsm_firmware_security_version_number_resp sec_info;
auto rc = decode_nsm_query_firmware_security_version_number_resp(
responsePtr, payloadLength, &cc, &reason_code, &sec_info);
if (rc != NSM_SW_SUCCESS || cc != NSM_SUCCESS)
{
std::cerr << "Response message error: "
<< "rc=" << rc << ", cc=" << (int)cc
<< ", reasonCode=" << (int)reason_code << "\n";
return;
}
ordered_json result;
result["Completion code"] = cc;
result["Reason code"] = reason_code;
result["Security Version"] = static_cast<uint16_t>(
htole16(sec_info.active_component_security_version));
result["Pending Security Version"] = static_cast<uint16_t>(
htole16(sec_info.pending_component_security_version));
result["Minimum Security Version"] =
static_cast<uint16_t>(htole16(sec_info.minimum_security_version));
result["Pending Minimum Security Version"] = static_cast<uint16_t>(
htole16(sec_info.pending_minimum_security_version));
DisplayInJson(result);
}
private:
uint16_t classification{};
uint16_t identifier{};
uint8_t index{};
};
class UpdateMinSecurityVersion : public CommandInterface
{
public:
~UpdateMinSecurityVersion() = default;
UpdateMinSecurityVersion() = delete;
UpdateMinSecurityVersion(const UpdateMinSecurityVersion&) = delete;
UpdateMinSecurityVersion(UpdateMinSecurityVersion&&) = default;
UpdateMinSecurityVersion&
operator=(const UpdateMinSecurityVersion&) = delete;
UpdateMinSecurityVersion& operator=(UpdateMinSecurityVersion&&) = default;
explicit UpdateMinSecurityVersion(const char* type, const char* name,
CLI::App* app) :
CommandInterface(type, name, app)
{
auto ccOptionGroup = app->add_option_group(
"Required", "Parameters for Update Minimum Security Version");
ccOptionGroup
->add_option(
"-r,--requestType", requestType,
"Request Type. 0 - most restrictive permitted value, 1 - specified value")
->required();
ccOptionGroup->add_option("-c,--classification", classification,
"Component classification");
ccOptionGroup->add_option("-i,--identifier", identifier,
"Component identifier");
ccOptionGroup->add_option("-d,--index", index, "Component index");
ccOptionGroup
->add_option(
"-n,--nonce", nonce,
"Nonce obtained from Enable Irreversible Configuration command")
->required();
ccOptionGroup->add_option("--reqMinSecVersion", reqMinSecVersion,
"Required if request type is 1");
}
std::pair<int, std::vector<uint8_t>> createRequestMsg() override
{
std::vector<uint8_t> requestMsg(
sizeof(nsm_msg_hdr) +
sizeof(nsm_firmware_update_min_sec_ver_req_command));
nsm_firmware_update_min_sec_ver_req nsm_req;
nsm_req.request_type = requestType;
nsm_req.component_classification = htole16(classification);
nsm_req.component_classification_index = index;
nsm_req.component_identifier = htole16(identifier);
nsm_req.nonce = nonce;
nsm_req.req_min_security_version = htole16(reqMinSecVersion);
auto request = reinterpret_cast<nsm_msg*>(requestMsg.data());
auto rc = encode_nsm_firmware_update_sec_ver_req(instanceId, &nsm_req,
request);
return std::make_pair(rc, requestMsg);
}
void parseResponseMsg(nsm_msg* responsePtr, size_t payloadLength) override
{
uint8_t cc = NSM_SUCCESS;
uint16_t reason_code = ERR_NULL;
struct nsm_firmware_update_min_sec_ver_resp sec_info;
auto rc = decode_nsm_firmware_update_sec_ver_resp(
responsePtr, payloadLength, &cc, &reason_code, &sec_info);
if (rc != NSM_SW_SUCCESS || cc != NSM_SUCCESS)
{
std::cerr << "Response message error: "
<< "rc=" << rc << ", cc=" << (int)cc
<< ", reasonCode=" << (int)reason_code << "\n";
return;
}
ordered_json result;
result["Completion code"] = cc;
result["Reason code"] = reason_code;
// Fill update methods response
ordered_json updateMethods;
bitfield32_t updateMethodBits = {sec_info.update_methods};
if (updateMethodBits.bits.bit0)
{
updateMethods.push_back("Automatic");
}
if (updateMethodBits.bits.bit1)
{
updateMethods.push_back("Self-Contained");
}
if (updateMethodBits.bits.bit2)
{
updateMethods.push_back("Medium-specific reset");
}
if (updateMethodBits.bits.bit3)
{
updateMethods.push_back("System reboot");
}
if (updateMethodBits.bits.bit4)
{
updateMethods.push_back("DC power cycle");
}
if (updateMethodBits.bits.bit5)
{
updateMethods.push_back("AC power cycle");
}
if (updateMethodBits.bits.bit16)
{
updateMethods.push_back("Warm Reset");
}
if (updateMethodBits.bits.bit17)
{
updateMethods.push_back("Hot Reset");
}
if (updateMethodBits.bits.bit17)
{
updateMethods.push_back("Function Level Reset");
}
result["UpdateMethods"] = updateMethods;
DisplayInJson(result);
}
private:
uint16_t classification{};
uint16_t identifier{};
uint8_t index{};
uint8_t requestType;
uint64_t nonce;
uint16_t reqMinSecVersion;
};
class IrreversibleConfig : public CommandInterface
{
public:
~IrreversibleConfig() = default;
IrreversibleConfig() = delete;
IrreversibleConfig(const IrreversibleConfig&) = delete;
IrreversibleConfig(IrreversibleConfig&&) = default;
IrreversibleConfig& operator=(const IrreversibleConfig&) = delete;
IrreversibleConfig& operator=(IrreversibleConfig&&) = default;
explicit IrreversibleConfig(const char* type, const char* name,
CLI::App* app) :
CommandInterface(type, name, app)
{
auto ccOptionGroup = app->add_option_group(
"Required", "Parameters for Irreversible Config Method");
ccOptionGroup
->add_option("-r,--requestType", requestType,
"Request Type. 0 - Query, 1 - Disable, 2 - Enable")
->required();
}
std::pair<int, std::vector<uint8_t>> createRequestMsg() override
{
std::vector<uint8_t> requestMsg(
sizeof(nsm_msg_hdr) +
sizeof(nsm_firmware_irreversible_config_req_command));
nsm_firmware_irreversible_config_req nsm_req;
nsm_req.request_type = requestType;
auto request = reinterpret_cast<nsm_msg*>(requestMsg.data());
auto rc = encode_nsm_firmware_irreversible_config_req(
instanceId, &nsm_req, request);
return std::make_pair(rc, requestMsg);
}
void parseResponseMsg(nsm_msg* responsePtr, size_t payloadLength) override
{
uint8_t cc = NSM_SUCCESS;
uint16_t reason_code = ERR_NULL;
ordered_json result;
switch (requestType)
{
case QUERY_IRREVERSIBLE_CFG:
{
struct nsm_firmware_irreversible_config_request_0_resp
cfg_0_resp{};
auto rc =
decode_nsm_firmware_irreversible_config_request_0_resp(
responsePtr, payloadLength, &cc, &reason_code,
&cfg_0_resp);
if (rc != NSM_SW_SUCCESS || cc != NSM_SUCCESS)
{
std::cerr << "Response message error: "
<< "rc=" << rc << ", cc=" << (int)cc
<< ", reasonCode=" << (int)reason_code << "\n";
return;
}
result["IrreversibleConfigurationState"] =
cfg_0_resp.irreversible_config_state;
break;
}
case DISABLE_IRREVERSIBLE_CFG:
{
auto rc =
decode_nsm_firmware_irreversible_config_request_1_resp(
responsePtr, payloadLength, &cc, &reason_code);
if (rc != NSM_SW_SUCCESS || cc != NSM_SUCCESS)
{
std::cerr << "Response message error: "
<< "rc=" << rc << ", cc=" << (int)cc
<< ", reasonCode=" << (int)reason_code << "\n";
return;
}
break;
}
case ENABLE_IRREVERSIBLE_CFG:
{
struct nsm_firmware_irreversible_config_request_2_resp
cfg_2_resp{};
auto rc =
decode_nsm_firmware_irreversible_config_request_2_resp(
responsePtr, payloadLength, &cc, &reason_code,
&cfg_2_resp);
if (rc != NSM_SW_SUCCESS || cc != NSM_SUCCESS)
{
std::cerr << "Response message error: "
<< "rc=" << rc << ", cc=" << (int)cc
<< ", reasonCode=" << (int)reason_code << "\n";
return;
}
result["Nonce"] = static_cast<uint64_t>(cfg_2_resp.nonce);
break;
}
default:
std::cerr << "Unknown request type " << requestType << "\n";
break;
}
result["Completion code"] = cc;
result["Reason code"] = reason_code;
DisplayInJson(result);
}
private:
uint8_t requestType;
};
class SetRoTProperty : public CommandInterface
{
public:
~SetRoTProperty() = default;
SetRoTProperty() = delete;
SetRoTProperty(const SetRoTProperty&) = delete;
SetRoTProperty(SetRoTProperty&&) = default;
SetRoTProperty& operator=(const SetRoTProperty&) = delete;
SetRoTProperty& operator=(SetRoTProperty&&) = default;
explicit SetRoTProperty(const char* type, const char* name, CLI::App* app) :
CommandInterface(type, name, app)
{
auto ccOptionGroup = app->add_option_group(
"Required", "Parameters for Set RoT Property");
ccOptionGroup->add_option("-c,--classification", classification,
"Component classification");
ccOptionGroup->add_option("-i,--identifier", identifier,
"Component identifier");
ccOptionGroup->add_option("-d,--index", index, "Component index");
ccOptionGroup
->add_option(
"-p,--property", property,
"Property (0: Redundancy Policy, 1: In-band Update Policy, 2: AP SKU ID)")
->check(CLI::Range(0, 2))
->required();
ccOptionGroup
->add_option(
"-r,--redundancy-policy", redundancyPolicy,
"Redundancy Policy (0: Manual Background Copy, 1: Automatic Background Copy) - only for Property 0")
->check(CLI::Range(0, 1));
ccOptionGroup
->add_option(
"-u,--update-policy", updatePolicy,
"In-band Update Policy (0: Disable, 1: Enable) - only for Property 1")
->check(CLI::Range(0, 1));
ccOptionGroup
->add_option(
"-a,--ap-sku-id", apSkuId,
"AP SKU ID (32-bit unsigned integer) - only for Property 2")
->check(CLI::Range(0U, UINT32_MAX));
ccOptionGroup
->add_option(
"-l,--lifespan", lifespan,
"Lifespan (0: Persistent, 1: One-shot for Property 0, Volatile for Property 1)")
->check(CLI::Range(0, 1));
// Add parse callback to validate conditional requirements based on
// property value
app->parse_complete_callback([this, ccOptionGroup]() {
auto apSkuIdOption = ccOptionGroup->get_option("--ap-sku-id");
if (property == 2 && apSkuIdOption->count() == 0)
{
throw CLI::ValidationError(
"--ap-sku-id",
"Option -a,--ap-sku-id is required when property is 2 (AP SKU ID)");
}
});
}
std::pair<int, std::vector<uint8_t>> createRequestMsg() override
{
std::vector<uint8_t> requestMsg(
sizeof(nsm_msg_hdr) +
sizeof(nsm_firmware_set_rot_property_req_command));
nsm_firmware_set_rot_property_req nsm_req;
nsm_req.component_classification = htole16(classification);
nsm_req.component_classification_index = index;
nsm_req.component_identifier = htole16(identifier);
nsm_req.property = property;
nsm_req.argument_length =
ARGUMENT_DATA_LENGTH; // Fixed length for first two properties
// Populate argument data based on property value
if (property == NSM_ROT_PROPERTY_REDUNDANCY_POLICY)
{
// Property 0: Redundancy Policy + Lifespan
nsm_req.argument_data[0] = redundancyPolicy;
nsm_req.argument_data[1] = lifespan;
}
else if (property == NSM_ROT_PROPERTY_INBAND_UPDATE_POLICY)
{
// Property 1: In-band Update Policy + Lifespan
nsm_req.argument_data[0] = updatePolicy;
nsm_req.argument_data[1] = lifespan;
}
else if (property == NSM_ROT_PROPERTY_AP_SKU_ID)
{
// Property 2: AP SKU ID
nsm_req.argument_length = AP_SKU_ID_DATA_LENGTH;
// Convert AP SKU ID to little-endian and copy to argument_data
uint32_t apSkuIdLE = htole32(apSkuId);
memcpy(&nsm_req.argument_data[0], &apSkuIdLE, sizeof(uint32_t));
nsm_req.argument_data[4] = lifespan;
}
auto request = reinterpret_cast<nsm_msg*>(requestMsg.data());
auto rc = encode_nsm_firmware_set_rot_property_req(instanceId, &nsm_req,
request);
return std::make_pair(rc, requestMsg);
}
void parseResponseMsg(nsm_msg* responsePtr, size_t payloadLength) override
{
uint8_t cc = NSM_SUCCESS;
uint16_t reason_code = ERR_NULL;
auto rc = decode_nsm_firmware_set_rot_property_resp(
responsePtr, payloadLength, &cc, &reason_code);
if (rc != NSM_SW_SUCCESS || cc != NSM_SUCCESS)
{
std::cerr << "Response message error: "
<< "rc=" << rc << ", cc=" << (int)cc
<< ", reasonCode=" << (int)reason_code << "\n";
return;
}
ordered_json result;
result["Completion code"] = cc;
result["Reason code"] = reason_code;
DisplayInJson(result);
}
private:
uint16_t classification{DEFAULT_VALUE};
uint16_t identifier{DEFAULT_VALUE};
uint8_t index{DEFAULT_VALUE};
uint8_t property{};
uint8_t redundancyPolicy{DEFAULT_VALUE};
uint8_t updatePolicy{DEFAULT_VALUE};
uint8_t lifespan{};
uint32_t apSkuId{0};
static constexpr uint8_t ARGUMENT_DATA_LENGTH = 2;
static constexpr uint8_t AP_SKU_ID_DATA_LENGTH = 5;
static constexpr uint8_t DEFAULT_VALUE = 255;
};
class DotCAKInstall : public CommandInterface
{
public:
~DotCAKInstall() = default;
DotCAKInstall() = delete;
DotCAKInstall(const DotCAKInstall&) = delete;
DotCAKInstall(DotCAKInstall&&) = default;
DotCAKInstall& operator=(const DotCAKInstall&) = delete;
DotCAKInstall& operator=(DotCAKInstall&&) = default;
explicit DotCAKInstall(const char* type, const char* name, CLI::App* app) :
CommandInterface(type, name, app)
{
auto ccOptionGroup =
app->add_option_group("Required", "Parameters for DotCAKInstall");
// CAK Key Auth Scheme - must be 0 or 1
ccOptionGroup
->add_option(
"--cak_key_auth_scheme", cakKeyAuthScheme,
"Valid values are 0 and 1, 0-DOT_LOCK allowed, 1 not allowed")
->required()
->check(CLI::Range(0, 1));
// CAK Key ECDSA file - must be valid file path and exactly 96 bytes
ccOptionGroup
->add_option("--cak_ecdsa_key", cakKeyEcdsaKeyFile,
"File containing 96 Bytes of ECDSA data (raw bytes)")
->required()
->check(CLI::ExistingFile)
->check([this](const std::string& filename) -> std::string {
return validateFileSize(filename, ECDSA_KEY_SIZE, "CAK key");
});
// CAK LMS file - optional, only required for hybrid auth scheme (1)
// For ECDSA-only auth scheme (0), LMS will be filled with 48 bytes of
// zeros
app->add_option(
"--cak_lms_key", cakLmsKeyFile,
"File containing 48 Bytes (raw bytes) - required only for hybrid auth scheme (1)")
->check(CLI::ExistingFile)
->check([this](const std::string& filename) -> std::string {
return validateFileSize(filename, LMS_KEY_SIZE, "CAK LMS");
});
// LAK Key Auth Scheme - must be 0 or 1
ccOptionGroup
->add_option("--lak_key_auth_scheme", lakKeyAuthScheme,
"LAK key authentication scheme (0 or 1)")
->required()
->check(CLI::Range(0, 1));
// LAK Key ECDSA file - must be valid file path and exactly 96 bytes
ccOptionGroup
->add_option("--lak_ecdsa_key", lakKeyEcdsaKeyFile,
"File containing 96 Bytes LAK key (raw bytes)")
->required()
->check(CLI::ExistingFile)
->check([this](const std::string& filename) -> std::string {
return validateFileSize(filename, ECDSA_KEY_SIZE, "LAK key");
});
// LAK LMS file - optional, only required for hybrid auth scheme (1)
// For ECDSA-only auth scheme (0), LMS will be filled with 48 bytes of
// zeros
app->add_option(
"--lak_lms_key", lakLmsKeyFile,
"File containing 48 Bytes LAK LMS (raw bytes) - required only for hybrid auth scheme (1)")
->check(CLI::ExistingFile)
->check([this](const std::string& filename) -> std::string {
return validateFileSize(filename, LMS_KEY_SIZE, "LAK LMS");
});
// Lock Disable - must be 0 or 1 (default: 0 = lock enabled)
ccOptionGroup
->add_option("--lock_disable", lockDisable,
"Contains state for lock allowing (default: 0)")
->default_val(0)
->check(CLI::Range(0, 1));
// Min SVN - must be valid uint32_t (0 to UINT32_MAX)
ccOptionGroup
->add_option("--min_svn", minSvn,
"Minimum Firmware Security Version")
->required()
->check(CLI::Range(0U, UINT32_MAX));
}
std::pair<int, std::vector<uint8_t>> createRequestMsg() override
{
// Read CAK key from file
std::vector<uint8_t> cakKey = readFileAsBytes(cakKeyEcdsaKeyFile);
if (cakKey.empty())
{
std::cerr << "Error: Failed to read CAK key file: "
<< cakKeyEcdsaKeyFile << "\n";
return std::make_pair(NSM_SW_ERROR, std::vector<uint8_t>());
}
// Read CAK LMS from file (only for Hybrid auth scheme)
std::vector<uint8_t> cakLms;
if (cakKeyAuthScheme == 1) // Hybrid (ECDSA + LMS)
{
if (cakLmsKeyFile.empty())
{
std::cerr
<< "Error: CAK LMS key file required for hybrid auth scheme\n";
return std::make_pair(NSM_SW_ERROR, std::vector<uint8_t>());
}
cakLms = readFileAsBytes(cakLmsKeyFile);
if (cakLms.empty())
{
std::cerr << "Error: Failed to read CAK LMS file: "
<< cakLmsKeyFile << "\n";
return std::make_pair(NSM_SW_ERROR, std::vector<uint8_t>());
}
}
else // ECDSA only - fill with zeros
{
cakLms.resize(LMS_KEY_SIZE, 0);
}
// Read LAK key from file
std::vector<uint8_t> lakKey = readFileAsBytes(lakKeyEcdsaKeyFile);
if (lakKey.empty())
{
std::cerr << "Error: Failed to read LAK key file: "
<< lakKeyEcdsaKeyFile << "\n";
return std::make_pair(NSM_SW_ERROR, std::vector<uint8_t>());
}
// Read LAK LMS from file (only for Hybrid auth scheme)
std::vector<uint8_t> lakLms;
if (lakKeyAuthScheme == 1) // Hybrid (ECDSA + LMS)
{
if (lakLmsKeyFile.empty())
{
std::cerr
<< "Error: LAK LMS key file required for hybrid auth scheme\n";
return std::make_pair(NSM_SW_ERROR, std::vector<uint8_t>());
}
lakLms = readFileAsBytes(lakLmsKeyFile);
if (lakLms.empty())
{
std::cerr << "Error: Failed to read LAK LMS file: "
<< lakLmsKeyFile << "\n";
return std::make_pair(NSM_SW_ERROR, std::vector<uint8_t>());
}
}
else // ECDSA only - fill with zeros
{
lakLms.resize(LMS_KEY_SIZE, 0);
}
std::vector<uint8_t> cakCryptoPcp(CRYPTO_PCP_SIZE, 0);
if (!nsm::dot::buildKeyAuthData(cakKeyAuthScheme, cakKey.data(),
cakLms.data(), cakCryptoPcp.data()))
{
std::cerr << "Error: Failed to build CAK key authentication data\n";
return std::make_pair(NSM_SW_ERROR, std::vector<uint8_t>());
}
std::vector<uint8_t> lakCryptoPcp(CRYPTO_PCP_SIZE, 0);
if (!nsm::dot::buildKeyAuthData(lakKeyAuthScheme, lakKey.data(),
lakLms.data(), lakCryptoPcp.data()))
{
std::cerr << "Error: Failed to build LAK key authentication data\n";
return std::make_pair(NSM_SW_ERROR, std::vector<uint8_t>());
}
// Display key authentication data in hex format (only when verbose is
// enabled)
if (isVerbose())
{
std::cout << "CAK key authentication data (" << CRYPTO_PCP_SIZE
<< " bytes): ";
for (size_t i = 0; i < cakCryptoPcp.size(); ++i)
{
std::cout << "0x" << std::hex << std::setw(2)
<< std::setfill('0')
<< static_cast<int>(cakCryptoPcp[i]);
if (i < cakCryptoPcp.size() - 1)
std::cout << ", ";
}
std::cout << std::dec << std::endl;
std::cout << "LAK key authentication data (" << CRYPTO_PCP_SIZE
<< " bytes): ";
for (size_t i = 0; i < lakCryptoPcp.size(); ++i)
{
std::cout << "0x" << std::hex << std::setw(2)
<< std::setfill('0')
<< static_cast<int>(lakCryptoPcp[i]);
if (i < lakCryptoPcp.size() - 1)
std::cout << ", ";
}
std::cout << std::dec << std::endl;
}
std::vector<uint8_t> requestMsg(
sizeof(nsm_msg_hdr) + sizeof(nsm_dot_cak_install_req_command));
nsm_dot_cak_install_req nsm_req;
// Copy CAK key authentication data (148 bytes per spec)
memcpy(nsm_req.cak_pub, cakCryptoPcp.data(), CRYPTO_PCP_SIZE);
// Copy LAK key authentication data (148 bytes per spec)
memcpy(nsm_req.lak_pub, lakCryptoPcp.data(), CRYPTO_PCP_SIZE);
nsm_req.lock_disable = lockDisable;
nsm_req.min_svn = htole32(minSvn);
auto request = reinterpret_cast<nsm_msg*>(requestMsg.data());
auto rc = encode_nsm_dot_cak_install_req(instanceId, &nsm_req, request);
return std::make_pair(rc, requestMsg);
}
void parseResponseMsg(nsm_msg* responsePtr, size_t payloadLength) override
{
// Validate input parameters
if (responsePtr == nullptr)
{
std::cerr << "Error: Response pointer is null\n";
return;
}
if (payloadLength < sizeof(nsm_common_resp))
{
std::cerr << "Error: Payload length too small, expected at least "
<< sizeof(nsm_common_resp) << " bytes, got "
<< payloadLength << " bytes\n";
return;
}
uint8_t cc = NSM_SUCCESS;
uint16_t reason_code = ERR_NULL;
auto rc = decode_nsm_dot_cak_install_resp(responsePtr, payloadLength,
&cc, &reason_code);
if (rc != NSM_SW_SUCCESS)
{
std::cerr << "Response message error: "
<< "rc=" << rc << ", cc=" << (int)cc
<< ", reasonCode=" << (int)reason_code << "\n";
return;
}
// Get response fields from nsm_common_resp
struct nsm_common_resp* resp =
(struct nsm_common_resp*)responsePtr->payload;
// Validate response data types
if (resp->command != NSM_FW_DOT_CAK_INSTALL)
{
std::cerr << "Warning: Unexpected command code in response: 0x"
<< std::hex << (int)resp->command << ", expected: 0x"
<< (int)NSM_FW_DOT_CAK_INSTALL << std::dec << "\n";
}
// Output response fields according to spec
ordered_json result;
// Add NSM header info as hex bytes
std::stringstream nsmHeaderHex;
for (int i = 0; i < 5; i++)
{
nsmHeaderHex << std::hex << std::setw(2) << std::setfill('0')
<< (int)((uint8_t*)&responsePtr->hdr)[i];
if (i < 4)
nsmHeaderHex << " ";
}
result["nsm_header"] = nsmHeaderHex.str();
std::stringstream cmdCode, compCode, res;
cmdCode << std::hex << std::setw(2) << std::setfill('0')
<< (int)resp->command;
compCode << std::hex << std::setw(2) << std::setfill('0')
<< (int)resp->completion_code;
res << std::hex << std::setw(4) << std::setfill('0')
<< (int)resp->reserved;
result["Command code"] = cmdCode.str();
result["Completion code"] = compCode.str();
result["Reserved"] = res.str();
DisplayInJson(result);
}
private:
uint8_t cakKeyAuthScheme;
std::string cakKeyEcdsaKeyFile;
std::string cakLmsKeyFile;
uint8_t lakKeyAuthScheme;
std::string lakKeyEcdsaKeyFile;
std::string lakLmsKeyFile;
uint8_t lockDisable;
uint32_t minSvn;
// Helper function to validate file size
std::string validateFileSize(const std::string& filename,
size_t expectedSize,
const std::string& fileType)
{
std::ifstream file(filename, std::ios::binary);
if (!file)
{
return "Cannot open " + fileType + " file: " + filename;
}
file.seekg(0, std::ios::end);
size_t actualSize = file.tellg();
file.close();
if (actualSize != expectedSize)
{
return fileType + " file must contain exactly " +
std::to_string(expectedSize) + " bytes, got " +
std::to_string(actualSize) + " bytes";
}
return ""; // Empty string means validation passed
}
std::vector<uint8_t> readFileAsBytes(const std::string& filename)
{
// Validate filename
if (filename.empty())
{
std::cerr << "Error: Filename is empty\n";
return {};
}
std::ifstream file(filename, std::ios::binary);
if (!file)
{
std::cerr << "Error: Cannot open file " << filename << "\n";
return {};
}
file.seekg(0, std::ios::end);
size_t size = file.tellg();
file.seekg(0, std::ios::beg);
// Validate file size
if (size == 0)
{
std::cerr << "Error: File " << filename << " is empty\n";
return {};
}
if (size > SIZE_MAX)
{
std::cerr << "Error: File " << filename
<< " is too large (size: " << size << " bytes)\n";
return {};
}
std::vector<uint8_t> buffer(size);
file.read(reinterpret_cast<char*>(buffer.data()), size);
// Validate read operation
if (file.gcount() != static_cast<std::streamsize>(size))
{
std::cerr << "Error: Failed to read complete file " << filename
<< " (expected: " << size
<< " bytes, read: " << file.gcount() << " bytes)\n";
return {};
}
return buffer;
}
};
class DotCAKBypass : public CommandInterface
{
public:
~DotCAKBypass() = default;
DotCAKBypass() = delete;
DotCAKBypass(const DotCAKBypass&) = delete;
DotCAKBypass(DotCAKBypass&&) = default;
DotCAKBypass& operator=(const DotCAKBypass&) = delete;
DotCAKBypass& operator=(DotCAKBypass&&) = default;
explicit DotCAKBypass(const char* type, const char* name, CLI::App* app) :
CommandInterface(type, name, app)
{
// No parameters required for this command
}
std::pair<int, std::vector<uint8_t>> createRequestMsg() override
{
std::vector<uint8_t> requestMsg(sizeof(nsm_msg_hdr) +
sizeof(nsm_dot_cak_bypass_req));
auto request = reinterpret_cast<nsm_msg*>(requestMsg.data());
auto rc = encode_nsm_dot_cak_bypass_req(instanceId, request);
return std::make_pair(rc, requestMsg);
}
void parseResponseMsg(nsm_msg* responsePtr, size_t payloadLength) override
{
if (payloadLength < sizeof(nsm_common_resp))
{
std::cerr << "Response payload length too short\n";
return;
}
uint8_t cc = NSM_SUCCESS;
uint16_t reason_code = ERR_NULL;
auto rc = decode_nsm_dot_cak_bypass_resp(responsePtr, payloadLength,
&cc, &reason_code);
if (rc != NSM_SW_SUCCESS)
{
std::cerr << "Response message error: "
<< "rc=" << rc << ", cc=" << (int)cc
<< ", reasonCode=" << (int)reason_code << "\n";
return;
}
// Output response fields according to spec
ordered_json result;
// Add NSM header info as hex bytes
std::stringstream nsmHeaderHex;
for (int i = 0; i < 5; i++)
{
nsmHeaderHex << std::hex << std::setw(2) << std::setfill('0')
<< (int)((uint8_t*)&responsePtr->hdr)[i];
if (i < 4)
nsmHeaderHex << " ";
}
result["nsm_header"] = nsmHeaderHex.str();
// For error responses, show reason code instead of detailed fields
if (cc != NSM_SUCCESS)
{
std::stringstream cmdCode;
cmdCode << std::hex << std::setw(2) << std::setfill('0')
<< (int)NSM_FW_DOT_CAK_BYPASS;
result["Command code"] = cmdCode.str();
std::stringstream compCode;
compCode << std::hex << std::setw(2) << std::setfill('0')
<< (int)cc;
result["Completion code"] = compCode.str();
std::stringstream reasonCode;
reasonCode << std::hex << std::setw(4) << std::setfill('0')
<< (int)reason_code;
result["reasonCode"] = reasonCode.str();
}
else
{
// Success case - show all fields per spec
auto* resp =
reinterpret_cast<nsm_common_resp*>(responsePtr->payload);
std::stringstream cmdCode, compCode, res;
cmdCode << std::hex << std::setw(2) << std::setfill('0')
<< (int)resp->command;
compCode << std::hex << std::setw(2) << std::setfill('0')
<< (int)resp->completion_code;
res << std::hex << std::setw(4) << std::setfill('0')
<< (int)le16toh(resp->reserved);
result["Command code"] = cmdCode.str();
result["Completion code"] = compCode.str();
result["Reserved"] = res.str();
}
DisplayInJson(result);
}
};
class ImageCopyControl : public CommandInterface
{
public:
~ImageCopyControl() = default;
ImageCopyControl() = delete;
ImageCopyControl(const ImageCopyControl&) = delete;
ImageCopyControl(ImageCopyControl&&) = default;
ImageCopyControl& operator=(const ImageCopyControl&) = delete;
ImageCopyControl& operator=(ImageCopyControl&&) = default;
explicit ImageCopyControl(const char* type, const char* name,
CLI::App* app) : CommandInterface(type, name, app)
{
auto ccOptionGroup = app->add_option_group(
"Required", "Parameters for Image Copy Control");
ccOptionGroup
->add_option(
"-r,--requestType", requestType,
"Request Type (0: Query Image Copy Progress, 1: Initiate Image Copy)")
->check(CLI::Range(0, 1))
->required();
ccOptionGroup->add_option("-n,--componentCount", componentCount,
"The number of component identities.");
ccOptionGroup->add_option(
"-c,--classification", classifications,
"Component classification(s) - can be specified multiple times");
ccOptionGroup->add_option(
"-i,--identifier", identifiers,
"Component identifier(s) - can be specified multiple times");
ccOptionGroup->add_option(
"-d,--index", indices,
"Component classification index/indices - can be specified multiple times");
}
std::pair<int, std::vector<uint8_t>> createRequestMsg() override
{
// Validate that all component arrays have the same size
if (!classifications.empty() || !identifiers.empty() ||
!indices.empty())
{
size_t maxSize = std::max(
{classifications.size(), identifiers.size(), indices.size()});
if (classifications.size() != maxSize ||
identifiers.size() != maxSize || indices.size() != maxSize)
{
std::cerr
<< "Error: All component parameters (classification, identifier, index) "
<< "must be specified the same number of times\n";
return std::make_pair(-1, std::vector<uint8_t>());
}
}
uint8_t compCount = static_cast<uint8_t>(classifications.size());
// Validate component count if expectedComponentCount was specified
if (componentCount != compCount)
{
std::cerr << "Error: Expected component count ("
<< (int)componentCount
<< ") does not match actual component count ("
<< (int)compCount << ")\n";
return std::make_pair(-1, std::vector<uint8_t>());
}
// Calculate message size: header + common_req + request + (component
// entries)
std::vector<uint8_t> requestMsg(
sizeof(nsm_msg_hdr) + sizeof(nsm_common_req) +
sizeof(nsm_firmware_image_copy_control_req) +
(compCount * sizeof(nsm_firmware_image_copy_component_entry)));
// Build component entries array
std::vector<nsm_firmware_image_copy_component_entry> componentEntries;
componentEntries.reserve(compCount);
for (size_t i = 0; i < compCount; ++i)
{
nsm_firmware_image_copy_component_entry entry;
entry.component_classification = classifications[i];
entry.component_identifier = identifiers[i];
entry.component_classification_index = indices[i];
componentEntries.push_back(entry);
}
nsm_firmware_image_copy_control_req nsm_req;
nsm_req.request_type = requestType;
nsm_req.component_count = compCount;
auto request = reinterpret_cast<nsm_msg*>(requestMsg.data());
auto rc = encode_nsm_firmware_image_copy_control_req(
instanceId, &nsm_req,
compCount > 0 ? componentEntries.data() : nullptr, request);
return std::make_pair(rc, requestMsg);
}
void parseResponseMsg(nsm_msg* responsePtr, size_t payloadLength) override
{
uint8_t cc = NSM_SUCCESS;
uint16_t reason_code = ERR_NULL;
ordered_json result;
switch (requestType)
{
case NSM_IMAGE_COPY_QUERY_PROGRESS: // Query Image Copy
// Progress
{
struct nsm_firmware_image_copy_control_query_progress_resp
image_copy_control_query{};
auto rc =
decode_nsm_firmware_image_copy_control_query_progress_resp(
responsePtr, payloadLength, &cc, &reason_code,
&image_copy_control_query);
if (rc != NSM_SW_SUCCESS || cc != NSM_SUCCESS)
{
std::cerr << "Response message error: " << "rc=" << rc
<< "\n";
if (cc != NSM_SUCCESS)
{
std::cerr << " Completion code: 0x" << std::hex
<< (int)cc << std::dec << " - "
<< getCompletionCodeDescription(cc) << "\n";
}
if (reason_code != ERR_NULL)
{
std::cerr << " Reason code: 0x" << std::hex
<< reason_code << std::dec << " - "
<< getReasonCodeDescription(reason_code)
<< "\n";
}
return;
}
if (image_copy_control_query.image_copy_progress >
UNSUPPORTED_PROGRESS_PERCENT)
{
std::cerr << "Incorrect progress percentage: "
<< image_copy_control_query.image_copy_progress
<< " (expected: 0-101)\n";
return;
}
// Map status code to human-readable string
std::string statusStr;
switch (image_copy_control_query.image_copy_status)
{
case NSM_IMAGE_COPY_NOT_TRIGGERED:
statusStr = "Image copy not triggered";
break;
case NSM_IMAGE_COPY_IN_PROGRESS:
statusStr = "In progress";
break;
case NSM_IMAGE_COPY_COMPLETE:
statusStr = "Complete";
break;
case NSM_IMAGE_COPY_UNDEFINED_FAILURE:
statusStr = "Undefined failure";
break;
case NSM_IMAGE_COPY_NO_VALID_IMAGE:
statusStr = "No valid image";
break;
case NSM_IMAGE_COPY_DESTINATION_WRITE_PROTECTED:
statusStr = "Destination write protected";
break;
case NSM_IMAGE_COPY_FAIL_FLASH_ACCESS:
statusStr = "Fail flash access";
break;
case NSM_IMAGE_COPY_FAILED_VERIFY:
statusStr = "Failed verify";
break;
default:
statusStr =
"Unknown status (" +
std::to_string(
image_copy_control_query.image_copy_status) +
")";
break;
}
result["Image copy status"] = statusStr;
result["Image copy status code"] =
image_copy_control_query.image_copy_status;
result["Image copy progress"] =
image_copy_control_query.image_copy_progress;
break;
}
case NSM_IMAGE_COPY_INITIATE_IMAGE_COPY: // Initiate Image Copy
{
auto rc =
decode_nsm_firmware_image_copy_control_initiate_copy_resp(
responsePtr, payloadLength, &cc, &reason_code);
if (rc != NSM_SW_SUCCESS || cc != NSM_SUCCESS)
{
std::cerr << "Response message error: " << "rc=" << rc
<< "\n";
if (cc != NSM_SUCCESS)
{
std::cerr << " Completion code: 0x" << std::hex
<< (int)cc << std::dec << " - "
<< getCompletionCodeDescription(cc) << "\n";
}
if (reason_code != ERR_NULL)
{
std::cerr << " Reason code: 0x" << std::hex
<< reason_code << std::dec << " - "
<< getReasonCodeDescription(reason_code)
<< "\n";
}
return;
}
break;
}
default:
{
std::cerr << "Unknown request type " << requestType << "\n";
break;
}
}
result["Completion code"] = cc;
result["Reason code"] = reason_code;
DisplayInJson(result);
}
private:
uint8_t requestType{};
std::vector<uint16_t> classifications;
std::vector<uint16_t> identifiers;
std::vector<uint8_t> indices;
uint8_t componentCount{DEFAULT_VALUE};
static constexpr uint8_t DEFAULT_VALUE = 0;
static constexpr uint8_t UNSUPPORTED_PROGRESS_PERCENT = 101;
// Helper function to get human-readable completion code description
static std::string getCompletionCodeDescription(uint8_t cc)
{
switch (cc)
{
case NSM_ERR_INVALID_DATA:
return "INVALID_DATA (The request payload contained invalid data or illegal value)";
case NSM_ERR_INVALID_STATE_FOR_COMMAND:
return "INVALID_STATE_FOR_COMMAND (The device is not in a state to expect this command)";
case NSM_ERR_INVALID_REQUEST_TYPE:
return "INVALID_REQUEST_TYPE (The requested request type is not supported by the device)";
default:
return "Unknown completion code";
}
}
// Helper function to get human-readable reason code description
static std::string getReasonCodeDescription(uint16_t reason_code)
{
switch (reason_code)
{
case ERR_PROPERTY_NOT_SUPPORTED:
return "PROPERTY_NOT_SUPPORTED (Property to be updated via SetRoTProperty command is not supported by the device)";
case ERR_LIFESPAN_VOLATILE_NOT_SUPPORTED:
return "LIFESPAN_VOLATILE_NOT_SUPPORTED (Volatile lifespan for a property is not supported by device)";
case ERR_LIFESPAN_PERSISTENT_NOT_SUPPORTED:
return "LIFESPAN_PERSISTENT_NOT_SUPPORTED (Persistent lifespan for a property is not supported by device)";
case ERR_NO_BOOT_COMPLETE:
return "NO_BOOT_COMPLETE (The RoT has not received a boot complete indication from the AP)";
case ERR_UPDATE_IN_PROGRESS:
return "UPDATE_IN_PROGRESS (A firmware update is in progress)";
case ERR_IMAGE_COPY_IN_PROGRESS:
return "IMAGE_COPY_IN_PROGRESS (An image copy is in progress)";
case ERR_IMAGE_COPY_COMPLETED:
return "IMAGE_COPY_COMPLETED (An image copy was completed successfully)";
case ERR_FLASH_WEAR_MITIGATION:
return "FLASH_WEAR_MITIGATION (A flash wear out mitigation policy is in effect)";
case ERR_INCOMPLETE_COMPONENT_SET:
return "INCOMPLETE_COMPONENT_SET (The RoT requires additional components to be included in the request)";
default:
return "Unknown reason code";
}
}
};
void registerCommand(CLI::App& app)
{
auto firmware = app.add_subcommand("firmware",
"Device firmware type commands");
firmware->require_subcommand(1);
auto getRotInformation = firmware->add_subcommand(
"GetRotInformation",
"Get information about a particular firmware set installed on an endpoint");
commands.push_back(std::make_unique<GetRotInformation>(
"firmware", "QueryRoTStateInformation", getRotInformation));
auto irreversibleConfig = firmware->add_subcommand(
"IrreversibleConfig",
"Query/Disable/Enable Irreversible Configuration");
commands.push_back(std::make_unique<IrreversibleConfig>(
"firmware", "IrreversibleConfig", irreversibleConfig));
auto queryCodeAuthKeyPerm = firmware->add_subcommand(
"QueryFWCodeAuthKey",
"Query firmware code authentication key permissions");
commands.push_back(std::make_unique<QueryCodeAuthKeyPerm>(
"firmware", "QueryFWCodeAuthKey", queryCodeAuthKeyPerm));
auto updateCodeAuthKeyPerm = firmware->add_subcommand(
"UpdateCodeAuthKeyPerm",
"Update firmware code authentication key permissions");
commands.push_back(std::make_unique<UpdateCodeAuthKeyPerm>(
"firmware", "UpdateCodeAuthKeyPerm", updateCodeAuthKeyPerm));
auto queryFirmwareSecurityVersion = firmware->add_subcommand(
"QueryFirmwareSecurityVersion", "Query Firmware Security Version");
commands.push_back(std::make_unique<QueryFirmwareSecurityVersion>(
"firmware", "QueryFirmwareSecurityVersion",
queryFirmwareSecurityVersion));
auto updateMinSecurityVersion = firmware->add_subcommand(
"UpdateMinSecurityVersion", "Update Minimum Firmware Security Version");
commands.push_back(std::make_unique<UpdateMinSecurityVersion>(
"firmware", "UpdateMinSecurityVersion", updateMinSecurityVersion));
auto setRoTProperty = firmware->add_subcommand("SetRoTProperty",
"Set RoT Property");
commands.push_back(std::make_unique<SetRoTProperty>(
"firmware", "SetRoTProperty", setRoTProperty));
auto dotCAKInstall = firmware->add_subcommand(
"DotCAKInstall", "Cak install command in uninitialized state");
commands.push_back(std::make_unique<DotCAKInstall>(
"firmware", "DotCAKInstall", dotCAKInstall));
auto dotCAKBypass = firmware->add_subcommand(
"DotCAKBypass", "Bypass DOT CAK install and continue boot");
commands.push_back(std::make_unique<DotCAKBypass>(
"firmware", "DotCAKBypass", dotCAKBypass));
auto imageCopyControl = firmware->add_subcommand("ImageCopyControl",
"Image Copy Control");
commands.push_back(std::make_unique<ImageCopyControl>(
"firmware", "ImageCopyControl", imageCopyControl));
}
} // namespace nsmtool::firmware