aboutsummaryrefslogtreecommitdiffstats
path: root/recipes-kernel/cryptodev/sdk_patches/0007-add-support-for-RSA-public-and-private-key-operation.patch
blob: 803b90ad263a4766edb9085ee9256c1f2916f98e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
From 6213ae5228a2ff0bb3521474ae37effda95a5d46 Mon Sep 17 00:00:00 2001
From: Cristian Stoica <cristian.stoica@nxp.com>
Date: Fri, 12 May 2017 17:04:40 +0300
Subject: [PATCH 7/9] add support for RSA public and private key operations

Only form 1 support is added with this patch. To maintain
compatibility with OpenBSD we need to reverse bignum buffers before
giving them to the kernel. This adds an artificial performance
penalty that can be resolved only with a CIOCKEY extension in
cryptodev API.

As of Linux kernel 4.12 it is not possible to give to the kernel
directly a pointer to a RSA key structure and must resort to a BER
encoding scheme.

Support for private keys in form 3 (CRT) must wait for updates and
fixes in Linux kernel crypto API.

Known issue:
Kernels <= v4.7 strip leading zeros from the result and we get padding
errors from Openssl: RSA_EAY_PUBLIC_DECRYPT: padding check failed
(Fixed with kernel commit "crypto: rsa - Generate fixed-length output"
9b45b7bba3d22de52e09df63c50f390a193a3f53)

Signed-off-by: Cristian Stoica <cristian.stoica@nxp.com>
---
 cryptlib.c      | 234 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 cryptlib.h      |   4 +-
 cryptodev_int.h |  17 ++++
 ioctl.c         |  17 +++-
 main.c          |  42 ++++++++++
 5 files changed, 312 insertions(+), 2 deletions(-)

diff --git a/cryptlib.c b/cryptlib.c
index 2c6028e..1c044a4 100644
--- a/cryptlib.c
+++ b/cryptlib.c
@@ -37,6 +37,10 @@
 #include <crypto/authenc.h>
 #include "cryptodev_int.h"
 #include "cipherapi.h"
+#if (LINUX_VERSION_CODE > KERNEL_VERSION(4, 3, 0))
+#include <linux/asn1_ber_bytecode.h>
+#include <crypto/akcipher.h>
+#endif
 
 extern const struct crypto_type crypto_givcipher_type;
 
@@ -435,3 +439,233 @@ int cryptodev_hash_final(struct hash_data *hdata, void *output)
 	return waitfor(&hdata->async.result, ret);
 }
 
