Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

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>

authored by

Chris Leech and committed by
Keith Busch
bd7b7ce9 1cc4cdae

+92 -36
+74 -20
drivers/nvme/common/auth.c
··· 351 351 } 352 352 EXPORT_SYMBOL_GPL(nvme_auth_transform_key); 353 353 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 + */ 354 373 int nvme_auth_augmented_challenge(u8 hmac_id, const u8 *skey, size_t skey_len, 355 374 const u8 *challenge, u8 *aug, size_t hlen) 356 375 { 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); 366 377 } 367 378 EXPORT_SYMBOL_GPL(nvme_auth_augmented_challenge); 368 379 ··· 414 403 } 415 404 EXPORT_SYMBOL_GPL(nvme_auth_gen_pubkey); 416 405 417 - int nvme_auth_gen_shared_secret(struct crypto_kpp *dh_tfm, 418 - const u8 *ctrl_key, size_t ctrl_key_len, 419 - u8 *sess_key, size_t sess_key_len) 406 + /** 407 + * nvme_auth_gen_session_key() - Generate an ephemeral session key 408 + * @dh_tfm: Diffie-Hellman transform with local private key already set 409 + * @public_key: Peer's public key 410 + * @public_key_len: Length of @public_key 411 + * @sess_key: Output buffer for the session key 412 + * @sess_key_len: Size of @sess_key buffer 413 + * @hash_id: Hash algorithm identifier 414 + * 415 + * NVMe base specification 8.3.5.5.9: The session key Ks shall be computed from 416 + * the ephemeral DH key (i.e., g^xy mod p) ... by applying the hash function 417 + * H() selected by the HashID parameter ... (i.e., Ks = H(g^xy mod p)). 418 + * 419 + * Return: 0 on success, negative errno on failure. 420 + */ 421 + int nvme_auth_gen_session_key(struct crypto_kpp *dh_tfm, 422 + const u8 *public_key, size_t public_key_len, 423 + u8 *sess_key, size_t sess_key_len, u8 hash_id) 420 424 { 421 425 struct kpp_request *req; 422 426 struct crypto_wait wait; 423 427 struct scatterlist src, dst; 428 + u8 *dh_secret; 429 + size_t dh_secret_len, hash_len; 424 430 int ret; 425 431 426 - req = kpp_request_alloc(dh_tfm, GFP_KERNEL); 427 - if (!req) 432 + hash_len = nvme_auth_hmac_hash_len(hash_id); 433 + if (!hash_len) { 434 + pr_warn("%s: invalid hash algorithm %d\n", __func__, hash_id); 435 + return -EINVAL; 436 + } 437 + 438 + if (sess_key_len != hash_len) { 439 + pr_warn("%s: sess_key buffer missized (%zu != %zu)\n", 440 + __func__, sess_key_len, hash_len); 441 + return -EINVAL; 442 + } 443 + 444 + dh_secret_len = crypto_kpp_maxsize(dh_tfm); 445 + dh_secret = kzalloc(dh_secret_len, GFP_KERNEL); 446 + if (!dh_secret) 428 447 return -ENOMEM; 429 448 449 + req = kpp_request_alloc(dh_tfm, GFP_KERNEL); 450 + if (!req) { 451 + ret = -ENOMEM; 452 + goto out_free_secret; 453 + } 454 + 430 455 crypto_init_wait(&wait); 431 - sg_init_one(&src, ctrl_key, ctrl_key_len); 432 - kpp_request_set_input(req, &src, ctrl_key_len); 433 - sg_init_one(&dst, sess_key, sess_key_len); 434 - kpp_request_set_output(req, &dst, sess_key_len); 456 + sg_init_one(&src, public_key, public_key_len); 457 + kpp_request_set_input(req, &src, public_key_len); 458 + sg_init_one(&dst, dh_secret, dh_secret_len); 459 + kpp_request_set_output(req, &dst, dh_secret_len); 435 460 kpp_request_set_callback(req, CRYPTO_TFM_REQ_MAY_BACKLOG, 436 461 crypto_req_done, &wait); 437 462 438 463 ret = crypto_wait_req(crypto_kpp_compute_shared_secret(req), &wait); 439 - 440 464 kpp_request_free(req); 465 + 466 + if (ret) 467 + goto out_free_secret; 468 + 469 + ret = nvme_auth_hash(hash_id, dh_secret, dh_secret_len, sess_key); 470 + 471 + out_free_secret: 472 + kfree_sensitive(dh_secret); 441 473 return ret; 442 474 } 443 - EXPORT_SYMBOL_GPL(nvme_auth_gen_shared_secret); 475 + EXPORT_SYMBOL_GPL(nvme_auth_gen_session_key); 444 476 445 477 int nvme_auth_parse_key(const char *secret, struct nvme_dhchap_key **ret_key) 446 478 {
+7 -6
drivers/nvme/host/auth.c
··· 588 588 } 589 589 590 590 gen_sesskey: 591 - chap->sess_key_len = chap->host_key_len; 591 + chap->sess_key_len = chap->hash_len; 592 592 chap->sess_key = kmalloc(chap->sess_key_len, GFP_KERNEL); 593 593 if (!chap->sess_key) { 594 594 chap->sess_key_len = 0; ··· 596 596 return -ENOMEM; 597 597 } 598 598 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); 602 603 if (ret) { 603 604 dev_dbg(ctrl->device, 604 - "failed to generate shared secret, error %d\n", ret); 605 + "failed to generate session key, error %d\n", ret); 605 606 chap->status = NVME_AUTH_DHCHAP_FAILURE_INCORRECT_PAYLOAD; 606 607 return ret; 607 608 } 608 - dev_dbg(ctrl->device, "shared secret %*ph\n", 609 + dev_dbg(ctrl->device, "session key %*ph\n", 609 610 (int)chap->sess_key_len, chap->sess_key); 610 611 return 0; 611 612 }
+8 -7
drivers/nvme/target/auth.c
··· 447 447 struct nvmet_ctrl *ctrl = req->sq->ctrl; 448 448 int ret; 449 449 450 - req->sq->dhchap_skey_len = ctrl->dh_keysize; 450 + req->sq->dhchap_skey_len = nvme_auth_hmac_hash_len(ctrl->shash_id); 451 451 req->sq->dhchap_skey = kzalloc(req->sq->dhchap_skey_len, GFP_KERNEL); 452 452 if (!req->sq->dhchap_skey) 453 453 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); 458 459 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); 460 461 else 461 - pr_debug("%s: shared secret %*ph\n", __func__, 462 + pr_debug("%s: session key %*ph\n", __func__, 462 463 (int)req->sq->dhchap_skey_len, 463 464 req->sq->dhchap_skey); 464 465
+3 -3
include/linux/nvme-auth.h
··· 49 49 int nvme_auth_gen_privkey(struct crypto_kpp *dh_tfm, u8 dh_gid); 50 50 int nvme_auth_gen_pubkey(struct crypto_kpp *dh_tfm, 51 51 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); 55 55 int nvme_auth_generate_psk(u8 hmac_id, const u8 *skey, size_t skey_len, 56 56 const u8 *c1, const u8 *c2, size_t hash_len, 57 57 u8 **ret_psk, size_t *ret_len);