/* 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/. */
/*
 *  The following code handles the storage of PKCS 11 modules used by the
 * NSS. For the rest of NSS, only one kind of database handle exists:
 *
 *     SFTKDBHandle
 *
 * There is one SFTKDBHandle for the each key database and one for each cert
 * database. These databases are opened as associated pairs, one pair per
 * slot. SFTKDBHandles are reference counted objects.
 *
 * Each SFTKDBHandle points to a low level database handle (SDB). This handle
 * represents the underlying physical database. These objects are not
 * reference counted, an are 'owned' by their respective SFTKDBHandles.
 *
 *
 */
#include "sftkdb.h"
#include "sftkdbti.h"
#include "pkcs11t.h"
#include "pkcs11i.h"
#include "sdb.h"
#include "prprf.h"
#include "secasn1.h"
#include "pratom.h"
#include "blapi.h"
#include "secoid.h"
#include "lowpbe.h"
#include "secdert.h"
#include "prsystem.h"
#include "lgglue.h"
#include "secerr.h"
#include "softoken.h"

/******************************************************************
 *
 * Key DB password handling functions
 *
 * These functions manage the key db password (set, reset, initialize, use).
 *
 * The key is managed on 'this side' of the database. All private data is
 * encrypted before it is sent to the database itself. Besides PBE's, the
 * database management code can also mix in various fixed keys so the data
 * in the database is no longer considered 'plain text'.
 */

/* take string password and turn it into a key. The key is dependent
 * on a global salt entry acquired from the database. This salted
 * value will be based to a pkcs5 pbe function before it is used
 * in an actual encryption */
static SECStatus
sftkdb_passwordToKey(SFTKDBHandle *keydb, SECItem *salt,
                     const char *pw, SECItem *key)
{
    SHA1Context *cx = NULL;
    SECStatus rv = SECFailure;

    key->data = PORT_Alloc(SHA1_LENGTH);
    if (key->data == NULL) {
        goto loser;
    }
    key->len = SHA1_LENGTH;

    cx = SHA1_NewContext();
    if (cx == NULL) {
        goto loser;
    }
    SHA1_Begin(cx);
    if (salt && salt->data) {
        SHA1_Update(cx, salt->data, salt->len);
    }
    SHA1_Update(cx, (unsigned char *)pw, PORT_Strlen(pw));
    SHA1_End(cx, key->data, &key->len, key->len);
    rv = SECSuccess;

loser:
    if (cx) {
        SHA1_DestroyContext(cx, PR_TRUE);
    }
    if (rv != SECSuccess) {
        if (key->data != NULL) {
            PORT_ZFree(key->data, key->len);
        }
        key->data = NULL;
    }
    return rv;
}

/*
 * Cipher text stored in the database contains 3 elements:
 * 1) an identifier describing the encryption algorithm.
 * 2) an entry specific salt value.
 * 3) the encrypted value.
 *
 * The following data structure represents the encrypted data in a decoded
 * (but still encrypted) form.
 */
typedef struct sftkCipherValueStr sftkCipherValue;
struct sftkCipherValueStr {
    PLArenaPool *arena;
    SECOidTag alg;
    NSSPKCS5PBEParameter *param;
    SECItem salt;
    SECItem value;
};

#define SFTK_CIPHERTEXT_VERSION 3

struct SFTKDBEncryptedDataInfoStr {
    SECAlgorithmID algorithm;
    SECItem encryptedData;
};
typedef struct SFTKDBEncryptedDataInfoStr SFTKDBEncryptedDataInfo;

SEC_ASN1_MKSUB(SECOID_AlgorithmIDTemplate)

const SEC_ASN1Template sftkdb_EncryptedDataInfoTemplate[] = {
    { SEC_ASN1_SEQUENCE,
      0, NULL, sizeof(SFTKDBEncryptedDataInfo) },
    { SEC_ASN1_INLINE | SEC_ASN1_XTRN,
      offsetof(SFTKDBEncryptedDataInfo, algorithm),
      SEC_ASN1_SUB(SECOID_AlgorithmIDTemplate) },
    { SEC_ASN1_OCTET_STRING,
      offsetof(SFTKDBEncryptedDataInfo, encryptedData) },
    { 0 }
};

/*
 * This parses the cipherText into cipher value. NOTE: cipherValue will point
 * to data in cipherText, if cipherText is freed, cipherValue will be invalid.
 */
static SECStatus
sftkdb_decodeCipherText(SECItem *cipherText, sftkCipherValue *cipherValue)
{
    PLArenaPool *arena = NULL;
    SFTKDBEncryptedDataInfo edi;
    SECStatus rv;

    arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
    if (arena == NULL) {
        return SECFailure;
    }
    cipherValue->arena = NULL;
    cipherValue->param = NULL;

    rv = SEC_QuickDERDecodeItem(arena, &edi, sftkdb_EncryptedDataInfoTemplate,
                                cipherText);
    if (rv != SECSuccess) {
        goto loser;
    }
    cipherValue->alg = SECOID_GetAlgorithmTag(&edi.algorithm);
    cipherValue->param = nsspkcs5_AlgidToParam(&edi.algorithm);
    if (cipherValue->param == NULL) {
        goto loser;
    }
    cipherValue->value = edi.encryptedData;
    cipherValue->arena = arena;

    return SECSuccess;
loser:
    if (cipherValue->param) {
        nsspkcs5_DestroyPBEParameter(cipherValue->param);
        cipherValue->param = NULL;
    }
    if (arena) {
        PORT_FreeArena(arena, PR_FALSE);
    }
    return SECFailure;
}

