root/usr/src/lib/pkcs11/pkcs11_softtoken/common/softDESCrypt.c
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */

/*
 * Copyright (c) 2003, 2010, Oracle and/or its affiliates. All rights reserved.
 * Copyright (c) 2018, Joyent, Inc.
 */

#include <pthread.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <sys/types.h>
#include <security/cryptoki.h>
#include <modes/modes.h>
#include <des_impl.h>
#include "softSession.h"
#include "softObject.h"
#include "softCrypt.h"
#include "softOps.h"

/*
 * Allocate context for the active encryption or decryption operation, and
 * generate DES or DES3 key schedule to speed up the operation.
 */
CK_RV
soft_des_crypt_init_common(soft_session_t *session_p,
    CK_MECHANISM_PTR pMechanism, soft_object_t *key_p,
    boolean_t encrypt)
{

        size_t size;
        soft_des_ctx_t *soft_des_ctx;

        soft_des_ctx = calloc(1, sizeof (soft_des_ctx_t));
        if (soft_des_ctx == NULL) {
                return (CKR_HOST_MEMORY);
        }

        /* Allocate key schedule for DES or DES3 based on key type. */
        if (key_p->key_type == CKK_DES)
                soft_des_ctx->key_sched = des_alloc_keysched(&size, DES, 0);
        else
                soft_des_ctx->key_sched = des_alloc_keysched(&size, DES3, 0);

        if (soft_des_ctx->key_sched == NULL) {
                free(soft_des_ctx);
                return (CKR_HOST_MEMORY);
        }

        soft_des_ctx->keysched_len = size;
        soft_des_ctx->key_type = key_p->key_type;

        (void) pthread_mutex_lock(&session_p->session_mutex);
        if (encrypt) {
                /* Called by C_EncryptInit. */
                session_p->encrypt.context = soft_des_ctx;
                session_p->encrypt.mech.mechanism = pMechanism->mechanism;
        } else {
                /* Called by C_DecryptInit. */
                session_p->decrypt.context = soft_des_ctx;
                session_p->decrypt.mech.mechanism = pMechanism->mechanism;
        }
        (void) pthread_mutex_unlock(&session_p->session_mutex);

        /*
         * If this is a non-sensitive key and it does NOT have
         * a key schedule yet, then allocate one and expand it.
         * Otherwise, if its a non-sensitive key, and it DOES have
         * a key schedule already attached to it, just copy the
         * pre-expanded schedule to the context and avoid the
         * extra key schedule expansion operation.
         */
        if (!(key_p->bool_attr_mask & SENSITIVE_BOOL_ON)) {
                if (OBJ_KEY_SCHED(key_p) == NULL) {
                        void *ks;
                        (void) pthread_mutex_lock(&key_p->object_mutex);
                        if (OBJ_KEY_SCHED(key_p) == NULL) {
                                if (key_p->key_type == CKK_DES)
                                        ks = des_alloc_keysched(&size, DES, 0);
                                else
                                        ks = des_alloc_keysched(&size, DES3, 0);
                                if (ks == NULL) {
                                        (void) pthread_mutex_unlock(
                                            &key_p->object_mutex);
                                        free(soft_des_ctx);
                                        return (CKR_HOST_MEMORY);
                                }
                                /* Initialize key schedule for DES or DES3. */
                                if (key_p->key_type == CKK_DES)
                                        des_init_keysched(
                                            OBJ_SEC(key_p)->sk_value, DES, ks);
                                else if (key_p->key_type == CKK_DES2)
                                        /*
                                         * DES3 encryption/decryption needs to
                                         * support a DES2 key.
                                         */
                                        des_init_keysched(
                                            OBJ_SEC(key_p)->sk_value, DES2, ks);
                                else
                                        des_init_keysched(
                                            OBJ_SEC(key_p)->sk_value, DES3, ks);

                                OBJ_KEY_SCHED_LEN(key_p) = size;
                                OBJ_KEY_SCHED(key_p) = ks;
                        }
                        (void) pthread_mutex_unlock(&key_p->object_mutex);
                }

                /* Copy the pre-expanded key schedule from the key object */
                (void) memcpy(soft_des_ctx->key_sched, OBJ_KEY_SCHED(key_p),
                    OBJ_KEY_SCHED_LEN(key_p));
                soft_des_ctx->keysched_len = OBJ_KEY_SCHED_LEN(key_p);
        } else {
                /* for sensitive keys, we cannot cache the key schedule */
                if (key_p->key_type == CKK_DES)
                        des_init_keysched(OBJ_SEC(key_p)->sk_value,
                            DES, soft_des_ctx->key_sched);
                else if (key_p->key_type == CKK_DES2)
                        /*
                         * DES3 encryption/decryption needs to
                         * support a DES2 key.
                         */
                        des_init_keysched(OBJ_SEC(key_p)->sk_value,
                            DES2, soft_des_ctx->key_sched);
                else
                        des_init_keysched(OBJ_SEC(key_p)->sk_value,
                            DES3, soft_des_ctx->key_sched);
        }

        return (CKR_OK);
}


