From 753f219caf6ed79db6858769caa09934fc25e957 Mon Sep 17 00:00:00 2001
From: Cristian Stoica <cristian.stoica@freescale.com>
Date: Fri, 30 May 2014 13:59:04 +0300
Subject: [PATCH 287/291] cryptodev: add

- block and stream ciphers have their keys copied from userspace
  just like before
- for aead composite ciphers, the cipher and hmac keys are
  combined into a single key

Signed-off-by: Cristian Stoica <cristian.stoica@freescale.com>
Signed-off-by: Phil Sutter <phil@nwl.cc>
---
 drivers/staging/crypto/cryptodev/cryptlib.c | 74 +++++++++++++++++++++++++++++
 drivers/staging/crypto/cryptodev/cryptlib.h |  3 ++
 drivers/staging/crypto/cryptodev/ioctl.c    | 21 +++++---
 3 files changed, 92 insertions(+), 6 deletions(-)

diff --git a/drivers/staging/crypto/cryptodev/cryptlib.c b/drivers/staging/crypto/cryptodev/cryptlib.c
index a7fbff412822..44ce763c77ba 100644
--- a/drivers/staging/crypto/cryptodev/cryptlib.c
+++ b/drivers/staging/crypto/cryptodev/cryptlib.c
@@ -34,6 +34,8 @@
 #include <crypto/hash.h>
 #include <crypto/cryptodev.h>
 #include <crypto/aead.h>
+#include <linux/rtnetlink.h>
+#include <crypto/authenc.h>
 #include "cryptodev_int.h"
 
 
@@ -53,6 +55,78 @@ static void cryptodev_complete(struct crypto_async_request *req, int err)
 	complete(&res->completion);
 }
 
