summaryrefslogtreecommitdiffstats
path: root/src/nvme/linux.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/nvme/linux.c')
-rw-r--r--src/nvme/linux.c1297
1 files changed, 1297 insertions, 0 deletions
diff --git a/src/nvme/linux.c b/src/nvme/linux.c
new file mode 100644
index 0000000..e29d9e7
--- /dev/null
+++ b/src/nvme/linux.c
@@ -0,0 +1,1297 @@
+// 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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#include <sys/param.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#ifdef CONFIG_OPENSSL
+#include <openssl/engine.h>
+#include <openssl/evp.h>
+#include <openssl/hmac.h>
+#include <openssl/kdf.h>
+
+#ifdef CONFIG_OPENSSL_3
+#include <openssl/core_names.h>
+#include <openssl/params.h>
+#endif
+#endif
+
+#ifdef CONFIG_KEYUTILS
+#include <keyutils.h>
+#endif
+
+#include <ccan/endian/endian.h>
+
+#include "cleanup.h"
+#include "linux.h"
+#include "tree.h"
+#include "log.h"
+#include "private.h"
+#include "base64.h"
+
+static int __nvme_open(const char *name)
+{
+ _cleanup_free_ char *path = NULL;
+ int ret;
+
+ ret = asprintf(&path, "%s/%s", "/dev", name);
+ if (ret < 0) {
+ errno = ENOMEM;
+ return -1;
+ }
+
+ return open(path, O_RDONLY);
+}
+
+int nvme_open(const char *name)
+{
+ int ret, fd, id, ns;
+ struct stat stat;
+ bool c;
+
+ ret = sscanf(name, "nvme%dn%d", &id, &ns);
+ if (ret != 1 && ret != 2) {
+ errno = EINVAL;
+ return -1;
+ }
+ c = ret == 1;
+
+ fd = __nvme_open(name);
+ if (fd < 0)
+ return fd;
+
+ ret = fstat(fd, &stat);
+ if (ret < 0)
+ goto close_fd;
+
+ if (c) {
+ if (!S_ISCHR(stat.st_mode)) {
+ errno = EINVAL;
+ goto close_fd;
+ }
+ } else if (!S_ISBLK(stat.st_mode)) {
+ errno = EINVAL;
+ goto close_fd;
+ }
+
+ return fd;
+
+close_fd:
+ close(fd);
+ return -1;
+}
+
+int nvme_fw_download_seq(int fd, __u32 size, __u32 xfer, __u32 offset,
+ void *buf)
+{
+ int err = 0;
+ struct nvme_fw_download_args args = {
+ .args_size = sizeof(args),
+ .fd = fd,
+ .offset = offset,
+ .data_len = xfer,
+ .data = buf,
+ .timeout = NVME_DEFAULT_IOCTL_TIMEOUT,
+ .result = NULL,
+ };
+
+ while (size > 0) {
+ args.data_len = MIN(xfer, size);
+ err = nvme_fw_download(&args);
+ if (err)
+ break;
+
+ args.data += xfer;
+ size -= xfer;
+ args.offset += xfer;
+ }
+
+ return err;
+}
+
+int nvme_get_telemetry_max(int fd, enum nvme_telemetry_da *da, size_t *data_tx)
+{
+ _cleanup_free_ struct nvme_id_ctrl *id_ctrl = NULL;
+ int err;
+
+ id_ctrl = __nvme_alloc(sizeof(*id_ctrl));
+ if (!id_ctrl) {
+ errno = ENOMEM;
+ return -1;
+ }
+ err = nvme_identify_ctrl(fd, id_ctrl);
+ if (err)
+ return err;
+
+ if (data_tx) {
+ *data_tx = id_ctrl->mdts;
+ if (id_ctrl->mdts) {
+ /* assuming CAP.MPSMIN is zero minimum Memory Page Size is at least
+ * 4096 bytes
+ */
+ *data_tx = (1 << id_ctrl->mdts) * 4096;
+ }
+ }
+ if (da) {
+ if (id_ctrl->lpa & 0x8)
+ *da = NVME_TELEMETRY_DA_3;
+ if (id_ctrl->lpa & 0x40)
+ *da = NVME_TELEMETRY_DA_4;
+
+ }
+ return err;
+}
+
+int nvme_get_telemetry_log(int fd, bool create, bool ctrl, bool rae, size_t max_data_tx,
+ enum nvme_telemetry_da da, struct nvme_telemetry_log **buf,
+ size_t *size)
+{
+ static const __u32 xfer = NVME_LOG_TELEM_BLOCK_SIZE;
+
+ struct nvme_telemetry_log *telem;
+ enum nvme_cmd_get_log_lid lid;
+ _cleanup_free_ void *log;
+ void *tmp;
+ int err;
+ size_t dalb;
+ struct nvme_get_log_args args = {
+ .args_size = sizeof(args),
+ .fd = fd,
+ .nsid = NVME_NSID_NONE,
+ .lsp = NVME_LOG_LSP_NONE,
+ .lsi = NVME_LOG_LSI_NONE,
+ .uuidx = NVME_UUID_NONE,
+ .timeout = NVME_DEFAULT_IOCTL_TIMEOUT,
+ .result = NULL,
+ .csi = NVME_CSI_NVM,
+ .rae = rae,
+ .ot = false,
+ };
+
+ *size = 0;
+
+ log = malloc(xfer);
+ if (!log) {
+ errno = ENOMEM;
+ return -1;
+ }
+
+ if (ctrl) {
+ err = nvme_get_log_telemetry_ctrl(fd, true, 0, xfer, log);
+ lid = NVME_LOG_LID_TELEMETRY_CTRL;
+ } else {
+ lid = NVME_LOG_LID_TELEMETRY_HOST;
+ if (create)
+ err = nvme_get_log_create_telemetry_host(fd, log);
+ else
+ err = nvme_get_log_telemetry_host(fd, 0, xfer, log);
+ }
+
+ if (err)
+ return err;
+
+ telem = log;
+ if (ctrl && !telem->ctrlavail) {
+ *buf = log;
+ log = NULL;
+ *size = xfer;
+ return 0;
+ }
+
+ switch (da) {
+ case NVME_TELEMETRY_DA_1:
+ dalb = le16_to_cpu(telem->dalb1);
+ break;
+ case NVME_TELEMETRY_DA_2:
+ dalb = le16_to_cpu(telem->dalb2);
+ break;
+ case NVME_TELEMETRY_DA_3:
+ /* dalb3 >= dalb2 >= dalb1 */
+ dalb = le16_to_cpu(telem->dalb3);
+ break;
+ case NVME_TELEMETRY_DA_4:
+ dalb = le32_to_cpu(telem->dalb4);
+ break;
+ default:
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (dalb == 0) {
+ errno = ENOENT;
+ return -1;
+ }
+
+ *size = (dalb + 1) * xfer;
+ tmp = realloc(log, *size);
+ if (!tmp) {
+ errno = ENOMEM;
+ return -1;
+ }
+ log = tmp;
+
+ args.lid = lid;
+ args.log = log;
+ args.len = *size;
+ err = nvme_get_log_page(fd, max_data_tx, &args);
+ if (err)
+ return err;
+
+ *buf = log;
+ log = NULL;
+ return 0;
+}
+
+
+static int nvme_check_get_telemetry_log(int fd, bool create, bool ctrl, bool rae,
+ struct nvme_telemetry_log **log, enum nvme_telemetry_da da,
+ size_t *size)
+{
+ enum nvme_telemetry_da max_da = 0;
+ int err = nvme_get_telemetry_max(fd, &max_da, NULL);
+
+ if (err)
+ return err;
+ if (da > max_da) {
+ errno = ENOENT;
+ return -1;
+ }
+ return nvme_get_telemetry_log(fd, create, ctrl, rae, 4096, da, log, size);
+}
+
+
+int nvme_get_ctrl_telemetry(int fd, bool rae, struct nvme_telemetry_log **log,
+ enum nvme_telemetry_da da, size_t *size)
+{
+ return nvme_check_get_telemetry_log(fd, false, true, rae, log, da, size);
+}
+
+int nvme_get_host_telemetry(int fd, struct nvme_telemetry_log **log,
+ enum nvme_telemetry_da da, size_t *size)
+{
+ return nvme_check_get_telemetry_log(fd, false, false, false, log, da, size);
+}
+
+int nvme_get_new_host_telemetry(int fd, struct nvme_telemetry_log **log,
+ enum nvme_telemetry_da da, size_t *size)
+{
+ return nvme_check_get_telemetry_log(fd, true, false, false, log, da, size);
+}
+
+int nvme_get_lba_status_log(int fd, bool rae, struct nvme_lba_status_log **log)
+{
+ __u32 size;
+ _cleanup_free_ struct nvme_lba_status_log *buf;
+ void *tmp;
+ int err;
+ struct nvme_get_log_args args = {
+ .args_size = sizeof(args),
+ .fd = fd,
+ .nsid = NVME_NSID_NONE,
+ .lsp = NVME_LOG_LSP_NONE,
+ .lsi = NVME_LOG_LSI_NONE,
+ .uuidx = NVME_UUID_NONE,
+ .timeout = NVME_DEFAULT_IOCTL_TIMEOUT,
+ .result = NULL,
+ .csi = NVME_CSI_NVM,
+ .rae = rae,
+ .ot = false,
+ };
+
+ buf = malloc(sizeof(*buf));
+ if (!buf)
+ return -1;
+
+ err = nvme_get_log_lba_status(fd, true, 0, sizeof(*buf), buf);
+ if (err) {
+ *log = NULL;
+ return err;
+ }
+
+ size = le32_to_cpu(buf->lslplen);
+ if (!size) {
+ *log = buf;
+ buf = NULL;
+ return 0;
+ }
+
+ tmp = realloc(buf, size);
+ if (!tmp) {
+ *log = NULL;
+ return -1;
+ }
+ buf = tmp;
+
+ args.lid = NVME_LOG_LID_LBA_STATUS;
+ args.log = buf;
+ args.len = size;
+ err = nvme_get_log_page(fd, 4096, &args);
+ if (err) {
+ *log = NULL;
+ return err;
+ }
+
+ *log = buf;
+ buf = NULL;
+ return 0;
+}
+
+static int nvme_ns_attachment(int fd, __u32 nsid, __u16 num_ctrls,
+ __u16 *ctrlist, bool attach, __u32 timeout)
+{
+ struct nvme_ctrl_list cntlist = { 0 };
+ struct nvme_ns_attach_args args = {
+ .args_size = sizeof(args),
+ .fd = fd,
+ .nsid = nsid,
+ .sel = NVME_NS_ATTACH_SEL_CTRL_DEATTACH,
+ .ctrlist = &cntlist,
+ .timeout = timeout,
+ };
+
+ if (attach)
+ args.sel = NVME_NS_ATTACH_SEL_CTRL_ATTACH;
+
+ nvme_init_ctrl_list(args.ctrlist, num_ctrls, ctrlist);
+ return nvme_ns_attach(&args);
+}
+
+int nvme_namespace_attach_ctrls(int fd, __u32 nsid, __u16 num_ctrls,
+ __u16 *ctrlist)
+{
+ return nvme_ns_attachment(fd, nsid, num_ctrls, ctrlist, true,
+ NVME_DEFAULT_IOCTL_TIMEOUT);
+}
+
+int nvme_namespace_detach_ctrls(int fd, __u32 nsid, __u16 num_ctrls,
+ __u16 *ctrlist)
+{
+ return nvme_ns_attachment(fd, nsid, num_ctrls, ctrlist, false,
+ NVME_DEFAULT_IOCTL_TIMEOUT);
+}
+
+int nvme_get_ana_log_len(int fd, size_t *analen)
+{
+ _cleanup_free_ struct nvme_id_ctrl *ctrl = NULL;
+ int ret;
+
+ ctrl = __nvme_alloc(sizeof(*ctrl));
+ if (!ctrl) {
+ errno = ENOMEM;
+ return -1;
+ }
+ ret = nvme_identify_ctrl(fd, ctrl);
+ if (ret)
+ return ret;
+
+ *analen = sizeof(struct nvme_ana_log) +
+ le32_to_cpu(ctrl->nanagrpid) * sizeof(struct nvme_ana_group_desc) +
+ le32_to_cpu(ctrl->mnan) * sizeof(__le32);
+ return 0;
+}
+
+int nvme_get_logical_block_size(int fd, __u32 nsid, int *blksize)
+{
+ _cleanup_free_ struct nvme_id_ns *ns = NULL;
+ __u8 flbas;
+ int ret;
+
+ ns = __nvme_alloc(sizeof(*ns));
+ if (!ns) {
+ errno = ENOMEM;
+ return -1;
+ }
+ ret = nvme_identify_ns(fd, nsid, ns);
+ if (ret)
+ return ret;
+
+ nvme_id_ns_flbas_to_lbaf_inuse(ns->flbas, &flbas);
+ *blksize = 1 << ns->lbaf[flbas].ds;
+
+ return 0;
+}
+
+static int __nvme_set_attr(const char *path, const char *value)
+{
+ _cleanup_fd_ int fd = -1;
+
+ fd = open(path, O_WRONLY);
+ if (fd < 0) {
+#if 0
+ nvme_msg(LOG_DEBUG, "Failed to open %s: %s\n", path,
+ strerror(errno));
+#endif
+ return -1;
+ }
+ return write(fd, value, strlen(value));
+}
+
+int nvme_set_attr(const char *dir, const char *attr, const char *value)
+{
+ _cleanup_free_ char *path = NULL;
+ int ret;
+
+ ret = asprintf(&path, "%s/%s", dir, attr);
+ if (ret < 0)
+ return -1;
+
+ return __nvme_set_attr(path, value);
+}
+
+static char *__nvme_get_attr(const char *path)
+{
+ char value[4096] = { 0 };
+ int ret, fd;
+ int saved_errno;
+
+ fd = open(path, O_RDONLY);
+ if (fd < 0)
+ return NULL;
+
+ ret = read(fd, value, sizeof(value) - 1);
+ saved_errno = errno;
+ close(fd);
+ if (ret < 0) {
+ errno = saved_errno;
+ return NULL;
+ }
+ errno = 0;
+ if (!strlen(value))
+ return NULL;
+
+ if (value[strlen(value) - 1] == '\n')
+ value[strlen(value) - 1] = '\0';
+ while (strlen(value) > 0 && value[strlen(value) - 1] == ' ')
+ value[strlen(value) - 1] = '\0';
+
+ return strlen(value) ? strdup(value) : NULL;
+}
+
+char *nvme_get_attr(const char *dir, const char *attr)
+{
+ _cleanup_free_ char *path = NULL;
+ int ret;
+
+ ret = asprintf(&path, "%s/%s", dir, attr);
+ if (ret < 0) {
+ errno = ENOMEM;
+ return NULL;
+ }
+
+ return __nvme_get_attr(path);
+}
+
+char *nvme_get_subsys_attr(nvme_subsystem_t s, const char *attr)
+{
+ return nvme_get_attr(nvme_subsystem_get_sysfs_dir(s), attr);
+}
+
+char *nvme_get_ctrl_attr(nvme_ctrl_t c, const char *attr)
+{
+ return nvme_get_attr(nvme_ctrl_get_sysfs_dir(c), attr);
+}
+
+char *nvme_get_ns_attr(nvme_ns_t n, const char *attr)
+{
+ return nvme_get_attr(nvme_ns_get_sysfs_dir(n), attr);
+}
+
+char *nvme_get_path_attr(nvme_path_t p, const char *attr)
+{
+ return nvme_get_attr(nvme_path_get_sysfs_dir(p), attr);
+}
+
+#ifndef CONFIG_OPENSSL
+int nvme_gen_dhchap_key(char *hostnqn, enum nvme_hmac_alg hmac,
+ unsigned int key_len, unsigned char *secret,
+ unsigned char *key)
+{
+ if (hmac != NVME_HMAC_ALG_NONE) {
+ nvme_msg(NULL, LOG_ERR, "HMAC transformation not supported; " \
+ "recompile with OpenSSL support.\n");
+ errno = -EINVAL;
+ return -1;
+ }
+
+ memcpy(key, secret, key_len);
+ return 0;
+}
+
+static int derive_retained_key(int hmac, const char *hostnqn,
+ unsigned char *generated,
+ unsigned char *retained,
+ size_t key_len)
+{
+ nvme_msg(NULL, LOG_ERR, "NVMe TLS is not supported; "
+ "recompile with OpenSSL support.\n");
+ errno = ENOTSUP;
+ return -1;
+}
+
+static int gen_tls_identity(const char *hostnqn, const char *subsysnqn,
+ int version, int hmac, char *identity,
+ unsigned char *retained, size_t key_len)
+{
+ if (version != 0) {
+ nvme_msg(NULL, LOG_ERR, "NVMe TLS 2.0 is not supported; "
+ "recompile with OpenSSL support.\n");
+ errno = ENOTSUP;
+ return -1;
+ }
+ sprintf(identity, "NVMe0R%02d %s %s",
+ hmac, hostnqn, subsysnqn);
+ return strlen(identity);
+}
+
+static int derive_tls_key(int hmac, const char *identity,
+ unsigned char *retained,
+ unsigned char *psk, size_t key_len)
+{
+ nvme_msg(NULL, LOG_ERR, "NVMe TLS is not supported; "
+ "recompile with OpenSSL support.\n");
+ errno = ENOTSUP;
+ return -1;
+}
+#else /* CONFIG_OPENSSL */
+static const EVP_MD *select_hmac(int hmac, size_t *key_len)
+{
+ const EVP_MD *md = NULL;
+
+ switch (hmac) {
+ case NVME_HMAC_ALG_SHA2_256:
+ md = EVP_sha256();
+ *key_len = 32;
+ break;
+ case NVME_HMAC_ALG_SHA2_384:
+ md = EVP_sha384();
+ *key_len = 48;
+ break;
+ default:
+ 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)
+
+static int derive_retained_key(int hmac, const char *hostnqn,
+ unsigned char *generated,
+ unsigned char *retained,
+ size_t key_len)
+{
+ const EVP_MD *md;
+ _cleanup_evp_pkey_ctx_ EVP_PKEY_CTX *ctx = NULL;
+ uint16_t length = key_len & 0xFFFF;
+ size_t hmac_len;
+
+ md = select_hmac(hmac, &hmac_len);
+ if (!md || hmac_len > key_len) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, NULL);
+ if (!ctx) {
+ errno = ENOMEM;
+ return -1;
+ }
+
+ if (EVP_PKEY_derive_init(ctx) <= 0) {
+ errno = ENOMEM;
+ return -1;
+ }
+ if (EVP_PKEY_CTX_set_hkdf_md(ctx, md) <= 0) {
+ errno = ENOKEY;
+ return -1;
+ }
+ if (EVP_PKEY_CTX_set1_hkdf_key(ctx, generated, key_len) <= 0) {
+ errno = ENOKEY;
+ return -1;
+ }
+ if (EVP_PKEY_CTX_add1_hkdf_info(ctx,
+ (const unsigned char *)&length, 2) <= 0) {
+ errno = ENOKEY;
+ return -1;
+ }
+ if (EVP_PKEY_CTX_add1_hkdf_info(ctx,
+ (const unsigned char *)"tls13 ", 6) <= 0) {
+ errno = ENOKEY;
+ return -1;
+ }
+ if (EVP_PKEY_CTX_add1_hkdf_info(ctx,
+ (const unsigned char *)"HostNQN", 7) <= 0) {
+ errno = ENOKEY;
+ return -1;
+ }
+ if (EVP_PKEY_CTX_add1_hkdf_info(ctx,
+ (const unsigned char *)hostnqn, strlen(hostnqn)) <= 0) {
+ errno = ENOKEY;
+ return -1;
+ }
+
+ if (EVP_PKEY_derive(ctx, retained, &key_len) <= 0) {
+ errno = ENOKEY;
+ return -1;
+ }
+
+ return key_len;
+}
+
+static int derive_tls_key(int hmac, const char *identity,
+ unsigned char *retained,
+ unsigned char *psk, size_t key_len)
+{
+ const EVP_MD *md;
+ _cleanup_evp_pkey_ctx_ EVP_PKEY_CTX *ctx = NULL;
+ size_t hmac_len;
+ uint16_t length = key_len & 0xFFFF;
+
+ md = select_hmac(hmac, &hmac_len);
+ if (!md || hmac_len > key_len) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, NULL);
+ if (!ctx) {
+ errno = ENOMEM;
+ return -1;
+ }
+
+ if (EVP_PKEY_derive_init(ctx) <= 0) {
+ errno = ENOMEM;
+ return -1;
+ }
+ if (EVP_PKEY_CTX_set_hkdf_md(ctx, md) <= 0) {
+ errno = ENOKEY;
+ return -1;
+ }
+ if (EVP_PKEY_CTX_set1_hkdf_key(ctx, retained, key_len) <= 0) {
+ errno = ENOKEY;
+ return -1;
+ }
+ if (EVP_PKEY_CTX_add1_hkdf_info(ctx,
+ (const unsigned char *)&length, 2) <= 0) {
+ errno = ENOKEY;
+ return -1;
+ }
+ if (EVP_PKEY_CTX_add1_hkdf_info(ctx,
+ (const unsigned char *)"tls13 ", 6) <= 0) {
+ errno = ENOKEY;
+ return -1;
+ }
+ if (EVP_PKEY_CTX_add1_hkdf_info(ctx,
+ (const unsigned char *)"nvme-tls-psk", 12) <= 0) {
+ errno = ENOKEY;
+ return -1;
+ }
+ if (EVP_PKEY_CTX_add1_hkdf_info(ctx,
+ (const unsigned char *)identity,
+ strlen(identity)) <= 0) {
+ errno = ENOKEY;
+ return -1;
+ }
+
+ if (EVP_PKEY_derive(ctx, psk, &key_len) <= 0) {
+ errno = ENOKEY;
+ return -1;
+ }
+
+ return key_len;
+}
+#endif /* CONFIG_OPENSSL */
+
+#ifdef CONFIG_OPENSSL_1
+static DEFINE_CLEANUP_FUNC(cleanup_hmac_ctx, HMAC_CTX *, HMAC_CTX_free)
+#define _cleanup_hmac_ctx_ __cleanup__(cleanup_hmac_ctx)
+
+int nvme_gen_dhchap_key(char *hostnqn, enum nvme_hmac_alg hmac,
+ unsigned int key_len, unsigned char *secret,
+ unsigned char *key)
+{
+ const char hmac_seed[] = "NVMe-over-Fabrics";
+ _cleanup_hmac_ctx_ HMAC_CTX *hmac_ctx = NULL;
+ const EVP_MD *md;
+
+ ENGINE_load_builtin_engines();
+ ENGINE_register_all_complete();
+
+ hmac_ctx = HMAC_CTX_new();
+ if (!hmac_ctx) {
+ errno = ENOMEM;
+ return -1;
+ }
+
+ switch (hmac) {
+ case NVME_HMAC_ALG_NONE:
+ memcpy(key, secret, key_len);
+ return 0;
+ case NVME_HMAC_ALG_SHA2_256:
+ md = EVP_sha256();
+ break;
+ case NVME_HMAC_ALG_SHA2_384:
+ md = EVP_sha384();
+ break;
+ case NVME_HMAC_ALG_SHA2_512:
+ md = EVP_sha512();
+ break;
+ default:
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (!md) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (!HMAC_Init_ex(hmac_ctx, secret, key_len, md, NULL)) {
+ errno = ENOMEM;
+ return -1;
+ }
+
+ if (!HMAC_Update(hmac_ctx, (unsigned char *)hostnqn,
+ strlen(hostnqn))) {
+ errno = ENOKEY;
+ return -1;
+ }
+
+ if (!HMAC_Update(hmac_ctx, (unsigned char *)hmac_seed,
+ strlen(hmac_seed))) {
+ errno = ENOKEY;
+ return -1;
+ }
+
+ if (!HMAC_Final(hmac_ctx, key, &key_len)) {
+ errno = ENOKEY;
+ return -1;
+ }
+
+ return 0;
+}
+
+static int gen_tls_identity(const char *hostnqn, const char *subsysnqn,
+ int version, int hmac, char *identity,
+ unsigned char *retained, size_t key_len)
+{
+ static const char hmac_seed[] = "NVMe-over-Fabrics";
+ size_t hmac_len;
+ const EVP_MD *md = select_hmac(hmac, &hmac_len);
+ _cleanup_hmac_ctx_ HMAC_CTX *hmac_ctx = NULL;
+ _cleanup_free_ unsigned char *psk_ctx = NULL;
+ _cleanup_free_ char *enc_ctx = NULL;
+ size_t len;
+
+ if (version == 0) {
+ sprintf(identity, "NVMe%01dR%02d %s %s",
+ version, hmac, hostnqn, subsysnqn);
+ return strlen(identity);
+ }
+ if (version > 1) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ hmac_ctx = HMAC_CTX_new();
+ if (!hmac_ctx) {
+ errno = ENOMEM;
+ return -1;
+ }
+ if (!md) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ psk_ctx = malloc(key_len);
+ if (!psk_ctx) {
+ errno = ENOMEM;
+ return -1;
+ }
+ if (!HMAC_Init_ex(hmac_ctx, retained, key_len, md, NULL)) {
+ errno = ENOMEM;
+ return -1;
+ }
+ if (!HMAC_Update(hmac_ctx, (unsigned char *)hostnqn,
+ strlen(hostnqn))) {
+ errno = ENOKEY;
+ return -1;
+ }
+ if (!HMAC_Update(hmac_ctx, (unsigned char *)" ", 1)) {
+ errno = ENOKEY;
+ return -1;
+ }
+ if (!HMAC_Update(hmac_ctx, (unsigned char *)subsysnqn,
+ strlen(subsysnqn))) {
+ errno = ENOKEY;
+ return -1;
+ }
+ if (!HMAC_Update(hmac_ctx, (unsigned char *)" ", 1)) {
+ errno = ENOKEY;
+ return -1;
+ }
+ if (!HMAC_Update(hmac_ctx, (unsigned char *)hmac_seed,
+ strlen(hmac_seed))) {
+ errno = ENOKEY;
+ return -1;
+ }
+ if (!HMAC_Final(hmac_ctx, psk_ctx, (unsigned int *)&key_len)) {
+ errno = ENOKEY;
+ return -1;
+ }
+ enc_ctx = malloc(key_len * 2);
+ memset(enc_ctx, 0, key_len * 2);
+ len = base64_encode(psk_ctx, key_len, enc_ctx);
+ if (len < 0) {
+ errno = ENOKEY;
+ return len;
+ }
+ sprintf(identity, "NVMe%01dR%02d %s %s %s",
+ version, hmac, hostnqn, subsysnqn, enc_ctx);
+ return strlen(identity);
+}
+#endif /* !CONFIG_OPENSSL_1 */
+
+#ifdef CONFIG_OPENSSL_3
+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)
+
+int nvme_gen_dhchap_key(char *hostnqn, enum nvme_hmac_alg hmac,
+ unsigned int key_len, unsigned char *secret,
+ unsigned char *key)
+{
+ const char hmac_seed[] = "NVMe-over-Fabrics";
+ OSSL_PARAM params[2], *p = params;
+ _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;
+ char *progq = NULL;
+ char *digest;
+ size_t len;
+
+ lib_ctx = OSSL_LIB_CTX_new();
+ if (!lib_ctx) {
+ errno = ENOMEM;
+ return -1;
+ }
+
+ mac = EVP_MAC_fetch(lib_ctx, OSSL_MAC_NAME_HMAC, progq);
+ if (!mac) {
+ errno = ENOMEM;
+ return -1;
+ }
+
+ mac_ctx = EVP_MAC_CTX_new(mac);
+ if (!mac_ctx) {
+ errno = ENOMEM;
+ return -1;
+ }
+
+ switch (hmac) {
+ case NVME_HMAC_ALG_NONE:
+ memcpy(key, secret, key_len);
+ return 0;
+ case NVME_HMAC_ALG_SHA2_256:
+ digest = OSSL_DIGEST_NAME_SHA2_256;
+ break;
+ case NVME_HMAC_ALG_SHA2_384:
+ digest = OSSL_DIGEST_NAME_SHA2_384;
+ break;
+ case NVME_HMAC_ALG_SHA2_512:
+ digest = OSSL_DIGEST_NAME_SHA2_512;
+ break;
+ default:
+ errno = EINVAL;
+ return -1;
+ }
+ *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)) {
+ errno = ENOKEY;
+ return -1;
+ }
+
+ if (!EVP_MAC_update(mac_ctx, (unsigned char *)hostnqn,
+ strlen(hostnqn))) {
+ errno = ENOKEY;
+ return -1;
+ }
+
+ if (!EVP_MAC_update(mac_ctx, (unsigned char *)hmac_seed,
+ strlen(hmac_seed))) {
+ errno = ENOKEY;
+ return -1;
+ }
+
+ if (!EVP_MAC_final(mac_ctx, key, &len, key_len)) {
+ errno = ENOKEY;
+ return -1;
+ }
+
+ if (len != key_len) {
+ errno = EMSGSIZE;
+ return -1;
+ }
+
+ return 0;
+}
+
+static int gen_tls_identity(const char *hostnqn, const char *subsysnqn,
+ int version, int hmac, char *identity,
+ unsigned char *retained, size_t key_len)
+{
+ static const char hmac_seed[] = "NVMe-over-Fabrics";
+ size_t hmac_len;
+ OSSL_PARAM params[2], *p = params;
+ _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;
+ char *progq = NULL;
+ char *digest = NULL;
+ _cleanup_free_ unsigned char *psk_ctx = NULL;
+ _cleanup_free_ char *enc_ctx = NULL;
+ size_t len;
+
+ if (version == 0) {
+ sprintf(identity, "NVMe%01dR%02d %s %s",
+ version, hmac, hostnqn, subsysnqn);
+ return strlen(identity);
+ }
+ if (version > 1) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ lib_ctx = OSSL_LIB_CTX_new();
+ if (!lib_ctx) {
+ errno = ENOMEM;
+ return -1;
+ }
+ mac = EVP_MAC_fetch(lib_ctx, OSSL_MAC_NAME_HMAC, progq);
+ if (!mac) {
+ errno = ENOMEM;
+ return -1;
+ }
+
+ mac_ctx = EVP_MAC_CTX_new(mac);
+ if (!mac_ctx) {
+ errno = ENOMEM;
+ return -1;
+ }
+ switch (hmac) {
+ case NVME_HMAC_ALG_SHA2_256:
+ digest = OSSL_DIGEST_NAME_SHA2_256;
+ break;
+ case NVME_HMAC_ALG_SHA2_384:
+ digest = OSSL_DIGEST_NAME_SHA2_384;
+ break;
+ default:
+ errno = EINVAL;
+ break;
+ }
+ if (!digest)
+ return -1;
+ *p++ = OSSL_PARAM_construct_utf8_string(OSSL_MAC_PARAM_DIGEST,
+ digest, 0);
+ *p = OSSL_PARAM_construct_end();
+
+ psk_ctx = malloc(key_len);
+ if (!psk_ctx) {
+ errno = ENOMEM;
+ return -1;
+ }
+
+ if (!EVP_MAC_init(mac_ctx, retained, key_len, params)) {
+ errno = ENOKEY;
+ return -1;
+ }
+ if (!EVP_MAC_update(mac_ctx, (unsigned char *)hostnqn,
+ strlen(hostnqn))) {
+ errno = ENOKEY;
+ return -1;
+ }
+ if (!EVP_MAC_update(mac_ctx, (unsigned char *)" ", 1)) {
+ errno = ENOKEY;
+ return -1;
+ }
+ if (!EVP_MAC_update(mac_ctx, (unsigned char *)subsysnqn,
+ strlen(subsysnqn))) {
+ errno = ENOKEY;
+ return -1;
+ }
+ if (!EVP_MAC_update(mac_ctx, (unsigned char *)" ", 1)) {
+ errno = ENOKEY;
+ return -1;
+ }
+ if (!EVP_MAC_update(mac_ctx, (unsigned char *)hmac_seed,
+ strlen(hmac_seed))) {
+ errno = ENOKEY;
+ return -1;
+ }
+ if (!EVP_MAC_final(mac_ctx, psk_ctx, &hmac_len, key_len)) {
+ errno = ENOKEY;
+ return -1;
+ }
+ if (hmac_len > key_len) {
+ errno = EMSGSIZE;
+ return -1;
+ }
+ enc_ctx = malloc(hmac_len * 2);
+ memset(enc_ctx, 0, hmac_len * 2);
+ len = base64_encode(psk_ctx, hmac_len, enc_ctx);
+ if (len < 0) {
+ errno = ENOKEY;
+ return len;
+ }
+ sprintf(identity, "NVMe%01dR%02d %s %s %s",
+ version, hmac, hostnqn, subsysnqn, enc_ctx);
+ return strlen(identity);
+}
+#endif /* !CONFIG_OPENSSL_3 */
+
+static int derive_nvme_keys(const char *hostnqn, const char *subsysnqn,
+ char *identity, int version,
+ int hmac, unsigned char *configured,
+ unsigned char *psk, int key_len)
+{
+ _cleanup_free_ unsigned char *retained = NULL;
+ int ret = -1;
+
+ if (!hostnqn || !subsysnqn || !identity || !psk) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ retained = malloc(key_len);
+ if (!retained) {
+ errno = ENOMEM;
+ return -1;
+ }
+ ret = derive_retained_key(hmac, hostnqn, configured, retained, key_len);
+ if (ret < 0)
+ return ret;
+ ret = gen_tls_identity(hostnqn, subsysnqn, version, hmac,
+ identity, retained, key_len);
+ if (ret < 0)
+ return ret;
+ return derive_tls_key(hmac, identity, retained, psk, key_len);
+}
+
+static size_t nvme_identity_len(int hmac, int version, const char *hostnqn,
+ const char *subsysnqn)
+{
+ size_t len;
+
+ if (!hostnqn || !subsysnqn) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ len = strlen(hostnqn) + strlen(subsysnqn) + 12;
+ if (version == 1) {
+ len += 66;
+ if (hmac == NVME_HMAC_ALG_SHA2_384)
+ len += 32;
+ } else if (version > 1) {
+ errno = EINVAL;
+ return -1;
+ }
+ return len;
+}
+
+char *nvme_generate_tls_key_identity(const char *hostnqn, const char *subsysnqn,
+ int version, int hmac,
+ unsigned char *configured_key, int key_len)
+{
+ char *identity;
+ size_t identity_len;
+ _cleanup_free_ unsigned char *psk = NULL;
+ int ret = -1;
+
+ identity_len = nvme_identity_len(hmac, version, hostnqn, subsysnqn);
+ if (identity_len < 0)
+ return NULL;
+
+ identity = malloc(identity_len);
+ if (!identity)
+ return NULL;
+
+ psk = malloc(key_len);
+ if (!psk)
+ goto out_free_identity;
+
+ memset(psk, 0, key_len);
+ ret = derive_nvme_keys(hostnqn, subsysnqn, identity, version, hmac,
+ configured_key, psk, key_len);
+out_free_identity:
+ if (ret < 0) {
+ free(identity);
+ identity = NULL;
+ }
+ return identity;
+}
+
+#ifdef CONFIG_KEYUTILS
+long nvme_lookup_keyring(const char *keyring)
+{
+ key_serial_t keyring_id;
+
+ keyring_id = find_key_by_type_and_desc("keyring", keyring, 0);
+ if (keyring_id < 0)
+ return 0;
+ return keyring_id;
+}
+
+char *nvme_describe_key_serial(long key_id)
+{
+ char *desc;
+
+ if (keyctl_describe_alloc(key_id, &desc) < 0)
+ desc = NULL;
+ return desc;
+}
+
+long nvme_lookup_key(const char *type, const char *identity)
+{
+ key_serial_t key;
+
+ key = keyctl_search(KEY_SPEC_SESSION_KEYRING, type, identity, 0);
+ if (key < 0)
+ return 0;
+ return key;
+}
+
+int nvme_set_keyring(long key_id)
+{
+ long err;
+
+ err = keyctl_link(key_id, KEY_SPEC_SESSION_KEYRING);
+ if (err < 0)
+ return -1;
+ return 0;
+}
+
+long nvme_insert_tls_key_versioned(const char *keyring, const char *key_type,
+ const char *hostnqn, const char *subsysnqn,
+ int version, int hmac,
+ unsigned char *configured_key, int key_len)
+{
+ key_serial_t keyring_id, key;
+ _cleanup_free_ char *identity = NULL;
+ size_t identity_len;
+ _cleanup_free_ unsigned char *psk = NULL;
+ int ret = -1;
+
+ keyring_id = nvme_lookup_keyring(keyring);
+ if (keyring_id == 0)
+ return -1;
+
+ identity_len = nvme_identity_len(hmac, version, hostnqn, subsysnqn);
+ if (identity_len < 0)
+ return -1;
+
+ identity = malloc(identity_len);
+ if (!identity) {
+ errno = ENOMEM;
+ return -1;
+ }
+
+ psk = malloc(key_len);
+ if (!psk) {
+ errno = ENOMEM;
+ return 0;
+ }
+ memset(psk, 0, key_len);
+ ret = derive_nvme_keys(hostnqn, subsysnqn, identity, version, hmac,
+ configured_key, psk, key_len);
+ if (ret != key_len)
+ return 0;
+
+ key = keyctl_search(keyring_id, key_type, identity, 0);
+ if (key > 0) {
+ if (keyctl_update(key, psk, key_len) < 0)
+ key = 0;
+ } else {
+ key = add_key(key_type, identity,
+ psk, key_len, keyring_id);
+ if (key < 0)
+ key = 0;
+ }
+ return key;
+}
+
+#else
+long nvme_lookup_keyring(const char *keyring)
+{
+ nvme_msg(NULL, LOG_ERR, "key operations not supported; "\
+ "recompile with keyutils support.\n");
+ errno = ENOTSUP;
+ return 0;
+}
+
+char *nvme_describe_key_serial(long key_id)
+{
+ nvme_msg(NULL, LOG_ERR, "key operations not supported; "\
+ "recompile with keyutils support.\n");
+ errno = ENOTSUP;
+ return NULL;
+}
+
+long nvme_lookup_key(const char *type, const char *identity)
+{
+ nvme_msg(NULL, LOG_ERR, "key operations not supported; "\
+ "recompile with keyutils support.\n");
+ errno = ENOTSUP;
+ return 0;
+}
+
+int nvme_set_keyring(long key_id)
+{
+ nvme_msg(NULL, LOG_ERR, "key operations not supported; "\
+ "recompile with keyutils support.\n");
+ errno = ENOTSUP;
+ return -1;
+}
+
+long nvme_insert_tls_key_versioned(const char *keyring, const char *key_type,
+ const char *hostnqn, const char *subsysnqn,
+ int version, int hmac,
+ unsigned char *configured_key, int key_len)
+{
+ nvme_msg(NULL, LOG_ERR, "key operations not supported; "
+ "recompile with keyutils support.\n");
+ errno = ENOTSUP;
+ return -1;
+}
+#endif
+
+long nvme_insert_tls_key(const char *keyring, const char *key_type,
+ const char *hostnqn, const char *subsysnqn, int hmac,
+ unsigned char *configured_key, int key_len)
+{
+ return nvme_insert_tls_key_versioned(keyring, key_type,
+ hostnqn, subsysnqn, 0, hmac,
+ configured_key, key_len);
+}