Files
s390-tools/zkey/kmip/zkey-kmip.c
Ingo Franzki 1b044b8a40 zkey: Support EP11 AES keys with prepended header to retain EP11 session
The pkey kernel module supports two key blob formats for EP11 AES keys.
The first one (PKEY_TYPE_EP11) contains a 16 bytes header that overlays
the first 32 bytes of the key blob which usually contain the ID of the
EP11 session to which the key is bound. For zkey/dm-crypt that session
ID used to be all zeros. The second blob format (PKEY_TYPE_EP11_AES)
prepends the 16 bytes header to the blob, an thus does not overlay the
blob. This format can be used for key blobs that are session-bound, i.e.
have a non-zero session ID in the first 32 bytes.

Change zkey to generate EP11 keys using the new format (i.e. pkey type
PKEY_TYPE_EP11_AES), but existing key blobs using the old format can
still be used.

Signed-off-by: Ingo Franzki <ifranzki@linux.ibm.com>
Reviewed-by: Joerg Schmidbauer <jschmidb@de.ibm.com>
Signed-off-by: Steffen Eiden <seiden@linux.ibm.com>
2023-08-21 17:09:26 +02:00

6908 lines
202 KiB
C

/*
* zkey-kmip - KMIP zkey KMS plugin
*
* Copyright IBM Corp. 2021
*
* 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 <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>
#include <stdarg.h>
#include <string.h>
#include <errno.h>
#include <err.h>
#include <fnmatch.h>
#include <sys/utsname.h>
#include <openssl/objects.h>
#include "lib/zt_common.h"
#include "lib/util_libc.h"
#include "lib/util_panic.h"
#include "lib/util_path.h"
#include "lib/util_base.h"
#include "zkey-kmip.h"
#include "../kms-plugin.h"
#include "../cca.h"
#include "../ep11.h"
#include "../utils.h"
#include "../pkey.h"
#include "../properties.h"
#include "libseckey/sk_utilities.h"
#include "libseckey/sk_ep11.h"
#if OPENSSL_VERSION_PREREQ(3, 0)
#include <openssl/core_names.h>
#endif
#define _set_error(ph, fmt...) plugin_set_error(&(ph)->pd, fmt)
typedef void (*t_CSNDSYI)(long *return_code,
long *reason_code,
long *exit_data_length,
unsigned char *exit_data,
long *rule_array_count,
unsigned char *rule_array,
long *RSA_enciphered_key_length,
unsigned char *RSA_enciphered_key,
long *RSA_private_key_identifier_length,
unsigned char *RSA_private_key_identifier,
long *target_key_identifier_length,
unsigned char *target_key_identifier);
typedef CK_RV (*m_UnwrapKey_t)(const CK_BYTE_PTR wrapped, CK_ULONG wlen,
const unsigned char *kek, size_t keklen,
const unsigned char *mackey,
size_t mklen, const unsigned char *pin,
size_t pinlen,
const CK_MECHANISM_PTR uwmech,
const CK_ATTRIBUTE_PTR ptempl,
CK_ULONG pcount,
unsigned char *unwrapped, size_t *uwlen,
CK_BYTE_PTR csum, CK_ULONG * cslen,
target_t target);
#define CKO_SECRET_KEY 0x00000004
#define CKK_AES 0x0000001F
#define CKA_IBM_PROTKEY_EXTRACTABLE 0x8001000C
#define KMS_KEY_PROP_DESCRIPTION "description"
#define KMS_KEY_PROP_XTS_KEY1_ID "xts-key1-id"
#define KMS_KEY_PROP_XTS_KEY2_ID "xts-key2-id"
#define KMIP_KEY_TYPE_ANY "(any)"
#define FREE_AND_SET_NULL(ptr) \
do { \
if ((ptr) != NULL) \
free((void *)ptr); \
(ptr) = NULL; \
} while (0)
#define CHECK_ERROR(cond, rc_var, rc, text, ph, label) \
do { \
if (cond) { \
(rc_var) = (rc); \
pr_verbose((&ph->pd), "%s: %s", (text), \
strerror(-(rc_var))); \
_set_error((ph), "%s: %s", (text), \
strerror(-(rc_var))); \
goto label; \
} \
} while (0)
struct kmip_enum_name {
uint32_t value;
const char *name;
};
static const struct kmip_enum_name required_operations[] = {
{ .value = KMIP_OPERATION_QUERY, .name = "Query" },
{ .value = KMIP_OPERATION_CREATE, .name = "Create" },
{ .value = KMIP_OPERATION_REGISTER, .name = "Register" },
{ .value = KMIP_OPERATION_ACTIVATE, .name = "Activate" },
{ .value = KMIP_OPERATION_REVOKE, .name = "Revoke" },
{ .value = KMIP_OPERATION_DESTROY, .name = "Destroy" },
{ .value = KMIP_OPERATION_GET, .name = "Get" },
{ .value = KMIP_OPERATION_LOCATE, .name = "Locate" },
{ .value = KMIP_OPERATION_GET_ATTRIBUTE_LIST,
.name = "Get Attribute List" },
{ .value = KMIP_OPERATION_GET_ATTRIBUTES,
.name = "Get Attributes" },
{ .value = KMIP_OPERATION_ADD_ATTRIBUTE,
.name = "Add Attribute" },
{ .value = KMIP_OPERATION_DELETE_ATTRIBUTE,
.name = "Delete Attribute" },
{ .value = 0, .name = NULL },
};
static const struct kmip_enum_name required_objtypes[] = {
{ .value = KMIP_OBJECT_TYPE_SYMMETRIC_KEY, .name = "Symmetric Key" },
{ .value = KMIP_OBJECT_TYPE_PUBLIC_KEY, .name = "Public Key" },
{ .value = 0, .name = NULL },
};
static const struct kmip_version kmip_version_1_0 = {
.major = 1, .minor = 0,
};
static const struct kmip_version kmip_version_1_2 = {
.major = 1, .minor = 2,
};
static const struct kmip_enum_name kmip_result_statuses[] = {
{ .value = KMIP_RESULT_STATUS_SUCCESS, .name = "Success" },
{ .value = KMIP_RESULT_STATUS_OPERATION_FAILED,
.name = "Operation Failed" },
{ .value = KMIP_RESULT_STATUS_OPERATION_PENDING,
.name = "Operation Pending" },
{ .value = KMIP_RESULT_STATUS_OPERATION_UNDONE,
.name = "Operation Undone" },
{ .value = 0, .name = NULL },
};
static const struct kmip_enum_name kmip_result_reasons[] = {
{ .value = KMIP_RESULT_REASON_ITEM_NOT_FOUND,
.name = "Item Not Found" },
{ .value = KMIP_RESULT_REASON_RESPONSE_TOO_LARGE,
.name = "Response Too Large" },
{ .value = KMIP_RESULT_REASON_AUTH_NOT_SUCCESSFUL,
.name = "Authentication Not Successful" },
{ .value = KMIP_RESULT_REASON_INVALID_MESSAGE,
.name = "Invalid Message" },
{ .value = KMIP_RESULT_REASON_OPERATION_NOT_SUCCESSFUL,
.name = "Operation Not Supported" },
{ .value = KMIP_RESULT_REASON_MISSING_DATA, .name = "Missing Data" },
{ .value = KMIP_RESULT_REASON_INVALIUD_FIELD, .name = "Invalid Field" },
{ .value = KMIP_RESULT_REASON_FEATURE_NOT_SUPPORTED,
.name = "Feature Not Supported" },
{ .value = KMIP_RESULT_REASON_OP_CANCELED_BY_REQUESTOR,
.name = "Operation Canceled By Requeste" },
{ .value = KMIP_RESULT_REASON_CRYPTOGRAPHIC_FAILURE,
.name = "Cryptographic Failure" },
{ .value = KMIP_RESULT_REASON_ILLEGAL_OPERATION,
.name = "Illegal Operation" },
{ .value = KMIP_RESULT_REASON_PERMISSION_DENIED,
.name = "Permission Denied" },
{ .value = KMIP_RESULT_REASON_OBJECT_ARCHIVED,
.name = "Object Archived" },
{ .value = KMIP_RESULT_REASON_INDEX_OUT_OF_BOUNDS,
.name = "Index Out Of Bounds" },
{ .value = KMIP_RESULT_REASON_APP_NAMESPACE_NOT_SUPPORTED,
.name = "Application Namespace Not Supported" },
{ .value = KMIP_RESULT_REASON_KEY_FORMAT_TYPE_NOT_SUPPORTED,
.name = "Key Format Type Not Supported" },
{ .value = KMIP_RESULT_REASON_KEY_COMPRESSION_TYPE_NOT_SUPPORTED,
.name = "Key Compression Type Not Supported" },
{ .value = KMIP_RESULT_REASON_ENCODING_OPTION_ERROR,
.name = "Encoding Option Error" },
{ .value = KMIP_RESULT_REASON_KEY_VALUE_NOT_PRESENT,
.name = "Key Value Not Present" },
{ .value = KMIP_RESULT_REASON_ATTESTATION_REQUIRED,
.name = "Attestation Required" },
{ .value = KMIP_RESULT_REASON_ATTESTATION_FAILED,
.name = "Attestation Failed" },
{ .value = KMIP_RESULT_REASON_SENSITIVE, .name = "Sensitive" },
{ .value = KMIP_RESULT_REASON_NOT_EXTRACTABLE,
.name = "Not Extractable" },
{ .value = KMIP_RESULT_REASON_OBJECT_ALREADY_EXISTS,
.name = "Object Already Exists" },
{ .value = KMIP_RESULT_REASON_INVALID_TICKET,
.name = "Invalid Ticket" },
{ .value = KMIP_RESULT_REASON_USAGE_LIMIT_EXCEEDED,
.name = "Usage Limit Exceeded" },
{ .value = KMIP_RESULT_REASON_NUMERIC_RANGE, .name = "Numeric Range" },
{ .value = KMIP_RESULT_REASON_INVALID_DATA_TYPE,
.name = "Invalid Data Type" },
{ .value = KMIP_RESULT_REASON_READ_ONLY_ATTRIBUTE,
.name = "Read Only Attribute" },
{ .value = KMIP_RESULT_REASON_MULTI_VALUED_ATTRIBUTE,
.name = "Multi Valued Attribute" },
{ .value = KMIP_RESULT_REASON_UNSUPPORTED_ATTRIBUTE,
.name = "Unsupported Attribute" },
{ .value = KMIP_RESULT_REASON_ATTRIBUTE_INSTANCE_NOT_FOUND,
.name = "Attribute Instance Not Found" },
{ .value = KMIP_RESULT_REASON_ATTRIBUTE_NOT_FOUND,
.name = "Attribute Not Found" },
{ .value = KMIP_RESULT_REASON_ATTRIBUTE_READ_ONLY,
.name = "Attribute Read Only" },
{ .value = KMIP_RESULT_REASON_ATTRIBUTE_SINGLE_VALUED,
.name = "Attribute Single Valued" },
{ .value = KMIP_RESULT_REASON_BAD_CRYPTOGRAPHIC_PARAMETERS,
.name = "Bad Cryptographic Parameters" },
{ .value = KMIP_RESULT_REASON_BAD_PASSWORD, .name = "Bad Password" },
{ .value = KMIP_RESULT_REASON_CODEC_ERROR, .name = "Codec Error" },
{ .value = KMIP_RESULT_REASON_ILLEGAL_OBJECT_TYPE,
.name = "Illegal Object Type" },
{ .value = KMIP_RESULT_REASON_INCOMPATIBLE_CRYPTO_USAGE_MASK,
.name = "Incompatible Cryptographic Usage Mask" },
{ .value = KMIP_RESULT_REASON_INTERNAL_SERVER_ERROR,
.name = "Internal Server Error" },
{ .value = KMIP_RESULT_REASON_INVALID_ASYNC_CORRELATION_VALUE,
.name = "Invalid Asynchronous Correlation Value" },
{ .value = KMIP_RESULT_REASON_INVALID_ATTRIBUTE,
.name = "Invalid Attribute" },
{ .value = KMIP_RESULT_REASON_INVALID_ATTRIBUTE_VALUE,
.name = "Invalid Attribute Value" },
{ .value = KMIP_RESULT_REASON_INVALID_CORRELATION_VALUE,
.name = "Invalid Correlation Value" },
{ .value = KMIP_RESULT_REASON_INVALID_CSR, .name = "Invalid CSR" },
{ .value = KMIP_RESULT_REASON_INVALID_OBJECT_TYPE,
.name = "Invalid Object Type" },
{ .value = KMIP_RESULT_REASON_KEY_WRAP_TYPE_NOT_SUPPORTED,
.name = "Key Wrap Type Not Supported" },
{ .value = KMIP_RESULT_REASON_MISSING_INITIALIZATION_VECTOR,
.name = "Missing Initialization Vector" },
{ .value = KMIP_RESULT_REASON_NOT_UNIQUE_NAME_ATTRIBUTE,
.name = "Non Unique Name Attribute" },
{ .value = KMIP_RESULT_REASON_OBJECT_DESTROYED,
.name = "Object Destroyed" },
{ .value = KMIP_RESULT_REASON_OBJECT_NOT_FOUND,
.name = "Object Not Found" },
{ .value = KMIP_RESULT_REASON_NOT_AUTHORISED,
.name = "Not Authorised" },
{ .value = KMIP_RESULT_REASON_SERVER_LIMIT_EXCEEDED,
.name = "Server Limit Exceeded" },
{ .value = KMIP_RESULT_REASON_UNKNOWN_ENUMERATION,
.name = "Unknown Enumeration" },
{ .value = KMIP_RESULT_REASON_UNKNOWN_MESSAGE_EXTENSION,
.name = "Unknown Message Extension" },
{ .value = KMIP_RESULT_REASON_UNKNOWN_TAG, .name = "Unknown Tag" },
{ .value = KMIP_RESULT_REASON_UNSUPPORTED_CRYPTO_PARAMETERS,
.name = "Unsupported Cryptographic Parameters" },
{ .value = KMIP_RESULT_REASON_UNSUPPORTED_PROTOCOL_VERSION,
.name = "Unsupported Protocol Version" },
{ .value = KMIP_RESULT_REASON_WRAPPING_OBJECT_ARCHIVED,
.name = "Wrapping Object Archived" },
{ .value = KMIP_RESULT_REASON_WRAPPING_OBJECT_DESTROYED,
.name = "Wrapping Object Destroyed" },
{ .value = KMIP_RESULT_REASON_WRAPPING_OBJECT_NOT_FOUND,
.name = "Wrapping Object Not Found" },
{ .value = KMIP_RESULT_REASON_WRONG_KEY_LIFECYCLE_STATE,
.name = "Wrong Key Lifecycle State" },
{ .value = KMIP_RESULT_REASON_PROTECTION_STORAGE_UNAVAILABLE,
.name = "Protection Storage Unavailable" },
{ .value = KMIP_RESULT_REASON_PKCS_11_CODE_ERROR,
.name = "PKCS#11 Codec Error" },
{ .value = KMIP_RESULT_REASON_PKCS_11_INVALID_FUNCTION,
.name = "PKCS#11 Invalid Function" },
{ .value = KMIP_RESULT_REASON_PKCS_11_INVALID_INTERFACE,
.name = "PKCS#11 Invalid Interface" },
{ .value = KMIP_RESULT_REASON_PRIVATE_PROT_STORAGE_UNAVAILABLE,
.name = "Private Protection Storage Unavailable" },
{ .value = KMIP_RESULT_REASON_PUBLIC_PROT_STORAGE_UNAVAILABLE,
.name = "Public Protection Storage Unavailable" },
{ .value = KMIP_RESULT_REASON_UNKNOWN_OBJECT_GROUP,
.name = "Unknown Object Group" },
{ .value = KMIP_RESULT_REASON_CONSTRAINT_VIOLATION,
.name = "Constraint Violation" },
{ .value = KMIP_RESULT_REASON_DUPLICATE_PROCESS_REQUEST,
.name = "Duplicate Process Request" },
{ .value = KMIP_RESULT_REASON_GENERAL_FAILURE,
.name = "General Failure" },
{ .value = 0, .name = NULL },
};
/**
* Informs a KMS plugin that it is bound to a zkey repository.
*
* Note: This function is called before kms_initialize()!
*
* @param config_path name of a directory where the KMS plugin can store
* its configuration and other files it needs to store
*
* @returns 0 on success, or a negative errno in case of an error.
*/
int kms_bind(const char *UNUSED(config_path))
{
return 0;
}
/**
* Checks if the plugin configuration is complete. Sets the appropriate flags
* in the plugin handle
*
* @param ph the plugin handle
*/
static void _check_config_complete(struct plugin_handle *ph)
{
ph->apqns_configured =
plugin_check_property(&ph->pd, KMIP_CONFIG_APQNS) &&
plugin_check_property(&ph->pd, KMIP_CONFIG_APQN_TYPE) &&
ph->card_type != CARD_TYPE_ANY;
ph->identity_key_generated =
plugin_check_property(&ph->pd, KMIP_CONFIG_IDENTITY_KEY) &&
plugin_check_property(&ph->pd,
KMIP_CONFIG_IDENTITY_KEY_ALGORITHM) &&
plugin_check_property(&ph->pd, KMIP_CONFIG_IDENTITY_KEY_PARAMS);
ph->client_cert_avail =
plugin_check_property(&ph->pd,
KMIP_CONFIG_CLIENT_CERTIFICATE) &&
plugin_check_property(&ph->pd,
KMIP_CONFIG_CLIENT_CERT_ALGORITHM);
ph->connection_configured =
plugin_check_property(&ph->pd, KMIP_CONFIG_SERVER) &&
plugin_check_property(&ph->pd, KMIP_CONFIG_SERVER_INFO) &&
plugin_check_property(&ph->pd, KMIP_CONFIG_PROFILE) &&
plugin_check_property(&ph->pd,
KMIP_CONFIG_VERIFY_SERVER_CERT) &&
plugin_check_property(&ph->pd, KMIP_CONFIG_VERIFY_HOSTNAME) &&
plugin_check_property(&ph->pd, KMIP_CONFIG_PROTOCOL_VERSION);
ph->wrapping_key_avail =
plugin_check_property(&ph->pd, KMIP_CONFIG_WRAPPING_KEY) &&
plugin_check_property(&ph->pd,
KMIP_CONFIG_WRAPPING_KEY_ALGORITHM) &&
plugin_check_property(&ph->pd,
KMIP_CONFIG_WRAPPING_KEY_PARAMS) &&
plugin_check_property(&ph->pd, KMIP_CONFIG_WRAPPING_KEY_ID);
ph->config_complete = ph->apqns_configured &&
ph->identity_key_generated &&
ph->client_cert_avail &&
ph->connection_configured &&
ph->wrapping_key_avail;
}
/**
* Returns a textual name of the specified card type.
*
* @param card_type the card type
*
* @returns a constant string, or NULL if an invalid card type is specified
*/
static const char *_card_type_to_str(enum card_type card_type)
{
switch (card_type) {
case CARD_TYPE_CCA:
return KMIP_APQN_TYPE_CCA;
case CARD_TYPE_EP11:
return KMIP_APQN_TYPE_EP11;
default:
return NULL;
}
}
/**
* Returns the card type for the textual name of the card type.
*
* @param card_type the card type as string
*
* @returns the card type value, or CARD_TYPE_ANY if unknown
*/
static enum card_type _card_type_from_str(const char *card_type)
{
if (strcmp(card_type, KMIP_APQN_TYPE_CCA) == 0)
return CARD_TYPE_CCA;
if (strcmp(card_type, KMIP_APQN_TYPE_EP11) == 0)
return CARD_TYPE_EP11;
return CARD_TYPE_ANY;
}
/**
* Unloads the CCA library
*
* @param ph the plugin handle
*/
static void _terminate_cca_library(struct plugin_handle *ph)
{
if (ph->cca_lib.cca_lib != NULL)
dlclose(ph->cca_lib.cca_lib);
ph->cca_lib.cca_lib = NULL;
}
/*
* Sets up the CCA library structure in the handle. Load the CCA library
* and selects one of the associated APQNs.
*
* @param ph the plugin handle
* @param apqns the associated APQNs
*
* @returns 0 on success, a negative errno in case of an error.
*/
static int _setup_cca_library(struct plugin_handle *ph, const char *apqns)
{
int rc;
_terminate_cca_library(ph);
rc = select_cca_adapter_by_apqns(&ph->pd, apqns, &ph->cca);
if (rc != 0) {
_set_error(ph, "Failed to select one of the associated APQNs: "
"%s", apqns);
_terminate_cca_library(ph);
}
ph->cca_lib.cca_lib = ph->cca.lib_csulcca;
return rc;
}
/**
* Unloads the Ep11 library
*
* @param ph the plugin handle
*/
static void _terminate_ep11_library(struct plugin_handle *ph)
{
if (ph->ep11.lib_ep11 == NULL)
return;
if (ph->ep11_lib.target != 0) {
free_ep11_target_for_apqn(&ph->ep11, ph->ep11_lib.target);
ph->ep11_lib.target = 0;
}
ph->ep11_lib.ep11_lib = NULL;
if (ph->ep11.lib_ep11 != NULL)
dlclose(ph->ep11.lib_ep11);
memset(&ph->ep11, 0, sizeof(ph->ep11));
}
/*
* Sets up the EP11 library structure in the handle. Load the EP11 library
* and sets up the EP11 target with the APQNs specified
*
* @param ph the plugin handle
* @param apqns the associated APQNs
*
* @returns 0 on success, a negative errno in case of an error.
*/
static int _setup_ep11_library(struct plugin_handle *ph, const char *apqns)
{
unsigned int card, domain;
bool selected = false;
char **apqn_list;
int rc, i;
rc = load_ep11_library(&ph->ep11, ph->pd.verbose);
if (rc != 0) {
_set_error(ph, "Failed load the EP11 host library");
return rc;
}
apqn_list = str_list_split(apqns);
for (i = 0; apqn_list[i] != NULL; i++) {
if (sscanf(apqn_list[i], "%x.%x", &card, &domain) != 2)
continue;
if (sysfs_is_apqn_online(card, domain, CARD_TYPE_EP11) != 1)
continue;
rc = get_ep11_target_for_apqn(&ph->ep11, card, domain,
&ph->ep11_lib.target,
ph->pd.verbose);
if (rc != 0) {
_set_error(ph, "Failed to get EP11 target for "
"APQN %02x.%04x: %s", card, domain,
strerror(-rc));
goto out;
}
selected = true;
break;
}
if (!selected) {
_set_error(ph, "None of the associated APQNs is "
"available: %s", apqns);
rc = -ENODEV;
goto out;
}
pr_verbose(&ph->pd, "Selected APQN %02x.%04x", card, domain);
ph->ep11_lib.ep11_lib = ph->ep11.lib_ep11;
out:
if (apqn_list != NULL)
str_list_free_string_array(apqn_list);
if (rc != 0)
_terminate_ep11_library(ph);
return rc;
}
/**
* Terminates the external secure key library structure in the handle
* and the OpenSSL secure key interface.
*
* @param ph the plugin handle
*/
static void _terminate_ext_lib(struct plugin_handle *ph)
{
if (ph->ext_lib.type != 0)
SK_OPENSSL_term();
switch (ph->ext_lib.type) {
case SK_EXT_LIB_CCA:
_terminate_cca_library(ph);
ph->ext_lib.cca = NULL;
break;
case SK_EXT_LIB_EP11:
_terminate_ep11_library(ph);
ph->ext_lib.ep11 = NULL;
break;
default:
break;
}
ph->ext_lib.type = 0;
}
/**
* Initializes the external secure key library structure in the handle
* with the information from the associated APQNs. Also initializes the
* OpenSSL secure key interface.
*
* @param ph the plugin handle
*
* @returns 0 on success, a negative errno in case of an error.
*/
static int _setup_ext_lib(struct plugin_handle *ph)
{
char *apqns;
int rc = 0;
if (ph->ext_lib.type != 0)
return 0;
if (!ph->apqns_configured) {
_set_error(ph, "The configuration is incomplete, you must "
"first configure the APQNs used with this plugin.");
return -EINVAL;
}
apqns = properties_get(ph->pd.properties, KMIP_CONFIG_APQNS);
if (apqns == NULL) {
_set_error(ph, "No APQN are associated with the plugin.");
return -ENODEV;
}
pr_verbose(&ph->pd, "Associated APQNs: %s", apqns);
switch (ph->card_type) {
case CARD_TYPE_CCA:
rc = _setup_cca_library(ph, apqns);
if (rc != 0)
goto out;
ph->ext_lib.type = SK_EXT_LIB_CCA;
ph->ext_lib.cca = &ph->cca_lib;
break;
case CARD_TYPE_EP11:
rc = _setup_ep11_library(ph, apqns);
if (rc != 0)
goto out;
ph->ext_lib.type = SK_EXT_LIB_EP11;
ph->ext_lib.ep11 = &ph->ep11_lib;
break;
default:
_set_error(ph, "The configuration is incomplete, you must "
"first configure the APQNs used with this plugin.");
return -EINVAL;
}
rc = SK_OPENSSL_init(ph->pd.verbose);
if (rc != 0)
_terminate_ext_lib(ph);
out:
free(apqns);
return rc;
}
/**
* Gets the client key as PKEY
*
* @param ph the plugin handle
* @param pkey on return: the client key as pkey
*
* @returns a KMS plugin handle, or NULL in case of an error.
*/
static int _get_client_key(struct plugin_handle *ph, EVP_PKEY **pkey)
{
unsigned char identity_key[KMIP_MAX_KEY_TOKEN_SIZE] = { 0 };
size_t identity_key_size = sizeof(identity_key);
bool rsa_pss = false;
char *cert_algo;
int rc;
if (ph->identity_secure_key == NULL)
return -EINVAL;
rc = SK_UTIL_read_key_blob(ph->identity_secure_key, identity_key,
&identity_key_size);
if (rc != 0) {
_set_error(ph, "Failed to load the identity key from '%s': %s",
ph->identity_secure_key, strerror(-rc));
return rc;
}
cert_algo = properties_get(ph->pd.properties,
KMIP_CONFIG_CLIENT_CERT_ALGORITHM);
if (cert_algo != NULL) {
rsa_pss = (strcmp(cert_algo, KMIP_KEY_ALGORITHM_RSA_PSS) == 0);
free(cert_algo);
}
rc = SK_OPENSSL_get_secure_key_as_pkey(identity_key, identity_key_size,
rsa_pss, pkey, &ph->ext_lib,
ph->pd.verbose);
if (rc != 0) {
_set_error(ph, "Failed to get the PKEY from the identity key: "
"%s", strerror(-rc));
return rc;
}
return 0;
}
/**
* Gets the KMIP config structure contents from the plugin properties
*
* @param ph the plugin handle
*
* @returns a KMS plugin handle, or NULL in case of an error.
*/
static int _get_kmip_config(struct plugin_handle *ph)
{
char *tmp;
int rc;
if (!ph->apqns_configured)
return 0;
rc = _setup_ext_lib(ph);
if (rc != 0)
return rc;
if (ph->server == NULL || ph->profile == NULL)
return 0;
rc = _get_client_key(ph, &ph->kmip_config.tls_client_key);
if (rc != 0)
return rc;
ph->kmip_config.transport = ph->profile->transport;
ph->kmip_config.encoding = ph->profile->encoding;
if (strncmp(ph->server, "https://", 8) == 0) {
/* User overrides transport to HTTPS */
ph->kmip_config.transport = KMIP_TRANSPORT_HTTPS;
ph->kmip_config.server = util_strdup(ph->server);
} else if (ph->kmip_config.transport == KMIP_TRANSPORT_HTTPS) {
/* HTTPS selected, but no URL specified: build URL */
util_asprintf((char **)&ph->kmip_config.server, "https://%s%s",
ph->server, ph->profile->https_uri);
} else {
ph->kmip_config.server = util_strdup(ph->server);
}
ph->kmip_config.tls_client_cert = properties_get(ph->pd.properties,
KMIP_CONFIG_CLIENT_CERTIFICATE);
ph->kmip_config.tls_ca = properties_get(ph->pd.properties,
KMIP_CONFIG_CA_BUNDLE);
ph->kmip_config.tls_issuer_cert = NULL;
ph->kmip_config.tls_pinned_pubkey = properties_get(ph->pd.properties,
KMIP_CONFIG_SERVER_PUBKEY);
ph->kmip_config.tls_server_cert = properties_get(ph->pd.properties,
KMIP_CONFIG_SERVER_CERT);
tmp = properties_get(ph->pd.properties, KMIP_CONFIG_VERIFY_SERVER_CERT);
ph->kmip_config.tls_verify_peer =
(tmp != NULL && strcasecmp(tmp, "yes") == 0);
if (tmp != NULL)
free(tmp);
tmp = properties_get(ph->pd.properties, KMIP_CONFIG_VERIFY_HOSTNAME);
ph->kmip_config.tls_verify_host =
(tmp != NULL && strcasecmp(tmp, "yes") == 0);
if (tmp != NULL)
free(tmp);
return 0;
}
/**
* Frees the KMIP config structure contents
*
* @param ph the plugin handle
*/
static void _free_kmip_config(struct plugin_handle *ph)
{
if (ph->kmip_config.server != NULL)
free((void *)ph->kmip_config.server);
if (ph->kmip_config.tls_client_key != NULL)
EVP_PKEY_free(ph->kmip_config.tls_client_key);
if (ph->kmip_config.tls_client_cert != NULL)
free((void *)ph->kmip_config.tls_client_cert);
if (ph->kmip_config.tls_ca != NULL)
free((void *)ph->kmip_config.tls_ca);
if (ph->kmip_config.tls_issuer_cert != NULL)
free((void *)ph->kmip_config.tls_issuer_cert);
if (ph->kmip_config.tls_pinned_pubkey != NULL)
free((void *)ph->kmip_config.tls_pinned_pubkey);
if (ph->kmip_config.tls_server_cert != NULL)
free((void *)ph->kmip_config.tls_server_cert);
if (ph->kmip_config.tls_cipher_list != NULL)
free((void *)ph->kmip_config.tls_cipher_list);
if (ph->kmip_config.tls13_cipher_list != NULL)
free((void *)ph->kmip_config.tls13_cipher_list);
memset(&ph->kmip_config, 0, sizeof(ph->kmip_config));
}
/**
* Initializes a KMS plugin for usage by zkey. When a repository is bound to a
* KMS plugin, zkey calls this function when opening the repository.
*
* @param config_path name of a directory where the KMS plugin can store
* its configuration and other files it needs to store
* @param verbose if true, the plugin should write verbose or debug
* messages to stderr during further processing.
*
* @returns a KMS plugin handle, or NULL in case of an error.
*/
kms_handle_t kms_initialize(const char *config_path, bool verbose)
{
struct plugin_handle *ph;
char *apqn_type = NULL;
char *tmp;
int rc;
util_assert(config_path != NULL, "Internal error: config_path is NULL");
ph = util_malloc(sizeof(struct plugin_handle));
memset(ph, 0, sizeof(struct plugin_handle));
rc = plugin_init(&ph->pd, "zkey-kmip", config_path,
KMIP_CONFIG_FILE, verbose);
if (rc != 0)
goto error;
_check_config_complete(ph);
pr_verbose(&ph->pd, "Plugin configuration is %scomplete",
ph->config_complete ? "" : "in");
ph->card_type = CARD_TYPE_ANY;
apqn_type = properties_get(ph->pd.properties, KMIP_CONFIG_APQN_TYPE);
if (apqn_type != NULL) {
ph->card_type = _card_type_from_str(apqn_type);
if (ph->card_type == CARD_TYPE_ANY) {
pr_verbose(&ph->pd, "APQN type invalid: %s", apqn_type);
free(apqn_type);
goto error;
}
free(apqn_type);
}
ph->identity_secure_key = properties_get(ph->pd.properties,
KMIP_CONFIG_IDENTITY_KEY);
ph->server = properties_get(ph->pd.properties, KMIP_CONFIG_SERVER);
tmp = properties_get(ph->pd.properties, KMIP_CONFIG_PROFILE);
if (tmp != NULL) {
rc = profile_find_by_name(ph, tmp, &ph->profile);
free(tmp);
if (rc != 0)
goto error;
}
tmp = properties_get(ph->pd.properties, KMIP_CONFIG_PROTOCOL_VERSION);
if (tmp != NULL &&
strcmp(tmp, KMIP_CONFIG_PROTOCOL_VERSION_PROFILE) != 0) {
if (sscanf(tmp, "%u.%u", &ph->kmip_version.major,
&ph->kmip_version.minor) != 2) {
_set_error(ph, "Invalid value for '%s': '%s'",
KMIP_CONFIG_PROTOCOL_VERSION, tmp);
rc = -EINVAL;
free(tmp);
goto error;
}
}
if (tmp != NULL)
free(tmp);
rc = _get_kmip_config(ph);
if (rc != 0)
goto error;
return (kms_handle_t)ph;
error:
if (strlen(ph->pd.error_msg) > 0)
warnx("%s", ph->pd.error_msg);
kms_terminate(ph);
return NULL;
}
/**
* Terminates the use of a KMS plugin. When a repository is bound to a KMS
* plugin, zkey calls this function when closing the repository.
*
* @param handle the KMS plugin handle obtained from kms_initialize()
*
* @returns 0 on success, or a negative errno in case of an error.
* Function kms_get_last_error() can be used to obtain more details about the
* error.
*/
int kms_terminate(const kms_handle_t handle)
{
struct plugin_handle *ph = handle;
util_assert(handle != NULL, "Internal error: handle is NULL");
pr_verbose(&ph->pd, "Plugin terminating");
if (ph->connection != NULL)
kmip_connection_free(ph->connection);
_free_kmip_config(ph);
if (ph->identity_secure_key != NULL)
free((void *)ph->identity_secure_key);
if (ph->server != NULL)
free((void *)ph->server);
if (ph->profile != NULL)
profile_free(ph->profile);
_terminate_ext_lib(ph);
plugin_term(&ph->pd);
free(ph);
return 0;
}
/**
* Returns a textual message about the last occurred error that occurred in the
* last called KMS plugin function. If no error occurred (i.e. the last plugin
* function returned rc = 0), then NULL is returned.
* The returned string is static or contained within the handle. It is valid
* only until the next KMS plugin function is called.
*
* @param handle the KMS plugin handle obtained from kms_initialize()
*
* @returns an error message of NULL
*/
const char *kms_get_last_error(const kms_handle_t handle)
{
struct plugin_handle *ph = handle;
util_assert(handle != NULL, "Internal error: handle is NULL");
pr_verbose(&ph->pd, "Last error: '%s'", ph->pd.error_msg);
if (strlen(ph->pd.error_msg) == 0)
return NULL;
return ph->pd.error_msg;
}
/**
* Returns true if the KMS plugin supports the specified key type.
*
* @param handle the KMS plugin handle obtained from kms_initialize()
* @param key_type the zkey key type, euch as 'CCA-AESDATA',
* 'CCA-AESCIPHER', 'EP11-AES'.
*
* @returns true if the KMS plugin supports the key type, false otherwise.
*/
bool kms_supports_key_type(const kms_handle_t handle,
const char *key_type)
{
struct plugin_handle *ph = handle;
util_assert(handle != NULL, "Internal error: handle is NULL");
util_assert(key_type != NULL, "Internal error: key_type is NULL");
plugin_clear_error(&ph->pd);
switch (ph->card_type) {
case CARD_TYPE_CCA:
if (strcasecmp(key_type, KEY_TYPE_CCA_AESDATA) == 0)
return true;
if (strcasecmp(key_type, KEY_TYPE_CCA_AESCIPHER) == 0)
return true;
break;
case CARD_TYPE_EP11:
if (strcasecmp(key_type, KEY_TYPE_EP11_AES) == 0)
return true;
break;
default:
if (strcasecmp(key_type, KEY_TYPE_CCA_AESDATA) == 0)
return true;
if (strcasecmp(key_type, KEY_TYPE_CCA_AESCIPHER) == 0)
return true;
if (strcasecmp(key_type, KEY_TYPE_EP11_AES) == 0)
return true;
break;
}
return false;
}
/**
* Displays information about the KMS Plugin and its current configuration on
* stdout.
*
* @param handle the KMS plugin handle obtained from kms_initialize()
*
* @returns 0 on success, or a negative errno in case of an error.
* Function kms_get_last_error() can be used to obtain more details about the
* error.
*/
int kms_display_info(const kms_handle_t handle)
{
struct plugin_handle *ph = handle;
X509 *cert = NULL;
char *tmp = NULL;
bool rsa;
BIO *b;
util_assert(handle != NULL, "Internal error: handle is NULL");
pr_verbose(&ph->pd, "Display Info");
plugin_clear_error(&ph->pd);
tmp = properties_get(ph->pd.properties,
KMIP_CONFIG_IDENTITY_KEY_ALGORITHM);
if (tmp != NULL) {
printf(" Identity key: %s", tmp);
rsa = strcmp(tmp, KMIP_KEY_ALGORITHM_RSA) == 0;
free(tmp);
tmp = properties_get(ph->pd.properties,
KMIP_CONFIG_IDENTITY_KEY_PARAMS);
if (tmp != NULL) {
printf(" (%s%s)", tmp, rsa ? " bits" : "");
free(tmp);
}
printf("\n");
} else {
printf(" Identity key: (configuration required)\n");
}
tmp = properties_get(ph->pd.properties, KMIP_CONFIG_IDENTITY_KEY_REENC);
if (tmp != NULL) {
printf(" (re-enciphering pending)\n");
free(tmp);
}
tmp = properties_get(ph->pd.properties,
KMIP_CONFIG_CLIENT_CERTIFICATE);
if (tmp != NULL) {
SK_UTIL_read_x509_certificate(tmp, &cert);
free(tmp);
if (cert != NULL) {
b = BIO_new_fp(stdout, BIO_NOCLOSE);
BIO_printf(b,
" Client certificate: Subject:\n");
X509_NAME_print_ex(b, X509_get_subject_name(cert), 26,
XN_FLAG_SEP_MULTILINE);
BIO_printf(b,
"\n Issuer:\n");
X509_NAME_print_ex(b, X509_get_issuer_name(cert), 26,
XN_FLAG_SEP_MULTILINE);
BIO_printf(b, "\n Validity:\n");
BIO_printf(b,
" Not before: ");
ASN1_TIME_print(b, X509_get0_notBefore(cert));
BIO_printf(b,
"\n Not after: ");
ASN1_TIME_print(b, X509_get0_notAfter(cert));
BIO_printf(b,
"\n Serial Number: ");
i2a_ASN1_INTEGER(b, X509_get0_serialNumber(cert));
BIO_printf(b, "\n");
BIO_free(b);
X509_free(cert);
} else {
printf(" Client certificate: (error)\n");
}
} else {
printf(" Client certificate: (configuration required)\n");
return 0;
}
tmp = properties_get(ph->pd.properties, KMIP_CONFIG_SERVER);
printf(" KMIP server: %s\n", tmp != NULL ? tmp :
"(configuration required)");
if (tmp != NULL)
free(tmp);
else
return 0;
tmp = properties_get(ph->pd.properties, KMIP_CONFIG_SERVER_INFO);
if (tmp != NULL) {
printf(" KMIP server info: %s\n", tmp);
free(tmp);
}
tmp = properties_get(ph->pd.properties, KMIP_CONFIG_PROFILE);
printf(" KMIP plugin profile: %s\n", tmp != NULL ? tmp :
"(configuration required)");
if (tmp != NULL)
free(tmp);
else
return 0;
if (ph->kmip_version.major != 0)
printf(" KMIP version: %u.%u\n",
ph->kmip_version.major, ph->kmip_version.minor);
else if (ph->profile != NULL && ph->profile->kmip_version.major != 0)
printf(" KMIP version: %u.%u (from profile)\n",
ph->profile->kmip_version.major,
ph->profile->kmip_version.minor);
else
printf(" KMIP version: (configuration required)\n");
tmp = properties_get(ph->pd.properties, KMIP_CONFIG_CA_BUNDLE);
printf(" CA-bundle: %s\n", tmp != NULL ? tmp :
"System's CA certificates");
if (tmp != NULL)
free(tmp);
tmp = properties_get(ph->pd.properties, KMIP_CONFIG_SERVER_CERT);
if (tmp != NULL) {
printf(" Trusting the server certificate\n");
free(tmp);
}
tmp = properties_get(ph->pd.properties, KMIP_CONFIG_SERVER_PUBKEY);
if (tmp != NULL) {
printf(" Using server public key pinning\n");
free(tmp);
}
tmp = properties_get(ph->pd.properties, KMIP_CONFIG_VERIFY_SERVER_CERT);
if (tmp != NULL && strcasecmp(tmp, "yes") == 0)
printf(" The server's certificate must be valid\n");
else
printf(" The server's certificate is not verified\n");
if (tmp != NULL)
free(tmp);
tmp = properties_get(ph->pd.properties, KMIP_CONFIG_VERIFY_HOSTNAME);
if (tmp != NULL) {
if (strcasecmp(tmp, "yes") == 0)
printf(" The server's certificate must match the "
"hostname\n");
free(tmp);
}
if (ph->profile != NULL) {
switch (ph->profile->auth_scheme) {
case KMIP_PROFILE_AUTH_TLS_CLIENT_CERT:
printf(" Authentication: TLS Client "
"Authentication\n");
break;
default:
printf(" Authentication: (unknown)\n");
break;
}
}
tmp = properties_get(ph->pd.properties, KMIP_CONFIG_WRAPPING_KEY_ID);
if (tmp != NULL) {
printf(" Wrapping key ID: %s\n", tmp);
free(tmp);
tmp = properties_get(ph->pd.properties,
KMIP_CONFIG_WRAPPING_KEY_LABEL);
if (tmp != NULL) {
printf(" Wrapping key label: %s\n", tmp);
free(tmp);
}
tmp = properties_get(ph->pd.properties,
KMIP_CONFIG_WRAPPING_KEY_ALGORITHM);
printf(" Wrapping algorithm: %s", tmp);
rsa = strcmp(tmp, KMIP_KEY_ALGORITHM_RSA) == 0;
free(tmp);
tmp = properties_get(ph->pd.properties,
KMIP_CONFIG_WRAPPING_KEY_PARAMS);
if (tmp != NULL) {
printf(" (%s%s)", tmp, rsa ? " bits" : "");
free(tmp);
}
if (ph->profile != NULL) {
switch (ph->profile->wrap_padding_method) {
case KMIP_PADDING_METHOD_PKCS_1_5:
printf(" with PKCS 1.5 padding");
break;
case KMIP_PADDING_METHOD_OAEP:
printf(" with OAEP padding");
switch (ph->profile->wrap_hashing_algo) {
case KMIP_HASHING_ALGO_SHA_1:
printf(" using SHA-1");
break;
case KMIP_HASHING_ALGO_SHA_256:
printf(" using SHA-256");
break;
default:
break;
}
break;
default:
break;
}
}
printf("\n");
tmp = properties_get(ph->pd.properties,
KMIP_CONFIG_WRAPPING_KEY_REENC);
if (tmp != NULL) {
printf(
" (re-enciphering pending)\n");
free(tmp);
}
} else {
printf(" Wrapping key ID: (configuration required)\n");
}
return 0;
}
#define OPT_TLS_PIN_SERVER_PUBKEY 256
#define OPT_TLS_TRUST_SERVER_CERT 257
#define OPT_TLS_DONT_VERIFY_SERVER_CERT 258
#define OPT_TLS_VERIFY_HOSTNAME 259
static const struct util_opt configure_options[] = {
{
.flags = UTIL_OPT_FLAG_SECTION,
.desc = "KMIP SPECIFIC OPTIONS FOR IDENTITY KEY GENERATION",
.command = KMS_COMMAND_CONFIGURE,
},
{
.option = { "gen-identity-key", required_argument, NULL, 'i'},
.argument = "KEY-SPEC",
.desc = "Generates an identity key for the KMIP plugin. The "
"identity key is a secure ECC or RSA key. The identity "
"key is automatically generated with the default "
"values ECC with curve secp521r1 when a certificate "
"signing request (CSR) or self-signed certificate is "
"to be generated and no identity key is available. Use "
"this option to generate or regenerate a new identity "
"key with with specific parameters. You need to "
"regenerate a certificate with the newly generated "
"identity key and reregister this certificate with the "
"KMIP server.",
.command = KMS_COMMAND_CONFIGURE,
},
{
.flags = UTIL_OPT_FLAG_SECTION,
.desc = "KMIP SPECIFIC OPTIONS FOR CERTIFICATE GENERATION",
.command = KMS_COMMAND_CONFIGURE,
},
{
.option = { "gen-csr", required_argument, NULL, 'c'},
.argument = "CSR-PEM-FILE",
.desc = "Generates a certificate signing request (CSR) with "
"the identity key and stores it in the specified PEM "
"file. Pass this CSR to a certificate authority (CA) "
"to request a CA-signed certificate for the KMIP "
"plugin. You need to register the certificate with the "
"KMIP server. Registering a client certificate with "
"the KMIP server is a manual procedure, and is "
"specific to the KMIP server used. The KMIP server "
"accepts communication with the KMIP plugin only after "
"the certificate was registered. You must also specify "
"the CA-signed certificate with the 'zkey kms "
"configure --client-cert' option so that the KMIP "
"plugin uses it for communicating with the KMIP."
"server.",
.command = KMS_COMMAND_CONFIGURE,
},
{
.option = { "gen-self-signed-cert", required_argument, NULL,
'C'},
.argument = "CERT-PEM-FILE",
.desc = "Generates a self-signed certificate with the "
"identity key and stores it in the specified PEM "
"file. You need to register the certificate with the "
"KMIP server. Registering a client certificate with "
"the KMIP server is a manual procedure, and is "
"specific to the KMIP server used. The KMIP server "
"accepts communication with the KMIP plugin only after "
"the certificate was registered.",
.command = KMS_COMMAND_CONFIGURE,
},
{
.option = { "cert-subject", required_argument, NULL, 's'},
.argument = "SUBJECT-RDNS",
.desc = "Specifies the subject name for generating a "
"certificate signing request (CSR) or self-signed "
"certificate, in the form '<type>=<value>(;<type>="
"<value>)*[;]' with types recognized by OpenSSL.",
.command = KMS_COMMAND_CONFIGURE,
},
{
.option = { "cert-extensions", required_argument, NULL, 'e'},
.argument = "EXTENSIONS",
.desc = "Specifies the certificate extensions for generating a "
"certificate signing request (CSR) or self-signed "
"certificate, in the form '<name>=[critical,]<value(s)>"
" (;<name>=[critical,]<value(s)>)*[;]' with extension "
"names and values recognized by OpenSSL. A certificate "
"used to authenticate at a KMIP server usually needs "
"the 'TLS Web client authentication' extended-key-"
"usage certificate extension. Additionally, the "
"'Common Name' field or the 'Subject Alternate Name' "
"extension must match the host name (or IP address) of "
"the client system. If no extended-key-usage extension "
"is specified, then a 'TLS Web client authentication' "
"extension ('extendedKeyUsage = clientAuth') is "
"automatically added. If no 'Subject Alternate Name' "
"extension is specified, then an 'Subject Alternate "
"Name' extension with the system's host name "
"(subjectAltName = DNS:hostname) is automatically "
"added.",
.command = KMS_COMMAND_CONFIGURE,
},
{
.option = { "renew-cert", required_argument, NULL, 'N'},
.argument = "CERT-PEM-FILE",
.desc = "Specifies an existing PEM file that contains the "
"certificate to be renewed. The subject name and "
"extensions of the certificate are used to generate "
"the certificate signing request (CSR) or renewed "
"self-signed certificate.",
.command = KMS_COMMAND_CONFIGURE,
},
{
.option = { "csr-new-header", 0, NULL, 'n'},
.desc = "Adds the word 'NEW' to the PEM file header and footer "
"lines on the certificate signing request. Some "
"software and some CAs require this marking.",
.command = KMS_COMMAND_CONFIGURE,
},
{
.option = { "cert-validity-days", required_argument, NULL, 'd'},
.argument = "DAYS",
.desc = "Specifies the number of days the self-signed "
"certificate is valid. The default is 30 days.",
.command = KMS_COMMAND_CONFIGURE,
},
{
.option = { "cert-digest", required_argument, NULL, 'D'},
.argument = "DIGEST",
.desc = "Specifies the digest algorithm to use when generating "
"a certificate signing request or self-signed "
"certificate. The default is determined by OpenSSL.",
.command = KMS_COMMAND_CONFIGURE,
},
{
.option = { "cert-rsa-pss", 0, NULL, 'P'},
.desc = "Uses the RSA-PSS algorithm to sign the certificate "
"signing request or the self-signed certificate. This "
"option is accepted only when the identity key type is "
"RSA, it is ignored otherwise.",
.command = KMS_COMMAND_CONFIGURE,
},
{
.flags = UTIL_OPT_FLAG_SECTION,
.desc = "KMIP SPECIFIC OPTIONS FOR CERTIFICATE REGISTRATION",
.command = KMS_COMMAND_CONFIGURE,
},
{
.option = { "client-cert", required_argument, NULL,
'r'},
.argument = "CERT-PEM-FILE",
.desc = "Uses a CA-signed certificate for authenticating the "
"KMIP plugin at the KMIP server. The certificate must "
"be registered with the KMIP server. Registering a "
"client certificate with the KMIP server is a manual "
"procedure, and is specific to the KMIP server used. "
"The KMIP server accepts communication with the KMIP "
"plugin only after the certificate has been "
"registered.",
.command = KMS_COMMAND_CONFIGURE,
},
{
.flags = UTIL_OPT_FLAG_SECTION,
.desc = "KMIP SPECIFIC OPTIONS FOR THE SERVER CONNECTION",
.command = KMS_COMMAND_CONFIGURE,
},
{
.option = { "kmip-server", required_argument, NULL, 'S'},
.argument = "KMIP-SERVER",
.desc = "Specifies the hostname or IP address of the KMIP "
"server, and an optional port number separated by a "
"colon. If no port number is specified, 5696 is used "
"for KMIP. To use HTTPS transport, specify the URL, "
"starting with 'https://', followed by the hostname or "
"IP address of the KMIP server, an optional port "
"number, and an URI (for example '/kmip').",
.command = KMS_COMMAND_CONFIGURE,
},
{
.option = { "profile", required_argument, NULL, 'p'},
.argument = "PROFILE-NAME",
.desc = "Specifies the name of the KMIP plugin profile to use "
"with the KMIP server connection. If no profile name "
"is specified, the KMIP plugin queries the KMIP server "
"information and attempts to match a profile to the "
"information. If no profile matches, the default "
"profile is used. Profiles are contained in the "
"directory '/etc/zkey/kmip/profiles'. You can set the "
"location of the profiles by using the environment "
"variable 'ZKEY_KMIP_PROFILES'.",
.command = KMS_COMMAND_CONFIGURE,
},
{
.option = { "tls-ca-bundle", required_argument, NULL, 'b'},
.argument = "CA-BUNDLE",
.desc = "Specifies the CA-bundle PEM file or directory "
"containing the CA certificates that are used to "
"verify the KMIP server certificate during TLS "
"handshake. If the option specifies a directory path, "
"the directory must have been prepared with the "
"'c_rehash' utility of OpenSSL. Default is to use the "
"system CA certificates.",
.command = KMS_COMMAND_CONFIGURE,
},
{
.option = { "tls-pin-server-pubkey", 0, NULL,
OPT_TLS_PIN_SERVER_PUBKEY },
.flags = UTIL_OPT_FLAG_NOSHORT,
.desc = "Pins the public key of the KMIP server. With a pinned "
"key, the KMIP plugin verifies that every connection "
"uses the same KMIP server-certificate public key that "
"was also used to configure the connection to the KMIP "
"server. This option can be used only with CA-signed "
"KMIP server certificates.",
.command = KMS_COMMAND_CONFIGURE,
},
{
.option = { "tls-trust-server-cert", 0, NULL,
OPT_TLS_TRUST_SERVER_CERT },
.flags = UTIL_OPT_FLAG_NOSHORT,
.desc = "Trusts the certificate of the KMIP server even if it "
"is a self-signed certificate, or it can not be "
"verified due to other reasons. Use this option "
"instead of the '--tls-pin-server-pubkey' option when "
"you are using self-signed KMIP server certificates.",
.command = KMS_COMMAND_CONFIGURE,
},
{
.option = { "tls-dont-verify-server-cert", 0, NULL,
OPT_TLS_DONT_VERIFY_SERVER_CERT },
.flags = UTIL_OPT_FLAG_NOSHORT,
.desc = "Do not verify the authenticity of the certificate of "
"the KMIP server. For self-signed KMIP server "
"certificates, this is the default. Use the "
"'--tls-pin-server-cert' option to ensure the "
"authenticity of the self-signed certificate "
"explicitly. For CA-signed KMIP server certificates, "
"the default is to verify them. This option disables "
"the verification.",
.command = KMS_COMMAND_CONFIGURE,
},
{
.option = { "tls-verify-hostname", 0, NULL,
OPT_TLS_VERIFY_HOSTNAME },
.flags = UTIL_OPT_FLAG_NOSHORT,
.desc = "Verifies that the KMIP server certificates 'Common "
"Name' field or a 'Subject Alternate Name' field "
"matches the hostname that is used to connect to the "
"KMIP server.",
.command = KMS_COMMAND_CONFIGURE,
},
{
.flags = UTIL_OPT_FLAG_SECTION,
.desc = "KMIP SPECIFIC OPTIONS FOR WRAPPING KEY GENERATION",
.command = KMS_COMMAND_CONFIGURE,
},
{
.option = { "gen-wrapping-key", 0, NULL, 'w' },
.desc = "Generates a new wrapping key (key-encrypting key) "
"based on the settings in the profile and registers it "
"with the KMIP server. A wrapping key is automatically "
"generated when the KMIP server connection is "
"configured. Use this option to generate a new "
"wrapping key at a later time.",
.command = KMS_COMMAND_CONFIGURE,
},
{
.option = { "label", required_argument, NULL, 'B'},
.argument = "LABEL",
.desc = "Specifies an optional human-readable identifier of "
"the wrapping key stored in the 'Name' KMIP attribute "
"of the key. KMIP names must usually be unique within "
"the KMIP server.",
.command = KMS_COMMAND_CONFIGURE,
},
UTIL_OPT_END,
};
static const struct util_opt generate_options[] = {
{
.flags = UTIL_OPT_FLAG_SECTION,
.desc = "KMIP SPECIFIC OPTIONS",
.command = KMS_COMMAND_GENERATE,
},
{
.option = { "label", required_argument, NULL, 'B'},
.argument = "LABEL[:LABEL]",
.desc = "Specifies an optional human-readable identifier of "
"the key or keys stored in the 'Name' KMIP attribute "
"of the key. KMIP names must be unique within the KMIP "
"server. For XTS type keys, two different labels must "
"be specified, separated by a colon.",
.command = KMS_COMMAND_GENERATE,
},
UTIL_OPT_END,
};
static const struct util_opt remove_options[] = {
{
.flags = UTIL_OPT_FLAG_SECTION,
.desc = "KMIP SPECIFIC OPTIONS",
.command = KMS_COMMAND_REMOVE,
},
{
.option = { "state", required_argument, NULL, 's'},
.argument = "STATE",
.desc = "The state to which to change the key in the KMIP "
"server, after removing the secure key from the local "
"secure key repository. Possible states are "
"'DEACTIVATED', 'COMPROMISED', 'DESTROYED', and "
"'DESTROYED-COMPROMISED'. If this option is not "
"specified, the state of the key in the KMIP server is "
"not changed, but the key is removed from the local "
"secure key repository only.",
.command = KMS_COMMAND_REMOVE,
},
UTIL_OPT_END,
};
static const struct util_opt list_import_options[] = {
{
.flags = UTIL_OPT_FLAG_SECTION,
.desc = "KMIP SPECIFIC OPTIONS",
.command = KMS_COMMAND_LIST_IMPORT,
},
{
.option = { "key-type", required_argument, NULL, 'K'},
.argument = "type",
.desc = "The type of the key to import. Possible values are '"
KEY_TYPE_CCA_AESDATA"', '"KEY_TYPE_CCA_AESCIPHER"' "
"and '"KEY_TYPE_EP11_AES"'. When this option is "
"omitted, the default is '"KEY_TYPE_CCA_AESDATA"' "
"when the KMIP plugin is bound to CCA-type APQNs, or "
"'"KEY_TYPE_EP11_AES"' when the KMIP plugin is bound "
"to EP11-type APQNs.",
.command = KMS_COMMAND_LIST_IMPORT,
},
UTIL_OPT_END,
};
/**
* Returns a list of KMS specific command line options that zkey should accept
* and pass to the appropriate KMS plugin function. The option list must be
* terminated by an UTIL_OPT_END entry (see util_opt.h). The options returned
* must not interfere with the already defined options of the zkey command.
* Field 'command' of the returned options should either be NULL or specify
* the command that it is for.
*
* If max_opts is not -1, then only up to max_opts options are allowed. If more
* options are returned, only up to max_opts options are used by zkey.
*
* @param command the command for which the KMS-specific options are
* to be returned, see KMS_COMMAND_xxx defines
* @param max_opts maximum number of options allowed. If -1 then there
* is no limit.
*
* @returns a list of options terminated by an UTIL_OPT_END entry, or NULL in
* case of an error.
* Function kms_get_last_error() can be used to obtain more details about the
* error.
*/
const struct util_opt *kms_get_command_options(const char *command,
int UNUSED(max_opts))
{
util_assert(command != NULL, "Internal error: command is NULL");
if (strcasecmp(command, KMS_COMMAND_CONFIGURE) == 0)
return configure_options;
if (strcasecmp(command, KMS_COMMAND_GENERATE) == 0)
return generate_options;
if (strcasecmp(command, KMS_COMMAND_REMOVE) == 0)
return remove_options;
if (strcasecmp(command, KMS_COMMAND_LIST_IMPORT) == 0)
return list_import_options;
return NULL;
}
struct config_options {
const char *generate_identity_key;
const char *sscert_pem_file;
const char *csr_pem_file;
const char *cert_subject;
const char *cert_extensions;
const char *renew_cert_pem_file;
bool csr_new_header;
const char *cert_validity_days;
const char *cert_digest;
bool cert_rsa_pss;
const char *client_cert;
const char *kmip_server;
const char *profile;
const char *tls_ca_bundle;
bool tls_pin_server_pubkey;
bool tls_trust_server_cert;
bool tls_dont_verify_server_cert;
bool tls_verify_hostname;
bool gen_wrapping_key;
const char *wrapping_key_label;
};
/**
* Check the specified APQns and assure that they are all of the right type.
*
* @param ph the plugin handle
* @param apqns a list of APQNs to associate with the KMS plugin, or
* NULL if no APQNs are specified.
* @param num_apqns number of APQNs in above array. 0 if no APQNs are
* specified.
*
* @returns 0 on success, a negative errno in case of an error.
*/
static int _check_apqns(struct plugin_handle *ph, const struct kms_apqn *apqns,
size_t num_apqns)
{
size_t i;
int rc;
if (num_apqns == 0)
return 0;
if (ph->card_type == CARD_TYPE_ANY) {
/*
* No APQNs configured yet, accept any APQN type, but all must
* be of the same type.
*/
ph->card_type = sysfs_get_card_type(apqns[0].card);
if (ph->card_type == CARD_TYPE_ANY) {
_set_error(ph, "The APQN %02x.%04x is not available or "
"has an unsupported type", apqns[0].card,
apqns[0].domain);
return -EINVAL;
}
}
pr_verbose(&ph->pd, "Check APQNs for card type %s",
_card_type_to_str(ph->card_type));
for (i = 0; i < num_apqns; i++) {
rc = sysfs_is_apqn_online(apqns[i].card, apqns[i].domain,
ph->card_type);
if (rc != 1) {
_set_error(ph, "APQN %02x.%04x is not of the right "
"type. The plugin is configured to use "
"APQNs of type %s", apqns[i].card,
apqns[i].domain,
_card_type_to_str(ph->card_type));
return -EINVAL;
}
}
return 0;
}
/**
* Parse a key specification and setup the key gen info struct
*
* @param ph the plugin handle
* @param key_spec the key specification (ECC:CURVE or RSA:KEYBITS).
* If NULL, the default key specification is used.
*
* @returns 0 on success, a negative errno in case of an error.
*/
static int _parse_key_spec(struct plugin_handle *ph, const char *key_spec,
struct sk_key_gen_info *gen_info)
{
char *copy = NULL, *algorithm, *params;
int rc = 0;
copy = util_strdup(key_spec);
algorithm = strtok(copy, ":");
if (algorithm == NULL) {
_set_error(ph, "Invalid key specification format: '%s'",
key_spec);
rc = -EINVAL;
goto out;
}
params = strtok(NULL, ":");
if (params == NULL) {
_set_error(ph, "Invalid key specification format: '%s'",
key_spec);
rc = -EINVAL;
goto out;
}
if (strcasecmp(algorithm, KMIP_KEY_ALGORITHM_RSA) == 0) {
gen_info->type = SK_KEY_TYPE_RSA;
} else if (strcasecmp(algorithm, KMIP_KEY_ALGORITHM_ECC) == 0) {
gen_info->type = SK_KEY_TYPE_EC;
} else {
_set_error(ph, "Invalid key algorithm: '%s'", key_spec);
rc = -EINVAL;
goto out;
}
switch (gen_info->type) {
case SK_KEY_TYPE_RSA:
gen_info->rsa.modulus_bits = atol(params);
switch (gen_info->rsa.modulus_bits) {
case 512:
case 1024:
case 2048:
case 4096:
break;
default:
_set_error(ph, "Invalid RSA key bits: '%s'", key_spec);
rc = -EINVAL;
goto out;
}
gen_info->rsa.pub_exp = 65537;
gen_info->rsa.x9_31 = false;
break;
case SK_KEY_TYPE_EC:
gen_info->ec.curve_nid = OBJ_txt2nid(params);
if (gen_info->ec.curve_nid == NID_undef) {
_set_error(ph, "Invalid ECC curve: '%s'", key_spec);
rc = -EINVAL;
goto out;
}
break;
}
out:
free(copy);
return rc;
}
/**
* Generates (or re-generates) a identity key for the plugin using the
* specified key specification, or the default key specifications, of none
* is specified.
*
* @param ph the plugin handle
* @param key_spec the key specification (ECC:CURVE or RSA:KEYBITS).
* If NULL, the default key specification is used.
*
* @returns 0 on success, a negative errno in case of an error.
*/
static int _generate_identity_key(struct plugin_handle *ph,
const char *key_spec)
{
unsigned char identity_key[KMIP_MAX_KEY_TOKEN_SIZE] = { 0 };
size_t identity_key_size = sizeof(identity_key);
char *reenc_file = NULL, *client_cert = NULL;
struct sk_key_gen_info gen_info = { 0 };
char tmp[200];
int rc;
if (key_spec == NULL)
key_spec = KMIP_DEFAULT_IDENTITY_KEY_SPEC;
_check_config_complete(ph);
if (!ph->apqns_configured) {
_set_error(ph, "The configuration is incomplete, you must "
"first configure the APQNs used with this plugin.");
return -EINVAL;
}
rc = _parse_key_spec(ph, key_spec, &gen_info);
if (rc != 0)
return rc;
if (ph->identity_secure_key != NULL) {
printf("ATTENTION: An identity key already exists\n");
util_print_indented("When you generate a new identity key, "
"you must re-generate a certificate and "
"re-register it with the KMIP server.", 0);
printf("%s: Re-generate the identity key [y/N]? ",
program_invocation_short_name);
if (!prompt_for_yes(ph->pd.verbose)) {
_set_error(ph, "Operation aborted by user");
return -ECANCELED;
}
} else {
util_asprintf((char **)&ph->identity_secure_key,
"%s/%s", ph->pd.config_path,
KMIP_CONFIG_IDENTITY_KEY_FILE);
rc = plugin_set_or_remove_property(&ph->pd,
KMIP_CONFIG_IDENTITY_KEY,
ph->identity_secure_key);
if (rc != 0)
goto out;
}
switch (gen_info.type) {
case SK_KEY_TYPE_RSA:
rc = plugin_set_or_remove_property(&ph->pd,
KMIP_CONFIG_IDENTITY_KEY_ALGORITHM,
KMIP_KEY_ALGORITHM_RSA);
if (rc != 0)
goto out;
sprintf(tmp, "%lu", gen_info.rsa.modulus_bits);
rc = plugin_set_or_remove_property(&ph->pd,
KMIP_CONFIG_IDENTITY_KEY_PARAMS,
tmp);
if (rc != 0)
goto out;
break;
case SK_KEY_TYPE_EC:
rc = plugin_set_or_remove_property(&ph->pd,
KMIP_CONFIG_IDENTITY_KEY_ALGORITHM,
KMIP_KEY_ALGORITHM_ECC);
if (rc != 0)
goto out;
rc = plugin_set_or_remove_property(&ph->pd,
KMIP_CONFIG_IDENTITY_KEY_PARAMS,
OBJ_nid2sn(gen_info.ec.curve_nid));
if (rc != 0)
goto out;
break;
default:
break;
}
rc = _setup_ext_lib(ph);
if (rc != 0)
goto out;
rc = SK_OPENSSL_generate_secure_key(identity_key, &identity_key_size,
&gen_info, &ph->ext_lib,
ph->pd.verbose);
if (rc != 0) {
_set_error(ph, "Failed to generate the identity key: %s",
strerror(-rc));
goto out;
}
rc = SK_UTIL_write_key_blob(ph->identity_secure_key, identity_key,
identity_key_size);
if (rc != 0) {
_set_error(ph, "Failed to write the identity key into file "
"'%s': %s", ph->identity_secure_key, strerror(-rc));
goto out;
}
rc = plugin_set_file_permission(&ph->pd, ph->identity_secure_key);
if (rc != 0)
goto out;
reenc_file = properties_get(ph->pd.properties,
KMIP_CONFIG_IDENTITY_KEY_REENC);
if (reenc_file != NULL) {
remove(reenc_file);
free(reenc_file);
properties_remove(ph->pd.properties,
KMIP_CONFIG_IDENTITY_KEY_REENC);
}
client_cert = properties_get(ph->pd.properties,
KMIP_CONFIG_CLIENT_CERTIFICATE);
if (client_cert != NULL) {
remove(client_cert);
free(client_cert);
properties_remove(ph->pd.properties,
KMIP_CONFIG_CLIENT_CERTIFICATE);
properties_remove(ph->pd.properties,
KMIP_CONFIG_CLIENT_CERT_ALGORITHM);
}
pr_verbose(&ph->pd, "Generated identity key into '%s'",
ph->identity_secure_key);
out:
return rc;
}
/**
* Add client authentication specific certificate extensions, if they are not
* already contained. The extension list is reallocated, if required.
* If no extended key usage extension is specified, then an 'TLS Web client
* authentication' extension ('extendedKeyUsage=clientAuth') is added.
* If no 'Subject Alternate Name' extension is specified, then an 'Subject
* Alternate Name' extension with the system's host name (subjectAltName=
* DNS:hostname) is added.
*
* @param ph the plugin handle
* @param extension_list the list of extensions
* @param num_extensions the number of extensions
* @param exts Stack of extensions to add of NULL.
*
* @returns 0 on success, a negative errno in case of an error.
*/
static int _add_client_auth_extensions(struct plugin_handle *ph,
char ***extension_list,
size_t *num_extensions,
const STACK_OF(X509_EXTENSION) *exts)
{
bool keyusage_found = false;
bool altname_found = false;
struct utsname utsname;
int rc, count, k, nid;
X509_EXTENSION *ex;
size_t elements;
char **list;
size_t i;
for (i = 0; i < *num_extensions; i++) {
if (strncmp((*extension_list)[i], KMIP_CERT_EXT_KEY_USAGE,
strlen(KMIP_CERT_EXT_KEY_USAGE)) == 0)
keyusage_found = true;
if (strncmp((*extension_list)[i],
KMIP_CERT_EXT_SUBJECT_ALT_NAME,
strlen(KMIP_CERT_EXT_SUBJECT_ALT_NAME)) == 0)
altname_found = true;
}
if (exts != NULL) {
count = sk_X509_EXTENSION_num(exts);
for (k = 0; k < count; k++) {
ex = sk_X509_EXTENSION_value(exts, k);
nid = OBJ_obj2nid(X509_EXTENSION_get_object(ex));
switch (nid) {
case NID_subject_alt_name:
altname_found = true;
break;
case NID_ext_key_usage:
keyusage_found = true;
break;
default:
break;
}
}
}
if (keyusage_found && altname_found)
return 0;
elements = *num_extensions;
if (!keyusage_found)
elements++;
if (!altname_found) {
elements++;
if (uname(&utsname) != 0) {
rc = -errno;
_set_error(ph, "Failed to obtain the system's "
"hostname: %s", strerror(-rc));
return rc;
}
}
list = util_realloc(*extension_list, elements * sizeof(char *));
i = 0;
if (!keyusage_found) {
list[*num_extensions + i] =
util_strdup(KMIP_CERT_EXT_KEY_USAGE_CLIENT_AUTH);
i++;
}
if (!altname_found) {
list[*num_extensions + i] = NULL;
util_asprintf(&list[*num_extensions + i],
KMIP_CERT_EXT_SUBJECT_ALT_NAME_DNS,
utsname.nodename);
i++;
}
*extension_list = list;
*num_extensions = elements;
return 0;
}
/**
* Generates certificate signing request or self-signed certificate using the
* identity key
*
* @param ph the plugin handle
* @param csr_pem_file name of the PEM file to store a CSR to. NULL if no
* CSR is to be generated.
* @param sscert_pem_file name of the PEM file to store a self-signed
* certificate to. NULL if no certificate is to be
* generated.
* @param subject the subject RNDs separated by semicolon (;). Can be
* NULL if a renew certificate is specified.
* @param extensions the extensions separated by semicolon (;). Can be
* NULL.
* @param renew_cert_pem_file name of a PEM file containing a certificate to
* renew. Can be NULL.
* @param csr_new_header if true output NEW header and footer lines in CSR
* @param validity_days the number of days the certificate is valid. Only
* valid when generating a self-signed certificate.
* Can be NULL.
* @param digest the digest to use with CSR and certificates. Can be
* NULL
* @param rsa_pss if true, RSA-PSS is used with RSA-based identity
* keys
*
* @returns 0 on success, a negative errno in case of an error.
*/
static int _generate_csr_sscert(struct plugin_handle *ph,
const char *csr_pem_file,
const char *sscert_pem_file,
const char *subject, const char *extensions,
const char *renew_cert_pem_file,
bool csr_new_header, const char *validity_days,
const char *digest, bool rsa_pss)
{
struct sk_rsa_pss_params rsa_pss_parms = {
.salt_len = RSA_PSS_SALTLEN_DIGEST, .mgf_digest_nid = 0 };
unsigned char identity_key[KMIP_MAX_KEY_TOKEN_SIZE] = { 0 };
size_t identity_key_size = sizeof(identity_key);
char **subject_rdn_list = NULL;
char **extension_list = NULL;
size_t num_subject_rdns = 0;
int digest_nid = NID_undef;
size_t num_extensions = 0;
char *client_cert = NULL;
X509 *renew_cert = NULL;
const char *cert_algo;
X509_REQ *csr = NULL;
X509 *ss_cert = NULL;
int days = 30;
int rc = 0;
size_t i;
_check_config_complete(ph);
if (!ph->apqns_configured) {
_set_error(ph, "The configuration is incomplete, you must "
"first configure the APQNs used with this plugin.");
return -EINVAL;
}
if (!ph->identity_key_generated) {
_set_error(ph, "The configuration is incomplete, you must "
"first generate the identity key.");
return -EINVAL;
}
if (csr_pem_file != NULL && sscert_pem_file != NULL) {
_set_error(ph, "Either '--gen-csr' or option "
"'--gen-self-signed-cert' can be specified.");
return -EINVAL;
}
if (csr_new_header && csr_pem_file == NULL) {
_set_error(ph, "Option '--csr-new-header' is only valid with "
"option '--gen-csr'.");
return -EINVAL;
}
if (validity_days != NULL && sscert_pem_file == NULL) {
_set_error(ph, "Option '--cert-validity-days' is only valid "
"with option '--gen-self-signed-cert'.");
return -EINVAL;
}
if (subject == NULL && renew_cert_pem_file == NULL) {
_set_error(ph, "Option '--cert-subject' is required, unless "
" option '--renew-cert' is specified.");
return -EINVAL;
}
if (sscert_pem_file != NULL && ph->client_cert_avail) {
printf("ATTENTION: A client certificate already exists\n");
util_print_indented("When you generate a new client "
"certificate, the existing certificate is "
"removed and must re-register the newly "
"created certificate with the KMIP server "
"and the KMIP plugin before you can "
"communicate with the KMIP server", 0);
printf("%s: Re-generate the client certificate [y/N]? ",
program_invocation_short_name);
if (!prompt_for_yes(ph->pd.verbose)) {
_set_error(ph, "Operation aborted by user");
return -ECANCELED;
}
}
if (validity_days != NULL) {
days = atoi(validity_days);
if (days <= 0) {
_set_error(ph, "Invalid validity days: '%s'",
validity_days);
return -EINVAL;
}
}
if (digest != NULL) {
digest_nid = OBJ_txt2nid(digest);
if (digest_nid == NID_undef) {
_set_error(ph, "Invalid digest: '%s'", digest);
return -EINVAL;
}
}
if (subject != NULL) {
rc = parse_list(subject, &subject_rdn_list, &num_subject_rdns);
if (rc != 0)
goto out;
}
if (extensions != NULL) {
rc = parse_list(extensions, &extension_list, &num_extensions);
if (rc != 0)
goto out;
}
if (renew_cert_pem_file != NULL) {
rc = SK_UTIL_read_x509_certificate(renew_cert_pem_file,
&renew_cert);
if (rc != 0) {
_set_error(ph, "Failed to load the renew certificate "
"from'%s'", renew_cert_pem_file);
goto out;
}
}
rc = _add_client_auth_extensions(ph, &extension_list, &num_extensions,
renew_cert != NULL ?
X509_get0_extensions(renew_cert)
: NULL);
if (rc != 0)
goto out;
rc = _setup_ext_lib(ph);
if (rc != 0)
goto out;
rc = SK_UTIL_read_key_blob(ph->identity_secure_key, identity_key,
&identity_key_size);
if (rc != 0) {
_set_error(ph, "Failed to load the identity key from '%s': %s",
ph->identity_secure_key, strerror(-rc));
goto out;
}
if (csr_pem_file != NULL) {
rc = SK_OPENSSL_generate_csr(identity_key, identity_key_size,
(const char **)subject_rdn_list,
num_subject_rdns, true, renew_cert,
(const char **)extension_list,
num_extensions, digest_nid,
rsa_pss ? &rsa_pss_parms : NULL,
&csr, &ph->ext_lib, ph->pd.verbose);
} else {
rc = SK_OPENSSL_generate_ss_cert(identity_key,
identity_key_size,
(const char **)subject_rdn_list,
num_subject_rdns, true,
renew_cert,
(const char **)extension_list,
num_extensions, days,
digest_nid,
rsa_pss ? &rsa_pss_parms :
NULL,
&ss_cert, &ph->ext_lib,
ph->pd.verbose);
}
switch (rc) {
case 0:
break;
case -EBADMSG:
_set_error(ph, "The subject or extensions could not be parsed "
"or are not recognized by OpenSSL.");
rc = -EINVAL;
goto out;
case -EEXIST:
_set_error(ph, "One of the subject name entries or extensions "
"is a duplicate.");
rc = -EINVAL;
goto out;
case -ENOTSUP:
_set_error(ph, "The specified digest is not supported.");
rc = -EINVAL;
goto out;
default:
_set_error(ph, "Failed to generate the %s: %s",
csr_pem_file != NULL ? "certificate signing request"
: "self-signed certificate",
strerror(-rc));
goto out;
}
if (csr_pem_file != NULL) {
rc = SK_UTIL_write_x509_request(csr_pem_file, csr,
csr_new_header);
if (rc != 0) {
_set_error(ph, "Failed to write the certificate "
"signing request to '%s'", csr_pem_file);
goto out;
}
pr_verbose(&ph->pd, "Generated certificate signing request "
"into '%s'", csr_pem_file);
} else {
switch (EVP_PKEY_id(X509_get0_pubkey(ss_cert))) {
case EVP_PKEY_RSA:
cert_algo = KMIP_KEY_ALGORITHM_RSA;
break;
case EVP_PKEY_RSA_PSS:
cert_algo = KMIP_KEY_ALGORITHM_RSA_PSS;
rsa_pss = true;
break;
case EVP_PKEY_EC:
cert_algo = KMIP_KEY_ALGORITHM_ECC;
break;
default:
_set_error(ph, "Unsupported certificate algorithm");
rc = -EINVAL;
goto out;
}
rc = SK_UTIL_write_x509_certificate(sscert_pem_file, ss_cert);
if (rc != 0) {
_set_error(ph, "Failed to write the self-signed "
"certificate to '%s'", sscert_pem_file);
goto out;
}
util_asprintf(&client_cert, "%s/%s", ph->pd.config_path,
KMIP_CONFIG_CLIENT_CERTIFICATE_FILE);
rc = plugin_set_or_remove_property(&ph->pd,
KMIP_CONFIG_CLIENT_CERTIFICATE, client_cert);
if (rc != 0)
goto out;
rc = plugin_set_or_remove_property(&ph->pd,
KMIP_CONFIG_CLIENT_CERT_ALGORITHM, cert_algo);
if (rc != 0)
goto out;
rc = SK_UTIL_write_x509_certificate(client_cert, ss_cert);
if (rc != 0) {
_set_error(ph, "Failed to write the self-signed "
"certificate to '%s'", client_cert);
goto out;
}
pr_verbose(&ph->pd, "Generated self-signed certificate into "
"'%s' and '%s'", sscert_pem_file, client_cert);
}
out:
if (subject_rdn_list != NULL) {
for (i = 0; i < num_subject_rdns; i++)
free(subject_rdn_list[i]);
free(subject_rdn_list);
}
if (extension_list != NULL) {
for (i = 0; i < num_extensions; i++)
free(extension_list[i]);
free(extension_list);
}
if (renew_cert != NULL)
X509_free(renew_cert);
if (ss_cert != NULL)
X509_free(ss_cert);
if (csr != NULL)
X509_REQ_free(csr);
if (client_cert != NULL)
free(client_cert);
return rc;
}
/**
* Checks that none of the options for generating a CSR or self-signed
* certificate is specified, and sets up the error message and return code if
* so.
*
* @param ph the plugin handle
* @param opts the config options structure
*
* @returns 0 on success, a negative errno in case of an error.
*/
static int _error_gen_csr_sscert_opts(struct plugin_handle *ph,
struct config_options *opts)
{
int rc = 0;
if (opts->cert_subject != NULL) {
_set_error(ph, "Option '--cert-subject' is only valid "
"together with options '--gen-csr' or "
"'--gen-self-signed-cert'.");
rc = -EINVAL;
goto out;
}
if (opts->cert_extensions != NULL) {
_set_error(ph, "Option '--cert-extensions' is only "
"valid together with options '--gen-csr' or "
"'--gen-self-signed-cert'.");
rc = -EINVAL;
goto out;
}
if (opts->renew_cert_pem_file != NULL) {
_set_error(ph, "Option '--renew-cert' is only "
"valid together with options '--gen-csr' or "
"'--gen-self-signed-cert'.");
rc = -EINVAL;
goto out;
}
if (opts->csr_new_header == true) {
_set_error(ph, "Option '--csr-new-header' is only "
"valid together with option '--gen-csr'.");
rc = -EINVAL;
goto out;
}
if (opts->cert_validity_days != NULL) {
_set_error(ph, "Option '--cert-validity-days' is only "
"valid together with option "
"'--gen-self-signed-cert'.");
rc = -EINVAL;
goto out;
}
if (opts->cert_digest != NULL) {
_set_error(ph, "Option '--cert-digest' is only "
"valid together with options '--gen-csr' or "
"'--gen-self-signed-cert'.");
rc = -EINVAL;
goto out;
}
if (opts->cert_rsa_pss == true) {
_set_error(ph, "Option '--cert-rsa-pss' is only "
"valid together with option '--gen-csr' or "
"'--gen-self-signed-cert'");
rc = -EINVAL;
goto out;
}
out:
return rc;
}
/**
* Connects to the KMIP server
*
* @param ph the plugin handle
*
* @returns 0 on success, a negative errno in case of an error.
*/
static int _connect_to_server(struct plugin_handle *ph)
{
int rc;
if (ph->connection != NULL)
kmip_connection_free(ph->connection);
ph->connection = NULL;
rc = kmip_connection_new(&ph->kmip_config, &ph->connection,
ph->pd.verbose);
if (rc != 0) {
_set_error(ph, "Failed to connect to KMIP server at '%s': "
"%s", ph->kmip_config.server, strerror(-rc));
return rc;
}
if (ph->kmip_version.major == 0)
ph->kmip_version = ph->profile->kmip_version;
pr_verbose(&ph->pd, "Protocol version: %u.%u", ph->kmip_version.major,
ph->kmip_version.minor);
kmip_set_default_protocol_version(&ph->kmip_version);
return 0;
}
/**
* Returns the name of the enumeration value.
*
* @param values the list of enumeration values
* @param value the value
*
* @returns a constant string
*/
static const char *_enum_value_to_str(const struct kmip_enum_name *values,
uint32_t value)
{
unsigned int i;
for (i = 0; values[i].name != NULL; i++) {
if (values[i].value == value)
return values[i].name;
}
return "UNKNOWN";
}
/**
* Check a KMIP response and extract information from it.
*
* @param ph the plugin handle
* @param resp the response KMIP node
* @param batch_item the batch item index (staring at 0)
* @param operation the operation (to verify the batch item)
* @param payload On return : the payload of this batch item
*
* @returns 0 on success, a negative errno in case of an error.
*/
static int _check_kmip_response(struct plugin_handle *ph,
struct kmip_node *resp, int32_t batch_item,
enum kmip_operation operation,
struct kmip_node **payload)
{
struct kmip_node *resp_hdr = NULL, *resp_bi = NULL;
enum kmip_result_status status = 0;
enum kmip_result_reason reason = 0;
const char *message = NULL;
int32_t batch_count;
int rc;
rc = kmip_get_response(resp, &resp_hdr, 0, NULL);
CHECK_ERROR(rc != 0, rc, rc, "Get KMIP response header failed",
ph, out);
rc = kmip_get_response_header(resp_hdr, NULL, NULL, NULL, NULL,
&batch_count);
CHECK_ERROR(rc != 0, rc, rc, "Get KMIP response header infos failed",
ph, out);
CHECK_ERROR(batch_item >= batch_count, rc, -EBADMSG,
"Response contains less batch items than expected",
ph, out);
rc = kmip_get_response(resp, NULL, batch_item, &resp_bi);
CHECK_ERROR(rc != 0, rc, rc, "Get KMIP response batch item failed",
ph, out);
rc = kmip_get_response_batch_item(resp_bi, NULL, NULL, NULL, &status,
&reason, &message, NULL, NULL,
payload);
CHECK_ERROR(rc != 0, rc, rc, "Get KMIP response status infos failed",
ph, out);
pr_verbose(&ph->pd, "KMIP response, operation: %d, status: %d, "
"reason: %d message: '%s'", operation, status, reason,
message ? message : "(none)");
if (status != KMIP_RESULT_STATUS_SUCCESS) {
_set_error(ph, "KMIP Request failed: Operation: '%s', "
"Status: '%s', Reason: '%s', Message: '%s'",
_enum_value_to_str(required_operations, operation),
_enum_value_to_str(kmip_result_statuses, status),
_enum_value_to_str(kmip_result_reasons, reason),
message ? message : "(none)");
rc = -EBADMSG;
goto out;
}
out:
kmip_node_free(resp_hdr);
kmip_node_free(resp_bi);
return rc;
}
/**
* Build a KMIP request with the up to 2 operations and payloads
*
* @param ph the plugin handle
* @param operation1 The 1st operation to perform
* @param req_pl1 the request payload of the 1st operation
* @param operation2 The 2nd operation to perform (or 0)
* @param req_pl2 the request payload of the 2nd operation (or NULL)
* @param req On return: the created request.
* @param batch_err_opt Batch error option
*
* @returns 0 on success, a negative errno in case of an error.
*/
static int _build_kmip_request2(struct plugin_handle *ph,
enum kmip_operation operation1,
struct kmip_node *req_pl1,
enum kmip_operation operation2,
struct kmip_node *req_pl2,
struct kmip_node **req,
enum kmip_batch_error_cont_option batch_err_opt)
{
struct kmip_node *req_bi1 = NULL, *req_bi2 = NULL, *req_hdr = NULL;
int rc = 0;
req_bi1 = kmip_new_request_batch_item(operation1, NULL, 0, req_pl1);
CHECK_ERROR(req_bi1 == NULL, rc, -ENOMEM, "Allocate KMIP node failed",
ph, out);
if (operation2 != 0) {
req_bi2 = kmip_new_request_batch_item(operation2, NULL, 0,
req_pl2);
CHECK_ERROR(req_bi2 == NULL, rc, -ENOMEM,
"Allocate KMIP node failed", ph, out);
}
req_hdr = kmip_new_request_header(NULL, 0, NULL, NULL, false, NULL,
batch_err_opt, true,
operation2 != 0 ? 2 : 1);
CHECK_ERROR(req_hdr == NULL, rc, -ENOMEM, "Allocate KMIP node failed",
ph, out);
*req = kmip_new_request_va(req_hdr, 2, req_bi1, req_bi2);
CHECK_ERROR(*req == NULL, rc, -ENOMEM, "Allocate KMIP node failed",
ph, out);
out:
kmip_node_free(req_bi1);
kmip_node_free(req_bi2);
kmip_node_free(req_hdr);
return rc;
}
/**
* Perform a KMIP request with up to 2 operations and payloads.
* Returns the response payloads.
*
* @param ph the plugin handle
* @param operation1 The 1st operation to perform
* @param req_pl1 the request payload if the 1st operation
* @param resp_pl 1 On return: the response payload.
* @param operation2 The 2nd operation to perform (or zero)
* @param req_pl2 the request payload of the 2nd operation (or NULL)
* @param resp_pl2 On return: the response payload.
* @param batch_err_opt Batch error option
*
* @returns 0 on success, a negative errno in case of an error.
*/
static int _perform_kmip_request2(struct plugin_handle *ph,
enum kmip_operation operation1,
struct kmip_node *req_pl1,
struct kmip_node **resp_pl1,
enum kmip_operation operation2,
struct kmip_node *req_pl2,
struct kmip_node **resp_pl2,
enum kmip_batch_error_cont_option batch_err_opt)
{
struct kmip_node *req = NULL, *resp = NULL;
int rc;
if (operation2 != 0)
pr_verbose(&ph->pd, "Perform KMIP request, operations: %d, %d",
operation1, operation2);
else
pr_verbose(&ph->pd, "Perform KMIP request, operation: %d",
operation1);
rc = _build_kmip_request2(ph, operation1, req_pl1, operation2, req_pl2,
&req, batch_err_opt);
if (rc != 0)
goto out;
rc = kmip_connection_perform(ph->connection, req, &resp,
ph->pd.verbose);
if (rc != 0) {
_set_error(ph, "Failed to perform KMIP request: %s",
strerror(-rc));
}
rc = _check_kmip_response(ph, resp, 0, operation1, resp_pl1);
if (rc != 0 && batch_err_opt == KMIP_BATCH_ERR_CONT_CONTINUE &&
operation2 != 0) {
rc = 0;
plugin_clear_error(&ph->pd);
}
if (rc != 0)
goto out;
if (operation2 != 0) {
rc = _check_kmip_response(ph, resp, 1, operation2, resp_pl2);
if (rc != 0)
goto out;
}
out:
kmip_node_free(req);
kmip_node_free(resp);
return rc;
}
/**
* Perform a KMIP request with the specified operation and payload. Returns the
* response payload.
*
* @param ph the plugin handle
* @param operation The operation to perform
* @param req_pl the request payload
* @param resp_pl On return: the response payload.
*
* @returns 0 on success, a negative errno in case of an error.
*/
static int _perform_kmip_request(struct plugin_handle *ph,
enum kmip_operation operation,
struct kmip_node *req_pl,
struct kmip_node **resp_pl)
{
return _perform_kmip_request2(ph, operation, req_pl, resp_pl, 0, NULL,
NULL, KMIP_BATCH_ERR_CONT_STOP);
}
/**
* Checks if all required enumeration values are contained in the query
* response payload
*
* @param ph the plugin handle
* @param query_function the query function to check
* @param enum_name the enumeration name (for error message)
* @param required the list of required values
* @param query_pl the QUERY response payload node
*
* @returns 0 on success, a negative errno in case of an error.
*/
static int _check_required_enum_values(struct plugin_handle *ph,
enum kmip_query_function query_function,
const char *enum_name,
const struct kmip_enum_name *required,
struct kmip_node *query_pl)
{
struct kmip_node *info = NULL;
unsigned int i, k;
bool found;
int rc;
for (i = 0; required[i].value != 0; i++) {
for (k = 0, found = false; !found; k++) {
rc = kmip_get_query_response_payload(query_pl,
query_function, NULL, k, &info);
if (rc != 0)
break;
if (kmip_node_get_enumeration(info) ==
required[i].value)
found = true;
kmip_node_free(info);
}
if (!found) {
_set_error(ph, "KMIP server does not support required "
"%s '%s'", enum_name, required[i].name);
return -EINVAL;
}
}
return 0;
}
/**
* Queries the KMIP server, checks if it supports all required features,
* and returns the server information string.
*
* @param ph the plugin handle
* @param server_info On return : the server information string. Must be
* freed by the caller
*
* @returns 0 on success, a negative errno in case of an error.
*/
static int _check_kmip_server(struct plugin_handle *ph, char **server_info)
{
struct kmip_node *req_pl = NULL, *resp_pl = NULL, *serv_info = NULL;
const char *info;
int rc = 0;
req_pl = kmip_new_query_request_payload_va(3, KMIP_QUERY_OPERATIONS,
KMIP_QUERY_OBJECTS, KMIP_QUERY_SERVER_INFORMATION);
CHECK_ERROR(req_pl == NULL, rc, -ENOMEM, "Allocate KMIP node failed",
ph, out);
rc = _perform_kmip_request(ph, KMIP_OPERATION_QUERY, req_pl, &resp_pl);
if (rc != 0)
goto out;
rc = _check_required_enum_values(ph, KMIP_QUERY_OPERATIONS, "operation",
required_operations, resp_pl);
if (rc != 0)
goto out;
rc = _check_required_enum_values(ph, KMIP_QUERY_OBJECTS, "object type",
required_objtypes, resp_pl);
if (rc != 0)
goto out;
rc = kmip_get_query_response_payload(resp_pl,
KMIP_QUERY_SERVER_INFORMATION,
NULL, 0, &serv_info);
CHECK_ERROR(rc != 0, rc, rc, "Failed to get server version",
ph, out);
info = kmip_node_get_text_string(serv_info);
CHECK_ERROR(info == NULL, rc, -EBADMSG, "Failed to get server version",
ph, out);
pr_verbose(&ph->pd, "Server info: '%s'", info);
*server_info = util_strdup(info);
out:
kmip_node_free(req_pl);
kmip_node_free(resp_pl);
kmip_node_free(serv_info);
return rc;
}
/**
* Discovers the KMIP protocol versions that the KMIP server supports
*
* @param ph the plugin handle
* @param version On return : the highest KMIP version that the server
* and the KMIP client supports
*
* @returns 0 on success, a negative errno in case of an error.
*/
static int _discover_kmip_versions(struct plugin_handle *ph,
struct kmip_version *version)
{
struct kmip_node *req_pl = NULL, *resp_pl = NULL;
int rc = 0;
req_pl = kmip_new_discover_versions_payload(-1, NULL);
CHECK_ERROR(req_pl == NULL, rc, -ENOMEM, "Allocate KMIP node failed",
ph, out);
rc = _perform_kmip_request(ph, KMIP_OPERATION_DISCOVER_VERSIONS,
req_pl, &resp_pl);
if (rc != 0)
goto out;
rc = kmip_get_discover_versions_response_payload(resp_pl, NULL, 0,
version);
CHECK_ERROR(rc != 0, rc, rc, "Failed to get discover version response",
ph, out);
out:
kmip_node_free(req_pl);
kmip_node_free(resp_pl);
return rc;
}
/**
* Configures the connection to the KMIP server
*
* @param ph the plugin handle
* @param kmip_server the KMIP server
* @param profil the profile to use
* @param tls_ca_bundle the file or directory name of the CA bundle to use
* @param tls_pin_server_pubkey if true, pin the server public key
* @param tls_trust_server_cert if true, trust the server certificate
* @param tls_dont_verify_server_cert if true, don't verify the server cert
* @param tls_verify_hostname if true verify the server's hostname
*
* @returns 0 on success, a negative errno in case of an error.
*/
static int _configure_connection(struct plugin_handle *ph,
const char *kmip_server,
const char *profile,
const char *tls_ca_bundle,
bool tls_pin_server_pubkey,
bool tls_trust_server_cert,
bool tls_dont_verify_server_cert,
bool tls_verify_hostname)
{
char *server_pubkey_temp = NULL;
char *server_pubkey_file = NULL;
char *server_cert_file = NULL;
char *server_cert_temp = NULL;
char *server_info = NULL;
bool self_signed = false;
bool verified = false;
bool valid = false;
char *file_name;
char tmp[50];
int rc;
if (tls_pin_server_pubkey && tls_trust_server_cert) {
_set_error(ph, "Option ' --tls-pin-server-pubkey' is not valid "
"together with option '--tls-pin-server-cert");
return -EINVAL;
}
if (!ph->apqns_configured) {
_set_error(ph, "The configuration is incomplete, you must "
"first configure the APQNs used with this plugin.");
return -EINVAL;
}
if (!ph->identity_key_generated) {
_set_error(ph, "The configuration is incomplete, you must "
"first generate the identity key.");
return -EINVAL;
}
if (!ph->client_cert_avail) {
_set_error(ph, "The configuration is incomplete, you must "
"first register the client certificate.");
return -EINVAL;
}
if (ph->server != NULL) {
util_print_indented("ATTENTION: The KMIP server connection "
"is already configured\n"
"When you re-configure the KMIP server "
"connection, you might need to re-register "
"this zkey client with the changed KMIP "
"server.", 0);
printf("%s: Re-configure the KMIP server connection [y/N]? ",
program_invocation_short_name);
if (!prompt_for_yes(ph->pd.verbose)) {
_set_error(ph, "Operation aborted by user");
return -ECANCELED;
}
}
FREE_AND_SET_NULL(ph->server);
ph->server = util_strdup(kmip_server);
if (ph->profile != NULL)
profile_free(ph->profile);
ph->profile = NULL;
rc = profile_find_by_name(ph, profile != NULL ? profile :
KMIP_PROFILES_DEFAULT_PROFILE_NAME,
&ph->profile);
if (rc != 0)
return rc;
if (ph->profile->kmip_version.major != 0)
ph->kmip_version = ph->profile->kmip_version;
else
ph->kmip_version = kmip_version_1_0;
rc = plugin_set_or_remove_property(&ph->pd, KMIP_CONFIG_SERVER,
ph->server);
if (rc != 0)
return rc;
rc = plugin_set_or_remove_property(&ph->pd, KMIP_CONFIG_PROFILE,
profile);
if (rc != 0)
return rc;
rc = plugin_set_or_remove_property(&ph->pd, KMIP_CONFIG_CA_BUNDLE,
tls_ca_bundle);
if (rc != 0)
return rc;
/* Establish initial KMIP config */
_free_kmip_config(ph);
rc = _get_kmip_config(ph);
if (rc != 0)
return rc;
/* Connect to the server the 1st time to get its certificate */
util_asprintf(&server_cert_temp, "%s/%s-tmp", ph->pd.config_path,
KMIP_CONFIG_SERVER_CERT_FILE);
util_asprintf(&server_pubkey_temp, "%s/%s-tmp", ph->pd.config_path,
KMIP_CONFIG_SERVER_PUBKEY_FILE);
rc = kmip_connection_get_server_cert(ph->kmip_config.server,
ph->kmip_config.transport,
ph->kmip_config.tls_ca,
ph->kmip_config.tls_client_key,
ph->kmip_config.tls_client_cert,
server_cert_temp,
server_pubkey_temp,
NULL, &verified, ph->pd.verbose);
if (rc != 0) {
_set_error(ph, "Failed to connect to KMIP server at '%s': "
"%s", ph->kmip_config.server, strerror(-rc));
goto out;
}
rc = plugin_check_certificate(&ph->pd, server_cert_temp, &self_signed,
&valid);
if (rc != 0) {
_set_error(ph, "Failed to check certificate PEM file '%s': %s",
server_cert_temp, strerror(-rc));
goto out;
}
pr_verbose(&ph->pd, "verified: %d", verified);
pr_verbose(&ph->pd, "self-signed: %d", self_signed);
pr_verbose(&ph->pd, "valid: %d", valid);
util_print_indented("The KMIP server presented the following "
"certificate to identify itself:", 0);
rc = plugin_print_certificates(&ph->pd, server_cert_temp);
if (rc != 0) {
_set_error(ph, "Failed to print the server certificate: %s",
strerror(-rc));
goto out;
}
printf("\n");
if (!valid)
printf("ATTENTION: The certificate is expired or not yet "
"valid.\n");
if (self_signed) {
printf("ATTENTION: The certificate is self-signed "
"and thus could not be verified.\n");
} else if (!verified) {
if (!tls_dont_verify_server_cert) {
if (tls_ca_bundle != NULL)
_set_error(ph, "The certificate could not be "
"verified using the specified CA "
"bundle '%s'. Use option "
"'--tls-dont-verify-server-cert' to "
"connect to this server anyway.",
tls_ca_bundle);
else
_set_error(ph, "The certificate could not be "
"verified using the system's "
"CA certificates. Use option "
"'--tls-dont-verify-server-cert' to "
"connect to this server anyway.");
rc = -EINVAL;
goto out;
}
}
printf("%s: Is this the KMIP server you intend to work with "
"[y/N]? ", program_invocation_short_name);
if (!prompt_for_yes(ph->pd.verbose)) {
_set_error(ph, "Operation aborted by user");
rc = -ECANCELED;
goto out;
}
ph->kmip_config.tls_verify_peer = !self_signed || tls_trust_server_cert;
if (tls_dont_verify_server_cert)
ph->kmip_config.tls_verify_peer = false;
rc = plugin_set_or_remove_property(&ph->pd,
KMIP_CONFIG_VERIFY_SERVER_CERT,
ph->kmip_config.tls_verify_peer ?
"yes" : "no");
if (rc != 0)
goto out;
ph->kmip_config.tls_verify_host = tls_verify_hostname;
rc = plugin_set_or_remove_property(&ph->pd,
KMIP_CONFIG_VERIFY_HOSTNAME,
ph->kmip_config.tls_verify_host ?
"yes" : "no");
if (rc != 0)
goto out;
/* Establish a connection to the server with the initial config */
rc = _connect_to_server(ph);
if (rc != 0)
goto out;
rc = _check_kmip_server(ph, &server_info);
if (rc != 0)
goto out;
rc = plugin_set_or_remove_property(&ph->pd, KMIP_CONFIG_SERVER_INFO,
server_info);
if (rc != 0)
return rc;
if (profile == NULL) {
/* Try to match a profile for the server */
if (ph->profile != NULL)
profile_free(ph->profile);
rc = profile_find_by_server_info(ph, server_info, &ph->profile);
if (rc != 0)
return rc;
pr_verbose(&ph->pd, "Profile selected: %s", ph->profile->name);
rc = plugin_set_or_remove_property(&ph->pd, KMIP_CONFIG_PROFILE,
ph->profile->name);
if (rc != 0)
return rc;
/* re-establish the kmip configuration with the new profile */
kmip_connection_free(ph->connection);
ph->connection = NULL;
_free_kmip_config(ph);
rc = _get_kmip_config(ph);
if (rc != 0)
return rc;
ph->kmip_version = ph->profile->kmip_version;
/* re-establish the connection with the new configuration */
rc = _connect_to_server(ph);
if (rc != 0)
goto out;
}
/* discover the KMIP protocol version if not pre-set by the profile */
if (ph->profile->kmip_version.major == 0) {
rc = _discover_kmip_versions(ph, &ph->kmip_version);
if (rc != 0) {
pr_verbose(&ph->pd, "DISCOVER-VERSION failed, retry "
"with KMIP v1.2");
plugin_clear_error(&ph->pd);
kmip_set_default_protocol_version(&kmip_version_1_2);
rc = _discover_kmip_versions(ph, &ph->kmip_version);
if (rc != 0) {
pr_verbose(&ph->pd, "2nd DISCOVER-VERSION "
"failed, assume KMIP server only "
"supports v1.0");
plugin_clear_error(&ph->pd);
ph->kmip_version = kmip_version_1_0;
rc = 0;
}
}
pr_verbose(&ph->pd, "Discovered protocol version: %u.%u",
ph->kmip_version.major, ph->kmip_version.minor);
kmip_set_default_protocol_version(&ph->kmip_version);
sprintf(tmp, "%u.%u", ph->kmip_version.major,
ph->kmip_version.minor);
rc = plugin_set_or_remove_property(&ph->pd,
KMIP_CONFIG_PROTOCOL_VERSION,
tmp);
if (rc != 0)
goto out;
} else {
rc = plugin_set_or_remove_property(&ph->pd,
KMIP_CONFIG_PROTOCOL_VERSION,
KMIP_CONFIG_PROTOCOL_VERSION_PROFILE);
if (rc != 0)
goto out;
}
if (ph->profile->auth_scheme != KMIP_PROFILE_AUTH_TLS_CLIENT_CERT) {
_set_error(ph, "Unsupported authentication scheme: %d",
ph->profile->auth_scheme);
rc = -EINVAL;
goto out;
}
FREE_AND_SET_NULL(ph->kmip_config.tls_server_cert);
util_asprintf(&server_cert_file, "%s/%s", ph->pd.config_path,
KMIP_CONFIG_SERVER_CERT_FILE);
if (tls_trust_server_cert) {
ph->kmip_config.tls_server_cert = util_strdup(server_cert_file);
rc = plugin_activate_temp_file(&ph->pd, server_cert_temp,
server_cert_file);
if (rc != 0)
goto out;
} else {
remove(server_cert_file);
}
rc = plugin_set_or_remove_property(&ph->pd, KMIP_CONFIG_SERVER_CERT,
tls_trust_server_cert ?
server_cert_file : NULL);
if (rc != 0)
goto out;
FREE_AND_SET_NULL(ph->kmip_config.tls_pinned_pubkey);
util_asprintf(&server_pubkey_file, "%s/%s", ph->pd.config_path,
KMIP_CONFIG_SERVER_PUBKEY_FILE);
if (tls_pin_server_pubkey) {
ph->kmip_config.tls_pinned_pubkey =
util_strdup(server_pubkey_file);
rc = plugin_activate_temp_file(&ph->pd, server_pubkey_temp,
server_pubkey_file);
if (rc != 0)
goto out;
} else {
remove(server_pubkey_file);
}
rc = plugin_set_or_remove_property(&ph->pd,
KMIP_CONFIG_SERVER_PUBKEY,
tls_pin_server_pubkey ?
server_pubkey_file : NULL);
if (rc != 0)
goto out;
/* Remove any wrapping key properties from previous configuration */
file_name = properties_get(ph->pd.properties, KMIP_CONFIG_WRAPPING_KEY);
if (file_name != NULL) {
remove(file_name);
free(file_name);
}
file_name = properties_get(ph->pd.properties,
KMIP_CONFIG_WRAPPING_KEY_REENC);
if (file_name != NULL) {
remove(file_name);
free(file_name);
}
rc = plugin_set_or_remove_property(&ph->pd, KMIP_CONFIG_WRAPPING_KEY,
NULL);
if (rc != 0)
goto out;
rc = plugin_set_or_remove_property(&ph->pd,
KMIP_CONFIG_WRAPPING_KEY_REENC,
NULL);
if (rc != 0)
goto out;
rc = plugin_set_or_remove_property(&ph->pd,
KMIP_CONFIG_WRAPPING_KEY_ALGORITHM,
NULL);
if (rc != 0)
goto out;
rc = plugin_set_or_remove_property(&ph->pd,
KMIP_CONFIG_WRAPPING_KEY_PARAMS,
NULL);
if (rc != 0)
goto out;
rc = plugin_set_or_remove_property(&ph->pd,
KMIP_CONFIG_WRAPPING_KEY_ID,
NULL);
if (rc != 0)
goto out;
rc = plugin_set_or_remove_property(&ph->pd,
KMIP_CONFIG_WRAPPING_KEY_LABEL,
NULL);
if (rc != 0)
goto out;
out:
if (server_cert_temp != NULL) {
remove(server_cert_temp);
free(server_cert_temp);
}
if (server_cert_file != NULL)
free(server_cert_file);
if (server_pubkey_temp != NULL) {
remove(server_pubkey_temp);
free(server_pubkey_temp);
}
if (server_pubkey_file != NULL)
free(server_pubkey_file);
if (server_info != NULL)
free(server_info);
return rc;
}
/**
* Checks that none of the options for seting up a connection is specified,
* and sets up the error message and return code if
* so.
*
* @param ph the plugin handle
* @param opts the config options structure
*
* @returns 0 on success, a negative errno in case of an error.
*/
static int _error_connection_opts(struct plugin_handle *ph,
struct config_options *opts)
{
int rc = 0;
if (opts->profile != NULL) {
_set_error(ph, "Option '--profile' is only valid "
"together with option '--kmip-server'.");
rc = -EINVAL;
goto out;
}
if (opts->tls_ca_bundle != NULL) {
_set_error(ph, "Option '--tls-ca-bundle' is only valid "
"together with option '--kmip-server'.");
rc = -EINVAL;
goto out;
}
if (opts->tls_pin_server_pubkey) {
_set_error(ph, "Option '--tls-pin-server-pubkey' is only valid "
"together with option '--kmip-server'.");
rc = -EINVAL;
goto out;
}
if (opts->tls_trust_server_cert) {
_set_error(ph, "Option '--tls-trust-server-cert' is only valid "
"together with option '--kmip-server'.");
rc = -EINVAL;
goto out;
}
if (opts->tls_dont_verify_server_cert) {
_set_error(ph, "Option '--tls-dont-verify-server-cert' is only "
"valid together with option '--kmip-server'.");
rc = -EINVAL;
goto out;
}
if (opts->tls_verify_hostname) {
_set_error(ph, "Option '--tls-verify-hostname' is only valid "
"together with option '--kmip-server'.");
rc = -EINVAL;
goto out;
}
out:
return rc;
}
/**
* Use a client certificate with the KMIP plugin. The client certificate's
* public key must match the identity key.
*
* @param ph the plugin handle
* @param client_cert The client certificate to use
*
* @returns 0 on success, a negative errno in case of an error.
*/
static int _use_client_cert(struct plugin_handle *ph, const char *client_cert)
{
unsigned char identity_key[KMIP_MAX_KEY_TOKEN_SIZE] = { 0 };
size_t identity_key_size = sizeof(identity_key);
char *client_cert_file = NULL;
EVP_PKEY *pkey = NULL;
bool rsa_pss = false;
X509 *cert = NULL;
char *cert_algo;
int rc;
_check_config_complete(ph);
if (!ph->apqns_configured) {
_set_error(ph, "The configuration is incomplete, you must "
"first configure the APQNs used with this plugin.");
return -EINVAL;
}
if (!ph->identity_key_generated) {
_set_error(ph, "The configuration is incomplete, you must "
"first generate the identity key.");
return -EINVAL;
}
if (ph->client_cert_avail) {
printf("ATTENTION: A client certificate already exists\n");
util_print_indented("When you set a new client certificate, "
"the existing certificate is removed and "
"you must re-register the new certificate "
"with the KMIP server before you can "
"communicate with the KMIP server", 0);
printf("%s: Set the new client certificate [y/N]? ",
program_invocation_short_name);
if (!prompt_for_yes(ph->pd.verbose)) {
_set_error(ph, "Operation aborted by user");
return -ECANCELED;
}
}
rc = _setup_ext_lib(ph);
if (rc != 0)
goto out;
rc = SK_UTIL_read_x509_certificate(client_cert, &cert);
if (rc != 0) {
_set_error(ph, "Failed to read the client certificate from "
"file '%s': %s", client_cert, strerror(-rc));
return rc;
}
if (ph->pd.verbose) {
pr_verbose(&ph->pd, "Client certificate read from '%s'",
client_cert);
X509_print_fp(stderr, cert);
}
rc = SK_UTIL_read_key_blob(ph->identity_secure_key, identity_key,
&identity_key_size);
if (rc != 0) {
_set_error(ph, "Failed to load the identity key from '%s': %s",
ph->identity_secure_key, strerror(-rc));
goto out;
}
switch (EVP_PKEY_id(X509_get0_pubkey(cert))) {
case EVP_PKEY_RSA:
cert_algo = KMIP_KEY_ALGORITHM_RSA;
break;
case EVP_PKEY_RSA_PSS:
cert_algo = KMIP_KEY_ALGORITHM_RSA_PSS;
rsa_pss = true;
break;
case EVP_PKEY_EC:
cert_algo = KMIP_KEY_ALGORITHM_ECC;
break;
default:
_set_error(ph, "Unsupported certificate algorithm");
rc = -EINVAL;
goto out;
}
rc = SK_OPENSSL_get_secure_key_as_pkey(identity_key, identity_key_size,
rsa_pss, &pkey, &ph->ext_lib,
ph->pd.verbose);
if (rc != 0) {
_set_error(ph, "Failed to get the PKEY from the identity key: "
"%s", strerror(-rc));
goto out;
}
#if !OPENSSL_VERSION_PREREQ(3, 0)
if (EVP_PKEY_cmp(X509_get0_pubkey(cert), pkey) != 1) {
#else
if (EVP_PKEY_eq(X509_get0_pubkey(cert), pkey) != 1) {
#endif
_set_error(ph, "The client certificate's public key does not "
"match the identity key.");
rc = -EINVAL;
goto out;
}
util_asprintf(&client_cert_file, "%s/%s", ph->pd.config_path,
KMIP_CONFIG_CLIENT_CERTIFICATE_FILE);
rc = plugin_set_or_remove_property(&ph->pd,
KMIP_CONFIG_CLIENT_CERTIFICATE, client_cert_file);
if (rc != 0)
goto out;
rc = plugin_set_or_remove_property(&ph->pd,
KMIP_CONFIG_CLIENT_CERT_ALGORITHM, cert_algo);
if (rc != 0)
goto out;
rc = SK_UTIL_write_x509_certificate(client_cert_file, cert);
if (rc != 0) {
_set_error(ph, "Failed to write the self-signed "
"certificate to '%s'", client_cert_file);
goto out;
}
pr_verbose(&ph->pd, "Client certificate stored in '%s'",
client_cert_file);
out:
if (pkey != NULL)
EVP_PKEY_free(pkey);
if (client_cert_file != NULL)
free(client_cert_file);
X509_free(cert);
return rc;
}
/**
* Set the state of a key.
*
* @param ph the plugin handle
* @param key_id the ID of the key to set the state
* @param state the new state
*
* @returns 0 on success, a negative errno in case of an error.
*/
static int _set_key_state(struct plugin_handle *ph, const char *key_id,
enum kmip_state state)
{
struct kmip_node *uid = NULL, *req_pl = NULL, *resp_pl = NULL;
enum kmip_operation operation;
int rc;
uid = kmip_new_unique_identifier(key_id, 0, 0);
CHECK_ERROR(uid == NULL, rc, -ENOMEM, "Allocate KMIP node failed",
ph, out);
switch (state) {
case KMIP_STATE_ACTIVE:
req_pl = kmip_new_activate_request_payload(uid);
operation = KMIP_OPERATION_ACTIVATE;
break;
case KMIP_STATE_DEACTIVATED:
req_pl = kmip_new_revoke_request_payload(uid,
KMIP_REVOK_RSN_SUPERSEDED, NULL, 0);
operation = KMIP_OPERATION_REVOKE;
break;
case KMIP_STATE_COMPROMISED:
req_pl = kmip_new_revoke_request_payload(uid,
KMIP_REVOK_RSN_KEY_COMPROMISE, NULL,
time(NULL));
operation = KMIP_OPERATION_REVOKE;
break;
case KMIP_STATE_DESTROYED:
req_pl = kmip_new_destroy_request_payload(uid);
operation = KMIP_OPERATION_DESTROY;
break;
default:
_set_error(ph, "Invalid state: %d", state);
rc = -EINVAL;
goto out;
}
CHECK_ERROR(req_pl == NULL, rc, -ENOMEM, "Allocate KMIP node failed",
ph, out);
rc = _perform_kmip_request(ph, operation, req_pl, &resp_pl);
out:
kmip_node_free(uid);
kmip_node_free(req_pl);
kmip_node_free(resp_pl);
return rc;
}
/**
* Returns true if the KMIP server supports the 'Sensitive' attribute.
* This is dependent on the profile settings, and the used KMIP protocol
* version (>= v1.4).
*
* @param ph the plugin handle
*
* @return true or false
*/
static bool _supports_sensitive_attr(struct plugin_handle *ph)
{
if (ph->kmip_version.major <= 1)
return false;
if (ph->kmip_version.major == 1 && ph->kmip_version.minor < 4)
return false;
return ph->profile->supports_sensitive_attr;
}
/**
* Returns true if the KMIP server supports the 'Description' attribute.
* This is dependent on the profile settings, and the used KMIP protocol
* version (>= v1.4).
*
* @param ph the plugin handle
*
* @return true or false
*/
static bool _supports_description_attr(struct plugin_handle *ph)
{
if (ph->kmip_version.major <= 1)
return false;
if (ph->kmip_version.major == 1 && ph->kmip_version.minor < 4)
return false;
return ph->profile->supports_description_attr;
}
/**
* Returns true if the KMIP server supports the 'Comment' attribute.
* This is dependent on the profile settings, and the used KMIP protocol
* version (>= v1.4).
*
* @param ph the plugin handle
*
* @return true or false
*/
static bool _supports_comment_attr(struct plugin_handle *ph)
{
if (ph->kmip_version.major <= 1)
return false;
if (ph->kmip_version.major == 1 && ph->kmip_version.minor < 4)
return false;
return ph->profile->supports_comment_attr;
}
/**
* Destroy a key.
*
* @param ph the plugin handle
* @param key_id the ID of the key to destroy
*
* @returns 0 on success, a negative errno in case of an error.
*/
static int _destroy_key(struct plugin_handle *ph, const char *key_id)
{
struct kmip_node *uid = NULL, *req_pl = NULL, *resp_pl = NULL;
int rc;
uid = kmip_new_unique_identifier(key_id, 0, 0);
CHECK_ERROR(uid == NULL, rc, -ENOMEM, "Allocate KMIP node failed",
ph, out);
req_pl = kmip_new_destroy_request_payload(uid);
CHECK_ERROR(req_pl == NULL, rc, -ENOMEM, "Allocate KMIP node failed",
ph, out);
rc = _perform_kmip_request(ph, KMIP_OPERATION_DESTROY, req_pl,
&resp_pl);
out:
kmip_node_free(uid);
kmip_node_free(req_pl);
kmip_node_free(resp_pl);
return rc;
}
/**
* Build Custom/Vendor attribute according to the Custom attribute style of the
* profile.
*
* @param ph the plugin handle
* @param name the attribute name
* @param value the attribute value
*
* @returns the attribute node or NULL in case of an error.
*/
static struct kmip_node *_build_custom_attr(struct plugin_handle *ph,
const char *name,
const char *value)
{
struct kmip_node *attr = NULL, *text;
char *v1_name = NULL;
text = kmip_node_new_text_string(KMIP_TAG_ATTRIBUTE_VALUE, NULL, value);
switch (ph->profile->cust_attr_scheme) {
case KMIP_PROFILE_CUST_ATTR_V1_STYLE:
util_asprintf(&v1_name, "zkey-%s", name);
attr = kmip_new_vendor_attribute("x", v1_name, text);
free(v1_name);
break;
case KMIP_PROFILE_CUST_ATTR_V2_STYLE:
attr = kmip_new_vendor_attribute("zkey", name, text);
break;
default:
_set_error(ph, "Invalid custom attribute style: %d",
ph->profile->cust_attr_scheme);
goto out;
}
out:
kmip_node_free(text);
return attr;
}
/**
* Build Description attribute, dependent on the profile settings.
* This is either a 'Description' attribute, a 'Comment' attribute, or a
* Custom/Vendor attribute according to the Custom attribute style of the
* profile.
*
* @param ph the plugin handle
* @param description the description text
*
* @returns the attribute node or NULL in case of an error.
*/
static struct kmip_node *_build_description_attr(struct plugin_handle *ph,
const char *description)
{
if (_supports_description_attr(ph))
return kmip_new_description(description);
if (_supports_comment_attr(ph))
return kmip_new_comment(description);
return _build_custom_attr(ph, KMS_KEY_PROP_DESCRIPTION, description);
}
/**
* Build Custom/Vendor attribute reference according to the Custom attribute
* style of the profile.
*
* @param ph the plugin handle
* @param name the attribute name
*
* @returns the attribute node or NULL in case of an error.
*/
static struct kmip_node *_build_custom_attr_ref(struct plugin_handle *ph,
const char *name)
{
struct kmip_node *attr_ref = NULL;
char *v1_name = NULL;
switch (ph->profile->cust_attr_scheme) {
case KMIP_PROFILE_CUST_ATTR_V1_STYLE:
util_asprintf(&v1_name, "zkey-%s", name);
attr_ref = kmip_new_attribute_reference(0, "x", v1_name);
free(v1_name);
break;
case KMIP_PROFILE_CUST_ATTR_V2_STYLE:
attr_ref = kmip_new_attribute_reference(0, "zkey", name);
break;
default:
_set_error(ph, "Invalid custom attribute style: %d",
ph->profile->cust_attr_scheme);
goto out;
}
out:
return attr_ref;
}
/**
* Build Description attribute reference, dependent on the profile settings.
* This is either a 'Description' attribute, a 'Comment' attribute, or a
* Custom/Vendor attribute according to the Custom attribute style of the
* profile.
*
* @param ph the plugin handle
*
* @returns the attribute node or NULL in case of an error.
*/
static struct kmip_node *_build_description_attr_ref(struct plugin_handle *ph)
{
if (_supports_description_attr(ph))
return kmip_new_attribute_reference(KMIP_TAG_DESCRIPTION,
NULL, NULL);
if (_supports_comment_attr(ph))
return kmip_new_attribute_reference(KMIP_TAG_COMMENT,
NULL, NULL);
return _build_custom_attr_ref(ph, KMS_KEY_PROP_DESCRIPTION);
}
/**
* Checks if the Attribute is a Custom/Vendor attribute that was set by this
* plugin, and returns its name and value.
*
* @param ph the plugin handle
* @param attr the custom/vendor attribute node
* @param name On return: the name of the custom attribute
* @param value On return: the value of the custom attribute
*
* @returns true if this is a custom/vendor attribute created by the plugin
*/
static bool _get_custom_attr(struct plugin_handle *ph, struct kmip_node *attr,
const char **name, const char **value)
{
struct kmip_node *attr_value = NULL;
const char *vendor_id, *attr_name;
bool ret = false;
int rc;
rc = kmip_get_vendor_attribute(attr, &vendor_id, &attr_name,
&attr_value);
if (rc != 0)
goto out;
switch (ph->profile->cust_attr_scheme) {
case KMIP_PROFILE_CUST_ATTR_V1_STYLE:
if (strcmp(vendor_id, "zkey") == 0)
break;
if (strcmp(vendor_id, "x") != 0 ||
strncmp(attr_name, "zkey-", 5) != 0) {
rc = -EBADMSG;
goto out;
}
attr_name += 5;
break;
case KMIP_PROFILE_CUST_ATTR_V2_STYLE:
if (strcmp(vendor_id, "zkey") != 0)
goto out;
break;
default:
goto out;
}
if (kmip_node_get_type(attr_value) != KMIP_TYPE_TEXT_STRING)
goto out;
if ((_supports_description_attr(ph) || _supports_comment_attr(ph)) &&
strcmp(attr_name, KMS_KEY_PROP_DESCRIPTION) == 0)
goto out;
if (ph->profile->supports_link_attr &&
(strcmp(attr_name, KMS_KEY_PROP_XTS_KEY1_ID) == 0 ||
strcmp(attr_name, KMS_KEY_PROP_XTS_KEY2_ID) == 0))
goto out;
if (name != NULL)
*name = attr_name;
if (value != NULL) {
*value = kmip_node_get_text_string(attr_value);
if (*value == NULL || strcmp(*value, " ") == 0)
*value = "";
}
ret = true;
out:
kmip_node_free(attr_value);
return ret;
}
/**
* Register and activate an RSA wrapping key.
*
* @param ph the plugin handle
* @param pkey the wrapping key as OpenSSL PKEY.
* @param wrapping_key_label the label name for the wrapping key (can be NULL)
*
* @returns 0 on success, a negative errno in case of an error.
*/
static int _register_rsa_wrapping_key(struct plugin_handle *ph, EVP_PKEY *pkey,
const char *wrapping_key_label)
{
struct kmip_node *kobj = NULL, *name_attr = NULL, *unique_id = NULL;
struct kmip_node *reg_req = NULL, *reg_resp = NULL, *descr_attr = NULL;
struct kmip_node *key = NULL, *kval = NULL, *kblock = NULL;
struct kmip_node *umask_attr = NULL, *cparams_attr = NULL;
struct kmip_node *act_req = NULL, *act_resp = NULL;
#if !OPENSSL_VERSION_PREREQ(3, 0)
const BIGNUM *modulus = NULL, *pub_exp = NULL;
#else
BIGNUM *modulus = NULL, *pub_exp = NULL;
#endif
const char *wrap_key_id = NULL;
char *description = NULL;
struct utsname utsname;
int rc;
pr_verbose(&ph->pd, "Wrapping key format: %d",
ph->profile->wrap_key_format);
pr_verbose(&ph->pd, "Wrap padding method: %d",
ph->profile->wrap_padding_method);
pr_verbose(&ph->pd, "Wrap hashing algorithm: %d",
ph->profile->wrap_hashing_algo);
switch (ph->profile->wrap_key_format) {
case KMIP_KEY_FORMAT_TYPE_PKCS_1:
key = kmip_new_pkcs1_public_key(pkey);
break;
case KMIP_KEY_FORMAT_TYPE_PKCS_8:
key = kmip_new_pkcs8_public_key(pkey);
break;
case KMIP_KEY_FORMAT_TYPE_TRANSPARENT_RSA_PUBLIC_KEY:
#if !OPENSSL_VERSION_PREREQ(3, 0)
modulus = RSA_get0_n(EVP_PKEY_get0_RSA(pkey));
pub_exp = RSA_get0_e(EVP_PKEY_get0_RSA(pkey));
#else
EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_RSA_N, &modulus);
EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_RSA_E, &pub_exp);
#endif
if (modulus == NULL || pub_exp == NULL) {
_set_error(ph, "Failed to get RSA public key parts");
rc = -EIO;
goto out;
}
key = kmip_new_transparent_rsa_public_key(modulus, pub_exp);
break;
default:
_set_error(ph, "Unsupported wrapping key format: %d",
ph->profile->wrap_key_format);
rc = -EINVAL;
goto out;
}
CHECK_ERROR(key == NULL, rc, -ENOMEM, "Allocate KMIP node failed",
ph, out);
kval = kmip_new_key_value_va(NULL, key, 0);
CHECK_ERROR(kval == NULL, rc, -ENOMEM, "Allocate KMIP node failed",
ph, out);
kblock = kmip_new_key_block(ph->profile->wrap_key_format, 0, kval,
ph->profile->wrap_key_algo,
ph->profile->wrap_key_size, NULL);
CHECK_ERROR(kblock == NULL, rc, -ENOMEM, "Allocate KMIP node failed",
ph, out);
kobj = kmip_new_public_key(kblock);
CHECK_ERROR(kobj == NULL, rc, -ENOMEM, "Allocate KMIP node failed",
ph, out);
if (wrapping_key_label != NULL) {
name_attr = kmip_new_name(wrapping_key_label,
KMIP_NAME_TYPE_UNINTERPRETED_TEXT_STRING);
CHECK_ERROR(name_attr == NULL, rc, -ENOMEM,
"Allocate KMIP node failed", ph, out);
}
umask_attr = kmip_new_cryptographic_usage_mask(
KMIP_CRY_USAGE_MASK_ENCRYPT |
KMIP_CRY_USAGE_MASK_WRAP_KEY);
CHECK_ERROR(umask_attr == NULL, rc, -ENOMEM,
"Allocate KMIP node failed", ph, out);
cparams_attr = kmip_new_cryptographic_parameters(NULL, 0,
ph->profile->wrap_padding_method,
ph->profile->wrap_padding_method ==
KMIP_PADDING_METHOD_OAEP ?
ph->profile->wrap_hashing_algo : 0,
KMIP_KEY_ROLE_TYPE_KEK, 0,
ph->profile->wrap_key_algo, NULL, NULL, NULL,
NULL, NULL, NULL, NULL, NULL,
ph->profile->wrap_padding_method ==
KMIP_PADDING_METHOD_OAEP ?
KMIP_MASK_GENERATOR_MGF1 : 0,
ph->profile->wrap_padding_method ==
KMIP_PADDING_METHOD_OAEP ?
ph->profile->wrap_hashing_algo : 0,
NULL);
CHECK_ERROR(cparams_attr == NULL, rc, -ENOMEM,
"Allocate KMIP node failed", ph, out);
if (uname(&utsname) != 0) {
rc = -errno;
_set_error(ph, "Failed to obtain the system's "
"hostname: %s", strerror(-rc));
goto out;
}
util_asprintf(&description, "Wrapping key for zkey client on system %s",
utsname.nodename);
descr_attr = _build_description_attr(ph, description);
free(description);
CHECK_ERROR(descr_attr == NULL, rc, -ENOMEM,
"Allocate KMIP node failed", ph, out);
reg_req = kmip_new_register_request_payload_va(NULL,
KMIP_OBJECT_TYPE_PUBLIC_KEY, kobj, NULL,
4, name_attr, umask_attr, cparams_attr,
descr_attr);
CHECK_ERROR(reg_req == NULL, rc, -ENOMEM, "Allocate KMIP node failed",
ph, out);
act_req = kmip_new_activate_request_payload(NULL); /* ID placeholder */
CHECK_ERROR(act_req == NULL, rc, -ENOMEM, "Allocate KMIP node failed",
ph, out);
rc = _perform_kmip_request2(ph, KMIP_OPERATION_REGISTER, reg_req,
&reg_resp, KMIP_OPERATION_ACTIVATE, act_req,
&act_resp, KMIP_BATCH_ERR_CONT_STOP);
if (rc != 0)
goto out;
rc = kmip_get_register_response_payload(reg_resp, &unique_id, NULL,
0, NULL);
CHECK_ERROR(rc != 0, rc, rc, "Failed to get key unique-id", ph, out);
rc = kmip_get_unique_identifier(unique_id, &wrap_key_id, NULL, NULL);
CHECK_ERROR(rc != 0, rc, rc, "Failed to get key unique-id", ph, out);
pr_verbose(&ph->pd, "Wrapping key ID: '%s'", wrap_key_id);
rc = plugin_set_or_remove_property(&ph->pd, KMIP_CONFIG_WRAPPING_KEY_ID,
wrap_key_id);
if (rc != 0)
goto out;
rc = plugin_set_or_remove_property(&ph->pd,
KMIP_CONFIG_WRAPPING_KEY_LABEL,
wrapping_key_label);
if (rc != 0)
goto out;
out:
kmip_node_free(key);
kmip_node_free(kval);
kmip_node_free(kblock);
kmip_node_free(kobj);
kmip_node_free(name_attr);
kmip_node_free(umask_attr);
kmip_node_free(cparams_attr);
kmip_node_free(descr_attr);
kmip_node_free(reg_req);
kmip_node_free(reg_resp);
kmip_node_free(act_req);
kmip_node_free(act_resp);
kmip_node_free(unique_id);
#if OPENSSL_VERSION_PREREQ(3, 0)
if (modulus != NULL)
BN_free(modulus);
if (pub_exp != NULL)
BN_free(pub_exp);
#endif
return rc;
}
/**
* (Re-)Generate a wrapping key using the settings from the profile.
* register the new wrapping key with the KMIP server, and deactivate any
* previous wrapping key (if any).
*
* @param ph the plugin handle
* @param wrapping_key_label the label name for the wrapping key (can be NULL)
*
* @returns 0 on success, a negative errno in case of an error.
*/
static int _generate_wrapping_key(struct plugin_handle *ph,
const char *wrapping_key_label)
{
unsigned char wrapping_key[KMIP_MAX_KEY_TOKEN_SIZE] = { 0 };
size_t wrapping_key_size = sizeof(wrapping_key);
struct sk_key_gen_info gen_info = { 0 };
char *wrapping_key_file_tmp = NULL;
char *wrapping_key_file = NULL;
char *prev_wrap_key_id = NULL;
char *reenc_file = NULL;
EVP_PKEY *pkey = NULL;
char tmp[200];
int rc = 0;
_check_config_complete(ph);
if (!ph->apqns_configured) {
_set_error(ph, "The configuration is incomplete, you must "
"first configure the APQNs used with this plugin.");
return -EINVAL;
}
if (!ph->connection_configured) {
_set_error(ph, "The configuration is incomplete, you must "
"first configure the KMIP server connection.");
return -EINVAL;
}
if (ph->connection == NULL) {
rc = _connect_to_server(ph);
if (rc != 0)
return rc;
}
prev_wrap_key_id = properties_get(ph->pd.properties,
KMIP_CONFIG_WRAPPING_KEY_ID);
pr_verbose(&ph->pd, "Previous wrapping key: '%s'", prev_wrap_key_id ?
prev_wrap_key_id : "(none)");
pr_verbose(&ph->pd, "Wrapping key algorithm: %d",
ph->profile->wrap_key_algo);
pr_verbose(&ph->pd, "Wrapping key size: %lu",
ph->profile->wrap_key_size);
util_asprintf(&wrapping_key_file_tmp, "%s/%s-tmp", ph->pd.config_path,
KMIP_CONFIG_WRAPPING_KEY_FILE);
util_asprintf((char **)&wrapping_key_file, "%s/%s", ph->pd.config_path,
KMIP_CONFIG_WRAPPING_KEY_FILE);
rc = plugin_set_or_remove_property(&ph->pd, KMIP_CONFIG_WRAPPING_KEY,
wrapping_key_file);
if (rc != 0)
goto out;
/* Generate the wrapping key */
switch (ph->profile->wrap_key_algo) {
case KMIP_CRYPTO_ALGO_RSA:
gen_info.type = SK_KEY_TYPE_RSA;
if (ph->profile->wrap_key_size == 0) {
_set_error(ph, "RSA Wrapping key size must be "
"specified");
rc = -EINVAL;
goto out;
}
gen_info.rsa.modulus_bits = ph->profile->wrap_key_size;
gen_info.rsa.pub_exp = 65537;
gen_info.rsa.x9_31 = false;
rc = plugin_set_or_remove_property(&ph->pd,
KMIP_CONFIG_WRAPPING_KEY_ALGORITHM,
KMIP_KEY_ALGORITHM_RSA);
if (rc != 0)
goto out;
sprintf(tmp, "%lu", gen_info.rsa.modulus_bits);
rc = plugin_set_or_remove_property(&ph->pd,
KMIP_CONFIG_WRAPPING_KEY_PARAMS, tmp);
if (rc != 0)
goto out;
break;
default:
_set_error(ph, "Unsupported wrapping key algorithm: %d",
ph->profile->wrap_key_algo);
rc = -EINVAL;
goto out;
}
rc = SK_OPENSSL_generate_secure_key(wrapping_key, &wrapping_key_size,
&gen_info, &ph->ext_lib,
ph->pd.verbose);
if (rc != 0) {
_set_error(ph, "Failed to generate the wrapping key: %s",
strerror(-rc));
goto out;
}
rc = SK_UTIL_write_key_blob(wrapping_key_file_tmp, wrapping_key,
wrapping_key_size);
if (rc != 0) {
_set_error(ph, "Failed to write the wrapping key into file "
"'%s': %s", wrapping_key_file_tmp, strerror(-rc));
goto out;
}
rc = plugin_set_file_permission(&ph->pd, wrapping_key_file_tmp);
if (rc != 0)
goto out;
/* Register the wrapping key with the KMIP server */
rc = SK_OPENSSL_get_secure_key_as_pkey(wrapping_key, wrapping_key_size,
false, &pkey, &ph->ext_lib,
ph->pd.verbose);
if (rc != 0) {
_set_error(ph, "Failed to get the PKEY from the wrapping key: "
"%s", strerror(-rc));
return rc;
}
switch (ph->profile->wrap_key_algo) {
case KMIP_CRYPTO_ALGO_RSA:
rc = _register_rsa_wrapping_key(ph, pkey, wrapping_key_label);
if (rc != 0)
goto out;
break;
default:
_set_error(ph, "Unsupported wrapping key algorithm: %d",
ph->profile->wrap_key_algo);
rc = -EINVAL;
goto out;
}
/* Activate the newly created wrapping key */
rc = plugin_activate_temp_file(&ph->pd, wrapping_key_file_tmp,
wrapping_key_file);
if (rc != 0)
goto out;
reenc_file = properties_get(ph->pd.properties,
KMIP_CONFIG_WRAPPING_KEY_REENC);
if (reenc_file != NULL) {
remove(reenc_file);
free(reenc_file);
properties_remove(ph->pd.properties,
KMIP_CONFIG_WRAPPING_KEY_REENC);
}
/* Deactivate and destroy previous wrapping key */
if (prev_wrap_key_id != NULL) {
rc = _set_key_state(ph, prev_wrap_key_id,
KMIP_STATE_DEACTIVATED);
if (rc != 0) {
/* Ignore error, just issue warning message */
warnx("WARNING: Failed to deactivate the previous "
"wrapping key '%s'", prev_wrap_key_id);
rc = 0;
plugin_clear_error(&ph->pd);
goto out;
}
printf("%s: Destroy the previous wrapping key at the KMIP "
"server [y/N]? ", program_invocation_short_name);
if (!prompt_for_yes(ph->pd.verbose))
goto out;
rc = _destroy_key(ph, prev_wrap_key_id);
if (rc != 0) {
/* Ignore error, just issue warning message */
warnx("WARNING: Failed to destroy the previous "
"wrapping key '%s'", prev_wrap_key_id);
rc = 0;
plugin_clear_error(&ph->pd);
}
}
out:
if (wrapping_key_file_tmp != NULL) {
remove(wrapping_key_file_tmp);
free(wrapping_key_file_tmp);
}
if (wrapping_key_file != NULL)
free(wrapping_key_file);
if (prev_wrap_key_id != NULL)
free(prev_wrap_key_id);
if (pkey != NULL)
EVP_PKEY_free(pkey);
return rc;
}
/**
* Configures (or re-configures) a KMS plugin. This function can be called
* several times to configure a KMS plugin is several steps (if supported by the
* KMS plugin). In case a configuration is not fully complete, this function
* may return -EAGAIN to indicate that it has accepted the configuration so far,
* but the configuration needs to be completed.
*
* A KMS plugin must be associated with at least one APQN. Thus, in a multi-step
* configuration, a list f APQNs must be specified at least once.
*
* @param handle the KMS plugin handle obtained from kms_initialize()
* @param apqns a list of APQNs to associate with the KMS plugin, or
* NULL if no APQNs are specified.
* @param num_apqns number of APQNs in above array. 0 if no APQNs are
* specified.
* @param options a list of options as specified by the user. These
* options are a subset of the possible options as
* returned by kms_get_command_options() with command
* KMS_COMMAND_CONFIGURE.
* @param num_options number of options in above array.
*
* @returns 0 on success, or a negative errno in case of an error.
* Function kms_get_last_error() can be used to obtain more details about the
* error.
* -EAGAIN to indicate that the specified configuration was accepted so far, but
* the configuration is still incomplete, and needs to be completed.
*/
int kms_configure(const kms_handle_t handle,
const struct kms_apqn *apqns, size_t num_apqns,
const struct kms_option *options, size_t num_options)
{
struct config_options opts = { 0 };
struct plugin_handle *ph = handle;
bool config_changed = false;
char *apqn_str = NULL;
int rc = 0;
size_t i;
util_assert(handle != NULL, "Internal error: handle is NULL");
util_assert(num_apqns == 0 || apqns != NULL,
"Internal error: apqns is NULL but num_apqns > 0");
util_assert(num_options == 0 || options != NULL,
"Internal error: options is NULL but num_options > 0 ");
pr_verbose(&ph->pd, "Configure");
for (i = 0; i < num_apqns; i++) {
pr_verbose(&ph->pd, " APQN: %02x.%04x", apqns[i].card,
apqns[i].domain);
}
for (i = 0; i < num_options; i++) {
if (isalnum(options[i].option))
pr_verbose(&ph->pd, " Option '%c': '%s'",
options[i].option,
options[i].argument != NULL ?
options[i].argument : "(null)");
else
pr_verbose(&ph->pd, " Option %d: '%s'",
options[i].option,
options[i].argument != NULL ?
options[i].argument : "(null)");
}
plugin_clear_error(&ph->pd);
if (apqns != NULL) {
rc = _check_apqns(ph, apqns, num_apqns);
if (rc != 0)
goto out;
if (num_apqns > 0 && ph->card_type == CARD_TYPE_CCA) {
rc = cross_check_cca_apka_apqns(&ph->pd, apqns,
num_apqns);
if (rc != 0) {
_set_error(ph, "Your CCA APKA master key setup "
"is improper");
goto out;
}
}
apqn_str = build_kms_apqn_string(apqns, num_apqns);
rc = properties_set(ph->pd.properties, KMIP_CONFIG_APQNS,
apqn_str);
if (rc != 0) {
_set_error(ph, "Failed to set APQNs property: %s",
strerror(-rc));
goto out;
}
rc = properties_set(ph->pd.properties, KMIP_CONFIG_APQN_TYPE,
_card_type_to_str(ph->card_type));
if (rc != 0) {
_set_error(ph, "Failed to set APQN-Type property: %s",
strerror(-rc));
goto out;
}
config_changed = true;
}
for (i = 0; i < num_options; i++) {
switch (options[i].option) {
case 'i':
opts.generate_identity_key = options[i].argument;
break;
case 'c':
opts.csr_pem_file = options[i].argument;
break;
case 'C':
opts.sscert_pem_file = options[i].argument;
break;
case 's':
opts.cert_subject = options[i].argument;
break;
case 'e':
opts.cert_extensions = options[i].argument;
break;
case 'N':
opts.renew_cert_pem_file = options[i].argument;
break;
case 'n':
opts.csr_new_header = true;
break;
case 'd':
opts.cert_validity_days = options[i].argument;
break;
case 'D':
opts.cert_digest = options[i].argument;
break;
case 'P':
opts.cert_rsa_pss = true;
break;
case 'r':
opts.client_cert = options[i].argument;
break;
case 'S':
opts.kmip_server = options[i].argument;
break;
case 'p':
opts.profile = options[i].argument;
break;
case 'b':
opts.tls_ca_bundle = options[i].argument;
break;
case OPT_TLS_PIN_SERVER_PUBKEY:
opts.tls_pin_server_pubkey = true;
break;
case OPT_TLS_TRUST_SERVER_CERT:
opts.tls_trust_server_cert = true;
break;
case OPT_TLS_DONT_VERIFY_SERVER_CERT:
opts.tls_dont_verify_server_cert = true;
break;
case OPT_TLS_VERIFY_HOSTNAME:
opts.tls_verify_hostname = true;
break;
case 'w':
opts.gen_wrapping_key = true;
break;
case 'B':
opts.wrapping_key_label = options[i].argument;
break;
default:
rc = -EINVAL;
if (isalnum(options[i].option))
_set_error(ph, "Unsupported option '%c'",
options[i].option);
else
_set_error(ph, "Unsupported option %d",
options[i].option);
goto out;
}
}
if (opts.generate_identity_key != NULL) {
rc = _generate_identity_key(ph, opts.generate_identity_key);
if (rc != 0)
goto out;
config_changed = true;
}
if (opts.csr_pem_file != NULL || opts.sscert_pem_file != NULL) {
if (opts.client_cert != NULL) {
_set_error(ph, "Option '--client-cert' in not valid "
"together with options '--gen-csr' or "
"'--gen-self-signed-cert'.");
rc = -EINVAL;
goto out;
}
if (!ph->identity_key_generated) {
/* Generate identity key with default key-specs */
rc = _generate_identity_key(ph, NULL);
if (rc != 0)
goto out;
config_changed = true;
}
rc = _generate_csr_sscert(ph, opts.csr_pem_file,
opts.sscert_pem_file,
opts.cert_subject,
opts.cert_extensions,
opts.renew_cert_pem_file,
opts.csr_new_header,
opts.cert_validity_days,
opts.cert_digest,
opts.cert_rsa_pss);
config_changed = true;
} else {
rc = _error_gen_csr_sscert_opts(ph, &opts);
}
if (rc != 0)
goto out;
if (opts.client_cert != NULL) {
rc = _use_client_cert(ph, opts.client_cert);
if (rc != 0)
goto out;
config_changed = true;
}
if (opts.kmip_server != NULL) {
rc = _configure_connection(ph, opts.kmip_server,
opts.profile,
opts.tls_ca_bundle,
opts.tls_pin_server_pubkey,
opts.tls_trust_server_cert,
opts.tls_dont_verify_server_cert,
opts.tls_verify_hostname);
config_changed = true;
opts.gen_wrapping_key = true;
} else {
rc = _error_connection_opts(ph, &opts);
}
if (rc != 0)
goto out;
if (opts.gen_wrapping_key) {
rc = _generate_wrapping_key(ph, opts.wrapping_key_label);
if (rc != 0)
goto out;
config_changed = true;
}
out:
if (apqn_str != NULL)
free(apqn_str);
if (rc == 0) {
if (config_changed) {
rc = plugin_save_config(&ph->pd);
if (rc != 0)
goto ret;
_check_config_complete(ph);
pr_verbose(&ph->pd,
"Plugin configuration is %scomplete",
ph->config_complete ? "" : "in");
}
if (!ph->config_complete)
rc = -EAGAIN;
}
ret:
return rc;
}
/**
* De-configures a KMS plugin. This is called by zkey when a repository is
* unbound from a KMS plugin. It gives the KMS plugin the chance to gracefully
* remove any files that the plugin has stored in its config directory. zkey
* will unconditionally remove all left over files when this function returns.
*
* @param handle the KMS plugin handle obtained from kms_initialize()
*
* @returns 0 on success, or a negative errno in case of an error.
* Function kms_get_last_error() can be used to obtain more details about the
* error.
*/
int kms_deconfigure(const kms_handle_t handle)
{
struct plugin_handle *ph = handle;
util_assert(handle != NULL, "Internal error: handle is NULL");
pr_verbose(&ph->pd, "Deconfigure");
plugin_clear_error(&ph->pd);
return 0;
}
/**
* Allows the KMS plugin to perform a login to the KMS (if required). This
* function is called at least once before any key operation function, typically
* shortly after opening the repository.
* The KMS plugin may prompt the user (by reading from stdin) for its
* credentials, if needed.
*
* It is suggested that a KMS plugin performs a login with the KMS once, and
* stores a login token (or similar) in its config directory. The next time
* the kms_login function is called, the login token can be reused (if still
* valid). This avoids to prompt the user for every key operation.
*
* @param handle the KMS plugin handle obtained from kms_initialize()
*
* @returns 0 on success, or a negative errno in case of an error.
* Function kms_get_last_error() can be used to obtain more details about the
* error.
*/
int kms_login(const kms_handle_t handle)
{
struct plugin_handle *ph = handle;
util_assert(handle != NULL, "Internal error: handle is NULL");
pr_verbose(&ph->pd, "Login");
plugin_clear_error(&ph->pd);
return 0;
}
/**
* Completes re-enciphering of a secure key
*
* @param ph the plugin handle
* @param key_file_prop the property name containing the key file name
* @param reenc_file_prop the property name containing the re-enciphered key
* file name
*
* @returns 0 on success, a negative errno in case of an error.
*/
static int _complete_reencipher(struct plugin_handle *ph,
const char *key_file_prop,
const char *reenc_file_prop)
{
char *key_file, *reenc_file;
int rc = 0;
key_file = properties_get(ph->pd.properties, key_file_prop);
reenc_file = properties_get(ph->pd.properties, reenc_file_prop);
if (key_file == NULL || reenc_file == NULL)
goto out;
rc = remove(key_file);
if (rc != 0) {
rc = -errno;
_set_error(ph, "Failed to remove file '%s': %s",
key_file, strerror(-rc));
goto out;
}
rc = rename(reenc_file, key_file);
if (rc != 0) {
rc = -errno;
_set_error(ph, "Failed to rename file '%s' to '%s': %s",
reenc_file, key_file, strerror(-rc));
goto out;
}
rc = properties_remove(ph->pd.properties, reenc_file_prop);
if (rc != 0) {
_set_error(ph, "Failed to remove property %s: %s",
reenc_file_prop, strerror(-rc));
goto out;
}
out:
if (key_file != NULL)
free(key_file);
if (reenc_file != NULL)
free(reenc_file);
return rc;
}
/**
* Re-enciphering a secure key
*
* @param ph the plugin handle
* @param to_new if true reencipher from CURRENT to NEW.
* @param key_file_name file name of the secure key file
* @param reenc_file_name file name of the re-enciphered key file
*
* @returns 0 on success, a negative errno in case of an error.
*/
static int _reencipher_key(struct plugin_handle *ph, bool to_new,
const char *key_file_name,
const char *reenc_file_name)
{
unsigned char secure_key[KMIP_MAX_KEY_TOKEN_SIZE] = { 0 };
size_t secure_key_size = sizeof(secure_key);
int rc;
rc = SK_UTIL_read_key_blob(key_file_name, secure_key, &secure_key_size);
if (rc != 0) {
_set_error(ph, "Failed to load the secure key from '%s': %s",
key_file_name, strerror(-rc));
return rc;
}
rc = SK_OPENSSL_reencipher_secure_key(secure_key, secure_key_size,
to_new, &ph->ext_lib,
ph->pd.verbose);
if (rc != 0) {
_set_error(ph, "Failed to re-encipher the secure key from file "
"'%s': %s", key_file_name, strerror(-rc));
return rc;
}
rc = SK_UTIL_write_key_blob(reenc_file_name, secure_key,
secure_key_size);
if (rc != 0) {
_set_error(ph, "Failed to write the secure key into '%s': %s",
reenc_file_name, strerror(-rc));
return rc;
}
rc = plugin_set_file_permission(&ph->pd, reenc_file_name);
if (rc != 0)
return rc;
return 0;
}
/**
* Called when the master keys of an APQN associated with the KMS plugin has
* been changed. The KMS plugin can then re-encipher all its secure keys (if
* any) that it has stored in its config directory.
*
* Keys that have been generated by the KMS plugin and stored in the zkey
* repository do not need to be re-enciphered by the KMS plugin. Those are
* re-enciphered by zkey without the help of the KMS plugin.
*
* HSM have different master key registers. Typically a CURRENT and a NEW master
* key register exists. The NEW register may be loaded with the new to be set
* master key, and secure keys can be re-enciphered with it proactively.
*
* CCA also supports an OLD master key register, that contains the previously
* used master key. You thus can re-encipher a secure key that is currently
* enciphered with the master key from the OLD register with the master key
* from the CURRENT register.
*
* HSMs may also support different master keys for different key types or
* algorithms. It is up to the KMS plugin to know which master key registers
* are used for its secure keys
*
* A staged re-encipherment is performed by re-enciphering a secure key with
* the new HSM master key, without making it available for use in the first
* stage. Only when the staged re-encipherment is completed, then the previously
* re-enciphered secure key is make available for use and the old on is removed.
*
* An in-place re-encipherment replaces the secure key right away with its
* re-enciphered version.
*
* @param handle the KMS plugin handle obtained from kms_initialize()
* @param mode Re-encipherment mode
* @param mkreg Re-encipherment register selection
* @param options a list of options as specified by the user. These
* options are a subset of the possible options as
* returned by kms_get_command_options() with command
* KMS_COMMAND_REENCIPHER.
* @param num_options number of options in above array.
*
* @returns 0 on success, or a negative errno in case of an error.
* Function kms_get_last_error() can be used to obtain more details about the
* error.
*/
int kms_reenciper(const kms_handle_t handle, enum kms_reencipher_mode mode,
enum kms_reenc_mkreg mkreg,
const struct kms_option *options, size_t num_options)
{
char *ident_reenc_file = NULL, *ident_key_file = NULL;
char *wrap_reenc_file = NULL, *wrap_key_file = NULL;
struct plugin_handle *ph = handle;
size_t i;
int rc = 0;
util_assert(handle != NULL, "Internal error: handle is NULL");
util_assert(num_options == 0 || options != NULL,
"Internal error: options is NULL but num_options > 0 ");
pr_verbose(&ph->pd, "Re-encipher mode: %d, kmreg=%d", mode, mkreg);
for (i = 0; i < num_options; i++) {
if (isalnum(options[i].option))
pr_verbose(&ph->pd, " Option '%c': '%s'",
options[i].option,
options[i].argument != NULL ?
options[i].argument : "(null)");
else
pr_verbose(&ph->pd, " Option %d: '%s'",
options[i].option,
options[i].argument != NULL ?
options[i].argument : "(null)");
}
plugin_clear_error(&ph->pd);
if (ph->identity_secure_key == NULL)
return 0;
ident_reenc_file = properties_get(ph->pd.properties,
KMIP_CONFIG_IDENTITY_KEY_REENC);
if (ident_reenc_file != NULL && mode == KMS_REENC_MODE_AUTO)
mode = KMS_REENC_MODE_STAGED_COMPLETE;
if (mode == KMS_REENC_MODE_STAGED_COMPLETE) {
if (ident_reenc_file == NULL) {
_set_error(ph, "Staged re-enciphering is not pending");
rc = -EINVAL;
goto out;
}
printf("Completing re-enciphering of KMIP plugin keys.\n");
rc = _complete_reencipher(ph, KMIP_CONFIG_IDENTITY_KEY,
KMIP_CONFIG_IDENTITY_KEY_REENC);
if (rc != 0)
goto out;
rc = _complete_reencipher(ph, KMIP_CONFIG_WRAPPING_KEY,
KMIP_CONFIG_WRAPPING_KEY_REENC);
if (rc != 0)
goto out;
rc = plugin_save_config(&ph->pd);
if (rc != 0)
goto out;
printf("Successfully completed re-enciphering of KMIP plugin "
"keys.\n");
rc = 0;
goto out;
}
if (ident_reenc_file != NULL)
free(ident_reenc_file);
ident_reenc_file = NULL;
if (ph->card_type == CARD_TYPE_EP11 &&
(mkreg == KMS_REENC_MKREG_FROM_OLD ||
mkreg == KMS_REENC_MKREG_FROM_OLD_TO_NEW)) {
_set_error(ph, "ERROR: An APQN of a IBM cryptographic adapter "
"in EP11 coprocessor mode does not have an OLD "
"master key register. Thus, you can not re-encipher "
"a secure key of type 'EP11-AES' from the OLD to "
"the CURRENT/NEW master key register.");
rc = -EINVAL;
goto out;
}
switch (mkreg) {
case KMS_REENC_MKREG_AUTO:
case KMS_REENC_MKREG_TO_NEW:
if (mode == KMS_REENC_MODE_AUTO)
mode = KMS_REENC_MODE_STAGED;
printf("Re-enciphering the KMIP plugin keys with the master "
"key in the NEW register.\n");
break;
case KMS_REENC_MKREG_FROM_OLD:
if (mode == KMS_REENC_MODE_AUTO)
mode = KMS_REENC_MODE_IN_PLACE;
printf("Re-enciphering the KMIP plugin keys with the master "
"key in the CURRENT register.\n");
break;
case KMS_REENC_MKREG_FROM_OLD_TO_NEW:
if (mode == KMS_REENC_MODE_AUTO)
mode = KMS_REENC_MODE_STAGED;
printf("Re-enciphering the KMIP plugin keys with the master "
"key in the CURRENT and then the NEW register.\n");
break;
default:
_set_error(ph, "Invalid re-encipher MK register selection");
rc = -EINVAL;
goto out;
}
ident_key_file = properties_get(ph->pd.properties,
KMIP_CONFIG_IDENTITY_KEY);
if (ident_key_file == NULL)
goto out;
wrap_key_file = properties_get(ph->pd.properties,
KMIP_CONFIG_WRAPPING_KEY);
if (mode == KMS_REENC_MODE_STAGED) {
util_asprintf(&ident_reenc_file, "%s/%s",
ph->pd.config_path,
KMIP_CONFIG_IDENTITY_KEY_REENC_FILE);
util_asprintf(&wrap_reenc_file, "%s/%s",
ph->pd.config_path,
KMIP_CONFIG_WRAPPING_KEY_REENC_FILE);
}
if (mkreg == KMS_REENC_MKREG_AUTO || mkreg == KMS_REENC_MKREG_TO_NEW ||
mkreg == KMS_REENC_MKREG_FROM_OLD_TO_NEW) {
rc = _reencipher_key(ph, true, ident_key_file,
ident_reenc_file ? ident_reenc_file :
ident_key_file);
if (rc != 0)
goto out;
if (wrap_key_file != NULL) {
rc = _reencipher_key(ph, true, wrap_key_file,
wrap_reenc_file ? wrap_reenc_file :
wrap_key_file);
if (rc != 0)
goto out;
}
}
if (mkreg == KMS_REENC_MKREG_FROM_OLD) {
rc = _reencipher_key(ph, false, ident_key_file,
ident_reenc_file ? ident_reenc_file :
ident_key_file);
if (rc != 0)
goto out;
if (wrap_key_file != NULL) {
rc = _reencipher_key(ph, false, wrap_key_file,
wrap_reenc_file ? wrap_reenc_file :
wrap_key_file);
if (rc != 0)
goto out;
}
}
if (mkreg == KMS_REENC_MKREG_FROM_OLD_TO_NEW) {
rc = _reencipher_key(ph, false, ident_reenc_file ?
ident_reenc_file : ident_key_file,
ident_reenc_file ? ident_reenc_file :
ident_key_file);
if (rc != 0)
goto out;
if (wrap_key_file != NULL) {
rc = _reencipher_key(ph, false, wrap_reenc_file ?
wrap_reenc_file : wrap_key_file,
wrap_reenc_file ? wrap_reenc_file :
wrap_key_file);
if (rc != 0)
goto out;
}
}
if (mode == KMS_REENC_MODE_STAGED) {
rc = plugin_set_or_remove_property(&ph->pd,
KMIP_CONFIG_IDENTITY_KEY_REENC,
ident_reenc_file);
if (rc != 0)
goto out;
rc = plugin_set_or_remove_property(&ph->pd,
KMIP_CONFIG_WRAPPING_KEY_REENC,
wrap_reenc_file);
if (rc != 0)
goto out;
} else {
rc = plugin_set_or_remove_property(&ph->pd,
KMIP_CONFIG_IDENTITY_KEY_REENC, NULL);
if (rc != 0)
goto out;
rc = plugin_set_or_remove_property(&ph->pd,
KMIP_CONFIG_WRAPPING_KEY_REENC, NULL);
if (rc != 0)
goto out;
}
rc = plugin_save_config(&ph->pd);
if (rc != 0)
goto out;
rc = 0;
if (mode == KMS_REENC_MODE_STAGED)
util_print_indented("Staged re-enciphering is initiated for "
"the KMIP plugin keys. After the NEW "
"master key has been set to become the "
"CURRENT master key run 'zkey kms "
"reencipher' with option '--complete' to "
"complete the re-enciphering process.", 0);
else
printf("Successfully re-enciphered the KMIP plugin keys\n");
out:
if (rc != 0 && ident_reenc_file != NULL)
remove(ident_reenc_file);
if (ident_reenc_file != NULL)
free(ident_reenc_file);
if (ident_key_file != NULL)
free(ident_key_file);
if (rc != 0 && wrap_reenc_file != NULL)
remove(wrap_reenc_file);
if (wrap_reenc_file != NULL)
free(wrap_reenc_file);
if (wrap_key_file != NULL)
free(wrap_key_file);
return rc;
}
/**
* Parse a label for use with the key mode. For a non-XTS key, the label must
* not contain a colon. For an XTS key, split the label at the colon and
* return the desired part.
*
* @param ph the plugin handle
* @param label the label to parse: 'label' or 'label1:label2'
* @param key_mode the key mode to parse the label for
*
* @returns the label part to use for the key mode. The returned string must be
* freed by the caller. NULL is returnd in case of an error.
*/
static char *_parse_label(struct plugin_handle *ph, const char *label,
enum kms_key_mode key_mode)
{
char *tok, *ret = NULL;
tok = strchr(label, ':');
switch (key_mode) {
case KMS_KEY_MODE_NON_XTS:
if (tok != NULL) {
_set_error(ph, "Label can not contain a colon");
return NULL;
}
ret = util_strdup(label);
break;
case KMS_KEY_MODE_XTS_1:
if (tok == NULL) {
_set_error(ph, "For an XTS key two labels must "
"be specified, separated by a colon");
return NULL;
}
ret = util_zalloc(tok - label + 1);
strncpy(ret, label, tok - label);
break;
case KMS_KEY_MODE_XTS_2:
if (tok == NULL) {
_set_error(ph, "For an XTS key two labels must "
"be specified, separated by a colon");
return NULL;
}
ret = util_strdup(tok + 1);
break;
default:
_set_error(ph, "Unsupported key mode: %d", key_mode);
return NULL;
}
if (strlen(ret) == 0 || ret[0] == ' ') {
_set_error(ph, "The specified label is invalid: '%s'", ret);
free(ret);
return NULL;
}
return ret;
}
/**
* Build an KMIP attribute from a KMS property. Some KMS properties are
* converted into specific KMIP attributes, the others into Custom/Vendor
* attributes.
*
* @param ph the plugin handle
* @param prop the KMS property to build an Attribute for
*
* @returns The KMIP attribute node, or NULL in case of an error
*/
static struct kmip_node *_build_attr_from_prop(struct plugin_handle *ph,
const struct kms_property *prop)
{
struct kmip_node *attr, *linked_id;
const char *value = prop->value;
/* No empty values are allowed */
if (value == NULL || strlen(value) == 0)
value = " ";
if (strcmp(prop->name, KMS_KEY_PROP_DESCRIPTION) == 0)
return _build_description_attr(ph, value);
if (ph->profile->supports_link_attr &&
strcmp(prop->name, KMS_KEY_PROP_XTS_KEY1_ID) == 0) {
linked_id = kmip_new_linked_object_identifier(value, 0, 0);
if (linked_id == NULL)
return NULL;
attr = kmip_new_link(KMIP_LINK_TYPE_PREVIOUS, linked_id);
kmip_node_free(linked_id);
return attr;
}
if (ph->profile->supports_link_attr &&
strcmp(prop->name, KMS_KEY_PROP_XTS_KEY2_ID) == 0) {
linked_id = kmip_new_linked_object_identifier(value, 0, 0);
if (linked_id == NULL)
return NULL;
attr = kmip_new_link(KMIP_LINK_TYPE_NEXT, linked_id);
kmip_node_free(linked_id);
return attr;
}
return _build_custom_attr(ph, prop->name, value);
}
/**
* Build an KMIP attribute reference from a KMS property. Some KMS properties
* are converted into specific KMIP attributes, the others into Custom/Vendor
* attributes.
*
* @param ph the plugin handle
* @param prop the KMS property to build an Attribute reference for
*
* @returns The KMIP attribute node, or NULL in case of an error
*/
static struct kmip_node *_build_attr_ref_from_prop(struct plugin_handle *ph,
const struct kms_property *prop)
{
if (strcmp(prop->name, KMS_KEY_PROP_DESCRIPTION) == 0)
return _build_description_attr_ref(ph);
if (ph->profile->supports_link_attr &&
(strcmp(prop->name, KMS_KEY_PROP_XTS_KEY1_ID) == 0 ||
strcmp(prop->name, KMS_KEY_PROP_XTS_KEY2_ID) == 0))
return kmip_new_attribute_reference(KMIP_TAG_LINK, NULL, NULL);
return _build_custom_attr_ref(ph, prop->name);
}
/**
* Generate an AES key at the KMIP server.
*
* @param ph the plugin handle
* @param key_bits the key size in bits (128, 196, 256)
* @param properties the KMS properties to set
* @param num_properties number of KMS properties
* @param label the key label (can be NULL)
* @param key_id On return: the key id. Must be freed by the caller.
*
* @returns 0 on success, a negative errno in case of an error.
*/
static int _generate_aes_key(struct plugin_handle *ph, size_t key_bits,
const struct kms_property *properties,
size_t num_properties,
const char *label, char **key_id)
{
struct kmip_node *act_req = NULL, *act_resp = NULL, *unique_id = NULL;
struct kmip_node **attrs = NULL, *crea_req = NULL, *crea_resp = NULL;
unsigned int num_attrs, i, idx = 0;
const char *uid;
int rc = 0;
num_attrs = 3 + (_supports_sensitive_attr(ph) ? 1 : 0) +
(label != NULL ? 1 : 0) + num_properties;
attrs = util_zalloc(num_attrs * sizeof(struct kmip_node *));
attrs[idx] = kmip_new_cryptographic_algorithm(KMIP_CRYPTO_ALGO_AES);
CHECK_ERROR(attrs[idx] == NULL, rc, -ENOMEM,
"Allocate KMIP node failed", ph, out);
idx++;
attrs[idx] = kmip_new_cryptographic_length(key_bits);
CHECK_ERROR(attrs[idx] == NULL, rc, -ENOMEM,
"Allocate KMIP node failed", ph, out);
idx++;
attrs[idx] = kmip_new_cryptographic_usage_mask(
KMIP_CRY_USAGE_MASK_ENCRYPT | KMIP_CRY_USAGE_MASK_DECRYPT);
CHECK_ERROR(attrs[idx] == NULL, rc, -ENOMEM,
"Allocate KMIP node failed", ph, out);
idx++;
if (_supports_sensitive_attr(ph)) {
attrs[idx] = kmip_new_sensitive(true);
CHECK_ERROR(attrs[idx] == NULL, rc, -ENOMEM,
"Allocate KMIP node failed", ph, out);
idx++;
}
if (label != NULL) {
attrs[idx] = kmip_new_name(label,
KMIP_NAME_TYPE_UNINTERPRETED_TEXT_STRING);
CHECK_ERROR(attrs[idx] == NULL, rc, -ENOMEM,
"Allocate KMIP node failed", ph, out);
idx++;
}
for (i = 0; i < num_properties; i++) {
attrs[idx] = _build_attr_from_prop(ph, &properties[i]);
CHECK_ERROR(attrs[idx] == NULL, rc, -ENOMEM,
"Allocate KMIP node failed", ph, out);
idx++;
}
crea_req = kmip_new_create_request_payload(NULL,
KMIP_OBJECT_TYPE_SYMMETRIC_KEY, NULL,
num_attrs, attrs);
CHECK_ERROR(crea_req == NULL, rc, -ENOMEM, "Allocate KMIP node failed",
ph, out);
act_req = kmip_new_activate_request_payload(NULL); /* ID placeholder */
CHECK_ERROR(act_req == NULL, rc, -ENOMEM, "Allocate KMIP node failed",
ph, out);
rc = _perform_kmip_request2(ph, KMIP_OPERATION_CREATE, crea_req,
&crea_resp, KMIP_OPERATION_ACTIVATE,
act_req, &act_resp,
KMIP_BATCH_ERR_CONT_STOP);
if (rc != 0)
goto out;
rc = kmip_get_create_response_payload(crea_resp, NULL, &unique_id,
NULL, 0, NULL);
CHECK_ERROR(rc != 0, rc, rc, "Failed to get key unique-id", ph, out);
rc = kmip_get_unique_identifier(unique_id, &uid, NULL, NULL);
CHECK_ERROR(rc != 0, rc, rc, "Failed to get key unique-id", ph, out);
pr_verbose(&ph->pd, "Key ID: '%s'", uid);
*key_id = util_strdup(uid);
out:
if (attrs != NULL) {
for (i = 0; i < num_attrs; i++)
kmip_node_free(attrs[i]);
free(attrs);
}
kmip_node_free(crea_req);
kmip_node_free(crea_resp);
kmip_node_free(act_req);
kmip_node_free(act_resp);
return rc;
}
/**
* Retrieves an AES key from the KMIP server. The key is wrapped with the
* RSA wrapping key.
*
* @param ph the plugin handle
* @param key_id the key id of the key to get
* @param wrapped_key On return: an allocated buffer with the wrapped key.
* Must be freed by the caller.
* @param wrapped_key_len On return: the size of the wrapped key.
* @param key_bits On return the cryptographic size of the key in bits
*
* @returns 0 on success, a negative errno in case of an error.
*/
static int _get_key_rsa_wrapped(struct plugin_handle *ph, const char *key_id,
unsigned char **wrapped_key,
size_t *wrapped_key_len, size_t *key_bits)
{
struct kmip_node *cparams = NULL, *wrap_id = NULL, *wkey_info = NULL;
struct kmip_node *wrap_spec = NULL, *req_pl = NULL, *resp_pl = NULL;
struct kmip_node *uid = NULL, *kobj = NULL, *kblock = NULL;
struct kmip_node *kval = NULL, *wrap = NULL, *key = NULL;
struct kmip_node *wkinfo = NULL, *wcparms = NULL;
enum kmip_hashing_algo halgo, mgfhalgo;
enum kmip_wrapping_method wmethod;
enum kmip_key_format_type ftype;
enum kmip_padding_method pmeth;
enum kmip_encoding_option enc;
enum kmip_mask_generator mgf;
enum kmip_object_type otype;
enum kmip_crypto_algo algo;
const unsigned char *kdata;
char *wrap_key_id = NULL;
uint32_t klen;
int32_t bits;
int rc = 0;
pr_verbose(&ph->pd, "Wrap padding method: %d",
ph->profile->wrap_padding_method);
pr_verbose(&ph->pd, "Wrap hashing algorithm: %d",
ph->profile->wrap_hashing_algo);
wrap_key_id = properties_get(ph->pd.properties,
KMIP_CONFIG_WRAPPING_KEY_ID);
if (wrap_key_id == NULL) {
_set_error(ph, "Wrapping key ID is not available");
return -EINVAL;
}
pr_verbose(&ph->pd, "Wrapping key id: '%s'", wrap_key_id);
cparams = kmip_new_cryptographic_parameters(NULL, 0,
ph->profile->wrap_padding_method,
ph->profile->wrap_padding_method ==
KMIP_PADDING_METHOD_OAEP ?
ph->profile->wrap_hashing_algo : 0,
KMIP_KEY_ROLE_TYPE_KEK, 0,
ph->profile->wrap_key_algo, NULL, NULL, NULL,
NULL, NULL, NULL, NULL, NULL,
ph->profile->wrap_padding_method ==
KMIP_PADDING_METHOD_OAEP ?
KMIP_MASK_GENERATOR_MGF1 : 0,
ph->profile->wrap_padding_method ==
KMIP_PADDING_METHOD_OAEP ?
ph->profile->wrap_hashing_algo : 0,
NULL);
CHECK_ERROR(cparams == NULL, rc, -ENOMEM, "Allocate KMIP node failed",
ph, out);
wrap_id = kmip_new_unique_identifier(wrap_key_id, 0, 0);
CHECK_ERROR(wrap_id == NULL, rc, -ENOMEM, "Allocate KMIP node failed",
ph, out);
wkey_info = kmip_new_key_info(false, wrap_id, cparams);
CHECK_ERROR(wkey_info == NULL, rc, -ENOMEM, "Allocate KMIP node failed",
ph, out);
wrap_spec = kmip_new_key_wrapping_specification_va(NULL,
KMIP_WRAPPING_METHOD_ENCRYPT, wkey_info, NULL,
KMIP_ENCODING_OPTION_NO, 0);
CHECK_ERROR(wrap_spec == NULL, rc, -ENOMEM, "Allocate KMIP node failed",
ph, out);
uid = kmip_new_unique_identifier(key_id, 0, 0);
CHECK_ERROR(uid == NULL, rc, -ENOMEM, "Allocate KMIP node failed",
ph, out);
req_pl = kmip_new_get_request_payload(NULL, uid,
KMIP_KEY_FORMAT_TYPE_RAW, 0, 0,
wrap_spec);
CHECK_ERROR(req_pl == NULL, rc, -ENOMEM, "Allocate KMIP node failed",
ph, out);
rc = _perform_kmip_request(ph, KMIP_OPERATION_GET, req_pl, &resp_pl);
if (rc != 0)
goto out;
rc = kmip_get_get_response_payload(resp_pl, &otype, NULL, &kobj);
CHECK_ERROR(rc != 0, rc, rc, "Failed to get wrapped key", ph, out);
CHECK_ERROR(otype != KMIP_OBJECT_TYPE_SYMMETRIC_KEY, rc, -EINVAL,
"Key is not a symmetric key", ph, out);
rc = kmip_get_symmetric_key(kobj, &kblock);
CHECK_ERROR(rc != 0, rc, rc, "Failed to get symmetric key", ph, out);
rc = kmip_get_key_block(kblock, &ftype, NULL, &kval, &algo, &bits,
&wrap);
CHECK_ERROR(rc != 0, rc, rc, "Failed to get key block", ph, out);
CHECK_ERROR(ftype != KMIP_KEY_FORMAT_TYPE_RAW, rc, -EINVAL,
"Key format is not RAW", ph, out);
CHECK_ERROR(algo != KMIP_CRYPTO_ALGO_AES, rc, -EINVAL,
"Key algorithm is not AES", ph, out);
CHECK_ERROR(bits < 128 || bits > 256, rc, -EINVAL,
"Key bit size is invalid", ph, out);
rc = kmip_get_key_wrapping_data(wrap, &wmethod, &wkinfo, NULL, NULL,
NULL, NULL, NULL, &enc);
CHECK_ERROR(rc != 0, rc, rc, "Failed to get wrapping data", ph, out);
CHECK_ERROR(wmethod != KMIP_WRAPPING_METHOD_ENCRYPT, rc, -EINVAL,
"Wrapping method is not 'Encrypt'", ph, out);
if (ph->kmip_version.major > 1 ||
(ph->kmip_version.major == 1 && ph->kmip_version.minor >= 2)) {
CHECK_ERROR(enc != KMIP_ENCODING_OPTION_NO, rc, -EINVAL,
"Encoding is not 'No encoding'", ph, out);
}
rc = kmip_get_key_info(wkinfo, NULL, &wcparms);
CHECK_ERROR(rc != 0, rc, rc, "Failed to get wrap key infos", ph, out);
rc = kmip_get_cryptographic_parameter(wcparms, NULL, &pmeth, &halgo,
NULL, NULL, &algo, NULL, NULL,
NULL, NULL, NULL, NULL, NULL,
NULL, &mgf, &mgfhalgo, NULL);
CHECK_ERROR(rc != 0, rc, rc, "Failed to get crypto params", ph, out);
if (ph->kmip_version.major > 1 ||
(ph->kmip_version.major == 1 && ph->kmip_version.minor >= 2)) {
CHECK_ERROR(algo != ph->profile->wrap_key_algo, rc, -EINVAL,
"wrap algorithm is not as expected", ph, out);
}
CHECK_ERROR(pmeth != ph->profile->wrap_padding_method, rc, -EINVAL,
"padding method is not as expected", ph, out);
if (ph->profile->wrap_padding_method == KMIP_PADDING_METHOD_OAEP) {
CHECK_ERROR(halgo != ph->profile->wrap_hashing_algo, rc,
-EINVAL, "hashing algorithm is not as expected",
ph, out);
if (ph->kmip_version.major > 1 ||
(ph->kmip_version.major == 1 &&
ph->kmip_version.minor >= 4)) {
CHECK_ERROR(mgf != KMIP_MASK_GENERATOR_MGF1, rc,
-EINVAL, "OAEP MGF is not as expected",
ph, out);
CHECK_ERROR(mgfhalgo != ph->profile->wrap_hashing_algo,
rc, -EINVAL, "MGF hashing algorithm is not "
"as expected", ph, out);
}
}
rc = kmip_get_key_value(kval, &key, NULL, 0, NULL);
CHECK_ERROR(rc != 0, rc, rc, "Failed to get key value", ph, out);
kdata = kmip_node_get_byte_string(key, &klen);
CHECK_ERROR(kdata == NULL, rc, -ENOMEM, "Failed to get key data",
ph, out);
pr_verbose(&ph->pd, "Wrapped key size: %u", klen);
*wrapped_key = util_malloc(klen);
*wrapped_key_len = klen;
memcpy(*wrapped_key, kdata, klen);
pr_verbose(&ph->pd, "AES key size: %u bits", bits);
*key_bits = bits;
out:
kmip_node_free(cparams);
kmip_node_free(wrap_id);
kmip_node_free(wkey_info);
kmip_node_free(wrap_spec);
kmip_node_free(uid);
kmip_node_free(req_pl);
kmip_node_free(resp_pl);
kmip_node_free(kobj);
kmip_node_free(kblock);
kmip_node_free(kval);
kmip_node_free(wrap);
kmip_node_free(wkinfo);
kmip_node_free(wcparms);
kmip_node_free(key);
if (wrap_key_id != NULL)
free(wrap_key_id);
return rc;
}
/**
* Unwraps (Imports) an wrapped AES key with an CCA RSA wrapping key
*
* @param ph the plugin handle
* @param wrapping_key a buffer containing the wrapping secure key
* @param wrapping_key_len the size of the wrapping key
* @param wrapped_key a buffer containing the wrapped key
* @param wrapped_key_len the size of the wrapped key
* @param key_blob A buffer to store the secure key to
* @param key_blob_length On entry: the size of the key blob buffer.
* On return: the size of the key blob
*
* @returns 0 on success, a negative errno in case of an error.
*/
static int _cca_unwrap_key_rsa(struct plugin_handle *ph,
const unsigned char *wrapping_key,
size_t wrapping_key_len,
const unsigned char *wrapped_key,
size_t wrapped_key_len,
unsigned char *unwrapped_key,
size_t *unwrapped_key_len)
{
long return_code, reason_code, rule_array_count;
unsigned char rule_array[3 * 8] = { 0 };
t_CSNDSYI dll_CSNDSYI;
/* Get the Symmetric Key Import function */
dll_CSNDSYI = (t_CSNDSYI)dlsym(ph->cca_lib.cca_lib, "CSNDSYI");
if (dll_CSNDSYI == NULL) {
_set_error(ph, "CCA library function CSNDSYI is not available");
return -ELIBACC;
}
pr_verbose(&ph->pd, "Wrap padding method: %d",
ph->profile->wrap_padding_method);
pr_verbose(&ph->pd, "Wrap hashing algorithm: %d",
ph->profile->wrap_hashing_algo);
memset(unwrapped_key, 0, *unwrapped_key_len);
switch (ph->profile->wrap_padding_method) {
case KMIP_PADDING_METHOD_PKCS_1_5:
rule_array_count = 2;
memcpy(rule_array, "AES PKCS-1.2 ", 2 * 8);
break;
case KMIP_PADDING_METHOD_OAEP:
rule_array_count = 3;
switch (ph->profile->wrap_hashing_algo) {
case KMIP_HASHING_ALGO_SHA_1:
memcpy(rule_array, "AES PKCSOAEPSHA-1 ", 3 * 8);
break;
case KMIP_HASHING_ALGO_SHA_256:
memcpy(rule_array, "AES PKCSOAEPSHA-256 ", 3 * 8);
break;
default:
_set_error(ph, "Unsupported hashing algorithm: %d",
ph->profile->wrap_hashing_algo);
return -EINVAL;
}
break;
default:
_set_error(ph, "Unsupported padding method: %d",
ph->profile->wrap_padding_method);
return -EINVAL;
}
*unwrapped_key_len = AESDATA_KEY_SIZE;
dll_CSNDSYI(&return_code, &reason_code, NULL, NULL,
&rule_array_count, rule_array,
(long *)&wrapped_key_len, (unsigned char *)wrapped_key,
(long *)&wrapping_key_len, (unsigned char *)wrapping_key,
(long *)&unwrapped_key_len, unwrapped_key);
if (return_code != 0) {
_set_error(ph, "CCA CSNDSYI (SYMMETRIC KEY IMPORT) failed. "
" return: %ld, reason: %ld", return_code,
reason_code);
return -EIO;
}
return 0;
}
/**
* Unwraps an wrapped AES key with an EP11 RSA wrapping key
*
* @param ph the plugin handle
* @param wrapping_key a buffer containing the wrapping secure key
* @param wrapping_key_len the size of the wrapping key
* @param wrapped_key a buffer containing the wrapped key
* @param wrapped_key_len the size of the wrapped key
* @param key_blob A buffer to store the secure key to
* @param key_blob_length On entry: the size of the key blob buffer.
* On return: the size of the key blob *
* @returns 0 on success, a negative errno in case of an error.
*/
static int _ep11_unwrap_key_rsa(struct plugin_handle *ph,
const unsigned char *wrapping_key,
size_t wrapping_key_len,
const unsigned char *wrapped_key,
size_t wrapped_key_len,
unsigned char *unwrapped_key,
size_t *unwrapped_key_len)
{
CK_OBJECT_CLASS key_class = CKO_SECRET_KEY;
CK_RSA_PKCS_OAEP_PARAMS oaep_param = { 0 };
size_t csum_len, key_blob_len, bit_len;
CK_KEY_TYPE key_type = CKK_AES;
m_UnwrapKey_t dll_m_UnwrapKey;
const unsigned char *key_blob;
struct ep11keytoken *ep11key;
struct ep11kblob_header *hdr;
CK_MECHANISM mech = { 0 };
CK_BYTE csum[7] = { 0 };
CK_BBOOL ck_true = true;
int pkey_fd, rc;
CK_RV rv;
CK_ATTRIBUTE template[] = {
{ CKA_CLASS, &key_class, sizeof(key_class) },
{ CKA_KEY_TYPE, &key_type, sizeof(key_type) },
{ CKA_ENCRYPT, &ck_true, sizeof(ck_true) },
{ CKA_DECRYPT, &ck_true, sizeof(ck_true) },
{ CKA_SIGN, &ck_true, sizeof(ck_true) },
{ CKA_VERIFY, &ck_true, sizeof(ck_true) },
{ CKA_IBM_PROTKEY_EXTRACTABLE, &ck_true, sizeof(ck_true) },
};
dll_m_UnwrapKey =
(m_UnwrapKey_t)dlsym(ph->ep11.lib_ep11, "m_UnwrapKey");
if (dll_m_UnwrapKey == NULL) {
_set_error(ph, "EP11 library function m_UnwrapKey is not "
"available");
return -ELIBACC;
}
pr_verbose(&ph->pd, "Wrap padding method: %d",
ph->profile->wrap_padding_method);
pr_verbose(&ph->pd, "Wrap hashing algorithm: %d",
ph->profile->wrap_hashing_algo);
if (*unwrapped_key_len < sizeof(struct ep11kblob_header) +
sizeof(struct ep11keytoken)) {
_set_error(ph, "Key buffer is too small");
return -EINVAL;
}
memset(unwrapped_key, 0, *unwrapped_key_len);
switch (ph->profile->wrap_padding_method) {
case KMIP_PADDING_METHOD_PKCS_1_5:
mech.mechanism = CKM_RSA_PKCS;
break;
case KMIP_PADDING_METHOD_OAEP:
mech.mechanism = CKM_RSA_PKCS_OAEP;
mech.pParameter = &oaep_param;
mech.ulParameterLen = sizeof(oaep_param);
switch (ph->profile->wrap_hashing_algo) {
case KMIP_HASHING_ALGO_SHA_1:
oaep_param.hashAlg = CKM_SHA_1;
oaep_param.mgf = CKG_MGF1_SHA1;
break;
case KMIP_HASHING_ALGO_SHA_256:
oaep_param.hashAlg = CKM_SHA256;
oaep_param.mgf = CKG_MGF1_SHA256;
break;
default:
_set_error(ph, "Unsupported hashing algorithm: %d",
ph->profile->wrap_hashing_algo);
return -EINVAL;
}
break;
default:
_set_error(ph, "Unsupported padding method: %d",
ph->profile->wrap_padding_method);
return -EINVAL;
}
key_blob = SK_EP11_get_key_blob(wrapping_key, wrapping_key_len);
key_blob_len = SK_EP11_get_key_blob_size(wrapping_key,
wrapping_key_len);
if (key_blob == NULL || key_blob_len == 0) {
_set_error(ph, "Invalid EP11 key blob");
return -EINVAL;
}
csum_len = sizeof(csum);
rv = dll_m_UnwrapKey((unsigned char *)wrapped_key, wrapped_key_len,
key_blob, key_blob_len, NULL, 0, NULL, 0, &mech,
template, sizeof(template) / sizeof(CK_ATTRIBUTE),
unwrapped_key, unwrapped_key_len,
csum, &csum_len,
ph->ep11_lib.target);
pr_verbose(&ph->pd, "EP11 m_UnwrapKey: rv: 0x%08lx", rv);
if (rv != CKR_OK) {
_set_error(ph, "EP11 m_UnwrapKey failed, rv: 0x%08lx", rv);
return -EIO;
}
pr_verbose(&ph->pd, "unwrapped_key_len: %lu", *unwrapped_key_len);
if (*unwrapped_key_len > sizeof(struct ep11keytoken)) {
_set_error(ph, "Unwrapped EP11 key blob is too long");
return -EIO;
}
if (csum_len < 4) {
_set_error(ph, "EP11 m_UnwrapKey returned invalid key infos");
return -EIO;
}
bit_len = csum[csum_len - 1] + 256 * csum[csum_len - 2] +
256 * 256 * csum[csum_len - 3] +
256 * 256 * 256 * csum[csum_len - 4];
/* Prepend and setup the EP11 token header */
hdr = (struct ep11kblob_header *)unwrapped_key;
ep11key = (struct ep11keytoken *)
(unwrapped_key + sizeof(struct ep11kblob_header));
memmove(ep11key, unwrapped_key, *unwrapped_key_len);
*unwrapped_key_len += sizeof(struct ep11kblob_header);
memset(hdr, 0, sizeof(struct ep11kblob_header));
hdr->type = TOKEN_TYPE_NON_CCA;
hdr->hver = 0;
hdr->len = *unwrapped_key_len;
hdr->version = TOKEN_VERSION_EP11_AES_WITH_HEADER;
hdr->bitlen = bit_len;
pr_verbose(&ph->pd, "unwrapped bit length: %u", hdr->bitlen);
/* return full length, blob is already zero padded */
*unwrapped_key_len =
sizeof(struct ep11kblob_header) + sizeof(struct ep11keytoken);
/*
* Check if the pkey module supports keys of type
* TOKEN_VERSION_EP11_AES_WITH_HEADER, older kernels may not support
* such keys. If it does not support such keys, convert the key to
* TOKEN_VERSION_EP11_AES type, if its session field is all zero
* (i.e. the key is not session bound).
*/
pkey_fd = open_pkey_device(ph->pd.verbose);
if (pkey_fd < 0) {
_set_error(ph, "Failed to open pkey device");
return -EIO;
}
rc = validate_secure_key(pkey_fd, unwrapped_key, *unwrapped_key_len,
NULL, NULL, NULL, ph->pd.verbose);
close(pkey_fd);
if (rc == -EINVAL || rc == -ENODEV) {
pr_verbose(&ph->pd, "The pkey kernel module does not support "
"PKEY_TYPE_EP11_AES, fall back to PKEY_TYPE_EP11");
if (is_ep11_key_session_bound(unwrapped_key,
*unwrapped_key_len)) {
_set_error(ph, "The unwrapped key is session bound. "
"Kernel support is required for such keys");
return -EIO;
}
key_blob_len = hdr->len;
*unwrapped_key_len -= sizeof(struct ep11kblob_header);
memmove(unwrapped_key,
unwrapped_key + sizeof(struct ep11kblob_header),
*unwrapped_key_len);
ep11key = (struct ep11keytoken *)unwrapped_key;
memset(&ep11key->session, 0, sizeof(ep11key->session));
ep11key->head.type = TOKEN_TYPE_NON_CCA;
ep11key->head.len = key_blob_len -
sizeof(struct ep11kblob_header);
ep11key->head.version = TOKEN_VERSION_EP11_AES;
ep11key->head.bitlen = bit_len;
} else if (rc != 0) {
_set_error(ph, "Failed to validate unwrapped key");
return rc;
}
return 0;
}
/**
* Unwraps an wrapped AES key with the RSA wrapping key
*
* @param ph the plugin handle
* @param wrapped_key a buffer containing the wrapped key
* @param wrapped_key_len the size of the wrapped key
* @param key_blob A buffer to store the secure key to
* @param key_blob_length On entry: the size of the key blob buffer.
* On return: the size of the key blob
* @param key_type The key type to unwrap
*
* @returns 0 on success, a negative errno in case of an error.
*/
static int _unwrap_key_rsa(struct plugin_handle *ph,
const unsigned char *wrapped_key,
size_t wrapped_key_len,
unsigned char *key_blob,
size_t *key_blob_length,
const char *key_type)
{
unsigned char wrapping_key[KMIP_MAX_KEY_TOKEN_SIZE] = { 0 };
unsigned char secure_key[MAX_SECURE_KEY_SIZE] = { 0 };
size_t wrapping_key_size = sizeof(wrapping_key);
size_t secure_key_len = sizeof(secure_key);
char *wrapping_key_file = NULL;
unsigned int out_len;
int rc;
wrapping_key_file = properties_get(ph->pd.properties,
KMIP_CONFIG_WRAPPING_KEY);
if (wrapping_key_file == NULL) {
_set_error(ph, "Wrapping key is not available");
return -EINVAL;
}
rc = SK_UTIL_read_key_blob(wrapping_key_file, wrapping_key,
&wrapping_key_size);
if (rc != 0) {
_set_error(ph, "Failed to load the secure key from '%s': %s",
wrapping_key_file, strerror(-rc));
goto out;
}
switch (ph->card_type) {
case CARD_TYPE_CCA:
rc = _cca_unwrap_key_rsa(ph, wrapping_key, wrapping_key_size,
wrapped_key, wrapped_key_len,
secure_key, &secure_key_len);
if (rc != 0)
goto out;
if (strcasecmp(key_type, KEY_TYPE_CCA_AESDATA) == 0) {
if (*key_blob_length < secure_key_len) {
_set_error(ph, "Secure key too large");
rc = -EINVAL;
goto out;
}
*key_blob_length = secure_key_len;
memcpy(key_blob, secure_key, secure_key_len);
goto out;
}
if (strcasecmp(key_type, KEY_TYPE_CCA_AESCIPHER) != 0) {
_set_error(ph, "Unsupported key type '%s'", key_type);
rc = -EINVAL;
goto out;
}
out_len = *key_blob_length;
rc = convert_aes_data_to_cipher_key(&ph->cca,
secure_key, secure_key_len,
key_blob, &out_len,
ph->pd.verbose);
if (rc != 0) {
_set_error(ph, "Converting the secure key from "
"CCA-AESDATA to CCA-AESCIPHER has failed");
goto out;
}
*key_blob_length = out_len;
rc = restrict_key_export(&ph->cca, key_blob, *key_blob_length,
ph->pd.verbose);
if (rc != 0) {
_set_error(ph, "Export restricting the secure key has "
"failed");
goto out;
}
break;
case CARD_TYPE_EP11:
rc = _ep11_unwrap_key_rsa(ph, wrapping_key, wrapping_key_size,
wrapped_key, wrapped_key_len,
secure_key, &secure_key_len);
if (rc != 0)
goto out;
*key_blob_length = secure_key_len;
memcpy(key_blob, secure_key, secure_key_len);
break;
default:
_set_error(ph, "Unsupported card type: %d", ph->card_type);
rc = -EINVAL;
break;
}
out:
if (wrapping_key_file != NULL)
free(wrapping_key_file);
return rc;
}
/**
* Check if the 'Always Sensitive' attribute is True for the key
*
* @param ph the plugin handle
* @param key_id the ID of the key to check
*
* @returns 0 on success, a negative errno in case of an error.
*/
static int _check_always_sensitive(struct plugin_handle *ph, const char *key_id)
{
struct kmip_node *uid = NULL, *req_pl = NULL, *resp_pl = NULL;
struct kmip_node *attr_ref = NULL, *attr = NULL;
int rc;
uid = kmip_new_unique_identifier(key_id, 0, 0);
CHECK_ERROR(uid == NULL, rc, -ENOMEM, "Allocate KMIP node failed",
ph, out);
attr_ref = kmip_new_attribute_reference(KMIP_TAG_ALWAYS_SENSITIVE, NULL,
NULL);
CHECK_ERROR(attr_ref == NULL, rc, -ENOMEM, "Allocate KMIP node failed",
ph, out);
req_pl = kmip_new_get_attributes_request_payload_va(NULL, uid, 1,
attr_ref);
CHECK_ERROR(req_pl == NULL, rc, -ENOMEM, "Allocate KMIP node failed",
ph, out);
rc = _perform_kmip_request(ph, KMIP_OPERATION_GET_ATTRIBUTES, req_pl,
&resp_pl);
if (rc != 0)
goto out;
rc = kmip_get_get_attributes_response_payload(resp_pl, NULL, NULL,
0, &attr);
CHECK_ERROR(rc != 0, rc, rc, "Failed to get attribute", ph, out);
CHECK_ERROR(kmip_node_get_tag(attr) != KMIP_TAG_ALWAYS_SENSITIVE, rc,
-EINVAL, "Unexpected attribute", ph, out);
if (kmip_node_get_boolean(attr) != true) {
_set_error(ph, "The 'Always Sensitive' attribute of the key "
"'%s' is false. This key might have been retrieved "
"in clear.", key_id);
rc = -EPERM;
goto out;
}
out:
kmip_node_free(uid);
kmip_node_free(attr_ref);
kmip_node_free(req_pl);
kmip_node_free(resp_pl);
kmip_node_free(attr);
return rc;
}
/**
* Retrieves an AES key from the KMIP server in the specified key type
*
* @param ph the plugin handle
* @param key_id the key id of the key to get
* @param key_blob A buffer to store the secure key to
* @param key_blob_length On entry: the size of the key blob buffer.
* On return: the size of the key blob
* @param key_type The key type to retrieve
* @param key_bits The expected key size, or 0 if it is not known
*
* @returns 0 on success, a negative errno in case of an error.
*/
static int _retrieve_key(struct plugin_handle *ph, const char *key_id,
unsigned char *key_blob, size_t *key_blob_length,
const char *key_type, size_t key_bits)
{
size_t bits = 0, wrapped_key_len = 0;
unsigned char *wrapped_key = NULL;
int rc = 0;
if (_supports_sensitive_attr(ph) &&
ph->profile->check_always_sensitive_attr) {
rc = _check_always_sensitive(ph, key_id);
if (rc != 0)
goto out;
}
pr_verbose(&ph->pd, "Wrapping key algorithm: %d",
ph->profile->wrap_key_algo);
switch (ph->profile->wrap_key_algo) {
case KMIP_CRYPTO_ALGO_RSA:
rc = _get_key_rsa_wrapped(ph, key_id, &wrapped_key,
&wrapped_key_len, &bits);
if (rc != 0)
goto out;
if (key_bits != 0 && key_bits != bits) {
_set_error(ph, "The retrieved key has an unexpected "
"key size: %u (expected %u)", bits,
key_bits);
rc = -EINVAL;
goto out;
}
rc = _unwrap_key_rsa(ph, wrapped_key, wrapped_key_len,
key_blob, key_blob_length, key_type);
if (rc != 0)
goto out;
break;
default:
_set_error(ph, "Unsupported wrapping key algorithm: %d",
ph->profile->wrap_key_algo);
return -EINVAL;
}
out:
if (wrapped_key != NULL)
free(wrapped_key);
return rc;
}
/**
* Generates a key in or with the KMS and returns a secure key that is
* enciphered under the current HSM master key.
*
* @param handle the KMS plugin handle obtained from kms_initialize()
* @param key_type the zkey key type, euch as 'CCA-AESDATA',
* 'CCA-AESCIPHER', 'EP11-AES'.
* @param key_bits the key bit size (e.g. 256 for an AES 256 bit key).
* @param properties a list of properties to associate the key with
* @param num_properties the number of properties in above array
* @param options a list of options as specified by the user. These
* options are a subset of the possible options as
* returned by kms_get_command_options() with command
* KMS_COMMAND_GENERATE.
* @param num_options number of options in above array.
* @param key_blob a buffer to return the key blob. The size of the
* buffer is specified in key_blob_length
* @param key_blob_length on entry: the size of the key_blob buffer.
* on exit: the size of the key blob returned.
* @param key_id a buffer to return the key-ID of the generated key.
* The key-id is a textual identifier uniquely
* identifying a key in the KMS and the KMS plugin.
* The returned key-id contains the terminating zero.
* @paran key_id_size size of the key_id buffer. It should be at least
* KMS_KEY_ID_SIZE + 1 bytes large.
* @param key_label a buffer to return the key-label of the generated
* key. The key-label is a textual identifier used to
* identify a key in the user interface of the KMS.
* A key label may be equal to the key-ID, or it may
* different. The returned key-label contains the
* terminating zero.
* @paran key_label_size size of the key_lanble buffer. It should be at least
* KMS_KEY_LABEL_SIZE + 1 bytes large.
*
* @returns 0 on success, or a negative errno in case of an error.
* Function kms_get_last_error() can be used to obtain more details about the
* error.
*/
int kms_generate_key(const kms_handle_t handle, const char *key_type,
size_t key_bits, enum kms_key_mode key_mode,
const struct kms_property *properties,
size_t num_properties,
const struct kms_option *options, size_t num_options,
unsigned char *key_blob, size_t *key_blob_length,
char *key_id, size_t key_id_size,
char *key_label, size_t key_label_size)
{
struct plugin_handle *ph = handle;
char *label = NULL, *id = NULL;
size_t i;
int rc;
util_assert(handle != NULL, "Internal error: handle is NULL");
util_assert(num_properties == 0 || properties != NULL,
"Internal error: properties is NULL but num_properties"
" > 0 ");
util_assert(num_options == 0 || options != NULL,
"Internal error: options is NULL but num_options > 0 ");
util_assert(key_blob != NULL, "Internal error: key_blob is NULL");
util_assert(key_blob_length != NULL, "Internal error: key_blob_length "
"is NULL");
util_assert(key_id != NULL, "Internal error: key_id is NULL");
util_assert(key_label != NULL, "Internal error: key_label is NULL");
pr_verbose(&ph->pd, "Generate key: key-type: '%s', keybits: %lu, "
"mode: %d", key_type, key_bits, key_mode);
for (i = 0; i < num_properties; i++) {
util_assert(properties[i].name != NULL,
"Internal error: property name is NULL");
util_assert(properties[i].value != NULL,
"Internal error: property value is NULL");
pr_verbose(&ph->pd, " Property '%s': '%s'", properties[i].name,
properties[i].value);
}
for (i = 0; i < num_options; i++) {
if (isalnum(options[i].option))
pr_verbose(&ph->pd, " Option '%c': '%s'",
options[i].option,
options[i].argument != NULL ?
options[i].argument : "(null)");
else
pr_verbose(&ph->pd, " Option %d: '%s'",
options[i].option,
options[i].argument != NULL ?
options[i].argument : "(null)");
}
plugin_clear_error(&ph->pd);
if (!ph->config_complete) {
_set_error(ph, "The configuration is incomplete, run 'zkey "
"kms configure [OPTIONS]' to complete the "
"configuration.");
return -EINVAL;
}
if (ph->connection == NULL) {
rc = _connect_to_server(ph);
if (rc != 0)
return rc;
}
for (i = 0; i < num_options; i++) {
switch (options[i].option) {
case 'B':
if (label != NULL)
break;
label = _parse_label(ph, options[i].argument, key_mode);
if (label == NULL)
return -EINVAL;
break;
default:
if (isalnum(options[i].option))
_set_error(ph, "Unsupported option '%c'",
options[i].option);
else
_set_error(ph, "Unsupported option %d",
options[i].option);
rc = -EINVAL;
goto out;
}
}
pr_verbose(&ph->pd, "Label: '%s'", label ? label : "(none)");
if (key_bits == 0)
key_bits = DEFAULT_KEYBITS;
rc = _generate_aes_key(ph, key_bits, properties, num_properties, label,
&id);
if (rc != 0)
goto out;
rc = _retrieve_key(ph, id, key_blob, key_blob_length, key_type,
key_bits);
if (rc != 0)
goto out;
strncpy(key_id, id, key_id_size);
key_id[key_id_size - 1] = '\0';
strncpy(key_label, label != NULL ? label : id, key_label_size);
key_label[key_label_size - 1] = '\0';
pr_verbose(&ph->pd, "Generated key id: '%s'", key_id);
pr_verbose(&ph->pd, "Generated key label: '%s'", key_label);
out:
if (label != NULL)
free(label);
if (id != NULL)
free(id);
return rc;
}
/**
* Set (Add/Modify) or delete a key attribute.
*
* @param ph the plugin handle
* @param key_id the ID of the key to set/delet th attribute for
* @param prop the KMS property. If the value is NULL, the
* attribute is deleted
*
* @returns 0 on success, a negative errno in case of an error.
*/
static int _set_key_attribute(struct plugin_handle *ph, const char *key_id,
const struct kms_property *prop)
{
struct kmip_node *req1_pl = NULL, *resp1_pl = NULL, *req2_pl = NULL;
struct kmip_node *resp2_pl = NULL, *attr_ref = NULL, *attr = NULL;
struct kmip_node *uid1 = NULL, *uid2 = NULL;
enum kmip_operation op2 = 0;
int rc;
uid1 = kmip_new_unique_identifier(key_id, 0, 0);
CHECK_ERROR(uid1 == NULL, rc, -ENOMEM, "Allocate KMIP node failed",
ph, out);
attr_ref = _build_attr_ref_from_prop(ph, prop);
CHECK_ERROR(attr_ref == NULL, rc, -ENOMEM, "Allocate KMIP node failed",
ph, out);
req1_pl = kmip_new_delete_attribute_request_payload(NULL, uid1, NULL,
attr_ref);
CHECK_ERROR(req1_pl == NULL, rc, -ENOMEM, "Allocate KMIP node failed",
ph, out);
if (prop->value != NULL) {
uid2 = kmip_new_unique_identifier(key_id, 0, 0);
CHECK_ERROR(uid2 == NULL, rc, -ENOMEM,
"Allocate KMIP node failed", ph, out);
attr = _build_attr_from_prop(ph, prop);
CHECK_ERROR(attr == NULL, rc, -ENOMEM,
"Allocate KMIP node failed", ph, out);
req2_pl = kmip_new_add_attribute_request_payload(NULL, uid2,
attr);
CHECK_ERROR(req2_pl == NULL, rc, -ENOMEM,
"Allocate KMIP node failed", ph, out);
op2 = KMIP_OPERATION_ADD_ATTRIBUTE;
}
rc = _perform_kmip_request2(ph, KMIP_OPERATION_DELETE_ATTRIBUTE,
req1_pl, &resp1_pl, op2, req2_pl, &resp2_pl,
KMIP_BATCH_ERR_CONT_CONTINUE);
out:
kmip_node_free(uid1);
kmip_node_free(attr_ref);
kmip_node_free(req1_pl);
kmip_node_free(resp1_pl);
kmip_node_free(uid2);
kmip_node_free(attr);
kmip_node_free(req2_pl);
kmip_node_free(resp2_pl);
return rc;
}
/**
* Sets (adds/replaces/removes) properties of a key. Already existing properties
* with the same property name are replaced, non-existing properties are added.
* To remove a property, set the property value to NULL.
*
* @param handle the KMS plugin handle obtained from kms_initialize()
* @param key_id the key-ID to set the properties for
* @param properties a list of properties to set
* @param num_properties the number of properties in above array
*
* @returns 0 on success, or a negative errno in case of an error.
* Function kms_get_last_error() can be used to obtain more details about the
* error.
*/
int kms_set_key_properties(const kms_handle_t handle, const char *key_id,
const struct kms_property *properties,
size_t num_properties)
{
struct plugin_handle *ph = handle;
int rc = 0;
size_t i;
util_assert(handle != NULL, "Internal error: handle is NULL");
util_assert(key_id != NULL, "Internal error: key_id is NULL");
util_assert(num_properties == 0 || properties != NULL,
"Internal error: properties is NULL but num_properties"
" > 0 ");
pr_verbose(&ph->pd, "Set key properties: key-ID: '%s'", key_id);
for (i = 0; i < num_properties; i++) {
util_assert(properties[i].name != NULL,
"Internal error: property name is NULL");
pr_verbose(&ph->pd, " Property '%s': '%s'", properties[i].name,
properties[i].value != NULL ? properties[i].value :
"(null)");
}
plugin_clear_error(&ph->pd);
if (!ph->config_complete) {
_set_error(ph, "The configuration is incomplete, run 'zkey "
"kms configure [OPTIONS]' to complete the "
"configuration.");
return -EINVAL;
}
if (ph->connection == NULL) {
rc = _connect_to_server(ph);
if (rc != 0)
return rc;
}
for (i = 0; i < num_properties; i++) {
rc = _set_key_attribute(ph, key_id, &properties[i]);
if (rc != 0)
break;
}
return rc;
}
/**
* Ensures that the properties array is at least count elements large, if not
* then it is reallocated to be at least that large, and the size is updated.
*
* @param array the properties array. May be updated with a
* re-allocated array
* @param size the size of the array. May be updated with the
* new size of the array
* @param count The number of required elements
*/
static void _reealloc_props_array(struct kms_property **array,
unsigned int *size, unsigned int count)
{
if (*size >= count)
return;
(*size) += 10;
*array = util_realloc(*array, *size * sizeof(struct kms_property));
}
/**
* Get a list of key attributes that can be mapped to KMS properties.
* The returned list of properties must be freed by the caller. Each property
* name and value must be freed individually (using free()), as well as the
* complete array.
*
* @param ph the plugin handle
* @param key_id the ID of the key to get the attributes for
* @param properties On return: a list of properties
* @param num_properties On return: the number of properties in above array
* @param key_name On return: the 'Name' of the key or NULL if no Name.
* Must be breed by the caller. Can be NULL to
* retrieve the name.
* @param key_bits On return: the size of the key in bits. Can be NULL.
* @param state On return: the state of the key. Can be NULL.
* @param obj_type On return: the object type. Can be NULL.
* @param algo On return: the algorithm of the key. Can be NULL.
* @param sensitive On return: true if the key is sensitive
* @param always_sensitive On return: true if the key was always sensitive
*
* @returns 0 on success, a negative errno in case of an error.
*/
static int _get_key_attributes(struct plugin_handle *ph, const char *key_id,
struct kms_property **properties,
size_t *num_properties, char **key_name,
size_t *key_bits, enum kmip_state *state,
enum kmip_object_type *obj_type,
enum kmip_crypto_algo *algo,
bool *sensitive, bool *always_sensitive)
{
struct kmip_node *req_pl = NULL, *resp_pl = NULL, *uid = NULL;
struct kmip_node *attr = NULL, *linked_id = NULL;
const char *description, *id, *name, *value;
struct kms_property *props = NULL;
enum kmip_link_type link_type;
unsigned int i, k, count = 0;
int32_t key_size;
int rc;
if (key_name != NULL)
*key_name = NULL;
if (key_bits != NULL)
*key_bits = 0;
if (state != NULL)
*state = 0;
if (obj_type != NULL)
*obj_type = 0;
if (algo != NULL)
*algo = 0;
if (sensitive != NULL)
*sensitive = false;
if (always_sensitive != NULL)
*always_sensitive = false;
uid = kmip_new_unique_identifier(key_id, 0, 0);
CHECK_ERROR(uid == NULL, rc, -ENOMEM, "Allocate KMIP node failed",
ph, out);
/* With no Attr-Refs specified, all attributes are to be returned */
req_pl = kmip_new_get_attributes_request_payload(NULL, uid, 0, NULL);
CHECK_ERROR(req_pl == NULL, rc, -ENOMEM, "Allocate KMIP node failed",
ph, out);
rc = _perform_kmip_request(ph, KMIP_OPERATION_GET_ATTRIBUTES, req_pl,
&resp_pl);
if (rc != 0)
goto out;
for (i = 0, k = 0; ; i++) {
rc = kmip_get_get_attributes_response_payload(resp_pl, NULL,
NULL, i, &attr);
if (rc != 0)
break;
switch (kmip_node_get_tag(attr)) {
case KMIP_TAG_DESCRIPTION:
if (!_supports_description_attr(ph))
break;
rc = kmip_get_description(attr, &description);
CHECK_ERROR(rc != 0, rc, rc,
"Failed to get description", ph, out);
if (description == NULL ||
strcmp(description, " ") == 0)
description = "";
_reealloc_props_array(&props, &count, k + 1);
props[k].name = util_strdup(KMS_KEY_PROP_DESCRIPTION);
props[k].value = util_strdup(description);
k++;
break;
case KMIP_TAG_COMMENT:
if (!_supports_comment_attr(ph) ||
_supports_description_attr(ph))
break;
rc = kmip_get_comment(attr, &description);
CHECK_ERROR(rc != 0, rc, rc,
"Failed to get comment", ph, out);
if (description == NULL ||
strcmp(description, " ") == 0)
description = "";
_reealloc_props_array(&props, &count, k + 1);
props[k].name = util_strdup(KMS_KEY_PROP_DESCRIPTION);
props[k].value = util_strdup(description);
k++;
break;
case KMIP_TAG_LINK:
if (!ph->profile->supports_link_attr)
break;
rc = kmip_get_link(attr, &link_type, &linked_id);
CHECK_ERROR(rc != 0, rc, rc,
"Failed to get link", ph, out);
_reealloc_props_array(&props, &count, k + 1);
if (link_type == KMIP_LINK_TYPE_NEXT)
props[k].name =
util_strdup(KMS_KEY_PROP_XTS_KEY2_ID);
else if (link_type == KMIP_LINK_TYPE_PREVIOUS)
props[k].name =
util_strdup(KMS_KEY_PROP_XTS_KEY1_ID);
else
break;
rc = kmip_get_linked_object_identifier(linked_id, &id,
NULL, NULL);
CHECK_ERROR(rc != 0, rc, rc,
"Failed to get link ID", ph, out);
props[k].value = util_strdup(id);
k++;
kmip_node_free(linked_id);
linked_id = NULL;
break;
case KMIP_TAG_ATTRIBUTE: /* Custom/Vendor attribute */
if (!_get_custom_attr(ph, attr, &name, &value))
break;
_reealloc_props_array(&props, &count, k + 1);
props[k].name = util_strdup(name);
props[k].value = util_strdup(value);
k++;
break;
case KMIP_TAG_NAME:
if (key_name == NULL || *key_name != NULL)
break;
rc = kmip_get_name(attr, &name, NULL);
CHECK_ERROR(rc != 0, rc, rc,
"Failed to get key name", ph, out);
if (name == NULL || strcmp(name, " ") == 0)
name = "";
*key_name = util_strdup(name);
break;
case KMIP_TAG_CRYPTOGRAPHIC_LENGTH:
if (key_bits == NULL)
break;
rc = kmip_get_cryptographic_length(attr, &key_size);
CHECK_ERROR(rc != 0, rc, rc,
"Failed to get key size", ph, out);
*key_bits = key_size;
break;
case KMIP_TAG_CRYPTOGRAPHIC_ALGORITHM:
if (algo == NULL)
break;
rc = kmip_get_cryptographic_algorithm(attr, algo);
CHECK_ERROR(rc != 0, rc, rc,
"Failed to get key algorithm", ph, out);
break;
case KMIP_TAG_OBJECT_TYPE:
if (obj_type == NULL)
break;
rc = kmip_get_object_type(attr, obj_type);
CHECK_ERROR(rc != 0, rc, rc,
"Failed to get object type", ph, out);
break;
case KMIP_TAG_STATE:
if (state == NULL)
break;
rc = kmip_get_state(attr, state);
CHECK_ERROR(rc != 0, rc, rc,
"Failed to get key state", ph, out);
break;
case KMIP_TAG_SENSITIVE:
if (sensitive == NULL)
break;
rc = kmip_get_sensitive(attr, sensitive);
CHECK_ERROR(rc != 0, rc, rc,
"Failed to get sensitive flag", ph, out);
break;
case KMIP_TAG_ALWAYS_SENSITIVE:
if (always_sensitive == NULL)
break;
rc = kmip_get_always_sensitive(attr, always_sensitive);
CHECK_ERROR(rc != 0, rc, rc,
"Failed to get always sensitive flag",
ph, out);
break;
default:
break;
}
kmip_node_free(attr);
attr = NULL;
}
*num_properties = k;
*properties = props;
rc = 0;
out:
kmip_node_free(uid);
kmip_node_free(req_pl);
kmip_node_free(resp_pl);
kmip_node_free(attr);
kmip_node_free(linked_id);
if (rc != 0 && props != NULL) {
for (i = 0; i < count; i++) {
free((char *)props[i].name);
free((char *)props[i].value);
}
free(props);
}
if (rc != 0 && key_name != NULL && *key_name != NULL) {
free(*key_name);
*key_name = NULL;
}
return rc;
}
/**
* Gets properties of a key.
*
* The returned list of properties must be freed by the caller. Each property
* name and value must be freed individually (using free()), as well as the
* complete array.
*
* @param handle the KMS plugin handle obtained from kms_initialize()
* @param key_id the key-ID to set the properties for
* @param properties On return: a list of properties
* @param num_properties On return: the number of properties in above array
*
* @returns 0 on success, or a negative errno in case of an error.
* Function kms_get_last_error() can be used to obtain more details about the
* error.
*/
int kms_get_key_properties(const kms_handle_t handle, const char *key_id,
struct kms_property **properties,
size_t *num_properties)
{
struct plugin_handle *ph = handle;
bool sensitive, always_sensitive;
enum kmip_object_type obj_type;
enum kmip_crypto_algo algo;
enum kmip_state state;
size_t i;
int rc;
util_assert(handle != NULL, "Internal error: handle is NULL");
util_assert(key_id != NULL, "Internal error: key_id is NULL");
util_assert(properties != NULL, "Internal error: properties is NULL");
util_assert(num_properties != NULL,
"Internal error: num_properties is NULL");
pr_verbose(&ph->pd, "Get key properties: key-ID: '%s'", key_id);
plugin_clear_error(&ph->pd);
if (!ph->config_complete) {
_set_error(ph, "The configuration is incomplete, run 'zkey "
"kms configure [OPTIONS]' to complete the "
"configuration.");
return -EINVAL;
}
if (ph->connection == NULL) {
rc = _connect_to_server(ph);
if (rc != 0)
return rc;
}
rc = _get_key_attributes(ph, key_id, properties, num_properties,
NULL, NULL, &state, &obj_type, &algo,
&sensitive, &always_sensitive);
if (rc != 0)
goto out;
if (state != KMIP_STATE_ACTIVE) {
_set_error(ph, "The key '%s' is not in state ACTIVE.", key_id);
rc = -EINVAL;
goto out;
}
if (obj_type != KMIP_OBJECT_TYPE_SYMMETRIC_KEY) {
_set_error(ph, "The key '%s' is not a symmetric key.", key_id);
rc = -EINVAL;
goto out;
}
if (algo != KMIP_CRYPTO_ALGO_AES) {
_set_error(ph, "The key '%s' is not an AES key.", key_id);
rc = -EINVAL;
goto out;
}
if (_supports_sensitive_attr(ph) && sensitive == false) {
_set_error(ph, "The key '%s' is not sensitive.", key_id);
rc = -EINVAL;
goto out;
}
if (_supports_sensitive_attr(ph) &&
ph->profile->check_always_sensitive_attr &&
always_sensitive == false) {
_set_error(ph, "The key '%s' was not always sensitive.",
key_id);
rc = -EINVAL;
goto out;
}
for (i = 0; i < *num_properties; i++) {
util_assert((*properties)[i].name != NULL,
"Internal error: property name is NULL");
pr_verbose(&ph->pd, " Property '%s': '%s'",
(*properties)[i].name, (*properties)[i].value);
}
out:
return rc;
}
/**
* Called when zkey removes a KMS-bound key from the zkey repository. The KMS
* plugin can then set the state of the key in the KMS, or remove it also from
* the KMS (this is usually not done).
*
* @param handle the KMS plugin handle obtained from kms_initialize()
* @param key_id the key-ID to set the properties for
* @param options a list of options as specified by the user. These
* options are a subset of the possible options as
* returned by kms_get_command_options() with command
* KMS_COMMAND_REMOVE.
* @param num_options number of options in above array.
*
* @returns 0 on success, or a negative errno in case of an error.
* Function kms_get_last_error() can be used to obtain more details about the
* error.
*/
int kms_remove_key(const kms_handle_t handle, const char *key_id,
const struct kms_option *options, size_t num_options)
{
struct plugin_handle *ph = handle;
char *state = NULL;
int rc = 0;
size_t i;
util_assert(handle != NULL, "Internal error: handle is NULL");
util_assert(key_id != NULL, "Internal error: key_id is NULL");
util_assert(num_options == 0 || options != NULL,
"Internal error: options is NULL but num_options > 0 ");
pr_verbose(&ph->pd, "Remove key: key-ID: '%s'", key_id);
for (i = 0; i < num_options; i++) {
if (isalnum(options[i].option))
pr_verbose(&ph->pd, " Option '%c': '%s'",
options[i].option,
options[i].argument != NULL ?
options[i].argument : "(null)");
else
pr_verbose(&ph->pd, " Option %d: '%s'",
options[i].option,
options[i].argument != NULL ?
options[i].argument : "(null)");
}
plugin_clear_error(&ph->pd);
for (i = 0; i < num_options; i++) {
switch (options[i].option) {
case 's':
state = util_strdup(options[i].argument);
util_str_toupper(state);
break;
default:
rc = -EINVAL;
if (isalnum(options[i].option))
_set_error(ph, "Unsupported option '%c'",
options[i].option);
else
_set_error(ph, "Unsupported option %d",
options[i].option);
goto out;
}
}
if (state == NULL)
goto out;
pr_verbose(&ph->pd, "State to set: '%s'", state);
if (strcmp(state, KMIP_KEY_STATE_DEACTIVATED) != 0 &&
strcmp(state, KMIP_KEY_STATE_COMPROMISED) != 0 &&
strcmp(state, KMIP_KEY_STATE_DESTROYED) != 0 &&
strcmp(state, KMIP_KEY_STATE_DESTROYED_COMPROMISED) != 0) {
_set_error(ph, "Invalid state specified: '%s'", state);
rc = -EINVAL;
goto out;
}
if (!ph->config_complete) {
_set_error(ph, "The configuration is incomplete, run 'zkey "
"kms configure [OPTIONS]' to complete the "
"configuration.");
return -EINVAL;
}
if (ph->connection == NULL) {
rc = _connect_to_server(ph);
if (rc != 0)
return rc;
}
if (strcmp(state, KMIP_KEY_STATE_DEACTIVATED) == 0 ||
strcmp(state, KMIP_KEY_STATE_DESTROYED) == 0) {
rc = _set_key_state(ph, key_id, KMIP_STATE_DEACTIVATED);
if (rc != 0)
goto out;
}
if (strcmp(state, KMIP_KEY_STATE_COMPROMISED) == 0 ||
strcmp(state, KMIP_KEY_STATE_DESTROYED_COMPROMISED) == 0) {
rc = _set_key_state(ph, key_id, KMIP_STATE_COMPROMISED);
if (rc != 0)
goto out;
}
if (strcmp(state, KMIP_KEY_STATE_DESTROYED) == 0 ||
strcmp(state, KMIP_KEY_STATE_DESTROYED_COMPROMISED) == 0) {
rc = _set_key_state(ph, key_id, KMIP_STATE_DESTROYED);
if (rc != 0)
goto out;
}
out:
if (state != NULL)
free(state);
return rc;
}
/**
* Process a located key item.
*
* @param ph the plugin handle
* @param key_id the ID of the key found
* @param label_pattern a pattern of the label used to filter the keys, or
* NULL if no label pattern is specified.
* @param key_type the key type
* @param callback a callback function that is called for each key that
* matches the filter (if any).
* @private_data a private pointer passed as is to the callback
* function. Can be used to pass user specific
* information to the callback.
*
* @returns 0 on success, a negative errno in case of an error.
*/
static int _process_list_item(struct plugin_handle *ph, const char *key_id,
const char *label_pattern, const char *key_type,
kms_list_callback callback, void *private_data)
{
size_t i, key_bits = 0, num_properties = 0;
struct kms_property *properties = NULL;
bool sensitive, always_sensitive;
enum kmip_object_type obj_type;
enum kmip_crypto_algo algo;
enum kmip_state state;
char *name = NULL;
int rc;
rc = _get_key_attributes(ph, key_id, &properties, &num_properties,
&name, &key_bits, &state, &obj_type, &algo,
&sensitive, &always_sensitive);
if (rc != 0)
goto out;
pr_verbose(&ph->pd, "Name: '%s'", name ? name : "(none)");
if (state != KMIP_STATE_ACTIVE) {
pr_verbose(&ph->pd, "State is not ACTIVE, skip.");
goto out;
}
if (obj_type != KMIP_OBJECT_TYPE_SYMMETRIC_KEY) {
pr_verbose(&ph->pd, "Object type is not Symmetric Key, skip.");
goto out;
}
if (algo != KMIP_CRYPTO_ALGO_AES) {
pr_verbose(&ph->pd, "Key algorithm is not AES, skip.");
goto out;
}
if (_supports_sensitive_attr(ph) && sensitive == false) {
pr_verbose(&ph->pd, "The key is not sensitive.");
goto out;
}
if (_supports_sensitive_attr(ph) &&
ph->profile->check_always_sensitive_attr &&
always_sensitive == false) {
pr_verbose(&ph->pd, "The key was not always sensitive.");
goto out;
}
if (label_pattern != NULL) {
if (fnmatch(label_pattern, name ? name : key_id, 0) != 0) {
pr_verbose(&ph->pd, "Label filter not matched");
goto out;
}
}
for (i = 0; i < num_properties; i++) {
pr_verbose(&ph->pd, " Property '%s': '%s'", properties[i].name,
properties[i].value != NULL ? properties[i].value :
"(null)");
}
rc = callback(key_id, name ? name : key_id, key_type ? key_type :
"(any)", key_bits, properties, num_properties, NULL, 0,
private_data);
out:
if (properties != NULL) {
for (i = 0; i < num_properties; i++) {
free((char *)properties[i].name);
free((char *)properties[i].value);
}
free(properties);
}
if (name != NULL)
free(name);
return rc;
}
/**
* List keys managed by the KMS. This list is independent of the zkey key
* repository. It lists keys as known by the KMS.
*
* @param handle the KMS plugin handle obtained from kms_initialize()
* @param label_pattern a pattern of the label used to filter the keys, or
* NULL if no label pattern is specified.
* @param properties a list of properties used to filter the keys, or
* NULL if no properties filter is specified.
* @param num_properties the number of properties in above array.
* @param options a list of options as specified by the user. These
* options are a subset of the possible options as
* returned by kms_get_command_options() with command
* KMS_COMMAND_LIST.
* @param num_options number of options in above array.*
* @param callback a callback function that is called for each key that
* matches the filter (if any).
* @private_data a private pointer passed as is to the callback
* function. Can be used to pass user specific
* information to the callback.
*
* @returns 0 on success, or a negative errno in case of an error.
* Function kms_get_last_error() can be used to obtain more details about the
* error.
*/
int kms_list_keys(const kms_handle_t handle, const char *label_pattern,
const struct kms_property *properties, size_t num_properties,
const struct kms_option *options, size_t num_options,
kms_list_callback callback, void *private_data)
{
struct kmip_node *req_pl = NULL, *resp_pl = NULL, *item_uid = NULL;
struct plugin_handle *ph = handle;
struct kmip_node **attrs = NULL;
bool label_filter = false;
char *key_type = NULL;
size_t num_attrs;
const char *id;
size_t i, k;
int rc = 0;
util_assert(handle != NULL, "Internal error: handle is NULL");
util_assert(num_properties == 0 || properties != NULL,
"Internal error: properties is NULL but num_properties "
"> 0 ");
util_assert(callback != NULL, "Internal error: callback is NULL");
pr_verbose(&ph->pd, "List Keys, label-pattern: '%s'",
label_pattern != NULL ? label_pattern : "(null)");
for (i = 0; i < num_properties; i++) {
util_assert(properties[i].name != NULL,
"Internal error: property name is NULL");
util_assert(properties[i].value != NULL,
"Internal error: property value is NULL");
pr_verbose(&ph->pd, " Property '%s': '%s'", properties[i].name,
properties[i].value);
}
for (i = 0; i < num_options; i++) {
if (isalnum(options[i].option))
pr_verbose(&ph->pd, " Option '%c': '%s'",
options[i].option,
options[i].argument != NULL ?
options[i].argument : "(null)");
else
pr_verbose(&ph->pd, " Option %d: '%s'",
options[i].option,
options[i].argument != NULL ?
options[i].argument : "(null)");
}
plugin_clear_error(&ph->pd);
for (i = 0; i < num_options; i++) {
switch (options[i].option) {
case 'K':
key_type = util_strdup(options[i].argument);
util_str_toupper(key_type);
break;
default:
rc = -EINVAL;
if (isalnum(options[i].option))
_set_error(ph, "Unsupported option '%c'",
options[i].option);
else
_set_error(ph, "Unsupported option %d",
options[i].option);
goto out;
}
}
if (key_type != NULL) {
switch (ph->card_type) {
case CARD_TYPE_CCA:
if (strcasecmp(key_type, KEY_TYPE_CCA_AESDATA) != 0 &&
strcasecmp(key_type, KEY_TYPE_CCA_AESCIPHER) != 0) {
_set_error(ph, "The KMIP plugin is bound to "
"CCA-type APQNs, and can only "
"import keys of type '%s' or '%s'.",
KEY_TYPE_CCA_AESDATA,
KEY_TYPE_CCA_AESCIPHER);
rc = -EINVAL;
goto out;
}
break;
case CARD_TYPE_EP11:
if (strcasecmp(key_type, KEY_TYPE_EP11_AES) != 0) {
_set_error(ph, "The KMIP plugin is bound to "
"CCA-type APQNs, and can only "
"import keys of type '%s'.",
KEY_TYPE_EP11_AES);
rc = -EINVAL;
goto out;
}
break;
default:
break;
}
}
if (!ph->config_complete) {
_set_error(ph, "The configuration is incomplete, run 'zkey "
"kms configure [OPTIONS]' to complete the "
"configuration.");
return -EINVAL;
}
if (ph->connection == NULL) {
rc = _connect_to_server(ph);
if (rc != 0)
return rc;
}
if (label_pattern != NULL &&
strchr(label_pattern, '*') == NULL &&
strchr(label_pattern, '?') == NULL)
label_filter = true;
num_attrs = 3 + num_properties;
if (label_filter)
num_attrs += 1;
attrs = util_zalloc(num_attrs * sizeof(struct kmip_node *));
k = 0;
attrs[k] = kmip_new_state(KMIP_STATE_ACTIVE);
CHECK_ERROR(attrs[k] == NULL, rc, -ENOMEM,
"Allocate KMIP node failed", ph, out);
k++;
attrs[k] = kmip_new_object_type(KMIP_OBJECT_TYPE_SYMMETRIC_KEY);
CHECK_ERROR(attrs[k] == NULL, rc, -ENOMEM,
"Allocate KMIP node failed", ph, out);
k++;
attrs[k] = kmip_new_cryptographic_algorithm(KMIP_CRYPTO_ALGO_AES);
CHECK_ERROR(attrs[k] == NULL, rc, -ENOMEM,
"Allocate KMIP node failed", ph, out);
k++;
if (label_filter) {
attrs[k] = kmip_new_name(label_pattern,
KMIP_NAME_TYPE_UNINTERPRETED_TEXT_STRING);
CHECK_ERROR(attrs[k] == NULL, rc, -ENOMEM,
"Allocate KMIP node failed", ph, out);
k++;
}
for (i = 0; i < num_properties; i++) {
attrs[k] = _build_attr_from_prop(ph, &properties[i]);
CHECK_ERROR(attrs[k] == NULL, rc, -ENOMEM,
"Allocate KMIP node failed", ph, out);
k++;
}
req_pl = kmip_new_locate_request_payload(NULL, 0, 0, 0, 0,
num_attrs, attrs);
CHECK_ERROR(req_pl == NULL, rc, -ENOMEM, "Allocate KMIP node failed",
ph, out);
rc = _perform_kmip_request(ph, KMIP_OPERATION_LOCATE, req_pl, &resp_pl);
if (rc != 0)
goto out;
for (i = 0; ; i++) {
rc = kmip_get_locate_response_payload(resp_pl, NULL, NULL, i,
&item_uid);
if (rc != 0)
break;
rc = kmip_get_unique_identifier(item_uid, &id, NULL, NULL);
CHECK_ERROR(rc != 0, rc, rc, "Failed to get item id", ph, out);
pr_verbose(&ph->pd, "Item ID: '%s'", id);
rc = _process_list_item(ph, id, label_pattern, key_type,
callback, private_data);
if (rc != 0)
goto out;
kmip_node_free(item_uid);
item_uid = NULL;
}
rc = 0;
out:
if (key_type != NULL)
free(key_type);
if (attrs != NULL) {
for (i = 0; i < num_attrs; i++)
kmip_node_free(attrs[i]);
free(attrs);
}
kmip_node_free(req_pl);
kmip_node_free(resp_pl);
kmip_node_free(item_uid);
return rc;
}
/**
* Imports a key from the KMS and returns a secure key that is
* enciphered under the current HSM master key.
*
* @param handle the KMS plugin handle obtained from kms_initialize()
* @param key_id the key-ID of the key to import
* @param key_type the zkey key type, like 'CCA-AESDATA',
* 'CCA-AESCIPHER', 'EP11-AES'.
* @param key_blob a buffer to return the key blob. The size of the
* buffer is specified in key_blob_length
* @param key_blob_length on entry: the size of the key_blob buffer.
* on exit: the size of the key blob returned.
*
* @returns 0 on success, or a negative errno in case of an error.
* Function kms_get_last_error() can be used to obtain more details about the
* error.
*/
int kms_import_key2(const kms_handle_t handle, const char *key_id,
const char *key_type,
unsigned char *key_blob, size_t *key_blob_length)
{
struct plugin_handle *ph = handle;
int rc = 0;
util_assert(handle != NULL, "Internal error: handle is NULL");
util_assert(key_blob != NULL, "Internal error: key_blob is NULL");
util_assert(key_blob_length != NULL, "Internal error: key_blob_length "
"is NULL");
pr_verbose(&ph->pd, "Import Key, key-ID: '%s'", key_id);
plugin_clear_error(&ph->pd);
if (!ph->config_complete) {
_set_error(ph, "The configuration is incomplete, run 'zkey "
"kms configure [OPTIONS]' to complete the "
"configuration.");
return -EINVAL;
}
if (ph->connection == NULL) {
rc = _connect_to_server(ph);
if (rc != 0)
return rc;
}
switch (ph->card_type) {
case CARD_TYPE_CCA:
if (key_type == NULL ||
(key_type != NULL && strcmp(key_type,
KMIP_KEY_TYPE_ANY) == 0))
key_type = KEY_TYPE_CCA_AESDATA;
if (strcasecmp(key_type, KEY_TYPE_CCA_AESDATA) != 0 &&
strcasecmp(key_type, KEY_TYPE_CCA_AESCIPHER) != 0) {
_set_error(ph, "The KMIP plugin is bound to "
"CCA-type APQNs, and can only "
"import keys of type '%s' or '%s'.",
KEY_TYPE_CCA_AESDATA,
KEY_TYPE_CCA_AESCIPHER);
return -EINVAL;
}
break;
case CARD_TYPE_EP11:
if (key_type == NULL ||
(key_type != NULL && strcmp(key_type,
KMIP_KEY_TYPE_ANY) == 0))
key_type = KEY_TYPE_EP11_AES;
if (strcasecmp(key_type, KEY_TYPE_EP11_AES) != 0) {
_set_error(ph, "The KMIP plugin is bound to "
"EP11-type APQNs, and can only "
"import keys of type '%s'.",
KEY_TYPE_EP11_AES);
return -EINVAL;
}
break;
default:
break;
}
rc = _retrieve_key(ph, key_id, key_blob, key_blob_length, key_type,
0);
return rc;
}
static const struct kms_functions kms_functions = {
.api_version = KMS_API_VERSION_2,
.kms_bind = kms_bind,
.kms_initialize = kms_initialize,
.kms_terminate = kms_terminate,
.kms_get_last_error = kms_get_last_error,
.kms_supports_key_type = kms_supports_key_type,
.kms_display_info = kms_display_info,
.kms_get_command_options = kms_get_command_options,
.kms_configure = kms_configure,
.kms_deconfigure = kms_deconfigure,
.kms_login = kms_login,
.kms_reenciper = kms_reenciper,
.kms_generate_key = kms_generate_key,
.kms_set_key_properties = kms_set_key_properties,
.kms_get_key_properties = kms_get_key_properties,
.kms_remove_key = kms_remove_key,
.kms_list_keys = kms_list_keys,
.kms_import_key2 = kms_import_key2,
};
/**
* Returns an address of a structure containing the KMS plugin functions.
* This function is exported by the KMS plugin, and its address is obtain
* via dlsym() after loading the plugin via dlopen().
* *
* @returns the address of a structure or NULL in case of an error.
*/
const struct kms_functions *kms_get_functions(void)
{
return &kms_functions;
}