+#if (LINUX_VERSION_CODE > KERNEL_VERSION(4, 3, 0))
+/* This function is necessary because the bignums in Linux kernel are MSB first
+ * (big endian) as opposed to LSB first as OpenBSD crypto layer uses */
+void reverse_buf(uint8_t *buf, size_t sz)
+{
+	int i;
+	uint8_t *end;
+	uint8_t tmp;
+
+	end = buf + sz;
+
+	for (i = 0; i < sz/2; i++) {
+		end--;
+
+		tmp = *buf;
+		*buf = *end;
+		*end = tmp;
+
+		buf++;
+	}
+}
+
+int ber_wr_tag(uint8_t **ber_ptr, uint8_t tag)
+{
+	**ber_ptr = tag;
+	*ber_ptr += 1;
+
+	return 0;
+}
+
+int ber_wr_len(uint8_t **ber_ptr, size_t len, size_t sz)
+{
+	if (len < 127) {
+		**ber_ptr = len;
+		*ber_ptr += 1;
+	} else {
+		size_t sz_save = sz;
+
+		sz--;
+		**ber_ptr = 0x80 | sz;
+
+		while (sz > 0) {
+			*(*ber_ptr + sz) = len & 0xff;
+			len >>= 8;
+			sz--;
+		}
+		*ber_ptr += sz_save;
+	}
+
+	return 0;
+}
+
+int ber_wr_int(uint8_t **ber_ptr, uint8_t *crp_p, size_t sz)
+{
+	int ret;
+
+	ret = copy_from_user(*ber_ptr, crp_p, sz);
+	reverse_buf(*ber_ptr, sz);
+
+	*ber_ptr += sz;
+
+	return ret;
+}
+
+/* calculate the size of the length field itself in BER encoding */
+size_t ber_enc_len(size_t len)
+{
+	size_t sz;
+
+	sz = 1;
+	if (len > 127) {		/* long encoding */
+		while (len != 0) {
+			len >>= 8;
+			sz++;
+		}
+	}
+
+	return sz;
+}
+
+void *cryptodev_alloc_rsa_pub_key(struct kernel_crypt_pkop *pkop,
+		uint32_t *key_len)
+{
+	struct crypt_kop *cop = &pkop->pkop;
+	uint8_t *ber_key;
+	uint8_t *ber_ptr;
+	uint32_t ber_key_len;
+	size_t s_sz;
+	size_t e_sz;
+	size_t n_sz;
+	size_t s_enc_len;
+	size_t e_enc_len;
+	size_t n_enc_len;
+	int err;
+
+	/* BER public key format:
+	 * SEQUENCE TAG         1 byte
+	 * SEQUENCE LENGTH	s_enc_len bytes
+	 * INTEGER TAG		1 byte
+	 * INTEGER LENGTH	n_enc_len bytes
+	 * INTEGER (n modulus)	n_sz bytes
+	 * INTEGER TAG		1 byte
+	 * INTEGER LENGTH	e_enc_len bytes
+	 * INTEGER (e exponent)	e_sz bytes
+	 */
+
+	e_sz = (cop->crk_param[1].crp_nbits + 7)/8;
+	n_sz = (cop->crk_param[2].crp_nbits + 7)/8;
+
+	e_enc_len = ber_enc_len(e_sz);
+	n_enc_len = ber_enc_len(n_sz);
+
+	/*
+	 * Sequence length is the size of all the fields following the sequence
+	 * tag, added together. The two added bytes account for the two INT
+	 * tags in the Public Key sequence
+	 */
+	s_sz = e_sz + e_enc_len + n_sz + n_enc_len + 2;
+	s_enc_len = ber_enc_len(s_sz);
+
+	/* The added byte accounts for the SEQ tag at the start of the key */
+	ber_key_len = s_sz + s_enc_len + 1;
+
+	/* Linux asn1_ber_decoder doesn't like keys that are too large */
+	if (ber_key_len > 65535) {
+		return NULL;
+	}
+
+	ber_key = kmalloc(ber_key_len, GFP_DMA);
+	if (!ber_key) {
+		return NULL;
+	}
+
+	ber_ptr = ber_key;
+
+	err = ber_wr_tag(&ber_ptr, _tag(UNIV, CONS, SEQ))         ||
+	      ber_wr_len(&ber_ptr, s_sz, s_enc_len)               ||
+	      ber_wr_tag(&ber_ptr, _tag(UNIV, PRIM, INT))         ||
+	      ber_wr_len(&ber_ptr, n_sz, n_enc_len)               ||
+	      ber_wr_int(&ber_ptr, cop->crk_param[2].crp_p, n_sz) ||
+	      ber_wr_tag(&ber_ptr, _tag(UNIV, PRIM, INT))         ||
+	      ber_wr_len(&ber_ptr, e_sz, e_enc_len)               ||
+	      ber_wr_int(&ber_ptr, cop->crk_param[1].crp_p, e_sz);
+	if (err != 0) {
+		goto free_key;
+	}
+
+	*key_len = ber_key_len;
+	return ber_key;
+
+free_key:
+	kfree(ber_key);
+	return NULL;
+}
+
+int crypto_bn_modexp(struct kernel_crypt_pkop *pkop)
+{
+	struct crypt_kop *cop = &pkop->pkop;
+	uint8_t *ber_key;
+	uint32_t ber_key_len;
+	size_t m_sz;
+	size_t c_sz;
+	size_t c_sz_max;
+	uint8_t *m_buf;
+	uint8_t *c_buf;
+	struct scatterlist src;
+	struct scatterlist dst;
+	int err;
+
+	ber_key = cryptodev_alloc_rsa_pub_key(pkop, &ber_key_len);
+	if (!ber_key) {
+		return -ENOMEM;
+	}
+
+	err = crypto_akcipher_set_pub_key(pkop->s, ber_key, ber_key_len);
+	if (err != 0) {
+		goto free_key;
+	}
+
+	m_sz = (cop->crk_param[0].crp_nbits + 7)/8;
+	c_sz = (cop->crk_param[3].crp_nbits + 7)/8;
+
+	m_buf = kmalloc(m_sz, GFP_DMA);
+	if (!m_buf) {
+		err = -ENOMEM;
+		goto free_key;
+	}
+
+	err = copy_from_user(m_buf, cop->crk_param[0].crp_p, m_sz);
+	if (err != 0) {
+		goto free_m_buf;
+	}
+	reverse_buf(m_buf, m_sz);
+
+	c_sz_max = crypto_akcipher_maxsize(pkop->s);
+	if (c_sz > c_sz_max) {
+		err = -EINVAL;
+		goto free_m_buf;
+	}
+
+	c_buf = kzalloc(c_sz_max, GFP_KERNEL);
+	if (!c_buf) {
+		goto free_m_buf;
+	}
+
+	sg_init_one(&src, m_buf, m_sz);
+	sg_init_one(&dst, c_buf, c_sz);
+
+	init_completion(&pkop->result.completion);
+	akcipher_request_set_callback(pkop->req, 0,
+			cryptodev_complete, &pkop->result);
+	akcipher_request_set_crypt(pkop->req, &src, &dst, m_sz, c_sz);
+
+	err = crypto_akcipher_encrypt(pkop->req);
+	err = waitfor(&pkop->result, err);
+
+	if (err == 0) {
+		reverse_buf(c_buf, c_sz);
+		err = copy_to_user(cop->crk_param[3].crp_p, c_buf, c_sz);
+	}
+
+	kfree(c_buf);
+free_m_buf:
+	kfree(m_buf);
+free_key:
+	kfree(ber_key);
+
+	return err;
+}
+#endif
diff --git a/cryptlib.h b/cryptlib.h
index 48fe9bd..f909c34 100644
--- a/cryptlib.h
+++ b/cryptlib.h
@@ -95,6 +95,8 @@ int cryptodev_hash_reset(struct hash_data *hdata);
 void cryptodev_hash_deinit(struct hash_data *hdata);
 int cryptodev_hash_init(struct hash_data *hdata, const char *alg_name,
 			int hmac_mode, void *mackey, size_t mackeylen);
