// SPDX-License-Identifier: LGPL-2.1-or-later
/*
 * This file is part of libnvme.
 * Copyright (c) 2020 Western Digital Corporation or its affiliates.
 *
 * Authors: Keith Busch <keith.busch@wdc.com>
 * 	    Chaitanya Kulkarni <chaitanya.kulkarni@wdc.com>
 */

#include <arpa/inet.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#if NVME_HAVE_SYS_RANDOM
#include <sys/random.h>
#endif
#include <sys/param.h>
#include <sys/stat.h>

#ifdef CONFIG_OPENSSL
#include <openssl/evp.h>
#include <openssl/hmac.h>
#include <openssl/kdf.h>
#include <openssl/core_names.h>
#include <openssl/params.h>
#endif

#ifdef CONFIG_KEYUTILS
#include <keyutils.h>

#define NVME_TLS_DEFAULT_KEYRING ".nvme"
#endif

#include <ccan/endian/endian.h>

#include <shared/base64-util.h>
#include <shared/crc32-util.h>
#include <shared/hex-util.h>
#include <shared/compiler-attributes-util.h>

#include <libnvme.h>

#include "cleanup.h"
#include "cleanup-linux.h"
#include "private.h"


#ifndef CONFIG_OPENSSL
static unsigned char default_hmac(size_t key_len)
{
	return LIBNVMF_HMAC_ALG_NONE;
}

__shr_public int libnvmf_gen_kxchap_key(struct libnvme_global_ctx *ctx,
		char *hostnqn, enum libnvmf_hmac_alg hmac,
		unsigned int key_len, unsigned char *secret,
		unsigned char *key)
{
	if (hmac != LIBNVMF_HMAC_ALG_NONE) {
		libnvme_msg(ctx, LIBNVME_LOG_ERR, "HMAC transformation not supported; "
			"recompile with OpenSSL support.\n");
		return -EINVAL;
	}

	memcpy(key, secret, key_len);
	return 0;
}

__shr_public int libnvmf_create_raw_secret(struct libnvme_global_ctx *ctx,
		const char *secret, size_t key_len, unsigned char **raw_secret)
{
	libnvme_msg(ctx, LIBNVME_LOG_ERR, "NVMe TLS is not supported; "
		 "recompile with OpenSSL support.\n");
	return -ENOTSUP;
}

static int derive_retained_key(struct libnvme_global_ctx *ctx,
		int hmac, const char *hostnqn, unsigned char *generated,
		unsigned char *retained, size_t key_len)
{
	libnvme_msg(ctx, LIBNVME_LOG_ERR, "NVMe TLS is not supported; "
		 "recompile with OpenSSL support.\n");
	return -ENOTSUP;
}

static int derive_retained_key_compat(struct libnvme_global_ctx *ctx,
		int hmac, const char *hostnqn, unsigned char *generated,
		unsigned char *retained, size_t key_len)
{
	libnvme_msg(ctx, LIBNVME_LOG_ERR, "NVMe TLS is not supported; "
		 "recompile with OpenSSL support.\n");
	return -ENOTSUP;
}

static int derive_psk_digest(struct libnvme_global_ctx *ctx,
		const char *hostnqn, const char *subsysnqn,
		int version, int cipher,
		unsigned char *retained, size_t key_len,
		char *digest, size_t digest_len)
{
	libnvme_msg(ctx, LIBNVME_LOG_ERR, "TLS PSK identity version 1 is not "
		 "supported; recompile with OpenSSL support.\n");
	return -ENOTSUP;
}

static int derive_tls_key(struct libnvme_global_ctx *ctx,
		int version, unsigned char cipher, const char *context,
		unsigned char *retained, unsigned char *psk, size_t key_len)
{
	libnvme_msg(ctx, LIBNVME_LOG_ERR, "NVMe TLS is not supported; "
		 "recompile with OpenSSL support.\n");
	return -ENOTSUP;
}

static int derive_tls_key_compat(struct libnvme_global_ctx *ctx,
		int version, unsigned char cipher, const char *context,
		unsigned char *retained, unsigned char *psk, size_t key_len)
{
	libnvme_msg(ctx, LIBNVME_LOG_ERR, "NVMe TLS is not supported; "
		 "recompile with OpenSSL support.\n");
	return -ENOTSUP;
}
#else /* CONFIG_OPENSSL */
static unsigned char default_hmac(size_t key_len)
{
	unsigned char hmac = LIBNVMF_HMAC_ALG_NONE;

	switch (key_len) {
	case 32:
		hmac = LIBNVMF_HMAC_ALG_SHA2_256;
		break;
	case 48:
		hmac = LIBNVMF_HMAC_ALG_SHA2_384;
		break;
	case 64:
		hmac = LIBNVMF_HMAC_ALG_SHA2_512;
		break;
	default:
		break;
	}
	return hmac;
}

static const EVP_MD *select_hmac(int hmac, size_t *hmac_len)
{
	const EVP_MD *md = NULL;

	switch (hmac) {
	case LIBNVMF_HMAC_ALG_SHA2_256:
		md = EVP_sha256();
		*hmac_len = 32;
		break;
	case LIBNVMF_HMAC_ALG_SHA2_384:
		md = EVP_sha384();
		*hmac_len = 48;
		break;
	default:
		*hmac_len = 0;
		break;
	}
	return md;
}

static DEFINE_CLEANUP_FUNC(
	cleanup_evp_pkey_ctx, EVP_PKEY_CTX *, EVP_PKEY_CTX_free)
#define __cleanup_evp_pkey_ctx __cleanup(cleanup_evp_pkey_ctx)

/* NVMe is using the TLS 1.3 HkdfLabel structure */
#define HKDF_INFO_MAX_LEN 514
#define HKDF_INFO_LABEL_MAX 256
#define HKDF_INFO_CONTEXT_MAX 256

/*
 * derive_retained_key()
 *
 * Derive a retained PSK according to the NVMe TCP Transport
 * Specification 1.3, section 3.6.1.3:
 *
 * The retained PSK is derived from the configured PSK. The configured PSK
 * shall be destroyed as soon as the retained PSK is generated and stored.
 * Each NVMe/TCP entity shall support:
 * 1) transforming the configured PSK into a retained PSK before it is stored
 *    by the NVMe/TCP entity for repeated use with another NVMe/TCP entity; and
 * 2) using the configured PSK as a retained PSK.
 *
 * The method to derive a retained PSK from a configured PSK shall be using
 * the HKDF-Extract and HKDF-Expand-Label operations (refer to RFC 5869 and
 * RFC 8446):
 * 1. PRK = HKDF-Extract(0, Configured PSK); and
 * 2. Retained PSK = HKDF-Expand-Label(PRK, “HostNQN”, NQNh,
 *                                     Length(Configured PSK)),
 * where NQNh is the NQN of the host.
 *
 * 'hmac' indicates the hash function to be used to transform the configured
 * PSK in a retained PSK, encoded as follows:
 *
 * - 0 indicates no transform (i.e., the configured PSK is used as a
 *   retained PSK)
 * - 1 indicates SHA-256
 * - 2 indicates SHA-384
 */
