root/usr/src/uts/common/gssapi/mechs/krb5/crypto/enc_provider/aes_provider.c
/*
 * Copyright 2010 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

/*
 * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
 * Note, this file is cstyle and lint clean and should stay that way.
 * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
 */

#include <k5-int.h>
#include <enc_provider.h>

#define BLOCK_SIZE 16

/*
 * AES encrypt using CipherText Stealing mode built on top of CBC mode.  CBC is
 * being used because the Solaris Cryptographic Framework/PKCS11 does not
 * currently support CTS while CBC is supported.  CBC as compared to ECB that
 * was previously used allows crypto providers to do the crypto more
 * efficiently.  In addition there is a crypto card (SCA6000) that did not
 * provide ECB mode so krb was unable to take advantage.  If CTS mode is ever
 * supported by the Solaris Cryptographic Framework then this code should be
 * changed to use that.
 *
 * CTS is based on what is described in Schneier's Applied Cryptography and RFC
 * 3962.
 */

#ifdef _KERNEL
/*ARGSUSED*/
krb5_error_code
krb5int_aes_encrypt(krb5_context context,
        const krb5_keyblock *key, const krb5_data *ivec,
        const krb5_data *input, krb5_data *output)
{
        int ret = 0;
        int nblocks, partialamount;
        crypto_mechanism_t mech;
        /*
         * nlobp = next to last output block pointer, lobp = last output block
         * pointer
         */
        char *nlobp, *lobp;
        char local_iv_data[BLOCK_SIZE];
        krb5_data local_iv;

        KRB5_LOG0(KRB5_INFO, "In krb5int_aes_encrypt(kernel): start");

        ASSERT(input != NULL);
        if (input->length < BLOCK_SIZE)
                return (KRB5_BAD_MSIZE);
        ASSERT(output != NULL);
        ASSERT(input->length == output->length);
        ASSERT(key != NULL);
        ASSERT(key->key_tmpl != NULL);
        ASSERT(key->kef_mt == crypto_mech2id(SUN_CKM_AES_CBC));

        if (ivec != NULL) {
                /*
                 * This function updates ivec->data if the ivec is passed in so
                 * it better have a data pointer and a proper length.
                 */
                if (ivec->data == NULL || ivec->length != BLOCK_SIZE) {
                        ASSERT(ivec->data != NULL);
                        ASSERT(ivec->length == BLOCK_SIZE);
                        KRB5_LOG1(KRB5_ERR, "In krb5int_aes_encrypt: error "
                            "ivec->data = %p ivec->length = %d",
                            (void *)ivec->data, ivec->length);
                        ret = KRB5_CRYPTO_INTERNAL;
                        goto cleanup;
                }
        }

        /* number of input blocks including partial block */
        nblocks = (input->length + BLOCK_SIZE - 1) / BLOCK_SIZE;
        KRB5_LOG(KRB5_INFO, "nblocks = %d", nblocks);
        /* get # of bytes in partially filled block */
        partialamount = input->length % BLOCK_SIZE;
        KRB5_LOG(KRB5_INFO, "partialamount = %d", partialamount);

        if (nblocks == 1 || (partialamount == 0)) {
                /*
                 * Simple case:
                 *
                 * Use CBC for all plaintext blocks, all must be full, then swap
                 * last 2 ciphertext blocks to implement CTS.  Note, CBC needs a
                 * non-NULL IV.
                 */
                if (ivec != NULL) {
                        local_iv.data = ivec->data;
                        local_iv.length = ivec->length;
                } else {
                        bzero(local_iv_data, sizeof (local_iv_data));
                        local_iv.data = local_iv_data;
                        local_iv.length = sizeof (local_iv_data);
                }

                /* Note using TRUE here because encryption is desired */
                ret = k5_ef_crypto((const char *)input->data,
                    (char *)output->data,
                    input->length, (krb5_keyblock *)key,
                    &local_iv, TRUE);

                if (ret != 0) {
                        KRB5_LOG(KRB5_ERR,
                            "k5_ef_crypto: error: ret = 0x%08x", ret);
                        goto cleanup;
                }

                if (nblocks > 1) {
                        /*
                         * swap last 2 ciphertext blocks to implement CTS
                         */
                        char tmp[BLOCK_SIZE];

                        nlobp = (char *)(output->data +
                            ((nblocks - 2) * BLOCK_SIZE));
                        lobp = (char *)(output->data +
                            ((nblocks - 1) * BLOCK_SIZE));

                        bcopy(nlobp, tmp, BLOCK_SIZE);
                        bcopy(lobp, nlobp, BLOCK_SIZE);
                        bcopy(tmp, lobp, BLOCK_SIZE);
                }
        } else {
                /*
                 * Complex case:
                 *
                 * This implements CTS mode where there is > 1 block and the
                 * last block is partially filled Uses CBC mode in the kCF, then
                 * does some swapping.
                 *
                 * pt = plain text, ct = cipher text
                 */
                char tmp_pt[BLOCK_SIZE], tmp_ct[BLOCK_SIZE];
                /* Note the iovec below is NOT the ivec in the crypto sense */
                struct iovec iovarray_pt[2], iovarray_ct[2];
                struct uio uio_pt, uio_ct;
                /* ct = ciphertext, pt = plaintext */
                crypto_data_t ct, pt;

                /* tmp_pt will provide 0 padding for last parital pt block */
                bzero(tmp_pt, sizeof (tmp_pt));

                /*
                 * Setup the uio/iovecs so only one call to crypto_encrypt() is
                 * made.  Plaintext first.
                 */
                pt.cd_format = CRYPTO_DATA_UIO;
                pt.cd_offset = 0;
                pt.cd_length = nblocks * BLOCK_SIZE;
                pt.cd_miscdata = NULL;
                bzero(&uio_pt, sizeof (uio_pt));
                pt.cd_uio = &uio_pt;
                pt.cd_uio->uio_iov = iovarray_pt;
                pt.cd_uio->uio_iovcnt = 2;
                pt.cd_uio->uio_segflg = UIO_SYSSPACE;

                /*
                 * first iovec has all full blocks of pt.
                 */
                pt.cd_uio->uio_iov[0].iov_base = (char *)input->data;
                /* use full block input */
                pt.cd_uio->uio_iov[0].iov_len = input->length - partialamount;

                KRB5_LOG(KRB5_INFO, "pt0 iov_len = %d",
                    (int)pt.cd_uio->uio_iov[0].iov_len);

                /*
                 * second iovec has the parital pt and 0 padding
                 */
                pt.cd_uio->uio_iov[1].iov_base = tmp_pt;
                /*
                 * since the first iovec includes the last partial pt,
                 * set length to enough bytes to pad out to a full block
                 */
                bcopy(input->data + (input->length - partialamount), tmp_pt,
                    partialamount);
                pt.cd_uio->uio_iov[1].iov_len = BLOCK_SIZE;

                /* setup ciphertext iovecs */
                ct.cd_format = CRYPTO_DATA_UIO;
                ct.cd_offset = 0;
                ct.cd_length = nblocks * BLOCK_SIZE;
                ct.cd_miscdata = NULL;
                bzero(&uio_ct, sizeof (uio_ct));
                ct.cd_uio = &uio_ct;
                ct.cd_uio->uio_iov = iovarray_ct;
                ct.cd_uio->uio_iovcnt = 2;
                ct.cd_uio->uio_segflg = UIO_SYSSPACE;

                /*
                 * First iovec has almost all the ct but not the ct for the last
                 * partial pt with the padding.  That will be stored in the
                 * secont ct iovec.
                 */
                ct.cd_uio->uio_iov[0].iov_base = (char *)output->data;
                ct.cd_uio->uio_iov[0].iov_len = output->length - partialamount;
                KRB5_LOG(KRB5_INFO, "ct0 iov_len = %d",
                    (int)ct.cd_uio->uio_iov[0].iov_len);
                /*
                 * Second iovec has the last ciphertext block
                 */
                ct.cd_uio->uio_iov[1].iov_base = tmp_ct;
                ct.cd_uio->uio_iov[1].iov_len = BLOCK_SIZE;

                /* This had better be AES CBC mode! */
                mech.cm_type = key->kef_mt;

                if (ivec == NULL) {
                        bzero(local_iv_data, sizeof (local_iv_data));
                        mech.cm_param = local_iv_data;
                        mech.cm_param_len = sizeof (local_iv_data);
                } else {
                        mech.cm_param = ivec->data;
                        mech.cm_param_len = ivec->length;
                }

                /* encrypt using AES CBC */
                ret = crypto_encrypt(&mech, &pt, (crypto_key_t *)&key->kef_key,
                    key->key_tmpl, &ct, NULL);

                if (ret != CRYPTO_SUCCESS) {
                        KRB5_LOG(KRB5_ERR,
                            "crypto_encrypt: error: ret = 0x%08x",
                            ret);
                        goto cleanup;
                }

                /*
                 * Swap:
                 * copy the next to last ct to last partial output block (only
                 * the partial amount is copied).
                 */
                nlobp = (char *)(output->data + ((nblocks - 2) * BLOCK_SIZE));
                lobp = (char *)(output->data + ((nblocks - 1) * BLOCK_SIZE));

                bcopy(nlobp, lobp, partialamount);
                /*
                 * copy the last ct output block to next to last output block
                 */
                bcopy(tmp_ct, nlobp, BLOCK_SIZE);

        } /* end partial block processing */

        /*
         * The ivec is updated to allow the caller to chain ivecs.  At this
         * point I don't think any kernel callers are using this however the
         * userland version of this function does it so this should be done in
         * kernel for consistency's sake.  This is not done for 1 block, got
         * this from MIT.  Note, the next to last output block is copied because
         * it contains the last full block of cipher text.
         */
        if (nblocks > 1 && ivec)
                (void) memcpy(ivec->data, nlobp, BLOCK_SIZE);

cleanup:
        if (ret)
                bzero(output->data, output->length);
        return (ret);
}

