* [PATCH] nvme-auth: Hash DH shared secret to create session key
@ 2026-03-11 1:21 Chris Leech
2026-03-11 5:13 ` Eric Biggers
` (2 more replies)
0 siblings, 3 replies; 7+ messages in thread
From: Chris Leech @ 2026-03-11 1:21 UTC (permalink / raw)
To: linux-nvme, Hannes Reinecke
Cc: Keith Busch, Christoph Hellwig, Sagi Grimberg, Chaitanya Kulkarni,
Eric Biggers
The NVMe Base Specification 8.3.5.5.9 states that the session key Ks
shall be computed from the ephemeral DH key by applying the hash
function selected by the HashID parameter.
The current implementation stores the raw DH shared secret as the
session key without hashing it. This causes redundant hash operations:
1. Augmented challenge computation (section 8.3.5.5.4) requires
Ca = HMAC(H(g^xy mod p), C). The code compensates by hashing the
unhashed session key in nvme_auth_augmented_challenge() to produce
the correct result.
2. PSK generation (section 8.3.5.5.9) requires PSK = HMAC(Ks, C1 || C2)
where Ks should already be H(g^xy mod p). As the DH shared secret
is always larger than the HMAC block size, HMAC internally hashes
it before use, accidentally producing the correct result.
When using secure channel concatenation with bidirectional
authentication, this results in hashing the DH value three times: twice
for augmented challenge calculations and once during PSK generation.
Fix this by:
- Modifying nvme_auth_gen_shared_secret() to hash the DH shared secret
once after computation: Ks = H(g^xy mod p)
- Removing the hash operation from nvme_auth_augmented_challenge()
as the session key is now already hashed
- Updating session key buffer size from DH key size to hash output size
- Adding specification references in comments
This avoid storing the raw DH shared secret and reduces the number of
hash operations from three to one when using secure channel
concatenation.
Signed-off-by: Chris Leech <cleech@redhat.com>
---
created on the nvme-7.1 branch
this depends on Eric Biggers "nvme-auth use crypto library" patches
from <20260302075959.338638-1-ebiggers@kernel.org>
drivers/nvme/common/auth.c | 88 ++++++++++++++++++++++++++++++--------
drivers/nvme/host/auth.c | 13 +++---
drivers/nvme/target/auth.c | 15 ++++---
include/linux/nvme-auth.h | 6 +--
4 files changed, 89 insertions(+), 33 deletions(-)
diff --git a/drivers/nvme/common/auth.c b/drivers/nvme/common/auth.c
index 2d325fb930836..9f585d1e21915 100644
--- a/drivers/nvme/common/auth.c
+++ b/drivers/nvme/common/auth.c
@@ -351,18 +351,29 @@ struct nvme_dhchap_key *nvme_auth_transform_key(
}
EXPORT_SYMBOL_GPL(nvme_auth_transform_key);
+/**
+ * nvme_auth_augmented_challenge() - Compute the augmented DH-HMAC-CHAP challenge
+ * @hmac_id: Hash algorithm identifier
+ * @skey: Session key
+ * @skey_len: Length of @skey
+ * @challenge: Challenge value
+ * @aug: Output buffer for the augmented challenge
+ * @hlen: Hash output length (length of @challenge and @aug)
+ *
+ * NVMe base specification 8.3.5.5.4: The augmented challenge is computed
+ * applying the HMAC function using the hash function H() selected by the
+ * HashID parameter ... with the hash of the ephemeral DH key ... as HMAC key
+ * to the challenge C (i.e., Ca = HMAC(H(g^xy mod p), C)).
+ *
+ * As the session key skey is already H(g^xy mod p) per section 8.3.5.5.9, use
+ * it directly as the HMAC key without additional hashing.
+ *
+ * Return: 0 on success, negative errno on failure.
+ */
int nvme_auth_augmented_challenge(u8 hmac_id, const u8 *skey, size_t skey_len,
const u8 *challenge, u8 *aug, size_t hlen)
{
- u8 hashed_key[NVME_AUTH_MAX_DIGEST_SIZE];
- int ret;
-
- ret = nvme_auth_hash(hmac_id, skey, skey_len, hashed_key);
- if (ret)
- return ret;
- ret = nvme_auth_hmac(hmac_id, hashed_key, hlen, challenge, hlen, aug);
- memzero_explicit(hashed_key, sizeof(hashed_key));
- return ret;
+ return nvme_auth_hmac(hmac_id, skey, skey_len, challenge, hlen, aug);
}
EXPORT_SYMBOL_GPL(nvme_auth_augmented_challenge);
@@ -403,33 +414,76 @@ int nvme_auth_gen_pubkey(struct crypto_kpp *dh_tfm,
}
EXPORT_SYMBOL_GPL(nvme_auth_gen_pubkey);
-int nvme_auth_gen_shared_secret(struct crypto_kpp *dh_tfm,
+/**
+ * nvme_auth_gen_session_key() - Generate an ephemeral session key
+ * @dh_tfm: Diffie-Hellman transform with local private key already set
+ * @ctrl_key: Peer's public key
+ * @ctrl_key_len: Length of @ctrl_key
+ * @sess_key: Output buffer for the session key
+ * @sess_key_len: Size of @sess_key buffer
+ * @hash_id: Hash algorithm identifier
+ *
+ * NVMe base specification 8.3.5.5.9: The session key Ks shall be computed from
+ * the ephemeral DH key (i.e., g^xy mod p) ... by applying the hash function
+ * H() selected by the HashID parameter ... (i.e., Ks = H(g^xy mod p)).
+ *
+ * Return: 0 on success, negative errno on failure.
+ */
+int nvme_auth_gen_session_key(struct crypto_kpp *dh_tfm,
const u8 *ctrl_key, size_t ctrl_key_len,
- u8 *sess_key, size_t sess_key_len)
+ u8 *sess_key, size_t sess_key_len, u8 hash_id)
{
struct kpp_request *req;
struct crypto_wait wait;
struct scatterlist src, dst;
+ u8 *dh_secret;
+ size_t dh_secret_len, hash_len;
int ret;
- req = kpp_request_alloc(dh_tfm, GFP_KERNEL);
- if (!req)
+ hash_len = nvme_auth_hmac_hash_len(hash_id);
+ if (!hash_len) {
+ pr_warn("%s: invalid hash algorithm %d\n", __func__, hash_id);
+ return -EINVAL;
+ }
+
+ if (sess_key_len < hash_len) {
+ pr_warn("%s: sess_key buffer too small (%zu < %zu)\n",
+ __func__, sess_key_len, hash_len);
+ return -EINVAL;
+ }
+
+ dh_secret_len = crypto_kpp_maxsize(dh_tfm);
+ dh_secret = kzalloc(dh_secret_len, GFP_KERNEL);
+ if (!dh_secret)
return -ENOMEM;
+ req = kpp_request_alloc(dh_tfm, GFP_KERNEL);
+ if (!req) {
+ ret = -ENOMEM;
+ goto out_free_secret;
+ }
+
crypto_init_wait(&wait);
sg_init_one(&src, ctrl_key, ctrl_key_len);
kpp_request_set_input(req, &src, ctrl_key_len);
- sg_init_one(&dst, sess_key, sess_key_len);
- kpp_request_set_output(req, &dst, sess_key_len);
+ sg_init_one(&dst, dh_secret, dh_secret_len);
+ kpp_request_set_output(req, &dst, dh_secret_len);
kpp_request_set_callback(req, CRYPTO_TFM_REQ_MAY_BACKLOG,
crypto_req_done, &wait);
ret = crypto_wait_req(crypto_kpp_compute_shared_secret(req), &wait);
-
kpp_request_free(req);
+
+ if (ret)
+ goto out_free_secret;
+
+ ret = nvme_auth_hash(hash_id, dh_secret, dh_secret_len, sess_key);
+
+out_free_secret:
+ kfree_sensitive(dh_secret);
return ret;
}
-EXPORT_SYMBOL_GPL(nvme_auth_gen_shared_secret);
+EXPORT_SYMBOL_GPL(nvme_auth_gen_session_key);
int nvme_auth_parse_key(const char *secret, struct nvme_dhchap_key **ret_key)
{
diff --git a/drivers/nvme/host/auth.c b/drivers/nvme/host/auth.c
index a85646891656a..7d9658149d146 100644
--- a/drivers/nvme/host/auth.c
+++ b/drivers/nvme/host/auth.c
@@ -587,7 +587,7 @@ static int nvme_auth_dhchap_exponential(struct nvme_ctrl *ctrl,
}
gen_sesskey:
- chap->sess_key_len = chap->host_key_len;
+ chap->sess_key_len = chap->hash_len;
chap->sess_key = kmalloc(chap->sess_key_len, GFP_KERNEL);
if (!chap->sess_key) {
chap->sess_key_len = 0;
@@ -595,16 +595,17 @@ static int nvme_auth_dhchap_exponential(struct nvme_ctrl *ctrl,
return -ENOMEM;
}
- ret = nvme_auth_gen_shared_secret(chap->dh_tfm,
- chap->ctrl_key, chap->ctrl_key_len,
- chap->sess_key, chap->sess_key_len);
+ ret = nvme_auth_gen_session_key(chap->dh_tfm,
+ chap->ctrl_key, chap->ctrl_key_len,
+ chap->sess_key, chap->sess_key_len,
+ chap->hash_id);
if (ret) {
dev_dbg(ctrl->device,
- "failed to generate shared secret, error %d\n", ret);
+ "failed to generate session key, error %d\n", ret);
chap->status = NVME_AUTH_DHCHAP_FAILURE_INCORRECT_PAYLOAD;
return ret;
}
- dev_dbg(ctrl->device, "shared secret %*ph\n",
+ dev_dbg(ctrl->device, "session key %*ph\n",
(int)chap->sess_key_len, chap->sess_key);
return 0;
}
diff --git a/drivers/nvme/target/auth.c b/drivers/nvme/target/auth.c
index b34610e2f19d4..b7e0f313aca59 100644
--- a/drivers/nvme/target/auth.c
+++ b/drivers/nvme/target/auth.c
@@ -449,18 +449,19 @@ int nvmet_auth_ctrl_sesskey(struct nvmet_req *req,
struct nvmet_ctrl *ctrl = req->sq->ctrl;
int ret;
- req->sq->dhchap_skey_len = ctrl->dh_keysize;
+ req->sq->dhchap_skey_len = nvme_auth_hmac_hash_len(ctrl->shash_id);
req->sq->dhchap_skey = kzalloc(req->sq->dhchap_skey_len, GFP_KERNEL);
if (!req->sq->dhchap_skey)
return -ENOMEM;
- ret = nvme_auth_gen_shared_secret(ctrl->dh_tfm,
- pkey, pkey_size,
- req->sq->dhchap_skey,
- req->sq->dhchap_skey_len);
+ ret = nvme_auth_gen_session_key(ctrl->dh_tfm,
+ pkey, pkey_size,
+ req->sq->dhchap_skey,
+ req->sq->dhchap_skey_len,
+ ctrl->shash_id);
if (ret)
- pr_debug("failed to compute shared secret, err %d\n", ret);
+ pr_debug("failed to compute session key, err %d\n", ret);
else
- pr_debug("%s: shared secret %*ph\n", __func__,
+ pr_debug("%s: session key %*ph\n", __func__,
(int)req->sq->dhchap_skey_len,
req->sq->dhchap_skey);
diff --git a/include/linux/nvme-auth.h b/include/linux/nvme-auth.h
index 184a1f9510fad..9ed8c759eb13e 100644
--- a/include/linux/nvme-auth.h
+++ b/include/linux/nvme-auth.h
@@ -49,9 +49,9 @@ int nvme_auth_augmented_challenge(u8 hmac_id, const u8 *skey, size_t skey_len,
int nvme_auth_gen_privkey(struct crypto_kpp *dh_tfm, u8 dh_gid);
int nvme_auth_gen_pubkey(struct crypto_kpp *dh_tfm,
u8 *host_key, size_t host_key_len);
-int nvme_auth_gen_shared_secret(struct crypto_kpp *dh_tfm,
- const u8 *ctrl_key, size_t ctrl_key_len,
- u8 *sess_key, size_t sess_key_len);
+int nvme_auth_gen_session_key(struct crypto_kpp *dh_tfm,
+ const u8 *ctrl_key, size_t ctrl_key_len,
+ u8 *sess_key, size_t sess_key_len, u8 hash_id);
int nvme_auth_generate_psk(u8 hmac_id, const u8 *skey, size_t skey_len,
const u8 *c1, const u8 *c2, size_t hash_len,
u8 **ret_psk, size_t *ret_len);
base-commit: c8414ea09f238d924a7ed04049c1893a188c0ee3
--
2.53.0
^ permalink raw reply related [flat|nested] 7+ messages in thread
* Re: [PATCH] nvme-auth: Hash DH shared secret to create session key
2026-03-11 1:21 [PATCH] nvme-auth: Hash DH shared secret to create session key Chris Leech
@ 2026-03-11 5:13 ` Eric Biggers
2026-03-11 15:47 ` Martin George
2026-03-11 23:16 ` [PATCH v2] " Chris Leech
2 siblings, 0 replies; 7+ messages in thread
From: Eric Biggers @ 2026-03-11 5:13 UTC (permalink / raw)
To: Chris Leech
Cc: linux-nvme, Hannes Reinecke, Keith Busch, Christoph Hellwig,
Sagi Grimberg, Chaitanya Kulkarni
On Tue, Mar 10, 2026 at 06:21:16PM -0700, Chris Leech wrote:
> +/**
> + * nvme_auth_gen_session_key() - Generate an ephemeral session key
> + * @dh_tfm: Diffie-Hellman transform with local private key already set
> + * @ctrl_key: Peer's public key
> + * @ctrl_key_len: Length of @ctrl_key
> + * @sess_key: Output buffer for the session key
> + * @sess_key_len: Size of @sess_key buffer
> + * @hash_id: Hash algorithm identifier
> + *
> + * NVMe base specification 8.3.5.5.9: The session key Ks shall be computed from
> + * the ephemeral DH key (i.e., g^xy mod p) ... by applying the hash function
> + * H() selected by the HashID parameter ... (i.e., Ks = H(g^xy mod p)).
> + *
> + * Return: 0 on success, negative errno on failure.
> + */
> +int nvme_auth_gen_session_key(struct crypto_kpp *dh_tfm,
> const u8 *ctrl_key, size_t ctrl_key_len,
ctrl_key and ctrl_key_len should be public_key and public_key_len. It
is the public key of the other side, which can be either the host or the
controller.
> + hash_len = nvme_auth_hmac_hash_len(hash_id);
> + if (!hash_len) {
> + pr_warn("%s: invalid hash algorithm %d\n", __func__, hash_id);
> + return -EINVAL;
> + }
> +
> + if (sess_key_len < hash_len) {
> + pr_warn("%s: sess_key buffer too small (%zu < %zu)\n",
> + __func__, sess_key_len, hash_len);
> + return -EINVAL;
> + }
Probably should be tightened to 'sess_key_len != hash_len', since this
function writes to exactly hash_len bytes and does not report the length
it actually wrote.
- Eric
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH] nvme-auth: Hash DH shared secret to create session key
2026-03-11 1:21 [PATCH] nvme-auth: Hash DH shared secret to create session key Chris Leech
2026-03-11 5:13 ` Eric Biggers
@ 2026-03-11 15:47 ` Martin George
2026-03-11 16:44 ` Chris Leech
2026-03-11 23:16 ` [PATCH v2] " Chris Leech
2 siblings, 1 reply; 7+ messages in thread
From: Martin George @ 2026-03-11 15:47 UTC (permalink / raw)
To: Chris Leech, linux-nvme, Hannes Reinecke
Cc: Keith Busch, Christoph Hellwig, Sagi Grimberg, Chaitanya Kulkarni,
Eric Biggers
On Tue, 2026-03-10 at 18:21 -0700, Chris Leech wrote:
> The NVMe Base Specification 8.3.5.5.9 states that the session key Ks
> shall be computed from the ephemeral DH key by applying the hash
> function selected by the HashID parameter.
>
> The current implementation stores the raw DH shared secret as the
> session key without hashing it. This causes redundant hash
> operations:
>
> 1. Augmented challenge computation (section 8.3.5.5.4) requires
> Ca = HMAC(H(g^xy mod p), C). The code compensates by hashing the
> unhashed session key in nvme_auth_augmented_challenge() to produce
> the correct result.
>
> 2. PSK generation (section 8.3.5.5.9) requires PSK = HMAC(Ks, C1 ||
> C2)
> where Ks should already be H(g^xy mod p). As the DH shared secret
> is always larger than the HMAC block size, HMAC internally hashes
> it before use, accidentally producing the correct result.
>
> When using secure channel concatenation with bidirectional
> authentication, this results in hashing the DH value three times:
> twice
> for augmented challenge calculations and once during PSK generation.
>
But secure channel concatenation is supported only with unidirectional
auth and not really with bidirectional auth, isn't it? As per NVMe base
spec 2.1, section 8.3.4.5.9 Generated PSK for TLS:
"The host may request secure channel concatenation with the TLS
protocol by setting the SC_C field in the AUTH_Negotiate message to
NEWTLSPSK while performing only unidirectional authentication. In this
case the host shall still send a challenge value C2 to the controller
and clear the sequence number S2 to 0h to indicate that controller
authentication is not requested."
-Martin
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH] nvme-auth: Hash DH shared secret to create session key
2026-03-11 15:47 ` Martin George
@ 2026-03-11 16:44 ` Chris Leech
2026-03-12 6:57 ` Hannes Reinecke
0 siblings, 1 reply; 7+ messages in thread
From: Chris Leech @ 2026-03-11 16:44 UTC (permalink / raw)
To: Martin George
Cc: linux-nvme, Hannes Reinecke, Keith Busch, Christoph Hellwig,
Sagi Grimberg, Chaitanya Kulkarni, Eric Biggers
On Wed, Mar 11, 2026 at 09:17:49PM +0530, Martin George wrote:
> On Tue, 2026-03-10 at 18:21 -0700, Chris Leech wrote:
> > When using secure channel concatenation with bidirectional
> > authentication, this results in hashing the DH value three times:
> > twice
> > for augmented challenge calculations and once during PSK generation.
> >
>
> But secure channel concatenation is supported only with unidirectional
> auth and not really with bidirectional auth, isn't it? As per NVMe base
> spec 2.1, section 8.3.4.5.9 Generated PSK for TLS:
>
> "The host may request secure channel concatenation with the TLS
> protocol by setting the SC_C field in the AUTH_Negotiate message to
> NEWTLSPSK while performing only unidirectional authentication. In this
> case the host shall still send a challenge value C2 to the controller
> and clear the sequence number S2 to 0h to indicate that controller
> authentication is not requested."
I believe this is specifying how secure channel concatenation can work
"while performing only unidirectional authentication", not that it's
only allowed with unidirectional. It's a special case where C2 is sent
in Reply without expecting an R2 value in Success1 or to send Success2.
It would be counterproductive to forbid the host from authenticating the
controller when a secure channel is desired.
- Chris
^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCH v2] nvme-auth: Hash DH shared secret to create session key
2026-03-11 1:21 [PATCH] nvme-auth: Hash DH shared secret to create session key Chris Leech
2026-03-11 5:13 ` Eric Biggers
2026-03-11 15:47 ` Martin George
@ 2026-03-11 23:16 ` Chris Leech
2026-03-12 4:09 ` Eric Biggers
2 siblings, 1 reply; 7+ messages in thread
From: Chris Leech @ 2026-03-11 23:16 UTC (permalink / raw)
To: linux-nvme, Hannes Reinecke
Cc: Keith Busch, Christoph Hellwig, Sagi Grimberg, Chaitanya Kulkarni,
Eric Biggers
The NVMe Base Specification 8.3.5.5.9 states that the session key Ks
shall be computed from the ephemeral DH key by applying the hash
function selected by the HashID parameter.
The current implementation stores the raw DH shared secret as the
session key without hashing it. This causes redundant hash operations:
1. Augmented challenge computation (section 8.3.5.5.4) requires
Ca = HMAC(H(g^xy mod p), C). The code compensates by hashing the
unhashed session key in nvme_auth_augmented_challenge() to produce
the correct result.
2. PSK generation (section 8.3.5.5.9) requires PSK = HMAC(Ks, C1 || C2)
where Ks should already be H(g^xy mod p). As the DH shared secret
is always larger than the HMAC block size, HMAC internally hashes
it before use, accidentally producing the correct result.
When using secure channel concatenation with bidirectional
authentication, this results in hashing the DH value three times: twice
for augmented challenge calculations and once during PSK generation.
Fix this by:
- Modifying nvme_auth_gen_shared_secret() to hash the DH shared secret
once after computation: Ks = H(g^xy mod p)
- Removing the hash operation from nvme_auth_augmented_challenge()
as the session key is now already hashed
- Updating session key buffer size from DH key size to hash output size
- Adding specification references in comments
This avoid storing the raw DH shared secret and reduces the number of
hash operations from three to one when using secure channel
concatenation.
Signed-off-by: Chris Leech <cleech@redhat.com>
---
v2:
- Renamed nvme_auth_gen_session_key() arguments from ctrl_key to
public_key, as it is the peers public DH key and this code is also
used on the target/controller side when the peer is the host.
- Changed argument validation from erroring on sess_key_len < hash_len
to sess_key_len != hash_len to be more precise in calling requirements
created on the nvme-7.1 branch
this depends on Eric Biggers "nvme-auth use crypto library" patches
from <20260302075959.338638-1-ebiggers@kernel.org>
drivers/nvme/common/auth.c | 88 ++++++++++++++++++++++++++++++--------
drivers/nvme/host/auth.c | 13 +++---
drivers/nvme/target/auth.c | 15 +++---
include/linux/nvme-auth.h | 6 +--
4 files changed, 92 insertions(+), 36 deletions(-)
diff --git a/drivers/nvme/common/auth.c b/drivers/nvme/common/auth.c
index 2d325fb930836..77f1d22512f8f 100644
--- a/drivers/nvme/common/auth.c
+++ b/drivers/nvme/common/auth.c
@@ -351,18 +351,29 @@ struct nvme_dhchap_key *nvme_auth_transform_key(
}
EXPORT_SYMBOL_GPL(nvme_auth_transform_key);
+/**
+ * nvme_auth_augmented_challenge() - Compute the augmented DH-HMAC-CHAP challenge
+ * @hmac_id: Hash algorithm identifier
+ * @skey: Session key
+ * @skey_len: Length of @skey
+ * @challenge: Challenge value
+ * @aug: Output buffer for the augmented challenge
+ * @hlen: Hash output length (length of @challenge and @aug)
+ *
+ * NVMe base specification 8.3.5.5.4: The augmented challenge is computed
+ * applying the HMAC function using the hash function H() selected by the
+ * HashID parameter ... with the hash of the ephemeral DH key ... as HMAC key
+ * to the challenge C (i.e., Ca = HMAC(H(g^xy mod p), C)).
+ *
+ * As the session key skey is already H(g^xy mod p) per section 8.3.5.5.9, use
+ * it directly as the HMAC key without additional hashing.
+ *
+ * Return: 0 on success, negative errno on failure.
+ */
int nvme_auth_augmented_challenge(u8 hmac_id, const u8 *skey, size_t skey_len,
const u8 *challenge, u8 *aug, size_t hlen)
{
- u8 hashed_key[NVME_AUTH_MAX_DIGEST_SIZE];
- int ret;
-
- ret = nvme_auth_hash(hmac_id, skey, skey_len, hashed_key);
- if (ret)
- return ret;
- ret = nvme_auth_hmac(hmac_id, hashed_key, hlen, challenge, hlen, aug);
- memzero_explicit(hashed_key, sizeof(hashed_key));
- return ret;
+ return nvme_auth_hmac(hmac_id, skey, skey_len, challenge, hlen, aug);
}
EXPORT_SYMBOL_GPL(nvme_auth_augmented_challenge);
@@ -403,33 +414,76 @@ int nvme_auth_gen_pubkey(struct crypto_kpp *dh_tfm,
}
EXPORT_SYMBOL_GPL(nvme_auth_gen_pubkey);
-int nvme_auth_gen_shared_secret(struct crypto_kpp *dh_tfm,
- const u8 *ctrl_key, size_t ctrl_key_len,
- u8 *sess_key, size_t sess_key_len)
+/**
+ * nvme_auth_gen_session_key() - Generate an ephemeral session key
+ * @dh_tfm: Diffie-Hellman transform with local private key already set
+ * @public_key: Peer's public key
+ * @public_key_len: Length of @public_key
+ * @sess_key: Output buffer for the session key
+ * @sess_key_len: Size of @sess_key buffer
+ * @hash_id: Hash algorithm identifier
+ *
+ * NVMe base specification 8.3.5.5.9: The session key Ks shall be computed from
+ * the ephemeral DH key (i.e., g^xy mod p) ... by applying the hash function
+ * H() selected by the HashID parameter ... (i.e., Ks = H(g^xy mod p)).
+ *
+ * Return: 0 on success, negative errno on failure.
+ */
+int nvme_auth_gen_session_key(struct crypto_kpp *dh_tfm,
+ const u8 *public_key, size_t public_key_len,
+ u8 *sess_key, size_t sess_key_len, u8 hash_id)
{
struct kpp_request *req;
struct crypto_wait wait;
struct scatterlist src, dst;
+ u8 *dh_secret;
+ size_t dh_secret_len, hash_len;
int ret;
- req = kpp_request_alloc(dh_tfm, GFP_KERNEL);
- if (!req)
+ hash_len = nvme_auth_hmac_hash_len(hash_id);
+ if (!hash_len) {
+ pr_warn("%s: invalid hash algorithm %d\n", __func__, hash_id);
+ return -EINVAL;
+ }
+
+ if (sess_key_len != hash_len) {
+ pr_warn("%s: sess_key buffer missized (%zu != %zu)\n",
+ __func__, sess_key_len, hash_len);
+ return -EINVAL;
+ }
+
+ dh_secret_len = crypto_kpp_maxsize(dh_tfm);
+ dh_secret = kzalloc(dh_secret_len, GFP_KERNEL);
+ if (!dh_secret)
return -ENOMEM;
+ req = kpp_request_alloc(dh_tfm, GFP_KERNEL);
+ if (!req) {
+ ret = -ENOMEM;
+ goto out_free_secret;
+ }
+
crypto_init_wait(&wait);
- sg_init_one(&src, ctrl_key, ctrl_key_len);
- kpp_request_set_input(req, &src, ctrl_key_len);
- sg_init_one(&dst, sess_key, sess_key_len);
- kpp_request_set_output(req, &dst, sess_key_len);
+ sg_init_one(&src, public_key, public_key_len);
+ kpp_request_set_input(req, &src, public_key_len);
+ sg_init_one(&dst, dh_secret, dh_secret_len);
+ kpp_request_set_output(req, &dst, dh_secret_len);
kpp_request_set_callback(req, CRYPTO_TFM_REQ_MAY_BACKLOG,
crypto_req_done, &wait);
ret = crypto_wait_req(crypto_kpp_compute_shared_secret(req), &wait);
-
kpp_request_free(req);
+
+ if (ret)
+ goto out_free_secret;
+
+ ret = nvme_auth_hash(hash_id, dh_secret, dh_secret_len, sess_key);
+
+out_free_secret:
+ kfree_sensitive(dh_secret);
return ret;
}
-EXPORT_SYMBOL_GPL(nvme_auth_gen_shared_secret);
+EXPORT_SYMBOL_GPL(nvme_auth_gen_session_key);
int nvme_auth_parse_key(const char *secret, struct nvme_dhchap_key **ret_key)
{
diff --git a/drivers/nvme/host/auth.c b/drivers/nvme/host/auth.c
index a85646891656a..7d9658149d146 100644
--- a/drivers/nvme/host/auth.c
+++ b/drivers/nvme/host/auth.c
@@ -587,7 +587,7 @@ static int nvme_auth_dhchap_exponential(struct nvme_ctrl *ctrl,
}
gen_sesskey:
- chap->sess_key_len = chap->host_key_len;
+ chap->sess_key_len = chap->hash_len;
chap->sess_key = kmalloc(chap->sess_key_len, GFP_KERNEL);
if (!chap->sess_key) {
chap->sess_key_len = 0;
@@ -595,16 +595,17 @@ static int nvme_auth_dhchap_exponential(struct nvme_ctrl *ctrl,
return -ENOMEM;
}
- ret = nvme_auth_gen_shared_secret(chap->dh_tfm,
- chap->ctrl_key, chap->ctrl_key_len,
- chap->sess_key, chap->sess_key_len);
+ ret = nvme_auth_gen_session_key(chap->dh_tfm,
+ chap->ctrl_key, chap->ctrl_key_len,
+ chap->sess_key, chap->sess_key_len,
+ chap->hash_id);
if (ret) {
dev_dbg(ctrl->device,
- "failed to generate shared secret, error %d\n", ret);
+ "failed to generate session key, error %d\n", ret);
chap->status = NVME_AUTH_DHCHAP_FAILURE_INCORRECT_PAYLOAD;
return ret;
}
- dev_dbg(ctrl->device, "shared secret %*ph\n",
+ dev_dbg(ctrl->device, "session key %*ph\n",
(int)chap->sess_key_len, chap->sess_key);
return 0;
}
diff --git a/drivers/nvme/target/auth.c b/drivers/nvme/target/auth.c
index b34610e2f19d4..b7e0f313aca59 100644
--- a/drivers/nvme/target/auth.c
+++ b/drivers/nvme/target/auth.c
@@ -449,18 +449,19 @@ int nvmet_auth_ctrl_sesskey(struct nvmet_req *req,
struct nvmet_ctrl *ctrl = req->sq->ctrl;
int ret;
- req->sq->dhchap_skey_len = ctrl->dh_keysize;
+ req->sq->dhchap_skey_len = nvme_auth_hmac_hash_len(ctrl->shash_id);
req->sq->dhchap_skey = kzalloc(req->sq->dhchap_skey_len, GFP_KERNEL);
if (!req->sq->dhchap_skey)
return -ENOMEM;
- ret = nvme_auth_gen_shared_secret(ctrl->dh_tfm,
- pkey, pkey_size,
- req->sq->dhchap_skey,
- req->sq->dhchap_skey_len);
+ ret = nvme_auth_gen_session_key(ctrl->dh_tfm,
+ pkey, pkey_size,
+ req->sq->dhchap_skey,
+ req->sq->dhchap_skey_len,
+ ctrl->shash_id);
if (ret)
- pr_debug("failed to compute shared secret, err %d\n", ret);
+ pr_debug("failed to compute session key, err %d\n", ret);
else
- pr_debug("%s: shared secret %*ph\n", __func__,
+ pr_debug("%s: session key %*ph\n", __func__,
(int)req->sq->dhchap_skey_len,
req->sq->dhchap_skey);
diff --git a/include/linux/nvme-auth.h b/include/linux/nvme-auth.h
index 184a1f9510fad..89902ae8b9298 100644
--- a/include/linux/nvme-auth.h
+++ b/include/linux/nvme-auth.h
@@ -49,9 +49,9 @@ int nvme_auth_augmented_challenge(u8 hmac_id, const u8 *skey, size_t skey_len,
int nvme_auth_gen_privkey(struct crypto_kpp *dh_tfm, u8 dh_gid);
int nvme_auth_gen_pubkey(struct crypto_kpp *dh_tfm,
u8 *host_key, size_t host_key_len);
-int nvme_auth_gen_shared_secret(struct crypto_kpp *dh_tfm,
- const u8 *ctrl_key, size_t ctrl_key_len,
- u8 *sess_key, size_t sess_key_len);
+int nvme_auth_gen_session_key(struct crypto_kpp *dh_tfm,
+ const u8 *public_key, size_t public_key_len,
+ u8 *sess_key, size_t sess_key_len, u8 hash_id);
int nvme_auth_generate_psk(u8 hmac_id, const u8 *skey, size_t skey_len,
const u8 *c1, const u8 *c2, size_t hash_len,
u8 **ret_psk, size_t *ret_len);
base-commit: c8414ea09f238d924a7ed04049c1893a188c0ee3
--
2.53.0
^ permalink raw reply related [flat|nested] 7+ messages in thread
* Re: [PATCH v2] nvme-auth: Hash DH shared secret to create session key
2026-03-11 23:16 ` [PATCH v2] " Chris Leech
@ 2026-03-12 4:09 ` Eric Biggers
0 siblings, 0 replies; 7+ messages in thread
From: Eric Biggers @ 2026-03-12 4:09 UTC (permalink / raw)
To: Chris Leech
Cc: linux-nvme, Hannes Reinecke, Keith Busch, Christoph Hellwig,
Sagi Grimberg, Chaitanya Kulkarni
On Wed, Mar 11, 2026 at 04:16:43PM -0700, Chris Leech wrote:
> The NVMe Base Specification 8.3.5.5.9 states that the session key Ks
> shall be computed from the ephemeral DH key by applying the hash
> function selected by the HashID parameter.
>
> The current implementation stores the raw DH shared secret as the
> session key without hashing it. This causes redundant hash operations:
>
> 1. Augmented challenge computation (section 8.3.5.5.4) requires
> Ca = HMAC(H(g^xy mod p), C). The code compensates by hashing the
> unhashed session key in nvme_auth_augmented_challenge() to produce
> the correct result.
>
> 2. PSK generation (section 8.3.5.5.9) requires PSK = HMAC(Ks, C1 || C2)
> where Ks should already be H(g^xy mod p). As the DH shared secret
> is always larger than the HMAC block size, HMAC internally hashes
> it before use, accidentally producing the correct result.
>
> When using secure channel concatenation with bidirectional
> authentication, this results in hashing the DH value three times: twice
> for augmented challenge calculations and once during PSK generation.
>
> Fix this by:
> - Modifying nvme_auth_gen_shared_secret() to hash the DH shared secret
> once after computation: Ks = H(g^xy mod p)
> - Removing the hash operation from nvme_auth_augmented_challenge()
> as the session key is now already hashed
> - Updating session key buffer size from DH key size to hash output size
> - Adding specification references in comments
>
> This avoid storing the raw DH shared secret and reduces the number of
> hash operations from three to one when using secure channel
> concatenation.
>
> Signed-off-by: Chris Leech <cleech@redhat.com>
> ---
> v2:
> - Renamed nvme_auth_gen_session_key() arguments from ctrl_key to
> public_key, as it is the peers public DH key and this code is also
> used on the target/controller side when the peer is the host.
> - Changed argument validation from erroring on sess_key_len < hash_len
> to sess_key_len != hash_len to be more precise in calling requirements
>
> created on the nvme-7.1 branch
> this depends on Eric Biggers "nvme-auth use crypto library" patches
> from <20260302075959.338638-1-ebiggers@kernel.org>
>
> drivers/nvme/common/auth.c | 88 ++++++++++++++++++++++++++++++--------
> drivers/nvme/host/auth.c | 13 +++---
> drivers/nvme/target/auth.c | 15 +++---
> include/linux/nvme-auth.h | 6 +--
> 4 files changed, 92 insertions(+), 36 deletions(-)
Reviewed-by: Eric Biggers <ebiggers@kernel.org>
- Eric
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH] nvme-auth: Hash DH shared secret to create session key
2026-03-11 16:44 ` Chris Leech
@ 2026-03-12 6:57 ` Hannes Reinecke
0 siblings, 0 replies; 7+ messages in thread
From: Hannes Reinecke @ 2026-03-12 6:57 UTC (permalink / raw)
To: Chris Leech, Martin George
Cc: linux-nvme, Keith Busch, Christoph Hellwig, Sagi Grimberg,
Chaitanya Kulkarni, Eric Biggers
On 3/11/26 17:44, Chris Leech wrote:
> On Wed, Mar 11, 2026 at 09:17:49PM +0530, Martin George wrote:
>> On Tue, 2026-03-10 at 18:21 -0700, Chris Leech wrote:
>>> When using secure channel concatenation with bidirectional
>>> authentication, this results in hashing the DH value three times:
>>> twice
>>> for augmented challenge calculations and once during PSK generation.
>>>
>>
>> But secure channel concatenation is supported only with unidirectional
>> auth and not really with bidirectional auth, isn't it? As per NVMe base
>> spec 2.1, section 8.3.4.5.9 Generated PSK for TLS:
>>
>> "The host may request secure channel concatenation with the TLS
>> protocol by setting the SC_C field in the AUTH_Negotiate message to
>> NEWTLSPSK while performing only unidirectional authentication. In this
>> case the host shall still send a challenge value C2 to the controller
>> and clear the sequence number S2 to 0h to indicate that controller
>> authentication is not requested."
>
> I believe this is specifying how secure channel concatenation can work
> "while performing only unidirectional authentication", not that it's
> only allowed with unidirectional. It's a special case where C2 is sent
> in Reply without expecting an R2 value in Success1 or to send Success2.
>
> It would be counterproductive to forbid the host from authenticating the
> controller when a secure channel is desired.
>
Yeah, I do agree. After careful re-reading I agree with Chris; the
quoted sentence is at the end of the section describing secure
concatenation, so it really is a clarification how to handle secure
concatenation if unidirectional authentication is employed.
We might go to fmds for clarification, though.
Cheers,
Hannes
--
Dr. Hannes Reinecke Kernel Storage Architect
hare@suse.de +49 911 74053 688
SUSE Software Solutions GmbH, Frankenstr. 146, 90461 Nürnberg
HRB 36809 (AG Nürnberg), GF: I. Totev, A. McDonald, W. Knoblich
^ permalink raw reply [flat|nested] 7+ messages in thread
end of thread, other threads:[~2026-03-12 6:57 UTC | newest]
Thread overview: 7+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-03-11 1:21 [PATCH] nvme-auth: Hash DH shared secret to create session key Chris Leech
2026-03-11 5:13 ` Eric Biggers
2026-03-11 15:47 ` Martin George
2026-03-11 16:44 ` Chris Leech
2026-03-12 6:57 ` Hannes Reinecke
2026-03-11 23:16 ` [PATCH v2] " Chris Leech
2026-03-12 4:09 ` Eric Biggers
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox