From fc53809803cd2bc2434e312b19a18fa36776da12 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Wed, 12 Jun 2024 05:50:40 +0200 Subject: Adding upstream version 256. Signed-off-by: Daniel Baumann --- src/pcrlock/pcrlock-firmware.c | 8 +- src/pcrlock/pcrlock.c | 779 ++++++++++++++++++++++++++++++----------- 2 files changed, 574 insertions(+), 213 deletions(-) (limited to 'src/pcrlock') diff --git a/src/pcrlock/pcrlock-firmware.c b/src/pcrlock/pcrlock-firmware.c index 73c68c2..6fd7363 100644 --- a/src/pcrlock/pcrlock-firmware.c +++ b/src/pcrlock/pcrlock-firmware.c @@ -100,12 +100,12 @@ int validate_firmware_header( if (size < (uint64_t) offsetof(TCG_PCClientPCREvent, event) + (uint64_t) h->eventDataSize) return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Event log too short for TCG_PCClientPCREvent events data."); - if (h->pcrIndex != 0) - return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Event log header has unexpected PCR index %" PRIu32, h->pcrIndex); if (h->eventType != EV_NO_ACTION) - return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Event log header has unexpected event type 0x%" PRIx32, h->eventType); + return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Event log header has unexpected event type 0x%08" PRIx32 ". (Probably not a TPM2 event log?)", h->eventType); + if (h->pcrIndex != 0) + return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Event log header has unexpected PCR index %" PRIu32 ". (Probably not a TPM2 event log?)", h->pcrIndex); if (!memeqzero(h->digest, sizeof(h->digest))) - return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Event log header has unexpected non-zero digest."); + return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Event log header has unexpected non-zero digest. (Probably not a TPM2 event log?)"); if (h->eventDataSize < offsetof(TCG_EfiSpecIDEvent, digestSizes)) return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Event log header too short for TCG_EfiSpecIdEvent."); diff --git a/src/pcrlock/pcrlock.c b/src/pcrlock/pcrlock.c index dde4dd9..1716fb3 100644 --- a/src/pcrlock/pcrlock.c +++ b/src/pcrlock/pcrlock.c @@ -9,14 +9,18 @@ #include "ask-password-api.h" #include "blockdev-util.h" +#include "boot-entry.h" #include "build.h" #include "chase.h" +#include "color-util.h" #include "conf-files.h" +#include "creds-util.h" #include "efi-api.h" #include "env-util.h" #include "escape.h" #include "fd-util.h" #include "fileio.h" +#include "find-esp.h" #include "format-table.h" #include "format-util.h" #include "fs-util.h" @@ -39,13 +43,24 @@ #include "random-util.h" #include "recovery-key.h" #include "sort-util.h" +#include "string-table.h" #include "terminal-util.h" #include "tpm2-util.h" #include "unaligned.h" #include "unit-name.h" #include "utf8.h" +#include "varlink.h" +#include "varlink-io.systemd.PCRLock.h" #include "verbs.h" +typedef enum RecoveryPinMode { + RECOVERY_PIN_HIDE, /* generate a recovery PIN automatically, but don't show it (alias: "no") */ + RECOVERY_PIN_SHOW, /* generate a recovery PIN automatically, and display it to the user */ + RECOVERY_PIN_QUERY, /* asks the user for a PIN to use interactively (alias: "yes") */ + _RECOVERY_PIN_MODE_MAX, + _RECOVERY_PIN_MODE_INVALID = -EINVAL, +} RecoveryPinMode; + static PagerFlags arg_pager_flags = 0; static JsonFormatFlags arg_json_format_flags = JSON_FORMAT_OFF|JSON_FORMAT_NEWLINE; static char **arg_components = NULL; @@ -56,15 +71,19 @@ static bool arg_raw_description = false; static char *arg_location_start = NULL; static char *arg_location_end = NULL; static TPM2_HANDLE arg_nv_index = 0; -static bool arg_recovery_pin = false; +static RecoveryPinMode arg_recovery_pin = RECOVERY_PIN_HIDE; static char *arg_policy_path = NULL; static bool arg_force = false; +static BootEntryTokenType arg_entry_token_type = BOOT_ENTRY_TOKEN_AUTO; +static char *arg_entry_token = NULL; +static bool arg_varlink = false; STATIC_DESTRUCTOR_REGISTER(arg_components, strv_freep); STATIC_DESTRUCTOR_REGISTER(arg_pcrlock_path, freep); STATIC_DESTRUCTOR_REGISTER(arg_location_start, freep); STATIC_DESTRUCTOR_REGISTER(arg_location_end, freep); STATIC_DESTRUCTOR_REGISTER(arg_policy_path, freep); +STATIC_DESTRUCTOR_REGISTER(arg_entry_token, freep); #define PCRLOCK_SECUREBOOT_POLICY_PATH "/var/lib/pcrlock.d/240-secureboot-policy.pcrlock.d/generated.pcrlock" #define PCRLOCK_FIRMWARE_CODE_EARLY_PATH "/var/lib/pcrlock.d/250-firmware-code-early.pcrlock.d/generated.pcrlock" @@ -94,6 +113,14 @@ STATIC_DESTRUCTOR_REGISTER(arg_policy_path, freep); (UINT32_C(1) << TPM2_PCR_SHIM_POLICY) | \ (UINT32_C(1) << TPM2_PCR_SYSTEM_IDENTITY)) +static const char* recovery_pin_mode_table[_RECOVERY_PIN_MODE_MAX] = { + [RECOVERY_PIN_HIDE] = "hide", + [RECOVERY_PIN_SHOW] = "show", + [RECOVERY_PIN_QUERY] = "query", +}; + +DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING_WITH_BOOLEAN(recovery_pin_mode, RecoveryPinMode, RECOVERY_PIN_QUERY); + typedef struct EventLogRecordBank EventLogRecordBank; typedef struct EventLogRecord EventLogRecord; typedef struct EventLogRegisterBank EventLogRegisterBank; @@ -549,7 +576,7 @@ static int event_log_record_extract_firmware_description(EventLogRecord *rec) { case EV_SEPARATOR: { if (rec->firmware_payload_size != sizeof(uint32_t)) { - log_warning_errno(SYNTHETIC_ERRNO(EBADMSG), "EFI separator field has wrong size, ignoring."); + log_warning("EFI separator field has wrong size, ignoring."); goto invalid; } @@ -567,7 +594,7 @@ static int event_log_record_extract_firmware_description(EventLogRecord *rec) { break; default: - log_warning_errno(SYNTHETIC_ERRNO(EBADMSG), "Unexpected separator payload %" PRIu32 ".", val); + log_warning("Unexpected separator payload %" PRIu32 ", ignoring.", val); goto invalid; } @@ -585,7 +612,7 @@ static int event_log_record_extract_firmware_description(EventLogRecord *rec) { return log_error_errno(r, "Failed to make C string from EFI action string: %m"); if (!string_is_safe(d)) { - log_warning_errno(SYNTHETIC_ERRNO(EBADMSG), "Unsafe EFI action string in record, ignoring."); + log_warning("Unsafe EFI action string in record, ignoring."); goto invalid; } @@ -597,14 +624,14 @@ static int event_log_record_extract_firmware_description(EventLogRecord *rec) { case EV_EFI_GPT_EVENT: { if (rec->firmware_payload_size < sizeof(GptHeader)) { - log_warning_errno(SYNTHETIC_ERRNO(EBADMSG), "GPT measurement too short, ignoring."); + log_warning("GPT measurement too short, ignoring."); goto invalid; } const GptHeader *h = rec->firmware_payload; if (!gpt_header_has_signature(h)) { - log_warning_errno(SYNTHETIC_ERRNO(EBADMSG), "GPT measurement does not cover a GPT partition table header, ignoring."); + log_warning("GPT measurement does not cover a GPT partition table header, ignoring."); goto invalid; } @@ -628,7 +655,7 @@ static int event_log_record_extract_firmware_description(EventLogRecord *rec) { return log_oom(); if (string_has_cc(d, NULL)) { - log_warning_errno(SYNTHETIC_ERRNO(EBADMSG), "Unsafe EFI action string in record, ignoring."); + log_warning("Unsafe EFI action string in record, ignoring."); goto invalid; } @@ -644,7 +671,7 @@ static int event_log_record_extract_firmware_description(EventLogRecord *rec) { size_t left = rec->firmware_payload_size; if (left == 0) { - log_warning_errno(SYNTHETIC_ERRNO(EBADMSG), "Empty tagged PC client event, ignoring."); + log_warning("Empty tagged PC client event, ignoring."); goto invalid; } @@ -652,13 +679,13 @@ static int event_log_record_extract_firmware_description(EventLogRecord *rec) { uint64_t m; if (left < offsetof(TCG_PCClientTaggedEvent, taggedEventData)) { - log_warning_errno(SYNTHETIC_ERRNO(EBADMSG), "Tagged PC client event too short, ignoring."); + log_warning("Tagged PC client event too short, ignoring."); goto invalid; } m = offsetof(TCG_PCClientTaggedEvent, taggedEventData) + (uint64_t) tag->taggedEventDataSize; if (left < m) { - log_warning_errno(SYNTHETIC_ERRNO(EBADMSG), "Tagged PC client event data too short, ignoring."); + log_warning("Tagged PC client event data too short, ignoring."); goto invalid; } @@ -728,7 +755,7 @@ static int event_log_record_extract_firmware_description(EventLogRecord *rec) { case EV_EFI_PLATFORM_FIRMWARE_BLOB: { const UEFI_PLATFORM_FIRMWARE_BLOB *blob; if (rec->firmware_payload_size != sizeof(UEFI_PLATFORM_FIRMWARE_BLOB)) { - log_warning_errno(SYNTHETIC_ERRNO(EBADMSG), "EV_EFI_PLATFORM_FIRMWARE_BLOB of wrong size, ignoring."); + log_warning("EV_EFI_PLATFORM_FIRMWARE_BLOB of wrong size, ignoring."); goto invalid; } @@ -747,14 +774,14 @@ static int event_log_record_extract_firmware_description(EventLogRecord *rec) { bool end = false; if (rec->firmware_payload_size < offsetof(UEFI_IMAGE_LOAD_EVENT, devicePath)) { - log_warning_errno(SYNTHETIC_ERRNO(EBADMSG), "Device path too short, ignoring."); + log_warning("Device path too short, ignoring."); goto invalid; } load = rec->firmware_payload; if (load->lengthOfDevicePath != rec->firmware_payload_size - offsetof(UEFI_IMAGE_LOAD_EVENT, devicePath)) { - log_warning_errno(SYNTHETIC_ERRNO(EBADMSG), "Device path size does not match, ignoring."); + log_warning("Device path size does not match, ignoring."); goto invalid; } @@ -764,7 +791,7 @@ static int event_log_record_extract_firmware_description(EventLogRecord *rec) { for (;;) { if (left == 0) { if (!end) { - log_warning_errno(SYNTHETIC_ERRNO(EBADMSG), "Garbage after device path end, ignoring."); + log_warning("Garbage after device path end, ignoring."); goto invalid; } @@ -772,12 +799,12 @@ static int event_log_record_extract_firmware_description(EventLogRecord *rec) { } if (end) { - log_warning_errno(SYNTHETIC_ERRNO(EBADMSG), "Garbage after device path end, ignoring."); + log_warning("Garbage after device path end, ignoring."); goto invalid; } if (left < offsetof(packed_EFI_DEVICE_PATH, path) || left < dp->length) { - log_warning_errno(SYNTHETIC_ERRNO(EBADMSG), "Device path element too short, ignoring."); + log_warning("Device path element too short, ignoring."); goto invalid; } @@ -896,7 +923,7 @@ static int event_log_load_firmware(EventLog *el) { payload_size == 17 && memcmp(payload, "StartupLocality", sizeof("StartupLocality")) == 0) { if (el->startup_locality_found) - return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "StartupLocality event found twice!"); + return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "StartupLocality event found twice."); el->startup_locality = ((const uint8_t*) payload)[sizeof("StartupLocality")]; el->startup_locality_found = true; @@ -926,23 +953,30 @@ static int event_log_load_firmware(EventLog *el) { assert(event->digests.count == n_algorithms); for (size_t i = 0; i < n_algorithms; i++, ha = ha_next) { - ha_next = (const uint8_t*) ha + offsetof(TPMT_HA, digest) + algorithms[i].digestSize; - /* The TPMT_HA is not aligned in the record, hence read the hashAlg field via an unaligned read */ assert_cc(__builtin_types_compatible_p(uint16_t, typeof(TPMI_ALG_HASH))); uint16_t hash_alg = unaligned_read_ne16((const uint8_t*) ha + offsetof(TPMT_HA, hashAlg)); - if (hash_alg != algorithms[i].algorithmId) - return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Hash algorithms in event log record don't match log."); + /* On some systems (some HyperV?) the order of hash algorithms announced in the + * header does not match the order in the records. Let's hence search for the right + * mapping */ + size_t j; + for (j = 0; j < n_algorithms; j++) + if (hash_alg == algorithms[j].algorithmId) + break; + if (j >= n_algorithms) + return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Hash algorithms in event log record not among those advertised by log header."); - if (!tpm2_hash_alg_to_string(algorithms[i].algorithmId)) + ha_next = (const uint8_t*) ha + offsetof(TPMT_HA, digest) + algorithms[j].digestSize; + + if (!tpm2_hash_alg_to_string(hash_alg)) continue; r = event_log_record_add_bank( record, - algorithms[i].algorithmId, + hash_alg, (const uint8_t*) ha + offsetof(TPMT_HA, digest), - algorithms[i].digestSize, + algorithms[j].digestSize, /* ret= */ NULL); if (r < 0) return log_error_errno(r, "Failed to add bank to event log record: %m"); @@ -1010,7 +1044,7 @@ static int event_log_record_parse_json(EventLogRecord *record, JsonVariant *j) { h = json_variant_by_key(k, "digest"); if (!h) - return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "'digests' field lacks 'digest' field"); + return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "'digests' field lacks 'digest' field."); r = json_variant_unhex(h, &hash, &hash_size); if (r < 0) @@ -1294,7 +1328,7 @@ static int event_log_calculate_pcrs(EventLog *el) { rec_b = event_log_record_find_bank(*rr, el->algorithms[i]); if (!rec_b) { - log_warning_errno(SYNTHETIC_ERRNO(ENXIO), "Record with missing bank '%s', ignoring.", n); + log_warning("Record with missing bank '%s', ignoring.", n); continue; } @@ -1932,40 +1966,6 @@ static int event_log_map_components(EventLog *el) { return event_log_validate_fully_recognized(el); } -static void hsv_to_rgb( - double h, double s, double v, - uint8_t* ret_r, uint8_t *ret_g, uint8_t *ret_b) { - - double c, x, m, r, g, b; - - assert(s >= 0 && s <= 100); - assert(v >= 0 && v <= 100); - assert(ret_r); - assert(ret_g); - assert(ret_b); - - c = (s / 100.0) * (v / 100.0); - x = c * (1 - fabs(fmod(h / 60.0, 2) - 1)); - m = (v / 100) - c; - - if (h >= 0 && h < 60) - r = c, g = x, b = 0.0; - else if (h >= 60 && h < 120) - r = x, g = c, b = 0.0; - else if (h >= 120 && h < 180) - r = 0.0, g = c, b = x; - else if (h >= 180 && h < 240) - r = 0.0, g = x, b = c; - else if (h >= 240 && h < 300) - r = x, g = 0.0, b = c; - else - r = c, g = 0.0, b = x; - - *ret_r = (uint8_t) ((r + m) * 255); - *ret_g = (uint8_t) ((g + m) * 255); - *ret_b = (uint8_t) ((b + m) * 255); -} - #define ANSI_TRUE_COLOR_MAX (7U + 3U + 1U + 3U + 1U + 3U + 2U) static const char *ansi_true_color(uint8_t r, uint8_t g, uint8_t b, char ret[static ANSI_TRUE_COLOR_MAX]) { @@ -2341,7 +2341,7 @@ static int event_determine_primary_algorithm(EventLog *el) { } FOREACH_ARRAY(alg, el->algorithms, el->n_algorithms) { - /* If we have SHA256, focus on that that */ + /* If we have SHA256, focus on that */ if (*alg == TPM2_ALG_SHA256) { el->primary_algorithm = *alg; @@ -2439,6 +2439,75 @@ static int verb_show_log(int argc, char *argv[], void *userdata) { return 0; } +static int event_log_record_to_cel(EventLogRecord *record, uint64_t *recnum, JsonVariant **ret) { + _cleanup_(json_variant_unrefp) JsonVariant *ja = NULL, *fj = NULL; + JsonVariant *cd = NULL; + const char *ct = NULL; + int r; + + assert(record); + assert(recnum); + assert(ret); + + LIST_FOREACH(banks, bank, record->banks) { + r = json_variant_append_arrayb( + &ja, JSON_BUILD_OBJECT( + JSON_BUILD_PAIR_STRING("hashAlg", tpm2_hash_alg_to_string(bank->algorithm)), + JSON_BUILD_PAIR_HEX("digest", bank->hash.buffer, bank->hash.size))); + if (r < 0) + return log_error_errno(r, "Failed to append CEL digest entry: %m"); + } + + if (!ja) { + r = json_variant_new_array(&ja, NULL, 0); + if (r < 0) + return log_error_errno(r, "Failed to allocate JSON array: %m"); + } + + if (EVENT_LOG_RECORD_IS_FIRMWARE(record)) { + _cleanup_free_ char *et = NULL; + const char *z; + + z = tpm2_log_event_type_to_string(record->firmware_event_type); + if (z) { + _cleanup_free_ char *b = NULL; + + b = strreplace(z, "-", "_"); + if (!b) + return log_oom(); + + et = strjoin("EV_", ascii_strupper(b)); + if (!et) + return log_oom(); + } else if (asprintf(&et, "%" PRIu32, record->firmware_event_type) < 0) + return log_oom(); + + r = json_build(&fj, JSON_BUILD_OBJECT( + JSON_BUILD_PAIR_STRING("event_type", et), + JSON_BUILD_PAIR_HEX("event_data", record->firmware_payload, record->firmware_payload_size))); + if (r < 0) + return log_error_errno(r, "Failed to build firmware event data: %m"); + + cd = fj; + ct = "pcclient_std"; + } else if (EVENT_LOG_RECORD_IS_USERSPACE(record)) { + cd = record->userspace_content; + ct = "systemd"; + } + + r = json_build(ret, + JSON_BUILD_OBJECT( + JSON_BUILD_PAIR_UNSIGNED("pcr", record->pcr), + JSON_BUILD_PAIR_UNSIGNED("recnum", ++(*recnum)), + JSON_BUILD_PAIR_VARIANT("digests", ja), + JSON_BUILD_PAIR_CONDITION(ct, "content_type", JSON_BUILD_STRING(ct)), + JSON_BUILD_PAIR_CONDITION(cd, "content", JSON_BUILD_VARIANT(cd)))); + if (r < 0) + return log_error_errno(r, "Failed to make CEL record: %m"); + + return 0; +} + static int verb_show_cel(int argc, char *argv[], void *userdata) { _cleanup_(json_variant_unrefp) JsonVariant *array = NULL; _cleanup_(event_log_freep) EventLog *el = NULL; @@ -2456,64 +2525,13 @@ static int verb_show_cel(int argc, char *argv[], void *userdata) { /* Output the event log in TCG CEL-JSON. */ FOREACH_ARRAY(rr, el->records, el->n_records) { - _cleanup_(json_variant_unrefp) JsonVariant *ja = NULL, *fj = NULL; - EventLogRecord *record = *rr; - JsonVariant *cd = NULL; - const char *ct = NULL; - - LIST_FOREACH(banks, bank, record->banks) { - r = json_variant_append_arrayb( - &ja, JSON_BUILD_OBJECT( - JSON_BUILD_PAIR_STRING("hashAlg", tpm2_hash_alg_to_string(bank->algorithm)), - JSON_BUILD_PAIR_HEX("digest", bank->hash.buffer, bank->hash.size))); - if (r < 0) - return log_error_errno(r, "Failed to append CEL digest entry: %m"); - } - - if (!ja) { - r = json_variant_new_array(&ja, NULL, 0); - if (r < 0) - return log_error_errno(r, "Failed to allocate JSON array: %m"); - } - - if (EVENT_LOG_RECORD_IS_FIRMWARE(record)) { - _cleanup_free_ char *et = NULL; - const char *z; - - z = tpm2_log_event_type_to_string(record->firmware_event_type); - if (z) { - _cleanup_free_ char *b = NULL; - - b = strreplace(z, "-", "_"); - if (!b) - return log_oom(); - - et = strjoin("EV_", ascii_strupper(b)); - if (!et) - return log_oom(); - } else if (asprintf(&et, "%" PRIu32, record->firmware_event_type) < 0) - return log_oom(); + _cleanup_(json_variant_unrefp) JsonVariant *cel = NULL; - r = json_build(&fj, JSON_BUILD_OBJECT( - JSON_BUILD_PAIR_STRING("event_type", et), - JSON_BUILD_PAIR_HEX("event_data", record->firmware_payload, record->firmware_payload_size))); - if (r < 0) - return log_error_errno(r, "Failed to build firmware event data: %m"); - - cd = fj; - ct = "pcclient_std"; - } else if (EVENT_LOG_RECORD_IS_USERSPACE(record)) { - cd = record->userspace_content; - ct = "systemd"; - } + r = event_log_record_to_cel(*rr, &recnum, &cel); + if (r < 0) + return r; - r = json_variant_append_arrayb(&array, - JSON_BUILD_OBJECT( - JSON_BUILD_PAIR_UNSIGNED("pcr", record->pcr), - JSON_BUILD_PAIR_UNSIGNED("recnum", ++recnum), - JSON_BUILD_PAIR_VARIANT("digests", ja), - JSON_BUILD_PAIR_CONDITION(ct, "content_type", JSON_BUILD_STRING(ct)), - JSON_BUILD_PAIR_CONDITION(cd, "content", JSON_BUILD_VARIANT(cd)))); + r = json_variant_append_array(&array, cel); if (r < 0) return log_error_errno(r, "Failed to append CEL record: %m"); } @@ -2603,17 +2621,17 @@ static int verb_list_components(int argc, char *argv[], void *userdata) { } } - if (table_get_rows(table) > 1 || !FLAGS_SET(arg_json_format_flags, JSON_FORMAT_OFF)) { + if (!table_isempty(table) || !FLAGS_SET(arg_json_format_flags, JSON_FORMAT_OFF)) { r = table_print_with_pager(table, arg_json_format_flags, arg_pager_flags, /* show_header= */ true); if (r < 0) return log_error_errno(r, "Failed to output table: %m"); } if (FLAGS_SET(arg_json_format_flags, JSON_FORMAT_OFF)) { - if (table_get_rows(table) > 1) - printf("\n%zu components listed.\n", table_get_rows(table) - 1); - else + if (table_isempty(table)) printf("No components defined.\n"); + else + printf("\n%zu components listed.\n", table_get_rows(table) - 1); } return 0; @@ -2661,7 +2679,7 @@ static int make_pcrlock_record( assert_se(a = tpm2_hash_alg_to_string(*pa)); assert_se(md = EVP_get_digestbyname(a)); hash_ssize = EVP_MD_size(md); - assert_se(hash_ssize > 0); + assert(hash_ssize > 0); hash_usize = hash_ssize; hash = malloc(hash_usize); @@ -2690,6 +2708,101 @@ static int make_pcrlock_record( return 0; } +static void evp_md_ctx_free_all(EVP_MD_CTX *(*md)[TPM2_N_HASH_ALGORITHMS]) { + assert(md); + FOREACH_ARRAY(alg, *md, TPM2_N_HASH_ALGORITHMS) + if (*alg) + EVP_MD_CTX_free(*alg); +} + +static int make_pcrlock_record_from_stream( + uint32_t pcr_mask, + FILE *f, + JsonVariant **ret_records) { + + _cleanup_(json_variant_unrefp) JsonVariant *digests = NULL; + _cleanup_(evp_md_ctx_free_all) EVP_MD_CTX *mdctx[TPM2_N_HASH_ALGORITHMS] = {}; + int r; + + assert(f); + assert(ret_records); + + for (size_t i = 0; i < TPM2_N_HASH_ALGORITHMS; i++) { + const char *a; + const EVP_MD *md; + + assert_se(a = tpm2_hash_alg_to_string(tpm2_hash_algorithms[i])); + assert_se(md = EVP_get_digestbyname(a)); + + mdctx[i] = EVP_MD_CTX_new(); + if (!mdctx[i]) + return log_oom(); + + if (EVP_DigestInit_ex(mdctx[i], md, NULL) != 1) + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Failed to initialize message digest for %s.", a); + } + + for (;;) { + uint8_t buffer[64*1024]; + size_t n; + + n = fread(buffer, 1, sizeof(buffer), f); + if (ferror(f)) + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to read file."); + if (n == 0 && feof(f)) + break; + + for (size_t i = 0; i < TPM2_N_HASH_ALGORITHMS; i++) + if (EVP_DigestUpdate(mdctx[i], buffer, n) != 1) + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Unable to hash data."); + } + + for (size_t i = 0; i < TPM2_N_HASH_ALGORITHMS; i++) { + const char *a; + int hash_ssize; + unsigned hash_usize; + + assert_se(a = tpm2_hash_alg_to_string(tpm2_hash_algorithms[i])); + hash_ssize = EVP_MD_CTX_size(mdctx[i]); + assert(hash_ssize > 0 && hash_ssize <= EVP_MAX_MD_SIZE); + hash_usize = hash_ssize; + unsigned char hash[hash_usize]; + + if (EVP_DigestFinal_ex(mdctx[i], hash, &hash_usize) != 1) + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Failed to finalize hash context for algorithn '%s'.", a); + + r = json_variant_append_arrayb( + &digests, + JSON_BUILD_OBJECT( + JSON_BUILD_PAIR("hashAlg", JSON_BUILD_STRING(a)), + JSON_BUILD_PAIR("digest", JSON_BUILD_HEX(hash, hash_usize)))); + if (r < 0) + return log_error_errno(r, "Failed to build JSON digest object: %m"); + } + + for (uint32_t i = 0; i < TPM2_PCRS_MAX; i++) { + _cleanup_(json_variant_unrefp) JsonVariant *record = NULL; + + if (!FLAGS_SET(pcr_mask, UINT32_C(1) << i)) + continue; + + r = json_build(&record, + JSON_BUILD_OBJECT( + JSON_BUILD_PAIR("pcr", JSON_BUILD_UNSIGNED(i)), + JSON_BUILD_PAIR("digests", JSON_BUILD_VARIANT(digests)))); + if (r < 0) + return log_error_errno(r, "Failed to build record object: %m"); + + r = json_variant_append_array(ret_records, record); + if (r < 0) + return log_error_errno(r, "Failed to append to JSON array: %m"); + } + + return 0; +} + static const char *pcrlock_path(const char *default_pcrlock_path) { return arg_pcrlock_path ?: arg_pcrlock_auto ? default_pcrlock_path : NULL; } @@ -2753,10 +2866,8 @@ static int unlink_pcrlock(const char *default_pcrlock_path) { } static int verb_lock_raw(int argc, char *argv[], void *userdata) { - _cleanup_(json_variant_unrefp) JsonVariant *array = NULL; - _cleanup_free_ char *data = NULL; + _cleanup_(json_variant_unrefp) JsonVariant *records = NULL; _cleanup_fclose_ FILE *f = NULL; - size_t size; int r; if (arg_pcr_mask == 0) @@ -2768,26 +2879,11 @@ static int verb_lock_raw(int argc, char *argv[], void *userdata) { return log_error_errno(errno, "Failed to open '%s': %m", argv[1]); } - r = read_full_stream(f ?: stdin, &data, &size); + r = make_pcrlock_record_from_stream(arg_pcr_mask, f ?: stdin, &records); if (r < 0) - return log_error_errno(r, "Failed to read data from stdin: %m"); - - for (uint32_t i = 0; i < TPM2_PCRS_MAX; i++) { - _cleanup_(json_variant_unrefp) JsonVariant *record = NULL; - - if (!FLAGS_SET(arg_pcr_mask, UINT32_C(1) << i)) - continue; - - r = make_pcrlock_record(i, data, size, &record); - if (r < 0) - return r; - - r = json_variant_append_array(&array, record); - if (r < 0) - return log_error_errno(r, "Failed to append to JSON array: %m"); - } + return r; - return write_pcrlock(array, NULL); + return write_pcrlock(records, NULL); } static int verb_unlock_simple(int argc, char *argv[], void *userdata) { @@ -2815,7 +2911,7 @@ static int verb_lock_secureboot_policy(int argc, char *argv[], void *userdata) { /* Generates expected records from the current SecureBoot state, as readable in the EFI variables * right now. */ - FOREACH_ARRAY(vv, variables, ELEMENTSOF(variables)) { + FOREACH_ELEMENT(vv, variables) { _cleanup_(json_variant_unrefp) JsonVariant *record = NULL; _cleanup_free_ char *name = NULL; @@ -3099,7 +3195,7 @@ static int verb_lock_gpt(int argc, char *argv[], void *userdata) { if (n < 0) return log_error_errno(errno, "Failed to read GPT header of block device: %m"); if ((size_t) n != sizeof(h)) - return log_error_errno(SYNTHETIC_ERRNO(EIO), "Short read trying to read GPT header: %m"); + return log_error_errno(SYNTHETIC_ERRNO(EIO), "Short read trying to read GPT header."); /* Try a couple of sector sizes */ for (size_t sz = 512; sz <= 4096; sz <<= 1) { @@ -3152,7 +3248,7 @@ static int verb_lock_gpt(int argc, char *argv[], void *userdata) { if (n < 0) return log_error_errno(errno, "Failed to read GPT partition table entries: %m"); if ((size_t) n != member_bufsz) - return log_error_errno(SYNTHETIC_ERRNO(EIO), "Short read while reading GPT partition table entries: %m"); + return log_error_errno(SYNTHETIC_ERRNO(EIO), "Short read while reading GPT partition table entries."); size_t vdata_size = le32toh(p->header_size) + sizeof(le64_t) + member_size * n_members; _cleanup_free_ void *vdata = malloc0(vdata_size); @@ -3795,10 +3891,9 @@ static int verb_unlock_kernel_cmdline(int argc, char *argv[], void *userdata) { } static int verb_lock_kernel_initrd(int argc, char *argv[], void *userdata) { - _cleanup_(json_variant_unrefp) JsonVariant *record = NULL, *array = NULL; - _cleanup_free_ void *data = NULL; + _cleanup_(json_variant_unrefp) JsonVariant *records = NULL; _cleanup_fclose_ FILE *f = NULL; - size_t size; + uint32_t pcr_mask = UINT32_C(1) << TPM2_PCR_KERNEL_INITRD; int r; if (argc >= 2) { @@ -3807,19 +3902,11 @@ static int verb_lock_kernel_initrd(int argc, char *argv[], void *userdata) { return log_error_errno(errno, "Failed to open '%s': %m", argv[1]); } - r = read_full_stream(f ?: stdin, (char**) &data, &size); - if (r < 0) - return log_error_errno(r, "Failed to read data from stdin: %m"); - - r = make_pcrlock_record(TPM2_PCR_KERNEL_INITRD /* = 9 */, data, size, &record); + r = make_pcrlock_record_from_stream(pcr_mask, f ?: stdin, &records); if (r < 0) return r; - r = json_variant_new_array(&array, &record, 1); - if (r < 0) - return log_error_errno(r, "Failed to create record array: %m"); - - r = write_pcrlock(array, PCRLOCK_KERNEL_INITRD_PATH); + r = write_pcrlock(records, PCRLOCK_KERNEL_INITRD_PATH); if (r < 0) return r; @@ -4197,7 +4284,129 @@ static int remove_policy_file(const char *path) { return 1; } -static int verb_make_policy(int argc, char *argv[], void *userdata) { +static int determine_boot_policy_file(char **ret) { + _cleanup_free_ char *path = NULL, *fn = NULL, *joined = NULL; + sd_id128_t machine_id; + int r; + + assert(ret); + + r = find_xbootldr_and_warn( + /* root= */ NULL, + /* path= */ NULL, + /* unprivileged_mode= */ false, + &path, + /* ret_uuid= */ NULL, + /* ret_devid= */ NULL); + if (r < 0) { + if (r != -ENOKEY) + return log_error_errno(r, "Failed to find XBOOTLDR partition: %m"); + + r = find_esp_and_warn( + /* root= */ NULL, + /* path= */ NULL, + /* unprivileged_mode= */ false, + &path, + /* ret_part= */ NULL, + /* ret_pstart= */ NULL, + /* ret_psize= */ NULL, + /* ret_uuid= */ NULL, + /* ret_devid= */ NULL); + if (r < 0) { + if (r != -ENOKEY) + return log_error_errno(r, "Failed to find ESP partition: %m"); + + *ret = NULL; + return 0; /* not found! */ + } + } + + r = sd_id128_get_machine(&machine_id); + if (r < 0) + return log_error_errno(r, "Failed to read machine ID: %m"); + + r = boot_entry_token_ensure( + /* root= */ NULL, + "/etc/kernel", + machine_id, + /* machine_id_is_random = */ false, + &arg_entry_token_type, + &arg_entry_token); + if (r < 0) + return r; + + fn = strjoin("pcrlock.", arg_entry_token, ".cred"); + if (!fn) + return log_oom(); + + if (!filename_is_valid(fn)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Credential name '%s' would not be a valid file name, refusing.", fn); + + joined = path_join(path, "loader/credentials", fn); + if (!joined) + return log_oom(); + + *ret = TAKE_PTR(joined); + return 1; /* found! */ +} + +static int write_boot_policy_file(const char *json_text) { + _cleanup_free_ char *boot_policy_file = NULL; + int r; + + assert(json_text); + + r = determine_boot_policy_file(&boot_policy_file); + if (r < 0) + return r; + if (r == 0) { + log_info("Did not find XBOOTLDR/ESP partition, not writing boot policy file."); + return 0; + } + + _cleanup_free_ char *c = NULL; + r = path_extract_filename(boot_policy_file, &c); + if (r < 0) + return log_error_errno(r, "Failed to extract file name from %s: %m", boot_policy_file); + + ascii_strlower(c); /* lowercase this file, no matter what, since stored on VFAT, and we don't want to + * run into case change incompatibilities */ + + _cleanup_(iovec_done) struct iovec encoded = {}; + r = encrypt_credential_and_warn( + CRED_AES256_GCM_BY_NULL, + c, + now(CLOCK_REALTIME), + /* not_after= */ USEC_INFINITY, + /* tpm2_device= */ NULL, + /* tpm2_hash_pcr_mask= */ 0, + /* tpm2_pubkey_path= */ NULL, + /* tpm2_pubkey_path_mask= */ 0, + UID_INVALID, + &IOVEC_MAKE_STRING(json_text), + CREDENTIAL_ALLOW_NULL, + &encoded); + if (r < 0) + return log_error_errno(r, "Failed to encode policy as credential: %m"); + + _cleanup_free_ char *base64_buf = NULL; + ssize_t base64_size; + base64_size = base64mem_full(encoded.iov_base, encoded.iov_len, 79, &base64_buf); + if (base64_size < 0) + return base64_size; + + r = write_string_file( + boot_policy_file, + base64_buf, + WRITE_STRING_FILE_ATOMIC|WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_SYNC|WRITE_STRING_FILE_MKDIR_0755); + if (r < 0) + return log_error_errno(r, "Failed to write boot policy file to '%s': %m", boot_policy_file); + + log_info("Written new boot policy to '%s'.", boot_policy_file); + return 1; +} + +static int make_policy(bool force, RecoveryPinMode recovery_pin_mode) { int r; /* Here's how this all works: after predicting all possible PCR values for next boot (with @@ -4207,7 +4416,7 @@ static int verb_make_policy(int argc, char *argv[], void *userdata) { * policies). * * Whenever we want to lock an encrypted object (for example FDE) against this policy, we'll use a - * PolicyAuthorizeNV epxression that pins the NV index in the policy, and permits access to any + * PolicyAuthorizeNV expression that pins the NV index in the policy, and permits access to any * policies matching the current NV index contents. * * We grant world-readable read access to the NV index. Write access is controlled by a PIN (which we @@ -4272,11 +4481,11 @@ static int verb_make_policy(int argc, char *argv[], void *userdata) { if (arg_nv_index != 0 && old_policy.nv_index != arg_nv_index) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Stored policy references different NV index (0x%x) than specified (0x%x), refusing.", old_policy.nv_index, arg_nv_index); - if (!arg_force && + if (!force && old_policy.algorithm == el->primary_algorithm && tpm2_pcr_prediction_equal(&old_policy.prediction, &new_prediction, el->primary_algorithm)) { log_info("Prediction is identical to current policy, skipping update."); - return EXIT_SUCCESS; + return 0; /* NOP */ } } @@ -4321,19 +4530,21 @@ static int verb_make_policy(int argc, char *argv[], void *userdata) { /* Acquire a recovery PIN, either from the user, or create a randomized one */ _cleanup_(erase_and_freep) char *pin = NULL; - if (arg_recovery_pin) { + if (recovery_pin_mode == RECOVERY_PIN_QUERY) { r = getenv_steal_erase("PIN", &pin); if (r < 0) return log_error_errno(r, "Failed to acquire PIN from environment: %m"); if (r == 0) { _cleanup_(strv_free_erasep) char **l = NULL; + AskPasswordRequest req = { + .message = "Recovery PIN", + .id = "pcrlock-recovery-pin", + .credential = "pcrlock.recovery-pin", + }; + r = ask_password_auto( - "Recovery PIN", - /* icon= */ NULL, - /* id= */ "pcrlock-recovery-pin", - /* key_name= */ NULL, - /* credential_name= */ "systemd-pcrlock.recovery-pin", + &req, /* until= */ 0, /* flags= */ 0, &l); @@ -4348,16 +4559,16 @@ static int verb_make_policy(int argc, char *argv[], void *userdata) { } } else if (!have_old_policy) { - char rnd[256]; - - r = crypto_random_bytes(rnd, sizeof(rnd)); + r = make_recovery_key(&pin); if (r < 0) return log_error_errno(r, "Failed to generate a randomized recovery PIN: %m"); - (void) base64mem(rnd, sizeof(rnd), &pin); - explicit_bzero_safe(rnd, sizeof(rnd)); - if (!pin) - return log_oom(); + if (recovery_pin_mode == RECOVERY_PIN_SHOW) + printf("%s Selected recovery PIN is: %s%s%s\n", + special_glyph(SPECIAL_GLYPH_LOCK_AND_KEY), + ansi_highlight_cyan(), + pin, + ansi_normal()); } _cleanup_(tpm2_handle_freep) Tpm2Handle *nv_handle = NULL; @@ -4375,7 +4586,7 @@ static int verb_make_policy(int argc, char *argv[], void *userdata) { CLEANUP_ERASE(auth); if (pin) { - r = tpm2_get_pin_auth(TPM2_ALG_SHA256, pin, &auth); + r = tpm2_auth_value_from_pin(TPM2_ALG_SHA256, pin, &auth); if (r < 0) return log_error_errno(r, "Failed to hash PIN: %m"); } else { @@ -4442,15 +4653,28 @@ static int verb_make_policy(int argc, char *argv[], void *userdata) { log_info("Retrieved PIN from TPM2 in %s.", FORMAT_TIMESPAN(usec_sub_unsigned(now(CLOCK_MONOTONIC), pin_start_usec), 1)); } - TPM2B_NV_PUBLIC nv_public = {}; + /* Now convert the PIN into an HMAC-SHA256 key that we can use in PolicySigned to protect access to the nvindex with */ + _cleanup_(tpm2_handle_freep) Tpm2Handle *pin_handle = NULL; + r = tpm2_hmac_key_from_pin(tc, encryption_session, &auth, &pin_handle); + if (r < 0) + return r; + TPM2B_NV_PUBLIC nv_public = {}; usec_t nv_index_start_usec = now(CLOCK_MONOTONIC); if (!iovec_is_set(&nv_blob)) { + _cleanup_(Esys_Freep) TPM2B_NAME *pin_name = NULL; + r = tpm2_get_name( + tc, + pin_handle, + &pin_name); + if (r < 0) + return log_error_errno(r, "Failed to get name of PIN from TPM2: %m"); + TPM2B_DIGEST recovery_policy_digest = TPM2B_DIGEST_MAKE(NULL, TPM2_SHA256_DIGEST_SIZE); - r = tpm2_calculate_policy_auth_value(&recovery_policy_digest); + r = tpm2_calculate_policy_signed(&recovery_policy_digest, pin_name); if (r < 0) - return log_error_errno(r, "Failed to calculate authentication value policy: %m"); + return log_error_errno(r, "Failed to calculate PolicySigned policy: %m"); log_debug("Allocating NV index to write PCR policy to..."); r = tpm2_define_policy_nv_index( @@ -4458,8 +4682,6 @@ static int verb_make_policy(int argc, char *argv[], void *userdata) { encryption_session, arg_nv_index, &recovery_policy_digest, - pin, - &auth, &nv_index, &nv_handle, &nv_public); @@ -4469,10 +4691,6 @@ static int verb_make_policy(int argc, char *argv[], void *userdata) { return log_error_errno(r, "Failed to allocate NV index: %m"); } - r = tpm2_set_auth_binary(tc, nv_handle, &auth); - if (r < 0) - return log_error_errno(r, "Failed to set authentication value on NV index: %m"); - _cleanup_(tpm2_handle_freep) Tpm2Handle *policy_session = NULL; r = tpm2_make_policy_session( tc, @@ -4482,9 +4700,11 @@ static int verb_make_policy(int argc, char *argv[], void *userdata) { if (r < 0) return log_error_errno(r, "Failed to allocate policy session: %m"); - r = tpm2_policy_auth_value( + r = tpm2_policy_signed_hmac_sha256( tc, policy_session, + pin_handle, + &IOVEC_MAKE(auth.buffer, auth.size), /* ret_policy_digest= */ NULL); if (r < 0) return log_error_errno(r, "Failed to submit authentication value policy: %m"); @@ -4595,9 +4815,15 @@ static int verb_make_policy(int argc, char *argv[], void *userdata) { log_info("Written new policy to '%s' and digest to TPM2 NV index 0x%x.", path, nv_index); + (void) write_boot_policy_file(text); + log_info("Overall time spent: %s", FORMAT_TIMESPAN(usec_sub_unsigned(now(CLOCK_MONOTONIC), start_usec), 1)); - return 0; + return 1; /* installed new policy */ +} + +static int verb_make_policy(int argc, char *argv[], void *userdata) { + return make_policy(arg_force, arg_recovery_pin); } static int undefine_policy_nv_index( @@ -4653,8 +4879,8 @@ static int undefine_policy_nv_index( return 0; } -static int verb_remove_policy(int argc, char *argv[], void *userdata) { - int r; +static int remove_policy(void) { + int ret = 0, r; _cleanup_(tpm2_pcrlock_policy_done) Tpm2PCRLockPolicy policy = {}; r = tpm2_pcrlock_policy_load(arg_policy_path, &policy); @@ -4669,22 +4895,31 @@ static int verb_remove_policy(int argc, char *argv[], void *userdata) { r = undefine_policy_nv_index(policy.nv_index, &policy.nv_handle, &policy.srk_handle); if (r < 0) log_notice("Failed to remove NV index, assuming data out of date, removing policy file."); - } - if (arg_policy_path) { - r = remove_policy_file(arg_policy_path); - if (r < 0) - return r; - - return 0; - } else { - int ret = 0; + RET_GATHER(ret, r); + } + if (arg_policy_path) + RET_GATHER(ret, remove_policy_file(arg_policy_path)); + else { RET_GATHER(ret, remove_policy_file("/var/lib/systemd/pcrlock.json")); RET_GATHER(ret, remove_policy_file("/run/systemd/pcrlock.json")); - - return ret; } + + _cleanup_free_ char *boot_policy_file = NULL; + r = determine_boot_policy_file(&boot_policy_file); + if (r == 0) + log_info("Did not find XBOOTLDR/ESP partition, not removing boot policy file."); + else if (r > 0) { + RET_GATHER(ret, remove_policy_file(boot_policy_file)); + } else + RET_GATHER(ret, r); + + return ret; +} + +static int verb_remove_policy(int argc, char *argv[], void *userdata) { + return remove_policy(); } static int help(int argc, char *argv[], void *userdata) { @@ -4744,6 +4979,8 @@ static int help(int argc, char *argv[], void *userdata) { " --pcrlock=PATH .pcrlock file to write expected PCR measurement to\n" " --policy=PATH JSON file to write policy output to\n" " --force Write policy even if it matches existing policy\n" + " --entry-token=machine-id|os-id|os-image-id|auto|literal:…\n" + " Boot entry token to use for this installation\n" "\nSee the %2$s for details.\n", program_invocation_short_name, link, @@ -4769,6 +5006,7 @@ static int parse_argv(int argc, char *argv[]) { ARG_PCRLOCK, ARG_POLICY, ARG_FORCE, + ARG_ENTRY_TOKEN, }; static const struct option options[] = { @@ -4785,6 +5023,7 @@ static int parse_argv(int argc, char *argv[]) { { "pcrlock", required_argument, NULL, ARG_PCRLOCK }, { "policy", required_argument, NULL, ARG_POLICY }, { "force", no_argument, NULL, ARG_FORCE }, + { "entry-token", required_argument, NULL, ARG_ENTRY_TOKEN }, {} }; @@ -4900,13 +5139,13 @@ static int parse_argv(int argc, char *argv[]) { } case ARG_RECOVERY_PIN: - r = parse_boolean_argument("--recovery-pin", optarg, &arg_recovery_pin); - if (r < 0) - return r; + arg_recovery_pin = recovery_pin_mode_from_string(optarg); + if (arg_recovery_pin < 0) + return log_error_errno(arg_recovery_pin, "Failed to parse --recovery-pin= mode: %s", optarg); break; case ARG_PCRLOCK: - if (isempty(optarg) || streq(optarg, "-")) + if (empty_or_dash(optarg)) arg_pcrlock_path = mfree(arg_pcrlock_path); else { r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_pcrlock_path); @@ -4918,7 +5157,7 @@ static int parse_argv(int argc, char *argv[]) { break; case ARG_POLICY: - if (isempty(optarg) || streq(optarg, "-")) + if (empty_or_dash(optarg)) arg_policy_path = mfree(arg_policy_path); else { r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_policy_path); @@ -4932,6 +5171,12 @@ static int parse_argv(int argc, char *argv[]) { arg_force = true; break; + case ARG_ENTRY_TOKEN: + r = parse_boot_entry_token_type(optarg, &arg_entry_token_type, &arg_entry_token); + if (r < 0) + return r; + break; + case '?': return -EINVAL; @@ -4952,6 +5197,14 @@ static int parse_argv(int argc, char *argv[]) { return log_oom(); } + r = varlink_invocation(VARLINK_ALLOW_ACCEPT); + if (r < 0) + return log_error_errno(r, "Failed to check if invoked in Varlink mode: %m"); + if (r > 0) { + arg_varlink = true; + arg_pager_flags |= PAGER_DISABLE; + } + return 1; } @@ -4994,17 +5247,125 @@ static int pcrlock_main(int argc, char *argv[]) { return dispatch_verb(argc, argv, verbs, NULL); } +static int vl_method_read_event_log(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata) { + _cleanup_(event_log_freep) EventLog *el = NULL; + uint64_t recnum = 0; + int r; + + assert(link); + + if (json_variant_elements(parameters) > 0) + return varlink_error_invalid_parameter(link, parameters); + + el = event_log_new(); + if (!el) + return log_oom(); + + r = event_log_load(el); + if (r < 0) + return r; + + _cleanup_(json_variant_unrefp) JsonVariant *rec_cel = NULL; + + FOREACH_ARRAY(rr, el->records, el->n_records) { + + if (rec_cel) { + r = varlink_notifyb(link, + JSON_BUILD_OBJECT(JSON_BUILD_PAIR_VARIANT("record", rec_cel))); + if (r < 0) + return r; + + rec_cel = json_variant_unref(rec_cel); + } + + r = event_log_record_to_cel(*rr, &recnum, &rec_cel); + if (r < 0) + return r; + } + + return varlink_replyb(link, + JSON_BUILD_OBJECT(JSON_BUILD_PAIR_CONDITION(rec_cel, "record", JSON_BUILD_VARIANT(rec_cel)))); +} + +typedef struct MethodMakePolicyParameters { + bool force; +} MethodMakePolicyParameters; + +static int vl_method_make_policy(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata) { + static const JsonDispatch dispatch_table[] = { + { "force", JSON_VARIANT_BOOLEAN, json_dispatch_boolean, offsetof(MethodMakePolicyParameters, force), 0 }, + {} + }; + MethodMakePolicyParameters p = {}; + int r; + + assert(link); + + r = varlink_dispatch(link, parameters, dispatch_table, &p); + if (r != 0) + return r; + + r = make_policy(p.force, /* recovery_key= */ RECOVERY_PIN_HIDE); + if (r < 0) + return r; + if (r == 0) + return varlink_error(link, "io.systemd.PCRLock.NoChange", NULL); + + return varlink_reply(link, NULL); +} + +static int vl_method_remove_policy(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata) { + int r; + + assert(link); + + if (json_variant_elements(parameters) > 0) + return varlink_error_invalid_parameter(link, parameters); + + r = remove_policy(); + if (r < 0) + return r; + + return varlink_reply(link, NULL); +} + static int run(int argc, char *argv[]) { int r; - log_show_color(true); - log_parse_environment(); - log_open(); + log_setup(); r = parse_argv(argc, argv); if (r <= 0) return r; + if (arg_varlink) { + _cleanup_(varlink_server_unrefp) VarlinkServer *varlink_server = NULL; + + /* Invocation as Varlink service */ + + r = varlink_server_new(&varlink_server, VARLINK_SERVER_ROOT_ONLY); + if (r < 0) + return log_error_errno(r, "Failed to allocate Varlink server: %m"); + + r = varlink_server_add_interface(varlink_server, &vl_interface_io_systemd_PCRLock); + if (r < 0) + return log_error_errno(r, "Failed to add Varlink interface: %m"); + + r = varlink_server_bind_method_many( + varlink_server, + "io.systemd.PCRLock.ReadEventLog", vl_method_read_event_log, + "io.systemd.PCRLock.MakePolicy", vl_method_make_policy, + "io.systemd.PCRLock.RemovePolicy", vl_method_remove_policy); + if (r < 0) + return log_error_errno(r, "Failed to bind Varlink methods: %m"); + + r = varlink_server_loop_auto(varlink_server); + if (r < 0) + return log_error_errno(r, "Failed to run Varlink event loop: %m"); + + return EXIT_SUCCESS; + } + return pcrlock_main(argc, argv); } -- cgit v1.2.3