/*
 * libekmfweb - EKMFWeb client library
 *
 * 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 <stdio.h>
#include <string.h>
#include <strings.h>
#include <errno.h>
#include <err.h>
#include <stdbool.h>
#include <sys/stat.h>
#include <time.h>

#include <curl/curl.h>

#include <openssl/bio.h>
#include <openssl/pem.h>
#include <openssl/x509.h>
#include <openssl/ssl.h>
#include <openssl/rsa.h>
#include <openssl/evp.h>

#include <json-c/json.h>
#ifndef JSON_C_TO_STRING_NOSLASHESCAPE
#define JSON_C_TO_STRING_NOSLASHESCAPE (1 << 4)
#endif

#include "lib/zt_common.h"

#include "libseckey/sk_openssl.h"

#include "ekmfweb/ekmfweb.h"
#include "utilities.h"
#include "cca.h"

#define DEFAULT_SESSION_EC_KEY_CURVE	NID_secp521r1

#define MAX_KEY_BLOB_SIZE		CCA_MAX_PKA_KEY_TOKEN_SIZE
#define MAX_SYM_KEY_BLOB_SIZE		CCA_MAX_SYM_KEY_TOKEN_SIZE

#define EKMF_URI_SYSTEM_PUBKEY		"/api/v1/system/publicKey"
#define EKMF_URI_SYSTEM_SETTINGS	"/api/v1/system/settings"
#define EKMF_URI_SYSTEM_FEATURES	"/api/v1/system/features"
#define EKMF_URI_SYSTEM_LOGIN		"/api/v1/system/login"
#define EKMF_URI_KEYS_GENERATE		"/api/v1/keys"
#define EKMF_URI_KEYS_EXPORT		"/api/v1/keys/%s/export"
#define EKMF_URI_KEYS_TAGS		"/api/v1/keys/%s/tags"
#define EKMF_URI_KEYS_EXPORT_CONTROL	"/api/v1/keys/%s/exportControl"
#define EKMF_URI_KEYS_SET_TAG		"/api/v1/keys/%s/tags/%s"
#define EKMF_URI_KEYS_GET		"/api/v1/keys/%s"
#define EKMF_URI_KEYS_SET_STATE		"/api/v1/keys/%s"
#define EKMF_URI_KEYS_LIST		"/api/v1/keys"			\
					"?state=%s"			\
					"&orderBy=%s"			\
					"&namePattern=%s"		\
					"&tags=%s"
#define EKMF_URI_KEYS_LIST_STATE	"&state="
#define EKMF_URI_TEMPLATE_GET		"/api/v1/templates/%s"
#define EKMF_URI_TEMPLATE_LIST		"/api/v1/templates"		\
					"?templateStates=%s"		\
					"&orderBy=%s"			\
					"&namePattern=%s"
#define EKMF_URI_TEMPLATE_SEQNO		"/api/v1/templates/%s/sequenceNumber"

#define LIST_ELEMENTS_PER_PAGE		20
#define TEMPLATE_STATE_ACTIVE		"ACTIVE"
#define KEY_STATE_ACTIVE		"ACTIVE"
#define KEY_ALGORITHM_AES		"AES"
#define KEYSTORE_TYPE_PERV_ENCR		"PERVASIVE_ENCRYPTION"
#define ORDER_BY_NAME_ASC		"name%3Aasc"
#define ORDER_BY_LABEL_ASC		"label%3Aasc"
#define SETTING_ID_IDENTITY_TEMPLATE	"ekmf.web.public.identity.template"
#define SETTING_ID_XTS_KEY1_TEMPLATE	"ekmf.web.public.xts.template.1"
#define SETTING_ID_XTS_KEY2_TEMPLATE	"ekmf.web.public.xts.template.2"
#define SETTING_ID_NON_XTS_TEMPLATE	"ekmf.web.public.non-xts.template"
#define FEATURE_ID_PERVASIVE_ENCRYPTION					\
		"com.ibm.ccc.ekmf.web.features.PervasiveEncryptionFeature"

void __attribute__ ((constructor)) ekmf_init(void);
void __attribute__ ((destructor)) ekmf_exit(void);

#define pr_verbose(verbose, fmt...)	do {				\
						if (verbose)		\
							warnx(fmt);	\
					} while (0)

#define CURL_ERROR_CHECK(rc, text, verbose, label)			\
		do {							\
			if (rc != CURLE_OK) {				\
				pr_verbose(verbose, "%s: %s", text,	\
					   curl_easy_strerror(rc));	\
				goto label;				\
			}						\
		} while (0)

#define JSON_CHECK_OBJ(obj, type, rc_var, rc, text, verbose, label)	\
		do {							\
			if (obj == NULL ||				\
			    !json_object_is_type(obj, type)) {		\
				rc_var = rc;				\
				pr_verbose(verbose, "%s: %s", text,	\
					   strerror(-rc_var));		\
				goto label;				\
			}						\
		} while (0)

#define JSON_CHECK_ERROR(cond, rc_var, rc, text, verbose, label)	\
		do {							\
			if (cond) {					\
				rc_var = rc;				\
				pr_verbose(verbose, "%s: %s", text,	\
					   strerror(-rc_var));		\
				goto label;				\
			}						\
		} while (0)

struct curl_header_cb_data {
	struct curl_slist **headers;
	bool error;
	bool verbose;
};

struct curl_write_cb_data {
	json_tokener *tok;
	json_object *obj;
	bool error;
	bool verbose;
};

struct curl_sslctx_cb_data {
	const char *tls_server_cert;
	bool error;
	bool verbose;
};

#define CURL_CERTINFO_CERT	"Cert:"
#define HTTP_HDR_CONTENT_TYPE	"Content-Type:"

static const char *accepted_content_types[] = { "application/json",
						"text/x-json",
						NULL};

struct ext_lib_info {
	struct sk_ext_lib ext_lib;
	union {
		struct sk_ext_cca_lib cca; /* Used if type = EXT_LIB_CCA */
		struct sk_ext_ep11_lib ep11; /* Used if type = EXT_LIB_EP11 */
	};
};

static void _ekmf_copy_ext_lib(const struct ekmf_ext_lib *ekmf_ext_lib,
			       struct ext_lib_info *ext_lib_info)
{
	switch (ekmf_ext_lib->type) {
	case EKMF_EXT_LIB_CCA:
		ext_lib_info->ext_lib.type = SK_EXT_LIB_CCA;
		ext_lib_info->ext_lib.cca = &ext_lib_info->cca;
		ext_lib_info->cca.cca_lib = ekmf_ext_lib->cca->cca_lib;
		break;
	default:
		ext_lib_info->ext_lib.type = 0;
	}
}

static void _ekmf_copy_key_gen_info(const struct ekmf_key_gen_info *ekmf_info,
				    struct sk_key_gen_info *sk_info)
{
	switch (ekmf_info->type) {
	case EKMF_KEY_TYPE_ECC:
		sk_info->type = SK_KEY_TYPE_EC;
		sk_info->ec.curve_nid = ekmf_info->params.ecc.curve_nid;
		break;
	case EKMF_KEY_TYPE_RSA:
		sk_info->type = SK_KEY_TYPE_RSA;
		sk_info->rsa.modulus_bits = ekmf_info->params.rsa.modulus_bits;
		sk_info->rsa.pub_exp = ekmf_info->params.rsa.pub_exp;
		sk_info->rsa.x9_31 = 0;
		break;
	default:
		sk_info->type = 0;
		break;
	}
}

static void _ekmf_copy_pss_params(
			const struct ekmf_rsa_pss_params *ekmf_pss_params,
			struct sk_rsa_pss_params *rsa_pss_params)
{
	if (ekmf_pss_params != NULL) {
		rsa_pss_params->mgf_digest_nid =
					ekmf_pss_params->mgf_digest_nid;
		rsa_pss_params->salt_len = ekmf_pss_params->salt_len;
	} else {
		rsa_pss_params->mgf_digest_nid = NID_undef;
		rsa_pss_params->salt_len = 0;
	}
}

/**
 * Extract the public key from a certificate in PEM format and store it into a
 * PEM file
 */
static int _ekmf_extract_pubkey(const char *cert, const char *pub_key_pem,
				bool verbose)
{
	EVP_PKEY *pkey = NULL;
	X509 *x509 = NULL;
	FILE *fp = NULL;
	BIO *b = NULL;
	int rc;

	b = BIO_new_mem_buf(cert, -1);
	if (b == NULL) {
		pr_verbose(verbose, "BIO_new_mem_buf failed");
		return -ENOMEM;
	}

	x509 = PEM_read_bio_X509(b, NULL, NULL, NULL);
	if (x509 == NULL) {
		pr_verbose(verbose, "PEM_read_bio_X509 failed");
		rc = -EIO;
		goto out;
	}

	pkey = X509_get0_pubkey(x509);
	if (pkey == NULL) {
		pr_verbose(verbose, "PEM_read_bio_X509 failed");
		rc = -EIO;
		goto out;
	}

	fp = fopen(pub_key_pem, "w");
	if (fp == NULL) {
		rc = -errno;
		pr_verbose(verbose, "File '%s': %s", pub_key_pem,
			   strerror(-rc));
		goto out;
	}

	if (!PEM_write_PUBKEY(fp, pkey)) {
		pr_verbose(verbose, "PEM_write_PUBKEY failed");
		rc = -EIO;
		goto out;
	}

out:
	if (fp != NULL)
		fclose(fp);
	if (x509 != NULL)
		X509_free(x509);
	if (b != NULL)
		BIO_free(b);
	return 0;
}

/**
 * Process the attributes of a certificate supplied by curl and writes the
 * PEM-format certificate attribute into the specified file pointer.
 */
static int _ekmf_process_certificate(FILE *fp, struct curl_slist *slist,
				     const char *pub_key_pem, bool verbose)
{
	char *cert;
	int rc;

	for (; slist != NULL; slist = slist->next) {
		pr_verbose(verbose, "%s", slist->data);

		if (strncmp(slist->data, CURL_CERTINFO_CERT,
					  strlen(CURL_CERTINFO_CERT)) == 0) {
			cert = slist->data + strlen(CURL_CERTINFO_CERT);

			if (fp != NULL) {
				if (fwrite(cert, strlen(cert), 1, fp) != 1) {
					rc = -errno;
					pr_verbose(verbose, "fwrite failed: %s",
						   strerror(-rc));
					return rc;
				}
			}

			if (pub_key_pem != NULL) {
				rc = _ekmf_extract_pubkey(cert, pub_key_pem,
							  verbose);
				if (rc != 0)
					return rc;
			}
		}
	}

	return 0;
}

/**
 * Callback called during curl_easy_perform() to handle received data.
 * Parse the (potentially partial) JSON data.
 */
static size_t _ekmf_dummy_write_cb(void *UNUSED(contents), size_t size,
				   size_t nmemb, void *UNUSED(userp))
{
	return size * nmemb;
}

/**
 * Connects to the specified server url and obtains the servers certificate
 * and its chain of signing certificates and stores them in the specified
 * PEM files.
 *
 * @param config            the configuration structure. Only the base_url must
 *                          be specified, all others are optional.
 * @param server_cert_pem   Optional: name of a PEM file to store the servers
 *                          certificate
 * @param server_pubkey_pem Optional: name of a PEM file to store the servers
 *                          public key (can be used for public key pinning)
 * @param ca_bundle_pem     Optional: name of a PEM file to store the CA
 *                          certificate chain as a bundle
 * @param verified          On return: If the server 's certificate has been
 *                          verified using the CA specification from the config
 *                          (if ca = NULL: default system CAs, otherwise path
 *                          or file to CAs).
 * @param error_msg         on return: If not NULL, then a textual error message
 *                          is returned in case of a failing request. The caller
 *                          must free the error string when it is not NULL.
 * @param verbose           if true, verbose messages are printed
 *
 * @returns zero for success, a negative errno in case of an error.
 */
int ekmf_get_server_cert_chain(const struct ekmf_config *config,
			       const char *server_cert_pem,
			       const char *server_pubkey_pem,
			       const char *ca_bundle_pem,
			       bool *verified, char **error_msg, bool verbose)
{
	char error_str[CURL_ERROR_SIZE] = { 0 };
	struct curl_certinfo *ci;
	long do_verify = 1;
	FILE *fp = NULL;
	struct stat sb;
	int i, rc = 0;

	CURL *curl;

	if (config == NULL)
		return -EINVAL;

	if (error_msg != NULL)
		*error_msg = NULL;

	pr_verbose(verbose, "Getting certificate chain for '%s'",
		   config->base_url);

	curl = curl_easy_init();
	if (curl == NULL) {
		pr_verbose(verbose, "curl_easy_init failed");
		return CURLE_FAILED_INIT;
	}

	rc = curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, error_str);
	CURL_ERROR_CHECK(rc, "curl_easy_setopt CURLOPT_ERRORBUFFER", verbose,
			 out);

	rc = curl_easy_setopt(curl, CURLOPT_VERBOSE, verbose ? 1 : 0);
	CURL_ERROR_CHECK(rc, "curl_easy_setopt CURLOPT_VERBOSE", verbose, out);

	rc = curl_easy_setopt(curl, CURLOPT_URL, config->base_url);
	CURL_ERROR_CHECK(rc, "curl_easy_setopt CURLOPT_URL", verbose, out);

	if (config->tls_ca != NULL) {
		if (stat(config->tls_ca, &sb) != 0) {
			rc = -errno;
			pr_verbose(verbose, "stat failed on '%s': %s",
				   config->tls_ca, strerror(-rc));
			goto out;
		}

		if (S_ISDIR(sb.st_mode)) {
			rc = curl_easy_setopt(curl, CURLOPT_CAPATH,
					      config->tls_ca);
			CURL_ERROR_CHECK(rc, "curl_easy_setopt CURLOPT_CAPATH",
					 verbose, out);
		} else {
			rc = curl_easy_setopt(curl, CURLOPT_CAINFO,
					      config->tls_ca);
			CURL_ERROR_CHECK(rc, "curl_easy_setopt CURLOPT_CAINFO",
					 verbose, out);
		}
	}

	rc = curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 0L);
	CURL_ERROR_CHECK(rc, "curl_easy_setopt CURLOPT_FOLLOWLOCATION",
			 verbose, out);

	if (config->tls_client_cert != NULL) {
		rc = curl_easy_setopt(curl, CURLOPT_SSLCERT,
				      config->tls_client_cert);
		CURL_ERROR_CHECK(rc, "curl_easy_setopt CURLOPT_SSLCERT",
				 verbose, out);
		rc = curl_easy_setopt(curl, CURLOPT_SSLCERTTYPE, "PEM");
		CURL_ERROR_CHECK(rc, "curl_easy_setopt CURLOPT_SSLCERTTYPE",
				 verbose, out);
	}

	if (config->tls_client_key != NULL) {
		rc = curl_easy_setopt(curl, CURLOPT_SSLKEY,
				      config->tls_client_key);
		CURL_ERROR_CHECK(rc, "curl_easy_setopt CURLOPT_SSLKEY", verbose,
				 out);
		rc = curl_easy_setopt(curl, CURLOPT_SSLKEYTYPE, "PEM");
		CURL_ERROR_CHECK(rc, "curl_easy_setopt CURLOPT_SSLKEYTYPE",
				 verbose, out);

		if (config->tls_client_key_passphrase != NULL) {
			rc = curl_easy_setopt(curl, CURLOPT_KEYPASSWD,
					 config->tls_client_key_passphrase);
			CURL_ERROR_CHECK(rc,
					 "curl_easy_setopt CURLOPT_KEYPASSWD",
					 verbose, out);
		}
	}

retry:
	rc = curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, do_verify);
	CURL_ERROR_CHECK(rc, "curl_easy_setopt CURLOPT_SSL_VERIFYPEER", verbose,
			 out);
	rc = curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);
	CURL_ERROR_CHECK(rc, "curl_easy_setopt CURLOPT_SSL_VERIFYHOST", verbose,
			 out);

	rc = curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION,
			      _ekmf_dummy_write_cb);
	CURL_ERROR_CHECK(rc, "curl_easy_setopt CURLOPT_WRITEFUNCTION", verbose,
			 out);

	rc = curl_easy_setopt(curl, CURLOPT_CERTINFO, 1L);
	CURL_ERROR_CHECK(rc, "curl_easy_setopt CURLOPT_CERTINFO", verbose, out);

	rc = curl_easy_perform(curl);
	if (rc == CURLE_SSL_CACERT && do_verify != 0) {
		do_verify = 0;
		goto retry;
	}

	CURL_ERROR_CHECK(rc, "curl_easy_perform", verbose, out);

	/*
	 * If the first try worked, the server certificate could be verified
	 * with the specified or default CA.
	 */
	if (verified != NULL)
		*verified = do_verify != 0;

	rc = curl_easy_getinfo(curl, CURLINFO_CERTINFO, &ci);
	CURL_ERROR_CHECK(rc, "curl_easy_getinfo CURLINFO_CERTINFO", verbose,
			 out);

	if (server_cert_pem != NULL) {
		fp = fopen(server_cert_pem, "w");
		if (fp == NULL) {
			rc = -errno;
			pr_verbose(verbose, "File '%s': %s", server_cert_pem,
				   strerror(-rc));
			goto out;
		}
	}

	pr_verbose(verbose, "%d certificates", ci->num_of_certs);

	/*
	 * Process all certificates in the list.
	 * First one is the server certificate, all following are
	 * CA certificates
	 */
	for (i = 0; i < ci->num_of_certs; i++) {
		pr_verbose(verbose, "Certificate %d:", i);

		rc = _ekmf_process_certificate(fp, ci->certinfo[i], i == 0 ?
						     server_pubkey_pem : NULL,
					       verbose);
		if (rc != 0)
			break;

		if (i == 0) {
			if (fp != NULL)
				fclose(fp);
			fp = NULL;

			/*
			 * Save CA-chain if requested, but only if the
			 * server certificate wasn't verified by the specified
			 * or default CA.
			 */
			if (ci->num_of_certs > 1 && ca_bundle_pem != NULL &&
			    do_verify == 0) {
				fp = fopen(ca_bundle_pem, "w");
				if (fp == NULL) {
					rc = -errno;
					pr_verbose(verbose, "File '%s': %s",
						   ca_bundle_pem,
						   strerror(-rc));
					break;
				}
			}
		}
	}

out:
	if (fp != NULL)
		fclose(fp);

	if (rc > 0 && error_msg != NULL && *error_msg == NULL) {
		pr_verbose(verbose, "Error: %s", error_str);
		if (asprintf(error_msg, "CURL: %s", strlen(error_str) > 0 ?
				error_str : curl_easy_strerror(rc)) < 0) {
			pr_verbose(verbose, "asprintf failed");
			rc = -ENOMEM;
		}
	}

	curl_easy_cleanup(curl);

	if (rc > 0)
		rc = -EIO;
	return rc;
}

/**
 * Callback called during curl_easy_perform() to handle received headers.
 * Check for the expected response content type.
 */
static size_t _ekmf_header_cb(void *contents, size_t size, size_t nmemb,
			     void *userp)
{
	struct curl_header_cb_data *cb = (struct curl_header_cb_data *)userp;
	size_t num = size * nmemb;
	size_t ofs;
	char *hdr = contents;
	char *val, *str;
	int i;

	if (num < 2)
		return num;

	if (cb->headers != NULL) {
		str = strndup((char *)contents, num - 2);
		if (str == NULL) {
			pr_verbose(cb->verbose, "strndup failed");
			return 0;
		}
		*cb->headers = curl_slist_append(*cb->headers, str);
		free(str);
		if (*cb->headers == NULL) {
			pr_verbose(cb->verbose, "curl_slist_append failed");
			return 0;
		}
	}

	if (num < strlen(HTTP_HDR_CONTENT_TYPE))
		goto out;

	if (strncasecmp(hdr, HTTP_HDR_CONTENT_TYPE,
			strlen(HTTP_HDR_CONTENT_TYPE)) != 0)
		goto out;

	ofs = strlen(HTTP_HDR_CONTENT_TYPE);
	val = hdr + ofs;
	while (*val == ' ' && ofs < num) {
		ofs++;
		val++;
	}
	if (ofs >= num)
		goto out;

	for (i = 0; accepted_content_types[i] != NULL; i++) {
		if (num - ofs >= strlen(accepted_content_types[i]) &&
		    strncasecmp(val, accepted_content_types[i],
				strlen(accepted_content_types[i])) == 0)
			goto out;
	}

	cb->error = true;
	pr_verbose(cb->verbose, "Unexpected response Content-Type: %.*s",
		   (int)(num - ofs), val);
	return 0;

out:
	return num;
}

/**
 * Callback called during curl_easy_perform() to handle received data.
 * Parse the (potentially partial) JSON data.
 */
static size_t _ekmf_write_cb(void *contents, size_t size, size_t nmemb,
			    void *userp)
{
	struct curl_write_cb_data *cb = (struct curl_write_cb_data *)userp;
	enum json_tokener_error jerr;
	size_t num = size * nmemb;

	pr_verbose(cb->verbose, "Response Data: ->%.*s<-", (int)num,
		   (char *)contents);

	if (cb->obj != NULL) {
		pr_verbose(cb->verbose, "JSON data already complete, but "
			   "additional data received");
		cb->error = true;
		return 0;
	}

	cb->obj = json_tokener_parse_ex(cb->tok, (const char *)contents, num);

	if (cb->obj == NULL) {
		jerr = json_tokener_get_error(cb->tok);
		if (jerr == json_tokener_continue)
			goto out;

		cb->error = true;
		pr_verbose(cb->verbose, "json_tokener_parse_ex failed: %s",
			   json_tokener_error_desc(jerr));
		return 0;
	}

out:
	return num;
}

/**
 * Extracts the EKMFWeb API error information form the response object.
 */
static int _ekmf_get_api_error(json_object *response_obj, char **error_msg)
{
	json_object *field;
	int code;

	if (response_obj == NULL || error_msg == NULL)
		return -EINVAL;

	if (!json_object_is_type(response_obj, json_type_object))
		return -EBADMSG;

	if (!json_object_object_get_ex(response_obj, "code", &field))
		return -EBADMSG;
	if (!json_object_is_type(field, json_type_int))
		return -EBADMSG;
	code = json_object_get_int(field);

	if (!json_object_object_get_ex(response_obj, "message", &field))
		return -EBADMSG;
	if (!json_object_is_type(field, json_type_string))
		return -EBADMSG;

	if (asprintf(error_msg, "EKMFWeb: %d: %s", code,
		     json_object_get_string(field)) < 0)
		return -ENOMEM;

	return 0;
}

/**
 * This callback called before the SSL handshake is performed.
 * It adds a pinned server certificate to the SSL certificate store, so
 * that it is treated as trusted, although it might be self-signed.
 */
static CURLcode _ekmf_sslctx_cb(CURL *UNUSED(curl), void *sslctx, void *parm)
{
	struct curl_sslctx_cb_data *sslctx_cb = parm;
	SSL_CTX *ssl_ctx = (SSL_CTX *)sslctx;
	X509_STORE *store;
	X509 *cert = NULL;
	int rc;

	if (ssl_ctx == NULL || sslctx_cb == NULL)
		return CURLE_ABORTED_BY_CALLBACK;

	if (sslctx_cb->tls_server_cert == NULL)
		return CURLE_OK;

	store = SSL_CTX_get_cert_store(ssl_ctx);
	if (store == NULL) {
		pr_verbose(sslctx_cb->verbose, "Failed to get SSL Store");
		return CURLE_ABORTED_BY_CALLBACK;
	}

	rc = read_x509_certificate(sslctx_cb->tls_server_cert, &cert);
	if (rc != 0) {
		pr_verbose(sslctx_cb->verbose, "Failed to read the server "
			   "certificate from file '%s'",
			   sslctx_cb->tls_server_cert);
		return CURLE_ABORTED_BY_CALLBACK;
	}

	rc = X509_STORE_add_cert(store, cert);
	if (rc != 1) {
		pr_verbose(sslctx_cb->verbose, "Failed to add server "
			   "certificate to SSL Store");
		X509_free(cert);
		return CURLE_ABORTED_BY_CALLBACK;
	}

	X509_free(cert);
	return CURLE_OK;
}

/**
 * Perform an HTTP request to the url constructed from the base_url in config
 * and th uri specified using the specified HTTP request.
 * The config structure contains information about TLS certificates.
 * If specified, it serializes the request data (JSON) and sends it to the
 * server. The response data (JSON) is parsed and returned in the response data.
 * If the response content type is not JSON, then an error is returned.
 * The HTTP status code is returned in status_code.
 *
 * @param config            the configuration structure
 * @param uri               the uri (and query parameters) to concatenate to the
 *                          base_url from the config structure.
 * @param request           the HTTP request to perform (e.g. GET, PUT, POST)
 * @param request_data      the JSON data to be sent with the request.
 * @param request_headers   a NULL terminated list of pointers to HTTP headers
 *                          to send along with the request. Can be NULL.
 * @param login_token       if not NULL, a Bearer token to authorize with
 * @param response_data     on return the JSON response data is returned. When
 *                          no longer needed, it must be released using
 *                          json_object_put()
 * @param response headers  address of a curl_slist to add response headers to,
 *                          or NULL to not return any headers.
 * @param status_code       on return the HTTP status code is returned
 * @param error_msg         on return: If not NULL, then a textual error message
 *                          is returned in case of a failing request. The caller
 *                          must free the error string when it is not NULL.
 * @param curl              a CURL handle to perform the request with.
 * @param verbose           if true, verbose messages are printed
 *
 * @returns zero for success, a negative errno or a positive CURL error code in
 *          case of an error
 */
static int _ekmf_perform_request(const struct ekmf_config *config,
				 const char *uri, const char *request,
				 json_object *request_data,
				 char **request_headers,
				 const char *login_token,
				 json_object **response_data,
				 struct curl_slist **response_headers,
				 long *status_code, char **error_msg,
				 CURL *curl, bool verbose)
{
	const struct curl_tlssessioninfo *info = NULL;
	struct curl_header_cb_data header_cb = { 0 };
	struct curl_sslctx_cb_data sslctx_cb = { 0 };
	struct curl_write_cb_data write_cb = { 0 };
	char error_str[CURL_ERROR_SIZE] = { 0 };
	struct curl_slist *list = NULL;
	char *url = NULL;
	const char *str;
	struct stat sb;
	char *auth_hdr;
	int i, rc;

	if (config == NULL || uri == NULL || request == NULL ||
	    status_code == NULL || curl == NULL)
		return -EINVAL;

	if (error_msg != NULL)
		*error_msg = NULL;

	if (asprintf(&url, "%s%s", config->base_url, uri) < 0) {
		pr_verbose(verbose, "asprintf failed");
		return -ENOMEM;
	}

	pr_verbose(verbose, "Performing request for '%s'", url);

	curl_easy_reset(curl);

	/*
	 * The CURLOPT_SSL_CTX_FUNCTION callback only works with the OpenSSL
	 * curl backend. Check that OpenSSL is the current curl backend.
	 */
	rc = curl_easy_getinfo(curl, CURLINFO_TLS_SSL_PTR, &info);
	CURL_ERROR_CHECK(rc, "curl_easy_getinfo CURLINFO_TLS_SSL_PTR", verbose,
			 out);
	if (info->backend != CURLSSLBACKEND_OPENSSL) {
		pr_verbose(verbose, "libcurl is not using the OpenSSL backend");
		rc = -EIO;
		goto out;
	}

	rc = curl_easy_setopt(curl, CURLOPT_VERBOSE, verbose ? 1 : 0);
	CURL_ERROR_CHECK(rc, "curl_easy_setopt CURLOPT_VERBOSE", verbose, out);

	rc = curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, error_str);
	CURL_ERROR_CHECK(rc, "curl_easy_setopt CURLOPT_ERRORBUFFER", verbose,
			 out);

	rc = curl_easy_setopt(curl, CURLOPT_URL, url);
	CURL_ERROR_CHECK(rc, "curl_easy_setopt CURLOPT_URL", verbose, out);

	rc = curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER,
			 config->tls_verify_peer ? 1L : 0L);
	CURL_ERROR_CHECK(rc, "curl_easy_setopt CURLOPT_SSL_VERIFYPEER", verbose,
			 out);
	rc = curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST,
			 config->tls_verify_host ? 2L : 0L);
	CURL_ERROR_CHECK(rc, "curl_easy_setopt CURLOPT_SSL_VERIFYHOST", verbose,
			 out);

	if (config->tls_ca != NULL) {
		if (stat(config->tls_ca, &sb) != 0) {
			rc = -errno;
			pr_verbose(verbose, "stat failed on '%s': %s",
				   config->tls_ca, strerror(-rc));
			goto out;
		}

		if (S_ISDIR(sb.st_mode)) {
			rc = curl_easy_setopt(curl, CURLOPT_CAPATH,
					      config->tls_ca);
			CURL_ERROR_CHECK(rc, "curl_easy_setopt CURLOPT_CAPATH",
					 verbose, out);
		} else {
			rc = curl_easy_setopt(curl, CURLOPT_CAINFO,
					      config->tls_ca);
			CURL_ERROR_CHECK(rc, "curl_easy_setopt CURLOPT_CAINFO",
					 verbose, out);
		}
	}

	if (config->tls_client_cert != NULL) {
		rc = curl_easy_setopt(curl, CURLOPT_SSLCERT,
				      config->tls_client_cert);
		CURL_ERROR_CHECK(rc, "curl_easy_setopt CURLOPT_SSLCERT",
				 verbose, out);
		rc = curl_easy_setopt(curl, CURLOPT_SSLCERTTYPE, "PEM");
		CURL_ERROR_CHECK(rc, "curl_easy_setopt CURLOPT_SSLCERTTYPE",
				 verbose, out);
	}

	if (config->tls_client_key != NULL) {
		rc = curl_easy_setopt(curl, CURLOPT_SSLKEY,
				      config->tls_client_key);
		CURL_ERROR_CHECK(rc, "curl_easy_setopt CURLOPT_SSLKEY", verbose,
				 out);
		rc = curl_easy_setopt(curl, CURLOPT_SSLKEYTYPE, "PEM");
		CURL_ERROR_CHECK(rc, "curl_easy_setopt CURLOPT_SSLKEYTYPE",
				 verbose, out);

		if (config->tls_client_key_passphrase != NULL) {
			rc = curl_easy_setopt(curl, CURLOPT_KEYPASSWD,
					 config->tls_client_key_passphrase);
			CURL_ERROR_CHECK(rc,
					 "curl_easy_setopt CURLOPT_KEYPASSWD",
					 verbose, out);
		}
	}

	if (config->tls_issuer_cert != NULL) {
		rc = curl_easy_setopt(curl, CURLOPT_ISSUERCERT,
				      config->tls_issuer_cert);
		CURL_ERROR_CHECK(rc, "curl_easy_setopt CURLOPT_ISSUERCERT",
				 verbose, out);
	}

	if (config->tls_pinned_pubkey != NULL) {
		rc = curl_easy_setopt(curl, CURLOPT_PINNEDPUBLICKEY,
				 config->tls_pinned_pubkey);
		CURL_ERROR_CHECK(rc, "curl_easy_setopt CURLOPT_PINNEDPUBLICKEY",
				 verbose, out);
	}

	if (config->max_redirs > 0) {
		rc = curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
		CURL_ERROR_CHECK(rc, "curl_easy_setopt CURLOPT_FOLLOWLOCATION",
				 verbose, out);
		rc = curl_easy_setopt(curl, CURLOPT_MAXREDIRS,
				      config->max_redirs);
		CURL_ERROR_CHECK(rc, "curl_easy_setopt CURLOPT_MAXREDIRS",
				 verbose, out);
		rc = curl_easy_setopt(curl, CURLOPT_POSTREDIR,
				      CURL_REDIR_POST_ALL);
		CURL_ERROR_CHECK(rc, "curl_easy_setopt CURLOPT_POSTREDIR",
				 verbose, out);
	} else {
		rc = curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 0L);
		CURL_ERROR_CHECK(rc, "curl_easy_setopt CURLOPT_FOLLOWLOCATION",
				 verbose, out);
	}

	if (strcmp(request, "GET") == 0) {
		rc = curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, NULL);
		CURL_ERROR_CHECK(rc, "curl_easy_setopt CURLOPT_CUSTOMREQUEST",
				 verbose, out);
		rc = curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L);
		CURL_ERROR_CHECK(rc, "curl_easy_setopt CURLOPT_HTTPGET",
				 verbose, out);
	} else {
		rc = curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST,
				      request);
		CURL_ERROR_CHECK(rc, "curl_easy_setopt CURLOPT_CUSTOMREQUEST",
				 verbose, out);

		if (request_data != NULL) {
			rc = curl_easy_setopt(curl, CURLOPT_POST, 1L);
			CURL_ERROR_CHECK(rc, "curl_easy_setopt CURLOPT_POST",
					 verbose, out);

			list = curl_slist_append(list,
				"Content-Type: application/json;charset=UTF-8");
			if (list == NULL) {
				pr_verbose(verbose, "curl_slist_append failed");
				rc = -ENOMEM;
				goto out;
			}

			/*
			 * The memory returned by json_object_to_json_string_ext
			 * is freed when the JSON object is freed.
			 */
			str = json_object_to_json_string_ext(request_data,
						JSON_C_TO_STRING_PLAIN |
						JSON_C_TO_STRING_NOSLASHESCAPE);
			pr_verbose(verbose, "Request Data: ->%s<-", str);
			rc = curl_easy_setopt(curl, CURLOPT_POSTFIELDS, str);
			CURL_ERROR_CHECK(rc,
					 "curl_easy_setopt CURLOPT_POSTFIELDS",
					 verbose, out);
		}
	}

	list = curl_slist_append(list, "Accept: application/json");
	if (list == NULL) {
		pr_verbose(verbose, "curl_slist_append failed");
		rc = -ENOMEM;
		goto out;
	}
	list = curl_slist_append(list, "Accept-Charset: UTF-8");
	if (list == NULL) {
		pr_verbose(verbose, "curl_slist_append failed");
		rc = -ENOMEM;
		goto out;
	}
	/* Disable "Expect: 100-continue" */
	list = curl_slist_append(list, "Expect:");
	if (list == NULL) {
		pr_verbose(verbose, "curl_slist_append failed");
		rc = -ENOMEM;
		goto out;
	}

	if (login_token != NULL) {
		/*
		 * Note: We could alternatively use CURLOPT_XOAUTH2_BEARER, with
		 * CURLOPT_HTTPAUTH using CURLAUTH_BEARER, but this seems to
		 * cause a memory leak in some curl versions.
		 */
		if (asprintf(&auth_hdr, "Authorization: Bearer %s",
			     login_token) < 0) {
			pr_verbose(verbose, "asprintf failed");
			rc = -ENOMEM;
			goto out;
		}
		list = curl_slist_append(list, auth_hdr);
		free(auth_hdr);
		if (list == NULL) {
			pr_verbose(verbose, "curl_slist_append failed");
			rc = -ENOMEM;
			goto out;
		}
	}

	for (i = 0; request_headers != NULL &&
		    request_headers[i] != NULL; i++) {
		list = curl_slist_append(list, request_headers[i]);
		if (list == NULL) {
			pr_verbose(verbose, "curl_slist_append failed");
			rc = -ENOMEM;
			goto out;
		}
	}

	rc = curl_easy_setopt(curl, CURLOPT_HTTPHEADER, list);
	CURL_ERROR_CHECK(rc, "curl_easy_setopt CURLOPT_HTTPHEADER", verbose,
			 out);

	header_cb.headers = response_headers;
	header_cb.verbose = verbose;

	write_cb.verbose = verbose;
	write_cb.tok = json_tokener_new();
	if (write_cb.tok == NULL) {
		pr_verbose(verbose, "json_tokener_new failed");
		rc = -ENOMEM;
		goto out;
	}

	sslctx_cb.tls_server_cert = config->tls_server_cert;
	sslctx_cb.verbose = verbose;

	rc = curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, _ekmf_header_cb);
	CURL_ERROR_CHECK(rc, "curl_easy_setopt CURLOPT_HEADERFUNCTION", verbose,
			 out);
	rc = curl_easy_setopt(curl, CURLOPT_HEADERDATA, (void *)&header_cb);
	CURL_ERROR_CHECK(rc, "curl_easy_setopt CURLOPT_HEADERDATA", verbose,
			 out);

	rc = curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, _ekmf_write_cb);
	CURL_ERROR_CHECK(rc, "curl_easy_setopt CURLOPT_WRITEFUNCTION", verbose,
			 out);
	rc = curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&write_cb);
	CURL_ERROR_CHECK(rc, "curl_easy_setopt CURLOPT_WRITEDATA", verbose,
			 out);

	if (config->tls_server_cert != NULL) {
		rc = curl_easy_setopt(curl, CURLOPT_SSL_CTX_FUNCTION,
				      _ekmf_sslctx_cb);
		CURL_ERROR_CHECK(rc, "curl_easy_setopt "
				 "CURLOPT_SSL_CTX_FUNCTION", verbose, out);
		rc = curl_easy_setopt(curl, CURLOPT_SSL_CTX_DATA,
				      &sslctx_cb);
		CURL_ERROR_CHECK(rc, "curl_easy_setopt CURLOPT_SSL_CTX_DATA",
				 verbose, out);
	}

	rc = curl_easy_perform(curl);
	if (rc != CURLE_OK) {
		pr_verbose(verbose, "curl_easy_perform for '%s' failed: %s",
			   url, curl_easy_strerror(rc));
		pr_verbose(verbose, "Error: %s", error_str);

		if (header_cb.error) {
			pr_verbose(verbose, "Unexpected Content-Type");
			rc = -EBADMSG;
			if (error_msg != NULL && *error_msg == NULL) {
				if (asprintf(error_msg, "Unexpected response "
					     "Content-Type") < 0)
					error_msg = NULL;
			}
		}
		if (write_cb.error) {
			pr_verbose(verbose, "JSON parsing failed");
			rc = -EBADMSG;
			if (error_msg != NULL && *error_msg == NULL) {
				if (asprintf(error_msg, "Failed to JSON parse "
					     "the response content") < 0)
					error_msg = NULL;
			}
		}
		goto out;
	}

	rc = curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, status_code);
	CURL_ERROR_CHECK(rc, "curl_easy_getinfo CURLINFO_RESPONSE_CODE",
			 verbose, out);

	if (*status_code >= 400 && write_cb.obj != NULL &&
	    error_msg != NULL && *error_msg == NULL) {
		rc = _ekmf_get_api_error(write_cb.obj, error_msg);
		json_object_put(write_cb.obj);
		write_cb.obj = NULL;
		if (rc != 0)
			goto out;
	}

	if (response_data != NULL) {
		*response_data = write_cb.obj;
	} else {
		if (write_cb.obj != NULL)
			json_object_put(write_cb.obj);
	}

out:
	if (write_cb.tok != NULL)
		json_tokener_free(write_cb.tok);
	if (url != NULL)
		free(url);
	if (list != NULL)
		curl_slist_free_all(list);

	if (rc > 0 && error_msg != NULL && *error_msg == NULL) {
		if (asprintf(error_msg, "CURL: %s", strlen(error_str) > 0 ?
				error_str : curl_easy_strerror(rc)) < 0) {
			pr_verbose(verbose, "asprintf failed");
			rc = -ENOMEM;
		}
	}

	curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, NULL);

	return rc;
}

/**
 * Allocates or reuses a CURL handle. If curl_handle is not NULL, and
 * points to a non-NULL CURL handle, it is used, otherwise a new CURL handle
 * is allocated.
 */
static int _ekmf_get_curl_handle(CURL **curl_handle, CURL **curl)
{
	if (curl == NULL)
		return -EINVAL;

	if (curl_handle != NULL)
		*curl = *curl_handle;

	if (*curl == NULL)
		*curl = curl_easy_init();

	if (*curl == NULL)
		return -EIO;

	return 0;
}

/**
 * Releases a CURL handle. If curl_handle is not NULL, then the used CURL
 * handle is passed back via *curl_handle. If curl_handle is NULL, then the
 * used CURL handle is destroyed.
 */
static void _ekmf_release_curl_handle(CURL **curl_handle, CURL *curl)
{
	if (curl == NULL)
		return;

	if (curl_handle != NULL)
		*curl_handle = curl;
	else
		curl_easy_cleanup(curl);
}

/**
 * Print the certificate(s) contained in the specified PEM file.
 *
 * @param cert_pem          the file name of the PEM file to print
 * @param verbose           if true, verbose messages are printed
 *
 * @returns -EIO if the file could not be opened. -ENOENT if the PEM file
 *          does not contain any certificates. 0 if success.
 */
int ekmf_print_certificates(const char *cert_pem, bool verbose)
{
	int rc = -ENOENT;
	X509 *cert;
	FILE *fp;

	if (cert_pem == NULL)
		return -EINVAL;

	fp = fopen(cert_pem, "r");
	if (fp == NULL) {
		pr_verbose(verbose, "File '%s': %s", cert_pem, strerror(errno));
		return -EIO;
	}

	while (1) {
		cert = PEM_read_X509(fp, NULL, NULL, NULL);
		if (cert == NULL)
			break;

		X509_print_ex_fp(stdout, cert, 0, X509_FLAG_NO_EXTENSIONS);

		X509_free(cert);
		rc = 0;
	}

	fclose(fp);
	return rc;
}

/**
 * Checks if the login token stored in the file denoted by field login_token
 * of the config structure is valid or not. The file (if existent) contains a
 * JSON Web Token (JWT, see RFC7519). It is valid if the current date and time
 * is before its expiration time ("exp" claim), and after or equal its
 * not-before time ("nbf" claim).
 * Note: The signature (if any) of the JWT is not checked, nor any other JWT
 * fields.
 *
 * @param config            the configuration structure
 * @param valid             On return: true if the token is valid, false if not
 * @param login_token       On return: If not NULL: the login token, if the
 *                          token is still valid. The returned string must
 *                          be freed by the caller when no longer needed.
 * @param verbose           if true, verbose messages are printed
 *
 * @returns a negative errno in case of an error, 0 if success.
 */
int ekmf_check_login_token(const struct ekmf_config *config, bool *valid,
			   char **login_token, bool verbose)
{
	json_object *jwt_payload = NULL;
	json_object *exp_claim = NULL;
	json_object *nbf_claim = NULL;
	char *token = NULL;
	size_t count, size;
	int64_t exp, nbf;
	FILE *fp = NULL;
	struct stat sb;
	int rc = 0;
	time_t now;

	if (config == NULL || valid == NULL)
		return -EINVAL;

	if (config->login_token == NULL) {
		*valid = false;
		return 0;
	}

	if (login_token != NULL)
		*login_token = NULL;

	pr_verbose(verbose, "Reading login token from file : '%s'",
		   config->login_token);

	if (stat(config->login_token, &sb)) {
		rc = -errno;
		pr_verbose(verbose, "stat on file %s failed: '%s'",
			   config->login_token, strerror(-rc));
		return rc;
	}
	size = sb.st_size;
	if (size == 0) {
		pr_verbose(verbose, "File %s is empty", config->login_token);
		rc = -EIO;
		goto out;
	}

	token = (char *)malloc(size + 1);
	if (token == NULL) {
		pr_verbose(verbose, "Failed to allocate a buffer");
		return -ENOMEM;
	}

	fp = fopen(config->login_token, "r");
	if (fp == NULL) {
		rc = -errno;
		pr_verbose(verbose, "Failed to open file %s: '%s'",
			   config->login_token, strerror(-rc));
		goto out;
	}

	count = fread(token, 1, size, fp);
	if (count != size) {
		pr_verbose(verbose, "Failed to read the token");
		rc = -EIO;
		goto out;
	}
	token[size] = '\0';
	if (token[size - 1] == '\n')
		token[size - 1] = '\0';

	fclose(fp);
	fp = NULL;

	time(&now);
	*valid = true;

	rc = parse_json_web_token(token, NULL, &jwt_payload, NULL, NULL);
	if (rc != 0) {
		pr_verbose(verbose, "parse_json_web_token failed");
		goto out;
	}

	if (json_object_object_get_ex(jwt_payload, "exp", &exp_claim) &&
	    json_object_is_type(exp_claim, json_type_int)) {
		exp = json_object_get_int64(exp_claim);
		if (exp == 0) {
			pr_verbose(verbose,
				   "failed to get value from exp claim");
			rc = -EIO;
			goto out;
		}

		if (now > exp) {
			pr_verbose(verbose, "JWT is expired");
			*valid = false;
		}
	}

	if (json_object_object_get_ex(jwt_payload, "nbf", &nbf_claim) &&
	    json_object_is_type(nbf_claim, json_type_int)) {
		nbf = json_object_get_int64(nbf_claim);
		if (nbf == 0) {
			pr_verbose(verbose,
				   "failed to get value from nbf claim");
			rc = -EIO;
			goto out;
		}

		if (now <= nbf) {
			pr_verbose(verbose, "JWT is not yet valid");
			*valid = false;
		}
	}

	if (login_token != NULL && *valid) {
		*login_token = token;
		token = NULL;
	}

out:
	if (jwt_payload != NULL)
		json_object_put(jwt_payload);
	if (token != NULL)
		free(token);
	if (fp != NULL)
		fclose(fp);

	return rc;
}