static int derive_retained_key(struct libnvme_global_ctx *ctx,
		int hmac, const char *hostnqn,
		unsigned char *configured, unsigned char *retained,
		size_t key_len)
{
	__cleanup_evp_pkey_ctx EVP_PKEY_CTX *ectx = NULL;
	__cleanup_free uint8_t *hkdf_info = NULL;
	char *hkdf_label;
	const EVP_MD *md;
	size_t hmac_len;
	char *pos;
	int ret;

	/* +1 byte so that the snprintf terminating null can not overflow */
	hkdf_info = malloc(HKDF_INFO_MAX_LEN + 1);
	if (!hkdf_info)
		return -ENOMEM;

	if (hmac == LIBNVMF_HMAC_ALG_NONE) {
		memcpy(retained, configured, key_len);
		return key_len;
	}

	md = select_hmac(hmac, &hmac_len);
	if (!md || !hmac_len)
		return -EINVAL;

	ectx = EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, NULL);
	if (!ectx)
		return -ENOMEM;

	if (EVP_PKEY_derive_init(ectx) <= 0)
		return -ENOMEM;

	if (EVP_PKEY_CTX_set_hkdf_md(ectx, md) <= 0)
		return -ENOKEY;

	if (EVP_PKEY_CTX_set1_hkdf_key(ectx, configured, key_len) <= 0)
		return -ENOKEY;

	if (key_len > USHRT_MAX)
		return -EINVAL;

	pos = (char *)hkdf_info;
	*(uint16_t *)pos = htons(key_len & 0xFFFF);
	pos += sizeof(uint16_t);

	hkdf_label = "tls13 HostNQN";
	ret = snprintf(pos, HKDF_INFO_LABEL_MAX + 1, "%c%s",
		       (int)strlen(hkdf_label), hkdf_label);
	if (ret <= 0 || ret > HKDF_INFO_LABEL_MAX)
		return -ENOKEY;
	pos += ret;

	ret = snprintf(pos, HKDF_INFO_CONTEXT_MAX + 1, "%c%s",
		       (int)strlen(hostnqn), hostnqn);
	if (ret <= 0 || ret > HKDF_INFO_CONTEXT_MAX)
		return -ENOKEY;
	pos += ret;

	if (EVP_PKEY_CTX_add1_hkdf_info(ectx, hkdf_info,
					(pos - (char *)hkdf_info)) <= 0)
		return -ENOKEY;

	if (EVP_PKEY_derive(ectx, retained, &key_len) <= 0)
		return -ENOKEY;

	return key_len;
}

static int derive_retained_key_compat(struct libnvme_global_ctx *ctx,
		int hmac, const char *hostnqn, unsigned char *configured,
		unsigned char *retained, size_t key_len)
{
	__cleanup_evp_pkey_ctx EVP_PKEY_CTX *ectx = NULL;
	__cleanup_free uint8_t *hkdf_info = NULL;
	const EVP_MD *md;
	size_t hmac_len;
	char *pos;
	int ret;

	if (hmac == LIBNVMF_HMAC_ALG_NONE) {
		memcpy(retained, configured, key_len);
		return key_len;
	}

	md = select_hmac(hmac, &hmac_len);
	if (!md || !hmac_len)
		return -EINVAL;

	ectx = EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, NULL);
	if (!ectx)
		return -ENOMEM;

	if (EVP_PKEY_derive_init(ectx) <= 0)
		return -ENOMEM;

	if (EVP_PKEY_CTX_set_hkdf_md(ectx, md) <= 0)
		return -ENOKEY;

	if (EVP_PKEY_CTX_set1_hkdf_key(ectx, configured, key_len) <= 0)
		return -ENOKEY;

	/* +1 byte so that the snprintf terminating null can not overflow */
	hkdf_info = malloc(HKDF_INFO_MAX_LEN + 1);
	if (!hkdf_info)
		return -ENOMEM;

	pos = (char *)hkdf_info;
	*(uint16_t *)pos = cpu_to_le16(key_len);
	pos += sizeof(uint16_t);

	ret = snprintf(pos, HKDF_INFO_LABEL_MAX + 1,
		       "tls13 HostNQN%s", hostnqn);
	if (ret <= 0 || ret > HKDF_INFO_LABEL_MAX)
		return -ENOKEY;
	pos += ret;

	if (EVP_PKEY_CTX_add1_hkdf_info(ectx, hkdf_info,
					(pos - (char *)hkdf_info)) <= 0)
		return -ENOKEY;

	if (EVP_PKEY_derive(ectx, retained, &key_len) <= 0)
		return -ENOKEY;

	return key_len;
}

/*
 * derive_tls_key()
 *
 * Derive a TLS PSK from a retained PSK.
 *
 * The TLS PSK shall be derived as follows from an input PSK (i.e., either
 * a retained PSK or a generated PSK) and a PSK identity using the HKDF-Extract
 * and HKDF-Expand-Label operations (refer to RFC 5869 and RFC 8446) where the
 * hash function is the one specified by the hash specifier of the PSK identity:
 * 1. PRK = HKDF-Extract(0, Input PSK); and
 * 2. TLS PSK = HKDF-Expand-Label(PRK, “nvme-tls-psk”, PskIdentity, L),
 * where PskIdentity is the PSK identity and L is the output size in bytes of
 * the hash function (i.e., 32 for SHA-256 and 48 for SHA-384).
 *
 * Note that this is _not_ the hash value as specified by the configured PSK,
 * but rather the hash function of the cipher suite associated with the
 * PSK:
 * - 1 indicates SHA-256 (for the TLS_AES_128_GCM_SHA256 cipher suite)
 * - 2 indicates SHA-384 (for the TLS_AES_256_GCM_SHA384 cipher suite)
 *
 * and the value '0' is invalid here.
 */

