/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/*
 * This file implements PKCS 11 on top of our existing security modules
 *
 * For more information about PKCS 11 See PKCS 11 Token Inteface Standard.
 *   This implementation has two slots:
 *      slot 1 is our generic crypto support. It does not require login.
 *   It supports Public Key ops, and all they bulk ciphers and hashes.
 *   It can also support Private Key ops for imported Private keys. It does
 *   not have any token storage.
 *      slot 2 is our private key support. It requires a login before use. It
 *   can store Private Keys and Certs as token objects. Currently only private
 *   keys and their associated Certificates are saved on the token.
 *
 *   In this implementation, session objects are only visible to the session
 *   that created or generated them.
 */
#include "seccomon.h"
#include "secitem.h"
#include "secport.h"
#include "blapi.h"
#include "pkcs11.h"
#include "pkcs11i.h"
#include "pkcs1sig.h"
#include "lowkeyi.h"
#include "secder.h"
#include "secdig.h"
#include "lowpbe.h" /* We do PBE below */
#include "pkcs11t.h"
#include "secoid.h"
#include "alghmac.h"
#include "softoken.h"
#include "secasn1.h"
#include "secerr.h"

#include "prprf.h"
#include "prenv.h"

#define __PASTE(x, y) x##y

/*
 * we renamed all our internal functions, get the correct
 * definitions for them...
 */
#undef CK_PKCS11_FUNCTION_INFO
#undef CK_NEED_ARG_LIST

#define CK_EXTERN extern
#define CK_PKCS11_FUNCTION_INFO(func) \
    CK_RV __PASTE(NS, func)
#define CK_NEED_ARG_LIST 1

#include "pkcs11f.h"

typedef struct {
    PRUint8 client_version[2];
    PRUint8 random[46];
} SSL3RSAPreMasterSecret;

static void
sftk_Null(void *data, PRBool freeit)
{
    return;
}

#ifdef EC_DEBUG
#define SEC_PRINT(str1, str2, num, sitem)             \
    printf("pkcs11c.c:%s:%s (keytype=%d) [len=%d]\n", \
           str1, str2, num, sitem->len);              \
    for (i = 0; i < sitem->len; i++) {                \
        printf("%02x:", sitem->data[i]);              \
    }                                                 \
    printf("\n")
#else
#undef EC_DEBUG
#define SEC_PRINT(a, b, c, d)
#endif

/*
 * free routines.... Free local type  allocated data, and convert
 * other free routines to the destroy signature.
 */
static void
sftk_FreePrivKey(NSSLOWKEYPrivateKey *key, PRBool freeit)
{
    nsslowkey_DestroyPrivateKey(key);
}

static void
sftk_Space(void *data, PRBool freeit)
{
    PORT_Free(data);
}

/*
 * map all the SEC_ERROR_xxx error codes that may be returned by freebl
 * functions to CKR_xxx.  return CKR_DEVICE_ERROR by default for backward
 * compatibility.
 */
static CK_RV
sftk_MapCryptError(int error)
{
    switch (error) {
        case SEC_ERROR_INVALID_ARGS:
        case SEC_ERROR_BAD_DATA: /* MP_RANGE gets mapped to this */
            return CKR_ARGUMENTS_BAD;
        case SEC_ERROR_INPUT_LEN:
            return CKR_DATA_LEN_RANGE;
        case SEC_ERROR_OUTPUT_LEN:
            return CKR_BUFFER_TOO_SMALL;
        case SEC_ERROR_LIBRARY_FAILURE:
            return CKR_GENERAL_ERROR;
        case SEC_ERROR_NO_MEMORY:
            return CKR_HOST_MEMORY;
        case SEC_ERROR_BAD_SIGNATURE:
            return CKR_SIGNATURE_INVALID;
        case SEC_ERROR_INVALID_KEY:
            return CKR_KEY_SIZE_RANGE;
        case SEC_ERROR_BAD_KEY:        /* an EC public key that fails validation */
            return CKR_KEY_SIZE_RANGE; /* the closest error code */
        case SEC_ERROR_UNSUPPORTED_EC_POINT_FORM:
            return CKR_TEMPLATE_INCONSISTENT;
        case SEC_ERROR_UNSUPPORTED_KEYALG:
            return CKR_MECHANISM_INVALID;
        case SEC_ERROR_UNSUPPORTED_ELLIPTIC_CURVE:
            return CKR_DOMAIN_PARAMS_INVALID;
        /* key pair generation failed after max number of attempts */
        case SEC_ERROR_NEED_RANDOM:
            return CKR_FUNCTION_FAILED;
    }
    return CKR_DEVICE_ERROR;
}

/* used by Decrypt and UnwrapKey (indirectly) */
static CK_RV
sftk_MapDecryptError(int error)
{
    switch (error) {
        case SEC_ERROR_BAD_DATA:
            return CKR_ENCRYPTED_DATA_INVALID;
        default:
            return sftk_MapCryptError(error);
    }
}

/*
 * return CKR_SIGNATURE_INVALID instead of CKR_DEVICE_ERROR by default for
 * backward compatibilty.
 */
static CK_RV
sftk_MapVerifyError(int error)
{
    CK_RV crv = sftk_MapCryptError(error);
    if (crv == CKR_DEVICE_ERROR)
        crv = CKR_SIGNATURE_INVALID;
    return crv;
}

/*
 * turn a CDMF key into a des key. CDMF is an old IBM scheme to export DES by
 * Deprecating a full des key to 40 bit key strenth.
 */
static CK_RV
sftk_cdmf2des(unsigned char *cdmfkey, unsigned char *deskey)
{
    unsigned char key1[8] = { 0xc4, 0x08, 0xb0, 0x54, 0x0b, 0xa1, 0xe0, 0xae };
    unsigned char key2[8] = { 0xef, 0x2c, 0x04, 0x1c, 0xe6, 0x38, 0x2f, 0xe6 };
    unsigned char enc_src[8];
    unsigned char enc_dest[8];
    unsigned int leng, i;
    DESContext *descx;
    SECStatus rv;

    /* zero the parity bits */
    for (i = 0; i < 8; i++) {
        enc_src[i] = cdmfkey[i] & 0xfe;
    }

    /* encrypt with key 1 */
    descx = DES_CreateContext(key1, NULL, NSS_DES, PR_TRUE);
    if (descx == NULL)
        return CKR_HOST_MEMORY;
    rv = DES_Encrypt(descx, enc_dest, &leng, 8, enc_src, 8);
    DES_DestroyContext(descx, PR_TRUE);
    if (rv != SECSuccess)
        return sftk_MapCryptError(PORT_GetError());

    /* xor source with des, zero the parity bits and deprecate the key*/
    for (i = 0; i < 8; i++) {
        if (i & 1) {
            enc_src[i] = (enc_src[i] ^ enc_dest[i]) & 0xfe;
        } else {
            enc_src[i] = (enc_src[i] ^ enc_dest[i]) & 0x0e;
        }
    }

    /* encrypt with key 2 */
    descx = DES_CreateContext(key2, NULL, NSS_DES, PR_TRUE);
    if (descx == NULL)
        return CKR_HOST_MEMORY;
    rv = DES_Encrypt(descx, deskey, &leng, 8, enc_src, 8);
    DES_DestroyContext(descx, PR_TRUE);
    if (rv != SECSuccess)
        return sftk_MapCryptError(PORT_GetError());

    /* set the corret parity on our new des key */
    sftk_FormatDESKey(deskey, 8);
    return CKR_OK;
}

/* NSC_DestroyObject destroys an object. */
CK_RV
NSC_DestroyObject(CK_SESSION_HANDLE hSession, CK_OBJECT_HANDLE hObject)
{
    SFTKSlot *slot = sftk_SlotFromSessionHandle(hSession);
    SFTKSession *session;
    SFTKObject *object;
    SFTKFreeStatus status;

    CHECK_FORK();

    if (slot == NULL) {
        return CKR_SESSION_HANDLE_INVALID;
    }
    /*
     * This whole block just makes sure we really can destroy the
     * requested object.
     */
    session = sftk_SessionFromHandle(hSession);
    if (session == NULL) {
        return CKR_SESSION_HANDLE_INVALID;
    }

    object = sftk_ObjectFromHandle(hObject, session);
    if (object == NULL) {
        sftk_FreeSession(session);
        return CKR_OBJECT_HANDLE_INVALID;
    }

    /* don't destroy a private object if we aren't logged in */
    if ((!slot->isLoggedIn) && (slot->needLogin) &&
        (sftk_isTrue(object, CKA_PRIVATE))) {
        sftk_FreeSession(session);
        sftk_FreeObject(object);
        return CKR_USER_NOT_LOGGED_IN;
    }

    /* don't destroy a token object if we aren't in a rw session */

    if (((session->info.flags & CKF_RW_SESSION) == 0) &&
        (sftk_isTrue(object, CKA_TOKEN))) {
        sftk_FreeSession(session);
        sftk_FreeObject(object);
        return CKR_SESSION_READ_ONLY;
    }

    sftk_DeleteObject(session, object);

    sftk_FreeSession(session);

    /*
     * get some indication if the object is destroyed. Note: this is not
     * 100%. Someone may have an object reference outstanding (though that
     * should not be the case by here. Also note that the object is "half"
     * destroyed. Our internal representation is destroyed, but it may still
     * be in the data base.
     */
    status = sftk_FreeObject(object);

    return (status != SFTK_DestroyFailure) ? CKR_OK : CKR_DEVICE_ERROR;
}

/*
 ************** Crypto Functions:     Utilities ************************
 */
/*
 * Utility function for converting PSS/OAEP parameter types into
 * HASH_HashTypes. Note: Only SHA family functions are defined in RFC 3447.
 */
static HASH_HashType
GetHashTypeFromMechanism(CK_MECHANISM_TYPE mech)
{
    switch (mech) {
        case CKM_SHA_1:
        case CKG_MGF1_SHA1:
            return HASH_AlgSHA1;
        case CKM_SHA224:
        case CKG_MGF1_SHA224:
            return HASH_AlgSHA224;
        case CKM_SHA256:
        case CKG_MGF1_SHA256:
            return HASH_AlgSHA256;
        case CKM_SHA384:
        case CKG_MGF1_SHA384:
            return HASH_AlgSHA384;
        case CKM_SHA512:
        case CKG_MGF1_SHA512:
            return HASH_AlgSHA512;
        default:
            return HASH_AlgNULL;
    }
}

/*
 * Returns true if "params" contains a valid set of PSS parameters
 */
static PRBool
sftk_ValidatePssParams(const CK_RSA_PKCS_PSS_PARAMS *params)
{
    if (!params) {
        return PR_FALSE;
    }
    if (GetHashTypeFromMechanism(params->hashAlg) == HASH_AlgNULL ||
        GetHashTypeFromMechanism(params->mgf) == HASH_AlgNULL) {
        return PR_FALSE;
    }
    return PR_TRUE;
}

/*
 * Returns true if "params" contains a valid set of OAEP parameters
 */
static PRBool
sftk_ValidateOaepParams(const CK_RSA_PKCS_OAEP_PARAMS *params)
{
    if (!params) {
        return PR_FALSE;
    }
    /* The requirements of ulSourceLen/pSourceData come from PKCS #11, which
     * state:
     *   If the parameter is empty, pSourceData must be NULL and
     *   ulSourceDataLen must be zero.
     */
    if (params->source != CKZ_DATA_SPECIFIED ||
        (GetHashTypeFromMechanism(params->hashAlg) == HASH_AlgNULL) ||
        (GetHashTypeFromMechanism(params->mgf) == HASH_AlgNULL) ||
        (params->ulSourceDataLen == 0 && params->pSourceData != NULL) ||
        (params->ulSourceDataLen != 0 && params->pSourceData == NULL)) {
        return PR_FALSE;
    }
    return PR_TRUE;
}

/*
 * return a context based on the SFTKContext type.
 */
SFTKSessionContext *
sftk_ReturnContextByType(SFTKSession *session, SFTKContextType type)
{
    switch (type) {
        case SFTK_ENCRYPT:
        case SFTK_DECRYPT:
            return session->enc_context;
        case SFTK_HASH:
            return session->hash_context;
        case SFTK_SIGN:
        case SFTK_SIGN_RECOVER:
        case SFTK_VERIFY:
        case SFTK_VERIFY_RECOVER:
            return session->hash_context;
    }
    return NULL;
}

/*
 * change a context based on the SFTKContext type.
 */
void
sftk_SetContextByType(SFTKSession *session, SFTKContextType type,
                      SFTKSessionContext *context)
{
    switch (type) {
        case SFTK_ENCRYPT:
        case SFTK_DECRYPT:
            session->enc_context = context;
            break;
        case SFTK_HASH:
            session->hash_context = context;
            break;
        case SFTK_SIGN:
        case SFTK_SIGN_RECOVER:
        case SFTK_VERIFY:
        case SFTK_VERIFY_RECOVER:
            session->hash_context = context;
            break;
    }
    return;
}

/*
 * code to grab the context. Needed by every C_XXXUpdate, C_XXXFinal,
 * and C_XXX function. The function takes a session handle, the context type,
 * and wether or not the session needs to be multipart. It returns the context,
 * and optionally returns the session pointer (if sessionPtr != NULL) if session
 * pointer is returned, the caller is responsible for freeing it.
 */
static CK_RV
sftk_GetContext(CK_SESSION_HANDLE handle, SFTKSessionContext **contextPtr,
                SFTKContextType type, PRBool needMulti, SFTKSession **sessionPtr)
{
    SFTKSession *session;
    SFTKSessionContext *context;

    session = sftk_SessionFromHandle(handle);
    if (session == NULL)
        return CKR_SESSION_HANDLE_INVALID;
    context = sftk_ReturnContextByType(session, type);
    /* make sure the context is valid */
    if ((context == NULL) || (context->type != type) || (needMulti && !(context->multi))) {
        sftk_FreeSession(session);
        return CKR_OPERATION_NOT_INITIALIZED;
    }
    *contextPtr = context;
    if (sessionPtr != NULL) {
        *sessionPtr = session;
    } else {
        sftk_FreeSession(session);
    }
    return CKR_OK;
}

/** Terminate operation (in the PKCS#11 spec sense).
 *  Intuitive name for FreeContext/SetNullContext pair.
 */
static void
sftk_TerminateOp(SFTKSession *session, SFTKContextType ctype,
                 SFTKSessionContext *context)
{
    sftk_FreeContext(context);
    sftk_SetContextByType(session, ctype, NULL);
}

/*
 ************** Crypto Functions:     Encrypt ************************
 */

/*
 * All the NSC_InitXXX functions have a set of common checks and processing they
 * all need to do at the beginning. This is done here.
 */
static CK_RV
sftk_InitGeneric(SFTKSession *session, SFTKSessionContext **contextPtr,
                 SFTKContextType ctype, SFTKObject **keyPtr,
                 CK_OBJECT_HANDLE hKey, CK_KEY_TYPE *keyTypePtr,
                 CK_OBJECT_CLASS pubKeyType, CK_ATTRIBUTE_TYPE operation)
{
    SFTKObject *key = NULL;
    SFTKAttribute *att;
    SFTKSessionContext *context;

    /* We can only init if there is not current context active */
    if (sftk_ReturnContextByType(session, ctype) != NULL) {
        return CKR_OPERATION_ACTIVE;
    }

    /* find the key */
    if (keyPtr) {
        key = sftk_ObjectFromHandle(hKey, session);
        if (key == NULL) {
            return CKR_KEY_HANDLE_INVALID;
        }

        /* make sure it's a valid  key for this operation */
        if (((key->objclass != CKO_SECRET_KEY) && (key->objclass != pubKeyType)) || !sftk_isTrue(key, operation)) {
            sftk_FreeObject(key);
            return CKR_KEY_TYPE_INCONSISTENT;
        }
        /* get the key type */
        att = sftk_FindAttribute(key, CKA_KEY_TYPE);
        if (att == NULL) {
            sftk_FreeObject(key);
            return CKR_KEY_TYPE_INCONSISTENT;
        }
        PORT_Assert(att->attrib.ulValueLen == sizeof(CK_KEY_TYPE));
        if (att->attrib.ulValueLen != sizeof(CK_KEY_TYPE)) {
            sftk_FreeAttribute(att);
            sftk_FreeObject(key);
            return CKR_ATTRIBUTE_VALUE_INVALID;
        }
        PORT_Memcpy(keyTypePtr, att->attrib.pValue, sizeof(CK_KEY_TYPE));
        sftk_FreeAttribute(att);
        *keyPtr = key;
    }

    /* allocate the context structure */
    context = (SFTKSessionContext *)PORT_Alloc(sizeof(SFTKSessionContext));
    if (context == NULL) {
        if (key)
            sftk_FreeObject(key);
        return CKR_HOST_MEMORY;
    }
    context->type = ctype;
    context->multi = PR_TRUE;
    context->rsa = PR_FALSE;
    context->cipherInfo = NULL;
    context->hashInfo = NULL;
    context->doPad = PR_FALSE;
    context->padDataLength = 0;
    context->key = key;
    context->blockSize = 0;
    context->maxLen = 0;

    *contextPtr = context;
    return CKR_OK;
}

static int
sftk_aes_mode(CK_MECHANISM_TYPE mechanism)
{
    switch (mechanism) {
        case CKM_AES_CBC_PAD:
        case CKM_AES_CBC:
            return NSS_AES_CBC;
        case CKM_AES_ECB:
            return NSS_AES;
        case CKM_AES_CTS:
            return NSS_AES_CTS;
        case CKM_AES_CTR:
            return NSS_AES_CTR;
        case CKM_AES_GCM:
            return NSS_AES_GCM;
    }
    return -1;
}

static SECStatus
sftk_RSAEncryptRaw(NSSLOWKEYPublicKey *key, unsigned char *output,
                   unsigned int *outputLen, unsigned int maxLen,
                   const unsigned char *input, unsigned int inputLen)
{
    SECStatus rv = SECFailure;

    PORT_Assert(key->keyType == NSSLOWKEYRSAKey);
    if (key->keyType != NSSLOWKEYRSAKey) {
        PORT_SetError(SEC_ERROR_INVALID_KEY);
        return SECFailure;
    }

    rv = RSA_EncryptRaw(&key->u.rsa, output, outputLen, maxLen, input,
                        inputLen);
    if (rv != SECSuccess && PORT_GetError() == SEC_ERROR_LIBRARY_FAILURE) {
        sftk_fatalError = PR_TRUE;
    }

    return rv;
}

static SECStatus
sftk_RSADecryptRaw(NSSLOWKEYPrivateKey *key, unsigned char *output,
                   unsigned int *outputLen, unsigned int maxLen,
                   const unsigned char *input, unsigned int inputLen)
{
    SECStatus rv = SECFailure;

    PORT_Assert(key->keyType == NSSLOWKEYRSAKey);
    if (key->keyType != NSSLOWKEYRSAKey) {
        PORT_SetError(SEC_ERROR_INVALID_KEY);
        return SECFailure;
    }

    rv = RSA_DecryptRaw(&key->u.rsa, output, outputLen, maxLen, input,
                        inputLen);
    if (rv != SECSuccess && PORT_GetError() == SEC_ERROR_LIBRARY_FAILURE) {
        sftk_fatalError = PR_TRUE;
    }

    return rv;
}

static SECStatus
sftk_RSAEncrypt(NSSLOWKEYPublicKey *key, unsigned char *output,
                unsigned int *outputLen, unsigned int maxLen,
                const unsigned char *input, unsigned int inputLen)
{
    SECStatus rv = SECFailure;

    PORT_Assert(key->keyType == NSSLOWKEYRSAKey);
    if (key->keyType != NSSLOWKEYRSAKey) {
        PORT_SetError(SEC_ERROR_INVALID_KEY);
        return SECFailure;
    }

    rv = RSA_EncryptBlock(&key->u.rsa, output, outputLen, maxLen, input,
                          inputLen);
    if (rv != SECSuccess && PORT_GetError() == SEC_ERROR_LIBRARY_FAILURE) {
        sftk_fatalError = PR_TRUE;
    }

    return rv;
}

static SECStatus
sftk_RSADecrypt(NSSLOWKEYPrivateKey *key, unsigned char *output,
                unsigned int *outputLen, unsigned int maxLen,
                const unsigned char *input, unsigned int inputLen)
{
    SECStatus rv = SECFailure;

    PORT_Assert(key->keyType == NSSLOWKEYRSAKey);
    if (key->keyType != NSSLOWKEYRSAKey) {
        PORT_SetError(SEC_ERROR_INVALID_KEY);
        return SECFailure;
    }

    rv = RSA_DecryptBlock(&key->u.rsa, output, outputLen, maxLen, input,
                          inputLen);
    if (rv != SECSuccess && PORT_GetError() == SEC_ERROR_LIBRARY_FAILURE) {
        sftk_fatalError = PR_TRUE;
    }

    return rv;
}

static SECStatus
sftk_RSAEncryptOAEP(SFTKOAEPEncryptInfo *info, unsigned char *output,
                    unsigned int *outputLen, unsigned int maxLen,
                    const unsigned char *input, unsigned int inputLen)
{
    HASH_HashType hashAlg;
    HASH_HashType maskHashAlg;

    PORT_Assert(info->key->keyType == NSSLOWKEYRSAKey);
    if (info->key->keyType != NSSLOWKEYRSAKey) {
        PORT_SetError(SEC_ERROR_INVALID_KEY);
        return SECFailure;
    }

    hashAlg = GetHashTypeFromMechanism(info->params->hashAlg);
    maskHashAlg = GetHashTypeFromMechanism(info->params->mgf);

    return RSA_EncryptOAEP(&info->key->u.rsa, hashAlg, maskHashAlg,
                           (const unsigned char *)info->params->pSourceData,
                           info->params->ulSourceDataLen, NULL, 0,
                           output, outputLen, maxLen, input, inputLen);
}

static SECStatus
sftk_RSADecryptOAEP(SFTKOAEPDecryptInfo *info, unsigned char *output,
                    unsigned int *outputLen, unsigned int maxLen,
                    const unsigned char *input, unsigned int inputLen)
{
    SECStatus rv = SECFailure;
    HASH_HashType hashAlg;
    HASH_HashType maskHashAlg;

    PORT_Assert(info->key->keyType == NSSLOWKEYRSAKey);
    if (info->key->keyType != NSSLOWKEYRSAKey) {
        PORT_SetError(SEC_ERROR_INVALID_KEY);
        return SECFailure;
    }

    hashAlg = GetHashTypeFromMechanism(info->params->hashAlg);
    maskHashAlg = GetHashTypeFromMechanism(info->params->mgf);

    rv = RSA_DecryptOAEP(&info->key->u.rsa, hashAlg, maskHashAlg,
                         (const unsigned char *)info->params->pSourceData,
                         info->params->ulSourceDataLen,
                         output, outputLen, maxLen, input, inputLen);
    if (rv != SECSuccess && PORT_GetError() == SEC_ERROR_LIBRARY_FAILURE) {
        sftk_fatalError = PR_TRUE;
    }
    return rv;
}

static SFTKChaCha20Poly1305Info *
sftk_ChaCha20Poly1305_CreateContext(const unsigned char *key,
                                    unsigned int keyLen,
                                    const CK_NSS_AEAD_PARAMS *params)
{
    SFTKChaCha20Poly1305Info *ctx;

    if (params->ulNonceLen != sizeof(ctx->nonce)) {
        PORT_SetError(SEC_ERROR_INPUT_LEN);
        return NULL;
    }

    ctx = PORT_New(SFTKChaCha20Poly1305Info);
    if (ctx == NULL) {
        return NULL;
    }

    if (ChaCha20Poly1305_InitContext(&ctx->freeblCtx, key, keyLen,
                                     params->ulTagLen) != SECSuccess) {
        PORT_Free(ctx);
        return NULL;
    }

    PORT_Memcpy(ctx->nonce, params->pNonce, sizeof(ctx->nonce));

    /* AAD data and length must both be null, or both non-null. */
    PORT_Assert((params->pAAD == NULL) == (params->ulAADLen == 0));

    if (params->ulAADLen > sizeof(ctx->ad)) {
        /* Need to allocate an overflow buffer for the additional data. */
        ctx->adOverflow = (unsigned char *)PORT_Alloc(params->ulAADLen);
        if (!ctx->adOverflow) {
            PORT_Free(ctx);
            return NULL;
        }
        PORT_Memcpy(ctx->adOverflow, params->pAAD, params->ulAADLen);
    } else {
        ctx->adOverflow = NULL;
        if (params->pAAD) {
            PORT_Memcpy(ctx->ad, params->pAAD, params->ulAADLen);
        }
    }
    ctx->adLen = params->ulAADLen;

    return ctx;
}

static void
sftk_ChaCha20Poly1305_DestroyContext(SFTKChaCha20Poly1305Info *ctx,
                                     PRBool freeit)
{
    ChaCha20Poly1305_DestroyContext(&ctx->freeblCtx, PR_FALSE);
    if (ctx->adOverflow != NULL) {
        PORT_Free(ctx->adOverflow);
        ctx->adOverflow = NULL;
    }
    ctx->adLen = 0;
    if (freeit) {
        PORT_Free(ctx);
    }
}

static SECStatus
sftk_ChaCha20Poly1305_Encrypt(const SFTKChaCha20Poly1305Info *ctx,
                              unsigned char *output, unsigned int *outputLen,
                              unsigned int maxOutputLen,
                              const unsigned char *input, unsigned int inputLen)
{
    const unsigned char *ad = ctx->adOverflow;

    if (ad == NULL) {
        ad = ctx->ad;
    }

    return ChaCha20Poly1305_Seal(&ctx->freeblCtx, output, outputLen,
                                 maxOutputLen, input, inputLen, ctx->nonce,
                                 sizeof(ctx->nonce), ad, ctx->adLen);
}

static SECStatus
sftk_ChaCha20Poly1305_Decrypt(const SFTKChaCha20Poly1305Info *ctx,
                              unsigned char *output, unsigned int *outputLen,
                              unsigned int maxOutputLen,
                              const unsigned char *input, unsigned int inputLen)
{
    const unsigned char *ad = ctx->adOverflow;

    if (ad == NULL) {
        ad = ctx->ad;
    }

    return ChaCha20Poly1305_Open(&ctx->freeblCtx, output, outputLen,
                                 maxOutputLen, input, inputLen, ctx->nonce,
                                 sizeof(ctx->nonce), ad, ctx->adLen);
}

/** NSC_CryptInit initializes an encryption/Decryption operation.
 *
 * Always called by NSC_EncryptInit, NSC_DecryptInit, NSC_WrapKey,NSC_UnwrapKey.
 * Called by NSC_SignInit, NSC_VerifyInit (via sftk_InitCBCMac) only for block
 *  ciphers MAC'ing.
 */
static CK_RV
sftk_CryptInit(CK_SESSION_HANDLE hSession, CK_MECHANISM_PTR pMechanism,
               CK_OBJECT_HANDLE hKey,
               CK_ATTRIBUTE_TYPE mechUsage, CK_ATTRIBUTE_TYPE keyUsage,
               SFTKContextType contextType, PRBool isEncrypt)
{
    SFTKSession *session;
    SFTKObject *key;
    SFTKSessionContext *context;
    SFTKAttribute *att;
    CK_RC2_CBC_PARAMS *rc2_param;
#if NSS_SOFTOKEN_DOES_RC5
    CK_RC5_CBC_PARAMS *rc5_param;
    SECItem rc5Key;
#endif
    CK_KEY_TYPE key_type;
    CK_RV crv = CKR_OK;
    unsigned effectiveKeyLength;
    unsigned char newdeskey[24];
    PRBool useNewKey = PR_FALSE;
    int t;

    crv = sftk_MechAllowsOperation(pMechanism->mechanism, mechUsage);
    if (crv != CKR_OK)
        return crv;

    session = sftk_SessionFromHandle(hSession);
    if (session == NULL)
        return CKR_SESSION_HANDLE_INVALID;

    crv = sftk_InitGeneric(session, &context, contextType, &key, hKey, &key_type,
                           isEncrypt ? CKO_PUBLIC_KEY : CKO_PRIVATE_KEY, keyUsage);

    if (crv != CKR_OK) {
        sftk_FreeSession(session);
        return crv;
    }

    context->doPad = PR_FALSE;
    switch (pMechanism->mechanism) {
        case CKM_RSA_PKCS:
        case CKM_RSA_X_509:
            if (key_type != CKK_RSA) {
                crv = CKR_KEY_TYPE_INCONSISTENT;
                break;
            }
            context->multi = PR_FALSE;
            context->rsa = PR_TRUE;
            if (isEncrypt) {
                NSSLOWKEYPublicKey *pubKey = sftk_GetPubKey(key, CKK_RSA, &crv);
                if (pubKey == NULL) {
                    crv = CKR_KEY_HANDLE_INVALID;
                    break;
                }
                context->maxLen = nsslowkey_PublicModulusLen(pubKey);
                context->cipherInfo = (void *)pubKey;
                context->update = (SFTKCipher)(pMechanism->mechanism == CKM_RSA_X_509
                                                   ? sftk_RSAEncryptRaw
                                                   : sftk_RSAEncrypt);
            } else {
                NSSLOWKEYPrivateKey *privKey = sftk_GetPrivKey(key, CKK_RSA, &crv);
                if (privKey == NULL) {
                    crv = CKR_KEY_HANDLE_INVALID;
                    break;
                }
                context->maxLen = nsslowkey_PrivateModulusLen(privKey);
                context->cipherInfo = (void *)privKey;
                context->update = (SFTKCipher)(pMechanism->mechanism == CKM_RSA_X_509
                                                   ? sftk_RSADecryptRaw
                                                   : sftk_RSADecrypt);
            }
            context->destroy = sftk_Null;
            break;
        case CKM_RSA_PKCS_OAEP:
            if (key_type != CKK_RSA) {
                crv = CKR_KEY_TYPE_INCONSISTENT;
                break;
            }
            if (pMechanism->ulParameterLen != sizeof(CK_RSA_PKCS_OAEP_PARAMS) ||
                !sftk_ValidateOaepParams((CK_RSA_PKCS_OAEP_PARAMS *)pMechanism->pParameter)) {
                crv = CKR_MECHANISM_PARAM_INVALID;
                break;
            }
            context->multi = PR_FALSE;
            context->rsa = PR_TRUE;
            if (isEncrypt) {
                SFTKOAEPEncryptInfo *info = PORT_New(SFTKOAEPEncryptInfo);
                if (info == NULL) {
                    crv = CKR_HOST_MEMORY;
                    break;
                }
                info->params = pMechanism->pParameter;
                info->key = sftk_GetPubKey(key, CKK_RSA, &crv);
                if (info->key == NULL) {
                    PORT_Free(info);
                    crv = CKR_KEY_HANDLE_INVALID;
                    break;
                }
                context->update = (SFTKCipher)sftk_RSAEncryptOAEP;
                context->maxLen = nsslowkey_PublicModulusLen(info->key);
                context->cipherInfo = info;
            } else {
                SFTKOAEPDecryptInfo *info = PORT_New(SFTKOAEPDecryptInfo);
                if (info == NULL) {
                    crv = CKR_HOST_MEMORY;
                    break;
                }
                info->params = pMechanism->pParameter;
                info->key = sftk_GetPrivKey(key, CKK_RSA, &crv);
                if (info->key == NULL) {
                    PORT_Free(info);
                    crv = CKR_KEY_HANDLE_INVALID;
                    break;
                }
                context->update = (SFTKCipher)sftk_RSADecryptOAEP;
                context->maxLen = nsslowkey_PrivateModulusLen(info->key);
                context->cipherInfo = info;
            }
            context->destroy = (SFTKDestroy)sftk_Space;
            break;
        case CKM_RC2_CBC_PAD:
            context->doPad = PR_TRUE;
        /* fall thru */
        case CKM_RC2_ECB:
        case CKM_RC2_CBC:
            context->blockSize = 8;
            if (key_type != CKK_RC2) {
                crv = CKR_KEY_TYPE_INCONSISTENT;
                break;
            }
            att = sftk_FindAttribute(key, CKA_VALUE);
            if (att == NULL) {
                crv = CKR_KEY_HANDLE_INVALID;
                break;
            }
            rc2_param = (CK_RC2_CBC_PARAMS *)pMechanism->pParameter;
            effectiveKeyLength = (rc2_param->ulEffectiveBits + 7) / 8;
            context->cipherInfo =
                RC2_CreateContext((unsigned char *)att->attrib.pValue,
                                  att->attrib.ulValueLen, rc2_param->iv,
                                  pMechanism->mechanism == CKM_RC2_ECB ? NSS_RC2 : NSS_RC2_CBC, effectiveKeyLength);
            sftk_FreeAttribute(att);
            if (context->cipherInfo == NULL) {
                crv = CKR_HOST_MEMORY;
                break;
            }
            context->update = (SFTKCipher)(isEncrypt ? RC2_Encrypt : RC2_Decrypt);
            context->destroy = (SFTKDestroy)RC2_DestroyContext;
            break;
#if NSS_SOFTOKEN_DOES_RC5
        case CKM_RC5_CBC_PAD:
            context->doPad = PR_TRUE;
        /* fall thru */
        case CKM_RC5_ECB:
        case CKM_RC5_CBC:
            if (key_type != CKK_RC5) {
                crv = CKR_KEY_TYPE_INCONSISTENT;
                break;
            }
            att = sftk_FindAttribute(key, CKA_VALUE);
            if (att == NULL) {
                crv = CKR_KEY_HANDLE_INVALID;
                break;
            }
            rc5_param = (CK_RC5_CBC_PARAMS *)pMechanism->pParameter;
            context->blockSize = rc5_param->ulWordsize * 2;
            rc5Key.data = (unsigned char *)att->attrib.pValue;
            rc5Key.len = att->attrib.ulValueLen;
            context->cipherInfo = RC5_CreateContext(&rc5Key, rc5_param->ulRounds,
                                                    rc5_param->ulWordsize, rc5_param->pIv,
                                                    pMechanism->mechanism == CKM_RC5_ECB ? NSS_RC5 : NSS_RC5_CBC);
            sftk_FreeAttribute(att);
            if (context->cipherInfo == NULL) {
                crv = CKR_HOST_MEMORY;
                break;
            }
            context->update = (SFTKCipher)(isEncrypt ? RC5_Encrypt : RC5_Decrypt);
            context->destroy = (SFTKDestroy)RC5_DestroyContext;
            break;
#endif
        case CKM_RC4:
            if (key_type != CKK_RC4) {
                crv = CKR_KEY_TYPE_INCONSISTENT;
                break;
            }
            att = sftk_FindAttribute(key, CKA_VALUE);
            if (att == NULL) {
                crv = CKR_KEY_HANDLE_INVALID;
                break;
            }
            context->cipherInfo =
                RC4_CreateContext((unsigned char *)att->attrib.pValue,
                                  att->attrib.ulValueLen);
            sftk_FreeAttribute(att);
            if (context->cipherInfo == NULL) {
                crv = CKR_HOST_MEMORY; /* WRONG !!! */
                break;
            }
            context->update = (SFTKCipher)(isEncrypt ? RC4_Encrypt : RC4_Decrypt);
            context->destroy = (SFTKDestroy)RC4_DestroyContext;
            break;
        case CKM_CDMF_CBC_PAD:
            context->doPad = PR_TRUE;
        /* fall thru */
        case CKM_CDMF_ECB:
        case CKM_CDMF_CBC:
            if (key_type != CKK_CDMF) {
                crv = CKR_KEY_TYPE_INCONSISTENT;
                break;
            }
            t = (pMechanism->mechanism == CKM_CDMF_ECB) ? NSS_DES : NSS_DES_CBC;
            goto finish_des;
        case CKM_DES_ECB:
            if (key_type != CKK_DES) {
                crv = CKR_KEY_TYPE_INCONSISTENT;
                break;
            }
            t = NSS_DES;
            goto finish_des;
        case CKM_DES_CBC_PAD:
            context->doPad = PR_TRUE;
        /* fall thru */
        case CKM_DES_CBC:
            if (key_type != CKK_DES) {
                crv = CKR_KEY_TYPE_INCONSISTENT;
                break;
            }
            t = NSS_DES_CBC;
            goto finish_des;
        case CKM_DES3_ECB:
            if ((key_type != CKK_DES2) && (key_type != CKK_DES3)) {
                crv = CKR_KEY_TYPE_INCONSISTENT;
                break;
            }
            t = NSS_DES_EDE3;
            goto finish_des;
        case CKM_DES3_CBC_PAD:
            context->doPad = PR_TRUE;
        /* fall thru */
        case CKM_DES3_CBC:
            if ((key_type != CKK_DES2) && (key_type != CKK_DES3)) {
                crv = CKR_KEY_TYPE_INCONSISTENT;
                break;
            }
            t = NSS_DES_EDE3_CBC;
        finish_des:
            context->blockSize = 8;
            att = sftk_FindAttribute(key, CKA_VALUE);
            if (att == NULL) {
                crv = CKR_KEY_HANDLE_INVALID;
                break;
            }
            if (key_type == CKK_DES2 &&
                (t == NSS_DES_EDE3_CBC || t == NSS_DES_EDE3)) {
                /* extend DES2 key to DES3 key. */
                memcpy(newdeskey, att->attrib.pValue, 16);
                memcpy(newdeskey + 16, newdeskey, 8);
                useNewKey = PR_TRUE;
            } else if (key_type == CKK_CDMF) {
                crv = sftk_cdmf2des((unsigned char *)att->attrib.pValue, newdeskey);
                if (crv != CKR_OK) {
                    sftk_FreeAttribute(att);
                    break;
                }
                useNewKey = PR_TRUE;
            }
            context->cipherInfo = DES_CreateContext(
                useNewKey ? newdeskey : (unsigned char *)att->attrib.pValue,
                (unsigned char *)pMechanism->pParameter, t, isEncrypt);
            if (useNewKey)
                memset(newdeskey, 0, sizeof newdeskey);
            sftk_FreeAttribute(att);
            if (context->cipherInfo == NULL) {
                crv = CKR_HOST_MEMORY;
                break;
            }
            context->update = (SFTKCipher)(isEncrypt ? DES_Encrypt : DES_Decrypt);
            context->destroy = (SFTKDestroy)DES_DestroyContext;
            break;
        case CKM_SEED_CBC_PAD:
            context->doPad = PR_TRUE;
        /* fall thru */
        case CKM_SEED_CBC:
            if (!pMechanism->pParameter ||
                pMechanism->ulParameterLen != 16) {
                crv = CKR_MECHANISM_PARAM_INVALID;
                break;
            }
        /* fall thru */
        case CKM_SEED_ECB:
            context->blockSize = 16;
            if (key_type != CKK_SEED) {
                crv = CKR_KEY_TYPE_INCONSISTENT;
                break;
            }
            att = sftk_FindAttribute(key, CKA_VALUE);
            if (att == NULL) {
                crv = CKR_KEY_HANDLE_INVALID;
                break;
            }
            context->cipherInfo = SEED_CreateContext(
                (unsigned char *)att->attrib.pValue,
                (unsigned char *)pMechanism->pParameter,
                pMechanism->mechanism == CKM_SEED_ECB ? NSS_SEED : NSS_SEED_CBC,
                isEncrypt);
            sftk_FreeAttribute(att);
            if (context->cipherInfo == NULL) {
                crv = CKR_HOST_MEMORY;
                break;
            }
            context->update = (SFTKCipher)(isEncrypt ? SEED_Encrypt : SEED_Decrypt);
            context->destroy = (SFTKDestroy)SEED_DestroyContext;
            break;

        case CKM_CAMELLIA_CBC_PAD:
            context->doPad = PR_TRUE;
        /* fall thru */
        case CKM_CAMELLIA_CBC:
            if (!pMechanism->pParameter ||
                pMechanism->ulParameterLen != 16) {
                crv = CKR_MECHANISM_PARAM_INVALID;
                break;
            }
        /* fall thru */
        case CKM_CAMELLIA_ECB:
            context->blockSize = 16;
            if (key_type != CKK_CAMELLIA) {
                crv = CKR_KEY_TYPE_INCONSISTENT;
                break;
            }
            att = sftk_FindAttribute(key, CKA_VALUE);
            if (att == NULL) {
                crv = CKR_KEY_HANDLE_INVALID;
                break;
            }
            context->cipherInfo = Camellia_CreateContext(
                (unsigned char *)att->attrib.pValue,
                (unsigned char *)pMechanism->pParameter,
                pMechanism->mechanism ==
                        CKM_CAMELLIA_ECB
                    ? NSS_CAMELLIA
                    : NSS_CAMELLIA_CBC,
                isEncrypt, att->attrib.ulValueLen);
            sftk_FreeAttribute(att);
            if (context->cipherInfo == NULL) {
                crv = CKR_HOST_MEMORY;
                break;
            }
            context->update = (SFTKCipher)(isEncrypt ? Camellia_Encrypt : Camellia_Decrypt);
            context->destroy = (SFTKDestroy)Camellia_DestroyContext;
            break;

        case CKM_AES_CBC_PAD:
            context->doPad = PR_TRUE;
        /* fall thru */
        case CKM_AES_ECB:
        case CKM_AES_CBC:
            context->blockSize = 16;
        case CKM_AES_CTS:
        case CKM_AES_CTR:
        case CKM_AES_GCM:
            if (pMechanism->mechanism == CKM_AES_GCM) {
                context->multi = PR_FALSE;
            }
            if (key_type != CKK_AES) {
                crv = CKR_KEY_TYPE_INCONSISTENT;
                break;
            }
            att = sftk_FindAttribute(key, CKA_VALUE);
            if (att == NULL) {
                crv = CKR_KEY_HANDLE_INVALID;
                break;
            }
            context->cipherInfo = AES_CreateContext(
                (unsigned char *)att->attrib.pValue,
                (unsigned char *)pMechanism->pParameter,
                sftk_aes_mode(pMechanism->mechanism),
                isEncrypt, att->attrib.ulValueLen, 16);
            sftk_FreeAttribute(att);
            if (context->cipherInfo == NULL) {
                crv = CKR_HOST_MEMORY;
                break;
            }
            context->update = (SFTKCipher)(isEncrypt ? AES_Encrypt : AES_Decrypt);
            context->destroy = (SFTKDestroy)AES_DestroyContext;
            break;

        case CKM_NSS_CHACHA20_POLY1305:
            if (pMechanism->ulParameterLen != sizeof(CK_NSS_AEAD_PARAMS)) {
                crv = CKR_MECHANISM_PARAM_INVALID;
                break;
            }
            context->multi = PR_FALSE;
            if (key_type != CKK_NSS_CHACHA20) {
                crv = CKR_KEY_TYPE_INCONSISTENT;
                break;
            }
            att = sftk_FindAttribute(key, CKA_VALUE);
            if (att == NULL) {
                crv = CKR_KEY_HANDLE_INVALID;
                break;
            }
            context->cipherInfo = sftk_ChaCha20Poly1305_CreateContext(
                (unsigned char *)att->attrib.pValue, att->attrib.ulValueLen,
                (CK_NSS_AEAD_PARAMS *)pMechanism->pParameter);
            sftk_FreeAttribute(att);
            if (context->cipherInfo == NULL) {
                crv = sftk_MapCryptError(PORT_GetError());
                break;
            }
            context->update = (SFTKCipher)(isEncrypt ? sftk_ChaCha20Poly1305_Encrypt : sftk_ChaCha20Poly1305_Decrypt);
            context->destroy = (SFTKDestroy)sftk_ChaCha20Poly1305_DestroyContext;
            break;

        case CKM_NSS_AES_KEY_WRAP_PAD:
            context->doPad = PR_TRUE;
        /* fall thru */
        case CKM_NSS_AES_KEY_WRAP:
            context->multi = PR_FALSE;
            context->blockSize = 8;
            if (key_type != CKK_AES) {
                crv = CKR_KEY_TYPE_INCONSISTENT;
                break;
            }
            att = sftk_FindAttribute(key, CKA_VALUE);
            if (att == NULL) {
                crv = CKR_KEY_HANDLE_INVALID;
                break;
            }
            context->cipherInfo = AESKeyWrap_CreateContext(
                (unsigned char *)att->attrib.pValue,
                (unsigned char *)pMechanism->pParameter,
                isEncrypt, att->attrib.ulValueLen);
            sftk_FreeAttribute(att);
            if (context->cipherInfo == NULL) {
                crv = CKR_HOST_MEMORY;
                break;
            }
            context->update = (SFTKCipher)(isEncrypt ? AESKeyWrap_Encrypt
                                                     : AESKeyWrap_Decrypt);
            context->destroy = (SFTKDestroy)AESKeyWrap_DestroyContext;
            break;

        default:
            crv = CKR_MECHANISM_INVALID;
            break;
    }

    if (crv != CKR_OK) {
        sftk_FreeContext(context);
        sftk_FreeSession(session);
        return crv;
    }
    sftk_SetContextByType(session, contextType, context);
    sftk_FreeSession(session);
    return CKR_OK;
}

