diff --git a/meson.build b/meson.build index 21ab85518f..2f8463ab8f 100644 --- a/meson.build +++ b/meson.build @@ -2382,6 +2382,7 @@ if conf.get('HAVE_LIBCRYPTSETUP') == 1 src/cryptsetup/cryptsetup-keyfile.c src/cryptsetup/cryptsetup-keyfile.h src/cryptsetup/cryptsetup-pkcs11.h + src/cryptsetup/cryptsetup-tpm2.h src/cryptsetup/cryptsetup.c '''.split()) @@ -2393,6 +2394,10 @@ if conf.get('HAVE_LIBCRYPTSETUP') == 1 systemd_cryptsetup_sources += files('src/cryptsetup/cryptsetup-fido2.c') endif + if conf.get('HAVE_TPM2') == 1 + systemd_cryptsetup_sources += files('src/cryptsetup/cryptsetup-tpm2.c') + endif + executable( 'systemd-cryptsetup', systemd_cryptsetup_sources, diff --git a/src/cryptsetup/cryptsetup-tpm2.c b/src/cryptsetup/cryptsetup-tpm2.c new file mode 100644 index 0000000000..af176c8806 --- /dev/null +++ b/src/cryptsetup/cryptsetup-tpm2.c @@ -0,0 +1,168 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "alloc-util.h" +#include "cryptsetup-tpm2.h" +#include "fileio.h" +#include "hexdecoct.h" +#include "json.h" +#include "parse-util.h" +#include "random-util.h" +#include "tpm2-util.h" + +int acquire_tpm2_key( + const char *volume_name, + const char *device, + uint32_t pcr_mask, + const char *key_file, + size_t key_file_size, + uint64_t key_file_offset, + const void *key_data, + size_t key_data_size, + const void *policy_hash, + size_t policy_hash_size, + void **ret_decrypted_key, + size_t *ret_decrypted_key_size) { + + _cleanup_free_ void *loaded_blob = NULL; + _cleanup_free_ char *auto_device = NULL; + size_t blob_size; + const void *blob; + int r; + + if (!device) { + r = tpm2_find_device_auto(LOG_DEBUG, &auto_device); + if (r == -ENODEV) + return -EAGAIN; /* Tell the caller to wait for a TPM2 device to show up */ + if (r < 0) + return r; + + device = auto_device; + } + + if (key_data) { + blob = key_data; + blob_size = key_data_size; + } else { + _cleanup_free_ char *bindname = NULL; + + /* If we read the salt via AF_UNIX, make this client recognizable */ + if (asprintf(&bindname, "@%" PRIx64"/cryptsetup-tpm2/%s", random_u64(), volume_name) < 0) + return log_oom(); + + r = read_full_file_full( + AT_FDCWD, key_file, + key_file_offset == 0 ? UINT64_MAX : key_file_offset, + key_file_size == 0 ? SIZE_MAX : key_file_size, + READ_FULL_FILE_CONNECT_SOCKET, + bindname, + (char**) &loaded_blob, &blob_size); + if (r < 0) + return r; + + blob = loaded_blob; + } + + return tpm2_unseal(device, pcr_mask, blob, blob_size, policy_hash, policy_hash_size, ret_decrypted_key, ret_decrypted_key_size); +} + +int find_tpm2_auto_data( + struct crypt_device *cd, + uint32_t search_pcr_mask, + int start_token, + uint32_t *ret_pcr_mask, + void **ret_blob, + size_t *ret_blob_size, + void **ret_policy_hash, + size_t *ret_policy_hash_size, + int *ret_keyslot, + int *ret_token) { + + _cleanup_free_ void *blob = NULL, *policy_hash = NULL; + size_t blob_size = 0, policy_hash_size = 0; + int r, keyslot = -1, token = -1; + uint32_t pcr_mask = 0; + + assert(cd); + + for (token = start_token; token < LUKS2_TOKENS_MAX; token++) { + _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; + JsonVariant *w, *e; + + r = cryptsetup_get_token_as_json(cd, token, "systemd-tpm2", &v); + if (IN_SET(r, -ENOENT, -EINVAL, -EMEDIUMTYPE)) + continue; + if (r < 0) + return log_error_errno(r, "Failed to read JSON token data off disk: %m"); + + w = json_variant_by_key(v, "tpm2-pcrs"); + if (!w || !json_variant_is_array(w)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "TPM2 token data lacks 'tpm2-pcrs' field."); + + assert(pcr_mask == 0); + JSON_VARIANT_ARRAY_FOREACH(e, w) { + uintmax_t u; + + if (!json_variant_is_number(e)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "TPM2 PCR is not a number."); + + u = json_variant_unsigned(e); + if (u >= TPM2_PCRS_MAX) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "TPM2 PCR number out of range."); + + pcr_mask |= UINT32_C(1) << u; + } + + if (search_pcr_mask != UINT32_MAX && + search_pcr_mask != pcr_mask) /* PCR mask doesn't match what is configured, ignore this entry */ + continue; + + assert(!blob); + w = json_variant_by_key(v, "tpm2-blob"); + if (!w || !json_variant_is_string(w)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "TPM2 token data lacks 'tpm2-blob' field."); + + r = unbase64mem(json_variant_string(w), (size_t) -1, &blob, &blob_size); + if (r < 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Invalid base64 data in 'tpm2-blob' field."); + + assert(!policy_hash); + w = json_variant_by_key(v, "tpm2-policy-hash"); + if (!w || !json_variant_is_string(w)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "TPM2 token data lacks 'tpm2-policy-hash' field."); + + r = unhexmem(json_variant_string(w), (size_t) -1, &policy_hash, &policy_hash_size); + if (r < 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Invalid base64 data in 'tpm2-policy-hash' field."); + + assert(keyslot < 0); + keyslot = cryptsetup_get_keyslot_from_token(v); + if (keyslot < 0) + return log_error_errno(keyslot, "Failed to extract keyslot index from TPM2 JSON data: %m"); + + break; + } + + if (!blob) + return log_error_errno(SYNTHETIC_ERRNO(ENXIO), + "No valid TPM2 token data found."); + + if (start_token <= 0) + log_info("Automatically discovered security TPM2 token unlocks volume."); + + *ret_pcr_mask = pcr_mask; + *ret_blob = TAKE_PTR(blob); + *ret_blob_size = blob_size; + *ret_policy_hash = TAKE_PTR(policy_hash); + *ret_policy_hash_size = policy_hash_size; + *ret_keyslot = keyslot; + *ret_token = token; + + return 0; +} diff --git a/src/cryptsetup/cryptsetup-tpm2.h b/src/cryptsetup/cryptsetup-tpm2.h new file mode 100644 index 0000000000..8ddf301a63 --- /dev/null +++ b/src/cryptsetup/cryptsetup-tpm2.h @@ -0,0 +1,74 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include + +#include "cryptsetup-util.h" +#include "log.h" +#include "time-util.h" + +#if HAVE_TPM2 + +int acquire_tpm2_key( + const char *volume_name, + const char *device, + uint32_t pcr_mask, + const char *key_file, + size_t key_file_size, + uint64_t key_file_offset, + const void *key_data, + size_t key_data_size, + const void *policy_hash, + size_t policy_hash_size, + void **ret_decrypted_key, + size_t *ret_decrypted_key_size); + +int find_tpm2_auto_data( + struct crypt_device *cd, + uint32_t search_pcr_mask, + int start_token, + uint32_t *ret_pcr_mask, + void **ret_blob, + size_t *ret_blob_size, + void **ret_policy_hash, + size_t *ret_policy_hash_size, + int *ret_keyslot, + int *ret_token); + +#else + +static inline int acquire_tpm2_key( + const char *volume_name, + const char *device, + uint32_t pcr_mask, + const char *key_file, + size_t key_file_size, + uint64_t key_file_offset, + const void *key_data, + size_t key_data_size, + const void *policy_hash, + size_t policy_hash_size, + void **ret_decrypted_key, + size_t *ret_decrypted_key_size) { + + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), + "TPM2 support not available."); +} + +static inline int find_tpm2_auto_data( + struct crypt_device *cd, + uint32_t search_pcr_mask, + int start_token, + uint32_t *ret_pcr_mask, + void **ret_blob, + size_t *ret_blob_size, + void **ret_policy_hash, + size_t *ret_policy_hash_size, + int *ret_keyslot, + int *ret_token) { + + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), + "TPM2 support not available."); +} + +#endif diff --git a/src/cryptsetup/cryptsetup.c b/src/cryptsetup/cryptsetup.c index d9df81b74a..538cda9c86 100644 --- a/src/cryptsetup/cryptsetup.c +++ b/src/cryptsetup/cryptsetup.c @@ -14,6 +14,7 @@ #include "cryptsetup-fido2.h" #include "cryptsetup-keyfile.h" #include "cryptsetup-pkcs11.h" +#include "cryptsetup-tpm2.h" #include "cryptsetup-util.h" #include "device-util.h" #include "escape.h" @@ -34,6 +35,7 @@ #include "random-util.h" #include "string-util.h" #include "strv.h" +#include "tpm2-util.h" /* internal helper */ #define ANY_LUKS "LUKS" @@ -72,6 +74,9 @@ static bool arg_fido2_device_auto = false; static void *arg_fido2_cid = NULL; static size_t arg_fido2_cid_size = 0; 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_DESTRUCTOR_REGISTER(arg_cipher, freep); STATIC_DESTRUCTOR_REGISTER(arg_hash, freep); @@ -81,6 +86,7 @@ STATIC_DESTRUCTOR_REGISTER(arg_pkcs11_uri, freep); STATIC_DESTRUCTOR_REGISTER(arg_fido2_device, freep); STATIC_DESTRUCTOR_REGISTER(arg_fido2_cid, freep); STATIC_DESTRUCTOR_REGISTER(arg_fido2_rp_id, freep); +STATIC_DESTRUCTOR_REGISTER(arg_tpm2_device, freep); /* Options Debian's crypttab knows we don't: @@ -327,6 +333,36 @@ static int parse_one_option(const char *option) { if (r < 0) return log_oom(); + } else if ((val = startswith(option, "tpm2-device="))) { + + if (streq(val, "auto")) { + arg_tpm2_device = mfree(arg_tpm2_device); + arg_tpm2_device_auto = true; + } else { + r = free_and_strdup(&arg_tpm2_device, val); + if (r < 0) + return log_oom(); + + arg_tpm2_device_auto = false; + } + + } else if ((val = startswith(option, "tpm2-pcrs="))) { + + if (isempty(val)) + arg_tpm2_pcr_mask = 0; + else { + uint32_t mask; + + r = tpm2_parse_pcrs(val, &mask); + if (r < 0) + return r; + + if (arg_tpm2_pcr_mask == UINT32_MAX) + arg_tpm2_pcr_mask = mask; + else + arg_tpm2_pcr_mask |= mask; + } + } else if ((val = startswith(option, "try-empty-password="))) { r = parse_boolean(val); @@ -556,10 +592,10 @@ static int attach_tcrypt( assert(name); assert(key_file || key_data || !strv_isempty(passwords)); - if (arg_pkcs11_uri || arg_pkcs11_uri_auto || arg_fido2_device || arg_fido2_device_auto) + if (arg_pkcs11_uri || arg_pkcs11_uri_auto || arg_fido2_device || arg_fido2_device_auto || arg_tpm2_device || arg_tpm2_device_auto) /* Ask for a regular password */ return log_error_errno(SYNTHETIC_ERRNO(EAGAIN), - "Sorry, but tcrypt devices are currently not supported in conjunction with pkcs11/fido2 support."); + "Sorry, but tcrypt devices are currently not supported in conjunction with pkcs11/fido2/tpm2 support."); if (arg_tcrypt_hidden) params.flags |= CRYPT_TCRYPT_HIDDEN_HEADER; @@ -907,6 +943,190 @@ static int attach_luks_or_plain_or_bitlk_by_pkcs11( return 0; } +static int make_tpm2_device_monitor(sd_event *event, sd_device_monitor **ret) { + _cleanup_(sd_device_monitor_unrefp) sd_device_monitor *monitor = NULL; + int r; + + assert(ret); + + r = sd_device_monitor_new(&monitor); + if (r < 0) + return log_error_errno(r, "Failed to allocate device monitor: %m"); + + r = sd_device_monitor_filter_add_match_subsystem_devtype(monitor, "tpmrm", NULL); + if (r < 0) + return log_error_errno(r, "Failed to configure device monitor: %m"); + + r = sd_device_monitor_attach_event(monitor, event); + if (r < 0) + return log_error_errno(r, "Failed to attach device monitor: %m"); + + r = sd_device_monitor_start(monitor, NULL, NULL); + if (r < 0) + return log_error_errno(r, "Failed to start device monitor: %m"); + + *ret = TAKE_PTR(monitor); + return 0; +} + +static int attach_luks_or_plain_or_bitlk_by_tpm2( + struct crypt_device *cd, + const char *name, + const char *key_file, + const void *key_data, + size_t key_data_size, + usec_t until, + uint32_t flags, + bool pass_volume_key) { + + _cleanup_(sd_device_monitor_unrefp) sd_device_monitor *monitor = NULL; + _cleanup_(erase_and_freep) void *decrypted_key = NULL; + _cleanup_(sd_event_unrefp) sd_event *event = NULL; + _cleanup_free_ char *friendly = NULL; + int keyslot = arg_key_slot, r; + size_t decrypted_key_size; + + assert(cd); + assert(name); + assert(arg_tpm2_device || arg_tpm2_device_auto); + + friendly = friendly_disk_name(crypt_get_device_name(cd), name); + if (!friendly) + return log_oom(); + + for (;;) { + bool processed = false; + + if (key_file || key_data) { + /* If key data is specified, use that */ + + r = acquire_tpm2_key( + name, + arg_tpm2_device, + arg_tpm2_pcr_mask == UINT32_MAX ? TPM2_PCR_MASK_DEFAULT : arg_tpm2_pcr_mask, + key_file, arg_keyfile_size, arg_keyfile_offset, + key_data, key_data_size, + NULL, 0, /* we don't know the policy hash */ + &decrypted_key, &decrypted_key_size); + if (r >= 0) + break; + if (r != -EAGAIN) /* EAGAIN means: no tpm2 chip found */ + return r; + } else { + _cleanup_free_ void *blob = NULL, *policy_hash = NULL; + size_t blob_size, policy_hash_size; + bool found_some = false; + int token = 0; /* first token to look at */ + + /* If no key data is specified, look for it in the header. In order to support + * software upgrades we'll iterate through all suitable tokens, maybe one of them + * works. */ + + for (;;) { + uint32_t pcr_mask; + + r = find_tpm2_auto_data( + cd, + arg_tpm2_pcr_mask, /* if != UINT32_MAX we'll only look for tokens with this PCR mask */ + token, /* search for the token with this index, or any later index than this */ + &pcr_mask, + &blob, &blob_size, + &policy_hash, &policy_hash_size, + &keyslot, + &token); + if (r == -ENXIO) { + /* No futher TPM2 tokens found in the LUKS2 header.*/ + if (found_some) + return log_debug_errno(SYNTHETIC_ERRNO(EAGAIN), + "No TPM2 metadata matching the current system state found in LUKS2 header, falling back to traditional unlocking."); + else + return log_debug_errno(SYNTHETIC_ERRNO(EAGAIN), + "No TPM2 metadata enrolled in LUKS2 header, falling back to traditional unlocking."); + } + if (r < 0) + return r; + + found_some = true; + + r = acquire_tpm2_key( + name, + arg_tpm2_device, + pcr_mask, + NULL, 0, 0, /* no key file */ + blob, blob_size, + policy_hash, policy_hash_size, + &decrypted_key, &decrypted_key_size); + if (r != -EPERM) + break; + + token++; /* try a different token next time */ + } + + if (r >= 0) + break; + if (r != -EAGAIN) /* EAGAIN means: no tpm2 chip found */ + return r; + } + + if (!monitor) { + /* We didn't find the TPM2 device. In this case, watch for it via udev. Let's create + * an event loop and monitor first. */ + + assert(!event); + + r = sd_event_default(&event); + if (r < 0) + return log_error_errno(r, "Failed to allocate event loop: %m"); + + r = make_tpm2_device_monitor(event, &monitor); + if (r < 0) + return r; + + log_info("TPM2 device not present for unlocking %s, waiting for it to become available.", friendly); + + /* Let's immediately rescan in case the device appeared in the time we needed + * to create and configure the monitor */ + continue; + } + + for (;;) { + /* Wait for one event, and then eat all subsequent events until there are no + * further ones */ + r = sd_event_run(event, processed ? 0 : UINT64_MAX); + if (r < 0) + return log_error_errno(r, "Failed to run event loop: %m"); + if (r == 0) + break; + + processed = true; + } + + log_debug("Got one or more potentially relevant udev events, rescanning for TPM2..."); + } + + if (pass_volume_key) + r = crypt_activate_by_volume_key(cd, name, decrypted_key, decrypted_key_size, flags); + else { + _cleanup_(erase_and_freep) char *base64_encoded = NULL; + + /* Before using this key as passphrase we base64 encode it, for compat with homed */ + + r = base64mem(decrypted_key, decrypted_key_size, &base64_encoded); + if (r < 0) + return log_oom(); + + r = crypt_activate_by_passphrase(cd, name, keyslot, base64_encoded, strlen(base64_encoded), flags); + } + if (r == -EPERM) { + log_error_errno(r, "Failed to activate with TPM2 decrypted key. (Key incorrect?)"); + return -EAGAIN; /* log actual error, but return EAGAIN */ + } + if (r < 0) + return log_error_errno(r, "Failed to activate with TPM2 acquired key: %m"); + + return 0; +} + static int attach_luks_or_plain_or_bitlk_by_key_data( struct crypt_device *cd, const char *name, @@ -1083,6 +1303,8 @@ static int attach_luks_or_plain_or_bitlk( crypt_get_volume_key_size(cd)*8, crypt_get_device_name(cd)); + if (arg_tpm2_device || arg_tpm2_device_auto) + return attach_luks_or_plain_or_bitlk_by_tpm2(cd, name, key_file, key_data, key_data_size, until, flags, pass_volume_key); if (arg_fido2_device || arg_fido2_device_auto) return attach_luks_or_plain_or_bitlk_by_fido2(cd, name, key_file, key_data, key_data_size, until, flags, pass_volume_key); if (arg_pkcs11_uri || arg_pkcs11_uri_auto) @@ -1305,14 +1527,14 @@ static int run(int argc, char *argv[]) { /* When we were able to acquire multiple keys, let's always process them in this order: * - * 1. A key acquired via PKCS#11 or FIDO2 token + * 1. A key acquired via PKCS#11 or FIDO2 token, or TPM2 chip * 2. The discovered key: i.e. key_data + key_data_size * 3. The configured key: i.e. key_file + arg_keyfile_offset + arg_keyfile_size * 4. The empty password, in case arg_try_empty_password is set * 5. We enquire the user for a password */ - if (!key_file && !key_data && !arg_pkcs11_uri && !arg_pkcs11_uri_auto && !arg_fido2_device && !arg_fido2_device_auto) { + if (!key_file && !key_data && !arg_pkcs11_uri && !arg_pkcs11_uri_auto && !arg_fido2_device && !arg_fido2_device_auto && !arg_tpm2_device && !arg_tpm2_device_auto) { if (arg_try_empty_password) { /* Hmm, let's try an empty password now, but only once */ @@ -1353,6 +1575,8 @@ static int run(int argc, char *argv[]) { arg_pkcs11_uri_auto = false; arg_fido2_device = mfree(arg_fido2_device); arg_fido2_device_auto = false; + arg_tpm2_device = mfree(arg_tpm2_device); + arg_tpm2_device_auto = false; } if (arg_tries != 0 && tries >= arg_tries)