Sign Up
Log In
Log In
or
Sign Up
Places
All Projects
Status Monitor
Collapse sidebar
home:aplanas:luks2
systemd
cryptenroll-Allow-specifying-PCR-values-in-a-fi...
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
File cryptenroll-Allow-specifying-PCR-values-in-a-file.patch of Package systemd
From 7f5e08a95abbffed0d29648a4013192559a58ad1 Mon Sep 17 00:00:00 2001 From: Fergus Dall <sidereal@google.com> Date: Mon, 30 May 2022 21:18:03 +1000 Subject: [PATCH] cryptenroll: Allow specifying PCR values in a file cryptenroll gains a new option --tpm2-pcr-file which contains PCR values to use instead of the current PCR values in the TPM. This allows a volume to be re-bound to an expected future system state, rather then just the current state. --- man/systemd-cryptenroll.xml | 28 ++++++ src/cryptenroll/cryptenroll-tpm2.c | 146 +++++++++++++++++++++++++++-- src/cryptenroll/cryptenroll-tpm2.h | 4 +- src/cryptenroll/cryptenroll.c | 12 ++- src/partition/repart.c | 15 ++- src/shared/creds-util.c | 2 + src/shared/tpm2-util.c | 40 ++++++-- src/shared/tpm2-util.h | 2 +- 8 files changed, 227 insertions(+), 22 deletions(-) diff --git a/man/systemd-cryptenroll.xml b/man/systemd-cryptenroll.xml index a18b070a3262..1fbfc70054b0 100644 --- a/man/systemd-cryptenroll.xml +++ b/man/systemd-cryptenroll.xml @@ -320,6 +320,34 @@ signatures likely will validate against pre-existing certificates.</para></listitem> </varlistentry> + <varlistentry> + <term><option>--tpm2-pcr-file=</option><replaceable>PATH</replaceable></term> + + <listitem><para>When enrolling a TPM2 device, bind against the PCR values in a file insead of the + current PCR values in the TPM. The file should contain a JSON object like this:</para> + + <programlisting> +{ + "tpm2-pcr-bank" : "sha256", + "tpm2-pcrs" : { + "0" : "0000000000000000000000000000000000000000000000000000000000000000", + "7" : "0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF" + } +} + </programlisting> + + <para>Field <literal>tpm2-pcr-bank</literal> must be <literal>sha1</literal> or + <literal>sha256</literal>.</para> + <para>Field <literal>tpm2-pcrs</literal> must be an object who's keys are the indices of PCR + registers, and who's values are (case insensitive) hex-strings of the desired PCR values.</para> + <para>Note that which PCRs to bind against is controlled by <option>--tpm2-pcrs</option>, not this + file. PCRs with an entry in the file that are not listed in <option>--tpm2-pcrs</option> are + ignored. Passing a file that lacks an entry for a PCR listed in <option>--tpm2-pcrs</option> is an + error. You cannot bind partially against the current PCR values and partially against fixed PCR + values by passing a file that lacks entries for some PCRs.</para> + </listitem> + </varlistentry> + <varlistentry> <term><option>--tpm2-with-pin=</option><replaceable>BOOL</replaceable></term> diff --git a/src/cryptenroll/cryptenroll-tpm2.c b/src/cryptenroll/cryptenroll-tpm2.c index e8c64dd7536c..b7b77672ae40 100644 --- a/src/cryptenroll/cryptenroll-tpm2.c +++ b/src/cryptenroll/cryptenroll-tpm2.c @@ -7,6 +7,8 @@ #include "hexdecoct.h" #include "json.h" #include "memory-util.h" +#include "sha256.h" +#include "stdio-util.h" #include "tpm2-util.h" static int search_policy_hash( @@ -126,11 +128,111 @@ static int get_pin(char **ret_pin_str, TPM2Flags *ret_flags) { return 0; } +static int parse_pcr_file(const char *pcr_file, + uint32_t pcr_mask, + void **ret_pcr_digest, + size_t *ret_pcr_digest_size, + uint16_t *ret_pcr_bank) { + _cleanup_(json_variant_unrefp) JsonVariant *json = NULL; + JsonVariant *v, *w; + unsigned line, column; + _cleanup_free_ void *pcr_digest = NULL, *pcr_value = NULL; + size_t pcr_digest_size = 0, pcr_value_size = 0; + uint16_t pcr_bank; + size_t pcr_value_expected_size = 0; + struct sha256_ctx hash; + int r, i; + + r = json_parse_file(NULL, pcr_file, 0, &json, &line, &column); + if (r < 0) + return log_error_errno( + r, + "Couldn't parse PCR file, error at line %d, col %d", + line, column); + if (!json_variant_is_object(json)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Top level object in PCR file must be an object"); + + v = json_variant_by_key(json, "tpm2-pcr-bank"); + if (!v) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "PCR file must have 'tpm2-pcr-bank' field"); + if (!json_variant_is_string(v)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "'tpm2-pcr-bank' field must be a string"); + + r = tpm2_pcr_bank_from_string(json_variant_string(v)); + if (r < 0) + return log_error_errno(r, "TPM2 PCR bank invalid or not supported: %s", json_variant_string(v)); + pcr_bank = r; + + if (pcr_bank == TPM2_ALG_SHA256) + pcr_value_expected_size = TPM2_SHA256_DIGEST_SIZE; + else if (pcr_bank == TPM2_ALG_SHA1) + pcr_value_expected_size = TPM2_SHA1_DIGEST_SIZE; + else + log_warning("Don't know how long PCR values should be (this is a bug)"); + + v = json_variant_by_key(json, "tpm2-pcrs"); + if (!v) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "PCR file must have 'tpm2-pcrs' field"); + if (!json_variant_is_object(v)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "'tpm2-pcrs' field must be an object"); + + // Note that the algorithm here is the *authentication session* hash, not the PCR hash, and this is + // always SHA256. + sha256_init_ctx(&hash); + pcr_digest = malloc(SHA256_DIGEST_SIZE); + pcr_digest_size = SHA256_DIGEST_SIZE; + if (!pcr_digest) + return log_oom(); + + for (i = 0; i < 24; i++) { + char key[10]; + + if (!((1 << i) & pcr_mask)) + continue; + + xsprintf(key, "%d", i); + + w = json_variant_by_key(v, key); + if (!w) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "PCR %d in mask, but not in file", i); + if (!json_variant_is_string(w)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "PCR %d value is not a string", i); + + r = unhexmem(json_variant_string(w), strlen(json_variant_string(w)), + &pcr_value, &pcr_value_size); + if (r < 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "PCR %d is not a hex string", i); + if (pcr_value_expected_size && pcr_value_size != pcr_value_expected_size) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "PCR %d value should be %ld bytes, is %ld", + i, pcr_value_expected_size, pcr_value_size); + + sha256_process_bytes(pcr_value, pcr_value_size, &hash); + freep(&pcr_value); + } + + sha256_finish_ctx(&hash, pcr_digest); + + *ret_pcr_digest = TAKE_PTR(pcr_digest); + *ret_pcr_digest_size = pcr_digest_size; + *ret_pcr_bank = pcr_bank; + return 0; +} + int enroll_tpm2(struct crypt_device *cd, const void *volume_key, size_t volume_key_size, const char *device, uint32_t pcr_mask, + const char *pcr_file, bool use_pin) { _cleanup_(erase_and_freep) void *secret = NULL, *secret2 = NULL; @@ -138,7 +240,9 @@ int enroll_tpm2(struct crypt_device *cd, _cleanup_(erase_and_freep) char *base64_encoded = NULL; size_t secret_size, secret2_size, blob_size, hash_size; _cleanup_free_ void *blob = NULL, *hash = NULL; - uint16_t pcr_bank, primary_alg; + _cleanup_free_ void *pcr_digest = NULL; + size_t pcr_digest_size = 0; + uint16_t pcr_bank = UINT16_MAX, primary_alg; const char *node; _cleanup_(erase_and_freep) char *pin_str = NULL; int r, keyslot; @@ -157,7 +261,27 @@ int enroll_tpm2(struct crypt_device *cd, return r; } - r = tpm2_seal(device, pcr_mask, pin_str, &secret, &secret_size, &blob, &blob_size, &hash, &hash_size, &pcr_bank, &primary_alg); + if (pcr_file) { + r = parse_pcr_file(pcr_file, pcr_mask, &pcr_digest, &pcr_digest_size, &pcr_bank); + if (r < 0) + return r; + } + + r = tpm2_seal( + device, + pcr_mask, + pcr_digest, + pcr_digest_size, + pcr_bank, + pin_str, + &secret, + &secret_size, + &blob, + &blob_size, + &hash, + &hash_size, + &pcr_bank, + &primary_alg); if (r < 0) return r; @@ -172,14 +296,18 @@ int enroll_tpm2(struct crypt_device *cd, return r; /* return existing keyslot, so that wiping won't kill it */ } - /* Quick verification that everything is in order, we are not in a hurry after all. */ - log_debug("Unsealing for verification..."); - r = tpm2_unseal(device, pcr_mask, pcr_bank, primary_alg, blob, blob_size, hash, hash_size, pin_str, &secret2, &secret2_size); - if (r < 0) - return r; + if (pcr_file == NULL) { + /* Quick verification that everything is in order, we are not in a hurry after all. */ + log_debug("Unsealing for verification..."); + r = tpm2_unseal(device, pcr_mask, pcr_bank, primary_alg, blob, blob_size, hash, hash_size, pin_str, &secret2, &secret2_size); + if (r < 0) + return r; - if (memcmp_nn(secret, secret_size, secret2, secret2_size) != 0) - return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "TPM2 seal/unseal verification failed."); + if (memcmp_nn(secret, secret_size, secret2, secret2_size) != 0) + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "TPM2 seal/unseal verification failed."); + } else { + log_info("Not checking if new token can be unsealed because --tpm2-pcr-file is set."); + } /* let's base64 encode the key to use, for compat with homed (and it's easier to every type it in by keyboard, if that might end up being necessary. */ r = base64mem(secret, secret_size, &base64_encoded); diff --git a/src/cryptenroll/cryptenroll-tpm2.h b/src/cryptenroll/cryptenroll-tpm2.h index 742f49b8d592..96f90d01c3a7 100644 --- a/src/cryptenroll/cryptenroll-tpm2.h +++ b/src/cryptenroll/cryptenroll-tpm2.h @@ -7,9 +7,9 @@ #include "log.h" #if HAVE_TPM2 -int enroll_tpm2(struct crypt_device *cd, const void *volume_key, size_t volume_key_size, const char *device, uint32_t pcr_mask, bool use_pin); +int enroll_tpm2(struct crypt_device *cd, const void *volume_key, size_t volume_key_size, const char *device, uint32_t pcr_mask, const char *pcr_file, bool use_pin); #else -static inline int enroll_tpm2(struct crypt_device *cd, const void *volume_key, size_t volume_key_size, const char *device, uint32_t pcr_mask, bool use_pin) { +static inline int enroll_tpm2(struct crypt_device *cd, const void *volume_key, size_t volume_key_size, const char *device, uint32_t pcr_mask, const char *pcr_file, bool use_pin) { return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "TPM2 key enrollment not supported."); } diff --git a/src/cryptenroll/cryptenroll.c b/src/cryptenroll/cryptenroll.c index 045adf871a16..d30b1000d773 100644 --- a/src/cryptenroll/cryptenroll.c +++ b/src/cryptenroll/cryptenroll.c @@ -32,6 +32,7 @@ static char *arg_pkcs11_token_uri = NULL; static char *arg_fido2_device = NULL; static char *arg_tpm2_device = NULL; static uint32_t arg_tpm2_pcr_mask = UINT32_MAX; +static char *arg_tpm2_pcr_file = NULL; static bool arg_tpm2_pin = false; static char *arg_node = NULL; static int *arg_wipe_slots = NULL; @@ -108,6 +109,8 @@ static int help(void) { " Enroll a TPM2 device\n" " --tpm2-pcrs=PCR1+PCR2+PCR3+…\n" " Specify TPM2 PCRs to seal against\n" + " --tpm2-pcr-file=PATH\n" + " Use PCR values from a file\n" " --tpm2-with-pin=BOOL\n" " Whether to require entering a PIN to unlock the volume\n" " --wipe-slot=SLOT1,SLOT2,…\n" @@ -131,6 +134,7 @@ static int parse_argv(int argc, char *argv[]) { ARG_FIDO2_DEVICE, ARG_TPM2_DEVICE, ARG_TPM2_PCRS, + ARG_TPM2_PCR_FILE, ARG_TPM2_PIN, ARG_WIPE_SLOT, ARG_FIDO2_WITH_PIN, @@ -152,6 +156,7 @@ static int parse_argv(int argc, char *argv[]) { { "fido2-with-user-verification", required_argument, NULL, ARG_FIDO2_WITH_UV }, { "tpm2-device", required_argument, NULL, ARG_TPM2_DEVICE }, { "tpm2-pcrs", required_argument, NULL, ARG_TPM2_PCRS }, + { "tpm2-pcr-file", required_argument, NULL, ARG_TPM2_PCR_FILE }, { "tpm2-with-pin", required_argument, NULL, ARG_TPM2_PIN }, { "wipe-slot", required_argument, NULL, ARG_WIPE_SLOT }, {} @@ -321,6 +326,11 @@ static int parse_argv(int argc, char *argv[]) { break; } + case ARG_TPM2_PCR_FILE: { + arg_tpm2_pcr_file = strdup(optarg); + break; + } + case ARG_TPM2_PIN: { r = parse_boolean_argument("--tpm2-with-pin=", optarg, &arg_tpm2_pin); if (r < 0) @@ -585,7 +595,7 @@ static int run(int argc, char *argv[]) { break; case ENROLL_TPM2: - slot = enroll_tpm2(cd, vk, vks, arg_tpm2_device, arg_tpm2_pcr_mask, arg_tpm2_pin); + slot = enroll_tpm2(cd, vk, vks, arg_tpm2_device, arg_tpm2_pcr_mask, arg_tpm2_pcr_file, arg_tpm2_pin); break; case _ENROLL_TYPE_INVALID: diff --git a/src/partition/repart.c b/src/partition/repart.c index 051242e836c5..f3132bbe8182 100644 --- a/src/partition/repart.c +++ b/src/partition/repart.c @@ -2640,7 +2640,20 @@ static int partition_encrypt( uint16_t pcr_bank, primary_alg; int keyslot; - r = tpm2_seal(arg_tpm2_device, arg_tpm2_pcr_mask, NULL, &secret, &secret_size, &blob, &blob_size, &hash, &hash_size, &pcr_bank, &primary_alg); + r = tpm2_seal( + arg_tpm2_device, + arg_tpm2_pcr_mask, + NULL, 0, + UINT16_MAX, + NULL, + &secret, + &secret_size, + &blob, + &blob_size, + &hash, + &hash_size, + &pcr_bank, + &primary_alg); if (r < 0) return log_error_errno(r, "Failed to seal to TPM2: %m"); diff --git a/src/shared/creds-util.c b/src/shared/creds-util.c index 3c89f527b400..0ec337ea24b6 100644 --- a/src/shared/creds-util.c +++ b/src/shared/creds-util.c @@ -577,6 +577,8 @@ int encrypt_credential_and_warn( if (try_tpm2) { r = tpm2_seal(tpm2_device, tpm2_pcr_mask, + NULL, 0, + UINT16_MAX, NULL, &tpm2_key, &tpm2_key_size, diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c index 84120000aaec..9fb0fbee61f3 100644 --- a/src/shared/tpm2-util.c +++ b/src/shared/tpm2-util.c @@ -666,12 +666,17 @@ static int tpm2_make_pcr_session( ESYS_TR tpmKey, ESYS_TR parent_session, uint32_t pcr_mask, + const TPM2B_DIGEST *pcr_digest, uint16_t pcr_bank, /* If UINT16_MAX, pick best bank automatically, otherwise specify bank explicitly. */ bool use_pin, + bool is_trial, ESYS_TR *ret_session, TPM2B_DIGEST **ret_policy_digest, TPMI_ALG_HASH *ret_pcr_bank) { + if (is_trial && (!pcr_digest || pcr_bank == UINT16_MAX)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Trial sessions must set both the expected PCR digest and the PCR bank"); + static const TPMT_SYM_DEF symmetric = { .algorithm = TPM2_ALG_AES, .keyBits = { @@ -695,8 +700,13 @@ static int tpm2_make_pcr_session( r = tpm2_pcr_mask_good(c, pcr_bank, pcr_mask); if (r < 0) return r; - if (r == 0) - log_notice("Selected TPM2 PCRs are not initialized on this system, most likely due to a firmware issue. PCR policy is effectively not enforced. Proceeding anyway."); + if (r == 0) { + if (!is_trial) { + log_notice("Selected TPM2 PCRs are not initialized on this system, most likely due to a firmware issue. PCR policy is effectively not enforced. Proceeding anyway."); + } else { + log_warning("Selected TPM2 PCRs are not initialized on this system, most likely due to a firmware issue. This may make this keyslot impossible to unlock. Proceeding anyway."); + } + } tpm2_pcr_mask_to_selecion(pcr_mask, pcr_bank, &pcr_selection); } else { @@ -719,7 +729,7 @@ static int tpm2_make_pcr_session( ESYS_TR_NONE, ESYS_TR_NONE, NULL, - TPM2_SE_POLICY, + is_trial ? TPM2_SE_TRIAL : TPM2_SE_POLICY, &symmetric, TPM2_ALG_SHA256, &session); @@ -735,7 +745,7 @@ static int tpm2_make_pcr_session( ESYS_TR_NONE, ESYS_TR_NONE, ESYS_TR_NONE, - NULL, + pcr_digest, &pcr_selection); if (rc != TSS2_RC_SUCCESS) { r = log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), @@ -821,6 +831,9 @@ static void hash_pin(const char *pin, size_t len, uint8_t ret_digest[static SHA2 int tpm2_seal( const char *device, uint32_t pcr_mask, + const void *pcr_digest, + size_t pcr_digest_size, + uint16_t pcr_bank, /* If UINT16_MAX, pick best bank automatically, otherwise specify bank explicitly. */ const char *pin, void **ret_secret, size_t *ret_secret_size, @@ -838,11 +851,11 @@ int tpm2_seal( static const TPML_PCR_SELECTION creation_pcr = {}; _cleanup_(erase_and_freep) void *secret = NULL; _cleanup_free_ void *blob = NULL, *hash = NULL; + _cleanup_free_ TPM2B_DIGEST *pcr_digest_struct = NULL; TPM2B_SENSITIVE_CREATE hmac_sensitive; ESYS_TR primary = ESYS_TR_NONE, session = ESYS_TR_NONE; TPMI_ALG_PUBLIC primary_alg; TPM2B_PUBLIC hmac_template; - TPMI_ALG_HASH pcr_bank; size_t k, blob_size; usec_t start; TSS2_RC rc; @@ -888,8 +901,18 @@ int tpm2_seal( if (r < 0) goto finish; - r = tpm2_make_pcr_session(c.esys_context, primary, session, pcr_mask, UINT16_MAX, !!pin, NULL, - &policy_digest, &pcr_bank); + if (pcr_digest) { + pcr_digest_struct = malloc(pcr_digest_size + 2); + if (!pcr_digest_struct) + return log_oom(); + + pcr_digest_struct->size = pcr_digest_size; + memcpy(&pcr_digest_struct->buffer, pcr_digest, pcr_digest_size); + } + + r = tpm2_make_pcr_session(c.esys_context, primary, session, pcr_mask, + pcr_digest_struct, pcr_bank, !!pin, !!pcr_digest, + NULL, &policy_digest, &pcr_bank); if (r < 0) goto finish; @@ -1101,7 +1124,8 @@ int tpm2_unseal( if (r < 0) goto finish; - r = tpm2_make_pcr_session(c.esys_context, primary, hmac_session, pcr_mask, pcr_bank, !!pin, &session, + r = tpm2_make_pcr_session(c.esys_context, primary, hmac_session, pcr_mask, + NULL, pcr_bank, !!pin, false, &session, &policy_digest, NULL); if (r < 0) goto finish; diff --git a/src/shared/tpm2-util.h b/src/shared/tpm2-util.h index ef19bed4f6cb..5654115a230d 100644 --- a/src/shared/tpm2-util.h +++ b/src/shared/tpm2-util.h @@ -44,7 +44,7 @@ extern TSS2_RC (*sym_Tss2_MU_TPM2B_PUBLIC_Unmarshal)(uint8_t const buffer[], siz int dlopen_tpm2(void); -int tpm2_seal(const char *device, uint32_t pcr_mask, const char *pin, void **ret_secret, size_t *ret_secret_size, void **ret_blob, size_t *ret_blob_size, void **ret_pcr_hash, size_t *ret_pcr_hash_size, uint16_t *ret_pcr_bank, uint16_t *ret_primary_alg); +int tpm2_seal(const char *device, uint32_t pcr_mask, const void *pcr_digest, size_t pcr_digest_size, uint16_t pcr_bank, const char *pin, void **ret_secret, size_t *ret_secret_size, void **ret_blob, size_t *ret_blob_size, void **ret_pcr_hash, size_t *ret_pcr_hash_size, uint16_t *ret_pcr_bank, uint16_t *ret_primary_alg); int tpm2_unseal(const char *device, uint32_t pcr_mask, uint16_t pcr_bank, uint16_t primary_alg, const void *blob, size_t blob_size, const void *pcr_hash, size_t pcr_hash_size, const char *pin, void **ret_secret, size_t *ret_secret_size); #endif
Locations
Projects
Search
Status Monitor
Help
OpenBuildService.org
Documentation
API Documentation
Code of Conduct
Contact
Support
@OBShq
Terms
openSUSE Build Service is sponsored by
The Open Build Service is an
openSUSE project
.
Sign Up
Log In
Places
Places
All Projects
Status Monitor