diff --git a/s390-tools-01-zkey-Add-support-for-retrieving-a-list-of-ultravisor-secrets.patch b/s390-tools-01-zkey-Add-support-for-retrieving-a-list-of-ultravisor-secrets.patch new file mode 100644 index 0000000..c409126 --- /dev/null +++ b/s390-tools-01-zkey-Add-support-for-retrieving-a-list-of-ultravisor-secrets.patch @@ -0,0 +1,299 @@ +From 8c4b2872b8e24c1a27d8201beb5979c66ac05268 Mon Sep 17 00:00:00 2001 +From: Ingo Franzki +Date: Thu, 15 Feb 2024 09:08:43 +0100 +Subject: [PATCH] zkey: Add support for retrieving a list of ultravisor secrets + +Add functions to interface with the ultravisor device (/dev/uv) when +running in a secure execution guest to retrieve a list of available +secrets. + +Signed-off-by: Ingo Franzki +Reviewed-by: Jorg Schmidbauer +Signed-off-by: Steffen Eiden +--- + zkey/Makefile | 4 +- + zkey/pvsecrets.c | 161 +++++++++++++++++++++++++++++++++++++++++++++++ + zkey/pvsecrets.h | 89 ++++++++++++++++++++++++++ + 3 files changed, 253 insertions(+), 1 deletion(-) + create mode 100644 zkey/pvsecrets.c + create mode 100644 zkey/pvsecrets.h + +diff --git a/zkey/Makefile b/zkey/Makefile +index 501c5ebf..cbecf125 100644 +--- a/zkey/Makefile ++++ b/zkey/Makefile +@@ -92,9 +92,11 @@ keystore.o: keystore.c keystore.h properties.h pkey.h cca.h ep11.h utils.h + zkey-cryptsetup.o: check-dep-zkey-cryptsetup zkey-cryptsetup.c pkey.h cca.h \ + ep11.h misc.h utils.h + kms.o: kms.c kms.h kms-plugin.h utils.h pkey.h ++pvsecrets.o: pvsecrets.h + + zkey: LDLIBS = -ldl -lcrypto +-zkey: zkey.o pkey.o cca.o ep11.o properties.o keystore.o utils.o kms.o $(libs) ++zkey: zkey.o pkey.o cca.o ep11.o properties.o keystore.o utils.o kms.o \ ++ pvsecrets.o $(libs) + $(LINK) $(ALL_LDFLAGS) $^ $(LDLIBS) -o $@ + + zkey-cryptsetup: LDLIBS = -ldl -lcryptsetup -ljson-c -lcrypto +diff --git a/zkey/pvsecrets.c b/zkey/pvsecrets.c +new file mode 100644 +index 00000000..2874fdf1 +--- /dev/null ++++ b/zkey/pvsecrets.c +@@ -0,0 +1,161 @@ ++/* ++ * zkey - Generate, re-encipher, and validate secure keys ++ * ++ * Copyright IBM Corp. 2024 ++ * ++ * s390-tools is free software; you can redistribute it and/or modify ++ * it under the terms of the MIT license. See LICENSE for details. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "lib/util_base.h" ++#include "lib/util_file.h" ++#include "lib/util_libc.h" ++#include "lib/util_panic.h" ++#include "lib/util_path.h" ++ ++#include "pvsecrets.h" ++ ++#define pr_verbose(verbose, fmt...) do { \ ++ if (verbose) \ ++ warnx(fmt); \ ++ } while (0) ++ ++/** ++ * Opens the ultravisor device and returns its file descriptor. ++ * This only succeeds when running in a secure execution guest. ++ * A failure of this function indicates that it is not running in a secure ++ * execution guest. ++ * ++ * @param verbose if true, verbose messages are printed ++ * ++ * @returns the file descriptor or -1 to indicate an error ++ */ ++int uv_open_device(bool verbose) ++{ ++ unsigned int pvguest = 0, max_retr_secrets = 0; ++ char *path = NULL; ++ int uv_fd, err; ++ ++ uv_fd = open(UVDEVICE, O_RDWR); ++ if (uv_fd < 0) { ++ err = errno; ++ warnx("File '%s:' %s\n", UVDEVICE, strerror(errno)); ++ if (err == EACCES) ++ warnx("Only the 'root' user is allowed to perform " ++ "this command"); ++ else ++ warnx("Ensure that you are running in a secure " ++ "execution guest, and that the 'uvdevice' " ++ "kernel module is loaded."); ++ return -1; ++ } ++ ++ path = util_path_sysfs(SYSFS_UV); ++ if (util_file_read_ui(&pvguest, 10, SYSFS_UV_PV_GUEST, path) != 0 || ++ pvguest != 1) { ++ warnx("You are not running in a secure execution guest."); ++ goto error; ++ } ++ ++ if (util_file_read_ui(&max_retr_secrets, 10, SYSFS_UV_MAX_SECRETS, ++ path) != 0 || ++ max_retr_secrets == 0) { ++ warnx("The ultravisor device is at a too old version, or " ++ "the ultravisor does not support retrievable secrets."); ++ goto error; ++ } ++ free(path); ++ ++ pr_verbose(verbose, "Device '%s' has been opened successfully", ++ UVDEVICE); ++ return uv_fd; ++ ++error: ++ free(path); ++ close(uv_fd); ++ ++ return -1; ++} ++ ++/** ++ * Retrieves a list of secrets from the ultravisor. Calls the supplied callback ++ * function for each secret found. ++ * ++ * @param uv_fd the file descriptor of the ultravisor device ++ * @param cb the callback function ++ * @param cb_private private data to pass to the callback function ++ * @param verbose if true, verbose messages are printed ++ * ++ * @returns 0 on success, a negative errno in case of an error ++ */ ++static int uv_list_secrets(int uv_fd, int (*cb)(u16 idx, u16 type, u32 len, ++ const u8 id[UV_SECRET_ID_LEN], ++ void *cb_private), ++ void *cb_private, bool verbose) ++{ ++ struct uvio_list_secrets *list; ++ struct uvio_ioctl_cb io; ++ unsigned int i; ++ int rc; ++ ++ util_assert(uv_fd != -1, "Internal error: uv_fd is -1"); ++ util_assert(cb != NULL, "Internal error: cb is NULL"); ++ ++ list = util_zalloc(UVIO_LIST_SECRETS_MAX_LEN); ++ ++ memset(&io, 0, sizeof(io)); ++ io.argument_addr = list; ++ io.argument_len = UVIO_LIST_SECRETS_MAX_LEN; ++ ++ rc = ioctl(uv_fd, UVIO_IOCTL_LIST_SECRETS, &io); ++ if (rc != 0) { ++ rc = -errno; ++ ++ pr_verbose(verbose, "ioctl UVIO_IOCTL_LIST_SECRETS: %s", ++ strerror(-rc)); ++ ++ if (rc == -ENOTTY || rc == -EINVAL) ++ warnx("The ultravisor device is at a too old version"); ++ ++ goto out; ++ } ++ ++ if (io.uv_rc != UVIO_RC_SUCCESS) { ++ pr_verbose(verbose, "ioctl UVIO_IOCTL_LIST_SECRETS' uv_rc: %u", ++ io.uv_rc); ++ rc = -EIO; ++ goto out; ++ } ++ ++ pr_verbose(verbose, "Number of secrets: %u", list->num_secrets_stored); ++ ++ for (i = 0; i < list->num_secrets_stored && ++ i < ARRAY_SIZE(list->secret_entries); i++) { ++ if (list->secret_entries[i].secret_type <= ++ UV_SECRET_TYPE_AP_ASSOCIATION) ++ continue; ++ ++ rc = cb(list->secret_entries[i].secret_idx, ++ list->secret_entries[i].secret_type, ++ list->secret_entries[i].secret_len, ++ list->secret_entries[i].secret_id, ++ cb_private); ++ if (rc != 0) ++ break; ++ } ++ ++out: ++ free(list); ++ ++ return rc; ++} +diff --git a/zkey/pvsecrets.h b/zkey/pvsecrets.h +new file mode 100644 +index 00000000..2667e859 +--- /dev/null ++++ b/zkey/pvsecrets.h +@@ -0,0 +1,89 @@ ++/* ++ * zkey - Generate, re-encipher, and validate secure keys ++ * ++ * This header file defines functions for the PV secrets support as well ++ * as the interface to the uv kernel module. ++ * ++ * Copyright IBM Corp. 2024 ++ * ++ * s390-tools is free software; you can redistribute it and/or modify ++ * it under the terms of the MIT license. See LICENSE for details. ++ */ ++ ++#ifndef PVSECRETS_H ++#define PVSECRETS_H ++ ++#include "lib/zt_common.h" ++ ++/* ++ * Definitions for the /dev/uv kernel module interface ++ */ ++#define UVDEVICE "/dev/uv" ++#define SYSFS_UV "firmware/uv" ++#define SYSFS_UV_PV_GUEST "%s/prot_virt_guest" ++#define SYSFS_UV_MAX_SECRETS "%s/query/max_retr_secrets" ++ ++struct uvio_ioctl_cb { ++ u32 flags; ++ u16 uv_rc; /* UV header rc value */ ++ u16 uv_rrc; /* UV header rrc value */ ++ void *argument_addr; /* Userspace address of uvio argument */ ++ u32 argument_len; ++ u8 reserved14[0x40 - 0x14]; /* must be zero */ ++}; ++ ++#define UVIO_IOCTL_LIST_SECRETS_NR 3 ++ ++#define UVIO_TYPE_UVC 'u' ++#define UVIO_IOCTL(nr) _IOWR(UVIO_TYPE_UVC, \ ++ nr, struct uvio_ioctl_cb) ++#define UVIO_IOCTL_LIST_SECRETS UVIO_IOCTL( \ ++ UVIO_IOCTL_LIST_SECRETS_NR) ++ ++#define UVIO_RC_SUCCESS 0x0001 ++#define UVIO_RC_MORE_DATA 0x0100 ++ ++#define UV_SECRET_TYPE_INVALID 0x00 ++#define UV_SECRET_TYPE_NULL 0x01 ++#define UV_SECRET_TYPE_AP_ASSOCIATION 0x02 ++#define UV_SECRET_TYPE_PLAIN_TEXT 0x03 ++#define UV_SECRET_TYPE_AES_128 0x04 ++#define UV_SECRET_TYPE_AES_192 0x05 ++#define UV_SECRET_TYPE_AES_256 0x06 ++#define UV_SECRET_TYPE_AES_XTS_128 0x07 ++#define UV_SECRET_TYPE_AES_XTS_256 0x08 ++#define UV_SECRET_TYPE_HMAC_SHA_256 0x09 ++#define UV_SECRET_TYPE_HMAC_SHA_512 0x0a ++#define UV_SECRET_TYPE_ECDSA_P256 0x11 ++#define UV_SECRET_TYPE_ECDSA_P384 0x12 ++#define UV_SECRET_TYPE_ECDSA_P521 0x13 ++#define UV_SECRET_TYPE_EDDSA_ED25519 0x14 ++#define UV_SECRET_TYPE_EDDSA_ED448 0x15 ++ ++#define UV_SECRET_ID_LEN 32 ++ ++#define UVIO_LIST_SECRETS_MAX_LEN 0x8000 ++ ++struct uvio_list_secret_entry { ++ u16 secret_idx; ++ u16 secret_type; ++ u32 secret_len; ++ u64 reserved; ++ u8 secret_id[UV_SECRET_ID_LEN]; ++} __packed; ++ ++#define UVIO_MAX_SECRET_ENTRIES ((UVIO_LIST_SECRETS_MAX_LEN - 16) / \ ++ sizeof(struct uvio_list_secret_entry)) ++ ++struct uvio_list_secrets { ++ u16 num_secrets_stored; ++ u16 num_secrets_total; ++ u16 next_secret_idx; ++ u16 reserved1; ++ u64 reserved2; ++ struct uvio_list_secret_entry secret_entries[UVIO_MAX_SECRET_ENTRIES]; ++} __packed; ++ ++int uv_open_device(bool verbose); ++ ++#endif diff --git a/s390-tools-02-zkey-Add-the--pvsecrets-list-command.patch b/s390-tools-02-zkey-Add-the--pvsecrets-list-command.patch new file mode 100644 index 0000000..5055ce4 --- /dev/null +++ b/s390-tools-02-zkey-Add-the--pvsecrets-list-command.patch @@ -0,0 +1,823 @@ +From 5ce79ea667ea946e6591fe898db13becad018667 Mon Sep 17 00:00:00 2001 +From: Ingo Franzki +Date: Thu, 15 Feb 2024 11:22:04 +0100 +Subject: [PATCH] zkey: Add the 'pvsecrets list' command + +The 'pvsecrets list' command lists the available protected virtualization +secrets. By default, only those pvsecret types are listed, that can be used +with zkey. If option '--all/-a' is specified, then all pvsecret types are +listed. Nevertheless, pvsecret types not supported by zkey can not be used +with zkey. + +This command only works when running in a secure execution guest. + +Signed-off-by: Ingo Franzki +Reviewed-by: Jorg Schmidbauer +Signed-off-by: Steffen Eiden +--- + zkey/pvsecrets.c | 393 +++++++++++++++++++++++++++++++++++++++++++++++ + zkey/pvsecrets.h | 4 + + zkey/zkey.1 | 91 ++++++++++- + zkey/zkey.c | 135 +++++++++++++++- + 4 files changed, 619 insertions(+), 4 deletions(-) + +diff --git a/zkey/pvsecrets.c b/zkey/pvsecrets.c +index 2874fdf1..7f28feba 100644 +--- a/zkey/pvsecrets.c ++++ b/zkey/pvsecrets.c +@@ -7,6 +7,7 @@ + * it under the terms of the MIT license. See LICENSE for details. + */ + ++#include + #include + #include + #include +@@ -17,14 +18,60 @@ + #include + #include + ++#include ++ + #include "lib/util_base.h" + #include "lib/util_file.h" + #include "lib/util_libc.h" + #include "lib/util_panic.h" + #include "lib/util_path.h" ++#include "lib/util_rec.h" + + #include "pvsecrets.h" + ++struct pvsecret_type_info { ++ u16 type; ++ const char *name; ++ bool zkey_usage; ++}; ++ ++static const struct pvsecret_type_info pvsecret_type_info[] = { ++ { .type = UV_SECRET_TYPE_NULL, .name = "NULL", ++ .zkey_usage = false }, ++ { .type = UV_SECRET_TYPE_AP_ASSOCIATION, .name = "AP-ASSOCIATION", ++ .zkey_usage = false }, ++ { .type = UV_SECRET_TYPE_PLAIN_TEXT, .name = "PLAIN-TEXT", ++ .zkey_usage = false }, ++ { .type = UV_SECRET_TYPE_AES_128, .name = "AES-128", ++ .zkey_usage = true }, ++ { .type = UV_SECRET_TYPE_AES_192, .name = "AES-192", ++ .zkey_usage = true }, ++ { .type = UV_SECRET_TYPE_AES_256, .name = "AES-256", ++ .zkey_usage = true }, ++ { .type = UV_SECRET_TYPE_AES_XTS_128, .name = "AES-XTS-128", ++ .zkey_usage = true }, ++ { .type = UV_SECRET_TYPE_AES_XTS_256, .name = "AES-XTS-256", ++ .zkey_usage = true }, ++ { .type = UV_SECRET_TYPE_HMAC_SHA_256, .name = "HMAC-SHA-256", ++ .zkey_usage = false }, ++ { .type = UV_SECRET_TYPE_HMAC_SHA_512, .name = "HMAC-SHA-512", ++ .zkey_usage = false }, ++ { .type = UV_SECRET_TYPE_ECDSA_P256, .name = "ECDSA-P256", ++ .zkey_usage = false }, ++ { .type = UV_SECRET_TYPE_ECDSA_P384, .name = "ECDSA-P384", ++ .zkey_usage = false }, ++ { .type = UV_SECRET_TYPE_ECDSA_P521, .name = "ECDSA-P521", ++ .zkey_usage = false }, ++ { .type = UV_SECRET_TYPE_EDDSA_ED25519, .name = "EDDSA-ED25519", ++ .zkey_usage = false }, ++ { .type = UV_SECRET_TYPE_EDDSA_ED448, .name = "EDDSA-ED448", ++ .zkey_usage = false }, ++ { .type = UV_SECRET_TYPE_INVALID, } ++}; ++ ++#define PVSECRETS_REC_ID "Secret ID" ++#define PVSECRETS_REC_TYPE "Type" ++ + #define pr_verbose(verbose, fmt...) do { \ + if (verbose) \ + warnx(fmt); \ +@@ -159,3 +206,349 @@ static int uv_list_secrets(int uv_fd, int (*cb)(u16 idx, u16 type, u32 len, + + return rc; + } ++ ++/** ++ * Returns true if the secret type is supported by zkey ++ * ++ * @param type the secret type ++ * ++ * @returns true if the type is supported, false otherwise ++ */ ++static bool is_pvsecret_type_supported(u16 type) ++{ ++ unsigned int i; ++ ++ for (i = 0; pvsecret_type_info[i].type != UV_SECRET_TYPE_INVALID; i++) { ++ if (pvsecret_type_info[i].type == type) ++ return pvsecret_type_info[i].zkey_usage; ++ } ++ ++ return false; ++} ++ ++/** ++ * Returns the secret type name for the specified secret type ++ * ++ * @param type the secret type ++ * ++ * @returns a constant string containing the type name ++ */ ++static const char *get_pvsecret_type_name(u16 type) ++{ ++ unsigned int i; ++ ++ for (i = 0; pvsecret_type_info[i].type != UV_SECRET_TYPE_INVALID; i++) { ++ if (pvsecret_type_info[i].type == type) ++ return pvsecret_type_info[i].name; ++ } ++ ++ return "[UNKNOWN]"; ++} ++ ++/** ++ * Returns the secret type for the specified type name ++ * ++ * @param name the secret type name ++ * ++ * @returns the secret type or UV_SECRET_TYPE_INVALID if unknown. ++ */ ++static u16 get_pvsecret_type_by_name(const char *name) ++{ ++ unsigned int i; ++ ++ for (i = 0; pvsecret_type_info[i].type != UV_SECRET_TYPE_INVALID; i++) { ++ if (strcasecmp(pvsecret_type_info[i].name, name) == 0) ++ return pvsecret_type_info[i].type; ++ } ++ ++ return UV_SECRET_TYPE_INVALID; ++} ++ ++/** ++ * Parses a 32 byte hex string into a 32 byte binary secret ID ++ * ++ * @param id_str the hex string to parse ++ * @param id the output buffer to store the secret ID ++ * ++ * @returns 0 for success or a negative errno in case of an error ++ */ ++static int parse_secret_id_str(const char *id_str, ++ unsigned char id[UV_SECRET_ID_LEN]) ++{ ++ char hex[3] = { 0 }; ++ unsigned long val; ++ unsigned int i; ++ char *endptr; ++ ++ util_assert(id_str != NULL, "Internal error: id_str is NULL"); ++ util_assert(id != NULL, "Internal error: id is NULL"); ++ ++ if (strncasecmp(id_str, "0x", 2) == 0) ++ id_str += 2; ++ ++ if (strlen(id_str) != UV_SECRET_ID_LEN * 2) ++ return -EINVAL; ++ ++ for (i = 0; i < UV_SECRET_ID_LEN; i++) { ++ hex[0] = id_str[i * 2]; ++ hex[1] = id_str[i * 2 + 1]; ++ ++ errno = 0; ++ val = strtoul(hex, &endptr, 16); ++ if (errno != 0 || *endptr != '\0' || val > 0xff) ++ return -EINVAL; ++ ++ id[i] = val; ++ } ++ ++ return 0; ++} ++ ++/** ++ * Get the 32 byte binary secret ID from the secret name by calculating the ++ * SHA-256 has from the name. ++ * ++ * @param name the name of the secret ++ * @param id the output buffer to store the secret ID ++ * ++ * @returns 0 for success or a negative errno in case of an error ++ */ ++static int get_secret_id_from_name(const char *name, ++ unsigned char id[UV_SECRET_ID_LEN]) ++{ ++ util_assert(name != NULL, "Internal error: id_str is NULL"); ++ util_assert(id != NULL, "Internal error: id is NULL"); ++ util_assert(UV_SECRET_ID_LEN == SHA256_DIGEST_LENGTH, ++ "Internal error: UV_SECRET_ID_LEN != SHA256_DIGEST_LENGTH"); ++ ++ if (SHA256((const unsigned char *)name, strlen(name), id) != id) ++ return -EIO; ++ ++ return 0; ++} ++ ++/** ++ * Gets the binary 32 byte secret id from either a hex string or a secret name. ++ * ++ * @param hex the secret id as hex string. Can be NULL. ++ * @param name the secret name. Can be NULL. If the id ++ * parameter is non-NULL, then this parameter is ++ * ignored. ++ * @param id Output: the 32 byte binary secret id. ++ * @param id_str Output: the secret id in printable ascii chars ++ * form, if name is non-NULL and the name length is ++ * less than UV_SECRET_ID_LEN. ++ * ++ * @returns 0 on success, a negative errno in case of an error. ++ * If neither the hex string nor the secret name is specified, 1 is returned, ++ * and the id parameter is not modified. ++ */ ++static int get_secret_id_from_hex_or_name(const char *hex, const char *name, ++ unsigned char id[UV_SECRET_ID_LEN], ++ char id_name[UV_SECRET_ID_LEN]) ++{ ++ int rc; ++ ++ util_assert(id != NULL, "Internal error: id is NULL"); ++ ++ if (hex != NULL) { ++ rc = parse_secret_id_str(hex, id); ++ if (rc != 0) { ++ warnx("Invalid pvsecret id specified: '%s'", hex); ++ return rc; ++ } ++ ++ return 0; ++ } ++ ++ if (name != NULL) { ++ rc = get_secret_id_from_name(name, id); ++ if (rc != 0) { ++ warnx("Failed to get the ID from pvsecret name: '%s'", ++ name); ++ return rc; ++ } ++ ++ if (strlen(name) < UV_SECRET_ID_LEN) { ++ strncpy(id_name, name, UV_SECRET_ID_LEN); ++ id_name[UV_SECRET_ID_LEN - 1] = '\0'; ++ } ++ ++ return 0; ++ } ++ ++ return 1; ++} ++ ++/** ++ * Checks if the secret id is printable. To be printable, all characters up to ++ * the first zero byte must be printable. All bytes after the first zero byte ++ * must be all zero. There must be at least one zero byte as the very last byte ++ * of the id. ++ * ++ * @param id the ID of the secret ++ * @param name Output: the id in the printable form and enclosed ++ * in single quotes if the id is printable. The max ++ * length of the name buffer is UV_SECRET_ID_LEN + 2: ++ * A starting quote, up to UV_SECRET_ID_LEN-1 chars, ++ * an ending quote and a zero termination byte. ++ * ++ * @returns true if the id is printable, false otherwise. ++ */ ++static bool is_printable_name(const u8 id[UV_SECRET_ID_LEN], ++ char name[UV_SECRET_ID_LEN + 2]) ++{ ++ bool end_found = false, printable_name = false; ++ unsigned int i; ++ ++ name[0] = '\''; ++ for (i = 0; i < UV_SECRET_ID_LEN; i++) { ++ if (!end_found) { ++ if (id[i] == '\0') { ++ name[1 + i] = '\''; ++ end_found = true; ++ } else if (isprint(id[i])) { ++ name[1 + i] = id[i]; ++ printable_name = true; ++ } else { ++ printable_name = false; ++ end_found = true; ++ } ++ } else if (id[i] != '\0') { ++ printable_name = false; ++ } ++ } ++ if (!end_found) ++ printable_name = false; ++ ++ return printable_name; ++} ++ ++struct list_secrets_data { ++ struct util_rec *rec; ++ bool all; ++ bool hex; ++ u16 type_filter; ++ bool id_filter; ++ char name[UV_SECRET_ID_LEN]; ++ unsigned char id[UV_SECRET_ID_LEN]; ++ unsigned int matched; ++}; ++ ++/** ++ * Callback used with pvsecrets_list function. Called for each secret. ++ * ++ * @param idx the index of the secret ++ * @param type the type of the secret ++ * @param id the ID of the secret ++ * @param cb_private callback private data ++ * ++ * @returns 0 on success, a negative errno in case of an error ++ */ ++static int pvsecrets_list_cb(u16 UNUSED(idx), u16 type, u32 UNUSED(len), ++ const u8 id[UV_SECRET_ID_LEN], void *cb_private) ++{ ++ struct list_secrets_data *list_data = cb_private; ++ char name[2 + UV_SECRET_ID_LEN] = { 0 }; ++ char hex[2 * UV_SECRET_ID_LEN + 1] = { 0 }; ++ unsigned int i; ++ ++ if (!list_data->all && !is_pvsecret_type_supported(type)) ++ return 0; ++ ++ if (list_data->type_filter != 0 && type != list_data->type_filter) ++ return 0; ++ ++ if (list_data->id_filter && ++ memcmp(id, list_data->name, UV_SECRET_ID_LEN) != 0 && ++ memcmp(id, list_data->id, UV_SECRET_ID_LEN) != 0) ++ return 0; ++ ++ for (i = 0; i < UV_SECRET_ID_LEN; i++) ++ sprintf(&hex[i * 2], "%02x", id[i]); ++ ++ if (!list_data->hex && is_printable_name(id, name)) ++ util_rec_set(list_data->rec, PVSECRETS_REC_ID, name); ++ else ++ util_rec_set(list_data->rec, PVSECRETS_REC_ID, hex); ++ util_rec_set(list_data->rec, PVSECRETS_REC_TYPE, ++ get_pvsecret_type_name(type)); ++ ++ if (list_data->matched == 0) ++ util_rec_print_hdr(list_data->rec); ++ ++ util_rec_print(list_data->rec); ++ ++ list_data->matched++; ++ ++ return 0; ++} ++ ++/** ++ * Lists protected virtualization secrets. ++ * ++ * @param uv_fd the file descriptor of the ultravisor device ++ * @param all if true, all secret types are listed ++ * @param hex if true, list the secret ID in hex, even if the ++ * secret ID would be printable ++ * @param type_filter only display secrets of the specified secret type. ++ * Can be NULL. ++ * @param secret_id the secret id to list. Can be NULL. ++ * @param secret_name the secret name to list. Can be NULL. If the id ++ * parameter is non-NULL, then this parameter is ++ * ignored. ++ * @param verbose if true, verbose messages are printed ++ * ++ * @returns 0 on success, a negative errno in case of an error ++ */ ++int pvsecrets_list(int uv_fd, bool all, bool hex, const char *type_filter, ++ const char *secret_id, const char *secret_name, ++ bool verbose) ++{ ++ struct list_secrets_data list_data = { 0 }; ++ int rc; ++ ++ util_assert(uv_fd != -1, "Internal error: uv_fd is -1"); ++ ++ list_data.all = all; ++ list_data.hex = hex; ++ list_data.type_filter = UV_SECRET_TYPE_INVALID; ++ ++ if (type_filter != NULL) { ++ list_data.type_filter = get_pvsecret_type_by_name(type_filter); ++ if (list_data.type_filter == UV_SECRET_TYPE_INVALID) { ++ warnx("Invalid pvsecret type specified: %s", ++ type_filter); ++ return -EINVAL; ++ } ++ } ++ ++ if (secret_id != NULL || secret_name != NULL) { ++ rc = get_secret_id_from_hex_or_name(secret_id, secret_name, ++ list_data.id, ++ list_data.name); ++ if (rc < 0) ++ return rc; ++ ++ list_data.id_filter = true; ++ } ++ ++ list_data.rec = util_rec_new_wide("-"); ++ util_rec_def(list_data.rec, PVSECRETS_REC_ID, UTIL_REC_ALIGN_LEFT, ++ UV_SECRET_ID_LEN * 2, PVSECRETS_REC_ID); ++ util_rec_def(list_data.rec, PVSECRETS_REC_TYPE, UTIL_REC_ALIGN_LEFT, ++ 12, PVSECRETS_REC_TYPE); ++ ++ rc = uv_list_secrets(uv_fd, pvsecrets_list_cb, &list_data, verbose); ++ if (rc != 0) { ++ warnx("Failed to list protected virtualization secrets: %s", ++ strerror(-rc)); ++ } ++ ++ util_rec_free(list_data.rec); ++ ++ if (list_data.matched == 0) ++ rc = -ENOENT; ++ ++ return rc; ++} +diff --git a/zkey/pvsecrets.h b/zkey/pvsecrets.h +index 2667e859..6acebfdd 100644 +--- a/zkey/pvsecrets.h ++++ b/zkey/pvsecrets.h +@@ -86,4 +86,8 @@ struct uvio_list_secrets { + + int uv_open_device(bool verbose); + ++int pvsecrets_list(int uv_fd, bool all, bool hex, const char *type_filter, ++ const char *secret_id, const char *secret_name, ++ bool verbose); ++ + #endif +diff --git a/zkey/zkey.1 b/zkey/zkey.1 +index 8c5a09a7..4386629f 100644 +--- a/zkey/zkey.1 ++++ b/zkey/zkey.1 +@@ -1,8 +1,8 @@ +-.\" Copyright IBM Corp. 2017, 2020 ++.\" Copyright IBM Corp. 2017, 2024 + .\" s390-tools is free software; you can redistribute it and/or modify + .\" it under the terms of the MIT license. See LICENSE for details. + .\" +-.TH ZKEY 1 "July 2020" "s390-tools" ++.TH ZKEY 1 "February 2024" "s390-tools" + .SH NAME + zkey \- Manage secure AES keys + . +@@ -1162,6 +1162,54 @@ fails. Use option \fB\-\-no\-volume\-check\fP to omit the volume check, and + refresh the keys even if the associated volume(s) do not exist. + . + . ++.SH COMMANDS FOR PROTECTED VIRTUALIZATION ++. ++Use the \fBpvsecrets\fP command to work with protected virtualization (PV) ++secrets. Protected virtualization secrets can be made available to a secure ++execution guest and can be used only within that guest. ++The \fBpvsecrets\fP command provides subcommands for protected ++virtualization specific operations. Use \fBzkey pvsecrets \-\-help\fP to show ++the available subcommands. These subcommands only work when running in a ++secure execution guest. Only the \fBroot\fP user is allowed to perform these ++subcommands. ++. ++.SS "List available protected virtualization secrets" ++. ++.B zkey pvsecrets ++.BR list | li ++.RB [ \-\-all | \-A ] ++.RB [ \-\-hex | \-H ] ++.RB [ \-\-pvsecret\-type | \-T ++.IR pvsecret\-type ] ++.RB [ \-\-pvsecret\-id | \-I ++.IR pvsecret\-id ] ++.RB [ \-\-pvsecret\-name | \-e ++.IR pvsecret\-name ] ++.RB [ \-\-verbose | \-V ] ++. ++.PP ++Use the ++.B pvsecrets list ++command to display a list of protected virtualization (PV) secrets. It displays ++the pvsecret ID as hex string of 32 bytes or as printable name enclosed in ++single quotes (\fB'\fP), if the pvsecret ID consists of only printable ++characters. Specify the \fB\-\-hex\fP option to list all pvsecret IDs as hex ++string. The ++.B pvsecrets list ++command also shows the pvsecret type of each secret. ++.PP ++You can filter the list of pvsecrets by pvsecret ID, pvsecret name and pvsecret ++type. Either the \fB\-\-pvsecret\-id\fP option or the ++\fB\-\-pvsecret\-name\fP option can be specified. By default the ++\fBpvsecrets list\fP command displays only those pvsecrets with types that ++are supported by the \fBzkey\fP tool. To list all pvsecret types, specify the ++\fB\-\-all\fP option. ++.PP ++This command is only available when running in a secure execution guest. ++Only the \fBroot\fP user is allowed to perform this command. ++. ++. ++. + . + .SH OPTIONS + .SS "Options for the generate command" +@@ -2030,6 +2078,45 @@ repository. This option only has an effect when specified together with option + . + . + . ++.SS "Options for the pvsecrets list command" ++.TP ++.BR \-A ", " \-\-all ++List all protected virtualization (PV) secret types, not only those that can be ++used with zkey. ++.TP ++.BR \-H ", " \-\-hex ++Show all protected virtualization (PV) secret IDs in hex, even if the ID ++contains only printable characters. ++.TP ++.BR \-T ", " \-\-pvsecret\-type\~\fIpvsecret\-type\fP ++Type of the protected virtualization (PV) secret to list. If omitted, all ++secret types are listed. Possible values are: \fBPLAIN\-TEXT\fP, \fBAES\-128\fP, ++\fBAES\-192\fP, \fBAES\-256\fP, \fBAES\-XTS\-128\fP, \fBAES\-XTS\-256\fP, ++\fBHMAC\-SHA\-256\fP, \fBHMAC\-SHA\-512\fP, \fBECDSA\-P256\fP, ++\fBECDSA\-P384\fP, \fBECDSA\-P521\fP, \fBEDDSA\-ED25519\fP, and ++\fBEDDSA\-ED448\fP. ++.TP ++.BR \-I ", " \-\-pvsecret\-id\~\fIpvsecret\-id\fP ++ID of the protected virtualization (PV) secret to list. The pvsecret ID is a 32 ++byte hex string, optionally prefixed by \fB0x\fP. You can use the YAML file that ++was created when using the \fBpvsecret create\fP command for adding the ++protected virtualization secret: ++\fB\-\-pvsecret\-id "$(yq .id \fP\fIYAML\-FILE\fP\fB)"\fP. ++You might have to install the \fByq\fP package first. ++Either the \fB\-\-pvsecret\-id\fP option or the \fB\-\-pvsecret\-name\fP option ++can be specified, but not both. ++.TP ++.BR \-e ", " \-\-pvsecret\-name\~\fIpvsecret\-name\fP ++Name of the protected virtualization (PV) secret to list. You can use the YAML ++file that was created when using the \fBpvsecret create\fP command for adding ++the protected virtualization secret: ++\fB\-\-pvsecret\-name "$(yq .name \fP\fIYAML\-FILE\fP\fB)"\fP. ++You might have to install the \fByq\fP package first. ++Either the \fB\-\-pvsecret\-id\fP option or the \fB\-\-pvsecret\-name\fP option ++can be specified, but not both. ++. ++. ++. + .SS "General options" + .TP + .BR \-V ", " \-\-verbose +diff --git a/zkey/zkey.c b/zkey/zkey.c +index 7c909ff0..adc48d60 100644 +--- a/zkey/zkey.c ++++ b/zkey/zkey.c +@@ -1,7 +1,7 @@ + /* + * zkey - Generate, re-encipher, and validate secure keys + * +- * Copyright IBM Corp. 2017, 2020 ++ * Copyright IBM Corp. 2017, 2024 + * + * s390-tools is free software; you can redistribute it and/or modify + * it under the terms of the MIT license. See LICENSE for details. +@@ -34,6 +34,7 @@ + #include "pkey.h" + #include "utils.h" + #include "kms.h" ++#include "pvsecrets.h" + + /* + * Program configuration +@@ -46,7 +47,7 @@ static const struct util_prg prg = { + { + .owner = "IBM Corp.", + .pub_first = 2017, +- .pub_last = 2020, ++ .pub_last = 2024, + }, + UTIL_PRG_COPYRIGHT_END + } +@@ -93,10 +94,16 @@ static struct zkey_globals { + bool open; + bool format; + bool refresh_properties; ++ bool all; ++ bool hex; ++ char *secret_type; ++ char *secret_id; ++ char *secret_name; + struct ext_lib lib; + struct cca_lib cca; + struct ep11_lib ep11; + int pkey_fd; ++ int uv_fd; + struct keystore *keystore; + struct kms_info kms_info; + int first_kms_option; +@@ -104,6 +111,7 @@ static struct zkey_globals { + size_t num_kms_options; + } g = { + .pkey_fd = -1, ++ .uv_fd = -1, + .sector_size = -1, + .lib.cca = &g.cca, + .lib.ep11 = &g.ep11, +@@ -135,6 +143,8 @@ static struct zkey_globals { + #define COMMAND_KMS_LIST "list" + #define COMMAND_KMS_IMPORT "import" + #define COMMAND_KMS_REFRESH "refresh" ++#define COMMAND_PVSECRETS "pvsecrets" ++#define COMMAND_PVSECRETS_LIST "list" + + #define OPT_COMMAND_PLACEHOLDER "PLACEHOLDER" + +@@ -1182,6 +1192,48 @@ static struct util_opt opt_vec[] = { + .flags = UTIL_OPT_FLAG_NOSHORT, + }, + /***********************************************************/ ++ { ++ .flags = UTIL_OPT_FLAG_SECTION, ++ .desc = "OPTIONS", ++ .command = COMMAND_PVSECRETS " " COMMAND_PVSECRETS_LIST, ++ }, ++ { ++ .option = {"all", 0, NULL, 'A'}, ++ .desc = "List all protected virtualization (PV) secret types, " ++ "not only those that can be used with zkey.", ++ .command = COMMAND_PVSECRETS " " COMMAND_PVSECRETS_LIST, ++ }, ++ { ++ .option = {"hex", 0, NULL, 'H'}, ++ .desc = "Show all protected virtualization (PV) secret IDs in " ++ "hex, even if the ID contains only printable " ++ "characters.", ++ .command = COMMAND_PVSECRETS " " COMMAND_PVSECRETS_LIST, ++ }, ++ { ++ .option = { "pvsecret-type", required_argument, NULL, 'T'}, ++ .argument = "PVSECRET-TYPE", ++ .desc = "Type of the protected virtualization (PV) secret to " ++ "list. If omitted, all pvsecret types are listed.", ++ .command = COMMAND_PVSECRETS " " COMMAND_PVSECRETS_LIST, ++ }, ++ { ++ .option = { "pvsecret-id", required_argument, NULL, 'I'}, ++ .argument = "PVSECRET-ID", ++ .desc = "ID of the protected virtualization (PV) secret to " ++ "list. Either '--pvsecret-id/-I' or " ++ "'--pvsecret-name/-e' can be specified, but not both.", ++ .command = COMMAND_PVSECRETS " " COMMAND_PVSECRETS_LIST, ++ }, ++ { ++ .option = { "pvsecret-name", required_argument, NULL, 'e'}, ++ .argument = "PVSECRET-NAME", ++ .desc = "Name of the protected virtualization (PV) secret to " ++ "list. Either '--pvsecret-name/-e' or " ++ "'--pvsecret-id/-I' can be specified, but not both.", ++ .command = COMMAND_PVSECRETS " " COMMAND_PVSECRETS_LIST, ++ }, ++ /***********************************************************/ + OPT_PLACEHOLDER, + OPT_PLACEHOLDER, + OPT_PLACEHOLDER, +@@ -1249,6 +1301,7 @@ struct zkey_command { + int need_cca_library; + int need_ep11_library; + int need_pkey_device; ++ int need_uv_device; + char *short_desc; + char *long_desc; + int has_options; +@@ -1285,6 +1338,7 @@ static int command_kms_reencipher(void); + static int command_kms_list(void); + static int command_kms_import(void); + static int command_kms_refresh(void); ++static int command_pvsecrets_list(void); + + static struct zkey_command zkey_kms_commands[] = { + { +@@ -1402,6 +1456,22 @@ static struct zkey_command zkey_kms_commands[] = { + { .command = NULL } + }; + ++static struct zkey_command zkey_pvsecrets_commands[] = { ++ { ++ .command = COMMAND_PVSECRETS_LIST, ++ .abbrev_len = 2, ++ .function = command_pvsecrets_list, ++ .short_desc = "Lists protected virtualization (PV) secrets", ++ .long_desc = "Lists available protected virtualization (PV) " ++ "secrets. This command is only available when " ++ "running in a secure execution guest. Only the " ++ "'root' user is allowed to perform this command", ++ .has_options = 1, ++ .need_uv_device = 1, ++ }, ++ { .command = NULL } ++}; ++ + static struct zkey_command zkey_commands[] = { + { + .command = COMMAND_GENERATE, +@@ -1565,6 +1635,21 @@ static struct zkey_command zkey_commands[] = { + .has_options = 1, + .sub_commands = zkey_kms_commands, + }, ++ { ++ .command = COMMAND_PVSECRETS, ++ .abbrev_len = 2, ++ .short_desc = "Protected virtualization (PV) support", ++ .long_desc = "Provides subcommands for working with protected " ++ "virtualization (PV) secrets. Protected " ++ "virtualization secrets can be made available to " ++ "a secure execution guest and can be used only " ++ "within that guest. Thus, these subcommands are " ++ "only available when running in a secure " ++ "execution guest. Only the 'root' user is allowed " ++ "to perform these subcommands.", ++ .has_options = 1, ++ .sub_commands = zkey_pvsecrets_commands, ++ }, + { .command = NULL } + }; + +@@ -2899,6 +2984,28 @@ static int command_kms_refresh(void) + return rc != 0 ? EXIT_FAILURE : EXIT_SUCCESS; + } + ++/* ++ * Command handler for 'pvsecrets list'. ++ * ++ * Lists available protected virtualization secrets ++ */ ++static int command_pvsecrets_list(void) ++{ ++ int rc; ++ ++ if (g.secret_id != NULL && g.secret_name != NULL) { ++ warnx("Either '--pvsecret-id/-I' or '--pvsecret-name/-e' can " ++ "be specified, but not both"); ++ util_prg_print_parse_error(); ++ return EXIT_FAILURE; ++ } ++ ++ rc = pvsecrets_list(g.uv_fd, g.all, g.hex, g.secret_type, g.secret_id, ++ g.secret_name, g.verbose); ++ ++ return rc != 0 ? EXIT_FAILURE : EXIT_SUCCESS; ++} ++ + /** + * Opens the keystore. The keystore directory is either the + * default directory or as specified in an environment variable +@@ -3234,6 +3341,21 @@ int main(int argc, char *argv[]) + case OPT_REMOVE_DUMMY_PASSPHRASE: + g.remove_passphrase = 1; + break; ++ case 'A': ++ g.all = 1; ++ break; ++ case 'H': ++ g.hex = 1; ++ break; ++ case 'T': ++ g.secret_type = optarg; ++ break; ++ case 'I': ++ g.secret_id = optarg; ++ break; ++ case 'e': ++ g.secret_name = optarg; ++ break; + case 'h': + print_help(command, sub_command); + return EXIT_SUCCESS; +@@ -3294,6 +3416,13 @@ int main(int argc, char *argv[]) + goto out; + } + } ++ if (cmd->need_uv_device) { ++ g.uv_fd = uv_open_device(g.verbose); ++ if (g.uv_fd == -1) { ++ rc = EXIT_FAILURE; ++ goto out; ++ } ++ } + + if (g.kms_info.plugin_lib != NULL) { + rc = init_kms_plugin(&g.kms_info, g.verbose); +@@ -3323,6 +3452,8 @@ int main(int argc, char *argv[]) + dlclose(g.ep11.lib_ep11); + if (g.pkey_fd >= 0) + close(g.pkey_fd); ++ if (g.uv_fd >= 0) ++ close(g.uv_fd); + if (g.keystore) + keystore_free(g.keystore); + if (g.kms_options != NULL) diff --git a/s390-tools-03-zkey-Add-PVSECRETS-AES-key-type.patch b/s390-tools-03-zkey-Add-PVSECRETS-AES-key-type.patch new file mode 100644 index 0000000..f17e3a7 --- /dev/null +++ b/s390-tools-03-zkey-Add-PVSECRETS-AES-key-type.patch @@ -0,0 +1,340 @@ +From fdf66dc148c09f1d3300cd3378e3f75a83c6214e Mon Sep 17 00:00:00 2001 +From: Ingo Franzki +Date: Thu, 15 Feb 2024 16:56:04 +0100 +Subject: [PATCH] zkey: Add PVSECRETS-AES key type + +Add the definitions and utility functions for the PVSECRETS-AES key type. +A PVSECRETS-AES key token contains the secret id of a protected +virtualization secret. It does not contain the key material, just a +reference to the key in the ultravisor. + +When such a key token is used to perform crypto operations later on, the +PAES kernel cipher will obtain the protected key belonging to this secret +id with the help of the pkey kernel module. + +Signed-off-by: Ingo Franzki +Reviewed-by: Jorg Schmidbauer +Signed-off-by: Steffen Eiden +--- + zkey/keystore.c | 4 +- + zkey/pkey.c | 110 ++++++++++++++++++++++++++++++++++++++++++++++- + zkey/pkey.h | 38 ++++++++++++---- + zkey/pvsecrets.h | 4 +- + 4 files changed, 143 insertions(+), 13 deletions(-) + +diff --git a/zkey/keystore.c b/zkey/keystore.c +index 9589d82d..771bc08d 100644 +--- a/zkey/keystore.c ++++ b/zkey/keystore.c +@@ -3,7 +3,7 @@ + * + * Keystore handling functions + * +- * Copyright IBM Corp. 2018, 2020 ++ * Copyright IBM Corp. 2018, 2024 + * + * s390-tools is free software; you can redistribute it and/or modify + * it under the terms of the MIT license. See LICENSE for details. +@@ -360,6 +360,8 @@ static int _keystore_valid_key_type(const char *key_type) + return 1; + if (strcasecmp(key_type, KEY_TYPE_EP11_AES) == 0) + return 1; ++ if (strcasecmp(key_type, KEY_TYPE_PVSECRET_AES) == 0) ++ return 1; + + return 0; + } +diff --git a/zkey/pkey.c b/zkey/pkey.c +index 821978bd..53c0a550 100644 +--- a/zkey/pkey.c ++++ b/zkey/pkey.c +@@ -1,7 +1,7 @@ + /* + * zkey - Generate, re-encipher, and validate secure keys + * +- * Copyright IBM Corp. 2018 ++ * Copyright IBM Corp. 2018, 2024 + * + * s390-tools is free software; you can redistribute it and/or modify + * it under the terms of the MIT license. See LICENSE for details. +@@ -26,6 +26,7 @@ + #include "lib/util_panic.h" + + #include "pkey.h" ++#include "pvsecrets.h" + #include "utils.h" + + #ifndef AF_ALG +@@ -1719,6 +1720,38 @@ bool is_ep11_key_session_bound(const u8 *key, size_t key_size) + } + } + ++/** ++ * Check if the specified key is a PVSECRET-AES key token. ++ * ++ * @param[in] key the secure key token ++ * @param[in] key_size the size of the secure key ++ * ++ * @returns true if the key is a PVSECRET token type ++ */ ++bool is_pvsecret_aes_key(const u8 *key, size_t key_size) ++{ ++ struct pvsecrettoken *pvsecret = (struct pvsecrettoken *)key; ++ ++ if (key == NULL || key_size < PVSECRET_KEY_SIZE) ++ return false; ++ ++ if (pvsecret->hdr.type != TOKEN_TYPE_NON_CCA) ++ return false; ++ if (pvsecret->hdr.version != TOKEN_VERSION_PVSECRET) ++ return false; ++ ++ switch (pvsecret->secret_type) { ++ case UV_SECRET_TYPE_AES_128: ++ case UV_SECRET_TYPE_AES_192: ++ case UV_SECRET_TYPE_AES_256: ++ case UV_SECRET_TYPE_AES_XTS_128: ++ case UV_SECRET_TYPE_AES_XTS_256: ++ return true; ++ default: ++ return false; ++ } ++} ++ + /** + * Check if the specified key is an XTS type key + * +@@ -1729,6 +1762,8 @@ bool is_ep11_key_session_bound(const u8 *key, size_t key_size) + */ + bool is_xts_key(const u8 *key, size_t key_size) + { ++ struct pvsecrettoken *pvsecret = (struct pvsecrettoken *)key; ++ + if (is_cca_aes_data_key(key, key_size)) { + if (key_size == 2 * AESDATA_KEY_SIZE && + is_cca_aes_data_key(key + AESDATA_KEY_SIZE, +@@ -1749,11 +1784,41 @@ bool is_xts_key(const u8 *key, size_t key_size) + is_ep11_aes_key_with_header(key + EP11_AES_KEY_SIZE, + key_size - EP11_AES_KEY_SIZE)) + return true; ++ } else if (is_pvsecret_aes_key(key, key_size)) { ++ switch (pvsecret->secret_type) { ++ case UV_SECRET_TYPE_AES_XTS_128: ++ case UV_SECRET_TYPE_AES_XTS_256: ++ return true; ++ default: ++ return false; ++ } + } + + return false; + } + ++/** ++ * Check if the specified key is a secure key type (i.e. requires a crypto card) ++ * ++ * @param[in] key the secure key token ++ * @param[in] key_size the size of the secure key ++ * ++ * @returns true if the key is a secure key type ++ */ ++bool is_secure_key(const u8 *key, size_t key_size) ++{ ++ if (is_cca_aes_data_key(key, key_size)) ++ return true; ++ if (is_cca_aes_cipher_key(key, key_size)) ++ return true; ++ if (is_ep11_aes_key(key, key_size)) ++ return true; ++ if (is_ep11_aes_key_with_header(key, key_size)) ++ return true; ++ ++ return false; ++} ++ + /** + * Gets the size in bits of the effective key of the specified secure key + * +@@ -1771,6 +1836,7 @@ int get_key_bit_size(const u8 *key, size_t key_size, size_t *bitsize) + struct aescipherkeytoken *cipherkey = (struct aescipherkeytoken *)key; + struct ep11keytoken *ep11key = (struct ep11keytoken *)key; + struct ep11kblob_header *hdr = (struct ep11kblob_header *)key; ++ struct pvsecrettoken *pvsecret = (struct pvsecrettoken *)key; + + util_assert(bitsize != NULL, "Internal error: bitsize is NULL"); + +@@ -1805,6 +1871,26 @@ int get_key_bit_size(const u8 *key, size_t key_size, size_t *bitsize) + (key + EP11_AES_KEY_SIZE); + *bitsize += hdr->bitlen; + } ++ } else if (is_pvsecret_aes_key(key, key_size)) { ++ switch (pvsecret->secret_type) { ++ case UV_SECRET_TYPE_AES_128: ++ *bitsize = 128; ++ break; ++ case UV_SECRET_TYPE_AES_192: ++ *bitsize = 192; ++ break; ++ case UV_SECRET_TYPE_AES_256: ++ *bitsize = 256; ++ break; ++ case UV_SECRET_TYPE_AES_XTS_128: ++ *bitsize = 128 * 2; ++ break; ++ case UV_SECRET_TYPE_AES_XTS_256: ++ *bitsize = 256 * 2; ++ break; ++ default: ++ return -EINVAL; ++ } + } else { + return -EINVAL; + } +@@ -1830,9 +1916,31 @@ const char *get_key_type(const u8 *key, size_t key_size) + return KEY_TYPE_EP11_AES; + if (is_ep11_aes_key_with_header(key, key_size)) + return KEY_TYPE_EP11_AES; ++ if (is_pvsecret_aes_key(key, key_size)) ++ return KEY_TYPE_PVSECRET_AES; ++ + return NULL; + } + ++/** ++ * Returns true if the key type is a secure key type ++ * ++ * @param[in] key_type the type of the key ++ * ++ * @returns true if the key type is a secure key type, false otherwise ++ */ ++bool is_secure_key_type(const char *key_type) ++{ ++ if (strcasecmp(key_type, KEY_TYPE_CCA_AESCIPHER) == 0) ++ return true; ++ if (strcasecmp(key_type, KEY_TYPE_CCA_AESDATA) == 0) ++ return true; ++ if (strcasecmp(key_type, KEY_TYPE_EP11_AES) == 0) ++ return true; ++ ++ return false; ++} ++ + /** + * Returns the minimum card level for a specific key type + * +diff --git a/zkey/pkey.h b/zkey/pkey.h +index 3b57c5f0..ce3bd8bd 100644 +--- a/zkey/pkey.h ++++ b/zkey/pkey.h +@@ -4,7 +4,7 @@ + * This header file defines the interface to the pkey kernel module. + * It defines a set of IOCTL commands with its associated structures. + * +- * Copyright IBM Corp. 2017, 2018 ++ * Copyright IBM Corp. 2017, 2024 + * + * s390-tools is free software; you can redistribute it and/or modify + * it under the terms of the MIT license. See LICENSE for details. +@@ -41,6 +41,8 @@ struct tokenheader { + #define TOKEN_VERSION_EP11_AES 0x03 + #define TOKEN_VERSION_EP11_AES_WITH_HEADER 0x06 + #define TOKEN_VERSION_EP11_ECC_WITH_HEADER 0x07 ++/* 0x08 is reserved for internal use */ ++#define TOKEN_VERSION_PVSECRET 0x09 + + struct aesdatakeytoken { + u8 type; /* TOKEN_TYPE_INTERNAL (0x01) for internal key token */ +@@ -116,6 +118,15 @@ struct ep11keytoken { + u8 padding[64]; + } __packed; + ++#define UV_SECRET_ID_LEN 32 ++ ++struct pvsecrettoken { ++ struct tokenheader hdr; ++ u16 secret_type; /* the secret type as the UV told us */ ++ u16 secret_len; /* length in bytes of the secret */ ++ u8 secretid[UV_SECRET_ID_LEN]; /* the secret id for this secret */ ++} __packed; ++ + #define ZERO_SESSION \ + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + +@@ -124,21 +135,26 @@ struct ep11keytoken { + #define EP11_KEY_SIZE sizeof(struct ep11keytoken) + #define EP11_AES_KEY_SIZE (sizeof(struct ep11kblob_header) + \ + sizeof(struct ep11keytoken)) ++#define PVSECRET_KEY_SIZE sizeof(struct pvsecrettoken) + + /* MAX/MIN from zt_common.h produces warnings for variable length arrays */ + #define _MIN(a, b) ((a) < (b) ? (a) : (b)) + #define _MAX(a, b) ((a) > (b) ? (a) : (b)) + + #define MAX_SECURE_KEY_SIZE _MAX( \ +- _MAX(EP11_KEY_SIZE, \ +- EP11_AES_KEY_SIZE), \ +- _MAX(AESDATA_KEY_SIZE, \ +- AESCIPHER_KEY_SIZE)) ++ _MAX( \ ++ _MAX(EP11_KEY_SIZE, \ ++ EP11_AES_KEY_SIZE), \ ++ _MAX(AESDATA_KEY_SIZE, \ ++ AESCIPHER_KEY_SIZE)), \ ++ PVSECRET_KEY_SIZE) + #define MIN_SECURE_KEY_SIZE _MIN( \ +- _MIN(EP11_KEY_SIZE, \ +- EP11_AES_KEY_SIZE), \ +- _MIN(AESDATA_KEY_SIZE, \ +- AESCIPHER_KEY_SIZE)) ++ _MIN( \ ++ _MIN(EP11_KEY_SIZE, \ ++ EP11_AES_KEY_SIZE), \ ++ _MIN(AESDATA_KEY_SIZE, \ ++ AESCIPHER_KEY_SIZE)), \ ++ PVSECRET_KEY_SIZE) + + struct pkey_seckey { + u8 seckey[AESDATA_KEY_SIZE]; /* the secure key blob */ +@@ -285,6 +301,7 @@ struct pkey_apqns4keytype { + #define KEY_TYPE_CCA_AESDATA "CCA-AESDATA" + #define KEY_TYPE_CCA_AESCIPHER "CCA-AESCIPHER" + #define KEY_TYPE_EP11_AES "EP11-AES" ++#define KEY_TYPE_PVSECRET_AES "PVSECRET-AES" + + #define DEFAULT_KEYBITS 256 + #define PAES_BLOCK_SIZE 16 +@@ -342,9 +359,12 @@ bool is_cca_aes_cipher_key(const u8 *key, size_t key_size); + bool is_ep11_aes_key(const u8 *key, size_t key_size); + bool is_ep11_aes_key_with_header(const u8 *key, size_t key_size); + bool is_ep11_key_session_bound(const u8 *key, size_t key_size); ++bool is_pvsecret_aes_key(const u8 *key, size_t key_size); + bool is_xts_key(const u8 *key, size_t key_size); ++bool is_secure_key(const u8 *key, size_t key_size); + int get_key_bit_size(const u8 *key, size_t key_size, size_t *bitsize); + const char *get_key_type(const u8 *key, size_t key_size); ++bool is_secure_key_type(const char *key_type); + int get_min_card_level_for_keytype(const char *key_type); + const struct fw_version *get_min_fw_version_for_keytype(const char *key_type); + enum card_type get_card_type_for_keytype(const char *key_type); +diff --git a/zkey/pvsecrets.h b/zkey/pvsecrets.h +index 6acebfdd..ad844035 100644 +--- a/zkey/pvsecrets.h ++++ b/zkey/pvsecrets.h +@@ -15,6 +15,8 @@ + + #include "lib/zt_common.h" + ++#include "pkey.h" ++ + /* + * Definitions for the /dev/uv kernel module interface + */ +@@ -60,8 +62,6 @@ struct uvio_ioctl_cb { + #define UV_SECRET_TYPE_EDDSA_ED25519 0x14 + #define UV_SECRET_TYPE_EDDSA_ED448 0x15 + +-#define UV_SECRET_ID_LEN 32 +- + #define UVIO_LIST_SECRETS_MAX_LEN 0x8000 + + struct uvio_list_secret_entry { diff --git a/s390-tools-04-zkey-Add-the-pvsecrets-import-command.patch b/s390-tools-04-zkey-Add-the-pvsecrets-import-command.patch new file mode 100644 index 0000000..4b5186c --- /dev/null +++ b/s390-tools-04-zkey-Add-the-pvsecrets-import-command.patch @@ -0,0 +1,784 @@ +From 95bf7eb285f39a8f827cc013393cc69b1265cd68 Mon Sep 17 00:00:00 2001 +From: Ingo Franzki +Date: Thu, 15 Feb 2024 15:14:04 +0100 +Subject: [PATCH] zkey: Add the 'pvsecrets import' command + +The 'pvsecrets import' command imports a protected virtualization secret +into the zkey key repository. Like other key import or key generation +commands, additional information can be associated with the imported key, +such as a textual description, the volume to encrypt with together with +the volume type, the sector size, and a dummy passphrase. You can not +associate a set of APQNs, since a protected virtualization secret does +not need or use a crypto card. + +This command only works when running in a secure execution guest. + +Signed-off-by: Ingo Franzki +Reviewed-by: Jorg Schmidbauer +Signed-off-by: Steffen Eiden +--- + zkey/keystore.c | 112 +++++++++++++++++++++++---------- + zkey/keystore.h | 9 ++- + zkey/pvsecrets.c | 126 +++++++++++++++++++++++++++++++++++++ + zkey/pvsecrets.h | 7 +++ + zkey/zkey.1 | 134 ++++++++++++++++++++++++++++++++++++++++ + zkey/zkey.c | 157 +++++++++++++++++++++++++++++++++++++++++++++++ + 6 files changed, 513 insertions(+), 32 deletions(-) + +diff --git a/zkey/keystore.c b/zkey/keystore.c +index 771bc08df..cde0caf58 100644 +--- a/zkey/keystore.c ++++ b/zkey/keystore.c +@@ -2259,9 +2259,11 @@ int keystore_generate_key_kms(struct keystore *keystore, const char *name, + } + + /** +- * Imports a secure key from a file and adds it to the key store ++ * Imports a secure key from a buffer and adds it to the key store + * + * @param[in] keystore the key store ++ * @param[in] secure_key the buffer containing the key ++ * @param[in] secure_key_size the size of the key + * @param[in] name the name of the key + * @param[in] description textual description of the key (optional, can be NULL) + * @param[in] volumes a comma separated list of volumes associated with this +@@ -2274,7 +2276,6 @@ int keystore_generate_key_kms(struct keystore *keystore, const char *name, + * of two and in range 512 - 4096 bytes. 0 means that + * the sector size is not specified and the system + * default is used. +- * @param[in] import_file The name of a secure key containing the key to import + * @param[in] volume_type the type of volume + * @param[in] gen_passphrase if true, generate a (dummy) passphrase for LUKS2 + * @param[in] passphrase_file the file name of a file containing a passphrase +@@ -2283,25 +2284,23 @@ int keystore_generate_key_kms(struct keystore *keystore, const char *name, + * + * @returns 0 for success or a negative errno in case of an error + */ +-int keystore_import_key(struct keystore *keystore, const char *name, +- const char *description, const char *volumes, +- const char *apqns, bool noapqncheck, size_t sector_size, +- const char *import_file, const char *volume_type, +- bool gen_passphrase, const char *passphrase_file, +- struct ext_lib *lib) ++int keystore_import(struct keystore *keystore, unsigned char *secure_key, ++ size_t secure_key_size, const char *name, ++ const char *description, const char *volumes, ++ const char *apqns, bool noapqncheck, size_t sector_size, ++ const char *volume_type, bool gen_passphrase, ++ const char *passphrase_file, struct ext_lib *lib) + { + struct key_filenames file_names = { 0 }; + struct properties *key_props = NULL; +- size_t secure_key_size; + const char *key_type; + u8 mkvp[MKVP_LENGTH]; + int selected = 1; +- u8 *secure_key; + int rc; + + util_assert(keystore != NULL, "Internal error: keystore is NULL"); + util_assert(name != NULL, "Internal error: name is NULL"); +- util_assert(import_file != NULL, "Internal error: import_file is NULL"); ++ util_assert(secure_key != NULL, "Internal error: secure_key is NULL"); + + rc = _keystore_get_key_filenames(keystore, name, &file_names); + if (rc != 0) +@@ -2311,27 +2310,29 @@ int keystore_import_key(struct keystore *keystore, const char *name, + if (rc != 0) + goto out_free_key_filenames; + +- secure_key = read_secure_key(import_file, &secure_key_size, +- keystore->verbose); +- if (secure_key == NULL) { +- rc = -ENOENT; +- goto out_free_key_filenames; +- } +- + key_type = get_key_type(secure_key, secure_key_size); + if (key_type == NULL) { + warnx("Key '%s' is not a valid secure key", name); +- free(secure_key); + rc = -EINVAL; + goto out_free_key_filenames; + } + ++ if (!is_secure_key(secure_key, secure_key_size)) { ++ if (apqns != NULL) { ++ warnx("No APQNs can be associated with keys of type %s", ++ key_type); ++ rc = -EINVAL; ++ goto out_free_props; ++ } ++ goto write_key; ++ } ++ + rc = get_master_key_verification_pattern(secure_key, secure_key_size, + mkvp, keystore->verbose); + if (rc != 0) { + warnx("Failed to get the master key verification pattern: %s", + strerror(-rc)); +- goto out_free_key; ++ goto out_free_props; + } + + rc = cross_check_apqns(apqns, mkvp, +@@ -2340,17 +2341,17 @@ int keystore_import_key(struct keystore *keystore, const char *name, + get_card_type_for_keytype(key_type), + true, keystore->verbose); + if (rc == -EINVAL) +- goto out_free_key; ++ goto out_free_props; + if (rc != 0 && rc != -ENOTSUP && noapqncheck == 0) { + warnx("Your master key setup is improper"); +- goto out_free_key; ++ goto out_free_props; + } + + if (is_cca_aes_cipher_key(secure_key, secure_key_size)) { + if (lib->cca->lib_csulcca == NULL) { + rc = load_cca_library(lib->cca, keystore->verbose); + if (rc != 0) +- goto out_free_key; ++ goto out_free_props; + } + + rc = select_cca_adapter_by_mkvp(lib->cca, mkvp, apqns, +@@ -2365,7 +2366,7 @@ int keystore_import_key(struct keystore *keystore, const char *name, + warnx("No APQN found that is suitable for " + "working with the secure AES key '%s'", name); + rc = 0; +- goto out_free_key; ++ goto out_free_props; + } + + rc = restrict_key_export(lib->cca, secure_key, secure_key_size, +@@ -2375,7 +2376,7 @@ int keystore_import_key(struct keystore *keystore, const char *name, + "key: %s", strerror(-rc)); + if (!selected) + print_msg_for_cca_envvars("secure AES key"); +- goto out_free_key; ++ goto out_free_props; + } + + rc = check_aes_cipher_key(secure_key, secure_key_size); +@@ -2386,15 +2387,14 @@ int keystore_import_key(struct keystore *keystore, const char *name, + if (!prompt_for_yes(keystore->verbose)) { + warnx("Operation aborted"); + rc = -ECANCELED; +- goto out_free_key; ++ goto out_free_props; + } + } + } + ++write_key: + rc = write_secure_key(file_names.skey_filename, secure_key, + secure_key_size, keystore->verbose); +- free(secure_key); +- secure_key = NULL; + if (rc != 0) + goto out_free_props; + +@@ -2414,9 +2414,6 @@ int keystore_import_key(struct keystore *keystore, const char *name, + "Successfully imported a secure key in '%s' and key info in '%s'", + file_names.skey_filename, file_names.info_filename); + +-out_free_key: +- if (secure_key != NULL) +- free(secure_key); + out_free_props: + if (key_props != NULL) + properties_free(key_props); +@@ -2431,6 +2428,59 @@ int keystore_import_key(struct keystore *keystore, const char *name, + return rc; + } + ++/** ++ * Imports a secure key from a file and adds it to the key store ++ * ++ * @param[in] keystore the key store ++ * @param[in] name the name of the key ++ * @param[in] description textual description of the key (optional, can be NULL) ++ * @param[in] volumes a comma separated list of volumes associated with this ++ * key (optional, can be NULL) ++ * @param[in] apqns a comma separated list of APQNs associated with this ++ * key (optional, can be NULL) ++ * @param[in] noapqncheck if true, the specified APQN(s) are not checked for ++ * existence and type. ++ * @param[in] sector_size the sector size to use with dm-crypt. It must be a ++ * power of two and in range 512 - 4096 bytes. 0 means ++ * that the sector size is not specified and the system ++ * default is used. ++ * @param[in] import_file The name of a secure key containing the key to import ++ * @param[in] volume_type the type of volume ++ * @param[in] gen_passphrase if true, generate a (dummy) passphrase for LUKS2 ++ * @param[in] passphrase_file the file name of a file containing a passphrase ++ * for LUKS2 (optional, can be NULL) ++ * @param[in] lib the external library struct ++ * ++ * @returns 0 for success or a negative errno in case of an error ++ */ ++int keystore_import_key(struct keystore *keystore, const char *name, ++ const char *description, const char *volumes, ++ const char *apqns, bool noapqncheck, size_t sector_size, ++ const char *import_file, const char *volume_type, ++ bool gen_passphrase, const char *passphrase_file, ++ struct ext_lib *lib) ++{ ++ size_t secure_key_size; ++ u8 *secure_key; ++ int rc; ++ ++ util_assert(import_file != NULL, "Internal error: import_file is NULL"); ++ ++ secure_key = read_secure_key(import_file, &secure_key_size, ++ keystore->verbose); ++ if (secure_key == NULL) ++ return -ENOENT; ++ ++ rc = keystore_import(keystore, secure_key, secure_key_size, name, ++ description, volumes, apqns, noapqncheck, ++ sector_size, volume_type, gen_passphrase, ++ passphrase_file, lib); ++ ++ if (secure_key != NULL) ++ free(secure_key); ++ ++ return rc; ++} + + /** + * Changes properties of a key in the keystore. +diff --git a/zkey/keystore.h b/zkey/keystore.h +index 1443b5df4..b4cae9aae 100644 +--- a/zkey/keystore.h ++++ b/zkey/keystore.h +@@ -3,7 +3,7 @@ + * + * Keystore handling functions + * +- * Copyright IBM Corp. 2018, 2020 ++ * Copyright IBM Corp. 2018, 2024 + * + * s390-tools is free software; you can redistribute it and/or modify + * it under the terms of the MIT license. See LICENSE for details. +@@ -65,6 +65,13 @@ int keystore_generate_key_kms(struct keystore *keystore, const char *name, + struct kms_option *kms_options, + size_t num_kms_options); + ++int keystore_import(struct keystore *keystore, unsigned char *secure_key, ++ size_t secure_key_size, const char *name, ++ const char *description, const char *volumes, ++ const char *apqns, bool noapqncheck, size_t sector_size, ++ const char *volume_type, bool gen_passphrase, ++ const char *passphrase_file, struct ext_lib *lib); ++ + int keystore_import_key(struct keystore *keystore, const char *name, + const char *description, const char *volumes, + const char *apqns, bool noapqncheck, size_t sector_size, +diff --git a/zkey/pvsecrets.c b/zkey/pvsecrets.c +index 7f28febad..a4b3a5a83 100644 +--- a/zkey/pvsecrets.c ++++ b/zkey/pvsecrets.c +@@ -552,3 +552,129 @@ int pvsecrets_list(int uv_fd, bool all, bool hex, const char *type_filter, + + return rc; + } ++ ++struct build_secret_key_blob_data { ++ unsigned char id[UV_SECRET_ID_LEN]; ++ char name[UV_SECRET_ID_LEN]; ++ struct pvsecrettoken token; ++ bool found; ++}; ++ ++/** ++ * Callback used to generate a pvsecrets key blob for a specific secret ID. ++ * Called for each secret. ++ * ++ * @param idx the index of the secret ++ * @param type the type of the secret ++ * @param id the ID of the secret ++ * @param cb_private callback private data ++ * ++ * @returns 0 on success, a negative errno in case of an error ++ */ ++static int pvsecrets_build_key_blob_cb(u16 UNUSED(idx), u16 type, u32 len, ++ const u8 id[UV_SECRET_ID_LEN], ++ void *cb_private) ++{ ++ struct build_secret_key_blob_data *build_blob_data = cb_private; ++ ++ if (build_blob_data->found) ++ return 0; ++ ++ if (memcmp(id, build_blob_data->name, UV_SECRET_ID_LEN) != 0 && ++ memcmp(id, build_blob_data->id, UV_SECRET_ID_LEN) != 0) ++ return 0; ++ ++ memset(&build_blob_data->token, 0, sizeof(build_blob_data->token)); ++ build_blob_data->token.hdr.type = TOKEN_TYPE_NON_CCA; ++ build_blob_data->token.hdr.version = TOKEN_VERSION_PVSECRET; ++ build_blob_data->token.secret_type = type; ++ build_blob_data->token.secret_len = len; ++ memcpy(build_blob_data->token.secretid, id, UV_SECRET_ID_LEN); ++ ++ build_blob_data->found = true; ++ ++ return 0; ++} ++ ++/** ++ * Imports a protected virtualization secure key from the UV and adds it to the ++ * key store ++ * ++ * @param keystore the key store ++ * @param uv_fd the file descriptor of the ultravisor device ++ * @param secret_id the secret id as 32 byte hex string. Can be NULL if ++ * secret_name is non-NULL. ++ * @param secret_name the secret name. Can be NULL if secret_id is non-NULL. ++ * @param name the name of the key in the repository ++ * @param description textual description of the key (optional, can be NULL) ++ * @param volumes a comma separated list of volumes associated with this ++ * key (optional, can be NULL) ++ * @param volume_type the type of volume ++ * @param sector_size the sector size to use with dm-crypt. It must be a ++ * power of two and in range 512 - 4096 bytes. 0 means ++ * that the sector size is not specified and the system ++ * default is used. ++ * @param gen_passphrase if true, generate a (dummy) passphrase for LUKS2 ++ * @param passphrase_file the file name of a file containing a passphrase ++ * for LUKS2 (optional, can be NULL) ++ * @param verbose if true, verbose messages are printed ++ * ++ * @returns 0 for success or a negative errno in case of an error ++ */ ++int pvsecrets_import(struct keystore *keystore, int uv_fd, ++ const char *secret_id, const char *secret_name, ++ const char *name, const char *description, ++ const char *volumes, const char *volume_type, ++ long sector_size, bool gen_passphrase, ++ const char *passphrase_file, bool verbose) ++{ ++ struct build_secret_key_blob_data build_blob_data = { 0 }; ++ int rc; ++ ++ util_assert(keystore != NULL, "Internal error: keystore is NULL"); ++ util_assert(uv_fd != -1, "Internal error: uv_fd is -1"); ++ util_assert(secret_id != NULL || secret_name != NULL, ++ "Internal error: secret_id and secrest_name is NULL"); ++ util_assert(name != NULL, "Internal error: name is NULL"); ++ ++ rc = get_secret_id_from_hex_or_name(secret_id, secret_name, ++ build_blob_data.id, ++ build_blob_data.name); ++ if (rc < 0) ++ return rc; ++ if (rc > 0) ++ return -EINVAL; ++ ++ rc = uv_list_secrets(uv_fd, pvsecrets_build_key_blob_cb, ++ &build_blob_data, verbose); ++ if (rc != 0) { ++ warnx("Failed to import the pvsecret with %s '%s': %s", ++ secret_id != NULL ? "id" : "name", ++ secret_id != NULL ? secret_id : secret_name, ++ strerror(-rc)); ++ return rc; ++ } ++ ++ if (!build_blob_data.found) { ++ warnx("The pvsecret with %s '%s' does not exist", ++ secret_id != NULL ? "id" : "name", ++ secret_id != NULL ? secret_id : secret_name); ++ return -ENOENT; ++ } ++ ++ if (!is_pvsecret_type_supported(build_blob_data.token.secret_type)) { ++ warnx("The type of the pvsecret with %s '%s' is not supported " ++ "by zkey: %s", secret_id != NULL ? "id" : "name", ++ secret_id != NULL ? secret_id : secret_name, ++ get_pvsecret_type_name( ++ build_blob_data.token.secret_type)); ++ return -EINVAL; ++ } ++ ++ rc = keystore_import(keystore, (unsigned char *)&build_blob_data.token, ++ sizeof(build_blob_data.token), name, description, ++ volumes, NULL, false, sector_size, volume_type, ++ gen_passphrase, passphrase_file, NULL); ++ ++ return rc; ++} +diff --git a/zkey/pvsecrets.h b/zkey/pvsecrets.h +index ad8440350..9503c5155 100644 +--- a/zkey/pvsecrets.h ++++ b/zkey/pvsecrets.h +@@ -16,6 +16,7 @@ + #include "lib/zt_common.h" + + #include "pkey.h" ++#include "keystore.h" + + /* + * Definitions for the /dev/uv kernel module interface +@@ -89,5 +90,11 @@ int uv_open_device(bool verbose); + int pvsecrets_list(int uv_fd, bool all, bool hex, const char *type_filter, + const char *secret_id, const char *secret_name, + bool verbose); ++int pvsecrets_import(struct keystore *keystore, int uv_fd, ++ const char *secret_id, const char *secret_name, ++ const char *name, const char *description, ++ const char *volumes, const char *volume_type, ++ long sector_size, bool gen_passphrase, ++ const char *passphrase_file, bool verbose); + + #endif +diff --git a/zkey/zkey.1 b/zkey/zkey.1 +index 4386629f9..ba71a839b 100644 +--- a/zkey/zkey.1 ++++ b/zkey/zkey.1 +@@ -1208,6 +1208,64 @@ are supported by the \fBzkey\fP tool. To list all pvsecret types, specify the + This command is only available when running in a secure execution guest. + Only the \fBroot\fP user is allowed to perform this command. + . ++.SS "Import protected virtualization secrets into the repository" ++. ++.B zkey pvsecrets ++.BR import | im ++.RB [ \-\-pvsecret\-id | \-I ++.IR pvsecret\-id ] ++.RB [ \-\-pvsecret\-name | \-e ++.IR pvsecret\-name ] ++.B \-\-name | \-N ++.IR key\-name ++.RB [ \-\-description | \-d ++.IR description ] ++.RB [ \-\-volumes | \-l ++.IR volume1:dmname1[,volume2:dmname2[,...]] ] ++.RB [ \-\-sector\-size | \-S ++.IR bytes ] ++.RB [ \-\-volume\-type | \-t ++.IR type ] ++.RB [ \-\-gen\-dummy\-passphrase ] ++.RB [ \-\-set\-dummy\-passphrase ++.IR passphrase\-file ] ++.RB [ \-\-verbose | \-V ] ++. ++.PP ++Use the ++.B pvsecrets import ++command to import a protected virtualization (PV) secret into the repository. ++Either the \fB\-\-pvsecret\-id\fP option or the \fB\-\-pvsecret\-name\fP ++option can be specified. If the pvsecret name is specified, then the ++\fB\-\-name\fP option can be omitted. The name of the protected virtualization ++secret key object in the repository will then be the same as the pvsecret name. ++.PP ++You can use the YAML file that was created when using the \fBpvsecret create\fP ++command for adding the protected virtualization secret: ++\fB\-\-pvsecret\-id "$(yq .id \fP\fIYAML\-FILE\fP\fB)"\fP or ++\fB\-\-pvsecret\-name "$(yq .name \fP\fIYAML\-FILE\fP\fB)"\fP. ++You might have to install the \fByq\fP package first. ++.PP ++A protected virtualization secret key object does not contain the key material, ++but only a reference (i.e. the secret ID) to the key in the ultravisor. ++When such a protected virtualization secret key object is used with ++\fBdm\-crypt\fP and the \fBPAES\fP kernel cipher, the key material (i.e. a ++protected key) is retrieved from the ultravisor and the crypto operation is ++performed with it. ++.PP ++When importing a protected virtualization secret in a key repository, ++additional information can be associated with it using the ++.B \-\-description ++, ++.B \-\-volumes ++, or the ++.B \-\-sector\-size ++options. APQNs can not be associated, because protected virtualization secrets ++do not require a crypto card. ++.PP ++This command is only available when running in a secure execution guest. ++Only the \fBroot\fP user is allowed to perform this command. ++. + . + . + . +@@ -2117,6 +2175,82 @@ can be specified, but not both. + . + . + . ++.SS "Options for the pvsecrets import command" ++.TP ++.BR \-I ", " \-\-pvsecret\-id\~\fIpvsecret\-id\fP ++ID of the protected virtualization (PV) secret to import. The pvsecret ID is a ++32 byte hex string, optionally prefixed by \fB0x\fP. You can use the YAML file ++that was created when using the \fBpvsecret create\fP command for adding the ++protected virtualization secret: ++\fB\-\-pvsecret\-id "$(yq .id \fP\fIYAML\-FILE\fP\fB)"\fP. ++You might have to install the \fByq\fP package first. ++Either the \fB\-\-pvsecret\-id\fP option or the \fB\-\-pvsecret\-name\fP option ++can be specified, but not both. ++.TP ++.BR \-e ", " \-\-pvsecret\-name\~\fIpvsecret\-name\fP ++Name of the protected virtualization (PV) secret to import. You can use the YAML ++file that was created when using the \fBpvsecret create\fP command for adding ++the protected virtualization secret: ++\fB\-\-pvsecret\-name "$(yq .name \fP\fIYAML\-FILE\fP\fB)"\fP. ++You might have to install the \fByq\fP package first. ++Either the \fB\-\-pvsecret\-id\fP option or the \fB\-\-pvsecret\-name\fP option ++can be specified, but not both. If the \fB\-\-pvsecret\-name\fP option is ++specified, then the \fB\-\-name\fP option can be omitted. The name of the ++protected virtualization secret key object in the repository will then be the ++same as the pvsecret name. ++.TP ++.BR \-N ", " \-\-name\~\fIkey\-name\fP ++Specifies the name of the protected virtualization secret key object in the key ++repository. If the \fB\-\-pvsecret\-name\fP option is specified, then the ++\fB\-\-name\fP option can be omitted. The name of the protected virtualization ++secret in the repository will then be the same as the pvsecret name. ++.TP ++.BR \-d ", " \-\-description\~\fIdescription\fP ++Specifies a textual description for the protected virtualization secret in the ++key repository. ++.TP ++.BR \-l ", " \-\-volumes\~\fIvolume1:dmname1[,volume2:dmname2[,...]]\fP ++Specifies a comma-separated list of volumes (block devices) which are ++associated with the protected virtualization secret in the repository. These ++volumes are to be encrypted using \fBdm\-crypt\fP with the protected ++virtualization secret. The volume association also contains the device-mapper ++name, separated by a colon, used with \fBdm\-crypt\fP. A specific volume can ++only be associated with a single key. ++.TP ++.BR \-S ", " \-\-sector\-size\~\fIbytes\fP ++Specifies the sector size in bytes used with \fBdm\-crypt\fP. It must be a power ++of two and in the range of 512 to 4096 bytes. If omitted, the system default ++sector size is used. ++.TP ++.BR \-t ", " \-\-volume\-type\~\fItype\fP ++Specifies the volume type of the associated volumes used with \fBdm\-crypt\fP. ++Possible values are \fBplain\fP and \fBluks2\fP. If omitted, \fBluks2\fP is ++used. This option is only available if ++.B zkey ++has been compiled with LUKS2 support enabled. If LUKS2 support is not enabled, ++the default volume type is \fBplain\fP. ++.TP ++.BR \-\-gen\-dummy\-passphrase ++Generate a dummy passphrase randomly and associate it with the protected ++virtualization secret used to encrypt LUKS2 volume(s). The LUKS2 passphrase is ++of less or no relevance for the security of the volume(s), when a protected ++virtualization secret is used to encrypt the volume(s), and can therefore be ++stored insecurely inside the key repository. If for a certain usage the ++passphrase is of relevance for security, then do not use this option. This ++option can only be specified for keys with a volume type of \fBluks2\fP. ++.TP ++.BR \-\-set\-dummy\-passphrase\~\fIpassphrase\-file\fP ++Set a dummy passphrase that is read from the specified file and associate it ++with the protected virtualization secret used to encrypt LUKS2 volume(s). ++The LUKS2 passphrase is of less or no relevance for the security of the ++volume(s), when an protected virtualization secret is used to encrypt the ++volume(s), and can therefore be stored insecurely inside the key repository. ++If for a certain usage the passphrase is of relevance for security, then do ++not use this option. This option can only be specified for keys with a volume ++type of \fBluks2\fP. ++. ++. ++. + .SS "General options" + .TP + .BR \-V ", " \-\-verbose +diff --git a/zkey/zkey.c b/zkey/zkey.c +index adc48d60b..6e9b32af5 100644 +--- a/zkey/zkey.c ++++ b/zkey/zkey.c +@@ -145,6 +145,7 @@ static struct zkey_globals { + #define COMMAND_KMS_REFRESH "refresh" + #define COMMAND_PVSECRETS "pvsecrets" + #define COMMAND_PVSECRETS_LIST "list" ++#define COMMAND_PVSECRETS_IMPORT "import" + + #define OPT_COMMAND_PLACEHOLDER "PLACEHOLDER" + +@@ -1234,6 +1235,103 @@ static struct util_opt opt_vec[] = { + .command = COMMAND_PVSECRETS " " COMMAND_PVSECRETS_LIST, + }, + /***********************************************************/ ++ { ++ .flags = UTIL_OPT_FLAG_SECTION, ++ .desc = "OPTIONS", ++ .command = COMMAND_PVSECRETS " " COMMAND_PVSECRETS_IMPORT, ++ }, ++ { ++ .option = { "pvsecret-id", required_argument, NULL, 'I'}, ++ .argument = "PVSECRET-ID", ++ .desc = "ID of the protected virtualization (PV) secret to " ++ "import. Either '--pvsecret-id/-I' or " ++ "'--pvsecret-name/-e' can be specified, but not both.", ++ .command = COMMAND_PVSECRETS " " COMMAND_PVSECRETS_IMPORT, ++ }, ++ { ++ .option = { "pvsecret-name", required_argument, NULL, 'e'}, ++ .argument = "PVSECRET-NAME", ++ .desc = "Name of the protected virtualization (PV) secret to " ++ "import. Either '--pvsecret-name/-e' or " ++ "'--pvsecret-id/-I' can be specified, but not both. " ++ "If the '--pvsecret-name/-e' option is specified, but " ++ "the '--name/-N' option is omitted, then the imported " ++ "protected virtualisation secret will be named the " ++ "same as the pvsecret name.", ++ .command = COMMAND_PVSECRETS " " COMMAND_PVSECRETS_IMPORT, ++ }, ++ { ++ .option = { "name", required_argument, NULL, 'N'}, ++ .argument = "NAME", ++ .desc = "Name of the imported protected virtualisation secret " ++ "in the repository. If the '--name/-N' option is " ++ "omitted, but the '--pvsecret-name/-e' is specified, " ++ "then the imported protected virtualisation secret " ++ "will be named the same as the pvsecret name.", ++ .command = COMMAND_PVSECRETS " " COMMAND_PVSECRETS_IMPORT, ++ }, ++ { ++ .option = { "description", required_argument, NULL, 'd'}, ++ .argument = "DESCRIPTION", ++ .desc = "Textual description of the protected virtualisation " ++ "secret in the repository", ++ .command = COMMAND_PVSECRETS " " COMMAND_PVSECRETS_IMPORT, ++ }, ++ { ++ .option = { "volumes", required_argument, NULL, 'l'}, ++ .argument = "VOLUME:DMNAME[,...]", ++ .desc = "Comma-separated pairs of volume and device-mapper " ++ "names that are associated with the protected " ++ "virtualisation secret in the repository", ++ .command = COMMAND_PVSECRETS " " COMMAND_PVSECRETS_IMPORT, ++ }, ++ { ++ .option = { "sector-size", required_argument, NULL, 'S'}, ++ .argument = "512|4096", ++ .desc = "The sector size used with dm-crypt. It must be a power " ++ "of two and in range 512 - 4096 bytes. If this option " ++ "is omitted, the system default sector size (512) is " ++ "used", ++ .command = COMMAND_PVSECRETS " " COMMAND_PVSECRETS_IMPORT, ++ }, ++#ifdef HAVE_LUKS2_SUPPORT ++ { ++ .option = { "volume-type", required_argument, NULL, 't'}, ++ .argument = "type", ++ .desc = "The type of the associated volume(s). Possible values " ++ "are 'plain' and 'luks2'. When this option is omitted, " ++ "the default is 'luks2'", ++ .command = COMMAND_PVSECRETS " " COMMAND_PVSECRETS_IMPORT, ++ }, ++#endif ++ { ++ .option = { "gen-dummy-passphrase", 0, NULL, ++ OPT_GEN_DUMMY_PASSPHRASE}, ++ .desc = "Generate a dummy passphrase and associate it with the " ++ "protected virtualisation secret used to encrypt LUKS2 " ++ "volume(s). The LUKS2 passphrase is of less or no " ++ "relevance for the security of the volume(s), when an " ++ "protected virtualisation secret is used to encrypt " ++ "the volume(s), and can therefore be stored insecurely " ++ "inside the secure key repository.", ++ .flags = UTIL_OPT_FLAG_NOSHORT, ++ .command = COMMAND_PVSECRETS " " COMMAND_PVSECRETS_IMPORT, ++ }, ++ { ++ .option = { "set-dummy-passphrase", required_argument, NULL, ++ OPT_SET_DUMMY_PASSPHRASE}, ++ .argument = "passphrase-file", ++ .desc = "Set a dummy passphrase to be associated with the " ++ "protected virtualisation secret used to encrypt LUKS2 " ++ "volume(s). The LUKS2 passphrase is of less or no " ++ "relevance for the security of the volume(s), when a " ++ "protected virtualisation secret is used to encrypt " ++ "the volume(s), and can therefore be stored insecurely " ++ "inside the secure key repository.", ++ .flags = UTIL_OPT_FLAG_NOSHORT, ++ .command = COMMAND_PVSECRETS " " COMMAND_PVSECRETS_IMPORT, ++ }, ++ /***********************************************************/ + OPT_PLACEHOLDER, + OPT_PLACEHOLDER, + OPT_PLACEHOLDER, +@@ -1339,6 +1437,7 @@ static int command_kms_list(void); + static int command_kms_import(void); + static int command_kms_refresh(void); + static int command_pvsecrets_list(void); ++static int command_pvsecrets_import(void); + + static struct zkey_command zkey_kms_commands[] = { + { +@@ -1469,6 +1568,19 @@ static struct zkey_command zkey_pvsecrets_commands[] = { + .has_options = 1, + .need_uv_device = 1, + }, ++ { ++ .command = COMMAND_PVSECRETS_IMPORT, ++ .abbrev_len = 2, ++ .function = command_pvsecrets_import, ++ .short_desc = "Imports a protected virtualization (PV) secret", ++ .long_desc = "Imports a protected virtualization (PV) secret " ++ "into the repository. This command is only " ++ "available when running in a secure execution " ++ "guest.", ++ .has_options = 1, ++ .need_keystore = 1, ++ .need_uv_device = 1, ++ }, + { .command = NULL } + }; + +@@ -3006,6 +3118,51 @@ static int command_pvsecrets_list(void) + return rc != 0 ? EXIT_FAILURE : EXIT_SUCCESS; + } + ++/* ++ * Command handler for 'pvsecrets import'. ++ * ++ * Import a protected virtualization secret into the repository ++ */ ++static int command_pvsecrets_import(void) ++{ ++ int rc; ++ ++ if (g.secret_id == NULL && g.secret_name == NULL) { ++ misc_print_required_parm("--pvsecret-id/-I or " ++ "--pvsecret-name/-e"); ++ return EXIT_FAILURE; ++ } ++ if (g.secret_id != NULL && g.secret_name != NULL) { ++ warnx("Either '--pvsecret-id/-I' or '--pvsecret-name/-e' can " ++ "be specified, but not both"); ++ util_prg_print_parse_error(); ++ return EXIT_FAILURE; ++ } ++ if (g.secret_name == NULL && g.name == NULL) { ++ misc_print_required_parm("--name/-N"); ++ return EXIT_FAILURE; ++ } ++ ++ if (g.sector_size < 0) ++ g.sector_size = 0; ++ ++ if (g.gen_passphrase && g.passphrase_file != NULL) { ++ warnx("Either '--gen-dummy-passphrase' or " ++ "'--set-dummy-passphrase' can be specified, but not " ++ "both"); ++ util_prg_print_parse_error(); ++ return EXIT_FAILURE; ++ } ++ ++ rc = pvsecrets_import(g.keystore, g.uv_fd, g.secret_id, g.secret_name, ++ g.name != NULL ? g.name : g.secret_name, ++ g.description, g.volumes, g.volume_type, ++ g.sector_size, g.gen_passphrase, ++ g.passphrase_file, g.verbose); ++ ++ return rc != 0 ? EXIT_FAILURE : EXIT_SUCCESS; ++} ++ + /** + * Opens the keystore. The keystore directory is either the + * default directory or as specified in an environment variable diff --git a/s390-tools-05-zkey-Reject-key-generation-and-APQN-association-for-PVSECRET-AES-keys.patch b/s390-tools-05-zkey-Reject-key-generation-and-APQN-association-for-PVSECRET-AES-keys.patch new file mode 100644 index 0000000..42da0b9 --- /dev/null +++ b/s390-tools-05-zkey-Reject-key-generation-and-APQN-association-for-PVSECRET-AES-keys.patch @@ -0,0 +1,137 @@ +From 5276d408fd10669b3d8e623455778a675e8dc149 Mon Sep 17 00:00:00 2001 +From: Ingo Franzki +Date: Mon, 19 Feb 2024 10:21:06 +0100 +Subject: [PATCH] zkey: Reject key generation and APQN association for + PVSECRET-AES keys + +Keys of type PVSECRET-AES can not be generated using 'zkey generate'. +Furthermore, APQNs can not be associated with keys of type PVSECRET-AES +via 'zkey change'. Reject that with a proper error message. + +Signed-off-by: Ingo Franzki +Reviewed-by: Jorg Schmidbauer +Signed-off-by: Steffen Eiden +--- + zkey/keystore.c | 32 +++++++++++++++++++++++--------- + zkey/zkey.1 | 7 +++++++ + zkey/zkey.c | 5 +++++ + 3 files changed, 35 insertions(+), 9 deletions(-) + +diff --git a/zkey/keystore.c b/zkey/keystore.c +index cde0caf5..db62e0a6 100644 +--- a/zkey/keystore.c ++++ b/zkey/keystore.c +@@ -2009,6 +2009,12 @@ int keystore_generate_key(struct keystore *keystore, const char *name, + return -EINVAL; + } + ++ if (!is_secure_key_type(key_type)) { ++ warnx("Keys of type %s can not be generated. Use 'zkey " ++ "pvsecret import' instead", key_type); ++ return -EINVAL; ++ } ++ + rc = _keystore_get_key_filenames(keystore, name, &file_names); + if (rc != 0) + goto out_free_key_filenames; +@@ -2535,9 +2541,9 @@ int keystore_change_key(struct keystore *keystore, const char *name, + const char *null_ptr = NULL; + char *upd_volumes = NULL; + size_t secure_key_size; ++ u8 *secure_key = NULL; + u8 mkvp[MKVP_LENGTH]; + char sect_size[30]; +- u8 *secure_key; + bool kms_bound; + int rc; + +@@ -2589,13 +2595,6 @@ int keystore_change_key(struct keystore *keystore, const char *name, + goto out; + } + +- rc = _keystore_change_association(key_props, PROP_NAME_APQNS, +- apqns, "APQN", +- _keystore_apqn_check, +- &apqn_check); +- if (rc != 0) +- goto out; +- + secure_key = read_secure_key(file_names.skey_filename, + &secure_key_size, + keystore->verbose); +@@ -2604,11 +2603,24 @@ int keystore_change_key(struct keystore *keystore, const char *name, + goto out; + } + ++ if (!is_secure_key(secure_key, secure_key_size)) { ++ warnx("No APQNs can be associated with keys of type %s", ++ get_key_type(secure_key, secure_key_size)); ++ rc = -EINVAL; ++ goto out; ++ } ++ ++ rc = _keystore_change_association(key_props, PROP_NAME_APQNS, ++ apqns, "APQN", ++ _keystore_apqn_check, ++ &apqn_check); ++ if (rc != 0) ++ goto out; ++ + rc = get_master_key_verification_pattern(secure_key, + secure_key_size, + mkvp, + keystore->verbose); +- free(secure_key); + if (rc) + goto out; + +@@ -2742,6 +2754,8 @@ int keystore_change_key(struct keystore *keystore, const char *name, + free(upd_volumes); + if (upd_volume_type != NULL) + free(upd_volume_type); ++ if (secure_key != NULL) ++ free(secure_key); + + if (rc != 0) + pr_verbose(keystore, "Failed to change key '%s': %s", +diff --git a/zkey/zkey.1 b/zkey/zkey.1 +index ba71a839..baaf8478 100644 +--- a/zkey/zkey.1 ++++ b/zkey/zkey.1 +@@ -402,6 +402,9 @@ additional information can be associated with a secure key using the + .B \-\-sector\-size + options. + .PP ++Keys of type \fBPVSECRET\-AES\fP do not use a cryptographic adapter, thus APQNs ++can not be associated with them. ++.PP + .B Note: + The \fBimport\fP command requires the CCA host library (libcsulcca.so) + to be installed when secure keys of type \fBCCA\-AESCIPHER\fP are imported. +@@ -564,6 +567,10 @@ APQNs that are associated with the key management system plugin. + Other associated information is also changed in the key management system when + changed using the change command. + .PP ++For keys of type \fBPVSECRET\-AES\fP you can not change or set the APQN ++association. These keys do not use a cryptographic adapter, thus APQNs can not ++be associated with them. ++.PP + .B Note: + The secure key itself cannot be changed, only information about the secure + key is changed. To rename a secure key, use the \fBrename\fP command. +diff --git a/zkey/zkey.c b/zkey/zkey.c +index 6e9b32af..36bdbcc0 100644 +--- a/zkey/zkey.c ++++ b/zkey/zkey.c +@@ -2001,6 +2001,11 @@ static int command_generate(void) + return command_generate_repository(); + if (g.key_type == NULL) + g.key_type = KEY_TYPE_CCA_AESDATA; ++ if (!is_secure_key_type(g.key_type)) { ++ warnx("Keys of type '%s' can not be generated. Use 'zkey " ++ "pvsecret import' instead", g.key_type); ++ return -EXIT_FAILURE; ++ } + if (g.pos_arg != NULL) { + if (g.volumes != NULL) { + warnx("Option '--volumes|-l' is not valid for " diff --git a/s390-tools-06-zkey-Reject-re-enciphering-of-PVSECRET-AES-keys.patch b/s390-tools-06-zkey-Reject-re-enciphering-of-PVSECRET-AES-keys.patch new file mode 100644 index 0000000..116e03a --- /dev/null +++ b/s390-tools-06-zkey-Reject-re-enciphering-of-PVSECRET-AES-keys.patch @@ -0,0 +1,145 @@ +From a8eb2bd4e7e74445c953906b33d450c2ace5223f Mon Sep 17 00:00:00 2001 +From: Ingo Franzki +Date: Mon, 19 Feb 2024 11:26:41 +0100 +Subject: [PATCH] zkey: Reject re-enciphering of PVSECRET-AES keys + +Keys of type PVSECRET-AES can not be reenciphered using 'zkey reencipher' +or 'zkey-cryptsetup reencipher'. Reject that with a proper error message. + +Signed-off-by: Ingo Franzki +Reviewed-by: Jorg Schmidbauer +Signed-off-by: Steffen Eiden +--- + zkey/keystore.c | 9 +++++++++ + zkey/zkey-cryptsetup.1 | 8 ++++++-- + zkey/zkey-cryptsetup.c | 16 ++++++++++++---- + zkey/zkey.1 | 4 ++++ + zkey/zkey.c | 7 +++++++ + 5 files changed, 38 insertions(+), 6 deletions(-) + +diff --git a/zkey/keystore.c b/zkey/keystore.c +index db62e0a6..4f795a28 100644 +--- a/zkey/keystore.c ++++ b/zkey/keystore.c +@@ -3567,6 +3567,15 @@ static int _keystore_process_reencipher(struct keystore *keystore, + goto out; + } + ++ if (!is_secure_key(secure_key, secure_key_size)) { ++ warnx("Key '%s' is of type %s and can not be re-enciphered, " ++ "skipping", name, get_key_type(secure_key, ++ secure_key_size)); ++ info->num_skipped++; ++ rc = 0; ++ goto out; ++ } ++ + apqns = properties_get(properties, PROP_NAME_APQNS); + if (apqns != NULL) + apqn_list = str_list_split(apqns); +diff --git a/zkey/zkey-cryptsetup.1 b/zkey/zkey-cryptsetup.1 +index c455f845..185edab9 100644 +--- a/zkey/zkey-cryptsetup.1 ++++ b/zkey/zkey-cryptsetup.1 +@@ -1,8 +1,8 @@ +-.\" Copyright IBM Corp. 2018 ++.\" Copyright IBM Corp. 2018, 2024 + .\" s390-tools is free software; you can redistribute it and/or modify + .\" it under the terms of the MIT license. See LICENSE for details. + .\" +-.TH ZKEY\-CRYPTSETUP 1 "May 2018" "s390-tools" ++.TH ZKEY\-CRYPTSETUP 1 "February 2024" "s390-tools" + .SH NAME + zkey\-cryptsetup \- Manage secure AES volume keys of volumes encrypted with + \fBLUKS2\fP and the \fBpaes\fP cipher +@@ -115,6 +115,10 @@ command to re-encipher a secure AES volume key of a volume encrypted with + re-enciphered when the master key of the cryptographic adapter in CCA or EP11 + coprocessor mode changes. + .PP ++Volume keys of type \fBPVSECRET\-AES\fP can not be re-enciphered. These keys do ++not use a cryptographic adapter, thus they do not need to be re-enciphered when ++the master key of a cryptographic adapter changes. ++.PP + The cryptographic adapter in CCA coprocessor mode has three different registers + to store master keys: + .RS 2 +diff --git a/zkey/zkey-cryptsetup.c b/zkey/zkey-cryptsetup.c +index 8b55f7d1..2b018a2a 100644 +--- a/zkey/zkey-cryptsetup.c ++++ b/zkey/zkey-cryptsetup.c +@@ -2,7 +2,7 @@ + * zkey-cryptsetup - Re-encipher or validate volume keys of volumes + * encrypted with LUKS2 and the paes cipher. + * +- * Copyright IBM Corp. 2018 ++ * Copyright IBM Corp. 2018, 2024 + * + * s390-tools is free software; you can redistribute it and/or modify + * it under the terms of the MIT license. See LICENSE for details. +@@ -82,7 +82,7 @@ static const struct util_prg prg = { + { + .owner = "IBM Corp.", + .pub_first = 2018, +- .pub_last = 2018, ++ .pub_last = 2024, + }, + UTIL_PRG_COPYRIGHT_END + } +@@ -1609,14 +1609,22 @@ static int reencipher_prepare(int token) + if (rc < 0) + goto out; + ++ securekeysize = keysize - integrity_keysize; ++ ++ if (!is_secure_key((u8 *)key, securekeysize)) { ++ warnx("The volume key of device '%s' is of type %s and can " ++ "not be re-enciphered", g.pos_arg, ++ get_key_type((u8 *)key, securekeysize)); ++ rc = -EINVAL; ++ goto out; ++ } ++ + reenc_tok.original_keyslot = rc; + + rc = ensure_is_active_keylot(reenc_tok.original_keyslot); + if (rc != 0) + goto out; + +- securekeysize = keysize - integrity_keysize; +- + rc = generate_key_verification_pattern((u8 *)key, securekeysize, + reenc_tok.verification_pattern, + sizeof(reenc_tok.verification_pattern), +diff --git a/zkey/zkey.1 b/zkey/zkey.1 +index baaf8478..316db5f0 100644 +--- a/zkey/zkey.1 ++++ b/zkey/zkey.1 +@@ -266,6 +266,10 @@ command to re-encipher an existing secure key with a new master key. + A secure key must be re-enciphered when the master key of the CCA or EP11 + cryptographic adapter changes. + .PP ++Keys of type \fBPVSECRET\-AES\fP can not be re-enciphered. These keys do not ++use a cryptographic adapter, thus they do not need to be re-enciphered when the ++master of a cryptographic adapter changes. ++.PP + The CCA cryptographic adapter has three different registers to store + master keys: + .RS 2 +diff --git a/zkey/zkey.c b/zkey/zkey.c +index 36bdbcc0..90b46106 100644 +--- a/zkey/zkey.c ++++ b/zkey/zkey.c +@@ -2118,6 +2118,13 @@ static int command_reencipher_file(void) + if (secure_key == NULL) + return EXIT_FAILURE; + ++ if (!is_secure_key(secure_key, secure_key_size)) { ++ warnx("A key of type %s can not be re-enciphered", ++ get_key_type(secure_key, secure_key_size)); ++ rc = EXIT_FAILURE; ++ goto out; ++ } ++ + rc = validate_secure_key(g.pkey_fd, secure_key, secure_key_size, NULL, + &is_old_mk, NULL, g.verbose); + if (rc != 0) { diff --git a/s390-tools-07-zkey-Support-validation-of-key-of-type-PVSECRET-AES.patch b/s390-tools-07-zkey-Support-validation-of-key-of-type-PVSECRET-AES.patch new file mode 100644 index 0000000..46bfd6b --- /dev/null +++ b/s390-tools-07-zkey-Support-validation-of-key-of-type-PVSECRET-AES.patch @@ -0,0 +1,407 @@ +From 833a8e7309ebf0ce70f2ee989ced5f87d6c3550b Mon Sep 17 00:00:00 2001 +From: Ingo Franzki +Date: Mon, 19 Feb 2024 10:25:54 +0100 +Subject: [PATCH] zkey: Support validation of key of type PVSECRET-AES + +Keys of type PVSECRET-AES can also be verified via the pkey IOCTL +PKEY_VERIFYKEY2, but the card and domain fields must be zero, because such +a key does not use a crypto card. Also XTS keys of type PVSRCRET-AES are +not represented by 2 concatenated keys but by just one key of type +PVSECRET-AES. Thus, special handling is required for XTS keys. + +Signed-off-by: Ingo Franzki +Reviewed-by: Jorg Schmidbauer +Signed-off-by: Steffen Eiden +--- + zkey/keystore.c | 50 ++++++++++++++++++++++-------------- + zkey/pkey.c | 46 +++++++++++++++++++++------------- + zkey/zkey-cryptsetup.1 | 4 ++- + zkey/zkey-cryptsetup.c | 45 +++++++++++++++++++-------------- + zkey/zkey.1 | 8 +++--- + zkey/zkey.c | 57 ++++++++++++++++++++++++------------------ + 6 files changed, 126 insertions(+), 84 deletions(-) + +diff --git a/zkey/keystore.c b/zkey/keystore.c +index 4f795a28..58f27df2 100644 +--- a/zkey/keystore.c ++++ b/zkey/keystore.c +@@ -3055,19 +3055,25 @@ static void _keystore_print_record(struct util_rec *rec, + util_rec_set(rec, REC_XTS, is_xts ? "Yes" : "No"); + util_rec_set(rec, REC_KEY_TYPE, key_type); + if (validation) { +- if (valid) +- util_rec_set(rec, REC_MASTERKEY, +- "%s master key (MKVP: %s)", +- is_old_mk ? "OLD" : "CURRENT", +- printable_mkvp( +- get_card_type_for_keytype(key_type), +- mkvp)); +- else +- util_rec_set(rec, REC_MASTERKEY, +- "(unknown, MKVP: %s)", +- printable_mkvp( +- get_card_type_for_keytype(key_type), +- mkvp)); ++ if (mkvp != NULL) { ++ if (valid) ++ util_rec_set(rec, REC_MASTERKEY, ++ "%s master key (MKVP: %s)", ++ is_old_mk ? "OLD" : "CURRENT", ++ printable_mkvp( ++ get_card_type_for_keytype( ++ key_type), ++ mkvp)); ++ else ++ util_rec_set(rec, REC_MASTERKEY, ++ "(unknown, MKVP: %s)", ++ printable_mkvp( ++ get_card_type_for_keytype( ++ key_type), ++ mkvp)); ++ } else { ++ util_rec_set(rec, REC_MASTERKEY, "(none)"); ++ } + } + if (volumes_argz != NULL) + util_rec_set_argz(rec, REC_VOLUMES, volumes_argz, +@@ -3294,17 +3300,22 @@ static int _keystore_process_validate(struct keystore *keystore, + valid = 1; + } + +- rc = get_master_key_verification_pattern(secure_key, secure_key_size, +- mkvp, keystore->verbose); +- if (rc != 0) +- goto out; ++ if (is_secure_key(secure_key, secure_key_size)) { ++ rc = get_master_key_verification_pattern(secure_key, ++ secure_key_size, ++ mkvp, ++ keystore->verbose); ++ if (rc != 0) ++ goto out; ++ } + + _keystore_print_record(info->rec, name, properties, 1, + file_names->skey_filename, secure_key_size, + is_xts_key(secure_key, secure_key_size), + clear_key_bitsize, valid, is_old_mk, + _keystore_reencipher_key_exists(file_names), +- mkvp, ++ is_secure_key(secure_key, secure_key_size) ? ++ mkvp : NULL, + _keystore_passphrase_file_exists(file_names) ? + file_names->pass_filename : NULL); + +@@ -3316,7 +3327,8 @@ static int _keystore_process_validate(struct keystore *keystore, + "master key\n", 0); + info->num_warnings++; + } +- if (info->noapqncheck == 0) ++ if (info->noapqncheck == 0 && ++ is_secure_key(secure_key, secure_key_size)) + if (_keystore_display_apqn_status(keystore, properties, + mkvp) != 0) + info->num_warnings++; +diff --git a/zkey/pkey.c b/zkey/pkey.c +index 53c0a550..25deb05a 100644 +--- a/zkey/pkey.c ++++ b/zkey/pkey.c +@@ -1287,33 +1287,43 @@ int validate_secure_key(int pkey_fd, + { + struct pkey_verifykey2 verifykey2; + struct pkey_apqn *list = NULL; ++ bool xts, valid, securekey; + u32 i, list_entries = 0; +- bool xts, valid; +- u32 flags; ++ u32 flags = 0; + int rc; + + util_assert(pkey_fd != -1, "Internal error: pkey_fd is -1"); + util_assert(secure_key != NULL, "Internal error: secure_key is NULL"); + +- xts = is_xts_key(secure_key, secure_key_size); ++ securekey = is_secure_key(secure_key, secure_key_size); ++ xts = securekey ? is_xts_key(secure_key, secure_key_size) : false; + +- flags = PKEY_FLAGS_MATCH_CUR_MKVP; +- if (is_cca_aes_data_key(secure_key, secure_key_size) || +- is_cca_aes_cipher_key(secure_key, secure_key_size)) +- flags |= PKEY_FLAGS_MATCH_ALT_MKVP; ++ if (securekey) { ++ flags = PKEY_FLAGS_MATCH_CUR_MKVP; ++ if (is_cca_aes_data_key(secure_key, secure_key_size) || ++ is_cca_aes_cipher_key(secure_key, secure_key_size)) ++ flags |= PKEY_FLAGS_MATCH_ALT_MKVP; + +- rc = build_apqn_list_for_key(pkey_fd, secure_key, +- HALF_KEYSIZE_FOR_XTS(secure_key_size, xts), +- flags, apqns, &list, &list_entries, +- verbose); +- if (rc != 0) { +- pr_verbose(verbose, "Failed to build a list of APQNs that can " +- "validate this secure key: %s", strerror(-rc)); +- return rc; ++ rc = build_apqn_list_for_key(pkey_fd, secure_key, ++ HALF_KEYSIZE_FOR_XTS( ++ secure_key_size, xts), ++ flags, apqns, &list, &list_entries, ++ verbose); ++ if (rc != 0) { ++ pr_verbose(verbose, "Failed to build a list of APQNs " ++ "that can validate this secure " ++ "key: %s", strerror(-rc)); ++ return rc; ++ } ++ } else { ++ list = util_malloc(sizeof(struct pkey_apqn)); ++ list[0].card = 0; ++ list[0].domain = 0; ++ list_entries = 1; + } + + if (is_old_mk != NULL) +- *is_old_mk = true; ++ *is_old_mk = securekey ? true : false; + if (clear_key_bitsize != NULL) + *clear_key_bitsize = 0; + +@@ -1333,7 +1343,7 @@ int validate_secure_key(int pkey_fd, + continue; + } + +- if (is_xts_key(secure_key, secure_key_size)) { ++ if (xts) { + rc = validate_secure_xts_key(pkey_fd, &list[i], + secure_key, + secure_key_size, +@@ -1358,7 +1368,7 @@ int validate_secure_key(int pkey_fd, + * If at least one of the APQNs have a matching current MK, + * then don't report OLD, even if some match the old MK. + */ +- if (is_old_mk && ++ if (securekey && is_old_mk && + (verifykey2.flags & PKEY_FLAGS_MATCH_CUR_MKVP)) + *is_old_mk = false; + } +diff --git a/zkey/zkey-cryptsetup.1 b/zkey/zkey-cryptsetup.1 +index 185edab9..ffd600d4 100644 +--- a/zkey/zkey-cryptsetup.1 ++++ b/zkey/zkey-cryptsetup.1 +@@ -68,7 +68,9 @@ It also displays the attributes of the secure key, such as key size, whether + it is a secure key that can be used for the XTS cipher mode, and the master key + register (CURRENT or OLD) with which the secure key is enciphered. + For further information about master key registers, see the +-\fBreencipher\fP command. ++\fBreencipher\fP command. Keys of type \fBPVSECRET\-AES\fP do not use a ++cryptographic adapter, thus no master key information is displayed for such ++keys. + .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 +diff --git a/zkey/zkey-cryptsetup.c b/zkey/zkey-cryptsetup.c +index 2b018a2a..65716f3b 100644 +--- a/zkey/zkey-cryptsetup.c ++++ b/zkey/zkey-cryptsetup.c +@@ -1346,8 +1346,7 @@ static int check_keysize_and_cipher_mode(const u8 *key, size_t keysize) + } + + if (strncmp(crypt_get_cipher_mode(g.cd), "xts", 3) == 0) { +- if (keysize < 2 * MIN_SECURE_KEY_SIZE || +- (key != NULL && !is_xts_key(key, keysize))) { ++ if (key != NULL && !is_xts_key(key, keysize)) { + warnx("The volume key size %lu is not valid for the " + "cipher mode '%s'", keysize, + crypt_get_cipher_mode(g.cd)); +@@ -1539,8 +1538,9 @@ static int validate_keyslot(int keyslot, char **key, size_t *keysize, + rc = -EINVAL; + goto out; + } +- pr_verbose("Volume key is currently enciphered with %s master key", +- is_old ? "OLD" : "CURRENT"); ++ if (is_secure_key((u8 *)vkey, vkeysize - ikeysize)) ++ pr_verbose("Volume key is currently enciphered with %s " ++ "master key", is_old ? "OLD" : "CURRENT"); + + if (key != NULL) + *key = vkey; +@@ -2023,12 +2023,14 @@ static int command_validate(void) + vp_tok_avail = 1; + } + +- rc = get_master_key_verification_pattern((u8 *)key, seckeysize, +- mkvp, g.verbose); +- if (rc != 0) { +- warnx("Failed to get the master key verification pattern: %s", +- strerror(-rc)); +- goto out; ++ if (is_secure_key((u8 *)key, seckeysize)) { ++ rc = get_master_key_verification_pattern((u8 *)key, seckeysize, ++ mkvp, g.verbose); ++ if (rc != 0) { ++ warnx("Failed to get the master key verification " ++ "pattern: %s", strerror(-rc)); ++ goto out; ++ } + } + + key_type = get_key_type((u8 *)key, seckeysize); +@@ -2041,15 +2043,19 @@ static int command_validate(void) + printf(" Key type: %s\n", key_type); + if (is_valid) { + printf(" Clear key size: %lu bits\n", clear_keysize); +- printf(" Enciphered with: %s master key (MKVP: " +- "%s)\n", is_old_mk ? "OLD" : "CURRENT", +- printable_mkvp(get_card_type_for_keytype(key_type), +- mkvp)); ++ if (is_secure_key((u8 *)key, seckeysize)) { ++ printf(" Enciphered with: %s master key (MKVP: " ++ "%s)\n", is_old_mk ? "OLD" : "CURRENT", ++ printable_mkvp(get_card_type_for_keytype( ++ key_type), mkvp)); ++ } + } else { + printf(" Clear key size: (unknown)\n"); +- printf(" Enciphered with: (unknown, MKVP: %s)\n", +- printable_mkvp(get_card_type_for_keytype(key_type), +- mkvp)); ++ if (is_secure_key((u8 *)key, seckeysize)) { ++ printf(" Enciphered with: (unknown, MKVP: %s)\n", ++ printable_mkvp(get_card_type_for_keytype( ++ key_type), mkvp)); ++ } + } + if (vp_tok_avail) + print_verification_pattern(vp_tok.verification_pattern); +@@ -2065,7 +2071,7 @@ static int command_validate(void) + if (!is_valid) + printf("\nATTENTION: The secure volume key is not valid.\n"); + +- if (is_old_mk) ++ if (is_secure_key((u8 *)key, seckeysize) && is_old_mk) + util_print_indented("\nWARNING: The secure volume key is " + "currently enciphered with the OLD " + "master key. To mitigate the danger of " +@@ -2195,7 +2201,8 @@ static int command_setkey(void) + goto out; + } + +- if (is_old_mk) { ++ if (is_secure_key(newkey, newkey_size - integrity_keysize) && ++ is_old_mk) { + util_asprintf(&msg, "The secure key in file '%s' is " + "enciphered with the master key in the OLD " + "master key register. Do you want to set this " +diff --git a/zkey/zkey.1 b/zkey/zkey.1 +index 316db5f0..b44eadf1 100644 +--- a/zkey/zkey.1 ++++ b/zkey/zkey.1 +@@ -209,7 +209,9 @@ It also displays the attributes of the secure key, such as key sizes, whether + it is a secure key that can be used for the XTS cipher mode, the master key + register (CURRENT or OLD) with which the secure key is enciphered, and other key + attributes. For further information about master key registers, see the +-\fBreencipher\fP command. ++\fBreencipher\fP command. Keys of type \fBPVSECRET\-AES\fP do not use a ++cryptographic adapter, thus no master key information is displayed for such ++keys. + .PP + The secure key can either be contained in a file in the file system, or in a + secure key repository. To validate a secure key contained in a file, specify +@@ -1599,8 +1601,8 @@ This option is only used for secure keys contained in the secure key repository. + .TP + .BR \-K ", " \-\-key\-type\~\fItype\fP + Specifies the key type of the secure key. Possible values are +-\fBCCA\-AESDATA\fP, \fBCCA\-AESCIPHER\fP, and \fBEP11\-AES\fP. Only keys with +-the specified key type are listed. ++\fBCCA\-AESDATA\fP, \fBCCA\-AESCIPHER\fP, \fBEP11\-AES\fP, and ++\fBPVSECRET\-AES\fP. Only keys with the specified key type are listed. + This option is only used for secure keys contained in the secure key repository. + .TP + .BR \-L ", " \-\-local\fP +diff --git a/zkey/zkey.c b/zkey/zkey.c +index 90b46106..39a527c4 100644 +--- a/zkey/zkey.c ++++ b/zkey/zkey.c +@@ -558,9 +558,10 @@ static struct util_opt opt_vec[] = { + .option = { "key-type", required_argument, NULL, 'K'}, + .argument = "type", + .desc = "The type of the key. Possible values are '" +- KEY_TYPE_CCA_AESDATA"', '"KEY_TYPE_CCA_AESCIPHER"' " +- "and '"KEY_TYPE_EP11_AES"'. Use this option to list " +- "all keys with the specified key type.", ++ KEY_TYPE_CCA_AESDATA "', '" KEY_TYPE_CCA_AESCIPHER ++ "', '" KEY_TYPE_EP11_AES "', and '" ++ KEY_TYPE_PVSECRET_AES "'. Use this option to list all " ++ "keys with the specified key type.", + .command = COMMAND_LIST, + }, + { +@@ -2345,13 +2346,16 @@ static int command_validate_file(void) + goto out; + } + +- rc = get_master_key_verification_pattern(secure_key, secure_key_size, +- mkvp, g.verbose); +- if (rc != 0) { +- warnx("Failed to get the master key verification pattern: %s", +- strerror(-rc)); +- rc = EXIT_FAILURE; +- goto out; ++ if (is_secure_key(secure_key, secure_key_size)) { ++ rc = get_master_key_verification_pattern(secure_key, ++ secure_key_size, ++ mkvp, g.verbose); ++ if (rc != 0) { ++ warnx("Failed to get the master key verification " ++ "pattern: %s", strerror(-rc)); ++ rc = EXIT_FAILURE; ++ goto out; ++ } + } + + key_type = get_key_type(secure_key, secure_key_size); +@@ -2363,25 +2367,30 @@ static int command_validate_file(void) + printf(" Clear key size: %lu bits\n", clear_key_size); + printf(" XTS type key: %s\n", + is_xts_key(secure_key, secure_key_size) ? "Yes" : "No"); +- printf(" Enciphered with: %s master key (MKVP: %s)\n", +- is_old_mk ? "OLD" : "CURRENT", +- printable_mkvp(get_card_type_for_keytype(key_type), mkvp)); ++ if (is_secure_key(secure_key, secure_key_size)) { ++ printf(" Enciphered with: %s master key (MKVP: %s)\n", ++ is_old_mk ? "OLD" : "CURRENT", ++ printable_mkvp(get_card_type_for_keytype(key_type), ++ mkvp)); ++ } + printf(" Verification pattern: %.*s\n", VERIFICATION_PATTERN_LEN / 2, + vp); + printf(" %.*s\n", VERIFICATION_PATTERN_LEN / 2, + &vp[VERIFICATION_PATTERN_LEN / 2]); + +- rc = cross_check_apqns(NULL, mkvp, +- get_min_card_level_for_keytype(key_type), +- get_min_fw_version_for_keytype(key_type), +- get_card_type_for_keytype(key_type), +- true, g.verbose); +- if (rc == -EINVAL) +- return EXIT_FAILURE; +- if (rc != 0 && rc != -ENOTSUP) { +- warnx("Your master key setup is improper"); +- rc = EXIT_FAILURE; +- goto out; ++ if (is_secure_key(secure_key, secure_key_size)) { ++ rc = cross_check_apqns(NULL, mkvp, ++ get_min_card_level_for_keytype(key_type), ++ get_min_fw_version_for_keytype(key_type), ++ get_card_type_for_keytype(key_type), ++ true, g.verbose); ++ if (rc == -EINVAL) ++ return EXIT_FAILURE; ++ if (rc != 0 && rc != -ENOTSUP) { ++ warnx("Your master key setup is improper"); ++ rc = EXIT_FAILURE; ++ goto out; ++ } + } + + out: diff --git a/s390-tools.changes b/s390-tools.changes index c526eba..295947a 100644 --- a/s390-tools.changes +++ b/s390-tools.changes @@ -1,3 +1,16 @@ +------------------------------------------------------------------- +Thu Jan 30 08:19:47 UTC 2025 - Nikolay Gueorguiev + +- Applied additional patches ( jsc#PED-9561 ( jsc#IBM-1447 ) ) + * s390-tools-01-zkey-Add-support-for-retrieving-a-list-of-ultravisor-secrets.patch + * s390-tools-02-zkey-Add-the--pvsecrets-list-command.patch + * s390-tools-03-zkey-Add-PVSECRETS-AES-key-type.patch + * s390-tools-04-zkey-Add-the-pvsecrets-import-command.patch + * s390-tools-05-zkey-Reject-key-generation-and-APQN-association-for-PVSECRET-AES-keys.patch + * s390-tools-06-zkey-Reject-re-enciphering-of-PVSECRET-AES-keys.patch + * s390-tools-07-zkey-Support-validation-of-key-of-type-PVSECRET-AES.patch +- Revendored vendor.tar.gz + ------------------------------------------------------------------- Thu Jan 9 07:05:53 UTC 2025 - Nikolay Gueorguiev diff --git a/s390-tools.spec b/s390-tools.spec index d739f4e..6bff6a6 100644 --- a/s390-tools.spec +++ b/s390-tools.spec @@ -181,6 +181,14 @@ Patch960: s390-tools-Support-unencrypted-SE-images-01.patch Patch961: s390-tools-pvimg-info-command-04.patch Patch962: s390-tools-pvimg-additional-01.patch ### +Patch970: s390-tools-01-zkey-Add-support-for-retrieving-a-list-of-ultravisor-secrets.patch +Patch971: s390-tools-02-zkey-Add-the--pvsecrets-list-command.patch +Patch972: s390-tools-03-zkey-Add-PVSECRETS-AES-key-type.patch +Patch973: s390-tools-04-zkey-Add-the-pvsecrets-import-command.patch +Patch974: s390-tools-05-zkey-Reject-key-generation-and-APQN-association-for-PVSECRET-AES-keys.patch +Patch975: s390-tools-06-zkey-Reject-re-enciphering-of-PVSECRET-AES-keys.patch +Patch976: s390-tools-07-zkey-Support-validation-of-key-of-type-PVSECRET-AES.patch +### Patch990: s390-tools-slfo-01-parse-ipl-device-for-activation.patch ### diff --git a/vendor.tar.gz b/vendor.tar.gz index b20eaf3..3a97d7c 100644 --- a/vendor.tar.gz +++ b/vendor.tar.gz @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c5844b1b0c04b5d882966d8a3335385f39a0bcca0693b87c018d8a7949b87739 -size 46334305 +oid sha256:a006e81236e6479a7141d04c11af6181b5baeee9bd1cd9140003ea79738a33bf +size 46331845