Skip to content

Commit bd7b7ce

Browse files
cleechkeithbusch
authored andcommitted
nvme-auth: Hash DH shared secret to create session key
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. Reviewed-by: Hannes Reinecke <hare@suse.de> Reviewed-by: Eric Biggers <ebiggers@kernel.org> Signed-off-by: Chris Leech <cleech@redhat.com> Signed-off-by: Keith Busch <kbusch@kernel.org>
1 parent 1cc4cda commit bd7b7ce

4 files changed

Lines changed: 92 additions & 36 deletions

File tree

drivers/nvme/common/auth.c

Lines changed: 74 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -351,18 +351,29 @@ struct nvme_dhchap_key *nvme_auth_transform_key(
351351
}
352352
EXPORT_SYMBOL_GPL(nvme_auth_transform_key);
353353

354+
/**
355+
* nvme_auth_augmented_challenge() - Compute the augmented DH-HMAC-CHAP challenge
356+
* @hmac_id: Hash algorithm identifier
357+
* @skey: Session key
358+
* @skey_len: Length of @skey
359+
* @challenge: Challenge value
360+
* @aug: Output buffer for the augmented challenge
361+
* @hlen: Hash output length (length of @challenge and @aug)
362+
*
363+
* NVMe base specification 8.3.5.5.4: The augmented challenge is computed
364+
* applying the HMAC function using the hash function H() selected by the
365+
* HashID parameter ... with the hash of the ephemeral DH key ... as HMAC key
366+
* to the challenge C (i.e., Ca = HMAC(H(g^xy mod p), C)).
367+
*
368+
* As the session key skey is already H(g^xy mod p) per section 8.3.5.5.9, use
369+
* it directly as the HMAC key without additional hashing.
370+
*
371+
* Return: 0 on success, negative errno on failure.
372+
*/
354373
int nvme_auth_augmented_challenge(u8 hmac_id, const u8 *skey, size_t skey_len,
355374
const u8 *challenge, u8 *aug, size_t hlen)
356375
{
357-
u8 hashed_key[NVME_AUTH_MAX_DIGEST_SIZE];
358-
int ret;
359-
360-
ret = nvme_auth_hash(hmac_id, skey, skey_len, hashed_key);
361-
if (ret)
362-
return ret;
363-
ret = nvme_auth_hmac(hmac_id, hashed_key, hlen, challenge, hlen, aug);
364-
memzero_explicit(hashed_key, sizeof(hashed_key));
365-
return ret;
376+
return nvme_auth_hmac(hmac_id, skey, skey_len, challenge, hlen, aug);
366377
}
367378
EXPORT_SYMBOL_GPL(nvme_auth_augmented_challenge);
368379

@@ -403,33 +414,76 @@ int nvme_auth_gen_pubkey(struct crypto_kpp *dh_tfm,
403414
}
404415
EXPORT_SYMBOL_GPL(nvme_auth_gen_pubkey);
405416

406-
int nvme_auth_gen_shared_secret(struct crypto_kpp *dh_tfm,
407-
const u8 *ctrl_key, size_t ctrl_key_len,
408-
u8 *sess_key, size_t sess_key_len)
417+
/**
418+
* nvme_auth_gen_session_key() - Generate an ephemeral session key
419+
* @dh_tfm: Diffie-Hellman transform with local private key already set
420+
* @public_key: Peer's public key
421+
* @public_key_len: Length of @public_key
422+
* @sess_key: Output buffer for the session key
423+
* @sess_key_len: Size of @sess_key buffer
424+
* @hash_id: Hash algorithm identifier
425+
*
426+
* NVMe base specification 8.3.5.5.9: The session key Ks shall be computed from
427+
* the ephemeral DH key (i.e., g^xy mod p) ... by applying the hash function
428+
* H() selected by the HashID parameter ... (i.e., Ks = H(g^xy mod p)).
429+
*
430+
* Return: 0 on success, negative errno on failure.
431+
*/
432+
int nvme_auth_gen_session_key(struct crypto_kpp *dh_tfm,
433+
const u8 *public_key, size_t public_key_len,
434+
u8 *sess_key, size_t sess_key_len, u8 hash_id)
409435
{
410436
struct kpp_request *req;
411437
struct crypto_wait wait;
412438
struct scatterlist src, dst;
439+
u8 *dh_secret;
440+
size_t dh_secret_len, hash_len;
413441
int ret;
414442

415-
req = kpp_request_alloc(dh_tfm, GFP_KERNEL);
416-
if (!req)
443+
hash_len = nvme_auth_hmac_hash_len(hash_id);
444+
if (!hash_len) {
445+
pr_warn("%s: invalid hash algorithm %d\n", __func__, hash_id);
446+
return -EINVAL;
447+
}
448+
449+
if (sess_key_len != hash_len) {
450+
pr_warn("%s: sess_key buffer missized (%zu != %zu)\n",
451+
__func__, sess_key_len, hash_len);
452+
return -EINVAL;
453+
}
454+
455+
dh_secret_len = crypto_kpp_maxsize(dh_tfm);
456+
dh_secret = kzalloc(dh_secret_len, GFP_KERNEL);
457+
if (!dh_secret)
417458
return -ENOMEM;
418459

460+
req = kpp_request_alloc(dh_tfm, GFP_KERNEL);
461+
if (!req) {
462+
ret = -ENOMEM;
463+
goto out_free_secret;
464+
}
465+
419466
crypto_init_wait(&wait);
420-
sg_init_one(&src, ctrl_key, ctrl_key_len);
421-
kpp_request_set_input(req, &src, ctrl_key_len);
422-
sg_init_one(&dst, sess_key, sess_key_len);
423-
kpp_request_set_output(req, &dst, sess_key_len);
467+
sg_init_one(&src, public_key, public_key_len);
468+
kpp_request_set_input(req, &src, public_key_len);
469+
sg_init_one(&dst, dh_secret, dh_secret_len);
470+
kpp_request_set_output(req, &dst, dh_secret_len);
424471
kpp_request_set_callback(req, CRYPTO_TFM_REQ_MAY_BACKLOG,
425472
crypto_req_done, &wait);
426473

427474
ret = crypto_wait_req(crypto_kpp_compute_shared_secret(req), &wait);
428-
429475
kpp_request_free(req);
476+
477+
if (ret)
478+
goto out_free_secret;
479+
480+
ret = nvme_auth_hash(hash_id, dh_secret, dh_secret_len, sess_key);
481+
482+
out_free_secret:
483+
kfree_sensitive(dh_secret);
430484
return ret;
431485
}
432-
EXPORT_SYMBOL_GPL(nvme_auth_gen_shared_secret);
486+
EXPORT_SYMBOL_GPL(nvme_auth_gen_session_key);
433487