static int derive_tls_key(struct libnvme_global_ctx *ctx,
		int version, unsigned char cipher, const char *context,
		unsigned char *retained, unsigned char *psk, size_t key_len)
{
	__cleanup_evp_pkey_ctx EVP_PKEY_CTX *ectx = NULL;
	__cleanup_free uint8_t *hkdf_info = NULL;
	char *hkdf_label;
	const EVP_MD *md;
	size_t hmac_len;
	char *pos;
	int ret;

	/* +1 byte so that the snprintf terminating null can not overflow */
	hkdf_info = malloc(HKDF_INFO_MAX_LEN + 1);
	if (!hkdf_info)
		return -ENOMEM;

	md = select_hmac(cipher, &hmac_len);
	if (!md || !hmac_len)
		return -EINVAL;

	ectx = EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, NULL);
	if (!ectx)
		return -ENOMEM;

	if (EVP_PKEY_derive_init(ectx) <= 0)
		return -ENOMEM;

	if (EVP_PKEY_CTX_set_hkdf_md(ectx, md) <= 0)
		return -ENOKEY;

	if (EVP_PKEY_CTX_set1_hkdf_key(ectx, retained, key_len) <= 0)
		return -ENOKEY;

	if (key_len > USHRT_MAX)
		return -EINVAL;

	pos = (char *)hkdf_info;
	*(uint16_t *)pos = htons(key_len & 0xFFFF);
	pos += sizeof(uint16_t);

	hkdf_label = "tls13 nvme-tls-psk";
	ret = snprintf(pos, HKDF_INFO_LABEL_MAX + 1, "%c%s",
		       (int)strlen(hkdf_label), hkdf_label);
	if (ret <= 0 || ret > HKDF_INFO_LABEL_MAX)
		return -ENOKEY;
	pos += ret;

	switch (version) {
	case 0:
		ret = snprintf(pos, HKDF_INFO_CONTEXT_MAX + 1, "%c%s",
			       (int)strlen(context), context);
		if (ret <= 0 || ret > HKDF_INFO_CONTEXT_MAX)
			return -ENOKEY;
		pos += ret;
		break;
	case 1:
		ret = snprintf(pos, HKDF_INFO_CONTEXT_MAX + 1, "%c%02d %s",
			       (int)strlen(context) + 3, cipher, context);
		if (ret <= 0 || ret > HKDF_INFO_CONTEXT_MAX)
			return -ENOKEY;
		pos += ret;
		break;
	default:
		return -ENOKEY;
	}

	if (EVP_PKEY_CTX_add1_hkdf_info(ectx, hkdf_info,
					(pos - (char *)hkdf_info)) <= 0)
		return -ENOKEY;

	if (EVP_PKEY_derive(ectx, psk, &key_len) <= 0)
		return -ENOKEY;

	return key_len;
}

static int derive_tls_key_compat(struct libnvme_global_ctx *ctx,
		int version, unsigned char cipher, const char *context,
		unsigned char *retained, unsigned char *psk, size_t key_len)
{
	__cleanup_evp_pkey_ctx EVP_PKEY_CTX *ectx = NULL;
	__cleanup_free uint8_t *hkdf_info = NULL;
	const EVP_MD *md;
	size_t hmac_len;
	char *pos;
	int ret;

	md = select_hmac(cipher, &hmac_len);
	if (!md || !hmac_len)
		return -EINVAL;

	ectx = EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, NULL);
	if (!ectx)
		return -ENOMEM;

	if (EVP_PKEY_derive_init(ectx) <= 0)
		return -ENOMEM;

	if (EVP_PKEY_CTX_set_hkdf_md(ectx, md) <= 0)
		return -ENOKEY;

	if (EVP_PKEY_CTX_set1_hkdf_key(ectx, retained, key_len) <= 0)
		return -ENOKEY;

	/* +1 byte so that the snprintf terminating null can not overflow */
	hkdf_info = malloc(HKDF_INFO_MAX_LEN + 1);
	if (!hkdf_info)
		return -ENOMEM;

	pos = (char *)hkdf_info;
	*(uint16_t *)pos = cpu_to_le16(key_len);
	pos += sizeof(uint16_t);

	ret = snprintf(pos, HKDF_INFO_LABEL_MAX + 1, "tls13 nvme-tls-psk");
	if (ret <= 0 || ret > HKDF_INFO_LABEL_MAX)
		return -ENOKEY;
	pos += ret;

	switch (version) {
	case 0:
		ret = snprintf(pos, HKDF_INFO_CONTEXT_MAX + 1, "%s", context);
		if (ret <= 0 || ret > HKDF_INFO_CONTEXT_MAX)
			return -ENOKEY;
		pos += ret;
		break;
	case 1:
		ret = snprintf(pos, HKDF_INFO_CONTEXT_MAX + 1, "%02d %s",
			       cipher, context);
		if (ret <= 0 || ret > HKDF_INFO_CONTEXT_MAX)
			return -ENOKEY;
		pos += ret;
		break;
	default:
		return -ENOKEY;
	}

	if (EVP_PKEY_CTX_add1_hkdf_info(ectx, hkdf_info,
					(pos - (char *)hkdf_info)) <= 0)
		return -ENOKEY;

	if (EVP_PKEY_derive(ectx, psk, &key_len) <= 0)
		return -ENOKEY;

	return key_len;
}

static DEFINE_CLEANUP_FUNC(
	cleanup_ossl_lib_ctx, OSSL_LIB_CTX *, OSSL_LIB_CTX_free)
#define __cleanup_ossl_lib_ctx __cleanup(cleanup_ossl_lib_ctx)
static DEFINE_CLEANUP_FUNC(cleanup_evp_mac_ctx, EVP_MAC_CTX *, EVP_MAC_CTX_free)
#define __cleanup_evp_mac_ctx __cleanup(cleanup_evp_mac_ctx)
static DEFINE_CLEANUP_FUNC(cleanup_evp_mac, EVP_MAC *, EVP_MAC_free)
#define __cleanup_evp_mac __cleanup(cleanup_evp_mac)

__shr_public int libnvmf_gen_kxchap_key(struct libnvme_global_ctx *ctx,
		char *hostnqn, enum libnvmf_hmac_alg hmac,
		unsigned int key_len, unsigned char *secret,
		unsigned char *key)
{
	const char hmac_seed[] = "NVMe-over-Fabrics";
	__cleanup_ossl_lib_ctx OSSL_LIB_CTX *lib_ctx = NULL;
	__cleanup_evp_mac_ctx EVP_MAC_CTX *mac_ctx = NULL;
	__cleanup_evp_mac EVP_MAC *mac = NULL;
	OSSL_PARAM params[2], *p = params;
	char *progq = NULL;
	char *digest;
	size_t len;

	lib_ctx = OSSL_LIB_CTX_new();
	if (!lib_ctx)
		return -ENOMEM;

	mac = EVP_MAC_fetch(lib_ctx, OSSL_MAC_NAME_HMAC, progq);
	if (!mac)
		return -ENOMEM;

	mac_ctx = EVP_MAC_CTX_new(mac);
	if (!mac_ctx)
		return -ENOMEM;

	switch (hmac) {
	case LIBNVMF_HMAC_ALG_NONE:
		memcpy(key, secret, key_len);
		return 0;
	case LIBNVMF_HMAC_ALG_SHA2_256:
		digest = OSSL_DIGEST_NAME_SHA2_256;
		break;
	case LIBNVMF_HMAC_ALG_SHA2_384:
		digest = OSSL_DIGEST_NAME_SHA2_384;
		break;
	case LIBNVMF_HMAC_ALG_SHA2_512:
		digest = OSSL_DIGEST_NAME_SHA2_512;
		break;
	default:
		return -EINVAL;
	}
	*p++ = OSSL_PARAM_construct_utf8_string(OSSL_MAC_PARAM_DIGEST,
						digest,
						0);
	*p = OSSL_PARAM_construct_end();

	if (!EVP_MAC_init(mac_ctx, secret, key_len, params))
		return -ENOKEY;

	if (!EVP_MAC_update(mac_ctx, (unsigned char *)hostnqn,
			    strlen(hostnqn)))
		return -ENOKEY;

	if (!EVP_MAC_update(mac_ctx, (unsigned char *)hmac_seed,
			    strlen(hmac_seed)))
		return -ENOKEY;

	if (!EVP_MAC_final(mac_ctx, key, &len, key_len))
		return -ENOKEY;

	if (len != key_len)
		return -EMSGSIZE;

	return 0;
}

static int derive_psk_digest(struct libnvme_global_ctx *ctx,
		const char *hostnqn, const char *subsysnqn,
		int version, int cipher,
		unsigned char *retained, size_t key_len,
		char *digest, size_t digest_len)
{
	static const char hmac_seed[] = "NVMe-over-Fabrics";
	__cleanup_ossl_lib_ctx OSSL_LIB_CTX *lib_ctx = NULL;
	__cleanup_evp_mac_ctx EVP_MAC_CTX *mac_ctx = NULL;
	__cleanup_free unsigned char *psk_ctx = NULL;
	__cleanup_evp_mac EVP_MAC *mac = NULL;
	OSSL_PARAM params[2], *p = params;
	size_t hmac_len;
	char *progq = NULL;
	char *dig = NULL;
	int len;

	lib_ctx = OSSL_LIB_CTX_new();
	if (!lib_ctx)
		return -ENOMEM;

	mac = EVP_MAC_fetch(lib_ctx, OSSL_MAC_NAME_HMAC, progq);
	if (!mac)
		return -ENOMEM;

	mac_ctx = EVP_MAC_CTX_new(mac);
	if (!mac_ctx)
		return -ENOMEM;

	switch (cipher) {
	case LIBNVMF_HMAC_ALG_SHA2_256:
		dig = OSSL_DIGEST_NAME_SHA2_256;
		break;
	case LIBNVMF_HMAC_ALG_SHA2_384:
		dig = OSSL_DIGEST_NAME_SHA2_384;
		break;
	default:
		return -EINVAL;
	}

	*p++ = OSSL_PARAM_construct_utf8_string(OSSL_MAC_PARAM_DIGEST,
						dig, 0);
	*p = OSSL_PARAM_construct_end();

	psk_ctx = malloc(key_len);
	if (!psk_ctx)
		return -ENOMEM;

	if (!EVP_MAC_init(mac_ctx, retained, key_len, params))
		return -ENOKEY;

	if (!EVP_MAC_update(mac_ctx, (unsigned char *)hostnqn,
			    strlen(hostnqn)))
		return -ENOKEY;

	if (!EVP_MAC_update(mac_ctx, (unsigned char *)" ", 1))
		return -ENOKEY;

	if (!EVP_MAC_update(mac_ctx, (unsigned char *)subsysnqn,
			    strlen(subsysnqn)))
		return -ENOKEY;

	if (!EVP_MAC_update(mac_ctx, (unsigned char *)" ", 1))
		return -ENOKEY;

	if (!EVP_MAC_update(mac_ctx, (unsigned char *)hmac_seed,
			    strlen(hmac_seed)))
		return -ENOKEY;

	if (!EVP_MAC_final(mac_ctx, psk_ctx, &hmac_len, key_len))
		return -ENOKEY;

	if (hmac_len > key_len)
		return -EMSGSIZE;

	if (hmac_len * 2 > digest_len)
		return -EINVAL;

	memset(digest, 0, digest_len);
	len = shr_base64_encode(psk_ctx, hmac_len, digest);
	if (len < 0)
		return len;

	return strlen(digest);
}

static ssize_t getrandom_bytes(void *buf, size_t buflen)
{
	ssize_t result;
#if NVME_HAVE_SYS_RANDOM
	result = getrandom(buf, buflen, GRND_NONBLOCK);
#else
	__cleanup_fd int fd = -1;

	fd = open("/dev/urandom", O_RDONLY);
	if (fd < 0)
		return -errno;
	result = read(fd, buf, buflen);
#endif
	if (result < 0)
		return -errno;
	return result;
}

static ssize_t getswordfish(struct libnvme_global_ctx *ctx,
		const char *seed, void *buf, size_t buflen)
{
	unsigned char hash[EVP_MAX_MD_SIZE];
	unsigned int counter = 0;
	EVP_MD_CTX *md_ctx;
	size_t copied = 0;

	md_ctx = EVP_MD_CTX_new();
	if (!md_ctx)
		return -ENOMEM;

	while (copied < buflen) {
		beint32_t be_counter;
		unsigned int hash_len;
		size_t to_copy;

		if (EVP_DigestInit_ex(md_ctx, EVP_sha256(), NULL) != 1)
			goto err;

		be_counter = cpu_to_be32(counter);

		EVP_DigestUpdate(md_ctx, seed, strlen(seed));
		EVP_DigestUpdate(md_ctx, &be_counter, sizeof(be_counter));

		if (EVP_DigestFinal_ex(md_ctx, hash, &hash_len) != 1)
			goto err;

		to_copy = buflen - copied;
		if (to_copy > hash_len)
			to_copy = hash_len;

		memcpy((unsigned char *)buf + copied, hash, to_copy);
		copied += to_copy;
		counter++;
	}

	EVP_MD_CTX_free(md_ctx);
	return buflen;

err:
	EVP_MD_CTX_free(md_ctx);
	return -EIO;
}

__shr_public int libnvmf_create_raw_secret(struct libnvme_global_ctx *ctx,
		const char *secret, size_t key_len, unsigned char **raw_secret)
{
	__cleanup_free unsigned char *buf = NULL;
	int secret_len = 0, i, err;

	if (key_len != 32 && key_len != 48 && key_len != 64) {
		libnvme_msg(ctx, LIBNVME_LOG_ERR, "Invalid key length %ld", key_len);
		return -EINVAL;
	}

	buf = malloc(key_len);
	if (!buf)
		return -ENOMEM;

	if (!secret) {
		err = getrandom_bytes(buf, key_len);
		if (err < 0)
			return err;

		goto out;
	}

	if (strlen(secret) < 4) {
		libnvme_msg(ctx, LIBNVME_LOG_ERR, "Input secret too short\n");
		return -EINVAL;
	}

	if (!strncmp(secret, "pin:", 4)) {
		err = getswordfish(ctx, &secret[4], buf, key_len);
		if (err < 0)
			return err;

		goto out;
	}

	for (i = 0; i < strlen(secret); i += 2) {
		int hi = shr_hex_to_int(secret[i]);
		int lo = shr_hex_to_int(secret[i + 1]);

		if (hi < 0 || lo < 0) {
			libnvme_msg(ctx, LIBNVME_LOG_ERR,
				"Invalid secret '%s'", secret);
			return -EINVAL;
		}
		if (i >= key_len * 2) {
			libnvme_msg(ctx, LIBNVME_LOG_ERR,
				"Skipping excess secret bytes\n");
			break;
		}
		buf[secret_len++] = (unsigned char)(hi << 4 | lo);
	}
	if (secret_len != key_len) {
		libnvme_msg(ctx, LIBNVME_LOG_ERR,
			"Invalid key length (%d bytes)\n", secret_len);
		return -EINVAL;
	}

out:
	*raw_secret = buf;
	buf = NULL;
	return 0;
}

