From nobody Sat Jul 25 00:42:04 2026 Received: from mout-b-210.mailbox.org (mout-b-210.mailbox.org [195.10.208.40]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id BEF43385D8A; Tue, 21 Jul 2026 14:18:07 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=195.10.208.40 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784643492; cv=none; b=ekN22iOs3QZv3MUoqWkH8y7jikNSsI40bMxPEPJUSNvi8rmvxJcX3Oc/HdR8SN2Mqwyy6KciNI4WwkP7aejOuzcOV3vnigaWLu0bsujOeBl+BYAzuJl8I0LDhQltI9ztL4sPhpyNC8Ida0DZg/zLDPhxNhJE2GXIpyBiqcOK5zk= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784643492; c=relaxed/simple; bh=4XMSSYeWGWr5pycq0X+lJ1beMqz58PF3+qRdMwTkOmU=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=jvfAieKJpNUrFqrsHfCOXOEvvlutBGHUD6STnJDkwZdFj1rD9mtsTNWTyjr2GMnPE4qDl1v+6+BxNT902+bz7FNMSma/BzY1IrAFPctbo0jjgwSeMatl70JUO/NoEVARhE8SRr7R21JZIwVN35+uz90D2q9ywwwqVerjE1JuLig= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=mandelbit.com; spf=pass smtp.mailfrom=mandelbit.com; dkim=pass (2048-bit key) header.d=mandelbit.com header.i=@mandelbit.com header.b=Za5buqjn; arc=none smtp.client-ip=195.10.208.40 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=mandelbit.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=mandelbit.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=mandelbit.com header.i=@mandelbit.com header.b="Za5buqjn" Received: from smtp102.mailbox.org (smtp102.mailbox.org [10.196.197.102]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange x25519 server-signature RSA-PSS (4096 bits) server-digest SHA512) (No client certificate requested) by mout-b-210.mailbox.org (Postfix) with ESMTPS id 4h4K2k0dsqzFqxk; Tue, 21 Jul 2026 16:07:26 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=mandelbit.com; s=MBO0001; t=1784642846; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=l7ophslGW/dSefBKTR4gUdzh1JD9Snwrm6mKJ6ZRaYM=; b=Za5buqjnzXgrmft9nhackNgjklTMpncgXordKGK4DDjywrL3mXMBwHPuvpJ3GNnA0akaia 1SzhTR/VLU16xNiXqx+clNsDatlIhaId5attyPr1w5c1hVVdUCshJIGwuWM/2S5s48kz41 H5iP6v0PB/zPUVO1HpGtTuYF4L1A/oOFGKaTIJLv/vwRllUFxYsrlVr9GgjGEqqZeJLzjT 0qMbNUWVyzrMMp1tpWROqnTs8DjDBwEvZz109VYWfvDUV+lHmW+4HaWOjigO9uo6+QvJau E9Pz7NtdX0xzG7uFx/+2AjiWjwwkzZTpOdBFiArHfhJVFrFkB+MC27l6vbuhwA== From: Marco Baffo To: linux-crypto@vger.kernel.org Cc: herbert@gondor.apana.org.au, davem@davemloft.net, ebiggers@kernel.org, Jason@zx2c4.com, ardb@kernel.org, tytso@mit.edu, jaegeuk@kernel.org, linux-fscrypt@vger.kernel.org, kbusch@kernel.org, axboe@kernel.dk, hch@lst.de, sagi@grimberg.me, hare@suse.de, linux-nvme@lists.infradead.org, andrew+netdev@lunn.ch, edumazet@google.com, kuba@kernel.org, pabeni@redhat.com, antonio@openvpn.net, sd@queasysnail.net, linux-kernel@vger.kernel.org, Marco Baffo Subject: [PATCH 1/5] lib/crypto: add HKDF-SHA{256,384,512} Date: Tue, 21 Jul 2026 16:06:40 +0200 Message-ID: <20260721140644.780006-2-marco@mandelbit.com> In-Reply-To: <20260721140644.780006-1-marco@mandelbit.com> References: <20260721140644.780006-1-marco@mandelbit.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" fscrypt and NVMe authentication each carry private HKDF (RFC 5869) code. Add common HKDF-SHA{256,384,512} helpers based on fscrypt's implementation to lib/crypto, so both users can share the code. Represent info as an array of segments processed as if concatenated. This avoids temporary concatenation buffers. Implement the helpers using the direct HMAC library API, so they cannot fail, perform no allocations, and make no indirect calls. HKDF-Extract stores the PRK as a prepared HMAC key, so key preparation is not repeated for each HKDF-Expand call. Use a template to instantiate the code for SHA-256, SHA-384, and SHA-512. Add the resulting objects to libsha256 and libsha512. Signed-off-by: Marco Baffo --- include/crypto/hkdf.h | 131 +++++++++++++++++++++++++++++++++++++ lib/crypto/Makefile | 4 +- lib/crypto/hkdf-sha256.c | 22 +++++++ lib/crypto/hkdf-sha512.c | 34 ++++++++++ lib/crypto/hkdf-template.h | 97 +++++++++++++++++++++++++++ 5 files changed, 286 insertions(+), 2 deletions(-) create mode 100644 include/crypto/hkdf.h create mode 100644 lib/crypto/hkdf-sha256.c create mode 100644 lib/crypto/hkdf-sha512.c create mode 100644 lib/crypto/hkdf-template.h diff --git a/include/crypto/hkdf.h b/include/crypto/hkdf.h new file mode 100644 index 000000000000..dda4dce9f037 --- /dev/null +++ b/include/crypto/hkdf.h @@ -0,0 +1,131 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * HKDF: HMAC-based Key Derivation Function (RFC 5869) + * + * Derived from fs/crypto/hkdf.c + * Copyright 2019 Google LLC + */ + +#ifndef _CRYPTO_HKDF_H +#define _CRYPTO_HKDF_H + +#include + +/* + * HKDF has two steps: + * + * 1. Extract: derive a pseudorandom key from input keying material and sa= lt. + * 2. Expand: derive output keying material from the pseudorandom key and = info. + */ + +/** + * struct hkdf_seg - one segment of the HKDF-Expand 'info' parameter + * @data: the segment data + * @len: length of @data in bytes + * + * The segments are processed as if concatenated, so a composite info can = be + * passed without assembling it in a temporary buffer first. + */ +struct hkdf_seg { + const void *data; + size_t len; +}; + +/** + * hkdf_sha256_extract() - HKDF-Extract using HMAC-SHA256 + * @prk: (output) the pseudorandom key, prepared for hkdf_sha256_expand() + * @salt: optional non-secret salt, or NULL for the RFC 5869 default + * (all-zero) salt + * @salt_len: length of @salt in bytes (must be 0 if @salt is NULL) + * @ikm: input keying material + * @ikm_len: length of @ikm in bytes + * + * Extract a SHA256_DIGEST_SIZE-byte pseudorandom key from @ikm. If @ikm = is + * already a pseudorandom key of that size, prepare @prk with + * hmac_sha256_preparekey() instead. + */ +void hkdf_sha256_extract(struct hmac_sha256_key *prk, + const u8 *salt, size_t salt_len, + const u8 *ikm, size_t ikm_len); + +/** + * hkdf_sha256_expand() - HKDF-Expand using HMAC-SHA256 + * @prk: pseudorandom key from hkdf_sha256_extract() or + * hmac_sha256_preparekey() + * @info: info segments, processed as if concatenated. Use different info= for + * different purposes. + * @info_nsegs: number of segments in @info + * @okm: (output) the output keying material + * @okm_len: length of @okm in bytes, at most 255 * SHA256_DIGEST_SIZE + * + * Expand @prk into @okm_len bytes of output keying material (RFC 5869 + * section 2.3). The same (@prk, @info) always yields the same output. + * Different @info values yield independent outputs, but different @okm_le= n do + * not: a shorter output is a truncation of a longer one. This may be cal= led many + * times per @prk and is thread-safe. + */ +void hkdf_sha256_expand(const struct hmac_sha256_key *prk, + const struct hkdf_seg *info, size_t info_nsegs, + u8 *okm, size_t okm_len); + +/** + * hkdf_sha384_extract() - HKDF-Extract using HMAC-SHA384 + * @prk: (output) the pseudorandom key, prepared for hkdf_sha384_expand() + * @salt: optional non-secret salt, or NULL for the RFC 5869 default + * (all-zero) salt + * @salt_len: length of @salt in bytes (must be 0 if @salt is NULL) + * @ikm: input keying material + * @ikm_len: length of @ikm in bytes + * + * SHA-384 variant of hkdf_sha256_extract(). + */ +void hkdf_sha384_extract(struct hmac_sha384_key *prk, + const u8 *salt, size_t salt_len, + const u8 *ikm, size_t ikm_len); + +/** + * hkdf_sha384_expand() - HKDF-Expand using HMAC-SHA384 + * @prk: pseudorandom key from hkdf_sha384_extract() or + * hmac_sha384_preparekey() + * @info: info segments, processed as if concatenated + * @info_nsegs: number of segments in @info + * @okm: (output) the output keying material + * @okm_len: length of @okm in bytes, at most 255 * SHA384_DIGEST_SIZE + * + * SHA-384 variant of hkdf_sha256_expand(). + */ +void hkdf_sha384_expand(const struct hmac_sha384_key *prk, + const struct hkdf_seg *info, size_t info_nsegs, + u8 *okm, size_t okm_len); + +/** + * hkdf_sha512_extract() - HKDF-Extract using HMAC-SHA512 + * @prk: (output) the pseudorandom key, prepared for hkdf_sha512_expand() + * @salt: optional non-secret salt, or NULL for the RFC 5869 default + * (all-zero) salt + * @salt_len: length of @salt in bytes (must be 0 if @salt is NULL) + * @ikm: input keying material + * @ikm_len: length of @ikm in bytes + * + * SHA-512 variant of hkdf_sha256_extract(). + */ +void hkdf_sha512_extract(struct hmac_sha512_key *prk, + const u8 *salt, size_t salt_len, + const u8 *ikm, size_t ikm_len); + +/** + * hkdf_sha512_expand() - HKDF-Expand using HMAC-SHA512 + * @prk: pseudorandom key from hkdf_sha512_extract() or + * hmac_sha512_preparekey() + * @info: info segments, processed as if concatenated + * @info_nsegs: number of segments in @info + * @okm: (output) the output keying material + * @okm_len: length of @okm in bytes, at most 255 * SHA512_DIGEST_SIZE + * + * SHA-512 variant of hkdf_sha256_expand(). + */ +void hkdf_sha512_expand(const struct hmac_sha512_key *prk, + const struct hkdf_seg *info, size_t info_nsegs, + u8 *okm, size_t okm_len); + +#endif /* _CRYPTO_HKDF_H */ diff --git a/lib/crypto/Makefile b/lib/crypto/Makefile index f1e9bf89785f..96173c1a0a31 100644 --- a/lib/crypto/Makefile +++ b/lib/crypto/Makefile @@ -300,7 +300,7 @@ endif # CONFIG_CRYPTO_LIB_SHA1_ARCH ##########################################################################= ###### =20 obj-$(CONFIG_CRYPTO_LIB_SHA256) +=3D libsha256.o -libsha256-y :=3D sha256.o +libsha256-y :=3D sha256.o hkdf-sha256.o ifeq ($(CONFIG_CRYPTO_LIB_SHA256_ARCH),y) CFLAGS_sha256.o +=3D -I$(src)/$(SRCARCH) =20 @@ -329,7 +329,7 @@ endif # CONFIG_CRYPTO_LIB_SHA256_ARCH ##########################################################################= ###### =20 obj-$(CONFIG_CRYPTO_LIB_SHA512) +=3D libsha512.o -libsha512-y :=3D sha512.o +libsha512-y :=3D sha512.o hkdf-sha512.o ifeq ($(CONFIG_CRYPTO_LIB_SHA512_ARCH),y) CFLAGS_sha512.o +=3D -I$(src)/$(SRCARCH) =20 diff --git a/lib/crypto/hkdf-sha256.c b/lib/crypto/hkdf-sha256.c new file mode 100644 index 000000000000..27004293b805 --- /dev/null +++ b/lib/crypto/hkdf-sha256.c @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * HKDF-SHA256 library functions (RFC 5869) + * + * Derived from fs/crypto/hkdf.c + * Copyright 2019 Google LLC + */ + +#include +#include + +#define HKDF_EXTRACT hkdf_sha256_extract +#define HKDF_EXPAND hkdf_sha256_expand +#define HMAC_KEY hmac_sha256_key +#define HMAC_CTX hmac_sha256_ctx +#define HMAC_PREPAREKEY hmac_sha256_preparekey +#define HMAC_INIT hmac_sha256_init +#define HMAC_UPDATE hmac_sha256_update +#define HMAC_FINAL hmac_sha256_final +#define HMAC_USINGRAWKEY hmac_sha256_usingrawkey +#define HKDF_HASHLEN SHA256_DIGEST_SIZE +#include "hkdf-template.h" diff --git a/lib/crypto/hkdf-sha512.c b/lib/crypto/hkdf-sha512.c new file mode 100644 index 000000000000..f03f9dad1beb --- /dev/null +++ b/lib/crypto/hkdf-sha512.c @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * HKDF-SHA384 and HKDF-SHA512 library functions (RFC 5869) + * + * Derived from fs/crypto/hkdf.c + * Copyright 2019 Google LLC + */ + +#include +#include + +#define HKDF_EXTRACT hkdf_sha384_extract +#define HKDF_EXPAND hkdf_sha384_expand +#define HMAC_KEY hmac_sha384_key +#define HMAC_CTX hmac_sha384_ctx +#define HMAC_PREPAREKEY hmac_sha384_preparekey +#define HMAC_INIT hmac_sha384_init +#define HMAC_UPDATE hmac_sha384_update +#define HMAC_FINAL hmac_sha384_final +#define HMAC_USINGRAWKEY hmac_sha384_usingrawkey +#define HKDF_HASHLEN SHA384_DIGEST_SIZE +#include "hkdf-template.h" + +#define HKDF_EXTRACT hkdf_sha512_extract +#define HKDF_EXPAND hkdf_sha512_expand +#define HMAC_KEY hmac_sha512_key +#define HMAC_CTX hmac_sha512_ctx +#define HMAC_PREPAREKEY hmac_sha512_preparekey +#define HMAC_INIT hmac_sha512_init +#define HMAC_UPDATE hmac_sha512_update +#define HMAC_FINAL hmac_sha512_final +#define HMAC_USINGRAWKEY hmac_sha512_usingrawkey +#define HKDF_HASHLEN SHA512_DIGEST_SIZE +#include "hkdf-template.h" diff --git a/lib/crypto/hkdf-template.h b/lib/crypto/hkdf-template.h new file mode 100644 index 000000000000..a1d6bbfdc689 --- /dev/null +++ b/lib/crypto/hkdf-template.h @@ -0,0 +1,97 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Implementation of HKDF ("HMAC-based Extract-and-Expand Key Derivation + * Function"), aka RFC 5869. See also the original paper (Krawczyk 2010): + * "Cryptographic Extraction and Key Derivation: The HKDF Scheme". + * + * Derived from fs/crypto/hkdf.c + * Copyright 2019 Google LLC + * + * This file is a template that generates HKDF on top of a specific HMAC + * library. The including file must define the following macros, and may + * include this file multiple times to instantiate multiple variants: + * + * HKDF_EXTRACT name of the HKDF-Extract function + * HKDF_EXPAND name of the HKDF-Expand function + * HMAC_KEY HMAC prepared-key type, without 'struct' + * HMAC_CTX HMAC context type, without 'struct' + * HMAC_PREPAREKEY the hmac_*_preparekey() function + * HMAC_INIT the hmac_*_init() function + * HMAC_UPDATE the hmac_*_update() function + * HMAC_FINAL the hmac_*_final() function + * HMAC_USINGRAWKEY the hmac_*_usingrawkey() function + * HKDF_HASHLEN hash digest size in bytes + */ + +/* + * HKDF-Extract (RFC 5869 section 2.2). Compute a pseudorandom key of + * HKDF_HASHLEN bytes from the input keying material and optional salt, and + * prepare it as an HMAC key. + * + * A NULL or empty 'salt', or any all-zero salt up to the HMAC block size, + * gives the same PRK as the RFC 5869 default salt of HKDF_HASHLEN zero by= tes. + * If 'salt' is NULL, 'salt_len' must be 0. + */ +void HKDF_EXTRACT(struct HMAC_KEY *prk, const u8 *salt, size_t salt_len, + const u8 *ikm, size_t ikm_len) +{ + u8 prk_bytes[HKDF_HASHLEN]; + + HMAC_USINGRAWKEY(salt ? salt : (const u8 *)"", salt_len, + ikm, ikm_len, prk_bytes); + HMAC_PREPAREKEY(prk, prk_bytes, sizeof(prk_bytes)); + memzero_explicit(prk_bytes, sizeof(prk_bytes)); +} +EXPORT_SYMBOL_GPL(HKDF_EXTRACT); + +/* + * HKDF-Expand (RFC 5869 section 2.3). Expand the pseudorandom key 'prk' = into + * 'okm_len' bytes of output keying material, parameterized by the + * application-specific info, given as 'info_nsegs' segments that are + * processed as if concatenated: + * + * T(0) =3D empty + * T(n) =3D HMAC(PRK, T(n-1) | info | n) for n =3D 1, 2, ... + * OKM =3D first okm_len bytes of T(1) | T(2) | ... + * + * This is thread-safe and may be called by multiple threads in parallel. + */ +void HKDF_EXPAND(const struct HMAC_KEY *prk, const struct hkdf_seg *info, + size_t info_nsegs, u8 *okm, size_t okm_len) +{ + struct HMAC_CTX ctx; + u8 counter =3D 1; + u8 tmp[HKDF_HASHLEN]; + + WARN_ON_ONCE(okm_len > 255 * HKDF_HASHLEN); + + for (size_t i =3D 0; i < okm_len; i +=3D HKDF_HASHLEN) { + HMAC_INIT(&ctx, prk); + if (i !=3D 0) + HMAC_UPDATE(&ctx, &okm[i - HKDF_HASHLEN], + HKDF_HASHLEN); + for (size_t j =3D 0; j < info_nsegs; j++) + HMAC_UPDATE(&ctx, info[j].data, info[j].len); + HMAC_UPDATE(&ctx, &counter, 1); + if (okm_len - i < HKDF_HASHLEN) { + HMAC_FINAL(&ctx, tmp); + memcpy(&okm[i], tmp, okm_len - i); + memzero_explicit(tmp, sizeof(tmp)); + } else { + HMAC_FINAL(&ctx, &okm[i]); + } + counter++; + } +} +EXPORT_SYMBOL_GPL(HKDF_EXPAND); + +#undef HKDF_EXTRACT +#undef HKDF_EXPAND +#undef HMAC_KEY +#undef HMAC_CTX +#undef HMAC_PREPAREKEY +#undef HMAC_INIT +#undef HMAC_UPDATE +#undef HMAC_FINAL +#undef HMAC_USINGRAWKEY +#undef HKDF_HASHLEN --=20 2.43.0 From nobody Sat Jul 25 00:42:04 2026 Received: from mout-b-202.mailbox.org (mout-b-202.mailbox.org [195.10.208.62]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 2C98426E71F; Tue, 21 Jul 2026 14:17:19 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=195.10.208.62 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784643443; cv=none; b=qqwQAAA6Xf4n1/iR90em9wrcX7+29PZYB7OFG4xeDrHmhhhcWo1VMKzrE+BOqu+LEgzHvybJnFGgMW+YkpbniG47MlmZ2vvuSk826r8WfH2vt6xQchxublTeitAERvOY7iHdS/pHB++Zw1qc3FytzXsWGI/AhkR1LsfJ9913DHg= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784643443; c=relaxed/simple; bh=3rEgPAvFadNafxB5pdlwF8vOaQeiTc8vMOOoefNREts=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=bLKpMrRjPRYJzdgLrhsnGviUlSifaM7Acec8L7BPV4lpkA9F5a+/dP+4Xbu9xbnDoHnLPpGF/CBywi9tlfUeNkumfHzyyZGIf+SDniMEfkTVXh+sWGXaziCenIu8yug40Z7QJ3esINY5NtbmE9etUFGkIj+BuK+4KhZzfSdyr2w= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=mandelbit.com; spf=pass smtp.mailfrom=mandelbit.com; dkim=pass (2048-bit key) header.d=mandelbit.com header.i=@mandelbit.com header.b=EbjZpFp5; arc=none smtp.client-ip=195.10.208.62 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=mandelbit.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=mandelbit.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=mandelbit.com header.i=@mandelbit.com header.b="EbjZpFp5" Received: from smtp102.mailbox.org (smtp102.mailbox.org [IPv6:2001:67c:2050:b231:465::102]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange x25519 server-signature RSA-PSS (4096 bits) server-digest SHA512) (No client certificate requested) by mout-b-202.mailbox.org (Postfix) with ESMTPS id 4h4K302WXwzKnVk; Tue, 21 Jul 2026 16:07:40 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=mandelbit.com; s=MBO0001; t=1784642860; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=xzYuQu47yJ1HQ5ZuxzznVUPcgHTdm0W8Y1TfHqsxMx0=; b=EbjZpFp5kLjc2agwH09hj/5CUCTRnBEzrnCtMxySnrnAlIWnSByOyVh9+TJJ8/iVqeamRX MX0x6QubXzuvuaYo2rRX4rnX23IhjNu4DQrH/JTuqfEjL0zEeF9a7VI/+qjJqyXsSmU0jF MENzEaEmQkOnoS4oEeK+eqUqOV4XwdSyHDExdlYcdZQBUdo56dyvoVxKv2c1ZKEoxSgnFw eCmPQX7zQ1bHc2GG+plPcA8CDiC7xGJbC/Niq68FKaassJi9TwTKp+KJeIw+IWuFKcthDm ZmjLqN7vr6eEIfpwTohAJEfcVBUhPezNPeKXfVJkZEYsfpitrxxMvoVklFYXAQ== Authentication-Results: outgoing_mbo_mout; dkim=none; spf=pass (outgoing_mbo_mout: domain of marco@mandelbit.com designates 2001:67c:2050:b231:465::102 as permitted sender) smtp.mailfrom=marco@mandelbit.com From: Marco Baffo To: linux-crypto@vger.kernel.org Cc: herbert@gondor.apana.org.au, davem@davemloft.net, ebiggers@kernel.org, Jason@zx2c4.com, ardb@kernel.org, tytso@mit.edu, jaegeuk@kernel.org, linux-fscrypt@vger.kernel.org, kbusch@kernel.org, axboe@kernel.dk, hch@lst.de, sagi@grimberg.me, hare@suse.de, linux-nvme@lists.infradead.org, andrew+netdev@lunn.ch, edumazet@google.com, kuba@kernel.org, pabeni@redhat.com, antonio@openvpn.net, sd@queasysnail.net, linux-kernel@vger.kernel.org, Marco Baffo Subject: [PATCH 2/5] lib/crypto: tests: add HKDF KUnit tests Date: Tue, 21 Jul 2026 16:06:41 +0200 Message-ID: <20260721140644.780006-3-marco@mandelbit.com> In-Reply-To: <20260721140644.780006-1-marco@mandelbit.com> References: <20260721140644.780006-1-marco@mandelbit.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-Rspamd-Queue-Id: 4h4K302WXwzKnVk Content-Type: text/plain; charset="utf-8" Add KUnit tests for the HKDF-SHA{256,384,512} helpers using the RFC 5869 SHA-256 vectors and SHA-384 and SHA-512 vectors generated with "openssl kdf" from the same inputs. Also test empty salt and info, multi-block output, and segmented info. Signed-off-by: Marco Baffo --- lib/crypto/.kunitconfig | 1 + lib/crypto/tests/Kconfig | 8 + lib/crypto/tests/Makefile | 1 + lib/crypto/tests/hkdf_kunit.c | 276 ++++++++++++++++++++++++++++++++++ 4 files changed, 286 insertions(+) create mode 100644 lib/crypto/tests/hkdf_kunit.c diff --git a/lib/crypto/.kunitconfig b/lib/crypto/.kunitconfig index 3efc854a2c08..82739358aa44 100644 --- a/lib/crypto/.kunitconfig +++ b/lib/crypto/.kunitconfig @@ -8,6 +8,7 @@ CONFIG_CRYPTO_LIB_BLAKE2S_KUNIT_TEST=3Dy CONFIG_CRYPTO_LIB_CHACHA20POLY1305_KUNIT_TEST=3Dy CONFIG_CRYPTO_LIB_CURVE25519_KUNIT_TEST=3Dy CONFIG_CRYPTO_LIB_GHASH_KUNIT_TEST=3Dy +CONFIG_CRYPTO_LIB_HKDF_KUNIT_TEST=3Dy CONFIG_CRYPTO_LIB_MD5_KUNIT_TEST=3Dy CONFIG_CRYPTO_LIB_MLDSA_KUNIT_TEST=3Dy CONFIG_CRYPTO_LIB_NH_KUNIT_TEST=3Dy diff --git a/lib/crypto/tests/Kconfig b/lib/crypto/tests/Kconfig index bc084dde424f..900e5d3afd00 100644 --- a/lib/crypto/tests/Kconfig +++ b/lib/crypto/tests/Kconfig @@ -52,6 +52,14 @@ config CRYPTO_LIB_GHASH_KUNIT_TEST help KUnit tests for the GHASH library functions. =20 +config CRYPTO_LIB_HKDF_KUNIT_TEST + tristate "KUnit tests for HKDF" if !KUNIT_ALL_TESTS + depends on KUNIT && CRYPTO_LIB_SHA256 && CRYPTO_LIB_SHA512 + default KUNIT_ALL_TESTS + help + KUnit tests for the HKDF-SHA256, HKDF-SHA384, and HKDF-SHA512 key + derivation functions. + config CRYPTO_LIB_MD5_KUNIT_TEST tristate "KUnit tests for MD5" if !KUNIT_ALL_TESTS depends on KUNIT && CRYPTO_LIB_MD5 diff --git a/lib/crypto/tests/Makefile b/lib/crypto/tests/Makefile index a739413500b6..15a2f9b54458 100644 --- a/lib/crypto/tests/Makefile +++ b/lib/crypto/tests/Makefile @@ -6,6 +6,7 @@ obj-$(CONFIG_CRYPTO_LIB_BLAKE2S_KUNIT_TEST) +=3D blake2s_ku= nit.o obj-$(CONFIG_CRYPTO_LIB_CHACHA20POLY1305_KUNIT_TEST) +=3D chacha20poly1305= _kunit.o obj-$(CONFIG_CRYPTO_LIB_CURVE25519_KUNIT_TEST) +=3D curve25519_kunit.o obj-$(CONFIG_CRYPTO_LIB_GHASH_KUNIT_TEST) +=3D ghash_kunit.o +obj-$(CONFIG_CRYPTO_LIB_HKDF_KUNIT_TEST) +=3D hkdf_kunit.o obj-$(CONFIG_CRYPTO_LIB_MD5_KUNIT_TEST) +=3D md5_kunit.o obj-$(CONFIG_CRYPTO_LIB_MLDSA_KUNIT_TEST) +=3D mldsa_kunit.o obj-$(CONFIG_CRYPTO_LIB_NH_KUNIT_TEST) +=3D nh_kunit.o diff --git a/lib/crypto/tests/hkdf_kunit.c b/lib/crypto/tests/hkdf_kunit.c new file mode 100644 index 000000000000..e8f65d761cc0 --- /dev/null +++ b/lib/crypto/tests/hkdf_kunit.c @@ -0,0 +1,276 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * KUnit tests for HKDF-SHA256, HKDF-SHA384, and HKDF-SHA512 + * + * The HKDF-SHA256 test vectors are from RFC 5869 Appendix A. RFC 5869 doe= s not + * provide SHA-384 or SHA-512 vectors, so those were generated with OpenSS= L's + * "openssl kdf" command (available since OpenSSL 3.0) using the same inpu= ts as + * the SHA-256 vectors. See: https://docs.openssl.org/3.0/man1/openssl-kd= f/ + * + * For example, the SHA-384 vector was generated with: + * + * $ openssl kdf -keylen 42 -kdfopt mode:EXTRACT_AND_EXPAND \ + * -kdfopt digest:SHA384 \ + * -kdfopt hexkey:0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b \ + * -kdfopt hexsalt:000102030405060708090a0b0c \ + * -kdfopt hexinfo:f0f1f2f3f4f5f6f7f8f9 HKDF + */ + +#include +#include + +#define RFC5869_A1_IKM_BYTE 0x0b +#define RFC5869_A1_IKM_LEN 22 +#define RFC5869_A1_SALT_START 0x00 +#define RFC5869_A1_SALT_LEN 13 +#define RFC5869_A1_INFO_START 0xf0 +#define RFC5869_A1_INFO_LEN 10 + +#define RFC5869_A2_IKM_START 0x00 +#define RFC5869_A2_IKM_LEN 80 +#define RFC5869_A2_SALT_START 0x60 +#define RFC5869_A2_SALT_LEN 80 +#define RFC5869_A2_INFO_START 0xb0 +#define RFC5869_A2_INFO_LEN 80 + +#define SEGMENT_TEST_INFO_LEN 40 +#define SEGMENT_TEST_OKM_LEN 100 + +/* RFC 5869 A.1: ikm =3D 22 x 0x0b, salt =3D 0x00..0x0c, info =3D 0xf0..0x= f9 */ +static const u8 rfc5869_a1_okm[] =3D { + 0x3c, 0xb2, 0x5f, 0x25, 0xfa, 0xac, 0xd5, 0x7a, + 0x90, 0x43, 0x4f, 0x64, 0xd0, 0x36, 0x2f, 0x2a, + 0x2d, 0x2d, 0x0a, 0x90, 0xcf, 0x1a, 0x5a, 0x4c, + 0x5d, 0xb0, 0x2d, 0x56, 0xec, 0xc4, 0xc5, 0xbf, + 0x34, 0x00, 0x72, 0x08, 0xd5, 0xb8, 0x87, 0x18, + 0x58, 0x65 +}; + +/* RFC 5869 A.2: ikm =3D 0x00..0x4f, salt =3D 0x60..0xaf, info =3D 0xb0..0= xff */ +static const u8 rfc5869_a2_okm[] =3D { + 0xb1, 0x1e, 0x39, 0x8d, 0xc8, 0x03, 0x27, 0xa1, + 0xc8, 0xe7, 0xf7, 0x8c, 0x59, 0x6a, 0x49, 0x34, + 0x4f, 0x01, 0x2e, 0xda, 0x2d, 0x4e, 0xfa, 0xd8, + 0xa0, 0x50, 0xcc, 0x4c, 0x19, 0xaf, 0xa9, 0x7c, + 0x59, 0x04, 0x5a, 0x99, 0xca, 0xc7, 0x82, 0x72, + 0x71, 0xcb, 0x41, 0xc6, 0x5e, 0x59, 0x0e, 0x09, + 0xda, 0x32, 0x75, 0x60, 0x0c, 0x2f, 0x09, 0xb8, + 0x36, 0x77, 0x93, 0xa9, 0xac, 0xa3, 0xdb, 0x71, + 0xcc, 0x30, 0xc5, 0x81, 0x79, 0xec, 0x3e, 0x87, + 0xc1, 0x4c, 0x01, 0xd5, 0xc1, 0xf3, 0x43, 0x4f, + 0x1d, 0x87 +}; + +/* RFC 5869 A.3: ikm =3D 22 x 0x0b, zero-length salt and info */ +static const u8 rfc5869_a3_okm[] =3D { + 0x8d, 0xa4, 0xe7, 0x75, 0xa5, 0x63, 0xc1, 0x8f, + 0x71, 0x5f, 0x80, 0x2a, 0x06, 0x3c, 0x5a, 0x31, + 0xb8, 0xa1, 0x1f, 0x5c, 0x5e, 0xe1, 0x87, 0x9e, + 0xc3, 0x45, 0x4e, 0x5f, 0x3c, 0x73, 0x8d, 0x2d, + 0x9d, 0x20, 0x13, 0x95, 0xfa, 0xa4, 0xb6, 0x1a, + 0x96, 0xc8 +}; + +/* HKDF-SHA384 with the RFC 5869 A.1 inputs */ +static const u8 hkdf_sha384_a1_okm[] =3D { + 0x9b, 0x50, 0x97, 0xa8, 0x60, 0x38, 0xb8, 0x05, + 0x30, 0x90, 0x76, 0xa4, 0x4b, 0x3a, 0x9f, 0x38, + 0x06, 0x3e, 0x25, 0xb5, 0x16, 0xdc, 0xbf, 0x36, + 0x9f, 0x39, 0x4c, 0xfa, 0xb4, 0x36, 0x85, 0xf7, + 0x48, 0xb6, 0x45, 0x77, 0x63, 0xe4, 0xf0, 0x20, + 0x4f, 0xc5 +}; + +/* HKDF-SHA512 with the RFC 5869 A.1 inputs */ +static const u8 hkdf_sha512_a1_okm[] =3D { + 0x83, 0x23, 0x90, 0x08, 0x6c, 0xda, 0x71, 0xfb, + 0x47, 0x62, 0x5b, 0xb5, 0xce, 0xb1, 0x68, 0xe4, + 0xc8, 0xe2, 0x6a, 0x1a, 0x16, 0xed, 0x34, 0xd9, + 0xfc, 0x7f, 0xe9, 0x2c, 0x14, 0x81, 0x57, 0x93, + 0x38, 0xda, 0x36, 0x2c, 0xb8, 0xd9, 0xf9, 0x25, + 0xd7, 0xcb +}; + +/* + * HKDF-SHA512 with the RFC 5869 A.3 inputs and okm_len =3D 150, so that t= he + * output spans multiple blocks and ends with a partial block + */ +static const u8 hkdf_sha512_a3_okm[] =3D { + 0xf5, 0xfa, 0x02, 0xb1, 0x82, 0x98, 0xa7, 0x2a, + 0x8c, 0x23, 0x89, 0x8a, 0x87, 0x03, 0x47, 0x2c, + 0x6e, 0xb1, 0x79, 0xdc, 0x20, 0x4c, 0x03, 0x42, + 0x5c, 0x97, 0x0e, 0x3b, 0x16, 0x4b, 0xf9, 0x0f, + 0xff, 0x22, 0xd0, 0x48, 0x36, 0xd0, 0xe2, 0x34, + 0x3b, 0xac, 0xc4, 0xe7, 0xcb, 0x60, 0x45, 0xfa, + 0xaa, 0x69, 0x8e, 0x0e, 0x3b, 0x3e, 0xb9, 0x13, + 0x31, 0x30, 0x6d, 0xef, 0x1d, 0xb8, 0x31, 0x9e, + 0x8a, 0x69, 0x9b, 0x5e, 0xe4, 0x5a, 0xb9, 0x93, + 0x84, 0x7d, 0xc4, 0xdf, 0x75, 0xbd, 0xe0, 0x23, + 0x69, 0x2c, 0x8c, 0x07, 0x10, 0xa6, 0x7a, 0x55, + 0x12, 0x3f, 0x10, 0xa8, 0xb2, 0xd8, 0x32, 0x7f, + 0x9e, 0xb1, 0x38, 0xda, 0x69, 0xd5, 0xbe, 0xa1, + 0xe0, 0x9a, 0x39, 0xea, 0x99, 0xa3, 0x41, 0xc0, + 0x0b, 0x2c, 0x9e, 0xe0, 0xd4, 0xba, 0x63, 0x21, + 0x15, 0xae, 0xc5, 0x16, 0xbb, 0x71, 0xe9, 0x22, + 0x7c, 0xb0, 0x0c, 0xa9, 0x8b, 0x7d, 0xfa, 0x8f, + 0x1c, 0x7e, 0xca, 0x60, 0xc9, 0x31, 0x28, 0x1f, + 0xa8, 0x5b, 0x87, 0x2f, 0x70, 0xb4 +}; + +/* + * Utility function to initialize ikm/salt/info buffers. + * Fill 'buf' with 'len' consecutive byte values beginning with 'start'. + */ +static void fill_range(u8 *buf, size_t len, u8 start) +{ + for (size_t i =3D 0; i < len; i++) + buf[i] =3D start + i; +} + +static void test_hkdf_sha256_rfc5869(struct kunit *test) +{ + struct hmac_sha256_key prk; + u8 ikm[RFC5869_A2_IKM_LEN]; + u8 salt[RFC5869_A2_SALT_LEN]; + u8 info[RFC5869_A2_INFO_LEN]; + u8 okm[sizeof(rfc5869_a2_okm)]; + struct hkdf_seg seg; + + /* A.1: basic test case */ + memset(ikm, RFC5869_A1_IKM_BYTE, RFC5869_A1_IKM_LEN); + fill_range(salt, RFC5869_A1_SALT_LEN, RFC5869_A1_SALT_START); + fill_range(info, RFC5869_A1_INFO_LEN, RFC5869_A1_INFO_START); + hkdf_sha256_extract(&prk, salt, RFC5869_A1_SALT_LEN, + ikm, RFC5869_A1_IKM_LEN); + seg =3D (struct hkdf_seg){ .data =3D info, .len =3D RFC5869_A1_INFO_LEN }; + hkdf_sha256_expand(&prk, &seg, 1, okm, sizeof(rfc5869_a1_okm)); + KUNIT_EXPECT_MEMEQ_MSG(test, okm, rfc5869_a1_okm, + sizeof(rfc5869_a1_okm), "RFC 5869 A.1"); + + /* A.2: longer inputs, multi-block output ending in a partial block */ + fill_range(ikm, RFC5869_A2_IKM_LEN, RFC5869_A2_IKM_START); + fill_range(salt, RFC5869_A2_SALT_LEN, RFC5869_A2_SALT_START); + fill_range(info, RFC5869_A2_INFO_LEN, RFC5869_A2_INFO_START); + hkdf_sha256_extract(&prk, salt, RFC5869_A2_SALT_LEN, + ikm, RFC5869_A2_IKM_LEN); + seg =3D (struct hkdf_seg){ .data =3D info, .len =3D RFC5869_A2_INFO_LEN }; + hkdf_sha256_expand(&prk, &seg, 1, okm, sizeof(rfc5869_a2_okm)); + KUNIT_EXPECT_MEMEQ_MSG(test, okm, rfc5869_a2_okm, + sizeof(rfc5869_a2_okm), "RFC 5869 A.2"); + + /* A.3: zero-length salt and info */ + memset(ikm, RFC5869_A1_IKM_BYTE, RFC5869_A1_IKM_LEN); + hkdf_sha256_extract(&prk, NULL, 0, ikm, RFC5869_A1_IKM_LEN); + hkdf_sha256_expand(&prk, NULL, 0, okm, sizeof(rfc5869_a3_okm)); + KUNIT_EXPECT_MEMEQ_MSG(test, okm, rfc5869_a3_okm, + sizeof(rfc5869_a3_okm), + "RFC 5869 A.3 with NULL salt and info"); + + /* A.3 again, with a non-NULL empty salt and an empty info segment */ + hkdf_sha256_extract(&prk, salt, 0, ikm, RFC5869_A1_IKM_LEN); + seg =3D (struct hkdf_seg){ .data =3D info, .len =3D 0 }; + hkdf_sha256_expand(&prk, &seg, 1, okm, sizeof(rfc5869_a3_okm)); + KUNIT_EXPECT_MEMEQ_MSG(test, okm, rfc5869_a3_okm, + sizeof(rfc5869_a3_okm), + "RFC 5869 A.3 with non-NULL empty salt and info"); +} + +static void test_hkdf_sha384_rfc5869_a1(struct kunit *test) +{ + struct hmac_sha384_key prk; + u8 ikm[RFC5869_A1_IKM_LEN]; + u8 salt[RFC5869_A1_SALT_LEN]; + u8 info[RFC5869_A1_INFO_LEN]; + u8 okm[sizeof(hkdf_sha384_a1_okm)]; + struct hkdf_seg seg; + + memset(ikm, RFC5869_A1_IKM_BYTE, sizeof(ikm)); + fill_range(salt, sizeof(salt), RFC5869_A1_SALT_START); + fill_range(info, sizeof(info), RFC5869_A1_INFO_START); + seg =3D (struct hkdf_seg){ .data =3D info, .len =3D sizeof(info) }; + hkdf_sha384_extract(&prk, salt, sizeof(salt), ikm, sizeof(ikm)); + hkdf_sha384_expand(&prk, &seg, 1, okm, sizeof(okm)); + KUNIT_EXPECT_MEMEQ_MSG(test, okm, hkdf_sha384_a1_okm, sizeof(okm), + "SHA-384 with RFC 5869 A.1 inputs"); +} + +static void test_hkdf_sha512_rfc5869_a1_a3(struct kunit *test) +{ + struct hmac_sha512_key prk; + u8 ikm[RFC5869_A1_IKM_LEN]; + u8 salt[RFC5869_A1_SALT_LEN]; + u8 info[RFC5869_A1_INFO_LEN]; + u8 okm[sizeof(hkdf_sha512_a3_okm)]; + struct hkdf_seg seg; + + memset(ikm, RFC5869_A1_IKM_BYTE, sizeof(ikm)); + fill_range(salt, sizeof(salt), RFC5869_A1_SALT_START); + fill_range(info, sizeof(info), RFC5869_A1_INFO_START); + seg =3D (struct hkdf_seg){ .data =3D info, .len =3D sizeof(info) }; + hkdf_sha512_extract(&prk, salt, sizeof(salt), ikm, sizeof(ikm)); + hkdf_sha512_expand(&prk, &seg, 1, okm, sizeof(hkdf_sha512_a1_okm)); + KUNIT_EXPECT_MEMEQ_MSG(test, okm, hkdf_sha512_a1_okm, + sizeof(hkdf_sha512_a1_okm), + "SHA-512 with RFC 5869 A.1 inputs"); + + hkdf_sha512_extract(&prk, NULL, 0, ikm, sizeof(ikm)); + hkdf_sha512_expand(&prk, NULL, 0, okm, sizeof(hkdf_sha512_a3_okm)); + KUNIT_EXPECT_MEMEQ_MSG(test, okm, hkdf_sha512_a3_okm, + sizeof(hkdf_sha512_a3_okm), + "SHA-512 with RFC 5869 A.3 inputs and 150-byte OKM"); +} + +/* Test that splitting info into segments does not influence the output. */ +static void test_hkdf_expand_info_segments(struct kunit *test) +{ + struct hmac_sha512_key prk; + u8 ikm[RFC5869_A1_IKM_LEN]; + u8 info[SEGMENT_TEST_INFO_LEN]; + u8 okm_ref[SEGMENT_TEST_OKM_LEN]; + u8 okm[SEGMENT_TEST_OKM_LEN]; + struct hkdf_seg segs[sizeof(info)]; + + memset(ikm, RFC5869_A1_IKM_BYTE, sizeof(ikm)); + fill_range(info, sizeof(info), 0x00); + hkdf_sha512_extract(&prk, NULL, 0, ikm, sizeof(ikm)); + + segs[0] =3D (struct hkdf_seg){ .data =3D info, .len =3D sizeof(info) }; + hkdf_sha512_expand(&prk, segs, 1, okm_ref, sizeof(okm_ref)); + + /* every possible two-way split */ + for (size_t split =3D 0; split <=3D sizeof(info); split++) { + segs[0] =3D (struct hkdf_seg){ .data =3D info, .len =3D split }; + segs[1] =3D (struct hkdf_seg){ .data =3D &info[split], + .len =3D sizeof(info) - split }; + memset(okm, 0, sizeof(okm)); + hkdf_sha512_expand(&prk, segs, 2, okm, sizeof(okm)); + KUNIT_EXPECT_MEMEQ_MSG(test, okm, okm_ref, sizeof(okm), + "failed for split=3D%zu", split); + } + + /* one segment per byte */ + for (size_t i =3D 0; i < sizeof(info); i++) + segs[i] =3D (struct hkdf_seg){ .data =3D &info[i], .len =3D 1 }; + memset(okm, 0, sizeof(okm)); + hkdf_sha512_expand(&prk, segs, sizeof(info), okm, sizeof(okm)); + KUNIT_EXPECT_MEMEQ_MSG(test, okm, okm_ref, sizeof(okm), + "one info segment per byte"); +} + +static struct kunit_case hkdf_test_cases[] =3D { + KUNIT_CASE(test_hkdf_sha256_rfc5869), + KUNIT_CASE(test_hkdf_sha384_rfc5869_a1), + KUNIT_CASE(test_hkdf_sha512_rfc5869_a1_a3), + KUNIT_CASE(test_hkdf_expand_info_segments), + {}, +}; + +static struct kunit_suite hkdf_test_suite =3D { + .name =3D "hkdf", + .test_cases =3D hkdf_test_cases, +}; + +kunit_test_suite(hkdf_test_suite); + +MODULE_DESCRIPTION("KUnit tests for HKDF-SHA256, HKDF-SHA384, and HKDF-SHA= 512"); +MODULE_LICENSE("GPL"); --=20 2.43.0 From nobody Sat Jul 25 00:42:04 2026 Received: from mout-b-203.mailbox.org (mout-b-203.mailbox.org [195.10.208.52]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 3B66439A7F4; Tue, 21 Jul 2026 14:18:18 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=195.10.208.52 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784643500; cv=none; b=rbXF7//mjzaZYCuT/DlLC8f1NIsLPNvDMkd8qvixP5Ff+dHSRCQKH9QHCftB2G0YAafrGeI/I/SZGgdxncP96ch1wmZAv6GVMi2JNZ4fehLy6IDRi04TwAOj0XdgNqIEL+ioPWwSD0Ke+mRbHnJvX6rFmzineCDxpM37CqRQBHg= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784643500; c=relaxed/simple; bh=hCB41rPj0jz44XQJs9/HaHZ+zx9tuwL7mnGPQGZUMxQ=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=RXXEeqPCezdKYvGeORomHSPSaZWpiRFBYwYZZstGxO7724oyEJEZEfkjknUk+CD/etyGutx4mH2e+X0v3QU6WNsEW3ZewITNS1ygnhDKikf6IRxjI4ymMpLp/MsVuYFK2nGaxpU7wsrXjHO0eRiXu1VSutNHzg7bjFNZrpNWS3E= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=mandelbit.com; spf=pass smtp.mailfrom=mandelbit.com; dkim=pass (2048-bit key) header.d=mandelbit.com header.i=@mandelbit.com header.b=JfcLgWp3; arc=none smtp.client-ip=195.10.208.52 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=mandelbit.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=mandelbit.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=mandelbit.com header.i=@mandelbit.com header.b="JfcLgWp3" Received: from smtp102.mailbox.org (smtp102.mailbox.org [IPv6:2001:67c:2050:b231:465::102]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange x25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) (No client certificate requested) by mout-b-203.mailbox.org (Postfix) with ESMTPS id 4h4K3H1FNjzLlrV; Tue, 21 Jul 2026 16:07:55 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=mandelbit.com; s=MBO0001; t=1784642875; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=MfzW64IqlFOm9N3cNKZG0XElklhfVlPozs5pACA20uY=; b=JfcLgWp3YwVpfMKAzDqPorRz0sTU6tAS9ht+8ttoZndQCly1IJGOwdDeEKI+5EI6x7X/Ao 4gbGfFzCPr9xykaZsNVen7OmNX5d6OL6eumW7I6dxKP/BCWTw7VJaYYspLvVJhWruhYrIg /jKFnNMiyISS+shvLdhcWnNn5kOXaIih1Y0d22NbYkpWXKDcfCwC+yqFSOEzIua81AMf9W LwPWG4vy+udrNmgmSrVABojrrUKIHHF6wA4OX1pAimgo2MHTJ+uUBNYYuO08bAge9sBdlH gJzPUQmqZuf5XersvXiSf3WDIKb/cX9EmKNv4rMk2Aw9aBPkfrHUf79r603khQ== Authentication-Results: outgoing_mbo_mout; dkim=none; spf=pass (outgoing_mbo_mout: domain of marco@mandelbit.com designates 2001:67c:2050:b231:465::102 as permitted sender) smtp.mailfrom=marco@mandelbit.com From: Marco Baffo To: linux-crypto@vger.kernel.org Cc: herbert@gondor.apana.org.au, davem@davemloft.net, ebiggers@kernel.org, Jason@zx2c4.com, ardb@kernel.org, tytso@mit.edu, jaegeuk@kernel.org, linux-fscrypt@vger.kernel.org, kbusch@kernel.org, axboe@kernel.dk, hch@lst.de, sagi@grimberg.me, hare@suse.de, linux-nvme@lists.infradead.org, andrew+netdev@lunn.ch, edumazet@google.com, kuba@kernel.org, pabeni@redhat.com, antonio@openvpn.net, sd@queasysnail.net, linux-kernel@vger.kernel.org, Marco Baffo Subject: [PATCH 3/5] fscrypt: use HKDF library functions Date: Tue, 21 Jul 2026 16:06:42 +0200 Message-ID: <20260721140644.780006-4-marco@mandelbit.com> In-Reply-To: <20260721140644.780006-1-marco@mandelbit.com> References: <20260721140644.780006-1-marco@mandelbit.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-Rspamd-Queue-Id: 4h4K3H1FNjzLlrV Content-Type: text/plain; charset="utf-8" Convert fscrypt to the common HKDF-SHA512 helpers without changing its derived keys. The info bytes are processed in the same order as before. An empty salt is equivalent to the previous 64-byte all-zero salt after HMAC key padding. Signed-off-by: Marco Baffo --- fs/crypto/hkdf.c | 56 +++++++++++------------------------------------- 1 file changed, 13 insertions(+), 43 deletions(-) diff --git a/fs/crypto/hkdf.c b/fs/crypto/hkdf.c index 706f56d0076e..7df7d466a3e4 100644 --- a/fs/crypto/hkdf.c +++ b/fs/crypto/hkdf.c @@ -1,8 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 /* - * Implementation of HKDF ("HMAC-based Extract-and-Expand Key Derivation - * Function"), aka RFC 5869. See also the original paper (Krawczyk 2010): - * "Cryptographic Extraction and Key Derivation: The HKDF Scheme". + * Key derivation using HKDF-SHA512. * * This is used to derive keys from the fscrypt master keys (or from the * "software secrets" which hardware derives from the fscrypt master keys,= in @@ -11,6 +9,8 @@ * Copyright 2019 Google LLC */ =20 +#include + #include "fscrypt_private.h" =20 /* @@ -24,19 +24,11 @@ * HKDF-SHA512 being much faster than HKDF-SHA256, as the longer digest si= ze of * SHA-512 causes HKDF-Expand to only need to do one iteration rather than= two. */ -#define HKDF_HASHLEN SHA512_DIGEST_SIZE =20 /* - * HKDF consists of two steps: - * - * 1. HKDF-Extract: extract a pseudorandom key of length HKDF_HASHLEN byte= s from - * the input keying material and optional salt. - * 2. HKDF-Expand: expand the pseudorandom key into output keying material= of - * any length, parameterized by an application-specific info string. - * * HKDF-Extract can be skipped if the input is already a pseudorandom key = of - * length HKDF_HASHLEN bytes. However, cipher modes other than AES-256-XT= S take - * shorter keys, and we don't want to force users of those modes to provide + * length SHA512_DIGEST_SIZE bytes. However, cipher modes other than AES-= 256-XTS + * take shorter keys, and we don't want to force users of those modes to p= rovide * unnecessarily long master keys. Thus fscrypt still does HKDF-Extract. = No * salt is used, since fscrypt master keys should already be pseudorandom = and * there's no way to persist a random salt per master key from kernel mode. @@ -50,13 +42,7 @@ void fscrypt_init_hkdf(struct hmac_sha512_key *hkdf, const u8 *master_key, unsigned int master_key_size) { - static const u8 default_salt[HKDF_HASHLEN]; - u8 prk[HKDF_HASHLEN]; - - hmac_sha512_usingrawkey(default_salt, sizeof(default_salt), - master_key, master_key_size, prk); - hmac_sha512_preparekey(hkdf, prk, sizeof(prk)); - memzero_explicit(prk, sizeof(prk)); + hkdf_sha512_extract(hkdf, NULL, 0, master_key, master_key_size); } =20 /* @@ -73,28 +59,12 @@ void fscrypt_hkdf_expand(const struct hmac_sha512_key *= hkdf, u8 context, const u8 *info, unsigned int infolen, u8 *okm, unsigned int okmlen) { - struct hmac_sha512_ctx ctx; - u8 counter =3D 1; - u8 tmp[HKDF_HASHLEN]; - - WARN_ON_ONCE(okmlen > 255 * HKDF_HASHLEN); + const struct hkdf_seg info_segs[] =3D { + { .data =3D "fscrypt\0", .len =3D 8 }, + { .data =3D &context, .len =3D 1 }, + { .data =3D info, .len =3D infolen }, + }; =20 - for (unsigned int i =3D 0; i < okmlen; i +=3D HKDF_HASHLEN) { - hmac_sha512_init(&ctx, hkdf); - if (i !=3D 0) - hmac_sha512_update(&ctx, &okm[i - HKDF_HASHLEN], - HKDF_HASHLEN); - hmac_sha512_update(&ctx, "fscrypt\0", 8); - hmac_sha512_update(&ctx, &context, 1); - hmac_sha512_update(&ctx, info, infolen); - hmac_sha512_update(&ctx, &counter, 1); - if (okmlen - i < HKDF_HASHLEN) { - hmac_sha512_final(&ctx, tmp); - memcpy(&okm[i], tmp, okmlen - i); - memzero_explicit(tmp, sizeof(tmp)); - } else { - hmac_sha512_final(&ctx, &okm[i]); - } - counter++; - } + hkdf_sha512_expand(hkdf, info_segs, ARRAY_SIZE(info_segs), + okm, okmlen); } --=20 2.43.0 From nobody Sat Jul 25 00:42:04 2026 Received: from mout-b-112.mailbox.org (mout-b-112.mailbox.org [195.10.208.42]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 4458938CFED; Tue, 21 Jul 2026 14:16:29 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=195.10.208.42 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784643395; cv=none; b=GfNJnyIYHM7ZlN9hQbFQ3SZYJbyqvfCj8258RdsfP3/wGaUfO/hLhJS2pbsHQRqvxL59OfIgrXum/lnMe9d+C0pxzGTqWKjjeUVc/3m8FU2Cj92eyCc5XhnkXUWaPetLPkIPwjMVy6qwIRjbE5UdZuLpM7OMUTEaJKDpo4IQyLo= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784643395; c=relaxed/simple; bh=ZVXTAcoPOMZxS+cyfGO3eOHhZH9MgO9Pdc9CYN7Zz9w=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=sQsv26jn9xEJKpUq9tFFjJ6MK8rmYsn6xIXOX+39jyDje04kP67eHBrVvrmq3aWHsfeJbade8CbOY2NFGBZ4TCuoRpDf4tBLhzIEcMSiv0bJOZAFSITx98w86uCoWl+F8nWB3ZyaGhZuhVpKBue28mcr9/nZejqEQgTZvF40z24= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=mandelbit.com; spf=pass smtp.mailfrom=mandelbit.com; dkim=pass (2048-bit key) header.d=mandelbit.com header.i=@mandelbit.com header.b=IHLUncq+; arc=none smtp.client-ip=195.10.208.42 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=mandelbit.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=mandelbit.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=mandelbit.com header.i=@mandelbit.com header.b="IHLUncq+" Received: from smtp102.mailbox.org (smtp102.mailbox.org [IPv6:2001:67c:2050:b231:465::102]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange x25519 server-signature RSA-PSS (4096 bits) server-digest SHA512) (No client certificate requested) by mout-b-112.mailbox.org (Postfix) with ESMTPS id 4h4K3Z1JwMz5wjs; Tue, 21 Jul 2026 16:08:10 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=mandelbit.com; s=MBO0001; t=1784642890; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=fEcFHyMklMggUILJ2hpXyk39iZmKc7NZk+gvN6un2mQ=; b=IHLUncq+lylS9wBKnPK6R3FOVwp7V/DqTx9Hd+6PuCbfqgWRxjqsh3ohk5xgG2qlrbfI/2 MhM3lMv2bKxJDU44eFTqaVk0JXGN89k90tT6+y5pGepMnjLLwGpsyTqlHTrL+i14lMzXhS XSt5Hiznv9mZsedY1EECeq/t5qjCu/zwe3FlojIxZoLy4UntAvmrr8ew+T5c2VUUGncG+M ctDpa1YrpBLmvSMi3AbdAP6GiscUKId4FeJ2+J1z7LczSfVk6Hnb1hIpku+TNqsWhRiL02 fPglRTjMmhsQODAR/ljDN/s5C4ZPpwVQYytQs5f10Fsrqrwr26TT8OnFXukuog== Authentication-Results: outgoing_mbo_mout; dkim=none; spf=pass (outgoing_mbo_mout: domain of marco@mandelbit.com designates 2001:67c:2050:b231:465::102 as permitted sender) smtp.mailfrom=marco@mandelbit.com From: Marco Baffo To: linux-crypto@vger.kernel.org Cc: herbert@gondor.apana.org.au, davem@davemloft.net, ebiggers@kernel.org, Jason@zx2c4.com, ardb@kernel.org, tytso@mit.edu, jaegeuk@kernel.org, linux-fscrypt@vger.kernel.org, kbusch@kernel.org, axboe@kernel.dk, hch@lst.de, sagi@grimberg.me, hare@suse.de, linux-nvme@lists.infradead.org, andrew+netdev@lunn.ch, edumazet@google.com, kuba@kernel.org, pabeni@redhat.com, antonio@openvpn.net, sd@queasysnail.net, linux-kernel@vger.kernel.org, Marco Baffo Subject: [PATCH 4/5] nvme-auth: use HKDF library functions for TLS PSK derivation Date: Tue, 21 Jul 2026 16:06:43 +0200 Message-ID: <20260721140644.780006-5-marco@mandelbit.com> In-Reply-To: <20260721140644.780006-1-marco@mandelbit.com> References: <20260721140644.780006-1-marco@mandelbit.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-Rspamd-Queue-Id: 4h4K3Z1JwMz5wjs Content-Type: text/plain; charset="utf-8" NVMe currently implements HKDF locally in nvme_auth_derive_tls_psk() and allocates a temporary buffer for the HKDF-Expand input. Use the common SHA-256 and SHA-384 HKDF helpers instead. Pass the HkdfLabel fields as separate info segments, eliminating the temporary allocation and its failure path. The derived keys are unchanged. The segments are processed as if concatenated, the expand counter byte is appended by the helper, and the empty salt is equivalent to the previous digest-sized all-zero salt after HMAC key padding. Signed-off-by: Marco Baffo --- drivers/nvme/common/auth.c | 120 +++++++++++++++++++++---------------- 1 file changed, 68 insertions(+), 52 deletions(-) diff --git a/drivers/nvme/common/auth.c b/drivers/nvme/common/auth.c index 77f1d22512f8..b44922dd1cf6 100644 --- a/drivers/nvme/common/auth.c +++ b/drivers/nvme/common/auth.c @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -691,14 +692,19 @@ EXPORT_SYMBOL_GPL(nvme_auth_generate_digest); int nvme_auth_derive_tls_psk(int hmac_id, const u8 *psk, size_t psk_len, const char *psk_digest, u8 **ret_psk) { - static const u8 default_salt[NVME_AUTH_MAX_DIGEST_SIZE]; static const char label[] =3D "tls13 nvme-tls-psk"; - const size_t label_len =3D sizeof(label) - 1; - u8 prk[NVME_AUTH_MAX_DIGEST_SIZE]; - size_t hash_len, ctx_len; - u8 *hmac_data =3D NULL, *tls_key; - size_t i; - int ret; + const u8 label_len =3D sizeof(label) - 1; + union { + struct hmac_sha256_key sha256; + struct hmac_sha384_key sha384; + } prk; + struct hkdf_seg info[6]; + size_t hash_len; + u8 output_len[2]; + u8 ctx_len; + char ctx_prefix[4]; + size_t digest_len; + u8 *tls_key; =20 hash_len =3D nvme_auth_hmac_hash_len(hmac_id); if (hash_len =3D=3D 0) { @@ -717,64 +723,74 @@ int nvme_auth_derive_tls_psk(int hmac_id, const u8 *p= sk, size_t psk_len, return -EINVAL; } =20 - /* HKDF-Extract */ - ret =3D nvme_auth_hmac(hmac_id, default_salt, hash_len, psk, psk_len, - prk); - if (ret) - goto out; - /* - * HKDF-Expand-Label (RFC 8446 section 7.1), with output length equal to - * the hash length (so only a single HMAC operation is needed) + * HKDF-Expand-Label (RFC 8446 section 7.1). The HKDF-Expand info + * parameter is an encoded HkdfLabel containing the output length, + * label, and context. */ =20 - hmac_data =3D kmalloc(/* output length */ 2 + - /* label */ 1 + label_len + - /* context (max) */ 1 + 3 + 1 + strlen(psk_digest) + - /* counter */ 1, - GFP_KERNEL); - if (!hmac_data) { - ret =3D -ENOMEM; - goto out; - } /* output length */ - i =3D 0; - hmac_data[i++] =3D hash_len >> 8; - hmac_data[i++] =3D hash_len; + output_len[0] =3D hash_len >> 8; + output_len[1] =3D hash_len; + info[0] =3D (struct hkdf_seg){ + .data =3D output_len, + .len =3D sizeof(output_len), + }; =20 /* label */ - static_assert(label_len <=3D 255); - hmac_data[i] =3D label_len; - memcpy(&hmac_data[i + 1], label, label_len); - i +=3D 1 + label_len; + static_assert(sizeof(label) - 1 <=3D 255); + info[1] =3D (struct hkdf_seg){ + .data =3D &label_len, + .len =3D sizeof(label_len), + }; + info[2] =3D (struct hkdf_seg){ + .data =3D label, + .len =3D label_len, + }; =20 /* context */ - ctx_len =3D sprintf(&hmac_data[i + 1], "%02d %s", hmac_id, psk_digest); - if (ctx_len > 255) { - ret =3D -EINVAL; - goto out; - } - hmac_data[i] =3D ctx_len; - i +=3D 1 + ctx_len; + digest_len =3D strlen(psk_digest); + if (digest_len > 255 - (sizeof(ctx_prefix) - 1)) + return -EINVAL; =20 - /* counter (this overwrites the NUL terminator written by sprintf) */ - hmac_data[i++] =3D 1; + snprintf(ctx_prefix, sizeof(ctx_prefix), "%02d ", hmac_id); + ctx_len =3D sizeof(ctx_prefix) - 1 + digest_len; + info[3] =3D (struct hkdf_seg){ + .data =3D &ctx_len, + .len =3D sizeof(ctx_len), + }; + info[4] =3D (struct hkdf_seg){ + .data =3D ctx_prefix, + .len =3D sizeof(ctx_prefix) - 1, + }; + info[5] =3D (struct hkdf_seg){ + .data =3D psk_digest, + .len =3D digest_len, + }; + + tls_key =3D kmalloc(psk_len, GFP_KERNEL); + if (!tls_key) + return -ENOMEM; =20 - tls_key =3D kzalloc(psk_len, GFP_KERNEL); - if (!tls_key) { - ret =3D -ENOMEM; - goto out; - } - ret =3D nvme_auth_hmac(hmac_id, prk, hash_len, hmac_data, i, tls_key); - if (ret) { + switch (hmac_id) { + case NVME_AUTH_HASH_SHA256: + hkdf_sha256_extract(&prk.sha256, NULL, 0, psk, psk_len); + hkdf_sha256_expand(&prk.sha256, info, ARRAY_SIZE(info), + tls_key, psk_len); + break; + case NVME_AUTH_HASH_SHA384: + hkdf_sha384_extract(&prk.sha384, NULL, 0, psk, psk_len); + hkdf_sha384_expand(&prk.sha384, info, ARRAY_SIZE(info), + tls_key, psk_len); + break; + default: + WARN_ON_ONCE(1); kfree_sensitive(tls_key); - goto out; + return -EINVAL; } + memzero_explicit(&prk, sizeof(prk)); *ret_psk =3D tls_key; -out: - kfree_sensitive(hmac_data); - memzero_explicit(prk, sizeof(prk)); - return ret; + return 0; } EXPORT_SYMBOL_GPL(nvme_auth_derive_tls_psk); =20 --=20 2.43.0 From nobody Sat Jul 25 00:42:04 2026 Received: from mout-b-210.mailbox.org (mout-b-210.mailbox.org [195.10.208.40]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 7A9FE383C66; Tue, 21 Jul 2026 14:08:28 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=195.10.208.40 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784642913; cv=none; b=ALNVq5MeI9n8F5eau0wsKoEttAk2oymj1DC10LEg+DmzBfv5tplWLcdxu/RtXy2m2CszezgOlKFr7sLhI5DK3e6HyLUCAO+NuC3Pl9g4GzVjKvWhpybvuhPOxtoclXPTqY17Yp6JCZxe6alQQrpDXWSTN3Z3Ij5HUwdfEXyrfi8= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784642913; c=relaxed/simple; bh=N0nKeQFwHnuK9YC+nVubDllLMC2GKxAlwtj15YXBpcI=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=VQ1ISDocpoWksbpmMzVu9po+7u77ebee6T7YaV8Jv3vd0z1zZLCvzmHV7c4Sg5WbRbYq/TbGnPAdZ0sJ6zPsap3mTp09Po3hV3MXe4SWS6QCtBmAGnucWL39XNzqxpasHd1Aa+5TI0vkmbODISaC+K23ajc2hZKhuj+l2J4PRTo= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=mandelbit.com; spf=pass smtp.mailfrom=mandelbit.com; dkim=pass (2048-bit key) header.d=mandelbit.com header.i=@mandelbit.com header.b=cL4a3L8m; arc=none smtp.client-ip=195.10.208.40 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=mandelbit.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=mandelbit.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=mandelbit.com header.i=@mandelbit.com header.b="cL4a3L8m" Received: from smtp102.mailbox.org (smtp102.mailbox.org [IPv6:2001:67c:2050:b231:465::102]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange x25519 server-signature RSA-PSS (4096 bits) server-digest SHA512) (No client certificate requested) by mout-b-210.mailbox.org (Postfix) with ESMTPS id 4h4K3r5d1tzFqxD; Tue, 21 Jul 2026 16:08:24 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=mandelbit.com; s=MBO0001; t=1784642904; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=PfveWYmL1wr1ejdxu7QcFKBlI7qBe206HNsUNF0rK2g=; b=cL4a3L8mR0lv9KaFycRZmerOTXMIv9TT/7LJ+4xNO43P5qUf1uuW4TbheXMhrZwiRl+BVQ P6O6HGF9p08K0XaAd9GO70H4Jz0fIHFn2CsRw3o26/PThQSRovBhyFBWh7w8PNwOmUnbTC AqUnysjkFsFw7aG9i7/8oETC3dV0ppw4YWf36O2wWo/usnlrAwgg41eLyQ+GCV8WVnzKD3 TDjad9Fdk+LhsmiJJb09z4NjKc8vAiVGjQ9dMqxpV90AKqx02Yed09/baI8FmLppq59Fbz hvY4OvnqLcXmvvtzcBdOZ8OV2ng4X9XDv3doJtLNtlW++F5h7lA5f5jN3SYJNg== Authentication-Results: outgoing_mbo_mout; dkim=none; spf=pass (outgoing_mbo_mout: domain of marco@mandelbit.com designates 2001:67c:2050:b231:465::102 as permitted sender) smtp.mailfrom=marco@mandelbit.com From: Marco Baffo To: linux-crypto@vger.kernel.org Cc: herbert@gondor.apana.org.au, davem@davemloft.net, ebiggers@kernel.org, Jason@zx2c4.com, ardb@kernel.org, tytso@mit.edu, jaegeuk@kernel.org, linux-fscrypt@vger.kernel.org, kbusch@kernel.org, axboe@kernel.dk, hch@lst.de, sagi@grimberg.me, hare@suse.de, linux-nvme@lists.infradead.org, andrew+netdev@lunn.ch, edumazet@google.com, kuba@kernel.org, pabeni@redhat.com, antonio@openvpn.net, sd@queasysnail.net, linux-kernel@vger.kernel.org, Marco Baffo Subject: [RFC PATCH 5/5] ovpn: use HKDF library functions for epoch key derivation Date: Tue, 21 Jul 2026 16:06:44 +0200 Message-ID: <20260721140644.780006-6-marco@mandelbit.com> In-Reply-To: <20260721140644.780006-1-marco@mandelbit.com> References: <20260721140644.780006-1-marco@mandelbit.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-Rspamd-Queue-Id: 4h4K3r5d1tzFqxD Content-Type: text/plain; charset="utf-8" ovpn carries a local shash-based HKDF-Expand implementation for deriving epoch PRKs, data keys, and implicit IVs. Replace it with hkdf_sha256_expand() and feed the HkdfLabel bytes as an array of segments processed as if concatenated. The derived keys are unchanged. Store PRKs as prepared HMAC-SHA256 keys embedded in struct ovpn_epoch_key instead of allocated shash transforms. ovpn_epoch_set_prk() now handles initial setup as well as epoch updates via hmac_sha256_preparekey(), replacing ovpn_epoch_init_key(). This removes the shash allocation and setkey failure paths. The embedded keys are wiped in place where the transforms were previously freed. The labels are compile-time constants, so check their encoded sizes at build time and remove the corresponding runtime error paths. Select CRYPTO_LIB_SHA256 instead of CRYPTO_HMAC and CRYPTO_SHA256, as the HKDF code was ovpn's only shash user. Signed-off-by: Marco Baffo --- drivers/net/Kconfig | 3 +- drivers/net/ovpn/crypto_epoch.c | 168 ++++++++------------------------ drivers/net/ovpn/crypto_epoch.h | 11 +-- drivers/net/ovpn/crypto_key.c | 50 +++------- 4 files changed, 60 insertions(+), 172 deletions(-) diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig index cd4193ce51b4..6f958535e60e 100644 --- a/drivers/net/Kconfig +++ b/drivers/net/Kconfig @@ -109,8 +109,7 @@ config OVPN select CRYPTO_AES select CRYPTO_GCM select CRYPTO_CHACHA20POLY1305 - select CRYPTO_HMAC - select CRYPTO_SHA256 + select CRYPTO_LIB_SHA256 select STREAM_PARSER help This module enhances the performance of the OpenVPN userspace software diff --git a/drivers/net/ovpn/crypto_epoch.c b/drivers/net/ovpn/crypto_epoc= h.c index bfdf030bb0fb..f63537b3b9f5 100644 --- a/drivers/net/ovpn/crypto_epoch.c +++ b/drivers/net/ovpn/crypto_epoch.c @@ -7,7 +7,7 @@ * Antonio Quartulli */ =20 -#include +#include #include =20 #include "crypto_epoch.h" @@ -17,111 +17,36 @@ #define OVPN_EPOCH_DATA_IV_LABEL "data_iv" #define OVPN_EPOCH_UPDATE_LABEL "datakey upd" #define OVPN_EPOCH_LABEL_PREFIX "ovpn " -#define OVPN_EPOCH_INFO_MAX_SIZE 21 =20 -#define OVPN_EPOCH_HASH_ALG "hmac(sha256)" +static_assert(OVPN_EPOCH_PRK_SIZE =3D=3D SHA256_DIGEST_SIZE); =20 -static int ovpn_hkdf_expand(struct crypto_shash *shash, const u8 *info, - size_t info_len, u8 *okm, size_t okm_len) -{ - unsigned int prev_len =3D 0, digest_len; - SHASH_DESC_ON_STACK(desc, shash); - u8 prev[OVPN_EPOCH_PRK_SIZE]; - size_t copied =3D 0, todo; - u8 counter =3D 1; - int ret =3D 0; - - digest_len =3D crypto_shash_digestsize(shash); - if (WARN_ON_ONCE(digest_len !=3D sizeof(prev))) - return -EINVAL; - - desc->tfm =3D shash; - - /* T(0) is the empty string */ - while (copied < okm_len) { - /* T(n) =3D HMAC-Hash(PRK, T(n-1) | info | n) */ - ret =3D crypto_shash_init(desc); - if (ret) - goto out; - ret =3D crypto_shash_update(desc, prev, prev_len); - if (ret) - goto out; - ret =3D crypto_shash_update(desc, info, info_len); - if (ret) - goto out; - ret =3D crypto_shash_update(desc, &counter, sizeof(counter)); - if (ret) - goto out; - ret =3D crypto_shash_final(desc, prev); - if (ret) - goto out; - - prev_len =3D digest_len; - /* copy a full digest block or the final partial block */ - todo =3D min_t(size_t, digest_len, okm_len - copied); - memcpy(okm + copied, prev, todo); - copied +=3D todo; - counter++; - } - -out: - memzero_explicit(prev, sizeof(prev)); - shash_desc_zero(desc); - return ret; -} - -struct crypto_shash *ovpn_epoch_init_key(const u8 *key, size_t key_size) -{ - struct crypto_shash *shash; - int ret; - - shash =3D crypto_alloc_shash(OVPN_EPOCH_HASH_ALG, 0, 0); - if (IS_ERR(shash)) - return shash; - - if (key_size !=3D crypto_shash_digestsize(shash)) { - crypto_free_shash(shash); - return ERR_PTR(-EINVAL); - } +/* The prefixed label length must fit its u8 field. */ +#define OVPN_EPOCH_FULL_LABEL_LEN(label) \ + (sizeof(OVPN_EPOCH_LABEL_PREFIX) - 1 + sizeof(label) - 1) =20 - /* store the PRK as the shash key so it can be advanced in place */ - ret =3D crypto_shash_setkey(shash, key, key_size); - if (ret) { - crypto_free_shash(shash); - return ERR_PTR(ret); - } - - return shash; -} +static_assert(OVPN_EPOCH_FULL_LABEL_LEN(OVPN_EPOCH_DATA_KEY_LABEL) <=3D U8= _MAX); +static_assert(OVPN_EPOCH_FULL_LABEL_LEN(OVPN_EPOCH_DATA_IV_LABEL) <=3D U8_= MAX); +static_assert(OVPN_EPOCH_FULL_LABEL_LEN(OVPN_EPOCH_UPDATE_LABEL) <=3D U8_M= AX); =20 -static int ovpn_expand_label(struct crypto_shash *shash, const u8 *label, - size_t label_len, u8 *okm, u16 okm_len) +static void ovpn_expand_label(const struct hmac_sha256_key *prk, + const u8 *label, + size_t label_len, u8 *okm, u16 okm_len) { static const u8 label_prefix[] =3D OVPN_EPOCH_LABEL_PREFIX; - u8 prefix_len =3D sizeof(label_prefix) - 1, full_len; - u8 info[OVPN_EPOCH_INFO_MAX_SIZE]; - u16 info_len; - int ret; - - if (WARN_ON_ONCE(!label_len || label_len > 250)) - return -EINVAL; - - full_len =3D prefix_len + label_len; - info_len =3D sizeof(okm_len) + full_len + 2; - if (WARN_ON_ONCE(info_len > sizeof(info))) - return -EINVAL; - - /* encode length, "ovpn " label and empty context */ - put_unaligned_be16(okm_len, info); - info[2] =3D full_len; - memcpy(&info[3], label_prefix, prefix_len); - memcpy(&info[3 + prefix_len], label, label_len); - info[3 + full_len] =3D 0; - - ret =3D ovpn_hkdf_expand(shash, info, info_len, okm, okm_len); - memzero_explicit(info, info_len); - - return ret; + static const u8 empty_context_len; + u8 hdr[3]; + const struct hkdf_seg info[] =3D { + { .data =3D hdr, .len =3D sizeof(hdr) }, + { .data =3D label_prefix, .len =3D sizeof(label_prefix) - 1 }, + { .data =3D label, .len =3D label_len }, + { .data =3D &empty_context_len, .len =3D 1 }, + }; + + /* encode okm length and prefixed label length */ + put_unaligned_be16(okm_len, hdr); + hdr[2] =3D sizeof(label_prefix) - 1 + label_len; + + hkdf_sha256_expand(prk, info, ARRAY_SIZE(info), okm, okm_len); } =20 /** @@ -140,27 +65,18 @@ int ovpn_epoch_derive_next_prk(const struct ovpn_epoch= _key *epoch_key, if (unlikely(epoch_key->epoch =3D=3D OVPN_MAX_EPOCH)) return -ERANGE; =20 - if (WARN_ON_ONCE(OVPN_EPOCH_PRK_SIZE !=3D - crypto_shash_digestsize(epoch_key->shash))) - return -EINVAL; + ovpn_expand_label(&epoch_key->prk, OVPN_EPOCH_UPDATE_LABEL, + sizeof(OVPN_EPOCH_UPDATE_LABEL) - 1, + next_prk, OVPN_EPOCH_PRK_SIZE); =20 - return ovpn_expand_label(epoch_key->shash, OVPN_EPOCH_UPDATE_LABEL, - sizeof(OVPN_EPOCH_UPDATE_LABEL) - 1, - next_prk, OVPN_EPOCH_PRK_SIZE); + return 0; } =20 -int ovpn_epoch_set_prk(struct ovpn_epoch_key *epoch_key, const u8 prk[], - u16 epoch) +void ovpn_epoch_set_prk(struct ovpn_epoch_key *epoch_key, const u8 prk[], + u16 epoch) { - int ret; - - ret =3D crypto_shash_setkey(epoch_key->shash, prk, OVPN_EPOCH_PRK_SIZE); - if (ret) - return ret; - + hmac_sha256_preparekey(&epoch_key->prk, prk, OVPN_EPOCH_PRK_SIZE); epoch_key->epoch =3D epoch; - - return 0; } =20 /** @@ -182,7 +98,7 @@ int ovpn_epoch_iterate(struct ovpn_epoch_key *epoch_key) goto out; =20 /* expose the next epoch only after its PRK is installed */ - ret =3D ovpn_epoch_set_prk(epoch_key, key, epoch_key->epoch + 1); + ovpn_epoch_set_prk(epoch_key, key, epoch_key->epoch + 1); =20 out: memzero_explicit(key, sizeof(key)); @@ -203,21 +119,19 @@ int ovpn_epoch_iterate(struct ovpn_epoch_key *epoch_k= ey) int ovpn_epoch_derive_key(const struct ovpn_epoch_key *epoch_key, u8 cipher_key[], u8 implicit_iv[]) { - int ret; - if (WARN_ON_ONCE(!epoch_key->cipher_key_len || epoch_key->cipher_key_len > OVPN_EPOCH_PRK_SIZE)) return -EINVAL; =20 /* derive the concrete AEAD key for the current epoch */ - ret =3D ovpn_expand_label(epoch_key->shash, OVPN_EPOCH_DATA_KEY_LABEL, - sizeof(OVPN_EPOCH_DATA_KEY_LABEL) - 1, - cipher_key, epoch_key->cipher_key_len); - if (ret) - return ret; + ovpn_expand_label(&epoch_key->prk, OVPN_EPOCH_DATA_KEY_LABEL, + sizeof(OVPN_EPOCH_DATA_KEY_LABEL) - 1, + cipher_key, epoch_key->cipher_key_len); =20 /* derive the implicit IV paired with that AEAD key */ - return ovpn_expand_label(epoch_key->shash, OVPN_EPOCH_DATA_IV_LABEL, - sizeof(OVPN_EPOCH_DATA_IV_LABEL) - 1, - implicit_iv, OVPN_NONCE_SIZE); + ovpn_expand_label(&epoch_key->prk, OVPN_EPOCH_DATA_IV_LABEL, + sizeof(OVPN_EPOCH_DATA_IV_LABEL) - 1, + implicit_iv, OVPN_NONCE_SIZE); + + return 0; } diff --git a/drivers/net/ovpn/crypto_epoch.h b/drivers/net/ovpn/crypto_epoc= h.h index 0dce1e905917..c14b46a6728d 100644 --- a/drivers/net/ovpn/crypto_epoch.h +++ b/drivers/net/ovpn/crypto_epoch.h @@ -10,7 +10,7 @@ #ifndef _NET_OVPN_OVPNEPOCH_H_ #define _NET_OVPN_OVPNEPOCH_H_ =20 -#include +#include #include #include #include @@ -21,11 +21,11 @@ =20 struct ovpn_key_ctx; =20 -/* crypto handle used for key derivation through HKDF-Expand-Label */ +/* key derivation state for HKDF-Expand-Label */ struct ovpn_epoch_key { u16 epoch; unsigned int cipher_key_len; - struct crypto_shash *shash; + struct hmac_sha256_key prk; }; =20 /* ring buffer of prederived future epoch data keys */ @@ -46,11 +46,10 @@ ovpn_epoch_future_keys_count(const struct ovpn_future_k= eys *fk) OVPN_EPOCH_FUTURE_KEYS_COUNT; } =20 -struct crypto_shash *ovpn_epoch_init_key(const u8 *key, size_t key_size); int ovpn_epoch_derive_next_prk(const struct ovpn_epoch_key *epoch_key, u8 next_prk[]); -int ovpn_epoch_set_prk(struct ovpn_epoch_key *epoch_key, const u8 prk[], - u16 epoch); +void ovpn_epoch_set_prk(struct ovpn_epoch_key *epoch_key, const u8 prk[], + u16 epoch); int ovpn_epoch_iterate(struct ovpn_epoch_key *epoch_key); int ovpn_epoch_derive_key(const struct ovpn_epoch_key *epoch_key, u8 cipher_key[], u8 implicit_iv[]); diff --git a/drivers/net/ovpn/crypto_key.c b/drivers/net/ovpn/crypto_key.c index 68ebf16c66c8..f07795fc62b9 100644 --- a/drivers/net/ovpn/crypto_key.c +++ b/drivers/net/ovpn/crypto_key.c @@ -8,7 +8,6 @@ */ =20 #include -#include #include =20 #include "ovpnpriv.h" @@ -271,7 +270,6 @@ static void ovpn_refill_future_buffer(struct ovpn_crypt= o_key_slot *ks, u16 replaced =3D 0, created =3D 0, free_slots, i; const struct ovpn_epoch_key *source_key; struct ovpn_epoch_key scratch_key =3D {}; - size_t prk_size =3D OVPN_EPOCH_PRK_SIZE; struct ovpn_future_keys *future_keys; struct ovpn_epoch_key *epoch_key; struct ovpn_key_ctx __rcu **slot; @@ -304,26 +302,12 @@ static void ovpn_refill_future_buffer(struct ovpn_cry= pto_key_slot *ks, /* derive new keys without holding the ring lock */ scratch_key.cipher_key_len =3D epoch_key->cipher_key_len; for (created =3D 0; created < free_slots; created++) { - source_key =3D scratch_key.shash ? &scratch_key : epoch_key; + source_key =3D created ? &scratch_key : epoch_key; ret =3D ovpn_epoch_derive_next_prk(source_key, next_prk); if (ret) goto err; =20 - if (!scratch_key.shash) { - scratch_key.shash =3D ovpn_epoch_init_key(next_prk, - prk_size); - if (IS_ERR(scratch_key.shash)) { - ret =3D PTR_ERR(scratch_key.shash); - scratch_key.shash =3D NULL; - goto err; - } - scratch_key.epoch =3D epoch_key->epoch + 1; - } else { - ret =3D ovpn_epoch_set_prk(&scratch_key, next_prk, - scratch_key.epoch + 1); - if (ret) - goto err; - } + ovpn_epoch_set_prk(&scratch_key, next_prk, source_key->epoch + 1); =20 new_futures[created] =3D ovpn_key_ctx_create_epoch(encrypt, ks->alg_name, @@ -334,9 +318,7 @@ static void ovpn_refill_future_buffer(struct ovpn_crypt= o_key_slot *ks, } } =20 - ret =3D ovpn_epoch_set_prk(epoch_key, next_prk, scratch_key.epoch); - if (ret) - goto err; + ovpn_epoch_set_prk(epoch_key, next_prk, scratch_key.epoch); =20 /* insert generated keys under the selected ring lock */ spin_lock_bh(lock); @@ -362,7 +344,7 @@ static void ovpn_refill_future_buffer(struct ovpn_crypt= o_key_slot *ks, for (i =3D replaced; i < created; i++) ovpn_key_ctx_put(new_futures[i]); =20 - crypto_free_shash(scratch_key.shash); + memzero_explicit(&scratch_key.prk, sizeof(scratch_key.prk)); memzero_explicit(next_prk, sizeof(next_prk)); =20 /* keep refilling until the ring is full */ @@ -374,8 +356,7 @@ static void ovpn_refill_future_buffer(struct ovpn_crypt= o_key_slot *ks, err: for (i =3D 0; i < created; i++) ovpn_key_ctx_put(new_futures[i]); - if (scratch_key.shash) - crypto_free_shash(scratch_key.shash); + memzero_explicit(&scratch_key.prk, sizeof(scratch_key.prk)); memzero_explicit(next_prk, sizeof(next_prk)); } =20 @@ -409,10 +390,10 @@ void ovpn_crypto_key_slot_destroy(struct ovpn_crypto_= key_slot *ks) ovpn_key_ctx_put(rcu_access_pointer(ks->decrypt)); =20 if (ks->epoch_format) { - if (ks->epoch_key_send.shash) - crypto_free_shash(ks->epoch_key_send.shash); - if (ks->epoch_key_recv.shash) - crypto_free_shash(ks->epoch_key_recv.shash); + memzero_explicit(&ks->epoch_key_send.prk, + sizeof(ks->epoch_key_send.prk)); + memzero_explicit(&ks->epoch_key_recv.prk, + sizeof(ks->epoch_key_recv.prk)); ovpn_key_ctx_put(rcu_access_pointer(ks->retiring_key)); for (i =3D 0; i < OVPN_EPOCH_FUTURE_KEYS_COUNT; i++) { future =3D rcu_access_pointer(ks->future_tx_keys.keys[i]); @@ -428,17 +409,12 @@ void ovpn_crypto_key_slot_destroy(struct ovpn_crypto_= key_slot *ks) static int ovpn_epoch_key_init(struct ovpn_epoch_key *epoch_key, const struct ovpn_epoch_prk *prk) { - int ret; - - epoch_key->shash =3D ovpn_epoch_init_key(prk->key, prk->key_size); - if (IS_ERR(epoch_key->shash)) { - ret =3D PTR_ERR(epoch_key->shash); - epoch_key->shash =3D NULL; - return ret; - } + if (prk->key_size !=3D OVPN_EPOCH_PRK_SIZE) + return -EINVAL; =20 /* epoch 0 is reserved for direct keys, so epoch keys start at 1 */ - epoch_key->epoch =3D 1; + ovpn_epoch_set_prk(epoch_key, prk->key, 1); + epoch_key->cipher_key_len =3D prk->cipher_key_len; =20 return 0; --=20 2.43.0