/**
 * Performs a login of the specified user with a passcode. On success the
 * returned login token is stored in the file denoted by field login_token
 * of the config structure, so that it can be used by subsequent requests.
 *
 * To perform a single request, set curl_handle to NULL. This will cause the
 * function to initialize a new CURL handle, use it, and destroy it.
 * If you plan to perform multiple requests to the same host, supply the address
 * of a CURL pointer that is initially NULL. This function will then initialize
 * a new CURL handle on the first call. On subsequent calls, pass in the address
 * of the same CURL pointer so that the CURL handle is reused. After the last
 * request, the CURL handle must be destroyed by calling ekmf_curl_destroy).
 *
 * @param config            the configuration structure
 * @param curl_handle       address of a CURL handle used for reusing the same
 *                          CURL handle with multiple requests.
 * @param user_id           the user-ID to log-in.
 * @param passcode          the passcode to log-in the user.
 * @param error_msg         on return: If not NULL, then a textual error message
 *                          is returned in case of a failing request. The caller
 *                          must free the error string when it is not NULL.
 * @param verbose           if true, verbose messages are printed
 *
 * @returns zero for success, a negative errno in case of an error.
 *          -EACCES is returned, if the passcode is no longer valid.
 */
int ekmf_login(const struct ekmf_config *config, CURL **curl_handle,
	       const char *user_id, const char *passcode, char **error_msg,
	       bool verbose)
{
	json_object *response_obj = NULL;
	json_object *request_obj = NULL;
	const char *login_token, *tok;
	CURL *curl = NULL;
	long status_code;
	FILE *fp = NULL;
	size_t count;
	int rc;

	if (config == NULL || user_id == NULL || passcode == NULL)
		return -EINVAL;

	rc = _ekmf_get_curl_handle(curl_handle, &curl);
	if (rc != 0) {
		pr_verbose(verbose, "Failed to get CURL handle");
		rc = -EIO;
		goto out;
	}

	request_obj = json_object_new_object();
	JSON_CHECK_ERROR(request_obj == NULL, rc, -ENOMEM,
			 "Failed to generate JSON object", verbose, out);

	rc = json_object_object_add_ex(request_obj, "userId",
				       json_object_new_string(user_id), 0);
	JSON_CHECK_ERROR(rc != 0, rc, -EIO, "Failed to add data to JSON object",
			 verbose, out);

	rc = json_object_object_add_ex(request_obj, "passcode",
				       json_object_new_string(passcode), 0);
	JSON_CHECK_ERROR(rc != 0, rc, -EIO, "Failed to add data to JSON object",
			 verbose, out);

	rc = _ekmf_perform_request(config, EKMF_URI_SYSTEM_LOGIN, "POST",
				   request_obj, NULL, NULL, &response_obj, NULL,
				   &status_code, error_msg, curl, verbose);
	if (rc != 0) {
		pr_verbose(verbose, "Failed perform the REST call");
		if (rc > 0)
			rc = -EIO;
		goto out;
	}

	switch (status_code) {
	case 200:
		break;
	case 410:
		pr_verbose(verbose, "The passcode is no longer valid");
		rc = -EACCES;
		goto out;
	default:
		pr_verbose(verbose, "REST Call failed with HTTP status code: "
			   "%ld", status_code);
		rc = -EIO;
		goto out;
	}

	JSON_CHECK_OBJ(response_obj, json_type_object, rc, -EIO,
		       "No or invalid response content", verbose, out);

	login_token = json_get_string(response_obj, "authorizationToken");
	JSON_CHECK_ERROR(login_token == NULL, rc, -EBADMSG,
			 "Invalid response content", verbose, out);

	if (strncmp(login_token, "Bearer ", 7) != 0) {
		rc = -EBADMSG;
		pr_verbose(verbose, "Received token is not a Bearer token");
		goto out;
	}

	tok = &login_token[7];
	while (*tok == ' ')
		tok++;

	fp = fopen(config->login_token, "w");
	if (fp == NULL) {
		rc = -errno;
		pr_verbose(verbose, "Failed to open file %s: '%s'",
			   config->login_token, strerror(-rc));
		goto out;
	}

	count = fwrite(tok, 1, strlen(tok), fp);
	if (count != strlen(tok)) {
		pr_verbose(verbose, "Failed to write the token");
		rc = -EIO;
		goto out;
	}

	pr_verbose(verbose, "Login token successfully updated in file '%s'",
		   config->login_token);

out:
	_ekmf_release_curl_handle(curl_handle, curl);

	if (request_obj != NULL)
		json_object_put(request_obj);
	if (response_obj != NULL)
		json_object_put(response_obj);
	if (fp != NULL)
		fclose(fp);

	return rc;

}

/**
 * Request the EKMFWeb server's public signing key and store it into PEM file
 * specified in field server_pubkey of the config structure.
 *
 * To perform a single request, set curl_handle to NULL. This will cause the
 * function to initialize a new CURL handle, use it, and destroy it.
 * If you plan to perform multiple requests to the same host, supply the address
 * of a CURL pointer that is initially NULL. This function will then initialize
 * a new CURL handle on the first call. On subsequent calls, pass in the address
 * of the same CURL pointer so that the CURL handle is reused. After the last
 * request, the CURL handle must be destroyed by calling ekmf_curl_destroy).
 *
 * @param config            the configuration structure
 * @param curl_handle       address of a CURL handle used for reusing the same
 *                          CURL handle with multiple requests.
 * @param error_msg         on return: If not NULL, then a textual error message
 *                          is returned in case of a failing request. The caller
 *                          must free the error string when it is not NULL.
 * @param verbose           if true, verbose messages are printed
 *
 * @returns zero for success, a negative errno in case of an error.
 *          -EACCES is returned, if no or no valid login token is available.
 */
int ekmf_get_public_key(const struct ekmf_config *config, CURL **curl_handle,
			char **error_msg, bool verbose)
{
	json_object *response_obj = NULL;
	char *login_token = NULL;
	bool token_valid = false;
	EVP_PKEY *pkey = NULL;
	CURL *curl = NULL;
	long status_code;
	int rc;

	if (config == NULL)
		return -EINVAL;

	rc = SK_OPENSSL_init(verbose);
	if (rc != 0) {
		pr_verbose(verbose, "Failed to initialize secure key support: "
			   "%s", strerror(-rc));
		return rc;
	}

	rc = ekmf_check_login_token(config, &token_valid, &login_token,
				    verbose);
	if (rc != 0 || !token_valid) {
		pr_verbose(verbose, "No valid login token available");
		rc = -EACCES;
		goto out;
	}

	rc = _ekmf_get_curl_handle(curl_handle, &curl);
	if (rc != 0) {
		pr_verbose(verbose, "Failed to get CURL handle");
		rc = -EIO;
		goto out;
	}

	rc = _ekmf_perform_request(config, EKMF_URI_SYSTEM_PUBKEY, "GET",
				   NULL, NULL, login_token, &response_obj, NULL,
				   &status_code, error_msg, curl, verbose);
	if (rc != 0) {
		pr_verbose(verbose, "Failed perform the REST call");
		if (rc > 0)
			rc = -EIO;
		goto out;
	}

	switch (status_code) {
	case 200:
		break;
	case 401:
		pr_verbose(verbose, "Not authorized");
		rc = -EACCES;
		goto out;
	default:
		pr_verbose(verbose, "REST Call failed with HTTP status code: "
			   "%ld", status_code);
		rc = -EIO;
		goto out;
	}

	JSON_CHECK_OBJ(response_obj, json_type_object, rc, -EIO,
		       "No or invalid response content", verbose, out);

	rc = json_web_key_as_pkey(response_obj, EVP_PKEY_RSA, &pkey);
	if (rc != 0) {
		pr_verbose(verbose, "Failed convert the JWK to PKEY");
		goto out;
	}

	rc = write_public_key(config->ekmf_server_pubkey, pkey);
	if (rc != 0) {
		pr_verbose(verbose, "Failed to write public key '%s': %s",
			   config->ekmf_server_pubkey, strerror(-rc));
		goto out;
	}

	pr_verbose(verbose, "EKMFWeb public key written to file '%s'",
		   config->ekmf_server_pubkey);

out:
	_ekmf_release_curl_handle(curl_handle, curl);

	if (response_obj != NULL)
		json_object_put(response_obj);
	if (login_token != NULL)
		free(login_token);
	if (pkey != NULL)
		EVP_PKEY_free(pkey);
	SK_OPENSSL_term();

	return rc;
}

/**
 * Finds the setting with the specified ID in the settings array, and returns
 * its value if found, or NULL otherwise. The returned string is allocated
 * and must be freed by the caller when no lnger used.
 *
 * @param settings_array    the JSON array containing the settings
 * @param config            the setting ID to get
 * @param verbose           if true, verbose messages are printed
 *
 * @returns an allocated string conmtaining the setting value, or NULL
 */
static char *_ekmf_find_setting(json_object *settings_array,
				const char *setting_id, bool verbose)
{
	const char *id, *value;
	json_object *obj;
	int rc = 0, i;

	JSON_CHECK_OBJ(settings_array, json_type_array, rc, -EIO,
		       "No settings array", verbose, out);

	for (i = 0; i < (int)json_object_array_length(settings_array); i++) {
		obj = json_object_array_get_idx(settings_array, i);
		JSON_CHECK_OBJ(obj, json_type_object, rc, -EIO,
			       "No settings object", verbose, out);

		id = json_get_string(obj, "id");
		JSON_CHECK_ERROR(id == NULL, rc, -EIO,
				 "No id field in settings object", verbose,
				 out);

		if (strcmp(id, setting_id) != 0)
			continue;

		value = json_get_string(obj, "value");
		JSON_CHECK_ERROR(value == NULL, rc, -EIO,
				 "No value field in settings object", verbose,
				 out);

		return strdup(value);
	}

	pr_verbose(verbose, "Setting '%s' not found", setting_id);
out:
	return NULL;
}

/**
 * Retrieves settings from the EKMFWeb server, such as the template names for
 * generating keys in EKMFWeb.
 *
 * To perform a single request, set curl_handle to NULL. This will cause the
 * function to initialize a new CURL handle, use it, and destroy it.
 * If you plan to perform multiple requests to the same host, supply the address
 * of a CURL pointer that is initially NULL. This function will then initialize
 * a new CURL handle on the first call. On subsequent calls, pass in the address
 * of the same CURL pointer so that the CURL handle is reused. After the last
 * request, the CURL handle must be destroyed by calling ekmf_curl_destroy).
 *
 * @param config            the configuration structure
 * @param curl_handle       address of a CURL handle used for reusing the same
 *                          CURL handle with multiple requests.
 * @param identity_template on return: If not NULL, the name of the template
 *                          used to generate identity keys with. The caller
 *                          must free the error string when it is not NULL.
 * @param xts_key1_template on return: If not NULL, the name of the template
 *                          used to generate the first XTS key with. The caller
 *                          must free the error string when it is not NULL.
 * @param xts_key1_template on return: If not NULL, the name of the template
 *                          used to generate the second XTS key with. The caller
 *                          must free the error string when it is not NULL.
 * @param xts_key1_template on return: If not NULL, the name of the template
 *                          used to generate a non-XTS key with. The caller
 *                          must free the error string when it is not NULL.
 * @param error_msg         on return: If not NULL, then a textual error message
 *                          is returned in case of a failing request. The caller
 *                          must free the error string when it is not NULL.
 * @param verbose           if true, verbose messages are printed
 *
 * @returns zero for success, a negative errno in case of an error.
 *          -EACCES is returned, if no or no valid login token is available.
 */
int ekmf_get_settings(const struct ekmf_config *config, CURL **curl_handle,
		      char **identity_template, char **xts_key1_template,
		      char **xts_key2_template, char **non_xts_template,
		      char **error_msg, bool verbose)
{
	json_object *response_obj = NULL;
	char *login_token = NULL;
	bool token_valid = false;
	CURL *curl = NULL;
	long status_code;
	int rc;

	if (config == NULL)
		return -EINVAL;

	if (identity_template == NULL && xts_key1_template == NULL &&
	    xts_key2_template == NULL && non_xts_template == NULL)
		return 0;

	rc = ekmf_check_login_token(config, &token_valid, &login_token,
				    verbose);
	if (rc != 0 || !token_valid) {
		pr_verbose(verbose, "No valid login token available");
		rc = -EACCES;
		goto out;
	}

	rc = _ekmf_get_curl_handle(curl_handle, &curl);
	if (rc != 0) {
		pr_verbose(verbose, "Failed to get CURL handle");
		rc = -EIO;
		goto out;
	}

	rc = _ekmf_perform_request(config, EKMF_URI_SYSTEM_SETTINGS, "GET",
				   NULL, NULL, login_token, &response_obj, NULL,
				   &status_code, error_msg, curl, verbose);
	if (rc != 0) {
		pr_verbose(verbose, "Failed perform the REST call");
		if (rc > 0)
			rc = -EIO;
		goto out;
	}

	switch (status_code) {
	case 200:
		break;
	case 401:
		pr_verbose(verbose, "Not authorized");
		rc = -EACCES;
		goto out;
	default:
		pr_verbose(verbose, "REST Call failed with HTTP status code: "
			   "%ld", status_code);
		rc = -EIO;
		goto out;
	}

	JSON_CHECK_OBJ(response_obj, json_type_array, rc, -EIO,
		       "No or invalid response content", verbose, out);

	if (identity_template != NULL) {
		*identity_template = _ekmf_find_setting(response_obj,
					SETTING_ID_IDENTITY_TEMPLATE, verbose);
		if (*identity_template == NULL) {
			if (error_msg != NULL) {
				if (asprintf(error_msg, "The EKMF Web setting "
					     "'Identity Key Template Name' "
					     "must be configured") < 0)
					error_msg = NULL;
			}
			rc = -EINVAL;
			goto out;
		}
	}

	if (xts_key1_template != NULL) {
		*xts_key1_template = _ekmf_find_setting(response_obj,
					SETTING_ID_XTS_KEY1_TEMPLATE, verbose);
		if (*xts_key1_template == NULL) {
			if (error_msg != NULL) {
				if (asprintf(error_msg, "The EKMF Web setting "
					     "'XTS Key Template Name (Key 1)' "
					     "must be configured") < 0)
					error_msg = NULL;
			}
			rc = -EINVAL;
			goto out;
		}
	}

	if (xts_key2_template != NULL) {
		*xts_key2_template = _ekmf_find_setting(response_obj,
					SETTING_ID_XTS_KEY2_TEMPLATE, verbose);
		if (*identity_template == NULL) {
			if (error_msg != NULL) {
				if (asprintf(error_msg, "The EKMF Web setting "
					     "'XTS Key Template Name (Key 2)' "
					     "must be configured") < 0)
					error_msg = NULL;
			}
			rc = -EINVAL;
			goto out;
		}
	}

	if (non_xts_template != NULL) {
		*non_xts_template = _ekmf_find_setting(response_obj,
					SETTING_ID_NON_XTS_TEMPLATE, verbose);
		if (*non_xts_template == NULL) {
			if (error_msg != NULL) {
				if (asprintf(error_msg, "The EKMF Web setting "
					     "'Non-XTS Key Template Name' "
					     "must be configured") < 0)
					error_msg = NULL;
			}
			rc = -EINVAL;
			goto out;
		}
	}

out:
	_ekmf_release_curl_handle(curl_handle, curl);

	if (response_obj != NULL)
		json_object_put(response_obj);
	if (login_token != NULL)
		free(login_token);
	if (rc != 0) {
		if (identity_template != NULL && *identity_template != NULL) {
			free(*identity_template);
			*identity_template = NULL;
		}
		if (xts_key1_template != NULL && *xts_key1_template != NULL) {
			free(*xts_key1_template);
			*xts_key1_template = NULL;
		}
		if (xts_key2_template != NULL && *xts_key2_template != NULL) {
			free(*xts_key2_template);
			*xts_key2_template = NULL;
		}
		if (non_xts_template != NULL && *non_xts_template != NULL) {
			free(*non_xts_template);
			*non_xts_template = NULL;
		}
	}

	return rc;
}

/**
 * Checks if the EKMFWeb server has the required Pervasive Encryption feature
 * installed
 *
 * To perform a single request, set curl_handle to NULL. This will cause the
 * function to initialize a new CURL handle, use it, and destroy it.
 * If you plan to perform multiple requests to the same host, supply the address
 * of a CURL pointer that is initially NULL. This function will then initialize
 * a new CURL handle on the first call. On subsequent calls, pass in the address
 * of the same CURL pointer so that the CURL handle is reused. After the last
 * request, the CURL handle must be destroyed by calling ekmf_curl_destroy).
 *
 * @param config            the configuration structure
 * @param curl_handle       address of a CURL handle used for reusing the same
 *                          CURL handle with multiple requests.
 * @param error_msg         on return: If not NULL, then a textual error message
 *                          is returned in case of a failing request. The caller
 *                          must free the error string when it is not NULL.
 * @param verbose           if true, verbose messages are printed
 *
 * @returns zero for success, a negative errno in case of an error.
 *          -ENOTSUP is returned, if the feature is not installed.
 */
int ekmf_check_feature(const struct ekmf_config *config, CURL **curl_handle,
		       char **error_msg, bool verbose)
{
	json_object *response_obj = NULL, *obj;
	bool found = false;
	CURL *curl = NULL;
	long status_code;
	const char *id;
	int rc, i;

	if (config == NULL)
		return -EINVAL;

	rc = _ekmf_get_curl_handle(curl_handle, &curl);
	if (rc != 0) {
		pr_verbose(verbose, "Failed to get CURL handle");
		rc = -EIO;
		goto out;
	}

	rc = _ekmf_perform_request(config, EKMF_URI_SYSTEM_FEATURES, "GET",
				   NULL, NULL, NULL, &response_obj, NULL,
				   &status_code, error_msg, curl, verbose);
	if (rc != 0) {
		pr_verbose(verbose, "Failed perform the REST call");
		if (rc > 0)
			rc = -EIO;
		goto out;
	}

	switch (status_code) {
	case 200:
		break;
	default:
		pr_verbose(verbose, "REST Call failed with HTTP status code: "
			   "%ld", status_code);
		rc = -EIO;
		goto out;
	}

	JSON_CHECK_OBJ(response_obj, json_type_array, rc, -EIO,
		       "No or invalid response content", verbose, out);

	for (i = 0; i < (int)json_object_array_length(response_obj); i++) {
		obj = json_object_array_get_idx(response_obj, i);
		JSON_CHECK_OBJ(obj, json_type_object, rc, -EIO,
			       "No features object", verbose, out);

		id = json_get_string(obj, "id");
		JSON_CHECK_ERROR(id == NULL, rc, -EIO,
				 "No id field in settings object", verbose,
				 out);

		if (strcmp(id, FEATURE_ID_PERVASIVE_ENCRYPTION) != 0)
			continue;

		found = true;
		break;
	}

	if (!found) {
		pr_verbose(verbose, "Feature '%s' is not installed",
			   FEATURE_ID_PERVASIVE_ENCRYPTION);
		rc = -ENOTSUP;
		if (asprintf(error_msg, "EKMF Web feature "
			     "'Pervasive Encryption' is not installed.")) {
			pr_verbose(verbose, "asprintf failed");
			rc = -ENOMEM;
		}
		goto out;
	}

out:
	_ekmf_release_curl_handle(curl_handle, curl);

	if (response_obj != NULL)
		json_object_put(response_obj);

	return rc;
}


/**
 * Build the party info JSON object as base64(sha256(key_uuid|timestamp)).
 * Digest_nid specifies the digest to use, ot 0 to use the default (SHA256).
 * The function returns the party info JSON object, as well as the raw party
 * info.
 */
static int _ekmf_build_party_info(const char *key_uuid, const char *timestamp,
				  int digest_nid, unsigned char *party_info,
				  size_t *party_info_length,
				  json_object **party_info_obj, bool verbose)
{
	unsigned int digest_len;
	EVP_MD_CTX *ctx = NULL;
	const EVP_MD *md;
	int rc;

	md = EVP_get_digestbynid(digest_nid != 0 ? digest_nid : NID_sha256);
	if (md == NULL) {
		pr_verbose(verbose, "Failed to get specified digest");
		rc = -EINVAL;
		goto out;
	}

	if (*party_info_length < (size_t)EVP_MD_size(md)) {
		pr_verbose(verbose, "Party info buffer is too small");
		return -ERANGE;
		goto out;
	}

	ctx = EVP_MD_CTX_create();
	if (ctx == NULL) {
		pr_verbose(verbose, "Failed to allocate MD context");
		rc = -ENOMEM;
		goto out;
	}

	rc = EVP_DigestInit_ex(ctx, md, NULL);
	if (rc != 1) {
		pr_verbose(verbose, "Failed to initialize MD context");
		rc = -EIO;
		goto out;
	}

	rc = EVP_DigestUpdate(ctx, key_uuid, strlen(key_uuid));
	if (rc != 1) {
		pr_verbose(verbose, "Failed to add data to the MD context");
		rc = -EIO;
		goto out;
	}

	rc = EVP_DigestUpdate(ctx, timestamp, strlen(timestamp));
	if (rc != 1) {
		pr_verbose(verbose, "Failed to add data to the MD context");
		rc = -EIO;
		goto out;
	}

	rc = EVP_DigestFinal_ex(ctx, party_info, &digest_len);
	if (rc != 1) {
		pr_verbose(verbose, "Failed to finalize the MD context");
		rc = -EIO;
		goto out;
	}

	*party_info_length = digest_len;
	*party_info_obj = json_object_new_base64url(party_info, digest_len);
	rc = 0;

out:
	if (ctx != NULL)
		EVP_MD_CTX_destroy(ctx);

	return rc;
}

/**
 * Builds a (detached) JSON Web Signature using the secure identity key from
 * the payload and returns a signature JSON object
 */
