741 lines
17 KiB
C
741 lines
17 KiB
C
/****************************************************************************
|
|
* 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) &&
|
|
alg != CRYPTO_ALGORITHM_MAX + 1) ||
|
|
crypto_drivers[driverid].cc_alg[alg] == 0)
|
|
{
|
|
nxmutex_unlock(&g_crypto_lock);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (alg != CRYPTO_ALGORITHM_MAX + 1)
|
|
{
|
|
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
|
|
}
|