tzpfms/src/tpm2.cpp

253 lines
11 KiB
C++

/* SPDX-License-Identifier: MIT */
#include "tpm2.hpp"
#include "fd.hpp"
#include "main.hpp"
#include "parse.hpp"
#include <algorithm>
#include <time.h>
template <class F>
static int try_or_passphrase(const char * what, const char * what_for, ESYS_CONTEXT * tpm2_ctx, TPM2_RC valid_error, ESYS_TR passphrased_object, F && func) {
auto err = func();
for(int i = 0; err == TPM2_RC_9 + valid_error && i < 3; ++i) {
if(i)
fprintf(stderr, "Couldn't %s: %s\n", what, Tss2_RC_Decode(err));
uint8_t * pass{};
size_t pass_len{};
TRY_MAIN(read_known_passphrase(what_for, pass, pass_len, sizeof(TPM2B_AUTH::buffer)));
quickscope_wrapper pass_deleter{[&] { free(pass); }};
TPM2B_AUTH auth{};
auth.size = pass_len;
memcpy(auth.buffer, pass, auth.size);
TRY_TPM2("set passphrase", Esys_TR_SetAuth(tpm2_ctx, passphrased_object, &auth));
err = func();
}
// TRY_TPM2() unrolled because no constexpr/string-literal-template arguments until C++20, which is not supported by GCC 8, which we need for Buster
if(err != TPM2_RC_SUCCESS) {
fprintf(stderr, "Couldn't %s: %s\n", what, Tss2_RC_Decode(err));
return __LINE__;
}
return 0;
}
TPM2B_DATA tpm2_creation_metadata(const char * dataset_name) {
TPM2B_DATA metadata{};
const auto now = time(nullptr);
const auto now_tm = localtime(&now);
metadata.size = snprintf((char *)metadata.buffer, sizeof(metadata.buffer), "%s %d-%02d-%02dT%02d:%02d:%02d %s", dataset_name, //
now_tm->tm_year + 1900, now_tm->tm_mon + 1, now_tm->tm_mday, now_tm->tm_hour, now_tm->tm_min, now_tm->tm_sec, //
TZPFMS_VERSION) +
1;
metadata.size = metadata.size > sizeof(metadata.buffer) ? sizeof(metadata.buffer) : metadata.size;
// fprintf(stderr, "%d/%zu: \"%s\"\n", metadata.size, sizeof(metadata.buffer), metadata.buffer);
return metadata;
}
int tpm2_parse_handle(const char * dataset_name, const char * handle_s, TPMI_DH_PERSISTENT & handle) {
if(parse_int(handle_s, handle)) {
fprintf(stderr, "Dataset %s's handle %s not valid.\n", dataset_name, handle_s);
return __LINE__;
}
return 0;
}
int tpm2_generate_rand(ESYS_CONTEXT * tpm2_ctx, void * into, size_t length) {
TPM2B_DIGEST * rand{};
TRY_TPM2("get random data from TPM", Esys_GetRandom(tpm2_ctx, ESYS_TR_NONE, ESYS_TR_NONE, ESYS_TR_NONE, length, &rand));
quickscope_wrapper rand_deleter{[=] { Esys_Free(rand); }};
if(rand->size != length) {
fprintf(stderr, "Wrong random size: wanted %zu, got %u bytes.\n", length, rand->size);
return __LINE__;
}
memcpy(into, rand->buffer, length);
return 0;
}
static int tpm2_find_unused_persistent_non_platform(ESYS_CONTEXT * tpm2_ctx, TPMI_DH_PERSISTENT & persistent_handle) {
TPMS_CAPABILITY_DATA * cap; // TODO: check for more data?
TRY_TPM2("Read used persistent TPM handles", Esys_GetCapability(tpm2_ctx, ESYS_TR_NONE, ESYS_TR_NONE, ESYS_TR_NONE, TPM2_CAP_HANDLES, TPM2_PERSISTENT_FIRST,
TPM2_MAX_CAP_HANDLES, nullptr, &cap));
quickscope_wrapper cap_deleter{[=] { Esys_Free(cap); }};
persistent_handle = 0;
switch(cap->data.handles.count) {
case 0:
persistent_handle = TPM2_PERSISTENT_FIRST;
break;
case TPM2_MAX_CAP_HANDLES:
break;
default:
for(TPM2_HC i = TPM2_PERSISTENT_FIRST; i <= TPM2_PERSISTENT_LAST && i <= TPM2_PLATFORM_PERSISTENT; ++i)
if(std::find(std::begin(cap->data.handles.handle), std::end(cap->data.handles.handle), i) == std::end(cap->data.handles.handle)) {
persistent_handle = i;
break;
}
}
if(!persistent_handle) {
fprintf(stderr, "All %zu persistent handles allocated! We're fucked!\n", TPM2_MAX_CAP_HANDLES);
return __LINE__;
}
return 0;
}
int tpm2_seal(ESYS_CONTEXT * tpm2_ctx, ESYS_TR tpm2_session, TPMI_DH_PERSISTENT & persistent_handle, const TPM2B_DATA & metadata, void * data,
size_t data_len) {
ESYS_TR primary_handle = ESYS_TR_NONE;
quickscope_wrapper primary_handle_deleter{[&] { Esys_FlushContext(tpm2_ctx, primary_handle); }};
{
const TPM2B_SENSITIVE_CREATE primary_sens{};
// Adapted from tpm2-tss-3.0.1/test/integration/esys-create-primary-hmac.int.c
TPM2B_PUBLIC pub{};
pub.publicArea.type = TPM2_ALG_RSA;
pub.publicArea.nameAlg = TPM2_ALG_SHA1;
pub.publicArea.objectAttributes = TPMA_OBJECT_USERWITHAUTH | TPMA_OBJECT_RESTRICTED | TPMA_OBJECT_DECRYPT | TPMA_OBJECT_FIXEDTPM | TPMA_OBJECT_FIXEDPARENT |
TPMA_OBJECT_SENSITIVEDATAORIGIN;
pub.publicArea.parameters.rsaDetail.symmetric.algorithm = TPM2_ALG_AES;
pub.publicArea.parameters.rsaDetail.symmetric.keyBits.aes = 128;
pub.publicArea.parameters.rsaDetail.symmetric.mode.aes = TPM2_ALG_CFB;
pub.publicArea.parameters.rsaDetail.scheme.scheme = TPM2_ALG_NULL;
pub.publicArea.parameters.rsaDetail.keyBits = 2048;
pub.publicArea.parameters.rsaDetail.exponent = 0;
const TPML_PCR_SELECTION pcrs{};
TPM2B_PUBLIC * public_ret{};
TPM2B_CREATION_DATA * creation_data{};
TPM2B_DIGEST * creation_hash{};
TPMT_TK_CREATION * creation_ticket{};
TRY_MAIN(try_or_passphrase("create primary encryption key", "owner hierarchy", tpm2_ctx, TPM2_RC_BAD_AUTH, ESYS_TR_RH_OWNER, [&] {
return Esys_CreatePrimary(tpm2_ctx, ESYS_TR_RH_OWNER, tpm2_session, ESYS_TR_NONE, ESYS_TR_NONE, &primary_sens, &pub, &metadata, &pcrs, &primary_handle,
&public_ret, &creation_data, &creation_hash, &creation_ticket);
}));
quickscope_wrapper creation_ticket_deleter{[=] { Esys_Free(creation_ticket); }};
quickscope_wrapper creation_hash_deleter{[=] { Esys_Free(creation_hash); }};
quickscope_wrapper creation_data_deleter{[=] { Esys_Free(creation_data); }};
quickscope_wrapper public_ret_deleter{[=] { Esys_Free(public_ret); }};
// TSS2_RC Esys_CertifyCreation ( ESYS_CONTEXT * esysContext,
// ESYS_TR signHandle,
// ESYS_TR objectHandle,
// ESYS_TR shandle1,
// ESYS_TR shandle2,
// ESYS_TR shandle3,
// const TPM2B_DATA * qualifyingData,
// const TPM2B_DIGEST * creationHash,
// const TPMT_SIG_SCHEME * inScheme,
// const TPMT_TK_CREATION * creationTicket,
// TPM2B_ATTEST ** certifyInfo,
// TPMT_SIGNATURE ** signature
// )
}
TPM2B_PRIVATE * sealant_private{};
TPM2B_PUBLIC * sealant_public{};
quickscope_wrapper sealant_public_deleter{[=] { Esys_Free(sealant_public); }};
quickscope_wrapper sealant_private_deleter{[=] { Esys_Free(sealant_private); }};
/// This is the object with the actual sealed data in it
{
TPM2B_SENSITIVE_CREATE secret_sens{};
secret_sens.sensitive.data.size = data_len;
memcpy(secret_sens.sensitive.data.buffer, data, secret_sens.sensitive.data.size);
{
uint8_t * passphrase{};
size_t passphrase_len{};
TRY_MAIN(read_new_passphrase("wrapping key (or empty for none)", passphrase, passphrase_len, sizeof(TPM2B_SENSITIVE_CREATE::sensitive.userAuth.buffer)));
quickscope_wrapper passphrase_deleter{[&] { free(passphrase); }};
secret_sens.sensitive.userAuth.size = passphrase_len;
memcpy(secret_sens.sensitive.userAuth.buffer, passphrase, secret_sens.sensitive.userAuth.size);
}
// Same args as tpm2-tools' tpm2_create(1)
TPM2B_PUBLIC pub{};
pub.publicArea.type = TPM2_ALG_KEYEDHASH;
pub.publicArea.nameAlg = TPM2_ALG_SHA256;
pub.publicArea.objectAttributes = TPMA_OBJECT_FIXEDTPM | TPMA_OBJECT_FIXEDPARENT | TPMA_OBJECT_USERWITHAUTH;
pub.publicArea.parameters.keyedHashDetail.scheme.scheme = TPM2_ALG_NULL;
const TPML_PCR_SELECTION pcrs{};
TPM2B_CREATION_DATA * creation_data{};
TPM2B_DIGEST * creation_hash{};
TPMT_TK_CREATION * creation_ticket{};
TRY_TPM2("create key seal", Esys_Create(tpm2_ctx, primary_handle, tpm2_session, ESYS_TR_NONE, ESYS_TR_NONE, &secret_sens, &pub, &metadata, &pcrs,
&sealant_private, &sealant_public, &creation_data, &creation_hash, &creation_ticket));
quickscope_wrapper creation_ticket_deleter{[=] { Esys_Free(creation_ticket); }};
quickscope_wrapper creation_hash_deleter{[=] { Esys_Free(creation_hash); }};
quickscope_wrapper creation_data_deleter{[=] { Esys_Free(creation_data); }};
}
ESYS_TR sealed_handle = ESYS_TR_NONE;
quickscope_wrapper sealed_handle_deleter{[&] { Esys_FlushContext(tpm2_ctx, sealed_handle); }};
/// Load the sealed object (keyedhash) into a transient handle
TRY_TPM2("load key seal", Esys_Load(tpm2_ctx, primary_handle, tpm2_session, ESYS_TR_NONE, ESYS_TR_NONE, sealant_private, sealant_public, &sealed_handle));
/// Find lowest unused persistent handle
TRY_MAIN(tpm2_find_unused_persistent_non_platform(tpm2_ctx, persistent_handle));
/// Persist the loaded handle in the TPM — this will make it available as $persistent_handle until we explicitly evict it back to the transient store
{
// Can't be flushed (tpm:parameter(1):value is out of range or is not correct for the context), plus, that's kinda the point
ESYS_TR new_handle;
TRY_TPM2("persist key seal",
Esys_EvictControl(tpm2_ctx, ESYS_TR_RH_OWNER, sealed_handle, tpm2_session, ESYS_TR_NONE, ESYS_TR_NONE, persistent_handle, &new_handle));
}
return 0;
}
int tpm2_unseal(ESYS_CONTEXT * tpm2_ctx, ESYS_TR tpm2_session, TPMI_DH_PERSISTENT persistent_handle, void * data, size_t data_len) {
// Entirely fake and not flushable (tpm:parameter(1):value is out of range or is not correct for the context)
ESYS_TR pandle;
TRY_TPM2("convert persistent handle to object", Esys_TR_FromTPMPublic(tpm2_ctx, persistent_handle, ESYS_TR_NONE, ESYS_TR_NONE, ESYS_TR_NONE, &pandle));
TPM2B_SENSITIVE_DATA * unsealed{};
quickscope_wrapper unsealed_deleter{[=] { Esys_Free(unsealed); }};
TRY_MAIN(try_or_passphrase("unseal wrapping key", "wrapping key", tpm2_ctx, TPM2_RC_AUTH_FAIL, pandle,
[&] { return Esys_Unseal(tpm2_ctx, pandle, tpm2_session, ESYS_TR_NONE, ESYS_TR_NONE, &unsealed); }));
if(unsealed->size != data_len) {
fprintf(stderr, "Unsealed data has wrong length %u, expected %zu!\n", unsealed->size, data_len);
return __LINE__;
}
memcpy(data, unsealed->buffer, data_len);
return 0;
}
int tpm2_free_persistent(ESYS_CONTEXT * tpm2_ctx, ESYS_TR tpm2_session, TPMI_DH_PERSISTENT persistent_handle) {
// Neither of these are flushable (tpm:parameter(1):value is out of range or is not correct for the context)
ESYS_TR pandle;
TRY_TPM2("convert persistent handle to object", Esys_TR_FromTPMPublic(tpm2_ctx, persistent_handle, ESYS_TR_NONE, ESYS_TR_NONE, ESYS_TR_NONE, &pandle));
ESYS_TR new_handle;
TRY_MAIN(try_or_passphrase("unpersist object", "owner hierarchy", tpm2_ctx, TPM2_RC_BAD_AUTH, ESYS_TR_RH_OWNER,
[&] { return Esys_EvictControl(tpm2_ctx, ESYS_TR_RH_OWNER, pandle, tpm2_session, ESYS_TR_NONE, ESYS_TR_NONE, 0, &new_handle); }));
return 0;
}