/*
 * unlike decode, Encode actually allocates a SECItem the caller must free
 * The caller can pass an optional arena to to indicate where to place
 * the resultant cipherText.
 */
static SECStatus
sftkdb_encodeCipherText(PLArenaPool *arena, sftkCipherValue *cipherValue,
                        SECItem **cipherText)
{
    SFTKDBEncryptedDataInfo edi;
    SECAlgorithmID *algid;
    SECStatus rv;
    PLArenaPool *localArena = NULL;

    localArena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
    if (localArena == NULL) {
        return SECFailure;
    }

    algid = nsspkcs5_CreateAlgorithmID(localArena, cipherValue->alg,
                                       cipherValue->param);
    if (algid == NULL) {
        rv = SECFailure;
        goto loser;
    }
    rv = SECOID_CopyAlgorithmID(localArena, &edi.algorithm, algid);
    SECOID_DestroyAlgorithmID(algid, PR_TRUE);
    if (rv != SECSuccess) {
        goto loser;
    }
    edi.encryptedData = cipherValue->value;

    *cipherText = SEC_ASN1EncodeItem(arena, NULL, &edi,
                                     sftkdb_EncryptedDataInfoTemplate);
    if (*cipherText == NULL) {
        rv = SECFailure;
    }

loser:
    if (localArena) {
        PORT_FreeArena(localArena, PR_FALSE);
    }

    return rv;
}

/*
 * Use our key to decode a cipherText block from the database.
 *
 * plain text is allocated by nsspkcs5_CipherData and must be freed
 * with SECITEM_FreeItem by the caller.
 */
SECStatus
sftkdb_DecryptAttribute(SECItem *passKey, SECItem *cipherText, SECItem **plain)
{
    SECStatus rv;
    sftkCipherValue cipherValue;

    /* First get the cipher type */
    rv = sftkdb_decodeCipherText(cipherText, &cipherValue);
    if (rv != SECSuccess) {
        goto loser;
    }

    *plain = nsspkcs5_CipherData(cipherValue.param, passKey, &cipherValue.value,
                                 PR_FALSE, NULL);
    if (*plain == NULL) {
        rv = SECFailure;
        goto loser;
    }

loser:
    if (cipherValue.param) {
        nsspkcs5_DestroyPBEParameter(cipherValue.param);
    }
    if (cipherValue.arena) {
        PORT_FreeArena(cipherValue.arena, PR_FALSE);
    }
    return rv;
}

/*
 * encrypt a block. This function returned the encrypted ciphertext which
 * the caller must free. If the caller provides an arena, cipherText will
 * be allocated out of that arena. This also generated the per entry
 * salt automatically.
 */
SECStatus
sftkdb_EncryptAttribute(PLArenaPool *arena, SECItem *passKey,
                        SECItem *plainText, SECItem **cipherText)
{
    SECStatus rv;
    sftkCipherValue cipherValue;
    SECItem *cipher = NULL;
    NSSPKCS5PBEParameter *param = NULL;
    unsigned char saltData[HASH_LENGTH_MAX];

    cipherValue.alg = SEC_OID_PKCS12_PBE_WITH_SHA1_AND_TRIPLE_DES_CBC;
    cipherValue.salt.len = SHA1_LENGTH;
    cipherValue.salt.data = saltData;
    RNG_GenerateGlobalRandomBytes(saltData, cipherValue.salt.len);

    param = nsspkcs5_NewParam(cipherValue.alg, HASH_AlgSHA1, &cipherValue.salt,
                              1);
    if (param == NULL) {
        rv = SECFailure;
        goto loser;
    }
    cipher = nsspkcs5_CipherData(param, passKey, plainText, PR_TRUE, NULL);
    if (cipher == NULL) {
        rv = SECFailure;
        goto loser;
    }
    cipherValue.value = *cipher;
    cipherValue.param = param;

    rv = sftkdb_encodeCipherText(arena, &cipherValue, cipherText);
    if (rv != SECSuccess) {
        goto loser;
    }

loser:
    if (cipher) {
        SECITEM_FreeItem(cipher, PR_TRUE);
    }
    if (param) {
        nsspkcs5_DestroyPBEParameter(param);
    }
    return rv;
}

/*
 * use the password and the pbe parameters to generate an HMAC for the
 * given plain text data. This is used by sftkdb_VerifyAttribute and
 * sftkdb_SignAttribute. Signature is returned in signData. The caller
 * must preallocate the space in the secitem.
 */