#endif /* CONFIG_OPENSSL */

static int gen_tls_identity(const char *hostnqn, const char *subsysnqn,
			    int version, int cipher, char *digest,
			    char *identity)
{
	if (version == 0) {
		sprintf(identity, "NVMe%01dR%02d %s %s",
			version, cipher, hostnqn, subsysnqn);
		return strlen(identity);
	}
	if (version > 1 || !digest)
		return -EINVAL;

	sprintf(identity, "NVMe%01dR%02d %s %s %s",
		version, cipher, hostnqn, subsysnqn, digest);
	return strlen(identity);
}

static int derive_nvme_keys(struct libnvme_global_ctx *ctx,
		const char *hostnqn, const char *subsysnqn,
		char *identity, int version,
		int hmac, unsigned char *configured,
		unsigned char *psk, int key_len, bool compat)
{
	__cleanup_free unsigned char *retained = NULL;
	__cleanup_free char *digest = NULL;
	char *context = identity;
	unsigned char cipher;
	int ret = -1;

	if (!hostnqn || !subsysnqn || !identity || !psk)
		return -EINVAL;

	retained = malloc(key_len);
	if (!retained)
		return -ENOMEM;

	if (compat)
		ret = derive_retained_key_compat(ctx, hmac, hostnqn, configured,
						 retained, key_len);
	else
		ret = derive_retained_key(ctx, hmac, hostnqn, configured,
					  retained, key_len);
	if (ret < 0)
		return ret;

	if (hmac == LIBNVMF_HMAC_ALG_NONE)
		cipher = default_hmac(key_len);
	else
		cipher = hmac;

	if (version == 1) {
		size_t digest_len = 2 * key_len;

		digest = malloc(digest_len);
		if (!digest)
			return -ENOMEM;

		ret = derive_psk_digest(ctx, hostnqn, subsysnqn, version,
					cipher, retained, key_len, digest,
					digest_len);
		if (ret < 0)
			return ret;
		context = digest;
	}
	ret = gen_tls_identity(hostnqn, subsysnqn, version, cipher,
			       digest, identity);
	if (ret < 0)
		return ret;
	if (compat)
		return derive_tls_key_compat(ctx, version, cipher, context,
			retained, psk, key_len);
	return derive_tls_key(ctx, version, cipher, context, retained,
			psk, key_len);
}

static ssize_t nvme_identity_len(int hmac, int version, const char *hostnqn,
				 const char *subsysnqn)
{
	ssize_t len;

	if (!hostnqn || !subsysnqn)
		return -EINVAL;

	len = strlen(hostnqn) + strlen(subsysnqn) + 12;
	if (version == 1) {
		len += 66;
		if (hmac == LIBNVMF_HMAC_ALG_SHA2_384)
			len += 32;
	} else if (version > 1) {
		return -EINVAL;
	}
	return len;
}

__shr_public int libnvmf_generate_tls_key_identity(
		struct libnvme_global_ctx *ctx, const char *hostnqn,
		const char *subsysnqn, int version, enum libnvmf_hmac_alg hmac,
		unsigned char *configured_key, int key_len, char **ident)
{
	__cleanup_free unsigned char *psk = NULL;
	__cleanup_free char *identity = NULL;
	ssize_t identity_len;
	int ret;

	identity_len = nvme_identity_len(hmac, version, hostnqn, subsysnqn);
	if (identity_len < 0)
		return -EINVAL;

	identity = malloc(identity_len);
	if (!identity)
		return -ENOMEM;

	psk = malloc(key_len);
	if (!psk)
		return -ENOMEM;

	memset(psk, 0, key_len);
	ret = derive_nvme_keys(ctx, hostnqn, subsysnqn, identity, version, hmac,
			       configured_key, psk, key_len, false);
	if (ret != key_len) {
		if (ret < 0)
			return ret;
		return -ENOKEY;
	}

	*ident = identity;
	identity = NULL;

	return 0;
}

__shr_public int libnvmf_generate_tls_key_identity_compat(
		struct libnvme_global_ctx *ctx, const char *hostnqn,
		const char *subsysnqn, int version, enum libnvmf_hmac_alg hmac,
		unsigned char *configured_key, int key_len, char **ident)
{
	__cleanup_free unsigned char *psk = NULL;
	__cleanup_free char *identity = NULL;
	ssize_t identity_len;
	int ret;

	identity_len = nvme_identity_len(hmac, version, hostnqn, subsysnqn);
	if (identity_len < 0)
		return -EINVAL;

	identity = malloc(identity_len);
	if (!identity)
		return -ENOMEM;

	psk = malloc(key_len);
	if (!psk)
		return -ENOMEM;

	memset(psk, 0, key_len);
	ret = derive_nvme_keys(ctx, hostnqn, subsysnqn, identity, version, hmac,
			       configured_key, psk, key_len, true);
	if (ret != key_len) {
		if (ret < 0)
			return ret;
		return -ENOKEY;
	}

	*ident = identity;
	identity = NULL;

	return 0;
}

#ifdef CONFIG_KEYUTILS
__shr_public int libnvmf_lookup_keyring(
		struct libnvme_global_ctx *ctx, const char *keyring, long *key)
{
	key_serial_t keyring_id;

	if (!keyring)
		keyring = NVME_TLS_DEFAULT_KEYRING;
	keyring_id = find_key_by_type_and_desc("keyring", keyring, 0);
	if (keyring_id < 0)
		return -errno;

	*key = keyring_id;
	return 0;
}

__shr_public char *libnvmf_describe_key_serial(
		struct libnvme_global_ctx *ctx, long key_id)
{
	__cleanup_free char *str = NULL;
	char *last;

	if (keyctl_describe_alloc(key_id, &str) < 0)
		return NULL;

	last = strrchr(str, ';');
	if (!last)
		return NULL;

	last++;
	if (strlen(last) == 0)
		return NULL;

	return strdup(last);
}

