/*
 * zkey-ekmfweb - EKMFWeb zkey KMS plugin
 *
 * Copyright IBM Corp. 2020
 *
 * 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 <argz.h>
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>
#include <stdarg.h>
#include <string.h>
#include <errno.h>
#include <err.h>
#include <sys/utsname.h>

#include <openssl/evp.h>
#include <openssl/x509.h>
#include <openssl/pem.h>
#include <openssl/opensslv.h>

#include "lib/zt_common.h"
#include "lib/util_libc.h"
#include "lib/util_panic.h"
#include "lib/util_path.h"
#include "lib/util_base.h"
#include "lib/util_rec.h"

#include "zkey-ekmfweb.h"
#include "../kms-plugin.h"
#include "../cca.h"
#include "../utils.h"
#include "../pkey.h"
#include "../properties.h"

#ifndef OPENSSL_VERSION_PREREQ
	#if defined(OPENSSL_VERSION_MAJOR) && defined(OPENSSL_VERSION_MINOR)
		#define OPENSSL_VERSION_PREREQ(maj, min)		\
			((OPENSSL_VERSION_MAJOR << 16) +		\
			OPENSSL_VERSION_MINOR >= ((maj) << 16) + (min))
	#else
		#define OPENSSL_VERSION_PREREQ(maj, min)		\
			(OPENSSL_VERSION_NUMBER >= (((maj) << 28) |	\
			((min) << 20)))
	#endif
#endif

#if OPENSSL_VERSION_PREREQ(3, 0)
#include <openssl/core_names.h>
#endif

#define FREE_AND_SET_NULL(ptr)					\
	do {							\
		if ((ptr) != NULL)				\
			free((void *)ptr);			\
		(ptr) = NULL;					\
	} while (0)

#define _set_error(ph, fmt...)	plugin_set_error(&(ph)->pd, fmt)

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

/**
 * Base64-encodes the passphrase to make it unreadable.
 *
 * @param passphrase        the passphrase to encode
 *
 * @returns the encoded passphrase or NULL in case of an error.
 * The caller must free the string when no longer needed.
 */
static char *_encode_passphrase(const char *passphrase)
{
	int inlen, outlen, len;
	char *out;

	inlen = strlen(passphrase);
	outlen = (inlen / 3) * 4;
	if (inlen % 3 > 0)
		outlen += 4;

	out = util_malloc(outlen + 1);
	memset(out, 0, outlen + 1);

	len = EVP_EncodeBlock((unsigned char *)out, (unsigned char *)passphrase,
			      inlen);
	if (len != outlen) {
		free(out);
		return NULL;
	}

	out[outlen] = '\0';
	return out;
}

/**
 * Base64-decodes the passphrase
 *
 * @param passphrase        the passphrase to decode
 *
 * @returns the decoded passphrase or NULL in case of an error.
 * The caller must free the string when no longer needed.
 */
static char *_decode_passphrase(const char *passphrase)
{
	int inlen, outlen, len;
	char *out;

	inlen = strlen(passphrase);
	outlen = (inlen / 4) * 3;
	if (inlen % 4 > 0)
		outlen += 3;

	out = util_malloc(outlen + 1);
	memset(out, 0, outlen + 1);

	len = EVP_DecodeBlock((unsigned char *)out, (unsigned char *)passphrase,
			      inlen);
	if (len != outlen) {
		free(out);
		return NULL;
	}

	out[outlen] = '\0';
	return out;
}

/**
 * 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, EKMFWEB_CONFIG_APQNS);

	ph->connection_configured =
		plugin_check_property(&ph->pd, EKMFWEB_CONFIG_URL) &&
		plugin_check_property(&ph->pd,
				      EKMFWEB_CONFIG_VERIFY_SERVER_CERT) &&
		plugin_check_property(&ph->pd, EKMFWEB_CONFIG_VERIFY_HOSTNAME);

	ph->settings_retrieved =
		plugin_check_property(&ph->pd, EKMFWEB_CONFIG_EKMFWEB_PUBKEY);

	ph->templates_retrieved =
		plugin_check_property(&ph->pd, EKMFWEB_CONFIG_TEMPLATE_XTS1) &&
		plugin_check_property(&ph->pd, EKMFWEB_CONFIG_TEMPLATE_XTS2) &&
		plugin_check_property(&ph->pd,
				      EKMFWEB_CONFIG_TEMPLATE_NONXTS) &&
		plugin_check_property(&ph->pd,
				      EKMFWEB_CONFIG_TEMPLATE_IDENTITY) &&
		plugin_check_property(&ph->pd,
				      EKMFWEB_CONFIG_TEMPLATE_XTS1_LABEL) &&
		plugin_check_property(&ph->pd,
				      EKMFWEB_CONFIG_TEMPLATE_XTS2_LABEL) &&
		plugin_check_property(&ph->pd,
				      EKMFWEB_CONFIG_TEMPLATE_NONXTS_LABEL) &&
		plugin_check_property(&ph->pd,
				      EKMFWEB_CONFIG_TEMPLATE_IDENTITY_LABEL) &&
		plugin_check_property(&ph->pd,
				      EKMFWEB_CONFIG_TEMPLATE_XTS1_ID) &&
		plugin_check_property(&ph->pd,
				      EKMFWEB_CONFIG_TEMPLATE_XTS2_ID) &&
		plugin_check_property(&ph->pd,
				      EKMFWEB_CONFIG_TEMPLATE_NONXTS_ID) &&
		plugin_check_property(&ph->pd,
				      EKMFWEB_CONFIG_TEMPLATE_IDENTITY_ID);

	ph->identity_key_generated =
		plugin_check_property(&ph->pd, EKMFWEB_CONFIG_IDENTITY_KEY) &&
		plugin_check_property(&ph->pd,
				      EKMFWEB_CONFIG_IDENTITY_KEY_ALGORITHM) &&
		plugin_check_property(&ph->pd,
				      EKMFWEB_CONFIG_IDENTITY_KEY_PARAMS);

	ph->registered =
		plugin_check_property(&ph->pd,
				      EKMFWEB_CONFIG_IDENTITY_KEY_LABEL) &&
		plugin_check_property(&ph->pd, EKMFWEB_CONFIG_IDENTITY_KEY_ID);

	ph->config_complete = ph->apqns_configured &&
			      ph->connection_configured &&
			      ph->settings_retrieved &&
			      ph->templates_retrieved &&
			      ph->identity_key_generated &&
			      ph->registered;
}

/**
 * Gets the EKMF 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_ekmf_config(struct plugin_handle *ph)
{
	char *tmp;

	ph->ekmf_config.identity_secure_key = properties_get(ph->pd.properties,
						EKMFWEB_CONFIG_IDENTITY_KEY);

	ph->ekmf_config.base_url = properties_get(ph->pd.properties,
						  EKMFWEB_CONFIG_URL);
	ph->ekmf_config.tls_ca = properties_get(ph->pd.properties,
						EKMFWEB_CONFIG_CA_BUNDLE);
	ph->ekmf_config.tls_client_cert = properties_get(ph->pd.properties,
						EKMFWEB_CONFIG_CLIENT_CERT);
	ph->ekmf_config.tls_client_key = properties_get(ph->pd.properties,
						EKMFWEB_CONFIG_CLIENT_KEY);

	tmp = properties_get(ph->pd.properties,
			    EKMFWEB_CONFIG_CLIENT_KEY_PASSPHRASE);
	if (tmp != NULL) {
		ph->ekmf_config.tls_client_key_passphrase =
				_decode_passphrase(tmp);
		free(tmp);
	}
	ph->ekmf_config.tls_issuer_cert = NULL;
	ph->ekmf_config.tls_pinned_pubkey = properties_get(ph->pd.properties,
						EKMFWEB_CONFIG_SERVER_PUBKEY);
	ph->ekmf_config.tls_server_cert = properties_get(ph->pd.properties,
						EKMFWEB_CONFIG_SERVER_CERT);
	tmp = properties_get(ph->pd.properties,
			     EKMFWEB_CONFIG_VERIFY_SERVER_CERT);
	ph->ekmf_config.tls_verify_peer =
				(tmp != NULL && strcasecmp(tmp, "yes") == 0);
	if (tmp != NULL)
		free(tmp);
	tmp = properties_get(ph->pd.properties, EKMFWEB_CONFIG_VERIFY_HOSTNAME);
	ph->ekmf_config.tls_verify_host =
				(tmp != NULL && strcasecmp(tmp, "yes") == 0);
	if (tmp != NULL)
		free(tmp);
	ph->ekmf_config.max_redirs = 0;

	ph->ekmf_config.login_token = properties_get(ph->pd.properties,
						EKMFWEB_CONFIG_LOGIN_TOKEN);

	ph->ekmf_config.ekmf_server_pubkey = properties_get(ph->pd.properties,
						EKMFWEB_CONFIG_EKMFWEB_PUBKEY);

	return 0;
}

/**
 * Frees the EKMF config structure contents
 *
 * @param ph                the plugin handle
 */
static void _free_ekmf_config(struct plugin_handle *ph)
{
	if (ph->ekmf_config.base_url != NULL)
		free((void *)ph->ekmf_config.base_url);
	if (ph->ekmf_config.tls_ca != NULL)
		free((void *)ph->ekmf_config.tls_ca);
	if (ph->ekmf_config.tls_client_cert != NULL)
		free((void *)ph->ekmf_config.tls_client_cert);
	if (ph->ekmf_config.tls_client_key != NULL)
		free((void *)ph->ekmf_config.tls_client_key);
	if (ph->ekmf_config.tls_client_key_passphrase != NULL)
		free((void *)ph->ekmf_config.tls_client_key_passphrase);
	if (ph->ekmf_config.tls_issuer_cert != NULL)
		free((void *)ph->ekmf_config.tls_issuer_cert);
	if (ph->ekmf_config.tls_pinned_pubkey != NULL)
		free((void *)ph->ekmf_config.tls_pinned_pubkey);
	if (ph->ekmf_config.tls_server_cert != NULL)
		free((void *)ph->ekmf_config.tls_server_cert);
	if (ph->ekmf_config.login_token != NULL)
		free((void *)ph->ekmf_config.login_token);
	if (ph->ekmf_config.identity_secure_key != NULL)
		free((void *)ph->ekmf_config.identity_secure_key);
	if (ph->ekmf_config.ekmf_server_pubkey != NULL)
		free((void *)ph->ekmf_config.ekmf_server_pubkey);
}

/**
 * Removes the login token file, if the error indicates an authorization or
 * authentication error (-EACCES or -EPERM)
 *
 * @param ph                the plugin handle
 * @param error             the negative errno value of the last error
 */
static void _remove_login_token_if_error(struct plugin_handle *ph, int error)
{
	switch (error) {
	case -EACCES:
	case -EPERM:
		remove(ph->ekmf_config.login_token);
		FREE_AND_SET_NULL(ph->ekmf_config.login_token);
		break;
	default:
		break;
	}

	return;
}

/**
 * UnlLoads the CCA library
 *
 * @param ph                the plugin handle
 */
static void _unload_cca_library(struct plugin_handle *ph)
{
	if (ph->cca.cca_lib != NULL)
		dlclose(ph->cca.cca_lib);
	ph->cca.cca_lib = NULL;

	ph->ext_lib.type = 0;
	ph->ext_lib.cca = NULL;
}

/**
 * Selects one the CCA APQNs associated with this plugin, and loads the CCA
 * library and sets up the external library field in the plugin handle.
 *
 * @param ph                the plugin handle
 *
 * @returns 0 on success, a negative errno in case of an error.
 */
static int _select_cca_adapter(struct plugin_handle *ph)
{
	struct cca_lib cca = { 0 };
	char *apqns;
	int rc = 0;

	apqns = properties_get(ph->pd.properties, EKMFWEB_CONFIG_APQNS);
	if (apqns == NULL) {
		_set_error(ph, "No APQN are associated with the plugin.");
		rc = -ENODEV;
		goto out;
	}

	pr_verbose(&ph->pd, "Associated APQNs: %s", apqns);

	_unload_cca_library(ph);

	rc = select_cca_adapter_by_apqns(&ph->pd, apqns, &cca);
	if (rc != 0) {
		_set_error(ph, "Failed to select one of the associated APQNs: "
			   "%s", apqns);
		goto out;
	}

	ph->cca.cca_lib = cca.lib_csulcca;
	ph->ext_lib.type = EKMF_EXT_LIB_CCA;
	ph->ext_lib.cca = &ph->cca;

out:
	if (apqns != NULL)
		free(apqns);

	if (rc != 0)
		_unload_cca_library(ph);

	return rc;
}

/**
 * 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;
	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-ekmfweb", config_path,
			 EKMFWEB_CONFIG_FILE, verbose);
	if (rc != 0)
		goto error;

	rc = _get_ekmf_config(ph);
	if (rc != 0)
		goto error;

	_check_config_complete(ph);
	pr_verbose(&ph->pd, "Plugin configuration is %scomplete",
		   ph->config_complete ? "" : "in");

	return (kms_handle_t)ph;

error:
	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");

	_free_ekmf_config(ph);
	_unload_cca_library(ph);

	if (ph->curl_handle != NULL)
		ekmf_curl_destroy(ph->curl_handle);

	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);

	if (strcasecmp(key_type, KEY_TYPE_CCA_AESCIPHER) == 0)
		return true;

	return false;
}

/**
 * Returns information about the public key in the PEM file
 *
 * @param ph               the plugin handle
 * @param pem_file         the name of a PEM file containing the public key
 * @param pkey_type        on return: If not NULL, the PKEY type (EVP_PKEY_EC
 *                         or EVP_PKEY_RSA)
 * @param ecc_curve_nid    on return: If not NULL and it is an ECC key, the
 *                         OpenSSL NID of the curve of the ECC key.
 * @param rsa_mod_bits     on return: If not NULL and it is an RSA key, the
 *                         modulus bit size of the RSA key.
 *
 * @returns 0 on success, or a negative errno in case of an error.
 */
static int _get_pub_key_info(struct plugin_handle *ph, const char *pem_file,
			     int *pkey_type, int *ecc_curve_nid,
			     int *rsa_mod_bits)
{
	int rc = 0, curve_nid, mod_len;
#if OPENSSL_VERSION_PREREQ(3, 0)
	size_t curve_len;
	char curve[80];
#endif
	EVP_PKEY *pkey;
	FILE *fp;

	fp = fopen(pem_file, "r");
	if (fp == NULL) {
		rc = -errno;
		_set_error(ph, "Failed to open pubkey PEM file '%s': %s",
			   pem_file, strerror(-rc));
		return rc;
	}

	pkey = PEM_read_PUBKEY(fp, NULL, NULL, NULL);

	fclose(fp);

	if (pkey == NULL) {
		rc = -EIO;
		_set_error(ph, "Failed to read pubkey from PEM file '%s': %s",
			   pem_file, strerror(-rc));
		return rc;
	}

	if (pkey_type != NULL)
		*pkey_type = EVP_PKEY_id(pkey);

	switch (EVP_PKEY_id(pkey)) {
	case EVP_PKEY_EC:
#if !OPENSSL_VERSION_PREREQ(3, 0)
		curve_nid = EC_GROUP_get_curve_name(EC_KEY_get0_group(
						EVP_PKEY_get0_EC_KEY(pkey)));
#else
		if (!EVP_PKEY_get_utf8_string_param(pkey,
						    OSSL_PKEY_PARAM_GROUP_NAME,
						    curve, sizeof(curve),
						    &curve_len)) {
			EVP_PKEY_free(pkey);
			_set_error(ph, "Failed to get the curve name");
			return -EIO;
		}
		curve_nid = OBJ_sn2nid(curve);
#endif
		if (ecc_curve_nid != NULL)
			*ecc_curve_nid = curve_nid;
		break;

	case EVP_PKEY_RSA:
#if !OPENSSL_VERSION_PREREQ(3, 0)
		mod_len = BN_num_bits(RSA_get0_n(EVP_PKEY_get0_RSA(pkey)));
#else
		if (!EVP_PKEY_get_int_param(pkey, OSSL_PKEY_PARAM_BITS,
					    &mod_len)) {
			EVP_PKEY_free(pkey);
			_set_error(ph, "Failed to get the RSA key size");
			return -EIO;
		}
#endif
		if (rsa_mod_bits != NULL)
			*rsa_mod_bits = mod_len;
		break;

	default:
		rc = -EIO;
		_set_error(ph, "Unknown pubkey type: %d", EVP_PKEY_id(pkey));
		break;
	}

	EVP_PKEY_free(pkey);
	return rc;
}

/**
 * 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)
{
	int rc, type = 0, curve = 0, mod_bits = 0;
	struct plugin_handle *ph = handle;
	char *tmp = NULL;
	bool rsa = false;
#ifdef EKMFWEB_SUPPORTS_RSA_DIGESTS_AND_PSS_SIGNATURES
	char *info;
#endif

	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, EKMFWEB_CONFIG_URL);
	printf("  EKMF Web server:      %s\n", tmp != NULL ? tmp :
			"(configuration required)");
	if (tmp != NULL)
		free(tmp);
	else
		return 0;
	tmp = properties_get(ph->pd.properties, EKMFWEB_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, EKMFWEB_CONFIG_CLIENT_CERT);
	printf("  Client certificate:   %s\n", tmp != NULL ? tmp : "(none)");
	if (tmp != NULL)
		free(tmp);
	tmp = properties_get(ph->pd.properties, EKMFWEB_CONFIG_CLIENT_KEY);
	printf("  Client private key:   %s\n", tmp != NULL ? tmp : "(none)");
	if (tmp != NULL) {
		free(tmp);
		tmp = properties_get(ph->pd.properties,
				     EKMFWEB_CONFIG_CLIENT_KEY_PASSPHRASE);
		if (tmp != NULL) {
			printf("                        "
			       "(passphrase protected)\n");
			free(tmp);
		}
	}
	tmp = properties_get(ph->pd.properties, EKMFWEB_CONFIG_SERVER_CERT);
	if (tmp != NULL) {
		printf("  Trusting the server certificate\n");
		free(tmp);
	}
	tmp = properties_get(ph->pd.properties, EKMFWEB_CONFIG_SERVER_PUBKEY);
	if (tmp != NULL) {
		printf("  Using server public key pinning\n");
		free(tmp);
	}
	tmp = properties_get(ph->pd.properties,
			     EKMFWEB_CONFIG_VERIFY_SERVER_CERT);
	if (tmp != NULL) {
		if (strcasecmp(tmp, "yes") == 0)
			printf("  The server's certificate must be valid\n");
		free(tmp);
	} else {
		printf("  The server's certificate is not verified\n");
	}
	tmp = properties_get(ph->pd.properties, EKMFWEB_CONFIG_VERIFY_HOSTNAME);
	if (tmp != NULL) {
		if (strcasecmp(tmp, "yes") == 0)
			printf("  The server's certificate must match the "
			       "hostname\n");
		free(tmp);
	}

	tmp = properties_get(ph->pd.properties, EKMFWEB_CONFIG_EKMFWEB_PUBKEY);
	if (tmp != NULL) {
		rc = _get_pub_key_info(ph, tmp, &type, &curve, &mod_bits);
		if (rc == 0) {
			switch (type) {
			case EVP_PKEY_EC:
				printf("  EKMF Web public key:  ECC (%s)\n",
				       OBJ_nid2sn(curve));
				break;
			case EVP_PKEY_RSA:
				printf("  EKMF Web public key:  RSA "
				       "(%d bits)\n", mod_bits);
				break;
			default:
				printf("  EKMF Web public key:  "
				       "(unknown key type)\n");
				break;
			}
		} else {
			printf("  EKMF Web public key:  (not available)\n");
		}
		free(tmp);
	} else {
		printf("  EKMF Web public key:  (configuration required)\n");
	}

	printf("  Key templates:\n");
	tmp = properties_get(ph->pd.properties,
			     EKMFWEB_CONFIG_TEMPLATE_IDENTITY);
	printf("    Identity:           %s\n", tmp != NULL ? tmp :
			"(configuration required)");
	if (tmp != NULL)
		free(tmp);
	tmp = properties_get(ph->pd.properties,
			     EKMFWEB_CONFIG_TEMPLATE_IDENTITY_LABEL);
	if (tmp != NULL) {
		printf("      Label template:   %s\n", tmp);
		free(tmp);
	}

	tmp = properties_get(ph->pd.properties, EKMFWEB_CONFIG_TEMPLATE_XTS1);
	printf("    XTS-Key1:           %s\n", tmp != NULL ? tmp :
			"(configuration required)");
	if (tmp != NULL)
		free(tmp);
	tmp = properties_get(ph->pd.properties,
			     EKMFWEB_CONFIG_TEMPLATE_XTS1_LABEL);
	if (tmp != NULL) {
		printf("      Label template:   %s\n", tmp);
		free(tmp);
	}

	tmp = properties_get(ph->pd.properties, EKMFWEB_CONFIG_TEMPLATE_XTS2);
	printf("    XTS-Key2:           %s\n", tmp != NULL ? tmp :
			"(configuration required)");
	if (tmp != NULL)
		free(tmp);
	tmp = properties_get(ph->pd.properties,
			     EKMFWEB_CONFIG_TEMPLATE_XTS2_LABEL);
	if (tmp != NULL) {
		printf("      Label template:   %s\n", tmp);
		free(tmp);
	}

	tmp = properties_get(ph->pd.properties, EKMFWEB_CONFIG_TEMPLATE_NONXTS);
	printf("    Non-XTS:            %s\n", tmp != NULL ? tmp :
			"(configuration required)");
	if (tmp != NULL)
		free(tmp);
	tmp = properties_get(ph->pd.properties,
			     EKMFWEB_CONFIG_TEMPLATE_NONXTS_LABEL);
	if (tmp != NULL) {
		printf("      Label template:   %s\n", tmp);
		free(tmp);
	}

	tmp = properties_get(ph->pd.properties,
			     EKMFWEB_CONFIG_IDENTITY_KEY_ALGORITHM);
	if (tmp != NULL) {
		printf("  Identity key:         %s", tmp);
		rsa = strcmp(tmp, EKMFWEB_KEY_ALGORITHM_RSA) == 0;
		free(tmp);
		tmp = properties_get(ph->pd.properties,
				     EKMFWEB_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,
			     EKMFWEB_CONFIG_IDENTITY_KEY_REENC);
	if (tmp != NULL) {
		printf("                        (re-enciphering pending)\n");
		free(tmp);
	}

	tmp = properties_get(ph->pd.properties,
			     EKMFWEB_CONFIG_IDENTITY_KEY_LABEL);
	printf("  Registered key label: %s\n", tmp != NULL ?
			tmp : "(registration required)");
	if (tmp != NULL)
		free(tmp);

#ifdef EKMFWEB_SUPPORTS_RSA_DIGESTS_AND_PSS_SIGNATURES
	printf("  Key transport settings:\n");

	tmp = properties_get(ph->pd.properties,
			     EKMFWEB_CONFIG_SESSION_KEY_CURVE);
	printf("    Session Key:        ECC (%s)\n",
	       tmp != NULL ? tmp : "secp521r1");
	if (tmp != NULL)
		free(tmp);

	info = properties_get(ph->pd.properties,
			      EKMFWEB_CONFIG_IDENTITY_KEY_INFO);
	if (info != NULL && strncmp(info, "RSA", 3) == 0) {
		tmp = properties_get(ph->pd.properties,
				     EKMFWEB_CONFIG_SESSION_RSA_SIGN_DIGEST);
		printf("    RSA sign digest:    %s\n",
		       tmp != NULL ? tmp : "SHA512");
		if (tmp != NULL)
			free(tmp);
		tmp = properties_get(ph->pd.properties,
				     EKMFWEB_CONFIG_SESSION_RSA_SIGN_PSS);
		printf("    RSA sign alorithm:  %s\n",
		       tmp != NULL && strcasecmp(tmp, "yes") == 0 ?
							"RSA-PSS" : "RSA");
		if (tmp != NULL)
			free(tmp);
	}
	if (info != NULL)
		free(info);
#endif

	return 0;
}
#define OPT_TLS_CLIENT_CERT			256
#define OPT_TLS_CLIENT_KEY			257
#define OPT_TLS_CLIENT_KEY_PASSPHRASE		258
#define OPT_TLS_PIN_SERVER_PUBKEY		259
#define OPT_TLS_TRUST_SERVER_CERT		260
#define OPT_TLS_DONT_VERIFY_SERVER_CERT		261
#define OPT_TLS_VERIFY_HOSTNAME			262
#ifdef EKMFWEB_SUPPORTS_RSA_DIGESTS_AND_PSS_SIGNATURES
#define OPT_KT_RSA_SIGNATURE_DIGEST		263
#define OPT_KT_RSA_PSS_SIGNATURE		264
#endif

static const struct util_opt configure_options[] = {
	{
		.flags = UTIL_OPT_FLAG_SECTION,
		.desc = "EKMFWEB SPECIFIC OPTIONS FOR THE SERVER CONNECTION",
		.command = KMS_COMMAND_CONFIGURE,
	},
	{
		.option = { "ekmfweb-url", required_argument, NULL, 'u'},
		.argument = "URL",
		.desc = "The URL of the EKMF Web server. The URL should start "
			"with 'https://', and may contain a port number "
			"separated by a colon. If no port number is specified, "
			"443 is used for HTTPS.",
		.command = KMS_COMMAND_CONFIGURE,
	},
	{
		.option = { "tls-ca-bundle", required_argument, NULL, 'b'},
		.argument = "CA-BUNDLE",
		.desc = "The CA bundle PEM file or directory containing the CA "
			"certificates used to verify the EKMF Web server "
			"certificate during TLS handshake. If this specifies a "
			"directory path, then this directory must have been "
			"prepared with OpenSSL's c_rehash utility. Default are "
			"the system CA certificates.",
		.command = KMS_COMMAND_CONFIGURE,
	},
	{
		.option = { "tls-client-cert", required_argument, NULL,
							OPT_TLS_CLIENT_CERT },
		.flags = UTIL_OPT_FLAG_NOSHORT,
		.argument = "PEM-FILE",
		.desc = "The PEM file containing the client's TLS certificate "
			"for use with TLS client authentication.",
		.command = KMS_COMMAND_CONFIGURE,
	},
	{
		.option = { "tls-client-key", required_argument, NULL,
							OPT_TLS_CLIENT_KEY },
		.flags = UTIL_OPT_FLAG_NOSHORT,
		.argument = "PEM-FILE",
		.desc = "The PEM file containing the client's private key "
			"for use with TLS client authentication.",
		.command = KMS_COMMAND_CONFIGURE,
	},
	{
		.option = { "tls-client-key-passphrase", required_argument,
					NULL, OPT_TLS_CLIENT_KEY_PASSPHRASE },
		.flags = UTIL_OPT_FLAG_NOSHORT,
		.argument = "PASSPHRASE",
		.desc = "If the PEM file is passphrase protected, this option "
			"specifies the passphrase to unlock the PEM file that "
			"is specified with option '--tls-client-key'.",
		.command = KMS_COMMAND_CONFIGURE,
	},
	{
		.option = { "tls-pin-server-pubkey", 0, NULL,
				OPT_TLS_PIN_SERVER_PUBKEY },
		.flags = UTIL_OPT_FLAG_NOSHORT,
		.desc = "Pin the EKMF Web server's public key to verify on "
			"every connection that the public key of the EKMF Web "
			"server's certificate is the same that was used when "
			"the connection to the EKMF Web server was configured. "
			"This option can only be used with CA signed EKMF Web "
			"server certificates.",
		.command = KMS_COMMAND_CONFIGURE,
	},
	{
		.option = { "tls-trust-server-cert", 0, NULL,
				OPT_TLS_TRUST_SERVER_CERT },
		.flags = UTIL_OPT_FLAG_NOSHORT,
		.desc = "Trust the EKMF Web server's certificate even if it is "
			"a self signed certificate, or could not be verified "
			"due to other reasons. This option can be used instead "
			"of option '--tls-pin-server-pubkey' with self signed "
			"EKMF Web 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 EKMF Web "
			"server's certificate. For self signed EKMF Web server "
			"certificates, this is the default. Use option "
			"'--tls-pin-server-cert' to ensure the self signed "
			"certificate's authenticity explicitly. CA signed "
			"EKMF Web server certificates are verified by default. "
			"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 = "Verify that the EKMF Web server certificate's 'Common "
			"Name' field or a 'Subject Alternate Name' field "
			"matches the host name used to connect to the EKMF "
			"Web server.",
		.command = KMS_COMMAND_CONFIGURE,
	},
	{
		.option = { "refresh-settings", 0, NULL, 'R' },
		.desc = "Refresh the EKMF Web server settings. This is "
			"automatically performed when the connection to the "
			"EKMF Web server is (re-)configured. Use this option "
			"when the settings of the already configured EKMF Web "
			"server have changed",
		.command = KMS_COMMAND_CONFIGURE,
	},
	{
		.flags = UTIL_OPT_FLAG_SECTION,
		.desc = "EKMFWEB SPECIFIC OPTIONS FOR IDENTITY KEY GENERATION",
		.command = KMS_COMMAND_CONFIGURE,
	},
	{
		.option = { "gen-identity-key", 0, NULL, 'i'},
		.desc = "Generate an identity key for the EKMF Web plugin. "
			"An identity key is automatically generated when the "
			"EKMF Web server connection has been configured. Use "
			"this option to generate a new identity key. You need "
			"to re-generate a registration certificate with the "
			"newly generated identity key, and re-register this "
			"zkey client with the EKMF Web server.",
		.command = KMS_COMMAND_CONFIGURE,
	},
	{
		.flags = UTIL_OPT_FLAG_SECTION,
		.desc = "EKMFWEB SPECIFIC OPTIONS FOR CERTIFICATE GENERATION",
		.command = KMS_COMMAND_CONFIGURE,
	},
	{
		.option = { "gen-csr", required_argument, NULL, 'c'},
		.argument = "CSR-PEM-FILE",
		.desc = "Generate a certificate signing request (CSR) with the "
			"identity key and store it into the specified PEM "
			"file. You pass this CSR to a certificate authority "
			"(CA) to have it issue a CA signed certificate for the "
			"EKMF Web plugin. You need to register the certificate "
			"with EKMF Web before you can access EKMF Web.",
		.command = KMS_COMMAND_CONFIGURE,
	},
	{
		.option = { "gen-self-signed-cert", required_argument, NULL,
			    'C'},
		.argument = "CERT-PEM-FILE",
		.desc = "Generate a self signed certificate with the "
			"identity key and store it into the specified PEM "
			"file. You need to register the certificate with EKMF "
			"Web before you can access EKMF Web.",
		.command = KMS_COMMAND_CONFIGURE,
	},
	{
		.option = { "cert-subject", required_argument, NULL, 's'},
		.argument = "SUBJECT-RDNS",
		.desc = "The subject name for generating a certificate signing "
			"request (CSR) or self signed certificate, in the form "
			"'<type>=<value>(;<type>=<value>)*[;]' with types "
			"recognized by OpenSSL.",
		.command = KMS_COMMAND_CONFIGURE,
	},
	{
		.option = { "cert-extensions", required_argument, NULL, 'e'},
		.argument = "EXTENSIONS",
		.desc = "The certificate extensions for generating a "
			"certificate signing request (CSR) or self signed "
			"certificate, in the form '<name>=[critical,]<value(s)>"
			" (;<name>=[critical,]<value(s)>)*[;]' with extension "
			"names and values recognized by OpenSSL.",
		.command = KMS_COMMAND_CONFIGURE,
	},
	{
		.option = { "renew-cert", required_argument, NULL, 'N'},
		.argument = "CERT-PEM-FILE",
		.desc = "An existing PEM file containing the certificate to be "
			"renewed. The certificate's subject name and extensions"
			" 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 need this.",
		.command = KMS_COMMAND_CONFIGURE,
	},
	{
		.option = { "cert-validity-days", required_argument, NULL, 'd'},
		.argument = "DAYS",
		.desc = "The number of days to certify the self signed "
			"certificate. The default is 30 days.",
		.command = KMS_COMMAND_CONFIGURE,
	},
	{
		.option = { "cert-digest", required_argument, NULL, 'D'},
		.argument = "DIGEST",
		.desc = "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,
	},
#ifdef EKMF_SUPPORTS_RSA_PSS_CERTIFICATES
	{
		.option = { "cert-rsa-pss", 0, NULL, 'P'},
		.desc = "Use the RSA-PSS algorithm to sign the certificate "
			"signing request or the self signed certificate. This "
			"option is only honored when the identity key type is "
			"RSA, it is ignored otherwise.",
		.command = KMS_COMMAND_CONFIGURE,
	},
#endif
	{
		.flags = UTIL_OPT_FLAG_SECTION,
		.desc = "EKMFWEB SPECIFIC OPTIONS FOR REGISTRATION",
		.command = KMS_COMMAND_CONFIGURE,
	},
	{
		.option = { "register", required_argument, NULL, 'r'},
		.argument = "CERT-FILE",
		.desc = "Register the zkey client with EKMF Web by generating "
			"an identity key in EKMF Web using the certificate "
			"from the specified file. Supported certificate files "
			"formats are .pem, .crt, .cert, .cer, and .der (i.e. "
			"either base64 or DER encoded). If you want to "
			"register a self signed certificate that you are about "
			"to generate using option '--gen-self-signed-cert', "
			"then specify the same certificate file name here, "
			"and the generated certificate is registered "
			"right away.",
		.command = KMS_COMMAND_CONFIGURE,
	},
	{
		.option = { "label-tags", required_argument, NULL, 'T'},
		.argument = "LABEL-TAGS",
		.desc = "The label tags for generating the identity key in "
			"EKMF Web when registering the zkey client, in the "
			"form '<tag>=<value>(,<tag>=<value>)*[,]' with tags as "
			"defined by the key template. Use 'zkey kms info' to "
			"display the key templates used by zkey. For "
			"registration, the template for identity keys is used.",
		.command = KMS_COMMAND_CONFIGURE,
	},
#ifdef EKMFWEB_SUPPORTS_RSA_DIGESTS_AND_PSS_SIGNATURES
	{
		.flags = UTIL_OPT_FLAG_SECTION,
		.desc = "EKMFWEB SPECIFIC OPTIONS FOR KEY TRANSPORT",
		.command = KMS_COMMAND_CONFIGURE,
	},
	{
		.option = { "session-rsa-sign-digest", required_argument, NULL,
						OPT_KT_RSA_SIGNATURE_DIGEST },
		.argument = "DIGEST",
		.flags = UTIL_OPT_FLAG_NOSHORT,
		.desc = "The digest algorithm to use for signing the key "
			"transport request, when the identity key type is "
			"RSA. Ignored otherwise. The default is SHA512.",
		.command = KMS_COMMAND_CONFIGURE,
	},
	{
		.option = { "session-rsa-sign-algo", required_argument, NULL,
						OPT_KT_RSA_PSS_SIGNATURE },
		.argument = "ALGORITHM",
		.flags = UTIL_OPT_FLAG_NOSHORT,
		.desc = "The algorithm to use for signing the key transport "
			"request, when the identity key type is RSA, ignored "
			"otherwise. Supported algorithms are 'RSA'and "
			"'RSA-PSS'.",
		.command = KMS_COMMAND_CONFIGURE,
	},
#endif
	UTIL_OPT_END,
};

static const struct util_opt generate_options[] = {
	{
		.flags = UTIL_OPT_FLAG_SECTION,
		.desc = "EKMFWEB SPECIFIC OPTIONS",
		.command = KMS_COMMAND_GENERATE,
	},
	{
		.option = { "label-tags", required_argument, NULL, 'T'},
		.argument = "LABEL-TAGS",
		.desc = "The label tags for generating a secure key in EKMF "
			"Web, in the form '<tag>=<value>(,<tag>=<value>)*[,]' "
			"with tags as defined by the key template. Use 'zkey "
			"kms info' to display the key templates used by zkey. "
			"For XTS type keys the two templates for XTS-Key1 and "
			"XTS-Key2 are used. For non-XTS type keys, the "
			"template for Non-XTS keys is used.",
		.command = KMS_COMMAND_GENERATE,
	},
	UTIL_OPT_END,
};

static const struct util_opt remove_options[] = {
	{
		.flags = UTIL_OPT_FLAG_SECTION,
		.desc = "EKMFWEB SPECIFIC OPTIONS",
		.command = KMS_COMMAND_REMOVE,
	},
	{
		.option = { "state", required_argument, NULL, 's'},
		.argument = "STATE",
		.desc = "The state to which to change the key in EKMF Web, "
			"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 EKMF Web 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_options[] = {
	{
		.flags = UTIL_OPT_FLAG_SECTION,
		.desc = "EKMFWEB SPECIFIC OPTIONS",
		.command = KMS_COMMAND_LIST,
	},
	{
		.option = { "states", required_argument, NULL, 's'},
		.argument = "STATES",
		.desc = "The states of the keys that are to be listed. "
			"Multiple states can be separated by comma. Possible "
			"states are 'PRE-ACTIVATION', 'ACTIVE', 'DEACTIVATED', "
			"'COMPROMISED', 'DESTROYED', and "
			"'DESTROYED-COMPROMISED'. If this "
			"option is not specified, only keys in state 'ACTIVE' "
			"are listed.",
		.command = KMS_COMMAND_LIST,
	},
	{
		.option = { "all", 0, NULL, 'a'},
		.desc = "List all keys that can be used for volume encryption. "
			"If this option is not specified, then only volume "
			"encryption keys that are allowed to be exported by "
			"EKMF Web using the identity key of this zkey client "
			"are listed.",
		.command = KMS_COMMAND_LIST,
	},
	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) == 0)
		return list_options;

	return NULL;
}

/**
 * Gets the OpenSSL curve NID from the infos from the identity template.
 *
 * @param ph                the plugin handle
 * @param curve             the name of the curve from the template
 * @param key_size          the size of the key in bits
 *
 * @returns the OpenSSL NID for the curve, or NID_undef in case of an error
 */