static SECStatus
sftkdb_pbehash(SECOidTag sigOid, SECItem *passKey,
               NSSPKCS5PBEParameter *param,
               CK_OBJECT_HANDLE objectID, CK_ATTRIBUTE_TYPE attrType,
               SECItem *plainText, SECItem *signData)
{
    SECStatus rv = SECFailure;
    SECItem *key = NULL;
    HMACContext *hashCx = NULL;
    HASH_HashType hashType = HASH_AlgNULL;
    const SECHashObject *hashObj;
    unsigned char addressData[SDB_ULONG_SIZE];

    hashType = HASH_FromHMACOid(param->encAlg);
    if (hashType == HASH_AlgNULL) {
        PORT_SetError(SEC_ERROR_INVALID_ALGORITHM);
        return SECFailure;
    }

    hashObj = HASH_GetRawHashObject(hashType);
    if (hashObj == NULL) {
        goto loser;
    }

    key = nsspkcs5_ComputeKeyAndIV(param, passKey, NULL, PR_FALSE);
    if (!key) {
        goto loser;
    }

    hashCx = HMAC_Create(hashObj, key->data, key->len, PR_TRUE);
    if (!hashCx) {
        goto loser;
    }
    HMAC_Begin(hashCx);
    /* Tie this value to a particular object. This is most important for
     * the trust attributes, where and attacker could copy a value for
     * 'validCA' from another cert in the database */
    sftk_ULong2SDBULong(addressData, objectID);
    HMAC_Update(hashCx, addressData, SDB_ULONG_SIZE);
    sftk_ULong2SDBULong(addressData, attrType);
    HMAC_Update(hashCx, addressData, SDB_ULONG_SIZE);

    HMAC_Update(hashCx, plainText->data, plainText->len);
    rv = HMAC_Finish(hashCx, signData->data, &signData->len, signData->len);

loser:
    if (hashCx) {
        HMAC_Destroy(hashCx, PR_TRUE);
    }
    if (key) {
        SECITEM_FreeItem(key, PR_TRUE);
    }
    return rv;
}

/*
 * Use our key to verify a signText block from the database matches
 * the plainText from the database. The signText is a PKCS 5 v2 pbe.
 * plainText is the plainText of the attribute.
 */
SECStatus
sftkdb_VerifyAttribute(SECItem *passKey, CK_OBJECT_HANDLE objectID,
                       CK_ATTRIBUTE_TYPE attrType,
                       SECItem *plainText, SECItem *signText)
{
    SECStatus rv;
    sftkCipherValue signValue;
    SECItem signature;
    unsigned char signData[HASH_LENGTH_MAX];

    /* First get the cipher type */
    rv = sftkdb_decodeCipherText(signText, &signValue);
    if (rv != SECSuccess) {
        goto loser;
    }
    signature.data = signData;
    signature.len = sizeof(signData);

    rv = sftkdb_pbehash(signValue.alg, passKey, signValue.param,
                        objectID, attrType, plainText, &signature);
    if (rv != SECSuccess) {
        goto loser;
    }
    if (SECITEM_CompareItem(&signValue.value, &signature) != 0) {
        PORT_SetError(SEC_ERROR_BAD_SIGNATURE);
        rv = SECFailure;
    }

loser:
    if (signValue.param) {
        nsspkcs5_DestroyPBEParameter(signValue.param);
    }
    if (signValue.arena) {
        PORT_FreeArena(signValue.arena, PR_FALSE);
    }
    return rv;
}

/*
 * Use our key to create a signText block the plain text of an
 * attribute. The signText is a PKCS 5 v2 pbe.
 */
SECStatus
sftkdb_SignAttribute(PLArenaPool *arena, SECItem *passKey,
                     CK_OBJECT_HANDLE objectID, CK_ATTRIBUTE_TYPE attrType,
                     SECItem *plainText, SECItem **signature)
{
    SECStatus rv;
    sftkCipherValue signValue;
    NSSPKCS5PBEParameter *param = NULL;
    unsigned char saltData[HASH_LENGTH_MAX];
    unsigned char signData[HASH_LENGTH_MAX];
    SECOidTag hmacAlg = SEC_OID_HMAC_SHA256; /* hash for authentication */
    SECOidTag prfAlg = SEC_OID_HMAC_SHA256;  /* hash for pb key generation */
    HASH_HashType prfType;
    unsigned int hmacLength;
    unsigned int prfLength;

    /* this code allows us to fetch the lengths and hashes on the fly
     * by simply changing the OID above */
    prfType = HASH_FromHMACOid(prfAlg);
    PORT_Assert(prfType != HASH_AlgNULL);
    prfLength = HASH_GetRawHashObject(prfType)->length;
    PORT_Assert(prfLength <= HASH_LENGTH_MAX);

    hmacLength = HASH_GetRawHashObject(HASH_FromHMACOid(hmacAlg))->length;
    PORT_Assert(hmacLength <= HASH_LENGTH_MAX);

    /* initialize our CipherValue structure */
    signValue.alg = SEC_OID_PKCS5_PBMAC1;
    signValue.salt.len = prfLength;
    signValue.salt.data = saltData;
    signValue.value.data = signData;
    signValue.value.len = hmacLength;
    RNG_GenerateGlobalRandomBytes(saltData, prfLength);

    /* initialize our pkcs5 parameter */
    param = nsspkcs5_NewParam(signValue.alg, HASH_AlgSHA1, &signValue.salt, 1);
    if (param == NULL) {
        rv = SECFailure;
        goto loser;
    }
    param->keyID = pbeBitGenIntegrityKey;
    /* set the PKCS 5 v2 parameters, not extractable from the
     * data passed into nsspkcs5_NewParam */
    param->encAlg = hmacAlg;
    param->hashType = prfType;
    param->keyLen = hmacLength;
    rv = SECOID_SetAlgorithmID(param->poolp, &param->prfAlg, prfAlg, NULL);
    if (rv != SECSuccess) {
        goto loser;
    }

    /* calculate the mac */
    rv = sftkdb_pbehash(signValue.alg, passKey, param, objectID, attrType,
                        plainText, &signValue.value);
    if (rv != SECSuccess) {
        goto loser;
    }
    signValue.param = param;

    /* write it out */
    rv = sftkdb_encodeCipherText(arena, &signValue, signature);
    if (rv != SECSuccess) {
        goto loser;
    }

loser:
    if (param) {
        nsspkcs5_DestroyPBEParameter(param);
    }
    return rv;
}

