Subject: zkey: Add zkey-cryptsetup tool From: Ingo Franzki Summary: zkey: Support CCA master key change with LUKS2 volumes using paes Description: Support the usage of protected key crypto for dm-crypt disks in LUKS2 format by providing a tool allowing to re-encipher a secure LUKS2 volume key when the CCA master key is changed Upstream-ID: 4eb80d14a0554c8a404f06beeb53522e45d5df6e Problem-ID: SEC1424.1 Upstream-Description: zkey: Add zkey-cryptsetup tool The zkey-cryptsetup tool is used to validate and re-encipher secure AES volume keys of volumes encrypted with LUKS2 and the paes cipher. Signed-off-by: Ingo Franzki Reviewed-by: Hendrik Brueckner Signed-off-by: Jan Höppner Signed-off-by: Ingo Franzki --- zkey/Makefile | 12 zkey/pkey.c | 158 +++ zkey/pkey.h | 7 zkey/zkey-cryptsetup.c | 2270 +++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 2444 insertions(+), 3 deletions(-) --- a/zkey/Makefile +++ b/zkey/Makefile @@ -18,9 +18,8 @@ check_dep: "HAVE_OPENSSL=0") CPPFLAGS += -I../include -LDLIBS += -ldl -lcrypto -all: check_dep zkey +all: check_dep zkey zkey-cryptsetup libs = $(rootdir)/libutil/libutil.a @@ -28,12 +27,19 @@ zkey.o: zkey.c pkey.h misc.h pkey.o: pkey.c pkey.h properties.o: properties.c properties.h keystore.o: keystore.c keystore.h properties.h +zkey-cryptsetup.o: zkey-cryptsetup.c pkey.h misc.h +zkey: LDLIBS = -ldl -lcrypto zkey: zkey.o pkey.o properties.o keystore.o $(libs) +zkey-cryptsetup: LDLIBS = -ldl -lcryptsetup -ljson-c +zkey-cryptsetup: zkey-cryptsetup.o pkey.o $(libs) + + install: all $(INSTALL) -d -m 755 $(DESTDIR)$(USRBINDIR) $(INSTALL) -g $(GROUP) -o $(OWNER) -m 755 zkey $(DESTDIR)$(USRBINDIR) + $(INSTALL) -g $(GROUP) -o $(OWNER) -m 755 zkey-cryptsetup $(DESTDIR)$(USRBINDIR) $(INSTALL) -d -m 755 $(DESTDIR)$(MANDIR)/man1 $(INSTALL) -m 644 -c zkey.1 $(DESTDIR)$(MANDIR)/man1 $(INSTALL) -d -m 770 $(DESTDIR)$(SYSCONFDIR)/zkey @@ -42,6 +48,6 @@ install: all endif clean: - rm -f *.o zkey + rm -f *.o zkey zkey-cryptsetup .PHONY: all install clean --- a/zkey/pkey.c +++ b/zkey/pkey.c @@ -11,11 +11,13 @@ #include #include #include +#include #include #include #include #include #include +#include #include #include @@ -25,6 +27,12 @@ #include "pkey.h" +#ifndef AF_ALG +#define AF_ALG 38 +#endif +#ifndef SOL_ALG +#define SOL_ALG 279 +#endif #define pr_verbose(verbose, fmt...) do { \ if (verbose) \ @@ -34,6 +42,8 @@ #define DOUBLE_KEYSIZE_FOR_XTS(keysize, xts) ((xts) ? 2 * (keysize) : (keysize)) #define HALF_KEYSIZE_FOR_XTS(keysize, xts) ((xts) ? (keysize) / 2 : (keysize)) +#define MAX_CIPHER_LEN 32 + /* * Definitions for the CCA library */ @@ -367,6 +377,8 @@ int generate_secure_key_random(int pkey_ if (rc < 0) { rc = -errno; warnx("Failed to generate a secure key: %s", strerror(errno)); + warnx("Make sure that all available CCA crypto adapters are " + "setup with the same master key"); goto out; } @@ -378,6 +390,8 @@ int generate_secure_key_random(int pkey_ rc = -errno; warnx("Failed to generate a secure key: %s", strerror(errno)); + warnx("Make sure that all available CCA crypto " + "adapters are setup with the same master key"); goto out; } @@ -465,6 +479,8 @@ int generate_secure_key_clear(int pkey_f rc = -errno; warnx("Failed to generate a secure key from a " "clear key: %s", strerror(errno)); + warnx("Make sure that all available CCA crypto adapters are " + "setup with the same master key"); goto out; } @@ -479,6 +495,8 @@ int generate_secure_key_clear(int pkey_f rc = -errno; warnx("Failed to generate a secure key from " "a clear key: %s", strerror(errno)); + warnx("Make sure that all available CCA crypto " + "adapters are setup with the same master key"); goto out; } @@ -746,3 +764,143 @@ int validate_secure_key(int pkey_fd, return 0; } + +/** + * Generate a key verification pattern of a secure key by encrypting the all + * zero message with the secure key using the AF_ALG interface + * + * @param[in] key the secure key token + * @param[in] key_size the size of the secure key + * @param[in] vp buffer where the verification pattern is returned + * @param[in] vp_len the size of the buffer + * @param[in] verbose if true, verbose messages are printed + * + * @returns 0 on success, a negative errno in case of an error + */ +int generate_key_verification_pattern(const char *key, size_t key_size, + char *vp, size_t vp_len, bool verbose) +{ + int tfmfd = -1, opfd = -1, rc = 0; + char null_msg[ENC_ZERO_LEN]; + char enc_zero[ENC_ZERO_LEN]; + struct af_alg_iv *alg_iv; + struct cmsghdr *header; + uint32_t *type; + ssize_t len; + size_t i; + + struct sockaddr_alg sa = { + .salg_family = AF_ALG, + .salg_type = "skcipher", + }; + struct iovec iov = { + .iov_base = (void *)null_msg, + .iov_len = sizeof(null_msg), + }; + int iv_msg_size = CMSG_SPACE(sizeof(*alg_iv) + PAES_BLOCK_SIZE); + char buffer[CMSG_SPACE(sizeof(*type)) + iv_msg_size]; + struct msghdr msg = { + .msg_control = buffer, + .msg_controllen = sizeof(buffer), + .msg_iov = &iov, + .msg_iovlen = 1, + }; + + if (vp_len < VERIFICATION_PATTERN_LEN) { + rc = -EMSGSIZE; + goto out; + } + + snprintf((char *)sa.salg_name, sizeof(sa.salg_name), "%s(paes)", + key_size > SECURE_KEY_SIZE ? "xts" : "cbc"); + + tfmfd = socket(AF_ALG, SOCK_SEQPACKET, 0); + if (tfmfd < 0) { + rc = -errno; + pr_verbose(verbose, "Failed to open an AF_ALG socket"); + goto out; + } + + if (bind(tfmfd, (struct sockaddr *)&sa, sizeof(sa)) < 0) { + rc = -errno; + pr_verbose(verbose, "Failed to bind the AF_ALG socket, " + "salg_name='%s' ", sa.salg_name); + goto out; + } + + if (setsockopt(tfmfd, SOL_ALG, ALG_SET_KEY, key, + key_size) < 0) { + rc = -errno; + pr_verbose(verbose, "Failed to set the key"); + goto out; + } + + opfd = accept(tfmfd, NULL, 0); + if (opfd < 0) { + rc = -errno; + pr_verbose(verbose, "Failed to accept on the AF_ALG socket"); + goto out; + } + + memset(null_msg, 0, sizeof(null_msg)); + memset(buffer, 0, sizeof(buffer)); + + header = CMSG_FIRSTHDR(&msg); + if (header == NULL) { + pr_verbose(verbose, "Failed to obtain control message header"); + rc = -EINVAL; + goto out; + } + + header->cmsg_level = SOL_ALG; + header->cmsg_type = ALG_SET_OP; + header->cmsg_len = CMSG_LEN(sizeof(*type)); + type = (void *)CMSG_DATA(header); + *type = ALG_OP_ENCRYPT; + + header = CMSG_NXTHDR(&msg, header); + if (header == NULL) { + pr_verbose(verbose, "Failed to obtain control message " + "header"); + rc = -EINVAL; + goto out; + } + header->cmsg_level = SOL_ALG; + header->cmsg_type = ALG_SET_IV; + header->cmsg_len = iv_msg_size; + alg_iv = (void *)CMSG_DATA(header); + alg_iv->ivlen = PAES_BLOCK_SIZE; + memcpy(alg_iv->iv, null_msg, PAES_BLOCK_SIZE); + + len = sendmsg(opfd, &msg, 0); + if (len != ENC_ZERO_LEN) { + pr_verbose(verbose, "Failed to send to the AF_ALG socket"); + rc = -errno; + goto out; + } + + len = read(opfd, enc_zero, sizeof(enc_zero)); + if (len != ENC_ZERO_LEN) { + pr_verbose(verbose, "Failed to receive from the AF_ALG socket"); + rc = -errno; + goto out; + } + + memset(vp, 0, vp_len); + for (i = 0; i < sizeof(enc_zero); i++) + sprintf(&vp[i * 2], "%02x", enc_zero[i]); + + pr_verbose(verbose, "Key verification pattern: %s", vp); + +out: + if (opfd != -1) + close(opfd); + if (tfmfd != -1) + close(tfmfd); + + if (rc != 0) + pr_verbose(verbose, "Failed to generate the key verification " + "pattern: %s", strerror(-rc)); + + return rc; +} --- a/zkey/pkey.h +++ b/zkey/pkey.h @@ -93,6 +93,10 @@ typedef void (*t_CSNBKTC)(long *return_c unsigned char *rule_array, unsigned char *key_identifier); +#define PAES_BLOCK_SIZE 16 +#define ENC_ZERO_LEN (2 * PAES_BLOCK_SIZE) +#define VERIFICATION_PATTERN_LEN (2 * ENC_ZERO_LEN + 1) + int load_cca_library(void **lib_csulcca, t_CSNBKTC *dll_CSNBKTC, bool verbose); int open_pkey_device(bool verbose); @@ -122,4 +126,7 @@ int key_token_change(t_CSNBKTC dll_CSNBK u8 *secure_key, unsigned int secure_key_size, char *method, bool verbose); +int generate_key_verification_pattern(const char *key, size_t key_size, + char *vp, size_t vp_len, bool verbose); + #endif --- /dev/null +++ b/zkey/zkey-cryptsetup.c @@ -0,0 +1,2270 @@ +/* + * zkey-cryptsetup - Re-encipher or validate volume keys of volumes + * encrypted with LUKS2 and the paes cipher. + * + * Copyright IBM Corp. 2018 + * + * s390-tools is free software; you can redistribute it and/or modify + * it under the terms of the MIT license. See LICENSE for details. + */ + +#define _LARGEFILE64_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "lib/util_base.h" +#include "lib/util_libc.h" +#include "lib/util_opt.h" +#include "lib/util_panic.h" +#include "lib/util_prg.h" +#include "lib/zt_common.h" + +#include "misc.h" +#include "pkey.h" + +#define MAX_KEY_SIZE (8 * 1024 * 1024) +#define MAX_PASSWORD_SIZE 512 +#define KEYFILE_BUFLEN 4096 +#define SEEK_BUFLEN 4096 + +#define PAES_VP_TOKEN_NAME "paes-verification-pattern" +#define PAES_VP_TOKEN_VP "verification-pattern" + +#define PAES_REENC_TOKEN_NAME "paes-reencipher" +#define PAES_REENC_TOKEN_VP "verification-pattern" +#define PAES_REENC_TOKEN_ORG_SLOT "original-keyslot" +#define PAES_REENC_TOKEN_UNB_SLOT "unbound-keyslot" + +struct reencipher_token { + char verification_pattern[VERIFICATION_PATTERN_LEN]; + unsigned int original_keyslot; + unsigned int unbound_keyslot; +}; + +struct vp_token { + char verification_pattern[VERIFICATION_PATTERN_LEN]; +}; + +__attribute__ ((unused)) +static void misc_print_required_parms(const char *parm_name1, + const char *parm_name2); + +/* + * Program configuration + */ +const struct util_prg prg = { + .desc = "Manage secure volume keys of volumes encrypted with LUKS2 and " + "the 'paes' cipher", + .command_args = "COMMAND DEVICE", + .args = "", + .copyright_vec = { + { + .owner = "IBM Corp.", + .pub_first = 2018, + .pub_last = 2018, + }, + UTIL_PRG_COPYRIGHT_END + } +}; + +/* + * Global variables for program options + */ +static struct zkey_cryptsetup_globals { + char *pos_arg; + char *keyfile; + long long keyfile_offset; + long long keyfile_size; + long long tries; + bool complete; + bool inplace; + bool staged; + char *master_key_file; + bool debug; + bool verbose; + void *lib_csulcca; + t_CSNBKTC dll_CSNBKTC; + int pkey_fd; + struct crypt_device *cd; +} g = { + .tries = 3, + .pkey_fd = -1, +}; + +/* + * Available commands + */ +#define COMMAND_REENCIPHER "reencipher" +#define COMMAND_VALIDATE "validate" +#define COMMAND_SETVP "setvp" +#define COMMAND_SETKEY "setkey" + +#define ZKEY_CRYPTSETUP_COMMAND_MAX_LEN 10 + +/* + * These options are exactly the same as for the cryptsetup tool + */ +#define OPT_PASSPHRASE_ENTRY(cmd) \ +{ \ + .option = {"key-file", required_argument, NULL, 'd'}, \ + .argument = "FILE-NAME", \ + .desc = "Read the passphrase from the specified file", \ + .command = cmd, \ +}, \ +{ \ + .option = {"keyfile-offset", required_argument, NULL, 'o'}, \ + .argument = "BYTES", \ + .desc = "Specifies the number of bytes to skip in the file " \ + "specified with option '--key-file'|'-d'", \ + .command = cmd, \ +}, \ +{ \ + .option = {"keyfile-size", required_argument, NULL, 'l'}, \ + .argument = "BYTES", \ + .desc = "Specifies the number of bytes to read from the file " \ + "specified with option '--key-file'|'-d'", \ + .command = cmd, \ +}, \ +{ \ + .option = {"tries", required_argument, NULL, 'T'}, \ + .argument = "NUMBER", \ + .desc = "Specifies how often the interactive input of the " \ + "passphrase can be retried", \ + .command = cmd, \ +} + +/* + * Configuration of command line options + */ +static struct util_opt opt_vec[] = { + /***********************************************************/ + { + .flags = UTIL_OPT_FLAG_SECTION, + .desc = "OPTIONS", + .command = COMMAND_REENCIPHER, + }, + { + .option = {"staged", 0, NULL, 's'}, + .desc = "Forces that the re-enciphering of a secure volume " + "key in the LUKS2 header is performed in staged mode", + .command = COMMAND_REENCIPHER, + }, + { + .option = {"in-place", 0, NULL, 'i'}, + .desc = "Forces an in-place re-enciphering of a secure volume " + "key in the LUKS2 header", + .command = COMMAND_REENCIPHER, + }, + { + .option = {"complete", 0, NULL, 'c'}, + .desc = "Completes a staged re-enciphering. Use this option " + "after the new CCA master key has been set (made " + "active)", + .command = COMMAND_REENCIPHER, + }, + OPT_PASSPHRASE_ENTRY(COMMAND_REENCIPHER), + /***********************************************************/ + { + .flags = UTIL_OPT_FLAG_SECTION, + .desc = "OPTIONS", + .command = COMMAND_VALIDATE, + }, + OPT_PASSPHRASE_ENTRY(COMMAND_VALIDATE), + /***********************************************************/ + { + .flags = UTIL_OPT_FLAG_SECTION, + .desc = "OPTIONS", + .command = COMMAND_SETVP, + }, + OPT_PASSPHRASE_ENTRY(COMMAND_SETVP), + /***********************************************************/ + { + .flags = UTIL_OPT_FLAG_SECTION, + .desc = "OPTIONS", + .command = COMMAND_SETKEY, + }, + { + .option = {"master-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", + .command = COMMAND_SETKEY, + }, + OPT_PASSPHRASE_ENTRY(COMMAND_SETKEY), + /***********************************************************/ + { + .flags = UTIL_OPT_FLAG_SECTION, + .desc = "COMMON OPTIONS" + }, + { + .option = {"debug", 0, NULL, 'D'}, + .desc = "Print additional debugging messages during " + "processing", + }, + { + .option = {"verbose", 0, NULL, 'V'}, + .desc = "Print additional information messages during " + "processing", + }, + UTIL_OPT_HELP, + UTIL_OPT_VERSION, + UTIL_OPT_END +}; + +#define ZKEY_CRYPTSETUP_COMMAND_STR_LEN 80 + +/* + * Table of supported commands + */ +struct zkey_cryptsetup_command { + char *command; + unsigned int abbrev_len; + int (*function)(void); + int need_cca_library; + int need_pkey_device; + char *short_desc; + char *long_desc; + int has_options; + char *pos_arg; + int open_device; +}; + +static int command_reencipher(void); +static int command_validate(void); +static int command_setvp(void); +static int command_setkey(void); + +static struct zkey_cryptsetup_command zkey_cryptsetup_commands[] = { + { + .command = COMMAND_REENCIPHER, + .abbrev_len = 2, + .function = command_reencipher, + .need_cca_library = 1, + .need_pkey_device = 1, + .short_desc = "Re-encipher a secure volume key", + .long_desc = "Re-encipher a secure volume key of a volume " + "encrypted with LUKS2 and the 'paes' cipher", + .has_options = 1, + .pos_arg = "DEVICE", + .open_device = 1, + }, + { + .command = COMMAND_VALIDATE, + .abbrev_len = 3, + .function = command_validate, + .need_pkey_device = 1, + .short_desc = "Validate a secure volume key", + .long_desc = "Validate a secure volume key of a volume " + "encrypted with LUKS2 and the 'paes' cipher", + .has_options = 1, + .pos_arg = "DEVICE", + .open_device = 1, + }, + { + .command = COMMAND_SETVP, + .abbrev_len = 4, + .function = command_setvp, + .need_pkey_device = 1, + .short_desc = "Set a verification pattern of the secure volume " + "key", + .long_desc = "Set a verification pattern of the secure AES " + "volume key of a volume encrypted with LUKS2 and " + "the 'paes' cipher", + .has_options = 1, + .pos_arg = "DEVICE", + .open_device = 1, + }, + { + .command = COMMAND_SETKEY, + .abbrev_len = 4, + .function = command_setkey, + .need_pkey_device = 1, + .short_desc = "Set a new secure volume key", + .long_desc = "Set a new secure AES volume key for a volume " + "encrypted with LUKS2 and the 'paes' cipher", + .has_options = 1, + .pos_arg = "DEVICE", + .open_device = 1, + }, + { .command = NULL } +}; + +#define pr_verbose(fmt...) do { \ + if (g.verbose) \ + warnx(fmt); \ + } while (0) + +static volatile int quit; + +/* + * Signal handler for SIGINT and SIGTERM + */ +static void int_handler(int sig __attribute__((__unused__))) +{ + quit++; +} + +/* + * Install signal handler for SIGINT and SIGTERM + */ +static void set_int_handler(void) +{ + struct sigaction sigaction_open; + + pr_verbose("Installing SIGINT/SIGTERM handler"); + memset(&sigaction_open, 0, sizeof(struct sigaction)); + sigaction_open.sa_handler = int_handler; + sigaction(SIGINT, &sigaction_open, 0); + sigaction(SIGTERM, &sigaction_open, 0); +} + +static void print_usage_command(const struct zkey_cryptsetup_command *command) +{ + char command_str[ZKEY_CRYPTSETUP_COMMAND_STR_LEN]; + unsigned int i; + + strncpy(command_str, command->command, sizeof(command_str) - 1); + for (i = 0; i < command->abbrev_len; i++) + command_str[i] = toupper(command_str[i]); + + printf("Usage: %s %s", + program_invocation_short_name, command_str); + if (command->pos_arg != NULL) + printf(" %s", command->pos_arg); + if (command->has_options) + printf(" [OPTIONS]"); + if (prg.args) + printf(" %s", prg.args); + printf("\n\n"); + util_print_indented(command->long_desc, 0); + + if (command->has_options) + printf("\n"); +} + +static void print_usage_command_list(void) +{ + struct zkey_cryptsetup_command *cmd = zkey_cryptsetup_commands; + char command_str[ZKEY_CRYPTSETUP_COMMAND_STR_LEN]; + unsigned int i; + + util_prg_print_help(); + + printf("COMMANDS\n"); + while (cmd->command) { + strcpy(command_str, cmd->command); + for (i = 0; i < cmd->abbrev_len; i++) + command_str[i] = toupper(command_str[i]); + printf(" %-*s %s\n", ZKEY_CRYPTSETUP_COMMAND_MAX_LEN, + command_str, cmd->short_desc); + cmd++; + } + printf("\n"); +} + +/* + * --help printout + */ +static void print_help(const struct zkey_cryptsetup_command *command) +{ + /* Print usage */ + if (!command) + print_usage_command_list(); + else + print_usage_command(command); + + /* Print parameter help */ + util_opt_print_help(); + + if (!command) { + printf("\n"); + printf("For more information use '%s COMMAND --help'.\n", + program_invocation_short_name); + } +} + +/* + * Log function called from libcryptsetup routines when debugging is enabled + */ +static void cryptsetup_log(int level, const char *msg, + void *usrptr __attribute__((unused))) +{ + switch (level) { + case CRYPT_LOG_NORMAL: + fputs(msg, stdout); + break; + case CRYPT_LOG_VERBOSE: + if (g.verbose) + fputs(msg, stdout); + break; + case CRYPT_LOG_ERROR: + fprintf(stderr, "%s: %s", program_invocation_short_name, msg); + break; + case CRYPT_LOG_DEBUG: + fprintf(stderr, "%s: # %s", program_invocation_short_name, msg); + break; + default: + warnx("Internal error on logging class for msg: %s", msg); + break; + } +} + +static void secure_free(void *area, size_t size) +{ + if (area == NULL) + return; + + memset(area, 0, size); + free(area); +} + +/* + * Seek a number of bytes in a file. + * + * A simple call to lseek(3) might not be possible for some inputs (e.g. + * reading from a pipe), so this function instead reads of up to 4K bytes + * at a time until the specified number of bytes. It returns -1 on read error + * or when it reaches EOF before the requested number of bytes have been + * discarded. + */ +static int keyfile_seek(int fd, size_t bytes) +{ + size_t next_read; + ssize_t bytes_r; + off64_t r; + char *tmp; + + r = lseek64(fd, bytes, SEEK_CUR); + if (r > 0) + return 0; + if (r < 0 && errno != ESPIPE) + return -1; + + tmp = util_malloc(SEEK_BUFLEN); + while (bytes > 0) { + next_read = bytes > SEEK_BUFLEN ? SEEK_BUFLEN : (size_t)bytes; + + bytes_r = read(fd, tmp, next_read); + if (bytes_r < 0) { + if (errno == EINTR) + continue; + secure_free(tmp, SEEK_BUFLEN); + return -1; + } + + if (bytes_r == 0) + break; + + bytes -= bytes_r; + } + + secure_free(tmp, SEEK_BUFLEN); + return bytes == 0 ? 0 : -1; +} + +/* + * Read data from fd into the specified buffer + */ +static ssize_t keyfile_read(int fd, void *buf, size_t length) +{ + size_t read_size = 0; + ssize_t r; + + if (fd < 0 || buf == NULL) + return -EINVAL; + + do { + r = read(fd, buf, length - read_size); + if (r == -1 && errno != EINTR) + return r; + if (r == 0) + return (ssize_t)read_size; + if (r > 0) { + read_size += (size_t)r; + buf = (char *)buf + r; + } + } while (read_size != length); + + return (ssize_t)length; +} + +/* + * Prompt for the password + */ +static int get_password_interactive(const char *prompt, char **pwd, + size_t *pwd_size) +{ + struct termios orig, tmp; + int infd, outfd, rc = 0; + char *pass; + int num; + + pass = calloc(MAX_PASSWORD_SIZE + 1, 1); + if (pass == NULL) { + warnx("Out of memory while reading passphrase"); + return -ENOMEM; + } + + infd = open("/dev/tty", O_RDWR); + if (infd == -1) { + infd = STDIN_FILENO; + outfd = STDERR_FILENO; + } else { + outfd = infd; + } + + if (prompt != NULL) { + if (write(outfd, prompt, strlen(prompt)) < 0) { + rc = -errno; + warnx("Failed to write prompt: %s", strerror(-rc)); + goto out_err; + } + } + + rc = tcgetattr(infd, &orig); + if (rc != 0) { + rc = -errno; + warnx("Failed to get terminal attributes: %s", strerror(-rc)); + goto out_err; + } + + memcpy(&tmp, &orig, sizeof(tmp)); + tmp.c_lflag &= ~ECHO; + + rc = tcsetattr(infd, TCSAFLUSH, &tmp); + if (rc != 0) { + rc = -errno; + warnx("Failed to set terminal attributes: %s", strerror(-rc)); + goto out_err; + } + + quit = 0; + num = read(infd, pass, MAX_PASSWORD_SIZE); + if (num > 0) + pass[num - 1] = '\0'; + else if (num == 0) + *pass = '\0'; + + if (quit) { + printf("\n"); + num = -1; + pr_verbose("Password entry aborted by user"); + } + + rc = tcsetattr(infd, TCSAFLUSH, &orig); + if (rc != 0) { + rc = -errno; + warnx("Failed to set terminal attributes: %s", strerror(-rc)); + goto out_err; + } + + if (num < 0) { + warnx("Failed to read the password"); + rc = -EIO; + goto out_err; + } + + *pwd = pass; + *pwd_size = strlen(pass); + rc = 0; + +out_err: + if (rc != 0) + secure_free(pass, MAX_PASSWORD_SIZE + 1); + else + write(outfd, "\n", 1); + + if (infd != STDIN_FILENO) + close(infd); + + return rc; +} + +/* + * Read the password from the key file + */ +static int get_password_file(char **pwd, size_t *pwd_size, const char *key_file, + size_t keyfile_offset, size_t key_size, + int stop_at_eol) +{ + int unlimited_read = 0; + size_t file_read_size; + int regular_file = 0; + int char_to_read = 0; + int fd, rc, newline; + char *pass = NULL; + int char_read = 0; + size_t buflen, i; + struct stat sb; + + fd = key_file ? open(key_file, O_RDONLY) : STDIN_FILENO; + if (fd < 0) { + rc = -errno; + warnx("Failed to open key file '%s': %s", key_file, + strerror(-rc)); + return rc; + } + + if (isatty(fd)) { + warnx("Cannot read key file from a terminal"); + rc = -EINVAL; + goto out_err; + } + + if (key_size == 0) { + key_size = MAX_KEY_SIZE + 1; + unlimited_read = 1; + buflen = KEYFILE_BUFLEN; + } else + buflen = key_size; + + if (key_file) { + rc = stat(key_file, &sb); + if (rc != 0) { + warnx("Failed to stat key file '%s': %s", key_file, + strerror(-rc)); + goto out_err; + } + if (S_ISREG(sb.st_mode)) { + regular_file = 1; + file_read_size = sb.st_size; + + if (keyfile_offset > file_read_size) { + warnx("Cannot seek to requested key file " + "offset %lu", keyfile_offset); + goto out_err; + } + file_read_size -= keyfile_offset; + + if (file_read_size >= key_size) + buflen = key_size; + else if (file_read_size) + buflen = file_read_size; + } + } + + pass = calloc(buflen, 1); + if (pass == NULL) { + warnx("Out of memory while reading passphrase"); + rc = -ENOMEM; + goto out_err; + } + + if (keyfile_offset && keyfile_seek(fd, keyfile_offset) < 0) { + warnx("Cannot seek to requested key file offset %lu", + keyfile_offset); + goto out_err; + } + + for (i = 0, newline = 0; i < key_size; i += char_read) { + if (i == buflen) { + buflen += 4096; + pass = realloc(pass, buflen); + if (pass == NULL) { + warnx("Out of memory while reading passphrase"); + rc = -ENOMEM; + goto out_err; + } + } + + if (stop_at_eol) + char_to_read = 1; + else + char_to_read = key_size < buflen ? + key_size - i : buflen - i; + + char_read = keyfile_read(fd, &pass[i], char_to_read); + if (char_read < 0) { + warnx("Error reading passphrase"); + rc = -EPIPE; + goto out_err; + } + + if (char_read == 0) + break; + + if (stop_at_eol && pass[i] == '\n') { + newline = 1; + pass[i] = '\0'; + break; + } + } + + if (!i && !regular_file && !newline) { + warnx("Nothing read on input"); + rc = -EPIPE; + goto out_err; + } + + if (unlimited_read && i == key_size) { + warnx("Maximum key size exceeded"); + rc = -EINVAL; + goto out_err; + } + + if (!unlimited_read && i != key_size) { + warnx("Cannot read requested amount of data"); + rc = -EINVAL; + goto out_err; + } + + *pwd = pass; + *pwd_size = i; + rc = 0; + +out_err: + if (fd != STDIN_FILENO) + close(fd); + if (rc != 0) + secure_free(pass, buflen); + + return rc; +} + +/* + * Check if the specfied file name denotes stdin + */ +static bool is_stdin(const char *file_name) +{ + if (file_name == NULL) + return true; + + return strcmp(file_name, "-") ? false : true; +} + +/* + * Prompt for the password or read the password from the keyfile. + */ +static int get_password(const char *prompt, char **pwd, size_t *pwd_size, + const char *key_file, size_t keyfile_offset, + size_t keyfile_size) +{ + int rc; + + if (is_stdin(key_file)) { + if (isatty(STDIN_FILENO)) { + if (keyfile_offset) { + warnx("Cannot use option --keyfile-offset with " + "terminal input"); + return -EINVAL; + } + if (keyfile_size) { + warnx("Cannot use option --keyfile-size with " + "terminal input"); + return -EINVAL; + } + + rc = get_password_interactive(prompt, pwd, pwd_size); + } else { + rc = get_password_file(pwd, pwd_size, NULL, + keyfile_offset, keyfile_size, + key_file == NULL); + } + } else { + rc = get_password_file(pwd, pwd_size, key_file, + keyfile_offset, keyfile_size, 0); + } + + return rc; +} +static int ensure_is_active_keylot(int keyslot) +{ + crypt_keyslot_info info; + + info = crypt_keyslot_status(g.cd, keyslot); + if (info != CRYPT_SLOT_ACTIVE && info != CRYPT_SLOT_ACTIVE_LAST) { + warnx("Keyslot %d is not a valid key slot", keyslot); + return -EINVAL; + } + + return 0; +} + +static int ensure_is_unbound_keylot(int keyslot) +{ + crypt_keyslot_info info; + + info = crypt_keyslot_status(g.cd, keyslot); + if (info != CRYPT_SLOT_UNBOUND) { + warnx("Key slot %d is not an unbound key slot", keyslot); + return -EINVAL; + } + + return 0; +} + +/* + * Returns the token number of the token of the specified name if found, + * -1 otherwise. + */ +static int find_token(struct crypt_device *cd, const char *name) +{ + crypt_token_info info; + const char *type; + int i; + + for (i = 0; ; i++) { + info = crypt_token_status(cd, i, &type); + if (info == CRYPT_TOKEN_INVALID) + break; + if (info == CRYPT_TOKEN_INACTIVE) + continue; + + if (strcmp(type, name) != 0) + continue; + + pr_verbose("'%s' token found at slot %d", name, i); + return i; + } + + pr_verbose("'%s' token not found", name); + return -1; +} + +/* + * Validate the reencipher token + */ +static int validate_reencipher_token(struct reencipher_token *tok) +{ + int rc; + + rc = ensure_is_unbound_keylot(tok->unbound_keyslot); + if (rc != 0) + return rc; + + rc = ensure_is_active_keylot(tok->original_keyslot); + if (rc != 0) + return rc; + + pr_verbose("The re-encipher token has been validated"); + + return 0; +} + +static int get_token(struct crypt_device *cd, int token, json_object **obj) +{ + const char *json; + int rc; + + if (obj == NULL) + return -EINVAL; + + rc = crypt_token_json_get(cd, token, &json); + if (rc < 0) { + warnx("Failed to get re-encipher token %d: %s", token, + strerror(-rc)); + return -rc; + } + + *obj = json_tokener_parse(json); + if (*obj == NULL) { + warnx("Failed to parse JSON"); + return -EINVAL; + } + + return 0; +} + +/* + * Reads the re-encipher token from the LUKS2 header + */ +static int get_reencipher_token(struct crypt_device *cd, int token, + struct reencipher_token *info, bool validate) +{ + json_object *jobj_org_keyslot = NULL; + json_object *jobj_unb_keyslot = NULL; + json_object *json_token = NULL; + json_object *jobj_vp = NULL; + const char *temp; + int rc; + + rc = get_token(cd, token, &json_token); + if (rc != 0) + return rc; + + if (!json_object_object_get_ex(json_token, PAES_REENC_TOKEN_VP, + &jobj_vp)) { + warnx("The re-encipher token is incomplete, '%s' is missing", + PAES_REENC_TOKEN_VP); + rc = -EINVAL; + goto out; + } + temp = json_object_get_string(jobj_vp); + if (temp == NULL) { + warnx("The re-encipher token is incomplete, '%s' is missing", + PAES_REENC_TOKEN_VP); + rc = -EINVAL; + goto out; + } + strncpy(info->verification_pattern, temp, + sizeof(info->verification_pattern)); + info->verification_pattern[ + sizeof(info->verification_pattern) - 1] = '\0'; + + if (!json_object_object_get_ex(json_token, PAES_REENC_TOKEN_ORG_SLOT, + &jobj_org_keyslot)) { + warnx("The re-encipher token is incomplete, '%s' is missing", + PAES_REENC_TOKEN_ORG_SLOT); + rc = -EINVAL; + goto out; + } + errno = 0; + info->original_keyslot = json_object_get_int64(jobj_org_keyslot); + if (errno != 0) { + warnx("The re-encipher token is incomplete, '%s' is missing", + PAES_REENC_TOKEN_ORG_SLOT); + rc = -EINVAL; + goto out; + } + + if (!json_object_object_get_ex(json_token, PAES_REENC_TOKEN_UNB_SLOT, + &jobj_unb_keyslot)) { + warnx("The re-encipher token is incomplete, '%s' is missing", + PAES_REENC_TOKEN_UNB_SLOT); + rc = -EINVAL; + goto out; + } + errno = 0; + info->unbound_keyslot = json_object_get_int64(jobj_unb_keyslot); + if (errno != 0) { + warnx("The re-encipher token is incomplete, '%s' is missing", + PAES_REENC_TOKEN_UNB_SLOT); + rc = -EINVAL; + goto out; + } + + pr_verbose("Re-encipher token: original-keyslot: %d, unbound-keyslot: " + "%d, verification-pattern: %s", info->original_keyslot, + info->unbound_keyslot, info->verification_pattern); + + rc = 0; + + if (validate) + rc = validate_reencipher_token(info); + +out: + if (json_token != NULL) + json_object_put(json_token); + + return rc; +} + +/* + * Writes the re-encipher token to the LUKS2 header + */ +static int put_reencipher_token(struct crypt_device *cd, int token, + struct reencipher_token *info) +{ + json_object *jobj, *jobj_keyslots; + char temp[20]; + int rc; + + pr_verbose("Re-encipher token: original-keyslot: %d, unbound-keyslot: " + "%d, verification-pattern: %s", info->original_keyslot, + info->unbound_keyslot, info->verification_pattern); + + jobj = json_object_new_object(); + json_object_object_add(jobj, "type", + json_object_new_string(PAES_REENC_TOKEN_NAME)); + + jobj_keyslots = json_object_new_array(); + sprintf(temp, "%d", info->unbound_keyslot); + json_object_array_add(jobj_keyslots, json_object_new_string(temp)); + json_object_object_add(jobj, "keyslots", jobj_keyslots); + + json_object_object_add(jobj, PAES_REENC_TOKEN_VP, + json_object_new_string( + info->verification_pattern)); + json_object_object_add(jobj, PAES_REENC_TOKEN_ORG_SLOT, + json_object_new_int64(info->original_keyslot)); + json_object_object_add(jobj, PAES_REENC_TOKEN_UNB_SLOT, + json_object_new_int64(info->unbound_keyslot)); + + rc = crypt_token_json_set(cd, token >= 0 ? token : CRYPT_ANY_TOKEN, + json_object_to_json_string_ext(jobj, + JSON_C_TO_STRING_PLAIN)); + + if (rc < 0) + warnx("Failed to add the re-encipher token to device " + "'%s': %s", g.pos_arg, strerror(-rc)); + else + pr_verbose("Re-encipher token put to token slot %d", + rc); + + json_object_put(jobj); + + return rc; +} + + +/* + * Reads the verification pattern token from the LUKS2 header + */ +static int get_vp_token(struct crypt_device *cd, int token, + struct vp_token *info) +{ + json_object *json_token = NULL; + json_object *jobj_vp = NULL; + const char *temp; + int rc; + + rc = get_token(cd, token, &json_token); + if (rc != 0) + return rc; + + if (!json_object_object_get_ex(json_token, PAES_VP_TOKEN_VP, + &jobj_vp)) { + warnx("The verification-pattern token is incomplete, '%s' is " + "missing", PAES_VP_TOKEN_VP); + rc = -EINVAL; + goto out; + } + temp = json_object_get_string(jobj_vp); + if (temp == NULL) { + warnx("The verification-pattern token is incomplete, '%s' is " + "missing", PAES_VP_TOKEN_VP); + rc = -EINVAL; + goto out; + } + strncpy(info->verification_pattern, temp, + sizeof(info->verification_pattern)); + info->verification_pattern[ + sizeof(info->verification_pattern) - 1] = '\0'; + + pr_verbose("Verification-pattern: %s", info->verification_pattern); + +out: + if (json_token != NULL) + json_object_put(json_token); + + return rc; +} + +/* + * Writes the verification pattern token to the LUKS2 header + */ +static int put_vp_token(struct crypt_device *cd, int token, + struct vp_token *info) +{ + json_object *jobj, *jobj_keyslots; + int rc; + + pr_verbose("Verification-pattern: %s", info->verification_pattern); + + jobj = json_object_new_object(); + json_object_object_add(jobj, "type", + json_object_new_string(PAES_VP_TOKEN_NAME)); + + jobj_keyslots = json_object_new_array(); + json_object_object_add(jobj, "keyslots", jobj_keyslots); + + json_object_object_add(jobj, PAES_VP_TOKEN_VP, + json_object_new_string( + info->verification_pattern)); + + rc = crypt_token_json_set(cd, token >= 0 ? token : CRYPT_ANY_TOKEN, + json_object_to_json_string_ext(jobj, + JSON_C_TO_STRING_PLAIN)); + + if (rc < 0) + warnx("Failed to add the verification-pattern token to device " + "'%s': %s", g.pos_arg, strerror(-rc)); + else + pr_verbose("Verification-pattern token put to token slot %d", + rc); + + json_object_put(jobj); + + return rc; +} + +/* + * Open the LUKS2 device + */ +static int open_device(const char *device, struct crypt_device **cd) +{ + const struct crypt_pbkdf_type *pbkdf; + struct crypt_device *cdev = NULL; + int rc; + + rc = crypt_init(&cdev, device); + if (rc != 0) { + warnx("Failed to open device '%s': %s", device, strerror(-rc)); + goto out; + } + + crypt_set_log_callback(cdev, cryptsetup_log, NULL); + + rc = crypt_load(cdev, CRYPT_LUKS, NULL); + if (rc != 0) { + warnx("Failed to load the header from device '%s': %s", device, + strerror(-rc)); + goto out; + } + + if (strcmp(crypt_get_type(cdev), CRYPT_LUKS2) != 0) { + warnx("Device '%s' is not a LUKS2 device", device); + rc = -EINVAL; + goto out; + } + + if (strcmp(crypt_get_cipher(cdev), "paes") != 0) { + warnx("Device '%s' is not encrypted using the 'paes' cipher", + device); + rc = -EINVAL; + goto out; + } + + pbkdf = crypt_get_pbkdf_type(cdev); + rc = crypt_set_pbkdf_type(cdev, pbkdf); + if (rc != 0) { + warnx("Failed to set the PBKDF-type for device '%s': %s", + device, strerror(-rc)); + goto out; + } + + *cd = cdev; + +out: + if (rc != 0) { + if (cdev != NULL) + crypt_free(cdev); + *cd = NULL; + } + + return rc; +} + +/* + * Prompts for yes or no. Returns true if 'y' or 'yes' was entered. + */ +static bool prompt_for_yes(void) +{ + char str[20]; + + if (fgets(str, sizeof(str), stdin) == NULL) + return false; + + if (str[strlen(str) - 1] == '\n') + str[strlen(str) - 1] = '\0'; + pr_verbose("Prompt reply: '%s'", str); + if (strcasecmp(str, "y") == 0 || strcasecmp(str, "yes") == 0) + return true; + + return false; +} + +/* + * Cleans up a left over re-encipher token and associated unbound keyslot + */ +static int cleanup_reencipher_token(int token) +{ + struct reencipher_token tok; + int rc; + + rc = get_reencipher_token(g.cd, token, &tok, false); + if (rc == 0) { + if (ensure_is_unbound_keylot(tok.unbound_keyslot) == 0) { + rc = crypt_keyslot_destroy(g.cd, tok.unbound_keyslot); + if (rc != 0) + pr_verbose("Failed to destroy unbound key slot " + "%d: %s", tok.unbound_keyslot, + strerror(-rc)); + else + pr_verbose("Successfully destroyed unbound key " + "slot %d", tok.unbound_keyslot); + } else { + pr_verbose("Key slot %d is not in unbound state, it is " + "not destroyed", tok.unbound_keyslot); + } + } else { + pr_verbose("Failed to get re-encipher token (ignored): %s", + strerror(-rc)); + } + + rc = crypt_token_json_set(g.cd, token, NULL); + if (rc < 0) + warnx("Failed to remove the re-encipher token: %s", + strerror(-rc)); + else + pr_verbose("Successfully removed re-encipher token %d", token); + + return rc; +} + +/* + * Activates an unbound key slot and removes the previous key slots + */ +static int activate_unbound_keyslot(int token, int keyslot, const char *key, + size_t keysize, char *password, + size_t password_len, char *complete_msg) +{ + crypt_keyslot_info info; + int rc, i, n; + + rc = crypt_keyslot_add_by_key(g.cd, keyslot, key, keysize, password, + password_len, CRYPT_VOLUME_KEY_SET); + if (rc < 0) { + warnx("Failed to activate the unbound key slot %d: %s", keyslot, + strerror(-rc)); + return rc; + } + + pr_verbose("Unbound key slot %d activated, it is now key slot %d", + keyslot, rc); + keyslot = rc; + + if (token >= 0) { + rc = crypt_token_json_set(g.cd, token, NULL); + if (rc < 0) { + warnx("Failed remove the re-encipher token %d: %s", + token, strerror(-rc)); + return rc; + } + } + + if (complete_msg != NULL) + util_print_indented(complete_msg, 0); + util_print_indented("All key slots containing the old volume key are " + "now in unbound state. Do you want to remove " + "these key slots?", 0); + + if (!prompt_for_yes()) + return 0; + + for (i = 0, n = 0; ; i++) { + if (i == keyslot) + continue; + + info = crypt_keyslot_status(g.cd, i); + if (info == CRYPT_SLOT_INVALID) + break; + if (info <= CRYPT_SLOT_ACTIVE_LAST) + continue; + + pr_verbose("Removing now unbound key slot %d", i); + rc = crypt_keyslot_destroy(g.cd, i); + if (rc < 0) { + warnx("Failed to remove previous key slot %d: %s", i, + strerror(-rc)); + } + + n++; + } + + if (n > 1) { + util_print_indented("\nWARNING: Before re-enciphering, the " + "volume's LUKS header had multiple active " + "key slots with the same key, but different " + "passwords. Use 'cryptsetup luksAddKey' if " + "you need more than one key slot.", 0); + } + + return rc; +} + +static int check_keysize_and_cipher_mode(size_t keysize) +{ + if (keysize == 0) { + warnx("Invalid volume key size"); + return -EINVAL; + } + + if (strncmp(crypt_get_cipher_mode(g.cd), "xts", 3) == 0) { + if (keysize != 2 * SECURE_KEY_SIZE) { + warnx("The volume key size %lu is not valid for the " + "cipher mode '%s'", keysize, + crypt_get_cipher_mode(g.cd)); + return -EINVAL; + } + } else { + if (keysize != SECURE_KEY_SIZE) { + warnx("The volume key size %lu is not valid for the " + "cipher mode '%s'", keysize, + crypt_get_cipher_mode(g.cd)); + return -EINVAL; + } + } + + return 0; +} + +/* + * Open a keyslot and get a secure key from a key slot. Optionally returns the + * key and password used to unlock the keyslot. You can either open a specific + * key slot, or let it choose based on the password (keyslot=CRYPT_ANY_SLOT). + */ +static int open_keyslot(int keyslot, char **key, size_t *keysize, + char **password, size_t *password_len, + const char *prompt) +{ + char *vkey = NULL; + char *pw = NULL; + long long tries; + size_t vkeysize; + size_t pw_len; + int rc; + + vkeysize = crypt_get_volume_key_size(g.cd); + pr_verbose("Volume key size: %lu", vkeysize); + + rc = check_keysize_and_cipher_mode(vkeysize); + if (rc != 0) + return rc; + + vkey = malloc(vkeysize); + if (vkey == NULL) { + warnx("Out of memory while allocating a buffer for the volume " + "key"); + return -ENOMEM; + } + + tries = (is_stdin(g.keyfile) && isatty(STDIN_FILENO)) ? g.tries : 1; + do { + if (pw != NULL) { + secure_free(pw, pw_len); + pw = NULL; + } + + rc = get_password(prompt, &pw, &pw_len, g.keyfile, + g.keyfile_offset, g.keyfile_size); + if (rc != 0) + goto out; + + rc = crypt_volume_key_get(g.cd, keyslot, vkey, &vkeysize, + pw, pw_len); + + if (rc == -EPERM || rc == -ENOENT) + warnx("No key available with this passphrase"); + + + } while ((rc == -EPERM || rc == -ENOENT) && (--tries > 0)); + + if (rc < 0) { + warnx("Failed to get volume key of device '%s': " + "%s", g.pos_arg, strerror(-rc)); + goto out; + } + + keyslot = rc; + pr_verbose("Volume key obtained from key slot %d", keyslot); + + if (key != NULL) + *key = vkey; + else + secure_free(vkey, vkeysize); + vkey = NULL; + if (keysize != NULL) + *keysize = vkeysize; + if (password != NULL) + *password = pw; + else + secure_free(pw, pw_len); + pw = NULL; + if (password_len != NULL) + *password_len = pw_len; + + rc = keyslot; + +out: + secure_free(vkey, vkeysize); + secure_free(pw, pw_len); + + return rc; +} + + +/* + * Validate and get a secure key from a key slot. Optionally returns the key + * and password used to unlock the keyslot. You can either validate a specific + * key slot, or let it choose based on the password (keyslot=CRYPT_ANY_SLOT). + */ +static int validate_keyslot(int keyslot, char **key, size_t *keysize, + char **password, size_t *password_len, + int *is_old_mk, size_t *clear_keysize, + const char *prompt, const char *invalid_msg) +{ + size_t vkeysize = 0; + char *vkey = NULL; + int rc, is_old; + + rc = open_keyslot(keyslot, &vkey, &vkeysize, password, password_len, + prompt); + if (rc < 0) + return rc; + + keyslot = rc; + + rc = validate_secure_key(g.pkey_fd, (u8 *)vkey, vkeysize, clear_keysize, + &is_old, g.verbose); + if (rc != 0) { + if (invalid_msg != NULL) + warnx("%s", invalid_msg); + else + warnx("The secure volume key of device '%s' is not " + "valid", g.pos_arg); + rc = -EINVAL; + goto out; + } + pr_verbose("Volume key is currently enciphered with %s master key", + is_old ? "OLD" : "CURRENT"); + + if (key != NULL) + *key = vkey; + else + secure_free(vkey, vkeysize); + vkey = NULL; + if (keysize != NULL) + *keysize = vkeysize; + if (is_old_mk != NULL) + *is_old_mk = is_old; + + rc = keyslot; + +out: + secure_free(vkey, vkeysize); + + return rc; +} + +/* + * Prepares for a re-enciphering of a secure volume key. Dependent on the + * options specified by the user and the state of the volume key, it starts + * a staged re-enciphering or performs an in-place re-enciphering. + */ +static int reencipher_prepare(int token) +{ + struct reencipher_token reenc_tok; + struct vp_token vp_tok; + char *password = NULL; + size_t password_len; + char *key = NULL; + size_t keysize; + int is_old_mk; + char *prompt; + char *msg; + int rc; + + if (token >= 0) { + util_asprintf(&msg, "Staged volume key re-enciphering is " + "already initiated for device '%s'. Do you want to " + "cancel the pending re-enciphering and start a " + "new re-enciphering process?", g.pos_arg); + util_print_indented(msg, 0); + free(msg); + + if (!prompt_for_yes()) { + warnx("Device '%s' is left unchanged", g.pos_arg); + return -ECANCELED; + } + + rc = cleanup_reencipher_token(token); + if (rc < 0) + return rc; + } + + util_asprintf(&prompt, "Enter passphrase for '%s': ", g.pos_arg); + rc = validate_keyslot(CRYPT_ANY_SLOT, &key, &keysize, &password, + &password_len, &is_old_mk, NULL, prompt, NULL); + free(prompt); + if (rc < 0) + goto out; + + reenc_tok.original_keyslot = rc; + + rc = ensure_is_active_keylot(reenc_tok.original_keyslot); + if (rc != 0) + goto out; + + rc = generate_key_verification_pattern(key, keysize, + reenc_tok.verification_pattern, + sizeof(reenc_tok.verification_pattern), + g.verbose); + if (rc != 0) { + warnx("Failed to generate the verification pattern: %s", + strerror(-rc)); + warnx("Make sure that kernel module 'paes_s390' is loaded and " + "that the 'paes' cipher is available"); + goto out; + } + + memcpy(vp_tok.verification_pattern, reenc_tok.verification_pattern, + sizeof(vp_tok.verification_pattern)); + token = find_token(g.cd, PAES_VP_TOKEN_NAME); + rc = put_vp_token(g.cd, token, &vp_tok); + if (rc < 0) + goto out; + + util_asprintf(&msg, "The secure volume key of device '%s' is " + "enciphered with the %s CCA master key and is being " + "re-enciphered with the %s CCA master key.", + g.pos_arg, is_old_mk ? "OLD" : "CURRENT", + is_old_mk ? "CURRENT" : "NEW"); + util_print_indented(msg, 0); + free(msg); + + rc = key_token_change(g.dll_CSNBKTC, (u8 *)key, keysize, + is_old_mk ? METHOD_OLD_TO_CURRENT : + METHOD_CURRENT_TO_NEW, + g.verbose); + if (rc != 0) { + warnx("Failed to re-encipher the secure volume key of device " + "'%s'", g.pos_arg); + rc = -EINVAL; + goto out; + } + + rc = crypt_keyslot_add_by_key(g.cd, CRYPT_ANY_SLOT, key, keysize, + password, password_len, + CRYPT_VOLUME_KEY_NO_SEGMENT); + if (rc < 0) { + warnx("Failed to add an unbound key slot to device '%s': %s", + g.pos_arg, strerror(-rc)); + goto out; + } + + reenc_tok.unbound_keyslot = rc; + pr_verbose("Re-enciphered volume key added to unbound key slot %d", + reenc_tok.unbound_keyslot); + + rc = ensure_is_unbound_keylot(reenc_tok.unbound_keyslot); + if (rc != 0) + goto out; + + if ((!is_old_mk && g.inplace) || + (is_old_mk && !g.staged)) { + if (!g.inplace) + printf("An in-place re-enciphering is performed.\n"); + + util_asprintf(&msg, "Re-enciphering has completed " + "successfully for device '%s'", g.pos_arg); + rc = activate_unbound_keyslot(-1, reenc_tok.unbound_keyslot, + key, keysize, password, + password_len, msg); + free(msg); + goto out; + } + + rc = put_reencipher_token(g.cd, CRYPT_ANY_TOKEN, &reenc_tok); + if (rc < 0) + goto out; + rc = 0; + + util_asprintf(&msg, "Staged re-enciphering is initiated for " + "device '%s'. After the NEW CCA master key has been set " + "to become the CURRENT master key, run 'zkey-cryptsetup " + "reencipher' with option '--complete' to complete the " + "re-enciphering process.", g.pos_arg, + program_invocation_short_name); + util_print_indented(msg, 0); + free(msg); + +out: + secure_free(password, password_len); + secure_free(key, keysize); + + return rc; +} + +/* + * Completes a staged re-enciphering. + */ +static int reencipher_complete(int token) +{ + char vp[VERIFICATION_PATTERN_LEN]; + struct reencipher_token tok; + char *password = NULL; + size_t password_len; + char *key = NULL; + size_t keysize; + int is_old_mk; + char *prompt; + char *msg; + int rc; + + rc = get_reencipher_token(g.cd, token, &tok, true); + if (rc != 0) { + warnx("Failed to get the re-encipher token from device '%s': " + "%s", g.pos_arg, strerror(-rc)); + return rc; + } + + util_asprintf(&msg, "The re-enciphered secure volume key for " + "device '%s' is not valid.\nThe new CCA master key might " + "yet have to be set as the CURRENT master key.", + g.pos_arg); + util_asprintf(&prompt, "Enter passphrase for key slot %d of '%s': ", + tok.original_keyslot, g.pos_arg); + rc = validate_keyslot(tok.unbound_keyslot, &key, &keysize, &password, + &password_len, &is_old_mk, NULL, prompt, msg); + free(msg); + free(prompt); + if (rc < 0) + goto out; + + rc = ensure_is_unbound_keylot(rc); + if (rc != 0) + goto out; + + if (is_old_mk) { + util_asprintf(&msg, "The re-enciphered secure volume key " + "of device '%s' is enciphered with the CCA " + "master key from the OLD master key register. " + "The CCA master key might have changed again, " + "before the previous volume key re-enciphering " + "was completed.\n" + "Do you want to re-encipher the secure key with " + "the CCA master key in the CURRENT master key " + "register?", g.pos_arg); + util_print_indented(msg, 0); + free(msg); + + if (!prompt_for_yes()) { + warnx("Re-enciphering was aborted"); + rc = -ECANCELED; + goto out; + } + + rc = key_token_change(g.dll_CSNBKTC, (u8 *)key, keysize, + METHOD_OLD_TO_CURRENT, g.verbose); + if (rc != 0) { + warnx("Failed to re-encipher the secure volume key for " + "device '%s'", g.pos_arg); + rc = -EINVAL; + goto out; + } + + rc = crypt_keyslot_destroy(g.cd, tok.unbound_keyslot); + if (rc < 0) { + warnx("Failed to remove unbound key slot %d: %s", + tok.unbound_keyslot, strerror(-rc)); + } + + rc = crypt_keyslot_add_by_key(g.cd, CRYPT_ANY_SLOT, key, + keysize, password, password_len, + CRYPT_VOLUME_KEY_NO_SEGMENT); + if (rc < 0) { + warnx("Failed to add an unbound key slot to device " + "'%s': %s", g.pos_arg, strerror(-rc)); + goto out; + } + + tok.unbound_keyslot = rc; + pr_verbose("Re-enciphered volume key added to unbound key " + "slot %d", tok.unbound_keyslot); + + } + + rc = generate_key_verification_pattern(key, keysize, vp, sizeof(vp), + g.verbose); + if (rc != 0) { + warnx("Failed to generate the verification pattern: %s", + strerror(-rc)); + warnx("Make sure that kernel module 'paes_s390' is loaded and " + "that the 'paes' cipher is available"); + goto out; + } + + if (strcmp(tok.verification_pattern, vp) != 0) { + warnx("The verification patterns of the new and old volume " + "keys do not match"); + rc = -EINVAL; + goto out; + } + + util_asprintf(&msg, "Re-enciphering has completed successfully for " + "device '%s'.", g.pos_arg); + rc = activate_unbound_keyslot(token, tok.unbound_keyslot, key, keysize, + password, password_len, msg); + free(msg); + +out: + secure_free(password, password_len); + secure_free(key, keysize); + + return rc; +} + + +/* + * Command handler for 'reencipher'. + * + * Re-encipher a volume key of a volume encrypted with LUKS2 and the + * 'paes' cipher + */ +static int command_reencipher(void) +{ + int token; + int rc; + + if (g.inplace && g.staged) { + warnx("Options '--in-place|-i' and '--staged|-s' are " + "mutual exclusive"); + util_prg_print_parse_error(); + return EXIT_FAILURE; + } + if (g.complete) { + if (g.inplace) { + warnx("Option '--in-place|-i' is not valid together " + "with '--complete|-p'"); + util_prg_print_parse_error(); + return EXIT_FAILURE; + } + if (g.staged) { + warnx("Option '--staged|-s' is not valid together " + "with '--complete|-p'"); + util_prg_print_parse_error(); + return EXIT_FAILURE; + } + } + + token = find_token(g.cd, PAES_REENC_TOKEN_NAME); + + if (token < 0 && g.complete) { + warnx("Staged volume key re-enciphering is not pending for " + "device '%s'", g.pos_arg); + return EXIT_FAILURE; + } + + if (token < 0 || g.staged || g.inplace) + rc = reencipher_prepare(token); + else + rc = reencipher_complete(token); + + return rc < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} + +static void print_verification_pattern(const char *vp) +{ + printf(" Verification pattern: %.*s\n", VERIFICATION_PATTERN_LEN / 2, + vp); + printf(" %.*s\n", VERIFICATION_PATTERN_LEN / 2, + &vp[VERIFICATION_PATTERN_LEN / 2]); +} + +/* + * Command handler for 'validate'. + * + * Validate a volume key of a volume encrypted with LUKS2 and the + * 'paes' cipher + */ +static int command_validate(void) +{ + int reenc_pending = 0, vp_tok_avail = 0, is_valid = 0, is_old_mk = 0; + struct reencipher_token reenc_tok; + struct vp_token vp_tok; + size_t clear_keysize; + size_t keysize = 0; + char *key = NULL; + char *prompt; + char *msg; + int token; + int rc; + + util_asprintf(&prompt, "Enter passphrase for '%s': ", g.pos_arg); + rc = open_keyslot(CRYPT_ANY_SLOT, &key, &keysize, NULL, NULL, prompt); + free(prompt); + if (rc < 0) + goto out; + + rc = ensure_is_active_keylot(rc); + if (rc != 0) + goto out; + + rc = validate_secure_key(g.pkey_fd, (u8 *)key, keysize, &clear_keysize, + &is_old_mk, g.verbose); + is_valid = (rc == 0); + + token = find_token(g.cd, PAES_REENC_TOKEN_NAME); + if (token >= 0) { + rc = get_reencipher_token(g.cd, token, &reenc_tok, true); + if (rc == 0) + reenc_pending = 1; + } + + token = find_token(g.cd, PAES_VP_TOKEN_NAME); + if (token >= 0) { + rc = get_vp_token(g.cd, token, &vp_tok); + if (rc == 0) + vp_tok_avail = 1; + } + + printf("Validation of secure volume key of device '%s':\n", g.pos_arg); + printf(" Status: %s\n", is_valid ? "Valid" : "Invalid"); + printf(" Secure key size: %lu bytes\n", keysize); + printf(" XTS type key: %s\n", + keysize > SECURE_KEY_SIZE ? "Yes" : "No"); + if (is_valid) { + printf(" Clear key size: %lu bits\n", clear_keysize); + printf(" Enciphered with: %s CCA master key\n", + is_old_mk ? "OLD" : "CURRENT"); + } else { + printf(" Clear key size: (unknown)\n"); + printf(" Enciphered with: (unknown)\n"); + } + if (vp_tok_avail) + print_verification_pattern(vp_tok.verification_pattern); + else if (reenc_pending) + print_verification_pattern(reenc_tok.verification_pattern); + else + printf(" Verification pattern: Not available\n"); + + + if (reenc_pending) + printf(" Volume key re-enciphering is pending\n"); + + if (!is_valid) + printf("\nATTENTION: The secure volume key is not valid.\n"); + + if (is_old_mk) + util_print_indented("\nWARNING: The secure volume key is " + "currently enciphered with the OLD CCA " + "master key. To mitigate the danger of " + "data loss re-encipher the volume key with " + "the CURRENT CCA master key.", 0); + + if (is_valid && !vp_tok_avail) { + util_asprintf(&msg, "\nWARNING: The volume key cannot be " + "identified because the key verification pattern " + "token is not available in the LUKS2 header. Use " + "the '%s setvp' command to set the token.", + program_invocation_short_name); + util_print_indented(msg, 0); + free(msg); + } + + rc = is_valid ? 0 : -EINVAL; + +out: + secure_free(key, keysize); + + return rc < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} + +/* + * Command handler for 'setvp'. + * + * Set the verification pattern token to allow identification of the key + */ +static int command_setvp(void) +{ + struct vp_token vp_tok; + size_t keysize = 0; + char *key = NULL; + char *prompt; + int token; + int rc; + + util_asprintf(&prompt, "Enter passphrase for '%s': ", g.pos_arg); + rc = validate_keyslot(CRYPT_ANY_SLOT, &key, &keysize, NULL, NULL, + NULL, NULL, prompt, NULL); + free(prompt); + if (rc < 0) + goto out; + + rc = ensure_is_active_keylot(rc); + if (rc != 0) + goto out; + + token = find_token(g.cd, PAES_VP_TOKEN_NAME); + + rc = generate_key_verification_pattern(key, keysize, + vp_tok.verification_pattern, + sizeof(vp_tok.verification_pattern), + g.verbose); + if (rc != 0) { + warnx("Failed to generate the verification pattern: %s", + strerror(-rc)); + warnx("Make sure that kernel module 'paes_s390' is loaded and " + "that the 'paes' cipher is available"); + goto out; + } + + rc = put_vp_token(g.cd, token, &vp_tok); + if (rc < 0) + goto out; + + rc = 0; + +out: + secure_free(key, keysize); + + return rc < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} + +/* + * Command handler for 'setkey'. + * + * Set a new volume key to allow to recover from an invalid volume key + */ +static int command_setkey(void) +{ + char vp[VERIFICATION_PATTERN_LEN]; + size_t password_len = 0; + struct vp_token vp_tok; + size_t newkey_size = 0; + char *password = NULL; + size_t keysize = 0; + u8 *newkey = NULL; + char *key = NULL; + int is_old_mk; + char *prompt; + int keyslot; + char *msg; + int token; + int rc; + + if (g.master_key_file == NULL) { + misc_print_required_parm("--master-key-file/-m"); + return EXIT_FAILURE; + } + + newkey = read_secure_key(g.master_key_file, &newkey_size, g.verbose); + if (newkey == NULL) + return EXIT_FAILURE; + + rc = check_keysize_and_cipher_mode(newkey_size); + if (rc != 0) + goto out; + + rc = validate_secure_key(g.pkey_fd, newkey, newkey_size, NULL, + &is_old_mk, g.verbose); + if (rc != 0) { + warnx("The secure key in file '%s' is not valid", + g.master_key_file); + goto out; + } + + if (is_old_mk) { + util_asprintf(&msg, "The secure key in file '%s' is " + "enciphered with the CCA master key in the OLD " + "master key register. Do you want to set this " + "key as the new volume key anyway?", + g.master_key_file); + util_print_indented(msg, 0); + free(msg); + + if (!prompt_for_yes()) { + rc = -EINVAL; + goto out; + } + } + + util_asprintf(&prompt, "Enter passphrase for '%s': ", g.pos_arg); + rc = open_keyslot(CRYPT_ANY_SLOT, &key, &keysize, &password, + &password_len, prompt); + free(prompt); + if (rc < 0) + goto out; + + if (keysize != newkey_size) { + warnx("The secure key in file '%s' has an invalid size", + g.master_key_file); + rc = -EINVAL; + goto out; + } + + if (memcmp(newkey, key, keysize) == 0) { + warnx("The secure key in file '%s' is equal to the current " + "volume key, setkey is ignored", g.master_key_file); + rc = 0; + goto out; + } + + rc = generate_key_verification_pattern((char *)newkey, newkey_size, vp, + sizeof(vp), g.verbose); + if (rc != 0) { + warnx("Failed to generate the verification pattern: %s", + strerror(-rc)); + warnx("Make sure that kernel module 'paes_s390' is loaded and " + "that the 'paes' cipher is available"); + goto out; + } + + token = find_token(g.cd, PAES_VP_TOKEN_NAME); + if (token >= 0) { + rc = get_vp_token(g.cd, token, &vp_tok); + if (rc < 0) { + warnx("Failed to get the verification pattern token: " + "%s", strerror(-rc)); + goto out; + } + + if (strcmp(vp_tok.verification_pattern, vp) != 0) { + warnx("The verification patterns of the new and old " + "volume keys do not match"); + rc = -EINVAL; + goto out; + } + } else { + util_asprintf(&msg, "ATTENTION: The key validation pattern " + "token is not available in the LUKS2 header. " + "Thus, the new volume key cannot be confirmed to " + "be correct. You will lose all data on the " + "volume if you set the wrong volume key!\n" + "Are you sure that the key in file '%s' is the " + "correct volume key for volume '%s'?", + g.master_key_file, g.pos_arg); + util_print_indented(msg, 0); + free(msg); + + if (!prompt_for_yes()) { + rc = -EINVAL; + goto out; + } + } + + rc = crypt_keyslot_add_by_key(g.cd, CRYPT_ANY_SLOT, (char *)newkey, + newkey_size, password, password_len, + CRYPT_VOLUME_KEY_NO_SEGMENT); + if (rc < 0) { + warnx("Failed to add an unbound key slot to device '%s': %s", + g.pos_arg, strerror(-rc)); + goto out; + } + keyslot = rc; + + rc = ensure_is_unbound_keylot(keyslot); + if (rc != 0) + goto out; + + pr_verbose("New volume key added to unbound key slot %d", keyslot); + + util_asprintf(&msg, "The volume key has been successfully set for " + "device '%s'", g.pos_arg); + rc = activate_unbound_keyslot(-1, keyslot, (char *)newkey, newkey_size, + password, password_len, msg); + free(msg); + if (rc < 0) + goto out; + + memcpy(vp_tok.verification_pattern, vp, + sizeof(vp_tok.verification_pattern)); + rc = put_vp_token(g.cd, token, &vp_tok); + if (rc < 0) + goto out; + + rc = 0; + +out: + secure_free(password, password_len); + secure_free(newkey, newkey_size); + secure_free(key, keysize); + + return rc < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} + + +static bool is_command(struct zkey_cryptsetup_command *command, const char *str) +{ + char command_str[ZKEY_CRYPTSETUP_COMMAND_STR_LEN]; + size_t str_len = strlen(str); + + util_assert(sizeof(command_str) > strlen(command->command), + "Buffer 'command_str' too small for %s", command->command); + if (str_len < command->abbrev_len) + return false; + if (str_len > strlen(command->command)) + return false; + strncpy(command_str, command->command, str_len); + if (strncasecmp(str, command_str, str_len) != 0) + return false; + + return true; +} + +/* + * Find the command in the command table + */ +struct zkey_cryptsetup_command *find_command(const char *command) +{ + struct zkey_cryptsetup_command *cmd = zkey_cryptsetup_commands; + + while (cmd->command) { + if (is_command(cmd, command)) + return cmd; + cmd++; + } + return NULL; +} + +/* + * Entry point + */ +int main(int argc, char *argv[]) +{ + struct zkey_cryptsetup_command *command = NULL; + int arg_count = argc; + char **args = argv; + char *endp; + int rc, c; + + util_prg_init(&prg); + util_opt_init(opt_vec, NULL); + + /* Get command if one is specified */ + if (argc >= 2 && strncmp(argv[1], "-", 1) != 0) { + command = find_command(argv[1]); + if (command == NULL) { + misc_print_invalid_command(argv[1]); + return EXIT_FAILURE; + } + + arg_count = argc - 1; + args = &argv[1]; + + if (argc >= 3 && strncmp(argv[2], "-", 1) != 0) { + g.pos_arg = argv[2]; + arg_count = argc - 2; + args = &argv[2]; + } + + } + + util_opt_set_command(command ? command->command : NULL); + util_prg_set_command(command ? command->command : NULL); + + while (1) { + c = util_opt_getopt_long(arg_count, args); + if (c == -1) + break; + switch (c) { + case 'c': + g.complete = 1; + break; + case 'i': + g.inplace = 1; + break; + case 's': + g.staged = 1; + break; + case 'd': + g.keyfile = optarg; + break; + case 'o': + g.keyfile_offset = strtoll(optarg, &endp, 0); + if (*optarg == '\0' || *endp != '\0' || + g.keyfile_offset < 0 || + (g.keyfile_offset == LLONG_MAX && + errno == ERANGE)) { + warnx("Invalid value for '--keyfile-offset'|" + "'-o': '%s'", optarg); + util_prg_print_parse_error(); + return EXIT_FAILURE; + } + break; + case 'l': + g.keyfile_size = strtoll(optarg, &endp, 0); + if (*optarg == '\0' || *endp != '\0' || + g.keyfile_size <= 0 || + (g.keyfile_size == LLONG_MAX && errno == ERANGE)) { + warnx("Invalid value for '--keyfile-size'|" + "'-l': '%s'", optarg); + util_prg_print_parse_error(); + return EXIT_FAILURE; + } + break; + case 'T': + g.tries = strtoll(optarg, &endp, 0); + if (*optarg == '\0' || *endp != '\0' || + g.tries <= 0 || + (g.tries == LLONG_MAX && errno == ERANGE)) { + warnx("Invalid value for '--tries'|'-T': '%s'", + optarg); + util_prg_print_parse_error(); + return EXIT_FAILURE; + } + break; + case 'm': + g.master_key_file = optarg; + break; + case 'D': + g.debug = true; + g.verbose = true; + break; + case 'V': + g.verbose = true; + break; + case 'h': + print_help(command); + return EXIT_SUCCESS; + case 'v': + util_prg_print_version(); + return EXIT_SUCCESS; + default: + util_opt_print_parse_error(c, args); + return EXIT_FAILURE; + } + } + + if (optind < arg_count) { + util_prg_print_arg_error(args[optind]); + return EXIT_FAILURE; + } + + if (command == NULL) { + misc_print_missing_command(); + return EXIT_FAILURE; + } + + if (command->need_cca_library) { + rc = load_cca_library(&g.lib_csulcca, &g.dll_CSNBKTC, + g.verbose); + if (rc != 0) { + rc = EXIT_FAILURE; + goto out; + } + } + if (command->need_pkey_device) { + g.pkey_fd = open_pkey_device(g.verbose); + if (g.pkey_fd == -1) { + rc = EXIT_FAILURE; + goto out; + } + } + + crypt_set_log_callback(NULL, cryptsetup_log, NULL); + if (g.debug) + crypt_set_debug_level(-1); + + if (command->open_device) { + if (g.pos_arg == NULL) { + misc_print_required_parm(command->pos_arg); + rc = EXIT_FAILURE; + goto out; + } + + rc = open_device(g.pos_arg, &g.cd); + if (rc != 0) { + g.cd = NULL; + rc = EXIT_FAILURE; + goto out; + } + } + + set_int_handler(); + + rc = command->function(); + +out: + if (g.lib_csulcca) + dlclose(g.lib_csulcca); + if (g.pkey_fd >= 0) + close(g.pkey_fd); + if (g.cd) + crypt_free(g.cd); + return rc; +}