__shr_public int libnvmf_lookup_key(
		struct libnvme_global_ctx *ctx, const char *type,
		const char *identity, long *keyp)
{
	key_serial_t key;

	key = keyctl_search(KEY_SPEC_SESSION_KEYRING, type, identity, 0);
	if (key < 0)
		return -errno;

	*keyp = key;
	return 0;
}

__shr_public int libnvmf_set_keyring(
		struct libnvme_global_ctx *ctx, long key_id)
{
	long err;

	if (key_id == 0) {
		if (libnvmf_lookup_keyring(ctx, NULL, &key_id))
			return -ENOKEY;
	}

	err = keyctl_link(key_id, KEY_SPEC_SESSION_KEYRING);
	if (err < 0)
		return -errno;
	return 0;
}

__shr_public int libnvmf_read_key(
		struct libnvme_global_ctx *ctx, long keyring_id, long key_id,
		int *len, unsigned char **key)
{
	void *buffer;
	int ret;

	ret = libnvmf_set_keyring(ctx, keyring_id);
	if (ret < 0)
		return ret;

	ret = keyctl_read_alloc(key_id, &buffer);
	if (ret < 0)
		return ret;

	*len = ret;
	*key = buffer;
	return 0;
}

__shr_public int libnvmf_update_key(
		struct libnvme_global_ctx *ctx, long keyring_id,
		const char *key_type, const char *identity,
		unsigned char *key_data, int key_len, long *keyp)
{
	long key;

	key = keyctl_search(keyring_id, key_type, identity, 0);
	if (key > 0) {
		if (keyctl_revoke(key) < 0)
			return -errno;
	}
	key = add_key(key_type, identity,
		      key_data, key_len, keyring_id);
	if (key < 0)
		return -errno;

	*keyp = key;
	return 0;
}

struct __scan_keys_data {
	struct libnvme_global_ctx *ctx;
	libnvmf_scan_tls_keys_cb_t cb;
	key_serial_t keyring;
	void *data;
};

int __scan_keys_cb(key_serial_t parent, key_serial_t key, char *desc,
		int desc_len, void *data)
{
	struct __scan_keys_data *d = data;
	int ver, hmac, uid, gid, perm;
	char type, *ptr;

	if (desc_len < 6)
		return 0;
	if (sscanf(desc, "psk;%d;%d;%08x;NVMe%01d%c%02d %*s",
		   &uid, &gid, &perm, &ver, &type, &hmac) != 6)
		return 0;
	/* skip key type */
	ptr = strchr(desc, ';');
	if (!ptr)
		return 0;
	/* skip key uid */
	ptr = strchr(ptr + 1, ';');
	if (!ptr)
		return 0;
	/* skip key gid */
	ptr = strchr(ptr + 1, ';');
	if (!ptr)
		return 0;
	/* skip key permissions */
	ptr = strchr(ptr + 1, ';');
	if (!ptr)
		return 0;
	/* Only use the key description for the callback */
	(d->cb)(d->ctx, d->keyring, key, ptr + 1, strlen(ptr) - 1, d->data);
	return 1;
}

__shr_public int libnvmf_scan_tls_keys(
		struct libnvme_global_ctx *ctx, const char *keyring,
		libnvmf_scan_tls_keys_cb_t cb, void *data)
{
	struct __scan_keys_data d;
	long keyring_id;
	int ret;

	ret = libnvmf_lookup_keyring(ctx, keyring, &keyring_id);
	if (ret)
		return ret;

	if (!keyring_id)
		return -EINVAL;

	ret = libnvmf_set_keyring(ctx, keyring_id);
	if (ret < 0)
		return ret;

	d.ctx = ctx;
	d.keyring = keyring_id;
	d.cb = cb;
	d.data = data;
	ret = recursive_key_scan(keyring_id, __scan_keys_cb, &d);
	return ret;
}

static int __nvme_insert_tls_key(struct libnvme_global_ctx *ctx,
		key_serial_t keyring_id, const char *key_type,
		const char *hostnqn, const char *subsysnqn,
		int version, int hmac, unsigned char *configured_key,
		int key_len, bool compat, long *keyp)
{
	__cleanup_free unsigned char *psk = NULL;
	__cleanup_free char *identity = NULL;
	ssize_t identity_len;
	long key;
	int ret;

	identity_len = nvme_identity_len(hmac, version, hostnqn, subsysnqn);
	if (identity_len < 0)
		return identity_len;

	identity = malloc(identity_len);
	if (!identity)
		return -ENOMEM;
	memset(identity, 0, identity_len);

	psk = malloc(key_len);
	if (!psk)
		return -ENOMEM;
	memset(psk, 0, key_len);
	ret = derive_nvme_keys(ctx, hostnqn, subsysnqn, identity, version, hmac,
			       configured_key, psk, key_len, compat);
	if (ret != key_len) {
		if (ret < 0)
			return ret;
		return -ENOKEY;
	}

	ret = libnvmf_update_key(ctx, keyring_id, key_type, identity,
			      psk, key_len, &key);
	if (ret)
		return ret;

	*keyp = key;
	return 0;
}

__shr_public int libnvmf_insert_tls_key_versioned(
		struct libnvme_global_ctx *ctx,
		const char *keyring, const char *key_type,
		const char *hostnqn, const char *subsysnqn,
		int version, enum libnvmf_hmac_alg hmac,
		unsigned char *configured_key, int key_len,
		long *key)
{
	long keyring_id;
	int ret;

	ret = libnvmf_lookup_keyring(ctx, keyring, &keyring_id);
	if (ret)
		return ret;

	ret = libnvmf_set_keyring(ctx, keyring_id);
	if (ret < 0)
		return ret;

	return __nvme_insert_tls_key(ctx, keyring_id, key_type,
		hostnqn, subsysnqn, version, hmac,
		configured_key, key_len, false, key);
}

__shr_public int libnvmf_insert_tls_key_compat(
		struct libnvme_global_ctx *ctx,
		const char *keyring, const char *key_type,
		const char *hostnqn, const char *subsysnqn,
		int version, enum libnvmf_hmac_alg hmac,
		unsigned char *configured_key, int key_len,
		long *key)
{
	long keyring_id;
	int ret;

	ret = libnvmf_lookup_keyring(ctx, keyring, &keyring_id);
	if (ret)
		return ret;

	ret = libnvmf_set_keyring(ctx, keyring_id);
	if (ret < 0)
		return ret;

	return __nvme_insert_tls_key(ctx, keyring_id, key_type,
		hostnqn, subsysnqn, version, hmac,
		configured_key, key_len, true, key);
}

__shr_public int libnvmf_revoke_tls_key(struct libnvme_global_ctx *ctx,
		const char *keyring, const char *key_type,
		const char *identity)
{
	long keyring_id, key;
	int ret;

	ret = libnvmf_lookup_keyring(ctx, keyring, &keyring_id);
	if (ret)
		return ret;

	key = keyctl_search(keyring_id, key_type, identity, 0);
	if (key < 0)
		return -errno;

	key = keyctl_revoke(key);
	if (key < 0)
		return -errno;

	return 0;
}

