From af877b0287418684608d05ce12cbbd0d81e13a89 Mon Sep 17 00:00:00 2001 From: Willy Zhang Date: Tue, 15 Sep 2026 07:23:13 +0000 Subject: [PATCH 1/3] [htool] Add SecurityV2 attestation key management commands Add subcommands under 'htool security' for managing attestation keys on devices supporting SecurityV2: - unload_attestation_key (Major 1, Minor 3) - gen_attestation_key_v1 (Major 1, Minor 15) - gen_attestation_key_v2 (Major 1, Minor 18) - load_attestation_key (Major 1, Minor 1) - load_attestation_key_from_csr_v1 (Major 1, Minor 16) - provision_attestation_key (one-shot unload + gen_v1 + load_from_csr_v1) Include unit tests covering success and error paths for each command. --- examples/host_commands.h | 30 ++ examples/htool.c | 70 +++ examples/htool_security_certificates.c | 479 +++++++++++++++++++ examples/htool_security_certificates.h | 18 + examples/htool_security_certificates_test.cc | 447 +++++++++++++++++ 5 files changed, 1044 insertions(+) diff --git a/examples/host_commands.h b/examples/host_commands.h index 9f65431..dabeb2d 100644 --- a/examples/host_commands.h +++ b/examples/host_commands.h @@ -169,6 +169,36 @@ struct hoth_security_v2_parameter { #define HOTH_PRV_CMD_HOTH_SECURITY_V2_GET_SIGNED_ATTESTATION_PUB_CERT_MINOR_COMMAND \ 25 +/** + * The command to load a signed attestation key certificate and wrapped key onto + * the device. + */ +#define HOTH_PRV_CMD_HOTH_SECURITY_V2_LOAD_ATTESTATION_KEY_MINOR_COMMAND 1 + +/** + * The command to unload the attestation key from the device. + */ +#define HOTH_PRV_CMD_HOTH_SECURITY_V2_UNLOAD_ATTESTATION_KEY_MINOR_COMMAND 3 + +/** + * The command to generate an attestation key using CSR v1 on the device. + */ +#define HOTH_PRV_CMD_HOTH_SECURITY_V2_GEN_ATTESTATION_KEY_USING_CSR_V1_MINOR_COMMAND \ + 15 + +/** + * The command to load a self-signed attestation key from CSR v1 on the device + * (requires firmware built with self-signed attestation key support). + */ +#define HOTH_PRV_CMD_HOTH_SECURITY_V2_LOAD_ATTESTATION_KEY_FROM_CSR_V1_MINOR_COMMAND \ + 16 + +/** + * The command to generate a versioned attestation key on the device. + */ +#define HOTH_PRV_CMD_HOTH_SECURITY_V2_GEN_VERSIONED_ATTESTATION_KEY_MINOR_COMMAND \ + 18 + /** * The command to get token information from the device. */ diff --git a/examples/htool.c b/examples/htool.c index 5d5d0c9..1aa4036 100644 --- a/examples/htool.c +++ b/examples/htool.c @@ -2113,6 +2113,76 @@ static const struct htool_cmd CMDS[] = { .desc = "The Signed Attestation Public Certificate"}, {}}, }, + { + .verbs = (const char*[]){"security", "unload_attestation_key", NULL}, + .desc = "Unload the Attestation Key from the device", + .func = htool_unload_attestation_key, + .params = (const struct htool_param[]){{}}, + }, + { + .verbs = (const char*[]){"security", "gen_attestation_key_v1", NULL}, + .desc = "Generate an Attestation Key using CSR v1", + .func = htool_gen_attestation_key_v1, + .params = + (const struct htool_param[]){ + {HTOOL_FLAG_VALUE, .name = "csr_output", .default_value = "", + .desc = "The Attestation Key CSR v1 output file"}, + {HTOOL_FLAG_VALUE, .name = "wrapped_key_output", + .default_value = "", + .desc = "The Wrapped Attestation Key output file"}, + {}}, + }, + { + .verbs = (const char*[]){"security", "gen_attestation_key_v2", NULL}, + .desc = "Generate an Attestation Key using CSR v2", + .func = htool_gen_attestation_key_v2, + .params = + (const struct htool_param[]){ + {HTOOL_FLAG_VALUE, .name = "fw_major_version", + .default_value = "0", + .desc = "The firmware major version to bind the Attestation " + "Key to"}, + {HTOOL_FLAG_VALUE, .name = "csr_output", .default_value = "", + .desc = "The Attestation Key CSR v2 output file"}, + {HTOOL_FLAG_VALUE, .name = "wrapped_key_output", + .default_value = "", + .desc = "The Wrapped Attestation Key output file"}, + {}}, + }, + { + .verbs = (const char*[]){"security", "load_attestation_key", NULL}, + .desc = "Load a CA-signed Attestation Key certificate and wrapped key", + .func = htool_load_attestation_key, + .params = + (const struct htool_param[]){ + {HTOOL_FLAG_VALUE, .name = "wrapped_key", .default_value = "", + .desc = "The Wrapped Attestation Key input file"}, + {HTOOL_FLAG_VALUE, .name = "cert", .default_value = "", + .desc = "The CA-signed Attestation Key Certificate input " + "file"}, + {}}, + }, + { + .verbs = (const char*[]){"security", "load_attestation_key_from_csr_v1", + NULL}, + .desc = "Load a self-signed Attestation Key from CSR v1 (requires " + "firmware with self-signed attestation key support)", + .func = htool_load_attestation_key_from_csr_v1, + .params = + (const struct htool_param[]){ + {HTOOL_FLAG_VALUE, .name = "wrapped_key", .default_value = "", + .desc = "The Wrapped Attestation Key input file"}, + {HTOOL_FLAG_VALUE, .name = "csr", .default_value = "", + .desc = "The Attestation Key CSR v1 input file"}, + {}}, + }, + { + .verbs = (const char*[]){"security", "provision_attestation_key", NULL}, + .desc = "Provision a self-signed Attestation Key (unload, gen_v1, " + "load_v1; requires firmware with self-signed support)", + .func = htool_provision_attestation_key, + .params = (const struct htool_param[]){{}}, + }, { .verbs = (const char*[]){"security", "attestation", NULL}, .desc = diff --git a/examples/htool_security_certificates.c b/examples/htool_security_certificates.c index c673ab7..0c220a0 100644 --- a/examples/htool_security_certificates.c +++ b/examples/htool_security_certificates.c @@ -287,3 +287,482 @@ int htool_get_device_id_cert(const struct htool_invocation* inv) { } return status; } + +// SecurityV2 parameters are padded to 4-byte alignment on the wire. Ensure all +// fixed-size attestation key buffer sizes are multiples of 4 so buffer sizing +// macros remain exact. +_Static_assert(ATTESTATION_KEY_CSR_V1_SIZE % 4 == 0, + "ATTESTATION_KEY_CSR_V1_SIZE must be 4-byte aligned"); +_Static_assert(ATTESTATION_KEY_CSR_V2_SIZE % 4 == 0, + "ATTESTATION_KEY_CSR_V2_SIZE must be 4-byte aligned"); +_Static_assert(WRAPPED_ATTESTATION_KEY_SIZE % 4 == 0, + "WRAPPED_ATTESTATION_KEY_SIZE must be 4-byte aligned"); +_Static_assert(ATTESTATION_CERT_SIZE % 4 == 0, + "ATTESTATION_CERT_SIZE must be 4-byte aligned"); + +static int read_exact_file(const char* filename, uint8_t* buf, size_t size) { + FILE* fp = fopen(filename, "rb"); + if (fp == NULL) { + printf("Error: %s, when attempting to open file: %s\n", strerror(errno), + filename); + return -1; + } + size_t bytes_read = fread(buf, 1, size, fp); + int extra = fgetc(fp); + int stream_err = ferror(fp); + fclose(fp); + if (bytes_read != size || extra != EOF || stream_err != 0) { + printf("Error: File %s must be exactly %zu bytes\n", filename, size); + return -1; + } + return 0; +} + +static int write_exact_file(const char* filename, const uint8_t* buf, + size_t size) { + FILE* fp = fopen(filename, "wb"); + if (fp == NULL) { + printf("Error: %s, when attempting to open file: %s\n", strerror(errno), + filename); + return -1; + } + size_t bytes_written = fwrite(buf, 1, size, fp); + int stream_err = ferror(fp); + fclose(fp); + if (bytes_written != size || stream_err != 0) { + printf("Error: Failed to write %zu bytes to %s\n", size, filename); + return -1; + } + return 0; +} + +static int exec_unload_attestation_key(struct libhoth_device* dev) { + uint8_t request_storage_hdr[HOTH_SECURITY_V2_REQUEST_SIZE(0)] = {}; + uint8_t response_storage_hdr[HOTH_SECURITY_V2_RESPONSE_SIZE(0)] = {}; + + int hoth_status = htool_exec_security_v2_cmd( + dev, + /*major=*/HOTH_PRV_CMD_HOTH_SECURITY_V2_GET_CERTIFICATES_MAJOR_COMMAND, + /*minor=*/ + HOTH_PRV_CMD_HOTH_SECURITY_V2_UNLOAD_ATTESTATION_KEY_MINOR_COMMAND, + /*base_command=*/HOTH_BASE_CMD(HOTH_PRV_CMD_HOTH_SECURITY_V2), + SECURITY_V2_BUFFER_PARAM(request_storage_hdr), NULL, 0, + SECURITY_V2_BUFFER_PARAM(response_storage_hdr), NULL, 0); + if (hoth_status != 0) { + printf( + "Unexpected Error: Returned status %d, while trying to send command to " + "unload the Attestation Key\n", + hoth_status); + } + return hoth_status; +} + +static int exec_gen_attestation_key_v1( + struct libhoth_device* dev, uint8_t csr[ATTESTATION_KEY_CSR_V1_SIZE], + uint8_t wrapped_key[WRAPPED_ATTESTATION_KEY_SIZE]) { + uint8_t request_storage_hdr[HOTH_SECURITY_V2_REQUEST_SIZE(0)] = {}; + uint8_t response_storage_hdr[HOTH_SECURITY_V2_RESPONSE_SIZE(2) + + ATTESTATION_KEY_CSR_V1_SIZE + + WRAPPED_ATTESTATION_KEY_SIZE] = {}; + struct security_v2_param response_params[] = { + { + .data = csr, + .size = ATTESTATION_KEY_CSR_V1_SIZE, + }, + { + .data = wrapped_key, + .size = WRAPPED_ATTESTATION_KEY_SIZE, + }, + }; + int hoth_status = htool_exec_security_v2_cmd( + dev, + /*major=*/HOTH_PRV_CMD_HOTH_SECURITY_V2_GET_CERTIFICATES_MAJOR_COMMAND, + /*minor=*/ + HOTH_PRV_CMD_HOTH_SECURITY_V2_GEN_ATTESTATION_KEY_USING_CSR_V1_MINOR_COMMAND, + /*base_command=*/HOTH_BASE_CMD(HOTH_PRV_CMD_HOTH_SECURITY_V2), + SECURITY_V2_BUFFER_PARAM(request_storage_hdr), NULL, 0, + SECURITY_V2_BUFFER_PARAM(response_storage_hdr), response_params, + ARRAY_SIZE(response_params)); + if (hoth_status != 0) { + printf( + "Unexpected Error: Returned status %d, while trying to send command to " + "generate the Attestation Key\n", + hoth_status); + } + return hoth_status; +} + +static int exec_gen_attestation_key_v2( + struct libhoth_device* dev, uint32_t fw_major_version, + uint8_t csr[ATTESTATION_KEY_CSR_V2_SIZE], + uint8_t wrapped_key[WRAPPED_ATTESTATION_KEY_SIZE]) { + uint32_t csr_version = 2; + struct security_v2_param request_params[] = { + { + .data = &csr_version, + .size = sizeof(csr_version), + }, + { + .data = &fw_major_version, + .size = sizeof(fw_major_version), + }, + }; + uint8_t request_storage_hdr[HOTH_SECURITY_V2_REQUEST_SIZE(2) + + sizeof(csr_version) + sizeof(fw_major_version)] = + {}; + uint8_t response_storage_hdr[HOTH_SECURITY_V2_RESPONSE_SIZE(2) + + ATTESTATION_KEY_CSR_V2_SIZE + + WRAPPED_ATTESTATION_KEY_SIZE] = {}; + struct security_v2_param response_params[] = { + { + .data = csr, + .size = ATTESTATION_KEY_CSR_V2_SIZE, + }, + { + .data = wrapped_key, + .size = WRAPPED_ATTESTATION_KEY_SIZE, + }, + }; + int hoth_status = htool_exec_security_v2_cmd( + dev, + /*major=*/HOTH_PRV_CMD_HOTH_SECURITY_V2_GET_CERTIFICATES_MAJOR_COMMAND, + /*minor=*/ + HOTH_PRV_CMD_HOTH_SECURITY_V2_GEN_VERSIONED_ATTESTATION_KEY_MINOR_COMMAND, + /*base_command=*/HOTH_BASE_CMD(HOTH_PRV_CMD_HOTH_SECURITY_V2), + SECURITY_V2_BUFFER_PARAM(request_storage_hdr), request_params, + ARRAY_SIZE(request_params), + SECURITY_V2_BUFFER_PARAM(response_storage_hdr), response_params, + ARRAY_SIZE(response_params)); + if (hoth_status != 0) { + printf( + "Unexpected Error: Returned status %d, while trying to send command to " + "generate the Attestation Key\n", + hoth_status); + } + return hoth_status; +} + +static int exec_load_attestation_key( + struct libhoth_device* dev, + uint8_t wrapped_key[WRAPPED_ATTESTATION_KEY_SIZE], + uint8_t cert[ATTESTATION_CERT_SIZE]) { + uint8_t request_storage_hdr[HOTH_SECURITY_V2_REQUEST_SIZE(2) + + WRAPPED_ATTESTATION_KEY_SIZE + + ATTESTATION_CERT_SIZE] = {}; + uint8_t response_storage_hdr[HOTH_SECURITY_V2_RESPONSE_SIZE(0)] = {}; + struct security_v2_param request_params[] = { + { + .data = wrapped_key, + .size = WRAPPED_ATTESTATION_KEY_SIZE, + }, + { + .data = cert, + .size = ATTESTATION_CERT_SIZE, + }, + }; + int hoth_status = htool_exec_security_v2_cmd( + dev, + /*major=*/HOTH_PRV_CMD_HOTH_SECURITY_V2_GET_CERTIFICATES_MAJOR_COMMAND, + /*minor=*/ + HOTH_PRV_CMD_HOTH_SECURITY_V2_LOAD_ATTESTATION_KEY_MINOR_COMMAND, + /*base_command=*/HOTH_BASE_CMD(HOTH_PRV_CMD_HOTH_SECURITY_V2), + SECURITY_V2_BUFFER_PARAM(request_storage_hdr), request_params, + ARRAY_SIZE(request_params), + SECURITY_V2_BUFFER_PARAM(response_storage_hdr), NULL, 0); + if (hoth_status != 0) { + printf( + "Unexpected Error: Returned status %d, while trying to send command to " + "load the Attestation Key\n", + hoth_status); + } + return hoth_status; +} + +static int exec_load_attestation_key_from_csr_v1( + struct libhoth_device* dev, + uint8_t wrapped_key[WRAPPED_ATTESTATION_KEY_SIZE], + uint8_t csr[ATTESTATION_KEY_CSR_V1_SIZE]) { + uint8_t request_storage_hdr[HOTH_SECURITY_V2_REQUEST_SIZE(2) + + WRAPPED_ATTESTATION_KEY_SIZE + + ATTESTATION_KEY_CSR_V1_SIZE] = {}; + uint8_t response_storage_hdr[HOTH_SECURITY_V2_RESPONSE_SIZE(0)] = {}; + struct security_v2_param request_params[] = { + { + .data = wrapped_key, + .size = WRAPPED_ATTESTATION_KEY_SIZE, + }, + { + .data = csr, + .size = ATTESTATION_KEY_CSR_V1_SIZE, + }, + }; + int hoth_status = htool_exec_security_v2_cmd( + dev, + /*major=*/HOTH_PRV_CMD_HOTH_SECURITY_V2_GET_CERTIFICATES_MAJOR_COMMAND, + /*minor=*/ + HOTH_PRV_CMD_HOTH_SECURITY_V2_LOAD_ATTESTATION_KEY_FROM_CSR_V1_MINOR_COMMAND, + /*base_command=*/HOTH_BASE_CMD(HOTH_PRV_CMD_HOTH_SECURITY_V2), + SECURITY_V2_BUFFER_PARAM(request_storage_hdr), request_params, + ARRAY_SIZE(request_params), + SECURITY_V2_BUFFER_PARAM(response_storage_hdr), NULL, 0); + if (hoth_status != 0) { + printf( + "Unexpected Error: Returned status %d, while trying to send command to " + "load the Attestation Key from CSR\n", + hoth_status); + } + return hoth_status; +} + +int htool_unload_attestation_key(const struct htool_invocation* inv) { + (void)inv; + struct libhoth_device* dev = htool_libhoth_device(); + if (!dev) { + return -1; + } + + libhoth_security_version sv = htool_get_security_version(dev); + switch (sv) { + case LIBHOTH_SECURITY_V2: + return exec_unload_attestation_key(dev); + // SECURITY_V3 not supported yet. + default: + printf("SECURITY_V3 not supported yet\n"); + return -1; + } +} + +int htool_gen_attestation_key_v1(const struct htool_invocation* inv) { + struct libhoth_device* dev = htool_libhoth_device(); + if (!dev) { + return -1; + } + + const char* csr_output_file; + if (htool_get_param_string(inv, "csr_output", &csr_output_file) != 0) { + return -1; + } + + const char* wrapped_key_output_file; + if (htool_get_param_string(inv, "wrapped_key_output", + &wrapped_key_output_file) != 0) { + return -1; + } + + bool is_csr_output_provided = strlen(csr_output_file) > 0; + bool is_wrapped_key_output_provided = strlen(wrapped_key_output_file) > 0; + + if (!is_csr_output_provided && !is_wrapped_key_output_provided) { + printf( + "Error: No valid csr_output provided and no valid wrapped_key_output " + "provided.\n"); + return -1; + } + + uint8_t csr[ATTESTATION_KEY_CSR_V1_SIZE] = {0}; + uint8_t wrapped_key[WRAPPED_ATTESTATION_KEY_SIZE] = {0}; + libhoth_security_version sv = htool_get_security_version(dev); + switch (sv) { + case LIBHOTH_SECURITY_V2: { + int hoth_status = exec_gen_attestation_key_v1(dev, csr, wrapped_key); + if (hoth_status != 0) { + return hoth_status; + } + if (is_csr_output_provided && + write_exact_file(csr_output_file, csr, sizeof(csr)) != 0) { + return -1; + } + if (is_wrapped_key_output_provided && + write_exact_file(wrapped_key_output_file, wrapped_key, + sizeof(wrapped_key)) != 0) { + return -1; + } + return 0; + } + // SECURITY_V3 not supported yet. + default: + printf("SECURITY_V3 not supported yet\n"); + return -1; + } +} + +int htool_gen_attestation_key_v2(const struct htool_invocation* inv) { + struct libhoth_device* dev = htool_libhoth_device(); + if (!dev) { + return -1; + } + + uint32_t fw_major_version; + if (htool_get_param_u32(inv, "fw_major_version", &fw_major_version) != 0) { + return -1; + } + + const char* csr_output_file; + if (htool_get_param_string(inv, "csr_output", &csr_output_file) != 0) { + return -1; + } + + const char* wrapped_key_output_file; + if (htool_get_param_string(inv, "wrapped_key_output", + &wrapped_key_output_file) != 0) { + return -1; + } + + bool is_csr_output_provided = strlen(csr_output_file) > 0; + bool is_wrapped_key_output_provided = strlen(wrapped_key_output_file) > 0; + + if (!is_csr_output_provided && !is_wrapped_key_output_provided) { + printf( + "Error: No valid csr_output provided and no valid wrapped_key_output " + "provided.\n"); + return -1; + } + + uint8_t csr[ATTESTATION_KEY_CSR_V2_SIZE] = {0}; + uint8_t wrapped_key[WRAPPED_ATTESTATION_KEY_SIZE] = {0}; + libhoth_security_version sv = htool_get_security_version(dev); + switch (sv) { + case LIBHOTH_SECURITY_V2: { + int hoth_status = + exec_gen_attestation_key_v2(dev, fw_major_version, csr, wrapped_key); + if (hoth_status != 0) { + return hoth_status; + } + if (is_csr_output_provided && + write_exact_file(csr_output_file, csr, sizeof(csr)) != 0) { + return -1; + } + if (is_wrapped_key_output_provided && + write_exact_file(wrapped_key_output_file, wrapped_key, + sizeof(wrapped_key)) != 0) { + return -1; + } + return 0; + } + // SECURITY_V3 not supported yet. + default: + printf("SECURITY_V3 not supported yet\n"); + return -1; + } +} + +int htool_load_attestation_key(const struct htool_invocation* inv) { + struct libhoth_device* dev = htool_libhoth_device(); + if (!dev) { + return -1; + } + + const char* wrapped_key_file; + if (htool_get_param_string(inv, "wrapped_key", &wrapped_key_file) != 0) { + return -1; + } + + const char* cert_file; + if (htool_get_param_string(inv, "cert", &cert_file) != 0) { + return -1; + } + + if (strlen(wrapped_key_file) == 0 || strlen(cert_file) == 0) { + printf( + "Error: Both wrapped_key and cert files must be provided to load the " + "Attestation Key.\n"); + return -1; + } + + uint8_t wrapped_key[WRAPPED_ATTESTATION_KEY_SIZE] = {0}; + if (read_exact_file(wrapped_key_file, wrapped_key, sizeof(wrapped_key)) != + 0) { + return -1; + } + + uint8_t cert[ATTESTATION_CERT_SIZE] = {0}; + if (read_exact_file(cert_file, cert, sizeof(cert)) != 0) { + return -1; + } + + libhoth_security_version sv = htool_get_security_version(dev); + switch (sv) { + case LIBHOTH_SECURITY_V2: + return exec_load_attestation_key(dev, wrapped_key, cert); + // SECURITY_V3 not supported yet. + default: + printf("SECURITY_V3 not supported yet\n"); + return -1; + } +} + +int htool_load_attestation_key_from_csr_v1(const struct htool_invocation* inv) { + struct libhoth_device* dev = htool_libhoth_device(); + if (!dev) { + return -1; + } + + const char* wrapped_key_file; + if (htool_get_param_string(inv, "wrapped_key", &wrapped_key_file) != 0) { + return -1; + } + + const char* csr_file; + if (htool_get_param_string(inv, "csr", &csr_file) != 0) { + return -1; + } + + if (strlen(wrapped_key_file) == 0 || strlen(csr_file) == 0) { + printf( + "Error: Both wrapped_key and csr files must be provided to load the " + "Attestation Key.\n"); + return -1; + } + + uint8_t wrapped_key[WRAPPED_ATTESTATION_KEY_SIZE] = {0}; + if (read_exact_file(wrapped_key_file, wrapped_key, sizeof(wrapped_key)) != + 0) { + return -1; + } + + uint8_t csr[ATTESTATION_KEY_CSR_V1_SIZE] = {0}; + if (read_exact_file(csr_file, csr, sizeof(csr)) != 0) { + return -1; + } + + libhoth_security_version sv = htool_get_security_version(dev); + switch (sv) { + case LIBHOTH_SECURITY_V2: + return exec_load_attestation_key_from_csr_v1(dev, wrapped_key, csr); + // SECURITY_V3 not supported yet. + default: + printf("SECURITY_V3 not supported yet\n"); + return -1; + } +} + +int htool_provision_attestation_key(const struct htool_invocation* inv) { + (void)inv; + struct libhoth_device* dev = htool_libhoth_device(); + if (!dev) { + return -1; + } + + libhoth_security_version sv = htool_get_security_version(dev); + switch (sv) { + case LIBHOTH_SECURITY_V2: { + int status = exec_unload_attestation_key(dev); + if (status != 0) { + return status; + } + + uint8_t csr[ATTESTATION_KEY_CSR_V1_SIZE] = {0}; + uint8_t wrapped_key[WRAPPED_ATTESTATION_KEY_SIZE] = {0}; + status = exec_gen_attestation_key_v1(dev, csr, wrapped_key); + if (status != 0) { + return status; + } + + return exec_load_attestation_key_from_csr_v1(dev, wrapped_key, csr); + } + // SECURITY_V3 not supported yet. + default: + printf("SECURITY_V3 not supported yet\n"); + return -1; + } +} diff --git a/examples/htool_security_certificates.h b/examples/htool_security_certificates.h index 67c52f2..3c7934a 100644 --- a/examples/htool_security_certificates.h +++ b/examples/htool_security_certificates.h @@ -29,6 +29,12 @@ struct security_v2_param; #define DEVICE_ENDORSEMENT_CERT_SIZE 160 +#define ATTESTATION_KEY_CSR_V1_SIZE 256 + +#define ATTESTATION_KEY_CSR_V2_SIZE 192 + +#define WRAPPED_ATTESTATION_KEY_SIZE 88 + // Gets the Attestation Public Certificate int htool_get_attestation_pub_cert(const struct htool_invocation* inv); // Gets the Signed Attestation Public Certificate @@ -37,6 +43,18 @@ int htool_get_signed_attestation_pub_cert(const struct htool_invocation* inv); int htool_get_alias_key_cert(const struct htool_invocation* inv); // Gets the Device ID Certificates int htool_get_device_id_cert(const struct htool_invocation* inv); +// Unloads the Attestation Key +int htool_unload_attestation_key(const struct htool_invocation* inv); +// Generates an Attestation Key using CSR v1 +int htool_gen_attestation_key_v1(const struct htool_invocation* inv); +// Generates an Attestation Key using CSR v2 +int htool_gen_attestation_key_v2(const struct htool_invocation* inv); +// Loads an Attestation Key from a CA-signed certificate +int htool_load_attestation_key(const struct htool_invocation* inv); +// Loads a self-signed Attestation Key from CSR v1 +int htool_load_attestation_key_from_csr_v1(const struct htool_invocation* inv); +// Provisions a self-signed Attestation Key +int htool_provision_attestation_key(const struct htool_invocation* inv); #ifdef __cplusplus } diff --git a/examples/htool_security_certificates_test.cc b/examples/htool_security_certificates_test.cc index 82ca395..6b92db2 100644 --- a/examples/htool_security_certificates_test.cc +++ b/examples/htool_security_certificates_test.cc @@ -604,3 +604,450 @@ TEST_F(HtoolSecurityCertificatesTest, GetAttestationPubCertFailCommand) { .WillOnce(Return(-1)); EXPECT_EQ(htool_get_attestation_pub_cert(&inv), -1); } + +TEST_F(HtoolSecurityCertificatesTest, UnloadAttestationKeySuccess) { + struct htool_invocation inv{}; + EXPECT_CALL(security_v2_mock_, htool_exec_security_v2_cmd) + .With(IsSecurityV2Command( + HOTH_PRV_CMD_HOTH_SECURITY_V2_GET_CERTIFICATES_MAJOR_COMMAND, + HOTH_PRV_CMD_HOTH_SECURITY_V2_UNLOAD_ATTESTATION_KEY_MINOR_COMMAND, + HOTH_BASE_CMD(HOTH_PRV_CMD_HOTH_SECURITY_V2), + HOTH_SECURITY_V2_REQUEST_SIZE(0), HOTH_SECURITY_V2_RESPONSE_SIZE(0))) + .WillOnce(Return(0)); + + EXPECT_EQ(htool_unload_attestation_key(&inv), 0); +} + +TEST_F(HtoolSecurityCertificatesTest, UnloadAttestationKeyFailure) { + struct htool_invocation inv{}; + EXPECT_CALL(security_v2_mock_, + htool_exec_security_v2_cmd(_, _, _, _, _, _, _, _, _, _)) + .WillOnce(Return(-1)); + + EXPECT_EQ(htool_unload_attestation_key(&inv), -1); +} + +TEST_F(HtoolSecurityCertificatesTest, GenAttestationKeyV1Success) { + struct htool_invocation inv{}; + std::string csr_output_file = tmp_dir_path_ + "/csr_v1.bin"; + std::string wrapped_key_output_file = tmp_dir_path_ + "/wrapped_key.bin"; + + EXPECT_CALL(invocation_mock_, GetParamString("csr_output", _)) + .WillOnce(DoAll(SetArgPointee<1>(csr_output_file.c_str()), Return(0))); + EXPECT_CALL(invocation_mock_, GetParamString("wrapped_key_output", _)) + .WillOnce( + DoAll(SetArgPointee<1>(wrapped_key_output_file.c_str()), Return(0))); + + uint8_t expected_csr[ATTESTATION_KEY_CSR_V1_SIZE] = {}; + for (size_t i = 0; i < sizeof(expected_csr); ++i) { + expected_csr[i] = static_cast(i & 0xff); + } + uint8_t expected_wrapped_key[WRAPPED_ATTESTATION_KEY_SIZE] = {}; + for (size_t i = 0; i < sizeof(expected_wrapped_key); ++i) { + expected_wrapped_key[i] = static_cast((i + 0x55) & 0xff); + } + + EXPECT_CALL(security_v2_mock_, htool_exec_security_v2_cmd) + .With(IsSecurityV2Command( + HOTH_PRV_CMD_HOTH_SECURITY_V2_GET_CERTIFICATES_MAJOR_COMMAND, + HOTH_PRV_CMD_HOTH_SECURITY_V2_GEN_ATTESTATION_KEY_USING_CSR_V1_MINOR_COMMAND, + HOTH_BASE_CMD(HOTH_PRV_CMD_HOTH_SECURITY_V2), + HOTH_SECURITY_V2_REQUEST_SIZE(0), + HOTH_SECURITY_V2_RESPONSE_SIZE(2) + sizeof(expected_csr) + + sizeof(expected_wrapped_key))) + .WillOnce([&](struct libhoth_device* dev, uint8_t major, uint8_t minor, + uint16_t base_command, + struct security_v2_buffer* request_buffer, + const struct security_v2_param* request_params, + uint16_t request_param_count, + struct security_v2_buffer* response_buffer, + struct security_v2_param* response_params, + uint16_t response_param_count) { + EXPECT_EQ(response_param_count, 2); + EXPECT_EQ(response_params[0].size, sizeof(expected_csr)); + EXPECT_EQ(response_params[1].size, sizeof(expected_wrapped_key)); + memcpy(response_params[0].data, expected_csr, sizeof(expected_csr)); + memcpy(response_params[1].data, expected_wrapped_key, + sizeof(expected_wrapped_key)); + return 0; + }); + + ASSERT_EQ(htool_gen_attestation_key_v1(&inv), 0); + + FILE* fp_csr = fopen(csr_output_file.c_str(), "rb"); + ASSERT_NE(fp_csr, nullptr); + uint8_t csr_contents[ATTESTATION_KEY_CSR_V1_SIZE]; + ASSERT_EQ(fread(csr_contents, 1, sizeof(csr_contents), fp_csr), + sizeof(csr_contents)); + EXPECT_EQ(memcmp(csr_contents, expected_csr, sizeof(expected_csr)), 0); + fclose(fp_csr); + + FILE* fp_key = fopen(wrapped_key_output_file.c_str(), "rb"); + ASSERT_NE(fp_key, nullptr); + uint8_t key_contents[WRAPPED_ATTESTATION_KEY_SIZE]; + ASSERT_EQ(fread(key_contents, 1, sizeof(key_contents), fp_key), + sizeof(key_contents)); + EXPECT_EQ( + memcmp(key_contents, expected_wrapped_key, sizeof(expected_wrapped_key)), + 0); + fclose(fp_key); +} + +TEST_F(HtoolSecurityCertificatesTest, GenAttestationKeyV1NoOutputFails) { + struct htool_invocation inv{}; + EXPECT_CALL(invocation_mock_, GetParamString("csr_output", _)) + .WillOnce(DoAll(SetArgPointee<1>(""), Return(0))); + EXPECT_CALL(invocation_mock_, GetParamString("wrapped_key_output", _)) + .WillOnce(DoAll(SetArgPointee<1>(""), Return(0))); + EXPECT_CALL(security_v2_mock_, + htool_exec_security_v2_cmd(_, _, _, _, _, _, _, _, _, _)) + .Times(0); + + EXPECT_EQ(htool_gen_attestation_key_v1(&inv), -1); +} + +TEST_F(HtoolSecurityCertificatesTest, GenAttestationKeyV2Success) { + struct htool_invocation inv{}; + std::string csr_output_file = tmp_dir_path_ + "/csr_v2.bin"; + std::string wrapped_key_output_file = tmp_dir_path_ + "/wrapped_key_v2.bin"; + uint32_t expected_fw_major_version = 3; + + EXPECT_CALL(invocation_mock_, GetParamU32("fw_major_version", _)) + .WillOnce(DoAll(SetArgPointee<1>(expected_fw_major_version), Return(0))); + EXPECT_CALL(invocation_mock_, GetParamString("csr_output", _)) + .WillOnce(DoAll(SetArgPointee<1>(csr_output_file.c_str()), Return(0))); + EXPECT_CALL(invocation_mock_, GetParamString("wrapped_key_output", _)) + .WillOnce( + DoAll(SetArgPointee<1>(wrapped_key_output_file.c_str()), Return(0))); + + uint8_t expected_csr[ATTESTATION_KEY_CSR_V2_SIZE] = {}; + for (size_t i = 0; i < sizeof(expected_csr); ++i) { + expected_csr[i] = static_cast((i + 0x10) & 0xff); + } + uint8_t expected_wrapped_key[WRAPPED_ATTESTATION_KEY_SIZE] = {}; + for (size_t i = 0; i < sizeof(expected_wrapped_key); ++i) { + expected_wrapped_key[i] = static_cast((i + 0xaa) & 0xff); + } + + EXPECT_CALL(security_v2_mock_, htool_exec_security_v2_cmd) + .With(IsSecurityV2Command( + HOTH_PRV_CMD_HOTH_SECURITY_V2_GET_CERTIFICATES_MAJOR_COMMAND, + HOTH_PRV_CMD_HOTH_SECURITY_V2_GEN_VERSIONED_ATTESTATION_KEY_MINOR_COMMAND, + HOTH_BASE_CMD(HOTH_PRV_CMD_HOTH_SECURITY_V2), + HOTH_SECURITY_V2_REQUEST_SIZE(2) + sizeof(uint32_t) + + sizeof(uint32_t), + HOTH_SECURITY_V2_RESPONSE_SIZE(2) + sizeof(expected_csr) + + sizeof(expected_wrapped_key))) + .WillOnce([&](struct libhoth_device* dev, uint8_t major, uint8_t minor, + uint16_t base_command, + struct security_v2_buffer* request_buffer, + const struct security_v2_param* request_params, + uint16_t request_param_count, + struct security_v2_buffer* response_buffer, + struct security_v2_param* response_params, + uint16_t response_param_count) { + EXPECT_EQ(request_param_count, 2); + EXPECT_EQ(request_params[0].size, sizeof(uint32_t)); + EXPECT_EQ(*static_cast(request_params[0].data), 2u); + EXPECT_EQ(request_params[1].size, sizeof(uint32_t)); + EXPECT_EQ(*static_cast(request_params[1].data), + expected_fw_major_version); + + EXPECT_EQ(response_param_count, 2); + EXPECT_EQ(response_params[0].size, sizeof(expected_csr)); + EXPECT_EQ(response_params[1].size, sizeof(expected_wrapped_key)); + memcpy(response_params[0].data, expected_csr, sizeof(expected_csr)); + memcpy(response_params[1].data, expected_wrapped_key, + sizeof(expected_wrapped_key)); + return 0; + }); + + ASSERT_EQ(htool_gen_attestation_key_v2(&inv), 0); + + FILE* fp_csr = fopen(csr_output_file.c_str(), "rb"); + ASSERT_NE(fp_csr, nullptr); + uint8_t csr_contents[ATTESTATION_KEY_CSR_V2_SIZE]; + ASSERT_EQ(fread(csr_contents, 1, sizeof(csr_contents), fp_csr), + sizeof(csr_contents)); + EXPECT_EQ(memcmp(csr_contents, expected_csr, sizeof(expected_csr)), 0); + fclose(fp_csr); + + FILE* fp_key = fopen(wrapped_key_output_file.c_str(), "rb"); + ASSERT_NE(fp_key, nullptr); + uint8_t key_contents[WRAPPED_ATTESTATION_KEY_SIZE]; + ASSERT_EQ(fread(key_contents, 1, sizeof(key_contents), fp_key), + sizeof(key_contents)); + EXPECT_EQ( + memcmp(key_contents, expected_wrapped_key, sizeof(expected_wrapped_key)), + 0); + fclose(fp_key); +} + +TEST_F(HtoolSecurityCertificatesTest, LoadAttestationKeyFromCsrV1Success) { + struct htool_invocation inv{}; + std::string wrapped_key_file = tmp_dir_path_ + "/input_wrapped_key.bin"; + std::string csr_file = tmp_dir_path_ + "/input_csr_v1.bin"; + + uint8_t expected_wrapped_key[WRAPPED_ATTESTATION_KEY_SIZE] = {}; + for (size_t i = 0; i < sizeof(expected_wrapped_key); ++i) { + expected_wrapped_key[i] = static_cast(i + 1); + } + uint8_t expected_csr[ATTESTATION_KEY_CSR_V1_SIZE] = {}; + for (size_t i = 0; i < sizeof(expected_csr); ++i) { + expected_csr[i] = static_cast(255 - (i & 0xff)); + } + + FILE* fp_key = fopen(wrapped_key_file.c_str(), "wb"); + ASSERT_NE(fp_key, nullptr); + ASSERT_EQ( + fwrite(expected_wrapped_key, 1, sizeof(expected_wrapped_key), fp_key), + sizeof(expected_wrapped_key)); + fclose(fp_key); + + FILE* fp_csr = fopen(csr_file.c_str(), "wb"); + ASSERT_NE(fp_csr, nullptr); + ASSERT_EQ(fwrite(expected_csr, 1, sizeof(expected_csr), fp_csr), + sizeof(expected_csr)); + fclose(fp_csr); + + EXPECT_CALL(invocation_mock_, GetParamString("wrapped_key", _)) + .WillOnce(DoAll(SetArgPointee<1>(wrapped_key_file.c_str()), Return(0))); + EXPECT_CALL(invocation_mock_, GetParamString("csr", _)) + .WillOnce(DoAll(SetArgPointee<1>(csr_file.c_str()), Return(0))); + + EXPECT_CALL(security_v2_mock_, htool_exec_security_v2_cmd) + .With(IsSecurityV2Command( + HOTH_PRV_CMD_HOTH_SECURITY_V2_GET_CERTIFICATES_MAJOR_COMMAND, + HOTH_PRV_CMD_HOTH_SECURITY_V2_LOAD_ATTESTATION_KEY_FROM_CSR_V1_MINOR_COMMAND, + HOTH_BASE_CMD(HOTH_PRV_CMD_HOTH_SECURITY_V2), + HOTH_SECURITY_V2_REQUEST_SIZE(2) + sizeof(expected_wrapped_key) + + sizeof(expected_csr), + HOTH_SECURITY_V2_RESPONSE_SIZE(0))) + .WillOnce([&](struct libhoth_device* dev, uint8_t major, uint8_t minor, + uint16_t base_command, + struct security_v2_buffer* request_buffer, + const struct security_v2_param* request_params, + uint16_t request_param_count, + struct security_v2_buffer* response_buffer, + struct security_v2_param* response_params, + uint16_t response_param_count) { + EXPECT_EQ(request_param_count, 2); + EXPECT_EQ(request_params[0].size, sizeof(expected_wrapped_key)); + EXPECT_EQ(memcmp(request_params[0].data, expected_wrapped_key, + sizeof(expected_wrapped_key)), + 0); + EXPECT_EQ(request_params[1].size, sizeof(expected_csr)); + EXPECT_EQ( + memcmp(request_params[1].data, expected_csr, sizeof(expected_csr)), + 0); + return 0; + }); + + EXPECT_EQ(htool_load_attestation_key_from_csr_v1(&inv), 0); +} + +TEST_F(HtoolSecurityCertificatesTest, + LoadAttestationKeyFromCsrV1WrongSizeFails) { + struct htool_invocation inv{}; + std::string wrapped_key_file = tmp_dir_path_ + "/short_wrapped_key.bin"; + std::string csr_file = tmp_dir_path_ + "/input_csr_v1.bin"; + + uint8_t short_key[10] = {0}; + FILE* fp_key = fopen(wrapped_key_file.c_str(), "wb"); + ASSERT_NE(fp_key, nullptr); + ASSERT_EQ(fwrite(short_key, 1, sizeof(short_key), fp_key), sizeof(short_key)); + fclose(fp_key); + + EXPECT_CALL(invocation_mock_, GetParamString("wrapped_key", _)) + .WillOnce(DoAll(SetArgPointee<1>(wrapped_key_file.c_str()), Return(0))); + EXPECT_CALL(invocation_mock_, GetParamString("csr", _)) + .WillOnce(DoAll(SetArgPointee<1>(csr_file.c_str()), Return(0))); + EXPECT_CALL(security_v2_mock_, + htool_exec_security_v2_cmd(_, _, _, _, _, _, _, _, _, _)) + .Times(0); + + EXPECT_EQ(htool_load_attestation_key_from_csr_v1(&inv), -1); +} + +TEST_F(HtoolSecurityCertificatesTest, ProvisionAttestationKeySuccess) { + struct htool_invocation inv{}; + uint8_t generated_csr[ATTESTATION_KEY_CSR_V1_SIZE] = {}; + for (size_t i = 0; i < sizeof(generated_csr); ++i) { + generated_csr[i] = static_cast((i * 3) & 0xff); + } + uint8_t generated_wrapped_key[WRAPPED_ATTESTATION_KEY_SIZE] = {}; + for (size_t i = 0; i < sizeof(generated_wrapped_key); ++i) { + generated_wrapped_key[i] = static_cast((i * 7 + 1) & 0xff); + } + + ::testing::InSequence seq; + + // 1. Unload existing attestation key + EXPECT_CALL(security_v2_mock_, htool_exec_security_v2_cmd) + .With(IsSecurityV2Command( + HOTH_PRV_CMD_HOTH_SECURITY_V2_GET_CERTIFICATES_MAJOR_COMMAND, + HOTH_PRV_CMD_HOTH_SECURITY_V2_UNLOAD_ATTESTATION_KEY_MINOR_COMMAND, + HOTH_BASE_CMD(HOTH_PRV_CMD_HOTH_SECURITY_V2), + HOTH_SECURITY_V2_REQUEST_SIZE(0), HOTH_SECURITY_V2_RESPONSE_SIZE(0))) + .WillOnce(Return(0)); + + // 2. Generate attestation key v1 + EXPECT_CALL(security_v2_mock_, htool_exec_security_v2_cmd) + .With(IsSecurityV2Command( + HOTH_PRV_CMD_HOTH_SECURITY_V2_GET_CERTIFICATES_MAJOR_COMMAND, + HOTH_PRV_CMD_HOTH_SECURITY_V2_GEN_ATTESTATION_KEY_USING_CSR_V1_MINOR_COMMAND, + HOTH_BASE_CMD(HOTH_PRV_CMD_HOTH_SECURITY_V2), + HOTH_SECURITY_V2_REQUEST_SIZE(0), + HOTH_SECURITY_V2_RESPONSE_SIZE(2) + sizeof(generated_csr) + + sizeof(generated_wrapped_key))) + .WillOnce([&](struct libhoth_device* dev, uint8_t major, uint8_t minor, + uint16_t base_command, + struct security_v2_buffer* request_buffer, + const struct security_v2_param* request_params, + uint16_t request_param_count, + struct security_v2_buffer* response_buffer, + struct security_v2_param* response_params, + uint16_t response_param_count) { + EXPECT_EQ(response_param_count, 2); + memcpy(response_params[0].data, generated_csr, sizeof(generated_csr)); + memcpy(response_params[1].data, generated_wrapped_key, + sizeof(generated_wrapped_key)); + return 0; + }); + + // 3. Load attestation key from CSR v1 with the generated bytes + EXPECT_CALL(security_v2_mock_, htool_exec_security_v2_cmd) + .With(IsSecurityV2Command( + HOTH_PRV_CMD_HOTH_SECURITY_V2_GET_CERTIFICATES_MAJOR_COMMAND, + HOTH_PRV_CMD_HOTH_SECURITY_V2_LOAD_ATTESTATION_KEY_FROM_CSR_V1_MINOR_COMMAND, + HOTH_BASE_CMD(HOTH_PRV_CMD_HOTH_SECURITY_V2), + HOTH_SECURITY_V2_REQUEST_SIZE(2) + sizeof(generated_wrapped_key) + + sizeof(generated_csr), + HOTH_SECURITY_V2_RESPONSE_SIZE(0))) + .WillOnce([&](struct libhoth_device* dev, uint8_t major, uint8_t minor, + uint16_t base_command, + struct security_v2_buffer* request_buffer, + const struct security_v2_param* request_params, + uint16_t request_param_count, + struct security_v2_buffer* response_buffer, + struct security_v2_param* response_params, + uint16_t response_param_count) { + EXPECT_EQ(request_param_count, 2); + EXPECT_EQ(memcmp(request_params[0].data, generated_wrapped_key, + sizeof(generated_wrapped_key)), + 0); + EXPECT_EQ(memcmp(request_params[1].data, generated_csr, + sizeof(generated_csr)), + 0); + return 0; + }); + + EXPECT_EQ(htool_provision_attestation_key(&inv), 0); +} + +TEST_F(HtoolSecurityCertificatesTest, + GenAttestationKeyV1DeviceFailDoesNotCreateFiles) { + struct htool_invocation inv{}; + std::string csr_output_file = tmp_dir_path_ + "/should_not_exist_csr.bin"; + std::string wrapped_key_output_file = + tmp_dir_path_ + "/should_not_exist_key.bin"; + + EXPECT_CALL(invocation_mock_, GetParamString("csr_output", _)) + .WillOnce(DoAll(SetArgPointee<1>(csr_output_file.c_str()), Return(0))); + EXPECT_CALL(invocation_mock_, GetParamString("wrapped_key_output", _)) + .WillOnce( + DoAll(SetArgPointee<1>(wrapped_key_output_file.c_str()), Return(0))); + + EXPECT_CALL(security_v2_mock_, + htool_exec_security_v2_cmd(_, _, _, _, _, _, _, _, _, _)) + .WillOnce(Return(-1)); + + EXPECT_EQ(htool_gen_attestation_key_v1(&inv), -1); + EXPECT_FALSE(std::filesystem::exists(csr_output_file)); + EXPECT_FALSE(std::filesystem::exists(wrapped_key_output_file)); +} + +TEST_F(HtoolSecurityCertificatesTest, LoadAttestationKeySuccess) { + struct htool_invocation inv{}; + std::string wrapped_key_file = tmp_dir_path_ + "/input_wrapped_key_ca.bin"; + std::string cert_file = tmp_dir_path_ + "/input_cert.bin"; + + uint8_t expected_wrapped_key[WRAPPED_ATTESTATION_KEY_SIZE] = {}; + for (size_t i = 0; i < sizeof(expected_wrapped_key); ++i) { + expected_wrapped_key[i] = static_cast(i + 0x20); + } + uint8_t expected_cert[ATTESTATION_CERT_SIZE] = {}; + for (size_t i = 0; i < sizeof(expected_cert); ++i) { + expected_cert[i] = static_cast(255 - (i & 0xff)); + } + + FILE* fp_key = fopen(wrapped_key_file.c_str(), "wb"); + ASSERT_NE(fp_key, nullptr); + ASSERT_EQ( + fwrite(expected_wrapped_key, 1, sizeof(expected_wrapped_key), fp_key), + sizeof(expected_wrapped_key)); + fclose(fp_key); + + FILE* fp_cert = fopen(cert_file.c_str(), "wb"); + ASSERT_NE(fp_cert, nullptr); + ASSERT_EQ(fwrite(expected_cert, 1, sizeof(expected_cert), fp_cert), + sizeof(expected_cert)); + fclose(fp_cert); + + EXPECT_CALL(invocation_mock_, GetParamString("wrapped_key", _)) + .WillOnce(DoAll(SetArgPointee<1>(wrapped_key_file.c_str()), Return(0))); + EXPECT_CALL(invocation_mock_, GetParamString("cert", _)) + .WillOnce(DoAll(SetArgPointee<1>(cert_file.c_str()), Return(0))); + + EXPECT_CALL(security_v2_mock_, htool_exec_security_v2_cmd) + .With(IsSecurityV2Command( + HOTH_PRV_CMD_HOTH_SECURITY_V2_GET_CERTIFICATES_MAJOR_COMMAND, + HOTH_PRV_CMD_HOTH_SECURITY_V2_LOAD_ATTESTATION_KEY_MINOR_COMMAND, + HOTH_BASE_CMD(HOTH_PRV_CMD_HOTH_SECURITY_V2), + HOTH_SECURITY_V2_REQUEST_SIZE(2) + sizeof(expected_wrapped_key) + + sizeof(expected_cert), + HOTH_SECURITY_V2_RESPONSE_SIZE(0))) + .WillOnce([&](struct libhoth_device* dev, uint8_t major, uint8_t minor, + uint16_t base_command, + struct security_v2_buffer* request_buffer, + const struct security_v2_param* request_params, + uint16_t request_param_count, + struct security_v2_buffer* response_buffer, + struct security_v2_param* response_params, + uint16_t response_param_count) { + EXPECT_EQ(request_param_count, 2); + EXPECT_EQ(request_params[0].size, sizeof(expected_wrapped_key)); + EXPECT_EQ(memcmp(request_params[0].data, expected_wrapped_key, + sizeof(expected_wrapped_key)), + 0); + EXPECT_EQ(request_params[1].size, sizeof(expected_cert)); + EXPECT_EQ(memcmp(request_params[1].data, expected_cert, + sizeof(expected_cert)), + 0); + return 0; + }); + + EXPECT_EQ(htool_load_attestation_key(&inv), 0); +} + +TEST_F(HtoolSecurityCertificatesTest, LoadAttestationKeyWrongSizeFails) { + struct htool_invocation inv{}; + std::string wrapped_key_file = tmp_dir_path_ + "/short_wrapped_key_ca.bin"; + std::string cert_file = tmp_dir_path_ + "/input_cert.bin"; + + uint8_t short_key[10] = {0}; + FILE* fp_key = fopen(wrapped_key_file.c_str(), "wb"); + ASSERT_NE(fp_key, nullptr); + ASSERT_EQ(fwrite(short_key, 1, sizeof(short_key), fp_key), sizeof(short_key)); + fclose(fp_key); + + EXPECT_CALL(invocation_mock_, GetParamString("wrapped_key", _)) + .WillOnce(DoAll(SetArgPointee<1>(wrapped_key_file.c_str()), Return(0))); + EXPECT_CALL(invocation_mock_, GetParamString("cert", _)) + .WillOnce(DoAll(SetArgPointee<1>(cert_file.c_str()), Return(0))); + EXPECT_CALL(security_v2_mock_, + htool_exec_security_v2_cmd(_, _, _, _, _, _, _, _, _, _)) + .Times(0); + + EXPECT_EQ(htool_load_attestation_key(&inv), -1); +} From e589c6879265a92de5075ac83a674c3ddd1fd846 Mon Sep 17 00:00:00 2001 From: Willy Zhang Date: Thu, 17 Sep 2026 20:28:43 +0000 Subject: [PATCH 2/3] [htool] Document attestation key file sizes in flag help read_exact_file rejects any input file that is not exactly the expected length, and the generated outputs are always fixed size, but the flag descriptions did not say what those sizes are. Spell them out so the requirement is discoverable from --help instead of only from a failure message. wrapped key 88 bytes attestation key CSR v1 256 bytes attestation key CSR v2 192 bytes CA-signed certificate 192 bytes Help text only; no behaviour change. --- examples/htool.c | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/examples/htool.c b/examples/htool.c index 1aa4036..629106e 100644 --- a/examples/htool.c +++ b/examples/htool.c @@ -2126,10 +2126,10 @@ static const struct htool_cmd CMDS[] = { .params = (const struct htool_param[]){ {HTOOL_FLAG_VALUE, .name = "csr_output", .default_value = "", - .desc = "The Attestation Key CSR v1 output file"}, + .desc = "The Attestation Key CSR v1 output file (256 bytes)"}, {HTOOL_FLAG_VALUE, .name = "wrapped_key_output", .default_value = "", - .desc = "The Wrapped Attestation Key output file"}, + .desc = "The Wrapped Attestation Key output file (88 bytes)"}, {}}, }, { @@ -2143,10 +2143,10 @@ static const struct htool_cmd CMDS[] = { .desc = "The firmware major version to bind the Attestation " "Key to"}, {HTOOL_FLAG_VALUE, .name = "csr_output", .default_value = "", - .desc = "The Attestation Key CSR v2 output file"}, + .desc = "The Attestation Key CSR v2 output file (192 bytes)"}, {HTOOL_FLAG_VALUE, .name = "wrapped_key_output", .default_value = "", - .desc = "The Wrapped Attestation Key output file"}, + .desc = "The Wrapped Attestation Key output file (88 bytes)"}, {}}, }, { @@ -2156,10 +2156,11 @@ static const struct htool_cmd CMDS[] = { .params = (const struct htool_param[]){ {HTOOL_FLAG_VALUE, .name = "wrapped_key", .default_value = "", - .desc = "The Wrapped Attestation Key input file"}, + .desc = "The Wrapped Attestation Key input file. Must be " + "exactly 88 bytes."}, {HTOOL_FLAG_VALUE, .name = "cert", .default_value = "", .desc = "The CA-signed Attestation Key Certificate input " - "file"}, + "file. Must be exactly 192 bytes."}, {}}, }, { @@ -2171,9 +2172,11 @@ static const struct htool_cmd CMDS[] = { .params = (const struct htool_param[]){ {HTOOL_FLAG_VALUE, .name = "wrapped_key", .default_value = "", - .desc = "The Wrapped Attestation Key input file"}, + .desc = "The Wrapped Attestation Key input file. Must be " + "exactly 88 bytes."}, {HTOOL_FLAG_VALUE, .name = "csr", .default_value = "", - .desc = "The Attestation Key CSR v1 input file"}, + .desc = "The Attestation Key CSR v1 input file. Must be " + "exactly 256 bytes."}, {}}, }, { From 2dce80793e474133fd8931b99b091f779fd3e685 Mon Sep 17 00:00:00 2001 From: Willy Zhang Date: Thu, 17 Sep 2026 20:30:18 +0000 Subject: [PATCH 3/3] [htool] Print attestation and certificate errors to stderr htool_security_certificates.c was the only file in this area still sending every diagnostic to stdout, which mixes error text into piped certificate output and diverges from the 23 of 33 files under examples/ that already use stderr. All 28 prints in the file are error paths, so convert them wholesale rather than leaving the file half-and-half. Two message defects fixed while here: - "Unknown Alias Key Version received: %d" had no trailing newline. - Two "Returned status %d, while trying" messages had a doubled space and were split mid-phrase across string literals. No test asserts on stdout or stderr, and no status codes change. --- examples/htool_security_certificates.c | 91 ++++++++++++++------------ 1 file changed, 50 insertions(+), 41 deletions(-) diff --git a/examples/htool_security_certificates.c b/examples/htool_security_certificates.c index 0c220a0..3cbf6ad 100644 --- a/examples/htool_security_certificates.c +++ b/examples/htool_security_certificates.c @@ -34,8 +34,8 @@ static int get_cert_v2_data(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; } @@ -62,11 +62,10 @@ static int get_cert_v2_data(const struct htool_invocation* inv, response_storage_hdr, response_storage_hdr_size), response_params, ARRAY_SIZE(response_params)); if (hoth_status != 0) { - printf( - "Unexpected Error: Returned status %d, while trying to send " - "command to " - "get the Certificate\n", - hoth_status); + fprintf(stderr, + "Unexpected Error: Returned status %d, while trying to send " + "command to get the Certificate\n", + hoth_status); status = hoth_status; goto cleanup; } @@ -76,7 +75,7 @@ static int get_cert_v2_data(const struct htool_invocation* inv, } // SECURITY_V3 not supported yet. default: - printf("SECURITY_V3 not supported yet\n"); + fprintf(stderr, "SECURITY_V3 not supported yet\n"); goto cleanup; } status = 0; @@ -148,7 +147,7 @@ int htool_get_alias_key_cert(const struct htool_invocation* inv) { status = htool_get_alias_key_v1_cert(inv); break; default: - printf("Unknown Alias Key Version received: %d", version); + fprintf(stderr, "Unknown Alias Key Version received: %d\n", version); break; } @@ -182,8 +181,8 @@ int htool_get_device_id_cert(const struct htool_invocation* inv) { if (strlen(cert_output_file) > 0) { cert_output_ptr = fopen(cert_output_file, "wb"); if (cert_output_ptr == NULL) { - printf("Error: %s, when attempting to open file: %s\n", strerror(errno), - cert_output_file); + fprintf(stderr, "Error: %s, when attempting to open file: %s\n", + strerror(errno), cert_output_file); goto cleanup; } is_cert_output_file_provided = true; @@ -200,8 +199,8 @@ int htool_get_device_id_cert(const struct htool_invocation* inv) { if (strlen(endorsement_cert_output_file) > 0) { endorsement_cert_output_ptr = fopen(endorsement_cert_output_file, "wb"); if (endorsement_cert_output_ptr == NULL) { - printf("Error: %s, when attempting to open file: %s.\n", strerror(errno), - endorsement_cert_output_file); + fprintf(stderr, "Error: %s, when attempting to open file: %s.\n", + strerror(errno), endorsement_cert_output_file); goto cleanup; } is_endorsement_cert_output_file_provided = true; @@ -209,7 +208,8 @@ int htool_get_device_id_cert(const struct htool_invocation* inv) { if (!is_cert_output_file_provided && !is_endorsement_cert_output_file_provided) { - printf( + fprintf( + stderr, "Error: No valid cert_output provided and No valid " "endorsement_cert_output provided." " Only a cert_output field can be provided to return only the Device " @@ -253,11 +253,10 @@ int htool_get_device_id_cert(const struct htool_invocation* inv) { SECURITY_V2_BUFFER_PARAM(response_storage_hdr), response_params, ARRAY_SIZE(response_params)); if (hoth_status != 0) { - printf( - "Unexpected Error: Returned status %d, while trying to send " - "command to " - "get the Device ID Certificates\n", - hoth_status); + fprintf(stderr, + "Unexpected Error: Returned status %d, while trying to send " + "command to get the Device ID Certificates\n", + hoth_status); status = hoth_status; goto cleanup; } @@ -274,7 +273,7 @@ int htool_get_device_id_cert(const struct htool_invocation* inv) { } // SECURITY_V3 not supported yet. default: - printf("SECURITY_V3 not supported yet\n"); + fprintf(stderr, "SECURITY_V3 not supported yet\n"); goto cleanup; } status = 0; @@ -303,8 +302,8 @@ _Static_assert(ATTESTATION_CERT_SIZE % 4 == 0, static int read_exact_file(const char* filename, uint8_t* buf, size_t size) { FILE* fp = fopen(filename, "rb"); if (fp == NULL) { - printf("Error: %s, when attempting to open file: %s\n", strerror(errno), - filename); + fprintf(stderr, "Error: %s, when attempting to open file: %s\n", + strerror(errno), filename); return -1; } size_t bytes_read = fread(buf, 1, size, fp); @@ -312,7 +311,8 @@ static int read_exact_file(const char* filename, uint8_t* buf, size_t size) { int stream_err = ferror(fp); fclose(fp); if (bytes_read != size || extra != EOF || stream_err != 0) { - printf("Error: File %s must be exactly %zu bytes\n", filename, size); + fprintf(stderr, "Error: File %s must be exactly %zu bytes\n", filename, + size); return -1; } return 0; @@ -322,15 +322,15 @@ static int write_exact_file(const char* filename, const uint8_t* buf, size_t size) { FILE* fp = fopen(filename, "wb"); if (fp == NULL) { - printf("Error: %s, when attempting to open file: %s\n", strerror(errno), - filename); + fprintf(stderr, "Error: %s, when attempting to open file: %s\n", + strerror(errno), filename); return -1; } size_t bytes_written = fwrite(buf, 1, size, fp); int stream_err = ferror(fp); fclose(fp); if (bytes_written != size || stream_err != 0) { - printf("Error: Failed to write %zu bytes to %s\n", size, filename); + fprintf(stderr, "Error: Failed to write %zu bytes to %s\n", size, filename); return -1; } return 0; @@ -349,7 +349,8 @@ static int exec_unload_attestation_key(struct libhoth_device* dev) { SECURITY_V2_BUFFER_PARAM(request_storage_hdr), NULL, 0, SECURITY_V2_BUFFER_PARAM(response_storage_hdr), NULL, 0); if (hoth_status != 0) { - printf( + fprintf( + stderr, "Unexpected Error: Returned status %d, while trying to send command to " "unload the Attestation Key\n", hoth_status); @@ -384,7 +385,8 @@ static int exec_gen_attestation_key_v1( SECURITY_V2_BUFFER_PARAM(response_storage_hdr), response_params, ARRAY_SIZE(response_params)); if (hoth_status != 0) { - printf( + fprintf( + stderr, "Unexpected Error: Returned status %d, while trying to send command to " "generate the Attestation Key\n", hoth_status); @@ -434,7 +436,8 @@ static int exec_gen_attestation_key_v2( SECURITY_V2_BUFFER_PARAM(response_storage_hdr), response_params, ARRAY_SIZE(response_params)); if (hoth_status != 0) { - printf( + fprintf( + stderr, "Unexpected Error: Returned status %d, while trying to send command to " "generate the Attestation Key\n", hoth_status); @@ -470,7 +473,8 @@ static int exec_load_attestation_key( ARRAY_SIZE(request_params), SECURITY_V2_BUFFER_PARAM(response_storage_hdr), NULL, 0); if (hoth_status != 0) { - printf( + fprintf( + stderr, "Unexpected Error: Returned status %d, while trying to send command to " "load the Attestation Key\n", hoth_status); @@ -506,7 +510,8 @@ static int exec_load_attestation_key_from_csr_v1( ARRAY_SIZE(request_params), SECURITY_V2_BUFFER_PARAM(response_storage_hdr), NULL, 0); if (hoth_status != 0) { - printf( + fprintf( + stderr, "Unexpected Error: Returned status %d, while trying to send command to " "load the Attestation Key from CSR\n", hoth_status); @@ -527,7 +532,7 @@ int htool_unload_attestation_key(const struct htool_invocation* inv) { return exec_unload_attestation_key(dev); // SECURITY_V3 not supported yet. default: - printf("SECURITY_V3 not supported yet\n"); + fprintf(stderr, "SECURITY_V3 not supported yet\n"); return -1; } } @@ -553,7 +558,8 @@ int htool_gen_attestation_key_v1(const struct htool_invocation* inv) { bool is_wrapped_key_output_provided = strlen(wrapped_key_output_file) > 0; if (!is_csr_output_provided && !is_wrapped_key_output_provided) { - printf( + fprintf( + stderr, "Error: No valid csr_output provided and no valid wrapped_key_output " "provided.\n"); return -1; @@ -581,7 +587,7 @@ int htool_gen_attestation_key_v1(const struct htool_invocation* inv) { } // SECURITY_V3 not supported yet. default: - printf("SECURITY_V3 not supported yet\n"); + fprintf(stderr, "SECURITY_V3 not supported yet\n"); return -1; } } @@ -612,7 +618,8 @@ int htool_gen_attestation_key_v2(const struct htool_invocation* inv) { bool is_wrapped_key_output_provided = strlen(wrapped_key_output_file) > 0; if (!is_csr_output_provided && !is_wrapped_key_output_provided) { - printf( + fprintf( + stderr, "Error: No valid csr_output provided and no valid wrapped_key_output " "provided.\n"); return -1; @@ -641,7 +648,7 @@ int htool_gen_attestation_key_v2(const struct htool_invocation* inv) { } // SECURITY_V3 not supported yet. default: - printf("SECURITY_V3 not supported yet\n"); + fprintf(stderr, "SECURITY_V3 not supported yet\n"); return -1; } } @@ -663,7 +670,8 @@ int htool_load_attestation_key(const struct htool_invocation* inv) { } if (strlen(wrapped_key_file) == 0 || strlen(cert_file) == 0) { - printf( + fprintf( + stderr, "Error: Both wrapped_key and cert files must be provided to load the " "Attestation Key.\n"); return -1; @@ -686,7 +694,7 @@ int htool_load_attestation_key(const struct htool_invocation* inv) { return exec_load_attestation_key(dev, wrapped_key, cert); // SECURITY_V3 not supported yet. default: - printf("SECURITY_V3 not supported yet\n"); + fprintf(stderr, "SECURITY_V3 not supported yet\n"); return -1; } } @@ -708,7 +716,8 @@ int htool_load_attestation_key_from_csr_v1(const struct htool_invocation* inv) { } if (strlen(wrapped_key_file) == 0 || strlen(csr_file) == 0) { - printf( + fprintf( + stderr, "Error: Both wrapped_key and csr files must be provided to load the " "Attestation Key.\n"); return -1; @@ -731,7 +740,7 @@ int htool_load_attestation_key_from_csr_v1(const struct htool_invocation* inv) { return exec_load_attestation_key_from_csr_v1(dev, wrapped_key, csr); // SECURITY_V3 not supported yet. default: - printf("SECURITY_V3 not supported yet\n"); + fprintf(stderr, "SECURITY_V3 not supported yet\n"); return -1; } } @@ -762,7 +771,7 @@ int htool_provision_attestation_key(const struct htool_invocation* inv) { } // SECURITY_V3 not supported yet. default: - printf("SECURITY_V3 not supported yet\n"); + fprintf(stderr, "SECURITY_V3 not supported yet\n"); return -1; } }