/*
 * safely swith the passed in key for the one caches in the keydb handle
 *
 * A key attached to the handle tells us the the token is logged in.
 * We can used the key attached to the handle in sftkdb_EncryptAttribute
 *  and sftkdb_DecryptAttribute calls.
 */
static void
sftkdb_switchKeys(SFTKDBHandle *keydb, SECItem *passKey)
{
    unsigned char *data;
    int len;

    if (keydb->passwordLock == NULL) {
        PORT_Assert(keydb->type != SFTK_KEYDB_TYPE);
        return;
    }

    /* an atomic pointer set would be nice */
    SKIP_AFTER_FORK(PZ_Lock(keydb->passwordLock));
    data = keydb->passwordKey.data;
    len = keydb->passwordKey.len;
    keydb->passwordKey.data = passKey->data;
    keydb->passwordKey.len = passKey->len;
    passKey->data = data;
    passKey->len = len;
    SKIP_AFTER_FORK(PZ_Unlock(keydb->passwordLock));
}

/*
 * returns true if we are in a middle of a merge style update.
 */
PRBool
sftkdb_InUpdateMerge(SFTKDBHandle *keydb)
{
    return keydb->updateID ? PR_TRUE : PR_FALSE;
}

/*
 * returns true if we are looking for the password for the user's old source
 * database as part of a merge style update.
 */
PRBool
sftkdb_NeedUpdateDBPassword(SFTKDBHandle *keydb)
{
    if (!sftkdb_InUpdateMerge(keydb)) {
        return PR_FALSE;
    }
    if (keydb->updateDBIsInit && !keydb->updatePasswordKey) {
        return PR_TRUE;
    }
    return PR_FALSE;
}

/*
 * fetch an update password key from a handle.
 */
SECItem *
sftkdb_GetUpdatePasswordKey(SFTKDBHandle *handle)
{
    SECItem *key = NULL;

    /* if we're a cert db, fetch it from our peer key db */
    if (handle->type == SFTK_CERTDB_TYPE) {
        handle = handle->peerDB;
    }

    /* don't have one */
    if (!handle) {
        return NULL;
    }

    PZ_Lock(handle->passwordLock);
    if (handle->updatePasswordKey) {
        key = SECITEM_DupItem(handle->updatePasswordKey);
    }
    PZ_Unlock(handle->passwordLock);

    return key;
}

/*
 * free the update password key from a handle.
 */
void
sftkdb_FreeUpdatePasswordKey(SFTKDBHandle *handle)
{
    SECItem *key = NULL;

    /* don't have one */
    if (!handle) {
        return;
    }

    /* if we're a cert db, we don't have one */
    if (handle->type == SFTK_CERTDB_TYPE) {
        return;
    }

    PZ_Lock(handle->passwordLock);
    if (handle->updatePasswordKey) {
        key = handle->updatePasswordKey;
        handle->updatePasswordKey = NULL;
    }
    PZ_Unlock(handle->passwordLock);

    if (key) {
        SECITEM_ZfreeItem(key, PR_TRUE);
    }

    return;
}

/*
 * what password db we use depends heavily on the update state machine
 *
 *  1) no update db, return the normal database.
 *  2) update db and no merge return the update db.
 *  3) update db and in merge:
 *      return the update db if we need the update db's password,
 *      otherwise return our normal datbase.
 */
static SDB *
sftk_getPWSDB(SFTKDBHandle *keydb)
{
    if (!keydb->update) {
        return keydb->db;
    }
    if (!sftkdb_InUpdateMerge(keydb)) {
        return keydb->update;
    }
    if (sftkdb_NeedUpdateDBPassword(keydb)) {
        return keydb->update;
    }
    return keydb->db;
}

/*
 * return success if we have a valid password entry.
 * This is will show up outside of PKCS #11 as CKF_USER_PIN_INIT
 * in the token flags.
 */
SECStatus
sftkdb_HasPasswordSet(SFTKDBHandle *keydb)
{
    SECItem salt, value;
    unsigned char saltData[SDB_MAX_META_DATA_LEN];
    unsigned char valueData[SDB_MAX_META_DATA_LEN];
    CK_RV crv;
    SDB *db;

    if (keydb == NULL) {
        return SECFailure;
    }

    db = sftk_getPWSDB(keydb);
    if (db == NULL) {
        return SECFailure;
    }

    salt.data = saltData;
    salt.len = sizeof(saltData);
    value.data = valueData;
    value.len = sizeof(valueData);
    crv = (*db->sdb_GetMetaData)(db, "password", &salt, &value);

    /* If no password is set, we can update right away */
    if (((keydb->db->sdb_flags & SDB_RDONLY) == 0) && keydb->update && crv != CKR_OK) {
        /* update the peer certdb if it exists */
        if (keydb->peerDB) {
            sftkdb_Update(keydb->peerDB, NULL);
        }
        sftkdb_Update(keydb, NULL);
    }
    return (crv == CKR_OK) ? SECSuccess : SECFailure;
}

