Merge pull request #30130 from poettering/pcrlock-root

pcrlock: add support for unlocking a root fs with a pcrlock file
This commit is contained in:
Luca Boccassi 2024-01-23 21:41:02 +00:00 committed by GitHub
commit f70daee8f2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 413 additions and 42 deletions

View file

@ -155,6 +155,19 @@
<para>If the new prediction matches the old this command terminates quickly and executes no further
operation. (Unless <option>--force</option> is specified, see below.)</para>
<para>Starting with v256, a copy of the <filename>/var/lib/systemd/pcrlock.json</filename> policy
file is encoded in a credential (see
<citerefentry><refentrytitle>systemd-creds</refentrytitle><manvolnum>1</manvolnum></citerefentry> for
details) and written to the EFI System Partition or XBOOTLDR partition, in the
<filename>/loader/credentials/</filename> subdirectory. There it is picked up at boot by
<citerefentry><refentrytitle>systemd-stub</refentrytitle><manvolnum>7</manvolnum></citerefentry> and
passed to the invoked initrd, where it can be used to unlock the root file system (which typically
contains <filename>/var/</filename>, which is where the primary copy of the policy is located, which
hence cannot be used to unlock the root file system). The credential file is named after the boot
entry token of the installation (see
<citerefentry><refentrytitle>bootctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>), which
is configurable via the <option>--entry-token=</option> switch, see below.</para>
<xi:include href="version-info.xml" xpointer="v255"/>
</listitem>
</varlistentry>
@ -531,6 +544,18 @@
<xi:include href="version-info.xml" xpointer="v255"/></listitem>
</varlistentry>
<varlistentry>
<term><option>--entry-token=</option></term>
<listitem><para>Sets the boot entry token to use for the file name for the pcrlock policy credential
in the EFI System Partition or XBOOTLDR partition. See the
<citerefentry><refentrytitle>bootctl</refentrytitle><manvolnum>1</manvolnum></citerefentry> option of
the same regarding expected values. This switch has an effect on the
<command>make-policy</command> command only.</para>
<xi:include href="version-info.xml" xpointer="v256"/></listitem>
</varlistentry>
<xi:include href="standard-options.xml" xpointer="json" />
<xi:include href="standard-options.xml" xpointer="no-pager" />
<xi:include href="standard-options.xml" xpointer="help" />
@ -553,6 +578,9 @@
<member><citerefentry><refentrytitle>systemd-cryptsetup@.service</refentrytitle><manvolnum>8</manvolnum></citerefentry></member>
<member><citerefentry><refentrytitle>systemd-repart</refentrytitle><manvolnum>8</manvolnum></citerefentry></member>
<member><citerefentry><refentrytitle>systemd-pcrmachine.service</refentrytitle><manvolnum>8</manvolnum></citerefentry></member>
<member><citerefentry><refentrytitle>systemd-creds</refentrytitle><manvolnum>1</manvolnum></citerefentry></member>
<member><citerefentry><refentrytitle>systemd-stub</refentrytitle><manvolnum>7</manvolnum></citerefentry></member>
<member><citerefentry><refentrytitle>bootctl</refentrytitle><manvolnum>1</manvolnum></citerefentry></member>
</simplelist></para>
</refsect1>

View file

@ -365,6 +365,7 @@ int enroll_tpm2(struct crypt_device *cd,
&IOVEC_MAKE(policy.buffer, policy.size),
use_pin ? &IOVEC_MAKE(binary_salt, sizeof(binary_salt)) : NULL,
&srk,
pcrlock_path ? &pcrlock_policy.nv_handle : NULL,
flags,
&v);
if (r < 0)

View file