#else /* User Space */

/*ARGSUSED*/
krb5_error_code
krb5int_aes_encrypt(krb5_context context,
        const krb5_keyblock *key, const krb5_data *ivec,
        const krb5_data *input, krb5_data *output)
{
        krb5_error_code ret = 0;
        int nblocks, partialamount;
        CK_RV rv;
        KRB5_MECH_TO_PKCS algos;
        CK_MECHANISM mechanism;
        CK_ULONG outlen;
        /*
         * nlobp = next to last output block pointer, lobp = last output block
         * pointer
         */
        char *nlobp, *lobp;
        char tmp_ivec[BLOCK_SIZE];

        assert(input != NULL);
        if (input->length < BLOCK_SIZE)
                return (KRB5_BAD_MSIZE);
        assert(output != NULL);
        assert(input->length == output->length);
        assert(key != NULL);

        if (ivec != NULL) {
                /*
                 * This function updates ivec->data if the ivec is passed in so
                 * it better have a data pointer and a proper length.
                 */
                if (ivec->data == NULL || ivec->length != BLOCK_SIZE) {
                        assert(ivec->data != NULL);
                        assert(ivec->length == BLOCK_SIZE);
                        KRB5_LOG1(KRB5_ERR, "In krb5int_aes_encrypt: error "
                            "ivec->data = %p ivec->length = %d", ivec->data,
                            ivec->length);
                        ret = KRB5_CRYPTO_INTERNAL;
                        goto cleanup;
                }
        }

        /* number of input blocks including partial block */
        nblocks = (input->length + BLOCK_SIZE - 1) / BLOCK_SIZE;
        KRB5_LOG(KRB5_INFO, "nblocks = %d", nblocks);
        /* get # of bytes in partially filled block */
        partialamount = input->length % BLOCK_SIZE;
        KRB5_LOG(KRB5_INFO, "partialamount = %d", partialamount);

        rv = get_algo(key->enctype, &algos);
        if (rv != CKR_OK)
                goto cleanup;
        assert(algos.enc_algo == CKM_AES_CBC);

        rv = init_key_uef(krb_ctx_hSession(context), (krb5_keyblock *)key);
        if (rv != CKR_OK)
                goto cleanup;

        mechanism.mechanism = algos.enc_algo;

        if (ivec == NULL) {
                bzero(tmp_ivec, sizeof (tmp_ivec));
                mechanism.pParameter = tmp_ivec;
                mechanism.ulParameterLen = sizeof (tmp_ivec);
        } else {
                mechanism.pParameter = ivec->data;
                mechanism.ulParameterLen = ivec->length;
        }
        /*
         * Note, since CBC is assumed to be the underlying mode, this
         * call to C_EncryptInit is setting the IV.  The IV in use here
         * is either the ivec passed in or a block of 0's.
         */
        rv = C_EncryptInit(krb_ctx_hSession(context), &mechanism, key->hKey);

        if (rv != CKR_OK) {
                KRB5_LOG(KRB5_ERR, "C_EncryptInit failed in "
                    "krb5int_aes_encrypt: rv = 0x%x", rv);
                goto cleanup;
        }

        if (nblocks == 1 || (partialamount == 0)) {
                /*
                 * Simple case:
                 *
                 * Use CBC for all plaintext blocks, all must be full, then swap
                 * last 2 ciphertext blocks to implement CTS.
                 */

                /*
                 * C_Encrypt/Decrypt requires a pointer to long, not a pointer
                 * to int cast to pointer to long!!!
                 */
                outlen = output->length;

                rv = C_Encrypt(krb_ctx_hSession(context),
                    (CK_BYTE_PTR)input->data,
                    input->length,
                    (CK_BYTE_PTR)output->data,
                    &outlen);

                if (rv != CKR_OK) {
                        KRB5_LOG(KRB5_ERR, "C_Encrypt failed in "
                            "krb5int_aes_encrypt: rv = 0x%x", rv);
                        goto cleanup;
                }

                assert(output->length == (unsigned int)outlen);

                if (nblocks > 1) {
                        /*
                         * swap last 2 ciphertext blocks to implement CTS
                         */
                        char tmp[BLOCK_SIZE];

                        nlobp = (char *)(output->data +
                            ((nblocks - 2) * BLOCK_SIZE));
                        lobp = (char *)(output->data +
                            ((nblocks - 1) * BLOCK_SIZE));

                        bcopy(nlobp, tmp, BLOCK_SIZE);
                        bcopy(lobp, nlobp, BLOCK_SIZE);
                        bcopy(tmp, lobp, BLOCK_SIZE);
                }
        } else {
                /*
                 * Complex case:
                 *
                 * This implements CTS mode where there is > 1 block and the
                 * last block is partially filled. Uses CBC mode in uCF/PKCS11,
                 * then does some swapping.
                 *
                 * pt = plain text, ct = cipher text
                 */
                char tmp_pt[BLOCK_SIZE], tmp_ct[BLOCK_SIZE];

                /*
                 * encrypt from P0...Pn-1 using CBC, last block of output is Cn
                 * & C'
                 */
                outlen = input->length - partialamount;

                rv = C_EncryptUpdate(krb_ctx_hSession(context),
                    (CK_BYTE_PTR)input->data,
                    input->length - partialamount,
                    (CK_BYTE_PTR)output->data,
                    &outlen);

                if (rv != CKR_OK) {
                        KRB5_LOG(KRB5_ERR, "C_EncryptUpdate failed in "
                            "krb5int_aes_encrypt: rv = 0x%x", rv);
                        goto cleanup;
                }

                /* tmp_pt will provide 0 padding for last parital pt block */
                bzero(tmp_pt, sizeof (tmp_pt));
                /* copy Pn to tmp_pt which has 0 padding */
                bcopy(input->data + (input->length - partialamount), tmp_pt,
                    partialamount);

                /* encrypt Pn with 0 padding, Cn & C' ivec, output is Cn-1 */
                outlen = sizeof (tmp_ct);

                rv = C_EncryptUpdate(krb_ctx_hSession(context),
                    (CK_BYTE_PTR)tmp_pt,
                    BLOCK_SIZE,
                    (CK_BYTE_PTR)tmp_ct,
                    &outlen);

                if (rv != CKR_OK) {
                        KRB5_LOG(KRB5_ERR, "C_Encrypt failed in "
                            "krb5int_aes_encrypt: rv = 0x%x", rv);
                        goto cleanup;
                }

                nlobp = (char *)(output->data + ((nblocks - 2) * BLOCK_SIZE));
                lobp = (char *)(output->data + ((nblocks - 1) * BLOCK_SIZE));

                /* copy Cn from next to last output block to last block */
                bcopy(nlobp, lobp, partialamount);
                /* copy Cn-1 from tmp_ct to next to last output block */
                bcopy(tmp_ct, nlobp, BLOCK_SIZE);

                /* Close the crypto session, ignore the output */
                rv = C_EncryptFinal(krb_ctx_hSession(context),
                    (CK_BYTE_PTR)tmp_ct, &outlen);

                if (rv != CKR_OK)
                        goto cleanup;
        }
        /*
         * The ivec is updated to allow the caller to chain ivecs, done for the
         * kcmd (rsh/rcp/etc...).  Note this is not done for 1 block although I
         * am not sure why but I'm continuing the tradition from the MIT code.
         * Note, the next to last output block is copied because it contains the
         * last full block of cipher text.
         */
        if (nblocks > 1 && ivec)
                (void) memcpy(ivec->data, nlobp, BLOCK_SIZE);

cleanup:
        if (rv != CKR_OK)
                ret = PKCS_ERR;

        if (ret)
                bzero(output->data, input->length);

        return (ret);
}
#endif /* _KERNEL */