#define SFTK_PW_CHECK_STRING "password-check"
#define SFTK_PW_CHECK_LEN 14

/*
 * check if the supplied password is valid
 */
SECStatus
sftkdb_CheckPassword(SFTKDBHandle *keydb, const char *pw, PRBool *tokenRemoved)
{
    SECStatus rv;
    SECItem salt, value;
    unsigned char saltData[SDB_MAX_META_DATA_LEN];
    unsigned char valueData[SDB_MAX_META_DATA_LEN];
    SECItem key;
    SECItem *result = NULL;
    SDB *db;
    CK_RV crv;

    if (keydb == NULL) {
        return SECFailure;
    }

    db = sftk_getPWSDB(keydb);
    if (db == NULL) {
        return SECFailure;
    }

    key.data = NULL;
    key.len = 0;

    if (pw == NULL)
        pw = "";

    /* get the entry from the database */
    salt.data = saltData;
    salt.len = sizeof(saltData);
    value.data = valueData;
    value.len = sizeof(valueData);
    crv = (*db->sdb_GetMetaData)(db, "password", &salt, &value);
    if (crv != CKR_OK) {
        rv = SECFailure;
        goto done;
    }

    /* get our intermediate key based on the entry salt value */
    rv = sftkdb_passwordToKey(keydb, &salt, pw, &key);
    if (rv != SECSuccess) {
        goto done;
    }

    /* decrypt the entry value */
    rv = sftkdb_DecryptAttribute(&key, &value, &result);
    if (rv != SECSuccess) {
        goto done;
    }

    /* if it's what we expect, update our key in the database handle and
     * return Success */
    if ((result->len == SFTK_PW_CHECK_LEN) &&
        PORT_Memcmp(result->data, SFTK_PW_CHECK_STRING, SFTK_PW_CHECK_LEN) == 0) {
        /*
         * We have a password, now lets handle any potential update cases..
         *
         * First, the normal case: no update. In this case we only need the
         *  the password for our only DB, which we now have, we switch
         *  the keys and fall through.
         * Second regular (non-merge) update: The target DB does not yet have
         *  a password initialized, we now have the password for the source DB,
         *  so we can switch the keys and simply update the target database.
         * Merge update case: This one is trickier.
         *   1) If we need the source DB password, then we just got it here.
         *       We need to save that password,
         *       then we need to check to see if we need or have the target
         *         database password.
         *       If we have it (it's the same as the source), or don't need
         *         it (it's not set or is ""), we can start the update now.
         *       If we don't have it, we need the application to get it from
         *         the user. Clear our sessions out to simulate a token
         *         removal. C_GetTokenInfo will change the token description
         *         and the token will still appear to be logged out.
         *   2) If we already have the source DB  password, this password is
         *         for the target database. We can now move forward with the
         *         update, as we now have both required passwords.
         *
         */
        PZ_Lock(keydb->passwordLock);
        if (sftkdb_NeedUpdateDBPassword(keydb)) {
            /* Squirrel this special key away.
             * This has the side effect of turning sftkdb_NeedLegacyPW off,
             * as well as changing which database is returned from
             * SFTK_GET_PW_DB (thus effecting both sftkdb_CheckPassword()
             * and sftkdb_HasPasswordSet()) */
            keydb->updatePasswordKey = SECITEM_DupItem(&key);
            PZ_Unlock(keydb->passwordLock);
            if (keydb->updatePasswordKey == NULL) {
                /* PORT_Error set by SECITEM_DupItem */
                rv = SECFailure;
                goto done;
            }

            /* Simulate a token removal -- we need to do this any
             * any case at this point so the token name is correct. */
            *tokenRemoved = PR_TRUE;

            /*
             * OK, we got the update DB password, see if we need a password
             * for the target...
             */
            if (sftkdb_HasPasswordSet(keydb) == SECSuccess) {
                /* We have a password, do we know what the password is?
                 *  check 1) for the password the user supplied for the
                 *           update DB,
                 *    and 2) for the null password.
                 *
                 * RECURSION NOTE: we are calling ourselves here. This means
                 *  any updates, switchKeys, etc will have been completed
                 *  if these functions return successfully, in those cases
                 *  just exit returning Success. We don't recurse infinitely
                 *  because we are making this call from a NeedUpdateDBPassword
                 *  block and we've already set that update password at this
                 *  point.  */
                rv = sftkdb_CheckPassword(keydb, pw, tokenRemoved);
                if (rv == SECSuccess) {
                    /* source and target databases have the same password, we
                     * are good to go */
                    goto done;
                }
                sftkdb_CheckPassword(keydb, "", tokenRemoved);

                /*
                 * Important 'NULL' code here. At this point either we
                 * succeeded in logging in with "" or we didn't.
                 *
                 *  If we did succeed at login, our machine state will be set
                 * to logged in appropriately. The application will find that
                 * it's logged in as soon as it opens a new session. We have
                 * also completed the update. Life is good.
                 *
                 *  If we did not succeed, well the user still successfully
                 * logged into the update database, since we faked the token
                 * removal it's just like the user logged into his smart card
                 * then removed it. the actual login work, so we report that
                 * success back to the user, but we won't actually be
                 * logged in. The application will find this out when it
                 * checks it's login state, thus triggering another password
                 * prompt so we can get the real target DB password.
                 *
                 * summary, we exit from here with SECSuccess no matter what.
                 */
                rv = SECSuccess;
                goto done;
            } else {
                /* there is no password, just fall through to update.
                 * update will write the source DB's password record
                 * into the target DB just like it would in a non-merge
                 * update case. */
            }
        } else {
            PZ_Unlock(keydb->passwordLock);
        }
        /* load the keys, so the keydb can parse it's key set */
        sftkdb_switchKeys(keydb, &key);

        /* we need to update, do it now */
        if (((keydb->db->sdb_flags & SDB_RDONLY) == 0) && keydb->update) {
            /* update the peer certdb if it exists */
            if (keydb->peerDB) {
                sftkdb_Update(keydb->peerDB, &key);
            }
            sftkdb_Update(keydb, &key);
        }
    } else {
        rv = SECFailure;
        /*PORT_SetError( bad password); */
    }

done:
    if (key.data) {
        PORT_ZFree(key.data, key.len);
    }
    if (result) {
        SECITEM_FreeItem(result, PR_TRUE);
    }
    return rv;
}