static int _get_curve_nid(struct plugin_handle *ph, const char *curve,
			  size_t key_size)
{
	int nid = NID_undef;

	if (strcmp(curve, EKMFWEB_CURVE_PRIME) == 0) {
		switch (key_size) {
		case 192:
			nid = NID_X9_62_prime192v1;
			break;
		case 224:
			nid = NID_secp224r1;
			break;
		case 256:
			nid = NID_X9_62_prime256v1;
			break;
		case 384:
			nid = NID_secp384r1;
			break;
		case 521:
			nid = NID_secp521r1;
			break;
		default:
			_set_error(ph, "Unsupported bit size %u of curve '%s'",
				   key_size, curve);
			goto out;
		}
	} else if (strcmp(curve, EKMFWEB_CURVE_BAINPOOL) == 0) {
		switch (key_size) {
		case 160:
			nid = NID_brainpoolP160r1;
			break;
		case 192:
			nid = NID_brainpoolP192r1;
			break;
		case 224:
			nid = NID_brainpoolP224r1;
			break;
		case 256:
			nid = NID_brainpoolP256r1;
			break;
		case 320:
			nid = NID_brainpoolP320r1;
			break;
		case 384:
			nid = NID_brainpoolP384r1;
			break;
		case 512:
			nid = NID_brainpoolP512r1;
			break;
		default:
			_set_error(ph, "Unsupported bit size %u of curve '%s'",
				   key_size, curve);
			goto out;
		}
	} else {
		_set_error(ph, "Unsupported curve '%s'", curve);
		goto out;
	}

out:
	return nid;
}

struct template_cb_data {
	const char *template;
	struct ekmf_template_info **info;
};

/**
 * Callback for ekmf_list_templates function to get template info by name
 *
 * @param curl_handle      a CURL handle that can be used to perform further
 *                         EKMFWeb functions within the callback.
 * @param template_info    a struct containing information about the template.
 *                         If any of the information needs to be kept, then the
 *                         callback function must make a copy of the
 *                         information. The memory holding the information
 *                         passed to the callback is no longer valid after the
 *                         callback has returned.
 * @param private          the private pointer that was specified with the
 *                         ekmf_list_templates invocation.
 *
 * @returns 0 on success, a negative errno in case of an error.
 */
static int _template_cb(CURL *UNUSED(curl_handle),
			struct ekmf_template_info *template_info,
			void *private)
{
	struct template_cb_data *data = private;
	int rc;

	if (*data->info != NULL)
		return 0;

	if (strcmp(template_info->name, data->template) != 0)
		return 0;

	rc = ekmf_clone_template_info(template_info, data->info);

	return rc;
}

/**
 * Get information about a template by name
 *
 * @param ph                the plugin handle
 * @param template          the name of the template
 * @param info              On return: the template info. Must be freed by the
 *                          caller via ekmf_free_template_info.
 *
 * @returns 0 on success, a negative errno in case of an error.
 */
static int _get_template_by_name(struct plugin_handle *ph, const char *template,
				 struct ekmf_template_info **info)
{
	struct template_cb_data data;
	char *error_msg = NULL;
	int rc;

	*info = NULL;

	data.template = template;
	data.info = info;

	rc = ekmf_list_templates(&ph->ekmf_config, &ph->curl_handle,
				 _template_cb, &data, template,
				 EKMFWEB_TEMPLATE_STATE_ACTIVE,
				 &error_msg, ph->pd.verbose);
	if (rc != 0) {
		_set_error(ph, "Failed to get the template '%s': %s", template,
			   error_msg != NULL ? error_msg : strerror(-rc));
		_remove_login_token_if_error(ph, rc);
		goto out;
	}
	if (*info == NULL) {
		rc = -ENOENT;
		_set_error(ph, "Template '%s' does not exist", template);
		goto out;
	}

out:
	if (error_msg != NULL)
		free(error_msg);

	return rc;
}

/**
 * Check a template if it is using the desired settings
 *
 * @param ph                the plugin handle
 * @param info              the template info
 * @param keystore_type     the expected keystore type
 * @param no_warnig         if true, do not issue warning messages
 *
 * @returns 0 on success, a negative errno in case of an error.
 */
static int _check_template(struct plugin_handle *ph,
			   struct ekmf_template_info *info,
			   const char *keystore_type, bool no_warning)
{
	char *identity_key_param = NULL;
	char *identity_key_alg = NULL;
	size_t modulus_bits;
	char *msg = NULL;
	int curve_nid;
	int rc = 0;

	if (strcmp(info->state, EKMFWEB_TEMPLATE_STATE_ACTIVE) != 0) {
		if (strcmp(info->state, EKMFWEB_TEMPLATE_STATE_HISTORY) == 0)
			_set_error(ph, "Template '%s' is in state '%s'. "
				   "If the template has been recently changed, "
				   "run 'zkey kms configure --refresh-settings'"
				   " to refresh the templates.", info->name,
				   EKMFWEB_TEMPLATE_STATE_HISTORY);
		else
			_set_error(ph, "Template '%s' is in state '%s', "
				   "but only templates in state '%s' can "
				   "be used.", info->name, info->state,
				   EKMFWEB_TEMPLATE_STATE_ACTIVE);
		rc = -EINVAL;
		goto out;
	}

	if (strcmp(info->key_state, EKMFWEB_KEY_STATE_ACTIVE) != 0) {
		_set_error(ph, "Template '%s' generates key in state '%s', but "
			   "only templates that generate keys in state '%s' "
			   "are supported.", info->name, info->key_state,
			   EKMFWEB_KEY_STATE_ACTIVE);
		rc = -EINVAL;
		goto out;
	}

	if (strcmp(info->keystore_type, keystore_type) != 0) {
		_set_error(ph, "Template '%s' uses key store type '%s', but "
			   "only key store type '%s' is supported for %s.",
			   info->name, info->keystore_type, keystore_type,
			   strcmp(keystore_type,
				  EKMFWEB_KEYSTORE_TYPE_PERV_ENCR) == 0 ?
				"volume encryption keys" : "identity keys");
		rc = -EINVAL;
		goto out;
	}

	if (strcmp(info->keystore_type, EKMFWEB_KEYSTORE_TYPE_PERV_ENCR) == 0) {
		if (strcmp(info->key_type, EKMFWEB_KEY_TYPE_CIPHER) != 0) {
			_set_error(ph, "Template '%s' generates keys of type "
				   "'%s', but only key type '%s' is supported "
				   "for volume encryption keys.",
				   info->name, info->key_type,
				   EKMFWEB_KEY_TYPE_CIPHER);
			rc = -EINVAL;
			goto out;
		}

		if (strcmp(info->algorithm, EKMFWEB_KEY_ALGORITHM_AES) != 0) {
			_set_error(ph, "Template '%s' generates keys with "
				   "algorithm '%s', but only algorithm '%s' is "
				   "supported for volume encryption keys.",
				   info->name, info->algorithm,
				   EKMFWEB_KEY_ALGORITHM_AES);
			rc = -EINVAL;
			goto out;
		}

		if (info->export_allowed == false) {
			_set_error(ph, "Template '%s' generates key that are "
				   "not allowed to be exported, but only "
				   "templates that generate keys that are "
				   "allowed to be exported are supported for "
				   "volume encryption keys.",
				   info->name);
			rc = -EINVAL;
			goto out;
		}
	}

	if (strcmp(info->keystore_type, EKMFWEB_KEYSTORE_TYPE_IDENTITY) == 0) {
		if (strcmp(info->algorithm, EKMFWEB_KEY_ALGORITHM_ECC) != 0 &&
		    strcmp(info->algorithm, EKMFWEB_KEY_ALGORITHM_RSA) != 0) {
			_set_error(ph, "Template '%s' generates keys with "
				   "algorithm '%s', but only algorithms '%s' "
				   "and '%s' are supported for identity keys.",
				   info->name, info->algorithm,
				   EKMFWEB_KEY_ALGORITHM_ECC,
				   EKMFWEB_KEY_ALGORITHM_RSA);
			rc = -EINVAL;
			goto out;
		}

		if (no_warning)
			goto out;

		identity_key_alg = properties_get(ph->pd.properties,
					EKMFWEB_CONFIG_IDENTITY_KEY_ALGORITHM);
		if (identity_key_alg == NULL)
			goto out;

		if (strcmp(info->algorithm, identity_key_alg) != 0) {
			util_asprintf(&msg, "WARNING: Template '%s' uses "
				      "algorithm '%s', but the existing "
				      "identity key uses algorithm '%s'. You "
				      "may need to generate a new identity "
				      "key and re-register this zkey client.",
				      info->name, info->algorithm,
				      identity_key_alg);
			util_print_indented(msg, 0);
			free(msg);
			goto out;
		}

		identity_key_param = properties_get(ph->pd.properties,
					EKMFWEB_CONFIG_IDENTITY_KEY_PARAMS);
		if (identity_key_param == NULL)
			goto out;

		if (strcmp(identity_key_alg, EKMFWEB_KEY_ALGORITHM_ECC) == 0) {
			curve_nid = _get_curve_nid(ph, info->curve,
						   info->key_size);
			if (curve_nid == NID_undef) {
				rc = -EINVAL;
				goto out;
			}
			if (OBJ_txt2nid(identity_key_param) != curve_nid) {
				util_asprintf(&msg, "WARNING: Template '%s' "
					      "uses algorithm ECC with curve "
					      "'%s', but the existing identity "
					      "key uses curve '%s'. You may "
					      "need to generate a new identity "
					      "key and re-register this zkey "
					      "client.", info->name,
					      OBJ_nid2sn(curve_nid),
					      identity_key_param);
				util_print_indented(msg, 0);
				free(msg);
			}
		} else if (strcmp(identity_key_alg, EKMFWEB_KEY_ALGORITHM_RSA)
									== 0) {
			modulus_bits = strtoul(identity_key_param, NULL, 10);
			if (modulus_bits != info->key_size) {
				util_asprintf(&msg, "WARNING: Template '%s' "
					      "uses algorithm RSA with a "
					      "modulus bit size of %lu, but "
					      "the existing identity key uses "
					      "%lu bits. You may need to "
					      "generate a new identity key and "
					      "re-register this zkey client.",
					      info->name, info->key_size,
					      modulus_bits);
				util_print_indented(msg, 0);
				free(msg);
			}
		}
	}

out:
	if (identity_key_alg != NULL)
		free(identity_key_alg);
	if (identity_key_param != NULL)
		free(identity_key_param);

	return rc;
}

/**
 * Check the 2 XTS templates
 *
 * @param ph                the plugin handle
 * @param xts1_info         the template info if XTS key 1
 * @param xts2_info         the template info if XTS key 2
 *
 * @returns 0 on success, a negative errno in case of an error.
 */
static int _check_xts_templates(struct plugin_handle *ph,
				struct ekmf_template_info *xts1_info,
				struct ekmf_template_info *xts2_info)
{
	size_t i;