/*
 * AES Decrypt using CipherText Stealing mode built on top of CBC mode.  See the
 * krb5int_aes_encrypt() comments for the reason CBC is being used.
 */

#ifdef _KERNEL
/*ARGSUSED*/
krb5_error_code
krb5int_aes_decrypt(krb5_context context,
        const krb5_keyblock *key, const krb5_data *ivec,
        const krb5_data *input, krb5_data *output)
{
        krb5_error_code ret = 0;
        int nblocks, partialamount;
        char local_iv_data[BLOCK_SIZE];
        krb5_data local_iv;

        KRB5_LOG0(KRB5_INFO, "In krb5int_aes_decrypt: start");

        ASSERT(input != NULL);
        if (input->length < BLOCK_SIZE)
                return (KRB5_BAD_MSIZE);
        ASSERT(output != NULL);
        ASSERT(input->length == output->length);
        ASSERT(key != NULL);
        ASSERT(key->kef_mt == crypto_mech2id(SUN_CKM_AES_CBC));

        if (ivec != NULL) {
                /*
                 * This function updates ivec->data if the ivec is passed in so
                 * it better have a data pointer and a proper length.
                 */
                if (ivec->data == NULL || ivec->length != BLOCK_SIZE) {
                        ASSERT(ivec->data != NULL);
                        ASSERT(ivec->length == BLOCK_SIZE);
                        KRB5_LOG1(KRB5_ERR, "In krb5int_aes_decrypt: error "
                            "ivec->data = %p ivec->length = %d",
                            (void *)ivec->data, ivec->length);
                        ret = KRB5_CRYPTO_INTERNAL;
                        goto cleanup;
                }
        }

        /* number of input blocks including partial block */
        nblocks = (input->length + BLOCK_SIZE - 1) / BLOCK_SIZE;
        KRB5_LOG(KRB5_INFO, "nblocks = %d", nblocks);
        /* get # of bytes in partially filled block */
        partialamount = input->length % BLOCK_SIZE;
        KRB5_LOG(KRB5_INFO, "partialamount = %d", partialamount);

        if (ivec != NULL) {
                local_iv.data = ivec->data;
                local_iv.length = ivec->length;
        } else {
                bzero(local_iv_data, sizeof (local_iv_data));
                local_iv.data = local_iv_data;
                local_iv.length = sizeof (local_iv_data);
        }

        if (nblocks == 1 || (partialamount == 0)) {
                char orig_input[BLOCK_SIZE * 2];
                /*
                 * nlibp = next to last input block pointer
                 * libp = last input block pointer
                 */
                char *nlibp, *libp;

                /*
                 * Simple case:
                 *
                 * Swap last 2 ciphertext blocks (all must be full), then use
                 * CBC to implement CTS.
                 */

                if (nblocks > 1) {
                        /*
                         * swap last 2 ciphertext blocks to implement CTS
                         */
                        char tmp[BLOCK_SIZE];

                        nlibp = input->data + ((nblocks - 2) * BLOCK_SIZE);
                        libp = input->data + ((nblocks - 1) * BLOCK_SIZE);

                        /* first save orig input data for later restore */
                        /* we know that partial amount is 0, because */
                        /* nblocks is > 1, so we copy the last two blocks */
                        bcopy(nlibp, orig_input, sizeof (orig_input));

                        /* swap */
                        bcopy(nlibp, tmp, BLOCK_SIZE);
                        bcopy(libp, nlibp, BLOCK_SIZE);
                        bcopy(tmp, libp, BLOCK_SIZE);
                }

                ret = k5_ef_crypto((const char *)input->data,
                    (char *)output->data,
                    input->length, (krb5_keyblock *)key,
                    &local_iv, FALSE);

                if (nblocks > 1) {
                        /* restore orig input data */
                        bcopy(orig_input, nlibp, sizeof (orig_input));
                }

                if (ret != 0) {
                        KRB5_LOG(KRB5_ERR,
                            "k5_ef_crypto returned error: ret = 0x%08x",
                            ret);
                        goto cleanup;
                }

        } else {
                krb5_data tmp_ivec;
                char tmp_ivec_data[BLOCK_SIZE], tmp_input_data[BLOCK_SIZE],
                    tmp_output_data[BLOCK_SIZE];
                /* pointers to Cn, Cn-1, Cn-2 CipherText */
                char *Cn, *Cn_1, *Cn_2;
                long length;

                /*
                 * Complex case:
                 *
                 * Decrypting in CTS where there is a partial block of
                 * ciphertext.
                 */

                /* setting pointers to CipherText for later use */
                Cn = input->data + (input->length - partialamount);
                /* Cn - 1 */
                Cn_1 = Cn - BLOCK_SIZE;
                /* Cn - 2 */
                Cn_2 = Cn_1 - BLOCK_SIZE;

                if (nblocks > 2) {
                        /* set length to include blocks C0 thru Cn-2 */
                        length = input->length - (BLOCK_SIZE + partialamount);

                        /*
                         * First decrypt C0 thru Cn-2 using CBC with the input
                         * ivec.
                         */
                        ret = k5_ef_crypto((const char *)input->data,
                            output->data, length, (krb5_keyblock *)key,
                            &local_iv, FALSE);

                        if (ret != 0) {
                                KRB5_LOG(KRB5_ERR,
                                    "k5_ef_crypto: error: ret = 0x%08x",
                                    ret);
                                goto cleanup;
                        }
                }
                /*
                 * Prepare to decrypt Cn-1 using a ivec of Cn with 0 padding.
                 */
                bzero(tmp_ivec_data, sizeof (tmp_ivec_data));
                /* the tmp ivec data holds Cn with 0 padding */
                bcopy(Cn, tmp_ivec_data, partialamount);
                tmp_ivec.data = tmp_ivec_data;
                tmp_ivec.length = sizeof (tmp_ivec_data);

                /* decrypt 1 block */
                length = BLOCK_SIZE;

                /*
                 * Now decrypt using Cn-1 input, Cn + 0 padding for ivec, Pn &
                 * C' output
                 */
                ret = k5_ef_crypto((const char *)Cn_1,
                    tmp_output_data, length,
                    (krb5_keyblock *)key, &tmp_ivec, FALSE);

                if (ret != 0) {
                        KRB5_LOG(KRB5_ERR,
                            "k5_ef_crypto: error: ret = 0x%08x",
                            ret);
                        goto cleanup;
                }
                /*
                 * tmp input data should hold Cn with C'
                 * Note, tmp_output_data contains Pn + C',
                 */
                /* copy Cn */
                bcopy(Cn, tmp_input_data, partialamount);
                /* copy C' */
                bcopy(tmp_output_data + partialamount,
                    tmp_input_data + partialamount,
                    (BLOCK_SIZE - partialamount));

                /* copy Pn in tmp output to output->data */
                bcopy(tmp_output_data,
                    output->data + (input->length - partialamount),
                    partialamount);

                if (nblocks > 2) {
                        /* use Cn-2 as ivec */
                        tmp_ivec.data = Cn_2;
                } else {
                        /* use 0 as ivec because Cn-2 does not exist */
                        bzero(tmp_ivec_data, sizeof (tmp_ivec_data));
                }

                /*
                 * Now decrypt Cn + C' input, using either Cn-2 or 0 for ivec
                 * (set above), Pn-1 output.
                 */
                ret = k5_ef_crypto((const char *)tmp_input_data,
                    (char *)output->data +
                    (input->length - (BLOCK_SIZE + partialamount)),
                    length, (krb5_keyblock *)key,
                    &tmp_ivec, FALSE);

                if (ret != 0) {
                        KRB5_LOG(KRB5_ERR,
                            "k5_ef_crypto: error: ret = 0x%08x", ret);
                        goto cleanup;
                }

        } /* end partial block processing */
        /*
         * The ivec is updated to allow the caller to chain ivecs.  At this
         * point I don't think any kernel callers are using this however the
         * userland version of this function does it so this should be done in
         * kernel for consistency's sake.  This is not done for 1 block, got
         * this from MIT.
         */
        if (nblocks > 1 && ivec) {
                (void) memcpy(ivec->data,
                    input->data + ((nblocks - 2) * BLOCK_SIZE),
                    BLOCK_SIZE);
        }

cleanup:
        if (ret)
                bzero(output->data, output->length);

        return (ret);
}

