text: /* Crypto operations using stored keys
   *
   * Copyright (c) 2016, Intel Corporation
   *
   * This program is free software; you can redistribute it and/or
   * modify it under the terms of the GNU General Public License
   * as published by the Free Software Foundation; either version
   * 2 of the License, or (at your option) any later version.
   */
  
  #include <linux/slab.h>
  #include <linux/uaccess.h>
  #include <linux/scatterlist.h>
  #include <linux/crypto.h>
  #include <crypto/hash.h>
  #include <crypto/kpp.h>
  #include <crypto/dh.h>
  #include <keys/user-type.h>
  #include "internal.h"
  
  static ssize_t dh_data_from_key(key_serial_t keyid, void **data)
  {
  	struct key *key;
  	key_ref_t key_ref;
  	long status;
  	ssize_t ret;
  
  	key_ref = lookup_user_key(keyid, 0, KEY_NEED_READ);
  	if (IS_ERR(key_ref)) {
  		ret = -ENOKEY;
  		goto error;
  	}
  
  	key = key_ref_to_ptr(key_ref);
  
  	ret = -EOPNOTSUPP;
  	if (key->type == &key_type_user) {
  		down_read(&key->sem);
  		status = key_validate(key);
  		if (status == 0) {
  			const struct user_key_payload *payload;
  			uint8_t *duplicate;
  
  			payload = user_key_payload_locked(key);
  
  			duplicate = kmemdup(payload->data, payload->datalen,
  					    GFP_KERNEL);
  			if (duplicate) {
  				*data = duplicate;
  				ret = payload->datalen;
  			} else {
  				ret = -ENOMEM;
  			}
  		}
  		up_read(&key->sem);
  	}
  
  	key_put(key);
  error:
  	return ret;
  }
  
  static void dh_free_data(struct dh *dh)
  {
  	kzfree(dh->key);
  	kzfree(dh->p);
  	kzfree(dh->g);
  }
  
  struct dh_completion {
  	struct completion completion;
  	int err;
  };
  
  static void dh_crypto_done(struct crypto_async_request *req, int err)
  {
  	struct dh_completion *compl = req->data;
  
  	if (err == -EINPROGRESS)
  		return;
  
  	compl->err = err;
  	complete(&compl->completion);
  }
  
  struct kdf_sdesc {
  	struct shash_desc shash;
  	char ctx[];
  };
  
  static int kdf_alloc(struct kdf_sdesc **sdesc_ret, char *hashname)
  {
  	struct crypto_shash *tfm;
  	struct kdf_sdesc *sdesc;
  	int size;
  	int err;
  
  	/* allocate synchronous hash */
  	tfm = crypto_alloc_shash(hashname, 0, 0);
  	if (IS_ERR(tfm)) {
  		pr_info("could not allocate digest TFM handle %s\n", hashname);
  		return PTR_ERR(tfm);
  	}
  
  	err = -EINVAL;
  	if (crypto_shash_digestsize(tfm) == 0)
  		goto out_free_tfm;
  
  	err = -ENOMEM;
  	size = sizeof(struct shash_desc) + crypto_shash_descsize(tfm);
  	sdesc = kmalloc(size, GFP_KERNEL);
  	if (!sdesc)
  		goto out_free_tfm;
  	sdesc->shash.tfm = tfm;
  	sdesc->shash.flags = 0x0;
  
  	*sdesc_ret = sdesc;
  
  	return 0;
  
  out_free_tfm:
  	crypto_free_shash(tfm);
  	return err;
  }
  
  static void kdf_dealloc(struct kdf_sdesc *sdesc)
  {
  	if (!sdesc)
  		return;
  
  	if (sdesc->shash.tfm)
  		crypto_free_shash(sdesc->shash.tfm);
  
  	kzfree(sdesc);
  }
  
  /*
   * Implementation of the KDF in counter mode according to SP800-108 section 5.1
   * as well as SP800-56A section 5.8.1 (Single-step KDF).
   *
   * SP800-56A:
   * The src pointer is defined as Z || other info where Z is the shared secret
   * from DH and other info is an arbitrary string (see SP800-56A section
   * 5.8.1.2).
   */
  static int kdf_ctr(struct kdf_sdesc *sdesc, const u8 *src, unsigned int slen,
  		   u8 *dst, unsigned int dlen, unsigned int zlen)
  {
  	struct shash_desc *desc = &sdesc->shash;
  	unsigned int h = crypto_shash_digestsize(desc->tfm);
  	int err = 0;
  	u8 *dst_orig = dst;
  	__be32 counter = cpu_to_be32(1);
  
  	while (dlen) {
  		err = crypto_shash_init(desc);
  		if (err)
  			goto err;
  
  		err = crypto_shash_update(desc, (u8 *)&counter, sizeof(__be32));
  		if (err)
  			goto err;
  
  		if (zlen && h) {
  			u8 tmpbuffer[h];
  			size_t chunk = min_t(size_t, zlen, h);
  			memset(tmpbuffer, 0, chunk);
  
  			do {
  				err = crypto_shash_update(desc, tmpbuffer,
  							  chunk);
  				if (err)
  					goto err;
  
  				zlen -= chunk;
  				chunk = min_t(size_t, zlen, h);
  			} while (zlen);
  		}
  
  		if (src && slen) {
  			err = crypto_shash_update(desc, src, slen);
  			if (err)
  				goto err;
  		}
  
  		if (dlen < h) {
  			u8 tmpbuffer[h];
  
  			err = crypto_shash_final(desc, tmpbuffer);
  			if (err)
  				goto err;
  			memcpy(dst, tmpbuffer, dlen);
  			memzero_explicit(tmpbuffer, h);
  			return 0;
  		} else {
  			err = crypto_shash_final(desc, dst);
  			if (err)
  				goto err;
  
  			dlen -= h;
  			dst += h;
  			counter = cpu_to_be32(be32_to_cpu(counter) + 1);
  		}
  	}
  
  	return 0;
  
  err:
  	memzero_explicit(dst_orig, dlen);
  	return err;
  }
  
  static int keyctl_dh_compute_kdf(struct kdf_sdesc *sdesc,
  				 char __user *buffer, size_t buflen,
  				 uint8_t *kbuf, size_t kbuflen, size_t lzero)
  {
  	uint8_t *outbuf = NULL;
  	int ret;
  
  	outbuf = kmalloc(buflen, GFP_KERNEL);
  	if (!outbuf) {
  		ret = -ENOMEM;
  		goto err;
  	}
  
  	ret = kdf_ctr(sdesc, kbuf, kbuflen, outbuf, buflen, lzero);
  	if (ret)
  		goto err;
  
  	ret = buflen;
  	if (copy_to_user(buffer, outbuf, buflen) != 0)
  		ret = -EFAULT;
  
  err:
  	kzfree(outbuf);
  	return ret;
  }
  
  long __keyctl_dh_compute(struct keyctl_dh_params __user *params,
  			 char __user *buffer, size_t buflen,
  			 struct keyctl_kdf_params *kdfcopy)
  {
  	long ret;
  	ssize_t dlen;
  	int secretlen;
  	int outlen;
  	struct keyctl_dh_params pcopy;
  	struct dh dh_inputs;
  	struct scatterlist outsg;
  	struct dh_completion compl;
  	struct crypto_kpp *tfm;
  	struct kpp_request *req;
  	uint8_t *secret;
  	uint8_t *outbuf;
  	struct kdf_sdesc *sdesc = NULL;
  
  	if (!params || (!buffer && buflen)) {
  		ret = -EINVAL;
  		goto out1;
  	}
  	if (copy_from_user(&pcopy, params, sizeof(pcopy)) != 0) {
  		ret = -EFAULT;
  		goto out1;
  	}
  
  	if (kdfcopy) {
  		char *hashname;
  
  		if (memchr_inv(kdfcopy->__spare, 0, sizeof(kdfcopy->__spare))) {
  			ret = -EINVAL;
  			goto out1;
  		}
  
  		if (buflen > KEYCTL_KDF_MAX_OUTPUT_LEN ||
  		    kdfcopy->otherinfolen > KEYCTL_KDF_MAX_OI_LEN) {
  			ret = -EMSGSIZE;
  			goto out1;
  		}
  
  		/* get KDF name string */
  		hashname = strndup_user(kdfcopy->hashname, CRYPTO_MAX_ALG_NAME);
  		if (IS_ERR(hashname)) {
  			ret = PTR_ERR(hashname);
  			goto out1;
  		}
  
  		/* allocate KDF from the kernel crypto API */
  		ret = kdf_alloc(&sdesc, hashname);
  		kfree(hashname);
  		if (ret)
  			goto out1;
  	}
  
  	memset(&dh_inputs, 0, sizeof(dh_inputs));
  
  	dlen = dh_data_from_key(pcopy.prime, &dh_inputs.p);
  	if (dlen < 0) {
  		ret = dlen;
  		goto out1;
  	}
  	dh_inputs.p_size = dlen;
  
  	dlen = dh_data_from_key(pcopy.base, &dh_inputs.g);
  	if (dlen < 0) {
  		ret = dlen;
  		goto out2;
  	}
  	dh_inputs.g_size = dlen;
  
  	dlen = dh_data_from_key(pcopy.private, &dh_inputs.key);
  	if (dlen < 0) {
  		ret = dlen;
  		goto out2;
  	}
  	dh_inputs.key_size = dlen;
  
  	secretlen = crypto_dh_key_len(&dh_inputs);
  	secret = kmalloc(secretlen, GFP_KERNEL);
  	if (!secret) {
  		ret = -ENOMEM;
  		goto out2;
  	}
  	ret = crypto_dh_encode_key(secret, secretlen, &dh_inputs);
  	if (ret)
  		goto out3;
  
  	tfm = crypto_alloc_kpp("dh", CRYPTO_ALG_TYPE_KPP, 0);
  	if (IS_ERR(tfm)) {
  		ret = PTR_ERR(tfm);
  		goto out3;
  	}
  
  	ret = crypto_kpp_set_secret(tfm, secret, secretlen);
  	if (ret)
  		goto out4;
  
  	outlen = crypto_kpp_maxsize(tfm);
  
  	if (!kdfcopy) {
  		/*
  		 * When not using a KDF, buflen 0 is used to read the
  		 * required buffer length
  		 */
  		if (buflen == 0) {
  			ret = outlen;
  			goto out4;
  		} else if (outlen > buflen) {
  			ret = -EOVERFLOW;
  			goto out4;
  		}
  	}
  
  	outbuf = kzalloc(kdfcopy ? (outlen + kdfcopy->otherinfolen) : outlen,
  			 GFP_KERNEL);
  	if (!outbuf) {
  		ret = -ENOMEM;
  		goto out4;
  	}
  
  	sg_init_one(&outsg, outbuf, outlen);
  
  	req = kpp_request_alloc(tfm, GFP_KERNEL);
  	if (!req) {
  		ret = -ENOMEM;
  		goto out5;
  	}
  
  	kpp_request_set_input(req, NULL, 0);
  	kpp_request_set_output(req, &outsg, outlen);
  	init_completion(&compl.completion);
  	kpp_request_set_callback(req, CRYPTO_TFM_REQ_MAY_BACKLOG |
  				 CRYPTO_TFM_REQ_MAY_SLEEP,
  				 dh_crypto_done, &compl);
  
  	/*
  	 * For DH, generate_public_key and generate_shared_secret are
  	 * the same calculation
  	 */
  	ret = crypto_kpp_generate_public_key(req);
  	if (ret == -EINPROGRESS) {
  		wait_for_completion(&compl.completion);
  		ret = compl.err;
  		if (ret)
  			goto out6;
  	}
  
  	if (kdfcopy) {
  		/*
  		 * Concatenate SP800-56A otherinfo past DH shared secret -- the
  		 * input to the KDF is (DH shared secret || otherinfo)
  		 */
  		if (copy_from_user(outbuf + req->dst_len, kdfcopy->otherinfo,
  				   kdfcopy->otherinfolen) != 0) {
  			ret = -EFAULT;
  			goto out6;
  		}
  
  		ret = keyctl_dh_compute_kdf(sdesc, buffer, buflen, outbuf,
  					    req->dst_len + kdfcopy->otherinfolen,
  					    outlen - req->dst_len);
  	} else if (copy_to_user(buffer, outbuf, req->dst_len) == 0) {
  		ret = req->dst_len;
  	} else {
  		ret = -EFAULT;
  	}
  
  out6:
  	kpp_request_free(req);
  out5:
  	kzfree(outbuf);
  out4:
  	crypto_free_kpp(tfm);
  out3:
  	kzfree(secret);
  out2:
  	dh_free_data(&dh_inputs);
  out1:
  	kdf_dealloc(sdesc);
  	return ret;
  }
  
  long keyctl_dh_compute(struct keyctl_dh_params __user *params,
  		       char __user *buffer, size_t buflen,
  		       struct keyctl_kdf_params __user *kdf)
  {
  	struct keyctl_kdf_params kdfcopy;
  
  	if (!kdf)
  		return __keyctl_dh_compute(params, buffer, buflen, NULL);
  
  	if (copy_from_user(&kdfcopy, kdf, sizeof(kdfcopy)) != 0)
  		return -EFAULT;
  
  	return __keyctl_dh_compute(params, buffer, buflen, &kdfcopy);
  }
  