diff mbox series

[RFC,1/3] crypto: essiv - create a new shash template for IV generation

Message ID 20190614083404.20514-2-ard.biesheuvel@linaro.org (mailing list archive)
State Superseded
Delegated to: Herbert Xu
Headers show
Series crypto: switch to shash for ESSIV generation | expand

Commit Message

Ard Biesheuvel June 14, 2019, 8:34 a.m. UTC
Two different users of ESSIV (encrypted salt|sector IV) implement the
algorithm using bare shash and cipher transform. This is not a huge
deal, since the algorithm is fairly simple, but it does require us to
keep the cipher interface public. Since the cipher interface is often
used incorrectly, and typically is not the correct primitive to use
outside of the crypto API, the intention is to turn it into an internal
primitive, only to be used by other crypto code.

In anticipation of that, this driver moves the essiv handling into the
crypto subsystem, where it can keep using the cipher interface. This
will also permit accelerated implementations to be provided, that
implement all the transformations directly rather than wiring together
other transforms.

Signed-off-by: Ard Biesheuvel <ard.biesheuvel@linaro.org>
---
 crypto/Kconfig  |   3 +
 crypto/Makefile |   1 +
 crypto/essiv.c  | 275 ++++++++++++++++++++
 3 files changed, 279 insertions(+)
diff mbox series

Patch

diff --git a/crypto/Kconfig b/crypto/Kconfig
index efeb307c0594..2e040514fa44 100644
--- a/crypto/Kconfig
+++ b/crypto/Kconfig
@@ -1882,6 +1882,9 @@  config CRYPTO_STATS
 config CRYPTO_HASH_INFO
 	bool
 
+config CRYPTO_ESSIV
+	tristate
+
 source "drivers/crypto/Kconfig"
 source "crypto/asymmetric_keys/Kconfig"
 source "certs/Kconfig"
diff --git a/crypto/Makefile b/crypto/Makefile
index 266a4cdbb9e2..ad1d99ba6d56 100644
--- a/crypto/Makefile
+++ b/crypto/Makefile
@@ -148,6 +148,7 @@  obj-$(CONFIG_CRYPTO_USER_API_AEAD) += algif_aead.o
 obj-$(CONFIG_CRYPTO_ZSTD) += zstd.o
 obj-$(CONFIG_CRYPTO_OFB) += ofb.o
 obj-$(CONFIG_CRYPTO_ECC) += ecc.o
+obj-$(CONFIG_CRYPTO_ESSIV) += essiv.o
 
 ecdh_generic-y += ecdh.o
 ecdh_generic-y += ecdh_helper.o
