mirror of
https://github.com/ibm-s390-linux/s390-tools.git
synced 2026-08-05 02:14:52 +00:00
Re-enciphering an EP11 secure key may fail with an error message like: "zkey-cryptsetup: Failed to re-encipher the secure volume key for device '<device>'" or "zkey: Failed to re-encipher '<key>' from CURRENT to NEW master key" or similar. The verbose messages show the following debug message: "Command XCP_ADM_REENCRYPT failed. rc = 0x20" This is due to uninitialized variables, which might cause the EP11 admin request to contain garbage data, causing it to fail with CKR_DATA_INVALID (0x20). Fixes:0be7efc956("zkey: Add support for re-enciphering EP11 secure keys") Fixes:4e2ebe0370("libseckey: Fix re-enciphering of EP11 secure key") Signed-off-by: Ingo Franzki <ifranzki@linux.ibm.com> Reviewed-by: Joerg Schmidbauer <jschmidb@de.ibm.com> Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
536 lines
16 KiB
C
536 lines
16 KiB
C
/*
|
|
* zkey - Generate, re-encipher, and validate secure keys
|
|
*
|
|
* Copyright IBM Corp. 2019
|
|
*
|
|
* 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 <dlfcn.h>
|
|
#include <err.h>
|
|
#include <errno.h>
|
|
#include <stdbool.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <stdint.h>
|
|
#include <sys/types.h>
|
|
#include <unistd.h>
|
|
|
|
#include "lib/util_base.h"
|
|
#include "lib/util_libc.h"
|
|
#include "lib/util_panic.h"
|
|
|
|
#include "ep11.h"
|
|
#include "pkey.h"
|
|
#include "utils.h"
|
|
|
|
#define pr_verbose(verbose, fmt...) do { \
|
|
if (verbose) \
|
|
warnx(fmt); \
|
|
} while (0)
|
|
|
|
/*
|
|
* Definitions for the EP11 library
|
|
*/
|
|
#define EP11_LIBRARY_NAME "libep11.so"
|
|
#define EP11_LIBRARY_VERSION 4
|
|
#define EP11_WEB_PAGE "http://www.ibm.com/security/cryptocards"
|
|
|
|
/**
|
|
* Returns the major and minor version of the of the used EP11 host library.
|
|
*
|
|
* @param[in] ep11 the EP11 library structure
|
|
* @param[in] verbose if true, verbose messages are printed
|
|
*
|
|
* @returns 0 on success, a negative errno in case of an error
|
|
*/
|
|
static int get_ep11_version(struct ep11_lib *ep11, bool verbose)
|
|
{
|
|
unsigned int host_version;
|
|
CK_ULONG version_len = sizeof(host_version);
|
|
CK_RV rc;
|
|
|
|
rc = ep11->dll_m_get_xcp_info(&host_version, &version_len,
|
|
CK_IBM_XCPHQ_VERSION, 0, 0);
|
|
if (rc != CKR_OK) {
|
|
pr_verbose(verbose, "Failed to obtain the EP11 host library "
|
|
"version: m_get_xcp_info: 0x%lx", rc);
|
|
return -EIO;
|
|
}
|
|
|
|
pr_verbose(verbose, "host_version: 0x%08x", host_version);
|
|
|
|
ep11->version.major = (host_version & 0x00FF0000) >> 16;
|
|
ep11->version.minor = (host_version & 0x0000FF00) >> 8;
|
|
ep11->version.modification = host_version & 0x000000FF;
|
|
/*
|
|
* EP11 host library < v2.0 returns an invalid version (i.e. 0x100).
|
|
* This can safely be treated as version 1.0
|
|
*/
|
|
if (ep11->version.major == 0) {
|
|
ep11->version.major = 1;
|
|
ep11->version.minor = 0;
|
|
}
|
|
|
|
pr_verbose(verbose, "EP11 library version: %u.%u.%u",
|
|
ep11->version.major, ep11->version.minor,
|
|
ep11->version.modification);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Loads the EP11 library and provides the entry points of several functions.
|
|
*
|
|
* @param[out] ep11 on return this contains the address of the EP11
|
|
* library and certain EP11 symbols. dlclose() should
|
|
* be used to free the library when no longer needed.
|
|
* @param verbose if true, verbose messages are printed
|
|
*
|
|
* @returns 0 on success, -ELIBACC in case of library load errors
|
|
*/
|
|
int load_ep11_library(struct ep11_lib *ep11, bool verbose)
|
|
{
|
|
char lib_name[256];
|
|
int libver;
|
|
int rc;
|
|
|
|
util_assert(ep11 != NULL, "Internal error: ep11 is NULL");
|
|
|
|
/* Load the EP11 library with highest available version'd SO name */
|
|
for (libver = EP11_LIBRARY_VERSION; libver >= 0; libver--) {
|
|
if (libver > 0)
|
|
sprintf(lib_name, "%s.%d", EP11_LIBRARY_NAME, libver);
|
|
else
|
|
sprintf(lib_name, "%s", EP11_LIBRARY_NAME);
|
|
|
|
ep11->lib_ep11 = dlopen(lib_name, RTLD_GLOBAL | RTLD_NOW);
|
|
if (ep11->lib_ep11 != NULL)
|
|
break;
|
|
}
|
|
if (ep11->lib_ep11 == NULL) {
|
|
pr_verbose(verbose, "%s", dlerror());
|
|
warnx("The command requires the IBM Z Enterprise PKCS #11 "
|
|
"(EP11) Support Program (EP11 host library).\n"
|
|
"For the supported environments and downloads, see:\n%s",
|
|
EP11_WEB_PAGE);
|
|
return -ELIBACC;
|
|
}
|
|
|
|
/* Get several EP11 host library functions */
|
|
ep11->dll_m_init = (m_init_t)dlsym(ep11->lib_ep11, "m_init");
|
|
ep11->dll_m_add_module = (m_add_module_t)dlsym(ep11->lib_ep11,
|
|
"m_add_module");
|
|
ep11->dll_m_rm_module = (m_rm_module_t)dlsym(ep11->lib_ep11,
|
|
"m_rm_module");
|
|
ep11->dll_m_get_xcp_info = (m_get_xcp_info_t)dlsym(ep11->lib_ep11,
|
|
"m_get_xcp_info");
|
|
|
|
ep11->dll_m_admin = (m_admin_t)dlsym(ep11->lib_ep11, "m_admin");
|
|
ep11->dll_xcpa_cmdblock = (xcpa_cmdblock_t)dlsym(ep11->lib_ep11,
|
|
"xcpa_cmdblock");
|
|
if (ep11->dll_xcpa_cmdblock == NULL)
|
|
ep11->dll_xcpa_cmdblock = (xcpa_cmdblock_t)dlsym(ep11->lib_ep11,
|
|
"ep11a_cmdblock");
|
|
ep11->dll_xcpa_internal_rv = (xcpa_internal_rv_t)dlsym(ep11->lib_ep11,
|
|
"xcpa_internal_rv");
|
|
if (ep11->dll_xcpa_internal_rv == NULL)
|
|
ep11->dll_xcpa_internal_rv =
|
|
(xcpa_internal_rv_t)dlsym(ep11->lib_ep11,
|
|
"ep11a_internal_rv");
|
|
|
|
/* dll_m_add_module and dll_m_rm_module may be NULL for V1 EP11 lib */
|
|
if (ep11->dll_m_init == NULL ||
|
|
ep11->dll_m_get_xcp_info == NULL ||
|
|
ep11->dll_m_admin == NULL ||
|
|
ep11->dll_xcpa_cmdblock == NULL ||
|
|
ep11->dll_xcpa_internal_rv == NULL) {
|
|
pr_verbose(verbose, "%s", dlerror());
|
|
warnx("The command requires the IBM Z Enterprise PKCS #11 "
|
|
"(EP11) Support Program (EP11 host library).\n"
|
|
"For the supported environments and downloads, see:\n%s",
|
|
EP11_WEB_PAGE);
|
|
dlclose(ep11->lib_ep11);
|
|
ep11->lib_ep11 = NULL;
|
|
return -ELIBACC;
|
|
}
|
|
|
|
/* Initialize the EP11 library */
|
|
rc = ep11->dll_m_init();
|
|
if (rc != 0) {
|
|
pr_verbose(verbose, "Failed to initialize the EP11 host "
|
|
"library: m_init: 0x%x", rc);
|
|
dlclose(ep11->lib_ep11);
|
|
ep11->lib_ep11 = NULL;
|
|
return -ELIBACC;
|
|
}
|
|
|
|
pr_verbose(verbose, "EP11 library '%s' has been loaded successfully",
|
|
lib_name);
|
|
|
|
return get_ep11_version(ep11, verbose);
|
|
}
|
|
|
|
/**
|
|
* Get an EP11 target handle for a specific APQN (card and domain)
|
|
*
|
|
* @param[in] ep11 the EP11 library structure
|
|
* @param[in] card the card number
|
|
* @param[in] domain the domain number
|
|
* @param[out] target on return: the target handle for the APQN
|
|
* @param verbose if true, verbose messages are printed
|
|
*
|
|
* @returns 0 on success, a negative errno in case of errors
|
|
*/
|
|
int get_ep11_target_for_apqn(struct ep11_lib *ep11, unsigned int card,
|
|
unsigned int domain, target_t *target,
|
|
bool verbose)
|
|
{
|
|
ep11_target_t *target_list;
|
|
struct XCP_Module module;
|
|
CK_RV rc;
|
|
|
|
util_assert(ep11 != NULL, "Internal error: ep11 is NULL");
|
|
util_assert(target != NULL, "Internal error: target is NULL");
|
|
|
|
*target = XCP_TGT_INIT;
|
|
|
|
if (ep11->dll_m_add_module != NULL) {
|
|
memset(&module, 0, sizeof(module));
|
|
module.version = ep11->version.major >= 3 ? XCP_MOD_VERSION_2
|
|
: XCP_MOD_VERSION_1;
|
|
module.flags = XCP_MFL_MODULE;
|
|
module.module_nr = card;
|
|
XCPTGTMASK_SET_DOM(module.domainmask, domain);
|
|
rc = ep11->dll_m_add_module(&module, target);
|
|
if (rc != 0) {
|
|
pr_verbose(verbose, "Failed to add APQN %02x.%04x: "
|
|
"m_add_module rc=0x%lx", card, domain, rc);
|
|
return -EIO;
|
|
}
|
|
} else {
|
|
/* Fall back to old target handling */
|
|
target_list = (ep11_target_t *)calloc(1, sizeof(ep11_target_t));
|
|
if (target_list == NULL)
|
|
return -ENOMEM;
|
|
target_list->length = 1;
|
|
target_list->apqns[0] = card;
|
|
target_list->apqns[1] = domain;
|
|
*target = (target_t)target_list;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Free an EP11 target handle
|
|
*
|
|
* @param[in] ep11 the EP11 library structure
|
|
* @param[in] target the target handle to free
|
|
*
|
|
* @returns 0 on success, a negative errno in case of errors
|
|
*/
|
|
void free_ep11_target_for_apqn(struct ep11_lib *ep11, target_t target)
|
|
{
|
|
util_assert(ep11 != NULL, "Internal error: ep11 is NULL");
|
|
|
|
if (ep11->dll_m_rm_module != NULL) {
|
|
ep11->dll_m_rm_module(NULL, target);
|
|
} else {
|
|
/*
|
|
* With the old target handling, target is a pointer to
|
|
* ep11_target_t
|
|
*/
|
|
free((ep11_target_t *)target);
|
|
}
|
|
}
|
|
|
|
struct find_mkvp_info {
|
|
u8 mkvp[MKVP_LENGTH];
|
|
unsigned int flags;
|
|
bool found;
|
|
unsigned int card;
|
|
unsigned int domain;
|
|
bool verbose;
|
|
};
|
|
|
|
static int find_mkvp(unsigned int card, unsigned int domain, void *handler_data)
|
|
{
|
|
struct find_mkvp_info *info = (struct find_mkvp_info *)handler_data;
|
|
struct mk_info mk_info;
|
|
bool found = false;
|
|
int rc;
|
|
|
|
rc = sysfs_get_mkvps(card, domain, &mk_info, info->verbose);
|
|
if (rc == -ENODEV)
|
|
return 0;
|
|
if (rc != 0)
|
|
return rc;
|
|
|
|
if (info->flags & FLAG_SEL_EP11_MATCH_CUR_MKVP)
|
|
if (mk_info.cur_mk.mk_state == MK_STATE_VALID &&
|
|
MKVP_EQ(mk_info.cur_mk.mkvp, info->mkvp))
|
|
found = true;
|
|
|
|
if (info->flags & FLAG_SEL_EP11_NEW_MUST_BE_SET)
|
|
if (mk_info.new_mk.mk_state != MK_STATE_COMMITTED)
|
|
found = false;
|
|
|
|
if (found) {
|
|
info->card = card;
|
|
info->domain = domain;
|
|
info->found = true;
|
|
|
|
pr_verbose(info->verbose, "%02x.%04x has the desired mkvp%s",
|
|
card, domain,
|
|
info->flags & FLAG_SEL_EP11_NEW_MUST_BE_SET ?
|
|
" and NEW MK set" : "");
|
|
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Selects an APQN to be used for the Ep11 host library that has the specified
|
|
* master key verification pattern
|
|
*
|
|
* @param[in] ep11 the EP11 library structure
|
|
* @param[in] mkvp the master key verification pattern to search for
|
|
* @param[in] apqns a comma separated list of APQNs. If NULL is specified,
|
|
* or an empty string, then all online EP11 APQNs are
|
|
* checked.
|
|
* @param[in] flags Flags that control the MKVM matching and NEW register
|
|
* checking. Multiple flags can be combined.
|
|
* @param[out] target on return: the target handle for the APQN. If this is
|
|
* NULL, then no target is built.
|
|
* @param[out] card on return: the card that was selected (can be NULL)
|
|
* @param[out] domain on return: the domain that was selected (can be NULL)
|
|
* @param[in] verbose if true, verbose messages are printed
|
|
*
|
|
* @returns 0 on success, a negative errno in case of errors
|
|
*/
|
|
int select_ep11_apqn_by_mkvp(struct ep11_lib *ep11, u8 *mkvp,
|
|
const char *apqns, unsigned int flags,
|
|
target_t *target, unsigned int *card,
|
|
unsigned int *domain, bool verbose)
|
|
{
|
|
struct find_mkvp_info info;
|
|
int rc;
|
|
|
|
util_assert(ep11 != NULL, "Internal error: ep11 is NULL");
|
|
util_assert(mkvp != NULL, "Internal error: mkvp is NULL");
|
|
|
|
pr_verbose(verbose, "Select mkvp %s in APQNs %s for the EP11 host "
|
|
"library", printable_mkvp(CARD_TYPE_EP11, mkvp),
|
|
apqns == NULL ? "ANY" : apqns);
|
|
|
|
memcpy(info.mkvp, mkvp, sizeof(info.mkvp));
|
|
info.flags = flags;
|
|
info.found = false;
|
|
info.card = 0;
|
|
info.domain = 0;
|
|
info.verbose = verbose;
|
|
|
|
rc = handle_apqns(apqns, CARD_TYPE_EP11, find_mkvp, &info, verbose);
|
|
if (rc < 0)
|
|
return rc;
|
|
|
|
if (!info.found)
|
|
return -ENODEV;
|
|
|
|
if (target != NULL) {
|
|
rc = get_ep11_target_for_apqn(ep11, info.card, info.domain,
|
|
target, verbose);
|
|
if (rc != 0)
|
|
return rc;
|
|
}
|
|
|
|
if (card != NULL)
|
|
*card = info.card;
|
|
if (domain != NULL)
|
|
*domain = info.domain;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Performs an EP11 administrative request to Re-encrypt a single EP11 secure
|
|
* key with a new EP11 master key (wrapping key).
|
|
*
|
|
* @param[in] ep11 the EP11 library structure
|
|
* @param[in] target the target handle to use for the re-encipher operation
|
|
* @param[in] card the card that corresponds to the target handle
|
|
* @param[in] domain the domain that corresponds to the target handle
|
|
* @param[in/out] ep11key_blob the EP11 key token to reencipher. The
|
|
* re-enciphered secure key will be returned in this
|
|
* buffer.
|
|
* @param[in] ep11key_size the size of the secure key
|
|
* @param[in] verbose if true, verbose messages are printed
|
|
*
|
|
* @returns 0 on success, a negative errno in case of errors
|
|
*/
|
|
static int ep11_adm_reencrypt(struct ep11_lib *ep11, target_t target,
|
|
unsigned int card, unsigned int domain,
|
|
u8 *ep11key_blob,
|
|
unsigned int ep11key_size, bool verbose)
|
|
{
|
|
struct ep11kblob_header *hdr = (struct ep11kblob_header *)ep11key_blob;
|
|
struct ep11keytoken *ep11key;
|
|
CK_BYTE resp[MAX_BLOBSIZE];
|
|
CK_BYTE req[MAX_BLOBSIZE];
|
|
char ep11_token_header[sizeof(ep11key->head)] = { 0 };
|
|
struct XCPadmresp lrb;
|
|
struct XCPadmresp rb;
|
|
bool with_header;
|
|
size_t resp_len;
|
|
size_t blob_len;
|
|
long req_len;
|
|
CK_RV rv;
|
|
int rc;
|
|
|
|
with_header = is_ep11_aes_key_with_header(ep11key_blob, ep11key_size);
|
|
ep11key = (struct ep11keytoken *)(with_header ?
|
|
ep11key_blob + sizeof(struct ep11kblob_header) :
|
|
ep11key_blob);
|
|
blob_len = with_header ? hdr->len - sizeof(struct ep11kblob_header) :
|
|
ep11key->head.len;
|
|
if (blob_len > ep11key_size) {
|
|
pr_verbose(verbose, "Blob length larger than secure key size");
|
|
return -EINVAL;
|
|
}
|
|
|
|
memset(&rb, 0, sizeof(rb));
|
|
memset(&lrb, 0, sizeof(lrb));
|
|
|
|
rb.domain = domain;
|
|
lrb.domain = domain;
|
|
|
|
if (!with_header) {
|
|
/*
|
|
* The token header is an overlay over the (all zero) session
|
|
* field
|
|
*/
|
|
memcpy(ep11_token_header, ep11key, sizeof(ep11_token_header));
|
|
memset(ep11key->session, 0, sizeof(ep11key->session));
|
|
}
|
|
|
|
resp_len = sizeof(resp);
|
|
req_len = ep11->dll_xcpa_cmdblock(req, sizeof(req), XCP_ADM_REENCRYPT,
|
|
&rb, NULL, (unsigned char *)ep11key,
|
|
blob_len);
|
|
if (req_len < 0) {
|
|
pr_verbose(verbose, "Failed to build XCP command block");
|
|
return -EIO;
|
|
}
|
|
|
|
rv = ep11->dll_m_admin(resp, &resp_len, NULL, NULL, req, req_len, NULL,
|
|
0, target);
|
|
if (rv != CKR_OK || resp_len == 0) {
|
|
pr_verbose(verbose, "Command XCP_ADM_REENCRYPT failed. "
|
|
"rc = 0x%lx, resp_len = %ld", rv, resp_len);
|
|
return -EIO;
|
|
}
|
|
|
|
rc = ep11->dll_xcpa_internal_rv(resp, resp_len, &lrb, &rv);
|
|
if (rc != 0) {
|
|
pr_verbose(verbose, "Failed to parse response. rc = %d", rc);
|
|
return -EIO;
|
|
}
|
|
|
|
if (rv != CKR_OK) {
|
|
pr_verbose(verbose, "Failed to re-encrypt the EP11 secure key. "
|
|
"rc = 0x%lx", rv);
|
|
switch (rv) {
|
|
case CKR_IBM_WKID_MISMATCH:
|
|
warnx("The EP11 secure key is currently encrypted "
|
|
"under a different master that does not match "
|
|
"the master key in the CURRENT master key "
|
|
"register of APQN %02X.%04X", card, domain);
|
|
break;
|
|
}
|
|
return -EIO;
|
|
}
|
|
|
|
if (blob_len != lrb.pllen) {
|
|
pr_verbose(verbose, "Re-encrypted EP11 secure key size has "
|
|
"changed: org-len: %lu, new-len: %lu", blob_len,
|
|
lrb.pllen);
|
|
return -EIO;
|
|
}
|
|
|
|
memcpy(ep11key, lrb.payload, blob_len);
|
|
if (!with_header)
|
|
memcpy(ep11key, ep11_token_header, sizeof(ep11_token_header));
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Re-encipher an EP11 secure key with a new EP11 master key (wrapping key).
|
|
*
|
|
* @param[in] ep11 the EP11 library structure
|
|
* @param[in] target the target handle to use for the re-encipher operation
|
|
* @param[in] card the card that corresponds to the target handle
|
|
* @param[in] domain the domain that corresponds to the target handle
|
|
* @param[in/out] secure_key the EP11 key token to reencipher. The re-enciphered
|
|
* secure key will be returned in this buffer.
|
|
* @param[in] secure_key_size the size of the secure key
|
|
* @param[in] verbose if true, verbose messages are printed
|
|
*
|
|
* @returns 0 on success, a negative errno in case of errors
|
|
*/
|
|
int reencipher_ep11_key(struct ep11_lib *ep11, target_t target,
|
|
unsigned int card, unsigned int domain, u8 *secure_key,
|
|
unsigned int secure_key_size, bool verbose)
|
|
{
|
|
CK_IBM_DOMAIN_INFO dinf;
|
|
CK_ULONG dinf_len = sizeof(dinf);
|
|
CK_RV rv;
|
|
int rc;
|
|
|
|
util_assert(ep11 != NULL, "Internal error: ep11 is NULL");
|
|
util_assert(secure_key != NULL, "Internal error: secure_key is NULL");
|
|
|
|
rv = ep11->dll_m_get_xcp_info(&dinf, &dinf_len, CK_IBM_XCPQ_DOMAIN, 0,
|
|
target);
|
|
if (rv != CKR_OK) {
|
|
pr_verbose(verbose, "Failed to query domain information for "
|
|
"%02X.%04X: m_get_xcp_info rc: 0x%lx", card, domain,
|
|
rv);
|
|
return -EIO;
|
|
}
|
|
|
|
if ((dinf.flags & CK_IBM_DOM_COMMITTED_NWK) == 0) {
|
|
warnx("The NEW master key register of APQN %02X.%04X is not "
|
|
"in COMMITTED state", card, domain);
|
|
return -ENODEV;
|
|
}
|
|
|
|
rc = ep11_adm_reencrypt(ep11, target, card, domain, secure_key,
|
|
secure_key_size, verbose);
|
|
if (rc != 0)
|
|
return rc;
|
|
|
|
if (is_xts_key(secure_key, secure_key_size)) {
|
|
if (is_ep11_aes_key_with_header(secure_key, secure_key_size)) {
|
|
secure_key += EP11_AES_KEY_SIZE;
|
|
secure_key_size -= EP11_AES_KEY_SIZE;
|
|
} else {
|
|
secure_key += EP11_KEY_SIZE;
|
|
secure_key_size -= EP11_KEY_SIZE;
|
|
}
|
|
|
|
rc = ep11_adm_reencrypt(ep11, target, card, domain, secure_key,
|
|
secure_key_size, verbose);
|
|
if (rc != 0)
|
|
return rc;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|