#else /* User Space */

/*ARGSUSED*/
krb5_error_code
krb5int_aes_decrypt(krb5_context context,
        const krb5_keyblock *key, const krb5_data *ivec,
        const krb5_data *input, krb5_data *output)
{
        krb5_error_code ret = 0;
        int nblocks, partialamount;
        CK_RV rv;
        KRB5_MECH_TO_PKCS algos;
        CK_MECHANISM mechanism;
        CK_ULONG outlen;
        char tmp_ivec[BLOCK_SIZE];

        assert(input != NULL);
        if (input->length < BLOCK_SIZE)
                return (KRB5_BAD_MSIZE);
        assert(output != NULL);
        assert(input->length == output->length);
        assert(key != NULL);

        if (ivec != NULL) {
                /*
                 * This function updates ivec->data if the ivec is passed in so
                 * it better have a data pointer and a proper length.
                 */
                if (ivec->data == NULL || ivec->length != BLOCK_SIZE) {
                        assert(ivec->data != NULL);
                        assert(ivec->length == BLOCK_SIZE);
                        KRB5_LOG1(KRB5_ERR, "In krb5int_aes_decrypt: error "
                            "ivec->data = %p ivec->length = %d", ivec->data,
                            ivec->length);
                        ret = KRB5_CRYPTO_INTERNAL;
                        goto cleanup;
                }
        }

        /* number of input blocks including partial block */
        nblocks = (input->length + BLOCK_SIZE - 1) / BLOCK_SIZE;
        KRB5_LOG(KRB5_INFO, "nblocks = %d", nblocks);
        /* get # of bytes in partially filled block */
        partialamount = input->length % BLOCK_SIZE;
        KRB5_LOG(KRB5_INFO, "partialamount = %d", partialamount);

        rv = get_algo(key->enctype, &algos);
        if (rv != CKR_OK)
                goto cleanup;
        assert(algos.enc_algo == CKM_AES_CBC);

        rv = init_key_uef(krb_ctx_hSession(context), (krb5_keyblock *)key);
        if (rv != CKR_OK) {
                goto cleanup;
        }

        mechanism.mechanism = algos.enc_algo;
        if (ivec == NULL) {
                bzero(tmp_ivec, sizeof (tmp_ivec));
                mechanism.pParameter = tmp_ivec;
                mechanism.ulParameterLen = sizeof (tmp_ivec);
        } else {
                mechanism.pParameter = ivec->data;
                mechanism.ulParameterLen = ivec->length;
        }

        if (nblocks == 1 || (partialamount == 0)) {
                char orig_input[BLOCK_SIZE * 2];
                /*
                 * nlibp = next to last input block pointer
                 * libp = last input block pointer
                 */
                char *nlibp, *libp;

                /*
                 * Simple case:
                 *
                 * Swap last 2 ciphertext blocks (all must be full), then use
                 * CBC to implement CTS.
                 */
                if (nblocks > 1) {
                        /*
                         * swap last 2 ciphertext blocks to implement CTS
                         */
                        char tmp[BLOCK_SIZE];

                        /*
                         * Note, the side effect with this is that we are
                         * modifying the input->data!
                         */
                        nlibp = input->data + ((nblocks - 2) * BLOCK_SIZE);
                        libp = input->data + ((nblocks - 1) * BLOCK_SIZE);

                        /* first save orig input data for later restore */
                        /* we know that partial amount is 0, because */
                        /* nblocks is > 1, so we copy the last two blocks */
                        bcopy(nlibp, orig_input, sizeof (orig_input));

                        bcopy(nlibp, tmp, BLOCK_SIZE);
                        bcopy(libp, nlibp, BLOCK_SIZE);
                        bcopy(tmp, libp, BLOCK_SIZE);
                }

                /*
                 * Note, since CBC is assumed to be the underlying mode, this
                 * call to C_DecryptInit is setting the IV.  The IV in use here
                 * is either the ivec passed in or a block of 0's.  All calls to
                 * C_DecryptInit set the IV in this function.
                 */
                rv = C_DecryptInit(krb_ctx_hSession(context), &mechanism,
                    key->hKey);
                if (rv != CKR_OK) {
                        KRB5_LOG(KRB5_ERR, "C_DecryptInit failed in "
                            "krb5int_aes_decrypt: rv = 0x%x", rv);
                        goto cleanup;
                }

                /*
                 * C_Encrypt/Decrypt requires a pointer to long, not a pointer
                 * to int cast to pointer to long!!!
                 */
                outlen = output->length;

                rv = C_Decrypt(krb_ctx_hSession(context),
                    (CK_BYTE_PTR)input->data,
                    input->length,
                    (CK_BYTE_PTR)output->data,
                    &outlen);

                if (nblocks > 1) {
                        /* restore orig input data */
                        bcopy(orig_input, nlibp, sizeof (orig_input));
                }
        } else {
                char tmp_ivec_data[BLOCK_SIZE], tmp_input_data[BLOCK_SIZE],
                    tmp_output_data[BLOCK_SIZE];
                /* pointers to Cn, Cn-1, Cn-2 CipherText */
                char *Cn, *Cn_1, *Cn_2;
                CK_ULONG length;

                /*
                 * Complex case:
                 *
                 * Decrypting in CTS where there is a partial block of
                 * ciphertext.
                 */

                /* setting pointers to CipherText for later use */
                Cn = input->data + (input->length - partialamount);
                /* Cn - 1 */
                Cn_1 = Cn - BLOCK_SIZE;
                /* Cn - 2 */
                Cn_2 = Cn_1 - BLOCK_SIZE;

                if (nblocks > 2) {
                        rv = C_DecryptInit(krb_ctx_hSession(context),
                            &mechanism, key->hKey);
                        if (rv != CKR_OK) {
                                KRB5_LOG(KRB5_ERR, "C_DecryptInit failed in "
                                    "krb5int_aes_decrypt: rv = 0x%x", rv);
                                goto cleanup;
                        }
                        /* set length to include blocks C0 thru Cn-2 */
                        length = input->length - (BLOCK_SIZE + partialamount);
                        outlen = length;
                        /*
                         * First decrypt C0 thru Cn-2 using CBC with the input
                         * ivec.
                         */
                        rv = C_Decrypt(krb_ctx_hSession(context),
                            (CK_BYTE_PTR)input->data,
                            length,
                            (CK_BYTE_PTR)output->data,
                            &outlen);
                        if (rv != CKR_OK)
                                goto cleanup;
                }

                /*
                 * Prepare to decrypt Cn-1 using a ivec of Cn with 0 padding.
                 */
                bzero(tmp_ivec_data, sizeof (tmp_ivec_data));
                /* the tmp ivec data holds Cn with 0 padding */
                bcopy(Cn, tmp_ivec_data, partialamount);

                /* decrypt 1 block */
                length = BLOCK_SIZE;
                outlen = length;

                /* set ivec to Cn with 0 padding */
                mechanism.pParameter = tmp_ivec_data;
                mechanism.ulParameterLen = sizeof (tmp_ivec_data);

                rv = C_DecryptInit(krb_ctx_hSession(context), &mechanism,
                    key->hKey);
                if (rv != CKR_OK) {
                        KRB5_LOG(KRB5_ERR, "C_DecryptInit failed in "
                            "krb5int_aes_decrypt: rv = 0x%x", rv);
                        goto cleanup;
                }

                /*
                 * Now decrypt using Cn-1 input, Cn + 0 padding for ivec, Pn &
                 * C' output
                 */
                rv = C_Decrypt(krb_ctx_hSession(context),
                    (CK_BYTE_PTR)Cn_1,
                    length,
                    (CK_BYTE_PTR)tmp_output_data,
                    &outlen);

                if (rv != CKR_OK)
                        goto cleanup;

                /*
                 * tmp input data should hold Cn with C'
                 * Note, tmp_output_data contains Pn + C',
                 */
                /* copy Cn */
                bcopy(Cn, tmp_input_data, partialamount);
                /* copy C' */
                bcopy(tmp_output_data + partialamount,
                    tmp_input_data + partialamount,
                    (BLOCK_SIZE - partialamount));

                /* copy Pn in tmp output to output->data last block */
                bcopy(tmp_output_data,
                    output->data + (input->length - partialamount),
                    partialamount);

                if (nblocks > 2) {
                        /* use Cn-2 as ivec */
                        mechanism.pParameter = Cn_2;
                } else {
                        /*
                         * nblocks == 2
                         *
                         * Cn-2 does not exist so either use 0 if input ivec
                         * does not exist or use the input ivec.
                         */
                        if (ivec == NULL) {
                                bzero(tmp_ivec_data, sizeof (tmp_ivec_data));
                        } else {
                                /* use original input ivec */
                                mechanism.pParameter = ivec->data;
                                mechanism.ulParameterLen = ivec->length;
                        }
                }

                rv = C_DecryptInit(krb_ctx_hSession(context), &mechanism,
                    key->hKey);
                if (rv != CKR_OK) {
                        KRB5_LOG(KRB5_ERR, "C_DecryptInit failed in "
                            "krb5int_aes_decrypt: rv = 0x%x", rv);
                        goto cleanup;
                }

                /*
                 * Now decrypt Cn + C' input, using either Cn-2, original input
                 * ivec or 0 for ivec (set above), Pn-1 output.
                 */
                rv = C_Decrypt(krb_ctx_hSession(context),
                    (CK_BYTE_PTR)tmp_input_data,
                    length,
                    (CK_BYTE_PTR)output->data + (input->length -
                    (BLOCK_SIZE + partialamount)),
                    &outlen);
                if (rv != CKR_OK)
                        goto cleanup;
        } /* end partial block processing */

        /*
         * The ivec is updated to allow the caller to chain ivecs, done for the
         * kcmd (rsh/rcp/etc...).  Note this is not done for 1 block although I
         * am not sure why but I'm continuing the tradition from the MIT code.
         */
        if (nblocks > 1 && ivec) {
                (void) memcpy(ivec->data,
                    input->data + ((nblocks - 2) * BLOCK_SIZE),
                    BLOCK_SIZE);
        }

cleanup:
        if (rv != CKR_OK)
                ret = PKCS_ERR;

        if (ret)
                bzero(output->data, input->length);

        return (ret);
}

