From 3f9992d82e0bc601bb33ad7b19a1722c4478c542 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 13 Sep 2021 12:33:21 +0200 Subject: [PATCH 1/5] memory-util: replace memeqzero() by a more generic memeqbyte() The new helper can check for any byte, no just zeroes. The old name is then converted into a macro that wraps our new version of the helper. --- src/basic/memory-util.c | 13 ++++++------- src/basic/memory-util.h | 4 +++- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/basic/memory-util.c b/src/basic/memory-util.c index 3338e355f7..2983762117 100644 --- a/src/basic/memory-util.c +++ b/src/basic/memory-util.c @@ -18,26 +18,25 @@ size_t page_size(void) { return pgsz; } -bool memeqzero(const void *data, size_t length) { - /* Does the buffer consist entirely of NULs? +bool memeqbyte(uint8_t byte, const void *data, size_t length) { + /* Does the buffer consist entirely of the same specific byte value? * Copied from https://github.com/systemd/casync/, copied in turn from * https://github.com/rustyrussell/ccan/blob/master/ccan/mem/mem.c#L92, * which is licensed CC-0. */ const uint8_t *p = data; - size_t i; /* Check first 16 bytes manually */ - for (i = 0; i < 16; i++, length--) { + for (size_t i = 0; i < 16; i++, length--) { if (length == 0) return true; - if (p[i]) + if (p[i] != byte) return false; } - /* Now we know first 16 bytes are NUL, memcmp with self. */ - return memcmp(data, p + i, length) == 0; + /* Now we know first 16 bytes match, memcmp() with self. */ + return memcmp(data, p + 16, length) == 0; } #if !HAVE_EXPLICIT_BZERO diff --git a/src/basic/memory-util.h b/src/basic/memory-util.h index e2b543cb8f..9f37431fc1 100644 --- a/src/basic/memory-util.h +++ b/src/basic/memory-util.h @@ -47,7 +47,9 @@ static inline int memcmp_nn(const void *s1, size_t n1, const void *s2, size_t n2 #define zero(x) (memzero(&(x), sizeof(x))) -bool memeqzero(const void *data, size_t length); +bool memeqbyte(uint8_t byte, const void *data, size_t length); + +#define memeqzero(data, length) memeqbyte(0x00, data, length) #define eqzero(x) memeqzero(x, sizeof(x)) From 2b92a67261934d5788613385279157eb1c9fd110 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 13 Sep 2021 10:52:43 +0200 Subject: [PATCH 2/5] tpm2: support RSA primary keys as fallback if TPM2 devices don't support ECC Previously, we hardcoded use of ECC as primary keys, since they are much faster (i.e. saving multiple seconds) to do TPM2 operations with. Alas, not all TPM2 chips appear to support ECC. Bummer. Let's hence add a fallback logic: if we can't create an ECC primary key, use an RSA key, and store that fact away. AFIU the security guarantees should be roughly the same, it's just that RSA primary keys is so much slower to work with than ECC. The primary key algorithm is used is stored in the JSON header of LUKS disks, in a new field. If the field is absent we assume to use ECC, to provide full compatibility with old systemd versions. The primary key algorithm is stored in a new field in the credentials file format (in fact, a previously unused zero space is used), too. Hopefully, this should ensure that TPM2 support will "just work" on more systems. Fixes: #20361 --- src/cryptenroll/cryptenroll-tpm2.c | 8 +- .../cryptsetup-token-systemd-tpm2.c | 30 ++- src/cryptsetup/cryptsetup-tokens/luks2-tpm2.c | 21 ++- src/cryptsetup/cryptsetup-tokens/luks2-tpm2.h | 2 + src/cryptsetup/cryptsetup-tpm2.c | 28 ++- src/cryptsetup/cryptsetup-tpm2.h | 4 + src/cryptsetup/cryptsetup.c | 5 +- src/partition/repart.c | 6 +- src/shared/creds-util.c | 21 ++- src/shared/tpm2-util.c | 171 +++++++++++++----- src/shared/tpm2-util.h | 28 ++- 11 files changed, 248 insertions(+), 76 deletions(-) diff --git a/src/cryptenroll/cryptenroll-tpm2.c b/src/cryptenroll/cryptenroll-tpm2.c index 697b4c2335..23deeed272 100644 --- a/src/cryptenroll/cryptenroll-tpm2.c +++ b/src/cryptenroll/cryptenroll-tpm2.c @@ -65,7 +65,7 @@ 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; + uint16_t pcr_bank, primary_alg; const char *node; int r, keyslot; @@ -76,7 +76,7 @@ int enroll_tpm2(struct crypt_device *cd, assert_se(node = crypt_get_device_name(cd)); - r = tpm2_seal(device, pcr_mask, &secret, &secret_size, &blob, &blob_size, &hash, &hash_size, &pcr_bank); + r = tpm2_seal(device, pcr_mask, &secret, &secret_size, &blob, &blob_size, &hash, &hash_size, &pcr_bank, &primary_alg); if (r < 0) return r; @@ -93,7 +93,7 @@ int enroll_tpm2(struct crypt_device *cd, /* 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, blob, blob_size, hash, hash_size, &secret2, &secret2_size); + r = tpm2_unseal(device, pcr_mask, pcr_bank, primary_alg, blob, blob_size, hash, hash_size, &secret2, &secret2_size); if (r < 0) return r; @@ -119,7 +119,7 @@ int enroll_tpm2(struct crypt_device *cd, if (keyslot < 0) return log_error_errno(keyslot, "Failed to add new TPM2 key to %s: %m", node); - r = tpm2_make_luks2_json(keyslot, pcr_mask, pcr_bank, blob, blob_size, hash, hash_size, &v); + r = tpm2_make_luks2_json(keyslot, pcr_mask, pcr_bank, primary_alg, blob, blob_size, hash, hash_size, &v); if (r < 0) return log_error_errno(r, "Failed to prepare TPM2 JSON token object: %m"); diff --git a/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-tpm2.c b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-tpm2.c index 0c9d566e0d..5471cb35b6 100644 --- a/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-tpm2.c +++ b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-tpm2.c @@ -57,7 +57,7 @@ _public_ int cryptsetup_token_open( const char *json; size_t blob_size, policy_hash_size, decrypted_key_size; uint32_t pcr_mask; - uint16_t pcr_bank; + uint16_t pcr_bank, primary_alg; systemd_tpm2_plugin_params params = { .search_pcr_mask = UINT32_MAX }; @@ -78,7 +78,7 @@ _public_ int cryptsetup_token_open( if (usrptr) params = *(systemd_tpm2_plugin_params *)usrptr; - r = parse_luks2_tpm2_data(json, params.search_pcr_mask, &pcr_mask, &pcr_bank, &base64_blob, &hex_policy_hash); + r = parse_luks2_tpm2_data(json, params.search_pcr_mask, &pcr_mask, &pcr_bank, &primary_alg, &base64_blob, &hex_policy_hash); if (r < 0) return log_debug_open_error(cd, r); @@ -95,6 +95,7 @@ _public_ int cryptsetup_token_open( r = acquire_luks2_key( pcr_mask, pcr_bank, + primary_alg, params.device, blob, blob_size, @@ -135,7 +136,7 @@ _public_ void cryptsetup_token_dump( int r; uint32_t pcr_mask; - uint16_t pcr_bank; + uint16_t pcr_bank, primary_alg; size_t decoded_blob_size; _cleanup_free_ char *base64_blob = NULL, *hex_policy_hash = NULL, *pcrs_str = NULL, *blob_str = NULL, *policy_hash_str = NULL; @@ -143,7 +144,7 @@ _public_ void cryptsetup_token_dump( assert(json); - r = parse_luks2_tpm2_data(json, UINT32_MAX, &pcr_mask, &pcr_bank, &base64_blob, &hex_policy_hash); + r = parse_luks2_tpm2_data(json, UINT32_MAX, &pcr_mask, &pcr_bank, &primary_alg, &base64_blob, &hex_policy_hash); if (r < 0) return (void) crypt_log_debug_errno(cd, r, "Failed to parse " TOKEN_NAME " metadata: %m."); @@ -167,6 +168,7 @@ _public_ void cryptsetup_token_dump( crypt_log(cd, "\ttpm2-pcrs: %s\n", strna(pcrs_str)); crypt_log(cd, "\ttpm2-bank: %s\n", strna(tpm2_pcr_bank_to_string(pcr_bank))); + crypt_log(cd, "\ttpm2-primary-alg: %s\n", strna(tpm2_primary_alg_to_string(primary_alg))); crypt_log(cd, "\ttpm2-blob: %s\n", blob_str); crypt_log(cd, "\ttpm2-policy-hash:" CRYPT_DUMP_LINE_SEP "%s\n", policy_hash_str); } @@ -212,7 +214,8 @@ _public_ int cryptsetup_token_validate( } } - /* The bank field is optional, since it was added in systemd 250 only. Before the bank was hardcoded to SHA256 */ + /* The bank field is optional, since it was added in systemd 250 only. Before the bank was hardcoded + * to SHA256. */ w = json_variant_by_key(v, "tpm2-pcr-bank"); if (w) { /* The PCR bank field is optional */ @@ -228,6 +231,23 @@ _public_ int cryptsetup_token_validate( } } + /* The primary key algorithm field is optional, since it was also added in systemd 250 only. Before + * the algorithm was hardcoded to ECC. */ + w = json_variant_by_key(v, "tpm2-primary-alg"); + if (w) { + /* The primary key algorithm is optional */ + + if (!json_variant_is_string(w)) { + crypt_log_debug(cd, "TPM2 primary key algorithm is not a string."); + return 1; + } + + if (tpm2_primary_alg_from_string(json_variant_string(w)) < 0) { + crypt_log_debug(cd, "TPM2 primary key algorithm invalid or not supported: %s", json_variant_string(w)); + return 1; + } + } + w = json_variant_by_key(v, "tpm2-blob"); if (!w || !json_variant_is_string(w)) { crypt_log_debug(cd, "TPM2 token data lacks 'tpm2-blob' field."); diff --git a/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.c b/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.c index a5571f31f6..4167275317 100644 --- a/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.c +++ b/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.c @@ -11,6 +11,7 @@ int acquire_luks2_key( uint32_t pcr_mask, uint16_t pcr_bank, + uint16_t primary_alg, const char *device, const void *key_data, size_t key_data_size, @@ -38,6 +39,7 @@ int acquire_luks2_key( return tpm2_unseal( device, pcr_mask, pcr_bank, + primary_alg, key_data, key_data_size, policy_hash, policy_hash_size, ret_decrypted_key, ret_decrypted_key_size); @@ -49,19 +51,21 @@ int parse_luks2_tpm2_data( uint32_t search_pcr_mask, uint32_t *ret_pcr_mask, uint16_t *ret_pcr_bank, + uint16_t *ret_primary_alg, char **ret_base64_blob, char **ret_hex_policy_hash) { int r; JsonVariant *w, *e; uint32_t pcr_mask = 0; - uint16_t pcr_bank = UINT16_MAX; + uint16_t pcr_bank = UINT16_MAX, primary_alg = TPM2_ALG_ECC; _cleanup_free_ char *base64_blob = NULL, *hex_policy_hash = NULL; _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; assert(json); assert(ret_pcr_mask); assert(ret_pcr_bank); + assert(ret_primary_alg); assert(ret_base64_blob); assert(ret_hex_policy_hash); @@ -104,6 +108,20 @@ int parse_luks2_tpm2_data( pcr_bank = r; } + w = json_variant_by_key(v, "tpm2-primary-alg"); + if (w) { + /* The primary key algorithm is optional */ + + if (!json_variant_is_string(w)) + return -EINVAL; + + r = tpm2_primary_alg_from_string(json_variant_string(w)); + if (r < 0) + return r; + + primary_alg = r; + } + w = json_variant_by_key(v, "tpm2-blob"); if (!w || !json_variant_is_string(w)) return -EINVAL; @@ -122,6 +140,7 @@ int parse_luks2_tpm2_data( *ret_pcr_mask = pcr_mask; *ret_pcr_bank = pcr_bank; + *ret_primary_alg = primary_alg; *ret_base64_blob = TAKE_PTR(base64_blob); *ret_hex_policy_hash = TAKE_PTR(hex_policy_hash); diff --git a/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.h b/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.h index 1a20f2cc1f..0c93ea82cc 100644 --- a/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.h +++ b/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.h @@ -7,6 +7,7 @@ struct crypt_device; int acquire_luks2_key( uint32_t pcr_mask, uint16_t pcr_bank, + uint16_t primary_alg, const char *device, const void *key_data, size_t key_data_size, @@ -20,5 +21,6 @@ int parse_luks2_tpm2_data( uint32_t search_pcr_mask, uint32_t *ret_pcr_mask, uint16_t *ret_pcr_bank, + uint16_t *ret_primary_alg, char **ret_base64_blob, char **ret_hex_policy_hash); diff --git a/src/cryptsetup/cryptsetup-tpm2.c b/src/cryptsetup/cryptsetup-tpm2.c index 8da1880a35..4d95dacca5 100644 --- a/src/cryptsetup/cryptsetup-tpm2.c +++ b/src/cryptsetup/cryptsetup-tpm2.c @@ -14,6 +14,7 @@ int acquire_tpm2_key( const char *device, uint32_t pcr_mask, uint16_t pcr_bank, + uint16_t primary_alg, const char *key_file, size_t key_file_size, uint64_t key_file_offset, @@ -63,7 +64,7 @@ int acquire_tpm2_key( blob = loaded_blob; } - return tpm2_unseal(device, pcr_mask, pcr_bank, blob, blob_size, policy_hash, policy_hash_size, ret_decrypted_key, ret_decrypted_key_size); + return tpm2_unseal(device, pcr_mask, pcr_bank, primary_alg, blob, blob_size, policy_hash, policy_hash_size, ret_decrypted_key, ret_decrypted_key_size); } int find_tpm2_auto_data( @@ -72,6 +73,7 @@ int find_tpm2_auto_data( int start_token, uint32_t *ret_pcr_mask, uint16_t *ret_pcr_bank, + uint16_t *ret_primary_alg, void **ret_blob, size_t *ret_blob_size, void **ret_policy_hash, @@ -84,6 +86,7 @@ int find_tpm2_auto_data( int r, keyslot = -1, token = -1; uint32_t pcr_mask = 0; uint16_t pcr_bank = UINT16_MAX; /* default: pick automatically */ + uint16_t primary_alg = TPM2_ALG_ECC; /* ECC was the only supported algorithm in systemd < 250, use that as implied default, for compatibility */ assert(cd); @@ -122,8 +125,11 @@ int find_tpm2_auto_data( search_pcr_mask != pcr_mask) /* PCR mask doesn't match what is configured, ignore this entry */ continue; - /* The bank field is optional, since it was added in systemd 250 only. Before the bank was hardcoded to SHA256 */ assert(pcr_bank == UINT16_MAX); + assert(primary_alg == TPM2_ALG_ECC); + + /* The bank field is optional, since it was added in systemd 250 only. Before the bank was + * hardcoded to SHA256. */ w = json_variant_by_key(v, "tpm2-pcr-bank"); if (w) { /* The PCR bank field is optional */ @@ -139,6 +145,23 @@ int find_tpm2_auto_data( pcr_bank = r; } + /* The primary key algorithm field is optional, since it was also added in systemd 250 + * only. Before the algorithm was hardcoded to ECC. */ + w = json_variant_by_key(v, "tpm2-primary-alg"); + if (w) { + /* The primary key algorithm is optional */ + + if (!json_variant_is_string(w)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "TPM2 primary key algorithm is not a string."); + + r = tpm2_primary_alg_from_string(json_variant_string(w)); + if (r < 0) + return log_error_errno(r, "TPM2 primary key algorithm invalid or not supported: %s", json_variant_string(w)); + + primary_alg = r; + } + assert(!blob); w = json_variant_by_key(v, "tpm2-blob"); if (!w || !json_variant_is_string(w)) @@ -184,6 +207,7 @@ int find_tpm2_auto_data( *ret_keyslot = keyslot; *ret_token = token; *ret_pcr_bank = pcr_bank; + *ret_primary_alg = primary_alg; return 0; } diff --git a/src/cryptsetup/cryptsetup-tpm2.h b/src/cryptsetup/cryptsetup-tpm2.h index a82ecb4594..bd04620462 100644 --- a/src/cryptsetup/cryptsetup-tpm2.h +++ b/src/cryptsetup/cryptsetup-tpm2.h @@ -14,6 +14,7 @@ int acquire_tpm2_key( const char *device, uint32_t pcr_mask, uint16_t pcr_bank, + uint16_t primary_alg, const char *key_file, size_t key_file_size, uint64_t key_file_offset, @@ -30,6 +31,7 @@ int find_tpm2_auto_data( int start_token, uint32_t *ret_pcr_mask, uint16_t *ret_pcr_bank, + uint16_t *ret_primary_alg, void **ret_blob, size_t *ret_blob_size, void **ret_policy_hash, @@ -44,6 +46,7 @@ static inline int acquire_tpm2_key( const char *device, uint32_t pcr_mask, uint16_t pcr_bank, + uint16_t primary_alg, const char *key_file, size_t key_file_size, uint64_t key_file_offset, @@ -64,6 +67,7 @@ static inline int find_tpm2_auto_data( int start_token, uint32_t *ret_pcr_mask, uint16_t *ret_pcr_bank, + uint16_t *ret_primary_alg, void **ret_blob, size_t *ret_blob_size, void **ret_policy_hash, diff --git a/src/cryptsetup/cryptsetup.c b/src/cryptsetup/cryptsetup.c index fdd57def86..ceaf10e647 100644 --- a/src/cryptsetup/cryptsetup.c +++ b/src/cryptsetup/cryptsetup.c @@ -1248,6 +1248,7 @@ static int attach_luks_or_plain_or_bitlk_by_tpm2( arg_tpm2_device, arg_tpm2_pcr_mask == UINT32_MAX ? TPM2_PCR_MASK_DEFAULT : arg_tpm2_pcr_mask, UINT16_MAX, + 0, key_file, arg_keyfile_size, arg_keyfile_offset, key_data, key_data_size, NULL, 0, /* we don't know the policy hash */ @@ -1284,7 +1285,7 @@ static int attach_luks_or_plain_or_bitlk_by_tpm2( for (;;) { uint32_t pcr_mask; - uint16_t pcr_bank; + uint16_t pcr_bank, primary_alg; r = find_tpm2_auto_data( cd, @@ -1292,6 +1293,7 @@ static int attach_luks_or_plain_or_bitlk_by_tpm2( token, /* search for the token with this index, or any later index than this */ &pcr_mask, &pcr_bank, + &primary_alg, &blob, &blob_size, &policy_hash, &policy_hash_size, &keyslot, @@ -1314,6 +1316,7 @@ static int attach_luks_or_plain_or_bitlk_by_tpm2( arg_tpm2_device, pcr_mask, pcr_bank, + primary_alg, NULL, 0, 0, /* no key file */ blob, blob_size, policy_hash, policy_hash_size, diff --git a/src/partition/repart.c b/src/partition/repart.c index 926dbb2ae4..bf6bac07c5 100644 --- a/src/partition/repart.c +++ b/src/partition/repart.c @@ -2618,10 +2618,10 @@ static int partition_encrypt( _cleanup_(erase_and_freep) void *secret = NULL; _cleanup_free_ void *blob = NULL, *hash = NULL; size_t secret_size, blob_size, hash_size; - uint16_t pcr_bank; + uint16_t pcr_bank, primary_alg; int keyslot; - r = tpm2_seal(arg_tpm2_device, arg_tpm2_pcr_mask, &secret, &secret_size, &blob, &blob_size, &hash, &hash_size, &pcr_bank); + r = tpm2_seal(arg_tpm2_device, arg_tpm2_pcr_mask, &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"); @@ -2643,7 +2643,7 @@ static int partition_encrypt( if (keyslot < 0) return log_error_errno(keyslot, "Failed to add new TPM2 key to %s: %m", node); - r = tpm2_make_luks2_json(keyslot, arg_tpm2_pcr_mask, pcr_bank, blob, blob_size, hash, hash_size, &v); + r = tpm2_make_luks2_json(keyslot, arg_tpm2_pcr_mask, pcr_bank, primary_alg, blob, blob_size, hash, hash_size, &v); if (r < 0) return log_error_errno(r, "Failed to prepare TPM2 JSON token object: %m"); diff --git a/src/shared/creds-util.c b/src/shared/creds-util.c index 9980cd93a9..d1ca3778b7 100644 --- a/src/shared/creds-util.c +++ b/src/shared/creds-util.c @@ -371,10 +371,10 @@ struct _packed_ encrypted_credential_header { }; struct _packed_ tpm2_credential_header { - le64_t pcr_mask; /* Note that the spec for PC Clients only mandates 24 PCRs, and that's what systems - * generally have. But keep the door open for more. */ - le16_t pcr_bank; /* For now, either TPM2_ALG_SHA256 or TPM2_ALG_SHA1 */ - le16_t _zero; /* Filler to maintain 32bit alignment */ + le64_t pcr_mask; /* Note that the spec for PC Clients only mandates 24 PCRs, and that's what systems + * generally have. But keep the door open for more. */ + le16_t pcr_bank; /* For now, either TPM2_ALG_SHA256 or TPM2_ALG_SHA1 */ + le16_t primary_alg; /* Primary key algorithm (either TPM2_ALG_RSA or TPM2_ALG_ECC for now) */ le32_t blob_size; le32_t policy_hash_size; uint8_t policy_hash_and_blob[]; @@ -441,10 +441,10 @@ int encrypt_credential_and_warn( size_t host_key_size = 0, tpm2_key_size = 0, tpm2_blob_size = 0, tpm2_policy_hash_size = 0, output_size, p, ml; _cleanup_free_ void *tpm2_blob = NULL, *tpm2_policy_hash = NULL, *iv = NULL, *output = NULL; _cleanup_free_ struct metadata_credential_header *m = NULL; + uint16_t tpm2_pcr_bank = 0, tpm2_primary_alg = 0; struct encrypted_credential_header *h; int ksz, bsz, ivsz, tsz, added, r; uint8_t md[SHA256_DIGEST_LENGTH]; - uint16_t tpm2_pcr_bank = 0; const EVP_CIPHER *cc; #if HAVE_TPM2 bool try_tpm2 = false; @@ -512,7 +512,8 @@ int encrypt_credential_and_warn( &tpm2_blob_size, &tpm2_policy_hash, &tpm2_policy_hash_size, - &tpm2_pcr_bank); + &tpm2_pcr_bank, + &tpm2_primary_alg); if (r < 0) { if (!sd_id128_is_null(with_key)) return r; @@ -606,6 +607,7 @@ int encrypt_credential_and_warn( t = (struct tpm2_credential_header*) ((uint8_t*) output + p); t->pcr_mask = htole64(tpm2_pcr_mask); t->pcr_bank = htole16(tpm2_pcr_bank); + t->primary_alg = htole16(tpm2_primary_alg); t->blob_size = htole32(tpm2_blob_size); t->policy_hash_size = htole32(tpm2_policy_hash_size); memcpy(t->policy_hash_and_blob, tpm2_blob, tpm2_blob_size); @@ -747,10 +749,10 @@ int decrypt_credential_and_warn( if (le64toh(t->pcr_mask) >= (UINT64_C(1) << TPM2_PCRS_MAX)) return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "TPM2 PCR mask out of range."); - if (!tpm2_pcr_bank_supported(le16toh(t->pcr_bank))) + if (!tpm2_pcr_bank_to_string(le16toh(t->pcr_bank))) return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "TPM2 PCR bank invalid or not supported"); - if (le16toh(t->_zero) != 0) - return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "TPM2 padding space not zero."); + if (!tpm2_primary_alg_to_string(le16toh(t->primary_alg))) + return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "TPM2 primary key algorithm invalid or not supported."); if (le32toh(t->blob_size) > CREDENTIAL_FIELD_SIZE_MAX) return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Unexpected TPM2 blob size."); if (le32toh(t->policy_hash_size) > CREDENTIAL_FIELD_SIZE_MAX) @@ -768,6 +770,7 @@ int decrypt_credential_and_warn( r = tpm2_unseal(tpm2_device, le64toh(t->pcr_mask), le16toh(t->pcr_bank), + le16toh(t->primary_alg), t->policy_hash_and_blob, le32toh(t->blob_size), t->policy_hash_and_blob + le32toh(t->blob_size), diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c index f29686e994..3cdb3ed7b0 100644 --- a/src/shared/tpm2-util.c +++ b/src/shared/tpm2-util.c @@ -257,10 +257,12 @@ static int tpm2_credit_random(ESYS_CONTEXT *c) { static int tpm2_make_primary( ESYS_CONTEXT *c, - ESYS_TR *ret_primary) { + ESYS_TR *ret_primary, + TPMI_ALG_PUBLIC alg, + TPMI_ALG_PUBLIC *ret_alg) { static const TPM2B_SENSITIVE_CREATE primary_sensitive = {}; - static const TPM2B_PUBLIC primary_template = { + static const TPM2B_PUBLIC primary_template_ecc = { .size = sizeof(TPMT_PUBLIC), .publicArea = { .type = TPM2_ALG_ECC, @@ -280,35 +282,102 @@ static int tpm2_make_primary( }, }, }; + static const TPM2B_PUBLIC primary_template_rsa = { + .size = sizeof(TPMT_PUBLIC), + .publicArea = { + .type = TPM2_ALG_RSA, + .nameAlg = TPM2_ALG_SHA256, + .objectAttributes = TPMA_OBJECT_RESTRICTED|TPMA_OBJECT_DECRYPT|TPMA_OBJECT_FIXEDTPM|TPMA_OBJECT_FIXEDPARENT|TPMA_OBJECT_SENSITIVEDATAORIGIN|TPMA_OBJECT_USERWITHAUTH, + .parameters = { + .rsaDetail = { + .symmetric = { + .algorithm = TPM2_ALG_AES, + .keyBits.aes = 128, + .mode.aes = TPM2_ALG_CFB, + }, + .scheme.scheme = TPM2_ALG_NULL, + .keyBits = 2048, + }, + }, + }, + }; + static const TPML_PCR_SELECTION creation_pcr = {}; ESYS_TR primary = ESYS_TR_NONE; TSS2_RC rc; + usec_t ts; log_debug("Creating primary key on TPM."); - rc = sym_Esys_CreatePrimary( - c, - ESYS_TR_RH_OWNER, - ESYS_TR_PASSWORD, - ESYS_TR_NONE, - ESYS_TR_NONE, - &primary_sensitive, - &primary_template, - NULL, - &creation_pcr, - &primary, - NULL, - NULL, - NULL, - NULL); + /* So apparently not all TPM2 devices support ECC. ECC is generally preferably, because it's so much + * faster, noticeably so (~10s vs. ~240ms on my system). Hence, unless explicitly configured let's + * try to use ECC first, and if that does not work, let's fall back to RSA. */ - if (rc != TSS2_RC_SUCCESS) - return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), - "Failed to generate primary key in TPM: %s", sym_Tss2_RC_Decode(rc)); + ts = now(CLOCK_MONOTONIC); - log_debug("Successfully created primary key on TPM."); + if (IN_SET(alg, 0, TPM2_ALG_ECC)) { + rc = sym_Esys_CreatePrimary( + c, + ESYS_TR_RH_OWNER, + ESYS_TR_PASSWORD, + ESYS_TR_NONE, + ESYS_TR_NONE, + &primary_sensitive, + &primary_template_ecc, + NULL, + &creation_pcr, + &primary, + NULL, + NULL, + NULL, + NULL); + + if (rc != TSS2_RC_SUCCESS) { + if (alg != 0) + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Failed to generate ECC primary key in TPM: %s", sym_Tss2_RC_Decode(rc)); + + log_debug("Failed to generate ECC primary key in TPM, trying RSA: %s", sym_Tss2_RC_Decode(rc)); + } else { + log_debug("Successfully created ECC primary key on TPM."); + alg = TPM2_ALG_ECC; + } + } + + if (IN_SET(alg, 0, TPM2_ALG_RSA)) { + rc = sym_Esys_CreatePrimary( + c, + ESYS_TR_RH_OWNER, + ESYS_TR_PASSWORD, + ESYS_TR_NONE, + ESYS_TR_NONE, + &primary_sensitive, + &primary_template_rsa, + NULL, + &creation_pcr, + &primary, + NULL, + NULL, + NULL, + NULL); + if (rc != TSS2_RC_SUCCESS) + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Failed to generate RSA primary key in TPM: %s", sym_Tss2_RC_Decode(rc)); + else if (alg == 0) { + log_notice("TPM2 chip apparently does not support ECC primary keys, falling back to RSA. " + "This likely means TPM2 operations will be relatively slow, please be patient."); + alg = TPM2_ALG_RSA; + } + + log_debug("Successfully created RSA primary key on TPM."); + } + + log_debug("Generating primary key on TPM2 took %s.", FORMAT_TIMESPAN(now(CLOCK_MONOTONIC) - ts, USEC_PER_MSEC)); *ret_primary = primary; + if (ret_alg) + *ret_alg = alg; + return 0; } @@ -525,7 +594,8 @@ int tpm2_seal( size_t *ret_blob_size, void **ret_pcr_hash, size_t *ret_pcr_hash_size, - uint16_t *ret_pcr_bank) { + uint16_t *ret_pcr_bank, + uint16_t *ret_primary_alg) { _cleanup_(tpm2_context_destroy) struct tpm2_context c = {}; _cleanup_(Esys_Freep) TPM2B_DIGEST *policy_digest = NULL; @@ -536,6 +606,7 @@ int tpm2_seal( _cleanup_free_ void *blob = NULL, *hash = NULL; TPM2B_SENSITIVE_CREATE hmac_sensitive; ESYS_TR primary = ESYS_TR_NONE; + TPMI_ALG_PUBLIC primary_alg; TPM2B_PUBLIC hmac_template; TPMI_ALG_HASH pcr_bank; size_t k, blob_size; @@ -555,13 +626,14 @@ int tpm2_seal( /* So here's what we do here: we connect to the TPM2 chip. It persistently contains a "seed" key that * is randomized when the TPM2 is first initialized or reset and remains stable across boots. We - * generate a "primary" key pair derived from that (RSA). Given the seed remains fixed this will - * result in the same key pair whenever we specify the exact same parameters for it. We then create a - * PCR-bound policy session, which calculates a hash on the current PCR values of the indexes we - * specify. We then generate a randomized key on the host (which is the key we actually enroll in the - * LUKS2 keyslots), which we upload into the TPM2, where it is encrypted with the "primary" key, - * taking the PCR policy session into account. We then download the encrypted key from the TPM2 - * ("sealing") and marshall it into binary form, which is ultimately placed in the LUKS2 JSON header. + * generate a "primary" key pair derived from that (ECC if possible, RSA as fallback). Given the seed + * remains fixed this will result in the same key pair whenever we specify the exact same parameters + * for it. We then create a PCR-bound policy session, which calculates a hash on the current PCR + * values of the indexes we specify. We then generate a randomized key on the host (which is the key + * we actually enroll in the LUKS2 keyslots), which we upload into the TPM2, where it is encrypted + * with the "primary" key, taking the PCR policy session into account. We then download the encrypted + * key from the TPM2 ("sealing") and marshall it into binary form, which is ultimately placed in the + * LUKS2 JSON header. * * The TPM2 "seed" key and "primary" keys never leave the TPM2 chip (and cannot be extracted at * all). The random key we enroll in LUKS2 we generate on the host using the Linux random device. It @@ -574,7 +646,7 @@ int tpm2_seal( if (r < 0) return r; - r = tpm2_make_primary(c.esys_context, &primary); + r = tpm2_make_primary(c.esys_context, &primary, 0, &primary_alg); if (r < 0) return r; @@ -701,6 +773,7 @@ int tpm2_seal( *ret_pcr_hash = TAKE_PTR(hash); *ret_pcr_hash_size = policy_digest->size; *ret_pcr_bank = pcr_bank; + *ret_primary_alg = primary_alg; r = 0; @@ -713,6 +786,7 @@ 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 *known_policy_hash, @@ -783,7 +857,7 @@ int tpm2_unseal( return log_error_errno(SYNTHETIC_ERRNO(EPERM), "Current policy digest does not match stored policy digest, cancelling TPM2 authentication attempt."); - r = tpm2_make_primary(c.esys_context, &primary); + r = tpm2_make_primary(c.esys_context, &primary, primary_alg, NULL); if (r < 0) return r; @@ -1012,6 +1086,7 @@ int tpm2_make_luks2_json( int keyslot, uint32_t pcr_mask, uint16_t pcr_bank, + uint16_t primary_alg, const void *blob, size_t blob_size, const void *policy_hash, @@ -1055,6 +1130,7 @@ int tpm2_make_luks2_json( JSON_BUILD_PAIR("tpm2-blob", JSON_BUILD_BASE64(blob, blob_size)), JSON_BUILD_PAIR("tpm2-pcrs", JSON_BUILD_VARIANT(a)), JSON_BUILD_PAIR_CONDITION(!!tpm2_pcr_bank_to_string(pcr_bank), "tpm2-pcr-bank", JSON_BUILD_STRING(tpm2_pcr_bank_to_string(pcr_bank))), + JSON_BUILD_PAIR_CONDITION(!!tpm2_primary_alg_to_string(primary_alg), "tpm2-primary-alg", JSON_BUILD_STRING(tpm2_primary_alg_to_string(primary_alg))), JSON_BUILD_PAIR("tpm2-policy-hash", JSON_BUILD_HEX(policy_hash, policy_hash_size)))); if (r < 0) return r; @@ -1065,24 +1141,9 @@ int tpm2_make_luks2_json( return keyslot; } -/* We want the helpers below to work also if TPM2 libs are not available, hence define these two defines if - * they are missing. */ -#ifndef TPM2_ALG_SHA256 -#define TPM2_ALG_SHA256 0xB -#endif - -#ifndef TPM2_ALG_SHA1 -#define TPM2_ALG_SHA1 0x4 -#endif - -int tpm2_pcr_bank_supported(uint16_t bank) { +const char *tpm2_pcr_bank_to_string(uint16_t bank) { /* For now, let's officially only support these two. We can extend this later on, should the need * arise. */ - return IN_SET(bank, TPM2_ALG_SHA256, TPM2_ALG_SHA1); -} - -const char *tpm2_pcr_bank_to_string(uint16_t bank) { - /* Similar here, only support the two for now, we can always extend this later. */ if (bank == TPM2_ALG_SHA256) return "sha256"; if (bank == TPM2_ALG_SHA1) @@ -1097,3 +1158,19 @@ int tpm2_pcr_bank_from_string(const char *bank) { return TPM2_ALG_SHA1; return -EINVAL; } + +const char *tpm2_primary_alg_to_string(uint16_t alg) { + if (alg == TPM2_ALG_ECC) + return "ecc"; + if (alg == TPM2_ALG_RSA) + return "rsa"; + return NULL; +} + +int tpm2_primary_alg_from_string(const char *alg) { + if (streq_ptr(alg, "ecc")) + return TPM2_ALG_ECC; + if (streq_ptr(alg, "rsa")) + return TPM2_ALG_RSA; + return -EINVAL; +} diff --git a/src/shared/tpm2-util.h b/src/shared/tpm2-util.h index 8032afe7b0..b8f12760d0 100644 --- a/src/shared/tpm2-util.h +++ b/src/shared/tpm2-util.h @@ -34,8 +34,8 @@ 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, 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); -int tpm2_unseal(const char *device, uint32_t pcr_mask, uint16_t pcr_bank, const void *blob, size_t blob_size, const void *pcr_hash, size_t pcr_hash_size, void **ret_secret, size_t *ret_secret_size); +int tpm2_seal(const char *device, uint32_t pcr_mask, 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, void **ret_secret, size_t *ret_secret_size); #endif @@ -44,17 +44,37 @@ int tpm2_find_device_auto(int log_level, char **ret); int tpm2_parse_pcrs(const char *s, uint32_t *ret); -int tpm2_make_luks2_json(int keyslot, uint32_t pcr_mask, uint16_t pcr_bank, const void *blob, size_t blob_size, const void *policy_hash, size_t policy_hash_size, JsonVariant **ret); +int tpm2_make_luks2_json(int keyslot, uint32_t pcr_mask, uint16_t pcr_bank, uint16_t primary_alg, const void *blob, size_t blob_size, const void *policy_hash, size_t policy_hash_size, JsonVariant **ret); #define TPM2_PCRS_MAX 24 /* Default to PCR 7 only */ #define TPM2_PCR_MASK_DEFAULT (UINT32_C(1) << 7) -int tpm2_pcr_bank_supported(uint16_t bank); +/* We want the helpers below to work also if TPM2 libs are not available, hence define these four defines if + * they are missing. */ +#ifndef TPM2_ALG_SHA256 +#define TPM2_ALG_SHA256 0xB +#endif + +#ifndef TPM2_ALG_SHA1 +#define TPM2_ALG_SHA1 0x4 +#endif + +#ifndef TPM2_ALG_ECC +#define TPM2_ALG_ECC 0x23 +#endif + +#ifndef TPM2_ALG_RSA +#define TPM2_ALG_RSA 0x1 +#endif + const char *tpm2_pcr_bank_to_string(uint16_t bank); int tpm2_pcr_bank_from_string(const char *bank); +const char *tpm2_primary_alg_to_string(uint16_t bank); +int tpm2_primary_alg_from_string(const char *alg); + typedef struct { uint32_t search_pcr_mask; const char *device; From 321a9d9ee5690ea57924b4724967812915c6c61d Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 13 Sep 2021 11:23:41 +0200 Subject: [PATCH 3/5] tpm2: check if PCR values make sense before using them Fixes: #20684 --- src/shared/tpm2-util.c | 182 ++++++++++++++++++++++++++++++++++------- src/shared/tpm2-util.h | 1 + 2 files changed, 155 insertions(+), 28 deletions(-) diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c index 3cdb3ed7b0..f5f46d14ee 100644 --- a/src/shared/tpm2-util.c +++ b/src/shared/tpm2-util.c @@ -29,6 +29,7 @@ TSS2_RC (*sym_Esys_GetCapability)(ESYS_CONTEXT *esysContext, ESYS_TR shandle1, E TSS2_RC (*sym_Esys_GetRandom)(ESYS_CONTEXT *esysContext, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, UINT16 bytesRequested, TPM2B_DIGEST **randomBytes) = NULL; TSS2_RC (*sym_Esys_Initialize)(ESYS_CONTEXT **esys_context, TSS2_TCTI_CONTEXT *tcti, TSS2_ABI_VERSION *abiVersion) = NULL; TSS2_RC (*sym_Esys_Load)(ESYS_CONTEXT *esysContext, ESYS_TR parentHandle, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_PRIVATE *inPrivate, const TPM2B_PUBLIC *inPublic, ESYS_TR *objectHandle) = NULL; +TSS2_RC (*sym_Esys_PCR_Read)(ESYS_CONTEXT *esysContext, ESYS_TR shandle1,ESYS_TR shandle2, ESYS_TR shandle3, const TPML_PCR_SELECTION *pcrSelectionIn, UINT32 *pcrUpdateCounter, TPML_PCR_SELECTION **pcrSelectionOut, TPML_DIGEST **pcrValues); TSS2_RC (*sym_Esys_PolicyGetDigest)(ESYS_CONTEXT *esysContext, ESYS_TR policySession, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, TPM2B_DIGEST **policyDigest) = NULL; TSS2_RC (*sym_Esys_PolicyPCR)(ESYS_CONTEXT *esysContext, ESYS_TR policySession, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_DIGEST *pcrDigest, const TPML_PCR_SELECTION *pcrs) = NULL; TSS2_RC (*sym_Esys_StartAuthSession)(ESYS_CONTEXT *esysContext, ESYS_TR tpmKey, ESYS_TR bind, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_NONCE *nonceCaller, TPM2_SE sessionType, const TPMT_SYM_DEF *symmetric, TPMI_ALG_HASH authHash, ESYS_TR *sessionHandle) = NULL; @@ -56,6 +57,7 @@ int dlopen_tpm2(void) { DLSYM_ARG(Esys_GetRandom), DLSYM_ARG(Esys_Initialize), DLSYM_ARG(Esys_Load), + DLSYM_ARG(Esys_PCR_Read), DLSYM_ARG(Esys_PolicyGetDigest), DLSYM_ARG(Esys_PolicyPCR), DLSYM_ARG(Esys_StartAuthSession), @@ -381,16 +383,108 @@ static int tpm2_make_primary( return 0; } +static void tpm2_pcr_mask_to_selecion(uint32_t mask, uint16_t bank, TPML_PCR_SELECTION *ret) { + assert(ret); + + /* We only do 24bit here, as that's what PC TPMs are supposed to support */ + assert(mask <= 0xFFFFFFU); + + *ret = (TPML_PCR_SELECTION) { + .count = 1, + .pcrSelections[0].hash = bank, + .pcrSelections[0].sizeofSelect = 3, + .pcrSelections[0].pcrSelect[0] = mask & 0xFF, + .pcrSelections[0].pcrSelect[1] = (mask >> 8) & 0xFF, + .pcrSelections[0].pcrSelect[2] = (mask >> 16) & 0xFF, + }; +} + +static unsigned find_nth_bit(uint32_t mask, unsigned n) { + uint32_t bit = 1; + + assert(n < 32); + + /* Returns the bit index of the nth set bit, e.g. mask=0b101001, n=3 → 5 */ + + for (unsigned i = 0; i < sizeof(mask)*8; i++) { + + if (bit & mask) { + if (n == 0) + return i; + + n--; + } + + bit <<= 1; + } + + return UINT_MAX; +} + +static int tpm2_pcr_mask_good( + ESYS_CONTEXT *c, + TPMI_ALG_HASH bank, + uint32_t mask) { + + _cleanup_(Esys_Freep) TPML_DIGEST *pcr_values = NULL; + TPML_PCR_SELECTION selection; + bool good = false; + TSS2_RC rc; + + assert(c); + + /* So we have the problem that some systems might have working TPM2 chips, but the firmware doesn't + * actually measure into them, or only into a suboptimal bank. If so, the PCRs should be all zero or + * all 0xFF. Detect that, so that we can warn and maybe pick a better bank. */ + + tpm2_pcr_mask_to_selecion(mask, bank, &selection); + + rc = sym_Esys_PCR_Read( + c, + ESYS_TR_NONE, + ESYS_TR_NONE, + ESYS_TR_NONE, + &selection, + NULL, + NULL, + &pcr_values); + if (rc != TSS2_RC_SUCCESS) + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Failed to read TPM2 PCRs: %s", sym_Tss2_RC_Decode(rc)); + + /* If at least one of the selected PCR values is something other than all 0x00 or all 0xFF we are happy. */ + for (unsigned i = 0; i < pcr_values->count; i++) { + if (DEBUG_LOGGING) { + _cleanup_free_ char *h = NULL; + unsigned j; + + h = hexmem(pcr_values->digests[i].buffer, pcr_values->digests[i].size); + j = find_nth_bit(mask, i); + assert(j != UINT_MAX); + + log_debug("PCR %u value: %s", j, strna(h)); + } + + if (!memeqbyte(0x00, pcr_values->digests[i].buffer, pcr_values->digests[i].size) && + !memeqbyte(0xFF, pcr_values->digests[i].buffer, pcr_values->digests[i].size)) + good = true; + } + + return good; +} + static int tpm2_get_best_pcr_bank( ESYS_CONTEXT *c, + uint32_t pcr_mask, TPMI_ALG_HASH *ret) { _cleanup_(Esys_Freep) TPMS_CAPABILITY_DATA *pcap = NULL; - TPMI_ALG_HASH hash = TPM2_ALG_SHA1; - bool found = false; + TPMI_ALG_HASH supported_hash = 0, hash_with_valid_pcr = 0; TPMI_YES_NO more; TSS2_RC rc; + assert(c); + rc = sym_Esys_GetCapability( c, ESYS_TR_NONE, @@ -409,6 +503,11 @@ static int tpm2_get_best_pcr_bank( for (size_t i = 0; i < pcap->data.assignedPCR.count; i++) { bool valid = true; + int good; + + /* For now we are only interested in the SHA1 and SHA256 banks */ + if (!IN_SET(pcap->data.assignedPCR.pcrSelections[i].hash, TPM2_ALG_SHA256, TPM2_ALG_SHA1)) + continue; /* As per * https://trustedcomputinggroup.org/wp-content/uploads/TCG_PCClient_PFP_r1p05_v23_pub.pdf a @@ -436,28 +535,58 @@ static int tpm2_get_best_pcr_bank( continue; } - if (pcap->data.assignedPCR.pcrSelections[i].hash == TPM2_ALG_SHA256) { - hash = TPM2_ALG_SHA256; - found = true; - break; - } + good = tpm2_pcr_mask_good(c, pcap->data.assignedPCR.pcrSelections[i].hash, pcr_mask); + if (good < 0) + return good; - if (pcap->data.assignedPCR.pcrSelections[i].hash == TPM2_ALG_SHA1) - found = true; + if (pcap->data.assignedPCR.pcrSelections[i].hash == TPM2_ALG_SHA256) { + supported_hash = TPM2_ALG_SHA256; + if (good) { + /* Great, SHA256 is supported and has initialized PCR values, we are done. */ + hash_with_valid_pcr = TPM2_ALG_SHA256; + break; + } + } else { + assert(pcap->data.assignedPCR.pcrSelections[i].hash == TPM2_ALG_SHA1); + + if (supported_hash == 0) + supported_hash = TPM2_ALG_SHA1; + + if (good && hash_with_valid_pcr == 0) + hash_with_valid_pcr = TPM2_ALG_SHA1; + } } - if (!found) + /* We preferably pick SHA256, but only if its PCRs are initialized or neither the SHA1 nor the SHA256 + * PCRs are initialized. If SHA256 is not supported but SHA1 is and its PCRs are too, we prefer + * SHA1. + * + * We log at LOG_NOTICE level whenever we end up using the SHA1 bank or when the PCRs we bind to are + * not initialized. */ + + if (hash_with_valid_pcr == TPM2_ALG_SHA256) { + assert(supported_hash == TPM2_ALG_SHA256); + log_debug("TPM2 device supports SHA256 PCR bank and SHA256 PCRs are valid, yay!"); + *ret = TPM2_ALG_SHA256; + } else if (hash_with_valid_pcr == TPM2_ALG_SHA1) { + if (supported_hash == TPM2_ALG_SHA256) + log_notice("TPM2 device supports both SHA1 and SHA256 PCR banks, but only SHA1 PCRs are valid, falling back to SHA1 bank. This reduces the security level substantially."); + else { + assert(supported_hash == TPM2_ALG_SHA1); + log_notice("TPM2 device lacks support for SHA256 PCR bank, but SHA1 bank is supported and SHA1 PCRs are valid, falling back to SHA1 bank. This reduces the security level substantially."); + } + + *ret = TPM2_ALG_SHA1; + } else if (supported_hash == TPM2_ALG_SHA256) { + log_notice("TPM2 device supports SHA256 PCR bank but none of the selected PCRs are valid! Firmware apparently did not initialize any of the selected PCRs. Proceeding anyway with SHA256 bank. PCR policy effectively unenforced!"); + *ret = TPM2_ALG_SHA256; + } else if (supported_hash == TPM2_ALG_SHA1) { + log_notice("TPM2 device lacks support for SHA256 bank, but SHA1 bank is supported, but none of the selected PCRs are valid! Firmware apparently did not initialize any of the selected PCRs. Proceeding anyway with SHA1 bank. PCR policy effectively unenforced!"); + *ret = TPM2_ALG_SHA1; + } else return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "TPM2 module supports neither SHA1 nor SHA256 PCR banks, cannot operate."); - if (hash == TPM2_ALG_SHA256) - log_debug("TPM2 device supports SHA256 PCR banks, yay!"); - else { - assert(hash == TPM2_ALG_SHA1); - log_debug("TPM2 device lacks support for SHA256 PCR banks, falling back to SHA1 banks."); - } - - *ret = hash; return 0; } @@ -478,15 +607,8 @@ static int tpm2_make_pcr_session( .aes = TPM2_ALG_CFB, } }; - TPML_PCR_SELECTION pcr_selection = { - .count = 1, - .pcrSelections[0].hash = TPM2_ALG_SHA256, /* overridden below, depending on TPM2 capabilities */ - .pcrSelections[0].sizeofSelect = 3, - .pcrSelections[0].pcrSelect[0] = pcr_mask & 0xFF, - .pcrSelections[0].pcrSelect[1] = (pcr_mask >> 8) & 0xFF, - .pcrSelections[0].pcrSelect[2] = (pcr_mask >> 16) & 0xFF, - }; _cleanup_(Esys_Freep) TPM2B_DIGEST *policy_digest = NULL; + TPML_PCR_SELECTION pcr_selection; ESYS_TR session = ESYS_TR_NONE; TSS2_RC rc; int r; @@ -496,13 +618,17 @@ static int tpm2_make_pcr_session( log_debug("Starting authentication session."); if (pcr_bank != UINT16_MAX) - pcr_selection.pcrSelections[0].hash = pcr_bank; + tpm2_pcr_mask_to_selecion(pcr_mask, pcr_bank, &pcr_selection); else { + TPMI_ALG_HASH h; + /* No bank configured, pick automatically. Some TPM2 devices only can do SHA1. If we detect * that use that, but preferably use SHA256 */ - r = tpm2_get_best_pcr_bank(c, &pcr_selection.pcrSelections[0].hash); + r = tpm2_get_best_pcr_bank(c, pcr_mask, &h); if (r < 0) return r; + + tpm2_pcr_mask_to_selecion(pcr_mask, h, &pcr_selection); } rc = sym_Esys_StartAuthSession( diff --git a/src/shared/tpm2-util.h b/src/shared/tpm2-util.h index b8f12760d0..cb57a847e2 100644 --- a/src/shared/tpm2-util.h +++ b/src/shared/tpm2-util.h @@ -19,6 +19,7 @@ extern TSS2_RC (*sym_Esys_GetCapability)(ESYS_CONTEXT *esysContext, ESYS_TR shan extern TSS2_RC (*sym_Esys_GetRandom)(ESYS_CONTEXT *esysContext, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, UINT16 bytesRequested, TPM2B_DIGEST **randomBytes); extern TSS2_RC (*sym_Esys_Initialize)(ESYS_CONTEXT **esys_context, TSS2_TCTI_CONTEXT *tcti, TSS2_ABI_VERSION *abiVersion); extern TSS2_RC (*sym_Esys_Load)(ESYS_CONTEXT *esysContext, ESYS_TR parentHandle, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_PRIVATE *inPrivate, const TPM2B_PUBLIC *inPublic, ESYS_TR *objectHandle); +extern TSS2_RC (*sym_Esys_PCR_Read)(ESYS_CONTEXT *esysContext, ESYS_TR shandle1,ESYS_TR shandle2, ESYS_TR shandle3, const TPML_PCR_SELECTION *pcrSelectionIn, UINT32 *pcrUpdateCounter, TPML_PCR_SELECTION **pcrSelectionOut, TPML_DIGEST **pcrValues); extern TSS2_RC (*sym_Esys_PolicyGetDigest)(ESYS_CONTEXT *esysContext, ESYS_TR policySession, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, TPM2B_DIGEST **policyDigest); extern TSS2_RC (*sym_Esys_PolicyPCR)(ESYS_CONTEXT *esysContext, ESYS_TR policySession, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_DIGEST *pcrDigest, const TPML_PCR_SELECTION *pcrs); extern TSS2_RC (*sym_Esys_StartAuthSession)(ESYS_CONTEXT *esysContext, ESYS_TR tpmKey, ESYS_TR bind, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_NONCE *nonceCaller, TPM2_SE sessionType, const TPMT_SYM_DEF *symmetric, TPMI_ALG_HASH authHash, ESYS_TR *sessionHandle); From d38466bae68fe746c6052a4dd50e27de0274e52d Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 13 Sep 2021 12:43:53 +0200 Subject: [PATCH 4/5] tpm2: log about invalid PCRs on each unsealing Let's log every time we use uninitialized PCRs when unsealing a secret via TPM2. This indicates a firmware issue usually, and is something we shouldn't just show when enrolling but also show every time we unseal, so that the fact that the selected PCR policy is pretty much pointless is repeatedly shown. --- src/shared/tpm2-util.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c index f5f46d14ee..9d5a0ed865 100644 --- a/src/shared/tpm2-util.c +++ b/src/shared/tpm2-util.c @@ -617,9 +617,15 @@ static int tpm2_make_pcr_session( log_debug("Starting authentication session."); - if (pcr_bank != UINT16_MAX) + if (pcr_bank != UINT16_MAX) { + 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."); + tpm2_pcr_mask_to_selecion(pcr_mask, pcr_bank, &pcr_selection); - else { + } else { TPMI_ALG_HASH h; /* No bank configured, pick automatically. Some TPM2 devices only can do SHA1. If we detect From f4529c4d970a3e787c4004332de4402c58de8971 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 13 Sep 2021 14:06:54 +0200 Subject: [PATCH 5/5] update TODO --- TODO | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/TODO b/TODO index b4fd8f59e7..8b966dc625 100644 --- a/TODO +++ b/TODO @@ -83,6 +83,10 @@ Janitorial Clean-ups: Features: +* in uefi stub: query firmware regarding which PCRs are being used, store that + in EFI var. then use this when enrolling TPM2 in cryptsetup to verify that + the selected PCRs actually are used by firmware. + * rework recursive read-only remount to use new mount API * PAM: pick auf one authentication token from credentials