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;