Re: [PATCH v2 3/7] crypto: add RFC5869 HKDF

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On Sun, Jan 24, 2021 at 03:03:28PM +0100, Stephan Müller wrote:
> RFC5869 specifies an extract and expand two-step key derivation
> function. The HKDF implementation is provided as a service function that
> operates on a caller-provided HMAC handle. The caller has to allocate
> the HMAC shash handle and then can invoke the HKDF service functions.
> The HKDF implementation ensures that the entire state is kept with the
> HMAC shash handle which implies that no additional state is required to
> be maintained by the HKDF implementation.
> 
> The extract function is invoked via the crypto_hkdf_extract call. RFC5869
> allows two optional parameters to be provided to the extract operation:
> the salt and input key material (IKM). Both are to be provided with the
> seed parameter where the salt is the first entry of the seed parameter
> and all subsequent entries are handled as IKM. If the caller intends to
> invoke the HKDF without salt, it has to provide a NULL/0 entry as first
> entry in seed.

The part about the "seed parameter" is outdated.

> The expand function is invoked via crypto_hkdf_expand and can be
> invoked multiple times. This function allows the caller to provide a
> context for the key derivation operation. As specified in RFC5869, it is
> optional. In case such context is not provided, the caller must provide
> NULL / 0 for the info / info_nvec parameters.

This commit message doesn't actually mention of *why* this patch is useful.  Is
it because there are going to be more uses of HKDF in the kernel besides the one
in fs/crypto/?  If so, what are those planned uses?

> diff --git a/crypto/Kconfig b/crypto/Kconfig
> index 9f375c2350f5..661287d7283b 100644
> --- a/crypto/Kconfig
> +++ b/crypto/Kconfig
> @@ -1862,6 +1862,13 @@ config CRYPTO_JITTERENTROPY
>  	  random numbers. This Jitterentropy RNG registers with
>  	  the kernel crypto API and can be used by any caller.
>  
> +config CRYPTO_HKDF
> +	tristate "Extract and Expand HKDF (RFC 5869)"
> +	select CRYPTO_HASH
> +	help
> +	  Enable the extract and expand key derivation function compliant
> +	  to RFC 5869.

This is just a library function, so it shouldn't be user-selectable.
It should just be selected by the kconfig options that need it.

> diff --git a/crypto/hkdf.c b/crypto/hkdf.c
> new file mode 100644
> index 000000000000..8e80eca202e7
> --- /dev/null
> +++ b/crypto/hkdf.c
> @@ -0,0 +1,199 @@
> +// SPDX-License-Identifier: GPL-2.0
> +
> +/*
> + * HMAC-based Extract-and-Expand Key Derivation Function (conformant to RFC5869)
> + *
> + * Copyright (C) 2020, Stephan Mueller <smueller@xxxxxxxxxx>
> + */
> +
> +#include <linux/module.h>
> +#include <crypto/hkdf.h>
> +#include <crypto/internal/kdf_selftest.h>
> +
> +/*
> + * HKDF expand phase
> + */
> +int crypto_hkdf_expand(struct crypto_shash *kmd,
> +		       const struct kvec *info, unsigned int info_nvec,
> +		       u8 *dst, unsigned int dlen)
> +{
> +	SHASH_DESC_ON_STACK(desc, kmd);
> +	const unsigned int h = crypto_shash_digestsize(kmd), dlen_orig = dlen;
> +	unsigned int i;
> +	int err = 0;
> +	u8 *dst_orig = dst;
> +	const u8 *prev = NULL;
> +	u8 ctr = 0x01;
> +
> +	if (dlen > h * 255)
> +		return -EINVAL;
> +
> +	desc->tfm = kmd;
> +
> +	/* T(1) and following */
> +	while (dlen) {
> +		err = crypto_shash_init(desc);
> +		if (err)
> +			goto out;
> +
> +		if (prev) {
> +			err = crypto_shash_update(desc, prev, h);
> +			if (err)
> +				goto out;
> +		}
> +
> +		for (i = 0; i < info_nvec; i++) {
> +			err = crypto_shash_update(desc, info[i].iov_base,
> +						  info[i].iov_len);
> +			if (err)
> +				goto out;
> +		}
> +
> +		if (dlen < h) {
> +			u8 tmpbuffer[HASH_MAX_DIGESTSIZE];
> +
> +			err = crypto_shash_finup(desc, &ctr, 1, tmpbuffer);
> +			if (err)
> +				goto out;
> +			memcpy(dst, tmpbuffer, dlen);
> +			memzero_explicit(tmpbuffer, h);
> +			goto out;
> +		}
> +
> +		err = crypto_shash_finup(desc, &ctr, 1, dst);
> +		if (err)
> +			goto out;
> +
> +		prev = dst;
> +		dst += h;
> +		dlen -= h;
> +		ctr++;
> +	}
> +
> +out:
> +	if (err)
> +		memzero_explicit(dst_orig, dlen_orig);
> +	shash_desc_zero(desc);
> +	return err;
> +}
> +EXPORT_SYMBOL(crypto_hkdf_expand);

EXPORT_SYMBOL_GPL?