/* NSC_EncryptInit initializes an encryption operation. */
CK_RV
NSC_EncryptInit(CK_SESSION_HANDLE hSession,
                CK_MECHANISM_PTR pMechanism, CK_OBJECT_HANDLE hKey)
{
    CHECK_FORK();
    return sftk_CryptInit(hSession, pMechanism, hKey, CKA_ENCRYPT, CKA_ENCRYPT,
                          SFTK_ENCRYPT, PR_TRUE);
}

/* NSC_EncryptUpdate continues a multiple-part encryption operation. */
CK_RV
NSC_EncryptUpdate(CK_SESSION_HANDLE hSession,
                  CK_BYTE_PTR pPart, CK_ULONG ulPartLen, CK_BYTE_PTR pEncryptedPart,
                  CK_ULONG_PTR pulEncryptedPartLen)
{
    SFTKSessionContext *context;
    unsigned int outlen, i;
    unsigned int padoutlen = 0;
    unsigned int maxout = *pulEncryptedPartLen;
    CK_RV crv;
    SECStatus rv;

    CHECK_FORK();

    /* make sure we're legal */
    crv = sftk_GetContext(hSession, &context, SFTK_ENCRYPT, PR_TRUE, NULL);
    if (crv != CKR_OK)
        return crv;

    if (!pEncryptedPart) {
        if (context->doPad) {
            CK_ULONG totalDataAvailable = ulPartLen + context->padDataLength;
            CK_ULONG blocksToSend = totalDataAvailable / context->blockSize;

            *pulEncryptedPartLen = blocksToSend * context->blockSize;
            return CKR_OK;
        }
        *pulEncryptedPartLen = ulPartLen;
        return CKR_OK;
    }

    /* do padding */
    if (context->doPad) {
        /* deal with previous buffered data */
        if (context->padDataLength != 0) {
            /* fill in the padded to a full block size */
            for (i = context->padDataLength;
                 (ulPartLen != 0) && i < context->blockSize; i++) {
                context->padBuf[i] = *pPart++;
                ulPartLen--;
                context->padDataLength++;
            }

            /* not enough data to encrypt yet? then return */
            if (context->padDataLength != context->blockSize) {
                *pulEncryptedPartLen = 0;
                return CKR_OK;
            }
            /* encrypt the current padded data */
            rv = (*context->update)(context->cipherInfo, pEncryptedPart,
                                    &padoutlen, context->blockSize, context->padBuf,
                                    context->blockSize);
            if (rv != SECSuccess) {
                return sftk_MapCryptError(PORT_GetError());
            }
            pEncryptedPart += padoutlen;
            maxout -= padoutlen;
        }
        /* save the residual */
        context->padDataLength = ulPartLen % context->blockSize;
        if (context->padDataLength) {
            PORT_Memcpy(context->padBuf,
                        &pPart[ulPartLen - context->padDataLength],
                        context->padDataLength);
            ulPartLen -= context->padDataLength;
        }
        /* if we've exhausted our new buffer, we're done */
        if (ulPartLen == 0) {
            *pulEncryptedPartLen = padoutlen;
            return CKR_OK;
        }
    }

    /* do it: NOTE: this assumes buf size in is >= buf size out! */
    rv = (*context->update)(context->cipherInfo, pEncryptedPart,
                            &outlen, maxout, pPart, ulPartLen);
    *pulEncryptedPartLen = (CK_ULONG)(outlen + padoutlen);
    return (rv == SECSuccess) ? CKR_OK : sftk_MapCryptError(PORT_GetError());
}

/* NSC_EncryptFinal finishes a multiple-part encryption operation. */
CK_RV
NSC_EncryptFinal(CK_SESSION_HANDLE hSession,
                 CK_BYTE_PTR pLastEncryptedPart, CK_ULONG_PTR pulLastEncryptedPartLen)
{
    SFTKSession *session;
    SFTKSessionContext *context;
    unsigned int outlen, i;
    unsigned int maxout = *pulLastEncryptedPartLen;
    CK_RV crv;
    SECStatus rv = SECSuccess;
    PRBool contextFinished = PR_TRUE;

    CHECK_FORK();

    /* make sure we're legal */
    crv = sftk_GetContext(hSession, &context, SFTK_ENCRYPT, PR_TRUE, &session);
    if (crv != CKR_OK)
        return crv;

    *pulLastEncryptedPartLen = 0;
    if (!pLastEncryptedPart) {
        /* caller is checking the amount of remaining data */
        if (context->blockSize > 0 && context->doPad) {
            *pulLastEncryptedPartLen = context->blockSize;
            contextFinished = PR_FALSE; /* still have padding to go */
        }
        goto finish;
    }

    /* do padding */
    if (context->doPad) {
        unsigned char padbyte = (unsigned char)(context->blockSize - context->padDataLength);
        /* fill out rest of pad buffer with pad magic*/
        for (i = context->padDataLength; i < context->blockSize; i++) {
            context->padBuf[i] = padbyte;
        }
        rv = (*context->update)(context->cipherInfo, pLastEncryptedPart,
                                &outlen, maxout, context->padBuf, context->blockSize);
        if (rv == SECSuccess)
            *pulLastEncryptedPartLen = (CK_ULONG)outlen;
    }

finish:
    if (contextFinished)
        sftk_TerminateOp(session, SFTK_ENCRYPT, context);
    sftk_FreeSession(session);
    return (rv == SECSuccess) ? CKR_OK : sftk_MapCryptError(PORT_GetError());
}

/* NSC_Encrypt encrypts single-part data. */
CK_RV
NSC_Encrypt(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pData,
            CK_ULONG ulDataLen, CK_BYTE_PTR pEncryptedData,
            CK_ULONG_PTR pulEncryptedDataLen)
{
    SFTKSession *session;
    SFTKSessionContext *context;
    unsigned int outlen;
    unsigned int maxoutlen = *pulEncryptedDataLen;
    CK_RV crv;
    CK_RV crv2;
    SECStatus rv = SECSuccess;
    SECItem pText;

    pText.type = siBuffer;
    pText.data = pData;
    pText.len = ulDataLen;

    CHECK_FORK();

    /* make sure we're legal */
    crv = sftk_GetContext(hSession, &context, SFTK_ENCRYPT, PR_FALSE, &session);
    if (crv != CKR_OK)
        return crv;

    if (!pEncryptedData) {
        *pulEncryptedDataLen = context->rsa ? context->maxLen : ulDataLen + 2 * context->blockSize;
        goto finish;
    }

    if (context->doPad) {
        if (context->multi) {
            CK_ULONG finalLen;
            /* padding is fairly complicated, have the update and final
             * code deal with it */
            sftk_FreeSession(session);
            crv = NSC_EncryptUpdate(hSession, pData, ulDataLen, pEncryptedData,
                                    pulEncryptedDataLen);
            if (crv != CKR_OK)
                *pulEncryptedDataLen = 0;
            maxoutlen -= *pulEncryptedDataLen;
            pEncryptedData += *pulEncryptedDataLen;
            finalLen = maxoutlen;
            crv2 = NSC_EncryptFinal(hSession, pEncryptedData, &finalLen);
            if (crv2 == CKR_OK)
                *pulEncryptedDataLen += finalLen;
            return crv == CKR_OK ? crv2 : crv;
        }
        /* doPad without multi means that padding must be done on the first
        ** and only update.  There will be no final.
        */
        PORT_Assert(context->blockSize > 1);
        if (context->blockSize > 1) {
            CK_ULONG remainder = ulDataLen % context->blockSize;
            CK_ULONG padding = context->blockSize - remainder;
            pText.len += padding;
            pText.data = PORT_ZAlloc(pText.len);
            if (pText.data) {
                memcpy(pText.data, pData, ulDataLen);
                memset(pText.data + ulDataLen, padding, padding);
            } else {
                crv = CKR_HOST_MEMORY;
                goto fail;
            }
        }
    }

    /* do it: NOTE: this assumes buf size is big enough. */
    rv = (*context->update)(context->cipherInfo, pEncryptedData,
                            &outlen, maxoutlen, pText.data, pText.len);
    crv = (rv == SECSuccess) ? CKR_OK : sftk_MapCryptError(PORT_GetError());
    *pulEncryptedDataLen = (CK_ULONG)outlen;
    if (pText.data != pData)
        PORT_ZFree(pText.data, pText.len);
fail:
    sftk_TerminateOp(session, SFTK_ENCRYPT, context);
finish:
    sftk_FreeSession(session);

    return crv;
}

/*
 ************** Crypto Functions:     Decrypt ************************
 */

/* NSC_DecryptInit initializes a decryption operation. */
CK_RV
NSC_DecryptInit(CK_SESSION_HANDLE hSession,
                CK_MECHANISM_PTR pMechanism, CK_OBJECT_HANDLE hKey)
{
    CHECK_FORK();
    return sftk_CryptInit(hSession, pMechanism, hKey, CKA_DECRYPT, CKA_DECRYPT,
                          SFTK_DECRYPT, PR_FALSE);
}

/* NSC_DecryptUpdate continues a multiple-part decryption operation. */
CK_RV
NSC_DecryptUpdate(CK_SESSION_HANDLE hSession,
                  CK_BYTE_PTR pEncryptedPart, CK_ULONG ulEncryptedPartLen,
                  CK_BYTE_PTR pPart, CK_ULONG_PTR pulPartLen)
{
    SFTKSessionContext *context;
    unsigned int padoutlen = 0;
    unsigned int outlen;
    unsigned int maxout = *pulPartLen;
    CK_RV crv;
    SECStatus rv;

    CHECK_FORK();

    /* make sure we're legal */
    crv = sftk_GetContext(hSession, &context, SFTK_DECRYPT, PR_TRUE, NULL);
    if (crv != CKR_OK)
        return crv;

    /* this can only happen on an NSS programming error */
    PORT_Assert((context->padDataLength == 0) || context->padDataLength == context->blockSize);

    if (context->doPad) {
        /* Check the data length for block ciphers. If we are padding,
         * then we must be using a block cipher. In the non-padding case
         * the error will be returned by the underlying decryption
         * function when we do the actual decrypt. We need to do the
         * check here to avoid returning a negative length to the caller
         * or reading before the beginning of the pEncryptedPart buffer.
         */
        if ((ulEncryptedPartLen == 0) ||
            (ulEncryptedPartLen % context->blockSize) != 0) {
            return CKR_ENCRYPTED_DATA_LEN_RANGE;
        }
    }

    if (!pPart) {
        if (context->doPad) {
            *pulPartLen =
                ulEncryptedPartLen + context->padDataLength - context->blockSize;
            return CKR_OK;
        }
        /* for stream ciphers there is are no constraints on ulEncryptedPartLen.
         * for block ciphers, it must be a multiple of blockSize. The error is
         * detected when this function is called again do decrypt the output.
         */
        *pulPartLen = ulEncryptedPartLen;
        return CKR_OK;
    }

    if (context->doPad) {
        /* first decrypt our saved buffer */
        if (context->padDataLength != 0) {
            rv = (*context->update)(context->cipherInfo, pPart, &padoutlen,
                                    maxout, context->padBuf, context->blockSize);
            if (rv != SECSuccess)
                return sftk_MapDecryptError(PORT_GetError());
            pPart += padoutlen;
            maxout -= padoutlen;
        }
        /* now save the final block for the next decrypt or the final */
        PORT_Memcpy(context->padBuf, &pEncryptedPart[ulEncryptedPartLen -
                                                     context->blockSize],
                    context->blockSize);
        context->padDataLength = context->blockSize;
        ulEncryptedPartLen -= context->padDataLength;
    }

    /* do it: NOTE: this assumes buf size in is >= buf size out! */
    rv = (*context->update)(context->cipherInfo, pPart, &outlen,
                            maxout, pEncryptedPart, ulEncryptedPartLen);
    *pulPartLen = (CK_ULONG)(outlen + padoutlen);
    return (rv == SECSuccess) ? CKR_OK : sftk_MapDecryptError(PORT_GetError());
}

/* NSC_DecryptFinal finishes a multiple-part decryption operation. */
CK_RV
NSC_DecryptFinal(CK_SESSION_HANDLE hSession,
                 CK_BYTE_PTR pLastPart, CK_ULONG_PTR pulLastPartLen)
{
    SFTKSession *session;
    SFTKSessionContext *context;
    unsigned int outlen;
    unsigned int maxout = *pulLastPartLen;
    CK_RV crv;
    SECStatus rv = SECSuccess;

    CHECK_FORK();

    /* make sure we're legal */
    crv = sftk_GetContext(hSession, &context, SFTK_DECRYPT, PR_TRUE, &session);
    if (crv != CKR_OK)
        return crv;

    *pulLastPartLen = 0;
    if (!pLastPart) {
        /* caller is checking the amount of remaining data */
        if (context->padDataLength > 0) {
            *pulLastPartLen = context->padDataLength;
        }
        goto finish;
    }

    if (context->doPad) {
        /* decrypt our saved buffer */
        if (context->padDataLength != 0) {
            /* this assumes that pLastPart is big enough to hold the *whole*
             * buffer!!! */
            rv = (*context->update)(context->cipherInfo, pLastPart, &outlen,
                                    maxout, context->padBuf, context->blockSize);
            if (rv != SECSuccess) {
                crv = sftk_MapDecryptError(PORT_GetError());
            } else {
                unsigned int padSize =
                    (unsigned int)pLastPart[context->blockSize - 1];
                if ((padSize > context->blockSize) || (padSize == 0)) {
                    crv = CKR_ENCRYPTED_DATA_INVALID;
                } else {
                    unsigned int i;
                    unsigned int badPadding = 0; /* used as a boolean */
                    for (i = 0; i < padSize; i++) {
                        badPadding |=
                            (unsigned int)pLastPart[context->blockSize - 1 - i] ^
                            padSize;
                    }
                    if (badPadding) {
                        crv = CKR_ENCRYPTED_DATA_INVALID;
                    } else {
                        *pulLastPartLen = outlen - padSize;
                    }
                }
            }
        }
    }

    sftk_TerminateOp(session, SFTK_DECRYPT, context);
finish:
    sftk_FreeSession(session);
    return crv;
}

/* NSC_Decrypt decrypts encrypted data in a single part. */
CK_RV
NSC_Decrypt(CK_SESSION_HANDLE hSession,
            CK_BYTE_PTR pEncryptedData, CK_ULONG ulEncryptedDataLen, CK_BYTE_PTR pData,
            CK_ULONG_PTR pulDataLen)
{
    SFTKSession *session;
    SFTKSessionContext *context;
    unsigned int outlen;
    unsigned int maxoutlen = *pulDataLen;
    CK_RV crv;
    CK_RV crv2;
    SECStatus rv = SECSuccess;

    CHECK_FORK();

    /* make sure we're legal */
    crv = sftk_GetContext(hSession, &context, SFTK_DECRYPT, PR_FALSE, &session);
    if (crv != CKR_OK)
        return crv;

    if (!pData) {
        *pulDataLen = ulEncryptedDataLen + context->blockSize;
        goto finish;
    }

    if (context->doPad && context->multi) {
        CK_ULONG finalLen;
        /* padding is fairly complicated, have the update and final
         * code deal with it */
        sftk_FreeSession(session);
        crv = NSC_DecryptUpdate(hSession, pEncryptedData, ulEncryptedDataLen,
                                pData, pulDataLen);
        if (crv != CKR_OK)
            *pulDataLen = 0;
        maxoutlen -= *pulDataLen;
        pData += *pulDataLen;
        finalLen = maxoutlen;
        crv2 = NSC_DecryptFinal(hSession, pData, &finalLen);
        if (crv2 == CKR_OK)
            *pulDataLen += finalLen;
        return crv == CKR_OK ? crv2 : crv;
    }

    rv = (*context->update)(context->cipherInfo, pData, &outlen, maxoutlen,
                            pEncryptedData, ulEncryptedDataLen);
    /* XXX need to do MUCH better error mapping than this. */
    crv = (rv == SECSuccess) ? CKR_OK : sftk_MapDecryptError(PORT_GetError());
    if (rv == SECSuccess && context->doPad) {
        unsigned int padding = pData[outlen - 1];
        if (padding > context->blockSize || !padding) {
            crv = CKR_ENCRYPTED_DATA_INVALID;
        } else {
            unsigned int i;
            unsigned int badPadding = 0; /* used as a boolean */
            for (i = 0; i < padding; i++) {
                badPadding |= (unsigned int)pData[outlen - 1 - i] ^ padding;
            }
            if (badPadding) {
                crv = CKR_ENCRYPTED_DATA_INVALID;
            } else {
                outlen -= padding;
            }
        }
    }
    *pulDataLen = (CK_ULONG)outlen;
    sftk_TerminateOp(session, SFTK_DECRYPT, context);
finish:
    sftk_FreeSession(session);
    return crv;
}

/*
 ************** Crypto Functions:     Digest (HASH)  ************************
 */