static int __nvme_import_tls_key(struct libnvme_global_ctx *ctx, long keyring_id,
		const char *hostnqn, const char *subsysnqn,
		const char *identity, const char *key,
		long *keyp)
{
	__cleanup_free unsigned char *key_data = NULL;
	unsigned char version;
	enum libnvmf_hmac_alg hmac;
	size_t key_len;
	int ret;

	ret = libnvmf_import_tls_key_versioned(ctx, key, &version,
					    &hmac, &key_len, &key_data);
	if (ret)
		return ret;

	if (hmac == LIBNVMF_HMAC_ALG_NONE || !identity) {
		/*
		 * This is a configured PSK (hmac 0) or we don't know the
		 * identity and so the assumption is it is also a
		 * configured PSK. Derive a TLS PSK from it and load the
		 * newly created key into the keystore.
		 */
		return __nvme_insert_tls_key(ctx, keyring_id, "psk",
			hostnqn, subsysnqn, version, hmac,
			key_data, key_len, false, keyp);
	}

	return libnvmf_update_key(ctx, keyring_id, "psk", identity,
			      key_data, key_len, keyp);
}

int __libnvmf_import_keys_from_config(struct libnvme_host *h, struct libnvme_ctrl *c,
		long *keyring_id, long *key_id)
{
	const char *hostnqn = libnvme_host_get_hostnqn(h);
	const char *subsysnqn = libnvme_ctrl_get_subsysnqn(c);
	const char *keyring, *key, *identity;
	long kr_id = 0, id = 0;
	int ret;

	if (!hostnqn || !subsysnqn) {
		libnvme_msg(h->ctx, LIBNVME_LOG_ERR, "Invalid NQNs (%s, %s)\n",
			 hostnqn, subsysnqn);
		return -EINVAL;
	}

	/* If we don't have a key avoid all keyring operations */
	key = libnvme_ctrl_get_tls_key(c);
	if (!key)
		goto out;

	if (libnvme_ctrl_get_keyring(c, &keyring, NULL) == 0) {
		ret = libnvmf_lookup_keyring(h->ctx, keyring, &kr_id);
		if (ret)
			return ret;
	} else
		kr_id = c->cfg.keyring_id;

	/*
	 * Fallback to the default keyring. This makes the keyring
	 * explicit on the ctrl rather than left unset, which matters
	 * to anything downstream that inspects this connection's
	 * parameters.
	 */
	if (!kr_id) {
		ret = libnvmf_lookup_keyring(h->ctx, ".nvme", &kr_id);
		if (ret)
			return ret;
	}

	if (libnvmf_set_keyring(h->ctx, kr_id) < 0) {
		libnvme_msg(h->ctx, LIBNVME_LOG_ERR, "Failed to set keyring\n");
		return -errno;
	}

	identity = libnvme_ctrl_get_tls_key_identity(c);
	if (identity) {
		ret = libnvmf_lookup_key(h->ctx, "psk", identity, &id);
		if (ret && !(ret == -ENOKEY || ret == -EKEYREVOKED)) {
			libnvme_msg(h->ctx, LIBNVME_LOG_ERR,
				 "Failed to lookup key for identity %s, error %d\n",
				  identity, ret);
			return ret;
		}
	}

	if (!id) {
		ret = __nvme_import_tls_key(h->ctx, kr_id, hostnqn,
					    subsysnqn, identity, key, &id);
		if (ret) {
			libnvme_msg(h->ctx, LIBNVME_LOG_ERR,
				 "Failed to insert TLS PSK, error %d\n", ret);
			return ret;
		}
	}

out:
	*keyring_id = kr_id;
	*key_id = id;

	return 0;
}
#else
__shr_public int libnvmf_lookup_keyring(
		struct libnvme_global_ctx *ctx, const char *keyring, long *key)
{
	libnvme_msg(ctx, LIBNVME_LOG_ERR, "key operations not supported; "
		 "recompile with keyutils support.\n");
	return -ENOTSUP;
}

__shr_public char *libnvmf_describe_key_serial(
		struct libnvme_global_ctx *ctx, long key_id)
{
	libnvme_msg(ctx, LIBNVME_LOG_ERR, "key operations not supported; "
		 "recompile with keyutils support.\n");
	return NULL;
}

__shr_public int libnvmf_lookup_key(
		struct libnvme_global_ctx *ctx, const char *type,
		const char *identity, long *key)
{
	libnvme_msg(ctx, LIBNVME_LOG_ERR, "key operations not supported; "
		 "recompile with keyutils support.\n");
	return -ENOTSUP;
}

__shr_public int libnvmf_set_keyring(
		struct libnvme_global_ctx *ctx, long key_id)
{
	libnvme_msg(ctx, LIBNVME_LOG_ERR, "key operations not supported; "
		 "recompile with keyutils support.\n");
	return -ENOTSUP;
}

__shr_public int libnvmf_read_key(
		struct libnvme_global_ctx *ctx, long keyring_id, long key_id,
		int *len, unsigned char **key)
{
	libnvme_msg(ctx, LIBNVME_LOG_ERR, "key operations not supported; "
		 "recompile with keyutils support.\n");
	return -ENOTSUP;
}

__shr_public int libnvmf_update_key(
		struct libnvme_global_ctx *ctx, long keyring_id,
		const char *key_type, const char *identity,
		unsigned char *key_data, int key_len, long *key)
{
	libnvme_msg(ctx, LIBNVME_LOG_ERR, "key operations not supported; "
		 "recompile with keyutils support.\n");
	return -ENOTSUP;
}

__shr_public int libnvmf_scan_tls_keys(
		struct libnvme_global_ctx *ctx, const char *keyring,
		libnvmf_scan_tls_keys_cb_t cb, void *data)
{
	libnvme_msg(ctx, LIBNVME_LOG_ERR, "key operations not supported; "
		 "recompile with keyutils support.\n");
	return -ENOTSUP;
}

__shr_public int libnvmf_insert_tls_key_versioned(
		struct libnvme_global_ctx *ctx,
		const char *keyring, const char *key_type,
		const char *hostnqn, const char *subsysnqn,
		int version, enum libnvmf_hmac_alg hmac,
		unsigned char *configured_key, int key_len,
		long *keyp)
{
	libnvme_msg(ctx, LIBNVME_LOG_ERR, "key operations not supported; "
		 "recompile with keyutils support.\n");
	return -ENOTSUP;
}

__shr_public int libnvmf_insert_tls_key_compat(
		struct libnvme_global_ctx *ctx,
		const char *keyring, const char *key_type,
		const char *hostnqn, const char *subsysnqn,
		int version, enum libnvmf_hmac_alg hmac,
		unsigned char *configured_key, int key_len,
		long *keyp)
{
	libnvme_msg(ctx, LIBNVME_LOG_ERR, "key operations not supported; "
		 "recompile with keyutils support.\n");
	return -ENOTSUP;
}

