From 2418e9c1409ba04ddff516a83d83b2daa3417832 Mon Sep 17 00:00:00 2001 From: Grigori Goronzy Date: Fri, 18 Feb 2022 11:56:02 +0100 Subject: [PATCH] cryptsetup: add support for TPM2 pin Extend cryptsetup for TPM2 pin entry, similar to FIDO2. (cherry picked from commit bea344a1a426e615ba87b66b6d3ff4b265c57a95) Related: #2087652 --- src/cryptsetup/cryptsetup-tpm2.c | 108 ++++++++++++++++++++++++++++++- src/cryptsetup/cryptsetup-tpm2.h | 16 ++++- src/cryptsetup/cryptsetup.c | 16 ++++- 3 files changed, 135 insertions(+), 5 deletions(-) diff --git a/src/cryptsetup/cryptsetup-tpm2.c b/src/cryptsetup/cryptsetup-tpm2.c index 05d76a684d..b84d64def8 100644 --- a/src/cryptsetup/cryptsetup-tpm2.c +++ b/src/cryptsetup/cryptsetup-tpm2.c @@ -1,7 +1,9 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include "alloc-util.h" +#include "ask-password-api.h" #include "cryptsetup-tpm2.h" +#include "env-util.h" #include "fileio.h" #include "hexdecoct.h" #include "json.h" @@ -9,6 +11,47 @@ #include "random-util.h" #include "tpm2-util.h" +static int get_pin(usec_t until, AskPasswordFlags ask_password_flags, bool headless, char **ret_pin_str) { + _cleanup_free_ char *pin_str = NULL; + _cleanup_strv_free_erase_ char **pin = NULL; + int r; + + assert(ret_pin_str); + + r = getenv_steal_erase("PIN", &pin_str); + if (r < 0) + return log_error_errno(r, "Failed to acquire PIN from environment: %m"); + if (!r) { + if (headless) + return log_error_errno( + SYNTHETIC_ERRNO(ENOPKG), + "PIN querying disabled via 'headless' option. " + "Use the '$PIN' environment variable."); + + pin = strv_free_erase(pin); + r = ask_password_auto( + "Please enter TPM2 PIN:", + "drive-harddisk", + NULL, + "tpm2-pin", + "cryptsetup.tpm2-pin", + until, + ask_password_flags, + &pin); + if (r < 0) + return log_error_errno(r, "Failed to ask for user pin: %m"); + assert(strv_length(pin) == 1); + + pin_str = strdup(pin[0]); + if (!pin_str) + return log_oom(); + } + + *ret_pin_str = TAKE_PTR(pin_str); + + return r; +} + int acquire_tpm2_key( const char *volume_name, const char *device, @@ -22,6 +65,10 @@ int acquire_tpm2_key( size_t key_data_size, const void *policy_hash, size_t policy_hash_size, + TPM2Flags flags, + usec_t until, + bool headless, + AskPasswordFlags ask_password_flags, void **ret_decrypted_key, size_t *ret_decrypted_key_size) { @@ -64,7 +111,51 @@ int acquire_tpm2_key( blob = loaded_blob; } - return tpm2_unseal(device, pcr_mask, pcr_bank, primary_alg, blob, blob_size, policy_hash, policy_hash_size, NULL, ret_decrypted_key, ret_decrypted_key_size); + if (!(flags & TPM2_FLAGS_USE_PIN)) + return tpm2_unseal( + device, + pcr_mask, + pcr_bank, + primary_alg, + blob, + blob_size, + policy_hash, + policy_hash_size, + NULL, + ret_decrypted_key, + ret_decrypted_key_size); + + for (int i = 5;; i--) { + _cleanup_(erase_and_freep) char *pin_str = NULL; + + if (i <= 0) + return -EACCES; + + r = get_pin(until, ask_password_flags, headless, &pin_str); + if (r < 0) + return r; + + r = tpm2_unseal( + device, + pcr_mask, + pcr_bank, + primary_alg, + blob, + blob_size, + policy_hash, + policy_hash_size, + pin_str, + ret_decrypted_key, + ret_decrypted_key_size); + /* We get this error in case there is an authentication policy mismatch. This should + * not happen, but this avoids confusing behavior, just in case. */ + if (IN_SET(r, -EPERM, -ENOLCK)) + return r; + if (r < 0) + continue; + + return r; + } } int find_tpm2_auto_data( @@ -79,11 +170,13 @@ int find_tpm2_auto_data( void **ret_policy_hash, size_t *ret_policy_hash_size, int *ret_keyslot, - int *ret_token) { + int *ret_token, + TPM2Flags *ret_flags) { _cleanup_free_ void *blob = NULL, *policy_hash = NULL; size_t blob_size = 0, policy_hash_size = 0; int r, keyslot = -1, token = -1; + TPM2Flags flags = 0; 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 */ @@ -196,6 +289,16 @@ int find_tpm2_auto_data( return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid base64 data in 'tpm2-policy-hash' field."); + w = json_variant_by_key(v, "tpm2-pin"); + if (w) { + if (!json_variant_is_boolean(w)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "TPM2 PIN policy is not a boolean."); + + if (json_variant_boolean(w)) + flags |= TPM2_FLAGS_USE_PIN; + } + break; } @@ -215,6 +318,7 @@ int find_tpm2_auto_data( *ret_token = token; *ret_pcr_bank = pcr_bank; *ret_primary_alg = primary_alg; + *ret_flags = flags; return 0; } diff --git a/src/cryptsetup/cryptsetup-tpm2.h b/src/cryptsetup/cryptsetup-tpm2.h index bd04620462..ab16d0a18f 100644 --- a/src/cryptsetup/cryptsetup-tpm2.h +++ b/src/cryptsetup/cryptsetup-tpm2.h @@ -3,9 +3,11 @@ #include +#include "ask-password-api.h" #include "cryptsetup-util.h" #include "log.h" #include "time-util.h" +#include "tpm2-util.h" #if HAVE_TPM2 @@ -22,6 +24,10 @@ int acquire_tpm2_key( size_t key_data_size, const void *policy_hash, size_t policy_hash_size, + TPM2Flags flags, + usec_t until, + bool headless, + AskPasswordFlags ask_password_flags, void **ret_decrypted_key, size_t *ret_decrypted_key_size); @@ -37,7 +43,8 @@ int find_tpm2_auto_data( void **ret_policy_hash, size_t *ret_policy_hash_size, int *ret_keyslot, - int *ret_token); + int *ret_token, + TPM2Flags *ret_flags); #else @@ -54,6 +61,10 @@ static inline int acquire_tpm2_key( size_t key_data_size, const void *policy_hash, size_t policy_hash_size, + TPM2Flags flags, + usec_t until, + bool headless, + AskPasswordFlags ask_password_flags, void **ret_decrypted_key, size_t *ret_decrypted_key_size) { @@ -73,7 +84,8 @@ static inline int find_tpm2_auto_data( void **ret_policy_hash, size_t *ret_policy_hash_size, int *ret_keyslot, - int *ret_token) { + int *ret_token, + TPM2Flags *ret_flags) { return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "TPM2 support not available."); diff --git a/src/cryptsetup/cryptsetup.c b/src/cryptsetup/cryptsetup.c index dddd976dc8..ede0f7ed0b 100644 --- a/src/cryptsetup/cryptsetup.c +++ b/src/cryptsetup/cryptsetup.c @@ -1301,9 +1301,15 @@ static int attach_luks_or_plain_or_bitlk_by_tpm2( key_file, arg_keyfile_size, arg_keyfile_offset, key_data, key_data_size, NULL, 0, /* we don't know the policy hash */ + 0, /* PIN is currently unhandled in this case */ + until, + arg_headless, + arg_ask_password_flags, &decrypted_key, &decrypted_key_size); if (r >= 0) break; + if (IN_SET(r, -EACCES, -ENOLCK)) + return log_error_errno(SYNTHETIC_ERRNO(EAGAIN), "TPM2 PIN unlock failed, falling back to traditional unlocking."); if (ERRNO_IS_NOT_SUPPORTED(r)) /* TPM2 support not compiled in? */ return log_debug_errno(SYNTHETIC_ERRNO(EAGAIN), "TPM2 support not available, falling back to traditional unlocking."); if (r != -EAGAIN) /* EAGAIN means: no tpm2 chip found */ @@ -1335,6 +1341,7 @@ static int attach_luks_or_plain_or_bitlk_by_tpm2( for (;;) { uint32_t pcr_mask; uint16_t pcr_bank, primary_alg; + TPM2Flags tpm2_flags; r = find_tpm2_auto_data( cd, @@ -1346,7 +1353,8 @@ static int attach_luks_or_plain_or_bitlk_by_tpm2( &blob, &blob_size, &policy_hash, &policy_hash_size, &keyslot, - &token); + &token, + &tpm2_flags); if (r == -ENXIO) /* No further TPM2 tokens found in the LUKS2 header. */ return log_debug_errno(SYNTHETIC_ERRNO(EAGAIN), @@ -1369,7 +1377,13 @@ static int attach_luks_or_plain_or_bitlk_by_tpm2( NULL, 0, 0, /* no key file */ blob, blob_size, policy_hash, policy_hash_size, + tpm2_flags, + until, + arg_headless, + arg_ask_password_flags, &decrypted_key, &decrypted_key_size); + if (IN_SET(r, -EACCES, -ENOLCK)) + return log_error_errno(SYNTHETIC_ERRNO(EAGAIN), "TPM2 PIN unlock failed, falling back to traditional unlocking."); if (r != -EPERM) break;