/*
 * soft_des_encrypt_common()
 *
 * Arguments:
 *      session_p:      pointer to soft_session_t struct
 *      pData:          pointer to the input data to be encrypted
 *      ulDataLen:      length of the input data
 *      pEncrypted:     pointer to the output data after encryption
 *      pulEncryptedLen: pointer to the length of the output data
 *      update:         boolean flag indicates caller is soft_encrypt
 *                      or soft_encrypt_update
 *
 * Description:
 *      This function calls the corresponding encrypt routine based
 *      on the mechanism.
 *
 * Returns:
 *      CKR_OK: success
 *      CKR_BUFFER_TOO_SMALL: the output buffer provided by application
 *                            is too small
 *      CKR_FUNCTION_FAILED: encrypt function failed
 *      CKR_DATA_LEN_RANGE: the input data is not a multiple of blocksize
 */
CK_RV
soft_des_encrypt_common(soft_session_t *session_p, CK_BYTE_PTR pData,
    CK_ULONG ulDataLen, CK_BYTE_PTR pEncrypted,
    CK_ULONG_PTR pulEncryptedLen, boolean_t update)
{
        int rc = 0;
        CK_RV rv = CKR_OK;
        soft_des_ctx_t *soft_des_ctx =
            (soft_des_ctx_t *)session_p->encrypt.context;
        des_ctx_t *des_ctx;
        CK_MECHANISM_TYPE mechanism = session_p->encrypt.mech.mechanism;
        CK_BYTE *in_buf = NULL;
        CK_BYTE *out_buf = NULL;
        CK_ULONG out_len;
        CK_ULONG total_len;
        CK_ULONG remain;
        boolean_t pad_mechanism = B_FALSE;

        pad_mechanism = (mechanism == CKM_DES_CBC_PAD ||
            mechanism == CKM_DES3_CBC_PAD);
        /*
         * DES only takes input length that is a multiple of blocksize
         * for C_Encrypt function with the mechanism CKM_DES<n>_ECB or
         * CKM_DES<n>_CBC.
         *
         * DES allows any input length for C_Encrypt function with the
         * mechanism CKM_DES<n>_CBC_PAD and for C_EncryptUpdate function.
         */
        if (!update && !pad_mechanism) {
                if ((ulDataLen % DES_BLOCK_LEN) != 0) {
                        rv = CKR_DATA_LEN_RANGE;
                        goto cleanup;
                }
        }

        if (!update) {
                /*
                 * Called by C_Encrypt
                 */
                if (pad_mechanism) {
                        /*
                         * For CKM_DES<n>_CBC_PAD, compute output length to
                         * count for the padding. If the length of input
                         * data is a multiple of blocksize, then make output
                         * length to be the sum of the input length and
                         * one blocksize. Otherwise, output length will
                         * be rounded up to the next multiple of blocksize.
                         */
                        out_len = DES_BLOCK_LEN *
                            (ulDataLen / DES_BLOCK_LEN + 1);
                } else {
                        /*
                         * For non-padding mode, the output length will
                         * be same as the input length.
                         */
                        out_len = ulDataLen;
                }

                /*
                 * If application asks for the length of the output buffer
                 * to hold the ciphertext?
                 */
                if (pEncrypted == NULL) {
                        *pulEncryptedLen = out_len;
                        return (CKR_OK);
                }

                /* Is the application-supplied buffer large enough? */
                if (*pulEncryptedLen < out_len) {
                        *pulEncryptedLen = out_len;
                        return (CKR_BUFFER_TOO_SMALL);
                }

                /* Encrypt pad bytes in a separate operation */
                if (pad_mechanism) {
                        out_len -= DES_BLOCK_LEN;
                }

                in_buf = pData;
                out_buf = pEncrypted;
        } else {
                /*
                 * Called by C_EncryptUpdate
                 *
                 * Add the lengths of last remaining data and current
                 * plaintext together to get the total input length.
                 */
                total_len = soft_des_ctx->remain_len + ulDataLen;

                /*
                 * If the total input length is less than one blocksize,
                 * or if the total input length is just one blocksize and
                 * the mechanism is CKM_DES<n>_CBC_PAD, we will need to delay
                 * encryption until when more data comes in next
                 * C_EncryptUpdate or when C_EncryptFinal is called.
                 */
                if ((total_len < DES_BLOCK_LEN) ||
                    (pad_mechanism && (total_len == DES_BLOCK_LEN))) {
                        if (pData != NULL) {
                                /*
                                 * Save input data and its length in
                                 * the remaining buffer of DES context.
                                 */
                                (void) memcpy(soft_des_ctx->data +
                                    soft_des_ctx->remain_len, pData, ulDataLen);
                                soft_des_ctx->remain_len += ulDataLen;
                        }

                        /* Set encrypted data length to 0. */
                        *pulEncryptedLen = 0;
                        return (CKR_OK);
                }

                /* Compute the length of remaing data. */
                remain = total_len % DES_BLOCK_LEN;

                /*
                 * Make sure that the output length is a multiple of
                 * blocksize.
                 */
                out_len = total_len - remain;

                /*
                 * If application asks for the length of the output buffer
                 * to hold the ciphertext?
                 */
                if (pEncrypted == NULL) {
                        *pulEncryptedLen = out_len;
                        return (CKR_OK);
                }

                /* Is the application-supplied buffer large enough? */
                if (*pulEncryptedLen < out_len) {
                        *pulEncryptedLen = out_len;
                        return (CKR_BUFFER_TOO_SMALL);
                }

                if (soft_des_ctx->remain_len != 0) {
                        /*
                         * Copy last remaining data and current input data
                         * to the output buffer.
                         */
                        (void) memmove(pEncrypted + soft_des_ctx->remain_len,
                            pData, out_len - soft_des_ctx->remain_len);
                        (void) memcpy(pEncrypted, soft_des_ctx->data,
                            soft_des_ctx->remain_len);
                        bzero(soft_des_ctx->data, soft_des_ctx->remain_len);

                        in_buf = pEncrypted;
                } else {
                        in_buf = pData;
                }
                out_buf = pEncrypted;
        }

        /*
         * Begin Encryption now.
         */
        switch (mechanism) {

        case CKM_DES_ECB:
        case CKM_DES3_ECB:
        {

                ulong_t i;
                uint8_t *tmp_inbuf;
                uint8_t *tmp_outbuf;

                for (i = 0; i < out_len; i += DES_BLOCK_LEN) {
                        tmp_inbuf = &in_buf[i];
                        tmp_outbuf = &out_buf[i];
                        /* Crunch one block of data for DES. */
                        if (soft_des_ctx->key_type == CKK_DES)
                                (void) des_crunch_block(
                                    soft_des_ctx->key_sched,
                                    tmp_inbuf, tmp_outbuf, B_FALSE);
                        else
                                (void) des3_crunch_block(
                                    soft_des_ctx->key_sched,
                                    tmp_inbuf, tmp_outbuf, B_FALSE);
                }

                if (update) {
                        /*
                         * For encrypt update, if there is remaining
                         * data, save it and its length in the context.
                         */
                        if (remain != 0)
                                (void) memcpy(soft_des_ctx->data, pData +
                                    (ulDataLen - remain), remain);
                        soft_des_ctx->remain_len = remain;
                }

                *pulEncryptedLen = out_len;
                break;
        }

        case CKM_DES_CBC:
        case CKM_DES_CBC_PAD:
        case CKM_DES3_CBC:
        case CKM_DES3_CBC_PAD:
        {
                crypto_data_t out;

                out.cd_format =  CRYPTO_DATA_RAW;
                out.cd_offset = 0;
                out.cd_length = out_len;
                out.cd_raw.iov_base = (char *)out_buf;
                out.cd_raw.iov_len = out_len;

                /* Encrypt multiple blocks of data. */
                rc = des_encrypt_contiguous_blocks(
                    (des_ctx_t *)soft_des_ctx->des_cbc,
                    (char *)in_buf, out_len, &out);

                if (rc != 0)
                        goto encrypt_failed;

                if (update) {
                        /*
                         * For encrypt update, if there is remaining data,
                         * save it and its length in the context.
                         */
                        if (remain != 0)
                                (void) memcpy(soft_des_ctx->data, pData +
                                    (ulDataLen - remain), remain);
                        soft_des_ctx->remain_len = remain;
                } else if (pad_mechanism) {
                        /*
                         * Save the remainder of the input
                         * block in a temporary block because
                         * we don't want to overrun the input buffer
                         * by tacking on pad bytes.
                         */
                        CK_BYTE tmpblock[DES_BLOCK_LEN];
                        (void) memcpy(tmpblock, in_buf + out_len,
                            ulDataLen - out_len);
                        soft_add_pkcs7_padding(tmpblock +
                            (ulDataLen - out_len),
                            DES_BLOCK_LEN, ulDataLen - out_len);

                        out.cd_offset = out_len;
                        out.cd_length = DES_BLOCK_LEN;
                        out.cd_raw.iov_base = (char *)out_buf;
                        out.cd_raw.iov_len = out_len + DES_BLOCK_LEN;

                        /* Encrypt last block containing pad bytes. */
                        rc = des_encrypt_contiguous_blocks(
                            (des_ctx_t *)soft_des_ctx->des_cbc,
                            (char *)tmpblock, DES_BLOCK_LEN, &out);
                        out_len += DES_BLOCK_LEN;
                }

                if (rc == 0) {
                        *pulEncryptedLen = out_len;
                        break;
                }
encrypt_failed:
                *pulEncryptedLen = 0;
                rv = CKR_FUNCTION_FAILED;
                goto cleanup;

        }
        } /* end switch */

        if (update)
                return (CKR_OK);

        /*
         * The following code will be executed if the caller is
         * soft_encrypt() or an error occurred. The encryption
         * operation will be terminated so we need to do some cleanup.
         */
cleanup:
        (void) pthread_mutex_lock(&session_p->session_mutex);
        des_ctx = (des_ctx_t *)soft_des_ctx->des_cbc;
        free(des_ctx);
        freezero(soft_des_ctx->key_sched, soft_des_ctx->keysched_len);
        freezero(session_p->encrypt.context, sizeof (soft_des_ctx_t));
        session_p->encrypt.context = NULL;
        (void) pthread_mutex_unlock(&session_p->session_mutex);

        return (rv);
}