> +
> +/*
> + * HKDF extract phase.
> + */
> +int crypto_hkdf_extract(struct crypto_shash *kmd,
> +			const u8 *salt, size_t saltlen,
> +			const u8 *ikm, size_t ikmlen)
> +{
> +	SHASH_DESC_ON_STACK(desc, kmd);
> +	unsigned int h = crypto_shash_digestsize(kmd);
> +	int err;
> +	static const u8 null_salt[HASH_MAX_DIGESTSIZE] = { 0 };
> +	u8 prk[HASH_MAX_DIGESTSIZE];
> +
> +	desc->tfm = kmd;
> +
> +	if (salt && saltlen) {

Checking 'salt && saltlen' like this is poor practice, as then if people
accidentally use salt == NULL && saltlen != 0 when they intended to use a salt,
the bug won't be detected.  Just doing 'if (saltlen)' would be better.

> +
> +	/* Extract the PRK */
> +	err = crypto_shash_init(desc);
> +	if (err)
> +		goto err;
> +
> +	err = crypto_shash_finup(desc, ikm, ikmlen, prk);
> +	if (err)
> +		goto err;

This should use crypto_shash_digest() instead of crypto_shash_init() +
crypto_shash_finup().

> +/* Test vectors from RFC 5869 appendix A */
> +static const struct kdf_testvec hkdf_hmac_sha256_tv_template[] = {
> +	{
> +		/* salt */
> +		.key = "\x00\x01\x02\x03\x04\x05\x06\x07"
> +		       "\x08\x09\x0a\x0b\x0c",
> +		.keylen  = 13,
> +		.ikm = "\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b"
> +		       "\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b"
> +		       "\x0b\x0b\x0b\x0b\x0b\x0b",
> +		.ikmlen = 22,
> +		.info = {
> +			.iov_base = "\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7"
> +				    "\xf8\xf9",
> +			.iov_len  = 10
> +		},
> +		.expected	  = "\x3c\xb2\x5f\x25\xfa\xac\xd5\x7a"
> +				    "\x90\x43\x4f\x64\xd0\x36\x2f\x2a"
> +				    "\x2d\x2d\x0a\x90\xcf\x1a\x5a\x4c"
> +				    "\x5d\xb0\x2d\x56\xec\xc4\xc5\xbf"
> +				    "\x34\x00\x72\x08\xd5\xb8\x87\x18"
> +				    "\x58\x65",
> +		.expectedlen	  = 42
> +	}, {
> +		/* salt */
> +		.key = NULL,
> +		.keylen  = 0,
> +		.ikm = "\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b"
> +		       "\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b"
> +		       "\x0b\x0b\x0b\x0b\x0b\x0b",
> +		.ikmlen  = 22,
> +		.info = {
> +			.iov_base = NULL,
> +			.iov_len  = 0
> +		},
> +		.expected	  = "\x8d\xa4\xe7\x75\xa5\x63\xc1\x8f"
> +				    "\x71\x5f\x80\x2a\x06\x3c\x5a\x31"
> +				    "\xb8\xa1\x1f\x5c\x5e\xe1\x87\x9e"
> +				    "\xc3\x45\x4e\x5f\x3c\x73\x8d\x2d"
> +				    "\x9d\x20\x13\x95\xfa\xa4\xb6\x1a"
> +				    "\x96\xc8",
> +		.expectedlen	  = 42
> +	}
> +};

The case of multiple entries in the 'info' kvec isn't being tested.

Also, it is confusing having both 'key' and 'ikm'.  'key' really should be
'salt'.

> diff --git a/include/crypto/hkdf.h b/include/crypto/hkdf.h
> new file mode 100644
> index 000000000000..c6989f786860
> --- /dev/null
> +++ b/include/crypto/hkdf.h
> @@ -0,0 +1,48 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +
> +/*
> + * Copyright (C) 2020, Stephan Mueller <smueller@xxxxxxxxxx>
> + */
> +
> +#ifndef _CRYPTO_HKDF_H
> +#define _CRYPTO_HKDF_H
> +
> +#include <crypto/hash.h>
> +#include <linux/uio.h>
> +
> +/**
> + * RFC 5869 HKDF expand operation
> + *
> + * @kmd Keyed message digest whose key was set with crypto_hkdf_extract
> + * @info optional context and application specific information - this may be
> + *	 NULL
> + * @info_vec number of optional context/application specific information entries
> + * @dst destination buffer that the caller already allocated
> + * @dlen length of the destination buffer - the KDF derives that amount of
> + *	 bytes.
> + *
> + * @return 0 on success, < 0 on error
> + */
> +int crypto_hkdf_expand(struct crypto_shash *kmd,
> +		       const struct kvec *info, unsigned int info_nvec,
> +		       u8 *dst, unsigned int dlen);
> +
> +/**
> + * RFC 5869 HKDF extract operation
> + *
> + * @kmd Keyed message digest allocated by the caller. The key should not have
> + *	been set.
> + * @salt The salt used for the KDF. It is permissible to provide NULL as salt
> + *	 which implies that the default salt is used.
> + * @saltlen Length of the salt buffer.
> + * @ikm The input key material (IKM). It is permissible to provide NULL as IKM.
> + * @ikmlen Length of the IKM buffer
> + * @seed_nvec number of seed entries (must be at least 1)

seed_nvec no longer exists.

- Eric



[Index of Archives]     [Kernel]     [Gnu Classpath]     [Gnu Crypto]     [DM Crypt]     [Netfilter]     [Bugtraq]

  Powered by Linux