diff options
Diffstat (limited to 'recipes-kernel/cryptodev/sdk_patches/0007-add-support-for-RSA-public-and-private-key-operation.patch')
-rw-r--r-- | recipes-kernel/cryptodev/sdk_patches/0007-add-support-for-RSA-public-and-private-key-operation.patch | 440 |
1 files changed, 440 insertions, 0 deletions
diff --git a/recipes-kernel/cryptodev/sdk_patches/0007-add-support-for-RSA-public-and-private-key-operation.patch b/recipes-kernel/cryptodev/sdk_patches/0007-add-support-for-RSA-public-and-private-key-operation.patch new file mode 100644 index 00000000..803b90ad --- /dev/null +++ b/recipes-kernel/cryptodev/sdk_patches/0007-add-support-for-RSA-public-and-private-key-operation.patch @@ -0,0 +1,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 + |