diff --git a/crypto/essiv.c b/crypto/essiv.c
new file mode 100644
index 000000000000..b985de394aa6
--- /dev/null
+++ b/crypto/essiv.c
@@ -0,0 +1,275 @@ 
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Cryptographic API.
+ *
+ * ESSIV: encrypted sector/salt initial vector (for block encryption)
+ *
+ * Copyright (c) 2019 Ard Biesheuvel <ard.biesheuvel@linaro.org>
+ */
+
+#include <linux/err.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+
+#include <crypto/internal/hash.h>
+
+struct essiv_instance_ctx {
+	struct crypto_shash_spawn	hash;
+	struct crypto_spawn		enc;
+};
+
+struct essiv_tfm_ctx {
+	struct crypto_shash		*hash;
+	struct crypto_cipher		*enc;
+};
+
+struct essiv_desc_ctx {
+	unsigned int			len;
+	unsigned int			max;
+	u8				buf[];
+};
+
+static int essiv_setkey(struct crypto_shash *tfm, const u8 *inkey,
+			unsigned int keylen)
+{
+	struct essiv_tfm_ctx *ctx = crypto_shash_ctx(tfm);
+	SHASH_DESC_ON_STACK(desc, ctx->hash);
+	u8 *digest = NULL;
+	int ret;
+
+	if (keylen) {
+		digest = kmalloc(crypto_shash_digestsize(ctx->hash),
+				 GFP_KERNEL);
+		if (!digest)
+			return -ENOMEM;
+
+		desc->tfm = ctx->hash;
+		crypto_shash_digest(desc, inkey, keylen, digest);
+	}
+	ret = crypto_cipher_setkey(ctx->enc,
+				   digest ?: page_address(ZERO_PAGE(0)),
+				   crypto_shash_digestsize(ctx->hash));
+
+	kzfree(digest);
+	return ret;
+}
+
+static int essiv_init(struct shash_desc *desc)
+{
+	struct essiv_desc_ctx *ctx = shash_desc_ctx(desc);
+
+	ctx->len = 0;
+	ctx->max = crypto_shash_digestsize(desc->tfm);
+	memset(ctx->buf, 0, ctx->max);
+
+	return 0;
+}
+
+static int essiv_update(struct shash_desc *desc, const u8 *p, unsigned int len)
+{
+	struct essiv_desc_ctx *ctx = shash_desc_ctx(desc);
+	int nbytes = min(len, ctx->max - ctx->len);
+
+	/* only permit input up to the block size of the cipher */
+	if (nbytes < len)
+		return -EINVAL;
+
+	memcpy(ctx->buf + ctx->len, p, nbytes);
+	ctx->len += nbytes;
+
+	return 0;
+}
+
+static int essiv_final(struct shash_desc *desc, u8 *out)
+{
+	struct essiv_tfm_ctx *tctx = crypto_shash_ctx(desc->tfm);
+	struct essiv_desc_ctx *ctx = shash_desc_ctx(desc);
+
+	crypto_cipher_encrypt_one(tctx->enc, out, ctx->buf);
+
+	return 0;
+}
+
+static int essiv_digest(struct shash_desc *desc, const u8 *p, unsigned int len,
+			u8 *out)
+{
+	struct essiv_tfm_ctx *tctx = crypto_shash_ctx(desc->tfm);
+	struct essiv_desc_ctx *ctx = shash_desc_ctx(desc);
+
+	if (len > ctx->max)
+		return -EINVAL;
+
+	memcpy(ctx->buf, p, len);
+	crypto_cipher_encrypt_one(tctx->enc, out, ctx->buf);
+
+	return 0;
+}
+
+static int essiv_init_tfm(struct crypto_tfm *tfm)
+{
+	struct crypto_instance *inst = (void *)tfm->__crt_alg;
+	struct essiv_instance_ctx *ictx = crypto_instance_ctx(inst);
+	struct essiv_tfm_ctx *tctx = crypto_tfm_ctx(tfm);
+
+	tctx->hash = crypto_spawn_shash(&ictx->hash);
+	if (IS_ERR(tctx->hash))
+		return PTR_ERR(tctx->hash);
+
+	tctx->enc = crypto_spawn_cipher(&ictx->enc);
+	if (IS_ERR(tctx->enc)) {
+		crypto_free_shash(tctx->hash);
+		return PTR_ERR(tctx->hash);
+	}
+	return 0;
+};
+
+static void essiv_exit_tfm(struct crypto_tfm *tfm)
+{
+	struct essiv_tfm_ctx *tctx = crypto_tfm_ctx(tfm);
+
+	crypto_free_cipher(tctx->enc);
+	crypto_free_shash(tctx->hash);
+}
+
+static void crypto_essiv_free(struct crypto_instance *inst)
+{
+	struct essiv_instance_ctx *ictx = crypto_instance_ctx(inst);
+
+	crypto_drop_shash(&ictx->hash);
+	crypto_drop_spawn(&ictx->enc);
+	kfree(inst);
+}
+
+static int crypto_essiv_create(struct crypto_template *tmpl,
+			       struct rtattr **tb)
+{
+	struct shash_instance *inst;
+	struct essiv_instance_ctx *ictx;
+	struct crypto_alg *hash_alg, *enc_alg;
+	struct cipher_alg *calg;
+	struct shash_alg *salg;
+	int err;
+
+	err = crypto_check_attr_type(tb, CRYPTO_ALG_TYPE_SHASH);
+	if (err)
+		return err;
+
+	salg = shash_attr_alg(tb[1], 0, 0);
+	if (IS_ERR(salg))
+		return PTR_ERR(salg);
+	hash_alg = &salg->base;
+
+	/* The underlying hash algorithm must be unkeyed */
+	err = -EINVAL;
+	if (crypto_shash_alg_has_setkey(salg)) {
+		pr_err("essiv: keyed hash algorithm '%s' not supported\n",
+		       hash_alg->cra_driver_name);
+		goto out_put_hash_alg;
+	}
+
+	enc_alg = crypto_attr_alg(tb[2], CRYPTO_ALG_TYPE_CIPHER,
+				     CRYPTO_ALG_TYPE_MASK);
+	if (IS_ERR(enc_alg)) {
+		err = PTR_ERR(enc_alg);
+		goto out_put_hash_alg;
+	}
+	calg = &enc_alg->cra_cipher;
+
+	if (salg->digestsize < calg->cia_min_keysize ||
+	    salg->digestsize > calg->cia_max_keysize) {
+		pr_err("essiv: digest size of '%s' unsupported as key size for '%s'\n",
+		       hash_alg->cra_driver_name, enc_alg->cra_driver_name);
+		goto out_put_algs;
+	}
+
+	inst = kzalloc(sizeof(*inst) + sizeof(*ictx), GFP_KERNEL);
+	if (!inst) {
+		err = -ENOMEM;
+		goto out_put_algs;
+	}
+	ictx = shash_instance_ctx(inst);
+
+	err = crypto_init_shash_spawn(&ictx->hash, salg,
+				      shash_crypto_instance(inst));
+	if (err)
+		goto out_free_inst;
+
+	err = crypto_init_spawn(&ictx->enc, enc_alg,
+				shash_crypto_instance(inst),
+				CRYPTO_ALG_TYPE_MASK);
+	if (err)
+		goto out_drop_hash;
+
+	err = -ENAMETOOLONG;
+	if (snprintf(inst->alg.base.cra_name, CRYPTO_MAX_ALG_NAME,
+		     "essiv(%s,%s)", enc_alg->cra_name,
+		     hash_alg->cra_name) >= CRYPTO_MAX_ALG_NAME)
+		goto out_drop_spawns;
+	if (snprintf(inst->alg.base.cra_driver_name, CRYPTO_MAX_ALG_NAME,
+		     "essiv(%s,%s)", enc_alg->cra_driver_name,
+		     hash_alg->cra_driver_name) >= CRYPTO_MAX_ALG_NAME)
+		goto out_drop_spawns;
+
+	inst->alg.base.cra_priority	= (hash_alg->cra_priority +
+				           enc_alg->cra_priority) / 2;
+	inst->alg.base.cra_blocksize	= enc_alg->cra_blocksize;
+	inst->alg.base.cra_ctxsize	= sizeof(struct essiv_tfm_ctx);
+
+	inst->alg.digestsize		= enc_alg->cra_blocksize;
+	inst->alg.descsize		= sizeof(struct essiv_desc_ctx) +
+					  enc_alg->cra_blocksize;
+	inst->alg.setkey		= essiv_setkey;
+	inst->alg.init			= essiv_init;
+	inst->alg.update		= essiv_update;
+	inst->alg.final			= essiv_final;
+	inst->alg.digest		= essiv_digest;
+
+	inst->alg.base.cra_init		= essiv_init_tfm;
+	inst->alg.base.cra_exit		= essiv_exit_tfm;
+
+	err = shash_register_instance(tmpl, inst);
+	if (err)
+		goto out_drop_spawns;
+
+	crypto_mod_put(enc_alg);
+	crypto_mod_put(hash_alg);
+	return 0;
+
+out_drop_spawns:
+	crypto_drop_spawn(&ictx->enc);
+out_drop_hash:
+	crypto_drop_shash(&ictx->hash);
+out_free_inst:
+	kfree(inst);
+out_put_algs:
+	crypto_mod_put(enc_alg);
+out_put_hash_alg:
+	crypto_mod_put(hash_alg);
+	return err;
+}
+
+static struct crypto_template crypto_essiv_tmpl = {
+	.name		= "essiv",
+	.create		= crypto_essiv_create,
+	.free		= crypto_essiv_free,
+	.module		= THIS_MODULE,
+};
+
+static int __init crypto_essiv_module_init(void)
+{
+	return crypto_register_template(&crypto_essiv_tmpl);
+}
+
+static void __exit crypto_essiv_module_exit(void)
+{
+	crypto_unregister_template(&crypto_essiv_tmpl);
+}
+
+subsys_initcall(crypto_essiv_module_init);
+module_exit(crypto_essiv_module_exit);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("shash template to generate ESSIV tags");
+MODULE_ALIAS_CRYPTO("essiv");