c034143269
- The linked list of cryptoini structures used in session initialization is replaced with a new flat structure: struct crypto_session_params. This session includes a new mode to define how the other fields should be interpreted. Available modes include: - COMPRESS (for compression/decompression) - CIPHER (for simply encryption/decryption) - DIGEST (computing and verifying digests) - AEAD (combined auth and encryption such as AES-GCM and AES-CCM) - ETA (combined auth and encryption using encrypt-then-authenticate) Additional modes could be added in the future (e.g. if we wanted to support TLS MtE for AES-CBC in the kernel we could add a new mode for that. TLS modes might also affect how AAD is interpreted, etc.) The flat structure also includes the key lengths and algorithms as before. However, code doesn't have to walk the linked list and switch on the algorithm to determine which key is the auth key vs encryption key. The 'csp_auth_*' fields are always used for auth keys and settings and 'csp_cipher_*' for cipher. (Compression algorithms are stored in csp_cipher_alg.) - Drivers no longer register a list of supported algorithms. This doesn't quite work when you factor in modes (e.g. a driver might support both AES-CBC and SHA2-256-HMAC separately but not combined for ETA). Instead, a new 'crypto_probesession' method has been added to the kobj interface for symmteric crypto drivers. This method returns a negative value on success (similar to how device_probe works) and the crypto framework uses this value to pick the "best" driver. There are three constants for hardware (e.g. ccr), accelerated software (e.g. aesni), and plain software (cryptosoft) that give preference in that order. One effect of this is that if you request only hardware when creating a new session, you will no longer get a session using accelerated software. Another effect is that the default setting to disallow software crypto via /dev/crypto now disables accelerated software. Once a driver is chosen, 'crypto_newsession' is invoked as before. - Crypto operations are now solely described by the flat 'cryptop' structure. The linked list of descriptors has been removed. A separate enum has been added to describe the type of data buffer in use instead of using CRYPTO_F_* flags to make it easier to add more types in the future if needed (e.g. wired userspace buffers for zero-copy). It will also make it easier to re-introduce separate input and output buffers (in-kernel TLS would benefit from this). Try to make the flags related to IV handling less insane: - CRYPTO_F_IV_SEPARATE means that the IV is stored in the 'crp_iv' member of the operation structure. If this flag is not set, the IV is stored in the data buffer at the 'crp_iv_start' offset. - CRYPTO_F_IV_GENERATE means that a random IV should be generated and stored into the data buffer. This cannot be used with CRYPTO_F_IV_SEPARATE. If a consumer wants to deal with explicit vs implicit IVs, etc. it can always generate the IV however it needs and store partial IVs in the buffer and the full IV/nonce in crp_iv and set CRYPTO_F_IV_SEPARATE. The layout of the buffer is now described via fields in cryptop. crp_aad_start and crp_aad_length define the boundaries of any AAD. Previously with GCM and CCM you defined an auth crd with this range, but for ETA your auth crd had to span both the AAD and plaintext (and they had to be adjacent). crp_payload_start and crp_payload_length define the boundaries of the plaintext/ciphertext. Modes that only do a single operation (COMPRESS, CIPHER, DIGEST) should only use this region and leave the AAD region empty. If a digest is present (or should be generated), it's starting location is marked by crp_digest_start. Instead of using the CRD_F_ENCRYPT flag to determine the direction of the operation, cryptop now includes an 'op' field defining the operation to perform. For digests I've added a new VERIFY digest mode which assumes a digest is present in the input and fails the request with EBADMSG if it doesn't match the internally-computed digest. GCM and CCM already assumed this, and the new AEAD mode requires this for decryption. The new ETA mode now also requires this for decryption, so IPsec and GELI no longer do their own authentication verification. Simple DIGEST operations can also do this, though there are no in-tree consumers. To eventually support some refcounting to close races, the session cookie is now passed to crypto_getop() and clients should no longer set crp_sesssion directly. - Assymteric crypto operation structures should be allocated via crypto_getkreq() and freed via crypto_freekreq(). This permits the crypto layer to track open asym requests and close races with a driver trying to unregister while asym requests are in flight. - crypto_copyback, crypto_copydata, crypto_apply, and crypto_contiguous_subsegment now accept the 'crp' object as the first parameter instead of individual members. This makes it easier to deal with different buffer types in the future as well as separate input and output buffers. It's also simpler for driver writers to use. - bus_dmamap_load_crp() loads a DMA mapping for a crypto buffer. This understands the various types of buffers so that drivers that use DMA do not have to be aware of different buffer types. - Helper routines now exist to build an auth context for HMAC IPAD and OPAD. This reduces some duplicated work among drivers. - Key buffers are now treated as const throughout the framework and in device drivers. However, session key buffers provided when a session is created are expected to remain alive for the duration of the session. - GCM and CCM sessions now only specify a cipher algorithm and a cipher key. The redundant auth information is not needed or used. - For cryptosoft, split up the code a bit such that the 'process' callback now invokes a function pointer in the session. This function pointer is set based on the mode (in effect) though it simplifies a few edge cases that would otherwise be in the switch in 'process'. It does split up GCM vs CCM which I think is more readable even if there is some duplication. - I changed /dev/crypto to support GMAC requests using CRYPTO_AES_NIST_GMAC as an auth algorithm and updated cryptocheck to work with it. - Combined cipher and auth sessions via /dev/crypto now always use ETA mode. The COP_F_CIPHER_FIRST flag is now a no-op that is ignored. This was actually documented as being true in crypto(4) before, but the code had not implemented this before I added the CIPHER_FIRST flag. - I have not yet updated /dev/crypto to be aware of explicit modes for sessions. I will probably do that at some point in the future as well as teach it about IV/nonce and tag lengths for AEAD so we can support all of the NIST KAT tests for GCM and CCM. - I've split up the exising crypto.9 manpage into several pages of which many are written from scratch. - I have converted all drivers and consumers in the tree and verified that they compile, but I have not tested all of them. I have tested the following drivers: - cryptosoft - aesni (AES only) - blake2 - ccr and the following consumers: - cryptodev - IPsec - ktls_ocf - GELI (lightly) I have not tested the following: - ccp - aesni with sha - hifn - kgssapi_krb5 - ubsec - padlock - safe - armv8_crypto (aarch64) - glxsb (i386) - sec (ppc) - cesa (armv7) - cryptocteon (mips64) - nlmsec (mips64) Discussed with: cem Relnotes: yes Sponsored by: Chelsio Communications Differential Revision: https://reviews.freebsd.org/D23677
1658 lines
38 KiB
C
1658 lines
38 KiB
C
/* $OpenBSD: cryptodev.c,v 1.52 2002/06/19 07:22:46 deraadt Exp $ */
|
|
|
|
/*-
|
|
* Copyright (c) 2001 Theo de Raadt
|
|
* Copyright (c) 2002-2006 Sam Leffler, Errno Consulting
|
|
* Copyright (c) 2014 The FreeBSD Foundation
|
|
* All rights reserved.
|
|
*
|
|
* Portions of this software were developed by John-Mark Gurney
|
|
* under sponsorship of the FreeBSD Foundation and
|
|
* Rubicon Communications, LLC (Netgate).
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
*
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
* 3. The name of the author may not be used to endorse or promote products
|
|
* derived from this software without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
|
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
|
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
|
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
|
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
|
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*
|
|
* Effort sponsored in part by the Defense Advanced Research Projects
|
|
* Agency (DARPA) and Air Force Research Laboratory, Air Force
|
|
* Materiel Command, USAF, under agreement number F30602-01-2-0537.
|
|
*/
|
|
|
|
#include <sys/cdefs.h>
|
|
__FBSDID("$FreeBSD$");
|
|
|
|
#include <sys/param.h>
|
|
#include <sys/systm.h>
|
|
#include <sys/malloc.h>
|
|
#include <sys/mbuf.h>
|
|
#include <sys/lock.h>
|
|
#include <sys/mutex.h>
|
|
#include <sys/sysctl.h>
|
|
#include <sys/file.h>
|
|
#include <sys/filedesc.h>
|
|
#include <sys/errno.h>
|
|
#include <sys/random.h>
|
|
#include <sys/conf.h>
|
|
#include <sys/kernel.h>
|
|
#include <sys/module.h>
|
|
#include <sys/fcntl.h>
|
|
#include <sys/bus.h>
|
|
#include <sys/user.h>
|
|
#include <sys/sdt.h>
|
|
|
|
#include <opencrypto/cryptodev.h>
|
|
#include <opencrypto/xform.h>
|
|
|
|
SDT_PROVIDER_DECLARE(opencrypto);
|
|
|
|
SDT_PROBE_DEFINE1(opencrypto, dev, ioctl, error, "int"/*line number*/);
|
|
|
|
#ifdef COMPAT_FREEBSD32
|
|
#include <sys/mount.h>
|
|
#include <compat/freebsd32/freebsd32.h>
|
|
|
|
struct session_op32 {
|
|
u_int32_t cipher;
|
|
u_int32_t mac;
|
|
u_int32_t keylen;
|
|
u_int32_t key;
|
|
int mackeylen;
|
|
u_int32_t mackey;
|
|
u_int32_t ses;
|
|
};
|
|
|
|
struct session2_op32 {
|
|
u_int32_t cipher;
|
|
u_int32_t mac;
|
|
u_int32_t keylen;
|
|
u_int32_t key;
|
|
int mackeylen;
|
|
u_int32_t mackey;
|
|
u_int32_t ses;
|
|
int crid;
|
|
int pad[4];
|
|
};
|
|
|
|
struct crypt_op32 {
|
|
u_int32_t ses;
|
|
u_int16_t op;
|
|
u_int16_t flags;
|
|
u_int len;
|
|
u_int32_t src, dst;
|
|
u_int32_t mac;
|
|
u_int32_t iv;
|
|
};
|
|
|
|
struct crparam32 {
|
|
u_int32_t crp_p;
|
|
u_int crp_nbits;
|
|
};
|
|
|
|
struct crypt_kop32 {
|
|
u_int crk_op;
|
|
u_int crk_status;
|
|
u_short crk_iparams;
|
|
u_short crk_oparams;
|
|
u_int crk_crid;
|
|
struct crparam32 crk_param[CRK_MAXPARAM];
|
|
};
|
|
|
|
struct cryptotstat32 {
|
|
struct timespec32 acc;
|
|
struct timespec32 min;
|
|
struct timespec32 max;
|
|
u_int32_t count;
|
|
};
|
|
|
|
struct cryptostats32 {
|
|
u_int32_t cs_ops;
|
|
u_int32_t cs_errs;
|
|
u_int32_t cs_kops;
|
|
u_int32_t cs_kerrs;
|
|
u_int32_t cs_intrs;
|
|
u_int32_t cs_rets;
|
|
u_int32_t cs_blocks;
|
|
u_int32_t cs_kblocks;
|
|
struct cryptotstat32 cs_invoke;
|
|
struct cryptotstat32 cs_done;
|
|
struct cryptotstat32 cs_cb;
|
|
struct cryptotstat32 cs_finis;
|
|
};
|
|
|
|
#define CIOCGSESSION32 _IOWR('c', 101, struct session_op32)
|
|
#define CIOCCRYPT32 _IOWR('c', 103, struct crypt_op32)
|
|
#define CIOCKEY32 _IOWR('c', 104, struct crypt_kop32)
|
|
#define CIOCGSESSION232 _IOWR('c', 106, struct session2_op32)
|
|
#define CIOCKEY232 _IOWR('c', 107, struct crypt_kop32)
|
|
|
|
static void
|
|
session_op_from_32(const struct session_op32 *from, struct session_op *to)
|
|
{
|
|
|
|
CP(*from, *to, cipher);
|
|
CP(*from, *to, mac);
|
|
CP(*from, *to, keylen);
|
|
PTRIN_CP(*from, *to, key);
|
|
CP(*from, *to, mackeylen);
|
|
PTRIN_CP(*from, *to, mackey);
|
|
CP(*from, *to, ses);
|
|
}
|
|
|
|
static void
|
|
session2_op_from_32(const struct session2_op32 *from, struct session2_op *to)
|
|
{
|
|
|
|
session_op_from_32((const struct session_op32 *)from,
|
|
(struct session_op *)to);
|
|
CP(*from, *to, crid);
|
|
}
|
|
|
|
static void
|
|
session_op_to_32(const struct session_op *from, struct session_op32 *to)
|
|
{
|
|
|
|
CP(*from, *to, cipher);
|
|
CP(*from, *to, mac);
|
|
CP(*from, *to, keylen);
|
|
PTROUT_CP(*from, *to, key);
|
|
CP(*from, *to, mackeylen);
|
|
PTROUT_CP(*from, *to, mackey);
|
|
CP(*from, *to, ses);
|
|
}
|
|
|
|
static void
|
|
session2_op_to_32(const struct session2_op *from, struct session2_op32 *to)
|
|
{
|
|
|
|
session_op_to_32((const struct session_op *)from,
|
|
(struct session_op32 *)to);
|
|
CP(*from, *to, crid);
|
|
}
|
|
|
|
static void
|
|
crypt_op_from_32(const struct crypt_op32 *from, struct crypt_op *to)
|
|
{
|
|
|
|
CP(*from, *to, ses);
|
|
CP(*from, *to, op);
|
|
CP(*from, *to, flags);
|
|
CP(*from, *to, len);
|
|
PTRIN_CP(*from, *to, src);
|
|
PTRIN_CP(*from, *to, dst);
|
|
PTRIN_CP(*from, *to, mac);
|
|
PTRIN_CP(*from, *to, iv);
|
|
}
|
|
|
|
static void
|
|
crypt_op_to_32(const struct crypt_op *from, struct crypt_op32 *to)
|
|
{
|
|
|
|
CP(*from, *to, ses);
|
|
CP(*from, *to, op);
|
|
CP(*from, *to, flags);
|
|
CP(*from, *to, len);
|
|
PTROUT_CP(*from, *to, src);
|
|
PTROUT_CP(*from, *to, dst);
|
|
PTROUT_CP(*from, *to, mac);
|
|
PTROUT_CP(*from, *to, iv);
|
|
}
|
|
|
|
static void
|
|
crparam_from_32(const struct crparam32 *from, struct crparam *to)
|
|
{
|
|
|
|
PTRIN_CP(*from, *to, crp_p);
|
|
CP(*from, *to, crp_nbits);
|
|
}
|
|
|
|
static void
|
|
crparam_to_32(const struct crparam *from, struct crparam32 *to)
|
|
{
|
|
|
|
PTROUT_CP(*from, *to, crp_p);
|
|
CP(*from, *to, crp_nbits);
|
|
}
|
|
|
|
static void
|
|
crypt_kop_from_32(const struct crypt_kop32 *from, struct crypt_kop *to)
|
|
{
|
|
int i;
|
|
|
|
CP(*from, *to, crk_op);
|
|
CP(*from, *to, crk_status);
|
|
CP(*from, *to, crk_iparams);
|
|
CP(*from, *to, crk_oparams);
|
|
CP(*from, *to, crk_crid);
|
|
for (i = 0; i < CRK_MAXPARAM; i++)
|
|
crparam_from_32(&from->crk_param[i], &to->crk_param[i]);
|
|
}
|
|
|
|
static void
|
|
crypt_kop_to_32(const struct crypt_kop *from, struct crypt_kop32 *to)
|
|
{
|
|
int i;
|
|
|
|
CP(*from, *to, crk_op);
|
|
CP(*from, *to, crk_status);
|
|
CP(*from, *to, crk_iparams);
|
|
CP(*from, *to, crk_oparams);
|
|
CP(*from, *to, crk_crid);
|
|
for (i = 0; i < CRK_MAXPARAM; i++)
|
|
crparam_to_32(&from->crk_param[i], &to->crk_param[i]);
|
|
}
|
|
#endif
|
|
|
|
struct csession {
|
|
TAILQ_ENTRY(csession) next;
|
|
crypto_session_t cses;
|
|
volatile u_int refs;
|
|
u_int32_t ses;
|
|
struct mtx lock; /* for op submission */
|
|
|
|
struct enc_xform *txform;
|
|
int hashsize;
|
|
int ivsize;
|
|
int mode;
|
|
|
|
void *key;
|
|
void *mackey;
|
|
};
|
|
|
|
struct cryptop_data {
|
|
struct csession *cse;
|
|
|
|
char *buf;
|
|
bool done;
|
|
};
|
|
|
|
struct fcrypt {
|
|
TAILQ_HEAD(csessionlist, csession) csessions;
|
|
int sesn;
|
|
struct mtx lock;
|
|
};
|
|
|
|
static struct timeval warninterval = { .tv_sec = 60, .tv_usec = 0 };
|
|
SYSCTL_TIMEVAL_SEC(_kern, OID_AUTO, cryptodev_warn_interval, CTLFLAG_RW,
|
|
&warninterval,
|
|
"Delay in seconds between warnings of deprecated /dev/crypto algorithms");
|
|
|
|
static int cryptof_ioctl(struct file *, u_long, void *,
|
|
struct ucred *, struct thread *);
|
|
static int cryptof_stat(struct file *, struct stat *,
|
|
struct ucred *, struct thread *);
|
|
static int cryptof_close(struct file *, struct thread *);
|
|
static int cryptof_fill_kinfo(struct file *, struct kinfo_file *,
|
|
struct filedesc *);
|
|
|
|
static struct fileops cryptofops = {
|
|
.fo_read = invfo_rdwr,
|
|
.fo_write = invfo_rdwr,
|
|
.fo_truncate = invfo_truncate,
|
|
.fo_ioctl = cryptof_ioctl,
|
|
.fo_poll = invfo_poll,
|
|
.fo_kqfilter = invfo_kqfilter,
|
|
.fo_stat = cryptof_stat,
|
|
.fo_close = cryptof_close,
|
|
.fo_chmod = invfo_chmod,
|
|
.fo_chown = invfo_chown,
|
|
.fo_sendfile = invfo_sendfile,
|
|
.fo_fill_kinfo = cryptof_fill_kinfo,
|
|
};
|
|
|
|
static struct csession *csefind(struct fcrypt *, u_int);
|
|
static bool csedelete(struct fcrypt *, u_int);
|
|
static struct csession *csecreate(struct fcrypt *, crypto_session_t,
|
|
struct crypto_session_params *, struct enc_xform *, void *,
|
|
struct auth_hash *, void *);
|
|
static void csefree(struct csession *);
|
|
|
|
static int cryptodev_op(struct csession *, struct crypt_op *,
|
|
struct ucred *, struct thread *td);
|
|
static int cryptodev_aead(struct csession *, struct crypt_aead *,
|
|
struct ucred *, struct thread *);
|
|
static int cryptodev_key(struct crypt_kop *);
|
|
static int cryptodev_find(struct crypt_find_op *);
|
|
|
|
/*
|
|
* Check a crypto identifier to see if it requested
|
|
* a software device/driver. This can be done either
|
|
* by device name/class or through search constraints.
|
|
*/
|
|
static int
|
|
checkforsoftware(int *cridp)
|
|
{
|
|
int crid;
|
|
|
|
crid = *cridp;
|
|
|
|
if (!crypto_devallowsoft) {
|
|
if (crid & CRYPTOCAP_F_SOFTWARE) {
|
|
if (crid & CRYPTOCAP_F_HARDWARE) {
|
|
*cridp = CRYPTOCAP_F_HARDWARE;
|
|
return 0;
|
|
}
|
|
return EINVAL;
|
|
}
|
|
if ((crid & CRYPTOCAP_F_HARDWARE) == 0 &&
|
|
(crypto_getcaps(crid) & CRYPTOCAP_F_HARDWARE) == 0)
|
|
return EINVAL;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* ARGSUSED */
|
|
static int
|
|
cryptof_ioctl(
|
|
struct file *fp,
|
|
u_long cmd,
|
|
void *data,
|
|
struct ucred *active_cred,
|
|
struct thread *td)
|
|
{
|
|
#define SES2(p) ((struct session2_op *)p)
|
|
struct crypto_session_params csp;
|
|
struct fcrypt *fcr = fp->f_data;
|
|
struct csession *cse;
|
|
struct session_op *sop;
|
|
struct crypt_op *cop;
|
|
struct crypt_aead *caead;
|
|
struct enc_xform *txform = NULL;
|
|
struct auth_hash *thash = NULL;
|
|
void *key = NULL;
|
|
void *mackey = NULL;
|
|
struct crypt_kop *kop;
|
|
crypto_session_t cses;
|
|
u_int32_t ses;
|
|
int error = 0, crid;
|
|
#ifdef COMPAT_FREEBSD32
|
|
struct session2_op sopc;
|
|
struct crypt_op copc;
|
|
struct crypt_kop kopc;
|
|
#endif
|
|
|
|
switch (cmd) {
|
|
case CIOCGSESSION:
|
|
case CIOCGSESSION2:
|
|
#ifdef COMPAT_FREEBSD32
|
|
case CIOCGSESSION32:
|
|
case CIOCGSESSION232:
|
|
if (cmd == CIOCGSESSION32) {
|
|
session_op_from_32(data, (struct session_op *)&sopc);
|
|
sop = (struct session_op *)&sopc;
|
|
} else if (cmd == CIOCGSESSION232) {
|
|
session2_op_from_32(data, &sopc);
|
|
sop = (struct session_op *)&sopc;
|
|
} else
|
|
#endif
|
|
sop = (struct session_op *)data;
|
|
switch (sop->cipher) {
|
|
case 0:
|
|
break;
|
|
case CRYPTO_DES_CBC:
|
|
txform = &enc_xform_des;
|
|
break;
|
|
case CRYPTO_3DES_CBC:
|
|
txform = &enc_xform_3des;
|
|
break;
|
|
case CRYPTO_BLF_CBC:
|
|
txform = &enc_xform_blf;
|
|
break;
|
|
case CRYPTO_CAST_CBC:
|
|
txform = &enc_xform_cast5;
|
|
break;
|
|
case CRYPTO_SKIPJACK_CBC:
|
|
txform = &enc_xform_skipjack;
|
|
break;
|
|
case CRYPTO_AES_CBC:
|
|
txform = &enc_xform_rijndael128;
|
|
break;
|
|
case CRYPTO_AES_XTS:
|
|
txform = &enc_xform_aes_xts;
|
|
break;
|
|
case CRYPTO_NULL_CBC:
|
|
txform = &enc_xform_null;
|
|
break;
|
|
case CRYPTO_ARC4:
|
|
txform = &enc_xform_arc4;
|
|
break;
|
|
case CRYPTO_CAMELLIA_CBC:
|
|
txform = &enc_xform_camellia;
|
|
break;
|
|
case CRYPTO_AES_ICM:
|
|
txform = &enc_xform_aes_icm;
|
|
break;
|
|
case CRYPTO_AES_NIST_GCM_16:
|
|
txform = &enc_xform_aes_nist_gcm;
|
|
break;
|
|
case CRYPTO_CHACHA20:
|
|
txform = &enc_xform_chacha20;
|
|
break;
|
|
case CRYPTO_AES_CCM_16:
|
|
txform = &enc_xform_ccm;
|
|
break;
|
|
|
|
default:
|
|
CRYPTDEB("invalid cipher");
|
|
SDT_PROBE1(opencrypto, dev, ioctl, error, __LINE__);
|
|
return (EINVAL);
|
|
}
|
|
|
|
switch (sop->mac) {
|
|
case 0:
|
|
break;
|
|
case CRYPTO_MD5_HMAC:
|
|
thash = &auth_hash_hmac_md5;
|
|
break;
|
|
case CRYPTO_POLY1305:
|
|
thash = &auth_hash_poly1305;
|
|
break;
|
|
case CRYPTO_SHA1_HMAC:
|
|
thash = &auth_hash_hmac_sha1;
|
|
break;
|
|
case CRYPTO_SHA2_224_HMAC:
|
|
thash = &auth_hash_hmac_sha2_224;
|
|
break;
|
|
case CRYPTO_SHA2_256_HMAC:
|
|
thash = &auth_hash_hmac_sha2_256;
|
|
break;
|
|
case CRYPTO_SHA2_384_HMAC:
|
|
thash = &auth_hash_hmac_sha2_384;
|
|
break;
|
|
case CRYPTO_SHA2_512_HMAC:
|
|
thash = &auth_hash_hmac_sha2_512;
|
|
break;
|
|
case CRYPTO_RIPEMD160_HMAC:
|
|
thash = &auth_hash_hmac_ripemd_160;
|
|
break;
|
|
#ifdef COMPAT_FREEBSD12
|
|
case CRYPTO_AES_128_NIST_GMAC:
|
|
case CRYPTO_AES_192_NIST_GMAC:
|
|
case CRYPTO_AES_256_NIST_GMAC:
|
|
/* Should always be paired with GCM. */
|
|
if (sop->cipher != CRYPTO_AES_NIST_GCM_16) {
|
|
CRYPTDEB("GMAC without GCM");
|
|
return (EINVAL);
|
|
}
|
|
break;
|
|
#endif
|
|
case CRYPTO_AES_NIST_GMAC:
|
|
switch (sop->mackeylen * 8) {
|
|
case 128:
|
|
thash = &auth_hash_nist_gmac_aes_128;
|
|
break;
|
|
case 192:
|
|
thash = &auth_hash_nist_gmac_aes_192;
|
|
break;
|
|
case 256:
|
|
thash = &auth_hash_nist_gmac_aes_256;
|
|
break;
|
|
default:
|
|
CRYPTDEB("invalid GMAC key length");
|
|
SDT_PROBE1(opencrypto, dev, ioctl, error,
|
|
__LINE__);
|
|
return (EINVAL);
|
|
}
|
|
break;
|
|
case CRYPTO_AES_CCM_CBC_MAC:
|
|
switch (sop->mackeylen) {
|
|
case 16:
|
|
thash = &auth_hash_ccm_cbc_mac_128;
|
|
break;
|
|
case 24:
|
|
thash = &auth_hash_ccm_cbc_mac_192;
|
|
break;
|
|
case 32:
|
|
thash = &auth_hash_ccm_cbc_mac_256;
|
|
break;
|
|
default:
|
|
CRYPTDEB("Invalid CBC MAC key size %d",
|
|
sop->keylen);
|
|
SDT_PROBE1(opencrypto, dev, ioctl,
|
|
error, __LINE__);
|
|
return (EINVAL);
|
|
}
|
|
break;
|
|
#ifdef notdef
|
|
case CRYPTO_MD5:
|
|
thash = &auth_hash_md5;
|
|
break;
|
|
#endif
|
|
case CRYPTO_SHA1:
|
|
thash = &auth_hash_sha1;
|
|
break;
|
|
case CRYPTO_SHA2_224:
|
|
thash = &auth_hash_sha2_224;
|
|
break;
|
|
case CRYPTO_SHA2_256:
|
|
thash = &auth_hash_sha2_256;
|
|
break;
|
|
case CRYPTO_SHA2_384:
|
|
thash = &auth_hash_sha2_384;
|
|
break;
|
|
case CRYPTO_SHA2_512:
|
|
thash = &auth_hash_sha2_512;
|
|
break;
|
|
|
|
case CRYPTO_NULL_HMAC:
|
|
thash = &auth_hash_null;
|
|
break;
|
|
|
|
case CRYPTO_BLAKE2B:
|
|
thash = &auth_hash_blake2b;
|
|
break;
|
|
case CRYPTO_BLAKE2S:
|
|
thash = &auth_hash_blake2s;
|
|
break;
|
|
|
|
default:
|
|
CRYPTDEB("invalid mac");
|
|
SDT_PROBE1(opencrypto, dev, ioctl, error, __LINE__);
|
|
return (EINVAL);
|
|
}
|
|
|
|
if (txform == NULL && thash == NULL)
|
|
return (EINVAL);
|
|
|
|
memset(&csp, 0, sizeof(csp));
|
|
|
|
if (sop->cipher == CRYPTO_AES_NIST_GCM_16) {
|
|
switch (sop->mac) {
|
|
#ifdef COMPAT_FREEBSD12
|
|
case CRYPTO_AES_128_NIST_GMAC:
|
|
case CRYPTO_AES_192_NIST_GMAC:
|
|
case CRYPTO_AES_256_NIST_GMAC:
|
|
if (sop->keylen != sop->mackeylen)
|
|
return (EINVAL);
|
|
break;
|
|
#endif
|
|
case 0:
|
|
break;
|
|
default:
|
|
return (EINVAL);
|
|
}
|
|
csp.csp_mode = CSP_MODE_AEAD;
|
|
} else if (sop->cipher == CRYPTO_AES_CCM_16) {
|
|
switch (sop->mac) {
|
|
#ifdef COMPAT_FREEBSD12
|
|
case CRYPTO_AES_CCM_CBC_MAC:
|
|
if (sop->keylen != sop->mackeylen)
|
|
return (EINVAL);
|
|
thash = NULL;
|
|
break;
|
|
#endif
|
|
case 0:
|
|
break;
|
|
default:
|
|
return (EINVAL);
|
|
}
|
|
csp.csp_mode = CSP_MODE_AEAD;
|
|
} else if (txform && thash)
|
|
csp.csp_mode = CSP_MODE_ETA;
|
|
else if (txform)
|
|
csp.csp_mode = CSP_MODE_CIPHER;
|
|
else
|
|
csp.csp_mode = CSP_MODE_DIGEST;
|
|
|
|
if (txform) {
|
|
csp.csp_cipher_alg = txform->type;
|
|
csp.csp_cipher_klen = sop->keylen;
|
|
if (sop->keylen > txform->maxkey ||
|
|
sop->keylen < txform->minkey) {
|
|
CRYPTDEB("invalid cipher parameters");
|
|
error = EINVAL;
|
|
SDT_PROBE1(opencrypto, dev, ioctl, error,
|
|
__LINE__);
|
|
goto bail;
|
|
}
|
|
|
|
key = malloc(csp.csp_cipher_klen, M_XDATA, M_WAITOK);
|
|
error = copyin(sop->key, key, csp.csp_cipher_klen);
|
|
if (error) {
|
|
CRYPTDEB("invalid key");
|
|
SDT_PROBE1(opencrypto, dev, ioctl, error,
|
|
__LINE__);
|
|
goto bail;
|
|
}
|
|
csp.csp_cipher_key = key;
|
|
csp.csp_ivlen = txform->ivsize;
|
|
}
|
|
|
|
if (thash) {
|
|
csp.csp_auth_alg = thash->type;
|
|
csp.csp_auth_klen = sop->mackeylen;
|
|
if (sop->mackeylen > thash->keysize ||
|
|
sop->mackeylen < 0) {
|
|
CRYPTDEB("invalid mac key length");
|
|
error = EINVAL;
|
|
SDT_PROBE1(opencrypto, dev, ioctl, error,
|
|
__LINE__);
|
|
goto bail;
|
|
}
|
|
|
|
if (csp.csp_auth_klen) {
|
|
mackey = malloc(csp.csp_auth_klen, M_XDATA,
|
|
M_WAITOK);
|
|
error = copyin(sop->mackey, mackey,
|
|
csp.csp_auth_klen);
|
|
if (error) {
|
|
CRYPTDEB("invalid mac key");
|
|
SDT_PROBE1(opencrypto, dev, ioctl,
|
|
error, __LINE__);
|
|
goto bail;
|
|
}
|
|
csp.csp_auth_key = mackey;
|
|
}
|
|
|
|
if (csp.csp_auth_alg == CRYPTO_AES_NIST_GMAC)
|
|
csp.csp_ivlen = AES_GCM_IV_LEN;
|
|
if (csp.csp_auth_alg == CRYPTO_AES_CCM_CBC_MAC)
|
|
csp.csp_ivlen = AES_CCM_IV_LEN;
|
|
}
|
|
|
|
/* NB: CIOCGSESSION2 has the crid */
|
|
if (cmd == CIOCGSESSION2
|
|
#ifdef COMPAT_FREEBSD32
|
|
|| cmd == CIOCGSESSION232
|
|
#endif
|
|
) {
|
|
crid = SES2(sop)->crid;
|
|
error = checkforsoftware(&crid);
|
|
if (error) {
|
|
CRYPTDEB("checkforsoftware");
|
|
SDT_PROBE1(opencrypto, dev, ioctl, error,
|
|
__LINE__);
|
|
goto bail;
|
|
}
|
|
} else
|
|
crid = CRYPTOCAP_F_HARDWARE;
|
|
error = crypto_newsession(&cses, &csp, crid);
|
|
if (error) {
|
|
CRYPTDEB("crypto_newsession");
|
|
SDT_PROBE1(opencrypto, dev, ioctl, error, __LINE__);
|
|
goto bail;
|
|
}
|
|
|
|
cse = csecreate(fcr, cses, &csp, txform, key, thash, mackey);
|
|
|
|
if (cse == NULL) {
|
|
crypto_freesession(cses);
|
|
error = EINVAL;
|
|
SDT_PROBE1(opencrypto, dev, ioctl, error, __LINE__);
|
|
CRYPTDEB("csecreate");
|
|
goto bail;
|
|
}
|
|
sop->ses = cse->ses;
|
|
if (cmd == CIOCGSESSION2
|
|
#ifdef COMPAT_FREEBSD32
|
|
|| cmd == CIOCGSESSION232
|
|
#endif
|
|
) {
|
|
/* return hardware/driver id */
|
|
SES2(sop)->crid = crypto_ses2hid(cse->cses);
|
|
}
|
|
bail:
|
|
if (error) {
|
|
free(key, M_XDATA);
|
|
free(mackey, M_XDATA);
|
|
}
|
|
#ifdef COMPAT_FREEBSD32
|
|
else {
|
|
if (cmd == CIOCGSESSION32)
|
|
session_op_to_32(sop, data);
|
|
else if (cmd == CIOCGSESSION232)
|
|
session2_op_to_32((struct session2_op *)sop,
|
|
data);
|
|
}
|
|
#endif
|
|
break;
|
|
case CIOCFSESSION:
|
|
ses = *(u_int32_t *)data;
|
|
if (!csedelete(fcr, ses)) {
|
|
SDT_PROBE1(opencrypto, dev, ioctl, error, __LINE__);
|
|
return (EINVAL);
|
|
}
|
|
break;
|
|
case CIOCCRYPT:
|
|
#ifdef COMPAT_FREEBSD32
|
|
case CIOCCRYPT32:
|
|
if (cmd == CIOCCRYPT32) {
|
|
cop = &copc;
|
|
crypt_op_from_32(data, cop);
|
|
} else
|
|
#endif
|
|
cop = (struct crypt_op *)data;
|
|
cse = csefind(fcr, cop->ses);
|
|
if (cse == NULL) {
|
|
SDT_PROBE1(opencrypto, dev, ioctl, error, __LINE__);
|
|
return (EINVAL);
|
|
}
|
|
error = cryptodev_op(cse, cop, active_cred, td);
|
|
csefree(cse);
|
|
#ifdef COMPAT_FREEBSD32
|
|
if (error == 0 && cmd == CIOCCRYPT32)
|
|
crypt_op_to_32(cop, data);
|
|
#endif
|
|
break;
|
|
case CIOCKEY:
|
|
case CIOCKEY2:
|
|
#ifdef COMPAT_FREEBSD32
|
|
case CIOCKEY32:
|
|
case CIOCKEY232:
|
|
#endif
|
|
if (!crypto_userasymcrypto) {
|
|
SDT_PROBE1(opencrypto, dev, ioctl, error, __LINE__);
|
|
return (EPERM); /* XXX compat? */
|
|
}
|
|
#ifdef COMPAT_FREEBSD32
|
|
if (cmd == CIOCKEY32 || cmd == CIOCKEY232) {
|
|
kop = &kopc;
|
|
crypt_kop_from_32(data, kop);
|
|
} else
|
|
#endif
|
|
kop = (struct crypt_kop *)data;
|
|
if (cmd == CIOCKEY
|
|
#ifdef COMPAT_FREEBSD32
|
|
|| cmd == CIOCKEY32
|
|
#endif
|
|
) {
|
|
/* NB: crypto core enforces s/w driver use */
|
|
kop->crk_crid =
|
|
CRYPTOCAP_F_HARDWARE | CRYPTOCAP_F_SOFTWARE;
|
|
}
|
|
mtx_lock(&Giant);
|
|
error = cryptodev_key(kop);
|
|
mtx_unlock(&Giant);
|
|
#ifdef COMPAT_FREEBSD32
|
|
if (cmd == CIOCKEY32 || cmd == CIOCKEY232)
|
|
crypt_kop_to_32(kop, data);
|
|
#endif
|
|
break;
|
|
case CIOCASYMFEAT:
|
|
if (!crypto_userasymcrypto) {
|
|
/*
|
|
* NB: if user asym crypto operations are
|
|
* not permitted return "no algorithms"
|
|
* so well-behaved applications will just
|
|
* fallback to doing them in software.
|
|
*/
|
|
*(int *)data = 0;
|
|
} else {
|
|
error = crypto_getfeat((int *)data);
|
|
if (error)
|
|
SDT_PROBE1(opencrypto, dev, ioctl, error,
|
|
__LINE__);
|
|
}
|
|
break;
|
|
case CIOCFINDDEV:
|
|
error = cryptodev_find((struct crypt_find_op *)data);
|
|
break;
|
|
case CIOCCRYPTAEAD:
|
|
caead = (struct crypt_aead *)data;
|
|
cse = csefind(fcr, caead->ses);
|
|
if (cse == NULL) {
|
|
SDT_PROBE1(opencrypto, dev, ioctl, error, __LINE__);
|
|
return (EINVAL);
|
|
}
|
|
error = cryptodev_aead(cse, caead, active_cred, td);
|
|
csefree(cse);
|
|
break;
|
|
default:
|
|
error = EINVAL;
|
|
SDT_PROBE1(opencrypto, dev, ioctl, error, __LINE__);
|
|
break;
|
|
}
|
|
return (error);
|
|
#undef SES2
|
|
}
|
|
|
|
static int cryptodev_cb(struct cryptop *);
|
|
|
|
static struct cryptop_data *
|
|
cod_alloc(struct csession *cse, size_t len, struct thread *td)
|
|
{
|
|
struct cryptop_data *cod;
|
|
|
|
cod = malloc(sizeof(struct cryptop_data), M_XDATA, M_WAITOK | M_ZERO);
|
|
|
|
cod->cse = cse;
|
|
cod->buf = malloc(len, M_XDATA, M_WAITOK);
|
|
return (cod);
|
|
}
|
|
|
|
static void
|
|
cod_free(struct cryptop_data *cod)
|
|
{
|
|
|
|
free(cod->buf, M_XDATA);
|
|
free(cod, M_XDATA);
|
|
}
|
|
|
|
static void
|
|
cryptodev_warn(struct csession *cse)
|
|
{
|
|
static struct timeval arc4warn, blfwarn, castwarn, deswarn, md5warn;
|
|
static struct timeval skipwarn, tdeswarn;
|
|
const struct crypto_session_params *csp;
|
|
|
|
csp = crypto_get_params(cse->cses);
|
|
switch (csp->csp_cipher_alg) {
|
|
case CRYPTO_DES_CBC:
|
|
if (ratecheck(&deswarn, &warninterval))
|
|
gone_in(13, "DES cipher via /dev/crypto");
|
|
break;
|
|
case CRYPTO_3DES_CBC:
|
|
if (ratecheck(&tdeswarn, &warninterval))
|
|
gone_in(13, "3DES cipher via /dev/crypto");
|
|
break;
|
|
case CRYPTO_BLF_CBC:
|
|
if (ratecheck(&blfwarn, &warninterval))
|
|
gone_in(13, "Blowfish cipher via /dev/crypto");
|
|
break;
|
|
case CRYPTO_CAST_CBC:
|
|
if (ratecheck(&castwarn, &warninterval))
|
|
gone_in(13, "CAST128 cipher via /dev/crypto");
|
|
break;
|
|
case CRYPTO_SKIPJACK_CBC:
|
|
if (ratecheck(&skipwarn, &warninterval))
|
|
gone_in(13, "Skipjack cipher via /dev/crypto");
|
|
break;
|
|
case CRYPTO_ARC4:
|
|
if (ratecheck(&arc4warn, &warninterval))
|
|
gone_in(13, "ARC4 cipher via /dev/crypto");
|
|
break;
|
|
}
|
|
|
|
switch (csp->csp_auth_alg) {
|
|
case CRYPTO_MD5_HMAC:
|
|
if (ratecheck(&md5warn, &warninterval))
|
|
gone_in(13, "MD5-HMAC authenticator via /dev/crypto");
|
|
break;
|
|
}
|
|
}
|
|
|
|
static int
|
|
cryptodev_op(
|
|
struct csession *cse,
|
|
struct crypt_op *cop,
|
|
struct ucred *active_cred,
|
|
struct thread *td)
|
|
{
|
|
struct cryptop_data *cod = NULL;
|
|
struct cryptop *crp = NULL;
|
|
int error;
|
|
|
|
if (cop->len > 256*1024-4) {
|
|
SDT_PROBE1(opencrypto, dev, ioctl, error, __LINE__);
|
|
return (E2BIG);
|
|
}
|
|
|
|
if (cse->txform) {
|
|
if (cop->len == 0 || (cop->len % cse->txform->blocksize) != 0) {
|
|
SDT_PROBE1(opencrypto, dev, ioctl, error, __LINE__);
|
|
return (EINVAL);
|
|
}
|
|
}
|
|
|
|
if (cop->mac && cse->hashsize == 0) {
|
|
SDT_PROBE1(opencrypto, dev, ioctl, error, __LINE__);
|
|
error = EINVAL;
|
|
goto bail;
|
|
}
|
|
|
|
/*
|
|
* The COP_F_CIPHER_FIRST flag predates explicit session
|
|
* modes, but the only way it was used was for EtA so allow it
|
|
* as long as it is consistent with EtA.
|
|
*/
|
|
if (cop->flags & COP_F_CIPHER_FIRST) {
|
|
if (cop->op != COP_ENCRYPT) {
|
|
SDT_PROBE1(opencrypto, dev, ioctl, error, __LINE__);
|
|
return (EINVAL);
|
|
}
|
|
}
|
|
|
|
cod = cod_alloc(cse, cop->len + cse->hashsize, td);
|
|
|
|
crp = crypto_getreq(cse->cses, M_WAITOK);
|
|
|
|
error = copyin(cop->src, cod->buf, cop->len);
|
|
if (error) {
|
|
SDT_PROBE1(opencrypto, dev, ioctl, error, __LINE__);
|
|
goto bail;
|
|
}
|
|
crp->crp_payload_start = 0;
|
|
crp->crp_payload_length = cop->len;
|
|
if (cse->hashsize)
|
|
crp->crp_digest_start = cop->len;
|
|
|
|
switch (cse->mode) {
|
|
case CSP_MODE_COMPRESS:
|
|
switch (cop->op) {
|
|
case COP_ENCRYPT:
|
|
crp->crp_op = CRYPTO_OP_COMPRESS;
|
|
break;
|
|
case COP_DECRYPT:
|
|
crp->crp_op = CRYPTO_OP_DECOMPRESS;
|
|
break;
|
|
default:
|
|
SDT_PROBE1(opencrypto, dev, ioctl, error, __LINE__);
|
|
error = EINVAL;
|
|
goto bail;
|
|
}
|
|
break;
|
|
case CSP_MODE_CIPHER:
|
|
switch (cop->op) {
|
|
case COP_ENCRYPT:
|
|
crp->crp_op = CRYPTO_OP_ENCRYPT;
|
|
break;
|
|
case COP_DECRYPT:
|
|
crp->crp_op = CRYPTO_OP_DECRYPT;
|
|
break;
|
|
default:
|
|
SDT_PROBE1(opencrypto, dev, ioctl, error, __LINE__);
|
|
error = EINVAL;
|
|
goto bail;
|
|
}
|
|
break;
|
|
case CSP_MODE_DIGEST:
|
|
switch (cop->op) {
|
|
case 0:
|
|
case COP_ENCRYPT:
|
|
case COP_DECRYPT:
|
|
crp->crp_op = CRYPTO_OP_COMPUTE_DIGEST;
|
|
break;
|
|
default:
|
|
SDT_PROBE1(opencrypto, dev, ioctl, error, __LINE__);
|
|
error = EINVAL;
|
|
goto bail;
|
|
}
|
|
break;
|
|
case CSP_MODE_ETA:
|
|
switch (cop->op) {
|
|
case COP_ENCRYPT:
|
|
crp->crp_op = CRYPTO_OP_ENCRYPT |
|
|
CRYPTO_OP_COMPUTE_DIGEST;
|
|
break;
|
|
case COP_DECRYPT:
|
|
crp->crp_op = CRYPTO_OP_DECRYPT |
|
|
CRYPTO_OP_VERIFY_DIGEST;
|
|
break;
|
|
default:
|
|
SDT_PROBE1(opencrypto, dev, ioctl, error, __LINE__);
|
|
error = EINVAL;
|
|
goto bail;
|
|
}
|
|
break;
|
|
default:
|
|
SDT_PROBE1(opencrypto, dev, ioctl, error, __LINE__);
|
|
error = EINVAL;
|
|
goto bail;
|
|
}
|
|
|
|
crp->crp_ilen = cop->len + cse->hashsize;
|
|
crp->crp_flags = CRYPTO_F_CBIMM | (cop->flags & COP_F_BATCH);
|
|
crp->crp_buf = cod->buf;
|
|
crp->crp_buf_type = CRYPTO_BUF_CONTIG;
|
|
crp->crp_callback = cryptodev_cb;
|
|
crp->crp_opaque = cod;
|
|
|
|
if (cop->iv) {
|
|
if (cse->ivsize == 0) {
|
|
SDT_PROBE1(opencrypto, dev, ioctl, error, __LINE__);
|
|
error = EINVAL;
|
|
goto bail;
|
|
}
|
|
error = copyin(cop->iv, crp->crp_iv, cse->ivsize);
|
|
if (error) {
|
|
SDT_PROBE1(opencrypto, dev, ioctl, error, __LINE__);
|
|
goto bail;
|
|
}
|
|
crp->crp_flags |= CRYPTO_F_IV_SEPARATE;
|
|
} else if (cse->ivsize != 0) {
|
|
crp->crp_iv_start = 0;
|
|
crp->crp_payload_start += cse->ivsize;
|
|
crp->crp_payload_length -= cse->ivsize;
|
|
}
|
|
|
|
if (cop->mac != NULL) {
|
|
error = copyin(cop->mac, cod->buf + cop->len, cse->hashsize);
|
|
if (error) {
|
|
SDT_PROBE1(opencrypto, dev, ioctl, error, __LINE__);
|
|
goto bail;
|
|
}
|
|
}
|
|
cryptodev_warn(cse);
|
|
again:
|
|
/*
|
|
* Let the dispatch run unlocked, then, interlock against the
|
|
* callback before checking if the operation completed and going
|
|
* to sleep. This insures drivers don't inherit our lock which
|
|
* results in a lock order reversal between crypto_dispatch forced
|
|
* entry and the crypto_done callback into us.
|
|
*/
|
|
error = crypto_dispatch(crp);
|
|
if (error != 0) {
|
|
SDT_PROBE1(opencrypto, dev, ioctl, error, __LINE__);
|
|
goto bail;
|
|
}
|
|
|
|
mtx_lock(&cse->lock);
|
|
while (!cod->done)
|
|
mtx_sleep(cod, &cse->lock, PWAIT, "crydev", 0);
|
|
mtx_unlock(&cse->lock);
|
|
|
|
if (crp->crp_etype == EAGAIN) {
|
|
crp->crp_etype = 0;
|
|
crp->crp_flags &= ~CRYPTO_F_DONE;
|
|
cod->done = false;
|
|
goto again;
|
|
}
|
|
|
|
if (crp->crp_etype != 0) {
|
|
SDT_PROBE1(opencrypto, dev, ioctl, error, __LINE__);
|
|
error = crp->crp_etype;
|
|
goto bail;
|
|
}
|
|
|
|
if (cop->dst != NULL) {
|
|
error = copyout(cod->buf, cop->dst, cop->len);
|
|
if (error) {
|
|
SDT_PROBE1(opencrypto, dev, ioctl, error, __LINE__);
|
|
goto bail;
|
|
}
|
|
}
|
|
|
|
if (cop->mac != NULL) {
|
|
error = copyout(cod->buf + cop->len, cop->mac, cse->hashsize);
|
|
if (error) {
|
|
SDT_PROBE1(opencrypto, dev, ioctl, error, __LINE__);
|
|
goto bail;
|
|
}
|
|
}
|
|
|
|
bail:
|
|
if (crp)
|
|
crypto_freereq(crp);
|
|
if (cod)
|
|
cod_free(cod);
|
|
|
|
return (error);
|
|
}
|
|
|
|
static int
|
|
cryptodev_aead(
|
|
struct csession *cse,
|
|
struct crypt_aead *caead,
|
|
struct ucred *active_cred,
|
|
struct thread *td)
|
|
{
|
|
struct cryptop_data *cod = NULL;
|
|
struct cryptop *crp = NULL;
|
|
int error;
|
|
|
|
if (caead->len > 256*1024-4 || caead->aadlen > 256*1024-4) {
|
|
SDT_PROBE1(opencrypto, dev, ioctl, error, __LINE__);
|
|
return (E2BIG);
|
|
}
|
|
|
|
if (cse->txform == NULL || cse->hashsize == 0 || caead->tag == NULL ||
|
|
(caead->len % cse->txform->blocksize) != 0) {
|
|
SDT_PROBE1(opencrypto, dev, ioctl, error, __LINE__);
|
|
return (EINVAL);
|
|
}
|
|
|
|
/*
|
|
* The COP_F_CIPHER_FIRST flag predates explicit session
|
|
* modes, but the only way it was used was for EtA so allow it
|
|
* as long as it is consistent with EtA.
|
|
*/
|
|
if (caead->flags & COP_F_CIPHER_FIRST) {
|
|
if (caead->op != COP_ENCRYPT) {
|
|
SDT_PROBE1(opencrypto, dev, ioctl, error, __LINE__);
|
|
return (EINVAL);
|
|
}
|
|
}
|
|
|
|
cod = cod_alloc(cse, caead->aadlen + caead->len + cse->hashsize, td);
|
|
|
|
crp = crypto_getreq(cse->cses, M_WAITOK);
|
|
|
|
error = copyin(caead->aad, cod->buf, caead->aadlen);
|
|
if (error) {
|
|
SDT_PROBE1(opencrypto, dev, ioctl, error, __LINE__);
|
|
goto bail;
|
|
}
|
|
crp->crp_aad_start = 0;
|
|
crp->crp_aad_length = caead->aadlen;
|
|
|
|
error = copyin(caead->src, cod->buf + caead->aadlen, caead->len);
|
|
if (error) {
|
|
SDT_PROBE1(opencrypto, dev, ioctl, error, __LINE__);
|
|
goto bail;
|
|
}
|
|
crp->crp_payload_start = caead->aadlen;
|
|
crp->crp_payload_length = caead->len;
|
|
crp->crp_digest_start = caead->aadlen + caead->len;
|
|
|
|
switch (cse->mode) {
|
|
case CSP_MODE_AEAD:
|
|
switch (caead->op) {
|
|
case COP_ENCRYPT:
|
|
crp->crp_op = CRYPTO_OP_ENCRYPT |
|
|
CRYPTO_OP_COMPUTE_DIGEST;
|
|
break;
|
|
case COP_DECRYPT:
|
|
crp->crp_op = CRYPTO_OP_DECRYPT |
|
|
CRYPTO_OP_VERIFY_DIGEST;
|
|
break;
|
|
default:
|
|
SDT_PROBE1(opencrypto, dev, ioctl, error, __LINE__);
|
|
error = EINVAL;
|
|
goto bail;
|
|
}
|
|
break;
|
|
case CSP_MODE_ETA:
|
|
switch (caead->op) {
|
|
case COP_ENCRYPT:
|
|
crp->crp_op = CRYPTO_OP_ENCRYPT |
|
|
CRYPTO_OP_COMPUTE_DIGEST;
|
|
break;
|
|
case COP_DECRYPT:
|
|
crp->crp_op = CRYPTO_OP_DECRYPT |
|
|
CRYPTO_OP_VERIFY_DIGEST;
|
|
break;
|
|
default:
|
|
SDT_PROBE1(opencrypto, dev, ioctl, error, __LINE__);
|
|
error = EINVAL;
|
|
goto bail;
|
|
}
|
|
break;
|
|
default:
|
|
SDT_PROBE1(opencrypto, dev, ioctl, error, __LINE__);
|
|
error = EINVAL;
|
|
goto bail;
|
|
}
|
|
|
|
crp->crp_ilen = caead->aadlen + caead->len + cse->hashsize;
|
|
crp->crp_flags = CRYPTO_F_CBIMM | (caead->flags & COP_F_BATCH);
|
|
crp->crp_buf = cod->buf;
|
|
crp->crp_buf_type = CRYPTO_BUF_CONTIG;
|
|
crp->crp_callback = cryptodev_cb;
|
|
crp->crp_opaque = cod;
|
|
|
|
if (caead->iv) {
|
|
/*
|
|
* Permit a 16-byte IV for AES-XTS, but only use the
|
|
* first 8 bytes as a block number.
|
|
*/
|
|
if (cse->mode == CSP_MODE_ETA &&
|
|
caead->ivlen == AES_BLOCK_LEN &&
|
|
cse->ivsize == AES_XTS_IV_LEN)
|
|
caead->ivlen = AES_XTS_IV_LEN;
|
|
|
|
if (caead->ivlen != cse->ivsize) {
|
|
error = EINVAL;
|
|
SDT_PROBE1(opencrypto, dev, ioctl, error, __LINE__);
|
|
goto bail;
|
|
}
|
|
|
|
error = copyin(caead->iv, crp->crp_iv, cse->ivsize);
|
|
if (error) {
|
|
SDT_PROBE1(opencrypto, dev, ioctl, error, __LINE__);
|
|
goto bail;
|
|
}
|
|
crp->crp_flags |= CRYPTO_F_IV_SEPARATE;
|
|
} else {
|
|
crp->crp_iv_start = crp->crp_payload_start;
|
|
crp->crp_payload_start += cse->ivsize;
|
|
crp->crp_payload_length -= cse->ivsize;
|
|
}
|
|
|
|
error = copyin(caead->tag, cod->buf + caead->len + caead->aadlen,
|
|
cse->hashsize);
|
|
if (error) {
|
|
SDT_PROBE1(opencrypto, dev, ioctl, error, __LINE__);
|
|
goto bail;
|
|
}
|
|
cryptodev_warn(cse);
|
|
again:
|
|
/*
|
|
* Let the dispatch run unlocked, then, interlock against the
|
|
* callback before checking if the operation completed and going
|
|
* to sleep. This insures drivers don't inherit our lock which
|
|
* results in a lock order reversal between crypto_dispatch forced
|
|
* entry and the crypto_done callback into us.
|
|
*/
|
|
error = crypto_dispatch(crp);
|
|
if (error != 0) {
|
|
SDT_PROBE1(opencrypto, dev, ioctl, error, __LINE__);
|
|
goto bail;
|
|
}
|
|
|
|
mtx_lock(&cse->lock);
|
|
while (!cod->done)
|
|
mtx_sleep(cod, &cse->lock, PWAIT, "crydev", 0);
|
|
mtx_unlock(&cse->lock);
|
|
|
|
if (crp->crp_etype == EAGAIN) {
|
|
crp->crp_etype = 0;
|
|
crp->crp_flags &= ~CRYPTO_F_DONE;
|
|
cod->done = false;
|
|
goto again;
|
|
}
|
|
|
|
if (crp->crp_etype != 0) {
|
|
error = crp->crp_etype;
|
|
SDT_PROBE1(opencrypto, dev, ioctl, error, __LINE__);
|
|
goto bail;
|
|
}
|
|
|
|
if (caead->dst != NULL) {
|
|
error = copyout(cod->buf + caead->aadlen, caead->dst,
|
|
caead->len);
|
|
if (error) {
|
|
SDT_PROBE1(opencrypto, dev, ioctl, error, __LINE__);
|
|
goto bail;
|
|
}
|
|
}
|
|
|
|
error = copyout(cod->buf + caead->aadlen + caead->len, caead->tag,
|
|
cse->hashsize);
|
|
if (error) {
|
|
SDT_PROBE1(opencrypto, dev, ioctl, error, __LINE__);
|
|
goto bail;
|
|
}
|
|
|
|
bail:
|
|
crypto_freereq(crp);
|
|
if (cod)
|
|
cod_free(cod);
|
|
|
|
return (error);
|
|
}
|
|
|
|
static int
|
|
cryptodev_cb(struct cryptop *crp)
|
|
{
|
|
struct cryptop_data *cod = crp->crp_opaque;
|
|
|
|
/*
|
|
* Lock to ensure the wakeup() is not missed by the loops
|
|
* waiting on cod->done in cryptodev_op() and
|
|
* cryptodev_aead().
|
|
*/
|
|
mtx_lock(&cod->cse->lock);
|
|
cod->done = true;
|
|
mtx_unlock(&cod->cse->lock);
|
|
wakeup(cod);
|
|
return (0);
|
|
}
|
|
|
|
static void
|
|
cryptodevkey_cb(struct cryptkop *krp)
|
|
{
|
|
|
|
wakeup_one(krp);
|
|
}
|
|
|
|
static int
|
|
cryptodev_key(struct crypt_kop *kop)
|
|
{
|
|
struct cryptkop *krp = NULL;
|
|
int error = EINVAL;
|
|
int in, out, size, i;
|
|
|
|
if (kop->crk_iparams + kop->crk_oparams > CRK_MAXPARAM) {
|
|
SDT_PROBE1(opencrypto, dev, ioctl, error, __LINE__);
|
|
return (EFBIG);
|
|
}
|
|
|
|
in = kop->crk_iparams;
|
|
out = kop->crk_oparams;
|
|
switch (kop->crk_op) {
|
|
case CRK_MOD_EXP:
|
|
if (in == 3 && out == 1)
|
|
break;
|
|
SDT_PROBE1(opencrypto, dev, ioctl, error, __LINE__);
|
|
return (EINVAL);
|
|
case CRK_MOD_EXP_CRT:
|
|
if (in == 6 && out == 1)
|
|
break;
|
|
SDT_PROBE1(opencrypto, dev, ioctl, error, __LINE__);
|
|
return (EINVAL);
|
|
case CRK_DSA_SIGN:
|
|
if (in == 5 && out == 2)
|
|
break;
|
|
SDT_PROBE1(opencrypto, dev, ioctl, error, __LINE__);
|
|
return (EINVAL);
|
|
case CRK_DSA_VERIFY:
|
|
if (in == 7 && out == 0)
|
|
break;
|
|
SDT_PROBE1(opencrypto, dev, ioctl, error, __LINE__);
|
|
return (EINVAL);
|
|
case CRK_DH_COMPUTE_KEY:
|
|
if (in == 3 && out == 1)
|
|
break;
|
|
SDT_PROBE1(opencrypto, dev, ioctl, error, __LINE__);
|
|
return (EINVAL);
|
|
default:
|
|
SDT_PROBE1(opencrypto, dev, ioctl, error, __LINE__);
|
|
return (EINVAL);
|
|
}
|
|
|
|
krp = (struct cryptkop *)malloc(sizeof *krp, M_XDATA, M_WAITOK|M_ZERO);
|
|
if (!krp) {
|
|
SDT_PROBE1(opencrypto, dev, ioctl, error, __LINE__);
|
|
return (ENOMEM);
|
|
}
|
|
krp->krp_op = kop->crk_op;
|
|
krp->krp_status = kop->crk_status;
|
|
krp->krp_iparams = kop->crk_iparams;
|
|
krp->krp_oparams = kop->crk_oparams;
|
|
krp->krp_crid = kop->crk_crid;
|
|
krp->krp_status = 0;
|
|
krp->krp_callback = cryptodevkey_cb;
|
|
|
|
for (i = 0; i < CRK_MAXPARAM; i++) {
|
|
if (kop->crk_param[i].crp_nbits > 65536) {
|
|
/* Limit is the same as in OpenBSD */
|
|
SDT_PROBE1(opencrypto, dev, ioctl, error, __LINE__);
|
|
goto fail;
|
|
}
|
|
krp->krp_param[i].crp_nbits = kop->crk_param[i].crp_nbits;
|
|
}
|
|
for (i = 0; i < krp->krp_iparams + krp->krp_oparams; i++) {
|
|
size = (krp->krp_param[i].crp_nbits + 7) / 8;
|
|
if (size == 0)
|
|
continue;
|
|
krp->krp_param[i].crp_p = malloc(size, M_XDATA, M_WAITOK);
|
|
if (i >= krp->krp_iparams)
|
|
continue;
|
|
error = copyin(kop->crk_param[i].crp_p, krp->krp_param[i].crp_p, size);
|
|
if (error) {
|
|
SDT_PROBE1(opencrypto, dev, ioctl, error, __LINE__);
|
|
goto fail;
|
|
}
|
|
}
|
|
|
|
error = crypto_kdispatch(krp);
|
|
if (error) {
|
|
SDT_PROBE1(opencrypto, dev, ioctl, error, __LINE__);
|
|
goto fail;
|
|
}
|
|
error = tsleep(krp, PSOCK, "crydev", 0);
|
|
if (error) {
|
|
/* XXX can this happen? if so, how do we recover? */
|
|
SDT_PROBE1(opencrypto, dev, ioctl, error, __LINE__);
|
|
goto fail;
|
|
}
|
|
|
|
kop->crk_crid = krp->krp_hid; /* device that did the work */
|
|
if (krp->krp_status != 0) {
|
|
error = krp->krp_status;
|
|
SDT_PROBE1(opencrypto, dev, ioctl, error, __LINE__);
|
|
goto fail;
|
|
}
|
|
|
|
for (i = krp->krp_iparams; i < krp->krp_iparams + krp->krp_oparams; i++) {
|
|
size = (krp->krp_param[i].crp_nbits + 7) / 8;
|
|
if (size == 0)
|
|
continue;
|
|
error = copyout(krp->krp_param[i].crp_p, kop->crk_param[i].crp_p, size);
|
|
if (error) {
|
|
SDT_PROBE1(opencrypto, dev, ioctl, error, __LINE__);
|
|
goto fail;
|
|
}
|
|
}
|
|
|
|
fail:
|
|
if (krp) {
|
|
kop->crk_status = krp->krp_status;
|
|
for (i = 0; i < CRK_MAXPARAM; i++) {
|
|
if (krp->krp_param[i].crp_p)
|
|
free(krp->krp_param[i].crp_p, M_XDATA);
|
|
}
|
|
free(krp, M_XDATA);
|
|
}
|
|
return (error);
|
|
}
|
|
|
|
static int
|
|
cryptodev_find(struct crypt_find_op *find)
|
|
{
|
|
device_t dev;
|
|
size_t fnlen = sizeof find->name;
|
|
|
|
if (find->crid != -1) {
|
|
dev = crypto_find_device_byhid(find->crid);
|
|
if (dev == NULL)
|
|
return (ENOENT);
|
|
strncpy(find->name, device_get_nameunit(dev), fnlen);
|
|
find->name[fnlen - 1] = '\x0';
|
|
} else {
|
|
find->name[fnlen - 1] = '\x0';
|
|
find->crid = crypto_find_driver(find->name);
|
|
if (find->crid == -1)
|
|
return (ENOENT);
|
|
}
|
|
return (0);
|
|
}
|
|
|
|
/* ARGSUSED */
|
|
static int
|
|
cryptof_stat(
|
|
struct file *fp,
|
|
struct stat *sb,
|
|
struct ucred *active_cred,
|
|
struct thread *td)
|
|
{
|
|
|
|
return (EOPNOTSUPP);
|
|
}
|
|
|
|
/* ARGSUSED */
|
|
static int
|
|
cryptof_close(struct file *fp, struct thread *td)
|
|
{
|
|
struct fcrypt *fcr = fp->f_data;
|
|
struct csession *cse;
|
|
|
|
while ((cse = TAILQ_FIRST(&fcr->csessions))) {
|
|
TAILQ_REMOVE(&fcr->csessions, cse, next);
|
|
KASSERT(cse->refs == 1,
|
|
("%s: crypto session %p with %d refs", __func__, cse,
|
|
cse->refs));
|
|
csefree(cse);
|
|
}
|
|
free(fcr, M_XDATA);
|
|
fp->f_data = NULL;
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
cryptof_fill_kinfo(struct file *fp, struct kinfo_file *kif, struct filedesc *fdp)
|
|
{
|
|
|
|
kif->kf_type = KF_TYPE_CRYPTO;
|
|
return (0);
|
|
}
|
|
|
|
static struct csession *
|
|
csefind(struct fcrypt *fcr, u_int ses)
|
|
{
|
|
struct csession *cse;
|
|
|
|
mtx_lock(&fcr->lock);
|
|
TAILQ_FOREACH(cse, &fcr->csessions, next) {
|
|
if (cse->ses == ses) {
|
|
refcount_acquire(&cse->refs);
|
|
mtx_unlock(&fcr->lock);
|
|
return (cse);
|
|
}
|
|
}
|
|
mtx_unlock(&fcr->lock);
|
|
return (NULL);
|
|
}
|
|
|
|
static bool
|
|
csedelete(struct fcrypt *fcr, u_int ses)
|
|
{
|
|
struct csession *cse;
|
|
|
|
mtx_lock(&fcr->lock);
|
|
TAILQ_FOREACH(cse, &fcr->csessions, next) {
|
|
if (cse->ses == ses) {
|
|
TAILQ_REMOVE(&fcr->csessions, cse, next);
|
|
mtx_unlock(&fcr->lock);
|
|
csefree(cse);
|
|
return (true);
|
|
}
|
|
}
|
|
mtx_unlock(&fcr->lock);
|
|
return (false);
|
|
}
|
|
|
|
struct csession *
|
|
csecreate(struct fcrypt *fcr, crypto_session_t cses,
|
|
struct crypto_session_params *csp, struct enc_xform *txform,
|
|
void *key, struct auth_hash *thash, void *mackey)
|
|
{
|
|
struct csession *cse;
|
|
|
|
cse = malloc(sizeof(struct csession), M_XDATA, M_NOWAIT | M_ZERO);
|
|
if (cse == NULL)
|
|
return NULL;
|
|
mtx_init(&cse->lock, "cryptodev", "crypto session lock", MTX_DEF);
|
|
refcount_init(&cse->refs, 1);
|
|
cse->key = key;
|
|
cse->mackey = mackey;
|
|
cse->mode = csp->csp_mode;
|
|
cse->cses = cses;
|
|
cse->txform = txform;
|
|
if (thash != NULL)
|
|
cse->hashsize = thash->hashsize;
|
|
else if (csp->csp_cipher_alg == CRYPTO_AES_NIST_GCM_16)
|
|
cse->hashsize = AES_GMAC_HASH_LEN;
|
|
else if (csp->csp_cipher_alg == CRYPTO_AES_CCM_16)
|
|
cse->hashsize = AES_CBC_MAC_HASH_LEN;
|
|
cse->ivsize = csp->csp_ivlen;
|
|
mtx_lock(&fcr->lock);
|
|
TAILQ_INSERT_TAIL(&fcr->csessions, cse, next);
|
|
cse->ses = fcr->sesn++;
|
|
mtx_unlock(&fcr->lock);
|
|
return (cse);
|
|
}
|
|
|
|
static void
|
|
csefree(struct csession *cse)
|
|
{
|
|
|
|
if (!refcount_release(&cse->refs))
|
|
return;
|
|
crypto_freesession(cse->cses);
|
|
mtx_destroy(&cse->lock);
|
|
if (cse->key)
|
|
free(cse->key, M_XDATA);
|
|
if (cse->mackey)
|
|
free(cse->mackey, M_XDATA);
|
|
free(cse, M_XDATA);
|
|
}
|
|
|
|
static int
|
|
cryptoioctl(struct cdev *dev, u_long cmd, caddr_t data, int flag, struct thread *td)
|
|
{
|
|
struct file *f;
|
|
struct fcrypt *fcr;
|
|
int fd, error;
|
|
|
|
switch (cmd) {
|
|
case CRIOGET:
|
|
error = falloc_noinstall(td, &f);
|
|
if (error)
|
|
break;
|
|
|
|
fcr = malloc(sizeof(struct fcrypt), M_XDATA, M_WAITOK | M_ZERO);
|
|
TAILQ_INIT(&fcr->csessions);
|
|
mtx_init(&fcr->lock, "fcrypt", NULL, MTX_DEF);
|
|
|
|
finit(f, FREAD | FWRITE, DTYPE_CRYPTO, fcr, &cryptofops);
|
|
error = finstall(td, f, &fd, 0, NULL);
|
|
if (error) {
|
|
mtx_destroy(&fcr->lock);
|
|
free(fcr, M_XDATA);
|
|
} else
|
|
*(uint32_t *)data = fd;
|
|
fdrop(f, td);
|
|
break;
|
|
case CRIOFINDDEV:
|
|
error = cryptodev_find((struct crypt_find_op *)data);
|
|
break;
|
|
case CRIOASYMFEAT:
|
|
error = crypto_getfeat((int *)data);
|
|
break;
|
|
default:
|
|
error = EINVAL;
|
|
break;
|
|
}
|
|
return (error);
|
|
}
|
|
|
|
static struct cdevsw crypto_cdevsw = {
|
|
.d_version = D_VERSION,
|
|
.d_ioctl = cryptoioctl,
|
|
.d_name = "crypto",
|
|
};
|
|
static struct cdev *crypto_dev;
|
|
|
|
/*
|
|
* Initialization code, both for static and dynamic loading.
|
|
*/
|
|
static int
|
|
cryptodev_modevent(module_t mod, int type, void *unused)
|
|
{
|
|
switch (type) {
|
|
case MOD_LOAD:
|
|
if (bootverbose)
|
|
printf("crypto: <crypto device>\n");
|
|
crypto_dev = make_dev(&crypto_cdevsw, 0,
|
|
UID_ROOT, GID_WHEEL, 0666,
|
|
"crypto");
|
|
return 0;
|
|
case MOD_UNLOAD:
|
|
/*XXX disallow if active sessions */
|
|
destroy_dev(crypto_dev);
|
|
return 0;
|
|
}
|
|
return EINVAL;
|
|
}
|
|
|
|
static moduledata_t cryptodev_mod = {
|
|
"cryptodev",
|
|
cryptodev_modevent,
|
|
0
|
|
};
|
|
MODULE_VERSION(cryptodev, 1);
|
|
DECLARE_MODULE(cryptodev, cryptodev_mod, SI_SUB_PSEUDO, SI_ORDER_ANY);
|
|
MODULE_DEPEND(cryptodev, crypto, 1, 1, 1);
|
|
MODULE_DEPEND(cryptodev, zlib, 1, 1, 1);
|