@ -42,7 +42,7 @@ _public_ int cryptsetup_token_open_pin(
void *usrptr /* plugin defined parameter passed to crypt_activate_by_token*() API */) {
_cleanup_(erase_and_freep) char *base64_encoded = NULL, *pin_string = NULL;
_cleanup_(iovec_done) struct iovec blob = {}, pubkey = {}, policy_hash = {}, salt = {}, srk = {};
_cleanup_(iovec_done) struct iovec blob = {}, pubkey = {}, policy_hash = {}, salt = {}, srk = {}, pcrlock_nv = {};
_cleanup_(iovec_done_erase) struct iovec decrypted_key = {};
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
uint32_t hash_pcr_mask, pubkey_pcr_mask;
@ -88,6 +88,7 @@ _public_ int cryptsetup_token_open_pin(
&policy_hash,
&salt,
&srk,
&pcrlock_nv,
&flags);
if (r < 0)
return log_debug_open_error(cd, r);
@ -109,6 +110,7 @@ _public_ int cryptsetup_token_open_pin(
&policy_hash,
&salt,
&srk,
&pcrlock_nv,
flags,
&decrypted_key);
if (r < 0)
@ -166,7 +168,7 @@ _public_ void cryptsetup_token_dump(
const char *json /* validated 'systemd-tpm2' token if cryptsetup_token_validate is defined */) {
_cleanup_free_ char *hash_pcrs_str = NULL, *pubkey_pcrs_str = NULL, *blob_str = NULL, *policy_hash_str = NULL, *pubkey_str = NULL;
_cleanup_(iovec_done) struct iovec blob = {}, pubkey = {}, policy_hash = {}, salt = {}, srk = {};
_cleanup_(iovec_done) struct iovec blob = {}, pubkey = {}, policy_hash = {}, salt = {}, srk = {}, pcrlock_nv = {};
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
uint32_t hash_pcr_mask, pubkey_pcr_mask;
uint16_t pcr_bank, primary_alg;
@ -191,6 +193,7 @@ _public_ void cryptsetup_token_dump(
&policy_hash,
&salt,
&srk,
&pcrlock_nv,
&flags);
if (r < 0)
return (void) crypt_log_debug_errno(cd, r, "Failed to parse " TOKEN_NAME " JSON fields: %m");
@ -226,6 +229,7 @@ _public_ void cryptsetup_token_dump(
crypt_log(cd, "\ttpm2-pcrlock: %s\n", true_false(flags & TPM2_FLAGS_USE_PCRLOCK));
crypt_log(cd, "\ttpm2-salt: %s\n", true_false(iovec_is_set(&salt)));
crypt_log(cd, "\ttpm2-srk: %s\n", true_false(iovec_is_set(&srk)));
crypt_log(cd, "\ttpm2-pcrlock-nv: %s\n", true_false(iovec_is_set(&pcrlock_nv)));
}
/*

View file

@ -27,6 +27,7 @@ int acquire_luks2_key(
const struct iovec *policy_hash,
const struct iovec *salt,
const struct iovec *srk,
const struct iovec *pcrlock_nv,
TPM2Flags flags,
struct iovec *ret_decrypted_key) {
@ -75,6 +76,14 @@ int acquire_luks2_key(
r = tpm2_pcrlock_policy_load(pcrlock_path, &pcrlock_policy);
if (r < 0)
return r;
if (r == 0) {
/* Not found? Then search among passed credentials */
r = tpm2_pcrlock_policy_from_credentials(srk, pcrlock_nv, &pcrlock_policy);
if (r < 0)
return r;
if (r == 0)
return log_error_errno(SYNTHETIC_ERRNO(EREMOTE), "Couldn't find pcrlock policy for volume.");
}
}
_cleanup_(tpm2_context_unrefp) Tpm2Context *tpm2_context = NULL;

View file

@ -20,5 +20,6 @@ int acquire_luks2_key(
const struct iovec *policy_hash,
const struct iovec *salt,
const struct iovec *srk,
const struct iovec *pcrlock_nv,
TPM2Flags flags,
struct iovec *decrypted_key);

View file

