diff --git a/examples/htool.c b/examples/htool.c index ed3c947..3e84dee 100644 --- a/examples/htool.c +++ b/examples/htool.c @@ -2077,6 +2077,17 @@ static const struct htool_cmd CMDS[] = { .desc = "The signed cert file."}, {}}, }, + { + .verbs = (const char*[]){"provisioning", "get_encryption_key", NULL}, + .desc = "Get the provisioning encryption key certificate chain", + .func = htool_provisioning_get_encryption_key, + .params = + (const struct htool_param[]){ + {HTOOL_FLAG_VALUE, .name = "output", + .desc = "File to write the provisioning encryption key " + "certificate chain to."}, + {}}, + }, { .verbs = (const char*[]){"provisioning", "store_secrets", NULL}, .desc = "Store secrets encrypted with the provisioning encryption key", diff --git a/examples/htool_provisioning.c b/examples/htool_provisioning.c index a325e1c..85e38e1 100644 --- a/examples/htool_provisioning.c +++ b/examples/htool_provisioning.c @@ -207,6 +207,51 @@ static int get_secrets(const struct htool_invocation* inv, uint8_t* secrets, secrets_size); } +int htool_provisioning_get_encryption_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* output_file; + if (htool_get_param_string(inv, "output", &output_file) != 0 || + strlen(output_file) == 0) { + fprintf(stderr, "--output must be specified.\n"); + return -1; + } + + uint8_t cert_chain[HOTH_KEY_PROVISIONING_MAX_ENCRYPTION_KEY_CERT_CHAIN_SIZE]; + size_t cert_chain_size = 0; + libhoth_error err = libhoth_key_provisioning_get_encryption_key( + dev, cert_chain, sizeof(cert_chain), &cert_chain_size); + if (err != HOTH_SUCCESS) { + fprintf(stderr, + "Error: 'key_provisioning_get_encryption_key' failed (0x%016" PRIx64 + "): ", + err); + libhoth_log_err(stderr, err); + return -1; + } + + FILE* 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); + return -1; + } + + if (fwrite(cert_chain, sizeof(uint8_t), cert_chain_size, output_ptr) != + cert_chain_size) { + fprintf(stderr, "Error writing to %s\n", output_file); + fclose(output_ptr); + return -1; + } + + fclose(output_ptr); + return 0; +} + int htool_provisioning_store_secrets(const struct htool_invocation* inv) { struct libhoth_device* dev = htool_libhoth_device(); if (!dev) { diff --git a/examples/htool_provisioning.h b/examples/htool_provisioning.h index 79560f9..6c4cb4c 100644 --- a/examples/htool_provisioning.h +++ b/examples/htool_provisioning.h @@ -33,6 +33,9 @@ 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); +// Retrieves the provisioning encryption key certificate chain from the device. +int htool_provisioning_get_encryption_key(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); diff --git a/examples/htool_provisioning_test.cc b/examples/htool_provisioning_test.cc index 9968f5e..d12bebc 100644 --- a/examples/htool_provisioning_test.cc +++ b/examples/htool_provisioning_test.cc @@ -958,6 +958,130 @@ TEST_F(HtoolProvisioningTest, LoadMldsaKeyMissingKey) { ASSERT_EQ(htool_provisioning_load_mldsa_key(&inv), -1); } +TEST_F(HtoolProvisioningTest, GetEncryptionKeySuccess) { + struct htool_invocation inv{}; + std::string tmp_output_file = + tmp_dir_path_ + "/encryption_key_cert_chain.bin"; + EXPECT_CALL(invocation_mock_, GetParamString("output", _)) + .WillOnce(DoAll(SetArgPointee<1>(tmp_output_file.c_str()), Return(0))); + + std::vector cert_chain(724); + for (size_t i = 0; i < cert_chain.size(); ++i) { + cert_chain[i] = static_cast(i & 0xff); + } + + EXPECT_CALL(mock_, send(_, _, _)) + .WillOnce([&](struct libhoth_device*, const void* req, size_t size) { + EXPECT_EQ(size, + sizeof(struct hoth_host_request) + + sizeof(struct hoth_key_provisioning_request_header)); + const auto* hoth_req = + static_cast(req); + EXPECT_EQ(hoth_req->command, + HOTH_BASE_CMD(HOTH_PRV_CMD_HOTH_KEY_PROVISIONING)); + EXPECT_EQ(hoth_req->data_len, + sizeof(struct hoth_key_provisioning_request_header)); + const auto* prov_req = reinterpret_cast< + const struct hoth_key_provisioning_request_header*>( + static_cast(req) + + sizeof(struct hoth_host_request)); + EXPECT_EQ(prov_req->version, HOTH_KEY_PROVISIONING_REQUEST_VERSION); + EXPECT_EQ(prov_req->command, HOTH_KEY_PROVISIONING_GET_ENCRYPTION_KEY); + EXPECT_EQ(prov_req->size, + sizeof(struct hoth_key_provisioning_request_header)); + return LIBHOTH_OK; + }); + + EXPECT_CALL(mock_, receive(_, _, _, _, _)) + .WillOnce(DoAll(CopyResp(cert_chain.data(), cert_chain.size()), + Return(LIBHOTH_OK))); + + ASSERT_EQ(htool_provisioning_get_encryption_key(&inv), 0); + + FILE* fp = fopen(tmp_output_file.c_str(), "rb"); + ASSERT_NE(fp, nullptr); + std::vector file_contents(cert_chain.size()); + ASSERT_EQ(fread(file_contents.data(), 1, file_contents.size(), fp), + cert_chain.size()); + EXPECT_EQ(fgetc(fp), EOF); + EXPECT_EQ(memcmp(file_contents.data(), cert_chain.data(), cert_chain.size()), + 0); + fclose(fp); + remove(tmp_output_file.c_str()); +} + +TEST_F(HtoolProvisioningTest, GetEncryptionKeyDeviceError) { + struct htool_invocation inv{}; + std::string tmp_output_file = + tmp_dir_path_ + "/encryption_key_cert_chain_err.bin"; + EXPECT_CALL(invocation_mock_, GetParamString("output", _)) + .WillOnce(DoAll(SetArgPointee<1>(tmp_output_file.c_str()), Return(0))); + + EXPECT_CALL(mock_, send(_, _, _)).WillOnce(Return(LIBHOTH_OK)); + EXPECT_CALL(mock_, receive(_, _, _, _, _)) + .WillOnce(Return(LIBHOTH_ERR_INTERFACE_NOT_FOUND)); + + ASSERT_EQ(htool_provisioning_get_encryption_key(&inv), -1); + EXPECT_EQ(fopen(tmp_output_file.c_str(), "rb"), nullptr); +} + +TEST_F(HtoolProvisioningTest, GetEncryptionKeyMissingOutput) { + struct htool_invocation inv{}; + EXPECT_CALL(invocation_mock_, GetParamString("output", _)) + .WillOnce(Return(-1)) + .WillOnce(DoAll(SetArgPointee<1>(""), Return(0))); + EXPECT_CALL(mock_, send(_, _, _)).Times(0); + EXPECT_CALL(mock_, receive(_, _, _, _, _)).Times(0); + + ASSERT_EQ(htool_provisioning_get_encryption_key(&inv), -1); + ASSERT_EQ(htool_provisioning_get_encryption_key(&inv), -1); +} + +TEST_F(HtoolProvisioningTest, GetEncryptionKeyOutputFileNotAbleToBeOpened) { + struct htool_invocation inv{}; + std::string tmp_output_file = "/path/to/nonexistent/file"; + EXPECT_CALL(invocation_mock_, GetParamString("output", _)) + .WillOnce(DoAll(SetArgPointee<1>(tmp_output_file.c_str()), Return(0))); + + std::vector cert_chain(724, 0xab); + EXPECT_CALL(mock_, send(_, _, _)).WillOnce(Return(LIBHOTH_OK)); + EXPECT_CALL(mock_, receive(_, _, _, _, _)) + .WillOnce(DoAll(CopyResp(cert_chain.data(), cert_chain.size()), + Return(LIBHOTH_OK))); + + ASSERT_EQ(htool_provisioning_get_encryption_key(&inv), -1); +} + +TEST_F(HtoolProvisioningTest, GetEncryptionKeyProtocolNullParams) { + uint8_t cert_chain[16] = {}; + size_t out_size = 0; + EXPECT_CALL(mock_, send(_, _, _)).Times(0); + EXPECT_EQ(libhoth_key_provisioning_get_encryption_key( + &hoth_dev_, nullptr, sizeof(cert_chain), &out_size), + LIBHOTH_ERR_CONSTRUCT(HOTH_CTX_CMD_EXEC, HOTH_HOST_SPACE_LIBHOTH, + LIBHOTH_ERR_INVALID_PARAMETER)); + EXPECT_EQ(libhoth_key_provisioning_get_encryption_key( + &hoth_dev_, cert_chain, sizeof(cert_chain), nullptr), + LIBHOTH_ERR_CONSTRUCT(HOTH_CTX_CMD_EXEC, HOTH_HOST_SPACE_LIBHOTH, + LIBHOTH_ERR_INVALID_PARAMETER)); +} + +TEST_F(HtoolProvisioningTest, GetEncryptionKeyProtocolBufferOverflow) { + std::vector cert_chain(32, 0xab); + uint8_t small_buf[16] = {}; + size_t out_size = 0; + + EXPECT_CALL(mock_, send(_, _, _)).WillOnce(Return(LIBHOTH_OK)); + EXPECT_CALL(mock_, receive(_, _, _, _, _)) + .WillOnce(DoAll(CopyResp(cert_chain.data(), cert_chain.size()), + Return(LIBHOTH_OK))); + + EXPECT_EQ(libhoth_key_provisioning_get_encryption_key( + &hoth_dev_, small_buf, sizeof(small_buf), &out_size), + LIBHOTH_ERR_CONSTRUCT(HOTH_CTX_CMD_EXEC, HOTH_HOST_SPACE_LIBHOTH, + LIBHOTH_ERR_RESPONSE_BUFFER_OVERFLOW)); +} + TEST_F(HtoolProvisioningTest, StoreSecretsProtocolTooLarge) { std::vector data(HOTH_KEY_PROVISIONING_MAX_SECRETS_SIZE + 1, 0x11); EXPECT_CALL(mock_, send(_, _, _)).Times(0); diff --git a/protocol/provisioning.c b/protocol/provisioning.c index 1ce557d..5da8b4e 100644 --- a/protocol/provisioning.c +++ b/protocol/provisioning.c @@ -71,13 +71,15 @@ static int exec_provisioning_log_cmd_legacy(struct libhoth_device* dev, // 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; + 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_KEY_PROVISIONING, /*version=*/0, req_payload, req_payload_size, - NULL, 0, &response_size); + resp_buf, resp_buf_size, out_resp_size); } int libhoth_provisioning_log_read(struct libhoth_device* dev, uint8_t* buf, @@ -249,6 +251,24 @@ libhoth_error libhoth_provisioning_log_commit(struct libhoth_device* dev, 0, &response_size); } +libhoth_error libhoth_key_provisioning_get_encryption_key( + struct libhoth_device* dev, uint8_t* cert_chain, size_t cert_chain_capacity, + size_t* out_size) { + if (cert_chain == NULL || out_size == NULL) { + return LIBHOTH_ERR_CONSTRUCT(HOTH_CTX_CMD_EXEC, HOTH_HOST_SPACE_LIBHOTH, + LIBHOTH_ERR_INVALID_PARAMETER); + } + + struct hoth_key_provisioning_request_header req = { + .version = HOTH_KEY_PROVISIONING_REQUEST_VERSION, + .command = HOTH_KEY_PROVISIONING_GET_ENCRYPTION_KEY, + .size = sizeof(req), + }; + + return exec_key_provisioning_cmd(dev, &req, sizeof(req), cert_chain, + cert_chain_capacity, out_size); +} + libhoth_error libhoth_key_provisioning_store_secrets(struct libhoth_device* dev, const uint8_t* secrets, size_t size) { @@ -270,7 +290,9 @@ libhoth_error libhoth_key_provisioning_store_secrets(struct libhoth_device* dev, }; memcpy(req.secrets, secrets, size); - return exec_key_provisioning_cmd(dev, &req, request_size); + size_t response_size = 0; + return exec_key_provisioning_cmd(dev, &req, request_size, NULL, 0, + &response_size); } libhoth_error libhoth_key_provisioning_load_mldsa_key( @@ -306,7 +328,9 @@ libhoth_error libhoth_key_provisioning_load_mldsa_key( }; memcpy(req.data, key + offset, chunk_size); - libhoth_error err = exec_key_provisioning_cmd(dev, &req, req_size); + size_t response_size = 0; + libhoth_error err = + exec_key_provisioning_cmd(dev, &req, req_size, NULL, 0, &response_size); if (err != HOTH_SUCCESS) { return err; } diff --git a/protocol/provisioning.h b/protocol/provisioning.h index 9019e1f..3d1856e 100644 --- a/protocol/provisioning.h +++ b/protocol/provisioning.h @@ -46,6 +46,9 @@ struct hoth_key_provisioning_request_header { uint16_t size; } __attribute__((packed)); +#define HOTH_KEY_PROVISIONING_MAX_ENCRYPTION_KEY_CERT_CHAIN_SIZE \ + (LIBHOTH_MAILBOX_SIZE - sizeof(struct hoth_host_response)) + #define HOTH_KEY_PROVISIONING_MAX_SECRETS_SIZE \ (LIBHOTH_MAILBOX_SIZE - sizeof(struct hoth_host_request) - \ sizeof(struct hoth_key_provisioning_request_header)) @@ -155,6 +158,13 @@ libhoth_error libhoth_provisioning_log_write(struct libhoth_device* dev, libhoth_error libhoth_provisioning_log_commit(struct libhoth_device* dev, const uint8_t* data, size_t size); +// Retrieves the provisioning encryption key certificate chain from the device +// into |cert_chain|, which must be able to hold |cert_chain_capacity| bytes. +// On success |out_size| receives the number of bytes written to |cert_chain|. +libhoth_error libhoth_key_provisioning_get_encryption_key( + struct libhoth_device* dev, uint8_t* cert_chain, size_t cert_chain_capacity, + size_t* out_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,