/* NSC_DigestInit initializes a message-digesting operation. */
CK_RV
NSC_DigestInit(CK_SESSION_HANDLE hSession,
               CK_MECHANISM_PTR pMechanism)
{
    SFTKSession *session;
    SFTKSessionContext *context;
    CK_RV crv = CKR_OK;

    CHECK_FORK();

    session = sftk_SessionFromHandle(hSession);
    if (session == NULL)
        return CKR_SESSION_HANDLE_INVALID;
    crv = sftk_InitGeneric(session, &context, SFTK_HASH, NULL, 0, NULL, 0, 0);
    if (crv != CKR_OK) {
        sftk_FreeSession(session);
        return crv;
    }

#define INIT_MECH(mech, mmm)                                   \
    case mech: {                                               \
        mmm##Context *mmm##_ctx = mmm##_NewContext();          \
        context->cipherInfo = (void *)mmm##_ctx;               \
        context->cipherInfoLen = mmm##_FlattenSize(mmm##_ctx); \
        context->currentMech = mech;                           \
        context->hashUpdate = (SFTKHash)mmm##_Update;          \
        context->end = (SFTKEnd)mmm##_End;                     \
        context->destroy = (SFTKDestroy)mmm##_DestroyContext;  \
        context->maxLen = mmm##_LENGTH;                        \
        if (mmm##_ctx)                                         \
            mmm##_Begin(mmm##_ctx);                            \
        else                                                   \
            crv = CKR_HOST_MEMORY;                             \
        break;                                                 \
    }

    switch (pMechanism->mechanism) {
        INIT_MECH(CKM_MD2, MD2)
        INIT_MECH(CKM_MD5, MD5)
        INIT_MECH(CKM_SHA_1, SHA1)
        INIT_MECH(CKM_SHA224, SHA224)
        INIT_MECH(CKM_SHA256, SHA256)
        INIT_MECH(CKM_SHA384, SHA384)
        INIT_MECH(CKM_SHA512, SHA512)

        default:
            crv = CKR_MECHANISM_INVALID;
            break;
    }

    if (crv != CKR_OK) {
        sftk_FreeContext(context);
        sftk_FreeSession(session);
        return crv;
    }
    sftk_SetContextByType(session, SFTK_HASH, context);
    sftk_FreeSession(session);
    return CKR_OK;
}

/* NSC_Digest digests data in a single part. */
CK_RV
NSC_Digest(CK_SESSION_HANDLE hSession,
           CK_BYTE_PTR pData, CK_ULONG ulDataLen, CK_BYTE_PTR pDigest,
           CK_ULONG_PTR pulDigestLen)
{
    SFTKSession *session;
    SFTKSessionContext *context;
    unsigned int digestLen;
    unsigned int maxout = *pulDigestLen;
    CK_RV crv;

    CHECK_FORK();

    /* make sure we're legal */
    crv = sftk_GetContext(hSession, &context, SFTK_HASH, PR_FALSE, &session);
    if (crv != CKR_OK)
        return crv;

    if (pDigest == NULL) {
        *pulDigestLen = context->maxLen;
        goto finish;
    }

    /* do it: */
    (*context->hashUpdate)(context->cipherInfo, pData, ulDataLen);
    /*  NOTE: this assumes buf size is bigenough for the algorithm */
    (*context->end)(context->cipherInfo, pDigest, &digestLen, maxout);
    *pulDigestLen = digestLen;

    sftk_TerminateOp(session, SFTK_HASH, context);
finish:
    sftk_FreeSession(session);
    return CKR_OK;
}

/* NSC_DigestUpdate continues a multiple-part message-digesting operation. */
CK_RV
NSC_DigestUpdate(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pPart,
                 CK_ULONG ulPartLen)
{
    SFTKSessionContext *context;
    CK_RV crv;

    CHECK_FORK();

    /* make sure we're legal */
    crv = sftk_GetContext(hSession, &context, SFTK_HASH, PR_TRUE, NULL);
    if (crv != CKR_OK)
        return crv;
    /* do it: */
    (*context->hashUpdate)(context->cipherInfo, pPart, ulPartLen);
    return CKR_OK;
}

/* NSC_DigestFinal finishes a multiple-part message-digesting operation. */
CK_RV
NSC_DigestFinal(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pDigest,
                CK_ULONG_PTR pulDigestLen)
{
    SFTKSession *session;
    SFTKSessionContext *context;
    unsigned int maxout = *pulDigestLen;
    unsigned int digestLen;
    CK_RV crv;

    CHECK_FORK();

    /* make sure we're legal */
    crv = sftk_GetContext(hSession, &context, SFTK_HASH, PR_TRUE, &session);
    if (crv != CKR_OK)
        return crv;

    if (pDigest != NULL) {
        (*context->end)(context->cipherInfo, pDigest, &digestLen, maxout);
        *pulDigestLen = digestLen;
        sftk_TerminateOp(session, SFTK_HASH, context);
    } else {
        *pulDigestLen = context->maxLen;
    }

    sftk_FreeSession(session);
    return CKR_OK;
}

/*
 * these helper functions are used by Generic Macing and Signing functions
 * that use hashes as part of their operations.
 */
#define DOSUB(mmm)                                                \
    static CK_RV                                                  \
        sftk_doSub##mmm(SFTKSessionContext *context)              \
    {                                                             \
        mmm##Context *mmm##_ctx = mmm##_NewContext();             \
        context->hashInfo = (void *)mmm##_ctx;                    \
        context->hashUpdate = (SFTKHash)mmm##_Update;             \
        context->end = (SFTKEnd)mmm##_End;                        \
        context->hashdestroy = (SFTKDestroy)mmm##_DestroyContext; \
        if (!context->hashInfo) {                                 \
            return CKR_HOST_MEMORY;                               \
        }                                                         \
        mmm##_Begin(mmm##_ctx);                                   \
        return CKR_OK;                                            \
    }

DOSUB(MD2)
DOSUB(MD5)
DOSUB(SHA1)
DOSUB(SHA224)
DOSUB(SHA256)
DOSUB(SHA384)
DOSUB(SHA512)

static SECStatus
sftk_SignCopy(
    CK_ULONG *copyLen,
    void *out, unsigned int *outLength,
    unsigned int maxLength,
    const unsigned char *hashResult,
    unsigned int hashResultLength)
{
    unsigned int toCopy = *copyLen;
    if (toCopy > maxLength) {
        toCopy = maxLength;
    }
    if (toCopy > hashResultLength) {
        toCopy = hashResultLength;
    }
    memcpy(out, hashResult, toCopy);
    if (outLength) {
        *outLength = toCopy;
    }
    return SECSuccess;
}

/* Verify is just a compare for HMAC */
static SECStatus
sftk_HMACCmp(CK_ULONG *copyLen, unsigned char *sig, unsigned int sigLen,
             unsigned char *hash, unsigned int hashLen)
{
    return (PORT_Memcmp(sig, hash, *copyLen) == 0) ? SECSuccess : SECFailure;
}

/*
 * common HMAC initalization routine
 */
static CK_RV
sftk_doHMACInit(SFTKSessionContext *context, HASH_HashType hash,
                SFTKObject *key, CK_ULONG mac_size)
{
    SFTKAttribute *keyval;
    HMACContext *HMACcontext;
    CK_ULONG *intpointer;
    const SECHashObject *hashObj = HASH_GetRawHashObject(hash);
    PRBool isFIPS = (key->slot->slotID == FIPS_SLOT_ID);

    /* required by FIPS 198 Section 4 */
    if (isFIPS && (mac_size < 4 || mac_size < hashObj->length / 2)) {
        return CKR_BUFFER_TOO_SMALL;
    }

    keyval = sftk_FindAttribute(key, CKA_VALUE);
    if (keyval == NULL)
        return CKR_KEY_SIZE_RANGE;

    HMACcontext = HMAC_Create(hashObj,
                              (const unsigned char *)keyval->attrib.pValue,
                              keyval->attrib.ulValueLen, isFIPS);
    context->hashInfo = HMACcontext;
    context->multi = PR_TRUE;
    sftk_FreeAttribute(keyval);
    if (context->hashInfo == NULL) {
        if (PORT_GetError() == SEC_ERROR_INVALID_ARGS) {
            return CKR_KEY_SIZE_RANGE;
        }
        return CKR_HOST_MEMORY;
    }
    context->hashUpdate = (SFTKHash)HMAC_Update;
    context->end = (SFTKEnd)HMAC_Finish;

    context->hashdestroy = (SFTKDestroy)HMAC_Destroy;
    intpointer = PORT_New(CK_ULONG);
    if (intpointer == NULL) {
        return CKR_HOST_MEMORY;
    }
    *intpointer = mac_size;
    context->cipherInfo = intpointer;
    context->destroy = (SFTKDestroy)sftk_Space;
    context->update = (SFTKCipher)sftk_SignCopy;
    context->verify = (SFTKVerify)sftk_HMACCmp;
    context->maxLen = hashObj->length;
    HMAC_Begin(HMACcontext);
    return CKR_OK;
}

/*
 *  SSL Macing support. SSL Macs are inited, then update with the base
 * hashing algorithm, then finalized in sign and verify
 */

/*
 * FROM SSL:
 * 60 bytes is 3 times the maximum length MAC size that is supported.
 * We probably should have one copy of this table. We still need this table
 * in ssl to 'sign' the handshake hashes.
 */
static unsigned char ssl_pad_1[60] = {
    0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36,
    0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36,
    0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36,
    0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36,
    0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36,
    0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36,
    0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36,
    0x36, 0x36, 0x36, 0x36
};
static unsigned char ssl_pad_2[60] = {
    0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c,
    0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c,
    0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c,
    0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c,
    0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c,
    0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c,
    0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c,
    0x5c, 0x5c, 0x5c, 0x5c
};

static SECStatus
sftk_SSLMACSign(SFTKSSLMACInfo *info, unsigned char *sig, unsigned int *sigLen,
                unsigned int maxLen, unsigned char *hash, unsigned int hashLen)
{
    unsigned char tmpBuf[SFTK_MAX_MAC_LENGTH];
    unsigned int out;

    info->begin(info->hashContext);
    info->update(info->hashContext, info->key, info->keySize);
    info->update(info->hashContext, ssl_pad_2, info->padSize);
    info->update(info->hashContext, hash, hashLen);
    info->end(info->hashContext, tmpBuf, &out, SFTK_MAX_MAC_LENGTH);
    PORT_Memcpy(sig, tmpBuf, info->macSize);
    *sigLen = info->macSize;
    return SECSuccess;
}

static SECStatus
sftk_SSLMACVerify(SFTKSSLMACInfo *info, unsigned char *sig, unsigned int sigLen,
                  unsigned char *hash, unsigned int hashLen)
{
    unsigned char tmpBuf[SFTK_MAX_MAC_LENGTH];
    unsigned int out;

    info->begin(info->hashContext);
    info->update(info->hashContext, info->key, info->keySize);
    info->update(info->hashContext, ssl_pad_2, info->padSize);
    info->update(info->hashContext, hash, hashLen);
    info->end(info->hashContext, tmpBuf, &out, SFTK_MAX_MAC_LENGTH);
    return (PORT_Memcmp(sig, tmpBuf, info->macSize) == 0) ? SECSuccess : SECFailure;
}

/*
 * common HMAC initalization routine
 */
static CK_RV
sftk_doSSLMACInit(SFTKSessionContext *context, SECOidTag oid,
                  SFTKObject *key, CK_ULONG mac_size)
{
    SFTKAttribute *keyval;
    SFTKBegin begin;
    int padSize;
    SFTKSSLMACInfo *sslmacinfo;
    CK_RV crv = CKR_MECHANISM_INVALID;

    if (oid == SEC_OID_SHA1) {
        crv = sftk_doSubSHA1(context);
        if (crv != CKR_OK)
            return crv;
        begin = (SFTKBegin)SHA1_Begin;
        padSize = 40;
    } else {
        crv = sftk_doSubMD5(context);
        if (crv != CKR_OK)
            return crv;
        begin = (SFTKBegin)MD5_Begin;
        padSize = 48;
    }
    context->multi = PR_TRUE;

    keyval = sftk_FindAttribute(key, CKA_VALUE);
    if (keyval == NULL)
        return CKR_KEY_SIZE_RANGE;

    context->hashUpdate(context->hashInfo, keyval->attrib.pValue,
                        keyval->attrib.ulValueLen);
    context->hashUpdate(context->hashInfo, ssl_pad_1, padSize);
    sslmacinfo = (SFTKSSLMACInfo *)PORT_Alloc(sizeof(SFTKSSLMACInfo));
    if (sslmacinfo == NULL) {
        sftk_FreeAttribute(keyval);
        return CKR_HOST_MEMORY;
    }
    sslmacinfo->macSize = mac_size;
    sslmacinfo->hashContext = context->hashInfo;
    PORT_Memcpy(sslmacinfo->key, keyval->attrib.pValue,
                keyval->attrib.ulValueLen);
    sslmacinfo->keySize = keyval->attrib.ulValueLen;
    sslmacinfo->begin = begin;
    sslmacinfo->end = context->end;
    sslmacinfo->update = context->hashUpdate;
    sslmacinfo->padSize = padSize;
    sftk_FreeAttribute(keyval);
    context->cipherInfo = (void *)sslmacinfo;
    context->destroy = (SFTKDestroy)sftk_Space;
    context->update = (SFTKCipher)sftk_SSLMACSign;
    context->verify = (SFTKVerify)sftk_SSLMACVerify;
    context->maxLen = mac_size;
    return CKR_OK;
}

/*
 ************** Crypto Functions:     Sign  ************************
 */

/**
 * Check if We're using CBCMacing and initialize the session context if we are.
 *  @param contextType SFTK_SIGN or SFTK_VERIFY
 *  @param keyUsage    check whether key allows this usage
 */
static CK_RV
sftk_InitCBCMac(CK_SESSION_HANDLE hSession, CK_MECHANISM_PTR pMechanism,
                CK_OBJECT_HANDLE hKey, CK_ATTRIBUTE_TYPE keyUsage,
                SFTKContextType contextType)

{
    CK_MECHANISM cbc_mechanism;
    CK_ULONG mac_bytes = SFTK_INVALID_MAC_SIZE;
    CK_RC2_CBC_PARAMS rc2_params;
#if NSS_SOFTOKEN_DOES_RC5
    CK_RC5_CBC_PARAMS rc5_params;
    CK_RC5_MAC_GENERAL_PARAMS *rc5_mac;
#endif
    unsigned char ivBlock[SFTK_MAX_BLOCK_SIZE];
    SFTKSessionContext *context;
    CK_RV crv;
    unsigned int blockSize;

    switch (pMechanism->mechanism) {
        case CKM_RC2_MAC_GENERAL:
            if (!pMechanism->pParameter) {
                return CKR_MECHANISM_PARAM_INVALID;
            }
            mac_bytes =
                ((CK_RC2_MAC_GENERAL_PARAMS *)pMechanism->pParameter)->ulMacLength;
        /* fall through */
        case CKM_RC2_MAC:
            /* this works because ulEffectiveBits is in the same place in both the
             * CK_RC2_MAC_GENERAL_PARAMS and CK_RC2_CBC_PARAMS */
            rc2_params.ulEffectiveBits = ((CK_RC2_MAC_GENERAL_PARAMS *)
                                              pMechanism->pParameter)
                                             ->ulEffectiveBits;
            PORT_Memset(rc2_params.iv, 0, sizeof(rc2_params.iv));
            cbc_mechanism.mechanism = CKM_RC2_CBC;
            cbc_mechanism.pParameter = &rc2_params;
            cbc_mechanism.ulParameterLen = sizeof(rc2_params);
            blockSize = 8;
            break;
#if NSS_SOFTOKEN_DOES_RC5
        case CKM_RC5_MAC_GENERAL:
            mac_bytes =
                ((CK_RC5_MAC_GENERAL_PARAMS *)pMechanism->pParameter)->ulMacLength;
        /* fall through */
        case CKM_RC5_MAC:
            /* this works because ulEffectiveBits is in the same place in both the
             * CK_RC5_MAC_GENERAL_PARAMS and CK_RC5_CBC_PARAMS */
            rc5_mac = (CK_RC5_MAC_GENERAL_PARAMS *)pMechanism->pParameter;
            rc5_params.ulWordsize = rc5_mac->ulWordsize;
            rc5_params.ulRounds = rc5_mac->ulRounds;
            rc5_params.pIv = ivBlock;
            if ((blockSize = rc5_mac->ulWordsize * 2) > SFTK_MAX_BLOCK_SIZE)
                return CKR_MECHANISM_PARAM_INVALID;
            rc5_params.ulIvLen = blockSize;
            PORT_Memset(ivBlock, 0, blockSize);
            cbc_mechanism.mechanism = CKM_RC5_CBC;
            cbc_mechanism.pParameter = &rc5_params;
            cbc_mechanism.ulParameterLen = sizeof(rc5_params);
            break;
#endif
        /* add cast and idea later */
        case CKM_DES_MAC_GENERAL:
            mac_bytes = *(CK_ULONG *)pMechanism->pParameter;
        /* fall through */
        case CKM_DES_MAC:
            blockSize = 8;
            PORT_Memset(ivBlock, 0, blockSize);
            cbc_mechanism.mechanism = CKM_DES_CBC;
            cbc_mechanism.pParameter = &ivBlock;
            cbc_mechanism.ulParameterLen = blockSize;
            break;
        case CKM_DES3_MAC_GENERAL:
            mac_bytes = *(CK_ULONG *)pMechanism->pParameter;
        /* fall through */
        case CKM_DES3_MAC:
            blockSize = 8;
            PORT_Memset(ivBlock, 0, blockSize);
            cbc_mechanism.mechanism = CKM_DES3_CBC;
            cbc_mechanism.pParameter = &ivBlock;
            cbc_mechanism.ulParameterLen = blockSize;
            break;
        case CKM_CDMF_MAC_GENERAL:
            mac_bytes = *(CK_ULONG *)pMechanism->pParameter;
        /* fall through */
        case CKM_CDMF_MAC:
            blockSize = 8;
            PORT_Memset(ivBlock, 0, blockSize);
            cbc_mechanism.mechanism = CKM_CDMF_CBC;
            cbc_mechanism.pParameter = &ivBlock;
            cbc_mechanism.ulParameterLen = blockSize;
            break;
        case CKM_SEED_MAC_GENERAL:
            mac_bytes = *(CK_ULONG *)pMechanism->pParameter;
        /* fall through */
        case CKM_SEED_MAC:
            blockSize = 16;
            PORT_Memset(ivBlock, 0, blockSize);
            cbc_mechanism.mechanism = CKM_SEED_CBC;
            cbc_mechanism.pParameter = &ivBlock;
            cbc_mechanism.ulParameterLen = blockSize;
            break;
        case CKM_CAMELLIA_MAC_GENERAL:
            mac_bytes = *(CK_ULONG *)pMechanism->pParameter;
        /* fall through */
        case CKM_CAMELLIA_MAC:
            blockSize = 16;
            PORT_Memset(ivBlock, 0, blockSize);
            cbc_mechanism.mechanism = CKM_CAMELLIA_CBC;
            cbc_mechanism.pParameter = &ivBlock;
            cbc_mechanism.ulParameterLen = blockSize;
            break;
        case CKM_AES_MAC_GENERAL:
            mac_bytes = *(CK_ULONG *)pMechanism->pParameter;
        /* fall through */
        case CKM_AES_MAC:
            blockSize = 16;
            PORT_Memset(ivBlock, 0, blockSize);
            cbc_mechanism.mechanism = CKM_AES_CBC;
            cbc_mechanism.pParameter = &ivBlock;
            cbc_mechanism.ulParameterLen = blockSize;
            break;
        default:
            return CKR_FUNCTION_NOT_SUPPORTED;
    }

    /* if MAC size is externally supplied, it should be checked.
     */
    if (mac_bytes == SFTK_INVALID_MAC_SIZE)
        mac_bytes = blockSize >> 1;
    else {
        if (mac_bytes > blockSize)
            return CKR_MECHANISM_PARAM_INVALID;
    }

    crv = sftk_CryptInit(hSession, &cbc_mechanism, hKey,
                         CKA_ENCRYPT, /* CBC mech is able to ENCRYPT, not SIGN/VERIFY */
                         keyUsage, contextType, PR_TRUE);
    if (crv != CKR_OK)
        return crv;
    crv = sftk_GetContext(hSession, &context, contextType, PR_TRUE, NULL);

    /* this shouldn't happen! */
    PORT_Assert(crv == CKR_OK);
    if (crv != CKR_OK)
        return crv;
    context->blockSize = blockSize;
    context->macSize = mac_bytes;
    return CKR_OK;
}

/*
 * encode RSA PKCS #1 Signature data before signing...
 */
static SECStatus
sftk_RSAHashSign(SFTKHashSignInfo *info, unsigned char *sig,
                 unsigned int *sigLen, unsigned int maxLen,
                 const unsigned char *hash, unsigned int hashLen)
{
    PORT_Assert(info->key->keyType == NSSLOWKEYRSAKey);
    if (info->key->keyType != NSSLOWKEYRSAKey) {
        PORT_SetError(SEC_ERROR_INVALID_KEY);
        return SECFailure;
    }

    return RSA_HashSign(info->hashOid, info->key, sig, sigLen, maxLen,
                        hash, hashLen);
}

/* XXX Old template; want to expunge it eventually. */
static DERTemplate SECAlgorithmIDTemplate[] = {
    { DER_SEQUENCE,
      0, NULL, sizeof(SECAlgorithmID) },
    { DER_OBJECT_ID,
      offsetof(SECAlgorithmID, algorithm) },
    { DER_OPTIONAL | DER_ANY,
      offsetof(SECAlgorithmID, parameters) },
    { 0 }
};

/*
 * XXX OLD Template.  Once all uses have been switched over to new one,
 * remove this.
 */
static DERTemplate SGNDigestInfoTemplate[] = {
    { DER_SEQUENCE,
      0, NULL, sizeof(SGNDigestInfo) },
    { DER_INLINE,
      offsetof(SGNDigestInfo, digestAlgorithm),
      SECAlgorithmIDTemplate },
    { DER_OCTET_STRING,
      offsetof(SGNDigestInfo, digest) },
    { 0 }
};

/*
 * encode RSA PKCS #1 Signature data before signing...
 */
SECStatus
RSA_HashSign(SECOidTag hashOid, NSSLOWKEYPrivateKey *key,
             unsigned char *sig, unsigned int *sigLen, unsigned int maxLen,
             const unsigned char *hash, unsigned int hashLen)
{
    SECStatus rv = SECFailure;
    SECItem digder;
    PLArenaPool *arena = NULL;
    SGNDigestInfo *di = NULL;

    digder.data = NULL;

    arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
    if (!arena) {
        goto loser;
    }

    /* Construct digest info */
    di = SGN_CreateDigestInfo(hashOid, hash, hashLen);
    if (!di) {
        goto loser;
    }

    /* Der encode the digest as a DigestInfo */
    rv = DER_Encode(arena, &digder, SGNDigestInfoTemplate, di);
    if (rv != SECSuccess) {
        goto loser;
    }

    /*
    ** Encrypt signature after constructing appropriate PKCS#1 signature
    ** block
    */
    rv = RSA_Sign(&key->u.rsa, sig, sigLen, maxLen, digder.data,
                  digder.len);
    if (rv != SECSuccess && PORT_GetError() == SEC_ERROR_LIBRARY_FAILURE) {
        sftk_fatalError = PR_TRUE;
    }

loser:
    SGN_DestroyDigestInfo(di);
    if (arena != NULL) {
        PORT_FreeArena(arena, PR_FALSE);
    }
    return rv;
}

static SECStatus
sftk_RSASign(NSSLOWKEYPrivateKey *key, unsigned char *output,
             unsigned int *outputLen, unsigned int maxOutputLen,
             const unsigned char *input, unsigned int inputLen)
{
    SECStatus rv = SECFailure;

    PORT_Assert(key->keyType == NSSLOWKEYRSAKey);
    if (key->keyType != NSSLOWKEYRSAKey) {
        PORT_SetError(SEC_ERROR_INVALID_KEY);
        return SECFailure;
    }

    rv = RSA_Sign(&key->u.rsa, output, outputLen, maxOutputLen, input,
                  inputLen);
    if (rv != SECSuccess && PORT_GetError() == SEC_ERROR_LIBRARY_FAILURE) {
        sftk_fatalError = PR_TRUE;
    }
    return rv;
}

static SECStatus
sftk_RSASignRaw(NSSLOWKEYPrivateKey *key, unsigned char *output,
                unsigned int *outputLen, unsigned int maxOutputLen,
                const unsigned char *input, unsigned int inputLen)
{
    SECStatus rv = SECFailure;

    PORT_Assert(key->keyType == NSSLOWKEYRSAKey);
    if (key->keyType != NSSLOWKEYRSAKey) {
        PORT_SetError(SEC_ERROR_INVALID_KEY);
        return SECFailure;
    }

    rv = RSA_SignRaw(&key->u.rsa, output, outputLen, maxOutputLen, input,
                     inputLen);
    if (rv != SECSuccess && PORT_GetError() == SEC_ERROR_LIBRARY_FAILURE) {
        sftk_fatalError = PR_TRUE;
    }
    return rv;
}

static SECStatus
sftk_RSASignPSS(SFTKHashSignInfo *info, unsigned char *sig,
                unsigned int *sigLen, unsigned int maxLen,
                const unsigned char *hash, unsigned int hashLen)
{
    SECStatus rv = SECFailure;
    HASH_HashType hashAlg;
    HASH_HashType maskHashAlg;
    CK_RSA_PKCS_PSS_PARAMS *params = (CK_RSA_PKCS_PSS_PARAMS *)info->params;

    PORT_Assert(info->key->keyType == NSSLOWKEYRSAKey);
    if (info->key->keyType != NSSLOWKEYRSAKey) {
        PORT_SetError(SEC_ERROR_INVALID_KEY);
        return SECFailure;
    }

    hashAlg = GetHashTypeFromMechanism(params->hashAlg);
    maskHashAlg = GetHashTypeFromMechanism(params->mgf);

    rv = RSA_SignPSS(&info->key->u.rsa, hashAlg, maskHashAlg, NULL,
                     params->sLen, sig, sigLen, maxLen, hash, hashLen);
    if (rv != SECSuccess && PORT_GetError() == SEC_ERROR_LIBRARY_FAILURE) {
        sftk_fatalError = PR_TRUE;
    }
    return rv;
}

static SECStatus
nsc_DSA_Verify_Stub(void *ctx, void *sigBuf, unsigned int sigLen,
                    void *dataBuf, unsigned int dataLen)
{
    SECItem signature, digest;
    NSSLOWKEYPublicKey *key = (NSSLOWKEYPublicKey *)ctx;

    signature.data = (unsigned char *)sigBuf;
    signature.len = sigLen;
    digest.data = (unsigned char *)dataBuf;
    digest.len = dataLen;
    return DSA_VerifyDigest(&(key->u.dsa), &signature, &digest);
}

static SECStatus
nsc_DSA_Sign_Stub(void *ctx, void *sigBuf,
                  unsigned int *sigLen, unsigned int maxSigLen,
                  void *dataBuf, unsigned int dataLen)
{
    SECItem signature, digest;
    SECStatus rv;
    NSSLOWKEYPrivateKey *key = (NSSLOWKEYPrivateKey *)ctx;

    signature.data = (unsigned char *)sigBuf;
    signature.len = maxSigLen;
    digest.data = (unsigned char *)dataBuf;
    digest.len = dataLen;
    rv = DSA_SignDigest(&(key->u.dsa), &signature, &digest);
    if (rv != SECSuccess && PORT_GetError() == SEC_ERROR_LIBRARY_FAILURE) {
        sftk_fatalError = PR_TRUE;
    }
    *sigLen = signature.len;
    return rv;
}

static SECStatus
nsc_ECDSAVerifyStub(void *ctx, void *sigBuf, unsigned int sigLen,
                    void *dataBuf, unsigned int dataLen)
{
    SECItem signature, digest;
    NSSLOWKEYPublicKey *key = (NSSLOWKEYPublicKey *)ctx;

    signature.data = (unsigned char *)sigBuf;
    signature.len = sigLen;
    digest.data = (unsigned char *)dataBuf;
    digest.len = dataLen;
    return ECDSA_VerifyDigest(&(key->u.ec), &signature, &digest);
}

static SECStatus
nsc_ECDSASignStub(void *ctx, void *sigBuf,
                  unsigned int *sigLen, unsigned int maxSigLen,
                  void *dataBuf, unsigned int dataLen)
{
    SECItem signature, digest;
    SECStatus rv;
    NSSLOWKEYPrivateKey *key = (NSSLOWKEYPrivateKey *)ctx;

    signature.data = (unsigned char *)sigBuf;
    signature.len = maxSigLen;
    digest.data = (unsigned char *)dataBuf;
    digest.len = dataLen;
    rv = ECDSA_SignDigest(&(key->u.ec), &signature, &digest);
    if (rv != SECSuccess && PORT_GetError() == SEC_ERROR_LIBRARY_FAILURE) {
        sftk_fatalError = PR_TRUE;
    }
    *sigLen = signature.len;
    return rv;
}

/* NSC_SignInit setups up the signing operations. There are three basic
 * types of signing:
 *      (1) the tradition single part, where "Raw RSA" or "Raw DSA" is applied
 *  to data in a single Sign operation (which often looks a lot like an
 *  encrypt, with data coming in and data going out).
 *      (2) Hash based signing, where we continually hash the data, then apply
 *  some sort of signature to the end.
 *      (3) Block Encryption CBC MAC's, where the Data is encrypted with a key,
 *  and only the final block is part of the mac.
 *
 *  For case number 3, we initialize a context much like the Encryption Context
 *  (in fact we share code). We detect case 3 in C_SignUpdate, C_Sign, and
 *  C_Final by the following method... if it's not multi-part, and it's doesn't
 *  have a hash context, it must be a block Encryption CBC MAC.
 *
 *  For case number 2, we initialize a hash structure, as well as make it
 *  multi-part. Updates are simple calls to the hash update function. Final
 *  calls the hashend, then passes the result to the 'update' function (which
 *  operates as a final signature function). In some hash based MAC'ing (as
 *  opposed to hash base signatures), the update function is can be simply a
 *  copy (as is the case with HMAC).
 */
CK_RV
NSC_SignInit(CK_SESSION_HANDLE hSession,
             CK_MECHANISM_PTR pMechanism, CK_OBJECT_HANDLE hKey)
{
    SFTKSession *session;
    SFTKObject *key;
    SFTKSessionContext *context;
    CK_KEY_TYPE key_type;
    CK_RV crv = CKR_OK;
    NSSLOWKEYPrivateKey *privKey;
    SFTKHashSignInfo *info = NULL;

    CHECK_FORK();

    /* Block Cipher MACing Algorithms use a different Context init method..*/
    crv = sftk_InitCBCMac(hSession, pMechanism, hKey, CKA_SIGN, SFTK_SIGN);
    if (crv != CKR_FUNCTION_NOT_SUPPORTED)
        return crv;

    /* we're not using a block cipher mac */
    session = sftk_SessionFromHandle(hSession);
    if (session == NULL)
        return CKR_SESSION_HANDLE_INVALID;
    crv = sftk_InitGeneric(session, &context, SFTK_SIGN, &key, hKey, &key_type,
                           CKO_PRIVATE_KEY, CKA_SIGN);
    if (crv != CKR_OK) {
        sftk_FreeSession(session);
        return crv;
    }

    context->multi = PR_FALSE;

#define INIT_RSA_SIGN_MECH(mmm)                         \
    case CKM_##mmm##_RSA_PKCS:                          \
        context->multi = PR_TRUE;                       \
        crv = sftk_doSub##mmm(context);                 \
        if (crv != CKR_OK)                              \
            break;                                      \
        context->update = (SFTKCipher)sftk_RSAHashSign; \
        info = PORT_New(SFTKHashSignInfo);              \
        if (info == NULL) {                             \
            crv = CKR_HOST_MEMORY;                      \
            break;                                      \
        }                                               \
        info->hashOid = SEC_OID_##mmm;                  \
        goto finish_rsa;

    switch (pMechanism->mechanism) {
        INIT_RSA_SIGN_MECH(MD5)
        INIT_RSA_SIGN_MECH(MD2)
        INIT_RSA_SIGN_MECH(SHA1)
        INIT_RSA_SIGN_MECH(SHA224)
        INIT_RSA_SIGN_MECH(SHA256)
        INIT_RSA_SIGN_MECH(SHA384)
        INIT_RSA_SIGN_MECH(SHA512)

        case CKM_RSA_PKCS:
            context->update = (SFTKCipher)sftk_RSASign;
            goto finish_rsa;
        case CKM_RSA_X_509:
            context->update = (SFTKCipher)sftk_RSASignRaw;
        finish_rsa:
            if (key_type != CKK_RSA) {
                crv = CKR_KEY_TYPE_INCONSISTENT;
                break;
            }
            context->rsa = PR_TRUE;
            privKey = sftk_GetPrivKey(key, CKK_RSA, &crv);
            if (privKey == NULL) {
                crv = CKR_KEY_TYPE_INCONSISTENT;
                break;
            }
            /* OK, info is allocated only if we're doing hash and sign mechanism.
             * It's necessary to be able to set the correct OID in the final
             * signature.
             */
            if (info) {
                info->key = privKey;
                context->cipherInfo = info;
                context->destroy = (SFTKDestroy)sftk_Space;
            } else {
                context->cipherInfo = privKey;
                context->destroy = (SFTKDestroy)sftk_Null;
            }
            context->maxLen = nsslowkey_PrivateModulusLen(privKey);
            break;
        case CKM_RSA_PKCS_PSS:
            if (key_type != CKK_RSA) {
                crv = CKR_KEY_TYPE_INCONSISTENT;
                break;
            }
            context->rsa = PR_TRUE;
            if (pMechanism->ulParameterLen != sizeof(CK_RSA_PKCS_PSS_PARAMS) ||
                !sftk_ValidatePssParams((const CK_RSA_PKCS_PSS_PARAMS *)pMechanism->pParameter)) {
                crv = CKR_MECHANISM_PARAM_INVALID;
                break;
            }
            info = PORT_New(SFTKHashSignInfo);
            if (info == NULL) {
                crv = CKR_HOST_MEMORY;
                break;
            }
            info->params = pMechanism->pParameter;
            info->key = sftk_GetPrivKey(key, CKK_RSA, &crv);
            if (info->key == NULL) {
                PORT_Free(info);
                break;
            }
            context->cipherInfo = info;
            context->destroy = (SFTKDestroy)sftk_Space;
            context->update = (SFTKCipher)sftk_RSASignPSS;
            context->maxLen = nsslowkey_PrivateModulusLen(info->key);
            break;

        case CKM_DSA_SHA1:
            context->multi = PR_TRUE;
            crv = sftk_doSubSHA1(context);
            if (crv != CKR_OK)
                break;
        /* fall through */
        case CKM_DSA:
            if (key_type != CKK_DSA) {
                crv = CKR_KEY_TYPE_INCONSISTENT;
                break;
            }
            privKey = sftk_GetPrivKey(key, CKK_DSA, &crv);
            if (privKey == NULL) {
                break;
            }
            context->cipherInfo = privKey;
            context->update = (SFTKCipher)nsc_DSA_Sign_Stub;
            context->destroy = (privKey == key->objectInfo) ? (SFTKDestroy)sftk_Null : (SFTKDestroy)sftk_FreePrivKey;
            context->maxLen = DSA_MAX_SIGNATURE_LEN;

            break;

        case CKM_ECDSA_SHA1:
            context->multi = PR_TRUE;
            crv = sftk_doSubSHA1(context);
            if (crv != CKR_OK)
                break;
        /* fall through */
        case CKM_ECDSA:
            if (key_type != CKK_EC) {
                crv = CKR_KEY_TYPE_INCONSISTENT;
                break;
            }
            privKey = sftk_GetPrivKey(key, CKK_EC, &crv);
            if (privKey == NULL) {
                crv = CKR_HOST_MEMORY;
                break;
            }
            context->cipherInfo = privKey;
            context->update = (SFTKCipher)nsc_ECDSASignStub;
            context->destroy = (privKey == key->objectInfo) ? (SFTKDestroy)sftk_Null : (SFTKDestroy)sftk_FreePrivKey;
            context->maxLen = MAX_ECKEY_LEN * 2;

            break;

#define INIT_HMAC_MECH(mmm)                                               \
    case CKM_##mmm##_HMAC_GENERAL:                                        \
        PORT_Assert(pMechanism->pParameter);                              \
        if (!pMechanism->pParameter) {                                    \
            crv = CKR_MECHANISM_PARAM_INVALID;                            \
            break;                                                        \
        }                                                                 \
        crv = sftk_doHMACInit(context, HASH_Alg##mmm, key,                \
                              *(CK_ULONG *)pMechanism->pParameter);       \
        break;                                                            \
    case CKM_##mmm##_HMAC:                                                \
        crv = sftk_doHMACInit(context, HASH_Alg##mmm, key, mmm##_LENGTH); \
        break;

            INIT_HMAC_MECH(MD2)
            INIT_HMAC_MECH(MD5)
            INIT_HMAC_MECH(SHA224)
            INIT_HMAC_MECH(SHA256)
            INIT_HMAC_MECH(SHA384)
            INIT_HMAC_MECH(SHA512)

        case CKM_SHA_1_HMAC_GENERAL:
            PORT_Assert(pMechanism->pParameter);
            if (!pMechanism->pParameter) {
                crv = CKR_MECHANISM_PARAM_INVALID;
                break;
            }
            crv = sftk_doHMACInit(context, HASH_AlgSHA1, key,
                                  *(CK_ULONG *)pMechanism->pParameter);
            break;
        case CKM_SHA_1_HMAC:
            crv = sftk_doHMACInit(context, HASH_AlgSHA1, key, SHA1_LENGTH);
            break;

        case CKM_SSL3_MD5_MAC:
            PORT_Assert(pMechanism->pParameter);
            if (!pMechanism->pParameter) {
                crv = CKR_MECHANISM_PARAM_INVALID;
                break;
            }
            crv = sftk_doSSLMACInit(context, SEC_OID_MD5, key,
                                    *(CK_ULONG *)pMechanism->pParameter);
            break;
        case CKM_SSL3_SHA1_MAC:
            PORT_Assert(pMechanism->pParameter);
            if (!pMechanism->pParameter) {
                crv = CKR_MECHANISM_PARAM_INVALID;
                break;
            }
            crv = sftk_doSSLMACInit(context, SEC_OID_SHA1, key,
                                    *(CK_ULONG *)pMechanism->pParameter);
            break;
        case CKM_TLS_PRF_GENERAL:
            crv = sftk_TLSPRFInit(context, key, key_type, HASH_AlgNULL, 0);
            break;
        case CKM_TLS_MAC: {
            CK_TLS_MAC_PARAMS *tls12_mac_params;
            HASH_HashType tlsPrfHash;
            const char *label;

            if (pMechanism->ulParameterLen != sizeof(CK_TLS_MAC_PARAMS)) {
                crv = CKR_MECHANISM_PARAM_INVALID;
                break;
            }
            tls12_mac_params = (CK_TLS_MAC_PARAMS *)pMechanism->pParameter;
            if (tls12_mac_params->prfMechanism == CKM_TLS_PRF) {
                /* The TLS 1.0 and 1.1 PRF */
                tlsPrfHash = HASH_AlgNULL;
                if (tls12_mac_params->ulMacLength != 12) {
                    crv = CKR_MECHANISM_PARAM_INVALID;
                    break;
                }
            } else {
                /* The hash function for the TLS 1.2 PRF */
                tlsPrfHash =
                    GetHashTypeFromMechanism(tls12_mac_params->prfMechanism);
                if (tlsPrfHash == HASH_AlgNULL ||
                    tls12_mac_params->ulMacLength < 12) {
                    crv = CKR_MECHANISM_PARAM_INVALID;
                    break;
                }
            }
            if (tls12_mac_params->ulServerOrClient == 1) {
                label = "server finished";
            } else if (tls12_mac_params->ulServerOrClient == 2) {
                label = "client finished";
            } else {
                crv = CKR_MECHANISM_PARAM_INVALID;
                break;
            }
            crv = sftk_TLSPRFInit(context, key, key_type, tlsPrfHash,
                                  tls12_mac_params->ulMacLength);
            if (crv == CKR_OK) {
                context->hashUpdate(context->hashInfo, label, 15);
            }
            break;
        }
        case CKM_NSS_TLS_PRF_GENERAL_SHA256:
            crv = sftk_TLSPRFInit(context, key, key_type, HASH_AlgSHA256, 0);
            break;

        case CKM_NSS_HMAC_CONSTANT_TIME: {
            sftk_MACConstantTimeCtx *ctx =
                sftk_HMACConstantTime_New(pMechanism, key);
            CK_ULONG *intpointer;

            if (ctx == NULL) {
                crv = CKR_ARGUMENTS_BAD;
                break;
            }
            intpointer = PORT_New(CK_ULONG);
            if (intpointer == NULL) {
                PORT_Free(ctx);
                crv = CKR_HOST_MEMORY;
                break;
            }
            *intpointer = ctx->hash->length;

            context->cipherInfo = intpointer;
            context->hashInfo = ctx;
            context->currentMech = pMechanism->mechanism;
            context->hashUpdate = sftk_HMACConstantTime_Update;
            context->hashdestroy = sftk_MACConstantTime_DestroyContext;
            context->end = sftk_MACConstantTime_EndHash;
            context->update = (SFTKCipher)sftk_SignCopy;
            context->destroy = sftk_Space;
            context->maxLen = 64;
            context->multi = PR_TRUE;
            break;
        }

        case CKM_NSS_SSL3_MAC_CONSTANT_TIME: {
            sftk_MACConstantTimeCtx *ctx =
                sftk_SSLv3MACConstantTime_New(pMechanism, key);
            CK_ULONG *intpointer;

            if (ctx == NULL) {
                crv = CKR_ARGUMENTS_BAD;
                break;
            }
            intpointer = PORT_New(CK_ULONG);
            if (intpointer == NULL) {
                PORT_Free(ctx);
                crv = CKR_HOST_MEMORY;
                break;
            }
            *intpointer = ctx->hash->length;

            context->cipherInfo = intpointer;
            context->hashInfo = ctx;
            context->currentMech = pMechanism->mechanism;
            context->hashUpdate = sftk_SSLv3MACConstantTime_Update;
            context->hashdestroy = sftk_MACConstantTime_DestroyContext;
            context->end = sftk_MACConstantTime_EndHash;
            context->update = (SFTKCipher)sftk_SignCopy;
            context->destroy = sftk_Space;
            context->maxLen = 64;
            context->multi = PR_TRUE;
            break;
        }

        default:
            crv = CKR_MECHANISM_INVALID;
            break;
    }

    if (crv != CKR_OK) {
        if (info)
            PORT_Free(info);
        sftk_FreeContext(context);
        sftk_FreeSession(session);
        return crv;
    }
    sftk_SetContextByType(session, SFTK_SIGN, context);
    sftk_FreeSession(session);
    return CKR_OK;
}

/** MAC one block of data by block cipher
 */
static CK_RV
sftk_MACBlock(SFTKSessionContext *ctx, void *blk)
{
    unsigned int outlen;
    return (SECSuccess == (ctx->update)(ctx->cipherInfo, ctx->macBuf, &outlen,
                                        SFTK_MAX_BLOCK_SIZE, blk, ctx->blockSize))
               ? CKR_OK
               : sftk_MapCryptError(PORT_GetError());
}

/** MAC last (incomplete) block of data by block cipher
 *
 *  Call once, then terminate MACing operation.
 */
static CK_RV
sftk_MACFinal(SFTKSessionContext *ctx)
{
    unsigned int padLen = ctx->padDataLength;
    /* pad and proceed the residual */
    if (padLen) {
        /* shd clr ctx->padLen to make sftk_MACFinal idempotent */
        PORT_Memset(ctx->padBuf + padLen, 0, ctx->blockSize - padLen);
        return sftk_MACBlock(ctx, ctx->padBuf);
    } else
        return CKR_OK;
}

/** The common implementation for {Sign,Verify}Update. (S/V only vary in their
 * setup and final operations).
 *
 * A call which results in an error terminates the operation [PKCS#11,v2.11]
 */
static CK_RV
sftk_MACUpdate(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pPart,
               CK_ULONG ulPartLen, SFTKContextType type)
{
    SFTKSession *session;
    SFTKSessionContext *context;
    CK_RV crv;

    /* make sure we're legal */
    crv = sftk_GetContext(hSession, &context, type, PR_TRUE, &session);
    if (crv != CKR_OK)
        return crv;

    if (context->hashInfo) {
        (*context->hashUpdate)(context->hashInfo, pPart, ulPartLen);
    } else {
        /* must be block cipher MACing */

        unsigned int blkSize = context->blockSize;
        unsigned char *residual = /* free room in context->padBuf */
            context->padBuf + context->padDataLength;
        unsigned int minInput = /* min input for MACing at least one block */
            blkSize - context->padDataLength;

        /* not enough data even for one block */
        if (ulPartLen < minInput) {
            PORT_Memcpy(residual, pPart, ulPartLen);
            context->padDataLength += ulPartLen;
            goto cleanup;
        }
        /* MACing residual */
        if (context->padDataLength) {
            PORT_Memcpy(residual, pPart, minInput);
            ulPartLen -= minInput;
            pPart += minInput;
            if (CKR_OK != (crv = sftk_MACBlock(context, context->padBuf)))
                goto terminate;
        }
        /* MACing full blocks */
        while (ulPartLen >= blkSize) {
            if (CKR_OK != (crv = sftk_MACBlock(context, pPart)))
                goto terminate;
            ulPartLen -= blkSize;
            pPart += blkSize;
        }
        /* save the residual */
        if ((context->padDataLength = ulPartLen))
            PORT_Memcpy(context->padBuf, pPart, ulPartLen);
    } /* blk cipher MACing */

    goto cleanup;

terminate:
    sftk_TerminateOp(session, type, context);
cleanup:
    sftk_FreeSession(session);
    return crv;
}

/* NSC_SignUpdate continues a multiple-part signature operation,
 * where the signature is (will be) an appendix to the data,
 * and plaintext cannot be recovered from the signature
 *
 * A call which results in an error terminates the operation [PKCS#11,v2.11]
 */
CK_RV
NSC_SignUpdate(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pPart,
               CK_ULONG ulPartLen)
{
    CHECK_FORK();
    return sftk_MACUpdate(hSession, pPart, ulPartLen, SFTK_SIGN);
}

/* NSC_SignFinal finishes a multiple-part signature operation,
 * returning the signature. */
CK_RV
NSC_SignFinal(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pSignature,
              CK_ULONG_PTR pulSignatureLen)
{
    SFTKSession *session;
    SFTKSessionContext *context;
    unsigned int outlen;
    unsigned int maxoutlen = *pulSignatureLen;
    CK_RV crv;

    CHECK_FORK();

    /* make sure we're legal */
    crv = sftk_GetContext(hSession, &context, SFTK_SIGN, PR_TRUE, &session);
    if (crv != CKR_OK)
        return crv;

    if (context->hashInfo) {
        unsigned int digestLen;
        unsigned char tmpbuf[SFTK_MAX_MAC_LENGTH];

        if (!pSignature) {
            outlen = context->maxLen;
            goto finish;
        }
        (*context->end)(context->hashInfo, tmpbuf, &digestLen, sizeof(tmpbuf));
        if (SECSuccess != (context->update)(context->cipherInfo, pSignature,
                                            &outlen, maxoutlen, tmpbuf, digestLen))
            crv = sftk_MapCryptError(PORT_GetError());
        /* CKR_BUFFER_TOO_SMALL here isn't continuable, let operation terminate.
         * Keeping "too small" CK_RV intact is a standard violation, but allows
         * application read EXACT signature length */
    } else {
        /* must be block cipher MACing */
        outlen = context->macSize;
        /* null or "too small" buf doesn't terminate operation [PKCS#11,v2.11]*/
        if (!pSignature || maxoutlen < outlen) {
            if (pSignature)
                crv = CKR_BUFFER_TOO_SMALL;
            goto finish;
        }
        if (CKR_OK == (crv = sftk_MACFinal(context)))
            PORT_Memcpy(pSignature, context->macBuf, outlen);
    }

    sftk_TerminateOp(session, SFTK_SIGN, context);
finish:
    *pulSignatureLen = outlen;
    sftk_FreeSession(session);
    return crv;
}

/* NSC_Sign signs (encrypts with private key) data in a single part,
 * where the signature is (will be) an appendix to the data,
 * and plaintext cannot be recovered from the signature */
CK_RV
NSC_Sign(CK_SESSION_HANDLE hSession,
         CK_BYTE_PTR pData, CK_ULONG ulDataLen, CK_BYTE_PTR pSignature,
         CK_ULONG_PTR pulSignatureLen)
{
    SFTKSession *session;
    SFTKSessionContext *context;
    CK_RV crv;

    CHECK_FORK();

    /* make sure we're legal */
    crv = sftk_GetContext(hSession, &context, SFTK_SIGN, PR_FALSE, &session);
    if (crv != CKR_OK)
        return crv;

    if (!pSignature) {
        /* see also how C_SignUpdate implements this */
        *pulSignatureLen = (!context->multi || context->hashInfo)
                               ? context->maxLen
                               : context->macSize; /* must be block cipher MACing */
        goto finish;
    }

    /* multi part Signing are completely implemented by SignUpdate and
     * sign Final */
    if (context->multi) {
        /* SignFinal can't follow failed SignUpdate */
        if (CKR_OK == (crv = NSC_SignUpdate(hSession, pData, ulDataLen)))
            crv = NSC_SignFinal(hSession, pSignature, pulSignatureLen);
    } else {
        /* single-part PKC signature (e.g. CKM_ECDSA) */
        unsigned int outlen;
        unsigned int maxoutlen = *pulSignatureLen;
        if (SECSuccess != (*context->update)(context->cipherInfo, pSignature,
                                             &outlen, maxoutlen, pData, ulDataLen))
            crv = sftk_MapCryptError(PORT_GetError());
        *pulSignatureLen = (CK_ULONG)outlen;
        /*  "too small" here is certainly continuable */
        if (crv != CKR_BUFFER_TOO_SMALL)
            sftk_TerminateOp(session, SFTK_SIGN, context);
    } /* single-part */

finish:
    sftk_FreeSession(session);
    return crv;
}

/*
 ************** Crypto Functions:     Sign Recover  ************************
 */
/* NSC_SignRecoverInit initializes a signature operation,
 * where the (digest) data can be recovered from the signature.
 * E.g. encryption with the user's private key */
CK_RV
NSC_SignRecoverInit(CK_SESSION_HANDLE hSession,
                    CK_MECHANISM_PTR pMechanism, CK_OBJECT_HANDLE hKey)
{
    CHECK_FORK();

    switch (pMechanism->mechanism) {
        case CKM_RSA_PKCS:
        case CKM_RSA_X_509:
            return NSC_SignInit(hSession, pMechanism, hKey);
        default:
            break;
    }
    return CKR_MECHANISM_INVALID;
}

/* NSC_SignRecover signs data in a single operation
 * where the (digest) data can be recovered from the signature.
 * E.g. encryption with the user's private key */
CK_RV
NSC_SignRecover(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pData,
                CK_ULONG ulDataLen, CK_BYTE_PTR pSignature, CK_ULONG_PTR pulSignatureLen)
{
    CHECK_FORK();

    return NSC_Sign(hSession, pData, ulDataLen, pSignature, pulSignatureLen);
}

/*
 ************** Crypto Functions:     verify  ************************
 */

/* Handle RSA Signature formatting */
static SECStatus
sftk_hashCheckSign(SFTKHashVerifyInfo *info, const unsigned char *sig,
                   unsigned int sigLen, const unsigned char *digest,
                   unsigned int digestLen)
{
    PORT_Assert(info->key->keyType == NSSLOWKEYRSAKey);
    if (info->key->keyType != NSSLOWKEYRSAKey) {
        PORT_SetError(SEC_ERROR_INVALID_KEY);
        return SECFailure;
    }

    return RSA_HashCheckSign(info->hashOid, info->key, sig, sigLen, digest,
                             digestLen);
}

SECStatus
RSA_HashCheckSign(SECOidTag digestOid, NSSLOWKEYPublicKey *key,
                  const unsigned char *sig, unsigned int sigLen,
                  const unsigned char *digestData, unsigned int digestLen)
{
    unsigned char *pkcs1DigestInfoData;
    SECItem pkcs1DigestInfo;
    SECItem digest;
    unsigned int bufferSize;
    SECStatus rv;

    /* pkcs1DigestInfo.data must be less than key->u.rsa.modulus.len */
    bufferSize = key->u.rsa.modulus.len;
    pkcs1DigestInfoData = PORT_ZAlloc(bufferSize);
    if (!pkcs1DigestInfoData) {
        PORT_SetError(SEC_ERROR_NO_MEMORY);
        return SECFailure;
    }

    pkcs1DigestInfo.data = pkcs1DigestInfoData;
    pkcs1DigestInfo.len = bufferSize;

    /* decrypt the block */
    rv = RSA_CheckSignRecover(&key->u.rsa, pkcs1DigestInfo.data,
                              &pkcs1DigestInfo.len, pkcs1DigestInfo.len,
                              sig, sigLen);
    if (rv != SECSuccess) {
        PORT_SetError(SEC_ERROR_BAD_SIGNATURE);
    } else {
        digest.data = (PRUint8 *)digestData;
        digest.len = digestLen;
        rv = _SGN_VerifyPKCS1DigestInfo(
            digestOid, &digest, &pkcs1DigestInfo,
            PR_TRUE /*XXX: unsafeAllowMissingParameters*/);
    }

    PORT_Free(pkcs1DigestInfoData);
    return rv;
}

static SECStatus
sftk_RSACheckSign(NSSLOWKEYPublicKey *key, const unsigned char *sig,
                  unsigned int sigLen, const unsigned char *digest,
                  unsigned int digestLen)
{
    PORT_Assert(key->keyType == NSSLOWKEYRSAKey);
    if (key->keyType != NSSLOWKEYRSAKey) {
        PORT_SetError(SEC_ERROR_INVALID_KEY);
        return SECFailure;
    }

    return RSA_CheckSign(&key->u.rsa, sig, sigLen, digest, digestLen);
}

static SECStatus
sftk_RSACheckSignRaw(NSSLOWKEYPublicKey *key, const unsigned char *sig,
                     unsigned int sigLen, const unsigned char *digest,
                     unsigned int digestLen)
{
    PORT_Assert(key->keyType == NSSLOWKEYRSAKey);
    if (key->keyType != NSSLOWKEYRSAKey) {
        PORT_SetError(SEC_ERROR_INVALID_KEY);
        return SECFailure;
    }

    return RSA_CheckSignRaw(&key->u.rsa, sig, sigLen, digest, digestLen);
}

static SECStatus
sftk_RSACheckSignPSS(SFTKHashVerifyInfo *info, const unsigned char *sig,
                     unsigned int sigLen, const unsigned char *digest,
                     unsigned int digestLen)
{
    HASH_HashType hashAlg;
    HASH_HashType maskHashAlg;
    CK_RSA_PKCS_PSS_PARAMS *params = (CK_RSA_PKCS_PSS_PARAMS *)info->params;

    PORT_Assert(info->key->keyType == NSSLOWKEYRSAKey);
    if (info->key->keyType != NSSLOWKEYRSAKey) {
        PORT_SetError(SEC_ERROR_INVALID_KEY);
        return SECFailure;
    }

    hashAlg = GetHashTypeFromMechanism(params->hashAlg);
    maskHashAlg = GetHashTypeFromMechanism(params->mgf);

    return RSA_CheckSignPSS(&info->key->u.rsa, hashAlg, maskHashAlg,
                            params->sLen, sig, sigLen, digest, digestLen);
}

/* NSC_VerifyInit initializes a verification operation,
 * where the signature is an appendix to the data,
 * and plaintext cannot be recovered from the signature (e.g. DSA) */
CK_RV
NSC_VerifyInit(CK_SESSION_HANDLE hSession,
               CK_MECHANISM_PTR pMechanism, CK_OBJECT_HANDLE hKey)
{
    SFTKSession *session;
    SFTKObject *key;
    SFTKSessionContext *context;
    CK_KEY_TYPE key_type;
    CK_RV crv = CKR_OK;
    NSSLOWKEYPublicKey *pubKey;
    SFTKHashVerifyInfo *info = NULL;

    CHECK_FORK();

    /* Block Cipher MACing Algorithms use a different Context init method..*/
    crv = sftk_InitCBCMac(hSession, pMechanism, hKey, CKA_VERIFY, SFTK_VERIFY);
    if (crv != CKR_FUNCTION_NOT_SUPPORTED)
        return crv;

    session = sftk_SessionFromHandle(hSession);
    if (session == NULL)
        return CKR_SESSION_HANDLE_INVALID;
    crv = sftk_InitGeneric(session, &context, SFTK_VERIFY, &key, hKey, &key_type,
                           CKO_PUBLIC_KEY, CKA_VERIFY);
    if (crv != CKR_OK) {
        sftk_FreeSession(session);
        return crv;
    }

    context->multi = PR_FALSE;

#define INIT_RSA_VFY_MECH(mmm)                            \
    case CKM_##mmm##_RSA_PKCS:                            \
        context->multi = PR_TRUE;                         \
        crv = sftk_doSub##mmm(context);                   \
        if (crv != CKR_OK)                                \
            break;                                        \
        context->verify = (SFTKVerify)sftk_hashCheckSign; \
        info = PORT_New(SFTKHashVerifyInfo);              \
        if (info == NULL) {                               \
            crv = CKR_HOST_MEMORY;                        \
            break;                                        \
        }                                                 \
        info->hashOid = SEC_OID_##mmm;                    \
        goto finish_rsa;

    switch (pMechanism->mechanism) {
        INIT_RSA_VFY_MECH(MD5)
        INIT_RSA_VFY_MECH(MD2)
        INIT_RSA_VFY_MECH(SHA1)
        INIT_RSA_VFY_MECH(SHA224)
        INIT_RSA_VFY_MECH(SHA256)
        INIT_RSA_VFY_MECH(SHA384)
        INIT_RSA_VFY_MECH(SHA512)

        case CKM_RSA_PKCS:
            context->verify = (SFTKVerify)sftk_RSACheckSign;
            goto finish_rsa;
        case CKM_RSA_X_509:
            context->verify = (SFTKVerify)sftk_RSACheckSignRaw;
        finish_rsa:
            if (key_type != CKK_RSA) {
                if (info)
                    PORT_Free(info);
                crv = CKR_KEY_TYPE_INCONSISTENT;
                break;
            }
            context->rsa = PR_TRUE;
            pubKey = sftk_GetPubKey(key, CKK_RSA, &crv);
            if (pubKey == NULL) {
                if (info)
                    PORT_Free(info);
                crv = CKR_KEY_TYPE_INCONSISTENT;
                break;
            }
            if (info) {
                info->key = pubKey;
                context->cipherInfo = info;
                context->destroy = sftk_Space;
            } else {
                context->cipherInfo = pubKey;
                context->destroy = sftk_Null;
            }
            break;
        case CKM_RSA_PKCS_PSS:
            if (key_type != CKK_RSA) {
                crv = CKR_KEY_TYPE_INCONSISTENT;
                break;
            }
            context->rsa = PR_TRUE;
            if (pMechanism->ulParameterLen != sizeof(CK_RSA_PKCS_PSS_PARAMS) ||
                !sftk_ValidatePssParams((const CK_RSA_PKCS_PSS_PARAMS *)pMechanism->pParameter)) {
                crv = CKR_MECHANISM_PARAM_INVALID;
                break;
            }
            info = PORT_New(SFTKHashVerifyInfo);
            if (info == NULL) {
                crv = CKR_HOST_MEMORY;
                break;
            }
            info->params = pMechanism->pParameter;
            info->key = sftk_GetPubKey(key, CKK_RSA, &crv);
            if (info->key == NULL) {
                PORT_Free(info);
                break;
            }
            context->cipherInfo = info;
            context->destroy = (SFTKDestroy)sftk_Space;
            context->verify = (SFTKVerify)sftk_RSACheckSignPSS;
            break;
        case CKM_DSA_SHA1:
            context->multi = PR_TRUE;
            crv = sftk_doSubSHA1(context);
            if (crv != CKR_OK)
                break;
        /* fall through */
        case CKM_DSA:
            if (key_type != CKK_DSA) {
                crv = CKR_KEY_TYPE_INCONSISTENT;
                break;
            }
            pubKey = sftk_GetPubKey(key, CKK_DSA, &crv);
            if (pubKey == NULL) {
                break;
            }
            context->cipherInfo = pubKey;
            context->verify = (SFTKVerify)nsc_DSA_Verify_Stub;
            context->destroy = sftk_Null;
            break;
        case CKM_ECDSA_SHA1:
            context->multi = PR_TRUE;
            crv = sftk_doSubSHA1(context);
            if (crv != CKR_OK)
                break;
        /* fall through */
        case CKM_ECDSA:
            if (key_type != CKK_EC) {
                crv = CKR_KEY_TYPE_INCONSISTENT;
                break;
            }
            pubKey = sftk_GetPubKey(key, CKK_EC, &crv);
            if (pubKey == NULL) {
                crv = CKR_HOST_MEMORY;
                break;
            }
            context->cipherInfo = pubKey;
            context->verify = (SFTKVerify)nsc_ECDSAVerifyStub;
            context->destroy = sftk_Null;
            break;

            INIT_HMAC_MECH(MD2)
            INIT_HMAC_MECH(MD5)
            INIT_HMAC_MECH(SHA224)
            INIT_HMAC_MECH(SHA256)
            INIT_HMAC_MECH(SHA384)
            INIT_HMAC_MECH(SHA512)

        case CKM_SHA_1_HMAC_GENERAL:
            PORT_Assert(pMechanism->pParameter);
            if (!pMechanism->pParameter) {
                crv = CKR_MECHANISM_PARAM_INVALID;
                break;
            }
            crv = sftk_doHMACInit(context, HASH_AlgSHA1, key,
                                  *(CK_ULONG *)pMechanism->pParameter);
            break;
        case CKM_SHA_1_HMAC:
            crv = sftk_doHMACInit(context, HASH_AlgSHA1, key, SHA1_LENGTH);
            break;

        case CKM_SSL3_MD5_MAC:
            PORT_Assert(pMechanism->pParameter);
            if (!pMechanism->pParameter) {
                crv = CKR_MECHANISM_PARAM_INVALID;
                break;
            }
            crv = sftk_doSSLMACInit(context, SEC_OID_MD5, key,
                                    *(CK_ULONG *)pMechanism->pParameter);
            break;
        case CKM_SSL3_SHA1_MAC:
            PORT_Assert(pMechanism->pParameter);
            if (!pMechanism->pParameter) {
                crv = CKR_MECHANISM_PARAM_INVALID;
                break;
            }
            crv = sftk_doSSLMACInit(context, SEC_OID_SHA1, key,
                                    *(CK_ULONG *)pMechanism->pParameter);
            break;
        case CKM_TLS_PRF_GENERAL:
            crv = sftk_TLSPRFInit(context, key, key_type, HASH_AlgNULL, 0);
            break;
        case CKM_NSS_TLS_PRF_GENERAL_SHA256:
            crv = sftk_TLSPRFInit(context, key, key_type, HASH_AlgSHA256, 0);
            break;

        default:
            crv = CKR_MECHANISM_INVALID;
            break;
    }

    if (crv != CKR_OK) {
        if (info)
            PORT_Free(info);
        sftk_FreeContext(context);
        sftk_FreeSession(session);
        return crv;
    }
    sftk_SetContextByType(session, SFTK_VERIFY, context);
    sftk_FreeSession(session);
    return CKR_OK;
}

/* NSC_Verify verifies a signature in a single-part operation,
 * where the signature is an appendix to the data,
 * and plaintext cannot be recovered from the signature */
CK_RV
NSC_Verify(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pData,
           CK_ULONG ulDataLen, CK_BYTE_PTR pSignature, CK_ULONG ulSignatureLen)
{
    SFTKSession *session;
    SFTKSessionContext *context;
    CK_RV crv;

    CHECK_FORK();

    /* make sure we're legal */
    crv = sftk_GetContext(hSession, &context, SFTK_VERIFY, PR_FALSE, &session);
    if (crv != CKR_OK)
        return crv;

    /* multi part Verifying are completely implemented by VerifyUpdate and
     * VerifyFinal */
    if (context->multi) {
        /* VerifyFinal can't follow failed VerifyUpdate */
        if (CKR_OK == (crv = NSC_VerifyUpdate(hSession, pData, ulDataLen)))
            crv = NSC_VerifyFinal(hSession, pSignature, ulSignatureLen);
    } else {
        if (SECSuccess != (*context->verify)(context->cipherInfo, pSignature,
                                             ulSignatureLen, pData, ulDataLen))
            crv = sftk_MapCryptError(PORT_GetError());

        sftk_TerminateOp(session, SFTK_VERIFY, context);
    }
    sftk_FreeSession(session);
    return crv;
}

/* NSC_VerifyUpdate continues a multiple-part verification operation,
 * where the signature is an appendix to the data,
 * and plaintext cannot be recovered from the signature
 *
 * A call which results in an error terminates the operation [PKCS#11,v2.11]
 */
CK_RV
NSC_VerifyUpdate(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pPart,
                 CK_ULONG ulPartLen)
{
    CHECK_FORK();
    return sftk_MACUpdate(hSession, pPart, ulPartLen, SFTK_VERIFY);
}

/* NSC_VerifyFinal finishes a multiple-part verification operation,
 * checking the signature. */
CK_RV
NSC_VerifyFinal(CK_SESSION_HANDLE hSession,
                CK_BYTE_PTR pSignature, CK_ULONG ulSignatureLen)
{
    SFTKSession *session;
    SFTKSessionContext *context;
    CK_RV crv;

    CHECK_FORK();

    if (!pSignature)
        return CKR_ARGUMENTS_BAD;

    /* make sure we're legal */
    crv = sftk_GetContext(hSession, &context, SFTK_VERIFY, PR_TRUE, &session);
    if (crv != CKR_OK)
        return crv;

    if (context->hashInfo) {
        unsigned int digestLen;
        unsigned char tmpbuf[SFTK_MAX_MAC_LENGTH];

        (*context->end)(context->hashInfo, tmpbuf, &digestLen, sizeof(tmpbuf));
        if (SECSuccess != (context->verify)(context->cipherInfo, pSignature,
                                            ulSignatureLen, tmpbuf, digestLen))
            crv = sftk_MapCryptError(PORT_GetError());
    } else if (ulSignatureLen != context->macSize) {
        /* must be block cipher MACing */
        crv = CKR_SIGNATURE_LEN_RANGE;
    } else if (CKR_OK == (crv = sftk_MACFinal(context))) {
        if (PORT_Memcmp(pSignature, context->macBuf, ulSignatureLen))
            crv = CKR_SIGNATURE_INVALID;
    }

    sftk_TerminateOp(session, SFTK_VERIFY, context);
    sftk_FreeSession(session);
    return crv;
}

/*
 ************** Crypto Functions:     Verify  Recover ************************
 */
static SECStatus
sftk_RSACheckSignRecover(NSSLOWKEYPublicKey *key, unsigned char *data,
                         unsigned int *dataLen, unsigned int maxDataLen,
                         const unsigned char *sig, unsigned int sigLen)
{
    PORT_Assert(key->keyType == NSSLOWKEYRSAKey);
    if (key->keyType != NSSLOWKEYRSAKey) {
        PORT_SetError(SEC_ERROR_INVALID_KEY);
        return SECFailure;
    }

    return RSA_CheckSignRecover(&key->u.rsa, data, dataLen, maxDataLen,
                                sig, sigLen);
}

static SECStatus
sftk_RSACheckSignRecoverRaw(NSSLOWKEYPublicKey *key, unsigned char *data,
                            unsigned int *dataLen, unsigned int maxDataLen,
                            const unsigned char *sig, unsigned int sigLen)
{
    PORT_Assert(key->keyType == NSSLOWKEYRSAKey);
    if (key->keyType != NSSLOWKEYRSAKey) {
        PORT_SetError(SEC_ERROR_INVALID_KEY);
        return SECFailure;
    }

    return RSA_CheckSignRecoverRaw(&key->u.rsa, data, dataLen, maxDataLen,
                                   sig, sigLen);
}

/* NSC_VerifyRecoverInit initializes a signature verification operation,
 * where the data is recovered from the signature.
 * E.g. Decryption with the user's public key */
CK_RV
NSC_VerifyRecoverInit(CK_SESSION_HANDLE hSession,
                      CK_MECHANISM_PTR pMechanism, CK_OBJECT_HANDLE hKey)
{
    SFTKSession *session;
    SFTKObject *key;
    SFTKSessionContext *context;
    CK_KEY_TYPE key_type;
    CK_RV crv = CKR_OK;
    NSSLOWKEYPublicKey *pubKey;

    CHECK_FORK();

    session = sftk_SessionFromHandle(hSession);
    if (session == NULL)
        return CKR_SESSION_HANDLE_INVALID;
    crv = sftk_InitGeneric(session, &context, SFTK_VERIFY_RECOVER,
                           &key, hKey, &key_type, CKO_PUBLIC_KEY, CKA_VERIFY_RECOVER);
    if (crv != CKR_OK) {
        sftk_FreeSession(session);
        return crv;
    }

    context->multi = PR_TRUE;

    switch (pMechanism->mechanism) {
        case CKM_RSA_PKCS:
        case CKM_RSA_X_509:
            if (key_type != CKK_RSA) {
                crv = CKR_KEY_TYPE_INCONSISTENT;
                break;
            }
            context->multi = PR_FALSE;
            context->rsa = PR_TRUE;
            pubKey = sftk_GetPubKey(key, CKK_RSA, &crv);
            if (pubKey == NULL) {
                break;
            }
            context->cipherInfo = pubKey;
            context->update = (SFTKCipher)(pMechanism->mechanism == CKM_RSA_X_509
                                               ? sftk_RSACheckSignRecoverRaw
                                               : sftk_RSACheckSignRecover);
            context->destroy = sftk_Null;
            break;
        default:
            crv = CKR_MECHANISM_INVALID;
            break;
    }

    if (crv != CKR_OK) {
        PORT_Free(context);
        sftk_FreeSession(session);
        return crv;
    }
    sftk_SetContextByType(session, SFTK_VERIFY_RECOVER, context);
    sftk_FreeSession(session);
    return CKR_OK;
}

/* NSC_VerifyRecover verifies a signature in a single-part operation,
 * where the data is recovered from the signature.
 * E.g. Decryption with the user's public key */
CK_RV
NSC_VerifyRecover(CK_SESSION_HANDLE hSession,
                  CK_BYTE_PTR pSignature, CK_ULONG ulSignatureLen,
                  CK_BYTE_PTR pData, CK_ULONG_PTR pulDataLen)
{
    SFTKSession *session;
    SFTKSessionContext *context;
    unsigned int outlen;
    unsigned int maxoutlen = *pulDataLen;
    CK_RV crv;
    SECStatus rv;

    CHECK_FORK();

    /* make sure we're legal */
    crv = sftk_GetContext(hSession, &context, SFTK_VERIFY_RECOVER,
                          PR_FALSE, &session);
    if (crv != CKR_OK)
        return crv;
    if (pData == NULL) {
        /* to return the actual size, we need  to do the decrypt, just return
         * the max size, which is the size of the input signature. */
        *pulDataLen = ulSignatureLen;
        rv = SECSuccess;
        goto finish;
    }

    rv = (*context->update)(context->cipherInfo, pData, &outlen, maxoutlen,
                            pSignature, ulSignatureLen);
    *pulDataLen = (CK_ULONG)outlen;

    sftk_TerminateOp(session, SFTK_VERIFY_RECOVER, context);
finish:
    sftk_FreeSession(session);
    return (rv == SECSuccess) ? CKR_OK : sftk_MapVerifyError(PORT_GetError());
}

/*
 **************************** Random Functions:  ************************
 */

/* NSC_SeedRandom mixes additional seed material into the token's random number
 * generator. */
CK_RV
NSC_SeedRandom(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pSeed,
               CK_ULONG ulSeedLen)
{
    SECStatus rv;

    CHECK_FORK();

    rv = RNG_RandomUpdate(pSeed, ulSeedLen);
    return (rv == SECSuccess) ? CKR_OK : sftk_MapCryptError(PORT_GetError());
}

/* NSC_GenerateRandom generates random data. */
CK_RV
NSC_GenerateRandom(CK_SESSION_HANDLE hSession,
                   CK_BYTE_PTR pRandomData, CK_ULONG ulRandomLen)
{
    SECStatus rv;

    CHECK_FORK();

    rv = RNG_GenerateGlobalRandomBytes(pRandomData, ulRandomLen);
    /*
     * This may fail with SEC_ERROR_NEED_RANDOM, which means the RNG isn't
     * seeded with enough entropy.
     */
    return (rv == SECSuccess) ? CKR_OK : sftk_MapCryptError(PORT_GetError());
}

/*
 **************************** Key Functions:  ************************
 */

/*
 * generate a password based encryption key. This code uses
 * PKCS5 to do the work.
 */
static CK_RV
nsc_pbe_key_gen(NSSPKCS5PBEParameter *pkcs5_pbe, CK_MECHANISM_PTR pMechanism,
                void *buf, CK_ULONG *key_length, PRBool faulty3DES)
{
    SECItem *pbe_key = NULL, iv, pwitem;
    CK_PBE_PARAMS *pbe_params = NULL;
    CK_PKCS5_PBKD2_PARAMS *pbkd2_params = NULL;

    *key_length = 0;
    iv.data = NULL;
    iv.len = 0;

    if (pMechanism->mechanism == CKM_PKCS5_PBKD2) {
        pbkd2_params = (CK_PKCS5_PBKD2_PARAMS *)pMechanism->pParameter;
        pwitem.data = (unsigned char *)pbkd2_params->pPassword;
        /* was this a typo in the PKCS #11 spec? */
        pwitem.len = *pbkd2_params->ulPasswordLen;
    } else {
        pbe_params = (CK_PBE_PARAMS *)pMechanism->pParameter;
        pwitem.data = (unsigned char *)pbe_params->pPassword;
        pwitem.len = pbe_params->ulPasswordLen;
    }
    pbe_key = nsspkcs5_ComputeKeyAndIV(pkcs5_pbe, &pwitem, &iv, faulty3DES);
    if (pbe_key == NULL) {
        return CKR_HOST_MEMORY;
    }

    PORT_Memcpy(buf, pbe_key->data, pbe_key->len);
    *key_length = pbe_key->len;
    SECITEM_ZfreeItem(pbe_key, PR_TRUE);
    pbe_key = NULL;

    if (iv.data) {
        if (pbe_params && pbe_params->pInitVector != NULL) {
            PORT_Memcpy(pbe_params->pInitVector, iv.data, iv.len);
        }
        PORT_Free(iv.data);
    }

    return CKR_OK;
}

/*
 * this is coded for "full" support. These selections will be limitted to
 * the official subset by freebl.
 */
static unsigned int
sftk_GetSubPrimeFromPrime(unsigned int primeBits)
{
    if (primeBits <= 1024) {
        return 160;
    } else if (primeBits <= 2048) {
        return 224;
    } else if (primeBits <= 3072) {
        return 256;
    } else if (primeBits <= 7680) {
        return 384;
    } else {
        return 512;
    }
}

static CK_RV
nsc_parameter_gen(CK_KEY_TYPE key_type, SFTKObject *key)
{
    SFTKAttribute *attribute;
    CK_ULONG counter;
    unsigned int seedBits = 0;
    unsigned int subprimeBits = 0;
    unsigned int primeBits;
    unsigned int j = 8; /* default to 1024 bits */
    CK_RV crv = CKR_OK;
    PQGParams *params = NULL;
    PQGVerify *vfy = NULL;
    SECStatus rv;

    attribute = sftk_FindAttribute(key, CKA_PRIME_BITS);
    if (attribute == NULL) {
        attribute = sftk_FindAttribute(key, CKA_PRIME);
        if (attribute == NULL) {
            return CKR_TEMPLATE_INCOMPLETE;
        } else {
            primeBits = attribute->attrib.ulValueLen;
            sftk_FreeAttribute(attribute);
        }
    } else {
        primeBits = (unsigned int)*(CK_ULONG *)attribute->attrib.pValue;
        sftk_FreeAttribute(attribute);
    }
    if (primeBits < 1024) {
        j = PQG_PBITS_TO_INDEX(primeBits);
        if (j == (unsigned int)-1) {
            return CKR_ATTRIBUTE_VALUE_INVALID;
        }
    }

    attribute = sftk_FindAttribute(key, CKA_NSS_PQG_SEED_BITS);
    if (attribute != NULL) {
        seedBits = (unsigned int)*(CK_ULONG *)attribute->attrib.pValue;
        sftk_FreeAttribute(attribute);
    }

    attribute = sftk_FindAttribute(key, CKA_SUBPRIME_BITS);
    if (attribute != NULL) {
        subprimeBits = (unsigned int)*(CK_ULONG *)attribute->attrib.pValue;
        sftk_FreeAttribute(attribute);
    }

    /* if P and Q are supplied, we want to generate a new G */
    attribute = sftk_FindAttribute(key, CKA_PRIME);
    if (attribute != NULL) {
        PLArenaPool *arena;

        sftk_FreeAttribute(attribute);
        arena = PORT_NewArena(1024);
        if (arena == NULL) {
            crv = CKR_HOST_MEMORY;
            goto loser;
        }
        params = PORT_ArenaAlloc(arena, sizeof(*params));
        if (params == NULL) {
            crv = CKR_HOST_MEMORY;
            goto loser;
        }
        params->arena = arena;
        crv = sftk_Attribute2SSecItem(arena, &params->prime, key, CKA_PRIME);
        if (crv != CKR_OK) {
            goto loser;
        }
        crv = sftk_Attribute2SSecItem(arena, &params->subPrime,
                                      key, CKA_SUBPRIME);
        if (crv != CKR_OK) {
            goto loser;
        }

        arena = PORT_NewArena(1024);
        if (arena == NULL) {
            crv = CKR_HOST_MEMORY;
            goto loser;
        }
        vfy = PORT_ArenaAlloc(arena, sizeof(*vfy));
        if (vfy == NULL) {
            crv = CKR_HOST_MEMORY;
            goto loser;
        }
        vfy->arena = arena;
        crv = sftk_Attribute2SSecItem(arena, &vfy->seed, key, CKA_NSS_PQG_SEED);
        if (crv != CKR_OK) {
            goto loser;
        }
        crv = sftk_Attribute2SSecItem(arena, &vfy->h, key, CKA_NSS_PQG_H);
        if (crv != CKR_OK) {
            goto loser;
        }
        sftk_DeleteAttributeType(key, CKA_PRIME);
        sftk_DeleteAttributeType(key, CKA_SUBPRIME);
        sftk_DeleteAttributeType(key, CKA_NSS_PQG_SEED);
        sftk_DeleteAttributeType(key, CKA_NSS_PQG_H);
    }

    sftk_DeleteAttributeType(key, CKA_PRIME_BITS);
    sftk_DeleteAttributeType(key, CKA_SUBPRIME_BITS);
    sftk_DeleteAttributeType(key, CKA_NSS_PQG_SEED_BITS);

    /* use the old PQG interface if we have old input data */
    if ((primeBits < 1024) || ((primeBits == 1024) && (subprimeBits == 0))) {
        if (seedBits == 0) {
            rv = PQG_ParamGen(j, &params, &vfy);
        } else {
            rv = PQG_ParamGenSeedLen(j, seedBits / 8, &params, &vfy);
        }
    } else {
        if (subprimeBits == 0) {
            subprimeBits = sftk_GetSubPrimeFromPrime(primeBits);
        }
        if (seedBits == 0) {
            seedBits = primeBits;
        }
        rv = PQG_ParamGenV2(primeBits, subprimeBits, seedBits / 8, &params, &vfy);
    }

    if (rv != SECSuccess) {
        if (PORT_GetError() == SEC_ERROR_LIBRARY_FAILURE) {
            sftk_fatalError = PR_TRUE;
        }
        return sftk_MapCryptError(PORT_GetError());
    }
    crv = sftk_AddAttributeType(key, CKA_PRIME,
                                params->prime.data, params->prime.len);
    if (crv != CKR_OK)
        goto loser;
    crv = sftk_AddAttributeType(key, CKA_SUBPRIME,
                                params->subPrime.data, params->subPrime.len);
    if (crv != CKR_OK)
        goto loser;
    crv = sftk_AddAttributeType(key, CKA_BASE,
                                params->base.data, params->base.len);
    if (crv != CKR_OK)
        goto loser;
    counter = vfy->counter;
    crv = sftk_AddAttributeType(key, CKA_NSS_PQG_COUNTER,
                                &counter, sizeof(counter));
    crv = sftk_AddAttributeType(key, CKA_NSS_PQG_SEED,
                                vfy->seed.data, vfy->seed.len);
    if (crv != CKR_OK)
        goto loser;
    crv = sftk_AddAttributeType(key, CKA_NSS_PQG_H,
                                vfy->h.data, vfy->h.len);
    if (crv != CKR_OK)
        goto loser;

loser:
    if (params) {
        PQG_DestroyParams(params);
    }

    if (vfy) {
        PQG_DestroyVerify(vfy);
    }
    return crv;
}

static CK_RV
nsc_SetupBulkKeyGen(CK_MECHANISM_TYPE mechanism, CK_KEY_TYPE *key_type,
                    CK_ULONG *key_length)
{
    CK_RV crv = CKR_OK;

    switch (mechanism) {
        case CKM_RC2_KEY_GEN:
            *key_type = CKK_RC2;
            if (*key_length == 0)
                crv = CKR_TEMPLATE_INCOMPLETE;
            break;
#if NSS_SOFTOKEN_DOES_RC5
        case CKM_RC5_KEY_GEN:
            *key_type = CKK_RC5;
            if (*key_length == 0)
                crv = CKR_TEMPLATE_INCOMPLETE;
            break;
#endif
        case CKM_RC4_KEY_GEN:
            *key_type = CKK_RC4;
            if (*key_length == 0)
                crv = CKR_TEMPLATE_INCOMPLETE;
            break;
        case CKM_GENERIC_SECRET_KEY_GEN:
            *key_type = CKK_GENERIC_SECRET;
            if (*key_length == 0)
                crv = CKR_TEMPLATE_INCOMPLETE;
            break;
        case CKM_CDMF_KEY_GEN:
            *key_type = CKK_CDMF;
            *key_length = 8;
            break;
        case CKM_DES_KEY_GEN:
            *key_type = CKK_DES;
            *key_length = 8;
            break;
        case CKM_DES2_KEY_GEN:
            *key_type = CKK_DES2;
            *key_length = 16;
            break;
        case CKM_DES3_KEY_GEN:
            *key_type = CKK_DES3;
            *key_length = 24;
            break;
        case CKM_SEED_KEY_GEN:
            *key_type = CKK_SEED;
            *key_length = 16;
            break;
        case CKM_CAMELLIA_KEY_GEN:
            *key_type = CKK_CAMELLIA;
            if (*key_length == 0)
                crv = CKR_TEMPLATE_INCOMPLETE;
            break;
        case CKM_AES_KEY_GEN:
            *key_type = CKK_AES;
            if (*key_length == 0)
                crv = CKR_TEMPLATE_INCOMPLETE;
            break;
        case CKM_NSS_CHACHA20_KEY_GEN:
            *key_type = CKK_NSS_CHACHA20;
            if (*key_length == 0)
                crv = CKR_TEMPLATE_INCOMPLETE;
            break;
        default:
            PORT_Assert(0);
            crv = CKR_MECHANISM_INVALID;
            break;
    }

    return crv;
}

CK_RV
nsc_SetupHMACKeyGen(CK_MECHANISM_PTR pMechanism, NSSPKCS5PBEParameter **pbe)
{
    SECItem salt;
    CK_PBE_PARAMS *pbe_params = NULL;
    NSSPKCS5PBEParameter *params;
    PLArenaPool *arena = NULL;
    SECStatus rv;

    *pbe = NULL;

    arena = PORT_NewArena(SEC_ASN1_DEFAULT_ARENA_SIZE);
    if (arena == NULL) {
        return CKR_HOST_MEMORY;
    }

    params = (NSSPKCS5PBEParameter *)PORT_ArenaZAlloc(arena,
                                                      sizeof(NSSPKCS5PBEParameter));
    if (params == NULL) {
        PORT_FreeArena(arena, PR_TRUE);
        return CKR_HOST_MEMORY;
    }

    params->poolp = arena;
    params->ivLen = 0;
    params->pbeType = NSSPKCS5_PKCS12_V2;
    params->hashType = HASH_AlgSHA1;
    params->encAlg = SEC_OID_SHA1; /* any invalid value */
    params->is2KeyDES = PR_FALSE;
    params->keyID = pbeBitGenIntegrityKey;
    pbe_params = (CK_PBE_PARAMS *)pMechanism->pParameter;
    params->iter = pbe_params->ulIteration;

    salt.data = (unsigned char *)pbe_params->pSalt;
    salt.len = (unsigned int)pbe_params->ulSaltLen;
    salt.type = siBuffer;
    rv = SECITEM_CopyItem(arena, &params->salt, &salt);
    if (rv != SECSuccess) {
        PORT_FreeArena(arena, PR_TRUE);
        return CKR_HOST_MEMORY;
    }
    switch (pMechanism->mechanism) {
        case CKM_NETSCAPE_PBE_SHA1_HMAC_KEY_GEN:
        case CKM_PBA_SHA1_WITH_SHA1_HMAC:
            params->hashType = HASH_AlgSHA1;
            params->keyLen = 20;
            break;
        case CKM_NETSCAPE_PBE_MD5_HMAC_KEY_GEN:
            params->hashType = HASH_AlgMD5;
            params->keyLen = 16;
            break;
        case CKM_NETSCAPE_PBE_MD2_HMAC_KEY_GEN:
            params->hashType = HASH_AlgMD2;
            params->keyLen = 16;
            break;
        case CKM_NSS_PKCS12_PBE_SHA224_HMAC_KEY_GEN:
            params->hashType = HASH_AlgSHA224;
            params->keyLen = 28;
            break;
        case CKM_NSS_PKCS12_PBE_SHA256_HMAC_KEY_GEN:
            params->hashType = HASH_AlgSHA256;
            params->keyLen = 32;
            break;
        case CKM_NSS_PKCS12_PBE_SHA384_HMAC_KEY_GEN:
            params->hashType = HASH_AlgSHA384;
            params->keyLen = 48;
            break;
        case CKM_NSS_PKCS12_PBE_SHA512_HMAC_KEY_GEN:
            params->hashType = HASH_AlgSHA512;
            params->keyLen = 64;
            break;
        default:
            PORT_FreeArena(arena, PR_TRUE);
            return CKR_MECHANISM_INVALID;
    }
    *pbe = params;
    return CKR_OK;
}

/* maybe this should be table driven? */
static CK_RV
nsc_SetupPBEKeyGen(CK_MECHANISM_PTR pMechanism, NSSPKCS5PBEParameter **pbe,
                   CK_KEY_TYPE *key_type, CK_ULONG *key_length)
{
    CK_RV crv = CKR_OK;
    SECOidData *oid;
    CK_PBE_PARAMS *pbe_params = NULL;
    NSSPKCS5PBEParameter *params = NULL;
    HASH_HashType hashType = HASH_AlgSHA1;
    CK_PKCS5_PBKD2_PARAMS *pbkd2_params = NULL;
    SECItem salt;
    CK_ULONG iteration = 0;

    *pbe = NULL;

    oid = SECOID_FindOIDByMechanism(pMechanism->mechanism);
    if (oid == NULL) {
        return CKR_MECHANISM_INVALID;
    }

    if (pMechanism->mechanism == CKM_PKCS5_PBKD2) {
        pbkd2_params = (CK_PKCS5_PBKD2_PARAMS *)pMechanism->pParameter;
        if (pbkd2_params == NULL) {
            return CKR_MECHANISM_PARAM_INVALID;
        }
        switch (pbkd2_params->prf) {
            case CKP_PKCS5_PBKD2_HMAC_SHA1:
                hashType = HASH_AlgSHA1;
                break;
            case CKP_PKCS5_PBKD2_HMAC_SHA224:
                hashType = HASH_AlgSHA224;
                break;
            case CKP_PKCS5_PBKD2_HMAC_SHA256:
                hashType = HASH_AlgSHA256;
                break;
            case CKP_PKCS5_PBKD2_HMAC_SHA384:
                hashType = HASH_AlgSHA384;
                break;
            case CKP_PKCS5_PBKD2_HMAC_SHA512:
                hashType = HASH_AlgSHA512;
                break;
            default:
                return CKR_MECHANISM_PARAM_INVALID;
        }
        if (pbkd2_params->saltSource != CKZ_SALT_SPECIFIED) {
            return CKR_MECHANISM_PARAM_INVALID;
        }
        salt.data = (unsigned char *)pbkd2_params->pSaltSourceData;
        salt.len = (unsigned int)pbkd2_params->ulSaltSourceDataLen;
        iteration = pbkd2_params->iterations;
    } else {
        pbe_params = (CK_PBE_PARAMS *)pMechanism->pParameter;
        salt.data = (unsigned char *)pbe_params->pSalt;
        salt.len = (unsigned int)pbe_params->ulSaltLen;
        iteration = pbe_params->ulIteration;
    }
    params = nsspkcs5_NewParam(oid->offset, hashType, &salt, iteration);
    if (params == NULL) {
        return CKR_MECHANISM_INVALID;
    }

    switch (params->encAlg) {
        case SEC_OID_DES_CBC:
            *key_type = CKK_DES;
            *key_length = params->keyLen;
            break;
        case SEC_OID_DES_EDE3_CBC:
            *key_type = params->is2KeyDES ? CKK_DES2 : CKK_DES3;
            *key_length = params->keyLen;
            break;
        case SEC_OID_RC2_CBC:
            *key_type = CKK_RC2;
            *key_length = params->keyLen;
            break;
        case SEC_OID_RC4:
            *key_type = CKK_RC4;
            *key_length = params->keyLen;
            break;
        case SEC_OID_PKCS5_PBKDF2:
            /* key type must already be set */
            if (*key_type == CKK_INVALID_KEY_TYPE) {
                crv = CKR_TEMPLATE_INCOMPLETE;
                break;
            }
            /* PBKDF2 needs to calculate the key length from the other parameters
             */
            if (*key_length == 0) {
                *key_length = sftk_MapKeySize(*key_type);
            }
            if (*key_length == 0) {
                crv = CKR_TEMPLATE_INCOMPLETE;
                break;
            }
            params->keyLen = *key_length;
            break;
        default:
            crv = CKR_MECHANISM_INVALID;
            nsspkcs5_DestroyPBEParameter(params);
            break;
    }
    if (crv == CKR_OK) {
        *pbe = params;
    }
    return crv;
}

/* NSC_GenerateKey generates a secret key, creating a new key object. */
CK_RV
NSC_GenerateKey(CK_SESSION_HANDLE hSession,
                CK_MECHANISM_PTR pMechanism, CK_ATTRIBUTE_PTR pTemplate, CK_ULONG ulCount,
                CK_OBJECT_HANDLE_PTR phKey)
{
    SFTKObject *key;
    SFTKSession *session;
    PRBool checkWeak = PR_FALSE;
    CK_ULONG key_length = 0;
    CK_KEY_TYPE key_type = CKK_INVALID_KEY_TYPE;
    CK_OBJECT_CLASS objclass = CKO_SECRET_KEY;
    CK_RV crv = CKR_OK;
    CK_BBOOL cktrue = CK_TRUE;
    int i;
    SFTKSlot *slot = sftk_SlotFromSessionHandle(hSession);
    unsigned char buf[MAX_KEY_LEN];
    enum { nsc_pbe,
           nsc_ssl,
           nsc_bulk,
           nsc_param,
           nsc_jpake } key_gen_type;
    NSSPKCS5PBEParameter *pbe_param;
    SSL3RSAPreMasterSecret *rsa_pms;
    CK_VERSION *version;
    /* in very old versions of NSS, there were implementation errors with key
     * generation methods.  We want to beable to read these, but not
     * produce them any more.  The affected algorithm was 3DES.
     */
    PRBool faultyPBE3DES = PR_FALSE;
    HASH_HashType hashType = HASH_AlgNULL;

    CHECK_FORK();

    if (!slot) {
        return CKR_SESSION_HANDLE_INVALID;
    }
    /*
     * now lets create an object to hang the attributes off of
     */
    key = sftk_NewObject(slot); /* fill in the handle later */
    if (key == NULL) {
        return CKR_HOST_MEMORY;
    }

    /*
     * load the template values into the object
     */
    for (i = 0; i < (int)ulCount; i++) {
        if (pTemplate[i].type == CKA_VALUE_LEN) {
            key_length = *(CK_ULONG *)pTemplate[i].pValue;
            continue;
        }
        /* some algorithms need keytype specified */
        if (pTemplate[i].type == CKA_KEY_TYPE) {
            key_type = *(CK_ULONG *)pTemplate[i].pValue;
            continue;
        }

        crv = sftk_AddAttributeType(key, sftk_attr_expand(&pTemplate[i]));
        if (crv != CKR_OK)
            break;
    }
    if (crv != CKR_OK) {
        sftk_FreeObject(key);
        return crv;
    }

    /* make sure we don't have any class, key_type, or value fields */
    sftk_DeleteAttributeType(key, CKA_CLASS);
    sftk_DeleteAttributeType(key, CKA_KEY_TYPE);
    sftk_DeleteAttributeType(key, CKA_VALUE);

    /* Now Set up the parameters to generate the key (based on mechanism) */
    key_gen_type = nsc_bulk; /* bulk key by default */
    switch (pMechanism->mechanism) {
        case CKM_CDMF_KEY_GEN:
        case CKM_DES_KEY_GEN:
        case CKM_DES2_KEY_GEN:
        case CKM_DES3_KEY_GEN:
            checkWeak = PR_TRUE;
        /* fall through */
        case CKM_RC2_KEY_GEN:
        case CKM_RC4_KEY_GEN:
        case CKM_GENERIC_SECRET_KEY_GEN:
        case CKM_SEED_KEY_GEN:
        case CKM_CAMELLIA_KEY_GEN:
        case CKM_AES_KEY_GEN:
        case CKM_NSS_CHACHA20_KEY_GEN:
#if NSS_SOFTOKEN_DOES_RC5
        case CKM_RC5_KEY_GEN:
#endif
            crv = nsc_SetupBulkKeyGen(pMechanism->mechanism, &key_type, &key_length);
            break;
        case CKM_SSL3_PRE_MASTER_KEY_GEN:
            key_type = CKK_GENERIC_SECRET;
            key_length = 48;
            key_gen_type = nsc_ssl;
            break;
        case CKM_PBA_SHA1_WITH_SHA1_HMAC:
        case CKM_NETSCAPE_PBE_SHA1_HMAC_KEY_GEN:
        case CKM_NETSCAPE_PBE_MD5_HMAC_KEY_GEN:
        case CKM_NETSCAPE_PBE_MD2_HMAC_KEY_GEN:
        case CKM_NSS_PKCS12_PBE_SHA224_HMAC_KEY_GEN:
        case CKM_NSS_PKCS12_PBE_SHA256_HMAC_KEY_GEN:
        case CKM_NSS_PKCS12_PBE_SHA384_HMAC_KEY_GEN:
        case CKM_NSS_PKCS12_PBE_SHA512_HMAC_KEY_GEN:
            key_gen_type = nsc_pbe;
            key_type = CKK_GENERIC_SECRET;
            crv = nsc_SetupHMACKeyGen(pMechanism, &pbe_param);
            break;
        case CKM_NETSCAPE_PBE_SHA1_FAULTY_3DES_CBC:
            faultyPBE3DES = PR_TRUE;
        /* fall through */
        case CKM_NETSCAPE_PBE_SHA1_TRIPLE_DES_CBC:
        case CKM_NETSCAPE_PBE_SHA1_40_BIT_RC2_CBC:
        case CKM_NETSCAPE_PBE_SHA1_DES_CBC:
        case CKM_NETSCAPE_PBE_SHA1_128_BIT_RC2_CBC:
        case CKM_NETSCAPE_PBE_SHA1_40_BIT_RC4:
        case CKM_NETSCAPE_PBE_SHA1_128_BIT_RC4:
        case CKM_PBE_SHA1_DES3_EDE_CBC:
        case CKM_PBE_SHA1_DES2_EDE_CBC:
        case CKM_PBE_SHA1_RC2_128_CBC:
        case CKM_PBE_SHA1_RC2_40_CBC:
        case CKM_PBE_SHA1_RC4_128:
        case CKM_PBE_SHA1_RC4_40:
        case CKM_PBE_MD5_DES_CBC:
        case CKM_PBE_MD2_DES_CBC:
        case CKM_PKCS5_PBKD2:
            key_gen_type = nsc_pbe;
            crv = nsc_SetupPBEKeyGen(pMechanism, &pbe_param, &key_type, &key_length);
            break;
        case CKM_DSA_PARAMETER_GEN:
            key_gen_type = nsc_param;
            key_type = CKK_DSA;
            objclass = CKO_KG_PARAMETERS;
            crv = CKR_OK;
            break;
        case CKM_NSS_JPAKE_ROUND1_SHA1:
            hashType = HASH_AlgSHA1;
            goto jpake1;
        case CKM_NSS_JPAKE_ROUND1_SHA256:
            hashType = HASH_AlgSHA256;
            goto jpake1;
        case CKM_NSS_JPAKE_ROUND1_SHA384:
            hashType = HASH_AlgSHA384;
            goto jpake1;
        case CKM_NSS_JPAKE_ROUND1_SHA512:
            hashType = HASH_AlgSHA512;
            goto jpake1;
        jpake1:
            key_gen_type = nsc_jpake;
            key_type = CKK_NSS_JPAKE_ROUND1;
            objclass = CKO_PRIVATE_KEY;
            if (pMechanism->pParameter == NULL ||
                pMechanism->ulParameterLen != sizeof(CK_NSS_JPAKERound1Params)) {
                crv = CKR_MECHANISM_PARAM_INVALID;
                break;
            }
            if (sftk_isTrue(key, CKA_TOKEN)) {
                crv = CKR_TEMPLATE_INCONSISTENT;
                break;
            }
            crv = CKR_OK;
            break;
        default:
            crv = CKR_MECHANISM_INVALID;
            break;
    }

    /* make sure we aren't going to overflow the buffer */
    if (sizeof(buf) < key_length) {
        /* someone is getting pretty optimistic about how big their key can
         * be... */
        crv = CKR_TEMPLATE_INCONSISTENT;
    }

    if (crv != CKR_OK) {
        sftk_FreeObject(key);
        return crv;
    }

    /* if there was no error,
     * key_type *MUST* be set in the switch statement above */
    PORT_Assert(key_type != CKK_INVALID_KEY_TYPE);

    /*
     * now to the actual key gen.
     */
    switch (key_gen_type) {
        case nsc_pbe:
            crv = nsc_pbe_key_gen(pbe_param, pMechanism, buf, &key_length,
                                  faultyPBE3DES);
            nsspkcs5_DestroyPBEParameter(pbe_param);
            break;
        case nsc_ssl:
            rsa_pms = (SSL3RSAPreMasterSecret *)buf;
            version = (CK_VERSION *)pMechanism->pParameter;
            rsa_pms->client_version[0] = version->major;
            rsa_pms->client_version[1] = version->minor;
            crv =
                NSC_GenerateRandom(0, &rsa_pms->random[0], sizeof(rsa_pms->random));
            break;
        case nsc_bulk:
            /* get the key, check for weak keys and repeat if found */
            do {
                crv = NSC_GenerateRandom(0, buf, key_length);
            } while (crv == CKR_OK && checkWeak && sftk_IsWeakKey(buf, key_type));
            break;
        case nsc_param:
            /* generate parameters */
            *buf = 0;
            crv = nsc_parameter_gen(key_type, key);
            break;
        case nsc_jpake:
            crv = jpake_Round1(hashType,
                               (CK_NSS_JPAKERound1Params *)pMechanism->pParameter,
                               key);
            break;
    }

    if (crv != CKR_OK) {
        sftk_FreeObject(key);
        return crv;
    }

    /* Add the class, key_type, and value */
    crv = sftk_AddAttributeType(key, CKA_CLASS, &objclass, sizeof(CK_OBJECT_CLASS));
    if (crv != CKR_OK) {
        sftk_FreeObject(key);
        return crv;
    }
    crv = sftk_AddAttributeType(key, CKA_KEY_TYPE, &key_type, sizeof(CK_KEY_TYPE));
    if (crv != CKR_OK) {
        sftk_FreeObject(key);
        return crv;
    }
    if (key_length != 0) {
        crv = sftk_AddAttributeType(key, CKA_VALUE, buf, key_length);
        if (crv != CKR_OK) {
            sftk_FreeObject(key);
            return crv;
        }
    }

    /* get the session */
    session = sftk_SessionFromHandle(hSession);
    if (session == NULL) {
        sftk_FreeObject(key);
        return CKR_SESSION_HANDLE_INVALID;
    }

    /*
     * handle the base object stuff
     */
    crv = sftk_handleObject(key, session);
    sftk_FreeSession(session);
    if (crv == CKR_OK && sftk_isTrue(key, CKA_SENSITIVE)) {
        crv = sftk_forceAttribute(key, CKA_ALWAYS_SENSITIVE, &cktrue, sizeof(CK_BBOOL));
    }
    if (crv == CKR_OK && !sftk_isTrue(key, CKA_EXTRACTABLE)) {
        crv = sftk_forceAttribute(key, CKA_NEVER_EXTRACTABLE, &cktrue, sizeof(CK_BBOOL));
    }
    if (crv == CKR_OK) {
        *phKey = key->handle;
    }
    sftk_FreeObject(key);
    return crv;
}

#define PAIRWISE_DIGEST_LENGTH SHA1_LENGTH /* 160-bits */
#define PAIRWISE_MESSAGE_LENGTH 20         /* 160-bits */

/*
 * FIPS 140-2 pairwise consistency check utilized to validate key pair.
 *
 * This function returns
 *   CKR_OK               if pairwise consistency check passed
 *   CKR_GENERAL_ERROR    if pairwise consistency check failed
 *   other error codes    if paiswise consistency check could not be
 *                        performed, for example, CKR_HOST_MEMORY.
 */
static CK_RV
sftk_PairwiseConsistencyCheck(CK_SESSION_HANDLE hSession,
                              SFTKObject *publicKey, SFTKObject *privateKey, CK_KEY_TYPE keyType)
{
    /*
     *                      Key type    Mechanism type
     *                      --------------------------------
     * For encrypt/decrypt: CKK_RSA  => CKM_RSA_PKCS
     *                      others   => CKM_INVALID_MECHANISM
     *
     * For sign/verify:     CKK_RSA  => CKM_RSA_PKCS
     *                      CKK_DSA  => CKM_DSA
     *                      CKK_EC   => CKM_ECDSA
     *                      others   => CKM_INVALID_MECHANISM
     *
     * None of these mechanisms has a parameter.
     */
    CK_MECHANISM mech = { 0, NULL, 0 };

    CK_ULONG modulusLen = 0;
    CK_ULONG subPrimeLen = 0;
    PRBool isEncryptable = PR_FALSE;
    PRBool canSignVerify = PR_FALSE;
    PRBool isDerivable = PR_FALSE;
    CK_RV crv;

    /* Variables used for Encrypt/Decrypt functions. */
    unsigned char *known_message = (unsigned char *)"Known Crypto Message";
    unsigned char plaintext[PAIRWISE_MESSAGE_LENGTH];
    CK_ULONG bytes_decrypted;
    unsigned char *ciphertext;
    unsigned char *text_compared;
    CK_ULONG bytes_encrypted;
    CK_ULONG bytes_compared;
    CK_ULONG pairwise_digest_length = PAIRWISE_DIGEST_LENGTH;

    /* Variables used for Signature/Verification functions. */
    /* Must be at least 256 bits for DSA2 digest */
    unsigned char *known_digest = (unsigned char *)"Mozilla Rules the World through NSS!";
    unsigned char *signature;
    CK_ULONG signature_length;

    if (keyType == CKK_RSA) {
        SFTKAttribute *attribute;

        /* Get modulus length of private key. */
        attribute = sftk_FindAttribute(privateKey, CKA_MODULUS);
        if (attribute == NULL) {
            return CKR_DEVICE_ERROR;
        }
        modulusLen = attribute->attrib.ulValueLen;
        if (*(unsigned char *)attribute->attrib.pValue == 0) {
            modulusLen--;
        }
        sftk_FreeAttribute(attribute);
    } else if (keyType == CKK_DSA) {
        SFTKAttribute *attribute;

        /* Get subprime length of private key. */
        attribute = sftk_FindAttribute(privateKey, CKA_SUBPRIME);
        if (attribute == NULL) {
            return CKR_DEVICE_ERROR;
        }
        subPrimeLen = attribute->attrib.ulValueLen;
        if (subPrimeLen > 1 && *(unsigned char *)attribute->attrib.pValue == 0) {
            subPrimeLen--;
        }
        sftk_FreeAttribute(attribute);
    }

    /**************************************************/
    /* Pairwise Consistency Check of Encrypt/Decrypt. */
    /**************************************************/

    isEncryptable = sftk_isTrue(privateKey, CKA_DECRYPT);

    /*
     * If the decryption attribute is set, attempt to encrypt
     * with the public key and decrypt with the private key.
     */
    if (isEncryptable) {
        if (keyType != CKK_RSA) {
            return CKR_DEVICE_ERROR;
        }
        bytes_encrypted = modulusLen;
        mech.mechanism = CKM_RSA_PKCS;

        /* Allocate space for ciphertext. */
        ciphertext = (unsigned char *)PORT_ZAlloc(bytes_encrypted);
        if (ciphertext == NULL) {
            return CKR_HOST_MEMORY;
        }

        /* Prepare for encryption using the public key. */
        crv = NSC_EncryptInit(hSession, &mech, publicKey->handle);
        if (crv != CKR_OK) {
            PORT_Free(ciphertext);
            return crv;
        }

        /* Encrypt using the public key. */
        crv = NSC_Encrypt(hSession,
                          known_message,
                          PAIRWISE_MESSAGE_LENGTH,
                          ciphertext,
                          &bytes_encrypted);
        if (crv != CKR_OK) {
            PORT_Free(ciphertext);
            return crv;
        }

        /* Always use the smaller of these two values . . . */
        bytes_compared = PR_MIN(bytes_encrypted, PAIRWISE_MESSAGE_LENGTH);

        /*
         * If there was a failure, the plaintext
         * goes at the end, therefore . . .
         */
        text_compared = ciphertext + bytes_encrypted - bytes_compared;

        /*
         * Check to ensure that ciphertext does
         * NOT EQUAL known input message text
         * per FIPS PUB 140-2 directive.
         */
        if (PORT_Memcmp(text_compared, known_message,
                        bytes_compared) == 0) {
            /* Set error to Invalid PRIVATE Key. */
            PORT_SetError(SEC_ERROR_INVALID_KEY);
            PORT_Free(ciphertext);
            return CKR_GENERAL_ERROR;
        }

        /* Prepare for decryption using the private key. */
        crv = NSC_DecryptInit(hSession, &mech, privateKey->handle);
        if (crv != CKR_OK) {
            PORT_Free(ciphertext);
            return crv;
        }

        memset(plaintext, 0, PAIRWISE_MESSAGE_LENGTH);

        /*
         * Initialize bytes decrypted to be the
         * expected PAIRWISE_MESSAGE_LENGTH.
         */
        bytes_decrypted = PAIRWISE_MESSAGE_LENGTH;

        /*
         * Decrypt using the private key.
         * NOTE:  No need to reset the
         *        value of bytes_encrypted.
         */
        crv = NSC_Decrypt(hSession,
                          ciphertext,
                          bytes_encrypted,
                          plaintext,
                          &bytes_decrypted);

        /* Finished with ciphertext; free it. */
        PORT_Free(ciphertext);

        if (crv != CKR_OK) {
            return crv;
        }

        /*
         * Check to ensure that the output plaintext
         * does EQUAL known input message text.
         */
        if ((bytes_decrypted != PAIRWISE_MESSAGE_LENGTH) ||
            (PORT_Memcmp(plaintext, known_message,
                         PAIRWISE_MESSAGE_LENGTH) != 0)) {
            /* Set error to Bad PUBLIC Key. */
            PORT_SetError(SEC_ERROR_BAD_KEY);
            return CKR_GENERAL_ERROR;
        }
    }

    /**********************************************/
    /* Pairwise Consistency Check of Sign/Verify. */
    /**********************************************/

    canSignVerify = sftk_isTrue(privateKey, CKA_SIGN);
    /* Unfortunately CKA_SIGN is always true in lg dbs. We have to check the
     * actual curve to determine if we can do sign/verify. */
    if (canSignVerify && keyType == CKK_EC) {
        NSSLOWKEYPrivateKey *privKey = sftk_GetPrivKey(privateKey, CKK_EC, &crv);
        if (privKey && privKey->u.ec.ecParams.name == ECCurve25519) {
            canSignVerify = PR_FALSE;
        }
    }

    if (canSignVerify) {
        /* Determine length of signature. */
        switch (keyType) {
            case CKK_RSA:
                signature_length = modulusLen;
                mech.mechanism = CKM_RSA_PKCS;
                break;
            case CKK_DSA:
                signature_length = DSA_MAX_SIGNATURE_LEN;
                pairwise_digest_length = subPrimeLen;
                mech.mechanism = CKM_DSA;
                break;
            case CKK_EC:
                signature_length = MAX_ECKEY_LEN * 2;
                mech.mechanism = CKM_ECDSA;
                break;
            default:
                return CKR_DEVICE_ERROR;
        }

        /* Allocate space for signature data. */
        signature = (unsigned char *)PORT_ZAlloc(signature_length);
        if (signature == NULL) {
            return CKR_HOST_MEMORY;
        }

        /* Sign the known hash using the private key. */
        crv = NSC_SignInit(hSession, &mech, privateKey->handle);
        if (crv != CKR_OK) {
            PORT_Free(signature);
            return crv;
        }

        crv = NSC_Sign(hSession,
                       known_digest,
                       pairwise_digest_length,
                       signature,
                       &signature_length);
        if (crv != CKR_OK) {
            PORT_Free(signature);
            return crv;
        }

        /* Verify the known hash using the public key. */
        crv = NSC_VerifyInit(hSession, &mech, publicKey->handle);
        if (crv != CKR_OK) {
            PORT_Free(signature);
            return crv;
        }

        crv = NSC_Verify(hSession,
                         known_digest,
                         pairwise_digest_length,
                         signature,
                         signature_length);

        /* Free signature data. */
        PORT_Free(signature);

        if ((crv == CKR_SIGNATURE_LEN_RANGE) ||
            (crv == CKR_SIGNATURE_INVALID)) {
            return CKR_GENERAL_ERROR;
        }
        if (crv != CKR_OK) {
            return crv;
        }
    }

    /**********************************************/
    /* Pairwise Consistency Check for Derivation  */
    /**********************************************/

    isDerivable = sftk_isTrue(privateKey, CKA_DERIVE);

    if (isDerivable) {
        /*
         * We are not doing consistency check for Diffie-Hellman Key -
         * otherwise it would be here
         * This is also true for Elliptic Curve Diffie-Hellman keys
         * NOTE: EC keys are currently subjected to pairwise
         * consistency check for signing/verification.
         */
        /*
         * FIPS 140-2 had the following pairwise consistency test for
         * public and private keys used for key agreement:
         *   If the keys are used to perform key agreement, then the
         *   cryptographic module shall create a second, compatible
         *   key pair.  The cryptographic module shall perform both
         *   sides of the key agreement algorithm and shall compare
         *   the resulting shared values.  If the shared values are
         *   not equal, the test shall fail.
         * This test was removed in Change Notice 3.
         */
    }

    return CKR_OK;
}

/* NSC_GenerateKeyPair generates a public-key/private-key pair,
 * creating new key objects. */
CK_RV
NSC_GenerateKeyPair(CK_SESSION_HANDLE hSession,
                    CK_MECHANISM_PTR pMechanism, CK_ATTRIBUTE_PTR pPublicKeyTemplate,
                    CK_ULONG ulPublicKeyAttributeCount, CK_ATTRIBUTE_PTR pPrivateKeyTemplate,
                    CK_ULONG ulPrivateKeyAttributeCount, CK_OBJECT_HANDLE_PTR phPublicKey,
                    CK_OBJECT_HANDLE_PTR phPrivateKey)
{
    SFTKObject *publicKey, *privateKey;
    SFTKSession *session;
    CK_KEY_TYPE key_type;
    CK_RV crv = CKR_OK;
    CK_BBOOL cktrue = CK_TRUE;
    SECStatus rv;
    CK_OBJECT_CLASS pubClass = CKO_PUBLIC_KEY;
    CK_OBJECT_CLASS privClass = CKO_PRIVATE_KEY;
    int i;
    SFTKSlot *slot = sftk_SlotFromSessionHandle(hSession);
    unsigned int bitSize;

    /* RSA */
    int public_modulus_bits = 0;
    SECItem pubExp;
    RSAPrivateKey *rsaPriv;

    /* DSA */
    PQGParams pqgParam;
    DHParams dhParam;
    DSAPrivateKey *dsaPriv;

    /* Diffie Hellman */
    DHPrivateKey *dhPriv;

    /* Elliptic Curve Cryptography */
    SECItem ecEncodedParams; /* DER Encoded parameters */
    ECPrivateKey *ecPriv;
    ECParams *ecParams;

    CHECK_FORK();

    if (!slot) {
        return CKR_SESSION_HANDLE_INVALID;
    }
    /*
     * now lets create an object to hang the attributes off of
     */
    publicKey = sftk_NewObject(slot); /* fill in the handle later */
    if (publicKey == NULL) {
        return CKR_HOST_MEMORY;
    }

    /*
     * load the template values into the publicKey
     */
    for (i = 0; i < (int)ulPublicKeyAttributeCount; i++) {
        if (pPublicKeyTemplate[i].type == CKA_MODULUS_BITS) {
            public_modulus_bits = *(CK_ULONG *)pPublicKeyTemplate[i].pValue;
            continue;
        }

        crv = sftk_AddAttributeType(publicKey,
                                    sftk_attr_expand(&pPublicKeyTemplate[i]));
        if (crv != CKR_OK)
            break;
    }

    if (crv != CKR_OK) {
        sftk_FreeObject(publicKey);
        return CKR_HOST_MEMORY;
    }

    privateKey = sftk_NewObject(slot); /* fill in the handle later */
    if (privateKey == NULL) {
        sftk_FreeObject(publicKey);
        return CKR_HOST_MEMORY;
    }
    /*
     * now load the private key template
     */
    for (i = 0; i < (int)ulPrivateKeyAttributeCount; i++) {
        if (pPrivateKeyTemplate[i].type == CKA_VALUE_BITS) {
            continue;
        }

        crv = sftk_AddAttributeType(privateKey,
                                    sftk_attr_expand(&pPrivateKeyTemplate[i]));
        if (crv != CKR_OK)
            break;
    }

    if (crv != CKR_OK) {
        sftk_FreeObject(publicKey);
        sftk_FreeObject(privateKey);
        return CKR_HOST_MEMORY;
    }
    sftk_DeleteAttributeType(privateKey, CKA_CLASS);
    sftk_DeleteAttributeType(privateKey, CKA_KEY_TYPE);
    sftk_DeleteAttributeType(privateKey, CKA_VALUE);
    sftk_DeleteAttributeType(publicKey, CKA_CLASS);
    sftk_DeleteAttributeType(publicKey, CKA_KEY_TYPE);
    sftk_DeleteAttributeType(publicKey, CKA_VALUE);

    /* Now Set up the parameters to generate the key (based on mechanism) */
    switch (pMechanism->mechanism) {
        case CKM_RSA_PKCS_KEY_PAIR_GEN:
            /* format the keys */
            sftk_DeleteAttributeType(publicKey, CKA_MODULUS);
            sftk_DeleteAttributeType(privateKey, CKA_NETSCAPE_DB);
            sftk_DeleteAttributeType(privateKey, CKA_MODULUS);
            sftk_DeleteAttributeType(privateKey, CKA_PRIVATE_EXPONENT);
            sftk_DeleteAttributeType(privateKey, CKA_PUBLIC_EXPONENT);
            sftk_DeleteAttributeType(privateKey, CKA_PRIME_1);
            sftk_DeleteAttributeType(privateKey, CKA_PRIME_2);
            sftk_DeleteAttributeType(privateKey, CKA_EXPONENT_1);
            sftk_DeleteAttributeType(privateKey, CKA_EXPONENT_2);
            sftk_DeleteAttributeType(privateKey, CKA_COEFFICIENT);
            key_type = CKK_RSA;
            if (public_modulus_bits == 0) {
                crv = CKR_TEMPLATE_INCOMPLETE;
                break;
            }
            if (public_modulus_bits < RSA_MIN_MODULUS_BITS) {
                crv = CKR_ATTRIBUTE_VALUE_INVALID;
                break;
            }
            if (public_modulus_bits % 2 != 0) {
                crv = CKR_ATTRIBUTE_VALUE_INVALID;
                break;
            }

            /* extract the exponent */
            crv = sftk_Attribute2SSecItem(NULL, &pubExp, publicKey, CKA_PUBLIC_EXPONENT);
            if (crv != CKR_OK)
                break;
            bitSize = sftk_GetLengthInBits(pubExp.data, pubExp.len);
            if (bitSize < 2) {
                crv = CKR_ATTRIBUTE_VALUE_INVALID;
                break;
            }
            crv = sftk_AddAttributeType(privateKey, CKA_PUBLIC_EXPONENT,
                                        sftk_item_expand(&pubExp));
            if (crv != CKR_OK) {
                PORT_Free(pubExp.data);
                break;
            }

            rsaPriv = RSA_NewKey(public_modulus_bits, &pubExp);
            PORT_Free(pubExp.data);
            if (rsaPriv == NULL) {
                if (PORT_GetError() == SEC_ERROR_LIBRARY_FAILURE) {
                    sftk_fatalError = PR_TRUE;
                }
                crv = sftk_MapCryptError(PORT_GetError());
                break;
            }
            /* now fill in the RSA dependent paramenters in the public key */
            crv = sftk_AddAttributeType(publicKey, CKA_MODULUS,
                                        sftk_item_expand(&rsaPriv->modulus));
            if (crv != CKR_OK)
                goto kpg_done;
            /* now fill in the RSA dependent paramenters in the private key */
            crv = sftk_AddAttributeType(privateKey, CKA_NETSCAPE_DB,
                                        sftk_item_expand(&rsaPriv->modulus));
            if (crv != CKR_OK)
                goto kpg_done;
            crv = sftk_AddAttributeType(privateKey, CKA_MODULUS,
                                        sftk_item_expand(&rsaPriv->modulus));
            if (crv != CKR_OK)
                goto kpg_done;
            crv = sftk_AddAttributeType(privateKey, CKA_PRIVATE_EXPONENT,
                                        sftk_item_expand(&rsaPriv->privateExponent));
            if (crv != CKR_OK)
                goto kpg_done;
            crv = sftk_AddAttributeType(privateKey, CKA_PRIME_1,
                                        sftk_item_expand(&rsaPriv->prime1));
            if (crv != CKR_OK)
                goto kpg_done;
            crv = sftk_AddAttributeType(privateKey, CKA_PRIME_2,
                                        sftk_item_expand(&rsaPriv->prime2));
            if (crv != CKR_OK)
                goto kpg_done;
            crv = sftk_AddAttributeType(privateKey, CKA_EXPONENT_1,
                                        sftk_item_expand(&rsaPriv->exponent1));
            if (crv != CKR_OK)
                goto kpg_done;
            crv = sftk_AddAttributeType(privateKey, CKA_EXPONENT_2,
                                        sftk_item_expand(&rsaPriv->exponent2));
            if (crv != CKR_OK)
                goto kpg_done;
            crv = sftk_AddAttributeType(privateKey, CKA_COEFFICIENT,
                                        sftk_item_expand(&rsaPriv->coefficient));
        kpg_done:
            /* Should zeroize the contents first, since this func doesn't. */
            PORT_FreeArena(rsaPriv->arena, PR_TRUE);
            break;
        case CKM_DSA_KEY_PAIR_GEN:
            sftk_DeleteAttributeType(publicKey, CKA_VALUE);
            sftk_DeleteAttributeType(privateKey, CKA_NETSCAPE_DB);
            sftk_DeleteAttributeType(privateKey, CKA_PRIME);
            sftk_DeleteAttributeType(privateKey, CKA_SUBPRIME);
            sftk_DeleteAttributeType(privateKey, CKA_BASE);
            key_type = CKK_DSA;

            /* extract the necessary parameters and copy them to the private key */
            crv = sftk_Attribute2SSecItem(NULL, &pqgParam.prime, publicKey, CKA_PRIME);
            if (crv != CKR_OK)
                break;
            crv = sftk_Attribute2SSecItem(NULL, &pqgParam.subPrime, publicKey,
                                          CKA_SUBPRIME);
            if (crv != CKR_OK) {
                PORT_Free(pqgParam.prime.data);
                break;
            }
            crv = sftk_Attribute2SSecItem(NULL, &pqgParam.base, publicKey, CKA_BASE);
            if (crv != CKR_OK) {
                PORT_Free(pqgParam.prime.data);
                PORT_Free(pqgParam.subPrime.data);
                break;
            }
            crv = sftk_AddAttributeType(privateKey, CKA_PRIME,
                                        sftk_item_expand(&pqgParam.prime));
            if (crv != CKR_OK) {
                PORT_Free(pqgParam.prime.data);
                PORT_Free(pqgParam.subPrime.data);
                PORT_Free(pqgParam.base.data);
                break;
            }
            crv = sftk_AddAttributeType(privateKey, CKA_SUBPRIME,
                                        sftk_item_expand(&pqgParam.subPrime));
            if (crv != CKR_OK) {
                PORT_Free(pqgParam.prime.data);
                PORT_Free(pqgParam.subPrime.data);
                PORT_Free(pqgParam.base.data);
                break;
            }
            crv = sftk_AddAttributeType(privateKey, CKA_BASE,
                                        sftk_item_expand(&pqgParam.base));
            if (crv != CKR_OK) {
                PORT_Free(pqgParam.prime.data);
                PORT_Free(pqgParam.subPrime.data);
                PORT_Free(pqgParam.base.data);
                break;
            }

            /*
             * these are checked by DSA_NewKey
             */
            bitSize = sftk_GetLengthInBits(pqgParam.subPrime.data,
                                           pqgParam.subPrime.len);
            if ((bitSize < DSA_MIN_Q_BITS) || (bitSize > DSA_MAX_Q_BITS)) {
                crv = CKR_TEMPLATE_INCOMPLETE;
                PORT_Free(pqgParam.prime.data);
                PORT_Free(pqgParam.subPrime.data);
                PORT_Free(pqgParam.base.data);
                break;
            }
            bitSize = sftk_GetLengthInBits(pqgParam.prime.data, pqgParam.prime.len);
            if ((bitSize < DSA_MIN_P_BITS) || (bitSize > DSA_MAX_P_BITS)) {
                crv = CKR_TEMPLATE_INCOMPLETE;
                PORT_Free(pqgParam.prime.data);
                PORT_Free(pqgParam.subPrime.data);
                PORT_Free(pqgParam.base.data);
                break;
            }
            bitSize = sftk_GetLengthInBits(pqgParam.base.data, pqgParam.base.len);
            if ((bitSize < 2) || (bitSize > DSA_MAX_P_BITS)) {
                crv = CKR_TEMPLATE_INCOMPLETE;
                PORT_Free(pqgParam.prime.data);
                PORT_Free(pqgParam.subPrime.data);
                PORT_Free(pqgParam.base.data);
                break;
            }

            /* Generate the key */
            rv = DSA_NewKey(&pqgParam, &dsaPriv);

            PORT_Free(pqgParam.prime.data);
            PORT_Free(pqgParam.subPrime.data);
            PORT_Free(pqgParam.base.data);

            if (rv != SECSuccess) {
                if (PORT_GetError() == SEC_ERROR_LIBRARY_FAILURE) {
                    sftk_fatalError = PR_TRUE;
                }
                crv = sftk_MapCryptError(PORT_GetError());
                break;
            }

            /* store the generated key into the attributes */
            crv = sftk_AddAttributeType(publicKey, CKA_VALUE,
                                        sftk_item_expand(&dsaPriv->publicValue));
            if (crv != CKR_OK)
                goto dsagn_done;

            /* now fill in the RSA dependent paramenters in the private key */
            crv = sftk_AddAttributeType(privateKey, CKA_NETSCAPE_DB,
                                        sftk_item_expand(&dsaPriv->publicValue));
            if (crv != CKR_OK)
                goto dsagn_done;
            crv = sftk_AddAttributeType(privateKey, CKA_VALUE,
                                        sftk_item_expand(&dsaPriv->privateValue));

        dsagn_done:
            /* should zeroize, since this function doesn't. */
            PORT_FreeArena(dsaPriv->params.arena, PR_TRUE);
            break;

        case CKM_DH_PKCS_KEY_PAIR_GEN:
            sftk_DeleteAttributeType(privateKey, CKA_PRIME);
            sftk_DeleteAttributeType(privateKey, CKA_BASE);
            sftk_DeleteAttributeType(privateKey, CKA_VALUE);
            sftk_DeleteAttributeType(privateKey, CKA_NETSCAPE_DB);
            key_type = CKK_DH;

            /* extract the necessary parameters and copy them to private keys */
            crv = sftk_Attribute2SSecItem(NULL, &dhParam.prime, publicKey,
                                          CKA_PRIME);
            if (crv != CKR_OK)
                break;
            crv = sftk_Attribute2SSecItem(NULL, &dhParam.base, publicKey, CKA_BASE);
            if (crv != CKR_OK) {
                PORT_Free(dhParam.prime.data);
                break;
            }
            crv = sftk_AddAttributeType(privateKey, CKA_PRIME,
                                        sftk_item_expand(&dhParam.prime));
            if (crv != CKR_OK) {
                PORT_Free(dhParam.prime.data);
                PORT_Free(dhParam.base.data);
                break;
            }
            crv = sftk_AddAttributeType(privateKey, CKA_BASE,
                                        sftk_item_expand(&dhParam.base));
            if (crv != CKR_OK) {
                PORT_Free(dhParam.prime.data);
                PORT_Free(dhParam.base.data);
                break;
            }
            bitSize = sftk_GetLengthInBits(dhParam.prime.data, dhParam.prime.len);
            if ((bitSize < DH_MIN_P_BITS) || (bitSize > DH_MAX_P_BITS)) {
                crv = CKR_TEMPLATE_INCOMPLETE;
                PORT_Free(dhParam.prime.data);
                PORT_Free(dhParam.base.data);
                break;
            }
            bitSize = sftk_GetLengthInBits(dhParam.base.data, dhParam.base.len);
            if ((bitSize < 1) || (bitSize > DH_MAX_P_BITS)) {
                crv = CKR_TEMPLATE_INCOMPLETE;
                PORT_Free(dhParam.prime.data);
                PORT_Free(dhParam.base.data);
                break;
            }

            rv = DH_NewKey(&dhParam, &dhPriv);
            PORT_Free(dhParam.prime.data);
            PORT_Free(dhParam.base.data);
            if (rv != SECSuccess) {
                if (PORT_GetError() == SEC_ERROR_LIBRARY_FAILURE) {
                    sftk_fatalError = PR_TRUE;
                }
                crv = sftk_MapCryptError(PORT_GetError());
                break;
            }

            crv = sftk_AddAttributeType(publicKey, CKA_VALUE,
                                        sftk_item_expand(&dhPriv->publicValue));
            if (crv != CKR_OK)
                goto dhgn_done;

            crv = sftk_AddAttributeType(privateKey, CKA_NETSCAPE_DB,
                                        sftk_item_expand(&dhPriv->publicValue));
            if (crv != CKR_OK)
                goto dhgn_done;

            crv = sftk_AddAttributeType(privateKey, CKA_VALUE,
                                        sftk_item_expand(&dhPriv->privateValue));

        dhgn_done:
            /* should zeroize, since this function doesn't. */
            PORT_FreeArena(dhPriv->arena, PR_TRUE);
            break;

        case CKM_EC_KEY_PAIR_GEN:
            sftk_DeleteAttributeType(privateKey, CKA_EC_PARAMS);
            sftk_DeleteAttributeType(privateKey, CKA_VALUE);
            sftk_DeleteAttributeType(privateKey, CKA_NETSCAPE_DB);
            key_type = CKK_EC;

            /* extract the necessary parameters and copy them to private keys */
            crv = sftk_Attribute2SSecItem(NULL, &ecEncodedParams, publicKey,
                                          CKA_EC_PARAMS);
            if (crv != CKR_OK)
                break;

            crv = sftk_AddAttributeType(privateKey, CKA_EC_PARAMS,
                                        sftk_item_expand(&ecEncodedParams));
            if (crv != CKR_OK) {
                PORT_Free(ecEncodedParams.data);
                break;
            }

            /* Decode ec params before calling EC_NewKey */
            rv = EC_DecodeParams(&ecEncodedParams, &ecParams);
            PORT_Free(ecEncodedParams.data);
            if (rv != SECSuccess) {
                crv = sftk_MapCryptError(PORT_GetError());
                break;
            }
            rv = EC_NewKey(ecParams, &ecPriv);
            if (rv != SECSuccess) {
                if (PORT_GetError() == SEC_ERROR_LIBRARY_FAILURE) {
                    sftk_fatalError = PR_TRUE;
                }
                PORT_FreeArena(ecParams->arena, PR_TRUE);
                crv = sftk_MapCryptError(PORT_GetError());
                break;
            }

            if (PR_GetEnvSecure("NSS_USE_DECODED_CKA_EC_POINT") ||
                ecParams->fieldID.type == ec_field_plain) {
                PORT_FreeArena(ecParams->arena, PR_TRUE);
                crv = sftk_AddAttributeType(publicKey, CKA_EC_POINT,
                                            sftk_item_expand(&ecPriv->publicValue));
            } else {
                PORT_FreeArena(ecParams->arena, PR_TRUE);
                SECItem *pubValue = SEC_ASN1EncodeItem(NULL, NULL,
                                                       &ecPriv->publicValue,
                                                       SEC_ASN1_GET(SEC_OctetStringTemplate));
                if (!pubValue) {
                    crv = CKR_ARGUMENTS_BAD;
                    goto ecgn_done;
                }
                crv = sftk_AddAttributeType(publicKey, CKA_EC_POINT,
                                            sftk_item_expand(pubValue));
                SECITEM_FreeItem(pubValue, PR_TRUE);
            }
            if (crv != CKR_OK)
                goto ecgn_done;

            crv = sftk_AddAttributeType(privateKey, CKA_VALUE,
                                        sftk_item_expand(&ecPriv->privateValue));
            if (crv != CKR_OK)
                goto ecgn_done;

            crv = sftk_AddAttributeType(privateKey, CKA_NETSCAPE_DB,
                                        sftk_item_expand(&ecPriv->publicValue));
        ecgn_done:
            /* should zeroize, since this function doesn't. */
            PORT_FreeArena(ecPriv->ecParams.arena, PR_TRUE);
            break;

        default:
            crv = CKR_MECHANISM_INVALID;
    }

    if (crv != CKR_OK) {
        sftk_FreeObject(privateKey);
        sftk_FreeObject(publicKey);
        return crv;
    }

    /* Add the class, key_type The loop lets us check errors blow out
     *  on errors and clean up at the bottom */
    session = NULL; /* make pedtantic happy... session cannot leave the*/
                    /* loop below NULL unless an error is set... */
    do {
        crv = sftk_AddAttributeType(privateKey, CKA_CLASS, &privClass,
                                    sizeof(CK_OBJECT_CLASS));
        if (crv != CKR_OK)
            break;
        crv = sftk_AddAttributeType(publicKey, CKA_CLASS, &pubClass,
                                    sizeof(CK_OBJECT_CLASS));
        if (crv != CKR_OK)
            break;
        crv = sftk_AddAttributeType(privateKey, CKA_KEY_TYPE, &key_type,
                                    sizeof(CK_KEY_TYPE));
        if (crv != CKR_OK)
            break;
        crv = sftk_AddAttributeType(publicKey, CKA_KEY_TYPE, &key_type,
                                    sizeof(CK_KEY_TYPE));
        if (crv != CKR_OK)
            break;
        session = sftk_SessionFromHandle(hSession);
        if (session == NULL)
            crv = CKR_SESSION_HANDLE_INVALID;
    } while (0);

    if (crv != CKR_OK) {
        sftk_FreeObject(privateKey);
        sftk_FreeObject(publicKey);
        return crv;
    }

    /*
     * handle the base object cleanup for the public Key
     */
    crv = sftk_handleObject(privateKey, session);
    if (crv != CKR_OK) {
        sftk_FreeSession(session);
        sftk_FreeObject(privateKey);
        sftk_FreeObject(publicKey);
        return crv;
    }

    /*
     * handle the base object cleanup for the private Key
     * If we have any problems, we destroy the public Key we've
     * created and linked.
     */
    crv = sftk_handleObject(publicKey, session);
    sftk_FreeSession(session);
    if (crv != CKR_OK) {
        sftk_FreeObject(publicKey);
        NSC_DestroyObject(hSession, privateKey->handle);
        sftk_FreeObject(privateKey);
        return crv;
    }
    if (sftk_isTrue(privateKey, CKA_SENSITIVE)) {
        crv = sftk_forceAttribute(privateKey, CKA_ALWAYS_SENSITIVE,
                                  &cktrue, sizeof(CK_BBOOL));
    }
    if (crv == CKR_OK && sftk_isTrue(publicKey, CKA_SENSITIVE)) {
        crv = sftk_forceAttribute(publicKey, CKA_ALWAYS_SENSITIVE,
                                  &cktrue, sizeof(CK_BBOOL));
    }
    if (crv == CKR_OK && !sftk_isTrue(privateKey, CKA_EXTRACTABLE)) {
        crv = sftk_forceAttribute(privateKey, CKA_NEVER_EXTRACTABLE,
                                  &cktrue, sizeof(CK_BBOOL));
    }
    if (crv == CKR_OK && !sftk_isTrue(publicKey, CKA_EXTRACTABLE)) {
        crv = sftk_forceAttribute(publicKey, CKA_NEVER_EXTRACTABLE,
                                  &cktrue, sizeof(CK_BBOOL));
    }

    if (crv == CKR_OK) {
        /* Perform FIPS 140-2 pairwise consistency check. */
        crv = sftk_PairwiseConsistencyCheck(hSession,
                                            publicKey, privateKey, key_type);
        if (crv != CKR_OK) {
            if (sftk_audit_enabled) {
                char msg[128];
                PR_snprintf(msg, sizeof msg,
                            "C_GenerateKeyPair(hSession=0x%08lX, "
                            "pMechanism->mechanism=0x%08lX)=0x%08lX "
                            "self-test: pair-wise consistency test failed",
                            (PRUint32)hSession, (PRUint32)pMechanism->mechanism,
                            (PRUint32)crv);
                sftk_LogAuditMessage(NSS_AUDIT_ERROR, NSS_AUDIT_SELF_TEST, msg);
            }
        }
    }

    if (crv != CKR_OK) {
        NSC_DestroyObject(hSession, publicKey->handle);
        sftk_FreeObject(publicKey);
        NSC_DestroyObject(hSession, privateKey->handle);
        sftk_FreeObject(privateKey);
        return crv;
    }

    *phPrivateKey = privateKey->handle;
    *phPublicKey = publicKey->handle;
    sftk_FreeObject(publicKey);
    sftk_FreeObject(privateKey);

    return CKR_OK;
}

static SECItem *
sftk_PackagePrivateKey(SFTKObject *key, CK_RV *crvp)
{
    NSSLOWKEYPrivateKey *lk = NULL;
    NSSLOWKEYPrivateKeyInfo *pki = NULL;
    SFTKAttribute *attribute = NULL;
    PLArenaPool *arena = NULL;
    SECOidTag algorithm = SEC_OID_UNKNOWN;
    void *dummy, *param = NULL;
    SECStatus rv = SECSuccess;
    SECItem *encodedKey = NULL;
#ifdef EC_DEBUG
    SECItem *fordebug;
#endif
    int savelen;

    if (!key) {
        *crvp = CKR_KEY_HANDLE_INVALID; /* really can't happen */
        return NULL;
    }

    attribute = sftk_FindAttribute(key, CKA_KEY_TYPE);
    if (!attribute) {
        *crvp = CKR_KEY_TYPE_INCONSISTENT;
        return NULL;
    }

    lk = sftk_GetPrivKey(key, *(CK_KEY_TYPE *)attribute->attrib.pValue, crvp);
    sftk_FreeAttribute(attribute);
    if (!lk) {
        return NULL;
    }

    arena = PORT_NewArena(2048); /* XXX different size? */
    if (!arena) {
        *crvp = CKR_HOST_MEMORY;
        rv = SECFailure;
        goto loser;
    }

    pki = (NSSLOWKEYPrivateKeyInfo *)PORT_ArenaZAlloc(arena,
                                                      sizeof(NSSLOWKEYPrivateKeyInfo));
    if (!pki) {
        *crvp = CKR_HOST_MEMORY;
        rv = SECFailure;
        goto loser;
    }
    pki->arena = arena;

    param = NULL;
    switch (lk->keyType) {
        case NSSLOWKEYRSAKey:
            prepare_low_rsa_priv_key_for_asn1(lk);
            dummy = SEC_ASN1EncodeItem(arena, &pki->privateKey, lk,
                                       nsslowkey_RSAPrivateKeyTemplate);
            algorithm = SEC_OID_PKCS1_RSA_ENCRYPTION;
            break;
        case NSSLOWKEYDSAKey:
            prepare_low_dsa_priv_key_export_for_asn1(lk);
            dummy = SEC_ASN1EncodeItem(arena, &pki->privateKey, lk,
                                       nsslowkey_DSAPrivateKeyExportTemplate);
            prepare_low_pqg_params_for_asn1(&lk->u.dsa.params);
            param = SEC_ASN1EncodeItem(NULL, NULL, &(lk->u.dsa.params),
                                       nsslowkey_PQGParamsTemplate);
            algorithm = SEC_OID_ANSIX9_DSA_SIGNATURE;
            break;
        case NSSLOWKEYECKey:
            prepare_low_ec_priv_key_for_asn1(lk);
            /* Public value is encoded as a bit string so adjust length
             * to be in bits before ASN encoding and readjust
             * immediately after.
             *
             * Since the SECG specification recommends not including the
             * parameters as part of ECPrivateKey, we zero out the curveOID
             * length before encoding and restore it later.
             */
            lk->u.ec.publicValue.len <<= 3;
            savelen = lk->u.ec.ecParams.curveOID.len;
            lk->u.ec.ecParams.curveOID.len = 0;
            dummy = SEC_ASN1EncodeItem(arena, &pki->privateKey, lk,
                                       nsslowkey_ECPrivateKeyTemplate);
            lk->u.ec.ecParams.curveOID.len = savelen;
            lk->u.ec.publicValue.len >>= 3;

#ifdef EC_DEBUG
            fordebug = &pki->privateKey;
            SEC_PRINT("sftk_PackagePrivateKey()", "PrivateKey", lk->keyType,
                      fordebug);
#endif

            param = SECITEM_DupItem(&lk->u.ec.ecParams.DEREncoding);

            algorithm = SEC_OID_ANSIX962_EC_PUBLIC_KEY;
            break;
        case NSSLOWKEYDHKey:
        default:
            dummy = NULL;
            break;
    }

    if (!dummy || ((lk->keyType == NSSLOWKEYDSAKey) && !param)) {
        *crvp = CKR_DEVICE_ERROR; /* should map NSS SECError */
        rv = SECFailure;
        goto loser;
    }

    rv = SECOID_SetAlgorithmID(arena, &pki->algorithm, algorithm,
                               (SECItem *)param);
    if (rv != SECSuccess) {
        *crvp = CKR_DEVICE_ERROR; /* should map NSS SECError */
        rv = SECFailure;
        goto loser;
    }

    dummy = SEC_ASN1EncodeInteger(arena, &pki->version,
                                  NSSLOWKEY_PRIVATE_KEY_INFO_VERSION);
    if (!dummy) {
        *crvp = CKR_DEVICE_ERROR; /* should map NSS SECError */
        rv = SECFailure;
        goto loser;
    }

    encodedKey = SEC_ASN1EncodeItem(NULL, NULL, pki,
                                    nsslowkey_PrivateKeyInfoTemplate);
    *crvp = encodedKey ? CKR_OK : CKR_DEVICE_ERROR;

#ifdef EC_DEBUG
    fordebug = encodedKey;
    SEC_PRINT("sftk_PackagePrivateKey()", "PrivateKeyInfo", lk->keyType,
              fordebug);
#endif
loser:
    if (arena) {
        PORT_FreeArena(arena, PR_TRUE);
    }

    if (lk && (lk != key->objectInfo)) {
        nsslowkey_DestroyPrivateKey(lk);
    }

    if (param) {
        SECITEM_ZfreeItem((SECItem *)param, PR_TRUE);
    }

    if (rv != SECSuccess) {
        return NULL;
    }

    return encodedKey;
}

/* it doesn't matter yet, since we colapse error conditions in the
 * level above, but we really should map those few key error differences */
static CK_RV
sftk_mapWrap(CK_RV crv)
{
    switch (crv) {
        case CKR_ENCRYPTED_DATA_INVALID:
            crv = CKR_WRAPPED_KEY_INVALID;
            break;
    }
    return crv;
}

/* NSC_WrapKey wraps (i.e., encrypts) a key. */
CK_RV
NSC_WrapKey(CK_SESSION_HANDLE hSession,
            CK_MECHANISM_PTR pMechanism, CK_OBJECT_HANDLE hWrappingKey,
            CK_OBJECT_HANDLE hKey, CK_BYTE_PTR pWrappedKey,
            CK_ULONG_PTR pulWrappedKeyLen)
{
    SFTKSession *session;
    SFTKAttribute *attribute;
    SFTKObject *key;
    CK_RV crv;

    CHECK_FORK();

    session = sftk_SessionFromHandle(hSession);
    if (session == NULL) {
        return CKR_SESSION_HANDLE_INVALID;
    }

    key = sftk_ObjectFromHandle(hKey, session);
    sftk_FreeSession(session);
    if (key == NULL) {
        return CKR_KEY_HANDLE_INVALID;
    }

    switch (key->objclass) {
        case CKO_SECRET_KEY: {
            SFTKSessionContext *context = NULL;
            SECItem pText;

            attribute = sftk_FindAttribute(key, CKA_VALUE);

            if (attribute == NULL) {
                crv = CKR_KEY_TYPE_INCONSISTENT;
                break;
            }
            crv = sftk_CryptInit(hSession, pMechanism, hWrappingKey,
                                 CKA_WRAP, CKA_WRAP, SFTK_ENCRYPT, PR_TRUE);
            if (crv != CKR_OK) {
                sftk_FreeAttribute(attribute);
                break;
            }

            pText.type = siBuffer;
            pText.data = (unsigned char *)attribute->attrib.pValue;
            pText.len = attribute->attrib.ulValueLen;

            /* Find out if this is a block cipher. */
            crv = sftk_GetContext(hSession, &context, SFTK_ENCRYPT, PR_FALSE, NULL);
            if (crv != CKR_OK || !context)
                break;
            if (context->blockSize > 1) {
                unsigned int remainder = pText.len % context->blockSize;
                if (!context->doPad && remainder) {
                    /* When wrapping secret keys with unpadded block ciphers,
                    ** the keys are zero padded, if necessary, to fill out
                    ** a full block.
                    */
                    pText.len += context->blockSize - remainder;
                    pText.data = PORT_ZAlloc(pText.len);
                    if (pText.data)
                        memcpy(pText.data, attribute->attrib.pValue,
                               attribute->attrib.ulValueLen);
                    else {
                        crv = CKR_HOST_MEMORY;
                        break;
                    }
                }
            }

            crv = NSC_Encrypt(hSession, (CK_BYTE_PTR)pText.data,
                              pText.len, pWrappedKey, pulWrappedKeyLen);
            /* always force a finalize, both on errors and when
             * we are just getting the size */
            if (crv != CKR_OK || pWrappedKey == NULL) {
                CK_RV lcrv;
                lcrv = sftk_GetContext(hSession, &context,
                                       SFTK_ENCRYPT, PR_FALSE, NULL);
                sftk_SetContextByType(session, SFTK_ENCRYPT, NULL);
                if (lcrv == CKR_OK && context) {
                    sftk_FreeContext(context);
                }
            }

            if (pText.data != (unsigned char *)attribute->attrib.pValue)
                PORT_ZFree(pText.data, pText.len);
            sftk_FreeAttribute(attribute);
            break;
        }

        case CKO_PRIVATE_KEY: {
            SECItem *bpki = sftk_PackagePrivateKey(key, &crv);
            SFTKSessionContext *context = NULL;

            if (!bpki) {
                break;
            }

            crv = sftk_CryptInit(hSession, pMechanism, hWrappingKey,
                                 CKA_WRAP, CKA_WRAP, SFTK_ENCRYPT, PR_TRUE);
            if (crv != CKR_OK) {
                SECITEM_ZfreeItem(bpki, PR_TRUE);
                crv = CKR_KEY_TYPE_INCONSISTENT;
                break;
            }

            crv = NSC_Encrypt(hSession, bpki->data, bpki->len,
                              pWrappedKey, pulWrappedKeyLen);
            /* always force a finalize */
            if (crv != CKR_OK || pWrappedKey == NULL) {
                CK_RV lcrv;
                lcrv = sftk_GetContext(hSession, &context,
                                       SFTK_ENCRYPT, PR_FALSE, NULL);
                sftk_SetContextByType(session, SFTK_ENCRYPT, NULL);
                if (lcrv == CKR_OK && context) {
                    sftk_FreeContext(context);
                }
            }
            SECITEM_ZfreeItem(bpki, PR_TRUE);
            break;
        }

        default:
            crv = CKR_KEY_TYPE_INCONSISTENT;
            break;
    }
    sftk_FreeObject(key);

    return sftk_mapWrap(crv);
}

/*
 * import a pprivate key info into the desired slot
 */
static SECStatus
sftk_unwrapPrivateKey(SFTKObject *key, SECItem *bpki)
{
    CK_BBOOL cktrue = CK_TRUE;
    CK_KEY_TYPE keyType = CKK_RSA;
    SECStatus rv = SECFailure;
    const SEC_ASN1Template *keyTemplate, *paramTemplate;
    void *paramDest = NULL;
    PLArenaPool *arena;
    NSSLOWKEYPrivateKey *lpk = NULL;
    NSSLOWKEYPrivateKeyInfo *pki = NULL;
    CK_RV crv = CKR_KEY_TYPE_INCONSISTENT;

    arena = PORT_NewArena(2048);
    if (!arena) {
        return SECFailure;
    }

    pki = (NSSLOWKEYPrivateKeyInfo *)PORT_ArenaZAlloc(arena,
                                                      sizeof(NSSLOWKEYPrivateKeyInfo));
    if (!pki) {
        PORT_FreeArena(arena, PR_FALSE);
        return SECFailure;
    }

    if (SEC_ASN1DecodeItem(arena, pki, nsslowkey_PrivateKeyInfoTemplate, bpki) != SECSuccess) {
        PORT_FreeArena(arena, PR_TRUE);
        return SECFailure;
    }

    lpk = (NSSLOWKEYPrivateKey *)PORT_ArenaZAlloc(arena,
                                                  sizeof(NSSLOWKEYPrivateKey));
    if (lpk == NULL) {
        goto loser;
    }
    lpk->arena = arena;

    switch (SECOID_GetAlgorithmTag(&pki->algorithm)) {
        case SEC_OID_PKCS1_RSA_ENCRYPTION:
        case SEC_OID_PKCS1_RSA_PSS_SIGNATURE:
            keyTemplate = nsslowkey_RSAPrivateKeyTemplate;
            paramTemplate = NULL;
            paramDest = NULL;
            lpk->keyType = NSSLOWKEYRSAKey;
            prepare_low_rsa_priv_key_for_asn1(lpk);
            break;
        case SEC_OID_ANSIX9_DSA_SIGNATURE:
            keyTemplate = nsslowkey_DSAPrivateKeyExportTemplate;
            paramTemplate = nsslowkey_PQGParamsTemplate;
            paramDest = &(lpk->u.dsa.params);
            lpk->keyType = NSSLOWKEYDSAKey;
            prepare_low_dsa_priv_key_export_for_asn1(lpk);
            prepare_low_pqg_params_for_asn1(&lpk->u.dsa.params);
            break;
        /* case NSSLOWKEYDHKey: */
        case SEC_OID_ANSIX962_EC_PUBLIC_KEY:
            keyTemplate = nsslowkey_ECPrivateKeyTemplate;
            paramTemplate = NULL;
            paramDest = &(lpk->u.ec.ecParams.DEREncoding);
            lpk->keyType = NSSLOWKEYECKey;
            prepare_low_ec_priv_key_for_asn1(lpk);
            prepare_low_ecparams_for_asn1(&lpk->u.ec.ecParams);
            break;
        default:
            keyTemplate = NULL;
            paramTemplate = NULL;
            paramDest = NULL;
            break;
    }

    if (!keyTemplate) {
        goto loser;
    }

    /* decode the private key and any algorithm parameters */
    rv = SEC_QuickDERDecodeItem(arena, lpk, keyTemplate, &pki->privateKey);

    if (lpk->keyType == NSSLOWKEYECKey) {
        /* convert length in bits to length in bytes */
        lpk->u.ec.publicValue.len >>= 3;
        rv = SECITEM_CopyItem(arena,
                              &(lpk->u.ec.ecParams.DEREncoding),
                              &(pki->algorithm.parameters));
        if (rv != SECSuccess) {
            goto loser;
        }
    }

    if (rv != SECSuccess) {
        goto loser;
    }
    if (paramDest && paramTemplate) {
        rv = SEC_QuickDERDecodeItem(arena, paramDest, paramTemplate,
                                    &(pki->algorithm.parameters));
        if (rv != SECSuccess) {
            goto loser;
        }
    }

    rv = SECFailure;

    switch (lpk->keyType) {
        case NSSLOWKEYRSAKey:
            keyType = CKK_RSA;
            if (sftk_hasAttribute(key, CKA_NETSCAPE_DB)) {
                sftk_DeleteAttributeType(key, CKA_NETSCAPE_DB);
            }
            crv = sftk_AddAttributeType(key, CKA_KEY_TYPE, &keyType,
                                        sizeof(keyType));
            if (crv != CKR_OK)
                break;
            crv = sftk_AddAttributeType(key, CKA_UNWRAP, &cktrue,
                                        sizeof(CK_BBOOL));
            if (crv != CKR_OK)
                break;
            crv = sftk_AddAttributeType(key, CKA_DECRYPT, &cktrue,
                                        sizeof(CK_BBOOL));
            if (crv != CKR_OK)
                break;
            crv = sftk_AddAttributeType(key, CKA_SIGN, &cktrue,
                                        sizeof(CK_BBOOL));
            if (crv != CKR_OK)
                break;
            crv = sftk_AddAttributeType(key, CKA_SIGN_RECOVER, &cktrue,
                                        sizeof(CK_BBOOL));
            if (crv != CKR_OK)
                break;
            crv = sftk_AddAttributeType(key, CKA_MODULUS,
                                        sftk_item_expand(&lpk->u.rsa.modulus));
            if (crv != CKR_OK)
                break;
            crv = sftk_AddAttributeType(key, CKA_PUBLIC_EXPONENT,
                                        sftk_item_expand(&lpk->u.rsa.publicExponent));
            if (crv != CKR_OK)
                break;
            crv = sftk_AddAttributeType(key, CKA_PRIVATE_EXPONENT,
                                        sftk_item_expand(&lpk->u.rsa.privateExponent));
            if (crv != CKR_OK)
                break;
            crv = sftk_AddAttributeType(key, CKA_PRIME_1,
                                        sftk_item_expand(&lpk->u.rsa.prime1));
            if (crv != CKR_OK)
                break;
            crv = sftk_AddAttributeType(key, CKA_PRIME_2,
                                        sftk_item_expand(&lpk->u.rsa.prime2));
            if (crv != CKR_OK)
                break;
            crv = sftk_AddAttributeType(key, CKA_EXPONENT_1,
                                        sftk_item_expand(&lpk->u.rsa.exponent1));
            if (crv != CKR_OK)
                break;
            crv = sftk_AddAttributeType(key, CKA_EXPONENT_2,
                                        sftk_item_expand(&lpk->u.rsa.exponent2));
            if (crv != CKR_OK)
                break;
            crv = sftk_AddAttributeType(key, CKA_COEFFICIENT,
                                        sftk_item_expand(&lpk->u.rsa.coefficient));
            break;
        case NSSLOWKEYDSAKey:
            keyType = CKK_DSA;
            crv = (sftk_hasAttribute(key, CKA_NETSCAPE_DB)) ? CKR_OK : CKR_KEY_TYPE_INCONSISTENT;
            if (crv != CKR_OK)
                break;
            crv = sftk_AddAttributeType(key, CKA_KEY_TYPE, &keyType,
                                        sizeof(keyType));
            if (crv != CKR_OK)
                break;
            crv = sftk_AddAttributeType(key, CKA_SIGN, &cktrue,
                                        sizeof(CK_BBOOL));
            if (crv != CKR_OK)
                break;
            crv = sftk_AddAttributeType(key, CKA_SIGN_RECOVER, &cktrue,
                                        sizeof(CK_BBOOL));
            if (crv != CKR_OK)
                break;
            crv = sftk_AddAttributeType(key, CKA_PRIME,
                                        sftk_item_expand(&lpk->u.dsa.params.prime));
            if (crv != CKR_OK)
                break;
            crv = sftk_AddAttributeType(key, CKA_SUBPRIME,
                                        sftk_item_expand(&lpk->u.dsa.params.subPrime));
            if (crv != CKR_OK)
                break;
            crv = sftk_AddAttributeType(key, CKA_BASE,
                                        sftk_item_expand(&lpk->u.dsa.params.base));
            if (crv != CKR_OK)
                break;
            crv = sftk_AddAttributeType(key, CKA_VALUE,
                                        sftk_item_expand(&lpk->u.dsa.privateValue));
            if (crv != CKR_OK)
                break;
            break;
#ifdef notdef
        case NSSLOWKEYDHKey:
            template = dhTemplate;
            templateCount = sizeof(dhTemplate) / sizeof(CK_ATTRIBUTE);
            keyType = CKK_DH;
            break;
#endif
        /* what about fortezza??? */
        case NSSLOWKEYECKey:
            keyType = CKK_EC;
            crv = (sftk_hasAttribute(key, CKA_NETSCAPE_DB)) ? CKR_OK : CKR_KEY_TYPE_INCONSISTENT;
            if (crv != CKR_OK)
                break;
            crv = sftk_AddAttributeType(key, CKA_KEY_TYPE, &keyType,
                                        sizeof(keyType));
            if (crv != CKR_OK)
                break;
            crv = sftk_AddAttributeType(key, CKA_SIGN, &cktrue,
                                        sizeof(CK_BBOOL));
            if (crv != CKR_OK)
                break;
            crv = sftk_AddAttributeType(key, CKA_SIGN_RECOVER, &cktrue,
                                        sizeof(CK_BBOOL));
            if (crv != CKR_OK)
                break;
            crv = sftk_AddAttributeType(key, CKA_DERIVE, &cktrue,
                                        sizeof(CK_BBOOL));
            if (crv != CKR_OK)
                break;
            crv = sftk_AddAttributeType(key, CKA_EC_PARAMS,
                                        sftk_item_expand(&lpk->u.ec.ecParams.DEREncoding));
            if (crv != CKR_OK)
                break;
            crv = sftk_AddAttributeType(key, CKA_VALUE,
                                        sftk_item_expand(&lpk->u.ec.privateValue));
            if (crv != CKR_OK)
                break;
            /* XXX Do we need to decode the EC Params here ?? */
            break;
        default:
            crv = CKR_KEY_TYPE_INCONSISTENT;
            break;
    }

loser:
    if (lpk) {
        nsslowkey_DestroyPrivateKey(lpk);
    }

    if (crv != CKR_OK) {
        return SECFailure;
    }

    return SECSuccess;
}

/* NSC_UnwrapKey unwraps (decrypts) a wrapped key, creating a new key object. */
CK_RV
NSC_UnwrapKey(CK_SESSION_HANDLE hSession,
              CK_MECHANISM_PTR pMechanism, CK_OBJECT_HANDLE hUnwrappingKey,
              CK_BYTE_PTR pWrappedKey, CK_ULONG ulWrappedKeyLen,
              CK_ATTRIBUTE_PTR pTemplate, CK_ULONG ulAttributeCount,
              CK_OBJECT_HANDLE_PTR phKey)
{
    SFTKObject *key = NULL;
    SFTKSession *session;
    CK_ULONG key_length = 0;
    unsigned char *buf = NULL;
    CK_RV crv = CKR_OK;
    int i;
    CK_ULONG bsize = ulWrappedKeyLen;
    SFTKSlot *slot = sftk_SlotFromSessionHandle(hSession);
    SECItem bpki;
    CK_OBJECT_CLASS target_type = CKO_SECRET_KEY;

    CHECK_FORK();

    if (!slot) {
        return CKR_SESSION_HANDLE_INVALID;
    }
    /*
     * now lets create an object to hang the attributes off of
     */
    key = sftk_NewObject(slot); /* fill in the handle later */
    if (key == NULL) {
        return CKR_HOST_MEMORY;
    }

    /*
     * load the template values into the object
     */
    for (i = 0; i < (int)ulAttributeCount; i++) {
        if (pTemplate[i].type == CKA_VALUE_LEN) {
            key_length = *(CK_ULONG *)pTemplate[i].pValue;
            continue;
        }
        if (pTemplate[i].type == CKA_CLASS) {
            target_type = *(CK_OBJECT_CLASS *)pTemplate[i].pValue;
        }
        crv = sftk_AddAttributeType(key, sftk_attr_expand(&pTemplate[i]));
        if (crv != CKR_OK)
            break;
    }
    if (crv != CKR_OK) {
        sftk_FreeObject(key);
        return crv;
    }

    crv = sftk_CryptInit(hSession, pMechanism, hUnwrappingKey, CKA_UNWRAP,
                         CKA_UNWRAP, SFTK_DECRYPT, PR_FALSE);
    if (crv != CKR_OK) {
        sftk_FreeObject(key);
        return sftk_mapWrap(crv);
    }

    /* allocate the buffer to decrypt into
     * this assumes the unwrapped key is never larger than the
     * wrapped key. For all the mechanisms we support this is true */
    buf = (unsigned char *)PORT_Alloc(ulWrappedKeyLen);
    bsize = ulWrappedKeyLen;

    crv = NSC_Decrypt(hSession, pWrappedKey, ulWrappedKeyLen, buf, &bsize);
    if (crv != CKR_OK) {
        sftk_FreeObject(key);
        PORT_Free(buf);
        return sftk_mapWrap(crv);
    }

    switch (target_type) {
        case CKO_SECRET_KEY:
            if (!sftk_hasAttribute(key, CKA_KEY_TYPE)) {
                crv = CKR_TEMPLATE_INCOMPLETE;
                break;
            }

            if (key_length == 0 || key_length > bsize) {
                key_length = bsize;
            }
            if (key_length > MAX_KEY_LEN) {
                crv = CKR_TEMPLATE_INCONSISTENT;
                break;
            }

            /* add the value */
            crv = sftk_AddAttributeType(key, CKA_VALUE, buf, key_length);
            break;
        case CKO_PRIVATE_KEY:
            bpki.data = (unsigned char *)buf;
            bpki.len = bsize;
            crv = CKR_OK;
            if (sftk_unwrapPrivateKey(key, &bpki) != SECSuccess) {
                crv = CKR_TEMPLATE_INCOMPLETE;
            }
            break;
        default:
            crv = CKR_TEMPLATE_INCONSISTENT;
            break;
    }

    PORT_ZFree(buf, bsize);
    if (crv != CKR_OK) {
        sftk_FreeObject(key);
        return crv;
    }

    /* get the session */
    session = sftk_SessionFromHandle(hSession);
    if (session == NULL) {
        sftk_FreeObject(key);
        return CKR_SESSION_HANDLE_INVALID;
    }

    /*
     * handle the base object stuff
     */
    crv = sftk_handleObject(key, session);
    *phKey = key->handle;
    sftk_FreeSession(session);
    sftk_FreeObject(key);

    return crv;
}

/*
 * The SSL key gen mechanism create's lots of keys. This function handles the
 * details of each of these key creation.
 */
static CK_RV
sftk_buildSSLKey(CK_SESSION_HANDLE hSession, SFTKObject *baseKey,
                 PRBool isMacKey, unsigned char *keyBlock, unsigned int keySize,
                 CK_OBJECT_HANDLE *keyHandle)
{
    SFTKObject *key;
    SFTKSession *session;
    CK_KEY_TYPE keyType = CKK_GENERIC_SECRET;
    CK_BBOOL cktrue = CK_TRUE;
    CK_BBOOL ckfalse = CK_FALSE;
    CK_RV crv = CKR_HOST_MEMORY;

    /*
     * now lets create an object to hang the attributes off of
     */
    *keyHandle = CK_INVALID_HANDLE;
    key = sftk_NewObject(baseKey->slot);
    if (key == NULL)
        return CKR_HOST_MEMORY;
    sftk_narrowToSessionObject(key)->wasDerived = PR_TRUE;

    crv = sftk_CopyObject(key, baseKey);
    if (crv != CKR_OK)
        goto loser;
    if (isMacKey) {
        crv = sftk_forceAttribute(key, CKA_KEY_TYPE, &keyType, sizeof(keyType));
        if (crv != CKR_OK)
            goto loser;
        crv = sftk_forceAttribute(key, CKA_DERIVE, &cktrue, sizeof(CK_BBOOL));
        if (crv != CKR_OK)
            goto loser;
        crv = sftk_forceAttribute(key, CKA_ENCRYPT, &ckfalse, sizeof(CK_BBOOL));
        if (crv != CKR_OK)
            goto loser;
        crv = sftk_forceAttribute(key, CKA_DECRYPT, &ckfalse, sizeof(CK_BBOOL));
        if (crv != CKR_OK)
            goto loser;
        crv = sftk_forceAttribute(key, CKA_SIGN, &cktrue, sizeof(CK_BBOOL));
        if (crv != CKR_OK)
            goto loser;
        crv = sftk_forceAttribute(key, CKA_VERIFY, &cktrue, sizeof(CK_BBOOL));
        if (crv != CKR_OK)
            goto loser;
        crv = sftk_forceAttribute(key, CKA_WRAP, &ckfalse, sizeof(CK_BBOOL));
        if (crv != CKR_OK)
            goto loser;
        crv = sftk_forceAttribute(key, CKA_UNWRAP, &ckfalse, sizeof(CK_BBOOL));
        if (crv != CKR_OK)
            goto loser;
    }
    crv = sftk_forceAttribute(key, CKA_VALUE, keyBlock, keySize);
    if (crv != CKR_OK)
        goto loser;

    /* get the session */
    crv = CKR_HOST_MEMORY;
    session = sftk_SessionFromHandle(hSession);
    if (session == NULL) {
        goto loser;
    }

    crv = sftk_handleObject(key, session);
    sftk_FreeSession(session);
    *keyHandle = key->handle;
loser:
    if (key)
        sftk_FreeObject(key);
    return crv;
}

/*
 * if there is an error, we need to free the keys we already created in SSL
 * This is the routine that will do it..
 */
static void
sftk_freeSSLKeys(CK_SESSION_HANDLE session,
                 CK_SSL3_KEY_MAT_OUT *returnedMaterial)
{
    if (returnedMaterial->hClientMacSecret != CK_INVALID_HANDLE) {
        NSC_DestroyObject(session, returnedMaterial->hClientMacSecret);
    }
    if (returnedMaterial->hServerMacSecret != CK_INVALID_HANDLE) {
        NSC_DestroyObject(session, returnedMaterial->hServerMacSecret);
    }
    if (returnedMaterial->hClientKey != CK_INVALID_HANDLE) {
        NSC_DestroyObject(session, returnedMaterial->hClientKey);
    }
    if (returnedMaterial->hServerKey != CK_INVALID_HANDLE) {
        NSC_DestroyObject(session, returnedMaterial->hServerKey);
    }
}

/*
 * when deriving from sensitive and extractable keys, we need to preserve some
 * of the semantics in the derived key. This helper routine maintains these
 * semantics.
 */
static CK_RV
sftk_DeriveSensitiveCheck(SFTKObject *baseKey, SFTKObject *destKey)
{
    PRBool hasSensitive;
    PRBool sensitive = PR_FALSE;
    PRBool hasExtractable;
    PRBool extractable = PR_TRUE;
    CK_RV crv = CKR_OK;
    SFTKAttribute *att;

    hasSensitive = PR_FALSE;
    att = sftk_FindAttribute(destKey, CKA_SENSITIVE);
    if (att) {
        hasSensitive = PR_TRUE;
        sensitive = (PRBool) * (CK_BBOOL *)att->attrib.pValue;
        sftk_FreeAttribute(att);
    }

    hasExtractable = PR_FALSE;
    att = sftk_FindAttribute(destKey, CKA_EXTRACTABLE);
    if (att) {
        hasExtractable = PR_TRUE;
        extractable = (PRBool) * (CK_BBOOL *)att->attrib.pValue;
        sftk_FreeAttribute(att);
    }

    /* don't make a key more accessible */
    if (sftk_isTrue(baseKey, CKA_SENSITIVE) && hasSensitive &&
        (sensitive == PR_FALSE)) {
        return CKR_KEY_FUNCTION_NOT_PERMITTED;
    }
    if (!sftk_isTrue(baseKey, CKA_EXTRACTABLE) && hasExtractable &&
        (extractable == PR_TRUE)) {
        return CKR_KEY_FUNCTION_NOT_PERMITTED;
    }

    /* inherit parent's sensitivity */
    if (!hasSensitive) {
        att = sftk_FindAttribute(baseKey, CKA_SENSITIVE);
        if (att == NULL)
            return CKR_KEY_TYPE_INCONSISTENT;
        crv = sftk_defaultAttribute(destKey, sftk_attr_expand(&att->attrib));
        sftk_FreeAttribute(att);
        if (crv != CKR_OK)
            return crv;
    }
    if (!hasExtractable) {
        att = sftk_FindAttribute(baseKey, CKA_EXTRACTABLE);
        if (att == NULL)
            return CKR_KEY_TYPE_INCONSISTENT;
        crv = sftk_defaultAttribute(destKey, sftk_attr_expand(&att->attrib));
        sftk_FreeAttribute(att);
        if (crv != CKR_OK)
            return crv;
    }

    /* we should inherit the parent's always extractable/ never sensitive info,
     * but handleObject always forces this attributes, so we would need to do
     * something special. */
    return CKR_OK;
}

/*
 * make known fixed PKCS #11 key types to their sizes in bytes
 */
unsigned long
sftk_MapKeySize(CK_KEY_TYPE keyType)
{
    switch (keyType) {
        case CKK_CDMF:
            return 8;
        case CKK_DES:
            return 8;
        case CKK_DES2:
            return 16;
        case CKK_DES3:
            return 24;
        /* IDEA and CAST need to be added */
        default:
            break;
    }
    return 0;
}

/* Inputs:
 *  key_len: Length of derived key to be generated.
 *  SharedSecret: a shared secret that is the output of a key agreement primitive.
 *  SharedInfo: (Optional) some data shared by the entities computing the secret key.
 *  SharedInfoLen: the length in octets of SharedInfo
 *  Hash: The hash function to be used in the KDF
 *  HashLen: the length in octets of the output of Hash
 * Output:
 *  key: Pointer to a buffer containing derived key, if return value is SECSuccess.
 */
static CK_RV
sftk_compute_ANSI_X9_63_kdf(CK_BYTE **key, CK_ULONG key_len, SECItem *SharedSecret,
                            CK_BYTE_PTR SharedInfo, CK_ULONG SharedInfoLen,
                            SECStatus Hash(unsigned char *, const unsigned char *, PRUint32),
                            CK_ULONG HashLen)
{
    unsigned char *buffer = NULL, *output_buffer = NULL;
    PRUint32 buffer_len, max_counter, i;
    SECStatus rv;
    CK_RV crv;

    /* Check that key_len isn't too long.  The maximum key length could be
     * greatly increased if the code below did not limit the 4-byte counter
     * to a maximum value of 255. */
    if (key_len > 254 * HashLen)
        return CKR_ARGUMENTS_BAD;

    if (SharedInfo == NULL)
        SharedInfoLen = 0;

    buffer_len = SharedSecret->len + 4 + SharedInfoLen;
    buffer = (CK_BYTE *)PORT_Alloc(buffer_len);
    if (buffer == NULL) {
        crv = CKR_HOST_MEMORY;
        goto loser;
    }

    max_counter = key_len / HashLen;
    if (key_len > max_counter * HashLen)
        max_counter++;

    output_buffer = (CK_BYTE *)PORT_Alloc(max_counter * HashLen);
    if (output_buffer == NULL) {
        crv = CKR_HOST_MEMORY;
        goto loser;
    }

    /* Populate buffer with SharedSecret || Counter || [SharedInfo]
     * where Counter is 0x00000001 */
    PORT_Memcpy(buffer, SharedSecret->data, SharedSecret->len);
    buffer[SharedSecret->len] = 0;
    buffer[SharedSecret->len + 1] = 0;
    buffer[SharedSecret->len + 2] = 0;
    buffer[SharedSecret->len + 3] = 1;
    if (SharedInfo) {
        PORT_Memcpy(&buffer[SharedSecret->len + 4], SharedInfo, SharedInfoLen);
    }

    for (i = 0; i < max_counter; i++) {
        rv = Hash(&output_buffer[i * HashLen], buffer, buffer_len);
        if (rv != SECSuccess) {
            /* 'Hash' should not fail. */
            crv = CKR_FUNCTION_FAILED;
            goto loser;
        }

        /* Increment counter (assumes max_counter < 255) */
        buffer[SharedSecret->len + 3]++;
    }

    PORT_ZFree(buffer, buffer_len);
    if (key_len < max_counter * HashLen) {
        PORT_Memset(output_buffer + key_len, 0, max_counter * HashLen - key_len);
    }
    *key = output_buffer;

    return CKR_OK;

loser:
    if (buffer) {
        PORT_ZFree(buffer, buffer_len);
    }
    if (output_buffer) {
        PORT_ZFree(output_buffer, max_counter * HashLen);
    }
    return crv;
}

static CK_RV
sftk_ANSI_X9_63_kdf(CK_BYTE **key, CK_ULONG key_len,
                    SECItem *SharedSecret,
                    CK_BYTE_PTR SharedInfo, CK_ULONG SharedInfoLen,
                    CK_EC_KDF_TYPE kdf)
{
    if (kdf == CKD_SHA1_KDF)
        return sftk_compute_ANSI_X9_63_kdf(key, key_len, SharedSecret, SharedInfo,
                                           SharedInfoLen, SHA1_HashBuf, SHA1_LENGTH);
    else if (kdf == CKD_SHA224_KDF)
        return sftk_compute_ANSI_X9_63_kdf(key, key_len, SharedSecret, SharedInfo,
                                           SharedInfoLen, SHA224_HashBuf, SHA224_LENGTH);
    else if (kdf == CKD_SHA256_KDF)
        return sftk_compute_ANSI_X9_63_kdf(key, key_len, SharedSecret, SharedInfo,
                                           SharedInfoLen, SHA256_HashBuf, SHA256_LENGTH);
    else if (kdf == CKD_SHA384_KDF)
        return sftk_compute_ANSI_X9_63_kdf(key, key_len, SharedSecret, SharedInfo,
                                           SharedInfoLen, SHA384_HashBuf, SHA384_LENGTH);
    else if (kdf == CKD_SHA512_KDF)
        return sftk_compute_ANSI_X9_63_kdf(key, key_len, SharedSecret, SharedInfo,
                                           SharedInfoLen, SHA512_HashBuf, SHA512_LENGTH);
    else
        return CKR_MECHANISM_INVALID;
}

/*
 * SSL Key generation given pre master secret
 */
#define NUM_MIXERS 9
static const char *const mixers[NUM_MIXERS] = {
    "A",
    "BB",
    "CCC",
    "DDDD",
    "EEEEE",
    "FFFFFF",
    "GGGGGGG",
    "HHHHHHHH",
    "IIIIIIIII"
};
#define SSL3_PMS_LENGTH 48
#define SSL3_MASTER_SECRET_LENGTH 48
#define SSL3_RANDOM_LENGTH 32

/* NSC_DeriveKey derives a key from a base key, creating a new key object. */
CK_RV
NSC_DeriveKey(CK_SESSION_HANDLE hSession,
              CK_MECHANISM_PTR pMechanism, CK_OBJECT_HANDLE hBaseKey,
              CK_ATTRIBUTE_PTR pTemplate, CK_ULONG ulAttributeCount,
              CK_OBJECT_HANDLE_PTR phKey)
{
    SFTKSession *session;
    SFTKSlot *slot = sftk_SlotFromSessionHandle(hSession);
    SFTKObject *key;
    SFTKObject *sourceKey;
    SFTKAttribute *att = NULL;
    SFTKAttribute *att2 = NULL;
    unsigned char *buf;
    SHA1Context *sha;
    MD5Context *md5;
    MD2Context *md2;
    CK_ULONG macSize;
    CK_ULONG tmpKeySize;
    CK_ULONG IVSize;
    CK_ULONG keySize = 0;
    CK_RV crv = CKR_OK;
    CK_BBOOL cktrue = CK_TRUE;
    CK_KEY_TYPE keyType = CKK_GENERIC_SECRET;
    CK_OBJECT_CLASS classType = CKO_SECRET_KEY;
    CK_KEY_DERIVATION_STRING_DATA *stringPtr;
    CK_MECHANISM_TYPE mechanism = pMechanism->mechanism;
    PRBool isTLS = PR_FALSE;
    PRBool isDH = PR_FALSE;
    HASH_HashType tlsPrfHash = HASH_AlgNULL;
    SECStatus rv;
    int i;
    unsigned int outLen;
    unsigned char sha_out[SHA1_LENGTH];
    unsigned char key_block[NUM_MIXERS * SFTK_MAX_MAC_LENGTH];
    PRBool isFIPS;
    HASH_HashType hashType;
    PRBool extractValue = PR_TRUE;

    CHECK_FORK();

    if (!slot) {
        return CKR_SESSION_HANDLE_INVALID;
    }
    /*
     * now lets create an object to hang the attributes off of
     */
    if (phKey)
        *phKey = CK_INVALID_HANDLE;

    key = sftk_NewObject(slot); /* fill in the handle later */
    if (key == NULL) {
        return CKR_HOST_MEMORY;
    }
    isFIPS = (slot->slotID == FIPS_SLOT_ID);

    /*
     * load the template values into the object
     */
    for (i = 0; i < (int)ulAttributeCount; i++) {
        crv = sftk_AddAttributeType(key, sftk_attr_expand(&pTemplate[i]));
        if (crv != CKR_OK)
            break;

        if (pTemplate[i].type == CKA_KEY_TYPE) {
            keyType = *(CK_KEY_TYPE *)pTemplate[i].pValue;
        }
        if (pTemplate[i].type == CKA_VALUE_LEN) {
            keySize = *(CK_ULONG *)pTemplate[i].pValue;
        }
    }
    if (crv != CKR_OK) {
        sftk_FreeObject(key);
        return crv;
    }

    if (keySize == 0) {
        keySize = sftk_MapKeySize(keyType);
    }

    switch (mechanism) {
        case CKM_NSS_JPAKE_ROUND2_SHA1:   /* fall through */
        case CKM_NSS_JPAKE_ROUND2_SHA256: /* fall through */
        case CKM_NSS_JPAKE_ROUND2_SHA384: /* fall through */
        case CKM_NSS_JPAKE_ROUND2_SHA512:
            extractValue = PR_FALSE;
            classType = CKO_PRIVATE_KEY;
            break;
        case CKM_NSS_JPAKE_FINAL_SHA1:   /* fall through */
        case CKM_NSS_JPAKE_FINAL_SHA256: /* fall through */
        case CKM_NSS_JPAKE_FINAL_SHA384: /* fall through */
        case CKM_NSS_JPAKE_FINAL_SHA512:
            extractValue = PR_FALSE;
        /* fall through */
        default:
            classType = CKO_SECRET_KEY;
    }

    crv = sftk_forceAttribute(key, CKA_CLASS, &classType, sizeof(classType));
    if (crv != CKR_OK) {
        sftk_FreeObject(key);
        return crv;
    }

    /* look up the base key we're deriving with */
    session = sftk_SessionFromHandle(hSession);
    if (session == NULL) {
        sftk_FreeObject(key);
        return CKR_SESSION_HANDLE_INVALID;
    }

    sourceKey = sftk_ObjectFromHandle(hBaseKey, session);
    sftk_FreeSession(session);
    if (sourceKey == NULL) {
        sftk_FreeObject(key);
        return CKR_KEY_HANDLE_INVALID;
    }

    if (extractValue) {
        /* get the value of the base key */
        att = sftk_FindAttribute(sourceKey, CKA_VALUE);
        if (att == NULL) {
            sftk_FreeObject(key);
            sftk_FreeObject(sourceKey);
            return CKR_KEY_HANDLE_INVALID;
        }
    }

    switch (mechanism) {
        /*
         * generate the master secret
         */
        case CKM_TLS12_MASTER_KEY_DERIVE:
        case CKM_TLS12_MASTER_KEY_DERIVE_DH:
        case CKM_NSS_TLS_MASTER_KEY_DERIVE_SHA256:
        case CKM_NSS_TLS_MASTER_KEY_DERIVE_DH_SHA256:
        case CKM_TLS_MASTER_KEY_DERIVE:
        case CKM_TLS_MASTER_KEY_DERIVE_DH:
        case CKM_SSL3_MASTER_KEY_DERIVE:
        case CKM_SSL3_MASTER_KEY_DERIVE_DH: {
            CK_SSL3_MASTER_KEY_DERIVE_PARAMS *ssl3_master;
            SSL3RSAPreMasterSecret *rsa_pms;
            unsigned char crsrdata[SSL3_RANDOM_LENGTH * 2];

            if ((mechanism == CKM_TLS12_MASTER_KEY_DERIVE) ||
                (mechanism == CKM_TLS12_MASTER_KEY_DERIVE_DH)) {
                CK_TLS12_MASTER_KEY_DERIVE_PARAMS *tls12_master =
                    (CK_TLS12_MASTER_KEY_DERIVE_PARAMS *)pMechanism->pParameter;
                tlsPrfHash = GetHashTypeFromMechanism(tls12_master->prfHashMechanism);
                if (tlsPrfHash == HASH_AlgNULL) {
                    crv = CKR_MECHANISM_PARAM_INVALID;
                    break;
                }
            } else if ((mechanism == CKM_NSS_TLS_MASTER_KEY_DERIVE_SHA256) ||
                       (mechanism == CKM_NSS_TLS_MASTER_KEY_DERIVE_DH_SHA256)) {
                tlsPrfHash = HASH_AlgSHA256;
            }

            if ((mechanism != CKM_SSL3_MASTER_KEY_DERIVE) &&
                (mechanism != CKM_SSL3_MASTER_KEY_DERIVE_DH)) {
                isTLS = PR_TRUE;
            }
            if ((mechanism == CKM_SSL3_MASTER_KEY_DERIVE_DH) ||
                (mechanism == CKM_TLS_MASTER_KEY_DERIVE_DH) ||
                (mechanism == CKM_NSS_TLS_MASTER_KEY_DERIVE_DH_SHA256) ||
                (mechanism == CKM_TLS12_MASTER_KEY_DERIVE_DH)) {
                isDH = PR_TRUE;
            }

            /* first do the consistency checks */
            if (!isDH && (att->attrib.ulValueLen != SSL3_PMS_LENGTH)) {
                crv = CKR_KEY_TYPE_INCONSISTENT;
                break;
            }
            att2 = sftk_FindAttribute(sourceKey, CKA_KEY_TYPE);
            if ((att2 == NULL) || (*(CK_KEY_TYPE *)att2->attrib.pValue !=
                                   CKK_GENERIC_SECRET)) {
                if (att2)
                    sftk_FreeAttribute(att2);
                crv = CKR_KEY_FUNCTION_NOT_PERMITTED;
                break;
            }
            sftk_FreeAttribute(att2);
            if (keyType != CKK_GENERIC_SECRET) {
                crv = CKR_KEY_FUNCTION_NOT_PERMITTED;
                break;
            }
            if ((keySize != 0) && (keySize != SSL3_MASTER_SECRET_LENGTH)) {
                crv = CKR_KEY_FUNCTION_NOT_PERMITTED;
                break;
            }

            /* finally do the key gen */
            ssl3_master = (CK_SSL3_MASTER_KEY_DERIVE_PARAMS *)
                              pMechanism->pParameter;

            PORT_Memcpy(crsrdata,
                        ssl3_master->RandomInfo.pClientRandom, SSL3_RANDOM_LENGTH);
            PORT_Memcpy(crsrdata + SSL3_RANDOM_LENGTH,
                        ssl3_master->RandomInfo.pServerRandom, SSL3_RANDOM_LENGTH);

            if (ssl3_master->pVersion) {
                SFTKSessionObject *sessKey = sftk_narrowToSessionObject(key);
                rsa_pms = (SSL3RSAPreMasterSecret *)att->attrib.pValue;
                /* don't leak more key material then necessary for SSL to work */
                if ((sessKey == NULL) || sessKey->wasDerived) {
                    ssl3_master->pVersion->major = 0xff;
                    ssl3_master->pVersion->minor = 0xff;
                } else {
                    ssl3_master->pVersion->major = rsa_pms->client_version[0];
                    ssl3_master->pVersion->minor = rsa_pms->client_version[1];
                }
            }
            if (ssl3_master->RandomInfo.ulClientRandomLen != SSL3_RANDOM_LENGTH) {
                crv = CKR_MECHANISM_PARAM_INVALID;
                break;
            }
            if (ssl3_master->RandomInfo.ulServerRandomLen != SSL3_RANDOM_LENGTH) {
                crv = CKR_MECHANISM_PARAM_INVALID;
                break;
            }

            if (isTLS) {
                SECStatus status;
                SECItem crsr = { siBuffer, NULL, 0 };
                SECItem master = { siBuffer, NULL, 0 };
                SECItem pms = { siBuffer, NULL, 0 };

                crsr.data = crsrdata;
                crsr.len = sizeof crsrdata;
                master.data = key_block;
                master.len = SSL3_MASTER_SECRET_LENGTH;
                pms.data = (unsigned char *)att->attrib.pValue;
                pms.len = att->attrib.ulValueLen;

                if (tlsPrfHash != HASH_AlgNULL) {
                    status = TLS_P_hash(tlsPrfHash, &pms, "master secret",
                                        &crsr, &master, isFIPS);
                } else {
                    status = TLS_PRF(&pms, "master secret", &crsr, &master, isFIPS);
                }
                if (status != SECSuccess) {
                    crv = CKR_FUNCTION_FAILED;
                    break;
                }
            } else {
                /* now allocate the hash contexts */
                md5 = MD5_NewContext();
                if (md5 == NULL) {
                    crv = CKR_HOST_MEMORY;
                    break;
                }
                sha = SHA1_NewContext();
                if (sha == NULL) {
                    PORT_Free(md5);
                    crv = CKR_HOST_MEMORY;
                    break;
                }
                for (i = 0; i < 3; i++) {
                    SHA1_Begin(sha);
                    SHA1_Update(sha, (unsigned char *)mixers[i], strlen(mixers[i]));
                    SHA1_Update(sha, (const unsigned char *)att->attrib.pValue,
                                att->attrib.ulValueLen);
                    SHA1_Update(sha, crsrdata, sizeof crsrdata);
                    SHA1_End(sha, sha_out, &outLen, SHA1_LENGTH);
                    PORT_Assert(outLen == SHA1_LENGTH);

                    MD5_Begin(md5);
                    MD5_Update(md5, (const unsigned char *)att->attrib.pValue,
                               att->attrib.ulValueLen);
                    MD5_Update(md5, sha_out, outLen);
                    MD5_End(md5, &key_block[i * MD5_LENGTH], &outLen, MD5_LENGTH);
                    PORT_Assert(outLen == MD5_LENGTH);
                }
                PORT_Free(md5);
                PORT_Free(sha);
            }

            /* store the results */
            crv = sftk_forceAttribute(key, CKA_VALUE, key_block, SSL3_MASTER_SECRET_LENGTH);
            if (crv != CKR_OK)
                break;
            keyType = CKK_GENERIC_SECRET;
            crv = sftk_forceAttribute(key, CKA_KEY_TYPE, &keyType, sizeof(keyType));
            if (isTLS) {
                /* TLS's master secret is used to "sign" finished msgs with PRF. */
                /* XXX This seems like a hack.   But SFTK_Derive only accepts
                 * one "operation" argument. */
                crv = sftk_forceAttribute(key, CKA_SIGN, &cktrue, sizeof(CK_BBOOL));
                if (crv != CKR_OK)
                    break;
                crv = sftk_forceAttribute(key, CKA_VERIFY, &cktrue, sizeof(CK_BBOOL));
                if (crv != CKR_OK)
                    break;
                /* While we're here, we might as well force this, too. */
                crv = sftk_forceAttribute(key, CKA_DERIVE, &cktrue, sizeof(CK_BBOOL));
                if (crv != CKR_OK)
                    break;
            }
            break;
        }

        /* Extended master key derivation [draft-ietf-tls-session-hash] */
        case CKM_NSS_TLS_EXTENDED_MASTER_KEY_DERIVE:
        case CKM_NSS_TLS_EXTENDED_MASTER_KEY_DERIVE_DH: {
            CK_NSS_TLS_EXTENDED_MASTER_KEY_DERIVE_PARAMS *ems_params;
            SSL3RSAPreMasterSecret *rsa_pms;
            SECStatus status;
            SECItem pms = { siBuffer, NULL, 0 };
            SECItem seed = { siBuffer, NULL, 0 };
            SECItem master = { siBuffer, NULL, 0 };

            ems_params = (CK_NSS_TLS_EXTENDED_MASTER_KEY_DERIVE_PARAMS *)
                             pMechanism->pParameter;

            /* First do the consistency checks */
            if ((mechanism == CKM_NSS_TLS_EXTENDED_MASTER_KEY_DERIVE) &&
                (att->attrib.ulValueLen != SSL3_PMS_LENGTH)) {
                crv = CKR_KEY_TYPE_INCONSISTENT;
                break;
            }
            att2 = sftk_FindAttribute(sourceKey, CKA_KEY_TYPE);
            if ((att2 == NULL) ||
                (*(CK_KEY_TYPE *)att2->attrib.pValue != CKK_GENERIC_SECRET)) {
                if (att2)
                    sftk_FreeAttribute(att2);
                crv = CKR_KEY_FUNCTION_NOT_PERMITTED;
                break;
            }
            sftk_FreeAttribute(att2);
            if (keyType != CKK_GENERIC_SECRET) {
                crv = CKR_KEY_FUNCTION_NOT_PERMITTED;
                break;
            }
            if ((keySize != 0) && (keySize != SSL3_MASTER_SECRET_LENGTH)) {
                crv = CKR_KEY_FUNCTION_NOT_PERMITTED;
                break;
            }

            /* Do the key derivation */
            pms.data = (unsigned char *)att->attrib.pValue;
            pms.len = att->attrib.ulValueLen;
            seed.data = ems_params->pSessionHash;
            seed.len = ems_params->ulSessionHashLen;
            master.data = key_block;
            master.len = SSL3_MASTER_SECRET_LENGTH;
            if (ems_params->prfHashMechanism == CKM_TLS_PRF) {
                /*
                 * In this case, the session hash is the concatenation of SHA-1
                 * and MD5, so it should be 36 bytes long.
                 */
                if (seed.len != MD5_LENGTH + SHA1_LENGTH) {
                    crv = CKR_TEMPLATE_INCONSISTENT;
                    break;
                }

                status = TLS_PRF(&pms, "extended master secret",
                                 &seed, &master, isFIPS);
            } else {
                const SECHashObject *hashObj;

                tlsPrfHash = GetHashTypeFromMechanism(ems_params->prfHashMechanism);
                if (tlsPrfHash == HASH_AlgNULL) {
                    crv = CKR_MECHANISM_PARAM_INVALID;
                    break;
                }

                hashObj = HASH_GetRawHashObject(tlsPrfHash);
                if (seed.len != hashObj->length) {
                    crv = CKR_TEMPLATE_INCONSISTENT;
                    break;
                }

                status = TLS_P_hash(tlsPrfHash, &pms, "extended master secret",
                                    &seed, &master, isFIPS);
            }
            if (status != SECSuccess) {
                crv = CKR_FUNCTION_FAILED;
                break;
            }

            /* Reflect the version if required */
            if (ems_params->pVersion) {
                SFTKSessionObject *sessKey = sftk_narrowToSessionObject(key);
                rsa_pms = (SSL3RSAPreMasterSecret *)att->attrib.pValue;
                /* don't leak more key material than necessary for SSL to work */
                if ((sessKey == NULL) || sessKey->wasDerived) {
                    ems_params->pVersion->major = 0xff;
                    ems_params->pVersion->minor = 0xff;
                } else {
                    ems_params->pVersion->major = rsa_pms->client_version[0];
                    ems_params->pVersion->minor = rsa_pms->client_version[1];
                }
            }

            /* Store the results */
            crv = sftk_forceAttribute(key, CKA_VALUE, key_block,
                                      SSL3_MASTER_SECRET_LENGTH);
            break;
        }

        case CKM_TLS12_KEY_AND_MAC_DERIVE:
        case CKM_NSS_TLS_KEY_AND_MAC_DERIVE_SHA256:
        case CKM_TLS_KEY_AND_MAC_DERIVE:
        case CKM_SSL3_KEY_AND_MAC_DERIVE: {
            CK_SSL3_KEY_MAT_PARAMS *ssl3_keys;
            CK_SSL3_KEY_MAT_OUT *ssl3_keys_out;
            CK_ULONG effKeySize;
            unsigned int block_needed;
            unsigned char srcrdata[SSL3_RANDOM_LENGTH * 2];
            unsigned char crsrdata[SSL3_RANDOM_LENGTH * 2];

            if (mechanism == CKM_TLS12_KEY_AND_MAC_DERIVE) {
                CK_TLS12_KEY_MAT_PARAMS *tls12_keys =
                    (CK_TLS12_KEY_MAT_PARAMS *)pMechanism->pParameter;
                tlsPrfHash = GetHashTypeFromMechanism(tls12_keys->prfHashMechanism);
                if (tlsPrfHash == HASH_AlgNULL) {
                    crv = CKR_MECHANISM_PARAM_INVALID;
                    break;
                }
            } else if (mechanism == CKM_NSS_TLS_KEY_AND_MAC_DERIVE_SHA256) {
                tlsPrfHash = HASH_AlgSHA256;
            }

            if (mechanism != CKM_SSL3_KEY_AND_MAC_DERIVE) {
                isTLS = PR_TRUE;
            }

            crv = sftk_DeriveSensitiveCheck(sourceKey, key);
            if (crv != CKR_OK)
                break;

            if (att->attrib.ulValueLen != SSL3_MASTER_SECRET_LENGTH) {
                crv = CKR_KEY_FUNCTION_NOT_PERMITTED;
                break;
            }
            att2 = sftk_FindAttribute(sourceKey, CKA_KEY_TYPE);
            if ((att2 == NULL) || (*(CK_KEY_TYPE *)att2->attrib.pValue !=
                                   CKK_GENERIC_SECRET)) {
                if (att2)
                    sftk_FreeAttribute(att2);
                crv = CKR_KEY_FUNCTION_NOT_PERMITTED;
                break;
            }
            sftk_FreeAttribute(att2);
            md5 = MD5_NewContext();
            if (md5 == NULL) {
                crv = CKR_HOST_MEMORY;
                break;
            }
            sha = SHA1_NewContext();
            if (sha == NULL) {
                MD5_DestroyContext(md5, PR_TRUE);
                crv = CKR_HOST_MEMORY;
                break;
            }
            ssl3_keys = (CK_SSL3_KEY_MAT_PARAMS *)pMechanism->pParameter;

            PORT_Memcpy(srcrdata,
                        ssl3_keys->RandomInfo.pServerRandom, SSL3_RANDOM_LENGTH);
            PORT_Memcpy(srcrdata + SSL3_RANDOM_LENGTH,
                        ssl3_keys->RandomInfo.pClientRandom, SSL3_RANDOM_LENGTH);

            PORT_Memcpy(crsrdata,
                        ssl3_keys->RandomInfo.pClientRandom, SSL3_RANDOM_LENGTH);
            PORT_Memcpy(crsrdata + SSL3_RANDOM_LENGTH,
                        ssl3_keys->RandomInfo.pServerRandom, SSL3_RANDOM_LENGTH);

            /*
             * clear out our returned keys so we can recover on failure
             */
            ssl3_keys_out = ssl3_keys->pReturnedKeyMaterial;
            ssl3_keys_out->hClientMacSecret = CK_INVALID_HANDLE;
            ssl3_keys_out->hServerMacSecret = CK_INVALID_HANDLE;
            ssl3_keys_out->hClientKey = CK_INVALID_HANDLE;
            ssl3_keys_out->hServerKey = CK_INVALID_HANDLE;

            /*
             * How much key material do we need?
             */
            macSize = ssl3_keys->ulMacSizeInBits / 8;
            effKeySize = ssl3_keys->ulKeySizeInBits / 8;
            IVSize = ssl3_keys->ulIVSizeInBits / 8;
            if (keySize == 0) {
                effKeySize = keySize;
            }

            /* bIsExport must be false. */
            if (ssl3_keys->bIsExport) {
                MD5_DestroyContext(md5, PR_TRUE);
                SHA1_DestroyContext(sha, PR_TRUE);
                crv = CKR_MECHANISM_PARAM_INVALID;
                break;
            }

            block_needed = 2 * (macSize + effKeySize + IVSize);
            PORT_Assert(block_needed <= sizeof key_block);
            if (block_needed > sizeof key_block)
                block_needed = sizeof key_block;

            /*
             * generate the key material: This looks amazingly similar to the
             * PMS code, and is clearly crying out for a function to provide it.
             */
            if (isTLS) {
                SECStatus status;
                SECItem srcr = { siBuffer, NULL, 0 };
                SECItem keyblk = { siBuffer, NULL, 0 };
                SECItem master = { siBuffer, NULL, 0 };

                srcr.data = srcrdata;
                srcr.len = sizeof srcrdata;
                keyblk.data = key_block;
                keyblk.len = block_needed;
                master.data = (unsigned char *)att->attrib.pValue;
                master.len = att->attrib.ulValueLen;

                if (tlsPrfHash != HASH_AlgNULL) {
                    status = TLS_P_hash(tlsPrfHash, &master, "key expansion",
                                        &srcr, &keyblk, isFIPS);
                } else {
                    status = TLS_PRF(&master, "key expansion", &srcr, &keyblk,
                                     isFIPS);
                }
                if (status != SECSuccess) {
                    goto key_and_mac_derive_fail;
                }
            } else {
                unsigned int block_bytes = 0;
                /* key_block =
                 *     MD5(master_secret + SHA('A' + master_secret +
                 *                      ServerHello.random + ClientHello.random)) +
                 *     MD5(master_secret + SHA('BB' + master_secret +
                 *                      ServerHello.random + ClientHello.random)) +
                 *     MD5(master_secret + SHA('CCC' + master_secret +
                 *                      ServerHello.random + ClientHello.random)) +
                 *     [...];
                 */
                for (i = 0; i < NUM_MIXERS && block_bytes < block_needed; i++) {
                    SHA1_Begin(sha);
                    SHA1_Update(sha, (unsigned char *)mixers[i], strlen(mixers[i]));
                    SHA1_Update(sha, (const unsigned char *)att->attrib.pValue,
                                att->attrib.ulValueLen);
                    SHA1_Update(sha, srcrdata, sizeof srcrdata);
                    SHA1_End(sha, sha_out, &outLen, SHA1_LENGTH);
                    PORT_Assert(outLen == SHA1_LENGTH);
                    MD5_Begin(md5);
                    MD5_Update(md5, (const unsigned char *)att->attrib.pValue,
                               att->attrib.ulValueLen);
                    MD5_Update(md5, sha_out, outLen);
                    MD5_End(md5, &key_block[i * MD5_LENGTH], &outLen, MD5_LENGTH);
                    PORT_Assert(outLen == MD5_LENGTH);
                    block_bytes += outLen;
                }
            }

            /*
             * Put the key material where it goes.
             */
            i = 0; /* now shows how much consumed */

            /*
             * The key_block is partitioned as follows:
             * client_write_MAC_secret[CipherSpec.hash_size]
             */
            crv = sftk_buildSSLKey(hSession, key, PR_TRUE, &key_block[i], macSize,
                                   &ssl3_keys_out->hClientMacSecret);
            if (crv != CKR_OK)
                goto key_and_mac_derive_fail;

            i += macSize;

            /*
             * server_write_MAC_secret[CipherSpec.hash_size]
             */
            crv = sftk_buildSSLKey(hSession, key, PR_TRUE, &key_block[i], macSize,
                                   &ssl3_keys_out->hServerMacSecret);
            if (crv != CKR_OK) {
                goto key_and_mac_derive_fail;
            }
            i += macSize;

            if (keySize) {
                /*
                ** Generate Domestic write keys and IVs.
                ** client_write_key[CipherSpec.key_material]
                */
                crv = sftk_buildSSLKey(hSession, key, PR_FALSE, &key_block[i],
                                       keySize, &ssl3_keys_out->hClientKey);
                if (crv != CKR_OK) {
                    goto key_and_mac_derive_fail;
                }
                i += keySize;

                /*
                ** server_write_key[CipherSpec.key_material]
                */
                crv = sftk_buildSSLKey(hSession, key, PR_FALSE, &key_block[i],
                                       keySize, &ssl3_keys_out->hServerKey);
                if (crv != CKR_OK) {
                    goto key_and_mac_derive_fail;
                }
                i += keySize;

                /*
                ** client_write_IV[CipherSpec.IV_size]
                */
                if (IVSize > 0) {
                    PORT_Memcpy(ssl3_keys_out->pIVClient,
                                &key_block[i], IVSize);
                    i += IVSize;
                }

                /*
                ** server_write_IV[CipherSpec.IV_size]
                */
                if (IVSize > 0) {
                    PORT_Memcpy(ssl3_keys_out->pIVServer,
                                &key_block[i], IVSize);
                    i += IVSize;
                }
                PORT_Assert(i <= sizeof key_block);
            }

            crv = CKR_OK;

            if (0) {
            key_and_mac_derive_fail:
                if (crv == CKR_OK)
                    crv = CKR_FUNCTION_FAILED;
                sftk_freeSSLKeys(hSession, ssl3_keys_out);
            }
            MD5_DestroyContext(md5, PR_TRUE);
            SHA1_DestroyContext(sha, PR_TRUE);
            sftk_FreeObject(key);
            key = NULL;
            break;
        }

        case CKM_CONCATENATE_BASE_AND_KEY: {
            SFTKObject *newKey;

            crv = sftk_DeriveSensitiveCheck(sourceKey, key);
            if (crv != CKR_OK)
                break;

            session = sftk_SessionFromHandle(hSession);
            if (session == NULL) {
                crv = CKR_SESSION_HANDLE_INVALID;
                break;
            }

            newKey = sftk_ObjectFromHandle(*(CK_OBJECT_HANDLE *)
                                                pMechanism->pParameter,
                                           session);
            sftk_FreeSession(session);
            if (newKey == NULL) {
                crv = CKR_KEY_HANDLE_INVALID;
                break;
            }

            if (sftk_isTrue(newKey, CKA_SENSITIVE)) {
                crv = sftk_forceAttribute(newKey, CKA_SENSITIVE, &cktrue,
                                          sizeof(CK_BBOOL));
                if (crv != CKR_OK) {
                    sftk_FreeObject(newKey);
                    break;
                }
            }

            att2 = sftk_FindAttribute(newKey, CKA_VALUE);
            if (att2 == NULL) {
                sftk_FreeObject(newKey);
                crv = CKR_KEY_HANDLE_INVALID;
                break;
            }
            tmpKeySize = att->attrib.ulValueLen + att2->attrib.ulValueLen;
            if (keySize == 0)
                keySize = tmpKeySize;
            if (keySize > tmpKeySize) {
                sftk_FreeObject(newKey);
                sftk_FreeAttribute(att2);
                crv = CKR_TEMPLATE_INCONSISTENT;
                break;
            }
            buf = (unsigned char *)PORT_Alloc(tmpKeySize);
            if (buf == NULL) {
                sftk_FreeAttribute(att2);
                sftk_FreeObject(newKey);
                crv = CKR_HOST_MEMORY;
                break;
            }

            PORT_Memcpy(buf, att->attrib.pValue, att->attrib.ulValueLen);
            PORT_Memcpy(buf + att->attrib.ulValueLen,
                        att2->attrib.pValue, att2->attrib.ulValueLen);

            crv = sftk_forceAttribute(key, CKA_VALUE, buf, keySize);
            PORT_ZFree(buf, tmpKeySize);
            sftk_FreeAttribute(att2);
            sftk_FreeObject(newKey);
            break;
        }

        case CKM_CONCATENATE_BASE_AND_DATA:
            crv = sftk_DeriveSensitiveCheck(sourceKey, key);
            if (crv != CKR_OK)
                break;

            stringPtr = (CK_KEY_DERIVATION_STRING_DATA *)pMechanism->pParameter;
            tmpKeySize = att->attrib.ulValueLen + stringPtr->ulLen;
            if (keySize == 0)
                keySize = tmpKeySize;
            if (keySize > tmpKeySize) {
                crv = CKR_TEMPLATE_INCONSISTENT;
                break;
            }
            buf = (unsigned char *)PORT_Alloc(tmpKeySize);
            if (buf == NULL) {
                crv = CKR_HOST_MEMORY;
                break;
            }

            PORT_Memcpy(buf, att->attrib.pValue, att->attrib.ulValueLen);
            PORT_Memcpy(buf + att->attrib.ulValueLen, stringPtr->pData,
                        stringPtr->ulLen);

            crv = sftk_forceAttribute(key, CKA_VALUE, buf, keySize);
            PORT_ZFree(buf, tmpKeySize);
            break;
        case CKM_CONCATENATE_DATA_AND_BASE:
            crv = sftk_DeriveSensitiveCheck(sourceKey, key);
            if (crv != CKR_OK)
                break;

            stringPtr = (CK_KEY_DERIVATION_STRING_DATA *)pMechanism->pParameter;
            tmpKeySize = att->attrib.ulValueLen + stringPtr->ulLen;
            if (keySize == 0)
                keySize = tmpKeySize;
            if (keySize > tmpKeySize) {
                crv = CKR_TEMPLATE_INCONSISTENT;
                break;
            }
            buf = (unsigned char *)PORT_Alloc(tmpKeySize);
            if (buf == NULL) {
                crv = CKR_HOST_MEMORY;
                break;
            }

            PORT_Memcpy(buf, stringPtr->pData, stringPtr->ulLen);
            PORT_Memcpy(buf + stringPtr->ulLen, att->attrib.pValue,
                        att->attrib.ulValueLen);

            crv = sftk_forceAttribute(key, CKA_VALUE, buf, keySize);
            PORT_ZFree(buf, tmpKeySize);
            break;
        case CKM_XOR_BASE_AND_DATA:
            crv = sftk_DeriveSensitiveCheck(sourceKey, key);
            if (crv != CKR_OK)
                break;

            stringPtr = (CK_KEY_DERIVATION_STRING_DATA *)pMechanism->pParameter;
            tmpKeySize = PR_MIN(att->attrib.ulValueLen, stringPtr->ulLen);
            if (keySize == 0)
                keySize = tmpKeySize;
            if (keySize > tmpKeySize) {
                crv = CKR_TEMPLATE_INCONSISTENT;
                break;
            }
            buf = (unsigned char *)PORT_Alloc(keySize);
            if (buf == NULL) {
                crv = CKR_HOST_MEMORY;
                break;
            }

            PORT_Memcpy(buf, att->attrib.pValue, keySize);
            for (i = 0; i < (int)keySize; i++) {
                buf[i] ^= stringPtr->pData[i];
            }

            crv = sftk_forceAttribute(key, CKA_VALUE, buf, keySize);
            PORT_ZFree(buf, keySize);
            break;

        case CKM_EXTRACT_KEY_FROM_KEY: {
            /* the following assumes 8 bits per byte */
            CK_ULONG extract = *(CK_EXTRACT_PARAMS *)pMechanism->pParameter;
            CK_ULONG shift = extract & 0x7; /* extract mod 8 the fast way */
            CK_ULONG offset = extract >> 3; /* extract div 8 the fast way */

            crv = sftk_DeriveSensitiveCheck(sourceKey, key);
            if (crv != CKR_OK)
                break;

            if (keySize == 0) {
                crv = CKR_TEMPLATE_INCOMPLETE;
                break;
            }
            /* make sure we have enough bits in the original key */
            if (att->attrib.ulValueLen <
                (offset + keySize + ((shift != 0) ? 1 : 0))) {
                crv = CKR_MECHANISM_PARAM_INVALID;
                break;
            }
            buf = (unsigned char *)PORT_Alloc(keySize);
            if (buf == NULL) {
                crv = CKR_HOST_MEMORY;
                break;
            }

            /* copy the bits we need into the new key */
            for (i = 0; i < (int)keySize; i++) {
                unsigned char *value =
                    ((unsigned char *)att->attrib.pValue) + offset + i;
                if (shift) {
                    buf[i] = (value[0] << (shift)) | (value[1] >> (8 - shift));
                } else {
                    buf[i] = value[0];
                }
            }

            crv = sftk_forceAttribute(key, CKA_VALUE, buf, keySize);
            PORT_ZFree(buf, keySize);
            break;
        }
        case CKM_MD2_KEY_DERIVATION:
            if (keySize == 0)
                keySize = MD2_LENGTH;
            if (keySize > MD2_LENGTH) {
                crv = CKR_TEMPLATE_INCONSISTENT;
                break;
            }
            /* now allocate the hash contexts */
            md2 = MD2_NewContext();
            if (md2 == NULL) {
                crv = CKR_HOST_MEMORY;
                break;
            }
            MD2_Begin(md2);
            MD2_Update(md2, (const unsigned char *)att->attrib.pValue,
                       att->attrib.ulValueLen);
            MD2_End(md2, key_block, &outLen, MD2_LENGTH);
            MD2_DestroyContext(md2, PR_TRUE);

            crv = sftk_forceAttribute(key, CKA_VALUE, key_block, keySize);
            break;
        case CKM_MD5_KEY_DERIVATION:
            if (keySize == 0)
                keySize = MD5_LENGTH;
            if (keySize > MD5_LENGTH) {
                crv = CKR_TEMPLATE_INCONSISTENT;
                break;
            }
            MD5_HashBuf(key_block, (const unsigned char *)att->attrib.pValue,
                        att->attrib.ulValueLen);

            crv = sftk_forceAttribute(key, CKA_VALUE, key_block, keySize);
            break;
        case CKM_SHA1_KEY_DERIVATION:
            if (keySize == 0)
                keySize = SHA1_LENGTH;
            if (keySize > SHA1_LENGTH) {
                crv = CKR_TEMPLATE_INCONSISTENT;
                break;
            }
            SHA1_HashBuf(key_block, (const unsigned char *)att->attrib.pValue,
                         att->attrib.ulValueLen);

            crv = sftk_forceAttribute(key, CKA_VALUE, key_block, keySize);
            break;

        case CKM_SHA224_KEY_DERIVATION:
            if (keySize == 0)
                keySize = SHA224_LENGTH;
            if (keySize > SHA224_LENGTH) {
                crv = CKR_TEMPLATE_INCONSISTENT;
                break;
            }
            SHA224_HashBuf(key_block, (const unsigned char *)att->attrib.pValue,
                           att->attrib.ulValueLen);

            crv = sftk_forceAttribute(key, CKA_VALUE, key_block, keySize);
            break;

        case CKM_SHA256_KEY_DERIVATION:
            if (keySize == 0)
                keySize = SHA256_LENGTH;
            if (keySize > SHA256_LENGTH) {
                crv = CKR_TEMPLATE_INCONSISTENT;
                break;
            }
            SHA256_HashBuf(key_block, (const unsigned char *)att->attrib.pValue,
                           att->attrib.ulValueLen);

            crv = sftk_forceAttribute(key, CKA_VALUE, key_block, keySize);
            break;

        case CKM_SHA384_KEY_DERIVATION:
            if (keySize == 0)
                keySize = SHA384_LENGTH;
            if (keySize > SHA384_LENGTH) {
                crv = CKR_TEMPLATE_INCONSISTENT;
                break;
            }
            SHA384_HashBuf(key_block, (const unsigned char *)att->attrib.pValue,
                           att->attrib.ulValueLen);

            crv = sftk_forceAttribute(key, CKA_VALUE, key_block, keySize);
            break;

        case CKM_SHA512_KEY_DERIVATION:
            if (keySize == 0)
                keySize = SHA512_LENGTH;
            if (keySize > SHA512_LENGTH) {
                crv = CKR_TEMPLATE_INCONSISTENT;
                break;
            }
            SHA512_HashBuf(key_block, (const unsigned char *)att->attrib.pValue,
                           att->attrib.ulValueLen);

            crv = sftk_forceAttribute(key, CKA_VALUE, key_block, keySize);
            break;

        case CKM_DH_PKCS_DERIVE: {
            SECItem derived, dhPublic;
            SECItem dhPrime, dhValue;
            /* sourceKey - values for the local existing low key */
            /* get prime and value attributes */
            crv = sftk_Attribute2SecItem(NULL, &dhPrime, sourceKey, CKA_PRIME);
            if (crv != SECSuccess)
                break;
            crv = sftk_Attribute2SecItem(NULL, &dhValue, sourceKey, CKA_VALUE);
            if (crv != SECSuccess) {
                PORT_Free(dhPrime.data);
                break;
            }

            dhPublic.data = pMechanism->pParameter;
            dhPublic.len = pMechanism->ulParameterLen;

            /* calculate private value - oct */
            rv = DH_Derive(&dhPublic, &dhPrime, &dhValue, &derived, keySize);

            PORT_Free(dhPrime.data);
            PORT_Free(dhValue.data);

            if (rv == SECSuccess) {
                sftk_forceAttribute(key, CKA_VALUE, derived.data, derived.len);
                PORT_ZFree(derived.data, derived.len);
            } else
                crv = CKR_HOST_MEMORY;

            break;
        }

        case CKM_ECDH1_DERIVE:
        case CKM_ECDH1_COFACTOR_DERIVE: {
            SECItem ecScalar, ecPoint;
            SECItem tmp;
            PRBool withCofactor = PR_FALSE;
            unsigned char *secret;
            unsigned char *keyData = NULL;
            unsigned int secretlen, pubKeyLen;
            CK_ECDH1_DERIVE_PARAMS *mechParams;
            NSSLOWKEYPrivateKey *privKey;
            PLArenaPool *arena = NULL;

            /* Check mechanism parameters */
            mechParams = (CK_ECDH1_DERIVE_PARAMS *)pMechanism->pParameter;
            if ((pMechanism->ulParameterLen != sizeof(CK_ECDH1_DERIVE_PARAMS)) ||
                ((mechParams->kdf == CKD_NULL) &&
                 ((mechParams->ulSharedDataLen != 0) ||
                  (mechParams->pSharedData != NULL)))) {
                crv = CKR_MECHANISM_PARAM_INVALID;
                break;
            }

            privKey = sftk_GetPrivKey(sourceKey, CKK_EC, &crv);
            if (privKey == NULL) {
                break;
            }

            /* Now we are working with a non-NULL private key */
            SECITEM_CopyItem(NULL, &ecScalar, &privKey->u.ec.privateValue);

            ecPoint.data = mechParams->pPublicData;
            ecPoint.len = mechParams->ulPublicDataLen;

            pubKeyLen = EC_GetPointSize(&privKey->u.ec.ecParams);

            /* if the len is too large, might be an encoded point */
            if (ecPoint.len > pubKeyLen) {
                SECItem newPoint;

                arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
                if (arena == NULL) {
                    goto ec_loser;
                }

                rv = SEC_QuickDERDecodeItem(arena, &newPoint,
                                            SEC_ASN1_GET(SEC_OctetStringTemplate),
                                            &ecPoint);
                if (rv != SECSuccess) {
                    goto ec_loser;
                }
                ecPoint = newPoint;
            }

            if (mechanism == CKM_ECDH1_COFACTOR_DERIVE) {
                withCofactor = PR_TRUE;
            }

            rv = ECDH_Derive(&ecPoint, &privKey->u.ec.ecParams, &ecScalar,
                             withCofactor, &tmp);
            PORT_Free(ecScalar.data);
            ecScalar.data = NULL;
            if (privKey != sourceKey->objectInfo) {
                nsslowkey_DestroyPrivateKey(privKey);
                privKey = NULL;
            }
            if (arena) {
                PORT_FreeArena(arena, PR_FALSE);
                arena = NULL;
            }

            if (rv != SECSuccess) {
                crv = sftk_MapCryptError(PORT_GetError());
                break;
            }

            /*
             * apply the kdf function.
             */
            if (mechParams->kdf == CKD_NULL) {
                /*
             * tmp is the raw data created by ECDH_Derive,
             * secret and secretlen are the values we will
             * eventually pass as our generated key.
             */
                secret = tmp.data;
                secretlen = tmp.len;
            } else {
                secretlen = keySize;
                crv = sftk_ANSI_X9_63_kdf(&secret, keySize,
                                          &tmp, mechParams->pSharedData,
                                          mechParams->ulSharedDataLen, mechParams->kdf);
                PORT_ZFree(tmp.data, tmp.len);
                if (crv != CKR_OK) {
                    break;
                }
                tmp.data = secret;
                tmp.len = secretlen;
            }

            /*
             * if keySize is supplied, then we are generating a key of a specific
             * length. This is done by taking the least significant 'keySize'
             * bytes from the unsigned value calculated by ECDH. Note: this may
             * mean padding temp with extra leading zeros from what ECDH_Derive
             * already returned (which itself may contain leading zeros).
             */
            if (keySize) {
                if (secretlen < keySize) {
                    keyData = PORT_ZAlloc(keySize);
                    if (!keyData) {
                        PORT_ZFree(tmp.data, tmp.len);
                        crv = CKR_HOST_MEMORY;
                        break;
                    }
                    PORT_Memcpy(&keyData[keySize - secretlen], secret, secretlen);
                    secret = keyData;
                } else {
                    secret += (secretlen - keySize);
                }
                secretlen = keySize;
            }

            sftk_forceAttribute(key, CKA_VALUE, secret, secretlen);
            PORT_ZFree(tmp.data, tmp.len);
            if (keyData) {
                PORT_ZFree(keyData, keySize);
            }
            break;

        ec_loser:
            crv = CKR_ARGUMENTS_BAD;
            PORT_Free(ecScalar.data);
            if (privKey != sourceKey->objectInfo)
                nsslowkey_DestroyPrivateKey(privKey);
            if (arena) {
                PORT_FreeArena(arena, PR_FALSE);
            }
            break;
        }

        /* See RFC 5869 and CK_NSS_HKDFParams for documentation. */
        case CKM_NSS_HKDF_SHA1:
            hashType = HASH_AlgSHA1;
            goto hkdf;
        case CKM_NSS_HKDF_SHA256:
            hashType = HASH_AlgSHA256;
            goto hkdf;
        case CKM_NSS_HKDF_SHA384:
            hashType = HASH_AlgSHA384;
            goto hkdf;
        case CKM_NSS_HKDF_SHA512:
            hashType = HASH_AlgSHA512;
            goto hkdf;
        hkdf : {
            const CK_NSS_HKDFParams *params =
                (const CK_NSS_HKDFParams *)pMechanism->pParameter;
            const SECHashObject *rawHash;
            unsigned hashLen;
            CK_BYTE buf[HASH_LENGTH_MAX];
            CK_BYTE *prk; /* psuedo-random key */
            CK_ULONG prkLen;
            CK_BYTE *okm; /* output keying material */

            rawHash = HASH_GetRawHashObject(hashType);
            if (rawHash == NULL || rawHash->length > sizeof buf) {
                crv = CKR_FUNCTION_FAILED;
                break;
            }
            hashLen = rawHash->length;

            if (pMechanism->ulParameterLen != sizeof(CK_NSS_HKDFParams) ||
                !params || (!params->bExpand && !params->bExtract) ||
                (params->bExtract && params->ulSaltLen > 0 && !params->pSalt) ||
                (params->bExpand && params->ulInfoLen > 0 && !params->pInfo)) {
                crv = CKR_MECHANISM_PARAM_INVALID;
                break;
            }
            if (keySize == 0 || keySize > sizeof key_block ||
                (!params->bExpand && keySize > hashLen) ||
                (params->bExpand && keySize > 255 * hashLen)) {
                crv = CKR_TEMPLATE_INCONSISTENT;
                break;
            }
            crv = sftk_DeriveSensitiveCheck(sourceKey, key);
            if (crv != CKR_OK)
                break;

            /* HKDF-Extract(salt, base key value) */
            if (params->bExtract) {
                CK_BYTE *salt;
                CK_ULONG saltLen;
                HMACContext *hmac;
                unsigned int bufLen;

                salt = params->pSalt;
                saltLen = params->ulSaltLen;
                if (salt == NULL) {
                    saltLen = hashLen;
                    salt = buf;
                    memset(salt, 0, saltLen);
                }
                hmac = HMAC_Create(rawHash, salt, saltLen, isFIPS);
                if (!hmac) {
                    crv = CKR_HOST_MEMORY;
                    break;
                }
                HMAC_Begin(hmac);
                HMAC_Update(hmac, (const unsigned char *)att->attrib.pValue,
                            att->attrib.ulValueLen);
                HMAC_Finish(hmac, buf, &bufLen, sizeof(buf));
                HMAC_Destroy(hmac, PR_TRUE);
                PORT_Assert(bufLen == rawHash->length);
                prk = buf;
                prkLen = bufLen;
            } else {
                /* PRK = base key value */
                prk = (CK_BYTE *)att->attrib.pValue;
                prkLen = att->attrib.ulValueLen;
            }

            /* HKDF-Expand */
            if (!params->bExpand) {
                okm = prk;
            } else {
                /* T(1) = HMAC-Hash(prk, "" | info | 0x01)
                 * T(n) = HMAC-Hash(prk, T(n-1) | info | n
                 * key material = T(1) | ... | T(n)
                 */
                HMACContext *hmac;
                CK_BYTE i;
                unsigned iterations = PR_ROUNDUP(keySize, hashLen) / hashLen;
                hmac = HMAC_Create(rawHash, prk, prkLen, isFIPS);
                if (hmac == NULL) {
                    crv = CKR_HOST_MEMORY;
                    break;
                }
                for (i = 1; i <= iterations; ++i) {
                    unsigned len;
                    HMAC_Begin(hmac);
                    if (i > 1) {
                        HMAC_Update(hmac, key_block + ((i - 2) * hashLen), hashLen);
                    }
                    if (params->ulInfoLen != 0) {
                        HMAC_Update(hmac, params->pInfo, params->ulInfoLen);
                    }
                    HMAC_Update(hmac, &i, 1);
                    HMAC_Finish(hmac, key_block + ((i - 1) * hashLen), &len,
                                hashLen);
                    PORT_Assert(len == hashLen);
                }
                HMAC_Destroy(hmac, PR_TRUE);
                okm = key_block;
            }
            /* key material = prk */
            crv = sftk_forceAttribute(key, CKA_VALUE, okm, keySize);
            break;
        } /* end of CKM_NSS_HKDF_* */

        case CKM_NSS_JPAKE_ROUND2_SHA1:
            hashType = HASH_AlgSHA1;
            goto jpake2;
        case CKM_NSS_JPAKE_ROUND2_SHA256:
            hashType = HASH_AlgSHA256;
            goto jpake2;
        case CKM_NSS_JPAKE_ROUND2_SHA384:
            hashType = HASH_AlgSHA384;
            goto jpake2;
        case CKM_NSS_JPAKE_ROUND2_SHA512:
            hashType = HASH_AlgSHA512;
            goto jpake2;
        jpake2:
            if (pMechanism->pParameter == NULL ||
                pMechanism->ulParameterLen != sizeof(CK_NSS_JPAKERound2Params))
                crv = CKR_MECHANISM_PARAM_INVALID;
            if (crv == CKR_OK && sftk_isTrue(key, CKA_TOKEN))
                crv = CKR_TEMPLATE_INCONSISTENT;
            if (crv == CKR_OK)
                crv = sftk_DeriveSensitiveCheck(sourceKey, key);
            if (crv == CKR_OK)
                crv = jpake_Round2(hashType,
                                   (CK_NSS_JPAKERound2Params *)pMechanism->pParameter,
                                   sourceKey, key);
            break;

        case CKM_NSS_JPAKE_FINAL_SHA1:
            hashType = HASH_AlgSHA1;
            goto jpakeFinal;
        case CKM_NSS_JPAKE_FINAL_SHA256:
            hashType = HASH_AlgSHA256;
            goto jpakeFinal;
        case CKM_NSS_JPAKE_FINAL_SHA384:
            hashType = HASH_AlgSHA384;
            goto jpakeFinal;
        case CKM_NSS_JPAKE_FINAL_SHA512:
            hashType = HASH_AlgSHA512;
            goto jpakeFinal;
        jpakeFinal:
            if (pMechanism->pParameter == NULL ||
                pMechanism->ulParameterLen != sizeof(CK_NSS_JPAKEFinalParams))
                crv = CKR_MECHANISM_PARAM_INVALID;
            /* We purposely do not do the derive sensitivity check; we want to be
               able to derive non-sensitive keys while allowing the ROUND1 and
               ROUND2 keys to be sensitive (which they always are, since they are
               in the CKO_PRIVATE_KEY class). The caller must include CKA_SENSITIVE
               in the template in order for the resultant keyblock key to be
               sensitive.
             */
            if (crv == CKR_OK)
                crv = jpake_Final(hashType,
                                  (CK_NSS_JPAKEFinalParams *)pMechanism->pParameter,
                                  sourceKey, key);
            break;

        default:
            crv = CKR_MECHANISM_INVALID;
    }
    if (att) {
        sftk_FreeAttribute(att);
    }
    sftk_FreeObject(sourceKey);
    if (crv != CKR_OK) {
        if (key)
            sftk_FreeObject(key);
        return crv;
    }

    /* link the key object into the list */
    if (key) {
        SFTKSessionObject *sessKey = sftk_narrowToSessionObject(key);
        PORT_Assert(sessKey);
        /* get the session */
        sessKey->wasDerived = PR_TRUE;
        session = sftk_SessionFromHandle(hSession);
        if (session == NULL) {
            sftk_FreeObject(key);
            return CKR_HOST_MEMORY;
        }

        crv = sftk_handleObject(key, session);
        sftk_FreeSession(session);
        *phKey = key->handle;
        sftk_FreeObject(key);
    }
    return crv;
}

/* NSC_GetFunctionStatus obtains an updated status of a function running
 * in parallel with an application. */
CK_RV
NSC_GetFunctionStatus(CK_SESSION_HANDLE hSession)
{
    CHECK_FORK();

    return CKR_FUNCTION_NOT_PARALLEL;
}

/* NSC_CancelFunction cancels a function running in parallel */
CK_RV
NSC_CancelFunction(CK_SESSION_HANDLE hSession)
{
    CHECK_FORK();

    return CKR_FUNCTION_NOT_PARALLEL;
}

/* NSC_GetOperationState saves the state of the cryptographic
 * operation in a session.
 * NOTE: This code only works for digest functions for now. eventually need
 * to add full flatten/resurect to our state stuff so that all types of state
 * can be saved */
CK_RV
NSC_GetOperationState(CK_SESSION_HANDLE hSession,
                      CK_BYTE_PTR pOperationState, CK_ULONG_PTR pulOperationStateLen)
{
    SFTKSessionContext *context;
    SFTKSession *session;
    CK_RV crv;
    CK_ULONG pOSLen = *pulOperationStateLen;

    CHECK_FORK();

    /* make sure we're legal */
    crv = sftk_GetContext(hSession, &context, SFTK_HASH, PR_TRUE, &session);
    if (crv != CKR_OK)
        return crv;

    *pulOperationStateLen = context->cipherInfoLen + sizeof(CK_MECHANISM_TYPE) + sizeof(SFTKContextType);
    if (pOperationState == NULL) {
        sftk_FreeSession(session);
        return CKR_OK;
    } else {
        if (pOSLen < *pulOperationStateLen) {
            return CKR_BUFFER_TOO_SMALL;
        }
    }
    PORT_Memcpy(pOperationState, &context->type, sizeof(SFTKContextType));
    pOperationState += sizeof(SFTKContextType);
    PORT_Memcpy(pOperationState, &context->currentMech,
                sizeof(CK_MECHANISM_TYPE));
    pOperationState += sizeof(CK_MECHANISM_TYPE);
    PORT_Memcpy(pOperationState, context->cipherInfo, context->cipherInfoLen);
    sftk_FreeSession(session);
    return CKR_OK;
}

#define sftk_Decrement(stateSize, len) \
    stateSize = ((stateSize) > (CK_ULONG)(len)) ? ((stateSize) - (CK_ULONG)(len)) : 0;

/* NSC_SetOperationState restores the state of the cryptographic
 * operation in a session. This is coded like it can restore lots of
 * states, but it only works for truly flat cipher structures. */
CK_RV
NSC_SetOperationState(CK_SESSION_HANDLE hSession,
                      CK_BYTE_PTR pOperationState, CK_ULONG ulOperationStateLen,
                      CK_OBJECT_HANDLE hEncryptionKey, CK_OBJECT_HANDLE hAuthenticationKey)
{
    SFTKSessionContext *context;
    SFTKSession *session;
    SFTKContextType type;
    CK_MECHANISM mech;
    CK_RV crv = CKR_OK;

    CHECK_FORK();

    while (ulOperationStateLen != 0) {
        /* get what type of state we're dealing with... */
        PORT_Memcpy(&type, pOperationState, sizeof(SFTKContextType));

        /* fix up session contexts based on type */
        session = sftk_SessionFromHandle(hSession);
        if (session == NULL)
            return CKR_SESSION_HANDLE_INVALID;
        context = sftk_ReturnContextByType(session, type);
        sftk_SetContextByType(session, type, NULL);
        if (context) {
            sftk_FreeContext(context);
        }
        pOperationState += sizeof(SFTKContextType);
        sftk_Decrement(ulOperationStateLen, sizeof(SFTKContextType));

        /* get the mechanism structure */
        PORT_Memcpy(&mech.mechanism, pOperationState, sizeof(CK_MECHANISM_TYPE));
        pOperationState += sizeof(CK_MECHANISM_TYPE);
        sftk_Decrement(ulOperationStateLen, sizeof(CK_MECHANISM_TYPE));
        /* should be filled in... but not necessary for hash */
        mech.pParameter = NULL;
        mech.ulParameterLen = 0;
        switch (type) {
            case SFTK_HASH:
                crv = NSC_DigestInit(hSession, &mech);
                if (crv != CKR_OK)
                    break;
                crv = sftk_GetContext(hSession, &context, SFTK_HASH, PR_TRUE,
                                      NULL);
                if (crv != CKR_OK)
                    break;
                PORT_Memcpy(context->cipherInfo, pOperationState,
                            context->cipherInfoLen);
                pOperationState += context->cipherInfoLen;
                sftk_Decrement(ulOperationStateLen, context->cipherInfoLen);
                break;
            default:
                /* do sign/encrypt/decrypt later */
                crv = CKR_SAVED_STATE_INVALID;
        }
        sftk_FreeSession(session);
        if (crv != CKR_OK)
            break;
    }
    return crv;
}

/* Dual-function cryptographic operations */

/* NSC_DigestEncryptUpdate continues a multiple-part digesting and encryption
 * operation. */
CK_RV
NSC_DigestEncryptUpdate(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pPart,
                        CK_ULONG ulPartLen, CK_BYTE_PTR pEncryptedPart,
                        CK_ULONG_PTR pulEncryptedPartLen)
{
    CK_RV crv;

    CHECK_FORK();

    crv = NSC_EncryptUpdate(hSession, pPart, ulPartLen, pEncryptedPart,
                            pulEncryptedPartLen);
    if (crv != CKR_OK)
        return crv;
    crv = NSC_DigestUpdate(hSession, pPart, ulPartLen);

    return crv;
}

/* NSC_DecryptDigestUpdate continues a multiple-part decryption and
 * digesting operation. */
CK_RV
NSC_DecryptDigestUpdate(CK_SESSION_HANDLE hSession,
                        CK_BYTE_PTR pEncryptedPart, CK_ULONG ulEncryptedPartLen,
                        CK_BYTE_PTR pPart, CK_ULONG_PTR pulPartLen)
{
    CK_RV crv;

    CHECK_FORK();

    crv = NSC_DecryptUpdate(hSession, pEncryptedPart, ulEncryptedPartLen,
                            pPart, pulPartLen);
    if (crv != CKR_OK)
        return crv;
    crv = NSC_DigestUpdate(hSession, pPart, *pulPartLen);

    return crv;
}

/* NSC_SignEncryptUpdate continues a multiple-part signing and
 * encryption operation. */
CK_RV
NSC_SignEncryptUpdate(CK_SESSION_HANDLE hSession, CK_BYTE_PTR pPart,
                      CK_ULONG ulPartLen, CK_BYTE_PTR pEncryptedPart,
                      CK_ULONG_PTR pulEncryptedPartLen)
{
    CK_RV crv;

    CHECK_FORK();

    crv = NSC_EncryptUpdate(hSession, pPart, ulPartLen, pEncryptedPart,
                            pulEncryptedPartLen);
    if (crv != CKR_OK)
        return crv;
    crv = NSC_SignUpdate(hSession, pPart, ulPartLen);

    return crv;
}

/* NSC_DecryptVerifyUpdate continues a multiple-part decryption
 * and verify operation. */
CK_RV
NSC_DecryptVerifyUpdate(CK_SESSION_HANDLE hSession,
                        CK_BYTE_PTR pEncryptedData, CK_ULONG ulEncryptedDataLen,
                        CK_BYTE_PTR pData, CK_ULONG_PTR pulDataLen)
{
    CK_RV crv;

    CHECK_FORK();

    crv = NSC_DecryptUpdate(hSession, pEncryptedData, ulEncryptedDataLen,
                            pData, pulDataLen);
    if (crv != CKR_OK)
        return crv;
    crv = NSC_VerifyUpdate(hSession, pData, *pulDataLen);

    return crv;
}

/* NSC_DigestKey continues a multi-part message-digesting operation,
 * by digesting the value of a secret key as part of the data already digested.
 */
CK_RV
NSC_DigestKey(CK_SESSION_HANDLE hSession, CK_OBJECT_HANDLE hKey)
{
    SFTKSession *session = NULL;
    SFTKObject *key = NULL;
    SFTKAttribute *att;
    CK_RV crv;

    CHECK_FORK();

    session = sftk_SessionFromHandle(hSession);
    if (session == NULL)
        return CKR_SESSION_HANDLE_INVALID;

    key = sftk_ObjectFromHandle(hKey, session);
    sftk_FreeSession(session);
    if (key == NULL)
        return CKR_KEY_HANDLE_INVALID;

    /* PUT ANY DIGEST KEY RESTRICTION CHECKS HERE */

    /* make sure it's a valid  key for this operation */
    if (key->objclass != CKO_SECRET_KEY) {
        sftk_FreeObject(key);
        return CKR_KEY_TYPE_INCONSISTENT;
    }
    /* get the key value */
    att = sftk_FindAttribute(key, CKA_VALUE);
    sftk_FreeObject(key);
    if (!att) {
        return CKR_KEY_HANDLE_INVALID;
    }
    crv = NSC_DigestUpdate(hSession, (CK_BYTE_PTR)att->attrib.pValue,
                           att->attrib.ulValueLen);
    sftk_FreeAttribute(att);
    return crv;
}