/*
 * return Success if the there is a cached password key.
 */
SECStatus
sftkdb_PWCached(SFTKDBHandle *keydb)
{
    return keydb->passwordKey.data ? SECSuccess : SECFailure;
}

static CK_RV
sftk_updateMacs(PLArenaPool *arena, SFTKDBHandle *handle,
                CK_OBJECT_HANDLE id, SECItem *newKey)
{
    CK_ATTRIBUTE authAttrs[] = {
        { CKA_MODULUS, NULL, 0 },
        { CKA_PUBLIC_EXPONENT, NULL, 0 },
        { CKA_CERT_SHA1_HASH, NULL, 0 },
        { CKA_CERT_MD5_HASH, NULL, 0 },
        { CKA_TRUST_SERVER_AUTH, NULL, 0 },
        { CKA_TRUST_CLIENT_AUTH, NULL, 0 },
        { CKA_TRUST_EMAIL_PROTECTION, NULL, 0 },
        { CKA_TRUST_CODE_SIGNING, NULL, 0 },
        { CKA_TRUST_STEP_UP_APPROVED, NULL, 0 },
        { CKA_NSS_OVERRIDE_EXTENSIONS, NULL, 0 },
    };
    CK_ULONG authAttrCount = sizeof(authAttrs) / sizeof(CK_ATTRIBUTE);
    unsigned int i, count;
    SFTKDBHandle *keyHandle = handle;
    SDB *keyTarget = NULL;

    id &= SFTK_OBJ_ID_MASK;

    if (handle->type != SFTK_KEYDB_TYPE) {
        keyHandle = handle->peerDB;
    }

    if (keyHandle == NULL) {
        return CKR_OK;
    }

    /* old DB's don't have meta data, finished with MACs */
    keyTarget = SFTK_GET_SDB(keyHandle);
    if ((keyTarget->sdb_flags & SDB_HAS_META) == 0) {
        return CKR_OK;
    }

    /*
     * STEP 1: find the MACed attributes of this object
     */
    (void)sftkdb_GetAttributeValue(handle, id, authAttrs, authAttrCount);
    count = 0;
    /* allocate space for the attributes */
    for (i = 0; i < authAttrCount; i++) {
        if ((authAttrs[i].ulValueLen == -1) || (authAttrs[i].ulValueLen == 0)) {
            continue;
        }
        count++;
        authAttrs[i].pValue = PORT_ArenaAlloc(arena, authAttrs[i].ulValueLen);
        if (authAttrs[i].pValue == NULL) {
            break;
        }
    }

    /* if count was zero, none were found, finished with MACs */
    if (count == 0) {
        return CKR_OK;
    }

    (void)sftkdb_GetAttributeValue(handle, id, authAttrs, authAttrCount);
    /* ignore error code, we expect some possible errors */

    /* GetAttributeValue just verified the old macs, safe to write
     * them out then... */
    for (i = 0; i < authAttrCount; i++) {
        SECItem *signText;
        SECItem plainText;
        SECStatus rv;

        if ((authAttrs[i].ulValueLen == -1) || (authAttrs[i].ulValueLen == 0)) {
            continue;
        }

        if (authAttrs[i].ulValueLen == sizeof(CK_ULONG) &&
            sftkdb_isULONGAttribute(authAttrs[i].type)) {
            CK_ULONG value = *(CK_ULONG *)authAttrs[i].pValue;
            sftk_ULong2SDBULong(authAttrs[i].pValue, value);
            authAttrs[i].ulValueLen = SDB_ULONG_SIZE;
        }

        plainText.data = authAttrs[i].pValue;
        plainText.len = authAttrs[i].ulValueLen;
        rv = sftkdb_SignAttribute(arena, newKey, id,
                                  authAttrs[i].type, &plainText, &signText);
        if (rv != SECSuccess) {
            return CKR_GENERAL_ERROR;
        }
        rv = sftkdb_PutAttributeSignature(handle, keyTarget, id,
                                          authAttrs[i].type, signText);
        if (rv != SECSuccess) {
            return CKR_GENERAL_ERROR;
        }
    }

    return CKR_OK;
}

