/* * 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 #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); \ } 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, false, NULL); return rc; }