/*
 * soft_des_decrypt_common()
 *
 * Arguments:
 *      session_p:      pointer to soft_session_t struct
 *      pEncrypted:     pointer to the input data to be decrypted
 *      ulEncryptedLen: length of the input data
 *      pData:          pointer to the output data
 *      pulDataLen:     pointer to the length of the output data
 *      Update:         boolean flag indicates caller is soft_decrypt
 *                      or soft_decrypt_update
 *
 * Description:
 *      This function calls the corresponding decrypt routine based
 *      on the mechanism.
 *
 * Returns:
 *      CKR_OK: success
 *      CKR_BUFFER_TOO_SMALL: the output buffer provided by application
 *                            is too small
 *      CKR_ENCRYPTED_DATA_LEN_RANGE: the input data is not a multiple
 *                                    of blocksize
 *      CKR_FUNCTION_FAILED: decrypt function failed
 */
CK_RV
soft_des_decrypt_common(soft_session_t *session_p, CK_BYTE_PTR pEncrypted,
    CK_ULONG ulEncryptedLen, CK_BYTE_PTR pData,
    CK_ULONG_PTR pulDataLen, boolean_t update)
{

        int rc = 0;
        CK_RV rv = CKR_OK;
        soft_des_ctx_t *soft_des_ctx =
            (soft_des_ctx_t *)session_p->decrypt.context;
        des_ctx_t *des_ctx;
        CK_MECHANISM_TYPE mechanism = session_p->decrypt.mech.mechanism;
        CK_BYTE *in_buf = NULL;
        CK_BYTE *out_buf = NULL;
        CK_ULONG out_len;
        CK_ULONG total_len;
        CK_ULONG remain;
        boolean_t pad_mechanism = B_FALSE;

        pad_mechanism = (mechanism == CKM_DES_CBC_PAD ||
            mechanism == CKM_DES3_CBC_PAD);
        /*
         * DES only takes input length that is a multiple of 8 bytes
         * for C_Decrypt function with the mechanism CKM_DES<n>_ECB,
         * CKM_DES<n>_CBC or CKM_DES<n>_CBC_PAD.
         *
         * DES allows any input length for C_DecryptUpdate function.
         */
        if (!update) {
                /*
                 * Called by C_Decrypt
                 */
                if ((ulEncryptedLen % DES_BLOCK_LEN) != 0) {
                        rv = CKR_ENCRYPTED_DATA_LEN_RANGE;
                        goto cleanup;
                }

                /*
                 * If application asks for the length of the output buffer
                 * to hold the plaintext?
                 */
                if (pData == NULL) {
                        *pulDataLen = ulEncryptedLen;
                        return (CKR_OK);
                }

                /* Is the application-supplied buffer large enough? */
                if (!pad_mechanism) {
                        if (*pulDataLen < ulEncryptedLen) {
                                *pulDataLen = ulEncryptedLen;
                                return (CKR_BUFFER_TOO_SMALL);
                        }

                        /* Set output length same as input length. */
                        out_len = ulEncryptedLen;
                } else {
                        /*
                         * For CKM_DES<n>_CBC_PAD, we don't know how
                         * many bytes for padding at this time, so
                         * we'd assume one block was padded.
                         */
                        if (*pulDataLen < (ulEncryptedLen - DES_BLOCK_LEN)) {
                                *pulDataLen = ulEncryptedLen - DES_BLOCK_LEN;
                                return (CKR_BUFFER_TOO_SMALL);
                        }
                        out_len = ulEncryptedLen - DES_BLOCK_LEN;
                }
                in_buf = pEncrypted;
                out_buf = pData;
        } else {
                /*
                 *  Called by C_DecryptUpdate
                 *
                 * Add the lengths of last remaining data and current
                 * input data together to get the total input length.
                 */
                total_len = soft_des_ctx->remain_len + ulEncryptedLen;

                /*
                 * If the total input length is less than one blocksize,
                 * or if the total input length is just one blocksize and
                 * the mechanism is CKM_DES<n>_CBC_PAD, we will need to delay
                 * decryption until when more data comes in next
                 * C_DecryptUpdate or when C_DecryptFinal is called.
                 */
                if ((total_len < DES_BLOCK_LEN) ||
                    (pad_mechanism && (total_len == DES_BLOCK_LEN))) {
                        if (pEncrypted != NULL) {
                                /*
                                 * Save input data and its length in
                                 * the remaining buffer of DES context.
                                 */
                                (void) memcpy(soft_des_ctx->data +
                                    soft_des_ctx->remain_len,
                                    pEncrypted, ulEncryptedLen);
                                soft_des_ctx->remain_len += ulEncryptedLen;
                        }

                        /* Set output data length to 0. */
                        *pulDataLen = 0;
                        return (CKR_OK);
                }

                /* Compute the length of remaing data. */
                remain = total_len % DES_BLOCK_LEN;

                /*
                 * Make sure that the output length is a multiple of
                 * blocksize.
                 */
                out_len = total_len - remain;

                if (pad_mechanism) {
                        /*
                         * If the input data length is a multiple of
                         * blocksize, then save the last block of input
                         * data in the remaining buffer. C_DecryptFinal
                         * will handle this last block of data.
                         */
                        if (remain == 0) {
                                remain = DES_BLOCK_LEN;
                                out_len -= DES_BLOCK_LEN;
                        }
                }

                /*
                 * If application asks for the length of the output buffer
                 * to hold the plaintext?
                 */
                if (pData == NULL) {
                        *pulDataLen = out_len;
                        return (CKR_OK);
                }

                /*
                 * Is the application-supplied buffer large enough?
                 */
                if (*pulDataLen < out_len) {
                        *pulDataLen = out_len;
                        return (CKR_BUFFER_TOO_SMALL);
                }

                if (soft_des_ctx->remain_len != 0) {
                        /*
                         * Copy last remaining data and current input data
                         * to the output buffer.
                         */
                        (void) memmove(pData + soft_des_ctx->remain_len,
                            pEncrypted, out_len - soft_des_ctx->remain_len);
                        (void) memcpy(pData, soft_des_ctx->data,
                            soft_des_ctx->remain_len);
                        bzero(soft_des_ctx->data, soft_des_ctx->remain_len);

                        in_buf = pData;
                } else {
                        in_buf = pEncrypted;
                }
                out_buf = pData;
        }

        /*
         * Begin Decryption.
         */
        switch (mechanism) {

        case CKM_DES_ECB:
        case CKM_DES3_ECB:
        {
                uint8_t *tmp_inbuf;
                uint8_t *tmp_outbuf;
                ulong_t i;

                for (i = 0; i < out_len; i += DES_BLOCK_LEN) {
                        tmp_inbuf = &in_buf[i];
                        tmp_outbuf = &out_buf[i];
                        /* Crunch one block of data for DES. */
                        if (soft_des_ctx->key_type == CKK_DES)
                                (void) des_crunch_block(
                                    soft_des_ctx->key_sched,
                                    tmp_inbuf, tmp_outbuf, B_TRUE);
                        else
                                (void) des3_crunch_block(
                                    soft_des_ctx->key_sched,
                                    tmp_inbuf, tmp_outbuf, B_TRUE);
                }

                if (update) {
                        /*
                         * For decrypt update, if there is remaining
                         * data, save it and its length in the context.
                         */
                        if (remain != 0)
                                (void) memcpy(soft_des_ctx->data, pEncrypted +
                                    (ulEncryptedLen - remain), remain);
                        soft_des_ctx->remain_len = remain;
                }

                *pulDataLen = out_len;
                break;
        }

        case CKM_DES_CBC:
        case CKM_DES_CBC_PAD:
        case CKM_DES3_CBC:
        case CKM_DES3_CBC_PAD:
        {
                crypto_data_t out;
                CK_ULONG rem_len;
                uint8_t last_block[DES_BLOCK_LEN];

                out.cd_format =  CRYPTO_DATA_RAW;
                out.cd_offset = 0;
                out.cd_length = out_len;
                out.cd_raw.iov_base = (char *)out_buf;
                out.cd_raw.iov_len = out_len;

                /* Decrypt multiple blocks of data. */
                rc = des_decrypt_contiguous_blocks(
                    (des_ctx_t *)soft_des_ctx->des_cbc,
                    (char *)in_buf, out_len, &out);

                if (rc != 0)
                        goto decrypt_failed;

                if (pad_mechanism && !update) {
                        /* Decrypt last block containing pad bytes. */
                        out.cd_offset = 0;
                        out.cd_length = DES_BLOCK_LEN;
                        out.cd_raw.iov_base = (char *)last_block;
                        out.cd_raw.iov_len = DES_BLOCK_LEN;

                        /* Decrypt last block containing pad bytes. */
                        rc = des_decrypt_contiguous_blocks(
                            (des_ctx_t *)soft_des_ctx->des_cbc,
                            (char *)in_buf + out_len, DES_BLOCK_LEN, &out);

                        if (rc != 0)
                                goto decrypt_failed;

                        /*
                         * Remove padding bytes after decryption of
                         * ciphertext block to produce the original
                         * plaintext.
                         */
                        rv = soft_remove_pkcs7_padding(last_block,
                            DES_BLOCK_LEN, &rem_len);
                        if (rv == CKR_OK) {
                                if (rem_len != 0)
                                        (void) memcpy(out_buf + out_len,
                                            last_block, rem_len);
                                *pulDataLen = out_len + rem_len;
                        } else {
                                *pulDataLen = 0;
                                goto cleanup;
                        }
                } else {
                        *pulDataLen = out_len;
                }

                if (update) {
                        /*
                         * For decrypt update, if there is remaining data,
                         * save it and its length in the context.
                         */
                        if (remain != 0)
                                (void) memcpy(soft_des_ctx->data, pEncrypted +
                                    (ulEncryptedLen - remain), remain);
                        soft_des_ctx->remain_len = remain;
                }

                if (rc == 0)
                        break;
decrypt_failed:
                *pulDataLen = 0;
                rv = CKR_FUNCTION_FAILED;
                goto cleanup;
        }
        } /* end switch */

        if (update)
                return (CKR_OK);

        /*
         * The following code will be executed if the caller is
         * soft_decrypt() or an error occurred. The decryption
         * operation will be terminated so we need to do some cleanup.
         */
cleanup:
        (void) pthread_mutex_lock(&session_p->session_mutex);
        des_ctx = (des_ctx_t *)soft_des_ctx->des_cbc;
        free(des_ctx);
        freezero(soft_des_ctx->key_sched, soft_des_ctx->keysched_len);
        freezero(session_p->decrypt.context, sizeof (soft_des_ctx_t));
        (void) pthread_mutex_unlock(&session_p->session_mutex);

        return (rv);
}


