diff --git a/TODO b/TODO index b1bc038ef5..d849c75a5f 100644 --- a/TODO +++ b/TODO @@ -137,6 +137,12 @@ Deprecations and removals: Features: +* systemd-repart: implement Integrity=data/meta and Integrity=inline for non-LUKS + case. Currently, only Integrity=inline combined with Encrypt= is implemented + and uses libcryptsetup features. Add support for plain dm-integrity setups when + integrity tags are stored by the device (inline), interleaved with data (data), + and on a separate device (meta). + * networkd/machined: implement reverse name lookups in the resolved hook * networkd's resolved hook: optionally map all lease IP addresses handed out to diff --git a/man/repart.d.xml b/man/repart.d.xml index e83ed34f79..5d32557745 100644 --- a/man/repart.d.xml +++ b/man/repart.d.xml @@ -909,6 +909,27 @@ + + Integrity= + + Enable integrity checking for the partition. Currently, the only supported option + is Integrity=inline which enables authenticated disk encryption for LUKS2 devices. + The option requires Encrypt= setting and can only be used in online image build. + Defaults to off, i.e. integrity protection is disabled. + Note: authenticated disk encryption is considered EXPERIMENTAL by cryptsetup. + + + + + + IntegrityAlgorithm= + Specify the integrity algorithm to be used for integrity verification. For + Integrity=inline the supported values are: hmac-sha1, + hmac-sha256(default), and hmac-sha512. + + + + Compression= diff --git a/src/analyze/analyze-image-policy.c b/src/analyze/analyze-image-policy.c index 2075d1b437..aa7f744555 100644 --- a/src/analyze/analyze-image-policy.c +++ b/src/analyze/analyze-image-policy.c @@ -20,8 +20,8 @@ static int table_add_designator_line(Table *table, PartitionDesignator d, Partit return log_oom(); color = (f & _PARTITION_POLICY_USE_MASK) == PARTITION_POLICY_IGNORE ? ansi_grey() : - ((f & (PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_ABSENT)) == - (PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_ABSENT)) ? ansi_highlight_yellow() : + ((f & (PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_ENCRYPTEDWITHINTEGRITY|PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_ABSENT)) == + (PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_ENCRYPTEDWITHINTEGRITY|PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_ABSENT)) ? ansi_highlight_yellow() : (f & _PARTITION_POLICY_USE_MASK) == PARTITION_POLICY_ABSENT ? ansi_highlight_red() : !(f & PARTITION_POLICY_UNPROTECTED) ? ansi_highlight_green() : NULL; diff --git a/src/repart/repart.c b/src/repart/repart.c index edd4816f13..8768375d47 100644 --- a/src/repart/repart.c +++ b/src/repart/repart.c @@ -105,7 +105,7 @@ /* To do LUKS2 offline encryption, we need to keep some extra free space at the end of the partition. */ #define LUKS2_METADATA_KEEP_FREE (LUKS2_METADATA_SIZE*2ULL) -/* LUKS2 volume key size. */ +/* LUKS2 default volume key size (no integrity). */ #define VOLUME_KEY_SIZE (512ULL/8ULL) /* Use 4K as the default filesystem sector size because as long as the partitions are aligned to 4K, the @@ -266,6 +266,21 @@ typedef enum EncryptMode { _ENCRYPT_MODE_INVALID = -EINVAL, } EncryptMode; +typedef enum IntegrityMode { + INTEGRITY_OFF, + INTEGRITY_INLINE, + _INTEGRITY_MODE_MAX, + _INTEGRITY_MODE_INVALID = -EINVAL, +} IntegrityMode; + +typedef enum IntegrityAlg { + INTEGRITY_ALG_HMAC_SHA1, + INTEGRITY_ALG_HMAC_SHA256, + INTEGRITY_ALG_HMAC_SHA512, + _INTEGRITY_ALG_MAX, + _INTEGRITY_ALG_INVALID = -EINVAL, +} IntegrityAlg; + typedef enum VerityMode { VERITY_OFF, VERITY_DATA, @@ -444,6 +459,8 @@ typedef struct Partition { struct iovec key; Tpm2PCRValue *tpm2_hash_pcr_values; size_t tpm2_n_hash_pcr_values; + IntegrityMode integrity; + IntegrityAlg integrity_alg; VerityMode verity; char *verity_match_key; MinimizeMode minimize; @@ -552,6 +569,22 @@ static const char *encrypt_mode_table[_ENCRYPT_MODE_MAX] = { [ENCRYPT_KEY_FILE_TPM2] = "key-file+tpm2", }; +/* Going forward, the plan is to add two more modes: + * [INTEGRITY_DATA] = "data" (interleave data and integrity tags on the same device), + * [INTEGRITY_META] = "meta" (use a separate device for storing integrity tags). + * Also, INTEGRITY_INLINE will be using hardware sector integrity fields when used + * without encryption. */ +static const char *integrity_mode_table[_INTEGRITY_MODE_MAX] = { + [INTEGRITY_OFF] = "off", /* no integrity protection */ + [INTEGRITY_INLINE] = "inline", /* luks2 storage when encrypted */ +}; + +static const char *integrity_alg_table[_INTEGRITY_ALG_MAX] = { + [INTEGRITY_ALG_HMAC_SHA1] = "hmac-sha1", + [INTEGRITY_ALG_HMAC_SHA256] = "hmac-sha256", + [INTEGRITY_ALG_HMAC_SHA512] = "hmac-sha512", +}; + static const char *verity_mode_table[_VERITY_MODE_MAX] = { [VERITY_OFF] = "off", [VERITY_DATA] = "data", @@ -584,6 +617,8 @@ static const char *progress_phase_table[_PROGRESS_PHASE_MAX] = { DEFINE_PRIVATE_STRING_TABLE_LOOKUP(empty_mode, EmptyMode); DEFINE_PRIVATE_STRING_TABLE_LOOKUP(append_mode, AppendMode); DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING_WITH_BOOLEAN(encrypt_mode, EncryptMode, ENCRYPT_KEY_FILE); +DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING_WITH_BOOLEAN(integrity_mode, IntegrityMode, INTEGRITY_INLINE); +DEFINE_PRIVATE_STRING_TABLE_LOOKUP(integrity_alg, IntegrityAlg); DEFINE_PRIVATE_STRING_TABLE_LOOKUP(verity_mode, VerityMode); DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING_WITH_BOOLEAN(minimize_mode, MinimizeMode, MINIMIZE_BEST); DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(progress_phase, ProgressPhase); @@ -2633,6 +2668,9 @@ static int config_parse_key_file( return parse_key_file(rvalue, &partition->key); } +static DEFINE_CONFIG_PARSE_ENUM_WITH_DEFAULT(config_parse_integrity, integrity_mode, IntegrityMode, INTEGRITY_OFF); +static DEFINE_CONFIG_PARSE_ENUM_WITH_DEFAULT(config_parse_integrity_alg, integrity_alg, IntegrityAlg, INTEGRITY_ALG_HMAC_SHA256); + 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); @@ -2787,6 +2825,8 @@ static int partition_read_definition( { "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", "Integrity", config_parse_integrity, 0, &p->integrity }, + { "Partition", "IntegrityAlgorithm", config_parse_integrity_alg, 0, &p->integrity_alg }, { "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 }, @@ -2921,6 +2961,10 @@ static int partition_read_definition( "SizeMinBytes=/SizeMaxBytes= cannot be used with Verity=%s.", verity_mode_to_string(p->verity)); + if (p->integrity == INTEGRITY_INLINE && p->encrypt == ENCRYPT_OFF) + return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL), + "Integrity=inline requires Encrypt=."); + if (p->default_subvolume && !ordered_hashmap_contains(p->subvolumes, p->default_subvolume)) return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL), "DefaultSubvolume= must be one of the paths in Subvolumes=."); @@ -4989,6 +5033,39 @@ static int partition_target_sync(Context *context, Partition *p, PartitionTarget return 0; } +/* libcryptsetup uses its own names for integrity algorithms, e.g. 'hmac(sha1)' but systemd + * prefers more standardized 'hmac-sha1', do the conversion here. Default to hmac(sha256). */ +static const char* dmcrypt_integrity_alg_name(Partition *p) { + if (p->integrity != INTEGRITY_INLINE) + return NULL; + + switch (p->integrity_alg) { + case INTEGRITY_ALG_HMAC_SHA1: + return "hmac(sha1)"; + case INTEGRITY_ALG_HMAC_SHA512: + return "hmac(sha512)"; + case INTEGRITY_ALG_HMAC_SHA256: + default: + return "hmac(sha256)"; + } +} + +/* Integrity puts specific limitations on the key size depending on the algorithm */ +static size_t dmcrypt_proper_key_size(Partition *p) { + if (p->integrity != INTEGRITY_INLINE) + return VOLUME_KEY_SIZE; + + switch (p->integrity_alg) { + case INTEGRITY_ALG_HMAC_SHA1: + return 672/8; + case INTEGRITY_ALG_HMAC_SHA512: + return 1024/8; + case INTEGRITY_ALG_HMAC_SHA256: + default: + return 768/8; + } +} + static int partition_encrypt(Context *context, Partition *p, PartitionTarget *target, bool offline) { #if HAVE_LIBCRYPTSETUP #if HAVE_TPM2 @@ -4997,6 +5074,7 @@ static int partition_encrypt(Context *context, Partition *p, PartitionTarget *ta _cleanup_fclose_ FILE *h = NULL; _cleanup_free_ char *hp = NULL, *vol = NULL, *dm_name = NULL; const char *passphrase = NULL; + const size_t volume_key_size = dmcrypt_proper_key_size(p); size_t passphrase_size = 0; const char *vt; int r; @@ -5021,6 +5099,7 @@ static int partition_encrypt(Context *context, Partition *p, PartitionTarget *ta .label = vl, .sector_size = partition_fs_sector_size(context, p), .data_device = offline ? node : NULL, + .integrity = dmcrypt_integrity_alg_name(p), }; struct crypt_params_reencrypt reencrypt_params = { .mode = CRYPT_REENCRYPT_ENCRYPT, @@ -5032,6 +5111,10 @@ static int partition_encrypt(Context *context, Partition *p, PartitionTarget *ta }; if (offline) { + /* libcryptsetup does not currently support reencryption of devices with integrity profiles.*/ + if (p->integrity == INTEGRITY_INLINE) + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Integrity=inline cannot be enabled in offline mode."); + r = var_tmp_dir(&vt); if (r < 0) return log_error_errno(r, "Failed to determine temporary files directory: %m"); @@ -5084,7 +5167,7 @@ static int partition_encrypt(Context *context, Partition *p, PartitionTarget *ta "xts-plain64", SD_ID128_TO_UUID_STRING(p->luks_uuid), NULL, - VOLUME_KEY_SIZE, + /* volume_key_size= */ volume_key_size, &luks_params); if (r < 0) return log_error_errno(r, "Failed to LUKS2 format future partition: %m"); @@ -5097,7 +5180,7 @@ static int partition_encrypt(Context *context, Partition *p, PartitionTarget *ta cd, CRYPT_ANY_SLOT, NULL, - VOLUME_KEY_SIZE, + /* volume_key_size= */ volume_key_size, strempty(iovec_key->iov_base), iovec_key->iov_len); if (r < 0) @@ -5272,7 +5355,7 @@ static int partition_encrypt(Context *context, Partition *p, PartitionTarget *ta cd, CRYPT_ANY_SLOT, /* volume_key= */ NULL, - /* volume_key_size= */ VOLUME_KEY_SIZE, + /* volume_key_size= */ volume_key_size, base64_encoded, base64_encoded_size); if (keyslot < 0) @@ -5372,11 +5455,30 @@ static int partition_encrypt(Context *context, Partition *p, PartitionTarget *ta cd, dm_name, NULL, - VOLUME_KEY_SIZE, - (arg_discard ? CRYPT_ACTIVATE_ALLOW_DISCARDS : 0) | CRYPT_ACTIVATE_PRIVATE); + /* volume_key_size= */ volume_key_size, + (arg_discard && p->integrity != INTEGRITY_INLINE ? CRYPT_ACTIVATE_ALLOW_DISCARDS : 0) | CRYPT_ACTIVATE_PRIVATE); if (r < 0) return log_error_errno(r, "Failed to activate LUKS superblock: %m"); + /* crypt_wipe() the whole device to avoid integrity errors upon mkfs */ + if (p->integrity == INTEGRITY_INLINE) { + r = sym_crypt_wipe( + cd, + vol, + CRYPT_WIPE_ZERO, + /* offset= */ 0, + /* length= */ 0, + /* wipe_block_size= */ 1 * U64_MB, + /* flags= */ 0, + /* progress= */ NULL, + /* usrptr= */ NULL); + if (r < 0) + return log_error_errno(r, "Failed to wipe LUKS device: %m"); + + log_info("%s integrity protection for future partition %" PRIu64 " initialized.", + integrity_alg_to_string(p->integrity_alg), p->partno); + } + dev_fd = open(vol, O_RDWR|O_CLOEXEC|O_NOCTTY); if (dev_fd < 0) return log_error_errno(errno, "Failed to open LUKS volume '%s': %m", vol); diff --git a/src/shared/cryptsetup-util.c b/src/shared/cryptsetup-util.c index 7f5ab38ef8..90e98c1b83 100644 --- a/src/shared/cryptsetup-util.c +++ b/src/shared/cryptsetup-util.c @@ -70,6 +70,8 @@ DLSYM_PROTOTYPE(crypt_token_set_external_path) = NULL; DLSYM_PROTOTYPE(crypt_token_status) = NULL; DLSYM_PROTOTYPE(crypt_volume_key_get) = NULL; DLSYM_PROTOTYPE(crypt_volume_key_keyring) = NULL; +DLSYM_PROTOTYPE(crypt_wipe) = NULL; +DLSYM_PROTOTYPE(crypt_get_integrity_info) = NULL; static void cryptsetup_log_glue(int level, const char *msg, void *usrptr) { @@ -274,7 +276,9 @@ int dlopen_cryptsetup(void) { #endif DLSYM_ARG(crypt_token_status), DLSYM_ARG(crypt_volume_key_get), - DLSYM_ARG(crypt_volume_key_keyring)); + DLSYM_ARG(crypt_volume_key_keyring), + DLSYM_ARG(crypt_wipe), + DLSYM_ARG(crypt_get_integrity_info)); if (r <= 0) return r; diff --git a/src/shared/cryptsetup-util.h b/src/shared/cryptsetup-util.h index ce9782b7c4..7c52049796 100644 --- a/src/shared/cryptsetup-util.h +++ b/src/shared/cryptsetup-util.h @@ -68,6 +68,8 @@ extern DLSYM_PROTOTYPE(crypt_token_set_external_path); extern DLSYM_PROTOTYPE(crypt_token_status); extern DLSYM_PROTOTYPE(crypt_volume_key_get); extern DLSYM_PROTOTYPE(crypt_volume_key_keyring); +extern DLSYM_PROTOTYPE(crypt_wipe); +extern DLSYM_PROTOTYPE(crypt_get_integrity_info); DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(struct crypt_device *, crypt_free, NULL); DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(struct crypt_device *, sym_crypt_free, NULL); diff --git a/src/shared/dissect-image.c b/src/shared/dissect-image.c index 05c2514593..0fe3a5b3e0 100644 --- a/src/shared/dissect-image.c +++ b/src/shared/dissect-image.c @@ -397,6 +397,114 @@ static int image_policy_check_protection( return 0; } +/* internal LUKS2 header defines */ +#define LUKS2_FIXED_HDR_SIZE UINT64_C(0x1000) +#define LUKS2_MAGIC "LUKS\xba\xbe" + +/* Matches the beginning of 'struct luks2_hdr_disk' from cryptsetup */ +struct luks_header_incomplete { + char luks_magic[sizeof(LUKS2_MAGIC) - 1]; + be16_t version; + be64_t hdr_len; +}; + +/* 'integrity' information from LUKS JSON header. Currenly, only 'type' is extracted/checked. */ +struct luks_integrity_data { + char *type; +}; + +static int integrity_information(const char *name, sd_json_variant *v, sd_json_dispatch_flags_t flags, void *userdata) { + static const sd_json_dispatch_field table[] = { + { "type", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(struct luks_integrity_data, type), SD_JSON_MANDATORY }, + {} + }; + + return sd_json_dispatch(v, table, flags, userdata); +} + +/* cryptsetup needs a loop device to work with a partition which has offset/size but + * dissect may be running unpriviliged. Implement a minimal custom LUKS header parser + * checking integrity protection information. */ +static int partition_is_luks2_integrity(int part_fd, uint64_t offset, uint64_t size) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; + _cleanup_free_ char *json = NULL; + sd_json_variant *w; + const char *key; + struct luks_header_incomplete header; + ssize_t sz, json_len; + int r; + + assert(part_fd >= 0); + + if (size < LUKS2_FIXED_HDR_SIZE) { + log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Partition is too small to contain a LUKS header."); + return 0; + } + + sz = pread(part_fd, &header, sizeof(header), offset); + if (sz < 0) + return log_error_errno(errno, "Failed to read LUKS header."); + if (sz != sizeof(header)) + return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to read LUKS header."); + + if (memcmp(header.luks_magic, LUKS2_MAGIC, sizeof(header.luks_magic)) != 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Partition's magic is not LUKS."); + + if (be16toh(header.version) != 2) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unsupported LUKS header version: %" PRIu16 ".", be16toh(header.version)); + + if (be64toh(header.hdr_len) > size) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "LUKS header length exceeds partition size."); + + if (be64toh(header.hdr_len) <= LUKS2_FIXED_HDR_SIZE || offset > UINT64_MAX - be64toh(header.hdr_len)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid LUKS header length: %" PRIu64 ".", be64toh(header.hdr_len)); + + json_len = be64toh(header.hdr_len) - LUKS2_FIXED_HDR_SIZE; + json = malloc(json_len + 1); + if (!json) + return -ENOMEM; + + sz = pread(part_fd, json, json_len, offset + LUKS2_FIXED_HDR_SIZE); + if (sz < 0) + return log_error_errno(errno, "Failed to read LUKS JSON header."); + if (sz != json_len) + return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to read LUKS JSON header."); + json[sz] = '\0'; + + r = sd_json_parse(json, /* flags = */ 0, &v, /* reterr_line = */ NULL, /* reterr_column = */ NULL); + if (r < 0) + return log_error_errno(r, "Failed to parse LUKS JSON header."); + + v = sd_json_variant_by_key(v, "segments"); + if (!v || !sd_json_variant_is_object(v)) { + log_debug("LUKS JSON header lacks 'segments' information, assuming no integrity."); + return 0; + } + + /* Verify that all segments have integrity protection */ + JSON_VARIANT_OBJECT_FOREACH(key, w, v) { + struct luks_integrity_data data = {}; + + static const sd_json_dispatch_field dispatch_segment[] = { + { "integrity", SD_JSON_VARIANT_OBJECT, integrity_information, 0, SD_JSON_MANDATORY }, + {} + }; + + r = sd_json_dispatch(w, dispatch_segment, SD_JSON_ALLOW_EXTENSIONS, &data); + if (r < 0) { + log_debug("Failed to get integrity information from LUKS JSON for segment %s, assuming no integrity.", key); + return 0; + } + + /* We don't require a particular integrity algorithm, everything but 'none' (which shouldn't + * be there in the first place but is theoretically possible) works. */ + if (streq(data.type, "none")) + return 0; + } + + return 1; +} + static int image_policy_check_partition_flags( const ImagePolicy *policy, PartitionDesignator designator, @@ -457,7 +565,16 @@ static int dissected_image_probe_filesystems( if (streq_ptr(p->fstype, "crypto_LUKS")) { m->encrypted = true; - found_flags = PARTITION_POLICY_UNUSED|PARTITION_POLICY_ENCRYPTED; /* found this one, and its definitely encrypted */ + + if (p->mount_node_fd >= 0) + r = partition_is_luks2_integrity(p->mount_node_fd, /* offset = */ 0, /* size = */ UINT64_MAX); + else + r = partition_is_luks2_integrity(fd, p->offset, p->size); + if (r < 0) + return r; + + /* found this one, it's definitely encrypted + with or without integrity checking */ + found_flags = PARTITION_POLICY_UNUSED|(r > 0 ? PARTITION_POLICY_ENCRYPTEDWITHINTEGRITY : PARTITION_POLICY_ENCRYPTED); } else /* found it, but it's definitely not encrypted, hence mask the encrypted flag, but * set all other ways that indicate "present". */ @@ -948,8 +1065,14 @@ static int dissect_image( if (verity_settings_data_covers(verity, PARTITION_ROOT)) found_flags = iovec_is_set(&verity->root_hash_sig) ? PARTITION_POLICY_SIGNED : PARTITION_POLICY_VERITY; - else - found_flags = encrypted ? PARTITION_POLICY_ENCRYPTED : PARTITION_POLICY_UNPROTECTED; + else if (encrypted) { + r = partition_is_luks2_integrity(fd, /* offset = */ 0, /* size = */ UINT64_MAX); + if (r < 0) + return r; + + found_flags = r > 0 ? PARTITION_POLICY_ENCRYPTEDWITHINTEGRITY : PARTITION_POLICY_ENCRYPTED; + } else + found_flags = PARTITION_POLICY_UNPROTECTED; r = image_policy_check_protection(policy, PARTITION_ROOT, found_flags); if (r < 0) @@ -1732,7 +1855,7 @@ static int dissect_image( /* Determine the verity protection level for this partition. */ PartitionPolicyFlags found_flags; if (m->partitions[di].found) { - found_flags = PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_UNUSED; + found_flags = PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_ENCRYPTEDWITHINTEGRITY|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_UNUSED; PartitionDesignator vi = partition_verity_hash_of(di); if (vi >= 0 && m->partitions[vi].found) { diff --git a/src/shared/image-policy.c b/src/shared/image-policy.c index 59d422dcd2..e917b03b06 100644 --- a/src/shared/image-policy.c +++ b/src/shared/image-policy.c @@ -78,7 +78,7 @@ static PartitionPolicyFlags partition_policy_normalized_flags(const PartitionPol * all needs no protection, because it *is* the protection */ if (partition_verity_hash_to_data(policy->designator) >= 0 || partition_verity_sig_to_data(policy->designator) >= 0) - flags &= ~(PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_ENCRYPTED); + flags &= ~(PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_ENCRYPTEDWITHINTEGRITY); /* if this designator has no verity concept, then mask off verity protection flags */ if (partition_verity_hash_of(policy->designator) < 0) @@ -187,6 +187,8 @@ static PartitionPolicyFlags policy_flag_from_string_one(const char *s) { return PARTITION_POLICY_SIGNED; if (streq(s, "encrypted")) return PARTITION_POLICY_ENCRYPTED; + if (streq(s, "encryptedwithintegrity")) + return PARTITION_POLICY_ENCRYPTEDWITHINTEGRITY; if (streq(s, "unprotected")) return PARTITION_POLICY_UNPROTECTED; if (streq(s, "unused")) @@ -423,6 +425,8 @@ int partition_policy_flags_to_string(PartitionPolicyFlags flags, bool simplify, l[m++] = "signed"; if (flags & PARTITION_POLICY_ENCRYPTED) l[m++] = "encrypted"; + if (flags & PARTITION_POLICY_ENCRYPTEDWITHINTEGRITY) + l[m++] = "encryptedwithintegrity"; if (flags & PARTITION_POLICY_UNPROTECTED) l[m++] = "unprotected"; if (flags & PARTITION_POLICY_UNUSED) @@ -863,8 +867,8 @@ const ImagePolicy image_policy_sysext = { * be. */ .n_policies = 2, .policies = { - { PARTITION_ROOT, PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT }, - { PARTITION_USR, PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT }, + { PARTITION_ROOT, PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_ENCRYPTEDWITHINTEGRITY|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT }, + { PARTITION_USR, PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_ENCRYPTEDWITHINTEGRITY|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT }, }, .default_flags = PARTITION_POLICY_IGNORE, }; @@ -884,7 +888,7 @@ const ImagePolicy image_policy_confext = { * are only interested in the /etc/ tree anyway, and that's really the only place it can be. */ .n_policies = 1, .policies = { - { PARTITION_ROOT, PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT }, + { PARTITION_ROOT, PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_ENCRYPTEDWITHINTEGRITY|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT }, }, .default_flags = PARTITION_POLICY_IGNORE, }; @@ -901,14 +905,14 @@ const ImagePolicy image_policy_container = { /* For systemd-nspawn containers we use all partitions, with the exception of swap */ .n_policies = 8, .policies = { - { PARTITION_ROOT, PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT }, - { PARTITION_USR, PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT }, - { PARTITION_HOME, PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT }, - { PARTITION_SRV, PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT }, + { PARTITION_ROOT, PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_ENCRYPTEDWITHINTEGRITY|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT }, + { PARTITION_USR, PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_ENCRYPTEDWITHINTEGRITY|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT }, + { PARTITION_HOME, PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_ENCRYPTEDWITHINTEGRITY|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT }, + { PARTITION_SRV, PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_ENCRYPTEDWITHINTEGRITY|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT }, { PARTITION_ESP, PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT }, { PARTITION_XBOOTLDR, PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT }, - { PARTITION_TMP, PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT }, - { PARTITION_VAR, PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT }, + { PARTITION_TMP, PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_ENCRYPTEDWITHINTEGRITY|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT }, + { PARTITION_VAR, PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_ENCRYPTEDWITHINTEGRITY|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT }, }, .default_flags = PARTITION_POLICY_IGNORE, }; @@ -917,15 +921,15 @@ const ImagePolicy image_policy_host = { /* For the host policy we basically use everything */ .n_policies = 9, .policies = { - { PARTITION_ROOT, PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT }, - { PARTITION_USR, PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT }, - { PARTITION_HOME, PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT }, - { PARTITION_SRV, PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT }, + { PARTITION_ROOT, PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_ENCRYPTEDWITHINTEGRITY|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT }, + { PARTITION_USR, PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_ENCRYPTEDWITHINTEGRITY|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT }, + { PARTITION_HOME, PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_ENCRYPTEDWITHINTEGRITY|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT }, + { PARTITION_SRV, PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_ENCRYPTEDWITHINTEGRITY|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT }, { PARTITION_ESP, PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT }, { PARTITION_XBOOTLDR, PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT }, - { PARTITION_SWAP, PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT }, - { PARTITION_TMP, PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT }, - { PARTITION_VAR, PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT }, + { PARTITION_SWAP, PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_ENCRYPTEDWITHINTEGRITY|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT }, + { PARTITION_TMP, PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_ENCRYPTEDWITHINTEGRITY|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT }, + { PARTITION_VAR, PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_ENCRYPTEDWITHINTEGRITY|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT }, }, .default_flags = PARTITION_POLICY_IGNORE, }; @@ -934,12 +938,12 @@ const ImagePolicy image_policy_service = { /* For RootImage= in services we skip ESP/XBOOTLDR and swap */ .n_policies = 6, .policies = { - { PARTITION_ROOT, PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT }, - { PARTITION_USR, PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT }, - { PARTITION_HOME, PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT }, - { PARTITION_SRV, PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT }, - { PARTITION_TMP, PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT }, - { PARTITION_VAR, PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT }, + { PARTITION_ROOT, PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_ENCRYPTEDWITHINTEGRITY|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT }, + { PARTITION_USR, PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_ENCRYPTEDWITHINTEGRITY|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT }, + { PARTITION_HOME, PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_ENCRYPTEDWITHINTEGRITY|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT }, + { PARTITION_SRV, PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_ENCRYPTEDWITHINTEGRITY|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT }, + { PARTITION_TMP, PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_ENCRYPTEDWITHINTEGRITY|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT }, + { PARTITION_VAR, PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_ENCRYPTEDWITHINTEGRITY|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT }, }, .default_flags = PARTITION_POLICY_IGNORE, }; diff --git a/src/shared/image-policy.h b/src/shared/image-policy.h index 037900b840..30d319ed69 100644 --- a/src/shared/image-policy.h +++ b/src/shared/image-policy.h @@ -14,29 +14,30 @@ typedef enum PartitionPolicyFlags { * on. Example: a default policy of "verity+encrypted" certainly makes sense, but for /home/ * partitions this gracefully degrades to "encrypted" (as we do not have a concept of verity for * /home/), and so on. */ - PARTITION_POLICY_VERITY = 1 << 0, /* must exist, activate with verity (only applies to root/usr partitions) */ - PARTITION_POLICY_SIGNED = 1 << 1, /* must exist, activate with signed verity (only applies to root/usr partitions) */ - PARTITION_POLICY_ENCRYPTED = 1 << 2, /* must exist, activate with LUKS encryption (applies to any data partition, but not to verity/signature partitions */ - PARTITION_POLICY_UNPROTECTED = 1 << 3, /* must exist, activate without encryption/verity */ - PARTITION_POLICY_UNUSED = 1 << 4, /* must exist, don't use */ - PARTITION_POLICY_ABSENT = 1 << 5, /* must not exist */ - PARTITION_POLICY_OPEN = PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_ENCRYPTED| - PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_UNUSED|PARTITION_POLICY_ABSENT, - PARTITION_POLICY_IGNORE = PARTITION_POLICY_UNUSED|PARTITION_POLICY_ABSENT, - _PARTITION_POLICY_USE_MASK = PARTITION_POLICY_OPEN, + PARTITION_POLICY_VERITY = 1 << 0, /* must exist, activate with verity (only applies to root/usr partitions) */ + PARTITION_POLICY_SIGNED = 1 << 1, /* must exist, activate with signed verity (only applies to root/usr partitions) */ + PARTITION_POLICY_ENCRYPTED = 1 << 2, /* must exist, activate with LUKS encryption (applies to any data partition, but not to verity/signature partitions */ + PARTITION_POLICY_ENCRYPTEDWITHINTEGRITY = 1 << 3, /* same as PARTITION_POLICY_ENCRYPTED but also requires integrity checking */ + PARTITION_POLICY_UNPROTECTED = 1 << 4, /* must exist, activate without encryption/verity */ + PARTITION_POLICY_UNUSED = 1 << 5, /* must exist, don't use */ + PARTITION_POLICY_ABSENT = 1 << 6, /* must not exist */ + PARTITION_POLICY_OPEN = PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_ENCRYPTEDWITHINTEGRITY| + PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_UNUSED|PARTITION_POLICY_ABSENT, + PARTITION_POLICY_IGNORE = PARTITION_POLICY_UNUSED|PARTITION_POLICY_ABSENT, + _PARTITION_POLICY_USE_MASK = PARTITION_POLICY_OPEN, - PARTITION_POLICY_READ_ONLY_OFF = 1 << 6, /* State of GPT partition flag "read-only" must be on */ - PARTITION_POLICY_READ_ONLY_ON = 1 << 7, - _PARTITION_POLICY_READ_ONLY_MASK = PARTITION_POLICY_READ_ONLY_OFF|PARTITION_POLICY_READ_ONLY_ON, - PARTITION_POLICY_GROWFS_OFF = 1 << 8, /* State of GPT partition flag "growfs" must be on */ - PARTITION_POLICY_GROWFS_ON = 1 << 9, - _PARTITION_POLICY_GROWFS_MASK = PARTITION_POLICY_GROWFS_OFF|PARTITION_POLICY_GROWFS_ON, - _PARTITION_POLICY_PFLAGS_MASK = _PARTITION_POLICY_READ_ONLY_MASK|_PARTITION_POLICY_GROWFS_MASK, + PARTITION_POLICY_READ_ONLY_OFF = 1 << 7, /* State of GPT partition flag "read-only" must be on */ + PARTITION_POLICY_READ_ONLY_ON = 1 << 8, + _PARTITION_POLICY_READ_ONLY_MASK = PARTITION_POLICY_READ_ONLY_OFF|PARTITION_POLICY_READ_ONLY_ON, + PARTITION_POLICY_GROWFS_OFF = 1 << 9, /* State of GPT partition flag "growfs" must be on */ + PARTITION_POLICY_GROWFS_ON = 1 << 10, + _PARTITION_POLICY_GROWFS_MASK = PARTITION_POLICY_GROWFS_OFF|PARTITION_POLICY_GROWFS_ON, + _PARTITION_POLICY_PFLAGS_MASK = _PARTITION_POLICY_READ_ONLY_MASK|_PARTITION_POLICY_GROWFS_MASK, - _PARTITION_POLICY_MASK = _PARTITION_POLICY_USE_MASK|_PARTITION_POLICY_READ_ONLY_MASK|_PARTITION_POLICY_GROWFS_MASK, + _PARTITION_POLICY_MASK = _PARTITION_POLICY_USE_MASK|_PARTITION_POLICY_READ_ONLY_MASK|_PARTITION_POLICY_GROWFS_MASK, - _PARTITION_POLICY_FLAGS_INVALID = -EINVAL, - _PARTITION_POLICY_FLAGS_ERRNO_MAX = -ERRNO_MAX, /* Ensure the whole errno range fits into this enum */ + _PARTITION_POLICY_FLAGS_INVALID = -EINVAL, + _PARTITION_POLICY_FLAGS_ERRNO_MAX = -ERRNO_MAX, /* Ensure the whole errno range fits into this enum */ } PartitionPolicyFlags; assert_cc((_PARTITION_POLICY_USE_MASK | _PARTITION_POLICY_PFLAGS_MASK) >= 0); /* ensure flags don't collide with errno range */ diff --git a/src/test/test-image-policy.c b/src/test/test-image-policy.c index 6cf7dff20b..512df19078 100644 --- a/src/test/test-image-policy.c +++ b/src/test/test-image-policy.c @@ -99,8 +99,8 @@ TEST_RET(test_image_policy_to_string) { test_policy_equiv("~", image_policy_equiv_deny); test_policy_equiv("=absent", image_policy_equiv_deny); test_policy_equiv("=open", image_policy_equiv_allow); - test_policy_equiv("=verity+signed+encrypted+unprotected+unused+absent", image_policy_equiv_allow); - test_policy_equiv("=signed+verity+encrypted+unused+unprotected+absent", image_policy_equiv_allow); + test_policy_equiv("=verity+signed+encrypted+encryptedwithintegrity+unprotected+unused+absent", image_policy_equiv_allow); + test_policy_equiv("=signed+verity+encrypted+encryptedwithintegrity+unused+unprotected+absent", image_policy_equiv_allow); test_policy_equiv("=ignore", image_policy_equiv_ignore); test_policy_equiv("=absent+unused", image_policy_equiv_ignore); test_policy_equiv("=unused+absent", image_policy_equiv_ignore); diff --git a/test/units/TEST-50-DISSECT.encrypted.sh b/test/units/TEST-50-DISSECT.encrypted.sh new file mode 100755 index 0000000000..9d067f597a --- /dev/null +++ b/test/units/TEST-50-DISSECT.encrypted.sh @@ -0,0 +1,48 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -eux +set -o pipefail + +defs="$(mktemp --directory "/tmp/test-repart.defs.XXXXXXXXXX")" +imgs="$(mktemp --directory "/var/tmp/test-repart.imgs.XXXXXXXXXX")" +# shellcheck disable=SC2064 +trap "rm -rf '$defs' '$imgs'" RETURN +chmod 0755 "$defs" + +echo "*** testcase for systemd-dissect and encrypted partitions ***" + +tee "$defs/root.conf" <> "$defs/root.conf" + + systemd-repart --pretty=yes \ + --definitions "$defs" \ + --empty=create \ + --size=100M \ + --seed="$seed" \ + --dry-run=no \ + --offline=no \ + "$imgs/encint.img" + + loop="$(losetup -P --show --find "$imgs/encint.img")" + udevadm wait --timeout=60 --settle "${loop:?}p1" + + volume="test-repart-luksint-$RANDOM" + dmstatus="$imgs/dmsetup-$RANDOM" + + touch "$imgs/empty-password" + + # the expectation for hmac-sha256 is 'integrity: hmac(sha256)' + cryptsetup luksDump "${loop}p1" | grep -q "integrity: $(echo "$1" | sed -r 's/^hmac-(.*)$/hmac(\1)/')" + + cryptsetup open --type=luks2 --key-file="$imgs/empty-password" "${loop}p1" "$volume" + dmsetup status > "$dmstatus" + cryptsetup close "$volume" + losetup -d "$loop" + # Check that there's a dm-integrity entry + grep -q "$volume""_dif.* integrity " "$dmstatus" +} + +testcase_luks2_integrity() { + _test_luks2_integrity "" + _test_luks2_integrity "hmac-sha1" + _test_luks2_integrity "hmac-sha256" + _test_luks2_integrity "hmac-sha512" +} + OFFLINE="yes" run_testcases diff --git a/test/units/TEST-65-ANALYZE.sh b/test/units/TEST-65-ANALYZE.sh index ed20be680a..f98ececc53 100755 --- a/test/units/TEST-65-ANALYZE.sh +++ b/test/units/TEST-65-ANALYZE.sh @@ -1083,7 +1083,7 @@ check deny no "$name" # Let's also test the "image-policy" verb -systemd-analyze image-policy '*' 2>&1 | grep -F "Long form: =verity+signed+encrypted+unprotected+unused+absent" >/dev/null +systemd-analyze image-policy '*' 2>&1 | grep -F "Long form: =verity+signed+encrypted+encryptedwithintegrity+unprotected+unused+absent" >/dev/null systemd-analyze image-policy '-' 2>&1 | grep -F "Long form: =unused+absent" >/dev/null systemd-analyze image-policy 'home=encrypted:usr=verity' 2>&1 | grep -F "Long form: usr=verity:home=encrypted:=unused+absent" >/dev/null systemd-analyze image-policy 'home=encrypted:usr=verity' 2>&1 | grep -e '^home \+encrypted \+' >/dev/null