+int cryptodev_get_cipher_keylen(unsigned int *keylen, struct session_op *sop,
+		int aead)
+{
+	/*
+	 * For blockciphers (AES-CBC) or non-composite aead ciphers (like AES-GCM),
+	 * the key length is simply the cipher keylen obtained from userspace. If
+	 * the cipher is composite aead, the keylen is the sum of cipher keylen,
+	 * hmac keylen and a key header length. This key format is the one used in
+	 * Linux kernel for composite aead ciphers (crypto/authenc.c)
+	 */
+	unsigned int klen = sop->keylen;
+
+	if (unlikely(sop->keylen > CRYPTO_CIPHER_MAX_KEY_LEN))
+		return -EINVAL;
+
+	if (aead && sop->mackeylen) {
+		if (unlikely(sop->mackeylen > CRYPTO_HMAC_MAX_KEY_LEN))
+			return -EINVAL;
+		klen += sop->mackeylen;
+		klen += RTA_SPACE(sizeof(struct crypto_authenc_key_param));
+	}
+
+	*keylen = klen;
+	return 0;
+}
+
+int cryptodev_get_cipher_key(uint8_t *key, struct session_op *sop, int aead)
+{
+	/*
+	 * Get cipher key from user-space. For blockciphers just copy it from
+	 * user-space. For composite aead ciphers combine it with the hmac key in
+	 * the format used by Linux kernel in crypto/authenc.c:
+	 *
+	 * [[AUTHENC_KEY_HEADER + CIPHER_KEYLEN] [AUTHENTICATION KEY] [CIPHER KEY]]
+	 */
+	struct crypto_authenc_key_param *param;
+	struct rtattr *rta;
+	int ret = 0;
+
+	if (aead && sop->mackeylen) {
+		/*
+		 * Composite aead ciphers. The first four bytes are the header type and
+		 * header length for aead keys
+		 */
+		rta = (void *)key;
+		rta->rta_type = CRYPTO_AUTHENC_KEYA_PARAM;
+		rta->rta_len = RTA_LENGTH(sizeof(*param));
+
+		/*
+		 * The next four bytes hold the length of the encryption key
+		 */
+		param = RTA_DATA(rta);
+		param->enckeylen = cpu_to_be32(sop->keylen);
+
+		/* Advance key pointer eight bytes and copy the hmac key */
+		key += RTA_SPACE(sizeof(*param));
+		if (unlikely(copy_from_user(key, sop->mackey, sop->mackeylen))) {
+			ret = -EFAULT;
+			goto error;
+		}
+		/* Advance key pointer past the hmac key */
+		key += sop->mackeylen;
+	}
+	/* now copy the blockcipher key */
+	if (unlikely(copy_from_user(key, sop->key, sop->keylen)))
+		ret = -EFAULT;
+
+error:
+	return ret;
+}
+
+
 int cryptodev_cipher_init(struct cipher_data *out, const char *alg_name,
 				uint8_t *keyp, size_t keylen, int stream, int aead)
 {
diff --git a/drivers/staging/crypto/cryptodev/cryptlib.h b/drivers/staging/crypto/cryptodev/cryptlib.h
index 074428467cb3..a0a8a63e2ff3 100644
--- a/drivers/staging/crypto/cryptodev/cryptlib.h
+++ b/drivers/staging/crypto/cryptodev/cryptlib.h
@@ -25,6 +25,9 @@ struct cipher_data {
 int cryptodev_cipher_init(struct cipher_data *out, const char *alg_name,
 			  uint8_t *key, size_t keylen, int stream, int aead);
 void cryptodev_cipher_deinit(struct cipher_data *cdata);
+int cryptodev_get_cipher_key(uint8_t *key, struct session_op *sop, int aead);
+int cryptodev_get_cipher_keylen(unsigned int *keylen, struct session_op *sop,
+		int aead);
 ssize_t cryptodev_cipher_decrypt(struct cipher_data *cdata,
 			const struct scatterlist *sg1,
 			struct scatterlist *sg2, size_t len);
diff --git a/drivers/staging/crypto/cryptodev/ioctl.c b/drivers/staging/crypto/cryptodev/ioctl.c
index fecbbff3c628..5a55a767bf30 100644
--- a/drivers/staging/crypto/cryptodev/ioctl.c
+++ b/drivers/staging/crypto/cryptodev/ioctl.c
@@ -45,6 +45,8 @@
 #include <linux/uaccess.h>
 #include <crypto/cryptodev.h>
 #include <linux/scatterlist.h>
+#include <linux/rtnetlink.h>
+#include <crypto/authenc.h>
 
 #include <linux/sysctl.h>
 
@@ -109,9 +111,16 @@ crypto_create_session(struct fcrypt *fcr, struct session_op *sop)
 	const char *alg_name = NULL;
 	const char *hash_name = NULL;
 	int hmac_mode = 1, stream = 0, aead = 0;
+	/*
+	 * With composite aead ciphers, only ckey is used and it can cover all the
+	 * structure space; otherwise both keys may be used simultaneously but they
+	 * are confined to their spaces
+	 */
 	struct {
 		uint8_t ckey[CRYPTO_CIPHER_MAX_KEY_LEN];
 		uint8_t mkey[CRYPTO_HMAC_MAX_KEY_LEN];
+		/* padding space for aead keys */
+		uint8_t pad[RTA_SPACE(sizeof(struct crypto_authenc_key_param))];
 	} keys;
 
 	/* Does the request make sense? */
@@ -226,20 +235,20 @@ crypto_create_session(struct fcrypt *fcr, struct session_op *sop)
 
 	/* Set-up crypto transform. */
 	if (alg_name) {
-		if (unlikely(sop->keylen > CRYPTO_CIPHER_MAX_KEY_LEN)) {
+		unsigned int keylen;
+		ret = cryptodev_get_cipher_keylen(&keylen, sop, aead);
+		if (unlikely(ret < 0)) {
 			ddebug(1, "Setting key failed for %s-%zu.",
 				alg_name, (size_t)sop->keylen*8);
-			ret = -EINVAL;
 			goto error_cipher;
 		}
 
-		if (unlikely(copy_from_user(keys.ckey, sop->key, sop->keylen))) {
-			ret = -EFAULT;
+		ret = cryptodev_get_cipher_key(keys.ckey, sop, aead);
+		if (unlikely(ret < 0))
 			goto error_cipher;
-		}
 
 		ret = cryptodev_cipher_init(&ses_new->cdata, alg_name, keys.ckey,
-						sop->keylen, stream, aead);
+						keylen, stream, aead);
 		if (ret < 0) {
 			ddebug(1, "Failed to load cipher for %s", alg_name);
 			ret = -EINVAL;
-- 
2.1.0