static int _ekmf_build_signature(unsigned char *key_blob,
				 size_t key_blob_length,
				 json_object *payload_obj,
				 json_object **signature_obj,
				 int digest_nid, bool use_rsa_pss,
				 const char *jws_kid,
				 const struct ekmf_ext_lib *ext_lib,
				 bool verbose)
{
	struct sk_rsa_pss_params rsa_pss_params;
	struct ext_lib_info ext_lib_info;
	EVP_PKEY_CTX *pkey_ctx = NULL;
	EVP_MD_CTX *md_ctx = NULL;
	EVP_PKEY *pkey = NULL;
	const char *payload;
	const char *jws_alg;
	int rc, curve_nid;
	char *jws = NULL;
	BIO *b;

	_ekmf_copy_ext_lib(ext_lib, &ext_lib_info);

	rc = SK_OPENSSL_get_secure_key_as_pkey(key_blob, key_blob_length,
					       use_rsa_pss, &pkey,
					       &ext_lib_info.ext_lib,
					       verbose);
	if (rc != 0) {
		pr_verbose(verbose, "Failed to get the identity PKEY");
		goto out;
	}

	/*
	 * Only the following combinations are allowed per RFC7518 for JSON
	 * Web Signatures (JWS) using ECC or RSA identity keys:
	 *   alg=ES256: ECDSA using P-256 and SHA-256
	 *   alg=ES384: ECDSA using P-384 and SHA-384
	 *   alg=ES512: ECDSA using P-521 and SHA-512
	 *   alg=RS256: RSA-PKCS1 using SHA-256
	 *   alg=RS384: RSA-PKCS1 using SHA-384
	 *   alg=RS512: RSA-PKCS1 using SHA-512
	 *   alg=PS256: RSA-PSS using SHA-256, MGF1 with SHA-256, salt=digest
	 *   alg=PS384: RSA-PSS using SHA-384, MGF1 with SHA-384, salt=digest
	 *   alg=PS512: RSA-PSS using SHA-512, MGF1 with SHA-512, salt=digest
	 */
	switch (EVP_PKEY_id(pkey)) {
	case EVP_PKEY_EC:
		curve_nid = SK_OPENSSL_get_curve_from_ec_pkey(pkey);
		switch (curve_nid) {
		case NID_secp521r1:
			digest_nid = NID_sha512;
			jws_alg = "ES512";
			break;
		case NID_secp384r1:
			digest_nid = NID_sha384;
			jws_alg = "ES384";
			break;
		case NID_X9_62_prime256v1:
			digest_nid = NID_sha256;
			jws_alg = "ES256";
			break;
		default:
			pr_verbose(verbose, "Unsupported curve");
			rc = -EINVAL;
			goto out;
		}
		break;
	case EVP_PKEY_RSA:
		switch (digest_nid) {
		case NID_sha256:
			jws_alg = "RS256";
			break;
		case NID_sha384:
			jws_alg = "RS384";
			break;
		case NID_sha512:
		case 0:
			jws_alg = "RS512";
			digest_nid = NID_sha512;
			break;
		default:
			pr_verbose(verbose, "Unsupported digest");
			rc = -EINVAL;
			goto out;
		}
		break;
	case EVP_PKEY_RSA_PSS:
		switch (digest_nid) {
		case NID_sha256:
			jws_alg = "PS256";
			break;
		case NID_sha384:
			jws_alg = "PS384";
			break;
		case NID_sha512:
		case 0:
			jws_alg = "PS512";
			digest_nid = NID_sha512;
			break;
		default:
			pr_verbose(verbose, "Unsupported digest");
			rc = -EINVAL;
			goto out;
		}
		rsa_pss_params.mgf_digest_nid = digest_nid;
		rsa_pss_params.salt_len = RSA_PSS_SALTLEN_DIGEST;
		break;
	default:
		pr_verbose(verbose, "Unsupported key type");
		rc = -EINVAL;
		goto out;
	}

	rc =  SK_OPENSSL_setup_sign_context(pkey, false, digest_nid,
					    &rsa_pss_params, &md_ctx, &pkey_ctx,
					    verbose);
	if (rc != 0) {
		pr_verbose(verbose, "Failed to setup secure key sign context");
		goto out;
	}

	payload = json_object_to_json_string_ext(payload_obj,
					JSON_C_TO_STRING_PLAIN |
					JSON_C_TO_STRING_NOSLASHESCAPE);
	if (payload == NULL) {
		pr_verbose(verbose, "Failed to get the payload string");
		rc = -EIO;
		goto out;
	}

	if (verbose) {
		pr_verbose(verbose, "JWS Payload: ->%s<-", payload);
		pr_verbose(verbose, "JWS alg: %s", jws_alg);
		pr_verbose(verbose, "Public signing key:");
		b = BIO_new_fp(stderr, BIO_NOCLOSE);
		PEM_write_bio_PUBKEY(b, pkey);
		BIO_free(b);
	}

	rc = create_json_web_signature(jws_alg, false, jws_kid,
				       (unsigned char *)payload,
				       strlen(payload), true, md_ctx, &jws);
	if (rc != 0) {
		pr_verbose(verbose, "Failed to build the JWS");
		goto out;
	}

	*signature_obj = json_object_new_string(jws);
	rc = 0;

out:
	if (md_ctx != NULL)
		EVP_MD_CTX_free(md_ctx);
	if (pkey != NULL)
		EVP_PKEY_free(pkey);
	if (jws != NULL)
		free(jws);

	return rc;
}

/**
 * Verifies the (detached) JSON Web Signature using the server's public signing
 * key and the response payload.
 * Note: This function removes the signature field from the response JSON
 *       object!
 */
static int _ekmf_verify_signature(json_object *response_obj,
				  EVP_PKEY *server_pubkey, bool verbose)
{
	json_object *signature_obj = NULL;
	const char *sign_payload;
	BIO *b;
	int rc;

	if (response_obj == NULL)
		return -EINVAL;

	json_object_object_get_ex(response_obj, "signature",
				  &signature_obj);
	JSON_CHECK_OBJ(signature_obj, json_type_string, rc, -EIO,
		       "Failed to get the response signature", verbose, out);

	json_object_get(signature_obj); /* Take ownership */
	json_object_object_del(response_obj, "signature");

	sign_payload = json_object_to_json_string_ext(response_obj,
					JSON_C_TO_STRING_PLAIN |
					JSON_C_TO_STRING_NOSLASHESCAPE);
	if (sign_payload == NULL) {
		rc = -ENOMEM;
		goto out;
	}

	if (verbose) {
		pr_verbose(verbose, "JWS Payload: ->%s<-", sign_payload);
		pr_verbose(verbose, "Public signing key:");
		b = BIO_new_fp(stderr, BIO_NOCLOSE);
		PEM_write_bio_PUBKEY(b, server_pubkey);
		BIO_free(b);
	}

	rc = verify_json_web_signature(json_object_get_string(signature_obj),
				       (const unsigned char *)sign_payload,
				       strlen(sign_payload), server_pubkey);
	if (rc != 0) {
		pr_verbose(verbose, "Signature verify of response failed");
		goto out;
	}

	pr_verbose(verbose, "Signature of response successfully verified");

out:
	if (signature_obj != NULL)
		json_object_put(signature_obj);

	return rc;
}

/**
 * Import the key retrieved from EKMFWeb.
 */
static int _ekmf_import_key(unsigned char *req_sess_key,
			    size_t req_sess_key_length,
			    unsigned char *req_party_info,
			    size_t req_party_info_length,
			    unsigned char *resp_party_info,
			    size_t resp_party_info_length,
			    json_object *resp_sess_jwk_obj,
			    json_object *resp_exp_jwk_obj,
			    unsigned char *key_blob, size_t *key_blob_length,
			    const struct ekmf_ext_lib *ext_lib, bool verbose)
{
	size_t resp_sess_ec_key_length, resp_exported_key_length;
	unsigned char resp_exported_key[MAX_SYM_KEY_BLOB_SIZE];
	unsigned char transport_key[MAX_SYM_KEY_BLOB_SIZE];
	unsigned char resp_sess_key[MAX_KEY_BLOB_SIZE];
	size_t party_info_length, transport_key_length;
	unsigned char *party_info = NULL;
	int rc;

	party_info_length = req_party_info_length + resp_party_info_length;
	party_info = malloc(party_info_length);
	if (party_info == NULL) {
		pr_verbose(verbose, "Failed to allocate memory");
		rc = -ENOMEM;
		goto out;
	}

	memcpy(party_info, req_party_info, req_party_info_length);
	memcpy(party_info + req_party_info_length, resp_party_info,
	       resp_party_info_length);

	switch (ext_lib->type) {
	case EKMF_EXT_LIB_CCA:
		resp_sess_ec_key_length = sizeof(resp_sess_key);
		rc = cca_import_key_from_json_web_key(ext_lib->cca,
						      resp_sess_jwk_obj,
						      resp_sess_key,
						      &resp_sess_ec_key_length,
						      verbose);
		if (rc != 0) {
			pr_verbose(verbose, "Failed to import the session EC "
				   "key");
			goto out;
		}

		transport_key_length = sizeof(transport_key);
		rc = cca_ec_dh_derive_importer(ext_lib->cca,
					       req_sess_key,
					       req_sess_key_length,
					       resp_sess_key,
					       resp_sess_ec_key_length,
					       party_info, party_info_length,
					       CCA_KDF_ANS_X9_63_CCA,
					       transport_key,
					       &transport_key_length,
					       verbose);
		if (rc != 0) {
			pr_verbose(verbose, "Failed to derive transport key");
			goto out;
		}

		resp_exported_key_length = sizeof(resp_exported_key);
		rc = cca_import_key_from_json_web_key(ext_lib->cca,
						      resp_exp_jwk_obj,
						      resp_exported_key,
						      &resp_exported_key_length,
						      verbose);
		if (rc != 0) {
			pr_verbose(verbose, "Failed to import the exported "
				   "key");
			goto out;
		}

		rc = cca_import_external_key(ext_lib->cca, resp_exported_key,
					     resp_exported_key_length,
					     transport_key,
					     transport_key_length,
					     key_blob, key_blob_length,
					     verbose);
		if (rc != 0) {
			pr_verbose(verbose, "Failed to unwrap the exported "
				   "key with the transport key");
			goto out;
		}

		break;
	default:
		pr_verbose(verbose, "Invalid ext lib type: %d", ext_lib->type);
		return -EINVAL;
	}

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

	return rc;
}

/*
 * Callback function for SK_OPENSSL_get_public_from_secure_key. Construct
 * a JSON Web key from a public EC key.
 */
static int _ekmf_pub_key_as_json_web_key(const struct sk_pub_key_info *pub_key,
					 void *private)
{
	json_object **jwk_ret = private;
	json_object *jwk_obj = NULL;
	int rc = 0;

	if (pub_key->type != SK_KEY_TYPE_EC)
		return -EINVAL;

	/* construct the JWK */
	jwk_obj = json_object_new_object();
	if (jwk_obj == NULL)
		return -ENOMEM;

	/*
	 * Note: The order of the fields is important, EKMFWeb expects it in
	 * exactly this order!
	 */
	rc = json_object_object_add_ex(jwk_obj, "kty",
				       json_object_new_string("EC"), 0);
	rc |= json_object_object_add_ex(jwk_obj, "crv", json_object_new_string(
				EC_curve_nid2nist(pub_key->ec.curve_nid)), 0);
	rc |= json_object_object_add_ex(jwk_obj, "x",
					json_object_new_base64url(pub_key->ec.x,
							pub_key->ec.prime_len),
					0);
	rc |= json_object_object_add_ex(jwk_obj, "y",
					json_object_new_base64url(pub_key->ec.y,
							pub_key->ec.prime_len),
					0);
	if (rc != 0) {
		json_object_put(jwk_obj);
		return -EIO;
	}

	*jwk_ret = jwk_obj;

	return 0;
}

/**
 * Requests a key to be retrieved from EKMFweb and imported under the current
 * HSM's master key.
 *
 * To perform a single request, set curl_handle to NULL. This will cause the
 * function to initialize a new CURL handle, use it, and destroy it.
 * If you plan to perform multiple requests to the same host, supply the address
 * of a CURL pointer that is initially NULL. This function will then initialize
 * a new CURL handle on the first call. On subsequent calls, pass in the address
 * of the same CURL pointer so that the CURL handle is reused. After the last
 * request, the CURL handle must be destroyed by calling ekmf_curl_destroy).
 *
 * @param config            the configuration structure
 * @param curl_handle       address of a CURL handle used for reusing the same
 *                          CURL handle with multiple requests.
 * @param key_uuid          the UUID of the key to retrieve
 * @param sess_ec_curve_nid The OpenSSL nid of the EC curve used for the session
 *                          ECC key. If 0, then the default curve is used.
 * @param sign_rsa_digest_nid The OpenSSL nid of a digest used to sign the
 *                          request with if the identity key is an RSA-type key.
 *                          If 0, then the default digest is used.
 *                          Ignored for ECC-type identity keys.
 * @param use_rsa_pss       If true, and the identity key is an RSA-type key,
 *                          use RSA-PSS to sign the request.
 * @param signature_kid     the Key ID for the signature of the request
 * @param key_blob          a buffer to store the retrieved key blob to
 * @param key_blob_length   On entry: the size ofthe buffer
 *                          On return: the size of the key blob retrieved
 * @param error_msg         on return: If not NULL, then a textual error message
 *                          is returned in case of a failing request. The caller
 *                          must free the error string when it is not NULL.
 * @param ext_lib           External secure key crypto library to use
 * @param verbose           if true, verbose messages are printed
 *
 * @returns zero for success, a negative errno in case of an error.
 *          -EACCES is returned, if no or no valid login token is available.
 *          -EPERM is returned if the login token does not have permission to
 *          retrieve the key
 */
int ekmf_retrieve_key(const struct ekmf_config *config, CURL **curl_handle,
		      const char *key_uuid, int sess_ec_curve_nid,
		      int sign_rsa_digest_nid, bool use_rsa_pss,
		      const char *signature_kid, unsigned char *key_blob,
		      size_t *key_blob_length, char **error_msg,
		      const struct ekmf_ext_lib *ext_lib, bool verbose)
{
	size_t req_party_info_length, resp_party_info_length;
	unsigned char req_party_info[SHA512_DIGEST_LENGTH];
	size_t req_sess_ec_key_length, identity_key_length;
	unsigned char req_sess_ec_key[MAX_KEY_BLOB_SIZE];
	unsigned char identity_key[MAX_KEY_BLOB_SIZE];
	json_object *resp_originator_obj = NULL;
	json_object *resp_addl_info_obj = NULL;
	json_object *req_party_info_obj = NULL;
	json_object *req_originator_obj = NULL;
	json_object *req_timestamp_obj = NULL;
	json_object *req_addl_info_obj = NULL;
	json_object *req_signature_obj = NULL;
	unsigned char *resp_party_info = NULL;
	json_object *resp_sess_jwk_obj = NULL;
	json_object *req_sess_jwk_obj = NULL;
	json_object *resp_exp_jwk_obj = NULL;
	struct ext_lib_info ext_lib_info;
	json_object *response_obj = NULL;
	json_object *request_obj = NULL;
	struct sk_key_gen_info gen_info;
	EVP_PKEY *server_pubkey = NULL;
	char *escaped_uuid = NULL;
	char *login_token = NULL;
	bool token_valid = false;
	CURL *curl = NULL;
	long status_code;
	char *uri = NULL;
	int rc;

	if (config == NULL || key_uuid == NULL || key_blob == NULL ||
	    key_blob_length == NULL || ext_lib == NULL)
		return -EINVAL;

	_ekmf_copy_ext_lib(ext_lib, &ext_lib_info);

	rc = SK_OPENSSL_init(verbose);
	if (rc != 0) {
		pr_verbose(verbose, "Failed to initialize secure key support: "
			   "%s", strerror(-rc));
		return rc;
	}

	rc = ekmf_check_login_token(config, &token_valid, &login_token,
				    verbose);
	if (rc != 0 || !token_valid) {
		pr_verbose(verbose, "No valid login token available");
		rc = -EACCES;
		goto out;
	}

	rc = _ekmf_get_curl_handle(curl_handle, &curl);
	if (rc != 0) {
		pr_verbose(verbose, "Failed to get CURL handle");
		rc = -EIO;
		goto out;
	}

	rc = read_public_key(config->ekmf_server_pubkey, &server_pubkey);
	if (rc != 0) {
		pr_verbose(verbose, "Failed to read EKMFWeb server's public key"
			   " '%s': %s", config->ekmf_server_pubkey,
			   strerror(-rc));
		goto out;
	}

	identity_key_length = sizeof(identity_key);
	rc = read_key_blob(config->identity_secure_key, identity_key,
			   &identity_key_length);
	if (rc != 0) {
		pr_verbose(verbose, "Failed to read identity key from file "
			   "'%s': %s", config->identity_secure_key,
			   strerror(-rc));
		goto out;
	}

	gen_info.type = SK_KEY_TYPE_EC;
	gen_info.ec.curve_nid = sess_ec_curve_nid != 0 ? sess_ec_curve_nid :
						DEFAULT_SESSION_EC_KEY_CURVE;

	req_sess_ec_key_length = sizeof(req_sess_ec_key);
	rc = SK_OPENSSL_generate_secure_key(req_sess_ec_key,
					    &req_sess_ec_key_length,
					    &gen_info, &ext_lib_info.ext_lib,
					    verbose);
	if (rc != 0) {
		pr_verbose(verbose, "Failed to generate a session EC "
			   "key");
		goto out;
	}

	rc = SK_OPENSSL_get_public_from_secure_key(req_sess_ec_key,
						   req_sess_ec_key_length,
						_ekmf_pub_key_as_json_web_key,
						   &req_sess_jwk_obj,
						   &ext_lib_info.ext_lib,
						   verbose);
	if (rc != 0 || req_sess_jwk_obj == NULL) {
		pr_verbose(verbose, "Failed to generate session JWK");
		goto out;
	}

	req_timestamp_obj = get_json_timestamp();
	JSON_CHECK_ERROR(req_timestamp_obj == NULL, rc, -EIO,
			 "Failed to generate timestamp", verbose, out);

	req_party_info_length = sizeof(req_party_info);
	rc = _ekmf_build_party_info(key_uuid,
				    json_object_get_string(req_timestamp_obj),
				    NID_sha256, req_party_info,
				    &req_party_info_length,
				    &req_party_info_obj, verbose);
	if (rc != 0) {
		pr_verbose(verbose, "Failed to build the party info");
		goto out;
	}

	/*
	 * Note: The order of the fields is important, EKMFWeb expects it in
	 * exactly this order!
	 */
	req_addl_info_obj = json_object_new_object();
	JSON_CHECK_ERROR(req_addl_info_obj == NULL, rc, -ENOMEM,
			 "Failed to generate JSON object", verbose, out);

	rc = json_object_object_add_ex(req_addl_info_obj, "kdf",
				       json_object_new_string("ANS-X9.63-CCA"),
				       0);
	JSON_CHECK_ERROR(rc != 0, rc, -EIO, "Failed to add data to JSON object",
			 verbose, out);
	rc = json_object_object_add_ex(req_addl_info_obj, "requestedKey",
				       json_object_new_string(key_uuid), 0);
	JSON_CHECK_ERROR(rc != 0, rc, -EIO, "Failed to add data to JSON object",
			 verbose, out);
	rc = json_object_object_add_ex(req_addl_info_obj, "timestamp",
				       req_timestamp_obj, 0);
	JSON_CHECK_ERROR(rc != 0, rc, -EIO, "Failed to add data to JSON object",
			 verbose, out);
	req_timestamp_obj = NULL;

	req_originator_obj = json_object_new_object();
	JSON_CHECK_ERROR(req_originator_obj == NULL, rc, -ENOMEM,
			 "Failed to generate JSON object", verbose, out);

	rc = json_object_object_add_ex(req_originator_obj, "session",
				       req_sess_jwk_obj, 0);
	JSON_CHECK_ERROR(rc != 0, rc, -EIO, "Failed to add data to JSON object",
			 verbose, out);
	req_sess_jwk_obj = NULL;
	rc = json_object_object_add_ex(req_originator_obj, "partyInfo",
				       req_party_info_obj, 0);
	JSON_CHECK_ERROR(rc != 0, rc, -EIO, "Failed to add data to JSON object",
			 verbose, out);
	req_party_info_obj = NULL;

	request_obj = json_object_new_object();
	JSON_CHECK_ERROR(request_obj == NULL, rc, -ENOMEM,
			 "Failed to generate JSON object", verbose, out);

	rc = json_object_object_add_ex(request_obj, "originator",
				       req_originator_obj, 0);
	JSON_CHECK_ERROR(rc != 0, rc, -EIO, "Failed to add data to JSON object",
			 verbose, out);
	req_originator_obj = NULL;
	rc = json_object_object_add_ex(request_obj, "additionalInfo",
				       req_addl_info_obj, 0);
	JSON_CHECK_ERROR(rc != 0, rc, -EIO, "Failed to add data to JSON object",
			 verbose, out);
	req_addl_info_obj = NULL;

	rc = _ekmf_build_signature(identity_key, identity_key_length,
				   request_obj, &req_signature_obj,
				   sign_rsa_digest_nid, use_rsa_pss,
				   signature_kid, ext_lib, verbose);
	if (rc != 0) {
		pr_verbose(verbose, "Failed to build the signature");
		goto out;
	}

	rc = json_object_object_add_ex(request_obj, "signature",
				       req_signature_obj, 0);
	JSON_CHECK_ERROR(rc != 0, rc, -EIO, "Failed to add data to JSON object",
			 verbose, out);
	req_signature_obj = NULL;

	escaped_uuid = curl_easy_escape(curl, key_uuid, 0);
	if (escaped_uuid == NULL) {
		pr_verbose(verbose, "Failed to url-escape the key uuid");
		rc = -EIO;
		goto out;
	}

	if (asprintf(&uri, EKMF_URI_KEYS_EXPORT, escaped_uuid) < 0) {
		pr_verbose(verbose, "asprintf failed");
		rc = -ENOMEM;
		goto out;
	}

	rc = _ekmf_perform_request(config, uri, "POST", request_obj, NULL,
				   login_token, &response_obj, NULL,
				   &status_code, error_msg, curl, verbose);
	if (rc != 0) {
		pr_verbose(verbose, "Failed perform the REST call");
		if (rc > 0)
			rc = -EIO;
		goto out;
	}

	switch (status_code) {
	case 200:
		break;
	case 400:
		pr_verbose(verbose, "Bad request");
		rc = -EBADMSG;
		goto out;
	case 401:
		pr_verbose(verbose, "Not authorized");
		rc = -EACCES;
		goto out;
	case 403:
		pr_verbose(verbose, "Insufficient permissions");
		rc = -EPERM;
		goto out;
	case 404:
		pr_verbose(verbose, "Not found");
		rc = -ENOENT;
		goto out;
	default:
		pr_verbose(verbose, "REST Call failed with HTTP status code: "
			   "%ld", status_code);
		rc = -EIO;
		goto out;
	}

	JSON_CHECK_OBJ(response_obj, json_type_object, rc, -EBADMSG,
		       "No or invalid response", verbose, out);

	rc = _ekmf_verify_signature(response_obj, server_pubkey, verbose);
	if (rc != 0)
		goto out;

	json_object_object_get_ex(response_obj, "originator",
				  &resp_originator_obj);
	JSON_CHECK_OBJ(resp_originator_obj, json_type_object, rc, -EBADMSG,
		       "Failed to get the response originator", verbose, out);

	json_object_object_get_ex(resp_originator_obj, "session",
				  &resp_sess_jwk_obj);
	JSON_CHECK_OBJ(resp_sess_jwk_obj, json_type_object, rc, -EBADMSG,
		       "Failed to get the response session key", verbose, out);

	rc = json_object_get_base64url(resp_originator_obj, "partyInfo",
				       NULL, &resp_party_info_length);
	if (rc != 0) {
		pr_verbose(verbose, "Failed to get the response partyInfo");
		goto out;
	}

	resp_party_info = malloc(resp_party_info_length);
	if (resp_party_info == NULL) {
		rc = -ENOMEM;
		goto out;
	}

	rc = json_object_get_base64url(resp_originator_obj, "partyInfo",
				       resp_party_info,
				       &resp_party_info_length);
	if (rc != 0) {
		pr_verbose(verbose, "Failed to get the response partyInfo");
		goto out;
	}

	json_object_object_get_ex(response_obj, "additionalInfo",
				  &resp_addl_info_obj);
	JSON_CHECK_OBJ(resp_addl_info_obj, json_type_object, rc, -EBADMSG,
		       "Failed to get the response addl.info", verbose, out);

	json_object_object_get_ex(resp_addl_info_obj, "exportedKey",
				  &resp_exp_jwk_obj);
	JSON_CHECK_OBJ(resp_exp_jwk_obj, json_type_object, rc, -EBADMSG,
		       "Failed to get the response exported key", verbose, out);

	rc = _ekmf_import_key(req_sess_ec_key, req_sess_ec_key_length,
			      req_party_info, req_party_info_length,
			      resp_party_info, resp_party_info_length,
			      resp_sess_jwk_obj, resp_exp_jwk_obj,
			      key_blob, key_blob_length, ext_lib, verbose);
	if (rc != 0) {
		pr_verbose(verbose, "Failed to import the retrieved key");
		goto out;
	}

out:
	_ekmf_release_curl_handle(curl_handle, curl);

	if (req_sess_jwk_obj != NULL)
		json_object_put(req_sess_jwk_obj);
	if (req_timestamp_obj != NULL)
		json_object_put(req_timestamp_obj);
	if (req_addl_info_obj != NULL)
		json_object_put(req_addl_info_obj);
	if (req_party_info_obj != NULL)
		json_object_put(req_party_info_obj);
	if (req_originator_obj != NULL)
		json_object_put(req_originator_obj);
	if (req_signature_obj != NULL)
		json_object_put(req_signature_obj);
	if (request_obj != NULL)
		json_object_put(request_obj);
	if (response_obj != NULL)
		json_object_put(response_obj);
	if (uri != NULL)
		free(uri);
	if (login_token != NULL)
		free(login_token);
	if (server_pubkey != NULL)
		EVP_PKEY_free(server_pubkey);
	if (resp_party_info != NULL)
		free(resp_party_info);
	if (escaped_uuid != NULL)
		curl_free(escaped_uuid);
	SK_OPENSSL_term();

	return rc;
}