434488
int nvme_auth_parse_key(const char *secret, struct nvme_dhchap_key **ret_key)
435489
{

drivers/nvme/host/auth.c

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -588,24 +588,25 @@ static int nvme_auth_dhchap_exponential(struct nvme_ctrl *ctrl,
588588
}
589589

590590
gen_sesskey:
591-
chap->sess_key_len = chap->host_key_len;
591+
chap->sess_key_len = chap->hash_len;
592592
chap->sess_key = kmalloc(chap->sess_key_len, GFP_KERNEL);
593593
if (!chap->sess_key) {
594594
chap->sess_key_len = 0;
595595
chap->status = NVME_AUTH_DHCHAP_FAILURE_FAILED;
596596
return -ENOMEM;
597597
}
598598

599-
ret = nvme_auth_gen_shared_secret(chap->dh_tfm,
600-
chap->ctrl_key, chap->ctrl_key_len,
601-
chap->sess_key, chap->sess_key_len);
599+
ret = nvme_auth_gen_session_key(chap->dh_tfm,
600+
chap->ctrl_key, chap->ctrl_key_len,
601+
chap->sess_key, chap->sess_key_len,
602+
chap->hash_id);
602603
if (ret) {
603604
dev_dbg(ctrl->device,
604-
"failed to generate shared secret, error %d\n", ret);
605+
"failed to generate session key, error %d\n", ret);
605606
chap->status = NVME_AUTH_DHCHAP_FAILURE_INCORRECT_PAYLOAD;
606607
return ret;
607608
}
608-
dev_dbg(ctrl->device, "shared secret %*ph\n",
609+
dev_dbg(ctrl->device, "session key %*ph\n",
609610
(int)chap->sess_key_len, chap->sess_key);
610611
return 0;
611612
}

drivers/nvme/target/auth.c

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -447,18 +447,19 @@ int nvmet_auth_ctrl_sesskey(struct nvmet_req *req,
447447
struct nvmet_ctrl *ctrl = req->sq->ctrl;
448448
int ret;
449449

450-
req->sq->dhchap_skey_len = ctrl->dh_keysize;
450+
req->sq->dhchap_skey_len = nvme_auth_hmac_hash_len(ctrl->shash_id);
451451
req->sq->dhchap_skey = kzalloc(req->sq->dhchap_skey_len, GFP_KERNEL);
452452
if (!req->sq->dhchap_skey)
453453
return -ENOMEM;
454-
ret = nvme_auth_gen_shared_secret(ctrl->dh_tfm,
455-
pkey, pkey_size,
456-
req->sq->dhchap_skey,
457-
req->sq->dhchap_skey_len);
454+
ret = nvme_auth_gen_session_key(ctrl->dh_tfm,
455+
pkey, pkey_size,
456+
req->sq->dhchap_skey,
457+
req->sq->dhchap_skey_len,
458+
ctrl->shash_id);
458459
if (ret)
459-
pr_debug("failed to compute shared secret, err %d\n", ret);
460+
pr_debug("failed to compute session key, err %d\n", ret);
460461
else
461-
pr_debug("%s: shared secret %*ph\n", __func__,
462+
pr_debug("%s: session key %*ph\n", __func__,
462463
(int)req->sq->dhchap_skey_len,
463464
req->sq->dhchap_skey);
464465

include/linux/nvme-auth.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,9 @@ int nvme_auth_augmented_challenge(u8 hmac_id, const u8 *skey, size_t skey_len,
4949
int nvme_auth_gen_privkey(struct crypto_kpp *dh_tfm, u8 dh_gid);
5050
int nvme_auth_gen_pubkey(struct crypto_kpp *dh_tfm,
5151
u8 *host_key, size_t host_key_len);
52-
int nvme_auth_gen_shared_secret(struct crypto_kpp *dh_tfm,
53-
const u8 *ctrl_key, size_t ctrl_key_len,
54-
u8 *sess_key, size_t sess_key_len);
52+
int nvme_auth_gen_session_key(struct crypto_kpp *dh_tfm,
53+
const u8 *public_key, size_t public_key_len,
54+
u8 *sess_key, size_t sess_key_len, u8 hash_id);
5555
int nvme_auth_generate_psk(u8 hmac_id, const u8 *skey, size_t skey_len,
5656
const u8 *c1, const u8 *c2, size_t hash_len,
5757
u8 **ret_psk, size_t *ret_len);

0 commit comments

Comments
 (0)