	if (strcasecmp(xts1_info->label_template,
		       xts2_info->label_template) == 0) {
		_set_error(ph, "The 2 XTS templates can not have the same "
			   "label template.");
		return -EINVAL;
	}

	if (xts1_info->key_size != xts2_info->key_size) {
		_set_error(ph, "The 2 XTS templates must have the same key "
			   "size.");
		return -EINVAL;
	}

	if (xts1_info->label_tags.num_tag_defs !=
				xts2_info->label_tags.num_tag_defs) {
		_set_error(ph, "The 2 XTS templates must have the same label "
			   "tags. The templates differ in the number of label "
			   "tags: '%s' '%s'", xts1_info->label_template,
			   xts2_info->label_template);
		return -EINVAL;
	}

	for (i = 0; i < xts1_info->label_tags.num_tag_defs; i++) {
		if (strcasecmp(xts1_info->label_tags.tag_defs[i].name,
			       xts2_info->label_tags.tag_defs[i].name) != 0) {
			_set_error(ph, "The 2 XTS templates must have the same "
				   "label tags. Mismatch in tag '%s': "
				   "'%s' '%s'",
				   xts1_info->label_tags.tag_defs[i].name,
				   xts1_info->label_template,
				   xts2_info->label_template);
			return -EINVAL;
		}
	}

	return 0;
}

struct template_infos {
	char *template;
	struct ekmf_template_info *info;
	const char *name_prop;
	const char *label_prop;
	const char *id_prop;
	const char *keystore_type;
};

#define NUM_TEMPLATES		4
#define IDENTITY		0
#define XTS1			1
#define XTS2			2
#define NONXTS			3

/**
 * Retrieves the key templates to be used by the plugin
 *
 * @param ph                the plugin handle
 *
 * @returns 0 on success, a negative errno in case of an error.
 */
static int _get_templates(struct plugin_handle *ph)
{
	struct template_infos tmpl[NUM_TEMPLATES] = {
		{ .template = NULL, .info = NULL,
		  .name_prop = EKMFWEB_CONFIG_TEMPLATE_IDENTITY,
		  .label_prop = EKMFWEB_CONFIG_TEMPLATE_IDENTITY_LABEL,
		  .id_prop = EKMFWEB_CONFIG_TEMPLATE_IDENTITY_ID,
		  .keystore_type = EKMFWEB_KEYSTORE_TYPE_IDENTITY, },
		{ .template = NULL, .info = NULL,
		  .name_prop = EKMFWEB_CONFIG_TEMPLATE_XTS1,
		  .label_prop = EKMFWEB_CONFIG_TEMPLATE_XTS1_LABEL,
		  .id_prop = EKMFWEB_CONFIG_TEMPLATE_XTS1_ID,
		  .keystore_type = EKMFWEB_KEYSTORE_TYPE_PERV_ENCR, },
		{ .template = NULL, .info = NULL,
		  .name_prop = EKMFWEB_CONFIG_TEMPLATE_XTS2,
		  .label_prop = EKMFWEB_CONFIG_TEMPLATE_XTS2_LABEL,
		  .id_prop = EKMFWEB_CONFIG_TEMPLATE_XTS2_ID,
		  .keystore_type = EKMFWEB_KEYSTORE_TYPE_PERV_ENCR, },
		{ .template = NULL, .info = NULL,
		  .name_prop = EKMFWEB_CONFIG_TEMPLATE_NONXTS,
		  .label_prop = EKMFWEB_CONFIG_TEMPLATE_NONXTS_LABEL,
		  .id_prop = EKMFWEB_CONFIG_TEMPLATE_NONXTS_ID,
		  .keystore_type = EKMFWEB_KEYSTORE_TYPE_PERV_ENCR, },
	};
	char *error_msg = NULL;
	int t, rc;

	rc = ekmf_get_settings(&ph->ekmf_config, &ph->curl_handle,
			       &tmpl[IDENTITY].template, &tmpl[XTS1].template,
			       &tmpl[XTS2].template, &tmpl[NONXTS].template,
			       &error_msg, ph->pd.verbose);
	if (rc != 0) {
		_set_error(ph, "Failed to get settings from EKMF Web: %s",
			   error_msg != NULL ? error_msg : strerror(-rc));
		goto out;
	}

	for (t = 0; t < NUM_TEMPLATES; t++) {
		rc = _get_template_by_name(ph, tmpl[t].template, &tmpl[t].info);
		if (rc != 0)
			goto out;
	}

	rc = _check_xts_templates(ph, tmpl[XTS1].info, tmpl[XTS2].info);
	if (rc != 0)
		goto out;

	for (t = 0; t < NUM_TEMPLATES; t++) {
		rc = _check_template(ph, tmpl[t].info, tmpl[t].keystore_type,
				     false);
		if (rc != 0)
			goto out;

		rc = plugin_set_or_remove_property(&ph->pd, tmpl[t].name_prop,
						   tmpl[t].template);
		if (rc != 0)
			goto out;
		rc = plugin_set_or_remove_property(&ph->pd, tmpl[t].label_prop,
						   tmpl[t].info->label_template);
		if (rc != 0)
			goto out;
		rc = plugin_set_or_remove_property(&ph->pd, tmpl[t].id_prop,
						   tmpl[t].info->uuid);
		if (rc != 0)
			goto out;
	}

out:
	for (t = 0; t < NUM_TEMPLATES; t++) {
		if (tmpl[t].info != NULL)
			ekmf_free_template_info(tmpl[t].info);
		if (tmpl[t].template != NULL)
			free(tmpl[t].template);
	}
	if (error_msg != NULL)
		free(error_msg);

	return rc;
}

/**
 * Retrieves the EKMF Web system settings. This requires a login. If no
 * valid login token is available, a login is performed.
 *
 * @param ph                the plugin handle
 *
 * @returns 0 on success, a negative errno in case of an error.
 */
static int _get_ekmfweb_settings(struct plugin_handle *ph)
{
	int rc, type = 0, curve = NID_undef;
	char *error_msg = NULL;

	_check_config_complete(ph);

	if (ph->ekmf_config.login_token != NULL) {
		remove(ph->ekmf_config.login_token);
		FREE_AND_SET_NULL(ph->ekmf_config.login_token);
	}
	rc = plugin_set_or_remove_property(&ph->pd, EKMFWEB_CONFIG_LOGIN_TOKEN,
					   NULL);
	if (rc != 0)
		goto out;
	rc = plugin_set_or_remove_property(&ph->pd, EKMFWEB_CONFIG_PASSCODE_URL,
					   NULL);
	if (rc != 0)
		goto out;

	rc = ekmf_check_feature(&ph->ekmf_config, &ph->curl_handle,
				&error_msg, ph->pd.verbose);
	if (rc != 0) {
		if (rc == -ENOTSUP)
			_set_error(ph, "%s", error_msg);
		else
			_set_error(ph, "Failed to check the features of the "
				   "EKMF Web server at '%s': %s",
				   ph->ekmf_config.base_url,
				   error_msg != NULL ? error_msg :
							   strerror(-rc));
		goto out;
	}

	rc = kms_login((kms_handle_t)ph);
	if (rc != 0)
		goto out;

	if (ph->ekmf_config.ekmf_server_pubkey != NULL)
		remove(ph->ekmf_config.ekmf_server_pubkey);
	FREE_AND_SET_NULL(ph->ekmf_config.ekmf_server_pubkey);

	util_asprintf((char **)&ph->ekmf_config.ekmf_server_pubkey,
		      "%s/%s", ph->pd.config_path,
		      EKMFWEB_CONFIG_EKMFWEB_PUBKEY_FILE);

	rc = ekmf_get_public_key(&ph->ekmf_config, &ph->curl_handle,
				 &error_msg, ph->pd.verbose);
	if (rc != 0) {
		_set_error(ph, "Failed to get the public key of the EKMF Web "
			   "server at '%s': %s", ph->ekmf_config.base_url,
			   error_msg != NULL ? error_msg : strerror(-rc));
		_remove_login_token_if_error(ph, rc);
		goto out;
	}

	rc = plugin_set_file_permission(&ph->pd,
					ph->ekmf_config.ekmf_server_pubkey);
	if (rc != 0)
		goto out;

	rc = plugin_set_or_remove_property(&ph->pd,
					   EKMFWEB_CONFIG_EKMFWEB_PUBKEY,
					   ph->ekmf_config.ekmf_server_pubkey);
	if (rc != 0)
		goto out;

	rc = _get_pub_key_info(ph, ph->ekmf_config.ekmf_server_pubkey, &type,
			       &curve, NULL);
	if (rc != 0)
		goto out;
	if (type != EVP_PKEY_EC || curve == NID_undef)
		curve = NID_secp521r1;

	rc = plugin_set_or_remove_property(&ph->pd,
					   EKMFWEB_CONFIG_SESSION_KEY_CURVE,
					   OBJ_nid2sn(curve));
	if (rc != 0)
		goto out;

	rc = _get_templates(ph);
	if (rc != 0)
		goto out;

out:
	if (error_msg != NULL)
		free(error_msg);

	return rc;
}

/**
 * Configures the connection to the EKMF Web server
 *
 * @param ph                the plugin handle
 * @param ekmfweb_url       the URL of the EKMF Web server
 * @param tls_ca_bundle     the file or directory name of the CA bundle to use
 * @param tls_client_cert   the file name of the client certificate
 * @param tls_client_key   the file name of the client private key
 * @param tls_client_key_passphrase   the passphrase to unlock the key
 * @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 *ekmfweb_url,
				 const char *tls_ca_bundle,
				 const char *tls_client_cert,
				 const char *tls_client_key,
				 const char *tls_client_key_passphrase,
				 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;
	bool self_signed = false;
	bool add_https = false;
	bool verified = false;
	bool valid = false;
	char *error = NULL;
	char *url = NULL;
	int rc = 0;
	char *tmp;

	if (tls_client_cert != NULL && tls_client_key == NULL) {
		_set_error(ph, "Option '--tls-client-key' is required when "
			   "option '--tls-client-cert' is specified.");
		return -EINVAL;
	}
	if (tls_client_key != NULL && tls_client_cert == NULL) {
		_set_error(ph, "Option '--tls-client-cert' is required when "
			   "option '--tls-client-key' is specified.");
		return -EINVAL;
	}
	if (tls_client_key_passphrase != NULL && tls_client_key == NULL) {
		_set_error(ph, "Option '--tls-client-key-passphrase' is only "
			   "valid together with option "
			   "'--tls-client-key'.");
		return -EINVAL;
	}
	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->ekmf_config.base_url != NULL) {
		util_print_indented("ATTENTION: The EKMF Web server connection "
				    "has already been configured!\n"
				    "When you re-configure the EKMF Web server "
				    "connection, you may need to re-register "
				    "this zkey client with the changed EKMF "
				    "Web server.", 0);
		printf("%s: Re-configure the EKMF Web server connection "
		       "[y/N]? ",
		       program_invocation_short_name);
		if (!prompt_for_yes(ph->pd.verbose)) {
			_set_error(ph, "Operation aborted by user");
			return -ECANCELED;
		}
	}

	if (strncmp(ekmfweb_url, "http://", 6) == 0) {
		_set_error(ph, "The use of insecured HTTP is not allowed.");
		return -EINVAL;
	}

	if (strncmp(ekmfweb_url, "https://", 7) != 0)
		add_https = true;

	util_asprintf(&url, "%s%s", add_https ? "https://" : "", ekmfweb_url);
	if (url[strlen(url) - 1] == '/')
		url[strlen(url) - 1] = '\0';

	pr_verbose(&ph->pd, "url: '%s'", url);

	FREE_AND_SET_NULL(ph->ekmf_config.base_url);
	ph->ekmf_config.base_url = url;

	rc = properties_set(ph->pd.properties, EKMFWEB_CONFIG_URL, url);
	if (rc != 0) {
		_set_error(ph, "Failed to set URL property: "
			   "%s", strerror(-rc));
		goto out;
	}

	FREE_AND_SET_NULL(ph->ekmf_config.tls_ca);
	if (tls_ca_bundle != NULL)
		ph->ekmf_config.tls_ca = util_strdup(tls_ca_bundle);
	rc = plugin_set_or_remove_property(&ph->pd, EKMFWEB_CONFIG_CA_BUNDLE,
					   tls_ca_bundle);

	FREE_AND_SET_NULL(ph->ekmf_config.tls_client_cert);
	if (tls_client_cert != NULL)
		ph->ekmf_config.tls_client_cert = util_strdup(tls_client_cert);
	rc = plugin_set_or_remove_property(&ph->pd, EKMFWEB_CONFIG_CLIENT_CERT,
					   tls_client_cert);
	if (rc != 0)
		goto out;

	FREE_AND_SET_NULL(ph->ekmf_config.tls_client_key);
	if (tls_client_key != NULL)
		ph->ekmf_config.tls_client_key = util_strdup(tls_client_key);
	rc = plugin_set_or_remove_property(&ph->pd, EKMFWEB_CONFIG_CLIENT_KEY,
					   tls_client_key);
	if (rc != 0)
		goto out;

	tmp = NULL;
	FREE_AND_SET_NULL(ph->ekmf_config.tls_client_key_passphrase);
	if (tls_client_key_passphrase != NULL) {
		ph->ekmf_config.tls_client_key_passphrase =
				util_strdup(tls_client_key_passphrase);
		tmp = _encode_passphrase(tls_client_key_passphrase);
		if (tmp == NULL) {
			_set_error(ph, "Failed to encode the passphrase");
			rc = -EIO;
			goto out;
		}
	}
	rc = plugin_set_or_remove_property(&ph->pd,
					   EKMFWEB_CONFIG_CLIENT_KEY_PASSPHRASE,
					   tmp);
	if (tmp != NULL)
		free(tmp);
	if (rc != 0)
		goto out;

	util_asprintf(&server_cert_temp, "%s/%s-tmp", ph->pd.config_path,
		      EKMFWEB_CONFIG_SERVER_CERT_FILE);
	util_asprintf(&server_pubkey_temp, "%s/%s-tmp", ph->pd.config_path,
		      EKMFWEB_CONFIG_SERVER_PUBKEY_FILE);

	rc = ekmf_get_server_cert_chain(&ph->ekmf_config,
					server_cert_temp,
					server_pubkey_temp,
					NULL, &verified,
					&error, ph->pd.verbose);
	if (rc != 0) {
		_set_error(ph, "Failed to connect to EKMF Web server at '%s': "
			   "%s", ph->ekmf_config.base_url,
			   error != NULL ? error : 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 EKMF Web server presented the following "
			    "certificate to identify itself:", 0);

	rc = ekmf_print_certificates(server_cert_temp, ph->pd.verbose);
	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 EKMF Web 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->ekmf_config.tls_verify_peer = !self_signed || tls_trust_server_cert;
	if (tls_dont_verify_server_cert)
		ph->ekmf_config.tls_verify_peer = false;
	rc = plugin_set_or_remove_property(&ph->pd,
					   EKMFWEB_CONFIG_VERIFY_SERVER_CERT,
					   ph->ekmf_config.tls_verify_peer ?
						     "yes" : "no");
	if (rc != 0)
		goto out;

	ph->ekmf_config.tls_verify_host = tls_verify_hostname;
	rc = plugin_set_or_remove_property(&ph->pd,
					   EKMFWEB_CONFIG_VERIFY_HOSTNAME,
					   ph->ekmf_config.tls_verify_host ?
						     "yes" : "no");
	if (rc != 0)
		goto out;

	rc = _get_ekmfweb_settings(ph);
	if (rc != 0) {
		util_print_indented("The server you are connected with is not "
				     "a valid EKMF Web server, or is not "
				     "configured properly", 0);
		goto out;
	}

	FREE_AND_SET_NULL(ph->ekmf_config.tls_server_cert);
	util_asprintf(&server_cert_file, "%s/%s", ph->pd.config_path,
		      EKMFWEB_CONFIG_SERVER_CERT_FILE);
	if (tls_trust_server_cert) {
		ph->ekmf_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, EKMFWEB_CONFIG_SERVER_CERT,
					   tls_trust_server_cert ?
						server_cert_file : NULL);
	if (rc != 0)
		goto out;

	FREE_AND_SET_NULL(ph->ekmf_config.tls_pinned_pubkey);
	util_asprintf(&server_pubkey_file, "%s/%s", ph->pd.config_path,
		      EKMFWEB_CONFIG_SERVER_PUBKEY_FILE);
	if (tls_pin_server_pubkey) {
		ph->ekmf_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,
					   EKMFWEB_CONFIG_SERVER_PUBKEY,
					   tls_pin_server_pubkey ?
						server_pubkey_file : 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 (error != NULL)
		free(error);

	return rc;
}

struct config_options {
	const char *ekmfweb_url;
	const char *tls_ca_bundle;
	const char *tls_client_cert;
	const char *tls_client_key;
	const char *tls_client_key_passphrase;
	bool tls_pin_server_pubkey;
	bool tls_trust_server_cert;
	bool tls_dont_verify_server_cert;
	bool tls_verify_hostname;
	bool refresh_settings;
	bool 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;
#ifdef EKMF_SUPPORTS_RSA_PSS_CERTIFICATES
	bool cert_rsa_pss;
#endif
	const char *register_cert_file;
	const char *register_label_tags;
#ifdef EKMFWEB_SUPPORTS_RSA_DIGESTS_AND_PSS_SIGNATURES
	const char *session_rsa_sign_digest;
	const char *session_rsa_sign_algo;
#endif
};

/**
 * 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->tls_ca_bundle != NULL) {
		_set_error(ph, "Option '--tls-ca-bundle' is only valid "
			   "together with option '--ekmfweb-url'.");
		rc = -EINVAL;
		goto out;
	}
	if (opts->tls_client_cert != NULL) {
		_set_error(ph, "Option '--tls-client-cert' is only valid "
			   "together with option '--ekmfweb-url'.");
		rc = -EINVAL;
		goto out;
	}
	if (opts->tls_client_key != NULL) {
		_set_error(ph, "Option '--tls-client-key' is only valid "
			   "together with option '--ekmfweb-url'.");
		rc = -EINVAL;
		goto out;
	}
	if (opts->tls_client_key_passphrase != NULL) {
		_set_error(ph, "Option '--tls-client-key-passphrase' is only "
			   "valid together with option '--ekmfweb-url'.");
		rc = -EINVAL;
		goto out;
	}
	if (opts->tls_pin_server_pubkey) {
		_set_error(ph, "Option '--tls-pin-server-pubkey' is only valid "
			   "together with option '--ekmfweb-url'.");
		rc = -EINVAL;
		goto out;
	}
	if (opts->tls_trust_server_cert) {
		_set_error(ph, "Option '--tls-trust-server-cert' is only valid "
			   "together with option '--ekmfweb-url'.");
		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 '--ekmfweb-url'.");
		rc = -EINVAL;
		goto out;
	}
	if (opts->tls_verify_hostname) {
		_set_error(ph, "Option '--tls-verify-hostname' is only valid "
			   "together with option '--ekmfweb-url'.");
		rc = -EINVAL;
		goto out;
	}

out:
	return rc;
}

/**
 * Generates (or re-generates) a identity key for the plugin using the
 * settings from the identity template
 *
 * @param ph                the plugin handle
 *
 * @returns 0 on success, a negative errno in case of an error.
 */