/*
 * Allocate and initialize a context for DES CBC mode of operation.
 */
void *
des_cbc_ctx_init(void *key_sched, size_t size, uint8_t *ivec, CK_KEY_TYPE type)
{

        cbc_ctx_t *cbc_ctx;

        if ((cbc_ctx = calloc(1, sizeof (cbc_ctx_t))) == NULL)
                return (NULL);

        cbc_ctx->cbc_keysched = key_sched;

        (void) memcpy(&cbc_ctx->cbc_iv[0], ivec, DES_BLOCK_LEN);

        cbc_ctx->cbc_lastp = (uint8_t *)&cbc_ctx->cbc_iv[0];
        cbc_ctx->cbc_keysched_len = size;
        if (type == CKK_DES)
                cbc_ctx->cbc_flags |= CBC_MODE;
        else
                cbc_ctx->cbc_flags |= CBC_MODE | DES3_STRENGTH;

        return (cbc_ctx);

}

/*
 * Allocate and initialize DES contexts for both signing and encrypting,
 * saving both context pointers in the session struct. For general-length DES
 * MAC, check the length in the parameter to see if it is in the right range.
 */
CK_RV
soft_des_sign_verify_init_common(soft_session_t *session_p,
    CK_MECHANISM_PTR pMechanism, soft_object_t *key_p, boolean_t sign_op)
{
        soft_des_ctx_t  *soft_des_ctx;
        CK_MECHANISM    encrypt_mech;
        CK_RV rv;

        if ((key_p->class != CKO_SECRET_KEY) || (key_p->key_type != CKK_DES)) {
                return (CKR_KEY_TYPE_INCONSISTENT);
        }

        /* allocate memory for the sign/verify context */
        soft_des_ctx = malloc(sizeof (soft_des_ctx_t));
        if (soft_des_ctx == NULL) {
                return (CKR_HOST_MEMORY);
        }

        soft_des_ctx->key_type = key_p->key_type;

        /* initialization vector is zero for DES MAC */
        bzero(soft_des_ctx->ivec, DES_BLOCK_LEN);

        switch (pMechanism->mechanism) {

        case CKM_DES_MAC_GENERAL:

                if (pMechanism->ulParameterLen !=
                    sizeof (CK_MAC_GENERAL_PARAMS)) {
                        free(soft_des_ctx);
                        return (CKR_MECHANISM_PARAM_INVALID);
                }

                if (*(CK_MAC_GENERAL_PARAMS *)pMechanism->pParameter >
                    DES_BLOCK_LEN) {
                        free(soft_des_ctx);
                        return (CKR_MECHANISM_PARAM_INVALID);
                }

                soft_des_ctx->mac_len = *((CK_MAC_GENERAL_PARAMS_PTR)
                    pMechanism->pParameter);

                /*FALLTHRU*/
        case CKM_DES_MAC:

                /*
                 * For non-general DES MAC, output is always half as
                 * large as block size
                 */
                if (pMechanism->mechanism == CKM_DES_MAC) {
                        soft_des_ctx->mac_len = DES_MAC_LEN;
                }

                /* allocate a context for DES encryption */
                encrypt_mech.mechanism = CKM_DES_CBC_PAD;
                encrypt_mech.pParameter = (void *)soft_des_ctx->ivec;
                encrypt_mech.ulParameterLen = DES_BLOCK_LEN;
                rv = soft_encrypt_init_internal(session_p, &encrypt_mech,
                    key_p);
                if (rv != CKR_OK) {
                        free(soft_des_ctx);
                        return (rv);
                }

                (void) pthread_mutex_lock(&session_p->session_mutex);

                if (sign_op) {
                        session_p->sign.context = soft_des_ctx;
                        session_p->sign.mech.mechanism = pMechanism->mechanism;
                } else {
                        session_p->verify.context = soft_des_ctx;
                        session_p->verify.mech.mechanism =
                            pMechanism->mechanism;
                }

                (void) pthread_mutex_unlock(&session_p->session_mutex);

                break;
        }
        return (CKR_OK);
}

