diff --git a/src/report/report-sign-tpm2.c b/src/report/report-sign-tpm2.c index 10fa62f31b..d94d15e94b 100644 --- a/src/report/report-sign-tpm2.c +++ b/src/report/report-sign-tpm2.c @@ -586,6 +586,14 @@ static int reply_report(sd_varlink *link, const Tpm2Report *report, const struct return log_error_errno(r, "Cannot convert NvPCR public area to JSON: %m"); } + const char *session_audit_hash_alg = NULL; + if (c->type == TPM2_REPORT_TYPE_SESSION_AUDIT) { + session_audit_hash_alg = tpm2_hash_alg_to_string_tss2(c->session_audit_hash_alg); + if (!session_audit_hash_alg) + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Unsupported session audit hash algorithm 0x%" PRIx16, c->session_audit_hash_alg); + } + TPMT_SIG_SCHEME scheme = { .scheme = c->signature->sigAlg, }; @@ -616,6 +624,7 @@ static int reply_report(sd_varlink *link, const Tpm2Report *report, const struct SD_JSON_BUILD_PAIR_CONDITION(c->type == TPM2_REPORT_TYPE_NVPCR, "nvpcrName", SD_JSON_BUILD_STRING(c->nv_pcr_name)), SD_JSON_BUILD_PAIR_CONDITION(c->type == TPM2_REPORT_TYPE_NVPCR, "nvPublic", SD_JSON_BUILD_VARIANT(nv_public)), SD_JSON_BUILD_PAIR_CONDITION(c->type != TPM2_REPORT_TYPE_SESSION_AUDIT && c->authenticated_data, "authenticatedData", SD_JSON_BUILD_STRING(c->authenticated_data)), + JSON_BUILD_PAIR_STRING_NON_EMPTY("sessionAuditHashAlg", session_audit_hash_alg), SD_JSON_BUILD_PAIR_VARIANT("attestInfo", attest_info), SD_JSON_BUILD_PAIR_VARIANT("signature", sig), SD_JSON_BUILD_PAIR_STRING("signaturePEM", sig_pem)); diff --git a/src/shared/tpm2-report.c b/src/shared/tpm2-report.c index 58f54f4858..545537b565 100644 --- a/src/shared/tpm2-report.c +++ b/src/shared/tpm2-report.c @@ -161,8 +161,10 @@ static int tpm2_generate_report_try( * Audit session exclusivity provides evidence that the sequence of attestations reflects a single * and consistent snapshot of the machine's state. */ + const TPMI_ALG_HASH audit_session_alg = TPM2_ALG_SHA256; + _cleanup_(tpm2_handle_freep) Tpm2Handle *audit_session = NULL; - r = tpm2_make_exclusive_audit_session(c, &audit_session); + r = tpm2_make_exclusive_audit_session(c, audit_session_alg, &audit_session); if (r < 0) return r; @@ -309,6 +311,7 @@ static int tpm2_generate_report_try( return log_oom_debug(); components[n_components++] = (Tpm2ReportComponent) { .type = TPM2_REPORT_TYPE_SESSION_AUDIT, + .session_audit_hash_alg = audit_session_alg, .attestation = TAKE_PTR(audit_info), .signature = TAKE_PTR(audit_signature), }; diff --git a/src/shared/tpm2-report.h b/src/shared/tpm2-report.h index c9ec9ff98c..78719cb4ab 100644 --- a/src/shared/tpm2-report.h +++ b/src/shared/tpm2-report.h @@ -39,6 +39,9 @@ typedef struct Tpm2ReportComponent { * data is the report digest we get. */ char *authenticated_data; + /* For session audit only */ + TPMI_ALG_HASH session_audit_hash_alg; + /* Common fields */ TPMS_ATTEST *attestation; TPMT_SIGNATURE *signature; diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c index f6bf2cc35a..65a4fdb8b0 100644 --- a/src/shared/tpm2-util.c +++ b/src/shared/tpm2-util.c @@ -4538,7 +4538,7 @@ static bool tpm2_is_audit_session(Tpm2Context *c, const Tpm2Handle *session) { return flags & TPMA_SESSION_AUDIT; } -int tpm2_make_exclusive_audit_session(Tpm2Context *c, Tpm2Handle **ret_session) { +int tpm2_make_exclusive_audit_session(Tpm2Context *c, TPMI_ALG_HASH hash, Tpm2Handle **ret_session) { TSS2_RC rc; int r; @@ -4547,6 +4547,11 @@ int tpm2_make_exclusive_audit_session(Tpm2Context *c, Tpm2Handle **ret_session) log_debug("Starting HMAC exclusive audit session."); + /* The caller can specify a hash algorithm, but we only support SHA-256 for now. */ + if (hash != TPM2_ALG_SHA256) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), + "Unsupported hash algorithm for audit session: only SHA-256 is supported"); + _cleanup_(tpm2_handle_freep) Tpm2Handle *session = NULL; r = tpm2_handle_new(c, &session); if (r < 0) @@ -4562,7 +4567,7 @@ int tpm2_make_exclusive_audit_session(Tpm2Context *c, Tpm2Handle **ret_session) /* nonceCaller= */ NULL, TPM2_SE_HMAC, &SESSION_TEMPLATE_SYM_NULL, - TPM2_ALG_SHA256, + hash, &session->esys_handle); if (rc != TSS2_RC_SUCCESS) return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), @@ -10404,7 +10409,7 @@ int tpm2_tpmt_public_to_pem(const TPMT_PUBLIC *public, char **ret) { #endif } -static const char* tpm2_hash_alg_to_string_tss2(TPMI_ALG_HASH alg) { +const char* tpm2_hash_alg_to_string_tss2(TPMI_ALG_HASH alg) { /* Note that this is different to tpm2_hash_alg_to_string because it * returns a string that aligns with those produced by TSS2 and compatible * with the TCG TSS2 JSON format. The JSON format is actually not case diff --git a/src/shared/tpm2-util.h b/src/shared/tpm2-util.h index 1d0ed81138..6820a3e5ce 100644 --- a/src/shared/tpm2-util.h +++ b/src/shared/tpm2-util.h @@ -143,7 +143,7 @@ int tpm2_pcr_values_hash_count(const Tpm2PCRValue *pcr_values, size_t n_pcr_valu int tpm2_tpml_pcr_selection_from_pcr_values(const Tpm2PCRValue *pcr_values, size_t n_pcr_values, TPML_PCR_SELECTION *ret_selection, TPM2B_DIGEST **ret_values, size_t *ret_n_values); int tpm2_make_encryption_session(Tpm2Context *c, const Tpm2Handle *primary, const Tpm2Handle *bind_key, Tpm2Handle **ret_session); -int tpm2_make_exclusive_audit_session(Tpm2Context *c, Tpm2Handle **ret_session); +int tpm2_make_exclusive_audit_session(Tpm2Context *c, TPMI_ALG_HASH hash, Tpm2Handle **ret_session); int tpm2_create_primary(Tpm2Context *c, const Tpm2Handle *session, ESYS_TR hierarchy, const TPM2B_PUBLIC *template, const TPM2B_SENSITIVE_CREATE *sensitive, TPM2B_PUBLIC **ret_public, Tpm2Handle **ret_handle); int tpm2_create(Tpm2Context *c, const Tpm2Handle *parent, const Tpm2Handle *session, const TPMT_PUBLIC *template, const TPMS_SENSITIVE_CREATE *sensitive, TPM2B_PUBLIC **ret_public, TPM2B_PRIVATE **ret_private); @@ -428,6 +428,7 @@ int tpm2_hmac_key_from_pin(Tpm2Context *c, const Tpm2Handle *session, const TPM2 int tpm2_tpmt_signature_to_pem(const TPMT_SIGNATURE *signature, char **ret); int tpm2_tpmt_public_to_pem(const TPMT_PUBLIC *public, char **ret); +const char* tpm2_hash_alg_to_string_tss2(TPMI_ALG_HASH alg); int tpm2_tpmt_signature_to_json(const TPMT_SIGNATURE *signature, sd_json_variant **ret); int tpm2_attest_info_to_json(const TPMT_SIG_SCHEME *scheme, const TPMS_ATTEST *attest, sd_json_variant **ret); int tpm2_tpmt_public_to_json(const TPMT_PUBLIC *public, sd_json_variant **ret); diff --git a/src/test/test-tpm2.c b/src/test/test-tpm2.c index 82c452a8a6..b854db6dc8 100644 --- a/src/test/test-tpm2.c +++ b/src/test/test-tpm2.c @@ -2328,7 +2328,7 @@ static void check_get_session_audit_digest(Tpm2Context *c) { ASSERT_OK(tpm2_create_primary(c, NULL, ESYS_TR_RH_OWNER, &template, NULL, NULL, &key)); _cleanup_(tpm2_handle_freep) Tpm2Handle *session = NULL; - ASSERT_OK(tpm2_make_exclusive_audit_session(c, &session)); + ASSERT_OK(tpm2_make_exclusive_audit_session(c, TPM2_ALG_SHA256, &session)); /* Use the session */ TPML_PCR_SELECTION pcrs; diff --git a/test/units/TEST-70-TPM2.report-sign.sh b/test/units/TEST-70-TPM2.report-sign.sh index 24ca9b9aff..21a7a1fd58 100755 --- a/test/units/TEST-70-TPM2.report-sign.sh +++ b/test/units/TEST-70-TPM2.report-sign.sh @@ -142,13 +142,6 @@ def hash(alg, data): return h.finalize() -def sha256(data): - """Digest the supplied data using SHA-256.""" - h = hashes.Hash(hashes.SHA256()) - h.update(data) - return h.finalize() - - # Helpers to serialize JSON encodings to the TPM wire format. def marshal_u8(v): @@ -356,9 +349,9 @@ def check_nvpcr(i, comp): sys.exit(f"component {i}: authenticatedData digest does not match attested extraData") -def check_session_audit(doc, key_name): +def check_session_audit(alg, doc, key_name): """Check the properties of the session-audit component.""" - digest = b"\x00" * 32 # Starting audit digest + digest = b"\x00" * HASHES[alg]().digest_size # Starting audit digest reported = None for comp in doc["components"]: @@ -392,11 +385,11 @@ def check_session_audit(doc, key_name): rp += marshal_tpmt_signature(comp["signature"]) # signature # Calculate cpHash and rpHash. - cp_hash = sha256(marshal_u32(cc) + names + cp) - rp_hash = sha256(marshal_u32(0) + marshal_u32(cc) + rp) + cp_hash = hash(alg, marshal_u32(cc) + names + cp) + rp_hash = hash(alg, marshal_u32(0) + marshal_u32(cc) + rp) # Extend the audit digest. - digest = sha256(digest + cp_hash + rp_hash) + digest = hash(alg, digest + cp_hash + rp_hash) if reported is None: sys.exit("no session audit component present") @@ -419,6 +412,7 @@ def main(): sys.exit("publicKeyPEM does not match the JSON public key") saw_report_binding = False + session_audit_digest_alg = None for i, comp in enumerate(data["components"]): scheme = comp["attestInfo"]["sig_scheme"] message = marshal_tpms_attest(comp["attestInfo"]["attest"]) @@ -454,6 +448,12 @@ def main(): sys.exit(f"component {i}: session audit extraData does not match the report digest") saw_report_binding = True + # This is hard-coded as SHA256 for now. + alg = comp.get("sessionAuditHashAlg") + if alg is None: + sys.exit(f"component {i}: missing sessionAuditHashAlg field") + session_audit_digest_alg = alg + print(comp["type"]) if not saw_report_binding: @@ -462,7 +462,7 @@ def main(): # The session audit digest must reproduce the audited command sequence that # produced the other components. key_name = digest_bytes_and_marshal_tpmt_ha(data["publicKey"]["nameAlg"], marshal_tpmt_public(data["publicKey"])) - check_session_audit(data, key_name) + check_session_audit(session_audit_digest_alg, data, key_name) if __name__ == "__main__":