static int _generate_identity_key(struct plugin_handle *ph)
{
	struct ekmf_template_info *template_info = NULL;
	struct ekmf_key_gen_info gen_info;
	char *template_uuid = NULL;
	char *reenc_file = NULL;
	char *error_msg = NULL;
	char key_params[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->templates_retrieved) {
		_set_error(ph, "The configuration is incomplete, you must "
			   "first configure the EKMF Web server connection.");
		return -EINVAL;
	}

	rc = kms_login((kms_handle_t)ph);
	if (rc != 0)
		goto out;

	template_uuid = properties_get(ph->pd.properties,
				       EKMFWEB_CONFIG_TEMPLATE_IDENTITY_ID);
	if (template_uuid == NULL) {
		rc = -EIO;
		_set_error(ph, "No identity key template configured");
		goto out;
	}

	rc = ekmf_get_template(&ph->ekmf_config, &ph->curl_handle,
			       template_uuid, &template_info,
			       &error_msg, ph->pd.verbose);
	if (rc != 0) {
		_set_error(ph, "Failed to get identity key template '%s': %s",
			   template_uuid, error_msg != NULL ? error_msg :
			   strerror(-rc));
		_remove_login_token_if_error(ph, rc);
		goto out;
	}

	rc = _check_template(ph, template_info, EKMFWEB_KEYSTORE_TYPE_IDENTITY,
			     true);
	if (rc != 0)
		goto out;

	pr_verbose(&ph->pd, "Identity template algorithm: '%s'",
		   template_info->algorithm);
	pr_verbose(&ph->pd, "Identity template key size: %lu",
		   template_info->key_size);

	if (strcmp(template_info->algorithm, EKMFWEB_KEY_ALGORITHM_ECC) == 0) {
		pr_verbose(&ph->pd, "Identity template curve: '%s'",
			   template_info->curve);

		gen_info.type = EKMF_KEY_TYPE_ECC;
		gen_info.params.ecc.curve_nid =
				_get_curve_nid(ph, template_info->curve,
					       template_info->key_size);
		if (gen_info.params.ecc.curve_nid == NID_undef)
			return -EINVAL;

		strcpy(key_params, OBJ_nid2sn(gen_info.params.ecc.curve_nid));
	} else if (strcmp(template_info->algorithm,
			  EKMFWEB_KEY_ALGORITHM_RSA) == 0) {
		gen_info.type = EKMF_KEY_TYPE_RSA;
		switch (template_info->key_size) {
		case 512:
		case 1024:
		case 2048:
		case 4096:
			gen_info.params.rsa.modulus_bits =
					template_info->key_size;
			break;
		default:
			_set_error(ph, "Invalid modulus bits: '%s'",
				   template_info->key_size);
			return -EINVAL;
		}
		gen_info.params.rsa.pub_exp =
					DEFAULT_IDENTITY_KEY_PUBLIC_EXPONENT;

		sprintf(key_params, "%lu", gen_info.params.rsa.modulus_bits);
	} else {
		_set_error(ph, "Invalid identity template algorithm type '%s'",
			   template_info->algorithm);
		return -EINVAL;
	}

	if (ph->ekmf_config.identity_secure_key != NULL) {
		printf("ATTENTION: An identity key already exists!\n");
		util_print_indented("When you generate a new identity key, "
				    "you will need to re-register this zkey "
				    "client with the EKMF Web 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->ekmf_config.identity_secure_key,
			      "%s/%s", ph->pd.config_path,
			      EKMFWEB_CONFIG_IDENTITY_KEY_FILE);

		rc = plugin_set_or_remove_property(&ph->pd,
						   EKMFWEB_CONFIG_IDENTITY_KEY,
					ph->ekmf_config.identity_secure_key);
		if (rc != 0)
			goto out;
	}

	rc = plugin_set_or_remove_property(&ph->pd,
					EKMFWEB_CONFIG_IDENTITY_KEY_ALGORITHM,
					template_info->algorithm);
	if (rc != 0)
		goto out;
	rc = plugin_set_or_remove_property(&ph->pd,
					   EKMFWEB_CONFIG_IDENTITY_KEY_PARAMS,
					   key_params);
	if (rc != 0)
		goto out;

	rc = _select_cca_adapter(ph);
	if (rc != 0)
		goto out;

	rc = ekmf_generate_identity_key(&ph->ekmf_config, &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 = plugin_set_file_permission(&ph->pd,
					ph->ekmf_config.identity_secure_key);
	if (rc != 0)
		goto out;

	reenc_file = properties_get(ph->pd.properties,
				    EKMFWEB_CONFIG_IDENTITY_KEY_REENC);
	if (reenc_file != NULL) {
		remove(reenc_file);
		free(reenc_file);
		properties_remove(ph->pd.properties,
				  EKMFWEB_CONFIG_IDENTITY_KEY_REENC);
	}

	properties_remove(ph->pd.properties, EKMFWEB_CONFIG_IDENTITY_KEY_LABEL);
	properties_remove(ph->pd.properties, EKMFWEB_CONFIG_IDENTITY_KEY_ID);

	pr_verbose(&ph->pd, "Generated identity key into '%s'",
		   ph->ekmf_config.identity_secure_key);

out:
	if (template_uuid != NULL)
		free(template_uuid);
	if (error_msg != NULL)
		free(error_msg);
	if (template_info != NULL)
		ekmf_free_template_info(template_info);

	return rc;
}

/**
 * Parses an unsigned number from a string.
 *
 * @param str                the string to parse
 *
 * @returns the parsed number, or -1 in case of an error.
 */
static long _parse_unsigned(const char *str)
{
	long val;
	char *endp;

	val = strtol(str, &endp, 0);
	if (*str == '\0' || *endp != '\0' ||
	    (val == LONG_MAX && errno == ERANGE))
		return -1;

	return val;
}

/**
 * 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 ekmf_rsa_pss_params rsa_pss_parms = {
		.salt_len = RSA_PSS_SALTLEN_MAX, .mgf_digest_nid = 0 };
	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;
	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 configure the EKMF Web server connection.");
		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 (validity_days != NULL) {
		days = _parse_unsigned(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;
	}

	rc = _select_cca_adapter(ph);
	if (rc != 0)
		goto out;

	if (csr_pem_file != NULL) {
		rc = ekmf_generate_csr(&ph->ekmf_config,
				       (const char **)subject_rdn_list,
				       num_subject_rdns, true,
				       renew_cert_pem_file,
				       (const char **)extension_list,
				       num_extensions, digest_nid,
				       rsa_pss ? &rsa_pss_parms : NULL,
				       csr_pem_file, csr_new_header,
				       &ph->ext_lib, ph->pd.verbose);
	} else {
		rc = ekmf_generate_ss_cert(&ph->ekmf_config,
				       (const char **)subject_rdn_list,
				       num_subject_rdns, true,
				       renew_cert_pem_file,
				       (const char **)extension_list,
				       num_extensions, days, digest_nid,
				       rsa_pss ? &rsa_pss_parms : NULL,
				       sscert_pem_file, &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)
		pr_verbose(&ph->pd, "Generated certificate signing request "
			   "into '%s'", csr_pem_file);
	else
		pr_verbose(&ph->pd, "Generated self signed certificate into "
			   "'%s'", sscert_pem_file);

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);
	}

	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;
	}
#ifdef EKMF_SUPPORTS_RSA_PSS_CERTIFICATES
	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;
	}
#endif

out:
	return rc;
}

/**
 * Frees an EKMF tag list
 *
 * @param ekmf_tag_list     the EKMF tag list
 */
static void _free_ekmf_tags(struct ekmf_tag_list *ekmf_tag_list)
{
	size_t i;

	if (ekmf_tag_list->tags == NULL)
		return;

	for (i = 0;  i < ekmf_tag_list->num_tags; i++) {
		free((char *)ekmf_tag_list->tags[i].name);
		free((char *)ekmf_tag_list->tags[i].value);
	}

	free(ekmf_tag_list->tags);
	ekmf_tag_list->tags = NULL;
	ekmf_tag_list->num_tags = 0;
}

/**
 * Parses the label tags passed in via option (<tag>=<value>;<tag>=<value;....)
 * and allocates an array of KMS properties. The tags must be freed by the
 * caller.
 *
 * @param ph                the plugin handle
 * @param template_info     the template info
 * @param label_tags        the label tags option value (can be NULL)
 * @param ekmf_tag_list     On return: a list of label tags
 *
 * @returns 0 on success, or a negative errno in case of an error.
 */
static int _parse_label_tags(struct plugin_handle *ph,
			     const struct ekmf_template_info *template_info,
			     const char *label_tags,
			     struct ekmf_tag_list *ekmf_tag_list)
{
	const struct ekmf_tag_def_list *tag_defs;
	char *tag, *value = NULL;
	char **tag_list = NULL;
	size_t i, k;
	int rc = 0;

	tag_defs = &template_info->label_tags;

	pr_verbose(&ph->pd, "Label tags: '%s'", label_tags);

	if (label_tags != NULL && strlen(label_tags) == 0)
		label_tags = NULL;
	tag_list = str_list_split(label_tags != NULL ? label_tags : "");

	ekmf_tag_list->tags = util_malloc(sizeof(struct ekmf_tag) *
						tag_defs->num_tag_defs);
	ekmf_tag_list->num_tags = tag_defs->num_tag_defs;

	memset(ekmf_tag_list->tags, 0,
	       sizeof(struct ekmf_tag) * tag_defs->num_tag_defs);

	for (i = 0, k = 0; i < tag_defs->num_tag_defs; i++) {
		pr_verbose(&ph->pd, "Expected tag: '%s'",
			   tag_defs->tag_defs[i].name);
		pr_verbose(&ph->pd, "Specified tag: '%s'", tag_list[k]);

		tag = tag_list[k] != NULL ? util_strdup(tag_list[k]) : NULL;
		if (tag != NULL) {
			value = strchr(tag, '=');
			if (value != NULL) {
				*value = '\0';
				value++;
			}
		}

		ekmf_tag_list->tags[i].name =
				util_strdup(tag_defs->tag_defs[i].name);

		if (strcasecmp(tag_defs->tag_defs[i].name,
			       EKMFWEB_SEQNO_TAG) == 0) {
			/* <seqno> tag may or may not be specified */
			if (tag != NULL && strcasecmp(tag,
						      EKMFWEB_SEQNO_TAG) == 0) {
				/* <seqno> tag may or may not have a value */
				if (value != NULL) {
					ekmf_tag_list->tags[i].value =
							util_strdup(value);
					util_str_toupper((char *)
						ekmf_tag_list->tags[i].value);
				} else {
					ekmf_tag_list->tags[i].value =
						util_strdup(EKMFWEB_SEQNO_NEXT);
				}
				k++;
			} else {
				ekmf_tag_list->tags[i].value =
						util_strdup(EKMFWEB_SEQNO_NEXT);
			}
		} else {
			if (tag == NULL) {
				if (label_tags != NULL)
					_set_error(ph, "Failed to parse label "
						   "tags. Expected tag '%s', "
						   "but no more tags are "
						   "specified.",
						   tag_defs->tag_defs[i].name);
				else
					_set_error(ph, "Option '--label-tags' "
						   "is required. Use 'zkey kms "
						   "info' to see which label "
						   "tags are required by the "
						   "key template(s)");
				rc = -EINVAL;
				goto out;
			}
			if (strcasecmp(tag, tag_defs->tag_defs[i].name) != 0) {
				_set_error(ph, "Failed to parse the specified "
					   "label tags: Expected tag '%s', but "
					   "found '%s'.",
					   tag_defs->tag_defs[i].name, tag);
				rc = -EINVAL;
				goto out;
			}
			if (value == NULL) {
				_set_error(ph, "Failed to parse the specified "
					   "label tags: Tag '%s' has no value",
					   tag_defs->tag_defs[i].name);
				rc = -EINVAL;
				goto out;
			}

			ekmf_tag_list->tags[i].value = util_strdup(value);
			util_str_toupper((char *)ekmf_tag_list->tags[i].value);
			k++;
		}

		pr_verbose(&ph->pd, "Tag: '%s', Value: '%s'",
			   ekmf_tag_list->tags[i].name,
			   ekmf_tag_list->tags[i].value);

		if (tag != NULL)
			free(tag);
		tag = NULL;
	}

	if (tag_list[k] != NULL) {
		_set_error(ph, "Failed to parse the specified label tags: More "
			   "tags specified than expected: '%s'", tag_list[k]);
		rc = -EINVAL;
		goto out;
	}

out:
	if (tag != NULL)
		free(tag);
	if (tag_list != NULL)
		str_list_free_string_array(tag_list);
	if (rc != 0)
		_free_ekmf_tags(ekmf_tag_list);

	return rc;
}

/**
 * Loads the certificate from a file into memory.
 *
 * @param ph                the plugin handle
 * @param cert_file         the file name of the certificate file
 * @param cert              On return: an allocated buffer containing the data
 * @param cert_size         On return: the size of the certificate data
 *
 * @returns 0 on success, or a negative errno in case of an error.
 */
static int _load_certificate(struct plugin_handle *ph, const char *cert_file,
			     unsigned char **cert, size_t *cert_size)
{
	size_t count, size;
	unsigned char *buf;
	struct stat sb;
	int rc = 0;
	FILE *fp;

	if (stat(cert_file, &sb)) {
		rc = -errno;
		_set_error(ph, "Failed to read certificate from file '%s': %s",
			   cert_file, strerror(-rc));
		return rc;
	}
	size = sb.st_size;

	fp = fopen(cert_file, "r");
	if (fp == NULL) {
		rc = -errno;
		_set_error(ph, "Failed to read certificate from file '%s': %s",
			   cert_file, strerror(-rc));
		return rc;
	}

	buf = util_malloc(size);
	count = fread(buf, 1, size, fp);
	if (count != size) {
		rc = ferror(fp) ? -errno : -EIO;
		_set_error(ph, "Failed to read certificate from file '%s': %s",
			   cert_file, strerror(-rc));
		goto out;
	}

	*cert_size = size;
	*cert = buf;

	pr_verbose(&ph->pd, "%lu bytes read from file '%s'", size, cert_file);
out:
	if (rc != 0)
		free(buf);
	fclose(fp);

	return rc;
}

/**
 * Registers the client with EKMF Web
 *
 * @param ph                the plugin handle
 * @param cert_file         the certificate file to register
 * @param label_tags        the label tags for generating an identity key
 *
 * @returns 0 on success, a negative errno in case of an error.
 */
static int _register_client(struct plugin_handle *ph, const char *cert_file,
			    const char *label_tags)
{
	struct ekmf_template_info *template_info = NULL;
	struct ekmf_tag_list label_tag_list = { 0 };
	struct ekmf_key_info *key_info = NULL;
	const char *template_uuid = NULL;
	unsigned char *cert = NULL;
	char *description = NULL;
	struct utsname utsname;
	char *error_msg = NULL;
	size_t cert_size = 0;
	char *key_id = NULL;
	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 configure the EKMF Web server connection.");
		return -EINVAL;
	}

	rc = kms_login((kms_handle_t)ph);
	if (rc != 0)
		goto out;

	key_id = properties_get(ph->pd.properties,
				EKMFWEB_CONFIG_IDENTITY_KEY_LABEL);
	if (key_id != NULL) {
		free(key_id);
		util_print_indented("ATTENTION: The zkey client has already "
				    "been registered with EKMF Web!\n"
				    "When you re-register with EKMF Web you "
				    "will no longer have access to keys that "
				    "have been generated in EKMF Web with your "
				    "previous registration, until an EKMF Web "
				    "operator approves the export of these "
				    "keys for the identity key that is being "
				    "generated with this registration.", 0);
		printf("%s: Re-register the zkey client [y/N]? ",
		       program_invocation_short_name);
		if (!prompt_for_yes(ph->pd.verbose)) {
			_set_error(ph, "Operation aborted by user");
			return -ECANCELED;
		}
	}

	rc  = _load_certificate(ph, cert_file, &cert, &cert_size);
	if (rc != 0)
		goto out;

	template_uuid = properties_get(ph->pd.properties,
				       EKMFWEB_CONFIG_TEMPLATE_IDENTITY_ID);
	if (template_uuid == NULL) {
		rc = -EIO;
		_set_error(ph, "No identity key template configured");
		goto out;
	}

	rc = ekmf_get_template(&ph->ekmf_config, &ph->curl_handle,
			       template_uuid, &template_info,
			       &error_msg, ph->pd.verbose);
	if (rc != 0) {
		_set_error(ph, "Failed to get identity key template '%s': %s",
			   template_uuid, error_msg != NULL ? error_msg :
			   strerror(-rc));
		_remove_login_token_if_error(ph, rc);
		goto out;
	}

	rc = _check_template(ph, template_info, EKMFWEB_KEYSTORE_TYPE_IDENTITY,
			     false);
	if (rc != 0)
		goto out;

	rc = _parse_label_tags(ph, template_info, label_tags, &label_tag_list);
	if (rc != 0)
		goto 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, "Identity key for zkey client on system %s",
		      utsname.nodename);

	rc = ekmf_generate_key(&ph->ekmf_config, &ph->curl_handle,
			      template_info->name, description,
			      &label_tag_list, NULL, NULL, cert, cert_size,
			      &key_info, &error_msg, ph->pd.verbose);
	if (rc != 0) {
		_set_error(ph, "Failed to generate identity key in EKMF Web:"
			   " %s", error_msg != NULL ? error_msg :
			   strerror(-rc));
		_remove_login_token_if_error(ph, rc);
		goto out;
	}

	rc = plugin_set_or_remove_property(&ph->pd,
					   EKMFWEB_CONFIG_IDENTITY_KEY_ID,
					   key_info->uuid);
	if (rc != 0)
		goto out;
	rc = plugin_set_or_remove_property(&ph->pd,
					   EKMFWEB_CONFIG_IDENTITY_KEY_LABEL,
					   key_info->label);
	if (rc != 0)
		goto out;

	pr_verbose(&ph->pd, "Generated identity key id: '%s'", key_info->uuid);
	pr_verbose(&ph->pd, "Generated identity key label: '%s'",
		   key_info->label);