/**
 * Callback function for the _ekmf_list_request function. This callback
 * is called for each result element. The curl handle can be used to perform
 * further requests within the callback. However, the curl handle must not be
 * closed/destroyed!
 */
typedef int (*ekmf_element_cb_t)(CURL *curl, json_object *element,
				 void *private, bool verbose);

/**
 * Performs a list request (GET) on a base list_uri and iterates over
 * multiple pages. The response of a list request is expected to be a
 * JSON array of elements. For each element, the element callback is called
 * with the element.
 * The list_uri must contain anything required to list the desired objects,
 * except the page and perPage UTL parameters. Those are added by this function.
 */
static int _ekmf_list_request(const struct ekmf_config *config,
			      const char *list_uri, CURL *curl,
			      ekmf_element_cb_t element_cb, void *private,
			      const char *login_token, char **error_msg,
			      bool verbose)
{
	json_object *response_obj = NULL;
	json_object *element_obj;
	int num, i, rc = 0;
	unsigned int page;
	char *uri = NULL;
	long status_code;
	bool has_query;

	if (config == NULL || list_uri == NULL || element_cb == NULL ||
	    curl == NULL)
		return -EINVAL;

	has_query = strchr(list_uri, '?') != NULL;

	for (page = 1; ; page++) {
		if (asprintf(&uri, "%s%sperPage=%u&page=%u", list_uri,
			     has_query ? "&" : "?", LIST_ELEMENTS_PER_PAGE,
			     page) < 0) {
			pr_verbose(verbose, "asprintf failed");
			rc = -ENOMEM;
			goto out;
		}

		rc = _ekmf_perform_request(config, uri, "GET", NULL, NULL,
					   login_token, &response_obj, NULL,
					   &status_code, error_msg, curl,
					   verbose);

		free(uri);
		uri = NULL;

		if (rc != 0) {
			pr_verbose(verbose, "Failed perform the REST call");
			if (rc > 0)
				rc = -EIO;
			goto out;
		}

		switch (status_code) {
		case 200:
			break;
		case 400:
			pr_verbose(verbose, "Bad request");
			rc = -EBADMSG;
			goto out;
		case 401:
			pr_verbose(verbose, "Not authorized");
			rc = -EACCES;
			goto out;
		case 403:
			pr_verbose(verbose, "Insufficient permissions");
			rc = -EPERM;
			goto out;
		default:
			pr_verbose(verbose, "REST Call failed with HTTP "
				   "status code: %ld", status_code);
			rc = -EIO;
			goto out;
		}

		JSON_CHECK_OBJ(response_obj, json_type_array, rc, -EIO,
			       "No or invalid response content", verbose, out);

		num = json_object_array_length(response_obj);
		if (num == 0)
			break;

		for (i = 0; i < num; i++) {
			element_obj = json_object_array_get_idx(response_obj,
								i);
			if (element_obj == NULL) {
				pr_verbose(verbose, "Failed to get array "
					   "element for index %d", i);
				rc = -EBADMSG;
				goto out;
			}

			rc = element_cb(curl, element_obj, private, verbose);
			if (rc != 0) {
				pr_verbose(verbose, "Element-callback failed "
					   "for index %d", i);
				goto out;
			}
		}

		if (response_obj != NULL)
			json_object_put(response_obj);
		response_obj = NULL;

		if (num < LIST_ELEMENTS_PER_PAGE)
			break;
	}

out:
	if (uri != NULL)
		free(uri);
	if (response_obj != NULL)
		json_object_put(response_obj);

	return rc;
}

struct ekmf_template_cb_data_t {
	ekmf_template_cb_t template_cb;
	void *cb_private;
};

/**
 * Callback for template list function. Builds the template info structure
 * and calls the application callback.
 */
static int _ekmf_template_cb(CURL *curl, json_object *element,
			     void *private, bool verbose)
{
	struct ekmf_template_cb_data_t *cb_data = private;
	struct ekmf_template_info template = { 0 };
	int rc;

	if (cb_data->template_cb == NULL) {
		pr_verbose(verbose, "No template callback function");
		return -EINVAL;
	}

	rc = json_build_template_info(element, &template, false);
	if (rc != 0) {
		pr_verbose(verbose, "Failed to build template info");
		goto out;
	}

	rc = cb_data->template_cb(curl, &template, cb_data->cb_private);
	if (rc != 0) {
		pr_verbose(verbose, "Template callback rc: %d", rc);
		goto out;
	}

out:
	free_tag_def_list(&template.label_tags, false);

	return rc;
}

/**
 * List available key templates. Only templates in state ACTIVE, with key
 * algorithm AES and keystore type PERVASIVE_ENCRYPTION are listed. The
 * templates are ordered by name in ascending order.
 *
 * To perform a single request, set curl_handle to NULL. This will cause the
 * function to initialize a new CURL handle, use it, and destroy it.
 * If you plan to perform multiple requests to the same host, supply the address
 * of a CURL pointer that is initially NULL. This function will then initialize
 * a new CURL handle on the first call. On subsequent calls, pass in the address
 * of the same CURL pointer so that the CURL handle is reused. After the last
 * request, the CURL handle must be destroyed by calling ekmf_curl_destroy).
 *
 * @param config            the configuration structure
 * @param curl_handle       address of a CURL handle used for reusing the same
 *                          CURL handle with multiple requests.
 * @param template_cb       a callback function that is called for each template
 *                          found
 * @param private           a pointer that is passed as-is to the callback
 * @param name_pattern      a pattern to filter by name, or NULL to list all.
 * @param state             the state of the templates to list. If NULL then
 *                          templates in state 'ACTIVE' are listed
 * @param error_msg         on return: If not NULL, then a textual error message
 *                          is returned in case of a failing request. The caller
 *                          must free the error string when it is not NULL.
 * @param verbose           if true, verbose messages are printed
 *
 * @returns zero for success, a negative errno in case of an error.
 *          -EACCES is returned, if no or no valid login token is available.
 *          -EPERM is returned if the login token does not have permission to
 *          list the templates
 */
int ekmf_list_templates(const struct ekmf_config *config, CURL **curl_handle,
			ekmf_template_cb_t template_cb, void *private,
			const char *name_pattern, const char *state,
			char **error_msg, bool verbose)
{
	struct ekmf_template_cb_data_t cb_data;
	char *escaped_name_pattern = NULL;
	char *escaped_state = NULL;
	char *login_token = NULL;
	bool token_valid = false;
	CURL *curl = NULL;
	char *uri = NULL;
	int rc;

	if (config == NULL || template_cb == NULL)
		return -EINVAL;

	rc = ekmf_check_login_token(config, &token_valid, &login_token,
				    verbose);
	if (rc != 0 || !token_valid) {
		pr_verbose(verbose, "No valid login token available");
		rc = -EACCES;
		goto out;
	}

	rc = _ekmf_get_curl_handle(curl_handle, &curl);
	if (rc != 0) {
		pr_verbose(verbose, "Failed to get CURL handle");
		rc = -EIO;
		goto out;
	}

	cb_data.template_cb = template_cb;
	cb_data.cb_private = private;

	escaped_name_pattern = curl_easy_escape(curl, name_pattern != NULL ?
							name_pattern : "*", 0);
	if (escaped_name_pattern == NULL) {
		pr_verbose(verbose, "Failed to url-escape the name pattern");
		rc = -EIO;
		goto out;
	}

	escaped_state = curl_easy_escape(curl, state != NULL ? state :
						TEMPLATE_STATE_ACTIVE, 0);
	if (escaped_state == NULL) {
		pr_verbose(verbose, "Failed to url-escape the state");
		rc = -EIO;
		goto out;
	}

	if (asprintf(&uri, EKMF_URI_TEMPLATE_LIST, escaped_state,
		     ORDER_BY_NAME_ASC, escaped_name_pattern) < 0) {
		pr_verbose(verbose, "asprintf failed");
		rc = -ENOMEM;
		goto out;
	}

	rc = _ekmf_list_request(config, uri, curl, _ekmf_template_cb,
				&cb_data, login_token, error_msg, verbose);
	if (rc != 0) {
		pr_verbose(verbose, "Failed to perform the list request");
		if (rc > 0)
			rc = -EIO;
		goto out;
	}

out:
	_ekmf_release_curl_handle(curl_handle, curl);

	if (login_token != NULL)
		free(login_token);
	if (uri != NULL)
		free(uri);
	if (escaped_name_pattern != NULL)
		curl_free(escaped_name_pattern);
	if (escaped_state != NULL)
		curl_free(escaped_state);

	return rc;
}

/**
 * Get a template by its UUID.
 *
 * To perform a single request, set curl_handle to NULL. This will cause the
 * function to initialize a new CURL handle, use it, and destroy it.
 * If you plan to perform multiple requests to the same host, supply the address
 * of a CURL pointer that is initially NULL. This function will then initialize
 * a new CURL handle on the first call. On subsequent calls, pass in the address
 * of the same CURL pointer so that the CURL handle is reused. After the last
 * request, the CURL handle must be destroyed by calling ekmf_curl_destroy).
 *
 * @param config            the configuration structure
 * @param curl_handle       address of a CURL handle used for reusing the same
 *                          CURL handle with multiple requests.
 * @param template_uuid     the UUID of the template to get
 * @param template          an address of a template info pointer. On return
 *                          the pointer is updated to point to a newly allocated
 *                          template info struct. It must be freed by the caller
 *                          using ekmf_free_template_info when no longer needed.
 * @param error_msg         on return: If not NULL, then a textual error message
 *                          is returned in case of a failing request. The caller
 *                          must free the error string when it is not NULL.
 * @param verbose           if true, verbose messages are printed
 *
 * @returns zero for success, a negative errno in case of an error.
 *          -EACCES is returned, if no or no valid login token is available.
 *          -EPERM is returned if the login token does not have permission to
 *          get the template
 */
int ekmf_get_template(const struct ekmf_config *config, CURL **curl_handle,
		      const char *template_uuid,
		      struct ekmf_template_info **template, char **error_msg,
		      bool verbose)
{
	json_object *response_obj = NULL;
	char *escaped_uuid = NULL;
	char *login_token = NULL;
	bool token_valid = false;
	CURL *curl = NULL;
	char *uri = NULL;
	long status_code;
	int rc;

	if (config == NULL || template_uuid == NULL || template == NULL)
		return -EINVAL;

	*template = NULL;

	rc = ekmf_check_login_token(config, &token_valid, &login_token,
				    verbose);
	if (rc != 0 || !token_valid) {
		pr_verbose(verbose, "No valid login token available");
		rc = -EACCES;
		goto out;
	}

	rc = _ekmf_get_curl_handle(curl_handle, &curl);
	if (rc != 0) {
		pr_verbose(verbose, "Failed to get CURL handle");
		rc = -EIO;
		goto out;
	}

	escaped_uuid = curl_easy_escape(curl, template_uuid, 0);
	if (escaped_uuid == NULL) {
		pr_verbose(verbose, "Failed to url-escape the template uuid");
		rc = -EIO;
		goto out;
	}

	if (asprintf(&uri, EKMF_URI_TEMPLATE_GET, escaped_uuid) < 0) {
		pr_verbose(verbose, "asprintf failed");
		rc = -ENOMEM;
		goto out;
	}

	rc = _ekmf_perform_request(config, uri, "GET", NULL, NULL,
				   login_token, &response_obj, NULL,
				   &status_code, error_msg, curl, verbose);
	if (rc != 0) {
		pr_verbose(verbose, "Failed perform the REST call");
		if (rc > 0)
			rc = -EIO;
		goto out;
	}

	switch (status_code) {
	case 200:
		break;
	case 400:
		pr_verbose(verbose, "Bad request");
		rc = -EBADMSG;
		goto out;
	case 401:
		pr_verbose(verbose, "Not authorized");
		rc = -EACCES;
		goto out;
	case 403:
		pr_verbose(verbose, "Insufficient permissions");
		rc = -EPERM;
		goto out;
	case 404:
		pr_verbose(verbose, "Not found");
		rc = -ENOENT;
		goto out;
	default:
		pr_verbose(verbose, "REST Call failed with HTTP status code: "
			   "%ld", status_code);
		rc = -EIO;
		goto out;
	}

	JSON_CHECK_OBJ(response_obj, json_type_object, rc, -EBADMSG,
		       "No or invalid response", verbose, out);

	*template = calloc(1, sizeof(struct ekmf_template_info));
	if (*template == NULL) {
		pr_verbose(verbose, "calloc failed");
		rc = -ENOMEM;
		goto out;
	}

	rc = json_build_template_info(response_obj, *template, true);
	if (rc != 0) {
		pr_verbose(verbose, "Failed to build template info");
		goto out;
	}

out:
	_ekmf_release_curl_handle(curl_handle, curl);

	if (response_obj != NULL)
		json_object_put(response_obj);
	if (login_token != NULL)
		free(login_token);
	if (uri != NULL)
		free(uri);
	if (escaped_uuid != NULL)
		curl_free(escaped_uuid);
	if (rc != 0 && *template != NULL) {
		free_template_info(*template);
		free(*template);
		*template = NULL;
	}

	return rc;
}

/**
 * Get the last used sequence number of a template by its UUID.
 *
 * To perform a single request, set curl_handle to NULL. This will cause the
 * function to initialize a new CURL handle, use it, and destroy it.
 * If you plan to perform multiple requests to the same host, supply the address
 * of a CURL pointer that is initially NULL. This function will then initialize
 * a new CURL handle on the first call. On subsequent calls, pass in the address
 * of the same CURL pointer so that the CURL handle is reused. After the last
 * request, the CURL handle must be destroyed by calling ekmf_curl_destroy).
 *
 * @param config            the configuration structure
 * @param curl_handle       address of a CURL handle used for reusing the same
 *                          CURL handle with multiple requests.
 * @param template_uuid     the UUID of the template to get
 * @param seqNumber         On return: the last used sequence number of this
 *                          template.
 * @param error_msg         on return: If not NULL, then a textual error message
 *                          is returned in case of a failing request. The caller
 *                          must free the error string when it is not NULL.
 * @param verbose           if true, verbose messages are printed
 *
 * @returns zero for success, a negative errno in case of an error.
 *          -EACCES is returned, if no or no valid login token is available.
 *          -EPERM is returned if the login token does not have permission to
 *          get the template
 */
int ekmf_get_last_seq_no(const struct ekmf_config *config, CURL **curl_handle,
			 const char *template_uuid, unsigned int *seqNumber,
			 char **error_msg, bool verbose)
{
	json_object *response_obj = NULL, *field = NULL;
	char *escaped_uuid = NULL;
	char *login_token = NULL;
	bool token_valid = false;
	CURL *curl = NULL;
	char *uri = NULL;
	long status_code;
	int rc;

	if (config == NULL || template_uuid == NULL || seqNumber == NULL)
		return -EINVAL;

	*seqNumber = 0;

	rc = ekmf_check_login_token(config, &token_valid, &login_token,
				    verbose);
	if (rc != 0 || !token_valid) {
		pr_verbose(verbose, "No valid login token available");
		rc = -EACCES;
		goto out;
	}

	rc = _ekmf_get_curl_handle(curl_handle, &curl);
	if (rc != 0) {
		pr_verbose(verbose, "Failed to get CURL handle");
		rc = -EIO;
		goto out;
	}

	escaped_uuid = curl_easy_escape(curl, template_uuid, 0);
	if (escaped_uuid == NULL) {
		pr_verbose(verbose, "Failed to url-escape the template uuid");
		rc = -EIO;
		goto out;
	}

	if (asprintf(&uri, EKMF_URI_TEMPLATE_SEQNO, escaped_uuid) < 0) {
		pr_verbose(verbose, "asprintf failed");
		rc = -ENOMEM;
		goto out;
	}

	rc = _ekmf_perform_request(config, uri, "GET", NULL, NULL,
				   login_token, &response_obj, NULL,
				   &status_code, error_msg, curl, verbose);
	if (rc != 0) {
		pr_verbose(verbose, "Failed perform the REST call");
		if (rc > 0)
			rc = -EIO;
		goto out;
	}

	switch (status_code) {
	case 200:
		break;
	case 400:
		pr_verbose(verbose, "Bad request");
		rc = -EBADMSG;
		goto out;
	case 401:
		pr_verbose(verbose, "Not authorized");
		rc = -EACCES;
		goto out;
	case 403:
		pr_verbose(verbose, "Insufficient permissions");
		rc = -EPERM;
		goto out;
	case 404:
		pr_verbose(verbose, "Not found");
		rc = -ENOENT;
		goto out;
	default:
		pr_verbose(verbose, "REST Call failed with HTTP status code: "
			   "%ld", status_code);
		rc = -EIO;
		goto out;
	}

	JSON_CHECK_OBJ(response_obj, json_type_object, rc, -EBADMSG,
		       "No or invalid response", verbose, out);

	json_object_object_get_ex(response_obj, "lastSequenceNumber", &field);
	JSON_CHECK_OBJ(field, json_type_int, rc, -EBADMSG,
		       "Invalid response", verbose, out);

	*seqNumber = json_object_get_int(field);

out:
	_ekmf_release_curl_handle(curl_handle, curl);

	if (response_obj != NULL)
		json_object_put(response_obj);
	if (login_token != NULL)
		free(login_token);
	if (uri != NULL)
		free(uri);
	if (escaped_uuid != NULL)
		curl_free(escaped_uuid);

	return rc;
}

/**
 * Clones a template info structure by making a deep copy of all strings and
 * arrays.
 * The copied template info must be freed using ekmf_free_template_info() by
 * the caller.
 *
 * @param src               the source template info structure
 * @param dest              the destination template info structure
 *
 * @returns zero for success, a negative errno in case of an error
 */
int ekmf_clone_template_info(const struct ekmf_template_info *src,
			     struct ekmf_template_info **dest)
{
	if (src == NULL || dest == NULL)
		return -EINVAL;

	*dest = calloc(1, sizeof(struct ekmf_template_info));
	if (*dest == NULL)
		return -ENOMEM;

	return clone_template_info(src, *dest);
}

/**
 * Free a template info structure.
 *
 * @param template          the template to free
 */
void ekmf_free_template_info(struct ekmf_template_info *template)
{
	free_template_info(template);

	free(template);
}

/**
 * Gets the custom tags of a key by key-uuid. The custom tags are returned as
 * JSON array. The returned JSON array must be freed by the caller using
 * json_object_put().
 */
static int _ekmf_get_custom_tags(const struct ekmf_config *config,
				 const char *key_uuid, CURL *curl,
				 json_object **custom_tags,
				 const char *login_token, char **error_msg,
				 bool verbose)
{
	json_object *response_obj = NULL;
	char *escaped_uuid = NULL;
	char *uri = NULL;
	long status_code;
	int rc;

	if (config == NULL || key_uuid == NULL || custom_tags == NULL ||
	    curl == NULL)
		return -EINVAL;

	escaped_uuid = curl_easy_escape(curl, key_uuid, 0);
	if (escaped_uuid == NULL) {
		pr_verbose(verbose, "Failed to url-escape the key uuid");
		rc = -EIO;
		goto out;
	}

	if (asprintf(&uri, EKMF_URI_KEYS_TAGS, escaped_uuid) < 0) {
		pr_verbose(verbose, "asprintf failed");
		rc = -ENOMEM;
		goto out;
	}

	rc = _ekmf_perform_request(config, uri, "GET", NULL, NULL, login_token,
				   &response_obj, NULL, &status_code, error_msg,
				   curl, verbose);
	if (rc != 0) {
		pr_verbose(verbose, "Failed perform the REST call");
		if (rc > 0)
			rc = -EIO;
		goto out;
	}

	switch (status_code) {
	case 200:
		break;
	case 400:
		pr_verbose(verbose, "Bad request");
		rc = -EBADMSG;
		goto out;
	case 401:
		pr_verbose(verbose, "Not authorized");
		rc = -EACCES;
		goto out;
	case 403:
		pr_verbose(verbose, "Insufficient permissions");
		rc = -EPERM;
		goto out;
	default:
		pr_verbose(verbose, "REST Call failed with HTTP status code: "
			   "%ld", status_code);
		rc = -EIO;
		goto out;
	}

	JSON_CHECK_OBJ(response_obj, json_type_array, rc, -EIO,
		       "No or invalid response content", verbose, out);

	*custom_tags = response_obj;
	rc = 0;

out:
	if (uri != NULL)
		free(uri);
	if (escaped_uuid != NULL)
		curl_free(escaped_uuid);
	if (rc != 0 && response_obj != NULL)
		json_object_put(response_obj);

	return rc;
}

