From 97136585da6477da050cb336c433da303dc1ee6a Mon Sep 17 00:00:00 2001 From: Ingo Franzki Date: Mon, 17 Feb 2025 16:48:11 +0100 Subject: [PATCH] zkey-cryptsetup: Support PHMAC integrity with convert command MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In case the volume is integrity protected, and the integrity algorithm is PHMAC, then the convert command also checks the integrity key part of the volume key, and then uses the secure integrity key, and sets the verification pattern to the verification-pattern token. Signed-off-by: Ingo Franzki Reviewed-by: Finn Callies Signed-off-by: Jan Höppner --- zkey/pkey.c | 28 ++++--- zkey/pkey.h | 6 ++ zkey/zkey-cryptsetup.1 | 12 +++ zkey/zkey-cryptsetup.c | 176 ++++++++++++++++++++++++++++++++++++++--- 4 files changed, 201 insertions(+), 21 deletions(-) diff --git a/zkey/pkey.c b/zkey/pkey.c index 6d0bfd5d..76147a7d 100644 --- a/zkey/pkey.c +++ b/zkey/pkey.c @@ -1635,10 +1635,11 @@ out: * * @returns 0 on success, a negative errno in case of an error */ -static int generate_hmac_key_verification_pattern(const u8 *key, - size_t key_size, - char *vp, size_t vp_len, - bool verbose) +int generate_hmac_key_verification_pattern(const u8 *key, + size_t key_size, + char *vp, size_t vp_len, + const char *cipher, + bool verbose) { int tfmfd = -1, opfd = -1, rc = 0, retry_count = 0; char null_msg[MAC_ZERO_LEN]; @@ -1656,13 +1657,18 @@ static int generate_hmac_key_verification_pattern(const u8 *key, goto out; } - rc = get_key_bit_size(key, key_size, &bitsize); - if (rc != 0) { - pr_verbose(verbose, "Failed to get the key size"); - goto out; + if (cipher != NULL) { + util_strlcpy((char *)sa.salg_name, cipher, + sizeof(sa.salg_name)); + } else { + rc = get_key_bit_size(key, key_size, &bitsize); + if (rc != 0) { + pr_verbose(verbose, "Failed to get the key size"); + goto out; + } + snprintf((char *)sa.salg_name, sizeof(sa.salg_name), + "phmac(sha%lu)", bitsize / 2); } - snprintf((char *)sa.salg_name, sizeof(sa.salg_name), "phmac(sha%lu)", - bitsize / 2); tfmfd = socket(AF_ALG, SOCK_SEQPACKET, 0); if (tfmfd < 0) { @@ -1767,7 +1773,7 @@ int generate_key_verification_pattern(const u8 *key, size_t key_size, if (is_hmac_key(key, key_size)) return generate_hmac_key_verification_pattern(key, key_size, vp, vp_len, - verbose); + NULL, verbose); pr_verbose(verbose, "Neither an AES nor an HMAC key"); return -EINVAL; diff --git a/zkey/pkey.h b/zkey/pkey.h index 7a1022f0..6e3a872f 100644 --- a/zkey/pkey.h +++ b/zkey/pkey.h @@ -362,6 +362,12 @@ int generate_aes_key_verification_pattern(const u8 *key, size_t key_size, const char *cipher, bool verbose); +int generate_hmac_key_verification_pattern(const u8 *key, + size_t key_size, + char *vp, size_t vp_len, + const char *cipher, + bool verbose); + int get_master_key_verification_pattern(const u8 *key, size_t key_size, u8 *mkvp, bool verbose); diff --git a/zkey/zkey-cryptsetup.1 b/zkey/zkey-cryptsetup.1 index e962cfd4..83298264 100644 --- a/zkey/zkey-cryptsetup.1 +++ b/zkey/zkey-cryptsetup.1 @@ -372,6 +372,8 @@ relevance. .I device .BR \-\-volume\-key\-file | \-m .IR file\-name +.RB [ \-\-integrity\-key\-size | \-I +.IR bytes ] .RB [ \-\-key\-file | \-d .IR file\-name ] .RB [ \-\-keyfile\-offset | \-o @@ -400,6 +402,13 @@ recoverable after a failing conversion, if you don't have a LUKS header backup. Specify the secure key file with option .B \-\-volume\-key\-file to convert the volume to use this secure key as the new volume key. +When the volume uses integrity protection, and you want to also use a secure +integrity key, then concatenate the secure AES encryption key and the secure +HMAC key, and specify the result as volume key. You must then also specify the +size of the secure HMAC key in bytes using the +.B \-\-integrity\-key\-size +option. This will then convert the integrity algorithm to use the \fBphmac\fP +cipher. .PP To open a key slot contained in the LUKS2 header of the volume, a passphrase is required. You are prompted for the passphrase, unless option @@ -504,6 +513,9 @@ Alias for the .BR \-\-volume\-key\-file\~\fIfile\-name\fP option. .TP +.BR \-I ", " \-\-integrity\-key\-size\~\fIbytes\fP +Specifies the size of the secure HMAC key part in bytes. +.TP .BR \-q ", " \-\-batch\-mode Suppresses all confirmation questions. Use with care! . diff --git a/zkey/zkey-cryptsetup.c b/zkey/zkey-cryptsetup.c index d463d9f0..5293f2b5 100644 --- a/zkey/zkey-cryptsetup.c +++ b/zkey/zkey-cryptsetup.c @@ -106,6 +106,7 @@ static struct zkey_cryptsetup_globals { bool inplace; bool staged; char *volume_key_file; + long long integrity_key_size; bool batch_mode; bool debug; bool verbose; @@ -266,7 +267,8 @@ static struct util_opt opt_vec[] = { .option = {"volume-key-file", required_argument, NULL, 'm'}, .argument = "FILE-NAME", .desc = "Specifies the name of a file containing the secure " - "AES key that is set as new volume key", + "AES key (and optionally a secure HMAC key) that is " + "set as new volume key", .command = COMMAND_CONVERT, }, { @@ -276,6 +278,16 @@ static struct util_opt opt_vec[] = { .command = COMMAND_CONVERT, .flags = UTIL_OPT_FLAG_NOSHORT, }, + { + .option = {"integrity-key-size", required_argument, NULL, 'I'}, + .argument = "BYTES", + .desc = "Size of the secure integrity key part of the volume " + "key in bytes. Specifying this option implies that " + "the integrity key part of the volume key is a secure " + "HMAC key. For using a clear integrity key, do not " + "specify this option.", + .command = COMMAND_CONVERT, + }, OPT_PASSPHRASE_ENTRY(COMMAND_CONVERT), { .option = {"batch-mode", 0, NULL, 'q'}, @@ -2798,16 +2810,23 @@ out: static int command_convert(void) { struct crypt_params_integrity ip_new = { 0 }; + char new_int_vp[VERIFICATION_PATTERN_LEN]; struct crypt_params_integrity ip = { 0 }; struct crypt_params_luks2 luks2 = { 0 }; + char int_vp[VERIFICATION_PATTERN_LEN]; char new_vp[VERIFICATION_PATTERN_LEN]; struct crypt_device *new_cd = NULL; char vp[VERIFICATION_PATTERN_LEN]; struct vp_token vp_tok = { 0 }; + const char *hmac_vp_alg = NULL; size_t integrity_keysize = 0; + char integrity_alg[200]; + size_t hmac_keysize = 0; size_t password_len = 0; size_t newekey_size = 0; + size_t newikey_size = 0; size_t newkey_size = 0; + int is_phmac_integrity; char *password = NULL; size_t ekeysize = 0; size_t keysize = 0; @@ -2845,26 +2864,78 @@ static int command_convert(void) if (rc == 0) integrity_keysize = ip.integrity_key_size; - newekey_size = newkey_size - integrity_keysize; + is_phmac_integrity = (g.integrity_key_size > 0); + + newikey_size = is_phmac_integrity ? + (size_t)g.integrity_key_size : integrity_keysize; + newekey_size = newkey_size - newikey_size; rc = check_keysize_and_cipher_mode(newkey, newekey_size); if (rc != 0) goto out; + if (is_phmac_integrity) { + if (strcmp(ip.integrity, "hmac(sha224)") != 0 && + strcmp(ip.integrity, "hmac(sha256)") != 0 && + strcmp(ip.integrity, "hmac(sha384)") != 0 && + strcmp(ip.integrity, "hmac(sha512)") != 0) { + warnx("The volume uses integrity algorithm '%s' and " + "thus can not be converted to use the 'phmac' " + "cipher", ip.integrity); + rc = -EINVAL; + goto out; + } + + if (is_pvsecret_hmac_key(newkey + newekey_size, newikey_size)) { + switch (hmac_keysize) { + case 512: + if (strcmp(ip.integrity, "hmac(sha224)") != 0 && + strcmp(ip.integrity, "hmac(sha256)") != 0) { + warnx("The secure integrity key only " + "supports 'hmac(sha224)' or " + "'hmac(sha256)' as integrity " + "algorithm, but the volume uses" + "'%s'", ip.integrity); + rc = -EINVAL; + goto out; + } + break; + case 1024: + if (strcmp(ip.integrity, "hmac(sha384)") != 0 && + strcmp(ip.integrity, "hmac(sha512)") != 0) { + warnx("The secure integrity key only " + "supports 'hmac(sha384)' or " + "'hmac(sha512)' as integrity " + "algorithm, but the volume uses" + "'%s'", ip.integrity); + rc = -EINVAL; + goto out; + } + break; + default: + break; + } + } + } + rc = validate_secure_key(g.pkey_fd, newkey, newekey_size, NULL, &is_old_mk, NULL, g.verbose); if (rc != 0) { - warnx("The secure key in file '%s' is not valid", + warnx("The %s key%s in file '%s' is not valid", + is_phmac_integrity ? "encryption" : "secure", + is_phmac_integrity ? " part" : "", g.volume_key_file); goto out; } if (is_secure_key(newkey, newekey_size) && is_old_mk) { - util_asprintf(&msg, "The secure key in file '%s' is " + util_asprintf(&msg, "The %s key%s in file '%s' is " "enciphered with the master key in the OLD " "master key register. Do you want to set this " "key as the new volume key anyway [y/N]?", + is_phmac_integrity ? "encryption" : "secure", + is_phmac_integrity ? " part" : "", g.volume_key_file); util_print_indented(msg, 0); free(msg); @@ -2876,6 +2947,35 @@ static int command_convert(void) } } + if (is_phmac_integrity) { + rc = validate_secure_key(g.pkey_fd, newkey + newekey_size, + newikey_size, NULL, + &is_old_mk, NULL, g.verbose); + if (rc != 0) { + warnx("The integrity key part in file '%s' is not " + "valid", g.volume_key_file); + goto out; + } + + if (is_secure_key(newkey + newekey_size, newikey_size) && + is_old_mk) { + util_asprintf(&msg, "The integrity key part in file " + "'%s' is enciphered with the master key " + "in the OLD master key register. Do you " + "want to set this key as the new volume " + "key anyway [y/N]?", g.volume_key_file); + util_print_indented(msg, 0); + free(msg); + + if (!_prompt_for_yes()) { + warnx("Device '%s' is left unchanged", + g.pos_arg); + rc = -EINVAL; + goto out; + } + } + } + rc = generate_key_verification_pattern(newkey, newekey_size, new_vp, sizeof(new_vp), g.verbose); @@ -2887,6 +2987,22 @@ static int command_convert(void) goto out; } + if (is_phmac_integrity) { + rc = generate_key_verification_pattern(newkey + newekey_size, + newikey_size, + new_int_vp, + sizeof(new_int_vp), + g.verbose); + if (rc != 0) { + warnx("Failed to generate the verification pattern: %s", + strerror(-rc)); + warnx("Make sure that kernel module 'phmac_s390' is " + "loaded and that the 'phmac' cipher is " + "available"); + goto out; + } + } + /* Get current (clear) volume key from LUKS2 header */ util_asprintf(&prompt, "Enter passphrase for '%s': ", g.pos_arg); rc = open_keyslot(CRYPT_ANY_SLOT, &key, &keysize, NULL, NULL, @@ -2897,13 +3013,13 @@ static int command_convert(void) ekeysize = keysize - integrity_keysize; - if (integrity_keysize > 0 && + if (newikey_size > 0 && !is_phmac_integrity && memcmp(newkey + newekey_size, key + ekeysize, - integrity_keysize) != 0) { + newikey_size) != 0) { warnx("The secure key in file '%s' contains a different " "integrity key (i.e. the last %lu bytes of the key) than " "the current volume key.", g.volume_key_file, - integrity_keysize); + newikey_size); rc = -EINVAL; goto out; } @@ -2919,7 +3035,29 @@ static int command_convert(void) goto out; } - if (strcmp(vp, new_vp) != 0) { + if (is_phmac_integrity) { + if (strcmp(ip.integrity, "hmac(sha224)") == 0 || + strcmp(ip.integrity, "hmac(sha256") == 0) + hmac_vp_alg = "hmac(sha256)"; + else if (strcmp(ip.integrity, "hmac(sha384)") == 0 || + strcmp(ip.integrity, "hmac(sha512)") != 0) + hmac_vp_alg = "hmac(sha512)"; + + rc = generate_hmac_key_verification_pattern(key + ekeysize, + integrity_keysize, + int_vp, + sizeof(int_vp), + hmac_vp_alg, + g.verbose); + if (rc != 0) { + warnx("Failed to generate the verification pattern: %s", + strerror(-rc)); + goto out; + } + } + + if (strcmp(vp, new_vp) != 0 || + (is_phmac_integrity && strcmp(int_vp, new_int_vp) != 0)) { warnx("The verification patterns of the new and old " "volume keys do not match"); rc = -EINVAL; @@ -2958,11 +3096,14 @@ static int command_convert(void) /* Re-format the device with the same settings, but using 'paes' */ #ifdef CRYPT_ACTIVATE_ERROR_AS_CORRUPTION /* Indicates libcryptsetup > 2.7.5 */ - ip_new.integrity_key_size = integrity_keysize; + ip_new.integrity_key_size = newikey_size; #endif + snprintf(integrity_alg, sizeof(integrity_alg), "%s%s", + is_phmac_integrity ? "p" : "", ip.integrity); + luks2.pbkdf = crypt_get_pbkdf_type(g.cd); - luks2.integrity = integrity_keysize > 0 ? ip.integrity : NULL; + luks2.integrity = integrity_keysize > 0 ? integrity_alg : NULL; luks2.integrity_params = integrity_keysize > 0 ? &ip_new : NULL; luks2.data_alignment = 0; luks2.data_device = NULL; @@ -2995,6 +3136,9 @@ static int command_convert(void) /* Add the verification pattern token */ memcpy(vp_tok.verification_pattern, new_vp, sizeof(vp_tok.verification_pattern)); + if (is_phmac_integrity) + memcpy(vp_tok.int_verification_pattern, new_int_vp, + sizeof(vp_tok.int_verification_pattern)); rc = put_vp_token(new_cd, -1, &vp_tok); if (rc < 0) @@ -3150,6 +3294,18 @@ int main(int argc, char *argv[]) case 'm': g.volume_key_file = optarg; break; + case 'I': + g.integrity_key_size = strtoll(optarg, &endp, 0); + if (*optarg == '\0' || *endp != '\0' || + g.integrity_key_size <= 0 || + (g.integrity_key_size == LLONG_MAX && + errno == ERANGE)) { + warnx("Invalid value for '--integrity-key-size'" + "|'-I': '%s'", optarg); + util_prg_print_parse_error(); + return EXIT_FAILURE; + } + break; case 'q': g.batch_mode = true; break;