out:
	if (template_uuid != NULL)
		free((char *)template_uuid);
	if (error_msg != NULL)
		free(error_msg);
	if (template_info != NULL)
		ekmf_free_template_info(template_info);
	_free_ekmf_tags(&label_tag_list);
	if (key_info != NULL)
		ekmf_free_key_info(key_info);
	if (cert != NULL)
		free(cert);
	if (description != NULL)
		free(description);

	return rc;
}

#ifdef EKMFWEB_SUPPORTS_RSA_DIGESTS_AND_PSS_SIGNATURES
/*
 * Configures the key transport specific settings
 *
 * @param ph                the plugin handle
 * @param session_rsa_sign_digest the name of the digest for RSA signatures
 * @param session_rsa_sign_algo the signature algorithm for RSA signatures
 *
 * @returns 0 on success, a negative errno in case of an error.
 */
static int _configure_key_transport(struct plugin_handle *ph,
				    const char *session_rsa_sign_digest,
				    const char *session_rsa_sign_algo)
{
	int nid, rc = 0;

	if (session_rsa_sign_digest != NULL) {
		nid = OBJ_txt2nid(session_rsa_sign_digest);
		if (nid == NID_undef) {
			_set_error(ph, "Invalid RSA signature digest '%s'",
				   session_rsa_sign_digest);
			return -EINVAL;
		}

		rc = plugin_set_or_remove_property(&ph->pd,
				    EKMFWEB_CONFIG_SESSION_RSA_SIGN_DIGEST,
				    session_rsa_sign_digest);
		if (rc != 0)
			goto out;
	}

	if (session_rsa_sign_algo != NULL) {
		if (strcasecmp(session_rsa_sign_algo, "RSA") == 0) {
			rc = plugin_set_or_remove_property(&ph->pd,
					EKMFWEB_CONFIG_SESSION_RSA_SIGN_PSS,
					NULL);
			if (rc != 0)
				goto out;
		} else if (strcasecmp(session_rsa_sign_algo, "RSA-PSS") == 0) {
			rc = plugin_set_or_remove_property(&ph->pd,
					EKMFWEB_CONFIG_SESSION_RSA_SIGN_PSS,
					"yes");
			if (rc != 0)
				goto out;
		} else {
			_set_error(ph, "Invalid RSA signature algorithm '%s'",
				   session_rsa_sign_algo);
			return -EINVAL;
		}
	}

out:
	return rc;
}
#endif

/**
 * 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) {
		if (num_apqns > 0) {
			rc = cross_check_cca_apka_apqns(&ph->pd, apqns,
							num_apqns);
			if (rc != 0) {
				_set_error(ph, "Your APKA master key setup is "
					   "improper");
				goto out;
			}
		}

		apqn_str = build_kms_apqn_string(apqns, num_apqns);
		rc = properties_set(ph->pd.properties, EKMFWEB_CONFIG_APQNS,
				    apqn_str);
		if (rc != 0) {
			_set_error(ph, "Failed to set APQNs property: %s",
				   strerror(-rc));
			goto out;
		}

		config_changed = true;
	}

	for (i = 0; i < num_options; i++) {
		switch (options[i].option) {
		case 'u':
			opts.ekmfweb_url = options[i].argument;
			break;
		case 'b':
			opts.tls_ca_bundle = options[i].argument;
			break;
		case OPT_TLS_CLIENT_CERT:
			opts.tls_client_cert = options[i].argument;
			break;
		case OPT_TLS_CLIENT_KEY:
			opts.tls_client_key = options[i].argument;
			break;
		case OPT_TLS_CLIENT_KEY_PASSPHRASE:
			opts.tls_client_key_passphrase = 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 'R':
			opts.refresh_settings = true;
			break;
		case 'i':
			opts.generate_identity_key = true;
			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;
#ifdef EKMF_SUPPORTS_RSA_PSS_CERTIFICATES
		case 'P':
			opts.cert_rsa_pss = true;
			break;
#endif
		case 'r':
			opts.register_cert_file = options[i].argument;
			break;
		case 'T':
			opts.register_label_tags = options[i].argument;
			break;
#ifdef EKMFWEB_SUPPORTS_RSA_DIGESTS_AND_PSS_SIGNATURES
		case OPT_KT_RSA_SIGNATURE_DIGEST:
			opts.session_rsa_sign_digest = options[i].argument;
			break;
		case OPT_KT_RSA_PSS_SIGNATURE:
			opts.session_rsa_sign_algo = options[i].argument;
			break;
#endif
		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.ekmfweb_url != NULL) {
		rc = _configure_connection(ph, opts.ekmfweb_url,
					   opts.tls_ca_bundle,
					   opts.tls_client_cert,
					   opts.tls_client_key,
					   opts.tls_client_key_passphrase,
					   opts.tls_pin_server_pubkey,
					   opts.tls_trust_server_cert,
					   opts.tls_dont_verify_server_cert,
					   opts.tls_verify_hostname);
		if (rc == 0) {
			config_changed = true;
			opts.refresh_settings = false; /* Already done */
		}
	} else {
		rc = _error_connection_opts(ph, &opts);
	}
	if (rc != 0)
		goto out;

	if (opts.refresh_settings) {
		rc = _get_ekmfweb_settings(ph);
		if (rc != 0)
			goto out;

		config_changed = true;
	}

	if ((ph->connection_configured && !ph->identity_key_generated) ||
	    opts.generate_identity_key) {
		rc = _generate_identity_key(ph);
		if (rc != 0)
			goto out;

		config_changed = true;
	}

	if (opts.csr_pem_file != NULL || opts.sscert_pem_file != NULL)
		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,
#ifdef EKMF_SUPPORTS_RSA_PSS_CERTIFICATES
					  opts.cert_rsa_pss,
#endif
					  false);
	else
		rc = _error_gen_csr_sscert_opts(ph, &opts);
	if (rc != 0)
		goto out;

	if (opts.register_cert_file != NULL) {
		rc = _register_client(ph, opts.register_cert_file,
				      opts.register_label_tags);
		if (rc != 0)
			goto out;

		config_changed = true;
	} else {
		if (opts.register_label_tags != NULL) {
			_set_error(ph, "Option ' --label-tags' is only valid "
				   "together with option '--register'.");
			rc = -EINVAL;
			goto out;
		}
	}

#ifdef EKMFWEB_SUPPORTS_RSA_DIGESTS_AND_PSS_SIGNATURES
	if (opts.session_rsa_sign_digest != NULL ||
	    opts.session_rsa_sign_algo != NULL) {
		rc = _configure_key_transport(ph, opts.session_rsa_sign_digest,
					      opts.session_rsa_sign_algo);
		if (rc != 0)
			goto out;

		config_changed = true;
	}
#endif

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;
}

/**
 * Prompts the user for input on stdin, and returns the entered value.
 * The returned string must be freed by the caller.
 *
 * @param ph                the plugin handle
 * @param msg               the message to prompt for the input (can be NULL)
 *
 * @returns the entered value, or NULL in case of an error.
 */
static char *_prompt_for_input(struct plugin_handle *ph, const char *msg)
{
	size_t input_len = 0;
	char *input = NULL;
	int rc;

	while (input_len == 0 || input == NULL || strlen(input) < 1) {
		if (msg != NULL)
			printf("%s: %s: ", program_invocation_short_name, msg);

		rc = getline(&input, &input_len, stdin);
		if (rc < 0) {
			_set_error(ph, "Failed to read from stdin: %s",
				   strerror(errno));
			if (input != NULL)
				free(input);
			return NULL;
		}

		if (input != NULL && input[strlen(input) - 1] == '\n')
			input[strlen(input) - 1] = '\0';
	}

	return input;
}