@ -70,6 +70,7 @@ int acquire_tpm2_key(
const struct iovec *policy_hash,
const struct iovec *salt,
const struct iovec *srk,
const struct iovec *pcrlock_nv,
TPM2Flags flags,
usec_t until,
bool headless,
@ -128,6 +129,14 @@ int acquire_tpm2_key(
r = tpm2_pcrlock_policy_load(pcrlock_path, &pcrlock_policy);
if (r < 0)
return r;
if (r == 0) {
/* Not found? Then search among passed credentials */
r = tpm2_pcrlock_policy_from_credentials(srk, pcrlock_nv, &pcrlock_policy);
if (r < 0)
return r;
if (r == 0)
return log_error_errno(SYNTHETIC_ERRNO(EREMOTE), "Couldn't find pcrlock policy for volume.");
}
}
_cleanup_(tpm2_context_unrefp) Tpm2Context *tpm2_context = NULL;
@ -219,6 +228,7 @@ int find_tpm2_auto_data(
struct iovec *ret_policy_hash,
struct iovec *ret_salt,
struct iovec *ret_srk,
struct iovec *ret_pcrlock_nv,
TPM2Flags *ret_flags,
int *ret_keyslot,
int *ret_token) {
@ -228,7 +238,7 @@ int find_tpm2_auto_data(
assert(cd);
for (token = start_token; token < sym_crypt_token_max(CRYPT_LUKS2); token++) {
_cleanup_(iovec_done) struct iovec blob = {}, policy_hash = {}, pubkey = {}, salt = {}, srk = {};
_cleanup_(iovec_done) struct iovec blob = {}, policy_hash = {}, pubkey = {}, salt = {}, srk = {}, pcrlock_nv = {};
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
uint32_t hash_pcr_mask, pubkey_pcr_mask;
uint16_t pcr_bank, primary_alg;
@ -253,6 +263,7 @@ int find_tpm2_auto_data(
&policy_hash,
&salt,
&srk,
&pcrlock_nv,
&flags);
if (r == -EUCLEAN) /* Gracefully handle issues in JSON fields not owned by us */
continue;
@ -276,6 +287,7 @@ int find_tpm2_auto_data(
*ret_keyslot = keyslot;
*ret_token = token;
*ret_srk = TAKE_STRUCT(srk);
*ret_pcrlock_nv = TAKE_STRUCT(pcrlock_nv);
*ret_flags = flags;
return 0;
}

View file

@ -28,6 +28,7 @@ int acquire_tpm2_key(
const struct iovec *policy_hash,
const struct iovec *salt,
const struct iovec *srk,
const struct iovec *pcrlock_nv,
TPM2Flags flags,
usec_t until,
bool headless,
@ -47,6 +48,7 @@ int find_tpm2_auto_data(
struct iovec *ret_policy_hash,
struct iovec *ret_salt,
struct iovec *ret_srk,
struct iovec *ret_pcrlock_nv,
TPM2Flags *ret_flags,
int *ret_keyslot,
int *ret_token);
@ -70,6 +72,7 @@ static inline int acquire_tpm2_key(
const struct iovec *policy_hash,
const struct iovec *salt,
const struct iovec *srk,
const struct iovec *pcrlock_nv,
TPM2Flags flags,
usec_t until,
bool headless,
@ -93,6 +96,7 @@ static inline int find_tpm2_auto_data(
struct iovec *ret_policy_hash,
struct iovec *ret_salt,
struct iovec *ret_srk,
struct iovec *ret_pcrlock_nv,
TPM2Flags *ret_flags,
int *ret_keyslot,
int *ret_token) {

View file

@ -1688,6 +1688,7 @@ static int attach_luks_or_plain_or_bitlk_by_tpm2(
/* policy_hash= */ NULL, /* we don't know the policy hash */
/* salt= */ NULL,
/* srk= */ NULL,
/* pcrlock_nv= */ NULL,
arg_tpm2_pin ? TPM2_FLAGS_USE_PIN : 0,
until,
arg_headless,
@ -1732,7 +1733,7 @@ static int attach_luks_or_plain_or_bitlk_by_tpm2(
* works. */
for (;;) {
_cleanup_(iovec_done) struct iovec pubkey = {}, salt = {}, srk = {};
_cleanup_(iovec_done) struct iovec pubkey = {}, salt = {}, srk = {}, pcrlock_nv = {};
uint32_t hash_pcr_mask, pubkey_pcr_mask;
uint16_t pcr_bank, primary_alg;
TPM2Flags tpm2_flags;
@ -1750,6 +1751,7 @@ static int attach_luks_or_plain_or_bitlk_by_tpm2(
&policy_hash,
&salt,
&srk,
&pcrlock_nv,
&tpm2_flags,
&keyslot,
&token);
@ -1784,6 +1786,7 @@ static int attach_luks_or_plain_or_bitlk_by_tpm2(
&policy_hash,
&salt,
&srk,
&pcrlock_nv,
tpm2_flags,
until,
arg_headless,

View file

@ -3929,6 +3929,7 @@ static int partition_encrypt(Context *context, Partition *p, PartitionTarget *ta
&IOVEC_MAKE(policy.buffer, policy.size),
/* salt= */ NULL, /* no salt because tpm2_seal has no pin */
&srk,
&pcrlock_policy.nv_handle,
flags,
&v);
if (r < 0)

View file

@ -9,15 +9,18 @@
#include "ask-password-api.h"
#include "blockdev-util.h"
#include "boot-entry.h"
#include "build.h"
#include "chase.h"
#include "color-util.h"
#include "conf-files.h"
#include "creds-util.h"
#include "efi-api.h"
#include "env-util.h"
#include "escape.h"
#include "fd-util.h"
#include "fileio.h"
#include "find-esp.h"
#include "format-table.h"
#include "format-util.h"
#include "fs-util.h"
@ -60,12 +63,15 @@ static TPM2_HANDLE arg_nv_index = 0;
static bool arg_recovery_pin = false;
static char *arg_policy_path = NULL;
static bool arg_force = false;
static BootEntryTokenType arg_entry_token_type = BOOT_ENTRY_TOKEN_AUTO;
static char *arg_entry_token = NULL;
STATIC_DESTRUCTOR_REGISTER(arg_components, strv_freep);
STATIC_DESTRUCTOR_REGISTER(arg_pcrlock_path, freep);
STATIC_DESTRUCTOR_REGISTER(arg_location_start, freep);
STATIC_DESTRUCTOR_REGISTER(arg_location_end, freep);
STATIC_DESTRUCTOR_REGISTER(arg_policy_path, freep);
STATIC_DESTRUCTOR_REGISTER(arg_entry_token, freep);
#define PCRLOCK_SECUREBOOT_POLICY_PATH "/var/lib/pcrlock.d/240-secureboot-policy.pcrlock.d/generated.pcrlock"
#define PCRLOCK_FIRMWARE_CODE_EARLY_PATH "/var/lib/pcrlock.d/250-firmware-code-early.pcrlock.d/generated.pcrlock"
@ -4164,6 +4170,127 @@ static int remove_policy_file(const char *path) {
return 1;
}
static int determine_boot_policy_file(char **ret) {
_cleanup_free_ char *path = NULL, *fn = NULL, *joined = NULL;
sd_id128_t machine_id;
int r;
assert(ret);
r = find_xbootldr_and_warn(
/* root= */ NULL,
/* path= */ NULL,
/* unprivileged_mode= */ false,
&path,
/* ret_uuid= */ NULL,
/* ret_devid= */ NULL);
if (r < 0) {
if (r != -ENOKEY)
return log_error_errno(r, "Failed to find XBOOTLDR partition: %m");
r = find_esp_and_warn(
/* root= */ NULL,
/* path= */ NULL,
/* unprivileged_mode= */ false,
&path,
/* ret_part= */ NULL,
/* ret_pstart= */ NULL,
/* ret_psize= */ NULL,
/* ret_uuid= */ NULL,
/* ret_devid= */ NULL);
if (r < 0) {
if (r != -ENOKEY)
return log_error_errno(r, "Failed to find ESP partition: %m");
*ret = NULL;
return 0; /* not found! */
}
}
r = sd_id128_get_machine(&machine_id);
if (r < 0)
return log_error_errno(r, "Failed to read machine ID: %m");
r = boot_entry_token_ensure(
/* root= */ NULL,
"/etc/kernel",
machine_id,
/* machine_id_is_random = */ false,
&arg_entry_token_type,
&arg_entry_token);
if (r < 0)
return r;
fn = strjoin("pcrlock.", arg_entry_token, ".cred");
if (!fn)
return log_oom();
if (!filename_is_valid(fn))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Credential name '%s' would not be a valid file name, refusing.", fn);
joined = path_join(path, "loader/credentials", fn);
if (!joined)
return log_oom();
*ret = TAKE_PTR(joined);
return 1; /* found! */
}
static int write_boot_policy_file(const char *json_text) {
_cleanup_free_ char *boot_policy_file = NULL;
int r;
assert(json_text);
r = determine_boot_policy_file(&boot_policy_file);
if (r < 0)
return r;
if (r == 0) {
log_info("Did not find XBOOTLDR/ESP partition, not writing boot policy file.");
return 0;
}
_cleanup_free_ char *c = NULL;
r = path_extract_filename(boot_policy_file, &c);
if (r < 0)
return log_error_errno(r, "Failed to extract file name from %s: %m", boot_policy_file);
ascii_strlower(c); /* lowercase this file, no matter what, since stored on VFAT, and we don't want to
* run into case change incompatibilities */
_cleanup_(iovec_done) struct iovec encoded = {};
r = encrypt_credential_and_warn(
CRED_AES256_GCM_BY_NULL,
c,
now(CLOCK_REALTIME),
/* not_after= */ USEC_INFINITY,
/* tpm2_device= */ NULL,
/* tpm2_hash_pcr_mask= */ 0,
/* tpm2_pubkey_path= */ NULL,
/* tpm2_pubkey_path_mask= */ 0,
&IOVEC_MAKE_STRING(json_text),
CREDENTIAL_ALLOW_NULL,
&encoded);
if (r < 0)
return log_error_errno(r, "Failed to encode policy as credential: %m");
_cleanup_free_ char *base64_buf = NULL;
ssize_t base64_size;
base64_size = base64mem_full(encoded.iov_base, encoded.iov_len, 79, &base64_buf);
if (base64_size < 0)
return base64_size;
r = write_string_file(
boot_policy_file,
base64_buf,
WRITE_STRING_FILE_ATOMIC|WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_SYNC|WRITE_STRING_FILE_MKDIR_0755);
if (r < 0)
return log_error_errno(r, "Failed to write boot policy file to '%s': %m", boot_policy_file);
log_info("Written new boot policy to '%s'.", boot_policy_file);
return 1;
}
static int verb_make_policy(int argc, char *argv[], void *userdata) {
int r;
@ -4562,6 +4689,8 @@ static int verb_make_policy(int argc, char *argv[], void *userdata) {
log_info("Written new policy to '%s' and digest to TPM2 NV index 0x%x.", path, nv_index);
(void) write_boot_policy_file(text);
log_info("Overall time spent: %s", FORMAT_TIMESPAN(usec_sub_unsigned(now(CLOCK_MONOTONIC), start_usec), 1));
return 0;
@ -4621,7 +4750,7 @@ static int undefine_policy_nv_index(
}
static int verb_remove_policy(int argc, char *argv[], void *userdata) {
int r;
int ret = 0, r;
_cleanup_(tpm2_pcrlock_policy_done) Tpm2PCRLockPolicy policy = {};
r = tpm2_pcrlock_policy_load(arg_policy_path, &policy);
@ -4636,22 +4765,27 @@ static int verb_remove_policy(int argc, char *argv[], void *userdata) {
r = undefine_policy_nv_index(policy.nv_index, &policy.nv_handle, &policy.srk_handle);
if (r < 0)
log_notice("Failed to remove NV index, assuming data out of date, removing policy file.");
RET_GATHER(ret, r);
}
if (arg_policy_path) {
r = remove_policy_file(arg_policy_path);
if (r < 0)
return r;
return 0;
} else {
int ret = 0;
if (arg_policy_path)
RET_GATHER(ret, remove_policy_file(arg_policy_path));
else {
RET_GATHER(ret, remove_policy_file("/var/lib/systemd/pcrlock.json"));
RET_GATHER(ret, remove_policy_file("/run/systemd/pcrlock.json"));
return ret;
}
_cleanup_free_ char *boot_policy_file = NULL;
r = determine_boot_policy_file(&boot_policy_file);
if (r == 0)
log_info("Did not find XBOOTLDR/ESP partition, not removing boot policy file.");
else if (r > 0) {
RET_GATHER(ret, remove_policy_file(boot_policy_file));
} else
RET_GATHER(ret, r);
return ret;
}
static int help(int argc, char *argv[], void *userdata) {
@ -4711,6 +4845,8 @@ static int help(int argc, char *argv[], void *userdata) {
" --pcrlock=PATH .pcrlock file to write expected PCR measurement to\n"
" --policy=PATH JSON file to write policy output to\n"
" --force Write policy even if it matches existing policy\n"
" --entry-token=machine-id|os-id|os-image-id|auto|literal:…\n"
" Boot entry token to use for this installation\n"
"\nSee the %2$s for details.\n",
program_invocation_short_name,
link,
@ -4736,6 +4872,7 @@ static int parse_argv(int argc, char *argv[]) {
ARG_PCRLOCK,
ARG_POLICY,
ARG_FORCE,
ARG_ENTRY_TOKEN,
};
static const struct option options[] = {
@ -4752,6 +4889,7 @@ static int parse_argv(int argc, char *argv[]) {
{ "pcrlock", required_argument, NULL, ARG_PCRLOCK },
{ "policy", required_argument, NULL, ARG_POLICY },
{ "force", no_argument, NULL, ARG_FORCE },
{ "entry-token", required_argument, NULL, ARG_ENTRY_TOKEN },
{}
};
@ -4899,6 +5037,12 @@ static int parse_argv(int argc, char *argv[]) {
arg_force = true;
break;
case ARG_ENTRY_TOKEN:
r = parse_boot_entry_token_type(optarg, &arg_entry_token_type, &arg_entry_token);
if (r < 0)
return r;
break;
case '?':
return -EINVAL;

View file

@ -4,6 +4,7 @@
#include "alloc-util.h"
#include "constants.h"
#include "creds-util.h"
#include "cryptsetup-util.h"
#include "dirent-util.h"
#include "dlfcn-util.h"
@ -25,6 +26,7 @@
#include "nulstr-util.h"
#include "parse-util.h"
#include "random-util.h"
#include "recurse-dir.h"
#include "sha256.h"
#include "sort-util.h"
#include "stat-util.h"
@ -6800,6 +6802,43 @@ int tpm2_pcrlock_search_file(const char *path, FILE **ret_file, char **ret_path)
return 0;
}
int tpm2_pcrlock_policy_from_json(
JsonVariant *v,
Tpm2PCRLockPolicy *ret_policy) {
/* We use a type check of _JSON_VARIANT_TYPE_INVALID for the integer fields to allow
* json_dispatch_uint32() to parse strings as integers to work around the integer type weakness of
* JSON's design. */
JsonDispatch policy_dispatch[] = {
{ "pcrBank", JSON_VARIANT_STRING, json_dispatch_tpm2_algorithm, offsetof(Tpm2PCRLockPolicy, algorithm), JSON_MANDATORY },
{ "pcrValues", JSON_VARIANT_ARRAY, json_dispatch_variant, offsetof(Tpm2PCRLockPolicy, prediction_json), JSON_MANDATORY },
{ "nvIndex", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint32, offsetof(Tpm2PCRLockPolicy, nv_index), JSON_MANDATORY },
{ "nvHandle", JSON_VARIANT_STRING, json_dispatch_unbase64_iovec, offsetof(Tpm2PCRLockPolicy, nv_handle), JSON_MANDATORY },
{ "nvPublic", JSON_VARIANT_STRING, json_dispatch_unbase64_iovec, offsetof(Tpm2PCRLockPolicy, nv_public), JSON_MANDATORY },
{ "srkHandle", JSON_VARIANT_STRING, json_dispatch_unbase64_iovec, offsetof(Tpm2PCRLockPolicy, srk_handle), JSON_MANDATORY },
{ "pinPublic", JSON_VARIANT_STRING, json_dispatch_unbase64_iovec, offsetof(Tpm2PCRLockPolicy, pin_public), JSON_MANDATORY },
{ "pinPrivate", JSON_VARIANT_STRING, json_dispatch_unbase64_iovec, offsetof(Tpm2PCRLockPolicy, pin_private), JSON_MANDATORY },
{}
};
_cleanup_(tpm2_pcrlock_policy_done) Tpm2PCRLockPolicy policy = {};
int r;
assert(v);
assert(ret_policy);
r = json_dispatch(v, policy_dispatch, JSON_LOG, &policy);
if (r < 0)
return r;
r = tpm2_pcr_prediction_from_json(&policy.prediction, policy.algorithm, policy.prediction_json);
if (r < 0)
return r;
*ret_policy = TAKE_STRUCT(policy);
return 1;
}
int tpm2_pcrlock_policy_load(
const char *path,
Tpm2PCRLockPolicy *ret_policy) {
@ -6816,41 +6855,140 @@ int tpm2_pcrlock_policy_load(
if (r < 0)
return log_error_errno(r, "Failed to load TPM2 pcrlock policy file: %m");
_cleanup_(json_variant_unrefp) JsonVariant *configuration_json = NULL;
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
r = json_parse_file(
f,
discovered_path,
/* flags = */ 0,
&configuration_json,
&v,
/* ret_line= */ NULL,
/* ret_column= */ NULL);
if (r < 0)
return log_error_errno(r, "Failed to parse existing pcrlock policy file '%s': %m", discovered_path);
JsonDispatch policy_dispatch[] = {
{ "pcrBank", JSON_VARIANT_STRING, json_dispatch_tpm2_algorithm, offsetof(Tpm2PCRLockPolicy, algorithm), JSON_MANDATORY },
{ "pcrValues", JSON_VARIANT_ARRAY, json_dispatch_variant, offsetof(Tpm2PCRLockPolicy, prediction_json), JSON_MANDATORY },
{ "nvIndex", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint32, offsetof(Tpm2PCRLockPolicy, nv_index), JSON_MANDATORY },
{ "nvHandle", JSON_VARIANT_STRING, json_dispatch_unbase64_iovec, offsetof(Tpm2PCRLockPolicy, nv_handle), JSON_MANDATORY },
{ "nvPublic", JSON_VARIANT_STRING, json_dispatch_unbase64_iovec, offsetof(Tpm2PCRLockPolicy, nv_public), JSON_MANDATORY },
{ "srkHandle", JSON_VARIANT_STRING, json_dispatch_unbase64_iovec, offsetof(Tpm2PCRLockPolicy, srk_handle), JSON_MANDATORY },
{ "pinPublic", JSON_VARIANT_STRING, json_dispatch_unbase64_iovec, offsetof(Tpm2PCRLockPolicy, pin_public), JSON_MANDATORY },
{ "pinPrivate", JSON_VARIANT_STRING, json_dispatch_unbase64_iovec, offsetof(Tpm2PCRLockPolicy, pin_private), JSON_MANDATORY },
{}
};
return tpm2_pcrlock_policy_from_json(v, ret_policy);
}
_cleanup_(tpm2_pcrlock_policy_done) Tpm2PCRLockPolicy policy = {};
static int pcrlock_policy_load_credential(
const char *name,
const struct iovec *data,
Tpm2PCRLockPolicy *ret) {
r = json_dispatch(configuration_json, policy_dispatch, JSON_LOG, &policy);
_cleanup_free_ char *c = NULL;
int r;
assert(name);
c = strdup(name);
if (!c)
return log_oom();
ascii_strlower(c); /* Lowercase, to match what we did at encryption time */
_cleanup_(iovec_done) struct iovec decoded = {};
r = decrypt_credential_and_warn(
c,
now(CLOCK_REALTIME),
/* tpm2_device= */ NULL,
/* tpm2_signature_path= */ NULL,
data,
CREDENTIAL_ALLOW_NULL,
&decoded);
if (r < 0)
return r;
r = tpm2_pcr_prediction_from_json(&policy.prediction, policy.algorithm, policy.prediction_json);
if (memchr(decoded.iov_base, 0, decoded.iov_len))
return log_error_errno(r, "Credential '%s' contains embedded NUL byte, refusing.", name);
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
r = json_parse(decoded.iov_base,
/* flags= */ 0,
&v,
/* ret_line= */ NULL,
/* ret_column= */ NULL);
if (r < 0)
return log_error_errno(r, "Failed to parse pcrlock policy: %m");
r = tpm2_pcrlock_policy_from_json(v, ret);
if (r < 0)
return r;
*ret_policy = TAKE_STRUCT(policy);
return 1;
return 0;
}
int tpm2_pcrlock_policy_from_credentials(
const struct iovec *srk,
const struct iovec *nv,
Tpm2PCRLockPolicy *ret) {
_cleanup_close_ int dfd = -EBADF;
int r;
/* During boot we'll not have access to the pcrlock.json file in /var/. In order to support
* pcrlock-bound root file systems we'll store a copy of the JSON data, wrapped in an (plaintext)
* credential in the ESP or XBOOTLDR partition. There might be multiple of those however (because of
* multi-boot), hence we use the SRK and NV data from the LUKS2 header as search key, and parse all
* such JSON policies until we find a matching one. */
const char *cp = secure_getenv("SYSTEMD_ENCRYPTED_SYSTEM_CREDENTIALS_DIRECTORY") ?: ENCRYPTED_SYSTEM_CREDENTIALS_DIRECTORY;
dfd = open(cp, O_CLOEXEC|O_DIRECTORY);
if (dfd < 0) {
if (errno == ENOENT) {
log_debug("No encrypted system credentials passed.");
return 0;
}
return log_error_errno(errno, "Faile to open system credentials directory.");
}
_cleanup_free_ DirectoryEntries *de = NULL;
r = readdir_all(dfd, RECURSE_DIR_IGNORE_DOT, &de);
if (r < 0)
return log_error_errno(r, "Failed to enumerate system credentials: %m");
FOREACH_ARRAY(i, de->entries, de->n_entries) {
_cleanup_(iovec_done) struct iovec data = {};
struct dirent *d = *i;
if (!startswith_no_case(d->d_name, "pcrlock.")) /* VFAT is case-insensitive, hence don't be too strict here */
continue;
r = read_full_file_full(
dfd, d->d_name,
/* offset= */ UINT64_MAX,
/* size= */ CREDENTIAL_ENCRYPTED_SIZE_MAX,
READ_FULL_FILE_UNBASE64|READ_FULL_FILE_FAIL_WHEN_LARGER,
/* bind_name= */ NULL,
(char**) &data.iov_base,
&data.iov_len);
if (r == -ENOENT)
continue;
if (r < 0) {
log_warning_errno(r, "Failed to read credentials file %s/%s, skipping: %m", ENCRYPTED_SYSTEM_CREDENTIALS_DIRECTORY, d->d_name);
continue;
}
_cleanup_(tpm2_pcrlock_policy_done) Tpm2PCRLockPolicy loaded_policy = {};
r = pcrlock_policy_load_credential(
d->d_name,
&data,
&loaded_policy);
if (r < 0) {
log_warning_errno(r, "Loading of pcrlock policy from credential '%s/%s' failed, skipping.", ENCRYPTED_SYSTEM_CREDENTIALS_DIRECTORY, d->d_name);
continue;
}
if ((!srk || iovec_memcmp(srk, &loaded_policy.srk_handle) == 0) &&
(!nv || iovec_memcmp(nv, &loaded_policy.nv_handle) == 0)) {
*ret = TAKE_STRUCT(loaded_policy);
return 1;
}
}
log_info("No pcrlock policy found among system credentials.");
*ret = (Tpm2PCRLockPolicy) {};
return 0;
}
int tpm2_load_public_key_file(const char *path, TPM2B_PUBLIC *ret) {
@ -6970,6 +7108,7 @@ int tpm2_make_luks2_json(
const struct iovec *policy_hash,
const struct iovec *salt,
const struct iovec *srk,
const struct iovec *pcrlock_nv,
TPM2Flags flags,
JsonVariant **ret) {
@ -7012,7 +7151,8 @@ int tpm2_make_luks2_json(
JSON_BUILD_PAIR_CONDITION(pubkey_pcr_mask != 0, "tpm2_pubkey_pcrs", JSON_BUILD_VARIANT(pkmj)),
JSON_BUILD_PAIR_CONDITION(pubkey_pcr_mask != 0, "tpm2_pubkey", JSON_BUILD_IOVEC_BASE64(pubkey)),
JSON_BUILD_PAIR_CONDITION(iovec_is_set(salt), "tpm2_salt", JSON_BUILD_IOVEC_BASE64(salt)),
JSON_BUILD_PAIR_CONDITION(iovec_is_set(srk), "tpm2_srk", JSON_BUILD_IOVEC_BASE64(srk))));
JSON_BUILD_PAIR_CONDITION(iovec_is_set(srk), "tpm2_srk", JSON_BUILD_IOVEC_BASE64(srk)),
JSON_BUILD_PAIR_CONDITION(iovec_is_set(pcrlock_nv), "tpm2_pcrlock_nv", JSON_BUILD_IOVEC_BASE64(pcrlock_nv))));
if (r < 0)
return r;
@ -7034,9 +7174,10 @@ int tpm2_parse_luks2_json(
struct iovec *ret_policy_hash,
struct iovec *ret_salt,
struct iovec *ret_srk,
struct iovec *ret_pcrlock_nv,
TPM2Flags *ret_flags) {
_cleanup_(iovec_done) struct iovec blob = {}, policy_hash = {}, pubkey = {}, salt = {}, srk = {};
_cleanup_(iovec_done) struct iovec blob = {}, policy_hash = {}, pubkey = {}, salt = {}, srk = {}, pcrlock_nv = {};
uint32_t hash_pcr_mask = 0, pubkey_pcr_mask = 0;
uint16_t primary_alg = TPM2_ALG_ECC; /* ECC was the only supported algorithm in systemd < 250, use that as implied default, for compatibility */
uint16_t pcr_bank = UINT16_MAX; /* default: pick automatically */
@ -7158,6 +7299,13 @@ int tpm2_parse_luks2_json(
return log_debug_errno(r, "Invalid base64 data in 'tpm2_srk' field.");
}
w = json_variant_by_key(v, "tpm2_pcrlock_nv");
if (w) {
r = json_variant_unbase64_iovec(w, &pcrlock_nv);
if (r < 0)
return log_debug_errno(r, "Invalid base64 data in 'tpm2_pcrlock_nv' field.");
}
if (ret_keyslot)
*ret_keyslot = keyslot;
if (ret_hash_pcr_mask)
@ -7176,11 +7324,12 @@ int tpm2_parse_luks2_json(
*ret_policy_hash = TAKE_STRUCT(policy_hash);
if (ret_salt)
*ret_salt = TAKE_STRUCT(salt);
if (ret_flags)
*ret_flags = flags;
if (ret_srk)
*ret_srk = TAKE_STRUCT(srk);
if (ret_pcrlock_nv)
*ret_pcrlock_nv = TAKE_STRUCT(pcrlock_nv);
if (ret_flags)
*ret_flags = flags;
return 0;
}

View file

@ -245,8 +245,10 @@ typedef struct Tpm2PCRLockPolicy {
} Tpm2PCRLockPolicy;
void tpm2_pcrlock_policy_done(Tpm2PCRLockPolicy *data);
int tpm2_pcrlock_policy_from_json(JsonVariant *v, Tpm2PCRLockPolicy *ret_policy);
int tpm2_pcrlock_search_file(const char *path, FILE **ret_file, char **ret_path);
int tpm2_pcrlock_policy_load(const char *path, Tpm2PCRLockPolicy *ret_policy);
int tpm2_pcrlock_policy_from_credentials(const struct iovec *srk, const struct iovec *nv, Tpm2PCRLockPolicy *ret);
int tpm2_index_to_handle(Tpm2Context *c, TPM2_HANDLE index, const Tpm2Handle *session, TPM2B_PUBLIC **ret_public, TPM2B_NAME **ret_name, TPM2B_NAME **ret_qname, Tpm2Handle **ret_handle);
int tpm2_index_from_handle(Tpm2Context *c, const Tpm2Handle *handle, TPM2_HANDLE *ret_index);
@ -383,8 +385,8 @@ int tpm2_find_device_auto(char **ret);
int tpm2_make_pcr_json_array(uint32_t pcr_mask, JsonVariant **ret);
int tpm2_parse_pcr_json_array(JsonVariant *v, uint32_t *ret);
int tpm2_make_luks2_json(int keyslot, uint32_t hash_pcr_mask, uint16_t pcr_bank, const struct iovec *pubkey, uint32_t pubkey_pcr_mask, uint16_t primary_alg, const struct iovec *blob, const struct iovec *policy_hash, const struct iovec *salt, const struct iovec *srk, TPM2Flags flags, JsonVariant **ret);
int tpm2_parse_luks2_json(JsonVariant *v, int *ret_keyslot, uint32_t *ret_hash_pcr_mask, uint16_t *ret_pcr_bank, struct iovec *ret_pubkey, uint32_t *ret_pubkey_pcr_mask, uint16_t *ret_primary_alg, struct iovec *ret_blob, struct iovec *ret_policy_hash, struct iovec *ret_salt, struct iovec *ret_srk, TPM2Flags *ret_flags);
int tpm2_make_luks2_json(int keyslot, uint32_t hash_pcr_mask, uint16_t pcr_bank, const struct iovec *pubkey, uint32_t pubkey_pcr_mask, uint16_t primary_alg, const struct iovec *blob, const struct iovec *policy_hash, const struct iovec *salt, const struct iovec *srk, const struct iovec *pcrlock_nv, TPM2Flags flags, JsonVariant **ret);
int tpm2_parse_luks2_json(JsonVariant *v, int *ret_keyslot, uint32_t *ret_hash_pcr_mask, uint16_t *ret_pcr_bank, struct iovec *ret_pubkey, uint32_t *ret_pubkey_pcr_mask, uint16_t *ret_primary_alg, struct iovec *ret_blob, struct iovec *ret_policy_hash, struct iovec *ret_salt, struct iovec *ret_srk, struct iovec *pcrlock_nv, TPM2Flags *ret_flags);
/* Default to PCR 7 only */
#define TPM2_PCR_INDEX_DEFAULT UINT32_C(7)

View file

@ -118,7 +118,20 @@ echo -n test70-take-two | "$SD_PCRLOCK" lock-raw --pcrlock=/var/lib/pcrlock.d/92
systemd-cryptsetup attach pcrlock "$img" - tpm2-device=auto,tpm2-pcrlock=/var/lib/systemd/pcrlock.json,headless
systemd-cryptsetup detach pcrlock
"$SD_PCRLOCK" remove-policy
# Now use the root fs support, i.e. make the tool write a copy of the pcrlock
# file as service credential to some temporary dir and remove the local copy, so that
# it has to use the credential version.
mkdir /tmp/fakexbootldr
SYSTEMD_XBOOTLDR_PATH=/tmp/fakexbootldr SYSTEMD_RELAX_XBOOTLDR_CHECKS=1 "$SD_PCRLOCK" make-policy --pcr="$PCRS" --force
mv /var/lib/systemd/pcrlock.json /var/lib/systemd/pcrlock.json.gone
systemd-creds decrypt /tmp/fakexbootldr/loader/credentials/pcrlock.*.cred
SYSTEMD_ENCRYPTED_SYSTEM_CREDENTIALS_DIRECTORY=/tmp/fakexbootldr/loader/credentials systemd-cryptsetup attach pcrlock "$img" - tpm2-device=auto,headless
systemd-cryptsetup detach pcrlock
mv /var/lib/systemd/pcrlock.json.gone /var/lib/systemd/pcrlock.json
SYSTEMD_XBOOTLDR_PATH=/tmp/fakexbootldr SYSTEMD_RELAX_XBOOTLDR_CHECKS=1 "$SD_PCRLOCK" remove-policy
"$SD_PCRLOCK" unlock-firmware-config
"$SD_PCRLOCK" unlock-gpt