/**
 * Gets the export control infos of a key by key-uuid. The export control info
 * is returned as JSON object. The returned JSON object must be freed by the
 * caller using  json_object_put().
 */
static int _ekmf_get_export_control(const struct ekmf_config *config,
				   const char *key_uuid, CURL *curl,
				   json_object **export_control,
				   const char *login_token, char **error_msg,
				   bool verbose)
{
	json_object *response_obj = NULL;
	char *escaped_uuid = NULL;
	char *uri = NULL;
	long status_code;
	int rc;

	if (config == NULL || key_uuid == NULL || export_control == NULL ||
	    curl == NULL)
		return -EINVAL;

	escaped_uuid = curl_easy_escape(curl, key_uuid, 0);
	if (escaped_uuid == NULL) {
		pr_verbose(verbose, "Failed to url-escape the key uuid");
		rc = -EIO;
		goto out;
	}

	if (asprintf(&uri, EKMF_URI_KEYS_EXPORT_CONTROL, escaped_uuid) < 0) {
		pr_verbose(verbose, "asprintf failed");
		rc = -ENOMEM;
		goto out;
	}

	rc = _ekmf_perform_request(config, uri, "GET", NULL, NULL, login_token,
				   &response_obj, NULL, &status_code, error_msg,
				   curl, verbose);
	if (rc != 0) {
		pr_verbose(verbose, "Failed perform the REST call");
		if (rc > 0)
			rc = -EIO;
		goto out;
	}

	switch (status_code) {
	case 200:
		break;
	case 400:
		pr_verbose(verbose, "Bad request");
		rc = -EBADMSG;
		goto out;
	case 401:
		pr_verbose(verbose, "Not authorized");
		rc = -EACCES;
		goto out;
	case 403:
		pr_verbose(verbose, "Insufficient permissions");
		rc = -EPERM;
		goto out;
	default:
		pr_verbose(verbose, "REST Call failed with HTTP status code: "
			   "%ld", status_code);
		rc = -EIO;
		goto out;
	}

	JSON_CHECK_OBJ(response_obj, json_type_object, rc, -EIO,
		       "No or invalid response content", verbose, out);

	*export_control = response_obj;
	rc = 0;

out:
	if (uri != NULL)
		free(uri);
	if (escaped_uuid != NULL)
		curl_free(escaped_uuid);
	if (rc != 0 && response_obj != NULL)
		json_object_put(response_obj);

	return rc;
}

/**
 * Get the custom tags for a key and build the key info structure
 */
static int _ekmf_build_key_info(const struct ekmf_config *config, CURL *curl,
				const char *login_token, json_object *obj,
				struct ekmf_key_info *key, bool copy,
				char **error_msg, bool verbose)
{
	json_object *export_control = NULL;
	json_object *custom_tags = NULL;
	int rc;

	rc = _ekmf_get_custom_tags(config, json_get_string(obj, "keyId"),
				   curl, &custom_tags, login_token, error_msg,
				   verbose);
	if (rc != 0) {
		pr_verbose(verbose, "Failed to get the custom tags for key %s",
			   json_get_string(obj, "keyId"));
		goto out;
	}

	rc = _ekmf_get_export_control(config, json_get_string(obj, "keyId"),
				      curl, &export_control, login_token,
				      error_msg, verbose);
	if (rc != 0) {
		pr_verbose(verbose, "Failed to get the custom tags for key %s",
			   json_get_string(obj, "keyId"));
		goto out;
	}

	rc = json_build_key_info(obj, custom_tags, export_control, key, copy);
	if (rc != 0) {
		pr_verbose(verbose, "Failed to build key info");
		goto out;
	}

out:
	/*
	 * Add custom tags and export control JSON objects to the key object,
	 * so that these objects are also owned by the key object, and thus are
	 * freed together with it, when the caller puts/frees the key object.
	 */
	if (custom_tags != NULL)
		json_object_object_add_ex(obj, "_custom_tags_", custom_tags, 0);
	if (export_control != NULL)
		json_object_object_add_ex(obj, "_export_control_",
					  export_control, 0);

	return rc;
}


struct ekmf_key_cb_data_t {
	const struct ekmf_config *config;
	const char *login_token;
	char **error_msg;
	ekmf_key_cb_t key_cb;
	void *cb_private;
};

/**
 * Callback for key list function. Builds the key info structure
 * and calls the application callback.
 */
static int _ekmf_key_cb(CURL *curl, json_object *element,
			void *private, bool verbose)
{
	struct ekmf_key_cb_data_t *cb_data = private;
	struct ekmf_key_info key = { 0 };
	int rc;

	if (cb_data->key_cb == NULL) {
		pr_verbose(verbose, "No key callback function");
		return -EINVAL;
	}

	rc = _ekmf_build_key_info(cb_data->config, curl, cb_data->login_token,
				  element, &key, false, cb_data->error_msg,
				  verbose);
	if (rc != 0) {
		pr_verbose(verbose, "Failed to build key info");
		goto out;
	}

	rc = cb_data->key_cb(curl, &key, cb_data->cb_private);
	if (rc != 0) {
		pr_verbose(verbose, "Key callback rc: %d", rc);
		goto out;
	}

out:
	free_tag_list(&key.label_tags, false);
	free_tag_list(&key.custom_tags, false);
	free_export_control(&key.export_control, false);

	return rc;
}

/**
 * Builds the state URL parameter(s) from a comma separated list of states
 *
 * @param curl              the curl handle
 * @param states            a comma separaed list of states
 *
 * @returns an allocated URL parameter value, or NULL in case of an error
 */
static char *_ekmf_build_state_filter(CURL *curl, const char *states)
{
	char *list, *tok, *ret = NULL, *tmp;
	char *escaped_state;

	if (states == NULL)
		goto error;

	list = strdup(states);
	if (list == NULL)
		goto error;

	tok = strtok(list, ",");
	while (tok != NULL) {
		escaped_state = curl_easy_escape(curl, tok, 0);
		if (escaped_state == NULL)
			goto error;

		if (asprintf(&tmp, "%s%s%s", ret != NULL ? ret : "",
			     ret == NULL ? "" : EKMF_URI_KEYS_LIST_STATE,
			     escaped_state) < 0)
			tmp = NULL;
		curl_free(escaped_state);
		if (tmp == NULL)
			goto error;
		if (ret != NULL)
			free(ret);
		ret = tmp;

		tok = strtok(NULL, ",");
	}

	free(list);
	return ret;

error:
	if (ret != NULL)
		free(ret);
	return NULL;
}

/**
 * List available keys. The keys are ordered by name in ascending order.
 *
 * To perform a single request, set curl_handle to NULL. This will cause the
 * function to initialize a new CURL handle, use it, and destroy it.
 * If you plan to perform multiple requests to the same host, supply the address
 * of a CURL pointer that is initially NULL. This function will then initialize
 * a new CURL handle on the first call. On subsequent calls, pass in the address
 * of the same CURL pointer so that the CURL handle is reused. After the last
 * request, the CURL handle must be destroyed by calling ekmf_curl_destroy).
 *
 * @param config            the configuration structure
 * @param curl_handle       address of a CURL handle used for reusing the same
 *                          CURL handle with multiple requests.
 * @param key_cb            a callback function that is called for each key
 *                          found
 * @param private           a pointer that is passed as-is to the callback
 * @param name_pattern      a pattern to filter by name, or NULL to list all.
 * @param states            the states of the keys to list, or NULL to list keys
 *                          in ACTIVE state only. Multiple states can be
 *                          specified separated by comma.
 * @param tags              a list of custom tags to use as filter, or NULL
 * @param error_msg         on return: If not NULL, then a textual error message
 *                          is returned in case of a failing request. The caller
 *                          must free the error string when it is not NULL.
 * @param verbose           if true, verbose messages are printed
 *
 * @returns zero for success, a negative errno in case of an error.
 *          -EACCES is returned, if no or no valid login token is available.
 *          -EPERM is returned if the login token does not have permission to
 *          list the keys
 */
int ekmf_list_keys(const struct ekmf_config *config, CURL **curl_handle,
		   ekmf_key_cb_t key_cb, void *private,
		   const char *name_pattern, const char *states,
		   const struct ekmf_tag_list *tags,
		   char **error_msg, bool verbose)
{
	struct ekmf_key_cb_data_t cb_data;
	char *escaped_name_pattern = NULL;
	json_object *tags_obj = NULL;
	char *state_filter = NULL;
	char *escaped_tags = NULL;
	char *login_token = NULL;
	bool token_valid = false;
	CURL *curl = NULL;
	char *uri = NULL;
	size_t i;
	int rc;

	if (config == NULL || key_cb == NULL)
		return -EINVAL;

	rc = ekmf_check_login_token(config, &token_valid, &login_token,
				    verbose);
	if (rc != 0 || !token_valid) {
		pr_verbose(verbose, "No valid login token available");
		rc = -EACCES;
		goto out;
	}

	rc = _ekmf_get_curl_handle(curl_handle, &curl);
	if (rc != 0) {
		pr_verbose(verbose, "Failed to get CURL handle");
		rc = -EIO;
		goto out;
	}

	cb_data.config = config;
	cb_data.login_token = login_token;
	cb_data.error_msg = error_msg;
	cb_data.key_cb = key_cb;
	cb_data.cb_private = private;

	escaped_name_pattern = curl_easy_escape(curl, name_pattern != NULL ?
							name_pattern : "*", 0);
	if (escaped_name_pattern == NULL) {
		pr_verbose(verbose, "Failed to url-escape the name pattern");
		rc = -EIO;
		goto out;
	}

	state_filter = _ekmf_build_state_filter(curl, states != NULL ? states :
						KEY_STATE_ACTIVE);
	if (state_filter == NULL) {
		pr_verbose(verbose, "Failed to build the state filter");
		rc = -EIO;
		goto out;
	}

	tags_obj = json_object_new_object();
	JSON_CHECK_ERROR(tags_obj == NULL, rc, -ENOMEM,
			 "Failed to generate JSON object", verbose, out);
	for (i = 0; tags != NULL && i < tags->num_tags; i++) {
		if (tags->tags[i].name == NULL || tags->tags[i].value == NULL) {
			rc = -EINVAL;
			goto out;
		}

		rc = json_object_object_add_ex(tags_obj, tags->tags[i].name,
					       json_object_new_string(
							tags->tags[i].value),
					       0);
		JSON_CHECK_ERROR(rc != 0, rc, -EIO, "Failed to add data to "
				 "JSON object", verbose, out);
	}

	escaped_tags = curl_easy_escape(curl, json_object_to_json_string_ext(
				tags_obj, JSON_C_TO_STRING_PLAIN |
					  JSON_C_TO_STRING_NOSLASHESCAPE), 0);
	if (escaped_tags == NULL) {
		pr_verbose(verbose, "Failed to url-escape the tags");
		rc = -EIO;
		goto out;
	}

	if (asprintf(&uri, EKMF_URI_KEYS_LIST, state_filter,
		     ORDER_BY_LABEL_ASC, escaped_name_pattern,
		     escaped_tags) < 0) {
		pr_verbose(verbose, "asprintf failed");
		rc = -ENOMEM;
		goto out;
	}

	rc = _ekmf_list_request(config, uri, curl, _ekmf_key_cb,
				&cb_data, login_token, error_msg, verbose);
	if (rc != 0) {
		pr_verbose(verbose, "Failed to perform the list request");
		if (rc > 0)
			rc = -EIO;
		goto out;
	}

out:
	_ekmf_release_curl_handle(curl_handle, curl);

	if (login_token != NULL)
		free(login_token);
	if (uri != NULL)
		free(uri);
	if (state_filter != NULL)
		free(state_filter);
	if (escaped_name_pattern != NULL)
		curl_free(escaped_name_pattern);
	if (escaped_tags != NULL)
		curl_free(escaped_tags);
	if (tags_obj != NULL)
		json_object_put(tags_obj);

	return rc;
}

/**
 * Get information about a key by its UUID.
 *
 * To perform a single request, set curl_handle to NULL. This will cause the
 * function to initialize a new CURL handle, use it, and destroy it.
 * If you plan to perform multiple requests to the same host, supply the address
 * of a CURL pointer that is initially NULL. This function will then initialize
 * a new CURL handle on the first call. On subsequent calls, pass in the address
 * of the same CURL pointer so that the CURL handle is reused. After the last
 * request, the CURL handle must be destroyed by calling ekmf_curl_destroy).
 *
 * @param config            the configuration structure
 * @param curl_handle       address of a CURL handle used for reusing the same
 *                          CURL handle with multiple requests.
 * @param key_uuid          the UUID of the key to get info for
 * @param key               an address of a key info pointer. On return
 *                          the pointer is updated to point to a newly allocated
 *                          key info struct. It must be freed by the caller
 *                          using ekmf_free_key_info when no longer needed.
 * @param error_msg         on return: If not NULL, then a textual error message
 *                          is returned in case of a failing request. The caller
 *                          must free the error string when it is not NULL.
 * @param verbose           if true, verbose messages are printed
 *
 * @returns zero for success, a negative errno in case of an error.
 *          -EACCES is returned, if no or no valid login token is available.
 *          -EPERM is returned if the login token does not have permission to
 *          get the key info
 */
int ekmf_get_key_info(const struct ekmf_config *config, CURL **curl_handle,
		      const char *key_uuid, struct ekmf_key_info **key,
		      char **error_msg, bool verbose)
{
	json_object *response_obj = NULL;
	char *escaped_uuid = NULL;
	char *login_token = NULL;
	bool token_valid = false;
	CURL *curl = NULL;
	char *uri = NULL;
	long status_code;
	int rc;

	if (config == NULL || key_uuid == NULL || key == NULL)
		return -EINVAL;

	*key = NULL;

	rc = ekmf_check_login_token(config, &token_valid, &login_token,
				    verbose);
	if (rc != 0 || !token_valid) {
		pr_verbose(verbose, "No valid login token available");
		rc = -EACCES;
		goto out;
	}

	rc = _ekmf_get_curl_handle(curl_handle, &curl);
	if (rc != 0) {
		pr_verbose(verbose, "Failed to get CURL handle");
		rc = -EIO;
		goto out;
	}

	escaped_uuid = curl_easy_escape(curl, key_uuid, 0);
	if (escaped_uuid == NULL) {
		pr_verbose(verbose, "Failed to url-escape the key uuid");
		rc = -EIO;
		goto out;
	}

	if (asprintf(&uri, EKMF_URI_KEYS_GET, escaped_uuid) < 0) {
		pr_verbose(verbose, "asprintf failed");
		rc = -ENOMEM;
		goto out;
	}

	rc = _ekmf_perform_request(config, uri, "GET", NULL, NULL,
				   login_token, &response_obj, NULL,
				   &status_code, error_msg, curl, verbose);
	if (rc != 0) {
		pr_verbose(verbose, "Failed perform the REST call");
		if (rc > 0)
			rc = -EIO;
		goto out;
	}

	switch (status_code) {
	case 200:
		break;
	case 400:
		pr_verbose(verbose, "Bad request");
		rc = -EBADMSG;
		goto out;
	case 401:
		pr_verbose(verbose, "Not authorized");
		rc = -EACCES;
		goto out;
	case 403:
		pr_verbose(verbose, "Insufficient permissions");
		rc = -EPERM;
		goto out;
	case 404:
		pr_verbose(verbose, "Not found");
		rc = -ENOENT;
		goto out;
	default:
		pr_verbose(verbose, "REST Call failed with HTTP status code: "
			   "%ld", status_code);
		rc = -EIO;
		goto out;
	}

	JSON_CHECK_OBJ(response_obj, json_type_object, rc, -EBADMSG,
		       "No or invalid response", verbose, out);

	*key = calloc(1, sizeof(struct ekmf_key_info));
	if (*key == NULL) {
		pr_verbose(verbose, "calloc failed");
		rc = -ENOMEM;
		goto out;
	}

	rc = _ekmf_build_key_info(config, curl, login_token, response_obj,
				  *key, true, error_msg, verbose);
	if (rc != 0) {
		pr_verbose(verbose, "Failed to build template info");
		goto out;
	}

out:
	_ekmf_release_curl_handle(curl_handle, curl);

	if (response_obj != NULL)
		json_object_put(response_obj);
	if (login_token != NULL)
		free(login_token);
	if (uri != NULL)
		free(uri);
	if (escaped_uuid != NULL)
		curl_free(escaped_uuid);
	if (rc != 0 && *key != NULL) {
		free_key_info(*key);
		free(*key);
		*key = NULL;
	}

	return rc;
}

/**
 * Changes the state of a key identified by its UUID. To update a key,
 * the timestamp from the last update is required. This can be found in
 * the key info struct in field update_on.
 *
 * To perform a single request, set curl_handle to NULL. This will cause the
 * function to initialize a new CURL handle, use it, and destroy it.
 * If you plan to perform multiple requests to the same host, supply the address
 * of a CURL pointer that is initially NULL. This function will then initialize
 * a new CURL handle on the first call. On subsequent calls, pass in the address
 * of the same CURL pointer so that the CURL handle is reused. After the last
 * request, the CURL handle must be destroyed by calling ekmf_curl_destroy).
 *
 * @param config            the configuration structure
 * @param curl_handle       address of a CURL handle used for reusing the same
 *                          CURL handle with multiple requests.
 * @param key_uuid          the UUID of the key to get info for
 * @param new_state         the new state of the key
 * @param updated_on        the timestamp of the last update (must match)
 * @param error_msg         on return: If not NULL, then a textual error message
 *                          is returned in case of a failing request. The caller
 *                          must free the error string when it is not NULL.
 * @param verbose           if true, verbose messages are printed
 *
 * @returns zero for success, a negative errno in case of an error.
 *          -EACCES is returned, if no or no valid login token is available.
 *          -EPERM is returned if the login token does not have permission to
 *          update the key.
 *          -EAGAIN is returned if the timestamp does not match, indicating that
 *          the key has been updated in the meantime.
 */
int ekmf_set_key_state(const struct ekmf_config *config, CURL **curl_handle,
		       const char *key_uuid, const char *new_state,
		       const char *updated_on, char **error_msg, bool verbose)
{
	char *request_headers[2] = { NULL, NULL };
	json_object *request_obj = NULL;
	char *escaped_uuid = NULL;
	char *login_token = NULL;
	bool token_valid = false;
	char *if_match_hdr = NULL;
	CURL *curl = NULL;
	char *uri = NULL;
	long status_code;
	int rc;

	if (config == NULL || key_uuid == NULL || new_state == NULL ||
	    updated_on == NULL)
		return -EINVAL;

	rc = ekmf_check_login_token(config, &token_valid, &login_token,
				    verbose);
	if (rc != 0 || !token_valid) {
		pr_verbose(verbose, "No valid login token available");
		rc = -EACCES;
		goto out;
	}

	rc = _ekmf_get_curl_handle(curl_handle, &curl);
	if (rc != 0) {
		pr_verbose(verbose, "Failed to get CURL handle");
		rc = -EIO;
		goto out;
	}

	request_obj = json_object_new_object();
	JSON_CHECK_ERROR(request_obj == NULL, rc, -ENOMEM,
			 "Failed to generate JSON object", verbose, out);

	rc = json_object_object_add_ex(request_obj, "state",
				       json_object_new_string(new_state), 0);
	JSON_CHECK_ERROR(rc != 0, rc, -EIO, "Failed to add data to JSON object",
			 verbose, out);

	escaped_uuid = curl_easy_escape(curl, key_uuid, 0);
	if (escaped_uuid == NULL) {
		pr_verbose(verbose, "Failed to url-escape the key uuid");
		rc = -EIO;
		goto out;
	}

	if (asprintf(&uri, EKMF_URI_KEYS_SET_STATE, escaped_uuid) < 0) {
		pr_verbose(verbose, "asprintf failed");
		rc = -ENOMEM;
		goto out;
	}

	if (asprintf(&if_match_hdr, "If-Match : %s", updated_on) < 0) {
		pr_verbose(verbose, "asprintf failed");
		rc = -ENOMEM;
		goto out;
	}
	request_headers[0] = if_match_hdr;

	rc = _ekmf_perform_request(config, uri, "PATCH", request_obj,
				   request_headers, login_token, NULL, NULL,
				   &status_code, error_msg, curl, verbose);
	if (rc != 0) {
		pr_verbose(verbose, "Failed perform the REST call");
		if (rc > 0)
			rc = -EIO;
		goto out;
	}

	switch (status_code) {
	case 204:
		break;
	case 400:
		pr_verbose(verbose, "Bad request");
		rc = -EBADMSG;
		goto out;
	case 401:
		pr_verbose(verbose, "Not authorized");
		rc = -EACCES;
		goto out;
	case 403:
		pr_verbose(verbose, "Insufficient permissions");
		rc = -EPERM;
		goto out;
	case 404:
		pr_verbose(verbose, "Not found");
		rc = -ENOENT;
		goto out;
	case 409:
		pr_verbose(verbose, "Key was updated in the meantime");
		rc = -EAGAIN;
		goto out;
	default:
		pr_verbose(verbose, "REST Call failed with HTTP status code: "
			   "%ld", status_code);
		rc = -EIO;
		goto out;
	}

out:
	_ekmf_release_curl_handle(curl_handle, curl);

	if (request_obj != NULL)
		json_object_put(request_obj);
	if (login_token != NULL)
		free(login_token);
	if (uri != NULL)
		free(uri);
	if (escaped_uuid != NULL)
		curl_free(escaped_uuid);
	if (if_match_hdr != NULL)
		free(if_match_hdr);

	return rc;
}

/**
 * Sets (adds/changes) a custom tag of a key identified by its UUID. To update
 * a key, the timestamp from the last update is required. This can be found in
 * the key info struct in field update_on.
 *
 * @param config            the configuration structure
 * @param curl              the CURL handle
 * @param login_token       the login token to authenticate
 * @param key_uuid          the UUID of the key to get info for
 * @param tag               the tag to set
 * @param updated_on        the timestamp of the last update (must match)
 * @param delete            if true, the tag is deleted, otherwise it is updated
 * @param etag              On return: the new update timestamp returned via
 *                          the etag HTTP header. Must be freed by the caller.
 * @param error_msg         on return: If not NULL, then a textual error message
 *                          is returned in case of a failing request. The caller
 *                          must free the error string when it is not NULL.
 * @param verbose           if true, verbose messages are printed
 *
 * @returns zero for success, a negative errno in case of an error.
 *          -EACCES is returned, if no or no valid login token is available.
 *          -EPERM is returned if the login token does not have permission to
 *          update the key.
 *          -EAGAIN is returned if the timestamp does not match, indicating that
 *          the key has been updated in the meantime.
 */
