diff --git a/man/crypttab.xml b/man/crypttab.xml index ac5c6ef6664..22411166a8d 100644 --- a/man/crypttab.xml +++ b/man/crypttab.xml @@ -677,6 +677,14 @@ of the current PCR state. + + + + Takes a boolean argument, defaults to false. Controls whether + TPM2 volume unlocking is bound to a PIN in addition to PCRs. Similarly, this option is only useful + when TPM2 enrollment metadata is not available. + + diff --git a/man/systemd-cryptenroll.xml b/man/systemd-cryptenroll.xml index d5fdb54cdd1..58a46267680 100644 --- a/man/systemd-cryptenroll.xml +++ b/man/systemd-cryptenroll.xml @@ -299,6 +299,24 @@ signatures likely will validate against pre-existing certificates. + + BOOL + + When enrolling a TPM2 device, controls whether to require the user to enter a PIN + when unlocking the volume in addition to PCR binding, based on TPM2 policy authentication. Defaults + to no. Despite being called PIN, any character can be used, not just numbers. + + + Note that incorrect PIN entry when unlocking increments the + TPM dictionary attack lockout mechanism, and may lock out users for a prolonged time, depending on + its configuration. The lockout mechanism is a global property of the TPM, + systemd-cryptenroll does not control or configure the lockout mechanism. You may + use tpm2-tss tools to inspect or configure the dictionary attack lockout, with + tpm2_getcap1 and + tpm2_dictionarylockout1 + commands, respectively. + + SLOT diff --git a/src/basic/hmac.h b/src/basic/hmac.h index a5682c439fe..e58c1838a3d 100644 --- a/src/basic/hmac.h +++ b/src/basic/hmac.h @@ -4,7 +4,7 @@ #include #include -#define SHA256_DIGEST_SIZE 32 +#include "sha256.h" /* Unoptimized implementation based on FIPS 198. 'res' has to be allocated by * the caller. Prefer external OpenSSL functions, and use this only when diff --git a/src/cryptenroll/cryptenroll-tpm2.c b/src/cryptenroll/cryptenroll-tpm2.c index 801014af11d..e8c64dd7536 100644 --- a/src/cryptenroll/cryptenroll-tpm2.c +++ b/src/cryptenroll/cryptenroll-tpm2.c @@ -1,7 +1,9 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include "alloc-util.h" +#include "ask-password-api.h" #include "cryptenroll-tpm2.h" +#include "env-util.h" #include "hexdecoct.h" #include "json.h" #include "memory-util.h" @@ -58,11 +60,78 @@ static int search_policy_hash( return -ENOENT; /* Not found */ } +static int get_pin(char **ret_pin_str, TPM2Flags *ret_flags) { + _cleanup_free_ char *pin_str = NULL; + int r; + TPM2Flags flags = 0; + + assert(ret_pin_str); + assert(ret_flags); + + r = getenv_steal_erase("NEWPIN", &pin_str); + if (r < 0) + return log_error_errno(r, "Failed to acquire PIN from environment: %m"); + if (r > 0) + flags |= TPM2_FLAGS_USE_PIN; + else { + for (size_t i = 5;; i--) { + _cleanup_strv_free_erase_ char **pin = NULL, **pin2 = NULL; + + if (i <= 0) + return log_error_errno( + SYNTHETIC_ERRNO(ENOKEY), "Too many attempts, giving up."); + + pin = strv_free_erase(pin); + r = ask_password_auto( + "Please enter TPM2 PIN:", + "drive-harddisk", + NULL, + "tpm2-pin", + "cryptenroll.tpm2-pin", + USEC_INFINITY, + 0, + &pin); + if (r < 0) + return log_error_errno(r, "Failed to ask for user pin: %m"); + assert(strv_length(pin) == 1); + + r = ask_password_auto( + "Please enter TPM2 PIN (repeat):", + "drive-harddisk", + NULL, + "tpm2-pin", + "cryptenroll.tpm2-pin", + USEC_INFINITY, + 0, + &pin2); + if (r < 0) + return log_error_errno(r, "Failed to ask for user pin: %m"); + assert(strv_length(pin) == 1); + + if (strv_equal(pin, pin2)) { + pin_str = strdup(*pin); + if (!pin_str) + return log_oom(); + flags |= TPM2_FLAGS_USE_PIN; + break; + } + + log_error("PINs didn't match, please try again!"); + } + } + + *ret_flags = flags; + *ret_pin_str = TAKE_PTR(pin_str); + + 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) { + uint32_t pcr_mask, + bool use_pin) { _cleanup_(erase_and_freep) void *secret = NULL, *secret2 = NULL; _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; @@ -71,7 +140,9 @@ int enroll_tpm2(struct crypt_device *cd, _cleanup_free_ void *blob = NULL, *hash = NULL; uint16_t pcr_bank, primary_alg; const char *node; + _cleanup_(erase_and_freep) char *pin_str = NULL; int r, keyslot; + TPM2Flags flags = 0; assert(cd); assert(volume_key); @@ -80,7 +151,13 @@ 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, &primary_alg); + if (use_pin) { + r = get_pin(&pin_str, &flags); + if (r < 0) + return r; + } + + r = tpm2_seal(device, pcr_mask, pin_str, &secret, &secret_size, &blob, &blob_size, &hash, &hash_size, &pcr_bank, &primary_alg); if (r < 0) return r; @@ -97,7 +174,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, primary_alg, 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, pin_str, &secret2, &secret2_size); if (r < 0) return r; @@ -123,7 +200,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, primary_alg, 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, flags, &v); if (r < 0) return log_error_errno(r, "Failed to prepare TPM2 JSON token object: %m"); diff --git a/src/cryptenroll/cryptenroll-tpm2.h b/src/cryptenroll/cryptenroll-tpm2.h index d5dd1b00036..742f49b8d59 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); +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); #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) { +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) { 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 e13f5b7ac85..2fd6d9080ec 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 bool arg_tpm2_pin = false; static char *arg_node = NULL; static int *arg_wipe_slots = NULL; static size_t arg_n_wipe_slots = 0; @@ -100,6 +101,8 @@ static int help(void) { " Enroll a TPM2 device\n" " --tpm2-pcrs=PCR1+PCR2+PCR3+…\n" " Specify TPM2 PCRs to seal against\n" + " --tpm2-with-pin=BOOL\n" + " Whether to require entering a PIN to unlock the volume\n" " --wipe-slot=SLOT1,SLOT2,…\n" " Wipe specified slots\n" "\nSee the %s for details.\n", @@ -121,6 +124,7 @@ static int parse_argv(int argc, char *argv[]) { ARG_FIDO2_DEVICE, ARG_TPM2_DEVICE, ARG_TPM2_PCRS, + ARG_TPM2_PIN, ARG_WIPE_SLOT, ARG_FIDO2_WITH_PIN, ARG_FIDO2_WITH_UP, @@ -139,6 +143,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-with-pin", required_argument, NULL, ARG_TPM2_PIN }, { "wipe-slot", required_argument, NULL, ARG_WIPE_SLOT }, {} }; @@ -301,6 +306,14 @@ static int parse_argv(int argc, char *argv[]) { break; } + case ARG_TPM2_PIN: { + r = parse_boolean_argument("--tpm2-with-pin=", optarg, &arg_tpm2_pin); + if (r < 0) + return r; + + break; + } + case ARG_WIPE_SLOT: { const char *p = optarg; @@ -558,7 +571,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); + slot = enroll_tpm2(cd, vk, vks, arg_tpm2_device, arg_tpm2_pcr_mask, arg_tpm2_pin); break; case _ENROLL_TYPE_INVALID: diff --git a/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-tpm2.c b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-tpm2.c index e2d28a5dda8..23df9749994 100644 --- a/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-tpm2.c +++ b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-tpm2.c @@ -6,8 +6,10 @@ #include "cryptsetup-token.h" #include "cryptsetup-token-util.h" #include "hexdecoct.h" +#include "json.h" #include "luks2-tpm2.h" #include "memory-util.h" +#include "strv.h" #include "tpm2-util.h" #include "version.h" @@ -78,7 +80,8 @@ _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, &primary_alg, &base64_blob, &hex_policy_hash); + TPM2Flags flags = 0; + r = parse_luks2_tpm2_data(json, params.search_pcr_mask, &pcr_mask, &pcr_bank, &primary_alg, &base64_blob, &hex_policy_hash, &flags); if (r < 0) return log_debug_open_error(cd, r); @@ -101,6 +104,7 @@ _public_ int cryptsetup_token_open( blob_size, policy_hash, policy_hash_size, + flags, &decrypted_key, &decrypted_key_size); if (r < 0) @@ -135,6 +139,7 @@ _public_ void cryptsetup_token_dump( const char *json /* validated 'systemd-tpm2' token if cryptsetup_token_validate is defined */) { int r; + TPM2Flags flags = 0; uint32_t pcr_mask; uint16_t pcr_bank, primary_alg; size_t decoded_blob_size; @@ -144,7 +149,7 @@ _public_ void cryptsetup_token_dump( assert(json); - r = parse_luks2_tpm2_data(json, UINT32_MAX, &pcr_mask, &pcr_bank, &primary_alg, &base64_blob, &hex_policy_hash); + r = parse_luks2_tpm2_data(json, UINT32_MAX, &pcr_mask, &pcr_bank, &primary_alg, &base64_blob, &hex_policy_hash, &flags); if (r < 0) return (void) crypt_log_debug_errno(cd, r, "Failed to parse " TOKEN_NAME " metadata: %m."); @@ -171,6 +176,7 @@ _public_ void cryptsetup_token_dump( 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); + crypt_log(cd, "\ttpm2-pin: %s\n", true_false(flags & TPM2_FLAGS_USE_PIN)); } /* @@ -268,5 +274,13 @@ _public_ int cryptsetup_token_validate( if (r < 0) return crypt_log_debug_errno(cd, r, "Invalid base64 data in 'tpm2-policy-hash' field: %m"); + w = json_variant_by_key(v, "tpm2-pin"); + if (w) { + if (!json_variant_is_boolean(w)) { + crypt_log_debug(cd, "TPM2 PIN policy is not a boolean."); + return 1; + } + } + return 0; } diff --git a/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.c b/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.c index 3d39dfa8848..0d6e4bc0f8c 100644 --- a/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.c +++ b/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.c @@ -1,11 +1,15 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include "alloc-util.h" +#include "ask-password-api.h" +#include "env-util.h" #include "hexdecoct.h" #include "json.h" +#include "log.h" #include "luks2-tpm2.h" #include "parse-util.h" #include "random-util.h" +#include "strv.h" #include "tpm2-util.h" int acquire_luks2_key( @@ -17,10 +21,12 @@ int acquire_luks2_key( size_t key_data_size, const void *policy_hash, size_t policy_hash_size, + TPM2Flags flags, void **ret_decrypted_key, size_t *ret_decrypted_key_size) { _cleanup_free_ char *auto_device = NULL; + _cleanup_(erase_and_freep) char *pin_str = NULL; int r; assert(ret_decrypted_key); @@ -36,12 +42,22 @@ int acquire_luks2_key( device = auto_device; } + r = getenv_steal_erase("PIN", &pin_str); + if (r < 0) + return log_error_errno(r, "Failed to acquire PIN from environment: %m"); + if (!r) { + /* PIN entry is not supported by plugin, let it fallback, possibly to sd-cryptsetup's + * internal handling. */ + if (flags & TPM2_FLAGS_USE_PIN) + return -EOPNOTSUPP; + } + return tpm2_unseal( device, pcr_mask, pcr_bank, primary_alg, key_data, key_data_size, - policy_hash, policy_hash_size, + policy_hash, policy_hash_size, pin_str, ret_decrypted_key, ret_decrypted_key_size); } @@ -53,7 +69,8 @@ int parse_luks2_tpm2_data( uint16_t *ret_pcr_bank, uint16_t *ret_primary_alg, char **ret_base64_blob, - char **ret_hex_policy_hash) { + char **ret_hex_policy_hash, + TPM2Flags *ret_flags) { int r; JsonVariant *w, *e; @@ -61,6 +78,7 @@ int parse_luks2_tpm2_data( 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; + TPM2Flags flags = 0; assert(json); assert(ret_pcr_mask); @@ -138,11 +156,21 @@ int parse_luks2_tpm2_data( if (!hex_policy_hash) return -ENOMEM; + w = json_variant_by_key(v, "tpm2-pin"); + if (w) { + if (!json_variant_is_boolean(w)) + return -EINVAL; + + if (json_variant_boolean(w)) + flags |= TPM2_FLAGS_USE_PIN; + } + *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); + *ret_flags = flags; return 0; } diff --git a/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.h b/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.h index 0c93ea82cc9..34c93216eee 100644 --- a/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.h +++ b/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.h @@ -2,6 +2,8 @@ #pragma once +#include "tpm2-util.h" + struct crypt_device; int acquire_luks2_key( @@ -13,6 +15,7 @@ int acquire_luks2_key( size_t key_data_size, const void *policy_hash, size_t policy_hash_size, + TPM2Flags flags, void **ret_decrypted_key, size_t *ret_decrypted_key_size); @@ -23,4 +26,5 @@ int parse_luks2_tpm2_data( uint16_t *ret_pcr_bank, uint16_t *ret_primary_alg, char **ret_base64_blob, - char **ret_hex_policy_hash); + char **ret_hex_policy_hash, + TPM2Flags *ret_flags); diff --git a/src/cryptsetup/cryptsetup-tpm2.c b/src/cryptsetup/cryptsetup-tpm2.c index cb139518a71..b84d64def85 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, 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 bd046204628..ab16d0a18fe 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 56a3eedacc9..c2075f53fd9 100644 --- a/src/cryptsetup/cryptsetup.c +++ b/src/cryptsetup/cryptsetup.c @@ -82,6 +82,7 @@ static char *arg_fido2_rp_id = NULL; static char *arg_tpm2_device = NULL; static bool arg_tpm2_device_auto = false; static uint32_t arg_tpm2_pcr_mask = UINT32_MAX; +static bool arg_tpm2_pin = false; static bool arg_headless = false; static usec_t arg_token_timeout_usec = 30*USEC_PER_SEC; @@ -387,6 +388,16 @@ static int parse_one_option(const char *option) { arg_tpm2_pcr_mask |= mask; } + } else if ((val = startswith(option, "tpm2-pin="))) { + + r = parse_boolean(val); + if (r < 0) { + log_error_errno(r, "Failed to parse %s, ignoring: %m", option); + return 0; + } + + arg_tpm2_pin = r; + } else if ((val = startswith(option, "try-empty-password="))) { r = parse_boolean(val); @@ -1301,9 +1312,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 */ + arg_tpm2_pin, + 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 +1352,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 +1364,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 +1388,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; diff --git a/src/fundamental/sha256.h b/src/fundamental/sha256.h index abc4167628d..e53197f2ef9 100644 --- a/src/fundamental/sha256.h +++ b/src/fundamental/sha256.h @@ -8,6 +8,8 @@ #include "types-fundamental.h" +#define SHA256_DIGEST_SIZE 32 + struct sha256_ctx { uint32_t H[8]; diff --git a/src/partition/repart.c b/src/partition/repart.c index 91645202fca..8345b6f5827 100644 --- a/src/partition/repart.c +++ b/src/partition/repart.c @@ -2656,7 +2656,7 @@ static int partition_encrypt( 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, &primary_alg); + r = tpm2_seal(arg_tpm2_device, arg_tpm2_pcr_mask, 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"); @@ -2678,7 +2678,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, primary_alg, 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, 0, &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 4d0681bc103..c4dcc396ac2 100644 --- a/src/shared/creds-util.c +++ b/src/shared/creds-util.c @@ -534,6 +534,7 @@ int encrypt_credential_and_warn( r = tpm2_seal(tpm2_device, tpm2_pcr_mask, + NULL, &tpm2_key, &tpm2_key_size, &tpm2_blob, @@ -803,6 +804,7 @@ int decrypt_credential_and_warn( le32toh(t->blob_size), t->policy_hash_and_blob + le32toh(t->blob_size), le32toh(t->policy_hash_size), + NULL, &tpm2_key, &tpm2_key_size); if (r < 0) diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c index 70a29294325..44fe899acd3 100644 --- a/src/shared/tpm2-util.c +++ b/src/shared/tpm2-util.c @@ -14,6 +14,7 @@ #include "hexdecoct.h" #include "memory-util.h" #include "random-util.h" +#include "sha256.h" #include "time-util.h" static void *libtss2_esys_dl = NULL; @@ -30,10 +31,12 @@ TSS2_RC (*sym_Esys_GetRandom)(ESYS_CONTEXT *esysContext, ESYS_TR shandle1, ESYS_ 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_PolicyAuthValue)(ESYS_CONTEXT *esysContext, ESYS_TR policySession, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3) = NULL; 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; TSS2_RC (*sym_Esys_Startup)(ESYS_CONTEXT *esysContext, TPM2_SU startupType) = NULL; +TSS2_RC (*sym_Esys_TR_SetAuth)(ESYS_CONTEXT *esysContext, ESYS_TR handle, TPM2B_AUTH const *authValue) = NULL; TSS2_RC (*sym_Esys_Unseal)(ESYS_CONTEXT *esysContext, ESYS_TR itemHandle, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, TPM2B_SENSITIVE_DATA **outData) = NULL; const char* (*sym_Tss2_RC_Decode)(TSS2_RC rc) = NULL; @@ -58,10 +61,12 @@ int dlopen_tpm2(void) { DLSYM_ARG(Esys_Initialize), DLSYM_ARG(Esys_Load), DLSYM_ARG(Esys_PCR_Read), + DLSYM_ARG(Esys_PolicyAuthValue), DLSYM_ARG(Esys_PolicyGetDigest), DLSYM_ARG(Esys_PolicyPCR), DLSYM_ARG(Esys_StartAuthSession), DLSYM_ARG(Esys_Startup), + DLSYM_ARG(Esys_TR_SetAuth), DLSYM_ARG(Esys_Unseal)); if (r < 0) return r; @@ -594,6 +599,7 @@ static int tpm2_make_pcr_session( ESYS_CONTEXT *c, uint32_t pcr_mask, uint16_t pcr_bank, /* If UINT16_MAX, pick best bank automatically, otherwise specify bank explicitly. */ + bool use_pin, ESYS_TR *ret_session, TPM2B_DIGEST **ret_policy_digest, TPMI_ALG_HASH *ret_pcr_bank) { @@ -669,6 +675,21 @@ static int tpm2_make_pcr_session( goto finish; } + if (use_pin) { + rc = sym_Esys_PolicyAuthValue( + c, + session, + ESYS_TR_NONE, + ESYS_TR_NONE, + ESYS_TR_NONE); + if (rc != TSS2_RC_SUCCESS) { + r = log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Failed to add authValue policy to TPM: %s", + sym_Tss2_RC_Decode(rc)); + goto finish; + } + } + if (DEBUG_LOGGING || ret_policy_digest) { log_debug("Acquiring policy digest."); @@ -717,9 +738,22 @@ finish: return r; } +static void hash_pin(const char *pin, size_t len, uint8_t ret_digest[static SHA256_DIGEST_SIZE]) { + struct sha256_ctx hash; + + assert(pin); + + sha256_init_ctx(&hash); + sha256_process_bytes(pin, len, &hash); + sha256_finish_ctx(&hash, ret_digest); + + explicit_bzero_safe(&hash, sizeof(hash)); +} + int tpm2_seal( const char *device, uint32_t pcr_mask, + const char *pin, void **ret_secret, size_t *ret_secret_size, void **ret_blob, @@ -782,7 +816,8 @@ int tpm2_seal( if (r < 0) return r; - r = tpm2_make_pcr_session(c.esys_context, pcr_mask, UINT16_MAX, NULL, &policy_digest, &pcr_bank); + r = tpm2_make_pcr_session(c.esys_context, pcr_mask, UINT16_MAX, !!pin, NULL, &policy_digest, + &pcr_bank); if (r < 0) goto finish; @@ -813,6 +848,10 @@ int tpm2_seal( .size = sizeof(hmac_sensitive.sensitive), .sensitive.data.size = 32, }; + if (pin) { + hash_pin(pin, strlen(pin), hmac_sensitive.sensitive.userAuth.buffer); + hmac_sensitive.sensitive.userAuth.size = SHA256_DIGEST_SIZE; + } assert(sizeof(hmac_sensitive.sensitive.data.buffer) >= hmac_sensitive.sensitive.data.size); (void) tpm2_credit_random(c.esys_context); @@ -910,6 +949,7 @@ int tpm2_seal( r = 0; finish: + explicit_bzero_safe(&hmac_sensitive, sizeof(hmac_sensitive)); primary = flush_context_verbose(c.esys_context, primary); return r; } @@ -923,6 +963,7 @@ int tpm2_unseal( size_t blob_size, const void *known_policy_hash, size_t known_policy_hash_size, + const char *pin, void **ret_secret, size_t *ret_secret_size) { @@ -978,7 +1019,7 @@ int tpm2_unseal( if (r < 0) return r; - r = tpm2_make_pcr_session(c.esys_context, pcr_mask, pcr_bank, &session, &policy_digest, NULL); + r = tpm2_make_pcr_session(c.esys_context, pcr_mask, pcr_bank, !!pin, &session, &policy_digest, NULL); if (r < 0) goto finish; @@ -1005,11 +1046,38 @@ int tpm2_unseal( &public, &hmac_key); if (rc != TSS2_RC_SUCCESS) { - r = log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), - "Failed to load HMAC key in TPM: %s", sym_Tss2_RC_Decode(rc)); + /* If we're in dictionary attack lockout mode, we should see a lockout error here, which we + * need to translate for the caller. */ + if (rc == TPM2_RC_LOCKOUT) + r = log_error_errno( + SYNTHETIC_ERRNO(ENOLCK), + "TPM2 device is in dictionary attack lockout mode."); + else + r = log_error_errno( + SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Failed to load HMAC key in TPM: %s", + sym_Tss2_RC_Decode(rc)); goto finish; } + if (pin) { + TPM2B_AUTH auth = { + .size = SHA256_DIGEST_SIZE + }; + + hash_pin(pin, strlen(pin), auth.buffer); + + rc = sym_Esys_TR_SetAuth(c.esys_context, hmac_key, &auth); + explicit_bzero_safe(&auth, sizeof(auth)); + if (rc != TSS2_RC_SUCCESS) { + r = log_error_errno( + SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Failed to load PIN in TPM: %s", + sym_Tss2_RC_Decode(rc)); + goto finish; + } + } + log_debug("Unsealing HMAC key."); rc = sym_Esys_Unseal( @@ -1223,6 +1291,7 @@ int tpm2_make_luks2_json( size_t blob_size, const void *policy_hash, size_t policy_hash_size, + TPM2Flags flags, JsonVariant **ret) { _cleanup_(json_variant_unrefp) JsonVariant *v = NULL, *a = NULL; @@ -1263,7 +1332,9 @@ int tpm2_make_luks2_json( 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)))); + JSON_BUILD_PAIR("tpm2-policy-hash", JSON_BUILD_HEX(policy_hash, policy_hash_size)), + JSON_BUILD_PAIR("tpm2-pin", JSON_BUILD_BOOLEAN(flags & TPM2_FLAGS_USE_PIN))) + ); if (r < 0) return r; diff --git a/src/shared/tpm2-util.h b/src/shared/tpm2-util.h index cb57a847e27..5a9bcf8c24f 100644 --- a/src/shared/tpm2-util.h +++ b/src/shared/tpm2-util.h @@ -1,9 +1,15 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once +#include + #include "json.h" #include "macro.h" +typedef enum TPM2Flags { + TPM2_FLAGS_USE_PIN = 1 << 0, +} TPM2Flags; + #if HAVE_TPM2 #include @@ -20,10 +26,12 @@ extern TSS2_RC (*sym_Esys_GetRandom)(ESYS_CONTEXT *esysContext, ESYS_TR shandle1 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_PolicyAuthValue)(ESYS_CONTEXT *esysContext, ESYS_TR policySession, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3); 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); extern TSS2_RC (*sym_Esys_Startup)(ESYS_CONTEXT *esysContext, TPM2_SU startupType); +extern TSS2_RC (*sym_Esys_TR_SetAuth)(ESYS_CONTEXT *esysContext, ESYS_TR handle, TPM2B_AUTH const *authValue); extern TSS2_RC (*sym_Esys_Unseal)(ESYS_CONTEXT *esysContext, ESYS_TR itemHandle, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, TPM2B_SENSITIVE_DATA **outData); extern const char* (*sym_Tss2_RC_Decode)(TSS2_RC rc); @@ -35,8 +43,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, 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); +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_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 @@ -45,7 +53,7 @@ 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, uint16_t primary_alg, 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, TPM2Flags flags, JsonVariant **ret); #define TPM2_PCRS_MAX 24 diff --git a/test/TEST-70-TPM2/Makefile b/test/TEST-70-TPM2/Makefile new file mode 100644 index 00000000000..9f65d4ca4fd --- /dev/null +++ b/test/TEST-70-TPM2/Makefile @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +all setup run clean clean-again: + @TEST_BASE_DIR=../ ./test.sh --$@ + +.PHONY: all setup run clean clean-again diff --git a/test/TEST-70-TPM2/test.sh b/test/TEST-70-TPM2/test.sh new file mode 100755 index 00000000000..d716614bcf7 --- /dev/null +++ b/test/TEST-70-TPM2/test.sh @@ -0,0 +1,40 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -e + +TEST_DESCRIPTION="cryptenroll/cryptsetup with TPM2 devices" +IMAGE_NAME="tpm2" +TEST_NO_NSPAWN=1 +TEST_REQUIRE_INSTALL_TESTS=0 + +# shellcheck source=test/test-functions +. "${TEST_BASE_DIR:?}/test-functions" + +command -v swtpm >/dev/null 2>&1 || exit 0 +command -v tpm2_pcrextend >/dev/null 2>&1 || exit 0 + +test_append_files() { + ( + local workspace="${1:?}" + + instmods tpm tpm_tis tpm_ibmvtpm + install_dmevent + generate_module_dependencies + inst_binary tpm2_pcrextend + ) +} + +machine="$(uname -m)" +tpmdevice="tpm-tis" +if [ "$machine" = "ppc64le" ]; then + # tpm-spapr support was introduced in qemu 5.0.0. Skip test for old qemu versions. + qemu_min_version "5.0.0" || exit 0 + tpmdevice="tpm-spapr" +fi + +tpmstate=$(mktemp -d) +swtpm socket --tpm2 --tpmstate dir="$tpmstate" --ctrl type=unixio,path="$tpmstate/sock" & +trap 'kill %%; rm -rf $tpmstate' SIGINT EXIT +QEMU_OPTIONS="-chardev socket,id=chrtpm,path=$tpmstate/sock -tpmdev emulator,id=tpm0,chardev=chrtpm -device $tpmdevice,tpmdev=tpm0" + +do_test "$@" diff --git a/test/test-functions b/test/test-functions index e815ce1c585..6619a7a44b0 100644 --- a/test/test-functions +++ b/test/test-functions @@ -1213,7 +1213,7 @@ install_missing_libraries() { local lib path # A number of dependencies is now optional via dlopen, so the install # script will not pick them up, since it looks at linkage. - for lib in libcryptsetup libidn libidn2 pwquality libqrencode tss2-esys tss2-rc tss2-mu libfido2 libbpf libelf libdw; do + for lib in libcryptsetup libidn libidn2 pwquality libqrencode tss2-esys tss2-rc tss2-mu tss2-tcti-device libfido2 libbpf libelf libdw; do ddebug "Searching for $lib via pkg-config" if pkg-config --exists "$lib"; then path="$(pkg-config --variable=libdir "$lib")" diff --git a/test/units/testsuite-70.service b/test/units/testsuite-70.service new file mode 100644 index 00000000000..c13c2d51a32 --- /dev/null +++ b/test/units/testsuite-70.service @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +[Unit] +Description=TEST-70-TPM2 + +[Service] +Type=oneshot +ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh diff --git a/test/units/testsuite-70.sh b/test/units/testsuite-70.sh new file mode 100755 index 00000000000..f395ef4e5e1 --- /dev/null +++ b/test/units/testsuite-70.sh @@ -0,0 +1,48 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -ex + +export SYSTEMD_LOG_LEVEL=debug + + +# Prepare fresh disk image +img="/var/tmp/test.img" +dd if=/dev/zero of=$img bs=1024k count=20 status=none +echo -n passphrase >/tmp/passphrase +cryptsetup luksFormat -q --use-urandom $img /tmp/passphrase + +# Enroll unlock with default PCR policy +env PASSWORD=passphrase systemd-cryptenroll --tpm2-device=auto $img +/usr/lib/systemd/systemd-cryptsetup attach test-volume $img - tpm2-device=auto,headless=1 +/usr/lib/systemd/systemd-cryptsetup detach test-volume + +# Check with wrong PCR +tpm2_pcrextend 7:sha256=0000000000000000000000000000000000000000000000000000000000000000 +/usr/lib/systemd/systemd-cryptsetup attach test-volume $img - tpm2-device=auto,headless=1 && { echo 'unexpected success'; exit 1; } + +# Enroll unlock with PCR+PIN policy +systemd-cryptenroll --wipe-slot=tpm2 $img +env PASSWORD=passphrase NEWPIN=123456 systemd-cryptenroll --tpm2-device=auto --tpm2-with-pin=true $img +env PIN=123456 /usr/lib/systemd/systemd-cryptsetup attach test-volume $img - tpm2-device=auto,headless=1 +/usr/lib/systemd/systemd-cryptsetup detach test-volume + +# Check failure with wrong PIN +env PIN=123457 /usr/lib/systemd/systemd-cryptsetup attach test-volume $img - tpm2-device=auto,headless=1 && { echo 'unexpected success'; exit 1; } + +# Check failure with wrong PCR (and correct PIN) +tpm2_pcrextend 7:sha256=0000000000000000000000000000000000000000000000000000000000000000 +env PIN=123456 /usr/lib/systemd/systemd-cryptsetup attach test-volume $img - tpm2-device=auto,headless=1 && { echo 'unexpected success'; exit 1; } + +# Enroll unlock with PCR 0+7 +systemd-cryptenroll --wipe-slot=tpm2 $img +env PASSWORD=passphrase systemd-cryptenroll --tpm2-device=auto --tpm2-pcrs=0+7 $img +/usr/lib/systemd/systemd-cryptsetup attach test-volume $img - tpm2-device=auto,headless=1 +/usr/lib/systemd/systemd-cryptsetup detach test-volume + +# Check with wrong PCR 0 +tpm2_pcrextend 0:sha256=0000000000000000000000000000000000000000000000000000000000000000 +/usr/lib/systemd/systemd-cryptsetup attach test-volume $img - tpm2-device=auto,headless=1 && exit 1 + +echo OK >/testok + +exit 0