diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-06-12 03:50:42 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-06-12 03:50:42 +0000 |
commit | 78e9bb837c258ac0ec7712b3d612cc2f407e731e (patch) | |
tree | f515d16b6efd858a9aeb5b0ef5d6f90bf288283d /src/cryptenroll | |
parent | Adding debian version 255.5-1. (diff) | |
download | systemd-78e9bb837c258ac0ec7712b3d612cc2f407e731e.tar.xz systemd-78e9bb837c258ac0ec7712b3d612cc2f407e731e.zip |
Merging upstream version 256.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/cryptenroll')
-rw-r--r-- | src/cryptenroll/cryptenroll-fido2.c | 7 | ||||
-rw-r--r-- | src/cryptenroll/cryptenroll-list.c | 2 | ||||
-rw-r--r-- | src/cryptenroll/cryptenroll-password.c | 42 | ||||
-rw-r--r-- | src/cryptenroll/cryptenroll-pkcs11.c | 86 | ||||
-rw-r--r-- | src/cryptenroll/cryptenroll-tpm2.c | 269 | ||||
-rw-r--r-- | src/cryptenroll/cryptenroll-tpm2.h | 10 | ||||
-rw-r--r-- | src/cryptenroll/cryptenroll.c | 201 | ||||
-rw-r--r-- | src/cryptenroll/cryptenroll.h | 1 |
8 files changed, 462 insertions, 156 deletions
diff --git a/src/cryptenroll/cryptenroll-fido2.c b/src/cryptenroll/cryptenroll-fido2.c index 2baeb92..baa630a 100644 --- a/src/cryptenroll/cryptenroll-fido2.c +++ b/src/cryptenroll/cryptenroll-fido2.c @@ -33,10 +33,10 @@ int load_volume_key_fido2( cd_node, device, /* until= */ 0, - /* headless= */ false, + "cryptenroll.fido2-pin", + ASK_PASSWORD_PUSH_CACHE|ASK_PASSWORD_ACCEPT_CACHED, &decrypted_key, - &decrypted_key_size, - ASK_PASSWORD_PUSH_CACHE|ASK_PASSWORD_ACCEPT_CACHED); + &decrypted_key_size); if (r == -EAGAIN) return log_error_errno(r, "FIDO2 token does not exist, or UV is blocked. Please try again."); if (r < 0) @@ -97,6 +97,7 @@ int enroll_fido2( /* user_display_name= */ node, /* user_icon_name= */ NULL, /* askpw_icon_name= */ "drive-harddisk", + /* askpw_credential= */ "cryptenroll.fido2-pin", lock_with, cred_alg, &cid, &cid_size, diff --git a/src/cryptenroll/cryptenroll-list.c b/src/cryptenroll/cryptenroll-list.c index d21df71..00a1a8e 100644 --- a/src/cryptenroll/cryptenroll-list.c +++ b/src/cryptenroll/cryptenroll-list.c @@ -114,7 +114,7 @@ int list_enrolled(struct crypt_device *cd) { return table_log_add_error(r); } - if (table_get_rows(t) <= 1) { + if (table_isempty(t)) { log_info("No slots found."); return 0; } diff --git a/src/cryptenroll/cryptenroll-password.c b/src/cryptenroll/cryptenroll-password.c index c35b609..a9bd8a1 100644 --- a/src/cryptenroll/cryptenroll-password.c +++ b/src/cryptenroll/cryptenroll-password.c @@ -38,9 +38,8 @@ int load_volume_key_password( return log_error_errno(r, "Password from environment variable $PASSWORD did not work: %m"); } else { AskPasswordFlags ask_password_flags = ASK_PASSWORD_PUSH_CACHE|ASK_PASSWORD_ACCEPT_CACHED; - _cleanup_free_ char *question = NULL, *disk_path = NULL; + _cleanup_free_ char *question = NULL, *id = NULL, *disk_path = NULL; unsigned i = 5; - const char *id; question = strjoin("Please enter current passphrase for disk ", cd_node, ":"); if (!question) @@ -50,7 +49,17 @@ int load_volume_key_password( if (!disk_path) return log_oom(); - id = strjoina("cryptsetup:", disk_path); + id = strjoin("cryptenroll:", disk_path); + if (!id) + return log_oom(); + + AskPasswordRequest req = { + .message = question, + .icon = "drive-harddisk", + .id = id, + .keyring = "cryptenroll", + .credential = "cryptenroll.passphrase", + }; for (;;) { _cleanup_strv_free_erase_ char **passwords = NULL; @@ -59,10 +68,7 @@ int load_volume_key_password( return log_error_errno(SYNTHETIC_ERRNO(ENOKEY), "Too many attempts, giving up."); - r = ask_password_auto( - question, "drive-harddisk", id, "cryptenroll", "cryptenroll.passphrase", USEC_INFINITY, - ask_password_flags, - &passwords); + r = ask_password_auto(&req, USEC_INFINITY, ask_password_flags, &passwords); if (r < 0) return log_error_errno(r, "Failed to query password: %m"); @@ -105,9 +111,8 @@ int enroll_password( if (r < 0) return log_error_errno(r, "Failed to acquire password from environment: %m"); if (r == 0) { - _cleanup_free_ char *disk_path = NULL; + _cleanup_free_ char *disk_path = NULL, *id = NULL; unsigned i = 5; - const char *id; assert_se(node = crypt_get_device_name(cd)); @@ -117,7 +122,16 @@ int enroll_password( if (!disk_path) return log_oom(); - id = strjoina("cryptsetup:", disk_path); + id = strjoin("cryptenroll-new:", disk_path); + if (!id) + return log_oom(); + + AskPasswordRequest req = { + .icon = "drive-harddisk", + .id = id, + .keyring = "cryptenroll", + .credential = "cryptenroll.new-passphrase", + }; for (;;) { _cleanup_strv_free_erase_ char **passwords = NULL, **passwords2 = NULL; @@ -131,7 +145,9 @@ int enroll_password( if (!question) return log_oom(); - r = ask_password_auto(question, "drive-harddisk", id, "cryptenroll", "cryptenroll.new-passphrase", USEC_INFINITY, 0, &passwords); + req.message = question; + + r = ask_password_auto(&req, USEC_INFINITY, /* flags= */ 0, &passwords); if (r < 0) return log_error_errno(r, "Failed to query password: %m"); @@ -142,7 +158,9 @@ int enroll_password( if (!question) return log_oom(); - r = ask_password_auto(question, "drive-harddisk", id, "cryptenroll", "cryptenroll.new-passphrase", USEC_INFINITY, 0, &passwords2); + req.message = question; + + r = ask_password_auto(&req, USEC_INFINITY, /* flags= */ 0, &passwords2); if (r < 0) return log_error_errno(r, "Failed to query password: %m"); diff --git a/src/cryptenroll/cryptenroll-pkcs11.c b/src/cryptenroll/cryptenroll-pkcs11.c index 54b6b86..1e4be00 100644 --- a/src/cryptenroll/cryptenroll-pkcs11.c +++ b/src/cryptenroll/cryptenroll-pkcs11.c @@ -6,7 +6,30 @@ #include "memory-util.h" #include "openssl-util.h" #include "pkcs11-util.h" -#include "random-util.h" + +static int uri_set_private_class(const char *uri, char **ret_uri) { + _cleanup_(sym_p11_kit_uri_freep) P11KitUri *p11kit_uri = NULL; + _cleanup_free_ char *private_uri = NULL; + int r; + + r = uri_from_string(uri, &p11kit_uri); + if (r < 0) + return log_error_errno(r, "Failed to parse PKCS#11 URI '%s': %m", uri); + + if (sym_p11_kit_uri_get_attribute(p11kit_uri, CKA_CLASS)) { + CK_OBJECT_CLASS class = CKO_PRIVATE_KEY; + CK_ATTRIBUTE attribute = { CKA_CLASS, &class, sizeof(class) }; + + if (sym_p11_kit_uri_set_attribute(p11kit_uri, &attribute) != P11_KIT_URI_OK) + return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to set class for URI '%s'.", uri); + + if (sym_p11_kit_uri_format(p11kit_uri, P11_KIT_URI_FOR_ANY, &private_uri) != P11_KIT_URI_OK) + return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to format PKCS#11 URI."); + } + + *ret_uri = TAKE_PTR(private_uri); + return 0; +} int enroll_pkcs11( struct crypt_device *cd, @@ -17,14 +40,13 @@ int enroll_pkcs11( _cleanup_(erase_and_freep) void *decrypted_key = NULL; _cleanup_(erase_and_freep) char *base64_encoded = NULL; _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; - _cleanup_free_ char *keyslot_as_string = NULL; - size_t decrypted_key_size, encrypted_key_size; - _cleanup_free_ void *encrypted_key = NULL; - _cleanup_(X509_freep) X509 *cert = NULL; + _cleanup_free_ char *keyslot_as_string = NULL, *private_uri = NULL; + size_t decrypted_key_size, saved_key_size; + _cleanup_free_ void *saved_key = NULL; + _cleanup_(EVP_PKEY_freep) EVP_PKEY *pkey = NULL; ssize_t base64_encoded_size; const char *node; - EVP_PKEY *pkey; - int keyslot, r; + int r; assert_se(cd); assert_se(volume_key); @@ -33,31 +55,20 @@ int enroll_pkcs11( assert_se(node = crypt_get_device_name(cd)); - r = pkcs11_acquire_certificate(uri, "volume enrollment operation", "drive-harddisk", &cert, NULL); + r = pkcs11_acquire_public_key( + uri, + "volume enrollment operation", + "drive-harddisk", + "cryptenroll.pkcs11-pin", + /* askpw_flags= */ 0, + &pkey, + /* ret_pin_used= */ NULL); if (r < 0) return r; - pkey = X509_get0_pubkey(cert); - if (!pkey) - return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to extract public key from X.509 certificate."); - - r = rsa_pkey_to_suitable_key_size(pkey, &decrypted_key_size); + r = pkey_generate_volume_keys(pkey, &decrypted_key, &decrypted_key_size, &saved_key, &saved_key_size); if (r < 0) - return log_error_errno(r, "Failed to determine RSA public key size."); - - log_debug("Generating %zu bytes random key.", decrypted_key_size); - - decrypted_key = malloc(decrypted_key_size); - if (!decrypted_key) - return log_oom(); - - r = crypto_random_bytes(decrypted_key, decrypted_key_size); - if (r < 0) - return log_error_errno(r, "Failed to generate random key: %m"); - - r = rsa_encrypt_bytes(pkey, decrypted_key, decrypted_key_size, &encrypted_key, &encrypted_key_size); - if (r < 0) - return log_error_errno(r, "Failed to encrypt key: %m"); + return log_error_errno(r, "Failed to generate volume keys: %m"); /* Let's base64 encode the key to use, for compat with homed (and it's easier to type it in by * keyboard, if that might ever end up being necessary.) */ @@ -69,7 +80,7 @@ int enroll_pkcs11( if (r < 0) return log_error_errno(r, "Failed to set minimal PBKDF: %m"); - keyslot = crypt_keyslot_add_by_volume_key( + int keyslot = crypt_keyslot_add_by_volume_key( cd, CRYPT_ANY_SLOT, volume_key, @@ -82,12 +93,19 @@ int enroll_pkcs11( if (asprintf(&keyslot_as_string, "%i", keyslot) < 0) return log_oom(); + /* Change 'type=cert' or 'type=public' in the provided URI to 'type=private' before storing in + a LUKS2 header. This allows users to use output of some PKCS#11 tools directly without + modifications. */ + r = uri_set_private_class(uri, &private_uri); + if (r < 0) + return r; + r = json_build(&v, - JSON_BUILD_OBJECT( - JSON_BUILD_PAIR("type", JSON_BUILD_CONST_STRING("systemd-pkcs11")), - JSON_BUILD_PAIR("keyslots", JSON_BUILD_ARRAY(JSON_BUILD_STRING(keyslot_as_string))), - JSON_BUILD_PAIR("pkcs11-uri", JSON_BUILD_STRING(uri)), - JSON_BUILD_PAIR("pkcs11-key", JSON_BUILD_BASE64(encrypted_key, encrypted_key_size)))); + JSON_BUILD_OBJECT( + JSON_BUILD_PAIR("type", JSON_BUILD_CONST_STRING("systemd-pkcs11")), + JSON_BUILD_PAIR("keyslots", JSON_BUILD_ARRAY(JSON_BUILD_STRING(keyslot_as_string))), + JSON_BUILD_PAIR("pkcs11-uri", JSON_BUILD_STRING(private_uri ?: uri)), + JSON_BUILD_PAIR("pkcs11-key", JSON_BUILD_BASE64(saved_key, saved_key_size)))); if (r < 0) return log_error_errno(r, "Failed to prepare PKCS#11 JSON token object: %m"); diff --git a/src/cryptenroll/cryptenroll-tpm2.c b/src/cryptenroll/cryptenroll-tpm2.c index 2d93e13..1ee3525 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" @@ -25,7 +28,7 @@ static int search_policy_hash( if (hash_size == 0) return 0; - for (int token = 0; token < sym_crypt_token_max(CRYPT_LUKS2); token ++) { + for (int token = 0; token < sym_crypt_token_max(CRYPT_LUKS2); token++) { _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; _cleanup_free_ void *thash = NULL; size_t thash_size = 0; @@ -51,7 +54,7 @@ static int search_policy_hash( return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "TPM2 token data lacks 'tpm2-policy-hash' field."); - r = unhexmem(json_variant_string(w), SIZE_MAX, &thash, &thash_size); + r = unhexmem(json_variant_string(w), &thash, &thash_size); if (r < 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid base64 data in 'tpm2-policy-hash' field."); @@ -84,28 +87,29 @@ static int get_pin(char **ret_pin_str, TPM2Flags *ret_flags) { return log_error_errno( SYNTHETIC_ERRNO(ENOKEY), "Too many attempts, giving up."); + AskPasswordRequest req = { + .message = "Please enter TPM2 PIN:", + .icon = "drive-harddisk", + .keyring = "tpm2-pin", + .credential = "cryptenroll.new-tpm2-pin", + }; + pin = strv_free_erase(pin); r = ask_password_auto( - "Please enter TPM2 PIN:", - "drive-harddisk", - NULL, - "tpm2-pin", - "cryptenroll.tpm2-pin", - USEC_INFINITY, - 0, + &req, + /* until= */ USEC_INFINITY, + /* flags= */ 0, &pin); if (r < 0) return log_error_errno(r, "Failed to ask for user pin: %m"); assert(strv_length(pin) == 1); + req.message = "Please enter TPM2 PIN (repeat):"; + r = ask_password_auto( - "Please enter TPM2 PIN (repeat):", - "drive-harddisk", - NULL, - "tpm2-pin", - "cryptenroll.tpm2-pin", + &req, USEC_INFINITY, - 0, + /* flags= */ 0, &pin2); if (r < 0) return log_error_errno(r, "Failed to ask for user pin: %m"); @@ -129,6 +133,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, + "cryptenroll.tpm2-pin", + /* askpw_flags= */ 0, + &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, @@ -137,22 +249,22 @@ int enroll_tpm2(struct crypt_device *cd, const char *device_key, Tpm2PCRValue *hash_pcr_values, size_t n_hash_pcr_values, - const char *pubkey_path, + const char *pcr_pubkey_path, + bool load_pcr_pubkey, 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_(erase_and_freep) void *secret = NULL; _cleanup_(json_variant_unrefp) JsonVariant *v = NULL, *signature_json = NULL; _cleanup_(erase_and_freep) char *base64_encoded = NULL; - _cleanup_free_ void *srk_buf = NULL; - size_t secret_size, blob_size, pubkey_size = 0, srk_buf_size = 0; - _cleanup_free_ void *blob = NULL, *pubkey = NULL; + _cleanup_(iovec_done) struct iovec srk = {}, blob = {}, pubkey = {}; + _cleanup_(iovec_done_erase) struct iovec secret = {}; 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] = {}; /* @@ -168,6 +280,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)); @@ -194,27 +307,33 @@ int enroll_tpm2(struct crypt_device *cd, } TPM2B_PUBLIC public = {}; - r = tpm2_load_pcr_public_key(pubkey_path, &pubkey, &pubkey_size); - if (r < 0) { - if (pubkey_path || signature_path || r != -ENOENT) - return log_error_errno(r, "Failed to read TPM PCR public key: %m"); - - log_debug_errno(r, "Failed to read TPM2 PCR public key, proceeding without: %m"); - pubkey_pcr_mask = 0; - } else { - r = tpm2_tpm2b_public_from_pem(pubkey, pubkey_size, &public); - if (r < 0) - return log_error_errno(r, "Could not convert public key to TPM2B_PUBLIC: %m"); + /* Load the PCR public key if specified explicitly, or if no pcrlock policy was specified and + * automatic loading of PCR public keys wasn't disabled explicitly. The reason we turn this off when + * pcrlock is configured is simply that we currently not support both in combination. */ + if (pcr_pubkey_path || (load_pcr_pubkey && !pcrlock_path)) { + r = tpm2_load_pcr_public_key(pcr_pubkey_path, &pubkey.iov_base, &pubkey.iov_len); + if (r < 0) { + if (pcr_pubkey_path || signature_path || r != -ENOENT) + return log_error_errno(r, "Failed to read TPM PCR public key: %m"); + + log_debug_errno(r, "Failed to read TPM2 PCR public key, proceeding without: %m"); + pubkey_pcr_mask = 0; + } else { + r = tpm2_tpm2b_public_from_pem(pubkey.iov_base, pubkey.iov_len, &public); + if (r < 0) + return log_error_errno(r, "Could not convert public key to TPM2B_PUBLIC: %m"); - if (signature_path) { - /* Also try to load the signature JSON object, to verify that our enrollment will work. - * This is optional however, skip it if it's not explicitly provided. */ + if (signature_path) { + /* Also try to load the signature JSON object, to verify that our enrollment will work. + * This is optional however, skip it if it's not explicitly provided. */ - r = tpm2_load_pcr_signature(signature_path, &signature_json); - if (r < 0) - return log_debug_errno(r, "Failed to read TPM PCR signature: %m"); + r = tpm2_load_pcr_signature(signature_path, &signature_json); + if (r < 0) + return log_error_errno(r, "Failed to read TPM PCR signature: %m"); + } } - } + } else + pubkey_pcr_mask = 0; bool any_pcr_value_specified = tpm2_pcr_values_has_any_values(hash_pcr_values, n_hash_pcr_values); @@ -223,6 +342,8 @@ int enroll_tpm2(struct crypt_device *cd, r = tpm2_pcrlock_policy_load(pcrlock_path, &pcrlock_policy); if (r < 0) return r; + if (r == 0) + return log_error_errno(SYNTHETIC_ERRNO(ENOENT), "Couldn't find pcrlock policy %s.", pcrlock_path); any_pcr_value_specified = true; flags |= TPM2_FLAGS_USE_PCRLOCK; @@ -252,8 +373,10 @@ int enroll_tpm2(struct crypt_device *cd, uint16_t hash_pcr_bank = 0; uint32_t hash_pcr_mask = 0; + if (n_hash_pcr_values > 0) { size_t hash_count; + r = tpm2_pcr_values_hash_count(hash_pcr_values, n_hash_pcr_values, &hash_count); if (r < 0) return log_error_errno(r, "Could not get hash count: %m"); @@ -261,17 +384,28 @@ int enroll_tpm2(struct crypt_device *cd, if (hash_count > 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Multiple PCR banks selected."); + /* If we use a literal PCR value policy, derive the bank to use from the algorithm specified on the hash values */ hash_pcr_bank = hash_pcr_values[0].hash; r = tpm2_pcr_values_to_mask(hash_pcr_values, n_hash_pcr_values, hash_pcr_bank, &hash_pcr_mask); if (r < 0) return log_error_errno(r, "Could not get hash mask: %m"); + } else if (pubkey_pcr_mask != 0) { + + /* If no literal PCR value policy is used, then let's determine the mask to use automatically + * from the measurements of the TPM. */ + r = tpm2_get_best_pcr_bank( + tpm2_context, + pubkey_pcr_mask, + &hash_pcr_bank); + if (r < 0) + return log_error_errno(r, "Failed to determine best PCR bank: %m"); } TPM2B_DIGEST policy = TPM2B_DIGEST_MAKE(NULL, TPM2_SHA256_DIGEST_SIZE); r = tpm2_calculate_sealing_policy( hash_pcr_values, n_hash_pcr_values, - pubkey ? &public : NULL, + iovec_is_set(&pubkey) ? &public : NULL, use_pin, pcrlock_path ? &pcrlock_policy : NULL, &policy); @@ -283,21 +417,21 @@ int enroll_tpm2(struct crypt_device *cd, seal_key_handle, &device_key_public, /* attributes= */ NULL, - /* secret= */ NULL, /* secret_size= */ 0, + /* secret= */ NULL, &policy, pin_str, - &secret, &secret_size, - &blob, &blob_size, - &srk_buf, &srk_buf_size); + &secret, + &blob, + &srk); else r = tpm2_seal(tpm2_context, seal_key_handle, &policy, pin_str, - &secret, &secret_size, - &blob, &blob_size, + &secret, + &blob, /* ret_primary_alg= */ NULL, - &srk_buf, &srk_buf_size); + &srk); if (r < 0) return log_error_errno(r, "Failed to seal to TPM2: %m"); @@ -307,39 +441,42 @@ 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 */ } /* If possible, verify the sealed data object. */ - if ((!pubkey || signature_json) && !any_pcr_value_specified && !device_key) { - _cleanup_(erase_and_freep) void *secret2 = NULL; - size_t secret2_size; + if ((!iovec_is_set(&pubkey) || signature_json) && !any_pcr_value_specified && !device_key) { + _cleanup_(iovec_done_erase) struct iovec secret2 = {}; log_debug("Unsealing for verification..."); r = tpm2_unseal(tpm2_context, hash_pcr_mask, hash_pcr_bank, - pubkey, pubkey_size, + &pubkey, pubkey_pcr_mask, signature_json, pin_str, pcrlock_path ? &pcrlock_policy : NULL, /* primary_alg= */ 0, - blob, blob_size, - policy.buffer, policy.size, - srk_buf, srk_buf_size, - &secret2, &secret2_size); + &blob, + &IOVEC_MAKE(policy.buffer, policy.size), + &srk, + &secret2); if (r < 0) return log_error_errno(r, "Failed to unseal secret using TPM2: %m"); - if (memcmp_nn(secret, secret_size, secret2, secret2_size) != 0) + if (iovec_memcmp(&secret, &secret2) != 0) return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "TPM2 seal/unseal verification failed."); } /* let's base64 encode the key to use, for compat with homed (and it's easier to every type it in by keyboard, if that might end up being necessary. */ - base64_encoded_size = base64mem(secret, secret_size, &base64_encoded); + base64_encoded_size = base64mem(secret.iov_base, secret.iov_len, &base64_encoded); if (base64_encoded_size < 0) return log_error_errno(base64_encoded_size, "Failed to base64 encode secret key: %m"); @@ -361,14 +498,14 @@ int enroll_tpm2(struct crypt_device *cd, keyslot, hash_pcr_mask, hash_pcr_bank, - pubkey, pubkey_size, + &pubkey, pubkey_pcr_mask, /* primary_alg= */ 0, - blob, blob_size, - policy.buffer, policy.size, - use_pin ? binary_salt : NULL, - use_pin ? sizeof(binary_salt) : 0, - srk_buf, srk_buf_size, + &blob, + &IOVEC_MAKE(policy.buffer, policy.size), + use_pin ? &IOVEC_MAKE(binary_salt, sizeof(binary_salt)) : NULL, + &srk, + pcrlock_path ? &pcrlock_policy.nv_handle : NULL, flags, &v); if (r < 0) @@ -379,5 +516,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 2fbcdd4..d722ed6 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_pcr_values, size_t n_hash_pcr_values, const char *pubkey_path, bool load_pcr_pubkey, 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_pcr_values, size_t n_hash_pcr_values, const char *pubkey_path, bool load_pcr_pubkey, uint32_t pubkey_pcr_mask, const char *signature_path, bool use_pin, const char *pcrlock_path, int *ret_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 1cb6652..04352bf 100644 --- a/src/cryptenroll/cryptenroll.c +++ b/src/cryptenroll/cryptenroll.c @@ -1,8 +1,10 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include <getopt.h> +#include <sys/mman.h> #include "ask-password-api.h" +#include "blockdev-util.h" #include "build.h" #include "cryptenroll-fido2.h" #include "cryptenroll-list.h" @@ -13,6 +15,7 @@ #include "cryptenroll-wipe.h" #include "cryptenroll.h" #include "cryptsetup-util.h" +#include "devnum-util.h" #include "env-util.h" #include "escape.h" #include "fileio.h" @@ -33,6 +36,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; @@ -42,6 +46,7 @@ static Tpm2PCRValue *arg_tpm2_hash_pcr_values = NULL; static size_t arg_tpm2_n_hash_pcr_values = 0; static bool arg_tpm2_pin = false; static char *arg_tpm2_public_key = NULL; +static bool arg_tpm2_load_public_key = true; static uint32_t arg_tpm2_public_key_pcr_mask = 0; static char *arg_tpm2_signature = NULL; static char *arg_tpm2_pcrlock = NULL; @@ -61,6 +66,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); @@ -98,6 +104,66 @@ static const char *const luks2_token_type_table[_ENROLL_TYPE_MAX] = { DEFINE_STRING_TABLE_LOOKUP(luks2_token_type, EnrollType); +static int determine_default_node(void) { + int r; + + /* If no device is specified we'll default to the backing device of /var/. + * + * Why /var/ and not just / you ask? + * + * On most systems /var/ is going to be on the root fs, hence the outcome is usually the same. + * + * However, on systems where / and /var/ are separate it makes more sense to default to /var/ because + * that's where the persistent and variable data is placed (i.e. where LUKS should be used) while / + * doesn't really have to be variable and could as well be immutable or ephemeral. Hence /var/ should + * be a better default. + * + * Or to say this differently: it makes sense to support well systems with /var/ being on /. It also + * makes sense to support well systems with them being separate, and /var/ being variable and + * persistent. But any other kind of system appears much less interesting to support, and in that + * case people should just specify the device name explicitly. */ + + dev_t devno; + r = get_block_device("/var", &devno); + if (r < 0) + return log_error_errno(r, "Failed to determine block device backing /var/: %m"); + if (r == 0) + return log_error_errno(SYNTHETIC_ERRNO(ENXIO), + "File system /var/ is on not backed by a (single) whole block device."); + + _cleanup_(sd_device_unrefp) sd_device *dev = NULL; + r = sd_device_new_from_devnum(&dev, 'b', devno); + if (r < 0) + return log_error_errno(r, "Unable to access backing block device for /var/: %m"); + + const char *dm_uuid; + r = sd_device_get_property_value(dev, "DM_UUID", &dm_uuid); + if (r == -ENOENT) + return log_error_errno(r, "Backing block device of /var/ is not a DM device: %m"); + if (r < 0) + return log_error_errno(r, "Unable to query DM_UUID udev property of backing block device for /var/: %m"); + + if (!startswith(dm_uuid, "CRYPT-LUKS2-")) + return log_error_errno(SYNTHETIC_ERRNO(ENXIO), "Block device backing /var/ is not a LUKS2 device."); + + _cleanup_(sd_device_unrefp) sd_device *origin = NULL; + r = block_device_get_originating(dev, &origin); + if (r < 0) + return log_error_errno(r, "Failed to get originating device of LUKS2 device backing /var/: %m"); + + const char *dp; + r = sd_device_get_devname(origin, &dp); + if (r < 0) + return log_error_errno(r, "Failed to get device path for LUKS2 device backing /var/: %m"); + + r = free_and_strdup_warn(&arg_node, dp); + if (r < 0) + return r; + + log_info("No device specified, defaulting to '%s'.", arg_node); + return 0; +} + static int help(void) { _cleanup_free_ char *link = NULL; int r; @@ -106,7 +172,7 @@ static int help(void) { if (r < 0) return log_oom(); - printf("%1$s [OPTIONS...] BLOCK-DEVICE\n\n" + printf("%1$s [OPTIONS...] [BLOCK-DEVICE]\n\n" "%5$sEnroll a security token or authentication credential to a LUKS volume.%6$s\n\n" " -h --help Show this help\n" " --version Show package version\n" @@ -117,6 +183,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 +240,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 +266,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 +374,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; @@ -409,9 +499,17 @@ static int parse_argv(int argc, char *argv[]) { break; case ARG_TPM2_PUBLIC_KEY: + /* an empty argument disables loading a public key */ + if (isempty(optarg)) { + arg_tpm2_load_public_key = false; + arg_tpm2_public_key = mfree(arg_tpm2_public_key); + break; + } + r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_tpm2_public_key); if (r < 0) return r; + arg_tpm2_load_public_key = true; break; @@ -507,17 +605,23 @@ static int parse_argv(int argc, char *argv[]) { } } - if (optind >= argc) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "No block device node specified, refusing."); - if (argc > optind+1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Too many arguments, refusing."); - r = parse_path_argument(argv[optind], false, &arg_node); - if (r < 0) - return r; + if (optind < argc) { + r = parse_path_argument(argv[optind], false, &arg_node); + if (r < 0) + return r; + } else { + if (wipe_requested()) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Wiping requested and no block device node specified, refusing."); + + r = determine_default_node(); + if (r < 0) + return r; + } if (arg_enroll_type == ENROLL_FIDO2) { @@ -533,31 +637,33 @@ static int parse_argv(int argc, char *argv[]) { } } - if (auto_pcrlock) { - assert(!arg_tpm2_pcrlock); + if (arg_enroll_type == ENROLL_TPM2) { + if (auto_pcrlock) { + assert(!arg_tpm2_pcrlock); - r = tpm2_pcrlock_search_file(NULL, NULL, &arg_tpm2_pcrlock); - if (r < 0) { - if (r != -ENOENT) - log_warning_errno(r, "Search for pcrlock.json failed, assuming it does not exist: %m"); - } else - log_info("Automatically using pcrlock policy '%s'.", arg_tpm2_pcrlock); - } + r = tpm2_pcrlock_search_file(NULL, NULL, &arg_tpm2_pcrlock); + if (r < 0) { + if (r != -ENOENT) + log_warning_errno(r, "Search for pcrlock.json failed, assuming it does not exist: %m"); + } else + log_info("Automatically using pcrlock policy '%s'.", arg_tpm2_pcrlock); + } - if (auto_public_key_pcr_mask) { - assert(arg_tpm2_public_key_pcr_mask == 0); - arg_tpm2_public_key_pcr_mask = INDEX_TO_MASK(uint32_t, TPM2_PCR_KERNEL_BOOT); - } + if (auto_public_key_pcr_mask) { + assert(arg_tpm2_public_key_pcr_mask == 0); + arg_tpm2_public_key_pcr_mask = INDEX_TO_MASK(uint32_t, TPM2_PCR_KERNEL_BOOT); + } - if (auto_hash_pcr_values && !arg_tpm2_pcrlock) { /* Only lock to PCR 7 by default if no pcrlock policy is around (which is a better replacement) */ - assert(arg_tpm2_n_hash_pcr_values == 0); + if (auto_hash_pcr_values && !arg_tpm2_pcrlock) { /* Only lock to PCR 7 by default if no pcrlock policy is around (which is a better replacement) */ + assert(arg_tpm2_n_hash_pcr_values == 0); - if (!GREEDY_REALLOC_APPEND( - arg_tpm2_hash_pcr_values, - arg_tpm2_n_hash_pcr_values, - &TPM2_PCR_VALUE_MAKE(TPM2_PCR_INDEX_DEFAULT, /* hash= */ 0, /* value= */ {}), - 1)) - return log_oom(); + if (!GREEDY_REALLOC_APPEND( + arg_tpm2_hash_pcr_values, + arg_tpm2_n_hash_pcr_values, + &TPM2_PCR_VALUE_MAKE(TPM2_PCR_INDEX_DEFAULT, /* hash= */ 0, /* value= */ {}), + 1)) + return log_oom(); + } } return 1; @@ -571,7 +677,7 @@ static int check_for_homed(struct crypt_device *cd) { /* Politely refuse operating on homed volumes. The enrolled tokens for the user record and the LUKS2 * volume should not get out of sync. */ - for (int token = 0; token < crypt_token_max(CRYPT_LUKS2); token ++) { + for (int token = 0; token < crypt_token_max(CRYPT_LUKS2); token++) { r = cryptsetup_get_token_as_json(cd, token, "systemd-homed", NULL); if (IN_SET(r, -ENOENT, -EINVAL, -EMEDIUMTYPE)) continue; @@ -644,7 +750,7 @@ static int prepare_luks( r = crypt_load(cd, CRYPT_LUKS2, NULL); if (r < 0) - return log_error_errno(r, "Failed to load LUKS2 superblock: %m"); + return log_error_errno(r, "Failed to load LUKS2 superblock of %s: %m", arg_node); r = check_for_homed(cd); if (r < 0) @@ -666,6 +772,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 +784,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,16 +806,17 @@ 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(); - log_open(); + log_setup(); r = parse_argv(argc, 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 +845,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); + 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_load_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 335d9cc..08ded3e 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; |