static int _ekmf_set_key_tag(const struct ekmf_config *config, CURL *curl,
			     const char *login_token,  const char *key_uuid,
			     const struct ekmf_tag *tag, const char *updated_on,
			     bool delete, char **etag, char **error_msg,
			     bool verbose)
{
	struct curl_slist *response_headers = NULL;
	char *request_headers[2] = { NULL, NULL };
	json_object *request_obj = NULL;
	char *escaped_tag_name = NULL;
	char *escaped_uuid = NULL;
	char *if_match_hdr = NULL;
	char *uri = NULL;
	long status_code;
	int rc;

	if (config == NULL || curl == NULL || login_token == NULL ||
	    key_uuid == NULL || tag == NULL || updated_on == NULL ||
	    etag == NULL)
		return -EINVAL;

	*etag = NULL;

	if (!delete) {
		request_obj = json_object_new_object();
		JSON_CHECK_ERROR(request_obj == NULL, rc, -ENOMEM,
				 "Failed to generate JSON object", verbose,
				 out);

		rc = json_object_object_add_ex(request_obj, "value",
					json_object_new_string(tag->value), 0);
		JSON_CHECK_ERROR(rc != 0, rc, -EIO, "Failed to add data to "
				 "JSON object", verbose, out);
	}

	escaped_uuid = curl_easy_escape(curl, key_uuid, 0);
	if (escaped_uuid == NULL) {
		pr_verbose(verbose, "Failed to url-escape the key uuid");
		rc = -EIO;
		goto out;
	}

	escaped_tag_name = curl_easy_escape(curl, tag->name, 0);
	if (escaped_tag_name == NULL) {
		pr_verbose(verbose, "Failed to url-escape the tag name");
		rc = -EIO;
		goto out;
	}

	if (asprintf(&uri, EKMF_URI_KEYS_SET_TAG, escaped_uuid,
		     escaped_tag_name) < 0) {
		pr_verbose(verbose, "asprintf failed");
		rc = -ENOMEM;
		goto out;
	}

	if (asprintf(&if_match_hdr, "If-Match : %s", updated_on) < 0) {
		pr_verbose(verbose, "asprintf failed");
		rc = -ENOMEM;
		goto out;
	}
	request_headers[0] = if_match_hdr;

	rc = _ekmf_perform_request(config, uri, delete ? "DELETE" : "PUT",
				   request_obj, request_headers, login_token,
				   NULL, &response_headers, &status_code,
				   error_msg, curl, verbose);
	if (rc != 0) {
		pr_verbose(verbose, "Failed perform the REST call");
		if (rc > 0)
			rc = -EIO;
		goto out;
	}

	switch (status_code) {
	case 200:
	case 204:
		break;
	case 400:
		pr_verbose(verbose, "Bad request");
		rc = -EBADMSG;
		goto out;
	case 401:
		pr_verbose(verbose, "Not authorized");
		rc = -EACCES;
		goto out;
	case 403:
		pr_verbose(verbose, "Insufficient permissions");
		rc = -EPERM;
		goto out;
	case 404:
		pr_verbose(verbose, "Not found");
		rc = -ENOENT;
		goto out;
	case 409:
		pr_verbose(verbose, "Key was updated in the meantime");
		rc = -EAGAIN;
		goto out;
	default:
		pr_verbose(verbose, "REST Call failed with HTTP status code: "
			   "%ld", status_code);
		rc = -EIO;
		goto out;
	}

	*etag = get_http_header_value(response_headers, "Etag");
	if (*etag == NULL) {
		pr_verbose(verbose, "No ETag in response headers");
		rc = -EBADMSG;
		goto out;
	}

out:
	if (request_obj != NULL)
		json_object_put(request_obj);
	if (uri != NULL)
		free(uri);
	if (escaped_uuid != NULL)
		curl_free(escaped_uuid);
	if (escaped_tag_name != NULL)
		curl_free(escaped_tag_name);
	if (if_match_hdr != NULL)
		free(if_match_hdr);
	if (response_headers != NULL)
		curl_slist_free_all(response_headers);

	return rc;
}

/**
 * Sets (changed/adds) custom tags of a key identified by its UUID. To update a
 * key, the timestamp from the last update is required. This can be found in
 * the key info struct in field update_on.
 *
 * To perform a single request, set curl_handle to NULL. This will cause the
 * function to initialize a new CURL handle, use it, and destroy it.
 * If you plan to perform multiple requests to the same host, supply the address
 * of a CURL pointer that is initially NULL. This function will then initialize
 * a new CURL handle on the first call. On subsequent calls, pass in the address
 * of the same CURL pointer so that the CURL handle is reused. After the last
 * request, the CURL handle must be destroyed by calling ekmf_curl_destroy).
 *
 * @param config            the configuration structure
 * @param curl_handle       address of a CURL handle used for reusing the same
 *                          CURL handle with multiple requests.
 * @param key_uuid          the UUID of the key to get info for
 * @param tags              a list of tags to set
 * @param updated_on        the timestamp of the last update (must match)
 * @param new_updated_on    on return: if not NULL, the new timestamp of the
 *                          current update. Can be used for subsequent updates
 *                          on the key.
 * @param error_msg         on return: If not NULL, then a textual error message
 *                          is returned in case of a failing request. The caller
 *                          must free the error string when it is not NULL.
 * @param verbose           if true, verbose messages are printed
 *
 * @returns zero for success, a negative errno in case of an error.
 *          -EACCES is returned, if no or no valid login token is available.
 *          -EPERM is returned if the login token does not have permission to
 *          update the key.
 *          -EAGAIN is returned if the timestamp does not match, indicating that
 *          the key has been updated in the meantime.
 */
int ekmf_set_key_tags(const struct ekmf_config *config, CURL **curl_handle,
		       const char *key_uuid, const struct ekmf_tag_list *tags,
		       const char *updated_on, char **new_updated_on,
		       char **error_msg, bool verbose)
{
	char *update_ts = (char *)updated_on;
	char *login_token = NULL;
	bool token_valid = false;
	CURL *curl = NULL;
	char *etag = NULL;
	size_t i;
	int rc;

	if (config == NULL || key_uuid == NULL || tags == NULL ||
	    updated_on == NULL)
		return -EINVAL;

	rc = ekmf_check_login_token(config, &token_valid, &login_token,
				    verbose);
	if (rc != 0 || !token_valid) {
		pr_verbose(verbose, "No valid login token available");
		rc = -EACCES;
		goto out;
	}

	rc = _ekmf_get_curl_handle(curl_handle, &curl);
	if (rc != 0) {
		pr_verbose(verbose, "Failed to get CURL handle");
		rc = -EIO;
		goto out;
	}

	for (i = 0; i < tags->num_tags; i++) {
		rc = _ekmf_set_key_tag(config, curl, login_token, key_uuid,
				       &tags->tags[i], update_ts, false, &etag,
				       error_msg, verbose);
		if (rc != 0) {
			pr_verbose(verbose, "Failed to set tag '%s'",
				   tags->tags[i].name);
			goto out;
		}

		if (update_ts != NULL && update_ts != updated_on)
			free(update_ts);
		update_ts = etag;
		etag = NULL;
	}

	if (new_updated_on != NULL)
		*new_updated_on = strdup(update_ts);

out:
	_ekmf_release_curl_handle(curl_handle, curl);

	if (login_token != NULL)
		free(login_token);
	if (update_ts != NULL && update_ts != updated_on)
		free(update_ts);
	if (etag != NULL)
		free(etag);

	return rc;
}

/**
 * Deletes custom tags of a key identified by its UUID. To update a
 * key, the timestamp from the last update is required. This can be found in
 * the key info struct in field update_on.
 *
 * To perform a single request, set curl_handle to NULL. This will cause the
 * function to initialize a new CURL handle, use it, and destroy it.
 * If you plan to perform multiple requests to the same host, supply the address
 * of a CURL pointer that is initially NULL. This function will then initialize
 * a new CURL handle on the first call. On subsequent calls, pass in the address
 * of the same CURL pointer so that the CURL handle is reused. After the last
 * request, the CURL handle must be destroyed by calling ekmf_curl_destroy).
 *
 * @param config            the configuration structure
 * @param curl_handle       address of a CURL handle used for reusing the same
 *                          CURL handle with multiple requests.
 * @param key_uuid          the UUID of the key to get info for
 * @param tags              a list of tags to delete. Only the name of the tags
 *                          must be present in the tag structs of the list, the
 *                          values are ignored.
 * @param updated_on        the timestamp of the last update (must match)
 * @param new_updated_on    on return: if not NULL, the new timestamp of the
 *                          current update. Can be used for subsequent updates
 *                          on the key.
 * @param error_msg         on return: If not NULL, then a textual error message
 *                          is returned in case of a failing request. The caller
 *                          must free the error string when it is not NULL.
 * @param verbose           if true, verbose messages are printed
 *
 * @returns zero for success, a negative errno in case of an error.
 *          -EACCES is returned, if no or no valid login token is available.
 *          -EPERM is returned if the login token does not have permission to
 *          update the key.
 *          -EAGAIN is returned if the timestamp does not match, indicating that
 *          the key has been updated in the meantime.
 */
int ekmf_delete_key_tags(const struct ekmf_config *config, CURL **curl_handle,
			 const char *key_uuid, const struct ekmf_tag_list *tags,
			 const char *updated_on, char **new_updated_on,
			 char **error_msg, bool verbose)
{
	char *update_ts = (char *)updated_on;
	char *login_token = NULL;
	bool token_valid = false;
	CURL *curl = NULL;
	char *etag = NULL;
	size_t i;
	int rc;

	if (config == NULL || key_uuid == NULL || tags == NULL ||
	    updated_on == NULL)
		return -EINVAL;

	rc = ekmf_check_login_token(config, &token_valid, &login_token,
				    verbose);
	if (rc != 0 || !token_valid) {
		pr_verbose(verbose, "No valid login token available");
		rc = -EACCES;
		goto out;
	}

	rc = _ekmf_get_curl_handle(curl_handle, &curl);
	if (rc != 0) {
		pr_verbose(verbose, "Failed to get CURL handle");
		rc = -EIO;
		goto out;
	}

	for (i = 0; i < tags->num_tags; i++) {
		rc = _ekmf_set_key_tag(config, curl, login_token, key_uuid,
				       &tags->tags[i], update_ts, true, &etag,
				       error_msg, verbose);
		if (rc != 0) {
			pr_verbose(verbose, "Failed to delete tag '%s'",
				   tags->tags[i].name);
			goto out;
		}

		if (update_ts != NULL && update_ts != updated_on)
			free(update_ts);
		update_ts = etag;
		etag = NULL;
	}

	if (new_updated_on != NULL)
		*new_updated_on = strdup(update_ts);

out:
	_ekmf_release_curl_handle(curl_handle, curl);

	if (login_token != NULL)
		free(login_token);
	if (update_ts != NULL && update_ts != updated_on)
		free(update_ts);
	if (etag != NULL)
		free(etag);

	return rc;
}

/**
 * Clones a key info structure by making a deep copy of all strings and
 * arrays.
 * The copied key info must be freed using ekmf_free_key_info() by
 * the caller.
 *
 * @param src               the source key info structure
 * @param dest              the destination key info structure
 *
 * @returns zero for success, a negative errno in case of an error
 */
int ekmf_clone_key_info(const struct ekmf_key_info *src,
			struct ekmf_key_info **dest)
{
	if (src == NULL || dest == NULL)
		return -EINVAL;

	*dest = calloc(1, sizeof(struct ekmf_key_info));
	if (*dest == NULL)
		return -ENOMEM;

	return clone_key_info(src, *dest);
}

/**
 * Free a key info structure.
 *
 * @param key               the key info to free
 */
void ekmf_free_key_info(struct ekmf_key_info *key)
{
	free_key_info(key);

	free(key);
}

/**
 * Build the export control JSON object.
 *
 * @param exporting_key     the key to add as exporting key
 * @param expctl_obj        on return: the export control JSON object
 * @param verbose           if true, verbose messages are printed
 *
 * @returns zero for success, a negative errno in case of an error.
 */
static int _ekmf_build_export_control(const char *exporting_key,
				      json_object **expctl_obj, bool verbose)
{
	json_object *exp_keys = NULL;
	json_object *exp_ref = NULL;
	char *href = NULL;
	int rc = 0;

	*expctl_obj = json_object_new_object();
	JSON_CHECK_ERROR(*expctl_obj == NULL, rc, -ENOMEM,
			 "Failed to generate JSON object",
			 verbose, out);

	rc = json_object_object_add_ex(*expctl_obj, "exportAllowed",
				       json_object_new_boolean(true), 0);
	JSON_CHECK_ERROR(rc != 0, rc, -EIO, "Failed to add data to "
			 "JSON object", verbose, out);

	exp_keys = json_object_new_array();
	JSON_CHECK_ERROR(exp_keys == NULL, rc, -ENOMEM,
			 "Failed to generate JSON object",
			 verbose, out);

	exp_ref = json_object_new_object();
	JSON_CHECK_ERROR(exp_ref == NULL, rc, -ENOMEM,
			 "Failed to generate JSON object",
			 verbose, out);

	rc = json_object_object_add_ex(exp_ref, "rel",
			json_object_new_string("exportAllowedWithKey"), 0);
	JSON_CHECK_ERROR(rc != 0, rc, -EIO, "Failed to add data to "
			 "JSON object", verbose, out);

	JSON_CHECK_ERROR(asprintf(&href, "/keys/%s", exporting_key) < 0, rc,
			 -ENOMEM, "Failed to allocate string", verbose, out);
	rc = json_object_object_add_ex(exp_ref, "href",
				       json_object_new_string(href), 0);
	JSON_CHECK_ERROR(rc != 0, rc, -EIO, "Failed to add data to "
			 "JSON object", verbose, out);

	rc = json_object_array_add(exp_keys, exp_ref);
	JSON_CHECK_ERROR(rc != 0, rc, -EIO, "Failed to add data to "
			 "JSON object", verbose, out);
	exp_ref = NULL;

	rc = json_object_object_add_ex(*expctl_obj, "allowedKeys", exp_keys, 0);
	JSON_CHECK_ERROR(rc != 0, rc, -EIO, "Failed to add data to "
			 "JSON object", verbose, out);
	exp_keys = NULL;

out:
	if (exp_keys != NULL)
		json_object_put(exp_keys);
	if (exp_ref != NULL)
		json_object_put(exp_ref);
	if (href != NULL)
		free(href);
	if (rc != 0 && *expctl_obj != NULL) {
		json_object_put(*expctl_obj);
		*expctl_obj = NULL;
	}

	return rc;
}

/**
 * Base64-encodes the data
 *
 * @param data              the data to encode
 * @param data_size         the size of the data in bytes
 *
 * @returns the encoded data or NULL in case of an error.
 * The caller must free the string when no longer needed.
 */
