diff --git a/man/systemd-cryptenroll.xml b/man/systemd-cryptenroll.xml index e6bbe2be63..95f5a450b6 100644 --- a/man/systemd-cryptenroll.xml +++ b/man/systemd-cryptenroll.xml @@ -222,10 +222,10 @@ Limitations Note that currently when enrolling a new key of one of the five supported types listed above, it is - required to first provide a passphrase, a recovery key or a FIDO2 token. It's currently not supported to - unlock a device with a TPM2/PKCS#11 key in order to enroll a new TPM2/PKCS#11 key. Thus, if in future key - roll-over is desired it's generally recommended to ensure a passphrase, a recovery key or a FIDO2 token - is always enrolled. + required to first provide a passphrase, a recovery key, a FIDO2 token, or a TPM2 key. It's currently not + supported to unlock a device with a PKCS#11 key in order to enroll a new PKCS#11 key. Thus, if in future + key roll-over is desired it's generally recommended to ensure a passphrase, a recovery key, a FIDO2 + token, or a TPM2 key is always enrolled. Also note that support for enrolling multiple FIDO2 tokens is currently limited. When multiple FIDO2 tokens are enrolled, systemd-cryptseup will perform pre-flight requests to attempt to @@ -310,6 +310,18 @@ + + PATH + + Use a TPM2 device insteaad of a password/passhprase read from stdin to unlock the + volume. Expects a device node path referring to the TPM2 chip (e.g. /dev/tpmrm0). + Alternatively the special value auto may be specified, in order to automatically + determine the device node of a currently discovered TPM2 device (of which there must be exactly one). + + + + + URI diff --git a/shell-completion/bash/systemd-cryptenroll b/shell-completion/bash/systemd-cryptenroll index 1723f75bee..f40a33bee6 100644 --- a/shell-completion/bash/systemd-cryptenroll +++ b/shell-completion/bash/systemd-cryptenroll @@ -52,6 +52,7 @@ _systemd_cryptenroll() { --password --recovery-key' [ARG]='--unlock-key-file --unlock-fido2-device + --unlock-tpm2-device --pkcs11-token-uri --fido2-credential-algorithm --fido2-device @@ -81,6 +82,9 @@ _systemd_cryptenroll() { --unlock-fido2-device) comps="auto $(__get_fido2_devices)" ;; + --unlock-tpm2-device) + comps="auto $(__get_tpm2_devices)" + ;; --pkcs11-token-uri) comps='auto list pkcs11:' ;; diff --git a/src/cryptenroll/cryptenroll-tpm2.c b/src/cryptenroll/cryptenroll-tpm2.c index b556c70931..fb862a3329 100644 --- a/src/cryptenroll/cryptenroll-tpm2.c +++ b/src/cryptenroll/cryptenroll-tpm2.c @@ -3,10 +3,13 @@ #include "alloc-util.h" #include "ask-password-api.h" #include "cryptenroll-tpm2.h" +#include "cryptsetup-tpm2.h" #include "env-util.h" +#include "errno-util.h" #include "fileio.h" #include "hexdecoct.h" #include "json.h" +#include "log.h" #include "memory-util.h" #include "random-util.h" #include "sha256.h" @@ -129,6 +132,114 @@ static int get_pin(char **ret_pin_str, TPM2Flags *ret_flags) { return 0; } +int load_volume_key_tpm2( + struct crypt_device *cd, + const char *cd_node, + const char *device, + void *ret_vk, + size_t *ret_vks) { + + _cleanup_(iovec_done_erase) struct iovec decrypted_key = {}; + _cleanup_(erase_and_freep) char *passphrase = NULL; + ssize_t passphrase_size; + int r; + + assert_se(cd); + assert_se(cd_node); + assert_se(ret_vk); + assert_se(ret_vks); + + bool found_some = false; + int token = 0; /* first token to look at */ + + for (;;) { + _cleanup_(iovec_done) struct iovec pubkey = {}, salt = {}, srk = {}, pcrlock_nv = {}; + _cleanup_(iovec_done) struct iovec blob = {}, policy_hash = {}; + uint32_t hash_pcr_mask, pubkey_pcr_mask; + uint16_t pcr_bank, primary_alg; + TPM2Flags tpm2_flags; + int keyslot; + + r = find_tpm2_auto_data( + cd, + UINT32_MAX, + token, + &hash_pcr_mask, + &pcr_bank, + &pubkey, + &pubkey_pcr_mask, + &primary_alg, + &blob, + &policy_hash, + &salt, + &srk, + &pcrlock_nv, + &tpm2_flags, + &keyslot, + &token); + if (r == -ENXIO) + return log_full_errno(LOG_NOTICE, + SYNTHETIC_ERRNO(EAGAIN), + found_some + ? "No TPM2 metadata matching the current system state found in LUKS2 header." + : "No TPM2 metadata enrolled in LUKS2 header."); + if (ERRNO_IS_NEG_NOT_SUPPORTED(r)) + /* TPM2 support not compiled in? */ + return log_debug_errno(SYNTHETIC_ERRNO(EAGAIN), "TPM2 support not available."); + if (r < 0) + return r; + + found_some = true; + + r = acquire_tpm2_key( + cd_node, + device, + hash_pcr_mask, + pcr_bank, + &pubkey, + pubkey_pcr_mask, + /* signature_path= */ NULL, + /* pcrlock_path= */ NULL, + primary_alg, + /* key_file= */ NULL, /* key_file_size= */ 0, /* key_file_offset= */ 0, /* no key file */ + &blob, + &policy_hash, + &salt, + &srk, + &pcrlock_nv, + tpm2_flags, + /* until= */ 0, + /* headless= */ false, + /* ask_password_flags */ false, + &decrypted_key); + if (IN_SET(r, -EACCES, -ENOLCK)) + return log_notice_errno(SYNTHETIC_ERRNO(EAGAIN), "TPM2 PIN unlock failed"); + if (r != -EPERM) + break; + + token++; /* try a different token next time */ + } + + if (r < 0) + return log_error_errno(r, "Unlocking via TPM2 device failed: %m"); + + passphrase_size = base64mem(decrypted_key.iov_base, decrypted_key.iov_len, &passphrase); + if (passphrase_size < 0) + return log_oom(); + + r = crypt_volume_key_get( + cd, + CRYPT_ANY_SLOT, + ret_vk, + ret_vks, + passphrase, + passphrase_size); + if (r < 0) + return log_error_errno(r, "Unlocking via TPM2 device failed: %m"); + + return r; +} + int enroll_tpm2(struct crypt_device *cd, const void *volume_key, size_t volume_key_size, @@ -141,7 +252,8 @@ int enroll_tpm2(struct crypt_device *cd, uint32_t pubkey_pcr_mask, const char *signature_path, bool use_pin, - const char *pcrlock_path) { + const char *pcrlock_path, + int *ret_slot_to_wipe) { _cleanup_(json_variant_unrefp) JsonVariant *v = NULL, *signature_json = NULL; _cleanup_(erase_and_freep) char *base64_encoded = NULL; @@ -150,7 +262,7 @@ int enroll_tpm2(struct crypt_device *cd, const char *node; _cleanup_(erase_and_freep) char *pin_str = NULL; ssize_t base64_encoded_size; - int r, keyslot; + int r, keyslot, slot_to_wipe = -1; TPM2Flags flags = 0; uint8_t binary_salt[SHA256_DIGEST_SIZE] = {}; /* @@ -166,6 +278,7 @@ int enroll_tpm2(struct crypt_device *cd, assert(volume_key_size > 0); assert(tpm2_pcr_values_valid(hash_pcr_values, n_hash_pcr_values)); assert(TPM2_PCR_MASK_VALID(pubkey_pcr_mask)); + assert(ret_slot_to_wipe); assert_se(node = crypt_get_device_name(cd)); @@ -305,8 +418,12 @@ int enroll_tpm2(struct crypt_device *cd, log_debug_errno(r, "PCR policy hash not yet enrolled, enrolling now."); else if (r < 0) return r; - else { + else if (use_pin) { + log_debug("This PCR set is already enrolled, re-enrolling anyway to update PIN."); + slot_to_wipe = r; + } else { log_info("This PCR set is already enrolled, executing no operation."); + *ret_slot_to_wipe = slot_to_wipe; return r; /* return existing keyslot, so that wiping won't kill it */ } @@ -376,5 +493,7 @@ int enroll_tpm2(struct crypt_device *cd, return log_error_errno(r, "Failed to add TPM2 JSON token to LUKS2 header: %m"); log_info("New TPM2 token enrolled as key slot %i.", keyslot); + + *ret_slot_to_wipe = slot_to_wipe; return keyslot; } diff --git a/src/cryptenroll/cryptenroll-tpm2.h b/src/cryptenroll/cryptenroll-tpm2.h index 2fbcdd4b2f..634d7637b9 100644 --- a/src/cryptenroll/cryptenroll-tpm2.h +++ b/src/cryptenroll/cryptenroll-tpm2.h @@ -8,9 +8,15 @@ #include "tpm2-util.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 seal_key_handle, const char *device_key, Tpm2PCRValue *hash_pcrs, size_t n_hash_pcrs, const char *pubkey_path, uint32_t pubkey_pcr_mask, const char *signature_path, bool use_pin, const char *pcrlock_path); +int load_volume_key_tpm2(struct crypt_device *cd, const char *cd_node, const char *device, void *ret_vk, size_t *ret_vks); +int enroll_tpm2(struct crypt_device *cd, const void *volume_key, size_t volume_key_size, const char *device, uint32_t seal_key_handle, const char *device_key, Tpm2PCRValue *hash_pcrs, size_t n_hash_pcrs, const char *pubkey_path, uint32_t pubkey_pcr_mask, const char *signature_path, bool use_pin, const char *pcrlock_path, int *ret_slot_to_wipe); #else -static inline int enroll_tpm2(struct crypt_device *cd, const void *volume_key, size_t volume_key_size, const char *device, uint32_t seal_key_handle, const char *device_key, Tpm2PCRValue *hash_pcrs, size_t n_hash_pcrs, const char *pubkey_path, uint32_t pubkey_pcr_mask, const char *signature_path, bool use_pin, const char *pcrlock_path) { +static inline int load_volume_key_tpm2(struct crypt_device *cd, const char *cd_node, const char *device, void *ret_vk, size_t *ret_vks) { + return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), + "TPM2 unlocking not supported."); +} + +static inline int enroll_tpm2(struct crypt_device *cd, const void *volume_key, size_t volume_key_size, const char *device, uint32_t seal_key_handle, const char *device_key, Tpm2PCRValue *hash_pcrs, size_t n_hash_pcrs, const char *pubkey_path, uint32_t pubkey_pcr_mask, const char *signature_path, bool use_pin, const char *pcrlock_path, int *slot_to_wipe) { 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 79d06e7414..5b9fc15dd8 100644 --- a/src/cryptenroll/cryptenroll.c +++ b/src/cryptenroll/cryptenroll.c @@ -1,6 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include +#include #include "ask-password-api.h" #include "build.h" @@ -33,6 +34,7 @@ static EnrollType arg_enroll_type = _ENROLL_TYPE_INVALID; static char *arg_unlock_keyfile = NULL; static UnlockType arg_unlock_type = UNLOCK_PASSWORD; static char *arg_unlock_fido2_device = NULL; +static char *arg_unlock_tpm2_device = NULL; static char *arg_pkcs11_token_uri = NULL; static char *arg_fido2_device = NULL; static char *arg_tpm2_device = NULL; @@ -61,6 +63,7 @@ assert_cc(sizeof(arg_wipe_slots_mask) * 8 >= _ENROLL_TYPE_MAX); STATIC_DESTRUCTOR_REGISTER(arg_unlock_keyfile, freep); STATIC_DESTRUCTOR_REGISTER(arg_unlock_fido2_device, freep); +STATIC_DESTRUCTOR_REGISTER(arg_unlock_tpm2_device, freep); STATIC_DESTRUCTOR_REGISTER(arg_pkcs11_token_uri, freep); STATIC_DESTRUCTOR_REGISTER(arg_fido2_device, freep); STATIC_DESTRUCTOR_REGISTER(arg_tpm2_device, freep); @@ -117,6 +120,8 @@ static int help(void) { " Use a file to unlock the volume\n" " --unlock-fido2-device=PATH\n" " Use a FIDO2 device to unlock the volume\n" + " --unlock-tpm2-device=PATH\n" + " Use a TPM2 device to unlock the volume\n" "\n%3$sSimple Enrollment:%4$s\n" " --password Enroll a user-supplied password\n" " --recovery-key Enroll a recovery key\n" @@ -172,6 +177,7 @@ static int parse_argv(int argc, char *argv[]) { ARG_RECOVERY_KEY, ARG_UNLOCK_KEYFILE, ARG_UNLOCK_FIDO2_DEVICE, + ARG_UNLOCK_TPM2_DEVICE, ARG_PKCS11_TOKEN_URI, ARG_FIDO2_DEVICE, ARG_TPM2_DEVICE, @@ -197,6 +203,7 @@ static int parse_argv(int argc, char *argv[]) { { "recovery-key", no_argument, NULL, ARG_RECOVERY_KEY }, { "unlock-key-file", required_argument, NULL, ARG_UNLOCK_KEYFILE }, { "unlock-fido2-device", required_argument, NULL, ARG_UNLOCK_FIDO2_DEVICE }, + { "unlock-tpm2-device", required_argument, NULL, ARG_UNLOCK_TPM2_DEVICE }, { "pkcs11-token-uri", required_argument, NULL, ARG_PKCS11_TOKEN_URI }, { "fido2-credential-algorithm", required_argument, NULL, ARG_FIDO2_CRED_ALG }, { "fido2-device", required_argument, NULL, ARG_FIDO2_DEVICE }, @@ -304,6 +311,26 @@ static int parse_argv(int argc, char *argv[]) { break; } + case ARG_UNLOCK_TPM2_DEVICE: { + _cleanup_free_ char *device = NULL; + + if (arg_unlock_type != UNLOCK_PASSWORD) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Multiple unlock methods specified at once, refusing."); + + assert(!arg_unlock_tpm2_device); + + if (!streq(optarg, "auto")) { + device = strdup(optarg); + if (!device) + return log_oom(); + } + + arg_unlock_type = UNLOCK_TPM2; + arg_unlock_tpm2_device = TAKE_PTR(device); + break; + } + case ARG_PKCS11_TOKEN_URI: { _cleanup_free_ char *uri = NULL; @@ -666,6 +693,10 @@ static int prepare_luks( switch (arg_unlock_type) { + case UNLOCK_PASSWORD: + r = load_volume_key_password(cd, arg_node, vk, &vks); + break; + case UNLOCK_KEYFILE: r = load_volume_key_keyfile(cd, vk, &vks); break; @@ -674,8 +705,8 @@ static int prepare_luks( r = load_volume_key_fido2(cd, arg_node, arg_unlock_fido2_device, vk, &vks); break; - case UNLOCK_PASSWORD: - r = load_volume_key_password(cd, arg_node, vk, &vks); + case UNLOCK_TPM2: + r = load_volume_key_tpm2(cd, arg_node, arg_unlock_tpm2_device, vk, &vks); break; default: @@ -696,7 +727,7 @@ static int run(int argc, char *argv[]) { _cleanup_(crypt_freep) struct crypt_device *cd = NULL; _cleanup_(erase_and_freep) void *vk = NULL; size_t vks; - int slot, r; + int slot, slot_to_wipe, r; log_show_color(true); log_parse_environment(); @@ -706,6 +737,9 @@ static int run(int argc, char *argv[]) { if (r <= 0) return r; + /* A delicious drop of snake oil */ + (void) mlockall(MCL_FUTURE); + cryptsetup_enable_logging(NULL); if (arg_enroll_type < 0) @@ -734,9 +768,21 @@ static int run(int argc, char *argv[]) { break; case ENROLL_TPM2: - slot = enroll_tpm2(cd, vk, vks, arg_tpm2_device, arg_tpm2_seal_key_handle, arg_tpm2_device_key, arg_tpm2_hash_pcr_values, arg_tpm2_n_hash_pcr_values, arg_tpm2_public_key, arg_tpm2_public_key_pcr_mask, arg_tpm2_signature, arg_tpm2_pin, arg_tpm2_pcrlock); - break; + slot = enroll_tpm2(cd, vk, vks, arg_tpm2_device, arg_tpm2_seal_key_handle, arg_tpm2_device_key, arg_tpm2_hash_pcr_values, arg_tpm2_n_hash_pcr_values, arg_tpm2_public_key, arg_tpm2_public_key_pcr_mask, arg_tpm2_signature, arg_tpm2_pin, arg_tpm2_pcrlock, &slot_to_wipe); + if (slot >= 0 && slot_to_wipe >= 0) { + /* Updating PIN on an existing enrollment */ + r = wipe_slots( + cd, + &slot_to_wipe, + /* n_explicit_slots= */ 1, + WIPE_EXPLICIT, + /* by_mask= */ 0, + /* except_slot= */ -1); + if (r < 0) + return r; + } + break; case _ENROLL_TYPE_INVALID: /* List enrolled slots if we are called without anything to enroll or wipe */ if (!wipe_requested()) diff --git a/src/cryptenroll/cryptenroll.h b/src/cryptenroll/cryptenroll.h index 335d9ccd71..08ded3e0e8 100644 --- a/src/cryptenroll/cryptenroll.h +++ b/src/cryptenroll/cryptenroll.h @@ -17,6 +17,7 @@ typedef enum UnlockType { UNLOCK_PASSWORD, UNLOCK_KEYFILE, UNLOCK_FIDO2, + UNLOCK_TPM2, _UNLOCK_TYPE_MAX, _UNLOCK_TYPE_INVALID = -EINVAL, } UnlockType; diff --git a/src/cryptsetup/cryptsetup.c b/src/cryptsetup/cryptsetup.c index 0840a71548..1a02730037 100644 --- a/src/cryptsetup/cryptsetup.c +++ b/src/cryptsetup/cryptsetup.c @@ -1724,7 +1724,6 @@ static int attach_luks_or_plain_or_bitlk_by_tpm2( } if (r == -EOPNOTSUPP) { /* Plugin not available, let's process TPM2 stuff right here instead */ - _cleanup_(iovec_done) struct iovec blob = {}, policy_hash = {}; bool found_some = false; int token = 0; /* first token to look at */ @@ -1734,6 +1733,7 @@ static int attach_luks_or_plain_or_bitlk_by_tpm2( for (;;) { _cleanup_(iovec_done) struct iovec pubkey = {}, salt = {}, srk = {}, pcrlock_nv = {}; + _cleanup_(iovec_done) struct iovec blob = {}, policy_hash = {}; uint32_t hash_pcr_mask, pubkey_pcr_mask; uint16_t pcr_bank, primary_alg; TPM2Flags tpm2_flags; diff --git a/src/cryptsetup/meson.build b/src/cryptsetup/meson.build index 90e2be7a91..9ccc0982b5 100644 --- a/src/cryptsetup/meson.build +++ b/src/cryptsetup/meson.build @@ -11,10 +11,6 @@ if conf.get('HAVE_P11KIT') == 1 systemd_cryptsetup_sources += files('cryptsetup-pkcs11.c') endif -if conf.get('HAVE_TPM2') == 1 - systemd_cryptsetup_sources += files('cryptsetup-tpm2.c') -endif - executables += [ executable_template + { 'name' : 'systemd-cryptsetup', diff --git a/src/cryptsetup/cryptsetup-tpm2.c b/src/shared/cryptsetup-tpm2.c similarity index 100% rename from src/cryptsetup/cryptsetup-tpm2.c rename to src/shared/cryptsetup-tpm2.c diff --git a/src/cryptsetup/cryptsetup-tpm2.h b/src/shared/cryptsetup-tpm2.h similarity index 100% rename from src/cryptsetup/cryptsetup-tpm2.h rename to src/shared/cryptsetup-tpm2.h diff --git a/src/shared/meson.build b/src/shared/meson.build index 2a39bb04df..dc9adeddc1 100644 --- a/src/shared/meson.build +++ b/src/shared/meson.build @@ -250,6 +250,10 @@ if conf.get('HAVE_LIBFIDO2') == 1 and conf.get('HAVE_LIBCRYPTSETUP') == 1 shared_sources += files('cryptsetup-fido2.c') endif +if conf.get('HAVE_TPM2') == 1 and conf.get('HAVE_LIBCRYPTSETUP') == 1 + shared_sources += files('cryptsetup-tpm2.c') +endif + generate_ip_protocol_list = find_program('generate-ip-protocol-list.sh') ip_protocol_list_txt = custom_target( 'ip-protocol-list.txt', diff --git a/test/units/testsuite-70.cryptenroll.sh b/test/units/testsuite-70.cryptenroll.sh index 3f8c14e54e..f18ef020a7 100755 --- a/test/units/testsuite-70.cryptenroll.sh +++ b/test/units/testsuite-70.cryptenroll.sh @@ -59,6 +59,18 @@ systemd-cryptenroll --fido2-with-user-verification=false "$IMAGE" systemd-cryptenroll --tpm2-pcrs=8 "$IMAGE" systemd-cryptenroll --tpm2-pcrs=boot-loader-code+boot-loader-config "$IMAGE" +# Unlocking using TPM2 +PASSWORD=foo systemd-cryptenroll --tpm2-device=auto "$IMAGE" +systemd-cryptenroll --unlock-tpm2-device=auto --recovery-key "$IMAGE" +systemd-cryptenroll --unlock-tpm2-device=auto --tpm2-device=auto --wipe-slot=tpm2 "$IMAGE" + +# Add PIN to TPM2 enrollment +NEWPIN=1234 systemd-cryptenroll --unlock-tpm2-device=auto --tpm2-device=auto --tpm2-with-pin=yes "$IMAGE" + +# Change PIN on TPM2 enrollment +PIN=1234 NEWPIN=4321 systemd-cryptenroll --unlock-tpm2-device=auto --tpm2-device=auto --tpm2-with-pin=yes "$IMAGE" +PIN=4321 systemd-cryptenroll --unlock-tpm2-device=auto --recovery-key "$IMAGE" + (! systemd-cryptenroll --fido2-with-client-pin=false) (! systemd-cryptenroll --fido2-with-user-presence=f "$IMAGE" /tmp/foo) (! systemd-cryptenroll --fido2-with-client-pin=1234 "$IMAGE")