// 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 * Chaitanya Kulkarni */ #include #include #include #include #include #include #include #include #ifdef CONFIG_OPENSSL #include #include #include #include #ifdef CONFIG_OPENSSL_3 #include #include #endif #endif #ifdef CONFIG_KEYUTILS #include #define NVME_TLS_DEFAULT_KEYRING ".nvme" #endif #include #include "cleanup.h" #include "linux.h" #include "tree.h" #include "log.h" #include "private.h" #include "base64.h" #include "crc32.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 = __nvme_alloc(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 = __nvme_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; if (!keyring) keyring = NVME_TLS_DEFAULT_KEYRING; 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; if (key_id == 0) { key_id = nvme_lookup_keyring(NULL); if (key_id == 0) { errno = ENOKEY; return -1; } } err = keyctl_link(key_id, KEY_SPEC_SESSION_KEYRING); if (err < 0) return -1; return 0; } unsigned char *nvme_read_key(long keyring_id, long key_id, int *len) { void *buffer; int ret; ret = nvme_set_keyring(keyring_id); if (ret < 0) { errno = -ret; return NULL; } ret = keyctl_read_alloc(key_id, &buffer); if (ret < 0) { errno = -ret; buffer = NULL; } else *len = ret; return buffer; } long nvme_update_key(long keyring_id, const char *key_type, const char *identity, unsigned char *key_data, int key_len) { long key; key = keyctl_search(keyring_id, key_type, identity, 0); if (key > 0) { if (keyctl_revoke(key) < 0) return 0; } key = add_key(key_type, identity, key_data, key_len, keyring_id); if (key < 0) key = 0; return key; } struct __scan_keys_data { nvme_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->keyring, key, ptr + 1, strlen(ptr) - 1, d->data); return 1; } int nvme_scan_tls_keys(const char *keyring, nvme_scan_tls_keys_cb_t cb, void *data) { struct __scan_keys_data d; key_serial_t keyring_id = nvme_lookup_keyring(keyring); int ret; if (!keyring_id) { errno = EINVAL; return -1; } ret = nvme_set_keyring(keyring_id); if (ret < 0) return ret; d.keyring = keyring_id; d.cb = cb; d.data = data; ret = recursive_key_scan(keyring_id, __scan_keys_cb, &d); return ret; } 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; keyring_id = nvme_lookup_keyring(keyring); if (keyring_id == 0) { errno = ENOKEY; return 0; } ret = nvme_set_keyring(keyring_id); if (ret < 0) return 0; identity_len = nvme_identity_len(hmac, version, hostnqn, subsysnqn); if (identity_len < 0) return 0; identity = malloc(identity_len); if (!identity) { errno = ENOMEM; return 0; } memset(identity, 0, identity_len); 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) { errno = ENOKEY; return 0; } key = nvme_update_key(keyring_id, key_type, identity, psk, key_len); 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; } unsigned char *nvme_read_key(long keyring_id, long key_id, int *len) { errno = ENOTSUP; return NULL; } long nvme_update_key(long keyring_id, const char *key_type, const char *identity, unsigned char *key_data, int key_len) { errno = ENOTSUP; return 0; } int nvme_scan_tls_keys(const char *keyring, nvme_scan_tls_keys_cb_t cb, void *data) { 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); } char *nvme_export_tls_key(const unsigned char *key_data, int key_len) { unsigned char raw_secret[52]; char *encoded_key; unsigned int raw_len, encoded_len, len; unsigned long crc = crc32(0L, NULL, 0); if (key_len == 32) { raw_len = 32; } else if (key_len == 48) { raw_len = 48; } else { errno = EINVAL; return NULL; } memcpy(raw_secret, key_data, raw_len); crc = 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) { errno = ENOMEM; return NULL; } memset(encoded_key, 0, encoded_len); len = sprintf(encoded_key, "NVMeTLSkey-1:%02x:", key_len == 32 ? 1 : 2); len += base64_encode(raw_secret, raw_len, encoded_key + len); encoded_key[len++] = ':'; encoded_key[len++] = '\0'; return encoded_key; } unsigned char *nvme_import_tls_key(const char *encoded_key, int *key_len, unsigned int *hmac) { unsigned char decoded_key[128], *key_data; unsigned int crc = crc32(0L, NULL, 0); unsigned int key_crc; int err, decoded_len; if (sscanf(encoded_key, "NVMeTLSkey-1:%02x:*s", &err) != 1) { errno = EINVAL; return NULL; } switch (err) { case 1: if (strlen(encoded_key) != 65) { errno = EINVAL; return NULL; } break; case 2: if (strlen(encoded_key) != 89) { errno = EINVAL; return NULL; } break; default: errno = EINVAL; return NULL; } *hmac = err; err = base64_decode(encoded_key + 16, strlen(encoded_key) - 17, decoded_key); if (err < 0) { errno = ENOKEY; return NULL; } decoded_len = err; decoded_len -= 4; if (decoded_len != 32 && decoded_len != 48) { errno = ENOKEY; return NULL; } crc = crc32(crc, decoded_key, decoded_len); key_crc = ((u_int32_t)decoded_key[decoded_len]) | ((u_int32_t)decoded_key[decoded_len + 1] << 8) | ((u_int32_t)decoded_key[decoded_len + 2] << 16) | ((u_int32_t)decoded_key[decoded_len + 3] << 24); if (key_crc != crc) { nvme_msg(NULL, LOG_ERR, "CRC mismatch (key %08x, crc %08x)", key_crc, crc); errno = ENOKEY; return NULL; } key_data = malloc(decoded_len); if (!key_data) { errno = ENOMEM; return NULL; } memcpy(key_data, decoded_key, decoded_len); *key_len = decoded_len; return key_data; }