static char *_ekmf_base64_encode(const unsigned char *data, size_t data_size)
{
	int outlen, len;
	char *out;

	outlen = (data_size / 3) * 4;
	if (data_size % 3 > 0)
		outlen += 4;

	out = calloc(outlen + 1, 1);
	if (out == NULL)
		return NULL;

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

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

/**
 * Build the key material JSON object
 *
 * @param certificate       the certificate to generate an identity key from
 * @param certificate_size  the size of the certificate
 * @param keymat_obj        on return: the key material JSON object
 * @param verbose           if true, verbose messages are printed
 *
 * @returns zero for success, a negative errno in case of an error.
 */
static int _ekmf_build_key_material(const unsigned char *certificate,
				    size_t certificate_size,
				    json_object **keymat_obj, bool verbose)
{
	char *payload = NULL;
	int rc = 0;

	*keymat_obj = json_object_new_object();
	JSON_CHECK_ERROR(*keymat_obj == NULL, rc, -ENOMEM,
				 "Failed to generate JSON object",
				 verbose, out);

	rc = json_object_object_add_ex(*keymat_obj, "type",
			json_object_new_string("ENCODED-CERTIFICATE"), 0);
	JSON_CHECK_ERROR(rc != 0, rc, -EIO, "Failed to add data to "
			 "JSON object", verbose, out);

	payload = _ekmf_base64_encode(certificate, certificate_size);
	JSON_CHECK_ERROR(*keymat_obj == NULL, rc, -EIO,
			 "Failed to base64 encode the certificate",
			 verbose, out);

	rc = json_object_object_add_ex(*keymat_obj, "payload",
				json_object_new_string(payload), 0);
	JSON_CHECK_ERROR(rc != 0, rc, -EIO, "Failed to add data to "
			 "JSON object", verbose, out);

out:
	if (rc != 0 && *keymat_obj != NULL) {
		json_object_put(*keymat_obj);
		*keymat_obj = NULL;
	}
	if (payload != NULL)
		free(payload);

	return rc;
}

/**
 * Generates a new key in EKMFWeb
 *
 * To perform a single request, set curl_handle to NULL. This will cause the
 * function to initialize a new CURL handle, use it, and destroy it.
 * If you plan to perform multiple requests to the same host, supply the address
 * of a CURL pointer that is initially NULL. This function will then initialize
 * a new CURL handle on the first call. On subsequent calls, pass in the address
 * of the same CURL pointer so that the CURL handle is reused. After the last
 * request, the CURL handle must be destroyed by calling ekmf_curl_destroy).
 *
 * @param config            the configuration structure
 * @param curl_handle       address of a CURL handle used for reusing the same
 *                          CURL handle with multiple requests.
 * @param template          the name of the template to generate the key with
 * @param description       Optional: a textual description of the key (can be
 *                          NULL)
 * @param label_tags        list of label tags. The label tags are required as
 *                          defined in the template
 * @param custom_tags       Optional: list of custom tags (can be NULL)
 * @param exporting_key     Optional: The uuid of the key that is allowed to
 *                          export the newly generated key (can be NULL).
 * @param certificate       Optional: The certificate to generate an identity
 *                          key from. Should be NULL for generating AES keys.
 * @param certificate_size  Optional: the size of the certificate. Required if
 *                          certificate is not NULL.
 * @param key_info          Optional: On return: If not NULL, a key info struct
 *                          is returned here containing key information. This
 *                          must be freed by the caller with ekmf_free_key_info
 *                          when no longer needed.
 * @param error_msg         on return: If not NULL, then a textual error message
 *                          is returned in case of a failing request. The caller
 *                          must free the error string when it is not NULL.
 * @param verbose           if true, verbose messages are printed
 *
 * @returns zero for success, a negative errno in case of an error.
 *          -EACCES is returned, if no or no valid login token is available.
 *          -EPERM is returned if the login token does not have permission to
 *          generate keys
 */
int ekmf_generate_key(const struct ekmf_config *config, CURL **curl_handle,
		      const char *template, const char *description,
		      const struct ekmf_tag_list *label_tags,
		      const struct ekmf_tag_list *custom_tags,
		      const char *exporting_key,
		      const unsigned char *certificate, size_t certificate_size,
		      struct ekmf_key_info **key_info,
		      char **error_msg, bool verbose)
{
	json_object *response_obj = NULL;
	json_object *request_obj = NULL;
	json_object *expctl_obj = NULL;
	json_object *keymat_obj = NULL;
	json_object *tags_obj = NULL;
	char *login_token = NULL;
	bool token_valid = false;
	CURL *curl = NULL;
	long status_code;
	int rc;

	if (config == NULL || template == NULL || label_tags == NULL ||
	    label_tags->num_tags == 0 || label_tags->tags == NULL)
		return -EINVAL;
	if (custom_tags != NULL && custom_tags->num_tags > 0 &&
	    custom_tags->tags == NULL)
		return -EINVAL;
	if (certificate != NULL && certificate_size == 0)
		return -EINVAL;

	if (key_info != NULL)
		*key_info = NULL;

	rc = ekmf_check_login_token(config, &token_valid, &login_token,
				    verbose);
	if (rc != 0 || !token_valid) {
		pr_verbose(verbose, "No valid login token available");
		rc = -EACCES;
		goto out;
	}

	rc = _ekmf_get_curl_handle(curl_handle, &curl);
	if (rc != 0) {
		pr_verbose(verbose, "Failed to get CURL handle");
		rc = -EIO;
		goto out;
	}

	request_obj = json_object_new_object();
	JSON_CHECK_ERROR(request_obj == NULL, rc, -ENOMEM,
			 "Failed to generate JSON object", verbose, out);

	rc = json_object_object_add_ex(request_obj, "templateName",
				       json_object_new_string(template), 0);
	JSON_CHECK_ERROR(rc != 0, rc, -EIO, "Failed to add data to JSON object",
			 verbose, out);

	rc = build_json_tag_list(label_tags, &tags_obj);
	if (rc != 0) {
		pr_verbose(verbose, "Failed to build label tag JSON object");
		goto out;
	}

	rc = json_object_object_add_ex(request_obj, "labelTags", tags_obj, 0);
	JSON_CHECK_ERROR(rc != 0, rc, -EIO, "Failed to add data to JSON object",
			 verbose, out);
	tags_obj = NULL;

	if (description != NULL) {
		rc = json_object_object_add_ex(request_obj, "description",
					json_object_new_string(description), 0);
		JSON_CHECK_ERROR(rc != 0, rc, -EIO, "Failed to add data to "
				 "JSON object", verbose, out);
	}

	if (certificate != NULL) {
		rc = _ekmf_build_key_material(certificate, certificate_size,
					      &keymat_obj, verbose);
		if (rc != 0)
			goto out;

		rc = json_object_object_add_ex(request_obj, "keyMaterial",
					       keymat_obj, 0);
		JSON_CHECK_ERROR(rc != 0, rc, -EIO,
				 "Failed to add data to JSON object",
				 verbose, out);
		keymat_obj = NULL;
	}

	if (custom_tags != NULL && custom_tags->num_tags > 0) {
		rc = build_json_tag_list(custom_tags, &tags_obj);
		if (rc != 0) {
			pr_verbose(verbose, "Failed to build custom tag JSON "
				   "object");
			goto out;
		}

		rc = json_object_object_add_ex(request_obj, "customTags",
					       tags_obj, 0);
		JSON_CHECK_ERROR(rc != 0, rc, -EIO, "Failed to add data to "
				 "JSON object", verbose, out);
		tags_obj = NULL;
	}

	if (exporting_key != NULL) {
		rc = _ekmf_build_export_control(exporting_key, &expctl_obj,
						verbose);
		if (rc != 0)
			goto out;

		rc = json_object_object_add_ex(request_obj, "exportControl",
					       expctl_obj, 0);
		JSON_CHECK_ERROR(rc != 0, rc, -EIO,
				 "Failed to add data to JSON object",
				 verbose, out);
		expctl_obj = NULL;
	}

	rc = _ekmf_perform_request(config, EKMF_URI_KEYS_GENERATE, "POST",
				   request_obj, NULL, login_token,
				   &response_obj, NULL, &status_code, error_msg,
				   curl, verbose);
	if (rc != 0) {
		pr_verbose(verbose, "Failed perform the REST call");
		if (rc > 0)
			rc = -EIO;
		goto out;
	}

	switch (status_code) {
	case 201:
		break;
	case 400:
		pr_verbose(verbose, "Bad request");
		rc = -EBADMSG;
		goto out;
	case 401:
		pr_verbose(verbose, "Not authorized");
		rc = -EACCES;
		goto out;
	case 403:
		pr_verbose(verbose, "Insufficient permissions");
		rc = -EPERM;
		goto out;
	case 409:
		pr_verbose(verbose, "A key with this label exist already");
		rc = -EEXIST;
		goto out;
	default:
		pr_verbose(verbose, "REST Call failed with HTTP status code: "
			   "%ld", status_code);
		rc = -EIO;
		goto out;
	}

	JSON_CHECK_OBJ(response_obj, json_type_object, rc, -EBADMSG,
		       "No or invalid response", verbose, out);

	if (key_info != NULL) {
		*key_info = calloc(1, sizeof(struct ekmf_key_info));
		if (*key_info == NULL) {
			pr_verbose(verbose, "calloc failed");
			rc = -ENOMEM;
			goto out;
		}

		rc = _ekmf_build_key_info(config, curl, login_token,
					  response_obj, *key_info, true,
					  error_msg, verbose);
		if (rc != 0) {
			pr_verbose(verbose, "Failed to build the key info");
			goto out;
		}
	}

out:
	_ekmf_release_curl_handle(curl_handle, curl);

	if (request_obj != NULL)
		json_object_put(request_obj);
	if (response_obj != NULL)
		json_object_put(response_obj);
	if (login_token != NULL)
		free(login_token);
	if (tags_obj != NULL)
		json_object_put(tags_obj);
	if (expctl_obj != NULL)
		json_object_put(expctl_obj);
	if (keymat_obj != NULL)
		json_object_put(keymat_obj);
	if (rc != 0 && key_info != NULL && *key_info != NULL) {
		free(*key_info);
		*key_info = NULL;
	}
	return rc;
}

/**
 * Generate a secure identity key used to identify the client to EKMFWeb.
 * The secure key blob is stored in a file specified in field
 * identity_secure_key of the config structure. If an secure key already exists
 * at that location, it is overwritten.
 *
 * @param config            the configuration structure. Only field
 *                          identity_secure_key must be specified, all others
 *                          are optional.
 * @param info              key generation info, such as key type (ECC or RSA)
 *                          and key parameters.
 * @param ext_lib           External secure key crypto library to use
 * @param verbose           if true, verbose messages are printed
 *
 * @returns a negative errno in case of an error, 0 if success.
 */
int ekmf_generate_identity_key(const struct ekmf_config *config,
			       const struct ekmf_key_gen_info *info,
			       const struct ekmf_ext_lib *ext_lib, bool verbose)
{
	unsigned char key_blob[MAX_KEY_BLOB_SIZE];
	size_t key_blob_size = sizeof(key_blob);
	struct ext_lib_info ext_lib_info;
	struct sk_key_gen_info gen_info;
	int rc;

	if (config == NULL || info == NULL || ext_lib == NULL)
		return -EINVAL;
	if (config->identity_secure_key == NULL)
		return -EINVAL;

	_ekmf_copy_ext_lib(ext_lib, &ext_lib_info);
	_ekmf_copy_key_gen_info(info, &gen_info);

	rc = SK_OPENSSL_generate_secure_key(key_blob, &key_blob_size, &gen_info,
					    &ext_lib_info.ext_lib, verbose);
	if (rc != 0) {
		pr_verbose(verbose, "Failed to generate a key: rc: %d - %s",
			   rc, strerror(-rc));
		return rc;
	}

	rc = write_key_blob(config->identity_secure_key, key_blob,
			    key_blob_size);
	if (rc != 0) {
		pr_verbose(verbose, "Failed to write the key to file '%s' "
			   "rc: %d - %s", config->identity_secure_key, rc,
			   strerror(-rc));
		return rc;
	}

	pr_verbose(verbose, "Secure identity key generated (%lu bytes) "
		   "and written to file '%s'", key_blob_size,
		   config->identity_secure_key);

	return 0;
}

/**
 * Re-encipher the secure identity key (form field identity_secure_key in
 * config) used to identify the client to EKMFWeb.
 * The secure key blob is encrypted using the HSM master key. Whenever the HSM
 * master key is being changed, the secure identity key must be re-enciphered.
 * You can either pro-actively re-encipher a secure key once the new master key
 * has been prepared (but not yet made active): to_new = true; or you can
 * re-encipher a secure key when the HSM master key has already been changed:
 * to_new = false. This requires that the HSM still has the old master key.
 * Not all HSMs support this.
 *
 * For pro-active re-encipherment it is suggested to store the re-enciphered
 * secure key on a separate place, until the new HSM master key has been made
 * active. Specify a file name in reenc_secure_key to do so. For an in-place
 * re-encipherment, set reenc_secure_key = NULL.
 *
 * @param config            the configuration structure. Only field
 *                          identity_secure_key must be specified, all others
 *                          are optional.
 * @param to_new            If true: the identity key is re-enciphered from the
 *                          current to the new master key.
 *                          If false: the identity key is re-enciphered from the
 *                          old to the current master key.
 * @param reenc_secure_key  if not NULL, then the re-enciphered secure key is
 *                          stored into the filename specified here. Otherwise
 *                          the re-enciphered secure key replaces the original
 *                          secure identity key.
 * @param ext_lib           External secure key crypto library to use
 * @param verbose           if true, verbose messages are printed
 *
 * @returns a negative errno in case of an error, 0 if success.
 * A -ENODEV indicates that the master keys are not loaded.
 */
int ekmf_reencipher_identity_key(const struct ekmf_config *config,
				 bool to_new, const char *reenc_secure_key,
				 const struct ekmf_ext_lib *ext_lib,
				 bool verbose)
{
	unsigned char key_blob[MAX_KEY_BLOB_SIZE];
	size_t key_blob_size = sizeof(key_blob);
	struct ext_lib_info ext_lib_info;
	const char *out_file;
	int rc;

	if (config == NULL || ext_lib == NULL)
		return -EINVAL;
	if (config->identity_secure_key == NULL)
		return -EINVAL;

	_ekmf_copy_ext_lib(ext_lib, &ext_lib_info);

	rc = read_key_blob(config->identity_secure_key, key_blob,
			   &key_blob_size);
	if (rc != 0) {
		pr_verbose(verbose, "Failed to read identity key from file "
			   "'%s': %s", config->identity_secure_key,
			   strerror(-rc));
		return rc;
	}

	rc = SK_OPENSSL_reencipher_secure_key(key_blob, key_blob_size, to_new,
					      &ext_lib_info.ext_lib, verbose);
	if (rc != 0) {
		pr_verbose(verbose, "Failed to re-encipher the secure identity "
			   "key from file '%s': %s",
			   config->identity_secure_key, strerror(-rc));
		return rc;
	}

	out_file = reenc_secure_key != NULL ? reenc_secure_key :
						config->identity_secure_key;
	rc = write_key_blob(out_file, key_blob, key_blob_size);
	if (rc != 0) {
		pr_verbose(verbose, "Failed to write identity key to file "
			   "'%s': %s", out_file, strerror(-rc));
		return rc;
	}

	return 0;
}

/**
 * Generate a certificate signing request using the secure identity key (field
 * identity_secure_key in config structure) with the specified subject name,
 * certificate extensions (if any), and writes the CSR to the specified file
 * in PEM format.
 *
 * To renew an existing certificate, specify renew_cert = true. In this case
 * the existing certificate (field sign_certificate in config struct) is read,
 * and the subject name is extracted from it. Any specified subject name RDNs
 * are added to the CSR. Also, the extensions are taken from the existing
 * certificate, and any specified extensions are added to the CSR.
 *
 * The CSR is signed using the secure identity key (field identity_secure_key in
 * config structure) with an signing algorithm matching the identity key (ECDSA,
 * RSA-PKCS, or RSA-PSS if rsa_pss is true), and the specified digest. If the
 * digest nid is zero, then a default digest is used.
 *
 * @param config            the configuration structure. Only field
 *                          identity_secure_key must be specified, all others
 *                          are optional.
 * @param subject_rdns      an array of strings, each string representing an
 *                          RDN in the form '[+]type=value'. If the type is
 *                          prepended with a '+', then this RDN is added to the
 *                          previous one.
 * @param num_subject_rdns  number of RDN elements in the array.
 * @param subject_utf8      if true, RDNs of type MBSTRING_UTF8 are created,
 *                          otherwise type is MBSTRING_ASC is used.
 * @param renew_cert_filename if not NULL, specifies the file name of a PEM file
 *                          containing an existing certificate that is renewed
 * @param extensions        an array of strings, each string representing an
 *                          certificate extension in the form 'type=value'.
 * @param num_extensions    number of extension elements in the array.
 * @param digest_nid        the OpenSSL digest nid to use with the signature
 *                          algorithm, or 0 to use the default
 * @param rsa_pss_params    if not NULL and the identity key is an RSA key, then
 *                          the CSR is signed with RSA-PSS using the specified
 *                          PSS parameters. Ignored if the identity key is an EC
 *                          key
 * @param csr_pem_filename  the name of the PEM file to which the CSR is written
 * @param new_hdr           if true, output "NEW" in the PEM header lines
 * @param ext_lib           External secure key crypto library to use
 * @param verbose           if true, verbose messages are printed
 *
 * @returns a negative errno in case of an error, 0 if success:
 *          -EINVAL: invalid parameter
 *          -ENOMEM: Failed to allocate memory
 *          -EBADMSG: an RDN or extension is not formatted correctly
 *          -EIO: OpenSSL failed to create the CSR
 *          -EEXIST: if one of the RDN name entries or extensions to add is a
 *                   duplicate
 *          -ENOTSUP: the specified digest is not supported
 *          any other errno from file I/O routines
 */
int ekmf_generate_csr(const struct ekmf_config *config,
		      const char *subject_rdns[], size_t num_subject_rdns,
		      bool subject_utf8, const char *renew_cert_filename,
		      const char *extensions[], size_t num_extensions,
		      int digest_nid,
		      struct ekmf_rsa_pss_params *rsa_pss_params,
		      const char *csr_pem_filename, bool new_hdr,
		      const struct ekmf_ext_lib *ext_lib, bool verbose)
{
	unsigned char key_blob[MAX_KEY_BLOB_SIZE];
	size_t key_blob_size = sizeof(key_blob);
	struct sk_rsa_pss_params pss_params;
	struct ext_lib_info ext_lib_info;
	X509_REQ *req = NULL;
	X509 *cert = NULL;
	int rc;

	if (config == NULL || ext_lib == NULL || csr_pem_filename == NULL)
		return -EINVAL;
	if (config->identity_secure_key == NULL)
		return -EINVAL;
	if (renew_cert_filename == NULL &&
	    (subject_rdns == NULL || num_subject_rdns == 0))
		return -EINVAL;
	if (num_extensions != 0 && extensions == NULL)
		return -EINVAL;

	_ekmf_copy_ext_lib(ext_lib, &ext_lib_info);

	rc = SK_OPENSSL_init(verbose);
	if (rc != 0) {
		pr_verbose(verbose, "Failed to initialize secure key support: "
			   "%s", strerror(-rc));
		return rc;
	}

	rc = read_key_blob(config->identity_secure_key, key_blob,
			   &key_blob_size);
	if (rc != 0) {
		pr_verbose(verbose, "Failed to read identity key from file "
			   "'%s': %s", config->identity_secure_key,
			   strerror(-rc));
		goto out;
	}

	if (renew_cert_filename != NULL) {
		rc = read_x509_certificate(renew_cert_filename, &cert);
		if (rc != 0) {
			pr_verbose(verbose, "Failed to open renew cert file "
				   "'%s': %s", renew_cert_filename,
				   strerror(-rc));
			goto out;
		}
	}

	if (rsa_pss_params != NULL)
		_ekmf_copy_pss_params(rsa_pss_params, &pss_params);

	rc = SK_OPENSSL_generate_csr(key_blob, key_blob_size,
				     subject_rdns, num_subject_rdns,
				     subject_utf8, cert,
				     extensions, num_extensions,
				     digest_nid, rsa_pss_params != NULL ?
						&pss_params : NULL, &req,
				     &ext_lib_info.ext_lib, verbose);
	if (rc != 0) {
		pr_verbose(verbose, "SK_OPENSSL_generate_csr failed "
			   "'%s': %s", csr_pem_filename, strerror(-rc));
		goto out;
	}

	rc = write_x509_request(csr_pem_filename, req, new_hdr);
	if (rc != 0) {
		pr_verbose(verbose, "Failed to write CSR to file "
			   "'%s': %s", csr_pem_filename, strerror(-rc));
		goto out;
	}

	pr_verbose(verbose, "Certificate Signing Request created successfully "
		   "into '%s'", csr_pem_filename);

out:
	if (cert != NULL)
		X509_free(cert);
	if (req != NULL)
		X509_REQ_free(req);
	SK_OPENSSL_term();

	return rc;
}

/**
 * Generate a self signed certificate using the secure identity key (field
 * identity_secure_key in config structure) with the specified subject name,
 * certificate extensions (if any), and writes the certificate the specified
 * file in PEM format.
 *
 * To renew an existing certificate, specify renew_cert = true. In this case
 * the existing certificate (field sign_certificate in config struct) is read,
 * and the subject name is extracted from it. Any specified subject name RDNs
 * are added to the certificate. Also, the extensions are taken from the
 * existing certificate, and any specified extensions are added to the new
 * certificate.
 *
 * The certificate is signed using the secure identity key (field
 * identity_secure_key in config structure) with an signing algorithm matching
 * the identity key (ECDSA, RSA-PKCS, or RSA-PSS if rsa_pss is true), and the
 * specified digest. If the digest nid is zero, then a default digest is used.
 *
 * @param config            the configuration structure. Only field
 *                          identity_secure_key must be specified, all others
 *                          are optional.
 * @param subject_rdns      an array of strings, each string representing an
 *                          RDN in the form '[+]type=value'. If the type is
 *                          prepended with a '+', then this RDN is added to the
 *                          previous one.
 * @param num_subject_rdns  number of RDN elements in the array.
 * @param subject_utf8      if true, RDNs of type MBSTRING_UTF8 are created,
 *                          otherwise type is MBSTRING_ASC is used.
 * @param renew_cert_filename if not NULL, specifies the file name of a PEM file
 *                          containing an existing certificate that is renewed
 * @param extensions        an array of strings, each string representing an
 *                          certificate extension in the form 'type=value'.
 * @param num_extensions    number of extension elements in the array.
 * @param validity_days     number if day from the current date how long the
 *                          certificate is valid.
 * @param digest_nid        the OpenSSL digest nid to use with the signature
 *                          algorithm, or 0 to use the default
 * @param rsa_pss_params    if not NULL and the identity key is an RSA key, then
 *                          the certificate is signed with RSA-PSS using the
 *                          specified PSS parameters. Ignored if the identity
 *                          key is an EC key
 * @param cert_pem_filename the name of the PEM file to which the Certificate
 *                          is written
 * @param ext_lib           External secure key crypto library to use
 * @param verbose           if true, verbose messages are printed
 *
 * @returns a negative errno in case of an error, 0 if success.
 *          -EINVAL: invalid parameter
 *          -ENOMEM: Failed to allocate memory
 *          -EBADMSG: an RDN or extension is not formatted correctly
 *          -EIO: OpenSSL failed to create the certificate
 *          -EEXIST: if one of the RDN name entries or extensions to add is a
 *                   duplicate
 *          -ENOTSUP: the specified digest is not supported
 *          any other errno from file I/O routines
 */
int ekmf_generate_ss_cert(const struct ekmf_config *config,
			  const char *subject_rdns[], size_t num_subject_rdns,
			  bool subject_utf8, const char *renew_cert_filename,
			  const char *extensions[], size_t num_extensions,
			  int validity_days, int digest_nid,
			  struct ekmf_rsa_pss_params *rsa_pss_params,
			  const char *cert_pem_filename,
			  const struct ekmf_ext_lib *ext_lib, bool verbose)
{
	unsigned char key_blob[MAX_KEY_BLOB_SIZE];
	size_t key_blob_size = sizeof(key_blob);
	struct sk_rsa_pss_params pss_params;
	struct ext_lib_info ext_lib_info;
	X509 *rcert = NULL;
	X509 *cert = NULL;
	int rc;

	if (config == NULL || ext_lib == NULL || cert_pem_filename == NULL)
		return -EINVAL;
	if (config->identity_secure_key == NULL)
		return -EINVAL;
	if (renew_cert_filename == NULL &&
	    (subject_rdns == NULL || num_subject_rdns == 0))
		return -EINVAL;
	if (num_extensions != 0 && extensions == NULL)
		return -EINVAL;

	_ekmf_copy_ext_lib(ext_lib, &ext_lib_info);

	rc = SK_OPENSSL_init(verbose);
	if (rc != 0) {
		pr_verbose(verbose, "Failed to initialize secure key support: "
			   "%s", strerror(-rc));
		return rc;
	}

	rc = read_key_blob(config->identity_secure_key, key_blob,
			   &key_blob_size);
	if (rc != 0) {
		pr_verbose(verbose, "Failed to read identity key from file "
			   "'%s': %s", config->identity_secure_key,
			   strerror(-rc));
		goto out;
	}

	if (renew_cert_filename != NULL) {
		rc = read_x509_certificate(renew_cert_filename, &rcert);
		if (rc != 0) {
			pr_verbose(verbose, "Failed to open renew cert file "
				   "'%s': %s", renew_cert_filename,
				   strerror(-rc));
			goto out;
		}
	}

	if (rsa_pss_params != NULL)
		_ekmf_copy_pss_params(rsa_pss_params, &pss_params);

	rc = SK_OPENSSL_generate_ss_cert(key_blob, key_blob_size,
					 subject_rdns, num_subject_rdns,
					 subject_utf8, rcert,
					 extensions, num_extensions,
					 validity_days, digest_nid,
					 rsa_pss_params != NULL ?
						&pss_params : NULL, &cert,
					 &ext_lib_info.ext_lib, verbose);
	if (rc != 0) {
		pr_verbose(verbose, "SK_OPENSSL_generate_ss_cert failed "
			   "'%s': %s", cert_pem_filename, strerror(-rc));
		goto out;
	}

	rc = write_x509_certificate(cert_pem_filename, cert);
	if (rc != 0) {
		pr_verbose(verbose, "Failed to write Certificate to file "
			   "'%s': %s", cert_pem_filename, strerror(-rc));
		goto out;
	}

	pr_verbose(verbose, "Self-signed Certificate created successfully "
		   "into '%s'", cert_pem_filename);

out:
	if (cert != NULL)
		X509_free(cert);
	if (rcert != NULL)
		X509_free(rcert);
	SK_OPENSSL_term();

	return rc;
}

/**
 * Validates that a certificate has the same public key as the secure identity
 * key (field identity_secure_key in config structure) .
 *
 * @param config            the configuration structure. Only field
 *                          identity_secure_key must be specified, all others
 *                          are optional.
 * @param x509_cert         the X509 certificate object to validate
 * @param ext_lib           External secure key crypto library to use
 * @param verbose           if true, verbose messages are printed
 *
 * @returns a negative errno in case of an error, 0 if success.
 *          -EINVAL: invalid parameter, or certificate is not valid
 *          -ENOMEM: Failed to allocate memory
 *          any other errno from file I/O routines
 */
int ekmf_validate_cert(const struct ekmf_config *config, const X509 *x509_cert,
		       const struct ekmf_ext_lib *ext_lib, bool verbose)
{
	unsigned char key_blob[MAX_KEY_BLOB_SIZE];
	size_t key_blob_size = sizeof(key_blob);
	struct ext_lib_info ext_lib_info;
	EVP_PKEY *pkey = NULL;
	int rc;

	if (config == NULL || ext_lib == NULL || x509_cert == NULL)
		return -EINVAL;
	if (config->identity_secure_key == NULL)
		return -EINVAL;

	_ekmf_copy_ext_lib(ext_lib, &ext_lib_info);

	rc = SK_OPENSSL_init(verbose);
	if (rc != 0) {
		pr_verbose(verbose, "Failed to initialize secure key support: "
			   "%s", strerror(-rc));
		return rc;
	}

	rc = read_key_blob(config->identity_secure_key, key_blob,
			   &key_blob_size);
	if (rc != 0) {
		pr_verbose(verbose, "Failed to read identity key from file "
			   "'%s': %s", config->identity_secure_key,
			   strerror(-rc));
		goto out;
	}

	rc = SK_OPENSSL_get_secure_key_as_pkey(key_blob, key_blob_size,
					       false, &pkey,
					       &ext_lib_info.ext_lib, verbose);
	if (rc != 0) {
		pr_verbose(verbose, "Failed to get the PKEY from the identity "
			   "key: %s", strerror(-rc));
		goto out;
	}

#if !OPENSSL_VERSION_PREREQ(3, 0)
	if (EVP_PKEY_cmp(pkey, X509_get0_pubkey(x509_cert)) != 1) {
#else
	if (EVP_PKEY_eq(pkey, X509_get0_pubkey(x509_cert)) != 1) {
#endif
		pr_verbose(verbose, "The certificate does not match with the "
			   "identity key");
		rc = -EINVAL;
		goto out;
	}

	pr_verbose(verbose, "Certificate successfully validated");

out:
	if (pkey != NULL)
		EVP_PKEY_free(pkey);

	SK_OPENSSL_term();

	return rc;
}

/**
 * Close the connection to the EKMFWeb server by destroying the CURL handle.
 *
 * @param curl_handle       the CURL handle to destroy
 */
void ekmf_curl_destroy(CURL *curl_handle)
{
	if (curl_handle == NULL)
		return;

	curl_easy_cleanup(curl_handle);
}

/**
 * Library constructor
 */
void __attribute__ ((constructor)) ekmf_init(void)
{
	CURLsslset rc;

	/*
	 * Ensure that curl uses OpenSSL as SSL backend. If curl has already
	 * been itialized by the calling application, the backend can't be
	 * changed anymore, but we continue anyway. However, it will later be
	 * checked if curl uses the OpenSSL backend, and a HTTPS connection
	 * will fail if it is not using the OpenSSL backend.
	 */
	rc = curl_global_sslset(CURLSSLBACKEND_OPENSSL, NULL, NULL);
	if (rc != CURLSSLSET_OK && rc != CURLSSLSET_TOO_LATE)
		errx(EXIT_FAILURE, "libekmfweb: libcurl was not built with "
		     "the OpenSSL backend");

	curl_global_init(CURL_GLOBAL_ALL);
}

/**
 * Library destructor
 */
void __attribute__ ((destructor)) ekmf_exit(void)
{
	curl_global_cleanup();
}