__shr_public int libnvmf_revoke_tls_key(struct libnvme_global_ctx *ctx,
		const char *keyring, const char *key_type,
		const char *identity)
{
	libnvme_msg(ctx, LIBNVME_LOG_ERR, "key operations not supported; "
		 "recompile with keyutils support.\n");
	return -ENOTSUP;
}

int __libnvmf_import_keys_from_config(struct libnvme_host *h, struct libnvme_ctrl *c,
				   long *keyring_id, long *key_id)
{
	*keyring_id = 0;
	*key_id = 0;

	return 0;
}
#endif

__shr_public int libnvmf_insert_tls_key(struct libnvme_global_ctx *ctx,
		const char *keyring, const char *key_type,
		const char *hostnqn, const char *subsysnqn,
		enum libnvmf_hmac_alg hmac,
		unsigned char *configured_key, int key_len, long *key)
{
	return libnvmf_insert_tls_key_versioned(ctx, keyring, key_type,
					     hostnqn, subsysnqn, 0, hmac,
					     configured_key, key_len, key);
}

/*
 * PSK Interchange Format
 * NVMeTLSkey-<v>:<xx>:<s>:
 *
 * x: version as one ASCII char
 * yy: hmac encoded as two ASCII chars
 *     00: no transform ('configured PSK')
 *     01: SHA-256
 *     02: SHA-384
 * s:  32 or 48 bytes binary followed by a CRC-32 of the configured PSK
 *     (4 bytes) encoded as base64
 */
__shr_public int libnvmf_export_tls_key_versioned(
		struct libnvme_global_ctx *ctx, unsigned char version,
		enum libnvmf_hmac_alg hmac, const unsigned char *key_data,
		size_t key_len, char **encoded_keyp)
{
	unsigned int raw_len, encoded_len, len;
	unsigned long crc = shr_crc32(0L, NULL, 0);
	unsigned char raw_secret[52];
	char *encoded_key;

	switch (hmac) {
	case LIBNVMF_HMAC_ALG_NONE:
		if (key_len != 32 && key_len != 48)
			return -EINVAL;
		break;
	case LIBNVMF_HMAC_ALG_SHA2_256:
		if (key_len != 32)
			return -EINVAL;
		break;
	case LIBNVMF_HMAC_ALG_SHA2_384:
		if (key_len != 48)
			return -EINVAL;
		break;
	default:
		return -EINVAL;
	}
	raw_len = key_len;

	memcpy(raw_secret, key_data, raw_len);
	crc = shr_crc32(crc, raw_secret, raw_len);
	raw_secret[raw_len++] = crc & 0xff;
	raw_secret[raw_len++] = (crc >> 8) & 0xff;
	raw_secret[raw_len++] = (crc >> 16) & 0xff;
	raw_secret[raw_len++] = (crc >> 24) & 0xff;

	encoded_len = (raw_len * 2) + 20;
	encoded_key = malloc(encoded_len);
	if (!encoded_key)
		return -ENOMEM;

	memset(encoded_key, 0, encoded_len);
	len = sprintf(encoded_key, "NVMeTLSkey-%x:%02x:", version, hmac);
	len += shr_base64_encode(raw_secret, raw_len, encoded_key + len);
	encoded_key[len++] = ':';
	encoded_key[len++] = '\0';

	*encoded_keyp = encoded_key;
	return 0;
}

__shr_public int libnvmf_export_tls_key(struct libnvme_global_ctx *ctx,
		const unsigned char *key_data, int key_len, char **encoded_keyp)
{
	enum libnvmf_hmac_alg hmac;

	if (key_len == 32)
		hmac = LIBNVMF_HMAC_ALG_SHA2_256;
	else
		hmac = LIBNVMF_HMAC_ALG_SHA2_384;

	return libnvmf_export_tls_key_versioned(ctx, 1, hmac, key_data,
		key_len, encoded_keyp);
}

__shr_public int libnvmf_import_tls_key_versioned(
		struct libnvme_global_ctx *ctx, const char *encoded_key,
		unsigned char *version, enum libnvmf_hmac_alg *hmac,
		size_t *key_len, unsigned char **keyp)
{
	unsigned char decoded_key[128], *key_data;
	unsigned int crc = shr_crc32(0L, NULL, 0);
	unsigned int key_crc;
	int err, _version, _hmac, decoded_len;
	size_t len;

	if (sscanf(encoded_key, "NVMeTLSkey-%d:%02x:*s",
		   &_version, &_hmac) != 2)
		return -EINVAL;

	if (_version != 1)
		return -EINVAL;

	*version = _version;

	len = strlen(encoded_key);
	switch (_hmac) {
	case LIBNVMF_HMAC_ALG_NONE:
		if (len != 65 && len != 89)
			return -EINVAL;
		break;
	case LIBNVMF_HMAC_ALG_SHA2_256:
		if (len != 65)
			return -EINVAL;
		break;
	case LIBNVMF_HMAC_ALG_SHA2_384:
		if (len != 89)
			return -EINVAL;
		break;
	default:
		return -EINVAL;
	}
	*hmac = _hmac;

	err = shr_base64_decode(encoded_key + 16, len - 17, decoded_key);
	if (err < 0)
		return -ENOKEY;

	decoded_len = err;
	decoded_len -= 4;
	if (decoded_len != 32 && decoded_len != 48)
		return -ENOKEY;

	crc = shr_crc32(crc, decoded_key, decoded_len);
	key_crc = ((uint32_t)decoded_key[decoded_len]) |
		((uint32_t)decoded_key[decoded_len + 1] << 8) |
		((uint32_t)decoded_key[decoded_len + 2] << 16) |
		((uint32_t)decoded_key[decoded_len + 3] << 24);
	if (key_crc != crc) {
		libnvme_msg(ctx, LIBNVME_LOG_ERR, "CRC mismatch (key %08x, crc %08x)",
			 key_crc, crc);
		return -ENOKEY;
	}

	key_data = malloc(decoded_len);
	if (!key_data)
		return -ENOMEM;
	memcpy(key_data, decoded_key, decoded_len);

	*key_len = decoded_len;
	*keyp = key_data;
	return 0;
}

__shr_public int libnvmf_import_tls_key(
		struct libnvme_global_ctx *ctx, const char *encoded_key,
		int *key_len, enum libnvmf_hmac_alg *hmac, unsigned char **keyp)
{
	unsigned char version;
	enum libnvmf_hmac_alg _hmac;
	unsigned char *psk;
	size_t len;
	int ret;

	ret = libnvmf_import_tls_key_versioned(ctx, encoded_key, &version,
					    &_hmac, &len, &psk);
	if (ret)
		return ret;

	*hmac = _hmac;
	*key_len = len;
	*keyp = psk;
	return 0;
}