static CK_RV
sftk_updateEncrypted(PLArenaPool *arena, SFTKDBHandle *keydb,
                     CK_OBJECT_HANDLE id, SECItem *newKey)
{
    CK_RV crv = CKR_OK;
    CK_RV crv2;
    CK_ATTRIBUTE *first, *last;
    CK_ATTRIBUTE privAttrs[] = {
        { CKA_VALUE, NULL, 0 },
        { CKA_PRIVATE_EXPONENT, NULL, 0 },
        { CKA_PRIME_1, NULL, 0 },
        { CKA_PRIME_2, NULL, 0 },
        { CKA_EXPONENT_1, NULL, 0 },
        { CKA_EXPONENT_2, NULL, 0 },
        { CKA_COEFFICIENT, NULL, 0 }
    };
    CK_ULONG privAttrCount = sizeof(privAttrs) / sizeof(CK_ATTRIBUTE);
    unsigned int i, count;

    /*
     * STEP 1. Read the old attributes in the clear.
     */

    /* Get the attribute sizes.
     *  ignore the error code, we will have unknown attributes here */
    crv2 = sftkdb_GetAttributeValue(keydb, id, privAttrs, privAttrCount);

    /*
     * find the valid block of attributes and fill allocate space for
     * their data */
    first = last = NULL;
    for (i = 0; i < privAttrCount; i++) {
        /* find the block of attributes that are appropriate for this
          * objects. There should only be once contiguous block, if not
          * there's an error.
          *
          * find the first and last good entry.
          */
        if ((privAttrs[i].ulValueLen == -1) || (privAttrs[i].ulValueLen == 0)) {
            if (!first)
                continue;
            if (!last) {
                /* previous entry was last good entry */
                last = &privAttrs[i - 1];
            }
            continue;
        }
        if (!first) {
            first = &privAttrs[i];
        }
        if (last) {
            /* OOPS, we've found another good entry beyond the end of the
             * last good entry, we need to fail here. */
            crv = CKR_GENERAL_ERROR;
            break;
        }
        privAttrs[i].pValue = PORT_ArenaAlloc(arena, privAttrs[i].ulValueLen);
        if (privAttrs[i].pValue == NULL) {
            crv = CKR_HOST_MEMORY;
            break;
        }
    }
    if (first == NULL) {
        /* no valid entries found, return error based on crv2 */
        return crv2;
    }
    if (last == NULL) {
        last = &privAttrs[privAttrCount - 1];
    }
    if (crv != CKR_OK) {
        return crv;
    }
    /* read the attributes */
    count = (last - first) + 1;
    crv = sftkdb_GetAttributeValue(keydb, id, first, count);
    if (crv != CKR_OK) {
        return crv;
    }

    /*
     * STEP 2: read the encrypt the attributes with the new key.
     */
    for (i = 0; i < count; i++) {
        SECItem plainText;
        SECItem *result;
        SECStatus rv;

        plainText.data = first[i].pValue;
        plainText.len = first[i].ulValueLen;
        rv = sftkdb_EncryptAttribute(arena, newKey, &plainText, &result);
        if (rv != SECSuccess) {
            return CKR_GENERAL_ERROR;
        }
        first[i].pValue = result->data;
        first[i].ulValueLen = result->len;
        /* clear our sensitive data out */
        PORT_Memset(plainText.data, 0, plainText.len);
    }

    /*
     * STEP 3: write the newly encrypted attributes out directly
     */
    id &= SFTK_OBJ_ID_MASK;
    keydb->newKey = newKey;
    crv = (*keydb->db->sdb_SetAttributeValue)(keydb->db, id, first, count);
    keydb->newKey = NULL;

    return crv;
}

static CK_RV
sftk_convertAttributes(SFTKDBHandle *handle,
                       CK_OBJECT_HANDLE id, SECItem *newKey)
{
    CK_RV crv = CKR_OK;
    PLArenaPool *arena = NULL;

    /* get a new arena to simplify cleanup */
    arena = PORT_NewArena(1024);
    if (!arena) {
        return CKR_HOST_MEMORY;
    }

    /*
     * first handle the MACS
     */
    crv = sftk_updateMacs(arena, handle, id, newKey);
    if (crv != CKR_OK) {
        goto loser;
    }

    if (handle->type == SFTK_KEYDB_TYPE) {
        crv = sftk_updateEncrypted(arena, handle, id, newKey);
        if (crv != CKR_OK) {
            goto loser;
        }
    }

    /* free up our mess */
    /* NOTE: at this point we know we've cleared out any unencrypted data */
    PORT_FreeArena(arena, PR_FALSE);
    return CKR_OK;

loser:
    /* there may be unencrypted data, clear it out down */
    PORT_FreeArena(arena, PR_TRUE);
    return crv;
}

/*
 * must be called with the old key active.
 */
