Files
s390-tools/zkey/pvsecrets.c
Ingo Franzki 95bf7eb285 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 <ifranzki@linux.ibm.com>
Reviewed-by: Jorg Schmidbauer <jschmidb@de.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
2025-01-10 16:38:45 +01:00

681 lines
20 KiB
C

/*
* 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 <ctype.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <stdbool.h>
#include <string.h>
#include <stdint.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <unistd.h>
#include <openssl/sha.h>
#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); \
} 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;
}
/**
* 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;
}
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;
}