#include <unistd.h>
#include "alloc-util.h"
+#include "build.h"
#include "efi-loader.h"
#include "fd-util.h"
#include "fileio.h"
#include "hexdecoct.h"
#include "json.h"
#include "main-func.h"
+#include "memstream-util.h"
#include "openssl-util.h"
#include "parse-argument.h"
#include "parse-util.h"
static JsonFormatFlags arg_json_format_flags = JSON_FORMAT_PRETTY_AUTO|JSON_FORMAT_COLOR_AUTO|JSON_FORMAT_OFF;
static PagerFlags arg_pager_flags = 0;
static bool arg_current = false;
+static char **arg_phase = NULL;
+static char *arg_append = NULL;
STATIC_DESTRUCTOR_REGISTER(arg_banks, strv_freep);
STATIC_DESTRUCTOR_REGISTER(arg_tpm2_device, freep);
STATIC_DESTRUCTOR_REGISTER(arg_private_key, freep);
STATIC_DESTRUCTOR_REGISTER(arg_public_key, freep);
+STATIC_DESTRUCTOR_REGISTER(arg_phase, strv_freep);
+STATIC_DESTRUCTOR_REGISTER(arg_append, freep);
static inline void free_sections(char*(*sections)[_UNIFIED_SECTION_MAX]) {
for (UnifiedSection c = 0; c < _UNIFIED_SECTION_MAX; c++)
return log_oom();
printf("%1$s [OPTIONS...] COMMAND ...\n"
- "\n%5$sPre-calculate and sign PCR hash for a unified kernel image.%6$s\n"
+ "\n%5$sPre-calculate and sign PCR hash for a unified kernel image (UKI).%6$s\n"
"\n%3$sCommands:%4$s\n"
" status Show current PCR values\n"
" calculate Calculate expected PCR values\n"
" -h --help Show this help\n"
" --version Print version\n"
" --no-pager Do not pipe output into a pager\n"
- " --linux=PATH Path Linux kernel ELF image\n"
- " --osrel=PATH Path to os-release file\n"
- " --cmdline=PATH Path to file with kernel command line\n"
- " --initrd=PATH Path to initrd image\n"
- " --splash=PATH Path to splash bitmap\n"
- " --dtb=PATH Path to Devicetree file\n"
- " --pcrpkey=PATH Path to public key for PCR signatures in DER format\n"
" -c --current Use current PCR values\n"
- " --bank=DIGEST Select TPM bank (SHA1, SHA256)\n"
+ " --phase=PHASE Specify a boot phase to sign for\n"
+ " --bank=DIGEST Select TPM bank (SHA1, SHA256, SHA384, SHA512)\n"
" --tpm2-device=PATH Use specified TPM2 device\n"
" --private-key=KEY Private key (PEM) to sign with\n"
" --public-key=KEY Public key (PEM) to validate against\n"
" --json=MODE Output as JSON\n"
" -j Same as --json=pretty on tty, --json=short otherwise\n"
+ " --append=PATH Load specified JSON signature, and append new signature to it\n"
+ "\n%3$sUKI PE Section Options:%4$s %3$sUKI PE Section%4$s\n"
+ " --linux=PATH Path to Linux kernel image file %7$s .linux\n"
+ " --osrel=PATH Path to os-release file %7$s .osrel\n"
+ " --cmdline=PATH Path to file with kernel command line %7$s .cmdline\n"
+ " --initrd=PATH Path to initrd image file %7$s .initrd\n"
+ " --splash=PATH Path to splash bitmap file %7$s .splash\n"
+ " --dtb=PATH Path to Devicetree file %7$s .dtb\n"
+ " --uname=PATH Path to 'uname -r' file %7$s .uname\n"
+ " --pcrpkey=PATH Path to public key for PCR signatures %7$s .pcrpkey\n"
"\nSee the %2$s for details.\n",
program_invocation_short_name,
link,
ansi_underline(),
ansi_normal(),
ansi_highlight(),
- ansi_normal());
+ ansi_normal(),
+ special_glyph(SPECIAL_GLYPH_ARROW_RIGHT));
return 0;
}
+static char *normalize_phase(const char *s) {
+ _cleanup_strv_free_ char **l = NULL;
+
+ /* Let's normalize phase expressions. We split the series of colon-separated words up, then remove
+ * all empty ones, and glue them back together again. In other words we remove duplicate ":", as well
+ * as leading and trailing ones. */
+
+ l = strv_split(s, ":"); /* Split series of words */
+ if (!l)
+ return NULL;
+
+ /* Remove all empty words and glue things back together */
+ return strv_join(strv_remove(l, ""), ":");
+}
+
static int parse_argv(int argc, char *argv[]) {
enum {
ARG_VERSION = 0x100,
ARG_INITRD,
ARG_SPLASH,
ARG_DTB,
+ ARG_UNAME,
_ARG_PCRSIG, /* the .pcrsig section is not input for signing, hence not actually an argument here */
_ARG_SECTION_LAST,
ARG_PCRPKEY = _ARG_SECTION_LAST,
ARG_PUBLIC_KEY,
ARG_TPM2_DEVICE,
ARG_JSON,
+ ARG_PHASE,
+ ARG_APPEND,
};
static const struct option options[] = {
{ "initrd", required_argument, NULL, ARG_INITRD },
{ "splash", required_argument, NULL, ARG_SPLASH },
{ "dtb", required_argument, NULL, ARG_DTB },
+ { "uname", required_argument, NULL, ARG_UNAME },
{ "pcrpkey", required_argument, NULL, ARG_PCRPKEY },
{ "current", no_argument, NULL, 'c' },
{ "bank", required_argument, NULL, ARG_BANK },
{ "private-key", required_argument, NULL, ARG_PRIVATE_KEY },
{ "public-key", required_argument, NULL, ARG_PUBLIC_KEY },
{ "json", required_argument, NULL, ARG_JSON },
+ { "phase", required_argument, NULL, ARG_PHASE },
+ { "append", required_argument, NULL, ARG_APPEND },
{}
};
break;
+ case ARG_PHASE: {
+ char *n;
+
+ n = normalize_phase(optarg);
+ if (!n)
+ return log_oom();
+
+ r = strv_consume(&arg_phase, TAKE_PTR(n));
+ if (r < 0)
+ return r;
+
+ break;
+ }
+
+ case ARG_APPEND:
+ r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_append);
+ if (r < 0)
+ return r;
+
+ break;
+
case '?':
return -EINVAL;
if (arg_current)
for (UnifiedSection us = 0; us < _UNIFIED_SECTION_MAX; us++)
if (arg_sections[us])
- return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "The --current switch cannot be used in combination with --linux= and related switches.");
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "The --current switch cannot be used in combination with --linux= and related switches.");
+
+ if (strv_isempty(arg_phase)) {
+ /* If no phases are specifically selected, pick everything from the beginning of the initrd
+ * to the beginning of shutdown. */
+ if (strv_extend_strv(&arg_phase,
+ STRV_MAKE("enter-initrd",
+ "enter-initrd:leave-initrd",
+ "enter-initrd:leave-initrd:sysinit",
+ "enter-initrd:leave-initrd:sysinit:ready"),
+ /* filter_duplicates= */ false) < 0)
+ return log_oom();
+ } else {
+ strv_sort(arg_phase);
+ strv_uniq(arg_phase);
+ }
+ _cleanup_free_ char *j = NULL;
+ j = strv_join(arg_phase, ", ");
+ if (!j)
+ return log_oom();
+
+ log_debug("Measuring boot phases: %s", j);
return 1;
}
+/* The PCR 11 state for one specific bank */
typedef struct PcrState {
char *bank;
const EVP_MD *md;
void *value;
size_t value_size;
+ void *saved_value; /* A copy of the original value we calculated, used by pcr_states_save()/pcr_states_restore() to come later back to */
} PcrState;
static void pcr_state_free_all(PcrState **pcr_state) {
for (size_t i = 0; (*pcr_state)[i].value; i++) {
free((*pcr_state)[i].bank);
free((*pcr_state)[i].value);
+ free((*pcr_state)[i].saved_value);
}
*pcr_state = mfree(*pcr_state);
assert(n > 0);
assert(pcr_states);
+ /* Virtually measures the components of a unified kernel image into PCR 11 */
+
if (arg_current) {
/* Shortcut things, if we should just use the current PCR value */
for (UnifiedSection c = 0; c < _UNIFIED_SECTION_MAX; c++) {
_cleanup_(evp_md_ctx_free_all) EVP_MD_CTX **mdctx = NULL;
- _cleanup_close_ int fd = -1;
+ _cleanup_close_ int fd = -EBADF;
uint64_t m = 0;
if (!arg_sections[c])
return log_oom();
if (EVP_DigestInit_ex(mdctx[i], pcr_states[i].md, NULL) != 1)
- return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to initialize data %s context.", pcr_states[i].bank);
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "Failed to initialize data %s context.", pcr_states[i].bank);
}
for (;;) {
return 0;
}
+static int measure_phase(PcrState *pcr_states, size_t n, const char *phase) {
+ _cleanup_strv_free_ char **l = NULL;
+ int r;
+
+ assert(pcr_states);
+ assert(n > 0);
+
+ /* Measure a phase string into PCR 11. This splits up the "phase" expression at colons, and then
+ * virtually extends each specified word into PCR 11, to model how during boot we measure a series of
+ * words into PCR 11, one for each phase. */
+
+ l = strv_split(phase, ":");
+ if (!l)
+ return log_oom();
+
+ STRV_FOREACH(word, l) {
+ size_t wl;
+
+ if (isempty(*word))
+ continue;
+
+ wl = strlen(*word);
+
+ for (size_t i = 0; i < n; i++) { /* For each bank */
+ _cleanup_free_ void *b = NULL;
+ int bsz;
+
+ bsz = EVP_MD_size(pcr_states[i].md);
+ assert(bsz > 0);
+
+ b = malloc(bsz);
+ if (!b)
+ return log_oom();
+
+ /* First hash the word itself */
+ if (EVP_Digest(*word, wl, b, NULL, pcr_states[i].md, NULL) != 1)
+ return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to hash word '%s'.", *word);
+
+ /* And then extend the PCR with the resulting hash */
+ r = pcr_state_extend(pcr_states + i, b, bsz);
+ if (r < 0)
+ return r;
+ }
+ }
+
+ return 0;
+}
+
static int pcr_states_allocate(PcrState **ret) {
_cleanup_(pcr_state_free_all) PcrState *pcr_states = NULL;
size_t n = 0;
return (int) n;
}
+static int pcr_states_save(PcrState *pcr_states, size_t n) {
+ assert(pcr_states);
+ assert(n > 0);
+
+ for (size_t i = 0; i < n; i++) {
+ _cleanup_free_ void *saved = NULL;
+
+ if (!pcr_states[i].value)
+ continue;
+
+ saved = memdup(pcr_states[i].value, pcr_states[i].value_size);
+ if (!saved)
+ return log_oom();
+
+ free_and_replace(pcr_states[i].saved_value, saved);
+ }
+
+ return 0;
+}
+
+static void pcr_states_restore(PcrState *pcr_states, size_t n) {
+ assert(pcr_states);
+ assert(n > 0);
+
+ for (size_t i = 0; i < n; i++) {
+
+ assert(pcr_states[i].value);
+ assert(pcr_states[i].saved_value);
+
+ memcpy(pcr_states[i].value, pcr_states[i].saved_value, pcr_states[i].value_size);
+ }
+}
+
static int verb_calculate(int argc, char *argv[], void *userdata) {
_cleanup_(json_variant_unrefp) JsonVariant *w = NULL;
_cleanup_(pcr_state_free_all) PcrState *pcr_states = NULL;
- size_t n;
int r;
if (!arg_sections[UNIFIED_SECTION_LINUX] && !arg_current)
- return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Either --linux= or --current must be specified, refusing.");
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "Either --linux= or --current must be specified, refusing.");
+ if (arg_append)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "The --append= switch is only supported for 'sign', not 'calculate'.");
+
+ assert(!strv_isempty(arg_banks));
+ assert(!strv_isempty(arg_phase));
r = pcr_states_allocate(&pcr_states);
if (r < 0)
return r;
- n = (size_t) r;
+ size_t n = r;
r = measure_kernel(pcr_states, n);
if (r < 0)
return r;
- for (size_t i = 0; i < n; i++) {
- if (arg_json_format_flags & JSON_FORMAT_OFF) {
- _cleanup_free_ char *hd = NULL;
+ /* Save the current state, so that we later can restore to it. This way we can measure the PCR values
+ * for multiple different boot phases without heaving to start from zero each time */
+ r = pcr_states_save(pcr_states, n);
+ if (r < 0)
+ return r;
- hd = hexmem(pcr_states[i].value, pcr_states[i].value_size);
- if (!hd)
- return log_oom();
+ STRV_FOREACH(phase, arg_phase) {
- printf("%" PRIu32 ":%s=%s\n", TPM_PCR_INDEX_KERNEL_IMAGE, pcr_states[i].bank, hd);
- } else {
- _cleanup_(json_variant_unrefp) JsonVariant *bv = NULL;
+ r = measure_phase(pcr_states, n, *phase);
+ if (r < 0)
+ return r;
- r = json_build(&bv,
- JSON_BUILD_ARRAY(
- JSON_BUILD_OBJECT(
- JSON_BUILD_PAIR("pcr", JSON_BUILD_INTEGER(TPM_PCR_INDEX_KERNEL_IMAGE)),
- JSON_BUILD_PAIR("hash", JSON_BUILD_HEX(pcr_states[i].value, pcr_states[i].value_size))
- )
- )
- );
- if (r < 0)
- return log_error_errno(r, "Failed to build JSON object: %m");
+ for (size_t i = 0; i < n; i++) {
+ if (arg_json_format_flags & JSON_FORMAT_OFF) {
+ _cleanup_free_ char *hd = NULL;
- r = json_variant_set_field(&w, pcr_states[i].bank, bv);
- if (r < 0)
- return log_error_errno(r, "Failed to add bank info to object: %m");
+ if (i == 0) {
+ fflush(stdout);
+ fprintf(stderr, "%s# PCR[%" PRIu32 "] Phase <%s>%s\n",
+ ansi_grey(),
+ TPM_PCR_INDEX_KERNEL_IMAGE,
+ isempty(*phase) ? ":" : *phase,
+ ansi_normal());
+ fflush(stderr);
+ }
+
+ hd = hexmem(pcr_states[i].value, pcr_states[i].value_size);
+ if (!hd)
+ return log_oom();
+
+ printf("%" PRIu32 ":%s=%s\n", TPM_PCR_INDEX_KERNEL_IMAGE, pcr_states[i].bank, hd);
+ } else {
+ _cleanup_(json_variant_unrefp) JsonVariant *bv = NULL, *array = NULL;
+
+ array = json_variant_ref(json_variant_by_key(w, pcr_states[i].bank));
+
+ r = json_build(&bv,
+ JSON_BUILD_OBJECT(
+ JSON_BUILD_PAIR_CONDITION(!isempty(*phase), "phase", JSON_BUILD_STRING(*phase)),
+ JSON_BUILD_PAIR("pcr", JSON_BUILD_INTEGER(TPM_PCR_INDEX_KERNEL_IMAGE)),
+ JSON_BUILD_PAIR("hash", JSON_BUILD_HEX(pcr_states[i].value, pcr_states[i].value_size))
+ )
+ );
+ if (r < 0)
+ return log_error_errno(r, "Failed to build JSON object: %m");
+
+ r = json_variant_append_array(&array, bv);
+ if (r < 0)
+ return log_error_errno(r, "Failed to append JSON object to array: %m");
+ r = json_variant_set_field(&w, pcr_states[i].bank, array);
+ if (r < 0)
+ return log_error_errno(r, "Failed to add bank info to object: %m");
+ }
}
+
+ /* Return to the original kernel measurement for the next phase calculation */
+ pcr_states_restore(pcr_states, n);
}
if (!FLAGS_SET(arg_json_format_flags, JSON_FORMAT_OFF)) {
return 0;
}
-static TPM2_ALG_ID convert_evp_md_name_to_tpm2_alg(const EVP_MD *md) {
- const char *mdname;
-
- mdname = EVP_MD_name(md);
- if (strcaseeq(mdname, "sha1"))
- return TPM2_ALG_SHA1;
- if (strcaseeq(mdname, "sha256"))
- return TPM2_ALG_SHA256;
- if (strcaseeq(mdname, "sha384"))
- return TPM2_ALG_SHA384;
- if (strcaseeq(mdname, "sha512"))
- return TPM2_ALG_SHA512;
-
- return TPM2_ALG_ERROR;
-}
-
static int verb_sign(int argc, char *argv[], void *userdata) {
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
_cleanup_(pcr_state_free_all) PcrState *pcr_states = NULL;
_cleanup_(EVP_PKEY_freep) EVP_PKEY *privkey = NULL, *pubkey = NULL;
- _cleanup_(tpm2_context_destroy) struct tpm2_context c = {};
- _cleanup_fclose_ FILE *privkeyf = NULL , *pubkeyf = NULL;
- ESYS_TR session_handle = ESYS_TR_NONE;
- TSS2_RC rc;
+ _cleanup_fclose_ FILE *privkeyf = NULL;
size_t n;
int r;
if (!arg_sections[UNIFIED_SECTION_LINUX] && !arg_current)
- return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Either --linux= or --current must be specified, refusing.");
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "Either --linux= or --current must be specified, refusing.");
if (!arg_private_key)
- return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No private key specified, use --private-key=.");
- if (!arg_public_key)
- return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No public key specified, use --public-key=.");
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "No private key specified, use --private-key=.");
+
+ assert(!strv_isempty(arg_banks));
+ assert(!strv_isempty(arg_phase));
+
+ if (arg_append) {
+ r = json_parse_file(NULL, arg_append, 0, &v, NULL, NULL);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse '%s': %m", arg_append);
+
+ if (!json_variant_is_object(v))
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "File '%s' is not a valid JSON object, refusing.", arg_append);
+ }
/* When signing we only support JSON output */
arg_json_format_flags &= ~JSON_FORMAT_OFF;
if (!privkeyf)
return log_error_errno(errno, "Failed to open private key file '%s': %m", arg_private_key);
- pubkeyf = fopen(arg_public_key, "re");
- if (!pubkeyf)
- return log_error_errno(errno, "Failed to open public key file '%s': %m", arg_public_key);
-
privkey = PEM_read_PrivateKey(privkeyf, NULL, NULL, NULL);
if (!privkey)
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to parse private key '%s'.", arg_private_key);
- pubkey = PEM_read_PUBKEY(pubkeyf, NULL, NULL, NULL);
- if (!pubkey)
- return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to parse public key '%s'.", arg_public_key);
+ if (arg_public_key) {
+ _cleanup_fclose_ FILE *pubkeyf = NULL;
+
+ pubkeyf = fopen(arg_public_key, "re");
+ if (!pubkeyf)
+ return log_error_errno(errno, "Failed to open public key file '%s': %m", arg_public_key);
+
+ pubkey = PEM_read_PUBKEY(pubkeyf, NULL, NULL, NULL);
+ if (!pubkey)
+ return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to parse public key '%s'.", arg_public_key);
+ } else {
+ _cleanup_(memstream_done) MemStream m = {};
+ FILE *tf;
+
+ /* No public key was specified, let's derive it automatically, if we can */
+
+ tf = memstream_init(&m);
+ if (!tf)
+ return log_oom();
+
+ if (i2d_PUBKEY_fp(tf, privkey) != 1)
+ return log_error_errno(SYNTHETIC_ERRNO(EIO),
+ "Failed to extract public key from private key file '%s'.", arg_private_key);
+
+ fflush(tf);
+ rewind(tf);
+
+ if (!d2i_PUBKEY_fp(tf, &pubkey))
+ return log_error_errno(SYNTHETIC_ERRNO(EIO),
+ "Failed to parse extracted public key of private key file '%s'.", arg_private_key);
+ }
r = pcr_states_allocate(&pcr_states);
if (r < 0)
if (r < 0)
return r;
- r = dlopen_tpm2();
+ r = pcr_states_save(pcr_states, n);
if (r < 0)
return r;
- r = tpm2_context_init(arg_tpm2_device, &c);
- if (r < 0)
- return r;
+ STRV_FOREACH(phase, arg_phase) {
- for (size_t i = 0; i < n; i++) {
- static const TPMT_SYM_DEF symmetric = {
- .algorithm = TPM2_ALG_AES,
- .keyBits.aes = 128,
- .mode.aes = TPM2_ALG_CFB,
- };
- PcrState *p = pcr_states + i;
-
- rc = sym_Esys_StartAuthSession(
- c.esys_context,
- ESYS_TR_NONE,
- ESYS_TR_NONE,
- ESYS_TR_NONE,
- ESYS_TR_NONE,
- ESYS_TR_NONE,
- NULL,
- TPM2_SE_TRIAL,
- &symmetric,
- TPM2_ALG_SHA256,
- &session_handle);
- if (rc != TSS2_RC_SUCCESS) {
- r = log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
- "Failed to open session in TPM: %s", sym_Tss2_RC_Decode(rc));
- goto finish;
- }
+ r = measure_phase(pcr_states, n, *phase);
+ if (r < 0)
+ return r;
- /* Generate a single hash value from the PCRs included in our policy. Given that that's
- * exactly one, the calculation is trivial. */
- TPM2B_DIGEST intermediate_digest = {
- .size = SHA256_DIGEST_SIZE,
- };
- assert(sizeof(intermediate_digest.buffer) >= SHA256_DIGEST_SIZE);
- sha256_direct(p->value, p->value_size, intermediate_digest.buffer);
+ for (size_t i = 0; i < n; i++) {
+ PcrState *p = pcr_states + i;
- TPM2_ALG_ID tpmalg = convert_evp_md_name_to_tpm2_alg(p->md);
- if (tpmalg == TPM2_ALG_ERROR) {
- r = log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Unsupported PCR bank");
- goto finish;
- }
+ int tpmalg = tpm2_hash_alg_from_string(EVP_MD_name(p->md));
+ if (tpmalg < 0)
+ return log_error_errno(tpmalg, "Unsupported PCR bank");
- TPML_PCR_SELECTION pcr_selection;
- tpm2_pcr_mask_to_selection(1 << TPM_PCR_INDEX_KERNEL_IMAGE, tpmalg, &pcr_selection);
-
- rc = sym_Esys_PolicyPCR(
- c.esys_context,
- session_handle,
- ESYS_TR_NONE,
- ESYS_TR_NONE,
- ESYS_TR_NONE,
- &intermediate_digest,
- &pcr_selection);
- if (rc != TSS2_RC_SUCCESS) {
- r = log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
- "Failed to push PCR policy into TPM: %s", sym_Tss2_RC_Decode(rc));
- goto finish;
- }
+ TPML_PCR_SELECTION pcr_selection;
+ tpm2_tpml_pcr_selection_from_mask(1 << TPM_PCR_INDEX_KERNEL_IMAGE,
+ tpmalg,
+ &pcr_selection);
- _cleanup_(Esys_Freep) TPM2B_DIGEST *pcr_policy_digest = NULL;
- rc = sym_Esys_PolicyGetDigest(
- c.esys_context,
- session_handle,
- ESYS_TR_NONE,
- ESYS_TR_NONE,
- ESYS_TR_NONE,
- &pcr_policy_digest);
- if (rc != TSS2_RC_SUCCESS) {
- r = log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
- "Failed to get policy digest from TPM: %s", sym_Tss2_RC_Decode(rc));
- goto finish;
- }
+ TPM2B_DIGEST pcr_values = {
+ .size = p->value_size,
+ };
+ assert(sizeof(pcr_values.buffer) >= p->value_size);
+ memcpy_safe(pcr_values.buffer, p->value, p->value_size);
- session_handle = tpm2_flush_context_verbose(c.esys_context, session_handle);
+ TPM2B_DIGEST pcr_policy_digest;
+ r = tpm2_digest_init(TPM2_ALG_SHA256, &pcr_policy_digest);
+ if (r < 0)
+ return r;
- _cleanup_(EVP_MD_CTX_freep) EVP_MD_CTX* mdctx = NULL;
- mdctx = EVP_MD_CTX_new();
- if (!mdctx) {
- r = log_oom();
- goto finish;
- }
+ r = tpm2_calculate_policy_pcr(&pcr_selection, &pcr_values, 1, &pcr_policy_digest);
+ if (r < 0)
+ return r;
- if (EVP_DigestSignInit(mdctx, NULL, p->md, NULL, privkey) != 1) {
- r = log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
- "Failed to initialize signature context.");
- goto finish;
- }
+ _cleanup_(EVP_MD_CTX_freep) EVP_MD_CTX* mdctx = NULL;
+ mdctx = EVP_MD_CTX_new();
+ if (!mdctx)
+ return log_oom();
- if (EVP_DigestSignUpdate(mdctx, pcr_policy_digest->buffer, pcr_policy_digest->size) != 1) {
- r = log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
- "Failed to sign data.");
- goto finish;
- }
+ if (EVP_DigestSignInit(mdctx, NULL, p->md, NULL, privkey) != 1)
+ return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
+ "Failed to initialize signature context.");
- size_t ss;
- if (EVP_DigestSignFinal(mdctx, NULL, &ss) != 1) {
- r = log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
- "Failed to finalize signature");
- goto finish;
- }
+ if (EVP_DigestSignUpdate(mdctx, pcr_policy_digest.buffer, pcr_policy_digest.size) != 1)
+ return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
+ "Failed to sign data.");
- _cleanup_free_ void *sig = malloc(ss);
- if (!ss) {
- r = log_oom();
- goto finish;
- }
+ size_t ss;
+ if (EVP_DigestSignFinal(mdctx, NULL, &ss) != 1)
+ return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
+ "Failed to finalize signature");
- if (EVP_DigestSignFinal(mdctx, sig, &ss) != 1) {
- r = log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
- "Failed to acquire signature data");
- goto finish;
- }
+ _cleanup_free_ void *sig = malloc(ss);
+ if (!sig)
+ return log_oom();
- _cleanup_free_ void *pubkey_fp = NULL;
- size_t pubkey_fp_size = 0;
- r = pubkey_fingerprint(pubkey, EVP_sha256(), &pubkey_fp, &pubkey_fp_size);
- if (r < 0)
- goto finish;
+ if (EVP_DigestSignFinal(mdctx, sig, &ss) != 1)
+ return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
+ "Failed to acquire signature data");
- _cleanup_(json_variant_unrefp) JsonVariant *bv = NULL, *a = NULL;
+ _cleanup_free_ void *pubkey_fp = NULL;
+ size_t pubkey_fp_size = 0;
+ r = pubkey_fingerprint(pubkey, EVP_sha256(), &pubkey_fp, &pubkey_fp_size);
+ if (r < 0)
+ return r;
- r = tpm2_make_pcr_json_array(UINT64_C(1) << TPM_PCR_INDEX_KERNEL_IMAGE, &a);
- if (r < 0) {
- log_error_errno(r, "Failed to build JSON PCR mask array: %m");
- goto finish;
- }
+ _cleanup_(json_variant_unrefp) JsonVariant *a = NULL;
+ r = tpm2_make_pcr_json_array(UINT64_C(1) << TPM_PCR_INDEX_KERNEL_IMAGE, &a);
+ if (r < 0)
+ return log_error_errno(r, "Failed to build JSON PCR mask array: %m");
- r = json_build(&bv, JSON_BUILD_ARRAY(
- JSON_BUILD_OBJECT(
- JSON_BUILD_PAIR("pcrs", JSON_BUILD_VARIANT(a)), /* PCR mask */
- JSON_BUILD_PAIR("pkfp", JSON_BUILD_HEX(pubkey_fp, pubkey_fp_size)), /* SHA256 fingerprint of public key (DER) used for the signature */
- JSON_BUILD_PAIR("pol", JSON_BUILD_HEX(pcr_policy_digest->buffer, pcr_policy_digest->size)), /* TPM2 policy hash that is signed */
- JSON_BUILD_PAIR("sig", JSON_BUILD_BASE64(sig, ss))))); /* signature data */
- if (r < 0) {
- log_error_errno(r, "Failed to build JSON object: %m");
- goto finish;
- }
+ _cleanup_(json_variant_unrefp) JsonVariant *bv = NULL;
+ r = json_build(&bv, JSON_BUILD_OBJECT(
+ JSON_BUILD_PAIR("pcrs", JSON_BUILD_VARIANT(a)), /* PCR mask */
+ JSON_BUILD_PAIR("pkfp", JSON_BUILD_HEX(pubkey_fp, pubkey_fp_size)), /* SHA256 fingerprint of public key (DER) used for the signature */
+ JSON_BUILD_PAIR("pol", JSON_BUILD_HEX(pcr_policy_digest.buffer, pcr_policy_digest.size)), /* TPM2 policy hash that is signed */
+ JSON_BUILD_PAIR("sig", JSON_BUILD_BASE64(sig, ss)))); /* signature data */
+ if (r < 0)
+ return log_error_errno(r, "Failed to build JSON object: %m");
+
+ _cleanup_(json_variant_unrefp) JsonVariant *av = NULL;
+ av = json_variant_ref(json_variant_by_key(v, p->bank));
+
+ r = json_variant_append_array_nodup(&av, bv);
+ if (r < 0)
+ return log_error_errno(r, "Failed to append JSON object: %m");
- r = json_variant_set_field(&v, p->bank, bv);
- if (r < 0) {
- log_error_errno(r, "Failed to add JSON field: %m");
- goto finish;
+ r = json_variant_set_field(&v, p->bank, av);
+ if (r < 0)
+ return log_error_errno(r, "Failed to add JSON field: %m");
}
- }
- if (!v)
- return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unable to find a single working PCR bank.");
+ /* Return to the original kernel measurement for the next phase calculation */
+ pcr_states_restore(pcr_states, n);
+ }
if (arg_json_format_flags & (JSON_FORMAT_PRETTY|JSON_FORMAT_PRETTY_AUTO))
pager_open(arg_pager_flags);
json_variant_dump(v, arg_json_format_flags, stdout, NULL);
- r = 0;
-finish:
- session_handle = tpm2_flush_context_verbose(c.esys_context, session_handle);
- return r;
+ return 0;
}
static int compare_reported_pcr_nr(uint32_t pcr, const char *varname, const char *description) {
log_warning("Warning: current kernel image does not support measuring itself, the command line or initrd system extension images.\n"
"The PCR measurements seen are unlikely to be valid.");
- r = compare_reported_pcr_nr(TPM_PCR_INDEX_KERNEL_IMAGE, EFI_LOADER_VARIABLE("StubPcrKernelImage"), "kernel image");
+ r = compare_reported_pcr_nr(TPM_PCR_INDEX_KERNEL_IMAGE, EFI_LOADER_VARIABLE(StubPcrKernelImage), "kernel image");
if (r < 0)
return r;
- r = compare_reported_pcr_nr(TPM_PCR_INDEX_KERNEL_PARAMETERS, EFI_LOADER_VARIABLE("StubPcrKernelParameters"), "kernel parameters");
+ r = compare_reported_pcr_nr(TPM_PCR_INDEX_KERNEL_PARAMETERS, EFI_LOADER_VARIABLE(StubPcrKernelParameters), "kernel parameters");
if (r < 0)
return r;
- r = compare_reported_pcr_nr(TPM_PCR_INDEX_INITRD_SYSEXTS, EFI_LOADER_VARIABLE("StubPcrInitRDSysExts"), "initrd system extension images");
+ r = compare_reported_pcr_nr(TPM_PCR_INDEX_INITRD_SYSEXTS, EFI_LOADER_VARIABLE(StubPcrInitRDSysExts), "initrd system extension images");
if (r < 0)
return r;