]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
pcrlock: reorder function definitions to match --help
authorZbigniew Jędrzejewski-Szmek <zbyszek@amutable.com>
Thu, 16 Apr 2026 17:22:11 +0000 (19:22 +0200)
committerZbigniew Jędrzejewski-Szmek <zbyszek@amutable.com>
Thu, 23 Apr 2026 15:41:00 +0000 (17:41 +0200)
The order was random, different in the source code, different in the
verb list, different in --help. Order source code by --help to make
the next patch manageable.

src/pcrlock/pcrlock.c

index fd8b9e95a4fd098371a60e9f7f5c63fb7a60009e..7820774fe08db0c1b356065c1acc4e8f9f4f1999 100644 (file)
@@ -2968,2102 +2968,2102 @@ static int unlink_pcrlock(const char *default_pcrlock_path) {
         return 0;
 }
 
-static int verb_lock_raw(int argc, char *argv[], uintptr_t _data, void *userdata) {
-        _cleanup_(sd_json_variant_unrefp) sd_json_variant *records = NULL;
-        _cleanup_fclose_ FILE *f = NULL;
-        int r;
-
-        if (arg_pcr_mask == 0)
-                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No PCR specified, refusing.");
+static int event_log_reduce_to_safe_pcrs(EventLog *el, uint32_t *pcrs) {
+        _cleanup_free_ char *dropped = NULL, *kept = NULL;
 
-        if (argc >= 2) {
-                f = fopen(argv[1], "re");
-                if (!f)
-                        return log_error_errno(errno, "Failed to open '%s': %m", argv[1]);
-        }
+        assert(el);
+        assert(pcrs);
 
-        r = make_pcrlock_record_from_stream(arg_pcr_mask, f ?: stdin, &records);
-        if (r < 0)
-                return r;
+        /* When we compile a new PCR policy we don't want to bind to PCRs which are fishy for one of three
+         * reasons:
+         *
+         * 1. The PCR value doesn't match the event log
+         * 2. The event log for the PCR contains measurements we don't know responsible components for
+         * 3. The event log for the PCR does not contain measurements for components we know
+         *
+         * This function checks for the three conditions and drops the PCR from the mask.
+         */
 
-        return write_pcrlock(records, NULL);
-}
+        for (uint32_t pcr = 0; pcr < TPM2_PCRS_MAX; pcr++) {
 
-static int verb_unlock_simple(int argc, char *argv[], uintptr_t _data, void *userdata) {
-        return unlink_pcrlock(NULL);
-}
+                if (!BIT_SET(*pcrs, pcr))
+                        continue;
 
-static int verb_lock_secureboot_policy(int argc, char *argv[], uintptr_t _data, void *userdata) {
-        static const struct {
-                sd_id128_t id;
-                const char *name;
-                int synthesize_empty; /* 0 → fail, > 0 → synthesize empty db, < 0 → skip */
-        } variables[] = {
-                { EFI_VENDOR_GLOBAL,   "SecureBoot", 0 },
-                { EFI_VENDOR_GLOBAL,   "PK",         1 },
-                { EFI_VENDOR_GLOBAL,   "KEK",        1 },
-                { EFI_VENDOR_DATABASE, "db",         1 },
-                { EFI_VENDOR_DATABASE, "dbx",        1 },
-                { EFI_VENDOR_DATABASE, "dbt",       -1 },
-                { EFI_VENDOR_DATABASE, "dbr",       -1 },
-        };
+                if (!event_log_pcr_checks_out(el, el->registers + pcr)) {
+                        log_notice("PCR %" PRIu32 " (%s) value does not match event log. Removing from set of PCRs.", pcr, strna(tpm2_pcr_index_to_string(pcr)));
+                        goto drop;
+                }
 
-        _cleanup_(sd_json_variant_unrefp) sd_json_variant *array = NULL;
-        int r;
+                if (!el->registers[pcr].fully_recognized) {
+                        log_notice("PCR %" PRIu32 " (%s) event log contains unrecognized measurements. Removing from set of PCRs.", pcr, strna(tpm2_pcr_index_to_string(pcr)));
+                        goto drop;
+                }
 
-        /* Generates expected records from the current SecureBoot state, as readable in the EFI variables
-         * right now. */
+                if (BIT_SET(el->missing_component_pcrs, pcr)) {
+                        log_notice("PCR %" PRIu32 " (%s) is touched by component we can't find in event log. Removing from set of PCRs.", pcr, strna(tpm2_pcr_index_to_string(pcr)));
+                        goto drop;
+                }
 
-        FOREACH_ELEMENT(vv, variables) {
-                _cleanup_(sd_json_variant_unrefp) sd_json_variant *record = NULL;
+                log_info("PCR %" PRIu32 " (%s) matches event log and fully consists of recognized measurements. Including in set of PCRs.", pcr, strna(tpm2_pcr_index_to_string(pcr)));
 
-                _cleanup_free_ char *name = NULL;
-                if (asprintf(&name, "%s-" SD_ID128_UUID_FORMAT_STR, vv->name, SD_ID128_FORMAT_VAL(vv->id)) < 0)
+                if (strextendf_with_separator(&kept, ", ", "%" PRIu32 " (%s)", pcr, tpm2_pcr_index_to_string(pcr)) < 0)
                         return log_oom();
 
-                _cleanup_free_ void *data = NULL;
-                size_t data_size;
-                r = efi_get_variable(name, NULL, &data, &data_size);
-                if (r < 0) {
-                        if (r != -ENOENT || vv->synthesize_empty == 0)
-                                return log_error_errno(r, "Failed to read EFI variable '%s': %m", name);
-                        if (vv->synthesize_empty < 0)
-                                continue;
-
-                        /* If the main database variables are not set we don't consider this an error, but
-                         * measure an empty database instead. */
-                        log_debug("EFI variable %s is not set, synthesizing empty variable for measurement.", name);
-                        data_size = 0;
-                }
+                continue;
 
-                _cleanup_free_ char16_t* name16 = utf8_to_utf16(vv->name, SIZE_MAX);
-                if (!name16)
-                        return log_oom();
-                size_t name16_bytes = char16_strlen(name16) * 2;
+        drop:
+                *pcrs &= ~(UINT32_C(1) << pcr);
 
-                size_t vdata_size = offsetof(UEFI_VARIABLE_DATA, unicodeName) + name16_bytes + data_size;
-                _cleanup_free_ UEFI_VARIABLE_DATA *vdata = malloc(vdata_size);
-                if (!vdata)
+                if (strextendf_with_separator(&dropped, ", ", "%" PRIu32 " (%s)", pcr, tpm2_pcr_index_to_string(pcr)) < 0)
                         return log_oom();
+        }
 
-                *vdata = (UEFI_VARIABLE_DATA) {
-                        .unicodeNameLength = name16_bytes / 2,
-                        .variableDataLength = data_size,
-                };
-
-                efi_id128_to_guid(vv->id, vdata->variableName);
-                memcpy(mempcpy(vdata->unicodeName, name16, name16_bytes), data, data_size);
-
-                r = make_pcrlock_record(TPM2_PCR_SECURE_BOOT_POLICY /* =7 */, vdata, vdata_size, &record);
-                if (r < 0)
-                        return r;
+        if (dropped)
+                log_notice("PCRs dropped from protection mask: %s", dropped);
+        else
+                log_debug("No PCRs dropped from protection mask.");
 
-                r = sd_json_variant_append_array(&array, record);
-                if (r < 0)
-                        return log_error_errno(r, "Failed to append to JSON array: %m");
-        }
+        if (kept)
+                log_notice("PCRs in protection mask: %s", kept);
+        else
+                log_notice("No PCRs kept in protection mask.");
 
-        return write_pcrlock(array, PCRLOCK_SECUREBOOT_POLICY_PATH);
+        return 0;
 }
 
-static int verb_unlock_secureboot_policy(int argc, char *argv[], uintptr_t _data, void *userdata) {
-        return unlink_pcrlock(PCRLOCK_SECUREBOOT_POLICY_PATH);
-}
+static int pcr_prediction_add_result(
+                Tpm2PCRPrediction *context,
+                Tpm2PCRPredictionResult *result,
+                uint32_t pcr,
+                const char *path) {
 
-static int event_log_record_is_secureboot_variable(EventLogRecord *rec, sd_id128_t uuid, const char *name) {
-        _cleanup_free_ char *found_name = NULL;
-        sd_id128_t found_uuid;
+        _cleanup_free_ Tpm2PCRPredictionResult *copy = NULL;
         int r;
 
-        assert(rec);
-        assert(name);
-
-        if (!EVENT_LOG_RECORD_IS_FIRMWARE(rec))
-                return false;
-
-        if (rec->pcr != TPM2_PCR_SECURE_BOOT_POLICY)
-                return false;
-
-        if (rec->event_payload_valid != EVENT_PAYLOAD_VALID_YES)
-                return false;
+        assert(context);
+        assert(result);
 
-        if (rec->firmware_event_type != EV_EFI_VARIABLE_DRIVER_CONFIG)
-                return false;
+        copy = newdup(Tpm2PCRPredictionResult, result, 1);
+        if (!copy)
+                return log_oom();
 
-        r = event_log_record_parse_variable_data(rec, &found_uuid, &found_name);
-        if (r == -EBADMSG)
-                return false;
+        r = ordered_set_ensure_put(context->results + pcr, &tpm2_pcr_prediction_result_hash_ops, copy);
+        if (r == -EEXIST) /* Multiple identical results for the same PCR are totally expected */
+                return 0;
         if (r < 0)
-                return r;
+                return log_error_errno(r, "Failed to insert result into set: %m");
 
-        if (!sd_id128_equal(found_uuid, uuid))
-                return false;
+        log_debug("Added prediction result %u for PCR %" PRIu32 " (path: %s)", ordered_set_size(context->results[pcr]), pcr, strempty(path));
 
-        return streq(found_name, name);
+        TAKE_PTR(copy);
+        return 0;
 }
 
-static bool event_log_record_is_secureboot_authority(EventLogRecord *rec) {
-        assert(rec);
-
-        if (!EVENT_LOG_RECORD_IS_FIRMWARE(rec))
-                return false;
-
-        if (rec->pcr != TPM2_PCR_SECURE_BOOT_POLICY)
-                return false;
+static const EVP_MD* evp_from_tpm2_alg(uint16_t alg) {
+        const char *name;
 
-        if (rec->event_payload_valid != EVENT_PAYLOAD_VALID_YES)
-                return false;
+        name = tpm2_hash_alg_to_string(alg);
+        if (!name)
+                return NULL;
 
-        return rec->firmware_event_type == EV_EFI_VARIABLE_AUTHORITY;
+        return EVP_get_digestbyname(name);
 }
 
-static int event_log_ensure_secureboot_consistency(EventLog *el) {
-        static const struct {
-                sd_id128_t id;
-                const char *name;
-                bool required;
-        } table[] = {
-                { EFI_VENDOR_GLOBAL,   "SecureBoot", true  },
-                { EFI_VENDOR_GLOBAL,   "PK",         true  },
-                { EFI_VENDOR_GLOBAL,   "KEK",        true  },
-                { EFI_VENDOR_DATABASE, "db",         true  },
-                { EFI_VENDOR_DATABASE, "dbx",        true  },
-                { EFI_VENDOR_DATABASE, "dbt",        false },
-                { EFI_VENDOR_DATABASE, "dbr",        false },
-                // FIXME: ensure we also find the separator here
-        };
-
-        EventLogRecord *records[ELEMENTSOF(table)] = {};
-        EventLogRecord *first_authority = NULL;
-
-        assert(el);
+static int event_log_component_variant_calculate(
+                Tpm2PCRPredictionResult *result,
+                EventLogComponentVariant *variant,
+                uint32_t pcr) {
 
-        /* Ensures that the PCR 7 records are complete and in order. Before we lock down PCR 7 we want to
-         * ensure its state is actually consistent. */
+        assert(result);
+        assert(variant);
 
-        FOREACH_ARRAY(rr, el->records, el->n_records) {
+        FOREACH_ARRAY(rr, variant->records, variant->n_records) {
                 EventLogRecord *rec = *rr;
-                size_t found = SIZE_MAX;
-
-                if (event_log_record_is_secureboot_authority(rec)) {
-                        if (first_authority)
-                                continue;
 
-                        first_authority = rec;
-                        // FIXME: also check that each authority record's data is also listed in 'db'
+                if (!EVENT_LOG_RECORD_IS_PCR(rec))
                         continue;
-                }
 
-                for (size_t i = 0; i < ELEMENTSOF(table); i++)
-                        if (event_log_record_is_secureboot_variable(rec, table[i].id, table[i].name)) {
-                                found = i;
-                                break;
-                        }
-                if (found == SIZE_MAX)
+                if (rec->pcr != pcr)
                         continue;
 
-                /* Require the authority records always come *after* database measurements */
-                if (first_authority)
-                        return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "SecureBoot authority before variable, refusing.");
+                for (size_t i = 0; i < TPM2_N_HASH_ALGORITHMS; i++) {
+                        _cleanup_(EVP_MD_CTX_freep) EVP_MD_CTX *md_ctx = NULL;
+                        EventLogRecordBank *b;
 
-                /* Check for duplicates */
-                if (records[found])
-                        return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Duplicate '%s' record, refusing.", rec->description);
+                        if (result->hash[i].size <= 0) /* already invalidated */
+                                continue;
 
-                /* Check for order */
-                for (size_t j = found + 1; j < ELEMENTSOF(table); j++)
-                        if (records[j])
-                                return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "'%s' record before '%s' record, refusing.", records[j]->description, rec->description);
+                        b = event_log_record_find_bank(rec, tpm2_hash_algorithms[i]);
+                        if (!b) {
+                                /* Can't calculate, hence invalidate */
+                                result->hash[i] = (TPM2B_DIGEST) {};
+                                continue;
+                        }
 
-                records[found] = rec;
-        }
+                        md_ctx = EVP_MD_CTX_new();
+                        if (!md_ctx)
+                                return log_oom();
 
-        /* Check for existence */
-        for (size_t i = 0; i < ELEMENTSOF(table); i++)
-                if (table[i].required && !records[i])
-                        return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Required record '%s' not found, refusing.", table[i].name);
-
-        /* At this point we know that all required variables have been measured, in the right order. */
-        return 0;
-}
-
-static int verb_lock_secureboot_authority(int argc, char *argv[], uintptr_t _data, void *userdata) {
-        _cleanup_(sd_json_variant_unrefp) sd_json_variant *array = NULL;
-        _cleanup_(event_log_freep) EventLog *el = NULL;
-        int r;
+                        const EVP_MD *md = ASSERT_PTR(evp_from_tpm2_alg(tpm2_hash_algorithms[i]));
 
-        /* Lock down the EV_EFI_VARIABLE_AUTHORITY records from the existing log. Note that there's not too
-         * much value in locking this down too much, since it stores only the result of the primary database
-         * checks, and that's what we should bind policy to. Moreover it's hard to predict, since extension
-         * card firmware validation will result in additional records here. */
+                        int sz = EVP_MD_size(md);
+                        assert(sz > 0);
+                        assert((size_t) sz <= sizeof_field(TPM2B_DIGEST, buffer));
 
-        if (!is_efi_secure_boot()) {
-                log_info("SecureBoot disabled, not generating authority .pcrlock file.");
-                return unlink_pcrlock(PCRLOCK_SECUREBOOT_AUTHORITY_PATH);
-        }
+                        assert(sz == tpm2_hash_alg_to_size(tpm2_hash_algorithms[i]));
 
-        el = event_log_new();
-        if (!el)
-                return log_oom();
+                        assert(result->hash[i].size == (size_t) sz);
+                        assert(b->hash.size == (size_t) sz);
 
-        r = event_log_add_algorithms_from_environment(el);
-        if (r < 0)
-                return r;
+                        if (EVP_DigestInit_ex(md_ctx, md, NULL) != 1)
+                                return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to initialize message digest.");
 
-        r = event_log_load(el);
-        if (r < 0)
-                return r;
+                        if (EVP_DigestUpdate(md_ctx, result->hash[i].buffer, sz) != 1)
+                                return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to hash bank value.");
 
-        r = event_log_read_pcrs(el);
-        if (r < 0)
-                return r;
+                        if (EVP_DigestUpdate(md_ctx, b->hash.buffer, sz) != 1)
+                                return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to hash data value.");
 
-        r = event_log_calculate_pcrs(el);
-        if (r < 0)
-                return r;
+                        unsigned l = (unsigned) sz;
+                        if (EVP_DigestFinal_ex(md_ctx, result->hash[i].buffer, &l) != 1)
+                                return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to finalize message digest.");
 
-        /* Before we base anything on the event log records, let's check that the event log state checks
-         * out. */
+                        assert(l == (unsigned) sz);
+                }
+        }
 
-        r = event_log_pcr_mask_checks_out(el, UINT32_C(1) << TPM2_PCR_SECURE_BOOT_POLICY);
-        if (r < 0)
-                return r;
+        return 0;
+}
 
-        r = event_log_validate_record_hashes(el);
-        if (r < 0)
-                return r;
+static int event_log_predict_pcrs(
+                EventLog *el,
+                Tpm2PCRPrediction *context,
+                Tpm2PCRPredictionResult *parent_result,
+                size_t component_index,
+                uint32_t pcr,
+                const char *path) {
 
-        r = event_log_ensure_secureboot_consistency(el);
-        if (r < 0)
-                return r;
+        EventLogComponent *component;
+        int count = 0, r;
 
-        FOREACH_ARRAY(rr, el->records, el->n_records) {
-                _cleanup_(sd_json_variant_unrefp) sd_json_variant *digests = NULL;
-                EventLogRecord *rec = *rr;
+        assert(el);
+        assert(context);
+        assert(parent_result);
 
-                if (!event_log_record_is_secureboot_authority(rec))
-                        continue;
+        /* Check if we reached the end of the components, generate a result, and backtrack */
+        if (component_index >= el->n_components ||
+            (arg_location_end && strcmp(el->components[component_index]->id, arg_location_end) > 0)) {
+                r = pcr_prediction_add_result(context, parent_result, pcr, path);
+                if (r < 0)
+                        return r;
 
-                log_debug("Locking down authority '%s'.", strna(rec->description));
+                return 1;
+        }
 
-                LIST_FOREACH(banks, bank, rec->banks) {
-                        r = sd_json_variant_append_arraybo(
-                                        &digests,
-                                        SD_JSON_BUILD_PAIR_STRING("hashAlg", tpm2_hash_alg_to_string(bank->algorithm)),
-                                        SD_JSON_BUILD_PAIR("digest", SD_JSON_BUILD_HEX(bank->hash.buffer, bank->hash.size)));
-                        if (r < 0)
-                                return log_error_errno(r, "Failed to build digests array: %m");
-                }
+        component = ASSERT_PTR(el->components[component_index]);
 
-                r = sd_json_variant_append_arraybo(
-                                &array,
-                                SD_JSON_BUILD_PAIR_UNSIGNED("pcr", rec->pcr),
-                                SD_JSON_BUILD_PAIR_VARIANT("digests", digests));
+        /* Check if we are just about to process a component after start, if so record a result and continue. */
+        if (arg_location_start && strcmp(component->id, arg_location_start) > 0) {
+                r = pcr_prediction_add_result(context, parent_result, pcr, path);
                 if (r < 0)
-                        return log_error_errno(r, "Failed to build record array: %m");
+                        return r;
         }
 
-        return write_pcrlock(array, PCRLOCK_SECUREBOOT_AUTHORITY_PATH);
-}
-
-static int verb_unlock_secureboot_authority(int argc, char *argv[], uintptr_t _data, void *userdata) {
-        return unlink_pcrlock(PCRLOCK_SECUREBOOT_AUTHORITY_PATH);
-}
-
-static int verb_lock_gpt(int argc, char *argv[], uintptr_t _data, void *userdata) {
-        _cleanup_(sd_json_variant_unrefp) sd_json_variant *array = NULL, *record = NULL;
-        _cleanup_(sd_device_unrefp) sd_device *d = NULL;
-        uint8_t h[2 * 4096]; /* space for at least two 4K sectors. GPT header should definitely be in here */
-        uint64_t start, n_members, member_size;
-        _cleanup_close_ int fd = -EBADF;
-        const GptHeader *p;
-        size_t found = 0;
-        ssize_t n;
-        int r;
+        FOREACH_ARRAY(ii, component->variants, component->n_variants) {
+                _cleanup_free_ Tpm2PCRPredictionResult *result = NULL;
+                EventLogComponentVariant *variant = *ii;
+                _cleanup_free_ char *subpath = NULL;
 
-        r = block_device_new_from_path(
-                        argc >= 2 ? argv[1] : "/",
-                        BLOCK_DEVICE_LOOKUP_WHOLE_DISK|BLOCK_DEVICE_LOOKUP_BACKING|BLOCK_DEVICE_LOOKUP_ORIGINATING,
-                        &d);
-        if (r < 0)
-                return log_error_errno(r, "Failed to determine root block device: %m");
+                /* Operate on a copy of the result */
 
-        fd = sd_device_open(d, O_CLOEXEC|O_RDONLY|O_NOCTTY);
-        if (fd < 0)
-                return log_error_errno(fd, "Failed to open root block device: %m");
+                if (path)
+                        subpath = strjoin(path, ":", component->id);
+                else
+                        subpath = strdup(component->id);
+                if (!subpath)
+                        return log_oom();
 
-        n = pread(fd, &h, sizeof(h), 0);
-        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.");
+                if (!streq(component->id, variant->id))
+                        if (!strextend(&subpath, "@", variant->id))
+                                return log_oom();
 
-        /* Try a couple of sector sizes */
-        for (size_t sz = 512; sz <= 4096; sz <<= 1) {
-                assert(sizeof(h) >= sz * 2);
-                p = (const GptHeader*) (h + sz); /* 2nd sector */
+                result = newdup(Tpm2PCRPredictionResult, parent_result, 1);
+                if (!result)
+                        return log_oom();
 
-                if (!gpt_header_has_signature(p))
-                        continue;
+                r = event_log_component_variant_calculate(
+                                result,
+                                variant,
+                                pcr);
+                if (r < 0)
+                        return r;
 
-                if (found != 0)
-                        return log_error_errno(SYNTHETIC_ERRNO(ENOTUNIQ),
-                                               "Disk has partition table for multiple sector sizes, refusing.");
+                r = event_log_predict_pcrs(
+                                el,
+                                context,
+                                result,
+                                component_index + 1, /* Next component */
+                                pcr,
+                                subpath);
+                if (r < 0)
+                        return r;
 
-                found = sz;
+                count += r;
         }
 
-        if (found == 0)
-                return log_error_errno(SYNTHETIC_ERRNO(EBADMSG),
-                                       "Disk does not have GPT partition table, refusing.");
+        return count;
+}
 
-        p = (const GptHeader*) (h + found);
+static ssize_t event_log_calculate_component_combinations(EventLog *el) {
+        ssize_t count = 1;
+        assert(el);
 
-        if (le32toh(p->header_size) > found)
-                return log_error_errno(SYNTHETIC_ERRNO(EBADMSG),
-                                       "GPT header size over long (%" PRIu32 "), refusing.", le32toh(p->header_size));
+        FOREACH_ARRAY(cc, el->components, el->n_components) {
+                EventLogComponent *c = *cc;
 
-        start = le64toh(p->partition_entry_lba);
-        if (start > UINT64_MAX / found)
-                return log_error_errno(SYNTHETIC_ERRNO(EBADMSG),
-                                       "Partition table start offset overflow, refusing.");
+                assert(c->n_variants > 0);
 
-        member_size = le32toh(p->size_of_partition_entry);
-        if (member_size < sizeof(GptPartitionEntry))
-                return log_error_errno(SYNTHETIC_ERRNO(EBADMSG),
-                                       "Partition entry size too short, refusing.");
+                /* Overflow check */
+                if (c->n_variants > (size_t) (SSIZE_MAX/count))
+                        return log_error_errno(SYNTHETIC_ERRNO(E2BIG), "Too many component combinations.");
+                count *= c->n_variants;
+        }
 
-        n_members = le32toh(p->number_of_partition_entries);
-        uint64_t member_bufsz = n_members * member_size;
-        if (member_bufsz > 1U*1024U*1024U)
-                return log_error_errno(SYNTHETIC_ERRNO(EBADMSG),
-                                       "Partition table size too large, refusing.");
+        return count;
+}
 
-        member_bufsz = ROUND_UP(member_bufsz, found);
+static int event_log_show_predictions(Tpm2PCRPrediction *context, uint16_t alg) {
+        int r;
 
-        _cleanup_free_ void *members = malloc(member_bufsz);
-        if (!members)
-                return log_oom();
+        assert(context);
 
-        n = pread(fd, members, member_bufsz, start * found);
-        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.");
+        pager_open(arg_pager_flags);
 
-        size_t vdata_size = le32toh(p->header_size) + sizeof(le64_t) + member_size * n_members;
-        _cleanup_free_ void *vdata = malloc0(vdata_size);
-        if (!vdata)
-                return log_oom();
+        if (sd_json_format_enabled(arg_json_format_flags)) {
+                _cleanup_(sd_json_variant_unrefp) sd_json_variant *j = NULL;
 
-        void *n_measured_entries = mempcpy(vdata, p, sizeof(GptHeader)); /* n_measured_entries is a 64bit value */
+                for (size_t i = 0; i < TPM2_N_HASH_ALGORITHMS; i++) {
+                        _cleanup_(sd_json_variant_unrefp) sd_json_variant *aj = NULL;
 
-        void *qq = (uint8_t*) n_measured_entries + sizeof(le64_t);
+                        r = tpm2_pcr_prediction_to_json(
+                                        context,
+                                        tpm2_hash_algorithms[i],
+                                        &aj);
+                        if (r < 0)
+                                return r;
 
-        for (uint64_t i = 0; i < n_members; i++) {
-                const GptPartitionEntry *entry = (const GptPartitionEntry*) ((const uint8_t*) members + (member_size * i));
+                        if (sd_json_variant_elements(aj) == 0)
+                                continue;
 
-                if (memeqzero(entry->partition_type_guid, sizeof(entry->partition_type_guid)))
-                        continue;
+                        r = sd_json_variant_set_field(
+                                        &j,
+                                        tpm2_hash_alg_to_string(tpm2_hash_algorithms[i]),
+                                        aj);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to add prediction bank to object: %m");
+                }
 
-                qq = mempcpy(qq, entry, member_size);
-                unaligned_write_le64(n_measured_entries, unaligned_read_le64(n_measured_entries) + 1);
+                if (!j) {
+                        r = sd_json_variant_new_object(&j, NULL, 0);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to allocated empty object: %m");
+                }
+
+                sd_json_variant_dump(j, arg_json_format_flags, /* f= */ NULL, /* prefix= */ NULL);
+                return 0;
         }
 
-        vdata_size = (uint8_t*) qq - (uint8_t*) vdata;
+        for (uint32_t pcr = 0; pcr < TPM2_PCRS_MAX; pcr++) {
+                Tpm2PCRPredictionResult *result;
+                if (!BIT_SET(context->pcrs, pcr))
+                        continue;
 
-        r = make_pcrlock_record(TPM2_PCR_BOOT_LOADER_CONFIG /* =5 */, vdata, vdata_size, &record);
-        if (r < 0)
-                return r;
+                if (ordered_set_isempty(context->results[pcr])) {
+                        printf("No results for PCR %u (%s).\n", pcr, tpm2_pcr_index_to_string(pcr));
+                        continue;
+                }
 
-        r = sd_json_variant_new_array(&array, &record, 1);
-        if (r < 0)
-                return log_error_errno(r, "Failed to append to JSON array: %m");
+                printf("%sResults for PCR %u (%s):%s\n", ansi_underline(), pcr, tpm2_pcr_index_to_string(pcr), ansi_normal());
 
-        return write_pcrlock(array, PCRLOCK_GPT_PATH);
-}
+                ORDERED_SET_FOREACH(result, context->results[pcr]) {
 
-static int verb_unlock_gpt(int argc, char *argv[], uintptr_t _data, void *userdata) {
-        return unlink_pcrlock(PCRLOCK_GPT_PATH);
-}
+                        _cleanup_free_ char *aa = NULL, *h = NULL;
+                        const char *a;
 
-static bool event_log_record_is_separator(const EventLogRecord *rec) {
-        assert(rec);
+                        TPM2B_DIGEST *hash = tpm2_pcr_prediction_result_get_hash(result, alg);
+                        if (!hash)
+                                continue;
 
-        /* Recognizes EV_SEPARATOR events */
+                        a = ASSERT_PTR(tpm2_hash_alg_to_string(alg));
+                        aa = strdup(a);
+                        if (!aa)
+                                return log_oom();
 
-        if (!EVENT_LOG_RECORD_IS_FIRMWARE(rec))
-                return false;
+                        ascii_strlower(aa);
 
-        if (rec->firmware_event_type != EV_SEPARATOR)
-                return false;
+                        h = hexmem(hash->buffer, hash->size);
+                        if (!h)
+                                return log_oom();
 
-        return rec->event_payload_valid == EVENT_PAYLOAD_VALID_YES; /* Insist the record is consistent */
+                        printf("  %s%-6s:%s %s\n", ansi_grey(), aa, ansi_normal(), h);
+                }
+        }
+
+        return 0;
 }
 
-static int event_log_record_is_action_calling_efi_app(const EventLogRecord *rec) {
-        _cleanup_free_ char *d = NULL;
-        int r;
+static int tpm2_pcr_prediction_run(
+                EventLog *el,
+                Tpm2PCRPrediction *context) {
 
-        assert(rec);
+        int r;
 
-        /* Recognizes the special EV_EFI_ACTION that is issues when the firmware passes control to the boot loader. */
+        assert(el);
+        assert(context);
 
-        if (!EVENT_LOG_RECORD_IS_FIRMWARE(rec))
-                return false;
+        for (uint32_t pcr = 0; pcr < TPM2_PCRS_MAX; pcr++) {
+                _cleanup_free_ Tpm2PCRPredictionResult *result = NULL;
 
-        if (rec->pcr != TPM2_PCR_BOOT_LOADER_CODE)
-                return false;
+                if (!BIT_SET(context->pcrs, pcr))
+                        continue;
 
-        if (rec->firmware_event_type != EV_EFI_ACTION)
-                return false;
+                result = new0(Tpm2PCRPredictionResult, 1);
+                if (!result)
+                        return log_oom();
 
-        if (rec->event_payload_valid != EVENT_PAYLOAD_VALID_YES) /* Insist the record is consistent */
-                return false;
+                for (size_t i = 0; i < TPM2_N_HASH_ALGORITHMS; i++)
+                        event_log_initial_pcr_state(el, pcr, tpm2_hash_alg_to_size(tpm2_hash_algorithms[i]), result->hash + i);
 
-        r = make_cstring(rec->firmware_payload, rec->firmware_payload_size, MAKE_CSTRING_ALLOW_TRAILING_NUL, &d);
-        if (r < 0)
-                return r;
+                r = event_log_predict_pcrs(
+                                el,
+                                context,
+                                result,
+                                /* component_index= */ 0,
+                                pcr,
+                                /* path= */ NULL);
+                if (r < 0)
+                        return r;
+        }
 
-        return streq(d, "Calling EFI Application from Boot Option");
+        return 0;
 }
 
-static void enable_json_sse(void) {
-        /* We shall write this to a single output stream? We have to output two files, hence try to be smart
-         * and enable JSON SSE */
-
-        if (!arg_pcrlock_path && arg_pcrlock_auto)
-                return;
+static int verb_predict(int argc, char *argv[], uintptr_t _data, void *userdata) {
+        _cleanup_(tpm2_pcr_prediction_done) Tpm2PCRPrediction context = {
+                arg_pcr_mask != 0 ? arg_pcr_mask : DEFAULT_PCR_MASK,
+        };
+        _cleanup_(event_log_freep) EventLog *el = NULL;
+        ssize_t count;
+        int r;
 
-        if (FLAGS_SET(arg_json_format_flags, SD_JSON_FORMAT_SSE))
-                return;
+        r = event_log_load_and_process(&el);
+        if (r < 0)
+                return r;
 
-        log_notice("Enabling JSON_SEQ mode, since writing two .pcrlock files to single output.");
-        arg_json_format_flags |= SD_JSON_FORMAT_SSE;
-}
+        count = event_log_calculate_component_combinations(el);
+        if (count < 0)
+                return count;
 
-static int verb_lock_firmware(int argc, char *argv[], uintptr_t _data, void *userdata) {
-        _cleanup_(sd_json_variant_unrefp) sd_json_variant *array_early = NULL, *array_late = NULL;
-        _cleanup_(event_log_freep) EventLog *el = NULL;
-        uint32_t always_mask, separator_mask, separator_seen_mask = 0, action_seen_mask = 0;
-        const char *default_pcrlock_early_path, *default_pcrlock_late_path;
-        int r;
+        log_info("%zi combinations of components.", count);
 
-        enable_json_sse();
+        r = event_log_reduce_to_safe_pcrs(el, &context.pcrs);
+        if (r < 0)
+                return r;
 
-        /* The PCRs we intend to cover. Note that we measure firmware, external *and* boot loader code/config
-         * here – but the latter only until the "separator" events are seen, which tell us where transition
-         * into OS boot loader happens. This reflects the fact that on some systems the firmware already
-         * measures some firmware-supplied apps into PCR 4. (e.g. Thinkpad X1 Gen9) */
-        if (endswith(argv[0], "firmware-code")) {
-                always_mask = (UINT32_C(1) << TPM2_PCR_PLATFORM_CODE) |      /* → 0 */
-                        (UINT32_C(1) << TPM2_PCR_EXTERNAL_CODE);             /* → 2 */
+        r = tpm2_pcr_prediction_run(el, &context);
+        if (r < 0)
+                return r;
 
-                separator_mask = UINT32_C(1) << TPM2_PCR_BOOT_LOADER_CODE;   /* → 4 */
+        return event_log_show_predictions(&context, el->primary_algorithm);
+}
 
-                default_pcrlock_early_path = PCRLOCK_FIRMWARE_CODE_EARLY_PATH;
-                default_pcrlock_late_path = PCRLOCK_FIRMWARE_CODE_LATE_PATH;
-        } else {
-                assert(endswith(argv[0], "firmware-config"));
-                always_mask = (UINT32_C(1) << TPM2_PCR_PLATFORM_CONFIG) |    /* → 1 */
-                        (UINT32_C(1) << TPM2_PCR_EXTERNAL_CONFIG);           /* → 3 */
+static int remove_policy_file(const char *path) {
+        assert(path);
 
-                separator_mask = UINT32_C(1) << TPM2_PCR_BOOT_LOADER_CONFIG; /* → 5 */
+        if (unlink(path) < 0) {
+                if (errno == ENOENT)
+                        return 0;
 
-                default_pcrlock_early_path = PCRLOCK_FIRMWARE_CONFIG_EARLY_PATH;
-                default_pcrlock_late_path = PCRLOCK_FIRMWARE_CONFIG_LATE_PATH;
+                return log_error_errno(errno, "Failed to remove policy file '%s': %m", path);
         }
 
-        el = event_log_new();
-        if (!el)
-                return log_oom();
+        log_info("Removed policy file '%s'.", path);
+        return 1;
+}
 
-        r = event_log_add_algorithms_from_environment(el);
-        if (r < 0)
-                return r;
+static int determine_boot_policy_file(char **ret_path, char **ret_credential_name) {
+        int r;
 
-        r = event_log_load(el);
+        _cleanup_free_ char *path = NULL;
+        r = get_global_boot_credentials_path(&path);
         if (r < 0)
                 return r;
+        if (r == 0) {
+                if (ret_path)
+                        *ret_path = NULL;
+                if (ret_credential_name)
+                        *ret_credential_name = NULL;
+                return 0; /* not found! */
+        }
 
-        r = event_log_read_pcrs(el);
+        sd_id128_t machine_id;
+        r = sd_id128_get_machine(&machine_id);
         if (r < 0)
-                return r;
+                return log_error_errno(r, "Failed to read machine ID: %m");
 
-        r = event_log_calculate_pcrs(el);
+        r = boot_entry_token_ensure(
+                        /* root= */ NULL,
+                        /* conf_root= */ NULL,
+                        machine_id,
+                        /* machine_id_is_random= */ false,
+                        &arg_entry_token_type,
+                        &arg_entry_token);
         if (r < 0)
                 return r;
 
-        r = event_log_validate_record_hashes(el);
-        if (r < 0)
-                return r;
+        _cleanup_free_ char *fn = strjoin("pcrlock.", arg_entry_token, ".cred");
+        if (!fn)
+                return log_oom();
 
-        /* Before we base anything on the event log records for any of the selected PCRs, let's check that
-         * the event log state checks out for them. */
+        if (!filename_is_valid(fn))
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Credential name '%s' would not be a valid file name, refusing.", fn);
 
-        r = event_log_pcr_mask_checks_out(el, always_mask|separator_mask);
-        if (r < 0)
-                return r;
+        _cleanup_free_ char *joined = NULL;
+        if (ret_path) {
+                joined = path_join(path, fn);
+                if (!joined)
+                        return log_oom();
+        }
 
-        // FIXME: before doing this, validate ahead-of-time that EV_SEPARATOR records exist for all entries,
-        //        and exactly once
+        _cleanup_free_ char *cn = NULL;
+        if (ret_credential_name) {
+                /* The .cred suffix of the file is stripped when PID 1 imports the credential, hence exclude it from
+                 * the embedded credential name. */
+                cn = strjoin("pcrlock.", arg_entry_token);
+                if (!cn)
+                        return log_oom();
 
-        FOREACH_ARRAY(rr, el->records, el->n_records) {
-                _cleanup_(sd_json_variant_unrefp) sd_json_variant *digests = NULL;
-                EventLogRecord *rec = *rr;
-                uint32_t bit = UINT32_C(1) << rec->pcr;
+                ascii_strlower(cn); /* lowercase this file, no matter what, since stored on VFAT, and we don't want
+                                     * to run into case change incompatibilities */
+        }
 
-                if (!EVENT_LOG_RECORD_IS_FIRMWARE(rec))
-                        continue;
+        if (ret_path)
+                *ret_path = TAKE_PTR(joined);
 
-                if (!FLAGS_SET(always_mask, bit) &&
-                    !(FLAGS_SET(separator_mask, bit) && !FLAGS_SET(separator_seen_mask|action_seen_mask, bit)))
-                        continue;
+        if (ret_credential_name)
+                *ret_credential_name = TAKE_PTR(cn);
 
-                /* If we hit the separator record, we stop processing the PCRs listed in `separator_mask` */
-                if (event_log_record_is_separator(rec)) {
-                        separator_seen_mask |= bit;
-                        continue;
-                }
+        return 1; /* found! */
+}
 
-                /* If we hit the special "Calling EFI Application from Boot Option" action we treat this the
-                 * same as a separator here, as that's where firmware passes control to boot loader. Note
-                 * that some EFI implementations forget to generate one of them. */
-                r = event_log_record_is_action_calling_efi_app(rec);
-                if (r < 0)
-                        return log_error_errno(r, "Failed to check if event is 'Calling EFI Application from Boot Option' action: %m");
-                if (r > 0) {
-                        action_seen_mask |= bit;
-                        continue;
-                }
-
-                LIST_FOREACH(banks, bank, rec->banks) {
-                        r = sd_json_variant_append_arraybo(
-                                        &digests,
-                                        SD_JSON_BUILD_PAIR_STRING("hashAlg", tpm2_hash_alg_to_string(bank->algorithm)),
-                                        SD_JSON_BUILD_PAIR("digest", SD_JSON_BUILD_HEX(bank->hash.buffer, bank->hash.size)));
-                        if (r < 0)
-                                return log_error_errno(r, "Failed to build digests array: %m");
-                }
+static int write_boot_policy_file(const char *json_text) {
+        _cleanup_free_ char *boot_policy_file = NULL, *credential_name = NULL;
+        int r;
 
-                r = sd_json_variant_append_arraybo(
-                                FLAGS_SET(separator_seen_mask, bit) ? &array_late : &array_early,
-                                SD_JSON_BUILD_PAIR_UNSIGNED("pcr", rec->pcr),
-                                SD_JSON_BUILD_PAIR_VARIANT("digests", digests));
-                if (r < 0)
-                        return log_error_errno(r, "Failed to build record array: %m");
-        }
+        assert(json_text);
 
-        r = write_pcrlock(array_early, default_pcrlock_early_path);
+        r = determine_boot_policy_file(&boot_policy_file, &credential_name);
         if (r < 0)
                 return r;
-
-        return write_pcrlock(array_late, default_pcrlock_late_path);
-}
-
-static int verb_unlock_firmware(int argc, char *argv[], uintptr_t _data, void *userdata) {
-        const char *default_pcrlock_early_path, *default_pcrlock_late_path;
-        int r;
-
-        if (endswith(argv[0], "firmware-code")) {
-                default_pcrlock_early_path = PCRLOCK_FIRMWARE_CODE_EARLY_PATH;
-                default_pcrlock_late_path = PCRLOCK_FIRMWARE_CODE_LATE_PATH;
-        } else {
-                default_pcrlock_early_path = PCRLOCK_FIRMWARE_CONFIG_EARLY_PATH;
-                default_pcrlock_late_path = PCRLOCK_FIRMWARE_CONFIG_LATE_PATH;
+        if (r == 0) {
+                log_info("Did not find XBOOTLDR/ESP partition, not writing boot policy file.");
+                return 0;
         }
 
-        r = unlink_pcrlock(default_pcrlock_early_path);
+        _cleanup_(iovec_done) struct iovec encoded = {};
+        r = encrypt_credential_and_warn(
+                        CRED_AES256_GCM_BY_NULL,
+                        credential_name,
+                        now(CLOCK_REALTIME),
+                        /* not_after= */ USEC_INFINITY,
+                        /* tpm2_device= */ NULL,
+                        /* tpm2_hash_pcr_mask= */ 0,
+                        /* tpm2_pubkey_path= */ NULL,
+                        /* tpm2_pubkey_pcr_mask= */ 0,
+                        UID_INVALID,
+                        &IOVEC_MAKE_STRING(json_text),
+                        CREDENTIAL_ALLOW_NULL,
+                        &encoded);
         if (r < 0)
-                return r;
-
-        if (arg_pcrlock_path) /* if the path is specified don't delete the same thing twice */
-                return 0;
+                return log_error_errno(r, "Failed to encode policy as credential: %m");
 
-        r = unlink_pcrlock(default_pcrlock_late_path);
+        r = write_base64_file_at(
+                        AT_FDCWD,
+                        boot_policy_file,
+                        &encoded,
+                        WRITE_STRING_FILE_ATOMIC|WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_SYNC|WRITE_STRING_FILE_MKDIR_0755|WRITE_STRING_FILE_LABEL);
         if (r < 0)
-                return r;
+                return log_error_errno(r, "Failed to write boot policy file to '%s': %m", boot_policy_file);
 
-        return 0;
+        log_info("Written new boot policy to '%s'.", boot_policy_file);
+        return 1;
 }
 
-static int verb_lock_machine_id(int argc, char *argv[], uintptr_t _data, void *userdata) {
-        _cleanup_(sd_json_variant_unrefp) sd_json_variant *record = NULL, *array = NULL;
-        _cleanup_free_ char *word = NULL;
+static int make_policy(bool force, RecoveryPinMode recovery_pin_mode) {
         int r;
 
-        r = pcrextend_machine_id_word(&word);
+        /* Here's how this all works: after predicting all possible PCR values for next boot (with
+         * alternatives) we'll calculate a policy from it as a combination of PolicyPCR + PolicyOR
+         * expressions. This is then stored in an NV index. When a component of the boot process is changed a
+         * new prediction is made and the NV index updated (which automatically invalidates any older
+         * policies).
+         *
+         * Whenever we want to lock an encrypted object (for example FDE) against this policy, we'll use a
+         * 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
+         * either generate locally or which the user can provide us with) which can also be used for
+         * recovery. This PIN is sealed to the TPM and is locked via PolicyAuthorizeNV to the NV index it
+         * protects (i.e. we dogfood 🌭 🐶 hard here). This means in order to update such a policy we need
+         * the policy to pass.
+         *
+         * Information about the used NV Index, the SRK of the TPM, the sealed PIN and the current PCR
+         * prediction data are stored in a JSON file in /var/lib/. In order to be able to unlock root disks
+         * this data must be also copied to the ESP so that it is available to the initrd. The data is not
+         * sensitive, as SRK and NV index are pinned by it, and the prediction data must match the NV index
+         * to be useful. */
+
+        usec_t start_usec = now(CLOCK_MONOTONIC);
+
+        _cleanup_(event_log_freep) EventLog *el = NULL;
+        r = event_log_load_and_process(&el);
         if (r < 0)
                 return r;
 
-        r = make_pcrlock_record(TPM2_PCR_SYSTEM_IDENTITY /* = 15 */, word, SIZE_MAX, &record);
+        _cleanup_(tpm2_pcr_prediction_done) Tpm2PCRPrediction new_prediction = {
+                arg_pcr_mask != 0 ? arg_pcr_mask : DEFAULT_PCR_MASK,
+        };
+        r = event_log_reduce_to_safe_pcrs(el, &new_prediction.pcrs);
         if (r < 0)
                 return r;
 
-        r = sd_json_variant_new_array(&array, &record, 1);
-        if (r < 0)
-                return log_error_errno(r, "Failed to create record array: %m");
+        if (!force && new_prediction.pcrs == 0)
+                log_notice("Set of PCRs to use for policy is empty. Generated policy will not provide any protection in its current form. Proceeding.");
 
-        return write_pcrlock(array, PCRLOCK_MACHINE_ID_PATH);
-}
+        usec_t predict_start_usec = now(CLOCK_MONOTONIC);
 
-static int verb_unlock_machine_id(int argc, char *argv[], uintptr_t _data, void *userdata) {
-        return unlink_pcrlock(PCRLOCK_MACHINE_ID_PATH);
-}
+        r = tpm2_pcr_prediction_run(el, &new_prediction);
+        if (r < 0)
+                return r;
 
-static int pcrlock_file_system_path(const char *normalized_path, char **ret) {
-        _cleanup_free_ char *s = NULL;
+        log_info("Predicted future PCRs in %s.", FORMAT_TIMESPAN(usec_sub_unsigned(now(CLOCK_MONOTONIC), predict_start_usec), 1));
 
-        assert(normalized_path);
-        assert(ret);
+        _cleanup_(sd_json_variant_unrefp) sd_json_variant *new_prediction_json = NULL;
+        r = tpm2_pcr_prediction_to_json(&new_prediction, el->primary_algorithm, &new_prediction_json);
+        if (r < 0)
+                return r;
 
-        if (path_equal(normalized_path, "/"))
-                s = strdup(PCRLOCK_ROOT_FILE_SYSTEM_PATH);
-        else {
-                /* We reuse the escaping we use for turning paths into unit names */
-                _cleanup_free_ char *escaped = NULL;
+        if (DEBUG_LOGGING)
+                (void) sd_json_variant_dump(new_prediction_json, SD_JSON_FORMAT_PRETTY_AUTO|SD_JSON_FORMAT_COLOR_AUTO, stderr, NULL);
 
-                assert(normalized_path[0] == '/');
-                assert(normalized_path[1] != '/');
+        /* v257 and older mistakenly used --pcrlock= for the path. To keep backward compatibility, let's fallback to it when
+         * --policy= is unspecified but --pcrlock is specified. */
+        if (!arg_policy_path && arg_pcrlock_path) {
+                log_notice("Specified --pcrlock= option for make-policy command. Please use --policy= instead.");
 
-                escaped = unit_name_escape(normalized_path + 1);
-                if (!escaped)
+                arg_policy_path = strdup(arg_pcrlock_path);
+                if (!arg_policy_path)
                         return log_oom();
-
-                s = strjoin(PCRLOCK_FILE_SYSTEM_PATH_PREFIX, escaped, ".pcrlock");
         }
-        if (!s)
-                return log_oom();
 
-        *ret = TAKE_PTR(s);
-        return 0;
-}
-
-static int verb_lock_file_system(int argc, char *argv[], uintptr_t _data, void *userdata) {
-        const char* paths[3] = {};
-        int r;
-
-        if (argc > 1)
-                paths[0] = argv[1];
-        else {
-                dev_t a, b;
-                paths[0] = "/";
+        _cleanup_(tpm2_pcrlock_policy_done) Tpm2PCRLockPolicy old_policy = {};
+        r = tpm2_pcrlock_policy_load(arg_policy_path, &old_policy);
+        if (r < 0)
+                return r;
 
-                r = get_block_device("/", &a);
-                if (r < 0)
-                        return log_error_errno(r, "Failed to get device of root file system: %m");
+        bool have_old_policy = r > 0;
 
-                r = get_block_device("/var", &b);
-                if (r < 0)
-                        return log_error_errno(r, "Failed to get device of /var/ file system: %m");
+        /* When we update the policy the old serializations for NV, SRK, PIN remain the same */
+        _cleanup_(iovec_done) struct iovec
+                nv_blob = TAKE_STRUCT(old_policy.nv_handle),
+                nv_public_blob = TAKE_STRUCT(old_policy.nv_public),
+                srk_blob = TAKE_STRUCT(old_policy.srk_handle),
+                pin_public = TAKE_STRUCT(old_policy.pin_public),
+                pin_private = TAKE_STRUCT(old_policy.pin_private);
 
-                /* if backing device is distinct, then measure /var/ too */
-                if (a != b)
-                        paths[1] = "/var";
+        if (have_old_policy) {
+                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);
 
-                enable_json_sse();
+                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 0; /* NOP */
+                }
         }
 
-        STRV_FOREACH(p, paths) {
-                _cleanup_free_ char *word = NULL, *normalized_path = NULL, *pcrlock_file = NULL;
-                _cleanup_(sd_json_variant_unrefp) sd_json_variant *record = NULL, *array = NULL;
+        _cleanup_(tpm2_context_unrefp) Tpm2Context *tc = NULL;
+        r = tpm2_context_new_or_warn(/* device= */ NULL, &tc);
+        if (r < 0)
+                return r;
 
-                r = pcrextend_file_system_word(*p, &word, &normalized_path);
-                if (r < 0)
-                        return r;
+        if (!tpm2_supports_command(tc, TPM2_CC_PolicyAuthorizeNV))
+                return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "TPM2 does not support PolicyAuthorizeNV command, refusing.");
+        if (!tpm2_supports_alg(tc, TPM2_ALG_SHA256))
+                return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "TPM2 does not support SHA-256 hash algorithm, refusing.");
 
-                r = pcrlock_file_system_path(normalized_path, &pcrlock_file);
-                if (r < 0)
-                        return r;
+        _cleanup_(tpm2_handle_freep) Tpm2Handle *srk_handle = NULL;
 
-                r = make_pcrlock_record(TPM2_PCR_SYSTEM_IDENTITY /* = 15 */, word, SIZE_MAX, &record);
+        r = tpm2_deserialize(
+                        tc,
+                        &srk_blob,
+                        &srk_handle);
+        if (r < 0)
+                return log_error_errno(r, "Failed to deserialize SRK TR: %m");
+        if (r == 0) {
+                r = tpm2_get_or_create_srk(
+                                tc,
+                                /* session= */ NULL,
+                                /* ret_public= */ NULL,
+                                /* ret_name= */ NULL,
+                                /* ret_qname= */ NULL,
+                                &srk_handle);
                 if (r < 0)
-                        return r;
+                        return log_error_errno(r, "Failed to install SRK: %m");
+        }
 
-                r = sd_json_variant_new_array(&array, &record, 1);
-                if (r < 0)
-                        return log_error_errno(r, "Failed to create record array: %m");
+        _cleanup_(tpm2_handle_freep) Tpm2Handle *encryption_session = NULL;
+        r = tpm2_make_encryption_session(
+                        tc,
+                        srk_handle,
+                        /* bind_key= */ &TPM2_HANDLE_NONE,
+                        &encryption_session);
+        if (r < 0)
+                return log_error_errno(r, "Failed to allocate encryption session: %m");
 
-                r = write_pcrlock(array, pcrlock_file);
+        /* Acquire a recovery PIN, either from the user, or create a randomized one */
+        _cleanup_(erase_and_freep) char *pin = NULL;
+        if (recovery_pin_mode == RECOVERY_PIN_QUERY) {
+                r = getenv_steal_erase("PIN", &pin);
                 if (r < 0)
-                        return r;
-        }
-
-        return 0;
-}
+                        return log_error_errno(r, "Failed to acquire PIN from environment: %m");
+                if (r == 0) {
+                        _cleanup_strv_free_erase_ char **l = NULL;
 
-static int verb_unlock_file_system(int argc, char *argv[], uintptr_t _data, void *userdata) {
-        const char* paths[3] = {};
-        int r;
+                        AskPasswordRequest req = {
+                                .tty_fd = -EBADF,
+                                .message = "Recovery PIN",
+                                .id = "pcrlock-recovery-pin",
+                                .credential = "pcrlock.recovery-pin",
+                                .until = USEC_INFINITY,
+                                .hup_fd = -EBADF,
+                        };
 
-        if (argc > 1)
-                paths[0] = argv[1];
-        else {
-                paths[0] = "/";
-                paths[1] = "/var";
-        }
+                        r = ask_password_auto(
+                                        &req,
+                                        /* flags= */ 0,
+                                        &l);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to query for recovery PIN: %m");
 
-        STRV_FOREACH(p, paths) {
-                _cleanup_free_ char *normalized_path = NULL, *pcrlock_file = NULL;
+                        if (strv_length(l) != 1)
+                                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected a single PIN only.");
 
-                r = chase(*p, NULL, 0, &normalized_path, NULL);
-                if (r < 0)
-                        return log_error_errno(r, "Failed to normal path '%s': %m", argv[1]);
+                        pin = TAKE_PTR(l[0]);
+                        l = mfree(l);
+                }
 
-                r = pcrlock_file_system_path(normalized_path, &pcrlock_file);
+        } else if (!have_old_policy) {
+                r = make_recovery_key(&pin);
                 if (r < 0)
-                        return r;
+                        return log_error_errno(r, "Failed to generate a randomized recovery PIN: %m");
 
-                r = unlink_pcrlock(pcrlock_file);
-                if (r < 0)
-                        return r;
+                if (recovery_pin_mode == RECOVERY_PIN_SHOW)
+                        printf("%s Selected recovery PIN is: %s%s%s\n",
+                               glyph(GLYPH_LOCK_AND_KEY),
+                               ansi_highlight_cyan(),
+                               pin,
+                               ansi_normal());
         }
 
-        return 0;
-}
-
-static int verb_lock_pe(int argc, char *argv[], uintptr_t _data, void *userdata) {
-        _cleanup_(sd_json_variant_unrefp) sd_json_variant *array = NULL;
-        _cleanup_close_ int fd = -EBADF;
-        int r;
-
-        // FIXME: Maybe also generate a matching EV_EFI_VARIABLE_AUTHORITY records here for each signature that
-        //        covers this PE plus its hash, as alternatives under the same component name
+        _cleanup_(tpm2_handle_freep) Tpm2Handle *nv_handle = NULL;
+        TPM2_HANDLE nv_index = 0;
 
-        if (argc >= 2) {
-                fd = open(argv[1], O_RDONLY|O_CLOEXEC);
-                if (fd < 0)
-                        return log_error_errno(errno, "Failed to open '%s': %m", argv[1]);
-        }
+        r = tpm2_deserialize(tc, &nv_blob, &nv_handle);
+        if (r < 0)
+                return log_error_errno(r, "Failed to deserialize NV index TR: %m");
+        if (r > 0)
+                nv_index = old_policy.nv_index;
 
-        if (arg_pcr_mask == 0)
-                arg_pcr_mask = UINT32_C(1) << TPM2_PCR_BOOT_LOADER_CODE;
+        TPM2B_AUTH auth = {};
+        CLEANUP_ERASE(auth);
 
-        for (uint32_t i = 0; i < TPM2_PCRS_MAX; i++) {
-                _cleanup_(sd_json_variant_unrefp) sd_json_variant *digests = NULL;
+        if (pin) {
+                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 {
+                assert(iovec_is_set(&pin_public));
+                assert(iovec_is_set(&pin_private));
 
-                if (!BIT_SET(arg_pcr_mask, i))
-                        continue;
+                log_debug("Retrieving PIN from sealed data.");
 
-                FOREACH_ARRAY(pa, tpm2_hash_algorithms, TPM2_N_HASH_ALGORITHMS) {
-                        _cleanup_free_ void *hash = NULL;
-                        size_t hash_size;
-                        const EVP_MD *md;
-                        const char *a;
+                usec_t pin_start_usec = now(CLOCK_MONOTONIC);
 
-                        assert_se(a = tpm2_hash_alg_to_string(*pa));
-                        assert_se(md = EVP_get_digestbyname(a));
+                _cleanup_(iovec_done_erase) struct iovec secret = {};
+                for (unsigned attempt = 0;; attempt++) {
+                        _cleanup_(tpm2_handle_freep) Tpm2Handle *policy_session = NULL;
 
-                        r = pe_hash(fd < 0 ? STDIN_FILENO : fd, md, &hash, &hash_size);
+                        r = tpm2_make_policy_session(
+                                        tc,
+                                        srk_handle,
+                                        encryption_session,
+                                        &policy_session);
                         if (r < 0)
-                                return log_error_errno(r, "Failed to hash PE binary: %m");
+                                return log_error_errno(r, "Failed to allocate policy session: %m");
 
-                        r = sd_json_variant_append_arraybo(
-                                        &digests,
-                                        SD_JSON_BUILD_PAIR_STRING("hashAlg", a),
-                                        SD_JSON_BUILD_PAIR("digest", SD_JSON_BUILD_HEX(hash, hash_size)));
+                        r = tpm2_policy_super_pcr(
+                                        tc,
+                                        policy_session,
+                                        &old_policy.prediction,
+                                        old_policy.algorithm);
                         if (r < 0)
-                                return log_error_errno(r, "Failed to build JSON digest object: %m");
-                }
-
-                r = sd_json_variant_append_arraybo(
-                                &array,
-                                SD_JSON_BUILD_PAIR_UNSIGNED("pcr", i),
-                                SD_JSON_BUILD_PAIR_VARIANT("digests", digests));
-                if (r < 0)
-                        return log_error_errno(r, "Failed to append record object: %m");
-        }
+                                return r;
 
-        return write_pcrlock(array, NULL);
-}
+                        r = tpm2_policy_authorize_nv(
+                                        tc,
+                                        policy_session,
+                                        nv_handle,
+                                        NULL);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to submit AuthorizeNV policy: %m");
 
-typedef void* SectionHashArray[_UNIFIED_SECTION_MAX * TPM2_N_HASH_ALGORITHMS];
+                        r = tpm2_unseal_data(
+                                        tc,
+                                        &pin_public,
+                                        &pin_private,
+                                        srk_handle,
+                                        policy_session,
+                                        encryption_session,
+                                        &secret);
+                        if (r < 0 && (r != -ESTALE || attempt >= 16))
+                                return log_error_errno(r, "Failed to unseal PIN: %m");
+                        if (r == 0)
+                                break;
 
-static void section_hashes_array_done(SectionHashArray *array) {
-        assert(array);
+                        log_debug("Trying again (attempt %u), as PCR values changed during unlock attempt.", attempt+1);
+                }
 
-        for (size_t i = 0; i < _UNIFIED_SECTION_MAX * TPM2_N_HASH_ALGORITHMS; i++)
-                free((*array)[i]);
-}
+                if (secret.iov_len > sizeof_field(TPM2B_AUTH, buffer))
+                        return log_error_errno(SYNTHETIC_ERRNO(E2BIG), "Decrypted PIN too large.");
 
-static int verb_lock_uki(int argc, char *argv[], uintptr_t _data, void *userdata) {
-        _cleanup_(sd_json_variant_unrefp) sd_json_variant *array = NULL, *pe_digests = NULL;
-        _cleanup_(section_hashes_array_done) SectionHashArray section_hashes = {};
-        size_t hash_sizes[TPM2_N_HASH_ALGORITHMS];
-        _cleanup_close_ int fd = -EBADF;
-        int r;
+                auth = (TPM2B_AUTH) {
+                        .size = secret.iov_len,
+                };
 
-        if (arg_pcr_mask != 0)
-                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "PCR not configurable for UKI lock down.");
+                memcpy_safe(auth.buffer, secret.iov_base, secret.iov_len);
 
-        if (argc >= 2) {
-                fd = open(argv[1], O_RDONLY|O_CLOEXEC);
-                if (fd < 0)
-                        return log_error_errno(errno, "Failed to open '%s': %m", argv[1]);
+                log_info("Retrieved PIN from TPM2 in %s.", FORMAT_TIMESPAN(usec_sub_unsigned(now(CLOCK_MONOTONIC), pin_start_usec), 1));
         }
 
-        for (size_t i = 0; i < TPM2_N_HASH_ALGORITHMS; i++) {
-                _cleanup_free_ void *peh = NULL;
-                const EVP_MD *md;
-                const char *a;
+        /* 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;
 
-                assert_se(a = tpm2_hash_alg_to_string(tpm2_hash_algorithms[i]));
-                assert_se(md = EVP_get_digestbyname(a));
+        TPM2B_NV_PUBLIC nv_public = {};
+        usec_t nv_index_start_usec = now(CLOCK_MONOTONIC);
 
-                r = pe_hash(fd < 0 ? STDIN_FILENO : fd, md, &peh, hash_sizes + i);
+        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 hash PE binary: %m");
+                        return log_error_errno(r, "Failed to get name of PIN from TPM2: %m");
 
-                r = sd_json_variant_append_arraybo(
-                                &pe_digests,
-                                SD_JSON_BUILD_PAIR_STRING("hashAlg", a),
-                                SD_JSON_BUILD_PAIR("digest", SD_JSON_BUILD_HEX(peh, hash_sizes[i])));
+                TPM2B_DIGEST recovery_policy_digest = TPM2B_DIGEST_MAKE(NULL, TPM2_SHA256_DIGEST_SIZE);
+                r = tpm2_calculate_policy_signed(&recovery_policy_digest, pin_name);
                 if (r < 0)
-                        return log_error_errno(r, "Failed to build JSON digest object: %m");
+                        return log_error_errno(r, "Failed to calculate PolicySigned policy: %m");
 
-                r = uki_hash(fd < 0 ? STDIN_FILENO : fd, md, section_hashes + (i * _UNIFIED_SECTION_MAX), hash_sizes + i);
+                log_debug("Allocating NV index to write PCR policy to...");
+                r = tpm2_define_policy_nv_index(
+                                tc,
+                                encryption_session,
+                                arg_nv_index,
+                                &recovery_policy_digest,
+                                &nv_index,
+                                &nv_handle,
+                                &nv_public);
+                if (r == -EEXIST)
+                        return log_error_errno(r, "NV index 0x%" PRIx32 " already allocated.", arg_nv_index);
                 if (r < 0)
-                        return log_error_errno(r, "Failed to UKI hash PE binary: %m");
+                        return log_error_errno(r, "Failed to allocate NV index: %m");
         }
 
-        r = sd_json_variant_append_arraybo(
-                        &array,
-                        SD_JSON_BUILD_PAIR_UNSIGNED("pcr", TPM2_PCR_BOOT_LOADER_CODE),
-                        SD_JSON_BUILD_PAIR_VARIANT("digests", pe_digests));
+        _cleanup_(tpm2_handle_freep) Tpm2Handle *policy_session = NULL;
+        r = tpm2_make_policy_session(
+                        tc,
+                        srk_handle,
+                        encryption_session,
+                        &policy_session);
         if (r < 0)
-                return log_error_errno(r, "Failed to append record object: %m");
+                return log_error_errno(r, "Failed to allocate policy session: %m");
 
-        for (UnifiedSection section = 0; section < _UNIFIED_SECTION_MAX; section++) {
-                _cleanup_(sd_json_variant_unrefp) sd_json_variant *section_digests = NULL, *record = NULL;
+        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");
 
-                if (!unified_section_measure(section))
-                        continue;
+        log_debug("Calculating new PCR policy to write...");
+        TPM2B_DIGEST new_super_pcr_policy_digest = TPM2B_DIGEST_MAKE(NULL, TPM2_SHA256_DIGEST_SIZE);
 
-                for (size_t i = 0; i < TPM2_N_HASH_ALGORITHMS; i++) {
-                        const char *a;
-                        void *hash;
+        usec_t pcr_policy_start_usec = now(CLOCK_MONOTONIC);
 
-                        hash = section_hashes[i * _UNIFIED_SECTION_MAX + section];
-                        if (!hash)
-                                continue;
+        r = tpm2_calculate_policy_super_pcr(
+                        &new_prediction,
+                        el->primary_algorithm,
+                        &new_super_pcr_policy_digest);
+        if (r < 0)
+                return log_error_errno(r, "Failed to calculate super PCR policy: %m");
 
-                        assert_se(a = tpm2_hash_alg_to_string(tpm2_hash_algorithms[i]));
+        log_info("Calculated new PCR policy in %s.", FORMAT_TIMESPAN(usec_sub_unsigned(now(CLOCK_MONOTONIC), pcr_policy_start_usec), 1));
 
-                        r = sd_json_variant_append_arraybo(
-                                        &section_digests,
-                                        SD_JSON_BUILD_PAIR_STRING("hashAlg", a),
-                                        SD_JSON_BUILD_PAIR("digest", SD_JSON_BUILD_HEX(hash, hash_sizes[i])));
-                        if (r < 0)
-                                return log_error_errno(r, "Failed to build JSON digest object: %m");
-                }
+        log_debug("Writing new PCR policy to NV index...");
+        r = tpm2_write_policy_nv_index(
+                        tc,
+                        policy_session,
+                        nv_index,
+                        nv_handle,
+                        &new_super_pcr_policy_digest);
+        if (r < 0)
+                return log_error_errno(r, "Failed to write to NV index: %m");
 
-                if (!section_digests)
-                        continue;
+        log_info("Updated NV index in %s.", FORMAT_TIMESPAN(usec_sub_unsigned(now(CLOCK_MONOTONIC), nv_index_start_usec), 1));
 
-                /* So we have digests for this section, hence generate a record for the section name first. */
-                r = make_pcrlock_record(TPM2_PCR_KERNEL_BOOT /* =11 */, unified_sections[section], strlen(unified_sections[section]) + 1, &record);
+        assert(iovec_is_set(&pin_public) == iovec_is_set(&pin_private));
+        if (!iovec_is_set(&pin_public)) {
+                TPM2B_DIGEST authnv_policy_digest  = TPM2B_DIGEST_MAKE(NULL, TPM2_SHA256_DIGEST_SIZE);
+
+                r = tpm2_calculate_policy_authorize_nv(&nv_public, &authnv_policy_digest);
                 if (r < 0)
-                        return r;
+                        return log_error_errno(r, "Failed to calculate AuthorizeNV policy: %m");
 
-                r = sd_json_variant_append_array(&array, record);
+                struct iovec data = {
+                        .iov_base = auth.buffer,
+                        .iov_len = auth.size,
+                };
+
+                usec_t pin_seal_start_usec = now(CLOCK_MONOTONIC);
+
+                log_debug("Sealing PIN to NV index policy...");
+                r = tpm2_seal_data(
+                                tc,
+                                &data,
+                                srk_handle,
+                                encryption_session,
+                                &authnv_policy_digest,
+                                &pin_public,
+                                &pin_private);
                 if (r < 0)
-                        return log_error_errno(r, "Failed to append JSON record array: %m");
+                        return log_error_errno(r, "Failed to seal PIN to NV auth policy: %m");
 
-                /* And then append a record for the section contents digests as well */
-                r = sd_json_variant_append_arraybo(
-                                &array,
-                                SD_JSON_BUILD_PAIR_UNSIGNED("pcr", TPM2_PCR_KERNEL_BOOT),
-                                SD_JSON_BUILD_PAIR_VARIANT("digests", section_digests));
+                log_info("Sealed PIN in %s.", FORMAT_TIMESPAN(usec_sub_unsigned(now(CLOCK_MONOTONIC), pin_seal_start_usec), 1));
+        }
+
+        if (!iovec_is_set(&nv_blob)) {
+                r = tpm2_serialize(tc, nv_handle, &nv_blob);
                 if (r < 0)
-                        return log_error_errno(r, "Failed to append record object: %m");
+                        return log_error_errno(r, "Failed to serialize NV index TR: %m");
         }
 
-        return write_pcrlock(array, NULL);
-}
+        if (!iovec_is_set(&srk_blob)) {
+                r = tpm2_serialize(tc, srk_handle, &srk_blob);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to serialize SRK index TR: %m");
+        }
 
-static int verb_lock_kernel_cmdline(int argc, char *argv[], uintptr_t _data, void *userdata) {
-        _cleanup_(sd_json_variant_unrefp) sd_json_variant *record = NULL, *array = NULL;
-        _cleanup_free_ char *cmdline = NULL;
-        int r;
+        if (!iovec_is_set(&nv_public_blob)) {
+                r = tpm2_marshal_nv_public(&nv_public, &nv_public_blob.iov_base, &nv_public_blob.iov_len);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to marshal NV public area: %m");
+        }
 
-        if (argc > 1) {
-                if (empty_or_dash(argv[1]))
-                        r = read_full_stream(stdin, &cmdline, NULL);
-                else
-                        r = read_full_file(argv[1], &cmdline, NULL);
-        } else
-                r = proc_cmdline(&cmdline);
+        _cleanup_(sd_json_variant_unrefp) sd_json_variant *new_configuration_json = NULL;
+        r = sd_json_buildo(
+                        &new_configuration_json,
+                        SD_JSON_BUILD_PAIR_STRING("pcrBank", tpm2_hash_alg_to_string(el->primary_algorithm)),
+                        SD_JSON_BUILD_PAIR_VARIANT("pcrValues", new_prediction_json),
+                        SD_JSON_BUILD_PAIR_INTEGER("nvIndex", nv_index),
+                        JSON_BUILD_PAIR_IOVEC_BASE64("nvHandle", &nv_blob),
+                        JSON_BUILD_PAIR_IOVEC_BASE64("nvPublic", &nv_public_blob),
+                        JSON_BUILD_PAIR_IOVEC_BASE64("srkHandle", &srk_blob),
+                        JSON_BUILD_PAIR_IOVEC_BASE64("pinPublic", &pin_public),
+                        JSON_BUILD_PAIR_IOVEC_BASE64("pinPrivate", &pin_private));
         if (r < 0)
-                return log_error_errno(r, "Failed to read cmdline: %m");
-
-        delete_trailing_chars(cmdline, "\n");
-
-        _cleanup_free_ char16_t *u = NULL;
-        u = utf8_to_utf16(cmdline, SIZE_MAX);
-        if (!u)
-                return log_oom();
+                return log_error_errno(r, "Failed to generate JSON: %m");
 
-        r = make_pcrlock_record(TPM2_PCR_KERNEL_INITRD /* = 9 */, u, char16_strlen(u)*2+2, &record);
+        _cleanup_free_ char *text = NULL;
+        r = sd_json_variant_format(new_configuration_json, 0, &text);
         if (r < 0)
-                return r;
+                return log_error_errno(r, "Failed to format new configuration to JSON: %m");
 
-        r = sd_json_variant_new_array(&array, &record, 1);
+        const char *path = arg_policy_path ?: (in_initrd() ? "/run/systemd/pcrlock.json" : "/var/lib/systemd/pcrlock.json");
+        r = write_string_file(path, text, WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_ATOMIC|WRITE_STRING_FILE_SYNC|WRITE_STRING_FILE_MKDIR_0755|WRITE_STRING_FILE_LABEL);
         if (r < 0)
-                return log_error_errno(r, "Failed to create record array: %m");
+                return log_error_errno(r, "Failed to write new configuration to '%s': %m", path);
 
-        r = write_pcrlock(array, PCRLOCK_KERNEL_CMDLINE_PATH);
-        if (r < 0)
-                return r;
+        if (!arg_policy_path && !in_initrd()) {
+                r = remove_policy_file("/run/systemd/pcrlock.json");
+                if (r < 0)
+                        return r;
+        }
 
-        return 0;
-}
+        log_info("Written new policy to '%s' and digest to TPM2 NV index 0x%x.", path, nv_index);
 
-static int verb_unlock_kernel_cmdline(int argc, char *argv[], uintptr_t _data, void *userdata) {
-        return unlink_pcrlock(PCRLOCK_KERNEL_CMDLINE_PATH);
-}
+        (void) write_boot_policy_file(text);
 
-static int verb_lock_kernel_initrd(int argc, char *argv[], uintptr_t _data, void *userdata) {
-        _cleanup_(sd_json_variant_unrefp) sd_json_variant *records = NULL;
-        _cleanup_fclose_ FILE *f = NULL;
-        uint32_t pcr_mask = UINT32_C(1) << TPM2_PCR_KERNEL_INITRD;
-        int r;
+        log_info("Overall time spent: %s", FORMAT_TIMESPAN(usec_sub_unsigned(now(CLOCK_MONOTONIC), start_usec), 1));
 
-        if (argc >= 2) {
-                f = fopen(argv[1], "re");
-                if (!f)
-                        return log_error_errno(errno, "Failed to open '%s': %m", argv[1]);
-        }
+        return 1; /* installed new policy */
+}
 
-        r = make_pcrlock_record_from_stream(pcr_mask, f ?: stdin, &records);
-        if (r < 0)
-                return r;
+static int verb_make_policy(int argc, char *argv[], uintptr_t _data, void *userdata) {
+        int r;
 
-        r = write_pcrlock(records, PCRLOCK_KERNEL_INITRD_PATH);
+        r = make_policy(arg_force, arg_recovery_pin);
         if (r < 0)
                 return r;
 
         return 0;
 }
 
-static int verb_unlock_kernel_initrd(int argc, char *argv[], uintptr_t _data, void *userdata) {
-        return unlink_pcrlock(PCRLOCK_KERNEL_INITRD_PATH);
-}
-
-static int event_log_reduce_to_safe_pcrs(EventLog *el, uint32_t *pcrs) {
-        _cleanup_free_ char *dropped = NULL, *kept = NULL;
-
-        assert(el);
-        assert(pcrs);
+static int undefine_policy_nv_index(
+                uint32_t nv_index,
+                const struct iovec *nv_blob,
+                const struct iovec *srk_blob) {
+        int r;
 
-        /* When we compile a new PCR policy we don't want to bind to PCRs which are fishy for one of three
-         * reasons:
-         *
-         * 1. The PCR value doesn't match the event log
-         * 2. The event log for the PCR contains measurements we don't know responsible components for
-         * 3. The event log for the PCR does not contain measurements for components we know
-         *
-         * This function checks for the three conditions and drops the PCR from the mask.
-         */
+        assert(nv_blob);
+        assert(srk_blob);
 
-        for (uint32_t pcr = 0; pcr < TPM2_PCRS_MAX; pcr++) {
+        _cleanup_(tpm2_context_unrefp) Tpm2Context *tc = NULL;
+        r = tpm2_context_new_or_warn(/* device= */ NULL, &tc);
+        if (r < 0)
+                return r;
 
-                if (!BIT_SET(*pcrs, pcr))
-                        continue;
+        _cleanup_(tpm2_handle_freep) Tpm2Handle *srk_handle = NULL;
+        r = tpm2_deserialize(
+                        tc,
+                        srk_blob,
+                        &srk_handle);
+        if (r < 0)
+                return log_error_errno(r, "Failed to deserialize SRK TR: %m");
 
-                if (!event_log_pcr_checks_out(el, el->registers + pcr)) {
-                        log_notice("PCR %" PRIu32 " (%s) value does not match event log. Removing from set of PCRs.", pcr, strna(tpm2_pcr_index_to_string(pcr)));
-                        goto drop;
-                }
+        _cleanup_(tpm2_handle_freep) Tpm2Handle *nv_handle = NULL;
+        r = tpm2_deserialize(
+                        tc,
+                        nv_blob,
+                        &nv_handle);
+        if (r < 0)
+                return log_error_errno(r, "Failed to deserialize NV TR: %m");
 
-                if (!el->registers[pcr].fully_recognized) {
-                        log_notice("PCR %" PRIu32 " (%s) event log contains unrecognized measurements. Removing from set of PCRs.", pcr, strna(tpm2_pcr_index_to_string(pcr)));
-                        goto drop;
-                }
+        _cleanup_(tpm2_handle_freep) Tpm2Handle *encryption_session = NULL;
+        r = tpm2_make_encryption_session(
+                        tc,
+                        srk_handle,
+                        /* bind_key= */ &TPM2_HANDLE_NONE,
+                        &encryption_session);
+        if (r < 0)
+                return r;
 
-                if (BIT_SET(el->missing_component_pcrs, pcr)) {
-                        log_notice("PCR %" PRIu32 " (%s) is touched by component we can't find in event log. Removing from set of PCRs.", pcr, strna(tpm2_pcr_index_to_string(pcr)));
-                        goto drop;
-                }
+        r = tpm2_undefine_nv_index(
+                        tc,
+                        encryption_session,
+                        nv_index,
+                        nv_handle);
+        if (r < 0)
+                return r;
 
-                log_info("PCR %" PRIu32 " (%s) matches event log and fully consists of recognized measurements. Including in set of PCRs.", pcr, strna(tpm2_pcr_index_to_string(pcr)));
+        log_info("Removed NV index 0x%x", nv_index);
+        return 0;
+}
 
-                if (strextendf_with_separator(&kept, ", ", "%" PRIu32 " (%s)", pcr, tpm2_pcr_index_to_string(pcr)) < 0)
-                        return log_oom();
+static int remove_policy(void) {
+        int ret = 0, r;
 
-                continue;
+        _cleanup_(tpm2_pcrlock_policy_done) Tpm2PCRLockPolicy policy = {};
+        r = tpm2_pcrlock_policy_load(arg_policy_path, &policy);
+        if (r == 0) {
+                log_info("No policy found.");
+                return 0;
+        }
 
-        drop:
-                *pcrs &= ~(UINT32_C(1) << pcr);
+        if (r < 0)
+                log_notice("Failed to load old policy file, assuming it is corrupted, removing.");
+        else {
+                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 (strextendf_with_separator(&dropped, ", ", "%" PRIu32 " (%s)", pcr, tpm2_pcr_index_to_string(pcr)) < 0)
-                        return log_oom();
+                RET_GATHER(ret, r);
         }
 
-        if (dropped)
-                log_notice("PCRs dropped from protection mask: %s", dropped);
-        else
-                log_debug("No PCRs dropped from protection mask.");
+        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"));
+        }
 
-        if (kept)
-                log_notice("PCRs in protection mask: %s", kept);
-        else
-                log_notice("No PCRs kept in protection mask.");
+        _cleanup_free_ char *boot_policy_file = NULL;
+        r = determine_boot_policy_file(&boot_policy_file, /* ret_credential_name= */ NULL);
+        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 0;
+        return ret;
 }
 
-static int pcr_prediction_add_result(
-                Tpm2PCRPrediction *context,
-                Tpm2PCRPredictionResult *result,
-                uint32_t pcr,
-                const char *path) {
+static int verb_remove_policy(int argc, char *argv[], uintptr_t _data, void *userdata) {
+        return remove_policy();
+}
 
-        _cleanup_free_ Tpm2PCRPredictionResult *copy = NULL;
+static int test_tpm2_support_pcrlock(Tpm2Support *ret) {
         int r;
 
-        assert(context);
-        assert(result);
+        assert(ret);
 
-        copy = newdup(Tpm2PCRPredictionResult, result, 1);
-        if (!copy)
-                return log_oom();
+        /* First check basic support */
+        Tpm2Support s = tpm2_support();
 
-        r = ordered_set_ensure_put(context->results + pcr, &tpm2_pcr_prediction_result_hash_ops, copy);
-        if (r == -EEXIST) /* Multiple identical results for the same PCR are totally expected */
-                return 0;
-        if (r < 0)
-                return log_error_errno(r, "Failed to insert result into set: %m");
+        /* If basic support is available, let's also check the things we need for systemd-pcrlock */
+        if (s == TPM2_SUPPORT_FULL) {
+                _cleanup_(tpm2_context_unrefp) Tpm2Context *tc = NULL;
+                r = tpm2_context_new_or_warn(/* device= */ NULL, &tc);
+                if (r < 0)
+                        return r;
 
-        log_debug("Added prediction result %u for PCR %" PRIu32 " (path: %s)", ordered_set_size(context->results[pcr]), pcr, strempty(path));
+                /* We strictly need TPM2_CC_PolicyAuthorizeNV for systemd-pcrlock to work */
+                SET_FLAG(s, TPM2_SUPPORT_AUTHORIZE_NV, tpm2_supports_command(tc, TPM2_CC_PolicyAuthorizeNV));
 
-        TAKE_PTR(copy);
+                log_debug("PolicyAuthorizeNV supported: %s", yes_no(FLAGS_SET(s, TPM2_SUPPORT_AUTHORIZE_NV)));
+
+                /* We also strictly need SHA-256 to work */
+                SET_FLAG(s, TPM2_SUPPORT_SHA256, tpm2_supports_alg(tc, TPM2_ALG_SHA256));
+
+                log_debug("SHA-256 supported: %s", yes_no(FLAGS_SET(s, TPM2_SUPPORT_SHA256)));
+        }
+
+        *ret = s;
         return 0;
 }
 
-static const EVP_MD* evp_from_tpm2_alg(uint16_t alg) {
-        const char *name;
+static int verb_is_supported(int argc, char *argv[], uintptr_t _data, void *userdata) {
+        int r;
 
-        name = tpm2_hash_alg_to_string(alg);
-        if (!name)
-                return NULL;
+        Tpm2Support s;
+        r = test_tpm2_support_pcrlock(&s);
+        if (r < 0)
+                return r;
 
-        return EVP_get_digestbyname(name);
-}
+        if (!arg_quiet) {
+                if (s == (TPM2_SUPPORT_FULL|TPM2_SUPPORT_API_PCRLOCK))
+                        printf("%syes%s\n", ansi_green(), ansi_normal());
+                else if (FLAGS_SET(s, TPM2_SUPPORT_FULL))
+                        printf("%sobsolete%s\n", ansi_red(), ansi_normal());
+                else if (s == TPM2_SUPPORT_NONE)
+                        printf("%sno%s\n", ansi_red(), ansi_normal());
+                else
+                        printf("%spartial%s\n", ansi_yellow(), ansi_normal());
+        }
 
-static int event_log_component_variant_calculate(
-                Tpm2PCRPredictionResult *result,
-                EventLogComponentVariant *variant,
-                uint32_t pcr) {
+        assert_cc((TPM2_SUPPORT_API|TPM2_SUPPORT_API_PCRLOCK) <= 255); /* make sure this is safe to use as process exit status */
 
-        assert(result);
-        assert(variant);
+        return ~s & (TPM2_SUPPORT_API|TPM2_SUPPORT_API_PCRLOCK);
+}
 
-        FOREACH_ARRAY(rr, variant->records, variant->n_records) {
-                EventLogRecord *rec = *rr;
+static int event_log_record_is_action_calling_efi_app(const EventLogRecord *rec) {
+        _cleanup_free_ char *d = NULL;
+        int r;
 
-                if (!EVENT_LOG_RECORD_IS_PCR(rec))
-                        continue;
+        assert(rec);
 
-                if (rec->pcr != pcr)
-                        continue;
+        /* Recognizes the special EV_EFI_ACTION that is issues when the firmware passes control to the boot loader. */
 
-                for (size_t i = 0; i < TPM2_N_HASH_ALGORITHMS; i++) {
-                        _cleanup_(EVP_MD_CTX_freep) EVP_MD_CTX *md_ctx = NULL;
-                        EventLogRecordBank *b;
+        if (!EVENT_LOG_RECORD_IS_FIRMWARE(rec))
+                return false;
 
-                        if (result->hash[i].size <= 0) /* already invalidated */
-                                continue;
+        if (rec->pcr != TPM2_PCR_BOOT_LOADER_CODE)
+                return false;
 
-                        b = event_log_record_find_bank(rec, tpm2_hash_algorithms[i]);
-                        if (!b) {
-                                /* Can't calculate, hence invalidate */
-                                result->hash[i] = (TPM2B_DIGEST) {};
-                                continue;
-                        }
+        if (rec->firmware_event_type != EV_EFI_ACTION)
+                return false;
 
-                        md_ctx = EVP_MD_CTX_new();
-                        if (!md_ctx)
-                                return log_oom();
+        if (rec->event_payload_valid != EVENT_PAYLOAD_VALID_YES) /* Insist the record is consistent */
+                return false;
 
-                        const EVP_MD *md = ASSERT_PTR(evp_from_tpm2_alg(tpm2_hash_algorithms[i]));
+        r = make_cstring(rec->firmware_payload, rec->firmware_payload_size, MAKE_CSTRING_ALLOW_TRAILING_NUL, &d);
+        if (r < 0)
+                return r;
 
-                        int sz = EVP_MD_size(md);
-                        assert(sz > 0);
-                        assert((size_t) sz <= sizeof_field(TPM2B_DIGEST, buffer));
+        return streq(d, "Calling EFI Application from Boot Option");
+}
 
-                        assert(sz == tpm2_hash_alg_to_size(tpm2_hash_algorithms[i]));
+static void enable_json_sse(void) {
+        /* We shall write this to a single output stream? We have to output two files, hence try to be smart
+         * and enable JSON SSE */
 
-                        assert(result->hash[i].size == (size_t) sz);
-                        assert(b->hash.size == (size_t) sz);
+        if (!arg_pcrlock_path && arg_pcrlock_auto)
+                return;
 
-                        if (EVP_DigestInit_ex(md_ctx, md, NULL) != 1)
-                                return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to initialize message digest.");
+        if (FLAGS_SET(arg_json_format_flags, SD_JSON_FORMAT_SSE))
+                return;
 
-                        if (EVP_DigestUpdate(md_ctx, result->hash[i].buffer, sz) != 1)
-                                return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to hash bank value.");
+        log_notice("Enabling JSON_SEQ mode, since writing two .pcrlock files to single output.");
+        arg_json_format_flags |= SD_JSON_FORMAT_SSE;
+}
 
-                        if (EVP_DigestUpdate(md_ctx, b->hash.buffer, sz) != 1)
-                                return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to hash data value.");
+static bool event_log_record_is_separator(const EventLogRecord *rec) {
+        assert(rec);
 
-                        unsigned l = (unsigned) sz;
-                        if (EVP_DigestFinal_ex(md_ctx, result->hash[i].buffer, &l) != 1)
-                                return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to finalize message digest.");
+        /* Recognizes EV_SEPARATOR events */
 
-                        assert(l == (unsigned) sz);
-                }
-        }
+        if (!EVENT_LOG_RECORD_IS_FIRMWARE(rec))
+                return false;
 
-        return 0;
+        if (rec->firmware_event_type != EV_SEPARATOR)
+                return false;
+
+        return rec->event_payload_valid == EVENT_PAYLOAD_VALID_YES; /* Insist the record is consistent */
 }
 
-static int event_log_predict_pcrs(
-                EventLog *el,
-                Tpm2PCRPrediction *context,
-                Tpm2PCRPredictionResult *parent_result,
-                size_t component_index,
-                uint32_t pcr,
-                const char *path) {
+static int verb_lock_firmware(int argc, char *argv[], uintptr_t _data, void *userdata) {
+        _cleanup_(sd_json_variant_unrefp) sd_json_variant *array_early = NULL, *array_late = NULL;
+        _cleanup_(event_log_freep) EventLog *el = NULL;
+        uint32_t always_mask, separator_mask, separator_seen_mask = 0, action_seen_mask = 0;
+        const char *default_pcrlock_early_path, *default_pcrlock_late_path;
+        int r;
 
-        EventLogComponent *component;
-        int count = 0, r;
+        enable_json_sse();
 
-        assert(el);
-        assert(context);
-        assert(parent_result);
+        /* The PCRs we intend to cover. Note that we measure firmware, external *and* boot loader code/config
+         * here – but the latter only until the "separator" events are seen, which tell us where transition
+         * into OS boot loader happens. This reflects the fact that on some systems the firmware already
+         * measures some firmware-supplied apps into PCR 4. (e.g. Thinkpad X1 Gen9) */
+        if (endswith(argv[0], "firmware-code")) {
+                always_mask = (UINT32_C(1) << TPM2_PCR_PLATFORM_CODE) |      /* → 0 */
+                        (UINT32_C(1) << TPM2_PCR_EXTERNAL_CODE);             /* → 2 */
 
-        /* Check if we reached the end of the components, generate a result, and backtrack */
-        if (component_index >= el->n_components ||
-            (arg_location_end && strcmp(el->components[component_index]->id, arg_location_end) > 0)) {
-                r = pcr_prediction_add_result(context, parent_result, pcr, path);
-                if (r < 0)
-                        return r;
+                separator_mask = UINT32_C(1) << TPM2_PCR_BOOT_LOADER_CODE;   /* → 4 */
 
-                return 1;
-        }
+                default_pcrlock_early_path = PCRLOCK_FIRMWARE_CODE_EARLY_PATH;
+                default_pcrlock_late_path = PCRLOCK_FIRMWARE_CODE_LATE_PATH;
+        } else {
+                assert(endswith(argv[0], "firmware-config"));
+                always_mask = (UINT32_C(1) << TPM2_PCR_PLATFORM_CONFIG) |    /* → 1 */
+                        (UINT32_C(1) << TPM2_PCR_EXTERNAL_CONFIG);           /* → 3 */
 
-        component = ASSERT_PTR(el->components[component_index]);
+                separator_mask = UINT32_C(1) << TPM2_PCR_BOOT_LOADER_CONFIG; /* → 5 */
 
-        /* Check if we are just about to process a component after start, if so record a result and continue. */
-        if (arg_location_start && strcmp(component->id, arg_location_start) > 0) {
-                r = pcr_prediction_add_result(context, parent_result, pcr, path);
-                if (r < 0)
-                        return r;
+                default_pcrlock_early_path = PCRLOCK_FIRMWARE_CONFIG_EARLY_PATH;
+                default_pcrlock_late_path = PCRLOCK_FIRMWARE_CONFIG_LATE_PATH;
         }
 
-        FOREACH_ARRAY(ii, component->variants, component->n_variants) {
-                _cleanup_free_ Tpm2PCRPredictionResult *result = NULL;
-                EventLogComponentVariant *variant = *ii;
-                _cleanup_free_ char *subpath = NULL;
+        el = event_log_new();
+        if (!el)
+                return log_oom();
 
-                /* Operate on a copy of the result */
+        r = event_log_add_algorithms_from_environment(el);
+        if (r < 0)
+                return r;
 
-                if (path)
-                        subpath = strjoin(path, ":", component->id);
-                else
-                        subpath = strdup(component->id);
-                if (!subpath)
-                        return log_oom();
+        r = event_log_load(el);
+        if (r < 0)
+                return r;
 
-                if (!streq(component->id, variant->id))
-                        if (!strextend(&subpath, "@", variant->id))
-                                return log_oom();
+        r = event_log_read_pcrs(el);
+        if (r < 0)
+                return r;
 
-                result = newdup(Tpm2PCRPredictionResult, parent_result, 1);
-                if (!result)
-                        return log_oom();
+        r = event_log_calculate_pcrs(el);
+        if (r < 0)
+                return r;
 
-                r = event_log_component_variant_calculate(
-                                result,
-                                variant,
-                                pcr);
-                if (r < 0)
-                        return r;
-
-                r = event_log_predict_pcrs(
-                                el,
-                                context,
-                                result,
-                                component_index + 1, /* Next component */
-                                pcr,
-                                subpath);
-                if (r < 0)
-                        return r;
-
-                count += r;
-        }
-
-        return count;
-}
-
-static ssize_t event_log_calculate_component_combinations(EventLog *el) {
-        ssize_t count = 1;
-        assert(el);
-
-        FOREACH_ARRAY(cc, el->components, el->n_components) {
-                EventLogComponent *c = *cc;
-
-                assert(c->n_variants > 0);
-
-                /* Overflow check */
-                if (c->n_variants > (size_t) (SSIZE_MAX/count))
-                        return log_error_errno(SYNTHETIC_ERRNO(E2BIG), "Too many component combinations.");
-                count *= c->n_variants;
-        }
-
-        return count;
-}
+        r = event_log_validate_record_hashes(el);
+        if (r < 0)
+                return r;
 
-static int event_log_show_predictions(Tpm2PCRPrediction *context, uint16_t alg) {
-        int r;
+        /* Before we base anything on the event log records for any of the selected PCRs, let's check that
+         * the event log state checks out for them. */
 
-        assert(context);
+        r = event_log_pcr_mask_checks_out(el, always_mask|separator_mask);
+        if (r < 0)
+                return r;
 
-        pager_open(arg_pager_flags);
+        // FIXME: before doing this, validate ahead-of-time that EV_SEPARATOR records exist for all entries,
+        //        and exactly once
 
-        if (sd_json_format_enabled(arg_json_format_flags)) {
-                _cleanup_(sd_json_variant_unrefp) sd_json_variant *j = NULL;
+        FOREACH_ARRAY(rr, el->records, el->n_records) {
+                _cleanup_(sd_json_variant_unrefp) sd_json_variant *digests = NULL;
+                EventLogRecord *rec = *rr;
+                uint32_t bit = UINT32_C(1) << rec->pcr;
 
-                for (size_t i = 0; i < TPM2_N_HASH_ALGORITHMS; i++) {
-                        _cleanup_(sd_json_variant_unrefp) sd_json_variant *aj = NULL;
+                if (!EVENT_LOG_RECORD_IS_FIRMWARE(rec))
+                        continue;
 
-                        r = tpm2_pcr_prediction_to_json(
-                                        context,
-                                        tpm2_hash_algorithms[i],
-                                        &aj);
-                        if (r < 0)
-                                return r;
+                if (!FLAGS_SET(always_mask, bit) &&
+                    !(FLAGS_SET(separator_mask, bit) && !FLAGS_SET(separator_seen_mask|action_seen_mask, bit)))
+                        continue;
 
-                        if (sd_json_variant_elements(aj) == 0)
-                                continue;
+                /* If we hit the separator record, we stop processing the PCRs listed in `separator_mask` */
+                if (event_log_record_is_separator(rec)) {
+                        separator_seen_mask |= bit;
+                        continue;
+                }
 
-                        r = sd_json_variant_set_field(
-                                        &j,
-                                        tpm2_hash_alg_to_string(tpm2_hash_algorithms[i]),
-                                        aj);
-                        if (r < 0)
-                                return log_error_errno(r, "Failed to add prediction bank to object: %m");
+                /* If we hit the special "Calling EFI Application from Boot Option" action we treat this the
+                 * same as a separator here, as that's where firmware passes control to boot loader. Note
+                 * that some EFI implementations forget to generate one of them. */
+                r = event_log_record_is_action_calling_efi_app(rec);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to check if event is 'Calling EFI Application from Boot Option' action: %m");
+                if (r > 0) {
+                        action_seen_mask |= bit;
+                        continue;
                 }
 
-                if (!j) {
-                        r = sd_json_variant_new_object(&j, NULL, 0);
+                LIST_FOREACH(banks, bank, rec->banks) {
+                        r = sd_json_variant_append_arraybo(
+                                        &digests,
+                                        SD_JSON_BUILD_PAIR_STRING("hashAlg", tpm2_hash_alg_to_string(bank->algorithm)),
+                                        SD_JSON_BUILD_PAIR("digest", SD_JSON_BUILD_HEX(bank->hash.buffer, bank->hash.size)));
                         if (r < 0)
-                                return log_error_errno(r, "Failed to allocated empty object: %m");
+                                return log_error_errno(r, "Failed to build digests array: %m");
                 }
 
-                sd_json_variant_dump(j, arg_json_format_flags, /* f= */ NULL, /* prefix= */ NULL);
-                return 0;
+                r = sd_json_variant_append_arraybo(
+                                FLAGS_SET(separator_seen_mask, bit) ? &array_late : &array_early,
+                                SD_JSON_BUILD_PAIR_UNSIGNED("pcr", rec->pcr),
+                                SD_JSON_BUILD_PAIR_VARIANT("digests", digests));
+                if (r < 0)
+                        return log_error_errno(r, "Failed to build record array: %m");
         }
 
-        for (uint32_t pcr = 0; pcr < TPM2_PCRS_MAX; pcr++) {
-                Tpm2PCRPredictionResult *result;
-                if (!BIT_SET(context->pcrs, pcr))
-                        continue;
-
-                if (ordered_set_isempty(context->results[pcr])) {
-                        printf("No results for PCR %u (%s).\n", pcr, tpm2_pcr_index_to_string(pcr));
-                        continue;
-                }
-
-                printf("%sResults for PCR %u (%s):%s\n", ansi_underline(), pcr, tpm2_pcr_index_to_string(pcr), ansi_normal());
-
-                ORDERED_SET_FOREACH(result, context->results[pcr]) {
+        r = write_pcrlock(array_early, default_pcrlock_early_path);
+        if (r < 0)
+                return r;
 
-                        _cleanup_free_ char *aa = NULL, *h = NULL;
-                        const char *a;
+        return write_pcrlock(array_late, default_pcrlock_late_path);
+}
 
-                        TPM2B_DIGEST *hash = tpm2_pcr_prediction_result_get_hash(result, alg);
-                        if (!hash)
-                                continue;
+static int verb_unlock_firmware(int argc, char *argv[], uintptr_t _data, void *userdata) {
+        const char *default_pcrlock_early_path, *default_pcrlock_late_path;
+        int r;
 
-                        a = ASSERT_PTR(tpm2_hash_alg_to_string(alg));
-                        aa = strdup(a);
-                        if (!aa)
-                                return log_oom();
+        if (endswith(argv[0], "firmware-code")) {
+                default_pcrlock_early_path = PCRLOCK_FIRMWARE_CODE_EARLY_PATH;
+                default_pcrlock_late_path = PCRLOCK_FIRMWARE_CODE_LATE_PATH;
+        } else {
+                default_pcrlock_early_path = PCRLOCK_FIRMWARE_CONFIG_EARLY_PATH;
+                default_pcrlock_late_path = PCRLOCK_FIRMWARE_CONFIG_LATE_PATH;
+        }
 
-                        ascii_strlower(aa);
+        r = unlink_pcrlock(default_pcrlock_early_path);
+        if (r < 0)
+                return r;
 
-                        h = hexmem(hash->buffer, hash->size);
-                        if (!h)
-                                return log_oom();
+        if (arg_pcrlock_path) /* if the path is specified don't delete the same thing twice */
+                return 0;
 
-                        printf("  %s%-6s:%s %s\n", ansi_grey(), aa, ansi_normal(), h);
-                }
-        }
+        r = unlink_pcrlock(default_pcrlock_late_path);
+        if (r < 0)
+                return r;
 
         return 0;
 }
 
-static int tpm2_pcr_prediction_run(
-                EventLog *el,
-                Tpm2PCRPrediction *context) {
+static int verb_lock_secureboot_policy(int argc, char *argv[], uintptr_t _data, void *userdata) {
+        static const struct {
+                sd_id128_t id;
+                const char *name;
+                int synthesize_empty; /* 0 → fail, > 0 → synthesize empty db, < 0 → skip */
+        } variables[] = {
+                { EFI_VENDOR_GLOBAL,   "SecureBoot", 0 },
+                { EFI_VENDOR_GLOBAL,   "PK",         1 },
+                { EFI_VENDOR_GLOBAL,   "KEK",        1 },
+                { EFI_VENDOR_DATABASE, "db",         1 },
+                { EFI_VENDOR_DATABASE, "dbx",        1 },
+                { EFI_VENDOR_DATABASE, "dbt",       -1 },
+                { EFI_VENDOR_DATABASE, "dbr",       -1 },
+        };
 
+        _cleanup_(sd_json_variant_unrefp) sd_json_variant *array = NULL;
         int r;
 
-        assert(el);
-        assert(context);
+        /* Generates expected records from the current SecureBoot state, as readable in the EFI variables
+         * right now. */
 
-        for (uint32_t pcr = 0; pcr < TPM2_PCRS_MAX; pcr++) {
-                _cleanup_free_ Tpm2PCRPredictionResult *result = NULL;
+        FOREACH_ELEMENT(vv, variables) {
+                _cleanup_(sd_json_variant_unrefp) sd_json_variant *record = NULL;
 
-                if (!BIT_SET(context->pcrs, pcr))
-                        continue;
+                _cleanup_free_ char *name = NULL;
+                if (asprintf(&name, "%s-" SD_ID128_UUID_FORMAT_STR, vv->name, SD_ID128_FORMAT_VAL(vv->id)) < 0)
+                        return log_oom();
 
-                result = new0(Tpm2PCRPredictionResult, 1);
-                if (!result)
+                _cleanup_free_ void *data = NULL;
+                size_t data_size;
+                r = efi_get_variable(name, NULL, &data, &data_size);
+                if (r < 0) {
+                        if (r != -ENOENT || vv->synthesize_empty == 0)
+                                return log_error_errno(r, "Failed to read EFI variable '%s': %m", name);
+                        if (vv->synthesize_empty < 0)
+                                continue;
+
+                        /* If the main database variables are not set we don't consider this an error, but
+                         * measure an empty database instead. */
+                        log_debug("EFI variable %s is not set, synthesizing empty variable for measurement.", name);
+                        data_size = 0;
+                }
+
+                _cleanup_free_ char16_t* name16 = utf8_to_utf16(vv->name, SIZE_MAX);
+                if (!name16)
                         return log_oom();
+                size_t name16_bytes = char16_strlen(name16) * 2;
 
-                for (size_t i = 0; i < TPM2_N_HASH_ALGORITHMS; i++)
-                        event_log_initial_pcr_state(el, pcr, tpm2_hash_alg_to_size(tpm2_hash_algorithms[i]), result->hash + i);
+                size_t vdata_size = offsetof(UEFI_VARIABLE_DATA, unicodeName) + name16_bytes + data_size;
+                _cleanup_free_ UEFI_VARIABLE_DATA *vdata = malloc(vdata_size);
+                if (!vdata)
+                        return log_oom();
 
-                r = event_log_predict_pcrs(
-                                el,
-                                context,
-                                result,
-                                /* component_index= */ 0,
-                                pcr,
-                                /* path= */ NULL);
+                *vdata = (UEFI_VARIABLE_DATA) {
+                        .unicodeNameLength = name16_bytes / 2,
+                        .variableDataLength = data_size,
+                };
+
+                efi_id128_to_guid(vv->id, vdata->variableName);
+                memcpy(mempcpy(vdata->unicodeName, name16, name16_bytes), data, data_size);
+
+                r = make_pcrlock_record(TPM2_PCR_SECURE_BOOT_POLICY /* =7 */, vdata, vdata_size, &record);
                 if (r < 0)
                         return r;
+
+                r = sd_json_variant_append_array(&array, record);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to append to JSON array: %m");
         }
 
-        return 0;
+        return write_pcrlock(array, PCRLOCK_SECUREBOOT_POLICY_PATH);
 }
 
-static int verb_predict(int argc, char *argv[], uintptr_t _data, void *userdata) {
-        _cleanup_(tpm2_pcr_prediction_done) Tpm2PCRPrediction context = {
-                arg_pcr_mask != 0 ? arg_pcr_mask : DEFAULT_PCR_MASK,
-        };
-        _cleanup_(event_log_freep) EventLog *el = NULL;
-        ssize_t count;
+static int verb_unlock_secureboot_policy(int argc, char *argv[], uintptr_t _data, void *userdata) {
+        return unlink_pcrlock(PCRLOCK_SECUREBOOT_POLICY_PATH);
+}
+
+static int event_log_record_is_secureboot_variable(EventLogRecord *rec, sd_id128_t uuid, const char *name) {
+        _cleanup_free_ char *found_name = NULL;
+        sd_id128_t found_uuid;
         int r;
 
-        r = event_log_load_and_process(&el);
-        if (r < 0)
-                return r;
+        assert(rec);
+        assert(name);
 
-        count = event_log_calculate_component_combinations(el);
-        if (count < 0)
-                return count;
+        if (!EVENT_LOG_RECORD_IS_FIRMWARE(rec))
+                return false;
 
-        log_info("%zi combinations of components.", count);
+        if (rec->pcr != TPM2_PCR_SECURE_BOOT_POLICY)
+                return false;
 
-        r = event_log_reduce_to_safe_pcrs(el, &context.pcrs);
-        if (r < 0)
-                return r;
+        if (rec->event_payload_valid != EVENT_PAYLOAD_VALID_YES)
+                return false;
 
-        r = tpm2_pcr_prediction_run(el, &context);
+        if (rec->firmware_event_type != EV_EFI_VARIABLE_DRIVER_CONFIG)
+                return false;
+
+        r = event_log_record_parse_variable_data(rec, &found_uuid, &found_name);
+        if (r == -EBADMSG)
+                return false;
         if (r < 0)
                 return r;
 
-        return event_log_show_predictions(&context, el->primary_algorithm);
-}
-
-static int remove_policy_file(const char *path) {
-        assert(path);
+        if (!sd_id128_equal(found_uuid, uuid))
+                return false;
 
-        if (unlink(path) < 0) {
-                if (errno == ENOENT)
-                        return 0;
+        return streq(found_name, name);
+}
 
-                return log_error_errno(errno, "Failed to remove policy file '%s': %m", path);
-        }
+static bool event_log_record_is_secureboot_authority(EventLogRecord *rec) {
+        assert(rec);
 
-        log_info("Removed policy file '%s'.", path);
-        return 1;
-}
+        if (!EVENT_LOG_RECORD_IS_FIRMWARE(rec))
+                return false;
 
-static int determine_boot_policy_file(char **ret_path, char **ret_credential_name) {
-        int r;
+        if (rec->pcr != TPM2_PCR_SECURE_BOOT_POLICY)
+                return false;
 
-        _cleanup_free_ char *path = NULL;
-        r = get_global_boot_credentials_path(&path);
-        if (r < 0)
-                return r;
-        if (r == 0) {
-                if (ret_path)
-                        *ret_path = NULL;
-                if (ret_credential_name)
-                        *ret_credential_name = NULL;
-                return 0; /* not found! */
-        }
+        if (rec->event_payload_valid != EVENT_PAYLOAD_VALID_YES)
+                return false;
 
-        sd_id128_t machine_id;
-        r = sd_id128_get_machine(&machine_id);
-        if (r < 0)
-                return log_error_errno(r, "Failed to read machine ID: %m");
+        return rec->firmware_event_type == EV_EFI_VARIABLE_AUTHORITY;
+}
 
-        r = boot_entry_token_ensure(
-                        /* root= */ NULL,
-                        /* conf_root= */ NULL,
-                        machine_id,
-                        /* machine_id_is_random= */ false,
-                        &arg_entry_token_type,
-                        &arg_entry_token);
-        if (r < 0)
-                return r;
+static int event_log_ensure_secureboot_consistency(EventLog *el) {
+        static const struct {
+                sd_id128_t id;
+                const char *name;
+                bool required;
+        } table[] = {
+                { EFI_VENDOR_GLOBAL,   "SecureBoot", true  },
+                { EFI_VENDOR_GLOBAL,   "PK",         true  },
+                { EFI_VENDOR_GLOBAL,   "KEK",        true  },
+                { EFI_VENDOR_DATABASE, "db",         true  },
+                { EFI_VENDOR_DATABASE, "dbx",        true  },
+                { EFI_VENDOR_DATABASE, "dbt",        false },
+                { EFI_VENDOR_DATABASE, "dbr",        false },
+                // FIXME: ensure we also find the separator here
+        };
 
-        _cleanup_free_ char *fn = strjoin("pcrlock.", arg_entry_token, ".cred");
-        if (!fn)
-                return log_oom();
+        EventLogRecord *records[ELEMENTSOF(table)] = {};
+        EventLogRecord *first_authority = NULL;
 
-        if (!filename_is_valid(fn))
-                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Credential name '%s' would not be a valid file name, refusing.", fn);
+        assert(el);
 
-        _cleanup_free_ char *joined = NULL;
-        if (ret_path) {
-                joined = path_join(path, fn);
-                if (!joined)
-                        return log_oom();
-        }
+        /* Ensures that the PCR 7 records are complete and in order. Before we lock down PCR 7 we want to
+         * ensure its state is actually consistent. */
 
-        _cleanup_free_ char *cn = NULL;
-        if (ret_credential_name) {
-                /* The .cred suffix of the file is stripped when PID 1 imports the credential, hence exclude it from
-                 * the embedded credential name. */
-                cn = strjoin("pcrlock.", arg_entry_token);
-                if (!cn)
-                        return log_oom();
+        FOREACH_ARRAY(rr, el->records, el->n_records) {
+                EventLogRecord *rec = *rr;
+                size_t found = SIZE_MAX;
 
-                ascii_strlower(cn); /* lowercase this file, no matter what, since stored on VFAT, and we don't want
-                                     * to run into case change incompatibilities */
-        }
+                if (event_log_record_is_secureboot_authority(rec)) {
+                        if (first_authority)
+                                continue;
 
-        if (ret_path)
-                *ret_path = TAKE_PTR(joined);
+                        first_authority = rec;
+                        // FIXME: also check that each authority record's data is also listed in 'db'
+                        continue;
+                }
 
-        if (ret_credential_name)
-                *ret_credential_name = TAKE_PTR(cn);
+                for (size_t i = 0; i < ELEMENTSOF(table); i++)
+                        if (event_log_record_is_secureboot_variable(rec, table[i].id, table[i].name)) {
+                                found = i;
+                                break;
+                        }
+                if (found == SIZE_MAX)
+                        continue;
 
-        return 1; /* found! */
-}
+                /* Require the authority records always come *after* database measurements */
+                if (first_authority)
+                        return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "SecureBoot authority before variable, refusing.");
 
-static int write_boot_policy_file(const char *json_text) {
-        _cleanup_free_ char *boot_policy_file = NULL, *credential_name = NULL;
-        int r;
+                /* Check for duplicates */
+                if (records[found])
+                        return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Duplicate '%s' record, refusing.", rec->description);
 
-        assert(json_text);
+                /* Check for order */
+                for (size_t j = found + 1; j < ELEMENTSOF(table); j++)
+                        if (records[j])
+                                return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "'%s' record before '%s' record, refusing.", records[j]->description, rec->description);
 
-        r = determine_boot_policy_file(&boot_policy_file, &credential_name);
-        if (r < 0)
-                return r;
-        if (r == 0) {
-                log_info("Did not find XBOOTLDR/ESP partition, not writing boot policy file.");
-                return 0;
+                records[found] = rec;
         }
 
-        _cleanup_(iovec_done) struct iovec encoded = {};
-        r = encrypt_credential_and_warn(
-                        CRED_AES256_GCM_BY_NULL,
-                        credential_name,
-                        now(CLOCK_REALTIME),
-                        /* not_after= */ USEC_INFINITY,
-                        /* tpm2_device= */ NULL,
-                        /* tpm2_hash_pcr_mask= */ 0,
-                        /* tpm2_pubkey_path= */ NULL,
-                        /* tpm2_pubkey_pcr_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");
-
-        r = write_base64_file_at(
-                        AT_FDCWD,
-                        boot_policy_file,
-                        &encoded,
-                        WRITE_STRING_FILE_ATOMIC|WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_SYNC|WRITE_STRING_FILE_MKDIR_0755|WRITE_STRING_FILE_LABEL);
-        if (r < 0)
-                return log_error_errno(r, "Failed to write boot policy file to '%s': %m", boot_policy_file);
+        /* Check for existence */
+        for (size_t i = 0; i < ELEMENTSOF(table); i++)
+                if (table[i].required && !records[i])
+                        return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Required record '%s' not found, refusing.", table[i].name);
 
-        log_info("Written new boot policy to '%s'.", boot_policy_file);
-        return 1;
+        /* At this point we know that all required variables have been measured, in the right order. */
+        return 0;
 }
 
-static int make_policy(bool force, RecoveryPinMode recovery_pin_mode) {
+static int verb_lock_secureboot_authority(int argc, char *argv[], uintptr_t _data, void *userdata) {
+        _cleanup_(sd_json_variant_unrefp) sd_json_variant *array = NULL;
+        _cleanup_(event_log_freep) EventLog *el = NULL;
         int r;
 
-        /* Here's how this all works: after predicting all possible PCR values for next boot (with
-         * alternatives) we'll calculate a policy from it as a combination of PolicyPCR + PolicyOR
-         * expressions. This is then stored in an NV index. When a component of the boot process is changed a
-         * new prediction is made and the NV index updated (which automatically invalidates any older
-         * policies).
-         *
-         * Whenever we want to lock an encrypted object (for example FDE) against this policy, we'll use a
-         * 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
-         * either generate locally or which the user can provide us with) which can also be used for
-         * recovery. This PIN is sealed to the TPM and is locked via PolicyAuthorizeNV to the NV index it
-         * protects (i.e. we dogfood 🌭 🐶 hard here). This means in order to update such a policy we need
-         * the policy to pass.
-         *
-         * Information about the used NV Index, the SRK of the TPM, the sealed PIN and the current PCR
-         * prediction data are stored in a JSON file in /var/lib/. In order to be able to unlock root disks
-         * this data must be also copied to the ESP so that it is available to the initrd. The data is not
-         * sensitive, as SRK and NV index are pinned by it, and the prediction data must match the NV index
-         * to be useful. */
+        /* Lock down the EV_EFI_VARIABLE_AUTHORITY records from the existing log. Note that there's not too
+         * much value in locking this down too much, since it stores only the result of the primary database
+         * checks, and that's what we should bind policy to. Moreover it's hard to predict, since extension
+         * card firmware validation will result in additional records here. */
 
-        usec_t start_usec = now(CLOCK_MONOTONIC);
+        if (!is_efi_secure_boot()) {
+                log_info("SecureBoot disabled, not generating authority .pcrlock file.");
+                return unlink_pcrlock(PCRLOCK_SECUREBOOT_AUTHORITY_PATH);
+        }
 
-        _cleanup_(event_log_freep) EventLog *el = NULL;
-        r = event_log_load_and_process(&el);
+        el = event_log_new();
+        if (!el)
+                return log_oom();
+
+        r = event_log_add_algorithms_from_environment(el);
         if (r < 0)
                 return r;
 
-        _cleanup_(tpm2_pcr_prediction_done) Tpm2PCRPrediction new_prediction = {
-                arg_pcr_mask != 0 ? arg_pcr_mask : DEFAULT_PCR_MASK,
-        };
-        r = event_log_reduce_to_safe_pcrs(el, &new_prediction.pcrs);
+        r = event_log_load(el);
         if (r < 0)
                 return r;
 
-        if (!force && new_prediction.pcrs == 0)
-                log_notice("Set of PCRs to use for policy is empty. Generated policy will not provide any protection in its current form. Proceeding.");
-
-        usec_t predict_start_usec = now(CLOCK_MONOTONIC);
+        r = event_log_read_pcrs(el);
+        if (r < 0)
+                return r;
 
-        r = tpm2_pcr_prediction_run(el, &new_prediction);
+        r = event_log_calculate_pcrs(el);
         if (r < 0)
                 return r;
 
-        log_info("Predicted future PCRs in %s.", FORMAT_TIMESPAN(usec_sub_unsigned(now(CLOCK_MONOTONIC), predict_start_usec), 1));
+        /* Before we base anything on the event log records, let's check that the event log state checks
+         * out. */
 
-        _cleanup_(sd_json_variant_unrefp) sd_json_variant *new_prediction_json = NULL;
-        r = tpm2_pcr_prediction_to_json(&new_prediction, el->primary_algorithm, &new_prediction_json);
+        r = event_log_pcr_mask_checks_out(el, UINT32_C(1) << TPM2_PCR_SECURE_BOOT_POLICY);
         if (r < 0)
                 return r;
 
-        if (DEBUG_LOGGING)
-                (void) sd_json_variant_dump(new_prediction_json, SD_JSON_FORMAT_PRETTY_AUTO|SD_JSON_FORMAT_COLOR_AUTO, stderr, NULL);
+        r = event_log_validate_record_hashes(el);
+        if (r < 0)
+                return r;
 
-        /* v257 and older mistakenly used --pcrlock= for the path. To keep backward compatibility, let's fallback to it when
-         * --policy= is unspecified but --pcrlock is specified. */
-        if (!arg_policy_path && arg_pcrlock_path) {
-                log_notice("Specified --pcrlock= option for make-policy command. Please use --policy= instead.");
+        r = event_log_ensure_secureboot_consistency(el);
+        if (r < 0)
+                return r;
 
-                arg_policy_path = strdup(arg_pcrlock_path);
-                if (!arg_policy_path)
-                        return log_oom();
+        FOREACH_ARRAY(rr, el->records, el->n_records) {
+                _cleanup_(sd_json_variant_unrefp) sd_json_variant *digests = NULL;
+                EventLogRecord *rec = *rr;
+
+                if (!event_log_record_is_secureboot_authority(rec))
+                        continue;
+
+                log_debug("Locking down authority '%s'.", strna(rec->description));
+
+                LIST_FOREACH(banks, bank, rec->banks) {
+                        r = sd_json_variant_append_arraybo(
+                                        &digests,
+                                        SD_JSON_BUILD_PAIR_STRING("hashAlg", tpm2_hash_alg_to_string(bank->algorithm)),
+                                        SD_JSON_BUILD_PAIR("digest", SD_JSON_BUILD_HEX(bank->hash.buffer, bank->hash.size)));
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to build digests array: %m");
+                }
+
+                r = sd_json_variant_append_arraybo(
+                                &array,
+                                SD_JSON_BUILD_PAIR_UNSIGNED("pcr", rec->pcr),
+                                SD_JSON_BUILD_PAIR_VARIANT("digests", digests));
+                if (r < 0)
+                        return log_error_errno(r, "Failed to build record array: %m");
         }
 
-        _cleanup_(tpm2_pcrlock_policy_done) Tpm2PCRLockPolicy old_policy = {};
-        r = tpm2_pcrlock_policy_load(arg_policy_path, &old_policy);
+        return write_pcrlock(array, PCRLOCK_SECUREBOOT_AUTHORITY_PATH);
+}
+
+static int verb_unlock_secureboot_authority(int argc, char *argv[], uintptr_t _data, void *userdata) {
+        return unlink_pcrlock(PCRLOCK_SECUREBOOT_AUTHORITY_PATH);
+}
+
+static int verb_lock_gpt(int argc, char *argv[], uintptr_t _data, void *userdata) {
+        _cleanup_(sd_json_variant_unrefp) sd_json_variant *array = NULL, *record = NULL;
+        _cleanup_(sd_device_unrefp) sd_device *d = NULL;
+        uint8_t h[2 * 4096]; /* space for at least two 4K sectors. GPT header should definitely be in here */
+        uint64_t start, n_members, member_size;
+        _cleanup_close_ int fd = -EBADF;
+        const GptHeader *p;
+        size_t found = 0;
+        ssize_t n;
+        int r;
+
+        r = block_device_new_from_path(
+                        argc >= 2 ? argv[1] : "/",
+                        BLOCK_DEVICE_LOOKUP_WHOLE_DISK|BLOCK_DEVICE_LOOKUP_BACKING|BLOCK_DEVICE_LOOKUP_ORIGINATING,
+                        &d);
         if (r < 0)
-                return r;
+                return log_error_errno(r, "Failed to determine root block device: %m");
 
-        bool have_old_policy = r > 0;
+        fd = sd_device_open(d, O_CLOEXEC|O_RDONLY|O_NOCTTY);
+        if (fd < 0)
+                return log_error_errno(fd, "Failed to open root block device: %m");
 
-        /* When we update the policy the old serializations for NV, SRK, PIN remain the same */
-        _cleanup_(iovec_done) struct iovec
-                nv_blob = TAKE_STRUCT(old_policy.nv_handle),
-                nv_public_blob = TAKE_STRUCT(old_policy.nv_public),
-                srk_blob = TAKE_STRUCT(old_policy.srk_handle),
-                pin_public = TAKE_STRUCT(old_policy.pin_public),
-                pin_private = TAKE_STRUCT(old_policy.pin_private);
+        n = pread(fd, &h, sizeof(h), 0);
+        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.");
 
-        if (have_old_policy) {
-                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);
+        /* Try a couple of sector sizes */
+        for (size_t sz = 512; sz <= 4096; sz <<= 1) {
+                assert(sizeof(h) >= sz * 2);
+                p = (const GptHeader*) (h + sz); /* 2nd sector */
 
-                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 0; /* NOP */
-                }
+                if (!gpt_header_has_signature(p))
+                        continue;
+
+                if (found != 0)
+                        return log_error_errno(SYNTHETIC_ERRNO(ENOTUNIQ),
+                                               "Disk has partition table for multiple sector sizes, refusing.");
+
+                found = sz;
         }
 
-        _cleanup_(tpm2_context_unrefp) Tpm2Context *tc = NULL;
-        r = tpm2_context_new_or_warn(/* device= */ NULL, &tc);
+        if (found == 0)
+                return log_error_errno(SYNTHETIC_ERRNO(EBADMSG),
+                                       "Disk does not have GPT partition table, refusing.");
+
+        p = (const GptHeader*) (h + found);
+
+        if (le32toh(p->header_size) > found)
+                return log_error_errno(SYNTHETIC_ERRNO(EBADMSG),
+                                       "GPT header size over long (%" PRIu32 "), refusing.", le32toh(p->header_size));
+
+        start = le64toh(p->partition_entry_lba);
+        if (start > UINT64_MAX / found)
+                return log_error_errno(SYNTHETIC_ERRNO(EBADMSG),
+                                       "Partition table start offset overflow, refusing.");
+
+        member_size = le32toh(p->size_of_partition_entry);
+        if (member_size < sizeof(GptPartitionEntry))
+                return log_error_errno(SYNTHETIC_ERRNO(EBADMSG),
+                                       "Partition entry size too short, refusing.");
+
+        n_members = le32toh(p->number_of_partition_entries);
+        uint64_t member_bufsz = n_members * member_size;
+        if (member_bufsz > 1U*1024U*1024U)
+                return log_error_errno(SYNTHETIC_ERRNO(EBADMSG),
+                                       "Partition table size too large, refusing.");
+
+        member_bufsz = ROUND_UP(member_bufsz, found);
+
+        _cleanup_free_ void *members = malloc(member_bufsz);
+        if (!members)
+                return log_oom();
+
+        n = pread(fd, members, member_bufsz, start * found);
+        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.");
+
+        size_t vdata_size = le32toh(p->header_size) + sizeof(le64_t) + member_size * n_members;
+        _cleanup_free_ void *vdata = malloc0(vdata_size);
+        if (!vdata)
+                return log_oom();
+
+        void *n_measured_entries = mempcpy(vdata, p, sizeof(GptHeader)); /* n_measured_entries is a 64bit value */
+
+        void *qq = (uint8_t*) n_measured_entries + sizeof(le64_t);
+
+        for (uint64_t i = 0; i < n_members; i++) {
+                const GptPartitionEntry *entry = (const GptPartitionEntry*) ((const uint8_t*) members + (member_size * i));
+
+                if (memeqzero(entry->partition_type_guid, sizeof(entry->partition_type_guid)))
+                        continue;
+
+                qq = mempcpy(qq, entry, member_size);
+                unaligned_write_le64(n_measured_entries, unaligned_read_le64(n_measured_entries) + 1);
+        }
+
+        vdata_size = (uint8_t*) qq - (uint8_t*) vdata;
+
+        r = make_pcrlock_record(TPM2_PCR_BOOT_LOADER_CONFIG /* =5 */, vdata, vdata_size, &record);
         if (r < 0)
                 return r;
 
-        if (!tpm2_supports_command(tc, TPM2_CC_PolicyAuthorizeNV))
-                return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "TPM2 does not support PolicyAuthorizeNV command, refusing.");
-        if (!tpm2_supports_alg(tc, TPM2_ALG_SHA256))
-                return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "TPM2 does not support SHA-256 hash algorithm, refusing.");
+        r = sd_json_variant_new_array(&array, &record, 1);
+        if (r < 0)
+                return log_error_errno(r, "Failed to append to JSON array: %m");
 
-        _cleanup_(tpm2_handle_freep) Tpm2Handle *srk_handle = NULL;
+        return write_pcrlock(array, PCRLOCK_GPT_PATH);
+}
 
-        r = tpm2_deserialize(
-                        tc,
-                        &srk_blob,
-                        &srk_handle);
-        if (r < 0)
-                return log_error_errno(r, "Failed to deserialize SRK TR: %m");
-        if (r == 0) {
-                r = tpm2_get_or_create_srk(
-                                tc,
-                                /* session= */ NULL,
-                                /* ret_public= */ NULL,
-                                /* ret_name= */ NULL,
-                                /* ret_qname= */ NULL,
-                                &srk_handle);
-                if (r < 0)
-                        return log_error_errno(r, "Failed to install SRK: %m");
+static int verb_unlock_gpt(int argc, char *argv[], uintptr_t _data, void *userdata) {
+        return unlink_pcrlock(PCRLOCK_GPT_PATH);
+}
+
+static int verb_lock_pe(int argc, char *argv[], uintptr_t _data, void *userdata) {
+        _cleanup_(sd_json_variant_unrefp) sd_json_variant *array = NULL;
+        _cleanup_close_ int fd = -EBADF;
+        int r;
+
+        // FIXME: Maybe also generate a matching EV_EFI_VARIABLE_AUTHORITY records here for each signature that
+        //        covers this PE plus its hash, as alternatives under the same component name
+
+        if (argc >= 2) {
+                fd = open(argv[1], O_RDONLY|O_CLOEXEC);
+                if (fd < 0)
+                        return log_error_errno(errno, "Failed to open '%s': %m", argv[1]);
         }
 
-        _cleanup_(tpm2_handle_freep) Tpm2Handle *encryption_session = NULL;
-        r = tpm2_make_encryption_session(
-                        tc,
-                        srk_handle,
-                        /* bind_key= */ &TPM2_HANDLE_NONE,
-                        &encryption_session);
-        if (r < 0)
-                return log_error_errno(r, "Failed to allocate encryption session: %m");
+        if (arg_pcr_mask == 0)
+                arg_pcr_mask = UINT32_C(1) << TPM2_PCR_BOOT_LOADER_CODE;
 
-        /* Acquire a recovery PIN, either from the user, or create a randomized one */
-        _cleanup_(erase_and_freep) char *pin = NULL;
-        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_erase_ char **l = NULL;
+        for (uint32_t i = 0; i < TPM2_PCRS_MAX; i++) {
+                _cleanup_(sd_json_variant_unrefp) sd_json_variant *digests = NULL;
 
-                        AskPasswordRequest req = {
-                                .tty_fd = -EBADF,
-                                .message = "Recovery PIN",
-                                .id = "pcrlock-recovery-pin",
-                                .credential = "pcrlock.recovery-pin",
-                                .until = USEC_INFINITY,
-                                .hup_fd = -EBADF,
-                        };
+                if (!BIT_SET(arg_pcr_mask, i))
+                        continue;
 
-                        r = ask_password_auto(
-                                        &req,
-                                        /* flags= */ 0,
-                                        &l);
-                        if (r < 0)
-                                return log_error_errno(r, "Failed to query for recovery PIN: %m");
+                FOREACH_ARRAY(pa, tpm2_hash_algorithms, TPM2_N_HASH_ALGORITHMS) {
+                        _cleanup_free_ void *hash = NULL;
+                        size_t hash_size;
+                        const EVP_MD *md;
+                        const char *a;
 
-                        if (strv_length(l) != 1)
-                                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected a single PIN only.");
+                        assert_se(a = tpm2_hash_alg_to_string(*pa));
+                        assert_se(md = EVP_get_digestbyname(a));
 
-                        pin = TAKE_PTR(l[0]);
-                        l = mfree(l);
+                        r = pe_hash(fd < 0 ? STDIN_FILENO : fd, md, &hash, &hash_size);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to hash PE binary: %m");
+
+                        r = sd_json_variant_append_arraybo(
+                                        &digests,
+                                        SD_JSON_BUILD_PAIR_STRING("hashAlg", a),
+                                        SD_JSON_BUILD_PAIR("digest", SD_JSON_BUILD_HEX(hash, hash_size)));
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to build JSON digest object: %m");
                 }
 
-        } else if (!have_old_policy) {
-                r = make_recovery_key(&pin);
+                r = sd_json_variant_append_arraybo(
+                                &array,
+                                SD_JSON_BUILD_PAIR_UNSIGNED("pcr", i),
+                                SD_JSON_BUILD_PAIR_VARIANT("digests", digests));
                 if (r < 0)
-                        return log_error_errno(r, "Failed to generate a randomized recovery PIN: %m");
+                        return log_error_errno(r, "Failed to append record object: %m");
+        }
 
-                if (recovery_pin_mode == RECOVERY_PIN_SHOW)
-                        printf("%s Selected recovery PIN is: %s%s%s\n",
-                               glyph(GLYPH_LOCK_AND_KEY),
-                               ansi_highlight_cyan(),
-                               pin,
-                               ansi_normal());
+        return write_pcrlock(array, NULL);
+}
+
+static int verb_unlock_simple(int argc, char *argv[], uintptr_t _data, void *userdata) {
+        return unlink_pcrlock(NULL);
+}
+
+typedef void* SectionHashArray[_UNIFIED_SECTION_MAX * TPM2_N_HASH_ALGORITHMS];
+
+static void section_hashes_array_done(SectionHashArray *array) {
+        assert(array);
+
+        for (size_t i = 0; i < _UNIFIED_SECTION_MAX * TPM2_N_HASH_ALGORITHMS; i++)
+                free((*array)[i]);
+}
+
+static int verb_lock_uki(int argc, char *argv[], uintptr_t _data, void *userdata) {
+        _cleanup_(sd_json_variant_unrefp) sd_json_variant *array = NULL, *pe_digests = NULL;
+        _cleanup_(section_hashes_array_done) SectionHashArray section_hashes = {};
+        size_t hash_sizes[TPM2_N_HASH_ALGORITHMS];
+        _cleanup_close_ int fd = -EBADF;
+        int r;
+
+        if (arg_pcr_mask != 0)
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "PCR not configurable for UKI lock down.");
+
+        if (argc >= 2) {
+                fd = open(argv[1], O_RDONLY|O_CLOEXEC);
+                if (fd < 0)
+                        return log_error_errno(errno, "Failed to open '%s': %m", argv[1]);
         }
 
-        _cleanup_(tpm2_handle_freep) Tpm2Handle *nv_handle = NULL;
-        TPM2_HANDLE nv_index = 0;
+        for (size_t i = 0; i < TPM2_N_HASH_ALGORITHMS; i++) {
+                _cleanup_free_ void *peh = NULL;
+                const EVP_MD *md;
+                const char *a;
 
-        r = tpm2_deserialize(tc, &nv_blob, &nv_handle);
-        if (r < 0)
-                return log_error_errno(r, "Failed to deserialize NV index TR: %m");
-        if (r > 0)
-                nv_index = old_policy.nv_index;
+                assert_se(a = tpm2_hash_alg_to_string(tpm2_hash_algorithms[i]));
+                assert_se(md = EVP_get_digestbyname(a));
 
-        TPM2B_AUTH auth = {};
-        CLEANUP_ERASE(auth);
+                r = pe_hash(fd < 0 ? STDIN_FILENO : fd, md, &peh, hash_sizes + i);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to hash PE binary: %m");
 
-        if (pin) {
-                r = tpm2_auth_value_from_pin(TPM2_ALG_SHA256, pin, &auth);
+                r = sd_json_variant_append_arraybo(
+                                &pe_digests,
+                                SD_JSON_BUILD_PAIR_STRING("hashAlg", a),
+                                SD_JSON_BUILD_PAIR("digest", SD_JSON_BUILD_HEX(peh, hash_sizes[i])));
                 if (r < 0)
-                        return log_error_errno(r, "Failed to hash PIN: %m");
-        } else {
-                assert(iovec_is_set(&pin_public));
-                assert(iovec_is_set(&pin_private));
+                        return log_error_errno(r, "Failed to build JSON digest object: %m");
 
-                log_debug("Retrieving PIN from sealed data.");
+                r = uki_hash(fd < 0 ? STDIN_FILENO : fd, md, section_hashes + (i * _UNIFIED_SECTION_MAX), hash_sizes + i);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to UKI hash PE binary: %m");
+        }
 
-                usec_t pin_start_usec = now(CLOCK_MONOTONIC);
+        r = sd_json_variant_append_arraybo(
+                        &array,
+                        SD_JSON_BUILD_PAIR_UNSIGNED("pcr", TPM2_PCR_BOOT_LOADER_CODE),
+                        SD_JSON_BUILD_PAIR_VARIANT("digests", pe_digests));
+        if (r < 0)
+                return log_error_errno(r, "Failed to append record object: %m");
 
-                _cleanup_(iovec_done_erase) struct iovec secret = {};
-                for (unsigned attempt = 0;; attempt++) {
-                        _cleanup_(tpm2_handle_freep) Tpm2Handle *policy_session = NULL;
+        for (UnifiedSection section = 0; section < _UNIFIED_SECTION_MAX; section++) {
+                _cleanup_(sd_json_variant_unrefp) sd_json_variant *section_digests = NULL, *record = NULL;
 
-                        r = tpm2_make_policy_session(
-                                        tc,
-                                        srk_handle,
-                                        encryption_session,
-                                        &policy_session);
-                        if (r < 0)
-                                return log_error_errno(r, "Failed to allocate policy session: %m");
+                if (!unified_section_measure(section))
+                        continue;
 
-                        r = tpm2_policy_super_pcr(
-                                        tc,
-                                        policy_session,
-                                        &old_policy.prediction,
-                                        old_policy.algorithm);
-                        if (r < 0)
-                                return r;
+                for (size_t i = 0; i < TPM2_N_HASH_ALGORITHMS; i++) {
+                        const char *a;
+                        void *hash;
 
-                        r = tpm2_policy_authorize_nv(
-                                        tc,
-                                        policy_session,
-                                        nv_handle,
-                                        NULL);
-                        if (r < 0)
-                                return log_error_errno(r, "Failed to submit AuthorizeNV policy: %m");
+                        hash = section_hashes[i * _UNIFIED_SECTION_MAX + section];
+                        if (!hash)
+                                continue;
 
-                        r = tpm2_unseal_data(
-                                        tc,
-                                        &pin_public,
-                                        &pin_private,
-                                        srk_handle,
-                                        policy_session,
-                                        encryption_session,
-                                        &secret);
-                        if (r < 0 && (r != -ESTALE || attempt >= 16))
-                                return log_error_errno(r, "Failed to unseal PIN: %m");
-                        if (r == 0)
-                                break;
+                        assert_se(a = tpm2_hash_alg_to_string(tpm2_hash_algorithms[i]));
 
-                        log_debug("Trying again (attempt %u), as PCR values changed during unlock attempt.", attempt+1);
+                        r = sd_json_variant_append_arraybo(
+                                        &section_digests,
+                                        SD_JSON_BUILD_PAIR_STRING("hashAlg", a),
+                                        SD_JSON_BUILD_PAIR("digest", SD_JSON_BUILD_HEX(hash, hash_sizes[i])));
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to build JSON digest object: %m");
                 }
 
-                if (secret.iov_len > sizeof_field(TPM2B_AUTH, buffer))
-                        return log_error_errno(SYNTHETIC_ERRNO(E2BIG), "Decrypted PIN too large.");
+                if (!section_digests)
+                        continue;
 
-                auth = (TPM2B_AUTH) {
-                        .size = secret.iov_len,
-                };
+                /* So we have digests for this section, hence generate a record for the section name first. */
+                r = make_pcrlock_record(TPM2_PCR_KERNEL_BOOT /* =11 */, unified_sections[section], strlen(unified_sections[section]) + 1, &record);
+                if (r < 0)
+                        return r;
 
-                memcpy_safe(auth.buffer, secret.iov_base, secret.iov_len);
+                r = sd_json_variant_append_array(&array, record);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to append JSON record array: %m");
 
-                log_info("Retrieved PIN from TPM2 in %s.", FORMAT_TIMESPAN(usec_sub_unsigned(now(CLOCK_MONOTONIC), pin_start_usec), 1));
+                /* And then append a record for the section contents digests as well */
+                r = sd_json_variant_append_arraybo(
+                                &array,
+                                SD_JSON_BUILD_PAIR_UNSIGNED("pcr", TPM2_PCR_KERNEL_BOOT),
+                                SD_JSON_BUILD_PAIR_VARIANT("digests", section_digests));
+                if (r < 0)
+                        return log_error_errno(r, "Failed to append record object: %m");
         }
 
-        /* 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);
+        return write_pcrlock(array, NULL);
+}
+
+static int verb_lock_machine_id(int argc, char *argv[], uintptr_t _data, void *userdata) {
+        _cleanup_(sd_json_variant_unrefp) sd_json_variant *record = NULL, *array = NULL;
+        _cleanup_free_ char *word = NULL;
+        int r;
+
+        r = pcrextend_machine_id_word(&word);
         if (r < 0)
                 return r;
 
-        TPM2B_NV_PUBLIC nv_public = {};
-        usec_t nv_index_start_usec = now(CLOCK_MONOTONIC);
+        r = make_pcrlock_record(TPM2_PCR_SYSTEM_IDENTITY /* = 15 */, word, SIZE_MAX, &record);
+        if (r < 0)
+                return r;
 
-        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");
+        r = sd_json_variant_new_array(&array, &record, 1);
+        if (r < 0)
+                return log_error_errno(r, "Failed to create record array: %m");
 
-                TPM2B_DIGEST recovery_policy_digest = TPM2B_DIGEST_MAKE(NULL, TPM2_SHA256_DIGEST_SIZE);
-                r = tpm2_calculate_policy_signed(&recovery_policy_digest, pin_name);
-                if (r < 0)
-                        return log_error_errno(r, "Failed to calculate PolicySigned policy: %m");
+        return write_pcrlock(array, PCRLOCK_MACHINE_ID_PATH);
+}
 
-                log_debug("Allocating NV index to write PCR policy to...");
-                r = tpm2_define_policy_nv_index(
-                                tc,
-                                encryption_session,
-                                arg_nv_index,
-                                &recovery_policy_digest,
-                                &nv_index,
-                                &nv_handle,
-                                &nv_public);
-                if (r == -EEXIST)
-                        return log_error_errno(r, "NV index 0x%" PRIx32 " already allocated.", arg_nv_index);
-                if (r < 0)
-                        return log_error_errno(r, "Failed to allocate NV index: %m");
-        }
+static int verb_unlock_machine_id(int argc, char *argv[], uintptr_t _data, void *userdata) {
+        return unlink_pcrlock(PCRLOCK_MACHINE_ID_PATH);
+}
 
-        _cleanup_(tpm2_handle_freep) Tpm2Handle *policy_session = NULL;
-        r = tpm2_make_policy_session(
-                        tc,
-                        srk_handle,
-                        encryption_session,
-                        &policy_session);
-        if (r < 0)
-                return log_error_errno(r, "Failed to allocate policy session: %m");
+static int pcrlock_file_system_path(const char *normalized_path, char **ret) {
+        _cleanup_free_ char *s = NULL;
 
-        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");
+        assert(normalized_path);
+        assert(ret);
 
-        log_debug("Calculating new PCR policy to write...");
-        TPM2B_DIGEST new_super_pcr_policy_digest = TPM2B_DIGEST_MAKE(NULL, TPM2_SHA256_DIGEST_SIZE);
+        if (path_equal(normalized_path, "/"))
+                s = strdup(PCRLOCK_ROOT_FILE_SYSTEM_PATH);
+        else {
+                /* We reuse the escaping we use for turning paths into unit names */
+                _cleanup_free_ char *escaped = NULL;
 
-        usec_t pcr_policy_start_usec = now(CLOCK_MONOTONIC);
+                assert(normalized_path[0] == '/');
+                assert(normalized_path[1] != '/');
 
-        r = tpm2_calculate_policy_super_pcr(
-                        &new_prediction,
-                        el->primary_algorithm,
-                        &new_super_pcr_policy_digest);
-        if (r < 0)
-                return log_error_errno(r, "Failed to calculate super PCR policy: %m");
+                escaped = unit_name_escape(normalized_path + 1);
+                if (!escaped)
+                        return log_oom();
 
-        log_info("Calculated new PCR policy in %s.", FORMAT_TIMESPAN(usec_sub_unsigned(now(CLOCK_MONOTONIC), pcr_policy_start_usec), 1));
+                s = strjoin(PCRLOCK_FILE_SYSTEM_PATH_PREFIX, escaped, ".pcrlock");
+        }
+        if (!s)
+                return log_oom();
 
-        log_debug("Writing new PCR policy to NV index...");
-        r = tpm2_write_policy_nv_index(
-                        tc,
-                        policy_session,
-                        nv_index,
-                        nv_handle,
-                        &new_super_pcr_policy_digest);
-        if (r < 0)
-                return log_error_errno(r, "Failed to write to NV index: %m");
+        *ret = TAKE_PTR(s);
+        return 0;
+}
 
-        log_info("Updated NV index in %s.", FORMAT_TIMESPAN(usec_sub_unsigned(now(CLOCK_MONOTONIC), nv_index_start_usec), 1));
+static int verb_lock_file_system(int argc, char *argv[], uintptr_t _data, void *userdata) {
+        const char* paths[3] = {};
+        int r;
 
-        assert(iovec_is_set(&pin_public) == iovec_is_set(&pin_private));
-        if (!iovec_is_set(&pin_public)) {
-                TPM2B_DIGEST authnv_policy_digest  = TPM2B_DIGEST_MAKE(NULL, TPM2_SHA256_DIGEST_SIZE);
+        if (argc > 1)
+                paths[0] = argv[1];
+        else {
+                dev_t a, b;
+                paths[0] = "/";
 
-                r = tpm2_calculate_policy_authorize_nv(&nv_public, &authnv_policy_digest);
+                r = get_block_device("/", &a);
                 if (r < 0)
-                        return log_error_errno(r, "Failed to calculate AuthorizeNV policy: %m");
-
-                struct iovec data = {
-                        .iov_base = auth.buffer,
-                        .iov_len = auth.size,
-                };
-
-                usec_t pin_seal_start_usec = now(CLOCK_MONOTONIC);
+                        return log_error_errno(r, "Failed to get device of root file system: %m");
 
-                log_debug("Sealing PIN to NV index policy...");
-                r = tpm2_seal_data(
-                                tc,
-                                &data,
-                                srk_handle,
-                                encryption_session,
-                                &authnv_policy_digest,
-                                &pin_public,
-                                &pin_private);
+                r = get_block_device("/var", &b);
                 if (r < 0)
-                        return log_error_errno(r, "Failed to seal PIN to NV auth policy: %m");
+                        return log_error_errno(r, "Failed to get device of /var/ file system: %m");
+
+                /* if backing device is distinct, then measure /var/ too */
+                if (a != b)
+                        paths[1] = "/var";
 
-                log_info("Sealed PIN in %s.", FORMAT_TIMESPAN(usec_sub_unsigned(now(CLOCK_MONOTONIC), pin_seal_start_usec), 1));
+                enable_json_sse();
         }
 
-        if (!iovec_is_set(&nv_blob)) {
-                r = tpm2_serialize(tc, nv_handle, &nv_blob);
-                if (r < 0)
-                        return log_error_errno(r, "Failed to serialize NV index TR: %m");
-        }
+        STRV_FOREACH(p, paths) {
+                _cleanup_free_ char *word = NULL, *normalized_path = NULL, *pcrlock_file = NULL;
+                _cleanup_(sd_json_variant_unrefp) sd_json_variant *record = NULL, *array = NULL;
 
-        if (!iovec_is_set(&srk_blob)) {
-                r = tpm2_serialize(tc, srk_handle, &srk_blob);
+                r = pcrextend_file_system_word(*p, &word, &normalized_path);
                 if (r < 0)
-                        return log_error_errno(r, "Failed to serialize SRK index TR: %m");
-        }
+                        return r;
 
-        if (!iovec_is_set(&nv_public_blob)) {
-                r = tpm2_marshal_nv_public(&nv_public, &nv_public_blob.iov_base, &nv_public_blob.iov_len);
+                r = pcrlock_file_system_path(normalized_path, &pcrlock_file);
                 if (r < 0)
-                        return log_error_errno(r, "Failed to marshal NV public area: %m");
-        }
-
-        _cleanup_(sd_json_variant_unrefp) sd_json_variant *new_configuration_json = NULL;
-        r = sd_json_buildo(
-                        &new_configuration_json,
-                        SD_JSON_BUILD_PAIR_STRING("pcrBank", tpm2_hash_alg_to_string(el->primary_algorithm)),
-                        SD_JSON_BUILD_PAIR_VARIANT("pcrValues", new_prediction_json),
-                        SD_JSON_BUILD_PAIR_INTEGER("nvIndex", nv_index),
-                        JSON_BUILD_PAIR_IOVEC_BASE64("nvHandle", &nv_blob),
-                        JSON_BUILD_PAIR_IOVEC_BASE64("nvPublic", &nv_public_blob),
-                        JSON_BUILD_PAIR_IOVEC_BASE64("srkHandle", &srk_blob),
-                        JSON_BUILD_PAIR_IOVEC_BASE64("pinPublic", &pin_public),
-                        JSON_BUILD_PAIR_IOVEC_BASE64("pinPrivate", &pin_private));
-        if (r < 0)
-                return log_error_errno(r, "Failed to generate JSON: %m");
+                        return r;
 
-        _cleanup_free_ char *text = NULL;
-        r = sd_json_variant_format(new_configuration_json, 0, &text);
-        if (r < 0)
-                return log_error_errno(r, "Failed to format new configuration to JSON: %m");
+                r = make_pcrlock_record(TPM2_PCR_SYSTEM_IDENTITY /* = 15 */, word, SIZE_MAX, &record);
+                if (r < 0)
+                        return r;
 
-        const char *path = arg_policy_path ?: (in_initrd() ? "/run/systemd/pcrlock.json" : "/var/lib/systemd/pcrlock.json");
-        r = write_string_file(path, text, WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_ATOMIC|WRITE_STRING_FILE_SYNC|WRITE_STRING_FILE_MKDIR_0755|WRITE_STRING_FILE_LABEL);
-        if (r < 0)
-                return log_error_errno(r, "Failed to write new configuration to '%s': %m", path);
+                r = sd_json_variant_new_array(&array, &record, 1);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to create record array: %m");
 
-        if (!arg_policy_path && !in_initrd()) {
-                r = remove_policy_file("/run/systemd/pcrlock.json");
+                r = write_pcrlock(array, pcrlock_file);
                 if (r < 0)
                         return r;
         }
 
-        log_info("Written new policy to '%s' and digest to TPM2 NV index 0x%x.", path, nv_index);
+        return 0;
+}
 
-        (void) write_boot_policy_file(text);
+static int verb_unlock_file_system(int argc, char *argv[], uintptr_t _data, void *userdata) {
+        const char* paths[3] = {};
+        int r;
 
-        log_info("Overall time spent: %s", FORMAT_TIMESPAN(usec_sub_unsigned(now(CLOCK_MONOTONIC), start_usec), 1));
+        if (argc > 1)
+                paths[0] = argv[1];
+        else {
+                paths[0] = "/";
+                paths[1] = "/var";
+        }
 
-        return 1; /* installed new policy */
-}
+        STRV_FOREACH(p, paths) {
+                _cleanup_free_ char *normalized_path = NULL, *pcrlock_file = NULL;
 
-static int verb_make_policy(int argc, char *argv[], uintptr_t _data, void *userdata) {
-        int r;
+                r = chase(*p, NULL, 0, &normalized_path, NULL);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to normal path '%s': %m", argv[1]);
 
-        r = make_policy(arg_force, arg_recovery_pin);
-        if (r < 0)
-                return r;
+                r = pcrlock_file_system_path(normalized_path, &pcrlock_file);
+                if (r < 0)
+                        return r;
+
+                r = unlink_pcrlock(pcrlock_file);
+                if (r < 0)
+                        return r;
+        }
 
         return 0;
 }
 
-static int undefine_policy_nv_index(
-                uint32_t nv_index,
-                const struct iovec *nv_blob,
-                const struct iovec *srk_blob) {
+static int verb_lock_kernel_cmdline(int argc, char *argv[], uintptr_t _data, void *userdata) {
+        _cleanup_(sd_json_variant_unrefp) sd_json_variant *record = NULL, *array = NULL;
+        _cleanup_free_ char *cmdline = NULL;
         int r;
 
-        assert(nv_blob);
-        assert(srk_blob);
-
-        _cleanup_(tpm2_context_unrefp) Tpm2Context *tc = NULL;
-        r = tpm2_context_new_or_warn(/* device= */ NULL, &tc);
+        if (argc > 1) {
+                if (empty_or_dash(argv[1]))
+                        r = read_full_stream(stdin, &cmdline, NULL);
+                else
+                        r = read_full_file(argv[1], &cmdline, NULL);
+        } else
+                r = proc_cmdline(&cmdline);
         if (r < 0)
-                return r;
+                return log_error_errno(r, "Failed to read cmdline: %m");
 
-        _cleanup_(tpm2_handle_freep) Tpm2Handle *srk_handle = NULL;
-        r = tpm2_deserialize(
-                        tc,
-                        srk_blob,
-                        &srk_handle);
-        if (r < 0)
-                return log_error_errno(r, "Failed to deserialize SRK TR: %m");
+        delete_trailing_chars(cmdline, "\n");
 
-        _cleanup_(tpm2_handle_freep) Tpm2Handle *nv_handle = NULL;
-        r = tpm2_deserialize(
-                        tc,
-                        nv_blob,
-                        &nv_handle);
-        if (r < 0)
-                return log_error_errno(r, "Failed to deserialize NV TR: %m");
+        _cleanup_free_ char16_t *u = NULL;
+        u = utf8_to_utf16(cmdline, SIZE_MAX);
+        if (!u)
+                return log_oom();
 
-        _cleanup_(tpm2_handle_freep) Tpm2Handle *encryption_session = NULL;
-        r = tpm2_make_encryption_session(
-                        tc,
-                        srk_handle,
-                        /* bind_key= */ &TPM2_HANDLE_NONE,
-                        &encryption_session);
+        r = make_pcrlock_record(TPM2_PCR_KERNEL_INITRD /* = 9 */, u, char16_strlen(u)*2+2, &record);
         if (r < 0)
                 return r;
 
-        r = tpm2_undefine_nv_index(
-                        tc,
-                        encryption_session,
-                        nv_index,
-                        nv_handle);
+        r = sd_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_CMDLINE_PATH);
         if (r < 0)
                 return r;
 
-        log_info("Removed NV index 0x%x", nv_index);
         return 0;
 }
 
-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);
-        if (r == 0) {
-                log_info("No policy found.");
-                return 0;
-        }
+static int verb_unlock_kernel_cmdline(int argc, char *argv[], uintptr_t _data, void *userdata) {
+        return unlink_pcrlock(PCRLOCK_KERNEL_CMDLINE_PATH);
+}
 
-        if (r < 0)
-                log_notice("Failed to load old policy file, assuming it is corrupted, removing.");
-        else {
-                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.");
+static int verb_lock_kernel_initrd(int argc, char *argv[], uintptr_t _data, void *userdata) {
+        _cleanup_(sd_json_variant_unrefp) sd_json_variant *records = NULL;
+        _cleanup_fclose_ FILE *f = NULL;
+        uint32_t pcr_mask = UINT32_C(1) << TPM2_PCR_KERNEL_INITRD;
+        int r;
 
-                RET_GATHER(ret, r);
+        if (argc >= 2) {
+                f = fopen(argv[1], "re");
+                if (!f)
+                        return log_error_errno(errno, "Failed to open '%s': %m", argv[1]);
         }
 
-        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"));
-        }
+        r = make_pcrlock_record_from_stream(pcr_mask, f ?: stdin, &records);
+        if (r < 0)
+                return r;
 
-        _cleanup_free_ char *boot_policy_file = NULL;
-        r = determine_boot_policy_file(&boot_policy_file, /* ret_credential_name= */ NULL);
-        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);
+        r = write_pcrlock(records, PCRLOCK_KERNEL_INITRD_PATH);
+        if (r < 0)
+                return r;
 
-        return ret;
+        return 0;
 }
 
-static int verb_remove_policy(int argc, char *argv[], uintptr_t _data, void *userdata) {
-        return remove_policy();
+static int verb_unlock_kernel_initrd(int argc, char *argv[], uintptr_t _data, void *userdata) {
+        return unlink_pcrlock(PCRLOCK_KERNEL_INITRD_PATH);
 }
 
-static int test_tpm2_support_pcrlock(Tpm2Support *ret) {
+static int verb_lock_raw(int argc, char *argv[], uintptr_t _data, void *userdata) {
+        _cleanup_(sd_json_variant_unrefp) sd_json_variant *records = NULL;
+        _cleanup_fclose_ FILE *f = NULL;
         int r;
 
-        assert(ret);
-
-        /* First check basic support */
-        Tpm2Support s = tpm2_support();
-
-        /* If basic support is available, let's also check the things we need for systemd-pcrlock */
-        if (s == TPM2_SUPPORT_FULL) {
-                _cleanup_(tpm2_context_unrefp) Tpm2Context *tc = NULL;
-                r = tpm2_context_new_or_warn(/* device= */ NULL, &tc);
-                if (r < 0)
-                        return r;
-
-                /* We strictly need TPM2_CC_PolicyAuthorizeNV for systemd-pcrlock to work */
-                SET_FLAG(s, TPM2_SUPPORT_AUTHORIZE_NV, tpm2_supports_command(tc, TPM2_CC_PolicyAuthorizeNV));
-
-                log_debug("PolicyAuthorizeNV supported: %s", yes_no(FLAGS_SET(s, TPM2_SUPPORT_AUTHORIZE_NV)));
-
-                /* We also strictly need SHA-256 to work */
-                SET_FLAG(s, TPM2_SUPPORT_SHA256, tpm2_supports_alg(tc, TPM2_ALG_SHA256));
+        if (arg_pcr_mask == 0)
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No PCR specified, refusing.");
 
-                log_debug("SHA-256 supported: %s", yes_no(FLAGS_SET(s, TPM2_SUPPORT_SHA256)));
+        if (argc >= 2) {
+                f = fopen(argv[1], "re");
+                if (!f)
+                        return log_error_errno(errno, "Failed to open '%s': %m", argv[1]);
         }
 
-        *ret = s;
-        return 0;
-}
-
-static int verb_is_supported(int argc, char *argv[], uintptr_t _data, void *userdata) {
-        int r;
-
-        Tpm2Support s;
-        r = test_tpm2_support_pcrlock(&s);
+        r = make_pcrlock_record_from_stream(arg_pcr_mask, f ?: stdin, &records);
         if (r < 0)
                 return r;
 
-        if (!arg_quiet) {
-                if (s == (TPM2_SUPPORT_FULL|TPM2_SUPPORT_API_PCRLOCK))
-                        printf("%syes%s\n", ansi_green(), ansi_normal());
-                else if (FLAGS_SET(s, TPM2_SUPPORT_FULL))
-                        printf("%sobsolete%s\n", ansi_red(), ansi_normal());
-                else if (s == TPM2_SUPPORT_NONE)
-                        printf("%sno%s\n", ansi_red(), ansi_normal());
-                else
-                        printf("%spartial%s\n", ansi_yellow(), ansi_normal());
-        }
-
-        assert_cc((TPM2_SUPPORT_API|TPM2_SUPPORT_API_PCRLOCK) <= 255); /* make sure this is safe to use as process exit status */
-
-        return ~s & (TPM2_SUPPORT_API|TPM2_SUPPORT_API_PCRLOCK);
+        return write_pcrlock(records, NULL);
 }
 
 static int help(void) {