-
+#if (LINUX_VERSION_CODE > KERNEL_VERSION(4, 3, 0))
+int crypto_bn_modexp(struct kernel_crypt_pkop *pkop);
+#endif
 
 #endif
diff --git a/cryptodev_int.h b/cryptodev_int.h
index c1879fd..7860c39 100644
--- a/cryptodev_int.h
+++ b/cryptodev_int.h
@@ -19,6 +19,10 @@
 #include <linux/scatterlist.h>
 #include <crypto/cryptodev.h>
 #include <crypto/aead.h>
+#if (LINUX_VERSION_CODE > KERNEL_VERSION(4, 3, 0))
+#include <crypto/internal/rsa.h>
+#endif
+
 
 #define PFX "cryptodev: "
 #define dprintk(level, severity, format, a...)			\
@@ -111,6 +115,18 @@ struct kernel_crypt_auth_op {
 	struct mm_struct *mm;
 };
 
+#if (LINUX_VERSION_CODE > KERNEL_VERSION(4, 3, 0))
+struct kernel_crypt_pkop {
+	struct crypt_kop pkop;
+
+	struct crypto_akcipher *s;    /* Transform pointer from CryptoAPI */
+	struct akcipher_request *req; /* PKC request allocated from CryptoAPI */
+	struct cryptodev_result result;	/* updated by completion handler */
+};
+
+int crypto_run_asym(struct kernel_crypt_pkop *pkop);
+#endif
+
 /* auth */
 
 int kcaop_from_user(struct kernel_crypt_auth_op *kcop,
@@ -122,6 +138,7 @@ int crypto_run(struct fcrypt *fcr, struct kernel_crypt_op *kcop);
 
 #include <cryptlib.h>
 
+
 /* other internal structs */
 struct csession {
 	struct list_head entry;
diff --git a/ioctl.c b/ioctl.c
index db7207a..8b0df4e 100644
--- a/ioctl.c
+++ b/ioctl.c
@@ -810,6 +810,9 @@ cryptodev_ioctl(struct file *filp, unsigned int cmd, unsigned long arg_)
 	struct session_op sop;
 	struct kernel_crypt_op kcop;
 	struct kernel_crypt_auth_op kcaop;
+#if (LINUX_VERSION_CODE > KERNEL_VERSION(4, 3, 0))
+	struct kernel_crypt_pkop pkop;
+#endif
 	struct crypt_priv *pcr = filp->private_data;
 	struct fcrypt *fcr;
 	struct session_info_op siop;
@@ -823,7 +826,11 @@ cryptodev_ioctl(struct file *filp, unsigned int cmd, unsigned long arg_)
 
 	switch (cmd) {
 	case CIOCASYMFEAT:
-		return put_user(0, p);
+		ses = 0;
+		if (crypto_has_alg("rsa", 0, 0)) {
+			ses = CRF_MOD_EXP;
+		}
+		return put_user(ses, p);
 	case CRIOGET:
 		fd = clonefd(filp);
 		ret = put_user(fd, p);
@@ -859,6 +866,14 @@ cryptodev_ioctl(struct file *filp, unsigned int cmd, unsigned long arg_)
 		if (unlikely(ret))
 			return ret;
 		return copy_to_user(arg, &siop, sizeof(siop));
+#if (LINUX_VERSION_CODE > KERNEL_VERSION(4, 3, 0))
+	case CIOCKEY:
+		ret = copy_from_user(&pkop.pkop, arg, sizeof(struct crypt_kop));
+		if (ret == 0) {
+			ret = crypto_run_asym(&pkop);
+		}
+		return ret;
+#endif
 	case CIOCCRYPT:
 		if (unlikely(ret = kcop_from_user(&kcop, fcr, arg))) {
 			dwarning(1, "Error copying from user");
diff --git a/main.c b/main.c
index 57e5c38..2bfe6f0 100644
--- a/main.c
+++ b/main.c
@@ -48,6 +48,9 @@
 #include "zc.h"
 #include "cryptlib.h"
 #include "version.h"
+#if (LINUX_VERSION_CODE > KERNEL_VERSION(4, 3, 0))
+#include <crypto/akcipher.h>
+#endif
 
 /* This file contains the traditional operations of encryption
  * and hashing of /dev/crypto.
@@ -265,3 +268,42 @@ out_unlock:
 	crypto_put_session(ses_ptr);
 	return ret;
 }
+
+#if (LINUX_VERSION_CODE > KERNEL_VERSION(4, 3, 0))
+int crypto_run_asym(struct kernel_crypt_pkop *pkop)
+{
+	int err;
+
+	pkop->s = crypto_alloc_akcipher("rsa", 0, 0);
+	if (IS_ERR(pkop->s)) {
+		return PTR_ERR(pkop->s);
+	}
+
+	pkop->req = akcipher_request_alloc(pkop->s, GFP_KERNEL);
+	if (pkop->req == NULL) {
+		err = -ENOMEM;
+		goto out_free_tfm;
+	}
+
+	switch (pkop->pkop.crk_op) {
+	case CRK_MOD_EXP: /* RSA_PUB or PRIV form 1 */
+		if (pkop->pkop.crk_iparams != 3 && pkop->pkop.crk_oparams != 1) {
+			err = -EINVAL;
+			goto out_free_req;
+		}
+		err = crypto_bn_modexp(pkop);
+		break;
+	default:
+		err = -EINVAL;
+		break;
+	}
+
+out_free_req:
+	kfree(pkop->req);
+
+out_free_tfm:
+	crypto_free_akcipher(pkop->s);
+
+	return err;
+}
+#endif
-- 
2.7.4