/**
 * 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;
	char *passcode_url = NULL;
	char *error_msg = NULL;
	char *passcode = NULL;
	char *user_id = NULL;
	bool valid = false;
	int rc;

	util_assert(handle != NULL, "Internal error: handle is NULL");

	pr_verbose(&ph->pd, "Login");

	plugin_clear_error(&ph->pd);

	if (!ph->connection_configured) {
		_set_error(ph, "The configuration is incomplete, run 'zkey "
			  "kms configure [OPTIONS]' to complete the "
			  "configuration.");
		return -EINVAL;
	}

	if (ph->ekmf_config.login_token != NULL) {
		rc = ekmf_check_login_token(&ph->ekmf_config, &valid, NULL,
					    ph->pd.verbose);
		pr_verbose(&ph->pd, "Login token valid: %d", valid);

		if (rc == 0 && valid)
			return 0;

		remove(ph->ekmf_config.login_token);
		FREE_AND_SET_NULL(ph->ekmf_config.login_token);

		rc = plugin_set_or_remove_property(&ph->pd,
						   EKMFWEB_CONFIG_LOGIN_TOKEN,
						   NULL);
		if (rc != 0)
			goto out;
	}

	passcode_url = properties_get(ph->pd.properties,
				      EKMFWEB_CONFIG_PASSCODE_URL);
	if (passcode_url == NULL) {
		util_asprintf(&passcode_url, "%s%s", ph->ekmf_config.base_url,
			      EKMFWEB_PASSCODE_URL);

		rc = plugin_set_or_remove_property(&ph->pd,
						   EKMFWEB_CONFIG_PASSCODE_URL,
						   passcode_url);
		if (rc != 0)
			goto out;
	}

	pr_verbose(&ph->pd, "passcode url: '%s'", passcode_url);

	user_id = _prompt_for_input(ph, "EKMF Web user ID");
	if (user_id == NULL) {
		rc = -EIO;
		goto out;
	}

	pr_verbose(&ph->pd, "User-id: '%s'", user_id);

	util_print_indented("Go to the following web page in your web browser, "
			    "login with the same user ID as entered above and "
			    "your password, and obtain a one time passcode and "
			    "enter it here.", 0);
	printf("%s\n", passcode_url);

	passcode = _prompt_for_input(ph, "Passcode");
	if (passcode == NULL) {
		rc = -EIO;
		goto out;
	}

	pr_verbose(&ph->pd, "Passcode: '%s'", passcode);

	util_asprintf((char **)&ph->ekmf_config.login_token, "%s/%s",
		      ph->pd.config_path, EKMFWEB_CONFIG_LOGIN_TOKEN_FILE);

	rc = ekmf_login(&ph->ekmf_config, &ph->curl_handle, user_id, passcode,
			&error_msg, ph->pd.verbose);
	if (rc != 0) {
		_set_error(ph, "Failed to login to EKMF Web server at '%s': "
			   "%s", ph->ekmf_config.base_url,
			   error_msg != NULL ? error_msg : strerror(-rc));
		goto out;
	}

	rc = plugin_set_file_permission(&ph->pd, ph->ekmf_config.login_token);
	if (rc != 0)
		goto out;

	rc = plugin_set_or_remove_property(&ph->pd, EKMFWEB_CONFIG_LOGIN_TOKEN,
					   ph->ekmf_config.login_token);
	if (rc != 0)
		goto out;

	rc = plugin_save_config(&ph->pd);
	if (rc != 0)
		goto out;

out:
	if (passcode_url != NULL)
		free(passcode_url);
	if (user_id != NULL)
		free(user_id);
	if (passcode != NULL)
		free(passcode);
	if (error_msg != NULL)
		free(error_msg);

	return rc;
}

/**
 * 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)
{
	struct plugin_handle *ph = handle;
	char *reenc_file = NULL;
	const char *tmp = NULL;
	size_t i;
	int rc;

	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->ekmf_config.identity_secure_key == NULL)
		return 0;

	reenc_file = properties_get(ph->pd.properties,
				    EKMFWEB_CONFIG_IDENTITY_KEY_REENC);
	if (reenc_file != NULL && mode == KMS_REENC_MODE_AUTO)
		mode = KMS_REENC_MODE_STAGED_COMPLETE;

	if (mode == KMS_REENC_MODE_STAGED_COMPLETE) {
		if (reenc_file == NULL) {
			_set_error(ph, "Staged re-enciphering is not pending");
			rc = -EINVAL;
			goto out;
		}

		printf("Completing re-enciphering of identity key.\n");

		rc = remove(ph->ekmf_config.identity_secure_key);
		if (rc != 0) {
			rc = -errno;
			_set_error(ph, "Failed to remove file '%s': %s",
				   ph->ekmf_config.identity_secure_key,
				   strerror(-rc));
			goto out;
		}

		rc = rename(reenc_file, ph->ekmf_config.identity_secure_key);
		if (rc != 0) {
			rc = -errno;
			_set_error(ph, "Failed to rename file '%s' to '%s': %s",
				   reenc_file,
				   ph->ekmf_config.identity_secure_key,
				   strerror(-rc));
			goto out;
		}

		rc = properties_remove(ph->pd.properties,
				       EKMFWEB_CONFIG_IDENTITY_KEY_REENC);
		if (rc != 0) {
			_set_error(ph, "Failed to remove property %s: %s",
				   EKMFWEB_CONFIG_IDENTITY_KEY_REENC,
				   strerror(-rc));
			goto out;
		}

		rc = plugin_save_config(&ph->pd);
		if (rc != 0)
			goto out;

		printf("Successfully completed re-enciphering of identity "
		       "key.\n");

		rc = 0;
		goto out;
	}

	if (reenc_file != NULL)
		free(reenc_file);
	reenc_file = NULL;

	rc = _select_cca_adapter(ph);
	if (rc != 0)
		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;

		if (mode == KMS_REENC_MODE_STAGED)
			util_asprintf(&reenc_file, "%s/%s", ph->pd.config_path,
				      EKMFWEB_CONFIG_IDENTITY_KEY_REENC_FILE);

		printf("Re-enciphering the identity key with the APKA master "
		       "key in the NEW register.\n");

		rc = ekmf_reencipher_identity_key(&ph->ekmf_config, true,
						  reenc_file, &ph->ext_lib,
						  ph->pd.verbose);
		if (rc != 0) {
			_set_error(ph, "Failed to re-encipher identity key "
				   "'%s': %s",
				   ph->ekmf_config.identity_secure_key,
				   strerror(-rc));
			goto out;
		}
		break;

	case KMS_REENC_MKREG_FROM_OLD:
		if (mode == KMS_REENC_MODE_AUTO)
			mode = KMS_REENC_MODE_IN_PLACE;

		if (mode == KMS_REENC_MODE_STAGED)
			util_asprintf(&reenc_file, "%s/%s", ph->pd.config_path,
				      EKMFWEB_CONFIG_IDENTITY_KEY_REENC_FILE);

		printf("Re-enciphering the identity key with the APKA master "
		       "key in the CURRENT register.\n");

		rc = ekmf_reencipher_identity_key(&ph->ekmf_config, false,
						  reenc_file, &ph->ext_lib,
						  ph->pd.verbose);
		if (rc != 0) {
			_set_error(ph, "Failed to re-encipher identity key "
				   "'%s': %s",
				   ph->ekmf_config.identity_secure_key,
				   strerror(-rc));
			goto out;
		}
		break;

	case KMS_REENC_MKREG_FROM_OLD_TO_NEW:
		if (mode == KMS_REENC_MODE_AUTO)
			mode = KMS_REENC_MODE_STAGED;

		if (mode == KMS_REENC_MODE_STAGED)
			util_asprintf(&reenc_file, "%s/%s", ph->pd.config_path,
				      EKMFWEB_CONFIG_IDENTITY_KEY_REENC_FILE);

		printf("Re-enciphering the identity key with the APKA master "
		       "key in the CURRENT and then the NEW register.\n");

		rc = ekmf_reencipher_identity_key(&ph->ekmf_config, false,
						  reenc_file, &ph->ext_lib,
						  ph->pd.verbose);
		if (rc != 0) {
			_set_error(ph, "Failed to re-encipher identity key "
				   "'%s': %s",
				   ph->ekmf_config.identity_secure_key,
				   strerror(-rc));
			goto out;
		}

		if (reenc_file != NULL) {
			tmp = ph->ekmf_config.identity_secure_key;
			ph->ekmf_config.identity_secure_key = reenc_file;
		}

		rc = ekmf_reencipher_identity_key(&ph->ekmf_config, true,
						  reenc_file, &ph->ext_lib,
						  ph->pd.verbose);

		if (tmp != NULL)
			ph->ekmf_config.identity_secure_key = tmp;

		if (rc != 0) {
			_set_error(ph, "Failed to re-encipher identity key "
				   "'%s': %s",
				   ph->ekmf_config.identity_secure_key,
				   strerror(-rc));
			goto out;
		}

		break;

	default:
		_set_error(ph, "Invalid re-encipher MK register selection");
		rc = -EINVAL;
		goto out;
	}

	if (mode == KMS_REENC_MODE_STAGED) {
		rc = plugin_set_file_permission(&ph->pd, reenc_file);
		if (rc != 0)
			goto out;

		rc = properties_set(ph->pd.properties,
				    EKMFWEB_CONFIG_IDENTITY_KEY_REENC,
				    reenc_file);
		if (rc != 0) {
			_set_error(ph, "Failed to set property %s: %s",
				   EKMFWEB_CONFIG_IDENTITY_KEY_REENC,
				   strerror(-rc));
			goto out;
		}

	} else {
		rc = properties_remove(ph->pd.properties,
				       EKMFWEB_CONFIG_IDENTITY_KEY_REENC);
		if (rc != 0 && rc != -ENOENT) {
			_set_error(ph, "Failed to remove property %s: %s",
				   EKMFWEB_CONFIG_IDENTITY_KEY_REENC,
				   strerror(-rc));
			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 identity key. 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 identity key\n");

out:
	if (rc != 0 && reenc_file != NULL)
		remove(reenc_file);
	if (reenc_file != NULL)
		free(reenc_file);

	return rc;
}

/**
 * Converts a list of KMS properties into a EKMF tag list
 *
 * @param ph                the plugin handle
 * @param properties        a list of properties to associate the key with
 * @param num_properties    the number of properties in above array
 * @param ekmf_tag_list     On return: a list of tags
 * @param null_values_only  if true, only properties with a NULL-value are
 *                          converted. If false, only properties with a non-NULL
 *                          value are converted.
 *
 * @returns 0 on success, or a negative errno in case of an error.
 */
static int _properties_to_ekmf_tags(struct plugin_handle *UNUSED(ph),
				    const struct kms_property *properties,
				    size_t num_properties,
				    struct ekmf_tag_list *ekmf_tag_list,
				    bool null_values_only)
{
	size_t i, k;

	ekmf_tag_list->num_tags = 0;
	ekmf_tag_list->tags = util_malloc(
				sizeof(struct ekmf_tag) * num_properties);

	for (i = 0, k = 0; i < num_properties; i++) {
		if (!null_values_only && properties[i].value == NULL)
			continue;
		if (null_values_only && properties[i].value != NULL)
			continue;

		ekmf_tag_list->tags[k].name = util_strdup(properties[i].name);
		ekmf_tag_list->tags[k].value = properties[i].value != NULL ?
				util_strdup(properties[i].value) : NULL;
		ekmf_tag_list->num_tags++;
		k++;
	}

	return 0;
}

/**
 * Converts an EKMF tag list into a list of KMS properties
 *
 * @param ph                the plugin handle
 * @param ekmf_tag_list     The list of tags
 * @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.
 */
static int _ekmf_tags_to_properties(struct plugin_handle *UNUSED(ph),
				    const struct ekmf_tag_list *ekmf_tag_list,
				    struct kms_property **properties,
				    size_t *num_properties)
{
	struct kms_property *props;
	size_t i;

	props = util_malloc(sizeof(struct kms_property) *
						ekmf_tag_list->num_tags);

	for (i = 0; i < ekmf_tag_list->num_tags; i++) {
		props[i].name = util_strdup(ekmf_tag_list->tags[i].name);
		props[i].value = ekmf_tag_list->tags[i].value != NULL ?
			util_strdup(ekmf_tag_list->tags[i].value) : NULL;
	}

	*properties = props;
	*num_properties = ekmf_tag_list->num_tags;

	return 0;
}

/**
 * Restricts an retrieved secure key from further export and checks the
 * required key attributes. If the secure key is not as expected, the user
 * is prompted to confirm the use of the key.
 *
 * @param ph                the plugin handle
 * @param key_blob          the secure key to restrict
 * @param key_blob_length   the size of the the secure key
 *
 * @returns 0 on success, or a negative errno in case of an error.
 */
static int _restrict_key(struct plugin_handle *ph, unsigned char *key_blob,
			 size_t key_blob_length)
{
	struct cca_lib cca = { 0 };
	int rc;

	cca.lib_csulcca = ph->cca.cca_lib;
	cca.dll_CSNBRKA = (t_CSNBRKA)dlsym(cca.lib_csulcca, "CSNBRKA");
	if (cca.dll_CSNBRKA == NULL) {
		_set_error(ph, "Failed to get CCA verb CSNBRKA");
		return -ELIBACC;
	}

	rc = restrict_key_export(&cca, key_blob, key_blob_length,
				 ph->pd.verbose);
	if (rc != 0) {
		_set_error(ph, "Failed to export-restrict the retrieved secure "
			   "key: %s", strerror(-rc));
		return rc;
	}

	rc = check_aes_cipher_key(key_blob, key_blob_length);
	if (rc != 0) {
		warnx("The secure key retrieved from EKMF Web might not be "
		      "secure");
		printf("%s: Do you want to use it anyway [y/N]? ",
		       program_invocation_short_name);
		if (!prompt_for_yes(ph->pd.verbose)) {
			warnx("Operation aborted");
			return -ECANCELED;
		}
	}

	return 0;
}

/**
 * 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 ekmf_template_info *template_info = NULL;
	struct ekmf_tag_list custom_tag_list = { 0 };
	struct ekmf_tag_list label_tag_list = { 0 };
	struct ekmf_key_info *key_info = NULL;
	int curve_nid = 0, digest_nid = 0;
	struct plugin_handle *ph = handle;
	const char *template_uuid = NULL;
	char *identity_key_uuid = NULL;
	const char *label_tags = NULL;
	const char *tmpl_prop_name;
	char *error_msg = NULL;
	bool rsa_pss = false;
	char *tmp;
	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 (strcasecmp(key_type, KEY_TYPE_CCA_AESCIPHER) != 0) {
		_set_error(ph, "Key type '%s' is not supported by EKMF Web",
			   key_type);
		return -ENOTSUP;
	}

	switch (key_mode) {
	case KMS_KEY_MODE_NON_XTS:
		tmpl_prop_name = EKMFWEB_CONFIG_TEMPLATE_NONXTS_ID;
		break;
	case KMS_KEY_MODE_XTS_1:
		tmpl_prop_name = EKMFWEB_CONFIG_TEMPLATE_XTS1_ID;
		break;
	case KMS_KEY_MODE_XTS_2:
		tmpl_prop_name = EKMFWEB_CONFIG_TEMPLATE_XTS2_ID;
		break;
	default:
		_set_error(ph, "Unsupported key mode: %d", key_mode);
		return -EINVAL;
	}

	identity_key_uuid = properties_get(ph->pd.properties,
					   EKMFWEB_CONFIG_IDENTITY_KEY_ID);
	if (identity_key_uuid == NULL) {
		_set_error(ph, "The zkey client is not registered with EKMF "
			  "Web, run 'zkey kms configure --register CERT-FILE' "
			  "to register the zkey client.");
		return -EINVAL;
	}

	pr_verbose(&ph->pd, "identity_key_uuid: '%s'", identity_key_uuid);

	rc = _select_cca_adapter(ph);
	if (rc != 0)
		goto out;

	for (i = 0; i < num_options; i++) {
		switch (options[i].option) {
		case 'T':
			label_tags = 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;
		}
	}

	template_uuid = properties_get(ph->pd.properties, tmpl_prop_name);
	if (template_uuid == NULL) {
		rc = -EIO;
		_set_error(ph, "No key template configured");
		goto out;
	}

	rc = ekmf_get_template(&ph->ekmf_config, &ph->curl_handle,
			       template_uuid, &template_info,
			       &error_msg, ph->pd.verbose);
	if (rc != 0) {
		_set_error(ph, "Failed to get key template '%s': %s",
			   template_uuid, error_msg != NULL ? error_msg :
			   strerror(-rc));
		_remove_login_token_if_error(ph, rc);
		goto out;
	}

	rc = _check_template(ph, template_info,
			     EKMFWEB_KEYSTORE_TYPE_PERV_ENCR, true);
	if (rc != 0)
		goto out;

	if (key_bits == 0)
		key_bits = template_info->key_size;
	if (key_bits != template_info->key_size) {
		_set_error(ph, "Key size %u bits is not allowed by the "
			   "template used to generate the key. The template "
			   "uses a key size of %u bits.", key_bits,
			   template_info->key_size);
		return -EINVAL;
	}

	rc = _parse_label_tags(ph, template_info, label_tags, &label_tag_list);
	if (rc != 0)
		goto out;

	rc = _properties_to_ekmf_tags(ph, properties, num_properties,
				      &custom_tag_list, false);
	if (rc != 0)
		goto out;

	rc = ekmf_generate_key(&ph->ekmf_config, &ph->curl_handle,
			      template_info->name, "Generated by zkey",
			      &label_tag_list, &custom_tag_list,
			      identity_key_uuid, NULL, 0, &key_info,
			      &error_msg, ph->pd.verbose);
	if (rc != 0) {
		_set_error(ph, "Failed to generate key in EKMF Web: %s",
			   error_msg != NULL ? error_msg : strerror(-rc));
		_remove_login_token_if_error(ph, rc);
		goto out;
	}

	tmp = properties_get(ph->pd.properties, EKMFWEB_CONFIG_SESSION_KEY_CURVE);
	if (tmp != NULL) {
		curve_nid = OBJ_txt2nid(tmp);
		free(tmp);
	}

#ifdef EKMFWEB_SUPPORTS_RSA_DIGESTS_AND_PSS_SIGNATURES
	tmp = properties_get(ph->pd.properties,
			     EKMFWEB_CONFIG_SESSION_RSA_SIGN_DIGEST);
	if (tmp != NULL) {
		digest_nid = OBJ_txt2nid(tmp);
		free(tmp);
	}

	tmp = properties_get(ph->pd.properties,
			     EKMFWEB_CONFIG_SESSION_RSA_SIGN_PSS);
	if (tmp != NULL) {
		if (strcasecmp(tmp, "yes") == 0)
			rsa_pss = true;
		free(tmp);
	}
#endif

	rc = ekmf_retrieve_key(&ph->ekmf_config, &ph->curl_handle,
				key_info->uuid, curve_nid, digest_nid, rsa_pss,
				identity_key_uuid, key_blob, key_blob_length,
				&error_msg, &ph->ext_lib, ph->pd.verbose);
	if (rc != 0) {
		_set_error(ph, "Failed to retrieve the generated key from EKMF "
			   "Web: %s", error_msg != NULL ? error_msg :
			   strerror(-rc));
		_remove_login_token_if_error(ph, rc);
		goto out;
	}

	rc = _restrict_key(ph, key_blob, *key_blob_length);
	if (rc != 0)
		goto out;

	strncpy(key_id, key_info->uuid, key_id_size);
	key_id[key_id_size - 1] = '\0';

	strncpy(key_label, key_info->label, 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 (identity_key_uuid != NULL)
		free(identity_key_uuid);
	if (template_uuid != NULL)
		free((char *)template_uuid);
	if (template_info != NULL)
		ekmf_free_template_info(template_info);
	_free_ekmf_tags(&label_tag_list);
	_free_ekmf_tags(&custom_tag_list);
	if (error_msg != NULL)
		free(error_msg);
	if (key_info != NULL)
		ekmf_free_key_info(key_info);

	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 ekmf_tag_list delete_tag_list = { 0 };
	struct ekmf_tag_list set_tag_list = { 0 };
	struct ekmf_key_info *key_info = NULL;
	struct plugin_handle *ph = handle;
	char *updated_on = NULL;
	char *error_msg = NULL;
	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(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;
	}

	rc = _properties_to_ekmf_tags(ph, properties, num_properties,
				      &set_tag_list, false);
	if (rc != 0)
		goto out;

	rc = _properties_to_ekmf_tags(ph, properties, num_properties,
				      &delete_tag_list, true);
	if (rc != 0)
		goto out;

	rc = ekmf_get_key_info(&ph->ekmf_config, &ph->curl_handle,
			       key_id, &key_info, &error_msg, ph->pd.verbose);
	if (rc != 0) {
		_set_error(ph, "Failed to get key '%s': %s", key_id,
			   error_msg != NULL ? error_msg : strerror(-rc));
		_remove_login_token_if_error(ph, rc);
		goto out;
	}

	if (set_tag_list.num_tags > 0) {
		rc = ekmf_set_key_tags(&ph->ekmf_config, &ph->curl_handle,
				       key_id, &set_tag_list,
				       key_info->updated_on, &updated_on,
				       &error_msg, ph->pd.verbose);
		if (rc != 0) {
			_set_error(ph, "Failed to set custom tags for key "
				   "'%s': %s", key_id, error_msg != NULL ?
						   error_msg : strerror(-rc));
			_remove_login_token_if_error(ph, rc);
			goto out;
		}
	}

	if (delete_tag_list.num_tags > 0) {
		rc = ekmf_delete_key_tags(&ph->ekmf_config, &ph->curl_handle,
				       key_id, &delete_tag_list,
				       updated_on != NULL ? updated_on :
						       key_info->updated_on,
				       NULL, &error_msg, ph->pd.verbose);
		if (rc != 0) {
			_set_error(ph, "Failed to delete custom tags for key "
				   "'%s': %s", key_id, error_msg != NULL ?
						   error_msg : strerror(-rc));
			_remove_login_token_if_error(ph, rc);
			goto out;
		}
	}

out:
	_free_ekmf_tags(&set_tag_list);
	_free_ekmf_tags(&delete_tag_list);
	if (key_info != NULL)
		ekmf_free_key_info(key_info);
	if (updated_on != NULL)
		free(updated_on);
	if (error_msg != NULL)
		free(error_msg);

	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 ekmf_key_info *key_info = NULL;
	struct plugin_handle *ph = handle;
	char *error_msg = NULL;
	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;
	}

	rc = ekmf_get_key_info(&ph->ekmf_config, &ph->curl_handle,
			       key_id, &key_info, &error_msg, ph->pd.verbose);
	if (rc != 0) {
		_set_error(ph, "Failed to get key '%s': %s", key_id,
			   error_msg != NULL ? error_msg : strerror(-rc));
		_remove_login_token_if_error(ph, rc);
		goto out;
	}

	rc = _ekmf_tags_to_properties(ph, &key_info->custom_tags, properties,
				      num_properties);
	if (rc != 0)
		goto out;

out:
	if (key_info != NULL)
		ekmf_free_key_info(key_info);
	if (error_msg != NULL)
		free(error_msg);

	return rc;
}

struct key_state {
	const char *state;
	const char *new_states[7];
};

static const struct key_state states[] = {
	{ .state = EKMFWEB_KEY_STATE_PRE_ACTIVATION,
	  .new_states = {
		EKMFWEB_KEY_STATE_ACTIVE,
		EKMFWEB_KEY_STATE_COMPROMISED,
		EKMFWEB_KEY_STATE_DESTROYED,
		NULL },
	},
	{ .state = EKMFWEB_KEY_STATE_ACTIVE,
	  .new_states = {
		EKMFWEB_KEY_STATE_ACTIVE,
		EKMFWEB_KEY_STATE_DEACTIVATED,
		EKMFWEB_KEY_STATE_COMPROMISED,
		EKMFWEB_KEY_STATE_DESTROYED,
		NULL },
	},
	{ .state = EKMFWEB_KEY_STATE_DEACTIVATED,
	  .new_states = {
		EKMFWEB_KEY_STATE_COMPROMISED,
		EKMFWEB_KEY_STATE_DESTROYED,
		NULL },
	},
	{ .state = EKMFWEB_KEY_STATE_COMPROMISED,
	  .new_states = {
		EKMFWEB_KEY_STATE_DESTROYED_COMPROMISED,
		NULL },
	},
	{ .state = EKMFWEB_KEY_STATE_DESTROYED,
	  .new_states = { NULL },
	},
	{ .state = EKMFWEB_KEY_STATE_DESTROYED_COMPROMISED,
	  .new_states = { NULL },
	},
	{ .state = NULL, .new_states = { NULL, }, },
};

/**
 * Checks if the new state is a valid state. If the current state is also
 * specified, then it checks also if the new state can be set from the current
 * state
 *
 * @param ph                the plugin handle
 * @param name              the key name
 * @param new_state         the new state to set
 * @param cur_state         the current state (can be NULL).
 *
 * @returns 0 on success, or a negative errno in case of an error.
 */
