/* * 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 #include #include #include #include #include #include #include #include #include #include #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 #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 '=(;=" ")*[;]' 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 '=[critical,]" " (;=[critical,])*[;]' 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, ®_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; }