#endif /* _KERNEL */

static krb5_error_code
k5_aes_make_key(krb5_context context,
        const krb5_data *randombits, krb5_keyblock *key)
{
        krb5_error_code ret = 0;
        if (key->length != 16 && key->length != 32)
                return (KRB5_BAD_KEYSIZE);
        if (randombits->length != key->length)
                return (KRB5_CRYPTO_INTERNAL);

        key->magic = KV5M_KEYBLOCK;
        key->dk_list = NULL;

#ifdef _KERNEL
        key->kef_key.ck_data = NULL;
        key->key_tmpl = NULL;
        (void) memcpy(key->contents, randombits->data, randombits->length);
        ret = init_key_kef(context->kef_cipher_mt, key);
#else
        key->hKey = CK_INVALID_HANDLE;
        (void) memcpy(key->contents, randombits->data, randombits->length);
        ret = init_key_uef(krb_ctx_hSession(context), key);
#endif /* _KERNEL */

        KRB5_LOG0(KRB5_INFO, "k5_aes_make_key() end\n");
        return (ret);
}

/*ARGSUSED*/
static krb5_error_code
krb5int_aes_init_state(krb5_context context, const krb5_keyblock *key,
        krb5_keyusage usage, krb5_data *state)
{
        if (!state)
                return (0);

        if (state && state->data)
                FREE(state->data, state->length);

        state->length = BLOCK_SIZE;
        state->data = (void *) MALLOC(BLOCK_SIZE);

        if (state->data == NULL)
                return (ENOMEM);

        (void) memset(state->data, 0, state->length);
        return (0);
}

const struct krb5_enc_provider krb5int_enc_aes128 = {
    BLOCK_SIZE,
    16, 16,
    krb5int_aes_encrypt,
    krb5int_aes_decrypt,
    k5_aes_make_key,
    krb5int_aes_init_state,
    krb5int_default_free_state
};

const struct krb5_enc_provider krb5int_enc_aes256 = {
    BLOCK_SIZE,
    32, 32,
    krb5int_aes_encrypt,
    krb5int_aes_decrypt,
    k5_aes_make_key,
    krb5int_aes_init_state,
    krb5int_default_free_state
};