/*
 * Called by soft_sign(), soft_sign_final(), soft_verify() or
 * soft_verify_final().
 */
CK_RV
soft_des_sign_verify_common(soft_session_t *session_p, CK_BYTE_PTR pData,
    CK_ULONG ulDataLen, CK_BYTE_PTR pSigned, CK_ULONG_PTR pulSignedLen,
    boolean_t sign_op, boolean_t Final)
{
        soft_des_ctx_t          *soft_des_ctx_sign_verify;
        soft_des_ctx_t          *soft_des_ctx_encrypt;
        CK_RV                   rv;
        CK_BYTE                 *pEncrypted = NULL;
        CK_ULONG                ulEncryptedLen = 0;
        uint8_t                 remainder;
        CK_BYTE                 last_block[DES_BLOCK_LEN];
        des_ctx_t               *des_ctx = NULL;

        if (sign_op) {
                soft_des_ctx_sign_verify =
                    (soft_des_ctx_t *)session_p->sign.context;

                if (soft_des_ctx_sign_verify->mac_len == 0) {
                        *pulSignedLen = 0;
                        goto clean_exit;
                }

                /* Application asks for the length of the output buffer. */
                if (pSigned == NULL) {
                        *pulSignedLen = soft_des_ctx_sign_verify->mac_len;
                        return (CKR_OK);
                }

                /* Is the application-supplied buffer large enough? */
                if (*pulSignedLen < soft_des_ctx_sign_verify->mac_len) {
                        *pulSignedLen = soft_des_ctx_sign_verify->mac_len;
                        return (CKR_BUFFER_TOO_SMALL);
                }
        } else {
                soft_des_ctx_sign_verify =
                    (soft_des_ctx_t *)session_p->verify.context;
        }

        if (Final) {
                soft_des_ctx_encrypt =
                    (soft_des_ctx_t *)session_p->encrypt.context;

                /*
                 * If there is data left in the buffer from a previous
                 * SignUpdate() call, pass enough zeroed data to a
                 * soft_sign_update call to pad the remainder
                 */
                if (soft_des_ctx_encrypt->remain_len != 0) {
                        bzero(last_block, DES_BLOCK_LEN);
                        ulEncryptedLen = DES_BLOCK_LEN;

                        /*
                         * By passing a buffer to soft_encrypt_final,
                         * we force it to pad the remaining block
                         * and encrypt it.
                         */
                        rv = soft_encrypt_final(session_p, last_block,
                            &ulEncryptedLen);
                        if (rv != CKR_OK) {
                                goto clean_exit;
                        }
                } else {
                        /*
                         * The last block of enciphered data is stored in:
                         * soft_des_ctx_encrypt->des_cbc->des_ctx->dc_lastp
                         * Copy that data to last_block
                         */
                        soft_des_ctx_encrypt =
                            (soft_des_ctx_t *)session_p->encrypt.context;
                        des_ctx = (des_ctx_t *)soft_des_ctx_encrypt->des_cbc;
                        (void) memcpy(last_block, des_ctx->dc_lastp,
                            DES_BLOCK_LEN);

                        /*
                         * Passing a NULL output buffer here
                         * forces the routine to just return.
                         */
                        rv = soft_encrypt_final(session_p, NULL,
                            &ulEncryptedLen);
                }

        } else {
                /*
                 * If the input length is not multiple of block size, then
                 * determine the correct encrypted data length by rounding
                 */
                remainder = ulDataLen % DES_BLOCK_LEN;
                /*
                 * Because we always use DES_CBC_PAD mechanism
                 * for sign/verify operations, the input will
                 * be padded to the next 8 byte boundary.
                 * Adjust the length fields here accordingly.
                 */
                ulEncryptedLen = ulDataLen + (DES_BLOCK_LEN - remainder);

                pEncrypted = malloc(sizeof (CK_BYTE) * ulEncryptedLen);
                if (pEncrypted == NULL) {
                        rv = CKR_HOST_MEMORY;
                        goto clean_exit;
                }

                /*
                 * Pad the last block with zeros by copying pData into a zeroed
                 * pEncrypted. Then pass pEncrypted into soft_encrypt as input
                 */
                bzero(pEncrypted, ulEncryptedLen);
                (void) memcpy(pEncrypted, pData, ulDataLen);

                rv = soft_encrypt(session_p, pEncrypted, ulDataLen,
                    pEncrypted, &ulEncryptedLen);
                (void) memcpy(last_block,
                    &pEncrypted[ulEncryptedLen - DES_BLOCK_LEN], DES_BLOCK_LEN);
        }

        if (rv == CKR_OK) {
                *pulSignedLen = soft_des_ctx_sign_verify->mac_len;

                /* the leftmost mac_len bytes of last_block is our MAC */
                (void) memcpy(pSigned, last_block, *pulSignedLen);
        }

clean_exit:

        (void) pthread_mutex_lock(&session_p->session_mutex);

        /* soft_encrypt_common() has freed the encrypt context */
        if (sign_op) {
                free(session_p->sign.context);
                session_p->sign.context = NULL;
        } else {
                free(session_p->verify.context);
                session_p->verify.context = NULL;
        }
        session_p->encrypt.flags = 0;

        (void) pthread_mutex_unlock(&session_p->session_mutex);

        if (pEncrypted) {
                free(pEncrypted);
        }

        return (rv);
}

