Received: by 2002:ad5:474a:0:0:0:0:0 with SMTP id i10csp1345277imu; Fri, 11 Jan 2019 21:20:38 -0800 (PST) X-Google-Smtp-Source: ALg8bN581+BqI3+It5NGx8XDgG4++VH5N+cfQ+nSv52tzIN5jVM7qL1U9uEImImjG6J6sB0zqlwW X-Received: by 2002:a65:534b:: with SMTP id w11mr5808180pgr.125.1547270438670; Fri, 11 Jan 2019 21:20:38 -0800 (PST) ARC-Seal: i=1; a=rsa-sha256; t=1547270438; cv=none; d=google.com; s=arc-20160816; b=yWBzwcYwVTV3O9NCKCTdtBV/tyK2KBD/2ivC3pOyoEbcukwkjNQUfuSy4GXHZp513l l8K7YE9sidevnGkzelMA8JvLM8XYXoPMBwpsEmKyOoPpmdzUNGyfpAHh63Z0eKrO/vQI X7iX1L6TcoBfM3D+uiZLtBd2HtrP56ffj7gRmqoOMoSW7iwqZPg1fI6cOomUvyKMenL1 s++U7zDu17V0GijrLUX1D0jn1u8Lk969lLJGaU4WKq98lh86zn0KGB17OfiIw9xyZ7tF 93AXpHEWt9GKAzyiOTURE3WxLiR1j2aXOkVudPoG1rQwQRTTKUwXLzYc5pTQHZfrnY81 eB8A== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-id:precedence:sender:user-agent:in-reply-to :content-transfer-encoding:content-disposition:mime-version :references:message-id:subject:cc:to:from:date:dkim-signature; bh=l6/lpH6CP6UuZscsa0TstdaEfwzBSKpq6EXInWzZLtc=; b=vOtk3zG1jLKidwi6IkjlOwwwqIIcQ14o1LqMf4k7z2q0nDK8tNHTQX/robhDPUjF1q 6lvfMiLGEtqOLBWtb0wpDt9PTCKkckgYC05yTGyQIoa1OtSY6+ElZlHItyM1bXNY8AjD g92wtEk0kgf5NQPYd6xnKclDrff0aKsQMmm+Zvm0PNVmPywNKJuE5iUwEAUuhLSApkp8 NOyo+i7/1gA9clElGN9BK9Htm7Pan0CYcgsPhLSObdDVJV9IIyJ0Sqg2d8y7+38vGWqv QpoBFuwgdvG0pRKILYb0xYrd+L4sh61ZdlhLi9oRLl+zQ5KCuWAXZJpNJEIG9jeatcVV GbRg== ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@kernel.org header.s=default header.b=sXtDlGY4; spf=pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=pass (p=NONE sp=NONE dis=NONE) header.from=kernel.org Return-Path: Received: from vger.kernel.org (vger.kernel.org. [209.132.180.67]) by mx.google.com with ESMTP id s5si14773083plp.139.2019.01.11.21.19.52; Fri, 11 Jan 2019 21:20:38 -0800 (PST) Received-SPF: pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) client-ip=209.132.180.67; Authentication-Results: mx.google.com; dkim=pass header.i=@kernel.org header.s=default header.b=sXtDlGY4; spf=pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=pass (p=NONE sp=NONE dis=NONE) header.from=kernel.org Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1725837AbfALFM6 (ORCPT + 99 others); Sat, 12 Jan 2019 00:12:58 -0500 Received: from mail.kernel.org ([198.145.29.99]:57876 "EHLO mail.kernel.org" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1725372AbfALFM6 (ORCPT ); Sat, 12 Jan 2019 00:12:58 -0500 Received: from sol.localdomain (c-24-23-143-129.hsd1.ca.comcast.net [24.23.143.129]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mail.kernel.org (Postfix) with ESMTPSA id A101320870; Sat, 12 Jan 2019 05:12:55 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=default; t=1547269976; bh=D17UknQHVPfr29u1eC1cac4xXP2PHxFdkCNANREZH3w=; h=Date:From:To:Cc:Subject:References:In-Reply-To:From; b=sXtDlGY4roeXE0AtH4I+hbY+V4LfSO7Tt7WwdhJNaZyiAy8yncTtvPKkO5tbZba6Z CRjlJVs/i8RKVknWXkk/YB7qRtmAgmK11FV0HtuPsfwxCCz2JzqEfzkH3zykkLzlNg BjNYVrDNw2LvQYK1VMAapKBP2VwSNhVTcnKiu2io= Date: Fri, 11 Jan 2019 21:12:54 -0800 From: Eric Biggers To: Stephan =?iso-8859-1?Q?M=FCller?= Cc: Herbert Xu , James Bottomley , Andy Lutomirski , "Lee, Chun-Yi" , "Rafael J . Wysocki" , Pavel Machek , linux-kernel@vger.kernel.org, linux-pm@vger.kernel.org, keyrings@vger.kernel.org, "Rafael J. Wysocki" , Chen Yu , Oliver Neukum , Ryan Chen , David Howells , Giovanni Gherdovich , Randy Dunlap , Jann Horn , Andy Lutomirski , linux-crypto@vger.kernel.org Subject: Re: [PATCH 4/6] crypto: hkdf - RFC5869 Key Derivation Function Message-ID: <20190112051252.GA639@sol.localdomain> References: <20190103143227.9138-1-jlee@suse.com> <20190109082103.GA8586@sol.localdomain> <9733066.Vrs4h5eWcW@positron.chronox.de> <2423373.Zd5ThvQH5g@positron.chronox.de> MIME-Version: 1.0 Content-Type: text/plain; charset=iso-8859-1 Content-Disposition: inline Content-Transfer-Encoding: 8bit In-Reply-To: <2423373.Zd5ThvQH5g@positron.chronox.de> User-Agent: Mutt/1.11.2 (2019-01-07) Sender: linux-kernel-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Hi Stephan, On Fri, Jan 11, 2019 at 08:10:39PM +0100, Stephan M?ller wrote: > The RFC5869 compliant Key Derivation Function is implemented as a > random number generator considering that it behaves like a deterministic > RNG. > Thanks for the proof of concept! I guess it ended up okay. But can you explain more the benefits of using the crypto_rng interface, as opposed to just some crypto_hkdf_*() helper functions that are exported for modules to use? > The extract and expand phases use different instances of the underlying > keyed message digest cipher to ensure that while the extraction phase > generates a new key for the expansion phase, the cipher for the > expansion phase can still be used. This approach is intended to aid > multi-threaded uses cases. I think you partially misunderstood what I was asking for. One HMAC tfm is sufficient as long as HKDF-Expand is separated from HKDF-Extract, which you've done. So just use one HMAC tfm, and in crypto_hkdf_seed() key it with the 'salt', and then afterwards with the 'prk'. Also everywhere in this patchset, please avoid using the word "cipher" to refer to algorithms that are not encryption/decryption. I know a lot of the crypto API docs do this, but I think it is a mistake and confusing. Hash algorithms and KDFs are not "ciphers". > > Signed-off-by: Stephan Mueller > --- > crypto/Kconfig | 6 + > crypto/Makefile | 1 + > crypto/hkdf.c | 290 ++++++++++++++++++++++++++++++++++++++++++++++++ > 3 files changed, 297 insertions(+) > create mode 100644 crypto/hkdf.c > > diff --git a/crypto/Kconfig b/crypto/Kconfig > index cc80d89e0cf5..0eee5e129fa3 100644 > --- a/crypto/Kconfig > +++ b/crypto/Kconfig > @@ -568,6 +568,12 @@ config CRYPTO_KDF > Support for KDF compliant to SP800-108. All three types of > KDF specified in SP800-108 are implemented. > > +config CRYPTO_HKDF > + tristate "HMAC-based Extract-and expand Key Derivation Function" > + select CRYPTO_RNG > + help > + Support for KDF compliant to RFC5869. > + Make the description "HMAC-based Extract-and-Expand Key Derivation Function (HKDF)"? There is a missing dash, and probably the acronym "HKDF" should be in there. > config CRYPTO_XCBC > tristate "XCBC support" > select CRYPTO_HASH > diff --git a/crypto/Makefile b/crypto/Makefile > index 69a0bb64b0ac..6bbb0a4dea13 100644 > --- a/crypto/Makefile > +++ b/crypto/Makefile > @@ -59,6 +59,7 @@ crypto_user-$(CONFIG_CRYPTO_STATS) += crypto_user_stat.o > obj-$(CONFIG_CRYPTO_CMAC) += cmac.o > obj-$(CONFIG_CRYPTO_HMAC) += hmac.o > obj-$(CONFIG_CRYPTO_KDF) += kdf.o > +obj-$(CONFIG_CRYPTO_HKDF) += hkdf.o > obj-$(CONFIG_CRYPTO_VMAC) += vmac.o > obj-$(CONFIG_CRYPTO_XCBC) += xcbc.o > obj-$(CONFIG_CRYPTO_NULL2) += crypto_null.o > diff --git a/crypto/hkdf.c b/crypto/hkdf.c > new file mode 100644 > index 000000000000..35a975ed64a8 > --- /dev/null > +++ b/crypto/hkdf.c > @@ -0,0 +1,290 @@ > +// SPDX-License-Identifier: GPL-2.0 > + > +/* > + * RFC 5869 Key-derivation function > + * People don't know what RFC xyz is. Please be more explicit than just the RFC number, e.g. "HKDF: HMAC-based Extract-and-Expand Key Derivation Function (RFC 5869)" > + * Copyright (C) 2019, Stephan Mueller > + */ > + > +/* > + * The HKDF extract phase is applied with crypto_rng_reset(). > + * The HKDF expand phase is applied with crypto_rng_generate(). > + * > + * NOTE: In-place cipher operations are not supported. > + */ What does an "in-place cipher operation" mean in this context? That the 'info' buffer must not overlap the 'dst' buffer? Maybe crypto_rng_generate() should check that for all crypto_rngs? Or is it different for different crypto_rngs? > + > +#include > +#include > +#include > +#include > +#include > +#include > + > +struct crypto_hkdf_ctx { > + struct crypto_shash *extract_kmd; > + struct crypto_shash *expand_kmd; > +}; > + > +#define CRYPTO_HKDF_MAX_DIGESTSIZE 64 > + > +/* > + * HKDF expand phase > + */ > +static int crypto_hkdf_random(struct crypto_rng *rng, > + const u8 *info, unsigned int infolen, > + u8 *dst, unsigned int dlen) Why call it crypto_hkdf_random() instead of crypto_hkdf_generate()? The latter would match rng_alg::generate. > +{ > + struct crypto_hkdf_ctx *ctx = crypto_tfm_ctx(crypto_rng_tfm(rng)); const > + struct crypto_shash *expand_kmd = ctx->expand_kmd; > + SHASH_DESC_ON_STACK(desc, expand_kmd); > + unsigned int h = crypto_shash_digestsize(expand_kmd); const > + int err = 0; > + u8 *dst_orig = dst; > + const u8 *prev = NULL; > + uint8_t ctr = 0x01; u8 instead of uint8_t > + > + if (dlen > h * 255) > + return -EINVAL; > + > + desc->tfm = expand_kmd; > + desc->flags = crypto_shash_get_flags(expand_kmd) & > + CRYPTO_TFM_REQ_MAY_SLEEP; This line setting desc->flags doesn't make sense. How is the user meant to control whether crypto_rng_generate() can sleep or not? Or can it always sleep? Either way this part is wrong since the user can't get access to the HMAC tfm to set this flag being checked for. > + > + /* 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; > + } > + > + if (info) { > + err = crypto_shash_update(desc, info, infolen); > + if (err) > + goto out; > + } > + > + if (dlen < h) { > + u8 tmpbuffer[CRYPTO_HKDF_MAX_DIGESTSIZE]; > + > + err = crypto_shash_finup(desc, &ctr, 1, tmpbuffer); > + if (err) > + goto out; > + memcpy(dst, tmpbuffer, dlen); > + memzero_explicit(tmpbuffer, h); > + goto out; > + } else { No need for the 'else'. > + 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); > + shash_desc_zero(desc); > + return err; > +} > + > +/* > + * HKDF extract phase. > + * > + * The seed is defined to be a concatenation of the salt and the IKM. > + * The data buffer is pre-pended by an rtattr which provides an u32 value > + * with the length of the salt. Thus, the buffer length - salt length is the > + * IKM length. > + */ > +static int crypto_hkdf_seed(struct crypto_rng *rng, > + const u8 *seed, unsigned int slen) > +{ > + struct crypto_hkdf_ctx *ctx = crypto_tfm_ctx(crypto_rng_tfm(rng)); const > + struct crypto_shash *extract_kmd = ctx->extract_kmd; > + struct crypto_shash *expand_kmd = ctx->expand_kmd; > + struct rtattr *rta = (struct rtattr *)seed; > + SHASH_DESC_ON_STACK(desc, extract_kmd); > + u32 saltlen; > + unsigned int h = crypto_shash_digestsize(extract_kmd); > + int err; > + const uint8_t null_salt[CRYPTO_HKDF_MAX_DIGESTSIZE] = { 0 }; static const > + u8 prk[CRYPTO_HKDF_MAX_DIGESTSIZE] = { 0 }; No need to initialize this. > + > + /* Require aligned buffer to directly read out saltlen below */ > + if (WARN_ON((unsigned long)seed & (sizeof(saltlen) - 1))) > + return -EINVAL; > + > + if (!RTA_OK(rta, slen)) > + return -EINVAL; > + if (rta->rta_type != 1) > + return -EINVAL; > + if (RTA_PAYLOAD(rta) < sizeof(saltlen)) > + return -EINVAL; > + saltlen = *((u32 *)RTA_DATA(rta)); I'm guessing you copied the weird "length as a rtattr payload" approach from the authenc template. I think it's not necessary. And it's overly error-prone, as shown by the authenc template getting the parsing wrong for years and you making the exact same mistake again here... (See https://patchwork.kernel.org/patch/10732803/) How about just using a u32 at the beginning without the 'rtattr' preceding it? Also it should use get_unaligned() so there is no alignment requirement the caller has to comply with. > + > + seed += RTA_ALIGN(rta->rta_len); > + slen -= RTA_ALIGN(rta->rta_len); > + > + if (slen < saltlen) > + return -EINVAL; > + > + desc->tfm = extract_kmd; desc->flags needs to be set. > + > + /* Set the salt as HMAC key */ > + if (saltlen) > + err = crypto_shash_setkey(extract_kmd, seed, saltlen); > + else > + err = crypto_shash_setkey(extract_kmd, null_salt, h); > + if (err) > + return err; > + > + /* Extract the PRK */ > + err = crypto_shash_digest(desc, seed + saltlen, slen - saltlen, prk); > + if (err) > + goto err; > + > + /* Set the PRK for the expand phase */ > + err = crypto_shash_setkey(expand_kmd, prk, h); > + if (err) > + goto err; > + > +err: > + shash_desc_zero(desc); > + memzero_explicit(prk, h); > + return err; > +} > + > +static int crypto_hkdf_init_tfm(struct crypto_tfm *tfm) > +{ > + struct crypto_instance *inst = crypto_tfm_alg_instance(tfm); > + struct crypto_shash_spawn *spawn = crypto_instance_ctx(inst); > + struct crypto_hkdf_ctx *ctx = crypto_tfm_ctx(tfm); > + struct crypto_shash *extract_kmd = NULL, *expand_kmd = NULL; > + unsigned int ds; > + > + extract_kmd = crypto_spawn_shash(spawn); > + if (IS_ERR(extract_kmd)) > + return PTR_ERR(extract_kmd); > + > + expand_kmd = crypto_spawn_shash(spawn); > + if (IS_ERR(expand_kmd)) { > + crypto_free_shash(extract_kmd); > + return PTR_ERR(expand_kmd); > + } > + > + ds = crypto_shash_digestsize(extract_kmd); > + /* Hashes with no digest size are not allowed for KDFs. */ > + if (!ds || ds > CRYPTO_HKDF_MAX_DIGESTSIZE) { > + crypto_free_shash(extract_kmd); > + crypto_free_shash(expand_kmd); > + return -EOPNOTSUPP; > + } The digest size should be validated when instantiating the template, not here. > + > + ctx->extract_kmd = extract_kmd; > + ctx->expand_kmd = expand_kmd; > + > + return 0; > +} > + > +static void crypto_hkdf_exit_tfm(struct crypto_tfm *tfm) > +{ > + struct crypto_hkdf_ctx *ctx = crypto_tfm_ctx(tfm); > + > + crypto_free_shash(ctx->extract_kmd); > + crypto_free_shash(ctx->expand_kmd); > +} > + > +static void crypto_kdf_free(struct rng_instance *inst) > +{ > + crypto_drop_spawn(rng_instance_ctx(inst)); > + kfree(inst); > +} > + > +static int crypto_hkdf_create(struct crypto_template *tmpl, struct rtattr **tb) > +{ > + struct rng_instance *inst; > + struct crypto_alg *alg; > + struct shash_alg *salg; > + int err; > + unsigned int ds, ss; > + > + err = crypto_check_attr_type(tb, CRYPTO_ALG_TYPE_RNG); > + if (err) > + return err; > + > + salg = shash_attr_alg(tb[1], 0, 0); > + if (IS_ERR(salg)) > + return PTR_ERR(salg); > + > + ds = salg->digestsize; > + ss = salg->statesize; I don't see what the 'statesize' is needed for. > + alg = &salg->base; Check here that the underlying algorithm really is "hmac(" something? Alternatively it may be a good idea to simplify usage by making the template just take the unkeyed hash directly, like "hkdf(sha512)". And if any users really need to specify a specific HMAC implementation then another template usable as "hkdf_base(hmac(sha512))" could be added later. > + > + inst = rng_alloc_instance("hkdf", alg); > + err = PTR_ERR(inst); > + if (IS_ERR(inst)) > + goto out_put_alg; > + > + err = crypto_init_shash_spawn(rng_instance_ctx(inst), salg, > + rng_crypto_instance(inst)); > + if (err) > + goto out_free_inst; This error path calls crypto_drop_spawn() without a prior successful crypto_init_spawn(). > + > + inst->alg.base.cra_priority = alg->cra_priority; > + inst->alg.base.cra_blocksize = alg->cra_blocksize; > + inst->alg.base.cra_alignmask = alg->cra_alignmask; > + > + inst->alg.generate = crypto_hkdf_random; > + inst->alg.seed = crypto_hkdf_seed; > + inst->alg.seedsize = ds; What does the seedsize mean here, given that crypto_hkdf_seed() actually takes a variable length seed? > + > + inst->alg.base.cra_init = crypto_hkdf_init_tfm; > + inst->alg.base.cra_exit = crypto_hkdf_exit_tfm; > + inst->alg.base.cra_ctxsize = ALIGN(sizeof(struct crypto_hkdf_ctx) + > + ss * 2, crypto_tfm_ctx_alignment()); Why isn't this simply sizeof(struct crypto_hkdf_ctx)? > + > + inst->free = crypto_kdf_free; > + > + err = rng_register_instance(tmpl, inst); > + > + if (err) { > +out_free_inst: > + crypto_kdf_free(inst); > + } > + > +out_put_alg: > + crypto_mod_put(alg); > + return err; > +} > + > +static struct crypto_template crypto_hkdf_tmpl = { > + .name = "hkdf", > + .create = crypto_hkdf_create, > + .module = THIS_MODULE, > +}; > + > +static int __init crypto_hkdf_init(void) > +{ > + return crypto_register_template(&crypto_hkdf_tmpl); > +} > + > +static void __exit crypto_hkdf_exit(void) > +{ > + crypto_unregister_template(&crypto_hkdf_tmpl); > +} > + > +module_init(crypto_hkdf_init); > +module_exit(crypto_hkdf_exit); > + > +MODULE_LICENSE("GPL"); MODULE_LICENSE("GPL") means GPLv2+ but the SPDX header says GPLv2 only. > +MODULE_AUTHOR("Stephan Mueller "); > +MODULE_DESCRIPTION("Key Derivation Function according to RFC 5869"); Mention "HKDF" in the module description? > +MODULE_ALIAS_CRYPTO("hkdf"); > -- > 2.20.1 Thanks! - Eric