/* * 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 #include #include #include #include #include #include #include #include #include #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; }