From d4397999324c5093380fc1e6c8ce430a58e57145 Mon Sep 17 00:00:00 2001 From: Emanuele Giuseppe Esposito Date: Mon, 14 Jul 2025 07:56:49 -0400 Subject: [PATCH 1/3] repart: use iovec structure for --key-file Use the iovec structure for --key-file, instead of a char pointer and a size. --- src/repart/repart.c | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/repart/repart.c b/src/repart/repart.c index 961d5d0e33..34e9986918 100644 --- a/src/repart/repart.c +++ b/src/repart/repart.c @@ -169,8 +169,7 @@ static bool arg_size_auto = false; static sd_json_format_flags_t arg_json_format_flags = SD_JSON_FORMAT_OFF; static PagerFlags arg_pager_flags = 0; static bool arg_legend = true; -static void *arg_key = NULL; -static size_t arg_key_size = 0; +static struct iovec arg_key = {}; static char *arg_private_key = NULL; static KeySourceType arg_private_key_source_type = OPENSSL_KEY_SOURCE_FILE; static char *arg_private_key_source = NULL; @@ -207,7 +206,7 @@ STATIC_DESTRUCTOR_REGISTER(arg_node, freep); STATIC_DESTRUCTOR_REGISTER(arg_root, freep); STATIC_DESTRUCTOR_REGISTER(arg_image, freep); STATIC_DESTRUCTOR_REGISTER(arg_definitions, strv_freep); -STATIC_DESTRUCTOR_REGISTER(arg_key, erase_and_freep); +STATIC_DESTRUCTOR_REGISTER(arg_key, iovec_done_erase); STATIC_DESTRUCTOR_REGISTER(arg_private_key, freep); STATIC_DESTRUCTOR_REGISTER(arg_private_key_source, freep); STATIC_DESTRUCTOR_REGISTER(arg_certificate, freep); @@ -4797,13 +4796,13 @@ static int partition_encrypt(Context *context, Partition *p, PartitionTarget *ta CRYPT_ANY_SLOT, NULL, VOLUME_KEY_SIZE, - strempty(arg_key), - arg_key_size); + strempty(arg_key.iov_base), + arg_key.iov_len); if (r < 0) return log_error_errno(r, "Failed to add LUKS2 key: %m"); - passphrase = strempty(arg_key); - passphrase_size = arg_key_size; + passphrase = strempty(arg_key.iov_base); + passphrase_size = arg_key.iov_len; } if (IN_SET(p->encrypt, ENCRYPT_TPM2, ENCRYPT_KEY_FILE_TPM2)) { @@ -8812,20 +8811,21 @@ static int parse_argv(int argc, char *argv[], X509 **ret_certificate, EVP_PKEY * break; case ARG_KEY_FILE: { - _cleanup_(erase_and_freep) char *k = NULL; - size_t n = 0; + struct iovec key = {}; r = read_full_file_full( - AT_FDCWD, optarg, UINT64_MAX, SIZE_MAX, + AT_FDCWD, optarg, + /* offset= */ UINT64_MAX, + /* size= */ SIZE_MAX, READ_FULL_FILE_SECURE|READ_FULL_FILE_WARN_WORLD_READABLE|READ_FULL_FILE_CONNECT_SOCKET, - NULL, - &k, &n); + /* bind_name= */ NULL, + (char **) &key.iov_base, + &key.iov_len); if (r < 0) return log_error_errno(r, "Failed to read key file '%s': %m", optarg); - erase_and_free(arg_key); - arg_key = TAKE_PTR(k); - arg_key_size = n; + iovec_done_erase(&arg_key); + arg_key = key; break; } From 49dcc89ddc15651ebca8da7a13e5c5b08ec247cb Mon Sep 17 00:00:00 2001 From: Emanuele Giuseppe Esposito Date: Thu, 3 Jul 2025 08:08:53 -0400 Subject: [PATCH 2/3] repart: make --tpm2-pcrs also configurable in repart.d/* Add repart.d TPM2PCRs= option with the same syntax as --tpm2-pcrs. This allows a per-partition pcr binding, and not rely on a global config applicable to all partitions. The global --tpm2-pcrs overrides TPM2PCRs config. If none of them is defined, rely on default. --- man/repart.d.xml | 13 ++++++++++ src/repart/repart.c | 58 +++++++++++++++++++++++++++++++++++---------- 2 files changed, 59 insertions(+), 12 deletions(-) diff --git a/man/repart.d.xml b/man/repart.d.xml index 8be41db0ec..0eaf22f9ae 100644 --- a/man/repart.d.xml +++ b/man/repart.d.xml @@ -867,6 +867,19 @@ + + TPM2PCRs= + + Configures the list of PCRs to use for LUKS2 volumes configured with + the Encrypt=tpm2 setting in partition files. + This option take the same parameters as the similary named options to + systemd-cryptenroll1 + and have the same effect on partitions where TPM2 enrollment is requested. + This option will be overridden by the global --tpm2-pcrs= option. + + + + Compression= diff --git a/src/repart/repart.c b/src/repart/repart.c index 34e9986918..19a339be29 100644 --- a/src/repart/repart.c +++ b/src/repart/repart.c @@ -411,6 +411,8 @@ typedef struct Partition { OrderedHashmap *subvolumes; char *default_subvolume; EncryptMode encrypt; + Tpm2PCRValue *tpm2_hash_pcr_values; + size_t tpm2_n_hash_pcr_values; VerityMode verity; char *verity_match_key; MinimizeMode minimize; @@ -674,6 +676,7 @@ static Partition* partition_free(Partition *p) { strv_free(p->make_symlinks); ordered_hashmap_free(p->subvolumes); free(p->default_subvolume); + free(p->tpm2_hash_pcr_values); free(p->verity_match_key); free(p->compression); free(p->compression_level); @@ -715,6 +718,7 @@ static void partition_foreignize(Partition *p) { p->make_symlinks = strv_free(p->make_symlinks); p->subvolumes = ordered_hashmap_free(p->subvolumes); p->default_subvolume = mfree(p->default_subvolume); + p->tpm2_hash_pcr_values = mfree(p->tpm2_hash_pcr_values); p->verity_match_key = mfree(p->verity_match_key); p->compression = mfree(p->compression); p->compression_level = mfree(p->compression_level); @@ -2465,6 +2469,33 @@ static int config_parse_encrypted_volume( return 0; } +static int config_parse_tpm2_pcrs( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + Partition *partition = ASSERT_PTR(data); + + assert(rvalue); + + if (isempty(rvalue)) { + /* Clear existing PCR values if empty */ + partition->tpm2_hash_pcr_values = mfree(partition->tpm2_hash_pcr_values); + partition->tpm2_n_hash_pcr_values = 0; + return 0; + } + + return tpm2_parse_pcr_argument_append(rvalue, &partition->tpm2_hash_pcr_values, + &partition->tpm2_n_hash_pcr_values); +} + static DEFINE_CONFIG_PARSE_ENUM_WITH_DEFAULT(config_parse_verity, verity_mode, VerityMode, VERITY_OFF); static DEFINE_CONFIG_PARSE_ENUM_WITH_DEFAULT(config_parse_minimize, minimize_mode, MinimizeMode, MINIMIZE_OFF); @@ -2570,6 +2601,7 @@ static int partition_read_definition(Partition *p, const char *path, const char { "Partition", "VerityHashBlockSizeBytes", config_parse_block_size, 0, &p->verity_hash_block_size }, { "Partition", "MountPoint", config_parse_mountpoint, 0, p }, { "Partition", "EncryptedVolume", config_parse_encrypted_volume, 0, p }, + { "Partition", "TPM2PCRs", config_parse_tpm2_pcrs, 0, p }, { "Partition", "Compression", config_parse_string, CONFIG_PARSE_STRING_SAFE_AND_ASCII, &p->compression }, { "Partition", "CompressionLevel", config_parse_string, CONFIG_PARSE_STRING_SAFE_AND_ASCII, &p->compression_level }, { "Partition", "SupplementFor", config_parse_string, 0, &p->supplement_for_name }, @@ -4813,8 +4845,10 @@ static int partition_encrypt(Context *context, Partition *p, PartitionTarget *ta ssize_t base64_encoded_size; int keyslot; TPM2Flags flags = 0; + Tpm2PCRValue *pcr_values = arg_tpm2_n_hash_pcr_values > 0 ? arg_tpm2_hash_pcr_values : p->tpm2_hash_pcr_values; + size_t n_pcr_values = arg_tpm2_n_hash_pcr_values > 0 ? arg_tpm2_n_hash_pcr_values : p->tpm2_n_hash_pcr_values; - if (arg_tpm2_n_hash_pcr_values == 0 && + if (n_pcr_values == 0 && arg_tpm2_public_key_pcr_mask == 0 && !arg_tpm2_pcrlock) log_notice("Notice: encrypting future partition %" PRIu64 ", locking against TPM2 with an empty policy, i.e. without any state or access restrictions.\n" @@ -4854,7 +4888,7 @@ static int partition_encrypt(Context *context, Partition *p, PartitionTarget *ta if (r < 0) return r; - if (!tpm2_pcr_values_has_all_values(arg_tpm2_hash_pcr_values, arg_tpm2_n_hash_pcr_values)) + if (!tpm2_pcr_values_has_all_values(pcr_values, n_pcr_values)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Must provide all PCR values when using TPM2 device key."); } else { @@ -4862,8 +4896,8 @@ static int partition_encrypt(Context *context, Partition *p, PartitionTarget *ta if (r < 0) return r; - if (!tpm2_pcr_values_has_all_values(arg_tpm2_hash_pcr_values, arg_tpm2_n_hash_pcr_values)) { - r = tpm2_pcr_read_missing_values(tpm2_context, arg_tpm2_hash_pcr_values, arg_tpm2_n_hash_pcr_values); + if (!tpm2_pcr_values_has_all_values(pcr_values, n_pcr_values)) { + r = tpm2_pcr_read_missing_values(tpm2_context, pcr_values, n_pcr_values); if (r < 0) return log_error_errno(r, "Could not read pcr values: %m"); } @@ -4871,17 +4905,17 @@ static int partition_encrypt(Context *context, Partition *p, PartitionTarget *ta uint16_t hash_pcr_bank = 0; uint32_t hash_pcr_mask = 0; - if (arg_tpm2_n_hash_pcr_values > 0) { + if (n_pcr_values > 0) { size_t hash_count; - r = tpm2_pcr_values_hash_count(arg_tpm2_hash_pcr_values, arg_tpm2_n_hash_pcr_values, &hash_count); + r = tpm2_pcr_values_hash_count(pcr_values, n_pcr_values, &hash_count); if (r < 0) return log_error_errno(r, "Could not get hash count: %m"); if (hash_count > 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Multiple PCR banks selected."); - hash_pcr_bank = arg_tpm2_hash_pcr_values[0].hash; - r = tpm2_pcr_values_to_mask(arg_tpm2_hash_pcr_values, arg_tpm2_n_hash_pcr_values, hash_pcr_bank, &hash_pcr_mask); + hash_pcr_bank = pcr_values[0].hash; + r = tpm2_pcr_values_to_mask(pcr_values, n_pcr_values, hash_pcr_bank, &hash_pcr_mask); if (r < 0) return log_error_errno(r, "Could not get hash mask: %m"); } @@ -4894,8 +4928,8 @@ static int partition_encrypt(Context *context, Partition *p, PartitionTarget *ta /* If both PCR public key unlock and pcrlock unlock is selected, then shard the encryption key. */ r = tpm2_calculate_sealing_policy( - arg_tpm2_hash_pcr_values, - arg_tpm2_n_hash_pcr_values, + pcr_values, + n_pcr_values, iovec_is_set(&pubkey) ? &public : NULL, /* use_pin= */ false, arg_tpm2_pcrlock && !iovec_is_set(&pubkey) ? &pcrlock_policy : NULL, @@ -4905,8 +4939,8 @@ static int partition_encrypt(Context *context, Partition *p, PartitionTarget *ta if (arg_tpm2_pcrlock && iovec_is_set(&pubkey)) { r = tpm2_calculate_sealing_policy( - arg_tpm2_hash_pcr_values, - arg_tpm2_n_hash_pcr_values, + pcr_values, + n_pcr_values, /* public= */ NULL, /* Turn this one off for the 2nd shard */ /* use_pin= */ false, &pcrlock_policy, /* But turn this one on */ From eb44fa4d198d1da11c998f77bc88f95aaf67e186 Mon Sep 17 00:00:00 2001 From: Emanuele Giuseppe Esposito Date: Mon, 14 Jul 2025 05:51:49 -0400 Subject: [PATCH 3/3] repart: make --key-file also configurable in repart.d/* Add repart.d KeyFile= option with the same syntax as --key-file. This allows a per-partition key file encryption, and not rely on a global key applicable to all partitions. The global --key-file overrides KeyFile config. If none of them is defined, rely on default. --- man/repart.d.xml | 12 +++++++ man/systemd-repart.xml | 6 ++-- src/repart/repart.c | 78 ++++++++++++++++++++++++++++++++---------- 3 files changed, 75 insertions(+), 21 deletions(-) diff --git a/man/repart.d.xml b/man/repart.d.xml index 0eaf22f9ae..9d8372fa30 100644 --- a/man/repart.d.xml +++ b/man/repart.d.xml @@ -880,6 +880,18 @@ + + KeyFile= + + Takes a file system path. This path must be absolute, otherwise the option is ignored. + Configures the encryption key to use when setting up LUKS2 volumes configured with the + Encrypt=key-file setting in partition files. Please refer to the documentation of + --key-file= for more details. This option will be overridden by the global + --key-file= option. + + + + Compression= diff --git a/man/systemd-repart.xml b/man/systemd-repart.xml index 7a739752eb..317ae05826 100644 --- a/man/systemd-repart.xml +++ b/man/systemd-repart.xml @@ -338,9 +338,9 @@ volumes configured with the Encrypt=key-file setting in partition files. Should refer to a regular file containing the key, or an AF_UNIX stream socket in the file system. In the latter case, a connection is made to it and the key read from it. If this switch - is not specified, the empty key (i.e. zero length key) is used. This behaviour is useful for setting - up encrypted partitions during early first boot that receive their user-supplied password only in a - later setup step. + is not specified, and no KeyFile= is specified in the partition file, the empty + key (i.e. zero length key) is used. This behaviour is useful for setting up encrypted partitions during + early first boot that receive their user-supplied password only in a later setup step. diff --git a/src/repart/repart.c b/src/repart/repart.c index 19a339be29..4b6fbb829a 100644 --- a/src/repart/repart.c +++ b/src/repart/repart.c @@ -411,6 +411,7 @@ typedef struct Partition { OrderedHashmap *subvolumes; char *default_subvolume; EncryptMode encrypt; + struct iovec key; Tpm2PCRValue *tpm2_hash_pcr_values; size_t tpm2_n_hash_pcr_values; VerityMode verity; @@ -681,6 +682,8 @@ static Partition* partition_free(Partition *p) { free(p->compression); free(p->compression_level); + iovec_done_erase(&p->key); + copy_files_free_many(p->copy_files, p->n_copy_files); iovec_done(&p->roothash); @@ -723,6 +726,8 @@ static void partition_foreignize(Partition *p) { p->compression = mfree(p->compression); p->compression_level = mfree(p->compression_level); + iovec_done_erase(&p->key); + copy_files_free_many(p->copy_files, p->n_copy_files); p->copy_files = NULL; p->n_copy_files = 0; @@ -2496,6 +2501,51 @@ static int config_parse_tpm2_pcrs( &partition->tpm2_n_hash_pcr_values); } +static int parse_key_file(const char *filename, struct iovec *key) { + _cleanup_(erase_and_freep) char *k = NULL; + size_t n = 0; + int r; + + r = read_full_file_full( + AT_FDCWD, filename, + /* offset= */ UINT64_MAX, + /* size= */ SIZE_MAX, + READ_FULL_FILE_SECURE|READ_FULL_FILE_WARN_WORLD_READABLE|READ_FULL_FILE_CONNECT_SOCKET, + /* bind_name= */ NULL, + &k, &n); + if (r < 0) + return log_error_errno(r, "Failed to read key file '%s': %m", filename); + + iovec_done_erase(key); + *key = IOVEC_MAKE(TAKE_PTR(k), n); + + return 0; +} + +static int config_parse_key_file( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + Partition *partition = ASSERT_PTR(userdata); + + assert(rvalue); + + if (isempty(rvalue)) { + iovec_done_erase(&partition->key); + return 0; + } + + return parse_key_file(rvalue, &partition->key); +} + static DEFINE_CONFIG_PARSE_ENUM_WITH_DEFAULT(config_parse_verity, verity_mode, VerityMode, VERITY_OFF); static DEFINE_CONFIG_PARSE_ENUM_WITH_DEFAULT(config_parse_minimize, minimize_mode, MinimizeMode, MINIMIZE_OFF); @@ -2602,6 +2652,7 @@ static int partition_read_definition(Partition *p, const char *path, const char { "Partition", "MountPoint", config_parse_mountpoint, 0, p }, { "Partition", "EncryptedVolume", config_parse_encrypted_volume, 0, p }, { "Partition", "TPM2PCRs", config_parse_tpm2_pcrs, 0, p }, + { "Partition", "KeyFile", config_parse_key_file, 0, p }, { "Partition", "Compression", config_parse_string, CONFIG_PARSE_STRING_SAFE_AND_ASCII, &p->compression }, { "Partition", "CompressionLevel", config_parse_string, CONFIG_PARSE_STRING_SAFE_AND_ASCII, &p->compression_level }, { "Partition", "SupplementFor", config_parse_string, 0, &p->supplement_for_name }, @@ -4823,18 +4874,21 @@ static int partition_encrypt(Context *context, Partition *p, PartitionTarget *ta return log_error_errno(r, "Failed to LUKS2 format future partition: %m"); if (IN_SET(p->encrypt, ENCRYPT_KEY_FILE, ENCRYPT_KEY_FILE_TPM2)) { + /* Use partition-specific key if available, otherwise fall back to global key */ + struct iovec *iovec_key = arg_key.iov_base ? &arg_key : &p->key; + r = sym_crypt_keyslot_add_by_volume_key( cd, CRYPT_ANY_SLOT, NULL, VOLUME_KEY_SIZE, - strempty(arg_key.iov_base), - arg_key.iov_len); + strempty(iovec_key->iov_base), + iovec_key->iov_len); if (r < 0) return log_error_errno(r, "Failed to add LUKS2 key: %m"); - passphrase = strempty(arg_key.iov_base); - passphrase_size = arg_key.iov_len; + passphrase = strempty(iovec_key->iov_base); + passphrase_size = iovec_key->iov_len; } if (IN_SET(p->encrypt, ENCRYPT_TPM2, ENCRYPT_KEY_FILE_TPM2)) { @@ -8845,21 +8899,9 @@ static int parse_argv(int argc, char *argv[], X509 **ret_certificate, EVP_PKEY * break; case ARG_KEY_FILE: { - struct iovec key = {}; - - r = read_full_file_full( - AT_FDCWD, optarg, - /* offset= */ UINT64_MAX, - /* size= */ SIZE_MAX, - READ_FULL_FILE_SECURE|READ_FULL_FILE_WARN_WORLD_READABLE|READ_FULL_FILE_CONNECT_SOCKET, - /* bind_name= */ NULL, - (char **) &key.iov_base, - &key.iov_len); + r = parse_key_file(optarg, &arg_key); if (r < 0) - return log_error_errno(r, "Failed to read key file '%s': %m", optarg); - - iovec_done_erase(&arg_key); - arg_key = key; + return r; break; }