/*
 * Called by soft_sign_update()
 */
CK_RV
soft_des_mac_sign_verify_update(soft_session_t *session_p, CK_BYTE_PTR pPart,
    CK_ULONG ulPartLen)
{
        /*
         * The DES MAC is calculated by taking the specified number of
         * left-most bytes within the last block of
         * encrypted data, while the context of the multi-part
         * encryption stores the block necessary for XORing with the
         * input as per cipher block chaining . Therefore, none of the
         * intermediary encrypted blocks of data are necessary for
         * the DES MAC, and we can create a placeholder local buffer
         * for the encrypted data, which is immediately throw away.
         */

        soft_des_ctx_t  *soft_des_ctx_encrypt;
        CK_BYTE         *pEncrypted = NULL;
        CK_ULONG        ulEncryptedLen;
        CK_ULONG        total_len;
        uint8_t         remainder;
        CK_RV           rv;

        soft_des_ctx_encrypt = (soft_des_ctx_t *)session_p->encrypt.context;

        /* Avoid the malloc if we won't be encrypting any data */
        total_len = soft_des_ctx_encrypt->remain_len + ulPartLen;

        if (total_len < DES_BLOCK_LEN) {
                rv = soft_encrypt_update(session_p, pPart, ulPartLen, NULL,
                    &ulEncryptedLen);
        } else {
                remainder = ulPartLen % DES_BLOCK_LEN;

                /* round up to the nearest multiple of block size */
                ulEncryptedLen = ulPartLen + (DES_BLOCK_LEN - remainder);
                pEncrypted = malloc(sizeof (CK_BYTE) * ulEncryptedLen);

                if (pEncrypted != NULL) {
                        rv = soft_encrypt_update(session_p, pPart, ulPartLen,
                            pEncrypted, &ulEncryptedLen);
                        free(pEncrypted);
                } else {
                        rv = CKR_HOST_MEMORY;
                }
        }
        return (rv);
}