From ff54151ac62ce0d28119099bb53c1de9ea4bd694 Mon Sep 17 00:00:00 2001 From: Willy Zhang Date: Tue, 15 Sep 2026 03:02:50 +0000 Subject: [PATCH 1/4] [htool] Add RoT provisioning commands Add provisioning subcommands to htool: - provisioning write: Write a provisioning blob to RoT storage from a binary file. - provisioning store_secrets: Store encrypted secrets to the RoT from a binary file. - provisioning load_mldsa_key: Load an ML-DSA key onto the RoT from a binary file. - provisioning validate_and_sign: Add optional --output flag to save the signed payload. --- examples/host_commands.h | 8 +- examples/htool.c | 30 +++ examples/htool_provisioning.c | 283 ++++++++++++++++++++++++- examples/htool_provisioning.h | 67 +++++- examples/htool_provisioning_test.cc | 306 +++++++++++++++++++++++++--- 5 files changed, 650 insertions(+), 44 deletions(-) diff --git a/examples/host_commands.h b/examples/host_commands.h index 9f65431..df6f606 100644 --- a/examples/host_commands.h +++ b/examples/host_commands.h @@ -79,9 +79,12 @@ struct hoth_response_flash_spi_info { */ #define HOTH_BASE_CMD(cmd) (HOTH_CMD_BOARD_SPECIFIC_BASE + (cmd)) -/* The major command identifier for the Provisioning Log read host command. */ +/* The major command identifier for the Provisioning Log host command. */ #define HOTH_PRV_CMD_HOTH_PROVISIONING_LOG 0x0040 +/* The major command identifier for the Key Provisioning host command. */ +#define HOTH_PRV_CMD_HOTH_KEY_PROVISIONING 0x0043 + /** * The request header structure for security v2 commands */ @@ -367,7 +370,4 @@ struct hoth_response_target_control { } #endif -// Host Command for Provisioning Commands -#define HOTH_PRV_CMD_HOTH_PROVISIONING_LOG 0x0040 - #endif // LIBHOTH_EXAMPLES_HOST_COMMANDS_H_ diff --git a/examples/htool.c b/examples/htool.c index ee8484b..99d669f 100644 --- a/examples/htool.c +++ b/examples/htool.c @@ -2058,6 +2058,36 @@ static const struct htool_cmd CMDS[] = { .desc = "The signed cert file."}, {}}, }, + { + .verbs = (const char*[]){"provisioning", "store_secrets", NULL}, + .desc = "Store secrets encrypted to the provisioning encryption key", + .func = htool_provisioning_store_secrets, + .params = + (const struct htool_param[]){ + {HTOOL_FLAG_VALUE, .name = "secrets", .default_value = "", + .desc = "File containing the encrypted secrets."}, + {}}, + }, + { + .verbs = (const char*[]){"provisioning", "write", NULL}, + .desc = "Write and commit the provisioning log", + .func = htool_provisioning_write, + .params = + (const struct htool_param[]){ + {HTOOL_FLAG_VALUE, .name = "input", .default_value = "", + .desc = "File containing the provisioning log to write."}, + {}}, + }, + { + .verbs = (const char*[]){"provisioning", "load_mldsa_key", NULL}, + .desc = "Load the ML-DSA-44 public key to the RoT", + .func = htool_provisioning_load_mldsa_key, + .params = + (const struct htool_param[]){ + {HTOOL_FLAG_VALUE, .name = "key", .default_value = "", + .desc = "Path to the 1312-byte ML-DSA-44 public key file."}, + {}}, + }, { .verbs = (const char*[]){"security", "get_alias_key_cert", NULL}, .desc = "Get the Alias Key Cert", diff --git a/examples/htool_provisioning.c b/examples/htool_provisioning.c index ae983d3..65e69b1 100644 --- a/examples/htool_provisioning.c +++ b/examples/htool_provisioning.c @@ -2,6 +2,8 @@ #include #include +#include +#include #include #include #include @@ -13,6 +15,7 @@ #include "htool_cmd.h" #include "htool_security_version.h" #include "protocol/host_cmd.h" +#include "transports/libhoth_device.h" // This is a standalone CRC32 that matches Titan Firmware. // A table-free bit-level implementation is okay since there are no @@ -34,6 +37,19 @@ uint32_t crc32(uint32_t initial_value, const uint8_t* buf, size_t size) { // Helper function used to return the lowest value integer given two integers static uint16_t min(uint16_t a, uint16_t b) { return (a < b) ? a : b; } +// Executes a command on the provisioning log host command (0x3E40) +static libhoth_error exec_provisioning_log_cmd(struct libhoth_device* dev, + const void* req_payload, + size_t req_payload_size, + void* resp_buf, + size_t resp_buf_size, + size_t* out_resp_size) { + return libhoth_hostcmd_exec_v2( + dev, /*command=*/HOTH_BASE_CMD(HOTH_PRV_CMD_HOTH_PROVISIONING_LOG), + /*version=*/0, req_payload, req_payload_size, resp_buf, resp_buf_size, + out_resp_size); +} + // Runs the command to read a portion of the provisioning log static int read_chunk_from_provisioning_log(struct libhoth_device* dev, const void* req_payload, @@ -216,11 +232,13 @@ int htool_validate_and_sign(const struct htool_invocation* inv) { goto cleanup; } - output_ptr = fopen(output_file, "wb"); - if (output_ptr == NULL) { - printf("Error: %s, when attempting to open file: %s\n", strerror(errno), - output_file); - goto cleanup; + if (strlen(output_file) > 0) { + output_ptr = fopen(output_file, "wb"); + if (output_ptr == NULL) { + printf("Error: %s, when attempting to open file: %s\n", strerror(errno), + output_file); + goto cleanup; + } } enum provisioning_log_op operation = PROVISIONING_LOG_VALIDATE_AND_SIGN; @@ -269,7 +287,9 @@ int htool_validate_and_sign(const struct htool_invocation* inv) { } // Write the signed provisioning_log into the output file - fwrite(response, response_size, sizeof(uint8_t), output_ptr); + if (output_ptr != NULL) { + fwrite(response, response_size, sizeof(uint8_t), output_ptr); + } break; } } @@ -295,3 +315,254 @@ int htool_validate_and_sign(const struct htool_invocation* inv) { } return status; } + +// Helper to read a binary file with size validation. +static int read_binary_file(const char* path, uint8_t* buf, size_t min_size, + size_t max_size, size_t* out_size) { + FILE* file = fopen(path, "rb"); + if (file == NULL) { + fprintf(stderr, "Error: %s, when attempting to open file: %s\n", + strerror(errno), path); + return -1; + } + + const size_t read_bytes = fread(buf, 1, max_size, file); + // A full buffer may mean the file was truncated; check for trailing bytes. + const bool too_large = (read_bytes == max_size) && (fgetc(file) != EOF); + const bool read_error = ferror(file) != 0; + fclose(file); + + if (read_error) { + fprintf(stderr, "Error reading %s\n", path); + return -1; + } + if (too_large) { + if (min_size == max_size) { + fprintf(stderr, + "Error: %s exceeds %zu bytes (must be exactly %zu bytes)\n", path, + max_size, max_size); + } else { + fprintf(stderr, "Error: %s exceeds maximum size of %zu bytes\n", path, + max_size); + } + return -1; + } + if (read_bytes < min_size) { + if (min_size == max_size) { + fprintf(stderr, "Error: %s size (%zu) must be exactly %zu bytes\n", path, + read_bytes, min_size); + } else if (read_bytes == 0) { + fprintf(stderr, "Error: %s is empty\n", path); + } else { + fprintf(stderr, "Error: %s size (%zu) is less than minimum %zu bytes\n", + path, read_bytes, min_size); + } + return -1; + } + + if (out_size != NULL) { + *out_size = read_bytes; + } + return 0; +} + +// Reads the encrypted secrets from `--secrets` (a binary file, as +// produced by the offline encryption tools). +static int get_secrets(const struct htool_invocation* inv, uint8_t* secrets, + size_t secrets_capacity, size_t* secrets_size) { + const char* secrets_file; + if (htool_get_param_string(inv, "secrets", &secrets_file) != 0 || + strlen(secrets_file) == 0) { + fprintf(stderr, "--secrets must be specified.\n"); + return -1; + } + + return read_binary_file(secrets_file, secrets, 1, secrets_capacity, + secrets_size); +} + +int htool_provisioning_store_secrets(const struct htool_invocation* inv) { + struct libhoth_device* dev = htool_libhoth_device(); + if (!dev) { + return -1; + } + + uint8_t secrets[HOTH_KEY_PROVISIONING_MAX_SECRETS_SIZE]; + size_t secrets_size = 0; + if (get_secrets(inv, secrets, sizeof(secrets), &secrets_size) != 0) { + return -1; + } + + const size_t request_size = + sizeof(struct hoth_key_provisioning_request_header) + secrets_size; + + struct hoth_key_provisioning_store_secrets_request req = { + .hdr = + { + .version = HOTH_KEY_PROVISIONING_REQUEST_VERSION, + .command = HOTH_KEY_PROVISIONING_STORE_SECRETS, + .size = (uint16_t)request_size, + }, + }; + memcpy(req.secrets, secrets, secrets_size); + + size_t response_size = 0; + libhoth_error err = libhoth_hostcmd_exec_v2( + dev, HOTH_BASE_CMD(HOTH_PRV_CMD_HOTH_KEY_PROVISIONING), + /*version=*/0, &req, request_size, NULL, 0, &response_size); + if (err != HOTH_SUCCESS) { + fprintf(stderr, + "Error: 'key_provisioning_store_secrets' failed (0x%016" PRIx64 + "): ", + err); + libhoth_log_err(stderr, err); + return -1; + } + printf("Stored %zu bytes of encrypted secrets\n", secrets_size); + return 0; +} + +int htool_provisioning_write(const struct htool_invocation* inv) { + struct libhoth_device* dev = htool_libhoth_device(); + if (!dev) { + fprintf(stderr, "Unable to retrieve libhoth_device\n"); + return -1; + } + + const char* input_file; + if (htool_get_param_string(inv, "input", &input_file) != 0 || + strlen(input_file) == 0) { + fprintf(stderr, "--input must be specified.\n"); + return -1; + } + + uint8_t log_data[PROVISIONING_LOG_MAX_SIZE]; + size_t file_size = 0; + if (read_binary_file(input_file, log_data, 1, PROVISIONING_LOG_MAX_SIZE, + &file_size) != 0) { + return -1; + } + + uint16_t bytes_written = 0; + while (bytes_written < file_size) { + uint16_t chunk_size = (uint16_t)(file_size - bytes_written); + if (chunk_size > PROVISIONING_LOG_WRITE_CHUNK_MAX_SIZE) { + chunk_size = PROVISIONING_LOG_WRITE_CHUNK_MAX_SIZE; + } + + struct hoth_provisioning_log_write_request write_req = { + .req = + { + .version = 1, + .operation = PROVISIONING_LOG_WRITE, + .reserved = 0, + .offset = bytes_written, + .size = chunk_size, + .checksum = 0, + }, + }; + memcpy(write_req.data, log_data + bytes_written, chunk_size); + + size_t response_size = 0; + libhoth_error err = exec_provisioning_log_cmd( + dev, &write_req, sizeof(write_req.req) + chunk_size, NULL, 0, + &response_size); + if (err != HOTH_SUCCESS) { + fprintf( + stderr, + "Error: 'provisioning_log_write' failed (0x%016" PRIx64 "): ", err); + libhoth_log_err(stderr, err); + return -1; + } + bytes_written += chunk_size; + } + + struct hoth_provisioning_log_request commit_req = { + .version = 1, + .operation = PROVISIONING_LOG_COMMIT, + .reserved = 0, + .offset = 0, + .size = (uint16_t)file_size, + .checksum = crc32(0, log_data, file_size), + }; + size_t response_size = 0; + libhoth_error err = exec_provisioning_log_cmd( + dev, &commit_req, sizeof(commit_req), NULL, 0, &response_size); + if (err != HOTH_SUCCESS) { + fprintf( + stderr, + "Error: 'provisioning_log_commit' failed (0x%016" PRIx64 "): ", err); + libhoth_log_err(stderr, err); + return -1; + } + + printf("Successfully wrote and committed %zu bytes of provisioning log\n", + file_size); + return 0; +} + +int htool_provisioning_load_mldsa_key(const struct htool_invocation* inv) { + struct libhoth_device* dev = htool_libhoth_device(); + if (!dev) { + fprintf(stderr, "Unable to retrieve libhoth_device\n"); + return -1; + } + + const char* key_file; + if (htool_get_param_string(inv, "key", &key_file) != 0 || + strlen(key_file) == 0) { + fprintf(stderr, "--key must be specified.\n"); + return -1; + } + + uint8_t key_buf[HOTH_KEY_PROVISIONING_MLDSA44_PUBLIC_KEY_BYTES]; + if (read_binary_file(key_file, key_buf, sizeof(key_buf), sizeof(key_buf), + NULL) != 0) { + return -1; + } + + uint16_t offset = 0; + while (offset < sizeof(key_buf)) { + uint16_t chunk_size = (uint16_t)(sizeof(key_buf) - offset); + if (chunk_size > HOTH_KEY_PROVISIONING_LOAD_KEY_CHUNK_MAX_SIZE) { + chunk_size = HOTH_KEY_PROVISIONING_LOAD_KEY_CHUNK_MAX_SIZE; + } + + const size_t req_size = + sizeof(struct hoth_key_provisioning_request_header) + + sizeof(struct hoth_key_provisioning_load_key_args) + chunk_size; + + struct hoth_key_provisioning_load_key_request req = { + .hdr = + { + .version = HOTH_KEY_PROVISIONING_REQUEST_VERSION, + .command = HOTH_KEY_PROVISIONING_LOAD_MLDSA_PUBLIC_KEY, + .size = (uint16_t)req_size, + }, + .args = + { + .offset = offset, + .size = chunk_size, + }, + }; + memcpy(req.data, key_buf + offset, chunk_size); + + size_t response_size = 0; + libhoth_error err = libhoth_hostcmd_exec_v2( + dev, HOTH_BASE_CMD(HOTH_PRV_CMD_HOTH_KEY_PROVISIONING), + /*version=*/0, &req, req_size, NULL, 0, &response_size); + if (err != HOTH_SUCCESS) { + fprintf(stderr, + "Error: 'key_provisioning_load_mldsa_key' failed (0x%016" PRIx64 + "): ", + err); + libhoth_log_err(stderr, err); + return -1; + } + + offset += chunk_size; + } + + printf("ML-DSA public key loaded successfully\n"); + return 0; +} diff --git a/examples/htool_provisioning.h b/examples/htool_provisioning.h index fa431e2..4d873bc 100644 --- a/examples/htool_provisioning.h +++ b/examples/htool_provisioning.h @@ -18,6 +18,10 @@ #include #include +#include "host_commands.h" +#include "protocol/host_cmd.h" +#include "transports/libhoth_device.h" + #ifdef __cplusplus extern "C" { #endif @@ -25,7 +29,51 @@ extern "C" { // Forward declaration struct htool_invocation; -#define PROVISIONING_LOG_MAX_SIZE 2048 +#define HOTH_KEY_PROVISIONING_REQUEST_VERSION 1 + +enum hoth_key_provisioning_command { + HOTH_KEY_PROVISIONING_GET_ENCRYPTION_KEY = 0, + HOTH_KEY_PROVISIONING_STORE_SECRETS = 1, + HOTH_KEY_PROVISIONING_LOAD_MLDSA_PUBLIC_KEY = 2, +}; + +struct hoth_key_provisioning_request_header { + uint8_t version; + uint8_t command; + uint16_t size; +} __attribute__((packed)); + +#define HOTH_KEY_PROVISIONING_MAX_SECRETS_SIZE \ + (LIBHOTH_MAILBOX_SIZE - sizeof(struct hoth_host_request) - \ + sizeof(struct hoth_key_provisioning_request_header)) + +struct hoth_key_provisioning_store_secrets_request { + struct hoth_key_provisioning_request_header hdr; + uint8_t secrets[HOTH_KEY_PROVISIONING_MAX_SECRETS_SIZE]; +} __attribute__((packed)); + +#define HOTH_KEY_PROVISIONING_MLDSA44_PUBLIC_KEY_BYTES 1312 + +struct hoth_key_provisioning_load_key_args { + uint16_t offset; + uint16_t size; +} __attribute__((packed)); + +#define HOTH_KEY_PROVISIONING_LOAD_KEY_CHUNK_MAX_SIZE \ + (LIBHOTH_MAILBOX_SIZE - sizeof(struct hoth_host_request) - \ + sizeof(struct hoth_key_provisioning_request_header) - \ + sizeof(struct hoth_key_provisioning_load_key_args)) + +struct hoth_key_provisioning_load_key_request { + struct hoth_key_provisioning_request_header hdr; + struct hoth_key_provisioning_load_key_args args; + uint8_t data[HOTH_KEY_PROVISIONING_LOAD_KEY_CHUNK_MAX_SIZE]; +} __attribute__((packed)); + +// Loads secrets that were encrypted to the provisioning encryption key. +int htool_provisioning_store_secrets(const struct htool_invocation* inv); + +#define PROVISIONING_LOG_MAX_SIZE 6144 #define PROVISIONING_LOG_CHUNK_MAX_SIZE 1008 @@ -52,8 +100,19 @@ struct hoth_provisioning_log { uint8_t data[PROVISIONING_LOG_CHUNK_MAX_SIZE]; } __attribute__((packed)); +#define PROVISIONING_LOG_WRITE_CHUNK_MAX_SIZE \ + (LIBHOTH_MAILBOX_SIZE - sizeof(struct hoth_host_request) - \ + sizeof(struct hoth_provisioning_log_request)) + +struct hoth_provisioning_log_write_request { + struct hoth_provisioning_log_request req; + uint8_t data[PROVISIONING_LOG_WRITE_CHUNK_MAX_SIZE]; +} __attribute__((packed)); + enum provisioning_log_op { PROVISIONING_LOG_READ = 0, + PROVISIONING_LOG_WRITE = 1, + PROVISIONING_LOG_COMMIT = 2, PROVISIONING_LOG_VALIDATE_AND_SIGN = 3, }; @@ -68,6 +127,12 @@ int htool_get_provisioning_log(const struct htool_invocation* inv); // Validate and Sign the provisioning log. int htool_validate_and_sign(const struct htool_invocation* inv); +// Writes and commits the provisioning log. +int htool_provisioning_write(const struct htool_invocation* inv); + +// Loads the ML-DSA-44 public key to the RoT. +int htool_provisioning_load_mldsa_key(const struct htool_invocation* inv); + #ifdef __cplusplus } #endif diff --git a/examples/htool_provisioning_test.cc b/examples/htool_provisioning_test.cc index 5178b46..57f5893 100644 --- a/examples/htool_provisioning_test.cc +++ b/examples/htool_provisioning_test.cc @@ -392,42 +392,30 @@ TEST_F(HtoolProvisioningTest, GetProvisioningLogResponseTooLarge) { .checksum = 0x12345678, }; - // First chunk - struct hoth_provisioning_log response1{}; - response1.hdr = header; - response1.hdr.size = PROVISIONING_LOG_CHUNK_MAX_SIZE; - std::vector data1(PROVISIONING_LOG_CHUNK_MAX_SIZE, 0xAA); - memcpy(response1.data, data1.data(), data1.size()); - - // Second chunk - struct hoth_provisioning_log response2{}; - response2.hdr = header; - response2.hdr.size = PROVISIONING_LOG_CHUNK_MAX_SIZE; - std::vector data2(PROVISIONING_LOG_CHUNK_MAX_SIZE, 0xBB); - memcpy(response2.data, data2.data(), data2.size()); - - // Third chunk (the one that will overflow) - struct hoth_provisioning_log response3{}; - response3.hdr = header; - uint16_t last_chunk_size = (header.size % PROVISIONING_LOG_CHUNK_MAX_SIZE); - if (last_chunk_size == 0) { - last_chunk_size = PROVISIONING_LOG_CHUNK_MAX_SIZE; + std::vector responses; + uint16_t bytes_simulated = 0; + while (bytes_simulated < header.size) { + uint16_t chunk_size = + std::min(static_cast(header.size - bytes_simulated), + static_cast(PROVISIONING_LOG_CHUNK_MAX_SIZE)); + struct hoth_provisioning_log resp{}; + resp.hdr = header; + resp.hdr.size = chunk_size; + memset(resp.data, 0xAA, chunk_size); + responses.push_back(resp); + bytes_simulated += chunk_size; } - response3.hdr.size = last_chunk_size; - std::vector data3(last_chunk_size, 0xCC); - memcpy(response3.data, data3.data(), data3.size()); EXPECT_CALL(mock_, send(_, _, _)).WillRepeatedly(Return(LIBHOTH_OK)); - EXPECT_CALL(mock_, receive(_, _, _, _, _)) - .WillOnce(DoAll(CopyResp(&header, sizeof(header)), Return(LIBHOTH_OK))) - .WillOnce(DoAll(CopyResp(&response1, sizeof(header) + - PROVISIONING_LOG_CHUNK_MAX_SIZE), - Return(LIBHOTH_OK))) - .WillOnce(DoAll(CopyResp(&response2, sizeof(header) + - PROVISIONING_LOG_CHUNK_MAX_SIZE), - Return(LIBHOTH_OK))) - .WillOnce(DoAll(CopyResp(&response3, sizeof(header) + last_chunk_size), - Return(LIBHOTH_OK))); + auto& receive_call = EXPECT_CALL(mock_, receive(_, _, _, _, _)) + .WillOnce(DoAll(CopyResp(&header, sizeof(header)), + Return(LIBHOTH_OK))); + for (size_t i = 0; i < responses.size(); ++i) { + uint16_t chunk_size = responses[i].hdr.size; + receive_call.WillOnce( + DoAll(CopyResp(&responses[i], sizeof(header) + chunk_size), + Return(LIBHOTH_OK))); + } ASSERT_EQ(htool_get_provisioning_log(&inv), -1); @@ -520,6 +508,34 @@ TEST_F(HtoolProvisioningTest, ValidateAndSignSuccess) { remove(tmp_output_file.c_str()); } +TEST_F(HtoolProvisioningTest, ValidateAndSignSuccessWithoutOutput) { + struct htool_invocation inv{}; + std::string tmp_perso_blob_file = + tmp_dir_path_ + "/perso_blob.ValidateAndSignSuccessWithoutOutput.bin"; + EXPECT_CALL(invocation_mock_, GetParamString("perso_blob", _)) + .WillOnce( + DoAll(SetArgPointee<1>(tmp_perso_blob_file.c_str()), Return(0))); + EXPECT_CALL(invocation_mock_, GetParamString("output", _)) + .WillOnce(DoAll(SetArgPointee<1>(""), Return(0))); + + std::vector perso_blob_data = {0x01, 0x02, 0x03, 0x04, 0x05}; + FILE* fp_in = fopen(tmp_perso_blob_file.c_str(), "wb"); + ASSERT_NE(fp_in, nullptr); + ASSERT_EQ(fwrite(perso_blob_data.data(), 1, perso_blob_data.size(), fp_in), + perso_blob_data.size()); + fclose(fp_in); + + std::vector signed_log_data = {0xDE, 0xAD, 0xBE, 0xEF}; + + EXPECT_CALL(mock_, send(_, _, _)).WillOnce(Return(LIBHOTH_OK)); + EXPECT_CALL(mock_, receive(_, _, _, _, _)) + .WillOnce(DoAll(CopyResp(signed_log_data.data(), signed_log_data.size()), + Return(LIBHOTH_OK))); + + ASSERT_EQ(htool_validate_and_sign(&inv), 0); + remove(tmp_perso_blob_file.c_str()); +} + TEST_F(HtoolProvisioningTest, ValidateAndSignUnexpectedErrorFromDevice) { struct htool_invocation inv{}; std::string tmp_perso_blob_file = @@ -725,3 +741,227 @@ TEST_F(HtoolProvisioningTest, ValidateAndSignTooLargeResponse) { remove(tmp_perso_blob_file.c_str()); remove(tmp_output_file.c_str()); } + +TEST_F(HtoolProvisioningTest, StoreSecretsFileSuccess) { + struct htool_invocation inv{}; + std::string tmp_secrets_file = tmp_dir_path_ + "/secrets.bin"; + FILE* fp = fopen(tmp_secrets_file.c_str(), "wb"); + ASSERT_NE(fp, nullptr); + uint8_t secret_bytes[] = {0xaa, 0xbb, 0xcc, 0xdd}; + ASSERT_EQ(fwrite(secret_bytes, 1, sizeof(secret_bytes), fp), + sizeof(secret_bytes)); + fclose(fp); + + EXPECT_CALL(invocation_mock_, GetParamString("secrets", _)) + .WillOnce(DoAll(SetArgPointee<1>(tmp_secrets_file.c_str()), Return(0))); + + EXPECT_CALL(mock_, send(_, _, _)) + .WillOnce([&](struct libhoth_device*, const void* req, size_t size) { + const auto* hoth_req = + static_cast(req); + EXPECT_EQ(hoth_req->command, + HOTH_BASE_CMD(HOTH_PRV_CMD_HOTH_KEY_PROVISIONING)); + const auto* store_req = reinterpret_cast< + const struct hoth_key_provisioning_store_secrets_request*>( + static_cast(req) + + sizeof(struct hoth_host_request)); + EXPECT_EQ(store_req->hdr.version, + HOTH_KEY_PROVISIONING_REQUEST_VERSION); + EXPECT_EQ(store_req->hdr.command, HOTH_KEY_PROVISIONING_STORE_SECRETS); + EXPECT_EQ(store_req->hdr.size, + sizeof(struct hoth_key_provisioning_request_header) + + sizeof(secret_bytes)); + EXPECT_EQ( + memcmp(store_req->secrets, secret_bytes, sizeof(secret_bytes)), 0); + return LIBHOTH_OK; + }); + + uint8_t dummy_resp = 0; + EXPECT_CALL(mock_, receive(_, _, _, _, _)) + .WillOnce(DoAll(CopyResp(&dummy_resp, 0), Return(LIBHOTH_OK))); + + ASSERT_EQ(htool_provisioning_store_secrets(&inv), 0); + remove(tmp_secrets_file.c_str()); +} + +TEST_F(HtoolProvisioningTest, StoreSecretsTooLargeFile) { + struct htool_invocation inv{}; + std::string tmp_secrets_file = tmp_dir_path_ + "/secrets_too_large.bin"; + std::vector data(HOTH_KEY_PROVISIONING_MAX_SECRETS_SIZE + 1, 0x11); + FILE* fp = fopen(tmp_secrets_file.c_str(), "wb"); + ASSERT_NE(fp, nullptr); + ASSERT_EQ(fwrite(data.data(), 1, data.size(), fp), data.size()); + fclose(fp); + + EXPECT_CALL(invocation_mock_, GetParamString("secrets", _)) + .WillOnce(DoAll(SetArgPointee<1>(tmp_secrets_file.c_str()), Return(0))); + + ASSERT_EQ(htool_provisioning_store_secrets(&inv), -1); + remove(tmp_secrets_file.c_str()); +} + +TEST_F(HtoolProvisioningTest, StoreSecretsMissingSecrets) { + struct htool_invocation inv{}; + EXPECT_CALL(invocation_mock_, GetParamString("secrets", _)) + .WillOnce(DoAll(SetArgPointee<1>(""), Return(0))); + + ASSERT_EQ(htool_provisioning_store_secrets(&inv), -1); +} + +TEST_F(HtoolProvisioningTest, WriteSuccess) { + struct htool_invocation inv{}; + std::string tmp_input_file = tmp_dir_path_ + "/prov_log_to_write.bin"; + std::vector data(1500, 0x5a); + FILE* fp = fopen(tmp_input_file.c_str(), "wb"); + ASSERT_NE(fp, nullptr); + ASSERT_EQ(fwrite(data.data(), 1, data.size(), fp), data.size()); + fclose(fp); + + EXPECT_CALL(invocation_mock_, GetParamString("input", _)) + .WillOnce(DoAll(SetArgPointee<1>(tmp_input_file.c_str()), Return(0))); + + // 1500 bytes: chunk 0 = 1004, chunk 1 = 496, then commit + int call_count = 0; + EXPECT_CALL(mock_, send(_, _, _)) + .Times(3) + .WillRepeatedly([&](struct libhoth_device*, const void* req, + size_t size) { + const auto* hoth_req = + static_cast(req); + EXPECT_EQ(hoth_req->command, + HOTH_BASE_CMD(HOTH_PRV_CMD_HOTH_PROVISIONING_LOG)); + const auto* write_req = + reinterpret_cast( + static_cast(req) + + sizeof(struct hoth_host_request)); + if (call_count == 0) { + EXPECT_EQ(write_req->req.operation, PROVISIONING_LOG_WRITE); + EXPECT_EQ(write_req->req.offset, 0); + EXPECT_EQ(write_req->req.size, 1004); + EXPECT_EQ(memcmp(write_req->data, data.data(), 1004), 0); + } else if (call_count == 1) { + EXPECT_EQ(write_req->req.operation, PROVISIONING_LOG_WRITE); + EXPECT_EQ(write_req->req.offset, 1004); + EXPECT_EQ(write_req->req.size, 496); + EXPECT_EQ(memcmp(write_req->data, data.data() + 1004, 496), 0); + } else if (call_count == 2) { + EXPECT_EQ(write_req->req.operation, PROVISIONING_LOG_COMMIT); + EXPECT_EQ(write_req->req.size, 1500); + EXPECT_EQ(write_req->req.checksum, + crc32(0, data.data(), data.size())); + } + call_count++; + return LIBHOTH_OK; + }); + + uint8_t dummy_resp = 0; + EXPECT_CALL(mock_, receive(_, _, _, _, _)) + .Times(3) + .WillRepeatedly(DoAll(CopyResp(&dummy_resp, 0), Return(LIBHOTH_OK))); + + ASSERT_EQ(htool_provisioning_write(&inv), 0); + remove(tmp_input_file.c_str()); +} + +TEST_F(HtoolProvisioningTest, WriteTooLargeFile) { + struct htool_invocation inv{}; + std::string tmp_input_file = tmp_dir_path_ + "/prov_log_too_large.bin"; + std::vector data(PROVISIONING_LOG_MAX_SIZE + 1, 0x5a); + FILE* fp = fopen(tmp_input_file.c_str(), "wb"); + ASSERT_NE(fp, nullptr); + ASSERT_EQ(fwrite(data.data(), 1, data.size(), fp), data.size()); + fclose(fp); + + EXPECT_CALL(invocation_mock_, GetParamString("input", _)) + .WillOnce(DoAll(SetArgPointee<1>(tmp_input_file.c_str()), Return(0))); + + ASSERT_EQ(htool_provisioning_write(&inv), -1); + remove(tmp_input_file.c_str()); +} + +TEST_F(HtoolProvisioningTest, WriteMissingInput) { + struct htool_invocation inv{}; + EXPECT_CALL(invocation_mock_, GetParamString("input", _)) + .WillOnce(DoAll(SetArgPointee<1>(""), Return(0))); + + ASSERT_EQ(htool_provisioning_write(&inv), -1); +} + +TEST_F(HtoolProvisioningTest, LoadMldsaKeyFileSuccess) { + struct htool_invocation inv{}; + std::string tmp_key_file = tmp_dir_path_ + "/mldsa.bin"; + std::vector key_data(HOTH_KEY_PROVISIONING_MLDSA44_PUBLIC_KEY_BYTES, + 0x44); + FILE* fp = fopen(tmp_key_file.c_str(), "wb"); + ASSERT_NE(fp, nullptr); + ASSERT_EQ(fwrite(key_data.data(), 1, key_data.size(), fp), key_data.size()); + fclose(fp); + + EXPECT_CALL(invocation_mock_, GetParamString("key", _)) + .WillOnce(DoAll(SetArgPointee<1>(tmp_key_file.c_str()), Return(0))); + + int call_count = 0; + EXPECT_CALL(mock_, send(_, _, _)) + .Times(2) + .WillRepeatedly([&](struct libhoth_device*, const void* req, + size_t size) { + const auto* hoth_req = + static_cast(req); + EXPECT_EQ(hoth_req->command, + HOTH_BASE_CMD(HOTH_PRV_CMD_HOTH_KEY_PROVISIONING)); + const auto* load_req = reinterpret_cast< + const struct hoth_key_provisioning_load_key_request*>( + static_cast(req) + + sizeof(struct hoth_host_request)); + EXPECT_EQ(load_req->hdr.version, HOTH_KEY_PROVISIONING_REQUEST_VERSION); + EXPECT_EQ(load_req->hdr.command, + HOTH_KEY_PROVISIONING_LOAD_MLDSA_PUBLIC_KEY); + if (call_count == 0) { + EXPECT_EQ(load_req->args.offset, 0); + EXPECT_EQ(load_req->args.size, 1008); + EXPECT_EQ(load_req->hdr.size, + sizeof(load_req->hdr) + sizeof(load_req->args) + 1008); + EXPECT_EQ(memcmp(load_req->data, key_data.data(), 1008), 0); + } else if (call_count == 1) { + EXPECT_EQ(load_req->args.offset, 1008); + EXPECT_EQ(load_req->args.size, 304); + EXPECT_EQ(load_req->hdr.size, + sizeof(load_req->hdr) + sizeof(load_req->args) + 304); + EXPECT_EQ(memcmp(load_req->data, key_data.data() + 1008, 304), 0); + } + call_count++; + return LIBHOTH_OK; + }); + uint8_t dummy_resp = 0; + EXPECT_CALL(mock_, receive(_, _, _, _, _)) + .Times(2) + .WillRepeatedly(DoAll(CopyResp(&dummy_resp, 0), Return(LIBHOTH_OK))); + + ASSERT_EQ(htool_provisioning_load_mldsa_key(&inv), 0); + remove(tmp_key_file.c_str()); +} + +TEST_F(HtoolProvisioningTest, LoadMldsaKeyTooLargeFile) { + struct htool_invocation inv{}; + std::string tmp_key_file = tmp_dir_path_ + "/mldsa_too_large.bin"; + std::vector key_data( + HOTH_KEY_PROVISIONING_MLDSA44_PUBLIC_KEY_BYTES + 1, 0x44); + FILE* fp = fopen(tmp_key_file.c_str(), "wb"); + ASSERT_NE(fp, nullptr); + ASSERT_EQ(fwrite(key_data.data(), 1, key_data.size(), fp), key_data.size()); + fclose(fp); + + EXPECT_CALL(invocation_mock_, GetParamString("key", _)) + .WillOnce(DoAll(SetArgPointee<1>(tmp_key_file.c_str()), Return(0))); + + ASSERT_EQ(htool_provisioning_load_mldsa_key(&inv), -1); + remove(tmp_key_file.c_str()); +} + +TEST_F(HtoolProvisioningTest, LoadMldsaKeyMissingKey) { + struct htool_invocation inv{}; + EXPECT_CALL(invocation_mock_, GetParamString("key", _)) + .WillOnce(DoAll(SetArgPointee<1>(""), Return(0))); + + ASSERT_EQ(htool_provisioning_load_mldsa_key(&inv), -1); +} From 643a70aa4dcdcb8e2c0ea61ba80ecb50f15df9f5 Mon Sep 17 00:00:00 2001 From: Willy Zhang Date: Thu, 17 Sep 2026 18:53:15 +0000 Subject: [PATCH 2/4] [htool] Address review feedback on provisioning commands - Document that the request header's `command` field holds a value from enum hoth_key_provisioning_command. - Clarify that --key takes a raw 1312-byte ML-DSA-44 public key, not PEM or DER. - Reword the store_secrets description: secrets are encrypted *with* the provisioning encryption key. - Send error messages to stderr, consistent with the newer code in this file and the rest of the codebase. --- examples/htool.c | 5 +-- examples/htool_provisioning.c | 61 ++++++++++++++++++----------------- examples/htool_provisioning.h | 2 +- 3 files changed, 35 insertions(+), 33 deletions(-) diff --git a/examples/htool.c b/examples/htool.c index 55caa9b..6536246 100644 --- a/examples/htool.c +++ b/examples/htool.c @@ -2061,7 +2061,7 @@ static const struct htool_cmd CMDS[] = { }, { .verbs = (const char*[]){"provisioning", "store_secrets", NULL}, - .desc = "Store secrets encrypted to the provisioning encryption key", + .desc = "Store secrets encrypted with the provisioning encryption key", .func = htool_provisioning_store_secrets, .params = (const struct htool_param[]){ @@ -2086,7 +2086,8 @@ static const struct htool_cmd CMDS[] = { .params = (const struct htool_param[]){ {HTOOL_FLAG_VALUE, .name = "key", .default_value = "", - .desc = "Path to the 1312-byte ML-DSA-44 public key file."}, + .desc = "Path to the ML-DSA-44 public key file. Must be " + "exactly 1312 raw bytes."}, {}}, }, { diff --git a/examples/htool_provisioning.c b/examples/htool_provisioning.c index 65e69b1..7252244 100644 --- a/examples/htool_provisioning.c +++ b/examples/htool_provisioning.c @@ -67,7 +67,7 @@ int htool_get_provisioning_log(const struct htool_invocation* inv) { int status = -1; struct libhoth_device* dev = htool_libhoth_device(); if (!dev) { - printf("Unable to retrieve libhoth_device\n"); + fprintf(stderr, "Unable to retrieve libhoth_device\n"); return -1; } @@ -80,8 +80,8 @@ int htool_get_provisioning_log(const struct htool_invocation* inv) { FILE* output_ptr = NULL; output_ptr = fopen(output_file, "wb"); if (output_ptr == NULL) { - printf("Error: %s, when attempting to open file: %s\n", strerror(errno), - output_file); + fprintf(stderr, "Error: %s, when attempting to open file: %s\n", + strerror(errno), output_file); goto cleanup; } enum provisioning_log_op operation = PROVISIONING_LOG_READ; @@ -134,7 +134,8 @@ int htool_get_provisioning_log(const struct htool_invocation* inv) { dev, &request, sizeof(request), &response, sizeof(response), &response_size); if (exec_status != 0) { - printf( + fprintf( + stderr, "Unexpected Error: Returned status %d, while trying to send " "command to " "read the provisioning_log\n", @@ -144,19 +145,19 @@ int htool_get_provisioning_log(const struct htool_invocation* inv) { } // Check if read bytes matches chunk size if (response_size != chunk_size + sizeof(prov_log_hdr_resp)) { - printf( - "Unexpected host command response size. Expecting %lu; Got " - "%lu\n", - chunk_size + sizeof(prov_log_hdr_resp), response_size); + fprintf(stderr, + "Unexpected host command response size. Expecting %lu; Got " + "%lu\n", + chunk_size + sizeof(prov_log_hdr_resp), response_size); status = 1; goto cleanup; } if (bytes_read + chunk_size > PROVISIONING_LOG_MAX_SIZE) { - printf( - "Unexpected Error: Bytes returned: %hu > " - "PROVISIONING_LOG_MAX_SIZE: %u\n", - bytes_read + chunk_size, PROVISIONING_LOG_MAX_SIZE); + fprintf(stderr, + "Unexpected Error: Bytes returned: %hu > " + "PROVISIONING_LOG_MAX_SIZE: %u\n", + bytes_read + chunk_size, PROVISIONING_LOG_MAX_SIZE); goto cleanup; } @@ -174,7 +175,7 @@ int htool_get_provisioning_log(const struct htool_invocation* inv) { // SECURITY_V3 not supported yet. default: status = -1; - printf("SECURITY_V3 is not supported yet\n"); + fprintf(stderr, "SECURITY_V3 is not supported yet\n"); goto cleanup; } @@ -195,7 +196,7 @@ int htool_validate_and_sign(const struct htool_invocation* inv) { uint8_t* perso_blob_data = NULL; struct libhoth_device* dev = htool_libhoth_device(); if (!dev) { - printf("Unable to retrieve libhoth_device\n"); + fprintf(stderr, "Unable to retrieve libhoth_device\n"); return -1; } @@ -207,8 +208,8 @@ int htool_validate_and_sign(const struct htool_invocation* inv) { perso_blob_ptr = fopen(perso_blob_file, "rb"); if (perso_blob_ptr == NULL) { - printf("Error: %s, when attempting to open file: %s\n", strerror(errno), - perso_blob_file); + fprintf(stderr, "Error: %s, when attempting to open file: %s\n", + strerror(errno), perso_blob_file); goto cleanup; } @@ -220,8 +221,8 @@ int htool_validate_and_sign(const struct htool_invocation* inv) { size_t bytes_read = fread(perso_blob_data, sizeof(uint8_t), perso_blob_size, perso_blob_ptr); if (bytes_read <= 0) { - printf("Error: %s, when trying to read perso_blob: %s\n", strerror(errno), - perso_blob_file); + fprintf(stderr, "Error: %s, when trying to read perso_blob: %s\n", + strerror(errno), perso_blob_file); goto cleanup; } @@ -235,8 +236,8 @@ int htool_validate_and_sign(const struct htool_invocation* inv) { if (strlen(output_file) > 0) { output_ptr = fopen(output_file, "wb"); if (output_ptr == NULL) { - printf("Error: %s, when attempting to open file: %s\n", strerror(errno), - output_file); + fprintf(stderr, "Error: %s, when attempting to open file: %s\n", + strerror(errno), output_file); goto cleanup; } } @@ -268,21 +269,21 @@ int htool_validate_and_sign(const struct htool_invocation* inv) { /*version=*/0, request_ptr, sizeof(request), &response, sizeof(response), &response_size); if (exec_status != 0) { - printf( - "Unexpected Error: Returned status %d, while trying to send " - "command to " - "read the provisioning_log\n", - exec_status); + fprintf(stderr, + "Unexpected Error: Returned status %d, while trying to send " + "command to " + "read the provisioning_log\n", + exec_status); status = exec_status; goto cleanup; } if (response_size > PROVISIONING_CERT_MAX_SIZE) { - printf( - "Unexpected Error: Bytes returned: %lu > " - "PROVISIONING_CERT_MAX_SIZE: %u\n", - response_size, PROVISIONING_CERT_MAX_SIZE); + fprintf(stderr, + "Unexpected Error: Bytes returned: %lu > " + "PROVISIONING_CERT_MAX_SIZE: %u\n", + response_size, PROVISIONING_CERT_MAX_SIZE); goto cleanup; } @@ -296,7 +297,7 @@ int htool_validate_and_sign(const struct htool_invocation* inv) { // SECURITY_V3 not supported yet. default: status = -1; - printf("SECURITY_V3 is not supported yet.\n"); + fprintf(stderr, "SECURITY_V3 is not supported yet.\n"); goto cleanup; } diff --git a/examples/htool_provisioning.h b/examples/htool_provisioning.h index 4d873bc..686a074 100644 --- a/examples/htool_provisioning.h +++ b/examples/htool_provisioning.h @@ -39,7 +39,7 @@ enum hoth_key_provisioning_command { struct hoth_key_provisioning_request_header { uint8_t version; - uint8_t command; + uint8_t command; // enum hoth_key_provisioning_command uint16_t size; } __attribute__((packed)); From 217ba3c565f04c608b51106d1897d770dc2cdad7 Mon Sep 17 00:00:00 2001 From: Willy Zhang Date: Thu, 17 Sep 2026 19:35:46 +0000 Subject: [PATCH 3/4] [htool] Move provisioning protocol definitions into protocol/ Follows the convention used by chipinfo, payload_status and opentitan_version: the host command codes and wire structs now live in protocol/provisioning.h next to the code that uses them, and the device I/O moves to protocol/provisioning.c. examples/htool_provisioning.c keeps flag parsing, file I/O, the security-version gate and user-facing output. Status codes, message text and ordering are unchanged, so the existing tests pass with only the crc32 -> libhoth_provisioning_crc32 rename. The rename is required because protocol/provisioning.h is an installed header and a bare crc32 symbol would collide with zlib. The mix of libhoth_hostcmd_exec (log read, validate-and-sign) and libhoth_hostcmd_exec_v2 (write, commit, key provisioning) is carried over as-is to keep this change behaviour-preserving. --- examples/BUILD | 4 +- examples/host_commands.h | 6 - examples/htool_provisioning.c | 360 +++++----------------------- examples/htool_provisioning.h | 99 +------- examples/htool_provisioning_test.cc | 6 +- protocol/BUILD | 11 + protocol/meson.build | 1 + protocol/provisioning.c | 308 ++++++++++++++++++++++++ protocol/provisioning.h | 173 +++++++++++++ 9 files changed, 561 insertions(+), 407 deletions(-) create mode 100644 protocol/provisioning.c create mode 100644 protocol/provisioning.h diff --git a/examples/BUILD b/examples/BUILD index a8fea54..4b4ca1e 100644 --- a/examples/BUILD +++ b/examples/BUILD @@ -67,12 +67,11 @@ cc_library( srcs = ["htool_provisioning.c"], hdrs = ["htool_provisioning.h"], deps = [ - ":host_commands", ":htool_cmd", ":htool_header", ":htool_macros", ":htool_security_version", - "//protocol:host_cmd", + "//protocol:provisioning", "//transports:libhoth_device", ], ) @@ -267,6 +266,7 @@ cc_binary( "//protocol:payload_status", "//protocol:payload_update", "//protocol:progress", + "//protocol:provisioning", "//protocol:reboot", "//protocol:rot_firmware_version", "//protocol:secure_boot", diff --git a/examples/host_commands.h b/examples/host_commands.h index df6f606..678fa0c 100644 --- a/examples/host_commands.h +++ b/examples/host_commands.h @@ -79,12 +79,6 @@ struct hoth_response_flash_spi_info { */ #define HOTH_BASE_CMD(cmd) (HOTH_CMD_BOARD_SPECIFIC_BASE + (cmd)) -/* The major command identifier for the Provisioning Log host command. */ -#define HOTH_PRV_CMD_HOTH_PROVISIONING_LOG 0x0040 - -/* The major command identifier for the Key Provisioning host command. */ -#define HOTH_PRV_CMD_HOTH_KEY_PROVISIONING 0x0043 - /** * The request header structure for security v2 commands */ diff --git a/examples/htool_provisioning.c b/examples/htool_provisioning.c index 7252244..cdc427f 100644 --- a/examples/htool_provisioning.c +++ b/examples/htool_provisioning.c @@ -1,6 +1,5 @@ #include "htool_provisioning.h" -#include #include #include #include @@ -10,61 +9,18 @@ #include #include -#include "host_commands.h" #include "htool.h" #include "htool_cmd.h" #include "htool_security_version.h" -#include "protocol/host_cmd.h" +#include "protocol/provisioning.h" #include "transports/libhoth_device.h" -// This is a standalone CRC32 that matches Titan Firmware. -// A table-free bit-level implementation is okay since there are no -// performance constraints in it's use in htool_validate_and_sign. -uint32_t crc32(uint32_t initial_value, const uint8_t* buf, size_t size) { - const uint32_t polynomial = 0xEDB88320; - - uint32_t crc = ~initial_value; - for (int i = 0; i < size; i++) { - uint8_t byte = ((uint8_t*)buf)[i]; - crc = crc ^ byte; - for (int j = 0; j < 8; j++, byte >>= 1) { - crc = (crc >> 1) ^ ((crc & 1) ? polynomial : 0); - } - } - return ~crc; -} - -// Helper function used to return the lowest value integer given two integers -static uint16_t min(uint16_t a, uint16_t b) { return (a < b) ? a : b; } - -// Executes a command on the provisioning log host command (0x3E40) -static libhoth_error exec_provisioning_log_cmd(struct libhoth_device* dev, - const void* req_payload, - size_t req_payload_size, - void* resp_buf, - size_t resp_buf_size, - size_t* out_resp_size) { - return libhoth_hostcmd_exec_v2( - dev, /*command=*/HOTH_BASE_CMD(HOTH_PRV_CMD_HOTH_PROVISIONING_LOG), - /*version=*/0, req_payload, req_payload_size, resp_buf, resp_buf_size, - out_resp_size); -} - -// Runs the command to read a portion of the provisioning log -static int read_chunk_from_provisioning_log(struct libhoth_device* dev, - const void* req_payload, - size_t req_payload_size, - void* resp_buf, - size_t resp_buf_size, - size_t* out_resp_size) { - return libhoth_hostcmd_exec( - dev, /*command=*/HOTH_BASE_CMD(HOTH_PRV_CMD_HOTH_PROVISIONING_LOG), - /*version=*/0, req_payload, req_payload_size, resp_buf, resp_buf_size, - out_resp_size); -} - int htool_get_provisioning_log(const struct htool_invocation* inv) { int status = -1; + FILE* output_ptr = NULL; + uint8_t provisioning_log_data[PROVISIONING_LOG_MAX_SIZE]; + size_t bytes_read = 0; + struct libhoth_device* dev = htool_libhoth_device(); if (!dev) { fprintf(stderr, "Unable to retrieve libhoth_device\n"); @@ -77,108 +33,29 @@ int htool_get_provisioning_log(const struct htool_invocation* inv) { return result; } - FILE* output_ptr = NULL; output_ptr = fopen(output_file, "wb"); if (output_ptr == NULL) { fprintf(stderr, "Error: %s, when attempting to open file: %s\n", strerror(errno), output_file); goto cleanup; } - enum provisioning_log_op operation = PROVISIONING_LOG_READ; - - struct hoth_provisioning_log_header prov_log_hdr_resp; - memset(&prov_log_hdr_resp, 0, sizeof(prov_log_hdr_resp)); - struct hoth_provisioning_log_request request = { - .version = 1, - .operation = operation, - .reserved = 0, - .offset = 0, - .size = 0, - .checksum = 0, - }; - - libhoth_security_version sv = htool_get_security_version(dev); - switch (sv) { - case LIBHOTH_SECURITY_V2: { - { - // Get Provisioning Log Header - size_t response_size = 0; - // Execute libhoth command to read provisiong log - int exec_status = read_chunk_from_provisioning_log( - dev, &request, sizeof(request), &prov_log_hdr_resp, - sizeof(prov_log_hdr_resp), &response_size); - if (exec_status != 0) { - status = exec_status; - goto cleanup; - } - - // Get Provisioning Log - uint16_t bytes_read = 0; - // Holds the provisioning log data while all of the chunks are being - // collected - uint8_t provisioning_log_data[PROVISIONING_LOG_MAX_SIZE]; - while (bytes_read < prov_log_hdr_resp.size) { - // Read the provisioning log in chunks - struct hoth_provisioning_log response; - memset(&response, 0, sizeof(response)); - // Get the size of the data to be requested - uint16_t chunk_size = min(prov_log_hdr_resp.size - bytes_read, - PROVISIONING_LOG_CHUNK_MAX_SIZE); - // Update the request to the appropriate size - request.offset = bytes_read; - request.size = chunk_size; - response_size = 0; - - // Execute libhoth command to read provisioning log - exec_status = read_chunk_from_provisioning_log( - dev, &request, sizeof(request), &response, sizeof(response), - &response_size); - if (exec_status != 0) { - fprintf( - stderr, - "Unexpected Error: Returned status %d, while trying to send " - "command to " - "read the provisioning_log\n", - exec_status); - status = exec_status; - goto cleanup; - } - // Check if read bytes matches chunk size - if (response_size != chunk_size + sizeof(prov_log_hdr_resp)) { - fprintf(stderr, - "Unexpected host command response size. Expecting %lu; Got " - "%lu\n", - chunk_size + sizeof(prov_log_hdr_resp), response_size); - status = 1; - goto cleanup; - } - - if (bytes_read + chunk_size > PROVISIONING_LOG_MAX_SIZE) { - fprintf(stderr, - "Unexpected Error: Bytes returned: %hu > " - "PROVISIONING_LOG_MAX_SIZE: %u\n", - bytes_read + chunk_size, PROVISIONING_LOG_MAX_SIZE); - goto cleanup; - } - - // Copy the read bytes into the provisioning_log_data buffer - memcpy(provisioning_log_data + bytes_read, response.data, chunk_size); - // Increment the amount of bytes of the provisioning_log that have - // been consumed - bytes_read += chunk_size; - } - // Write the provisioning_log that was read into the output file - fwrite(provisioning_log_data, bytes_read, sizeof(uint8_t), output_ptr); - break; - } - } - // SECURITY_V3 not supported yet. - default: - status = -1; - fprintf(stderr, "SECURITY_V3 is not supported yet\n"); - goto cleanup; + + // SECURITY_V3 not supported yet. + if (htool_get_security_version(dev) != LIBHOTH_SECURITY_V2) { + status = -1; + fprintf(stderr, "SECURITY_V3 is not supported yet\n"); + goto cleanup; + } + + status = libhoth_provisioning_log_read( + dev, provisioning_log_data, sizeof(provisioning_log_data), &bytes_read); + if (status != 0) { + goto cleanup; } + // Write the provisioning_log that was read into the output file + fwrite(provisioning_log_data, bytes_read, sizeof(uint8_t), output_ptr); + // Return success if no other errors have occured at this point status = 0; // Success @@ -194,6 +71,9 @@ int htool_validate_and_sign(const struct htool_invocation* inv) { FILE* perso_blob_ptr = NULL; FILE* output_ptr = NULL; uint8_t* perso_blob_data = NULL; + uint8_t cert[PROVISIONING_CERT_MAX_SIZE]; + size_t cert_size = 0; + struct libhoth_device* dev = htool_libhoth_device(); if (!dev) { fprintf(stderr, "Unable to retrieve libhoth_device\n"); @@ -242,63 +122,23 @@ int htool_validate_and_sign(const struct htool_invocation* inv) { } } - enum provisioning_log_op operation = PROVISIONING_LOG_VALIDATE_AND_SIGN; - - // Collect all of the bytes from the request - uint8_t response[PROVISIONING_CERT_MAX_SIZE]; - memset(response, 0, sizeof(response)); - uint32_t checksum = crc32(0, perso_blob_data, perso_blob_size); - struct hoth_provisioning_log_request request = { - .version = 1, - .operation = operation, - .reserved = 0, - .offset = 0, - .size = perso_blob_size, - .checksum = checksum, - }; - - libhoth_security_version sv = htool_get_security_version(dev); - switch (sv) { - case LIBHOTH_SECURITY_V2: { - { - // Validate and Sign the Provisioning Log - size_t response_size = 0; - uint8_t* request_ptr = (uint8_t*)&request; - int exec_status = libhoth_hostcmd_exec( - dev, /*command=*/HOTH_BASE_CMD(HOTH_PRV_CMD_HOTH_PROVISIONING_LOG), - /*version=*/0, request_ptr, sizeof(request), &response, - sizeof(response), &response_size); - if (exec_status != 0) { - fprintf(stderr, - "Unexpected Error: Returned status %d, while trying to send " - "command to " - "read the provisioning_log\n", - exec_status); - - status = exec_status; - goto cleanup; - } - - if (response_size > PROVISIONING_CERT_MAX_SIZE) { - fprintf(stderr, - "Unexpected Error: Bytes returned: %lu > " - "PROVISIONING_CERT_MAX_SIZE: %u\n", - response_size, PROVISIONING_CERT_MAX_SIZE); - goto cleanup; - } - - // Write the signed provisioning_log into the output file - if (output_ptr != NULL) { - fwrite(response, response_size, sizeof(uint8_t), output_ptr); - } - break; - } - } - // SECURITY_V3 not supported yet. - default: - status = -1; - fprintf(stderr, "SECURITY_V3 is not supported yet.\n"); - goto cleanup; + // SECURITY_V3 not supported yet. + if (htool_get_security_version(dev) != LIBHOTH_SECURITY_V2) { + status = -1; + fprintf(stderr, "SECURITY_V3 is not supported yet.\n"); + goto cleanup; + } + + memset(cert, 0, sizeof(cert)); + status = libhoth_provisioning_log_validate_and_sign( + dev, perso_blob_data, perso_blob_size, cert, sizeof(cert), &cert_size); + if (status != 0) { + goto cleanup; + } + + // Write the signed provisioning_log into the output file + if (output_ptr != NULL) { + fwrite(cert, cert_size, sizeof(uint8_t), output_ptr); } // Return success if no other errors have occured at this point @@ -394,23 +234,8 @@ int htool_provisioning_store_secrets(const struct htool_invocation* inv) { return -1; } - const size_t request_size = - sizeof(struct hoth_key_provisioning_request_header) + secrets_size; - - struct hoth_key_provisioning_store_secrets_request req = { - .hdr = - { - .version = HOTH_KEY_PROVISIONING_REQUEST_VERSION, - .command = HOTH_KEY_PROVISIONING_STORE_SECRETS, - .size = (uint16_t)request_size, - }, - }; - memcpy(req.secrets, secrets, secrets_size); - - size_t response_size = 0; - libhoth_error err = libhoth_hostcmd_exec_v2( - dev, HOTH_BASE_CMD(HOTH_PRV_CMD_HOTH_KEY_PROVISIONING), - /*version=*/0, &req, request_size, NULL, 0, &response_size); + libhoth_error err = + libhoth_key_provisioning_store_secrets(dev, secrets, secrets_size); if (err != HOTH_SUCCESS) { fprintf(stderr, "Error: 'key_provisioning_store_secrets' failed (0x%016" PRIx64 @@ -444,51 +269,15 @@ int htool_provisioning_write(const struct htool_invocation* inv) { return -1; } - uint16_t bytes_written = 0; - while (bytes_written < file_size) { - uint16_t chunk_size = (uint16_t)(file_size - bytes_written); - if (chunk_size > PROVISIONING_LOG_WRITE_CHUNK_MAX_SIZE) { - chunk_size = PROVISIONING_LOG_WRITE_CHUNK_MAX_SIZE; - } + libhoth_error err = libhoth_provisioning_log_write(dev, log_data, file_size); + if (err != HOTH_SUCCESS) { + fprintf(stderr, + "Error: 'provisioning_log_write' failed (0x%016" PRIx64 "): ", err); + libhoth_log_err(stderr, err); + return -1; + } - struct hoth_provisioning_log_write_request write_req = { - .req = - { - .version = 1, - .operation = PROVISIONING_LOG_WRITE, - .reserved = 0, - .offset = bytes_written, - .size = chunk_size, - .checksum = 0, - }, - }; - memcpy(write_req.data, log_data + bytes_written, chunk_size); - - size_t response_size = 0; - libhoth_error err = exec_provisioning_log_cmd( - dev, &write_req, sizeof(write_req.req) + chunk_size, NULL, 0, - &response_size); - if (err != HOTH_SUCCESS) { - fprintf( - stderr, - "Error: 'provisioning_log_write' failed (0x%016" PRIx64 "): ", err); - libhoth_log_err(stderr, err); - return -1; - } - bytes_written += chunk_size; - } - - struct hoth_provisioning_log_request commit_req = { - .version = 1, - .operation = PROVISIONING_LOG_COMMIT, - .reserved = 0, - .offset = 0, - .size = (uint16_t)file_size, - .checksum = crc32(0, log_data, file_size), - }; - size_t response_size = 0; - libhoth_error err = exec_provisioning_log_cmd( - dev, &commit_req, sizeof(commit_req), NULL, 0, &response_size); + err = libhoth_provisioning_log_commit(dev, log_data, file_size); if (err != HOTH_SUCCESS) { fprintf( stderr, @@ -522,46 +311,15 @@ int htool_provisioning_load_mldsa_key(const struct htool_invocation* inv) { return -1; } - uint16_t offset = 0; - while (offset < sizeof(key_buf)) { - uint16_t chunk_size = (uint16_t)(sizeof(key_buf) - offset); - if (chunk_size > HOTH_KEY_PROVISIONING_LOAD_KEY_CHUNK_MAX_SIZE) { - chunk_size = HOTH_KEY_PROVISIONING_LOAD_KEY_CHUNK_MAX_SIZE; - } - - const size_t req_size = - sizeof(struct hoth_key_provisioning_request_header) + - sizeof(struct hoth_key_provisioning_load_key_args) + chunk_size; - - struct hoth_key_provisioning_load_key_request req = { - .hdr = - { - .version = HOTH_KEY_PROVISIONING_REQUEST_VERSION, - .command = HOTH_KEY_PROVISIONING_LOAD_MLDSA_PUBLIC_KEY, - .size = (uint16_t)req_size, - }, - .args = - { - .offset = offset, - .size = chunk_size, - }, - }; - memcpy(req.data, key_buf + offset, chunk_size); - - size_t response_size = 0; - libhoth_error err = libhoth_hostcmd_exec_v2( - dev, HOTH_BASE_CMD(HOTH_PRV_CMD_HOTH_KEY_PROVISIONING), - /*version=*/0, &req, req_size, NULL, 0, &response_size); - if (err != HOTH_SUCCESS) { - fprintf(stderr, - "Error: 'key_provisioning_load_mldsa_key' failed (0x%016" PRIx64 - "): ", - err); - libhoth_log_err(stderr, err); - return -1; - } - - offset += chunk_size; + libhoth_error err = + libhoth_key_provisioning_load_mldsa_key(dev, key_buf, sizeof(key_buf)); + if (err != HOTH_SUCCESS) { + fprintf(stderr, + "Error: 'key_provisioning_load_mldsa_key' failed (0x%016" PRIx64 + "): ", + err); + libhoth_log_err(stderr, err); + return -1; } printf("ML-DSA public key loaded successfully\n"); diff --git a/examples/htool_provisioning.h b/examples/htool_provisioning.h index 686a074..79560f9 100644 --- a/examples/htool_provisioning.h +++ b/examples/htool_provisioning.h @@ -18,9 +18,7 @@ #include #include -#include "host_commands.h" -#include "protocol/host_cmd.h" -#include "transports/libhoth_device.h" +#include "protocol/provisioning.h" #ifdef __cplusplus extern "C" { @@ -29,104 +27,15 @@ extern "C" { // Forward declaration struct htool_invocation; -#define HOTH_KEY_PROVISIONING_REQUEST_VERSION 1 - -enum hoth_key_provisioning_command { - HOTH_KEY_PROVISIONING_GET_ENCRYPTION_KEY = 0, - HOTH_KEY_PROVISIONING_STORE_SECRETS = 1, - HOTH_KEY_PROVISIONING_LOAD_MLDSA_PUBLIC_KEY = 2, -}; - -struct hoth_key_provisioning_request_header { - uint8_t version; - uint8_t command; // enum hoth_key_provisioning_command - uint16_t size; -} __attribute__((packed)); - -#define HOTH_KEY_PROVISIONING_MAX_SECRETS_SIZE \ - (LIBHOTH_MAILBOX_SIZE - sizeof(struct hoth_host_request) - \ - sizeof(struct hoth_key_provisioning_request_header)) - -struct hoth_key_provisioning_store_secrets_request { - struct hoth_key_provisioning_request_header hdr; - uint8_t secrets[HOTH_KEY_PROVISIONING_MAX_SECRETS_SIZE]; -} __attribute__((packed)); - -#define HOTH_KEY_PROVISIONING_MLDSA44_PUBLIC_KEY_BYTES 1312 - -struct hoth_key_provisioning_load_key_args { - uint16_t offset; - uint16_t size; -} __attribute__((packed)); - -#define HOTH_KEY_PROVISIONING_LOAD_KEY_CHUNK_MAX_SIZE \ - (LIBHOTH_MAILBOX_SIZE - sizeof(struct hoth_host_request) - \ - sizeof(struct hoth_key_provisioning_request_header) - \ - sizeof(struct hoth_key_provisioning_load_key_args)) - -struct hoth_key_provisioning_load_key_request { - struct hoth_key_provisioning_request_header hdr; - struct hoth_key_provisioning_load_key_args args; - uint8_t data[HOTH_KEY_PROVISIONING_LOAD_KEY_CHUNK_MAX_SIZE]; -} __attribute__((packed)); - -// Loads secrets that were encrypted to the provisioning encryption key. -int htool_provisioning_store_secrets(const struct htool_invocation* inv); - -#define PROVISIONING_LOG_MAX_SIZE 6144 - -#define PROVISIONING_LOG_CHUNK_MAX_SIZE 1008 - -#define PROVISIONING_CERT_MAX_SIZE 240 - -struct hoth_provisioning_log_header { - uint8_t version; // 1 - uint8_t reserved; - uint16_t size; // size of the log content - uint32_t checksum; // CRC32 checksum of |size| bytes of log data -} __attribute__((packed)); - -struct hoth_provisioning_log_request { - uint8_t version; // 1 - uint8_t operation; // enum provisioning_log_op - uint16_t reserved; - uint16_t offset; // Chunked read/write offset - uint16_t size; // Chunked read/write size - uint32_t checksum; // CRC32 checksum of the full provisioning log -} __attribute__((packed)); - -struct hoth_provisioning_log { - struct hoth_provisioning_log_header hdr; - uint8_t data[PROVISIONING_LOG_CHUNK_MAX_SIZE]; -} __attribute__((packed)); - -#define PROVISIONING_LOG_WRITE_CHUNK_MAX_SIZE \ - (LIBHOTH_MAILBOX_SIZE - sizeof(struct hoth_host_request) - \ - sizeof(struct hoth_provisioning_log_request)) - -struct hoth_provisioning_log_write_request { - struct hoth_provisioning_log_request req; - uint8_t data[PROVISIONING_LOG_WRITE_CHUNK_MAX_SIZE]; -} __attribute__((packed)); - -enum provisioning_log_op { - PROVISIONING_LOG_READ = 0, - PROVISIONING_LOG_WRITE = 1, - PROVISIONING_LOG_COMMIT = 2, - PROVISIONING_LOG_VALIDATE_AND_SIGN = 3, -}; - -// This is a standalone CRC32 that matches Titan Firmware. -// A table-free bit-level implementation is okay since there are no -// performance constraints in it's use in htool_validate_and_sign. -uint32_t crc32(uint32_t initial_value, const uint8_t* buf, size_t size); - // Retrieve the provisioning log from the device. int htool_get_provisioning_log(const struct htool_invocation* inv); // Validate and Sign the provisioning log. int htool_validate_and_sign(const struct htool_invocation* inv); +// Loads secrets that were encrypted with the provisioning encryption key. +int htool_provisioning_store_secrets(const struct htool_invocation* inv); + // Writes and commits the provisioning log. int htool_provisioning_write(const struct htool_invocation* inv); diff --git a/examples/htool_provisioning_test.cc b/examples/htool_provisioning_test.cc index 57f5893..e3a66ef 100644 --- a/examples/htool_provisioning_test.cc +++ b/examples/htool_provisioning_test.cc @@ -452,8 +452,8 @@ TEST_F(HtoolProvisioningTest, ValidateAndSignSuccess) { std::vector signed_log_data = {0xDE, 0xAD, 0xBE, 0xEF}; // Expected request payload for ValidateAndSign - uint32_t prov_checksum = - crc32(0, perso_blob_data.data(), perso_blob_data.size()); + uint32_t prov_checksum = libhoth_provisioning_crc32(0, perso_blob_data.data(), + perso_blob_data.size()); struct hoth_provisioning_log_request expected_provisioning_req = { .version = 1, .operation = PROVISIONING_LOG_VALIDATE_AND_SIGN, @@ -848,7 +848,7 @@ TEST_F(HtoolProvisioningTest, WriteSuccess) { EXPECT_EQ(write_req->req.operation, PROVISIONING_LOG_COMMIT); EXPECT_EQ(write_req->req.size, 1500); EXPECT_EQ(write_req->req.checksum, - crc32(0, data.data(), data.size())); + libhoth_provisioning_crc32(0, data.data(), data.size())); } call_count++; return LIBHOTH_OK; diff --git a/protocol/BUILD b/protocol/BUILD index 9d91f5a..dcfad7c 100644 --- a/protocol/BUILD +++ b/protocol/BUILD @@ -654,3 +654,14 @@ cc_test( "@googletest//:gtest_main", ], ) + +cc_library( + name = "provisioning", + srcs = ["provisioning.c"], + hdrs = ["provisioning.h"], + deps = [ + ":host_cmd", + ":libhoth_status", + "//transports:libhoth_device", + ], +) diff --git a/protocol/meson.build b/protocol/meson.build index 9c1c85d..3e0ba20 100644 --- a/protocol/meson.build +++ b/protocol/meson.build @@ -25,6 +25,7 @@ protocol_srcs = [ 'firmware_update.c', 'util.c', 'console.c', + 'provisioning.c', 'gpio_drive_strength.c', 'update_session.c', 'status.c' diff --git a/protocol/provisioning.c b/protocol/provisioning.c new file mode 100644 index 0000000..c86da89 --- /dev/null +++ b/protocol/provisioning.c @@ -0,0 +1,308 @@ +// Copyright 2026 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 "provisioning.h" + +#include +#include +#include +#include +#include + +#include "host_cmd.h" + +uint32_t libhoth_provisioning_crc32(uint32_t initial_value, const uint8_t* buf, + size_t size) { + const uint32_t polynomial = 0xEDB88320; + + uint32_t crc = ~initial_value; + for (size_t i = 0; i < size; i++) { + uint8_t byte = buf[i]; + crc = crc ^ byte; + for (int j = 0; j < 8; j++, byte >>= 1) { + crc = (crc >> 1) ^ ((crc & 1) ? polynomial : 0); + } + } + return ~crc; +} + +// Helper function used to return the lowest value integer given two integers +static uint16_t min_u16(uint16_t a, uint16_t b) { return (a < b) ? a : b; } + +// Executes a command on the provisioning log host command (0x3E40) +static libhoth_error exec_provisioning_log_cmd(struct libhoth_device* dev, + const void* req_payload, + size_t req_payload_size, + void* resp_buf, + size_t resp_buf_size, + size_t* out_resp_size) { + return libhoth_hostcmd_exec_v2(dev, + /*command=*/HOTH_CMD_BOARD_SPECIFIC_BASE + + HOTH_PRV_CMD_HOTH_PROVISIONING_LOG, + /*version=*/0, req_payload, req_payload_size, + resp_buf, resp_buf_size, out_resp_size); +} + +// Runs a provisioning log command, returning the legacy status code. +static int exec_provisioning_log_cmd_legacy(struct libhoth_device* dev, + const void* req_payload, + size_t req_payload_size, + void* resp_buf, + size_t resp_buf_size, + size_t* out_resp_size) { + return libhoth_hostcmd_exec(dev, + /*command=*/HOTH_CMD_BOARD_SPECIFIC_BASE + + HOTH_PRV_CMD_HOTH_PROVISIONING_LOG, + /*version=*/0, req_payload, req_payload_size, + resp_buf, resp_buf_size, out_resp_size); +} + +// Executes a key provisioning host command (0x3E43) +static libhoth_error exec_key_provisioning_cmd(struct libhoth_device* dev, + const void* req_payload, + size_t req_payload_size) { + size_t response_size = 0; + return libhoth_hostcmd_exec_v2(dev, + /*command=*/HOTH_CMD_BOARD_SPECIFIC_BASE + + HOTH_PRV_CMD_HOTH_KEY_PROVISIONING, + /*version=*/0, req_payload, req_payload_size, + NULL, 0, &response_size); +} + +int libhoth_provisioning_log_read(struct libhoth_device* dev, uint8_t* buf, + size_t buf_size, size_t* out_size) { + struct hoth_provisioning_log_header prov_log_hdr_resp; + memset(&prov_log_hdr_resp, 0, sizeof(prov_log_hdr_resp)); + struct hoth_provisioning_log_request request = { + .version = 1, + .operation = PROVISIONING_LOG_READ, + .reserved = 0, + .offset = 0, + .size = 0, + .checksum = 0, + }; + + // Get Provisioning Log Header + size_t response_size = 0; + int exec_status = exec_provisioning_log_cmd_legacy( + dev, &request, sizeof(request), &prov_log_hdr_resp, + sizeof(prov_log_hdr_resp), &response_size); + if (exec_status != 0) { + return exec_status; + } + + // Get Provisioning Log + uint16_t bytes_read = 0; + while (bytes_read < prov_log_hdr_resp.size) { + // Read the provisioning log in chunks + struct hoth_provisioning_log response; + memset(&response, 0, sizeof(response)); + // Get the size of the data to be requested + uint16_t chunk_size = min_u16(prov_log_hdr_resp.size - bytes_read, + PROVISIONING_LOG_CHUNK_MAX_SIZE); + // Update the request to the appropriate size + request.offset = bytes_read; + request.size = chunk_size; + response_size = 0; + + exec_status = exec_provisioning_log_cmd_legacy( + dev, &request, sizeof(request), &response, sizeof(response), + &response_size); + if (exec_status != 0) { + fprintf(stderr, + "Unexpected Error: Returned status %d, while trying to send " + "command to " + "read the provisioning_log\n", + exec_status); + return exec_status; + } + // Check if read bytes matches chunk size + if (response_size != chunk_size + sizeof(prov_log_hdr_resp)) { + fprintf(stderr, + "Unexpected host command response size. Expecting %lu; Got " + "%lu\n", + chunk_size + sizeof(prov_log_hdr_resp), response_size); + return 1; + } + + if (bytes_read + chunk_size > buf_size) { + fprintf(stderr, + "Unexpected Error: Bytes returned: %hu > " + "PROVISIONING_LOG_MAX_SIZE: %zu\n", + (uint16_t)(bytes_read + chunk_size), buf_size); + return -1; + } + + // Copy the read bytes into the caller's buffer + memcpy(buf + bytes_read, response.data, chunk_size); + // Increment the amount of bytes of the provisioning_log that have + // been consumed + bytes_read += chunk_size; + } + + if (out_size != NULL) { + *out_size = bytes_read; + } + return 0; +} + +int libhoth_provisioning_log_validate_and_sign(struct libhoth_device* dev, + const uint8_t* blob, + size_t blob_size, uint8_t* cert, + size_t cert_capacity, + size_t* out_cert_size) { + uint32_t checksum = libhoth_provisioning_crc32(0, blob, blob_size); + struct hoth_provisioning_log_request request = { + .version = 1, + .operation = PROVISIONING_LOG_VALIDATE_AND_SIGN, + .reserved = 0, + .offset = 0, + .size = (uint16_t)blob_size, + .checksum = checksum, + }; + + size_t response_size = 0; + int exec_status = exec_provisioning_log_cmd_legacy( + dev, &request, sizeof(request), cert, cert_capacity, &response_size); + if (exec_status != 0) { + fprintf(stderr, + "Unexpected Error: Returned status %d, while trying to send " + "command to " + "read the provisioning_log\n", + exec_status); + return exec_status; + } + + if (response_size > cert_capacity) { + fprintf(stderr, + "Unexpected Error: Bytes returned: %lu > " + "PROVISIONING_CERT_MAX_SIZE: %zu\n", + response_size, cert_capacity); + return -1; + } + + if (out_cert_size != NULL) { + *out_cert_size = response_size; + } + return 0; +} + +libhoth_error libhoth_provisioning_log_write(struct libhoth_device* dev, + const uint8_t* data, size_t size) { + uint16_t bytes_written = 0; + while (bytes_written < size) { + uint16_t chunk_size = (uint16_t)(size - bytes_written); + if (chunk_size > PROVISIONING_LOG_WRITE_CHUNK_MAX_SIZE) { + chunk_size = PROVISIONING_LOG_WRITE_CHUNK_MAX_SIZE; + } + + struct hoth_provisioning_log_write_request write_req = { + .req = + { + .version = 1, + .operation = PROVISIONING_LOG_WRITE, + .reserved = 0, + .offset = bytes_written, + .size = chunk_size, + .checksum = 0, + }, + }; + memcpy(write_req.data, data + bytes_written, chunk_size); + + size_t response_size = 0; + libhoth_error err = exec_provisioning_log_cmd( + dev, &write_req, sizeof(write_req.req) + chunk_size, NULL, 0, + &response_size); + if (err != HOTH_SUCCESS) { + return err; + } + bytes_written += chunk_size; + } + + return HOTH_SUCCESS; +} + +libhoth_error libhoth_provisioning_log_commit(struct libhoth_device* dev, + const uint8_t* data, + size_t size) { + struct hoth_provisioning_log_request commit_req = { + .version = 1, + .operation = PROVISIONING_LOG_COMMIT, + .reserved = 0, + .offset = 0, + .size = (uint16_t)size, + .checksum = libhoth_provisioning_crc32(0, data, size), + }; + size_t response_size = 0; + return exec_provisioning_log_cmd(dev, &commit_req, sizeof(commit_req), NULL, + 0, &response_size); +} + +libhoth_error libhoth_key_provisioning_store_secrets(struct libhoth_device* dev, + const uint8_t* secrets, + size_t size) { + const size_t request_size = + sizeof(struct hoth_key_provisioning_request_header) + size; + + struct hoth_key_provisioning_store_secrets_request req = { + .hdr = + { + .version = HOTH_KEY_PROVISIONING_REQUEST_VERSION, + .command = HOTH_KEY_PROVISIONING_STORE_SECRETS, + .size = (uint16_t)request_size, + }, + }; + memcpy(req.secrets, secrets, size); + + return exec_key_provisioning_cmd(dev, &req, request_size); +} + +libhoth_error libhoth_key_provisioning_load_mldsa_key( + struct libhoth_device* dev, const uint8_t* key, size_t size) { + uint16_t offset = 0; + while (offset < size) { + uint16_t chunk_size = (uint16_t)(size - offset); + if (chunk_size > HOTH_KEY_PROVISIONING_LOAD_KEY_CHUNK_MAX_SIZE) { + chunk_size = HOTH_KEY_PROVISIONING_LOAD_KEY_CHUNK_MAX_SIZE; + } + + const size_t req_size = + sizeof(struct hoth_key_provisioning_request_header) + + sizeof(struct hoth_key_provisioning_load_key_args) + chunk_size; + + struct hoth_key_provisioning_load_key_request req = { + .hdr = + { + .version = HOTH_KEY_PROVISIONING_REQUEST_VERSION, + .command = HOTH_KEY_PROVISIONING_LOAD_MLDSA_PUBLIC_KEY, + .size = (uint16_t)req_size, + }, + .args = + { + .offset = offset, + .size = chunk_size, + }, + }; + memcpy(req.data, key + offset, chunk_size); + + libhoth_error err = exec_key_provisioning_cmd(dev, &req, req_size); + if (err != HOTH_SUCCESS) { + return err; + } + + offset += chunk_size; + } + + return HOTH_SUCCESS; +} diff --git a/protocol/provisioning.h b/protocol/provisioning.h new file mode 100644 index 0000000..9019e1f --- /dev/null +++ b/protocol/provisioning.h @@ -0,0 +1,173 @@ +// Copyright 2026 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. + +#ifndef LIBHOTH_PROTOCOL_PROVISIONING_H_ +#define LIBHOTH_PROTOCOL_PROVISIONING_H_ + +#include +#include + +#include "protocol/host_cmd.h" +#include "protocol/status.h" +#include "transports/libhoth_device.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* The major command identifier for the Provisioning Log host command. */ +#define HOTH_PRV_CMD_HOTH_PROVISIONING_LOG 0x0040 + +/* The major command identifier for the Key Provisioning host command. */ +#define HOTH_PRV_CMD_HOTH_KEY_PROVISIONING 0x0043 + +#define HOTH_KEY_PROVISIONING_REQUEST_VERSION 1 + +enum hoth_key_provisioning_command { + HOTH_KEY_PROVISIONING_GET_ENCRYPTION_KEY = 0, + HOTH_KEY_PROVISIONING_STORE_SECRETS = 1, + HOTH_KEY_PROVISIONING_LOAD_MLDSA_PUBLIC_KEY = 2, +}; + +struct hoth_key_provisioning_request_header { + uint8_t version; + uint8_t command; // enum hoth_key_provisioning_command + uint16_t size; +} __attribute__((packed)); + +#define HOTH_KEY_PROVISIONING_MAX_SECRETS_SIZE \ + (LIBHOTH_MAILBOX_SIZE - sizeof(struct hoth_host_request) - \ + sizeof(struct hoth_key_provisioning_request_header)) + +struct hoth_key_provisioning_store_secrets_request { + struct hoth_key_provisioning_request_header hdr; + uint8_t secrets[HOTH_KEY_PROVISIONING_MAX_SECRETS_SIZE]; +} __attribute__((packed)); + +#define HOTH_KEY_PROVISIONING_MLDSA44_PUBLIC_KEY_BYTES 1312 + +struct hoth_key_provisioning_load_key_args { + uint16_t offset; + uint16_t size; +} __attribute__((packed)); + +#define HOTH_KEY_PROVISIONING_LOAD_KEY_CHUNK_MAX_SIZE \ + (LIBHOTH_MAILBOX_SIZE - sizeof(struct hoth_host_request) - \ + sizeof(struct hoth_key_provisioning_request_header) - \ + sizeof(struct hoth_key_provisioning_load_key_args)) + +struct hoth_key_provisioning_load_key_request { + struct hoth_key_provisioning_request_header hdr; + struct hoth_key_provisioning_load_key_args args; + uint8_t data[HOTH_KEY_PROVISIONING_LOAD_KEY_CHUNK_MAX_SIZE]; +} __attribute__((packed)); + +#define PROVISIONING_LOG_MAX_SIZE 6144 + +#define PROVISIONING_LOG_CHUNK_MAX_SIZE 1008 + +#define PROVISIONING_CERT_MAX_SIZE 240 + +struct hoth_provisioning_log_header { + uint8_t version; // 1 + uint8_t reserved; + uint16_t size; // size of the log content + uint32_t checksum; // CRC32 checksum of |size| bytes of log data +} __attribute__((packed)); + +struct hoth_provisioning_log_request { + uint8_t version; // 1 + uint8_t operation; // enum provisioning_log_op + uint16_t reserved; + uint16_t offset; // Chunked read/write offset + uint16_t size; // Chunked read/write size + uint32_t checksum; // CRC32 checksum of the full provisioning log +} __attribute__((packed)); + +struct hoth_provisioning_log { + struct hoth_provisioning_log_header hdr; + uint8_t data[PROVISIONING_LOG_CHUNK_MAX_SIZE]; +} __attribute__((packed)); + +#define PROVISIONING_LOG_WRITE_CHUNK_MAX_SIZE \ + (LIBHOTH_MAILBOX_SIZE - sizeof(struct hoth_host_request) - \ + sizeof(struct hoth_provisioning_log_request)) + +struct hoth_provisioning_log_write_request { + struct hoth_provisioning_log_request req; + uint8_t data[PROVISIONING_LOG_WRITE_CHUNK_MAX_SIZE]; +} __attribute__((packed)); + +enum provisioning_log_op { + PROVISIONING_LOG_READ = 0, + PROVISIONING_LOG_WRITE = 1, + PROVISIONING_LOG_COMMIT = 2, + PROVISIONING_LOG_VALIDATE_AND_SIGN = 3, +}; + +// This is a standalone CRC32 that matches Titan Firmware. +// A table-free bit-level implementation is okay since there are no +// performance constraints in its use by the provisioning commands. +uint32_t libhoth_provisioning_crc32(uint32_t initial_value, const uint8_t* buf, + size_t size); + +// Reads the full provisioning log into |buf|, which must be able to hold +// |buf_size| bytes. On success |out_size| receives the number of bytes read. +// +// Returns 0 on success, 1 if the device reported an unexpected response size +// or the log is larger than |buf_size|, and a legacy libhoth status code +// (see libhoth_hostcmd_exec) on a transport or device failure. +int libhoth_provisioning_log_read(struct libhoth_device* dev, uint8_t* buf, + size_t buf_size, size_t* out_size); + +// Asks the device to validate and sign the personalization blob in |blob|, +// returning the resulting certificate in |cert|. On success |out_cert_size| +// receives the number of bytes written to |cert|. +// +// Returns 0 on success, and a legacy libhoth status code (see +// libhoth_hostcmd_exec) on a transport or device failure. Returns -1 if the +// device returned more than |cert_capacity| bytes. +int libhoth_provisioning_log_validate_and_sign(struct libhoth_device* dev, + const uint8_t* blob, + size_t blob_size, uint8_t* cert, + size_t cert_capacity, + size_t* out_cert_size); + +// Writes |size| bytes of provisioning log from |data| to the device. The log +// is not durable until libhoth_provisioning_log_commit() is called. +libhoth_error libhoth_provisioning_log_write(struct libhoth_device* dev, + const uint8_t* data, size_t size); + +// Commits a previously written provisioning log. |data| and |size| describe +// the same buffer passed to libhoth_provisioning_log_write() and are used to +// compute the checksum the device verifies. +libhoth_error libhoth_provisioning_log_commit(struct libhoth_device* dev, + const uint8_t* data, size_t size); + +// Stores |size| bytes of secrets that were encrypted with the provisioning +// encryption key. +libhoth_error libhoth_key_provisioning_store_secrets(struct libhoth_device* dev, + const uint8_t* secrets, + size_t size); + +// Loads an ML-DSA-44 public key onto the device. |size| must be +// HOTH_KEY_PROVISIONING_MLDSA44_PUBLIC_KEY_BYTES. +libhoth_error libhoth_key_provisioning_load_mldsa_key( + struct libhoth_device* dev, const uint8_t* key, size_t size); + +#ifdef __cplusplus +} +#endif + +#endif // LIBHOTH_PROTOCOL_PROVISIONING_H_ From 7bdbb110acd2d2dfc38d07c9df752be5971ca2b8 Mon Sep 17 00:00:00 2001 From: Willy Zhang Date: Tue, 22 Sep 2026 02:36:21 +0000 Subject: [PATCH 4/4] [htool] Remove SecurityV2 check from provisioning log commands --- examples/BUILD | 2 -- examples/htool_provisioning.c | 15 --------------- examples/htool_provisioning_test.cc | 8 -------- 3 files changed, 25 deletions(-) diff --git a/examples/BUILD b/examples/BUILD index 4b4ca1e..3e45369 100644 --- a/examples/BUILD +++ b/examples/BUILD @@ -70,7 +70,6 @@ cc_library( ":htool_cmd", ":htool_header", ":htool_macros", - ":htool_security_version", "//protocol:provisioning", "//transports:libhoth_device", ], @@ -293,7 +292,6 @@ cc_test( ":host_commands", ":htool_cmd", ":htool_provisioning", - ":htool_security_version", "//examples/test:test_util", "//protocol:host_cmd", "//protocol/test:libhoth_device_mock", diff --git a/examples/htool_provisioning.c b/examples/htool_provisioning.c index cdc427f..a325e1c 100644 --- a/examples/htool_provisioning.c +++ b/examples/htool_provisioning.c @@ -11,7 +11,6 @@ #include "htool.h" #include "htool_cmd.h" -#include "htool_security_version.h" #include "protocol/provisioning.h" #include "transports/libhoth_device.h" @@ -40,13 +39,6 @@ int htool_get_provisioning_log(const struct htool_invocation* inv) { goto cleanup; } - // SECURITY_V3 not supported yet. - if (htool_get_security_version(dev) != LIBHOTH_SECURITY_V2) { - status = -1; - fprintf(stderr, "SECURITY_V3 is not supported yet\n"); - goto cleanup; - } - status = libhoth_provisioning_log_read( dev, provisioning_log_data, sizeof(provisioning_log_data), &bytes_read); if (status != 0) { @@ -122,13 +114,6 @@ int htool_validate_and_sign(const struct htool_invocation* inv) { } } - // SECURITY_V3 not supported yet. - if (htool_get_security_version(dev) != LIBHOTH_SECURITY_V2) { - status = -1; - fprintf(stderr, "SECURITY_V3 is not supported yet.\n"); - goto cleanup; - } - memset(cert, 0, sizeof(cert)); status = libhoth_provisioning_log_validate_and_sign( dev, perso_blob_data, perso_blob_size, cert, sizeof(cert), &cert_size); diff --git a/examples/htool_provisioning_test.cc b/examples/htool_provisioning_test.cc index e3a66ef..6accea1 100644 --- a/examples/htool_provisioning_test.cc +++ b/examples/htool_provisioning_test.cc @@ -29,7 +29,6 @@ #include "examples/test/test_util.h" #include "host_commands.h" -#include "htool_security_version.h" #include "protocol/host_cmd.h" #include "protocol/test/libhoth_device_mock.h" #include "transports/libhoth_device.h" @@ -53,13 +52,6 @@ extern "C" int htool_get_param_string(const struct htool_invocation* inv, struct libhoth_device* mock_dev = nullptr; struct libhoth_device* htool_libhoth_device() { return mock_dev; } -// Mocking htool_get_security_version -static libhoth_security_version mock_security_version = LIBHOTH_SECURITY_V2; -libhoth_security_version htool_get_security_version( - struct libhoth_device* dev) { - return mock_security_version; -} - class HtoolProvisioningTest : public LibHothTest { protected: void SetUp() override {