CK_RV
sftkdb_convertObjects(SFTKDBHandle *handle, CK_ATTRIBUTE *template,
                      CK_ULONG count, SECItem *newKey)
{
    SDBFind *find = NULL;
    CK_ULONG idCount = SFTK_MAX_IDS;
    CK_OBJECT_HANDLE ids[SFTK_MAX_IDS];
    CK_RV crv, crv2;
    unsigned int i;

    crv = sftkdb_FindObjectsInit(handle, template, count, &find);

    if (crv != CKR_OK) {
        return crv;
    }
    while ((crv == CKR_OK) && (idCount == SFTK_MAX_IDS)) {
        crv = sftkdb_FindObjects(handle, find, ids, SFTK_MAX_IDS, &idCount);
        for (i = 0; (crv == CKR_OK) && (i < idCount); i++) {
            crv = sftk_convertAttributes(handle, ids[i], newKey);
        }
    }
    crv2 = sftkdb_FindObjectsFinal(handle, find);
    if (crv == CKR_OK)
        crv = crv2;

    return crv;
}

/*
 * change the database password.
 */
SECStatus
sftkdb_ChangePassword(SFTKDBHandle *keydb,
                      char *oldPin, char *newPin, PRBool *tokenRemoved)
{
    SECStatus rv = SECSuccess;
    SECItem plainText;
    SECItem newKey;
    SECItem *result = NULL;
    SECItem salt, value;
    SFTKDBHandle *certdb;
    unsigned char saltData[SDB_MAX_META_DATA_LEN];
    unsigned char valueData[SDB_MAX_META_DATA_LEN];
    CK_RV crv;
    SDB *db;

    if (keydb == NULL) {
        return SECFailure;
    }

    db = SFTK_GET_SDB(keydb);
    if (db == NULL) {
        return SECFailure;
    }

    newKey.data = NULL;

    /* make sure we have a valid old pin */
    crv = (*keydb->db->sdb_Begin)(keydb->db);
    if (crv != CKR_OK) {
        rv = SECFailure;
        goto loser;
    }
    salt.data = saltData;
    salt.len = sizeof(saltData);
    value.data = valueData;
    value.len = sizeof(valueData);
    crv = (*db->sdb_GetMetaData)(db, "password", &salt, &value);
    if (crv == CKR_OK) {
        rv = sftkdb_CheckPassword(keydb, oldPin, tokenRemoved);
        if (rv == SECFailure) {
            goto loser;
        }
    } else {
        salt.len = SHA1_LENGTH;
        RNG_GenerateGlobalRandomBytes(salt.data, salt.len);
    }

    rv = sftkdb_passwordToKey(keydb, &salt, newPin, &newKey);
    if (rv != SECSuccess) {
        goto loser;
    }

    /*
     * convert encrypted entries here.
     */
    crv = sftkdb_convertObjects(keydb, NULL, 0, &newKey);
    if (crv != CKR_OK) {
        rv = SECFailure;
        goto loser;
    }
    /* fix up certdb macs */
    certdb = keydb->peerDB;
    if (certdb) {
        CK_ATTRIBUTE objectType = { CKA_CLASS, 0, sizeof(CK_OBJECT_CLASS) };
        CK_OBJECT_CLASS myClass = CKO_NETSCAPE_TRUST;

        objectType.pValue = &myClass;
        crv = sftkdb_convertObjects(certdb, &objectType, 1, &newKey);
        if (crv != CKR_OK) {
            rv = SECFailure;
            goto loser;
        }
        myClass = CKO_PUBLIC_KEY;
        crv = sftkdb_convertObjects(certdb, &objectType, 1, &newKey);
        if (crv != CKR_OK) {
            rv = SECFailure;
            goto loser;
        }
    }

    plainText.data = (unsigned char *)SFTK_PW_CHECK_STRING;
    plainText.len = SFTK_PW_CHECK_LEN;

    rv = sftkdb_EncryptAttribute(NULL, &newKey, &plainText, &result);
    if (rv != SECSuccess) {
        goto loser;
    }
    value.data = result->data;
    value.len = result->len;
    crv = (*keydb->db->sdb_PutMetaData)(keydb->db, "password", &salt, &value);
    if (crv != CKR_OK) {
        rv = SECFailure;
        goto loser;
    }
    crv = (*keydb->db->sdb_Commit)(keydb->db);
    if (crv != CKR_OK) {
        rv = SECFailure;
        goto loser;
    }

    keydb->newKey = NULL;

    sftkdb_switchKeys(keydb, &newKey);

loser:
    if (newKey.data) {
        PORT_ZFree(newKey.data, newKey.len);
    }
    if (result) {
        SECITEM_FreeItem(result, PR_TRUE);
    }
    if (rv != SECSuccess) {
        (*keydb->db->sdb_Abort)(keydb->db);
    }

    return rv;
}

/*
 * lose our cached password
 */
SECStatus
sftkdb_ClearPassword(SFTKDBHandle *keydb)
{
    SECItem oldKey;
    oldKey.data = NULL;
    oldKey.len = 0;
    sftkdb_switchKeys(keydb, &oldKey);
    if (oldKey.data) {
        PORT_ZFree(oldKey.data, oldKey.len);
    }
    return SECSuccess;
}