Merge pull request #25006 from poettering/pcr15
cryptsetup: measure LUKS volume keys to PCR 15
This commit is contained in:
+189
-99
@@ -2,14 +2,21 @@
|
||||
|
||||
#include <getopt.h>
|
||||
|
||||
#include <sd-device.h>
|
||||
#include <sd-messages.h>
|
||||
|
||||
#include "blkid-util.h"
|
||||
#include "blockdev-util.h"
|
||||
#include "build.h"
|
||||
#include "chase-symlinks.h"
|
||||
#include "efi-loader.h"
|
||||
#include "efivars.h"
|
||||
#include "env-util.h"
|
||||
#include "escape.h"
|
||||
#include "fd-util.h"
|
||||
#include "main-func.h"
|
||||
#include "mountpoint-util.h"
|
||||
#include "openssl-util.h"
|
||||
#include "parse-util.h"
|
||||
#include "parse-argument.h"
|
||||
#include "pretty-print.h"
|
||||
#include "tpm-pcr.h"
|
||||
#include "tpm2-util.h"
|
||||
@@ -17,9 +24,12 @@
|
||||
static bool arg_graceful = false;
|
||||
static char *arg_tpm2_device = NULL;
|
||||
static char **arg_banks = NULL;
|
||||
static char *arg_file_system = NULL;
|
||||
static bool arg_machine_id = false;
|
||||
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_banks, strv_freep);
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_tpm2_device, freep);
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_file_system, freep);
|
||||
|
||||
static int help(int argc, char *argv[], void *userdata) {
|
||||
_cleanup_free_ char *link = NULL;
|
||||
@@ -29,7 +39,9 @@ static int help(int argc, char *argv[], void *userdata) {
|
||||
if (r < 0)
|
||||
return log_oom();
|
||||
|
||||
printf("%1$s [OPTIONS...] WORD ...\n"
|
||||
printf("%1$s [OPTIONS...] WORD\n"
|
||||
"%1$s [OPTIONS...] --file-system=PATH\n"
|
||||
"%1$s [OPTIONS...] --machine-id\n"
|
||||
"\n%5$sMeasure boot phase into TPM2 PCR 11.%6$s\n"
|
||||
"\n%3$sOptions:%4$s\n"
|
||||
" -h --help Show this help\n"
|
||||
@@ -37,6 +49,8 @@ static int help(int argc, char *argv[], void *userdata) {
|
||||
" --bank=DIGEST Select TPM bank (SHA1, SHA256)\n"
|
||||
" --tpm2-device=PATH Use specified TPM2 device\n"
|
||||
" --graceful Exit gracefully if no TPM2 device is found\n"
|
||||
" --file-system=PATH Measure UUID/labels of file system into PCR 15\n"
|
||||
" --machine-id Measure machine ID into PCR 15\n"
|
||||
"\nSee the %2$s for details.\n",
|
||||
program_invocation_short_name,
|
||||
link,
|
||||
@@ -54,6 +68,8 @@ static int parse_argv(int argc, char *argv[]) {
|
||||
ARG_BANK,
|
||||
ARG_TPM2_DEVICE,
|
||||
ARG_GRACEFUL,
|
||||
ARG_FILE_SYSTEM,
|
||||
ARG_MACHINE_ID,
|
||||
};
|
||||
|
||||
static const struct option options[] = {
|
||||
@@ -62,10 +78,12 @@ static int parse_argv(int argc, char *argv[]) {
|
||||
{ "bank", required_argument, NULL, ARG_BANK },
|
||||
{ "tpm2-device", required_argument, NULL, ARG_TPM2_DEVICE },
|
||||
{ "graceful", no_argument, NULL, ARG_GRACEFUL },
|
||||
{ "file-system", required_argument, NULL, ARG_FILE_SYSTEM },
|
||||
{ "machine-id", no_argument, NULL, ARG_MACHINE_ID },
|
||||
{}
|
||||
};
|
||||
|
||||
int c;
|
||||
int c, r;
|
||||
|
||||
assert(argc >= 0);
|
||||
assert(argv);
|
||||
@@ -113,6 +131,17 @@ static int parse_argv(int argc, char *argv[]) {
|
||||
arg_graceful = true;
|
||||
break;
|
||||
|
||||
case ARG_FILE_SYSTEM:
|
||||
r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_file_system);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
break;
|
||||
|
||||
case ARG_MACHINE_ID:
|
||||
arg_machine_id = true;
|
||||
break;
|
||||
|
||||
case '?':
|
||||
return -EINVAL;
|
||||
|
||||
@@ -120,49 +149,101 @@ static int parse_argv(int argc, char *argv[]) {
|
||||
assert_not_reached();
|
||||
}
|
||||
|
||||
if (arg_file_system && arg_machine_id)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--file-system= and --machine-id may not be combined.");
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int determine_banks(struct tpm2_context *c) {
|
||||
_cleanup_free_ TPMI_ALG_HASH *algs = NULL;
|
||||
int n_algs, r;
|
||||
static int determine_banks(struct tpm2_context *c, unsigned target_pcr_nr) {
|
||||
_cleanup_strv_free_ char **l = NULL;
|
||||
int r;
|
||||
|
||||
assert(c);
|
||||
|
||||
if (!strv_isempty(arg_banks)) /* Explicitly configured? Then use that */
|
||||
return 0;
|
||||
|
||||
n_algs = tpm2_get_good_pcr_banks(c->esys_context, UINT32_C(1) << TPM_PCR_INDEX_KERNEL_IMAGE, &algs);
|
||||
if (n_algs <= 0)
|
||||
return n_algs;
|
||||
r = tpm2_get_good_pcr_banks_strv(c->esys_context, UINT32_C(1) << target_pcr_nr, &l);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
for (int i = 0; i < n_algs; i++) {
|
||||
const EVP_MD *implementation;
|
||||
const char *salg;
|
||||
strv_free_and_replace(arg_banks, l);
|
||||
return 0;
|
||||
}
|
||||
|
||||
salg = tpm2_pcr_bank_to_string(algs[i]);
|
||||
if (!salg)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "TPM2 operates with unknown PCR algorithm, can't measure.");
|
||||
static int get_file_system_word(
|
||||
sd_device *d,
|
||||
const char *prefix,
|
||||
char **ret) {
|
||||
|
||||
implementation = EVP_get_digestbyname(salg);
|
||||
if (!implementation)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "TPM2 operates with unsupported PCR algorithm, can't measure.");
|
||||
int r;
|
||||
|
||||
r = strv_extend(&arg_banks, EVP_MD_name(implementation));
|
||||
assert(d);
|
||||
assert(prefix);
|
||||
assert(ret);
|
||||
|
||||
_cleanup_close_ int block_fd = sd_device_open(d, O_RDONLY|O_CLOEXEC|O_NONBLOCK);
|
||||
if (block_fd < 0)
|
||||
return block_fd;
|
||||
|
||||
_cleanup_(blkid_free_probep) blkid_probe b = blkid_new_probe();
|
||||
if (!b)
|
||||
return -ENOMEM;
|
||||
|
||||
errno = 0;
|
||||
r = blkid_probe_set_device(b, block_fd, 0, 0);
|
||||
if (r != 0)
|
||||
return errno_or_else(ENOMEM);
|
||||
|
||||
(void) blkid_probe_enable_superblocks(b, 1);
|
||||
(void) blkid_probe_set_superblocks_flags(b, BLKID_SUBLKS_TYPE|BLKID_SUBLKS_UUID|BLKID_SUBLKS_LABEL);
|
||||
(void) blkid_probe_enable_partitions(b, 1);
|
||||
(void) blkid_probe_set_partitions_flags(b, BLKID_PARTS_ENTRY_DETAILS);
|
||||
|
||||
errno = 0;
|
||||
r = blkid_do_safeprobe(b);
|
||||
if (r == _BLKID_SAFEPROBE_ERROR)
|
||||
return errno_or_else(EIO);
|
||||
if (IN_SET(r, _BLKID_SAFEPROBE_AMBIGUOUS, _BLKID_SAFEPROBE_NOT_FOUND))
|
||||
return -ENOPKG;
|
||||
|
||||
assert(r == _BLKID_SAFEPROBE_FOUND);
|
||||
|
||||
_cleanup_strv_free_ char **l = strv_new(prefix);
|
||||
if (!l)
|
||||
return log_oom();
|
||||
|
||||
FOREACH_STRING(field, "TYPE", "UUID", "LABEL", "PART_ENTRY_UUID", "PART_ENTRY_TYPE", "PART_ENTRY_NAME") {
|
||||
const char *v = NULL;
|
||||
|
||||
(void) blkid_probe_lookup_value(b, field, &v, NULL);
|
||||
|
||||
_cleanup_free_ char *escaped = xescape(strempty(v), ":"); /* Avoid ambiguity around ":" */
|
||||
if (!escaped)
|
||||
return log_oom();
|
||||
|
||||
r = strv_consume(&l, TAKE_PTR(escaped));
|
||||
if (r < 0)
|
||||
return log_oom();
|
||||
|
||||
}
|
||||
|
||||
assert(strv_length(l) == 7); /* We always want 7 components, to avoid ambiguous strings */
|
||||
|
||||
_cleanup_free_ char *word = strv_join(l, ":");
|
||||
if (!word)
|
||||
return log_oom();
|
||||
|
||||
*ret = TAKE_PTR(word);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int run(int argc, char *argv[]) {
|
||||
_cleanup_(tpm2_context_destroy) struct tpm2_context c = {};
|
||||
_cleanup_free_ char *joined = NULL, *pcr_string = NULL;
|
||||
const char *word;
|
||||
unsigned pcr_nr;
|
||||
_cleanup_free_ char *joined = NULL, *word = NULL;
|
||||
unsigned target_pcr_nr;
|
||||
size_t length;
|
||||
TSS2_RC rc;
|
||||
int r;
|
||||
|
||||
log_setup();
|
||||
@@ -171,16 +252,79 @@ static int run(int argc, char *argv[]) {
|
||||
if (r <= 0)
|
||||
return r;
|
||||
|
||||
if (optind+1 != argc)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected a single argument.");
|
||||
if (arg_file_system) {
|
||||
_cleanup_free_ char *normalized = NULL, *normalized_escaped = NULL;
|
||||
_cleanup_(sd_device_unrefp) sd_device *d = NULL;
|
||||
_cleanup_close_ int dfd = -EBADF;
|
||||
|
||||
word = argv[optind];
|
||||
if (optind != argc)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected no argument.");
|
||||
|
||||
/* Refuse to measure an empty word. We want to be able to write the series of measured words
|
||||
* separated by colons, where multiple separating colons are collapsed. Thus it makes sense to
|
||||
* disallow an empty word to avoid ambiguities. */
|
||||
if (isempty(word))
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "String to measure cannot be empty, refusing.");
|
||||
dfd = chase_symlinks_and_open(arg_file_system, NULL, 0, O_DIRECTORY|O_CLOEXEC, &normalized);
|
||||
if (dfd < 0)
|
||||
return log_error_errno(dfd, "Failed to open path '%s': %m", arg_file_system);
|
||||
|
||||
r = fd_is_mount_point(dfd, NULL, 0);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to determine if path '%s' is mount point: %m", normalized);
|
||||
if (r == 0)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(ENOTDIR), "Specified path '%s' is not a mount point, refusing: %m", normalized);
|
||||
|
||||
normalized_escaped = xescape(normalized, ":"); /* Avoid ambiguity around ":" */
|
||||
if (!normalized_escaped)
|
||||
return log_oom();
|
||||
|
||||
_cleanup_free_ char* prefix = strjoin("file-system:", normalized_escaped);
|
||||
if (!prefix)
|
||||
return log_oom();
|
||||
|
||||
r = block_device_new_from_fd(dfd, BLOCK_DEVICE_LOOKUP_BACKING, &d);
|
||||
if (r < 0) {
|
||||
log_notice_errno(r, "Unable to determine backing block device of '%s', measuring generic fallback file system identity string: %m", arg_file_system);
|
||||
|
||||
word = strjoin(prefix, "::::::");
|
||||
if (!word)
|
||||
return log_oom();
|
||||
} else {
|
||||
r = get_file_system_word(d, prefix, &word);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to get file system identifier string for '%s': %m", arg_file_system);
|
||||
}
|
||||
|
||||
target_pcr_nr = TPM_PCR_INDEX_VOLUME_KEY; /* → PCR 15 */
|
||||
|
||||
} else if (arg_machine_id) {
|
||||
sd_id128_t mid;
|
||||
|
||||
if (optind != argc)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected no argument.");
|
||||
|
||||
r = sd_id128_get_machine(&mid);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to acquire machine ID: %m");
|
||||
|
||||
word = strjoin("machine-id:", SD_ID128_TO_STRING(mid));
|
||||
if (!word)
|
||||
return log_oom();
|
||||
|
||||
target_pcr_nr = TPM_PCR_INDEX_VOLUME_KEY; /* → PCR 15 */
|
||||
|
||||
} else {
|
||||
if (optind+1 != argc)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected a single argument.");
|
||||
|
||||
word = strdup(argv[optind]);
|
||||
if (!word)
|
||||
return log_oom();
|
||||
|
||||
/* Refuse to measure an empty word. We want to be able to write the series of measured words
|
||||
* separated by colons, where multiple separating colons are collapsed. Thus it makes sense to
|
||||
* disallow an empty word to avoid ambiguities. */
|
||||
if (isempty(word))
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "String to measure cannot be empty, refusing.");
|
||||
|
||||
target_pcr_nr = TPM_PCR_INDEX_KERNEL_IMAGE; /* → PCR 11 */
|
||||
}
|
||||
|
||||
if (arg_graceful && tpm2_support() != TPM2_SUPPORT_FULL) {
|
||||
log_notice("No complete TPM2 support detected, exiting gracefully.");
|
||||
@@ -189,32 +333,13 @@ static int run(int argc, char *argv[]) {
|
||||
|
||||
length = strlen(word);
|
||||
|
||||
int b = getenv_bool("SYSTEMD_PCRPHASE_STUB_VERIFY");
|
||||
if (b < 0 && b != -ENXIO)
|
||||
log_warning_errno(b, "Unable to parse $SYSTEMD_PCRPHASE_STUB_VERIFY value, ignoring.");
|
||||
|
||||
/* Skip logic if sd-stub is not used, after all PCR 11 might have a very different purpose then. */
|
||||
r = efi_get_variable_string(EFI_LOADER_VARIABLE(StubPcrKernelImage), &pcr_string);
|
||||
if (r == -ENOENT) {
|
||||
if (b != 0) {
|
||||
log_info("Kernel stub did not measure kernel image into PCR %u, skipping measurement.", TPM_PCR_INDEX_KERNEL_IMAGE);
|
||||
return EXIT_SUCCESS;
|
||||
} else
|
||||
log_notice("Kernel stub did not measure kernel image into PCR %u, but told to measure anyway, hence proceeding.", TPM_PCR_INDEX_KERNEL_IMAGE);
|
||||
} else if (r < 0)
|
||||
return log_error_errno(r, "Failed to read StubPcrKernelImage EFI variable: %m");
|
||||
else {
|
||||
/* Let's validate that the stub announced PCR 11 as we expected. */
|
||||
r = safe_atou(pcr_string, &pcr_nr);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to parse StubPcrKernelImage EFI variable: %s", pcr_string);
|
||||
if (pcr_nr != TPM_PCR_INDEX_KERNEL_IMAGE) {
|
||||
if (b != 0)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EREMOTE), "Kernel stub measured kernel image into PCR %u, which is different than expected %u.", pcr_nr, TPM_PCR_INDEX_KERNEL_IMAGE);
|
||||
else
|
||||
log_notice("Kernel stub measured kernel image into PCR %u, which is different than expected %u, but told to measure anyway, hence proceeding.", pcr_nr, TPM_PCR_INDEX_KERNEL_IMAGE);
|
||||
} else
|
||||
log_debug("Kernel stub reported same PCR %u as we want to use, proceeding.", TPM_PCR_INDEX_KERNEL_IMAGE);
|
||||
r = efi_stub_measured();
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to detect if we are running on a kernel image with TPM measurement enabled: %m");
|
||||
if (r == 0) {
|
||||
log_info("Kernel stub did not measure kernel image into PCR %u, skipping userspace measurement, too.", TPM_PCR_INDEX_KERNEL_IMAGE);
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
r = dlopen_tpm2();
|
||||
@@ -225,62 +350,27 @@ static int run(int argc, char *argv[]) {
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = determine_banks(&c);
|
||||
r = determine_banks(&c, target_pcr_nr);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (strv_isempty(arg_banks)) /* Still none? */
|
||||
return log_error_errno(SYNTHETIC_ERRNO(ENOENT), "Found a TPM2 without enabled PCR banks. Can't operate.");
|
||||
|
||||
TPML_DIGEST_VALUES values = {};
|
||||
STRV_FOREACH(bank, arg_banks) {
|
||||
const EVP_MD *implementation;
|
||||
int id;
|
||||
|
||||
assert_se(implementation = EVP_get_digestbyname(*bank));
|
||||
|
||||
if (values.count >= ELEMENTSOF(values.digests))
|
||||
return log_error_errno(SYNTHETIC_ERRNO(E2BIG), "Too many banks selected.");
|
||||
|
||||
if ((size_t) EVP_MD_size(implementation) > sizeof(values.digests[values.count].digest))
|
||||
return log_error_errno(SYNTHETIC_ERRNO(E2BIG), "Hash result too large for TPM2.");
|
||||
|
||||
id = tpm2_pcr_bank_from_string(EVP_MD_name(implementation));
|
||||
if (id < 0)
|
||||
return log_error_errno(id, "Can't map hash name to TPM2.");
|
||||
|
||||
values.digests[values.count].hashAlg = id;
|
||||
|
||||
if (EVP_Digest(word, length, (unsigned char*) &values.digests[values.count].digest, NULL, implementation, NULL) != 1)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to hash word.");
|
||||
|
||||
values.count++;
|
||||
}
|
||||
|
||||
joined = strv_join(arg_banks, ", ");
|
||||
if (!joined)
|
||||
return log_oom();
|
||||
|
||||
log_debug("Measuring '%s' into PCR index %u, banks %s.", word, TPM_PCR_INDEX_KERNEL_IMAGE, joined);
|
||||
log_debug("Measuring '%s' into PCR index %u, banks %s.", word, target_pcr_nr, joined);
|
||||
|
||||
rc = sym_Esys_PCR_Extend(
|
||||
c.esys_context,
|
||||
ESYS_TR_PCR0 + TPM_PCR_INDEX_KERNEL_IMAGE, /* → PCR 11 */
|
||||
ESYS_TR_PASSWORD,
|
||||
ESYS_TR_NONE,
|
||||
ESYS_TR_NONE,
|
||||
&values);
|
||||
if (rc != TSS2_RC_SUCCESS)
|
||||
return log_error_errno(
|
||||
SYNTHETIC_ERRNO(ENOTRECOVERABLE),
|
||||
"Failed to measure '%s': %s",
|
||||
word,
|
||||
sym_Tss2_RC_Decode(rc));
|
||||
r = tpm2_extend_bytes(c.esys_context, arg_banks, target_pcr_nr, word, length, NULL, 0);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
log_struct(LOG_INFO,
|
||||
"MESSAGE_ID=" SD_MESSAGE_TPM_PCR_EXTEND_STR,
|
||||
LOG_MESSAGE("Successfully extended PCR index %u with '%s' (banks %s).", TPM_PCR_INDEX_KERNEL_IMAGE, word, joined),
|
||||
LOG_MESSAGE("Extended PCR index %u with '%s' (banks %s).", target_pcr_nr, word, joined),
|
||||
"MEASURING=%s", word,
|
||||
"PCR=%u", TPM_PCR_INDEX_KERNEL_IMAGE,
|
||||
"PCR=%u", target_pcr_nr,
|
||||
"BANKS=%s", joined);
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
|
||||
Reference in New Issue
Block a user