static int _check_state(struct plugin_handle *ph, const char *name,
			const char *new_state, const char *cur_state)
{
	bool ok = false;
	int i, k;

	for (i = 0; states[i].state != NULL; i++) {
		if (strcasecmp(new_state, states[i].state) == 0) {
			ok = true;
			break;
		}
	}

	if (!ok) {
		_set_error(ph, "Invalid state specified: '%s'", new_state);
		return -EINVAL;
	}

	if (cur_state == NULL)
		return 0;

	for (i = 0; states[i].state != NULL; i++) {
		if (strcasecmp(cur_state, states[i].state) == 0) {
			for (k = 0; states[i].new_states[k] != NULL; k++) {
				if (strcasecmp(new_state,
					       states[i].new_states[k]) == 0)
					return 0;
			}

			_set_error(ph, "Key '%s' is in state '%s' and can not "
				   "be changed to state '%s'", name,
				   cur_state, new_state);
			return -EINVAL;
		}
	}

	_set_error(ph, "Key '%s' is in an invalid state: '%s'", name,
		   cur_state);
	return -EINVAL;
}

/**
 * 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 ekmf_key_info *key_info = NULL;
	struct plugin_handle *ph = handle;
	char *error_msg = NULL;
	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 (!ph->config_complete) {
		_set_error(ph, "The configuration is incomplete, run 'zkey "
			  "kms configure [OPTIONS]' to complete the "
			  "configuration.");
		return -EINVAL;
	}

	rc = ekmf_get_key_info(&ph->ekmf_config, &ph->curl_handle,
			       key_id, &key_info, &error_msg, ph->pd.verbose);
	if (rc != 0) {
		_set_error(ph, "Failed to get key '%s': %s", key_id,
			   error_msg != NULL ? error_msg : strerror(-rc));
		_remove_login_token_if_error(ph, rc);
		goto out;
	}

	pr_verbose(&ph->pd, "Key state: '%s'", key_info->state);

	rc = _check_state(ph, key_info->label, state, key_info->state);
	if (rc != 0)
		goto out;

	rc = ekmf_set_key_state(&ph->ekmf_config, &ph->curl_handle,
				key_id, state, key_info->updated_on,
				&error_msg, ph->pd.verbose);
	if (rc != 0) {
		_set_error(ph, "Failed to set key state '%s': %s",
				key_id, error_msg != NULL ? error_msg :
			   strerror(-rc));
		_remove_login_token_if_error(ph, rc);
		goto out;
	}

out:
	if (key_info != NULL)
		ekmf_free_key_info(key_info);
	if (error_msg != NULL)
		free(error_msg);
	if (state != NULL)
		free(state);

	return rc;
}

struct list_data {
	struct plugin_handle *ph;
	bool list_all;
	const char *exporting_key;
	kms_list_callback callback;
	void *private;
};

/**
 * Check if the key can be exported by the exporting key
 *
 * @param key_info          the key to check
 * @param exporting_key     the exporting key
 *
 * @returns true if export is allowed, false otherwise
 */
static bool _check_exportability(struct ekmf_key_info *key_info,
				 const char *exporting_key)
{
	bool found = false;
	size_t i;

	if (!key_info->export_control.export_allowed || exporting_key == NULL)
		return false;

	for (i = 0; i < key_info->export_control.num_exporting_keys; i++) {
		if (strcmp(key_info->export_control.exporting_keys[i].uuid,
			   exporting_key) == 0) {
			found = true;
			break;
		}
	}

	return found;
}

/*
 * Like argz_add, but formats the string first
 */
static error_t argz_add_fmt(char **argz, size_t *argz_len, const char *fmt, ...)
{
	va_list ap;
	error_t rc;
	char *str;

	va_start(ap, fmt);
	util_vasprintf(&str, fmt, ap);
	va_end(ap);

	rc = argz_add(argz, argz_len, str);

	free(str);
	return rc;
}

/**
 * Callback function used with the ekmf_list_keys function. This
 * callback is called for each key found.
 *
 * @param curl_handle      a CURL handle that can be used to perform further
 *                         EKMFWeb functions within the callback.
 * @param template_info    a struct containing information about the key.
 *                         If any of the information needs to be kept, then the
 *                         callback function must make a copy of the
 *                         information. The memory holding the information
 *                         passed to the callback is no longer valid after the
 *                         callback has returned.
 * @param private          the private pointer that was specified with the
 *                         ekmf_list_keys invocation.
 *
 * @returns zero for success, a negative errno in case of an error.
 * When a nonzero return code is returned, the key listing process stops,
 * and ekmf_list_keys returns the return code from the callback.
 */
static int _list_callback(CURL *curl_handle, struct ekmf_key_info *key_info,
			  void *private)
{
	struct kms_property *properties = NULL;
	struct list_data *data = private;
	size_t i, num_properties = 0;
	size_t addl_info_len = 0;
	char *addl_info = NULL;
	int rc = 0;

	data->ph->curl_handle = curl_handle;

	if (strcmp(key_info->keystore_type,
		   EKMFWEB_KEYSTORE_TYPE_PERV_ENCR) != 0)
		goto out;
	if (strcmp(key_info->key_type, EKMFWEB_KEY_TYPE_CIPHER) != 0)
		goto out;
	if (strcmp(key_info->algorithm, EKMFWEB_KEY_ALGORITHM_AES) != 0)
		goto out;
	if (!data->list_all && !_check_exportability(key_info,
						     data->exporting_key))
		goto out;

	rc = _ekmf_tags_to_properties(data->ph, &key_info->custom_tags,
				      &properties, &num_properties);
	if (rc != 0)
		goto out;

	rc = argz_add_fmt(&addl_info, &addl_info_len, "State: %s",
			  key_info->state);
	if (rc != 0)
		goto out;
	for (i = 0; i < key_info->export_control.num_exporting_keys; i++) {
		rc = argz_add_fmt(&addl_info, &addl_info_len, "%s %s",
			i == 0 ? "Exporting keys:" : "               ",
			key_info->export_control.exporting_keys[i].name);
		if (rc != 0)
			goto out;
	}

	rc = data->callback(key_info->uuid, key_info->label,
			    KEY_TYPE_CCA_AESCIPHER, key_info->key_size,
			    properties, num_properties,
			    addl_info, addl_info_len, data->private);

out:
	if (addl_info != NULL)
		free(addl_info);
	if (properties != NULL) {
		for (i = 0;  i < num_properties; i++) {
			free((char *)properties[i].name);
			free((char *)properties[i].value);
		}
		free(properties);
	}

	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 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 ekmf_tag_list tag_list = { 0 };
	struct plugin_handle *ph = handle;
	struct list_data data = { 0 };
	char **state_list = NULL;
	char *error_msg = NULL;
	char *states = NULL;
	int rc = 0;
	size_t i;

	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);

	if (!ph->config_complete) {
		_set_error(ph, "The configuration is incomplete, run 'zkey "
			  "kms configure [OPTIONS]' to complete the "
			  "configuration.");
		return -EINVAL;
	}

	data.ph = ph;
	data.list_all = false;
	data.callback = callback;
	data.private = private_data;

	for (i = 0; i < num_options; i++) {
		switch (options[i].option) {
		case 's':
			states = util_strdup(options[i].argument);
			util_str_toupper(states);
			break;
		case 'a':
			data.list_all = true;
			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;
		}
	}

	pr_verbose(&ph->pd, "State filter: '%s'", states != NULL ? states :
		   "(none)");
	pr_verbose(&ph->pd, "List all: %d", data.list_all);

	if (states != NULL) {
		state_list = str_list_split(states);
		for (i = 0; state_list[i] != NULL; i++) {
			rc = _check_state(ph, NULL, state_list[i], NULL);
			if (rc != 0)
				goto out;
		}
	}

	data.exporting_key = properties_get(ph->pd.properties,
					    EKMFWEB_CONFIG_IDENTITY_KEY_ID);

	rc = _properties_to_ekmf_tags(ph, properties, num_properties,
				      &tag_list, false);
	if (rc != 0)
		goto out;

	rc = ekmf_list_keys(&ph->ekmf_config, &ph->curl_handle,
			    _list_callback, &data, label_pattern, states,
			    &tag_list, &error_msg, ph->pd.verbose);
	if (rc != 0) {
		_set_error(ph, "Failed to list keys: %s",
			   error_msg != NULL ? error_msg : strerror(-rc));
		_remove_login_token_if_error(ph, rc);
		goto out;
	}

out:
	if (states != NULL)
		free(states);
	if (state_list != NULL)
		str_list_free_string_array(state_list);
	if (error_msg != NULL)
		free(error_msg);
	if (data.exporting_key != NULL)
		free((char *)data.exporting_key);
	_free_ekmf_tags(&tag_list);

	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_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_key(const kms_handle_t handle, const char *key_id,
		   unsigned char *key_blob, size_t *key_blob_length)
{
	struct plugin_handle *ph = handle;
	int curve_nid = 0, digest_nid = 0;
	char *identity_key_uuid = NULL;
	char *error_msg = NULL;
	bool rsa_pss = false;
	int rc = 0;
	char *tmp;

	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;
	}

	identity_key_uuid = properties_get(ph->pd.properties,
					   EKMFWEB_CONFIG_IDENTITY_KEY_ID);
	if (identity_key_uuid == NULL) {
		_set_error(ph, "The zkey client is not registered with EKMF "
			  "Web, run 'zkey kms configure --register CERT-FILE' "
			  "to register the zkey client.");
		return -EINVAL;
	}

	pr_verbose(&ph->pd, "identity_key_uuid: '%s'", identity_key_uuid);

	rc = _select_cca_adapter(ph);
	if (rc != 0)
		goto out;

	tmp = properties_get(ph->pd.properties, EKMFWEB_CONFIG_SESSION_KEY_CURVE);
	if (tmp != NULL) {
		curve_nid = OBJ_txt2nid(tmp);
		free(tmp);
	}

#ifdef EKMFWEB_SUPPORTS_RSA_DIGESTS_AND_PSS_SIGNATURES
	tmp = properties_get(ph->pd.properties,
			     EKMFWEB_CONFIG_SESSION_RSA_SIGN_DIGEST);
	if (tmp != NULL) {
		digest_nid = OBJ_txt2nid(tmp);
		free(tmp);
	}

	tmp = properties_get(ph->pd.properties,
			     EKMFWEB_CONFIG_SESSION_RSA_SIGN_PSS);
	if (tmp != NULL) {
		if (strcasecmp(tmp, "yes") == 0)
			rsa_pss = true;
		free(tmp);
	}
#endif

	rc = ekmf_retrieve_key(&ph->ekmf_config, &ph->curl_handle,
				key_id, curve_nid, digest_nid, rsa_pss,
				identity_key_uuid, key_blob, key_blob_length,
				&error_msg, &ph->ext_lib, ph->pd.verbose);
	if (rc != 0) {
		_set_error(ph, "Failed to retrieve key '%s' from EKMF "
			   "Web: %s", key_id, error_msg != NULL ? error_msg :
			   strerror(-rc));
		_remove_login_token_if_error(ph, rc);
		goto out;
	}

	rc = _restrict_key(ph, key_blob, *key_blob_length);
	if (rc != 0)
		goto out;

out:
	if (identity_key_uuid != NULL)
		free(identity_key_uuid);

	return rc;
}

static const struct kms_functions kms_functions = {
	.api_version = KMS_API_VERSION_1,
	.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_key = kms_import_key,
};

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