| /**************************************************************************** |
| * crypto/crypto.c |
| * $OpenBSD: crypto.c,v 1.65 2014/07/13 23:24:47 deraadt Exp $ |
| * |
| * The author of this code is Angelos D. Keromytis (angelos@cis.upenn.edu) |
| * |
| * This code was written by Angelos D. Keromytis in Athens, Greece, in |
| * February 2000. Network Security Technologies Inc. (NSTI) kindly |
| * supported the development of this code. |
| * |
| * Copyright (c) 2000, 2001 Angelos D. Keromytis |
| * |
| * Permission to use, copy, and modify this software with or without fee |
| * is hereby granted, provided that this entire notice is included in |
| * all source code copies of any software which is or includes a copy or |
| * modification of this software. |
| * |
| * THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR |
| * IMPLIED WARRANTY. IN PARTICULAR, NONE OF THE AUTHORS MAKES ANY |
| * REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE |
| * MERCHANTABILITY OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR |
| * PURPOSE. |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Included Files |
| ****************************************************************************/ |
| |
| #include <nuttx/config.h> |
| |
| #include <sys/types.h> |
| #include <stdbool.h> |
| #include <string.h> |
| #include <poll.h> |
| #include <debug.h> |
| #include <errno.h> |
| #include <crypto/cryptodev.h> |
| #include <nuttx/fs/fs.h> |
| #include <nuttx/mutex.h> |
| #include <nuttx/kmalloc.h> |
| #include <nuttx/crypto/crypto.h> |
| |
| /**************************************************************************** |
| * Public Data |
| ****************************************************************************/ |
| |
| FAR struct cryptocap *crypto_drivers = NULL; |
| int crypto_drivers_num = 0; |
| |
| /**************************************************************************** |
| * Private Data |
| ****************************************************************************/ |
| |
| static mutex_t g_crypto_lock = NXMUTEX_INITIALIZER; |
| |
| /**************************************************************************** |
| * Public Functions |
| ****************************************************************************/ |
| |
| /* Create a new session. */ |
| |
| int crypto_newsession(FAR uint64_t *sid, |
| FAR struct cryptoini *cri, |
| int hard) |
| { |
| uint32_t hid; |
| uint32_t lid; |
| uint32_t hid2 = -1; |
| FAR struct cryptocap *cpc; |
| FAR struct cryptoini *cr; |
| int turn = 0; |
| int err; |
| |
| if (crypto_drivers == NULL) |
| { |
| return -EINVAL; |
| } |
| |
| nxmutex_lock(&g_crypto_lock); |
| |
| /* The algorithm we use here is pretty stupid; just use the |
| * first driver that supports all the algorithms we need. Do |
| * a double-pass over all the drivers, ignoring software ones |
| * at first, to deal with cases of drivers that register after |
| * the software one(s) --- e.g., PCMCIA crypto cards. |
| * |
| * XXX We need more smarts here (in real life too, but that's |
| * XXX another story altogether). |
| */ |
| |
| do |
| { |
| for (hid = 0; hid < crypto_drivers_num; hid++) |
| { |
| cpc = &crypto_drivers[hid]; |
| |
| /* If it's not initialized or has remaining sessions |
| * referencing it, skip. |
| */ |
| |
| if (cpc->cc_newsession == NULL || |
| (cpc->cc_flags & CRYPTOCAP_F_CLEANUP)) |
| { |
| continue; |
| } |
| |
| if (cpc->cc_flags & CRYPTOCAP_F_SOFTWARE) |
| { |
| /* First round of search, ignore |
| * software drivers. |
| */ |
| |
| if (turn == 0) |
| { |
| continue; |
| } |
| } |
| else |
| { |
| /* !CRYPTOCAP_F_SOFTWARE |
| * Second round of search, only software. |
| */ |
| |
| if (turn == 1) |
| { |
| continue; |
| } |
| } |
| |
| /* See if all the algorithms are supported. */ |
| |
| for (cr = cri; cr; cr = cr->cri_next) |
| { |
| if (cpc->cc_alg[cr->cri_alg] == 0) |
| { |
| break; |
| } |
| } |
| |
| /* If even one algorithm is not supported, |
| * keep searching. |
| */ |
| |
| if (cr != NULL) |
| { |
| continue; |
| } |
| |
| /* If we had a previous match, see how it compares |
| * to this one. Keep "remembering" whichever is |
| * the best of the two. |
| */ |
| |
| if (hid2 != -1) |
| { |
| /* Compare session numbers, pick the one |
| * with the lowest. |
| * XXX Need better metrics, this will |
| * XXX just do un-weighted round-robin. |
| */ |
| |
| if (crypto_drivers[hid].cc_sessions <= |
| crypto_drivers[hid2].cc_sessions) |
| { |
| hid2 = hid; |
| } |
| } |
| else |
| { |
| /* Remember this one, for future |
| * comparisons. |
| */ |
| |
| hid2 = hid; |
| } |
| } |
| |
| /* If we found something worth remembering, leave. The |
| * side-effect is that we will always prefer a hardware |
| * driver over the software one. |
| */ |
| |
| if (hid2 != -1) |
| { |
| break; |
| } |
| |
| turn++; |
| |
| /* If we only want hardware drivers, don't do second pass. */ |
| } |
| while (turn <= 2 && hard == 0); |
| |
| hid = hid2; |
| |
| /* Can't do everything in one session. |
| * XXX Fix this. We need to inject a "virtual" session |
| * XXX layer right about here. |
| */ |
| |
| if (hid == -1) |
| { |
| nxmutex_unlock(&g_crypto_lock); |
| return -EINVAL; |
| } |
| |
| /* Call the driver initialization routine. */ |
| |
| lid = hid; /* Pass the driver ID. */ |
| err = crypto_drivers[hid].cc_newsession(&lid, cri); |
| if (err == 0) |
| { |
| *sid = hid; |
| *sid <<= 32; |
| *sid |= (lid & 0xffffffff); |
| crypto_drivers[hid].cc_sessions++; |
| } |
| |
| nxmutex_unlock(&g_crypto_lock); |
| return err; |
| } |
| |
| /* Delete an existing session (or a reserved session on an unregistered |
| * driver). |
| */ |
| |
| int crypto_freesession(uint64_t sid) |
| { |
| int err = 0; |
| uint32_t hid; |
| |
| if (crypto_drivers == NULL) |
| { |
| return -EINVAL; |
| } |
| |
| /* Determine two IDs. */ |
| |
| hid = (sid >> 32) & 0xffffffff; |
| |
| if (hid >= crypto_drivers_num) |
| { |
| return -ENOENT; |
| } |
| |
| nxmutex_lock(&g_crypto_lock); |
| |
| if (crypto_drivers[hid].cc_sessions) |
| { |
| crypto_drivers[hid].cc_sessions--; |
| } |
| |
| /* Call the driver cleanup routine, if available. */ |
| |
| if (crypto_drivers[hid].cc_freesession) |
| { |
| err = crypto_drivers[hid].cc_freesession(sid); |
| } |
| |
| /* If this was the last session of a driver marked as invalid, |
| * make the entry available for reuse. |
| */ |
| |
| if ((crypto_drivers[hid].cc_flags & CRYPTOCAP_F_CLEANUP) && |
| crypto_drivers[hid].cc_sessions == 0) |
| { |
| explicit_bzero(&crypto_drivers[hid], sizeof(struct cryptocap)); |
| } |
| |
| nxmutex_unlock(&g_crypto_lock); |
| return err; |
| } |
| |
| /* Find an empty slot. */ |
| |
| int crypto_get_driverid(uint8_t flags) |
| { |
| FAR struct cryptocap *newdrv; |
| int i; |
| |
| nxmutex_lock(&g_crypto_lock); |
| |
| if (crypto_drivers_num == 0) |
| { |
| crypto_drivers_num = CRYPTO_DRIVERS_INITIAL; |
| crypto_drivers = kmm_calloc(crypto_drivers_num, |
| sizeof(struct cryptocap)); |
| if (crypto_drivers == NULL) |
| { |
| crypto_drivers_num = 0; |
| nxmutex_unlock(&g_crypto_lock); |
| return -1; |
| } |
| |
| bzero(crypto_drivers, crypto_drivers_num * |
| sizeof(struct cryptocap)); |
| } |
| |
| for (i = 0; i < crypto_drivers_num; i++) |
| { |
| if (crypto_drivers[i].cc_process == NULL && |
| !(crypto_drivers[i].cc_flags & CRYPTOCAP_F_CLEANUP) && |
| crypto_drivers[i].cc_sessions == 0) |
| { |
| crypto_drivers[i].cc_sessions = 1; /* Mark */ |
| crypto_drivers[i].cc_flags = flags; |
| nxmutex_unlock(&g_crypto_lock); |
| return i; |
| } |
| } |
| |
| /* Out of entries, allocate some more. */ |
| |
| if (i == crypto_drivers_num) |
| { |
| if (crypto_drivers_num >= CRYPTO_DRIVERS_MAX) |
| { |
| nxmutex_unlock(&g_crypto_lock); |
| return -1; |
| } |
| |
| newdrv = kmm_calloc(crypto_drivers_num * 2, |
| sizeof(struct cryptocap)); |
| if (newdrv == NULL) |
| { |
| nxmutex_unlock(&g_crypto_lock); |
| return -1; |
| } |
| |
| bcopy(crypto_drivers, newdrv, |
| crypto_drivers_num * sizeof(struct cryptocap)); |
| bzero(&newdrv[crypto_drivers_num], |
| crypto_drivers_num * sizeof(struct cryptocap)); |
| |
| newdrv[i].cc_sessions = 1; /* Mark */ |
| newdrv[i].cc_flags = flags; |
| crypto_drivers_num *= 2; |
| |
| kmm_free(crypto_drivers); |
| crypto_drivers = newdrv; |
| nxmutex_unlock(&g_crypto_lock); |
| return i; |
| } |
| |
| /* Shouldn't really get here... */ |
| |
| nxmutex_unlock(&g_crypto_lock); |
| return -1; |
| } |
| |
| /* Register a crypto driver. It should be called once for each algorithm |
| * supported by the driver. |
| */ |
| |
| int crypto_kregister(uint32_t driverid, FAR int *kalg, |
| CODE int (*kprocess)(FAR struct cryptkop *)) |
| { |
| int i; |
| |
| if (driverid >= crypto_drivers_num || kalg == NULL || |
| crypto_drivers == NULL) |
| { |
| return -EINVAL; |
| } |
| |
| nxmutex_lock(&g_crypto_lock); |
| |
| for (i = 0; i <= CRK_ALGORITHM_MAX; i++) |
| { |
| /* XXX Do some performance testing to determine |
| * placing. We probably need an auxiliary data |
| * structure that describes relative performances. |
| */ |
| |
| crypto_drivers[driverid].cc_kalg[i] = kalg[i]; |
| } |
| |
| crypto_drivers[driverid].cc_kprocess = kprocess; |
| |
| nxmutex_unlock(&g_crypto_lock); |
| return 0; |
| } |
| |
| /* Register a crypto driver. */ |
| |
| int crypto_register(uint32_t driverid, FAR int *alg, |
| CODE int (*newses)(FAR uint32_t *, |
| FAR struct cryptoini *), |
| CODE int (*freeses)(uint64_t), |
| CODE int (*process)(FAR struct cryptop *)) |
| { |
| int i; |
| |
| if (driverid >= crypto_drivers_num || alg == NULL || |
| crypto_drivers == NULL) |
| { |
| return -EINVAL; |
| } |
| |
| nxmutex_lock(&g_crypto_lock); |
| |
| for (i = 0; i <= CRYPTO_ALGORITHM_MAX; i++) |
| { |
| /* XXX Do some performance testing to determine |
| * placing. We probably need an auxiliary data |
| * structure that describes relative performances. |
| */ |
| |
| crypto_drivers[driverid].cc_alg[i] = alg[i]; |
| } |
| |
| crypto_drivers[driverid].cc_newsession = newses; |
| crypto_drivers[driverid].cc_process = process; |
| crypto_drivers[driverid].cc_freesession = freeses; |
| crypto_drivers[driverid].cc_sessions = 0; /* Unmark */ |
| |
| nxmutex_unlock(&g_crypto_lock); |
| |
| return 0; |
| } |
| |
| /* Unregister a crypto driver. If there are pending sessions using it, |
| * leave enough information around so that subsequent calls using those |
| * sessions will correctly detect the driver being unregistered and reroute |
| * the request. |
| */ |
| |
| int crypto_unregister(uint32_t driverid, int alg) |
| { |
| int i = CRYPTO_ALGORITHM_MAX + 1; |
| uint32_t ses; |
| |
| nxmutex_lock(&g_crypto_lock); |
| |
| /* Sanity checks. */ |
| |
| if (driverid >= crypto_drivers_num || crypto_drivers == NULL || |
| alg <= 0 || alg > (CRYPTO_ALGORITHM_MAX + 1)) |
| { |
| nxmutex_unlock(&g_crypto_lock); |
| return -EINVAL; |
| } |
| |
| if (alg != CRYPTO_ALGORITHM_MAX + 1) |
| { |
| if (crypto_drivers[driverid].cc_alg[alg] == 0) |
| { |
| nxmutex_unlock(&g_crypto_lock); |
| return -EINVAL; |
| } |
| |
| crypto_drivers[driverid].cc_alg[alg] = 0; |
| |
| /* Was this the last algorithm ? */ |
| |
| for (i = 1; i <= CRYPTO_ALGORITHM_MAX; i++) |
| { |
| if (crypto_drivers[driverid].cc_alg[i] != 0) |
| { |
| break; |
| } |
| } |
| } |
| |
| /* If a driver unregistered its last algorithm or all of them |
| * (alg == CRYPTO_ALGORITHM_MAX + 1), cleanup its entry. |
| */ |
| |
| if (i == CRYPTO_ALGORITHM_MAX + 1 || alg == CRYPTO_ALGORITHM_MAX + 1) |
| { |
| ses = crypto_drivers[driverid].cc_sessions; |
| bzero(&crypto_drivers[driverid], sizeof(struct cryptocap)); |
| if (ses != 0) |
| { |
| /* If there are pending sessions, just mark as invalid. */ |
| |
| crypto_drivers[driverid].cc_flags |= CRYPTOCAP_F_CLEANUP; |
| crypto_drivers[driverid].cc_sessions = ses; |
| } |
| } |
| |
| nxmutex_unlock(&g_crypto_lock); |
| return 0; |
| } |
| |
| /* Dispatch an asymmetric crypto request to the appropriate crypto devices. */ |
| |
| int crypto_kinvoke(FAR struct cryptkop *krp) |
| { |
| extern int cryptodevallowsoft; |
| uint32_t hid; |
| int error; |
| |
| /* Sanity checks. */ |
| |
| if (krp == NULL) |
| { |
| return -EINVAL; |
| } |
| |
| nxmutex_lock(&g_crypto_lock); |
| for (hid = 0; hid < crypto_drivers_num; hid++) |
| { |
| if ((crypto_drivers[hid].cc_flags & CRYPTOCAP_F_SOFTWARE) && |
| cryptodevallowsoft == 0) |
| { |
| continue; |
| } |
| |
| if (crypto_drivers[hid].cc_kprocess == NULL) |
| { |
| continue; |
| } |
| |
| if ((crypto_drivers[hid].cc_kalg[krp->krp_op] & |
| CRYPTO_ALG_FLAG_SUPPORTED) == 0) |
| { |
| continue; |
| } |
| |
| break; |
| } |
| |
| if (hid == crypto_drivers_num) |
| { |
| krp->krp_status = -ENODEV; |
| nxmutex_unlock(&g_crypto_lock); |
| return 0; |
| } |
| |
| krp->krp_hid = hid; |
| |
| crypto_drivers[hid].cc_koperations++; |
| |
| error = crypto_drivers[hid].cc_kprocess(krp); |
| if (error) |
| { |
| krp->krp_status = error; |
| } |
| |
| nxmutex_unlock(&g_crypto_lock); |
| return 0; |
| } |
| |
| /* Dispatch a crypto request to the appropriate crypto devices. */ |
| |
| int crypto_invoke(FAR struct cryptop *crp) |
| { |
| FAR struct cryptodesc *crd; |
| uint64_t nid; |
| uint32_t hid; |
| int error; |
| |
| /* Sanity checks. */ |
| |
| if (crp == NULL) |
| { |
| return -EINVAL; |
| } |
| |
| nxmutex_lock(&g_crypto_lock); |
| if (crp->crp_desc == NULL || crypto_drivers == NULL) |
| { |
| crp->crp_etype = -EINVAL; |
| nxmutex_unlock(&g_crypto_lock); |
| return 0; |
| } |
| |
| hid = (crp->crp_sid >> 32) & 0xffffffff; |
| if (hid >= crypto_drivers_num) |
| { |
| goto migrate; |
| } |
| |
| if (crypto_drivers[hid].cc_flags & CRYPTOCAP_F_CLEANUP) |
| { |
| crypto_freesession(crp->crp_sid); |
| goto migrate; |
| } |
| |
| if (crypto_drivers[hid].cc_process == NULL) |
| { |
| goto migrate; |
| } |
| |
| crypto_drivers[hid].cc_operations++; |
| crypto_drivers[hid].cc_bytes += crp->crp_ilen; |
| |
| error = crypto_drivers[hid].cc_process(crp); |
| if (error) |
| { |
| if (error == -ERESTART) |
| { |
| /* Unregister driver and migrate session. */ |
| |
| crypto_unregister(hid, CRYPTO_ALGORITHM_MAX + 1); |
| goto migrate; |
| } |
| else |
| { |
| crp->crp_etype = error; |
| } |
| } |
| |
| nxmutex_unlock(&g_crypto_lock); |
| return 0; |
| |
| migrate: |
| |
| /* Migrate session. */ |
| |
| for (crd = crp->crp_desc; crd->crd_next; crd = crd->crd_next) |
| { |
| crd->CRD_INI.cri_next = &(crd->crd_next->CRD_INI); |
| } |
| |
| if (crypto_newsession(&nid, &(crp->crp_desc->CRD_INI), 0) == 0) |
| { |
| crp->crp_sid = nid; |
| } |
| |
| crp->crp_etype = -EAGAIN; |
| nxmutex_unlock(&g_crypto_lock); |
| return 0; |
| } |
| |
| /* Release a set of crypto descriptors. */ |
| |
| void crypto_freereq(FAR struct cryptop *crp) |
| { |
| FAR struct cryptodesc *crd; |
| |
| if (crp == NULL) |
| { |
| return; |
| } |
| |
| nxmutex_lock(&g_crypto_lock); |
| |
| while ((crd = crp->crp_desc) != NULL) |
| { |
| crp->crp_desc = crd->crd_next; |
| kmm_free(crd); |
| } |
| |
| kmm_free(crp); |
| nxmutex_unlock(&g_crypto_lock); |
| } |
| |
| /* Acquire a set of crypto descriptors. */ |
| |
| FAR struct cryptop *crypto_getreq(int num) |
| { |
| FAR struct cryptodesc *crd; |
| FAR struct cryptop *crp; |
| |
| nxmutex_lock(&g_crypto_lock); |
| |
| crp = kmm_malloc(sizeof(struct cryptop)); |
| if (crp == NULL) |
| { |
| nxmutex_unlock(&g_crypto_lock); |
| return NULL; |
| } |
| |
| bzero(crp, sizeof(struct cryptop)); |
| |
| while (num--) |
| { |
| crd = kmm_calloc(1, sizeof(struct cryptodesc)); |
| if (crd == NULL) |
| { |
| nxmutex_unlock(&g_crypto_lock); |
| crypto_freereq(crp); |
| return NULL; |
| } |
| |
| crd->crd_next = crp->crp_desc; |
| crp->crp_desc = crd; |
| } |
| |
| nxmutex_unlock(&g_crypto_lock); |
| return crp; |
| } |
| |
| int crypto_getfeat(FAR int *featp) |
| { |
| extern int cryptodevallowsoft; |
| extern int userasymcrypto; |
| int hid; |
| int kalg; |
| int feat = 0; |
| |
| if (userasymcrypto == 0) |
| { |
| goto out; |
| } |
| |
| for (hid = 0; hid < crypto_drivers_num; hid++) |
| { |
| if ((crypto_drivers[hid].cc_flags & CRYPTOCAP_F_SOFTWARE) && |
| cryptodevallowsoft == 0) |
| { |
| continue; |
| } |
| |
| if (crypto_drivers[hid].cc_kprocess == NULL) |
| { |
| continue; |
| } |
| |
| for (kalg = 0; kalg <= CRK_ALGORITHM_MAX; kalg++) |
| { |
| if ((crypto_drivers[hid].cc_kalg[kalg] & |
| CRYPTO_ALG_FLAG_SUPPORTED) != 0) |
| { |
| feat |= 1 << kalg; |
| } |
| } |
| } |
| |
| out: |
| *featp = feat; |
| return 0; |
| } |
| |
| int up_cryptoinitialize(void) |
| { |
| #ifdef CONFIG_CRYPTO_ALGTEST |
| int ret = crypto_test(); |
| if (ret) |
| { |
| crypterr("ERROR: crypto test failed\n"); |
| } |
| else |
| { |
| cryptinfo("crypto test OK\n"); |
| } |
| |
| return ret; |
| #else |
| return OK; |
| #endif |
| } |