#include <sys/types.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>
#include <sys/kmem.h>
#include <sys/note.h>
#include <sys/crypto/common.h>
#include <sys/crypto/spi.h>
#include <sys/crypto/dca.h>
#if defined(__x86)
#include <sys/byteorder.h>
#define UNALIGNED_POINTERS_PERMITTED
#endif
static int dca_3desstart(dca_t *, uint32_t, dca_request_t *);
static void dca_3desdone(dca_request_t *, int);
int
dca_3des(crypto_ctx_t *ctx, crypto_data_t *in,
crypto_data_t *out, crypto_req_handle_t req, int flags)
{
int len;
int rv;
dca_request_t *reqp = ctx->cc_provider_private;
dca_request_t *des_ctx = ctx->cc_provider_private;
dca_t *dca = ctx->cc_provider;
crypto_data_t *nin = &reqp->dr_ctx.in_dup;
len = dca_length(in);
if (len % DESBLOCK) {
DBG(dca, DWARN, "input not an integral number of DES blocks");
(void) dca_free_context(ctx);
if (flags & DR_DECRYPT) {
return (CRYPTO_ENCRYPTED_DATA_LEN_RANGE);
} else {
return (CRYPTO_DATA_LEN_RANGE);
}
}
if (in->cd_miscdata != NULL) {
#ifdef UNALIGNED_POINTERS_PERMITTED
uint32_t *p = (uint32_t *)in->cd_miscdata;
des_ctx->dr_ctx.iv[0] = htonl(p[0]);
des_ctx->dr_ctx.iv[1] = htonl(p[1]);
#else
uchar_t *p = (uchar_t *)in->cd_miscdata;
des_ctx->dr_ctx.iv[0] = p[0]<<24 | p[1]<<16 | p[2]<<8 | p[3];
des_ctx->dr_ctx.iv[1] = p[4]<<24 | p[5]<<16 | p[6]<<8 | p[7];
#endif
}
if (len > dca_length(out)) {
DBG(dca, DWARN, "inadequate output space (need %d, got %d)",
len, dca_length(out));
out->cd_length = len;
return (CRYPTO_BUFFER_TOO_SMALL);
}
if ((rv = dca_verifyio(in, out)) != CRYPTO_SUCCESS) {
(void) dca_free_context(ctx);
return (rv);
}
if (len == 0) {
out->cd_length = 0;
(void) dca_free_context(ctx);
return (CRYPTO_SUCCESS);
}
if ((rv = dca_dupcrypto(in, nin)) != CRYPTO_SUCCESS) {
(void) dca_free_context(ctx);
return (rv);
}
out->cd_length = 0;
reqp->dr_kcf_req = req;
reqp->dr_in = nin;
reqp->dr_out = out;
reqp->dr_job_stat = DS_3DESJOBS;
reqp->dr_byte_stat = DS_3DESBYTES;
rv = dca_3desstart(dca, flags, reqp);
if (rv != CRYPTO_QUEUED && rv != CRYPTO_BUFFER_TOO_SMALL) {
(void) dca_free_context(ctx);
}
return (rv);
}
void
dca_3desctxfree(void *arg)
{
crypto_ctx_t *ctx = (crypto_ctx_t *)arg;
dca_request_t *des_ctx = ctx->cc_provider_private;
if (des_ctx == NULL)
return;
des_ctx->dr_ctx.atomic = 0;
des_ctx->dr_ctx.ctx_cm_type = 0;
ctx->cc_provider_private = NULL;
if (des_ctx->destroy)
dca_destroyreq(des_ctx);
else
dca_freereq(des_ctx);
}
int
dca_3desupdate(crypto_ctx_t *ctx, crypto_data_t *in,
crypto_data_t *out, crypto_req_handle_t req, int flags)
{
int len;
int rawlen;
int rv;
dca_request_t *reqp = ctx->cc_provider_private;
dca_request_t *des_ctx = ctx->cc_provider_private;
dca_t *dca = ctx->cc_provider;
crypto_data_t *nin = &reqp->dr_ctx.in_dup;
rawlen = dca_length(in) + des_ctx->dr_ctx.residlen;
len = ROUNDDOWN(rawlen, DESBLOCK);
if (in->cd_miscdata != NULL) {
#ifdef UNALIGNED_POINTERS_PERMITTED
uint32_t *p = (uint32_t *)in->cd_miscdata;
des_ctx->dr_ctx.iv[0] = htonl(p[0]);
des_ctx->dr_ctx.iv[1] = htonl(p[1]);
#else
uchar_t *p = (uchar_t *)in->cd_miscdata;
des_ctx->dr_ctx.iv[0] = p[0]<<24 | p[1]<<16 | p[2]<<8 | p[3];
des_ctx->dr_ctx.iv[1] = p[4]<<24 | p[5]<<16 | p[6]<<8 | p[7];
#endif
}
if (len > dca_length(out)) {
DBG(dca, DWARN, "not enough output space (need %d, got %d)",
len, dca_length(out));
out->cd_length = len;
return (CRYPTO_BUFFER_TOO_SMALL);
}
if ((rv = dca_verifyio(in, out)) != CRYPTO_SUCCESS) {
(void) dca_free_context(ctx);
return (rv);
}
reqp->dr_kcf_req = req;
if (len == 0) {
if ((rv = dca_getbufbytes(in, 0,
(rawlen % DESBLOCK) - des_ctx->dr_ctx.residlen,
des_ctx->dr_ctx.resid + des_ctx->dr_ctx.residlen)) !=
CRYPTO_SUCCESS) {
DBG(dca, DWARN,
"dca_3desupdate: dca_getbufbytes() failed for residual only pass");
dca_freereq(reqp);
return (rv);
}
des_ctx->dr_ctx.residlen = rawlen % DESBLOCK;
out->cd_length = 0;
return (CRYPTO_SUCCESS);
}
if (des_ctx->dr_ctx.residlen) {
bcopy(des_ctx->dr_ctx.resid, des_ctx->dr_ctx.activeresid,
des_ctx->dr_ctx.residlen);
des_ctx->dr_ctx.activeresidlen = des_ctx->dr_ctx.residlen;
}
if ((rv = dca_getbufbytes(in, len - des_ctx->dr_ctx.residlen,
rawlen % DESBLOCK, des_ctx->dr_ctx.resid)) != CRYPTO_SUCCESS) {
DBG(dca, DWARN, "dca_3desupdate: dca_getbufbytes() failed");
(void) dca_free_context(ctx);
return (rv);
}
des_ctx->dr_ctx.residlen = rawlen % DESBLOCK;
if ((rv = dca_dupcrypto(in, nin)) != CRYPTO_SUCCESS) {
(void) dca_free_context(ctx);
return (rv);
}
out->cd_length = 0;
reqp->dr_in = nin;
reqp->dr_out = out;
reqp->dr_job_stat = DS_3DESJOBS;
reqp->dr_byte_stat = DS_3DESBYTES;
rv = dca_3desstart(dca, flags, reqp);
if (rv != CRYPTO_QUEUED && rv != CRYPTO_BUFFER_TOO_SMALL) {
(void) dca_free_context(ctx);
}
return (rv);
}
int
dca_3desfinal(crypto_ctx_t *ctx, crypto_data_t *out, int mode)
{
dca_request_t *des_ctx = ctx->cc_provider_private;
dca_t *dca = ctx->cc_provider;
int rv = CRYPTO_SUCCESS;
ASSERT(ctx->cc_provider_private != NULL);
if (des_ctx->dr_ctx.residlen != 0) {
DBG(dca, DWARN, "dca_3desfinal: invalid nonzero residual");
if (mode & DR_DECRYPT) {
rv = CRYPTO_ENCRYPTED_DATA_LEN_RANGE;
} else {
rv = CRYPTO_DATA_LEN_RANGE;
}
}
(void) dca_free_context(ctx);
out->cd_length = 0;
return (rv);
}
int
dca_3desatomic(crypto_provider_handle_t provider,
crypto_session_id_t session_id, crypto_mechanism_t *mechanism,
crypto_key_t *key, crypto_data_t *input, crypto_data_t *output,
int kmflag, crypto_req_handle_t req, int mode)
{
crypto_ctx_t ctx;
int rv;
ctx.cc_provider = provider;
ctx.cc_session = session_id;
if ((dca_length(input) & (DESBLOCK - 1)) != 0) {
DBG(NULL, DWARN, "dca_3desatomic: input not multiple of BS");
if (mode & DR_DECRYPT) {
return (CRYPTO_ENCRYPTED_DATA_LEN_RANGE);
} else {
return (CRYPTO_DATA_LEN_RANGE);
}
}
rv = dca_3desctxinit(&ctx, mechanism, key, kmflag, mode);
if (rv != CRYPTO_SUCCESS) {
DBG(NULL, DWARN, "dca_3desatomic: dca_3desctxinit() failed");
return (rv);
}
((dca_request_t *)ctx.cc_provider_private)->dr_ctx.atomic = 1;
if (input == output) {
((dca_request_t *)ctx.cc_provider_private)->dr_flags
|= DR_INPLACE;
}
rv = dca_3des(&ctx, input, output, req, mode);
if ((rv != CRYPTO_QUEUED) && (rv != CRYPTO_SUCCESS)) {
DBG(NULL, DWARN, "dca_3desatomic: dca_3des() failed");
output->cd_length = 0;
}
if (rv != CRYPTO_QUEUED)
dca_3desctxfree(&ctx);
return (rv);
}
int
dca_3desstart(dca_t *dca, uint32_t flags, dca_request_t *reqp)
{
size_t len;
crypto_data_t *in = reqp->dr_in;
int rv;
dca_request_t *ctx = reqp;
uint32_t iv[2];
len = ctx->dr_ctx.activeresidlen + dca_length(in);
len = ROUNDDOWN(min(len, MAXPACKET), DESBLOCK);
reqp->dr_pkt_length = (uint16_t)len;
iv[0] = ctx->dr_ctx.iv[0];
iv[1] = ctx->dr_ctx.iv[1];
if (flags & DR_DECRYPT) {
uchar_t ivstore[DESBLOCK];
#ifdef UNALIGNED_POINTERS_PERMITTED
uint32_t *ivp = (uint32_t *)ivstore;
#else
uchar_t *ivp = ivstore;
#endif
if ((len == DESBLOCK) && ctx->dr_ctx.activeresidlen) {
bcopy(ctx->dr_ctx.activeresid, ivstore,
ctx->dr_ctx.activeresidlen);
rv = dca_getbufbytes(in, 0,
DESBLOCK - ctx->dr_ctx.activeresidlen,
ivstore + ctx->dr_ctx.activeresidlen);
} else {
rv = dca_getbufbytes(in,
len - DESBLOCK - ctx->dr_ctx.activeresidlen,
DESBLOCK, ivstore);
}
if (rv != CRYPTO_SUCCESS) {
DBG(dca, DWARN,
"dca_3desstart: dca_getbufbytes() failed");
return (rv);
}
#ifdef UNALIGNED_POINTERS_PERMITTED
ctx->dr_ctx.iv[0] = htonl(ivp[0]);
ctx->dr_ctx.iv[1] = htonl(ivp[1]);
#else
ctx->dr_ctx.iv[0] =
ivp[0]<<24 | ivp[1]<<16 | ivp[2]<<8 | ivp[3];
ctx->dr_ctx.iv[1] =
ivp[4]<<24 | ivp[5]<<16 | ivp[6]<<8 | ivp[7];
#endif
}
reqp->dr_flags &= ~(DR_SCATTER | DR_GATHER);
if ((len < dca_mindma) || (ctx->dr_ctx.activeresidlen > 0) ||
dca_sgcheck(dca, reqp->dr_in, DCA_SG_CONTIG) ||
dca_sgcheck(dca, reqp->dr_out, DCA_SG_WALIGN)) {
reqp->dr_flags |= DR_SCATTER | DR_GATHER;
}
if (!(reqp->dr_flags & (DR_SCATTER | DR_GATHER))) {
if (dca_bindchains(reqp, len, len) == DDI_SUCCESS) {
reqp->dr_in->cd_offset += len;
reqp->dr_in->cd_length -= len;
} else {
DBG(dca, DWARN,
"dca_3desstart: dca_bindchains() failed");
return (CRYPTO_DEVICE_ERROR);
}
}
if (reqp->dr_flags & DR_GATHER) {
rv = dca_resid_gather(in, (char *)ctx->dr_ctx.activeresid,
&ctx->dr_ctx.activeresidlen, reqp->dr_ibuf_kaddr, len);
if (rv != CRYPTO_SUCCESS) {
DBG(dca, DWARN,
"dca_3desstart: dca_resid_gather() failed");
return (rv);
}
(void) ddi_dma_sync(reqp->dr_ibuf_dmah, 0, len,
DDI_DMA_SYNC_FORDEV);
if (dca_check_dma_handle(dca, reqp->dr_ibuf_dmah,
DCA_FM_ECLASS_NONE) != DDI_SUCCESS) {
reqp->destroy = TRUE;
return (CRYPTO_DEVICE_ERROR);
}
reqp->dr_in_paddr = reqp->dr_ibuf_head.dc_buffer_paddr;
reqp->dr_in_next = reqp->dr_ibuf_head.dc_next_paddr;
if (len > reqp->dr_ibuf_head.dc_buffer_length)
reqp->dr_in_len = reqp->dr_ibuf_head.dc_buffer_length;
else
reqp->dr_in_len = len;
}
if (reqp->dr_flags & DR_SCATTER) {
reqp->dr_out_paddr = reqp->dr_obuf_head.dc_buffer_paddr;
reqp->dr_out_next = reqp->dr_obuf_head.dc_next_paddr;
if (len > reqp->dr_obuf_head.dc_buffer_length)
reqp->dr_out_len = reqp->dr_obuf_head.dc_buffer_length;
else
reqp->dr_out_len = len;
}
reqp->dr_flags |= flags;
reqp->dr_callback = dca_3desdone;
PUTCTX32(reqp, CTX_3DESIVHI, iv[0]);
PUTCTX32(reqp, CTX_3DESIVLO, iv[1]);
return (dca_start(dca, reqp, MCR1, 1));
}
void
dca_3desdone(dca_request_t *reqp, int errno)
{
crypto_data_t *out = reqp->dr_out;
dca_request_t *ctx = reqp;
ASSERT(ctx != NULL);
if (errno == CRYPTO_SUCCESS) {
size_t off;
off = dca_length(out) + reqp->dr_pkt_length - DESBLOCK;
if (reqp->dr_flags & DR_SCATTER) {
(void) ddi_dma_sync(reqp->dr_obuf_dmah, 0,
reqp->dr_out_len, DDI_DMA_SYNC_FORKERNEL);
if (dca_check_dma_handle(reqp->dr_dca,
reqp->dr_obuf_dmah, DCA_FM_ECLASS_NONE) !=
DDI_SUCCESS) {
reqp->destroy = TRUE;
errno = CRYPTO_DEVICE_ERROR;
goto errout;
}
errno = dca_scatter(reqp->dr_obuf_kaddr,
reqp->dr_out, reqp->dr_out_len, 0);
if (errno != CRYPTO_SUCCESS) {
DBG(NULL, DWARN,
"dca_3desdone: dca_scatter() failed");
goto errout;
}
} else {
out->cd_length += reqp->dr_pkt_length;
}
if (reqp->dr_flags & DR_ENCRYPT) {
uchar_t ivstore[DESBLOCK];
#ifdef UNALIGNED_POINTERS_PERMITTED
uint32_t *iv = (uint32_t *)ivstore;
#else
uchar_t *iv = ivstore;
#endif
errno = dca_getbufbytes(out, off, DESBLOCK,
(uchar_t *)iv);
if (errno != CRYPTO_SUCCESS) {
DBG(NULL, DWARN,
"dca_3desdone: dca_getbufbytes() failed");
goto errout;
}
#ifdef UNALIGNED_POINTERS_PERMITTED
ctx->dr_ctx.iv[0] = htonl(iv[0]);
ctx->dr_ctx.iv[1] = htonl(iv[1]);
#else
ctx->dr_ctx.iv[0] =
iv[0]<<24 | iv[1]<<16 | iv[2]<<8 | iv[3];
ctx->dr_ctx.iv[1] =
iv[4]<<24 | iv[5]<<16 | iv[6]<<8 | iv[7];
#endif
}
if (dca_length(reqp->dr_in) >= 8) {
errno = dca_3desstart(reqp->dr_dca, reqp->dr_flags,
reqp);
if (errno == CRYPTO_QUEUED) {
return;
}
}
}
errout:
if (reqp->dr_ctx.atomic) {
if ((errno == CRYPTO_SUCCESS) && (ctx->dr_ctx.residlen != 0)) {
DBG(NULL, DWARN,
"dca_3desdone: invalid nonzero residual");
if (reqp->dr_flags & DR_DECRYPT) {
errno = CRYPTO_ENCRYPTED_DATA_LEN_RANGE;
} else {
errno = CRYPTO_DATA_LEN_RANGE;
}
}
}
ASSERT(reqp->dr_kcf_req != NULL);
crypto_op_notification(reqp->dr_kcf_req, errno);
DBG(NULL, DINTR,
"dca_3desdone: returning %d to the kef via crypto_op_notification",
errno);
if (reqp->dr_ctx.atomic) {
reqp->dr_context = NULL;
reqp->dr_ctx.atomic = 0;
reqp->dr_ctx.ctx_cm_type = 0;
if (reqp->destroy)
dca_destroyreq(reqp);
else
dca_freereq(reqp);
}
}
int
dca_3desctxinit(crypto_ctx_t *ctx, crypto_mechanism_t *mechanism,
crypto_key_t *key, int kmflag, int flags)
{
dca_request_t *des_ctx;
dca_t *dca = ctx->cc_provider;
#ifdef UNALIGNED_POINTERS_PERMITTED
uint32_t *param;
uint32_t *value32;
#else
uchar_t *param;
#endif
uchar_t *value;
size_t paramsz;
unsigned len;
int i, j;
paramsz = mechanism->cm_param_len;
#ifdef UNALIGNED_POINTERS_PERMITTED
param = (uint32_t *)mechanism->cm_param;
#else
param = (uchar_t *)mechanism->cm_param;
#endif
if ((paramsz != 0) && (paramsz != DES_IV_LEN)) {
DBG(NULL, DWARN,
"dca_3desctxinit: parameter(IV) length not %d (%d)",
DES_IV_LEN, paramsz);
return (CRYPTO_MECHANISM_PARAM_INVALID);
}
if ((des_ctx = dca_getreq(dca, MCR1, 1)) == NULL) {
dca_error(dca, "unable to allocate request for 3DES");
return (CRYPTO_HOST_MEMORY);
}
if (param != NULL) {
ASSERT(paramsz == DES_IV_LEN);
#ifdef UNALIGNED_POINTERS_PERMITTED
des_ctx->dr_ctx.iv[0] = htonl(param[0]);
des_ctx->dr_ctx.iv[1] = htonl(param[1]);
#else
des_ctx->dr_ctx.iv[0] = param[0]<<24 | param[1]<<16 |
param[2]<<8 | param[3];
des_ctx->dr_ctx.iv[1] = param[4]<<24 | param[5]<<16 |
param[6]<<8 | param[7];
#endif
}
des_ctx->dr_ctx.residlen = 0;
des_ctx->dr_ctx.activeresidlen = 0;
des_ctx->dr_ctx.ctx_cm_type = mechanism->cm_type;
ctx->cc_provider_private = des_ctx;
if (key->ck_format != CRYPTO_KEY_RAW) {
DBG(NULL, DWARN,
"dca_3desctxinit: only raw crypto key type support with DES/3DES");
dca_3desctxfree(ctx);
return (CRYPTO_KEY_TYPE_INCONSISTENT);
}
len = key->ck_length;
value = (uchar_t *)key->ck_data;
if (flags & DR_TRIPLE) {
switch (len) {
case 192:
for (i = 0; i < 6; i++) {
des_ctx->dr_ctx.key[i] = 0;
for (j = 0; j < 4; j++) {
des_ctx->dr_ctx.key[i] <<= 8;
des_ctx->dr_ctx.key[i] |= *value;
value++;
}
}
break;
case 128:
for (i = 0; i < 4; i++) {
des_ctx->dr_ctx.key[i] = 0;
for (j = 0; j < 4; j++) {
des_ctx->dr_ctx.key[i] <<= 8;
des_ctx->dr_ctx.key[i] |= *value;
value++;
}
}
des_ctx->dr_ctx.key[4] = des_ctx->dr_ctx.key[0];
des_ctx->dr_ctx.key[5] = des_ctx->dr_ctx.key[1];
break;
default:
DBG(NULL, DWARN, "Incorrect 3DES keysize (%d)", len);
dca_3desctxfree(ctx);
return (CRYPTO_KEY_SIZE_RANGE);
}
} else {
if (len != 64) {
DBG(NULL, DWARN, "Incorrect DES keysize (%d)", len);
dca_3desctxfree(ctx);
return (CRYPTO_KEY_SIZE_RANGE);
}
#ifdef UNALIGNED_POINTERS_PERMITTED
value32 = (uint32_t *)value;
des_ctx->dr_ctx.key[0] = htonl(value32[0]);
des_ctx->dr_ctx.key[1] = htonl(value32[1]);
#else
des_ctx->dr_ctx.key[0] =
value[0]<<24 | value[1]<<16 | value[2]<<8 | value[3];
des_ctx->dr_ctx.key[1] =
value[4]<<24 | value[5]<<16 | value[6]<<8 | value[7];
#endif
des_ctx->dr_ctx.key[4] =
des_ctx->dr_ctx.key[2] = des_ctx->dr_ctx.key[0];
des_ctx->dr_ctx.key[5] =
des_ctx->dr_ctx.key[3] = des_ctx->dr_ctx.key[1];
}
PUTCTX16(des_ctx, CTX_LENGTH, CTX_3DES_LENGTH);
PUTCTX16(des_ctx, CTX_CMD, CMD_3DES);
PUTCTX32(des_ctx, CTX_3DESDIRECTION,
flags & DR_ENCRYPT ? CTX_3DES_ENCRYPT : CTX_3DES_DECRYPT);
PUTCTX32(des_ctx, CTX_3DESKEY1HI, des_ctx->dr_ctx.key[0]);
PUTCTX32(des_ctx, CTX_3DESKEY1LO, des_ctx->dr_ctx.key[1]);
PUTCTX32(des_ctx, CTX_3DESKEY2HI, des_ctx->dr_ctx.key[2]);
PUTCTX32(des_ctx, CTX_3DESKEY2LO, des_ctx->dr_ctx.key[3]);
PUTCTX32(des_ctx, CTX_3DESKEY3HI, des_ctx->dr_ctx.key[4]);
PUTCTX32(des_ctx, CTX_3DESKEY3LO, des_ctx->dr_ctx.key[5]);
return (CRYPTO_SUCCESS);
}