/* 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/. */

/*
 * Merge the source token into the target token.
 */

#include "secmod.h"
#include "secmodi.h"
#include "secmodti.h"
#include "pk11pub.h"
#include "pk11priv.h"
#include "pkcs11.h"
#include "seccomon.h"
#include "secerr.h"
#include "keyhi.h"
#include "hasht.h"
#include "cert.h"
#include "certdb.h"

/*************************************************************************
 *
 *             short utilities to aid in the merge
 *
 *************************************************************************/

/*
 * write a bunch of attributes out to an existing object.
 */
static SECStatus
pk11_setAttributes(PK11SlotInfo *slot, CK_OBJECT_HANDLE id,
                   CK_ATTRIBUTE *setTemplate, CK_ULONG setTemplCount)
{
    CK_RV crv;
    CK_SESSION_HANDLE rwsession;

    rwsession = PK11_GetRWSession(slot);
    if (rwsession == CK_INVALID_SESSION) {
        PORT_SetError(SEC_ERROR_BAD_DATA);
        return SECFailure;
    }
    crv = PK11_GETTAB(slot)->C_SetAttributeValue(rwsession, id,
                                                 setTemplate, setTemplCount);
    PK11_RestoreROSession(slot, rwsession);
    if (crv != CKR_OK) {
        PORT_SetError(PK11_MapError(crv));
        return SECFailure;
    }
    return SECSuccess;
}

/*
 * copy a template of attributes from a source object to a target object.
 * if target object is not given, create it.
 */
static SECStatus
pk11_copyAttributes(PLArenaPool *arena,
                    PK11SlotInfo *targetSlot, CK_OBJECT_HANDLE targetID,
                    PK11SlotInfo *sourceSlot, CK_OBJECT_HANDLE sourceID,
                    CK_ATTRIBUTE *copyTemplate, CK_ULONG copyTemplateCount)
{
    SECStatus rv;
    CK_ATTRIBUTE *newTemplate = NULL;
    CK_RV crv;

    crv = PK11_GetAttributes(arena, sourceSlot, sourceID,
                             copyTemplate, copyTemplateCount);
    /* if we have missing attributes, just skip them and create the object */
    if (crv == CKR_ATTRIBUTE_TYPE_INVALID) {
        CK_ULONG i, j;
        newTemplate = PORT_NewArray(CK_ATTRIBUTE, copyTemplateCount);
        if (!newTemplate) {
            return SECFailure;
        }
        /* remove the unknown attributes. If we don't have enough attributes
         * PK11_CreateNewObject() will fail */
        for (i = 0, j = 0; i < copyTemplateCount; i++) {
            if (copyTemplate[i].ulValueLen != -1) {
                newTemplate[j] = copyTemplate[i];
                j++;
            }
        }
        copyTemplate = newTemplate;
        copyTemplateCount = j;
        crv = PK11_GetAttributes(arena, sourceSlot, sourceID,
                                 copyTemplate, copyTemplateCount);
    }
    if (crv != CKR_OK) {
        PORT_SetError(PK11_MapError(crv));
        PORT_Free(newTemplate);
        return SECFailure;
    }
    if (targetID == CK_INVALID_HANDLE) {
        /* we need to create the object */
        rv = PK11_CreateNewObject(targetSlot, CK_INVALID_SESSION,
                                  copyTemplate, copyTemplateCount, PR_TRUE, &targetID);
    } else {
        /* update the existing object with the new attributes */
        rv = pk11_setAttributes(targetSlot, targetID,
                                copyTemplate, copyTemplateCount);
    }
    if (newTemplate) {
        PORT_Free(newTemplate);
    }
    return rv;
}

/*
 * look for a matching object across tokens.
 */
static SECStatus
pk11_matchAcrossTokens(PLArenaPool *arena, PK11SlotInfo *targetSlot,
                       PK11SlotInfo *sourceSlot,
                       CK_ATTRIBUTE *template, CK_ULONG tsize,
                       CK_OBJECT_HANDLE id, CK_OBJECT_HANDLE *peer)
{

    CK_RV crv;
    *peer = CK_INVALID_HANDLE;

    crv = PK11_GetAttributes(arena, sourceSlot, id, template, tsize);
    if (crv != CKR_OK) {
        PORT_SetError(PK11_MapError(crv));
        goto loser;
    }

    if (template[0].ulValueLen == -1) {
        crv = CKR_ATTRIBUTE_TYPE_INVALID;
        PORT_SetError(PK11_MapError(crv));
        goto loser;
    }

    *peer = pk11_FindObjectByTemplate(targetSlot, template, tsize);
    return SECSuccess;

loser:
    return SECFailure;
}

/*
 * Encrypt using key and parameters
 */
SECStatus
pk11_encrypt(PK11SymKey *symKey, CK_MECHANISM_TYPE mechType, SECItem *param,
             SECItem *input, SECItem **output)
{
    PK11Context *ctxt = NULL;
    SECStatus rv = SECSuccess;

    if (*output) {
        SECITEM_FreeItem(*output, PR_TRUE);
    }
    *output = SECITEM_AllocItem(NULL, NULL, input->len + 20 /*slop*/);
    if (!*output) {
        rv = SECFailure;
        goto done;
    }

    ctxt = PK11_CreateContextBySymKey(mechType, CKA_ENCRYPT, symKey, param);
    if (ctxt == NULL) {
        rv = SECFailure;
        goto done;
    }

    rv = PK11_CipherOp(ctxt, (*output)->data,
                       (int *)&((*output)->len),
                       (*output)->len, input->data, input->len);

done:
    if (ctxt) {
        PK11_Finalize(ctxt);
        PK11_DestroyContext(ctxt, PR_TRUE);
    }
    if (rv != SECSuccess) {
        if (*output) {
            SECITEM_FreeItem(*output, PR_TRUE);
            *output = NULL;
        }
    }
    return rv;
}

/*************************************************************************
 *
 *            Private Keys
 *
 *************************************************************************/

/*
 * Fetch the key usage based on the pkcs #11 flags
 */
unsigned int
pk11_getPrivateKeyUsage(PK11SlotInfo *slot, CK_OBJECT_HANDLE id)
{
    unsigned int usage = 0;

    if ((PK11_HasAttributeSet(slot, id, CKA_UNWRAP, PR_FALSE) ||
         PK11_HasAttributeSet(slot, id, CKA_DECRYPT, PR_FALSE))) {
        usage |= KU_KEY_ENCIPHERMENT;
    }
    if (PK11_HasAttributeSet(slot, id, CKA_DERIVE, PR_FALSE)) {
        usage |= KU_KEY_AGREEMENT;
    }
    if ((PK11_HasAttributeSet(slot, id, CKA_SIGN_RECOVER, PR_FALSE) ||
         PK11_HasAttributeSet(slot, id, CKA_SIGN, PR_FALSE))) {
        usage |= KU_DIGITAL_SIGNATURE;
    }
    return usage;
}

/*
 * merge a private key,
 *
 * Private keys are merged using PBE wrapped keys with a random
 * value as the 'password'. Once the base key is moved, The remaining
 * attributes (SUBJECT) is copied.
 */
static SECStatus
pk11_mergePrivateKey(PK11SlotInfo *targetSlot, PK11SlotInfo *sourceSlot,
                     CK_OBJECT_HANDLE id, void *targetPwArg, void *sourcePwArg)
{
    SECKEYPrivateKey *sourceKey = NULL;
    CK_OBJECT_HANDLE targetKeyID;
    SECKEYEncryptedPrivateKeyInfo *epki = NULL;
    char *nickname = NULL;
    SECItem nickItem;
    SECItem pwitem;
    SECItem publicValue;
    PLArenaPool *arena = NULL;
    SECStatus rv = SECSuccess;
    unsigned int keyUsage;
    unsigned char randomData[SHA1_LENGTH];
    SECOidTag algTag = SEC_OID_PKCS12_V2_PBE_WITH_SHA1_AND_3KEY_TRIPLE_DES_CBC;
    CK_ATTRIBUTE privTemplate[] = {
        { CKA_ID, NULL, 0 },
        { CKA_CLASS, NULL, 0 }
    };
    CK_ULONG privTemplateCount = sizeof(privTemplate) / sizeof(privTemplate[0]);
    CK_ATTRIBUTE privCopyTemplate[] = {
        { CKA_SUBJECT, NULL, 0 }
    };
    CK_ULONG privCopyTemplateCount =
        sizeof(privCopyTemplate) / sizeof(privCopyTemplate[0]);

    arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
    if (arena == NULL) {
        rv = SECFailure;
        goto done;
    }

    /* check to see if the key is already in the target slot */
    rv = pk11_matchAcrossTokens(arena, targetSlot, sourceSlot, privTemplate,
                                privTemplateCount, id, &targetKeyID);
    if (rv != SECSuccess) {
        goto done;
    }

    if (targetKeyID != CK_INVALID_HANDLE) {
        /* match found,  not an error ... */
        goto done;
    }

    /* get an NSS representation of our source key */
    sourceKey = PK11_MakePrivKey(sourceSlot, nullKey, PR_FALSE,
                                 id, sourcePwArg);
    if (sourceKey == NULL) {
        rv = SECFailure;
        goto done;
    }

    /* Load the private key */
    /* generate a random pwitem */
    rv = PK11_GenerateRandom(randomData, sizeof(randomData));
    if (rv != SECSuccess) {
        goto done;
    }
    pwitem.data = randomData;
    pwitem.len = sizeof(randomData);
    /* fetch the private key encrypted */
    epki = PK11_ExportEncryptedPrivKeyInfo(sourceSlot, algTag, &pwitem,
                                           sourceKey, 1, sourcePwArg);
    if (epki == NULL) {
        rv = SECFailure;
        goto done;
    }
    nickname = PK11_GetObjectNickname(sourceSlot, id);
    /* NULL nickanme is fine (in fact is often normal) */
    if (nickname) {
        nickItem.data = (unsigned char *)nickname;
        nickItem.len = PORT_Strlen(nickname);
    }
    keyUsage = pk11_getPrivateKeyUsage(sourceSlot, id);
    /* pass in the CKA_ID */
    publicValue.data = privTemplate[0].pValue;
    publicValue.len = privTemplate[0].ulValueLen;
    rv = PK11_ImportEncryptedPrivateKeyInfo(targetSlot, epki, &pwitem,
                                            nickname ? &nickItem : NULL, &publicValue,
                                            PR_TRUE, PR_TRUE, sourceKey->keyType, keyUsage,
                                            targetPwArg);
    if (rv != SECSuccess) {
        goto done;
    }

    /* make sure it made it */
    rv = pk11_matchAcrossTokens(arena, targetSlot, sourceSlot, privTemplate,
                                privTemplateCount, id, &targetKeyID);
    if (rv != SECSuccess) {
        goto done;
    }

    if (targetKeyID == CK_INVALID_HANDLE) {
        /* this time the key should exist */
        rv = SECFailure;
        goto done;
    }

    /* fill in remaining attributes */
    rv = pk11_copyAttributes(arena, targetSlot, targetKeyID, sourceSlot, id,
                             privCopyTemplate, privCopyTemplateCount);
done:
    /* make sure the 'key' is cleared */
    PORT_Memset(randomData, 0, sizeof(randomData));
    if (nickname) {
        PORT_Free(nickname);
    }
    if (sourceKey) {
        SECKEY_DestroyPrivateKey(sourceKey);
    }
    if (epki) {
        SECKEY_DestroyEncryptedPrivateKeyInfo(epki, PR_TRUE);
    }
    if (arena) {
        PORT_FreeArena(arena, PR_FALSE);
    }
    return rv;
}

/*************************************************************************
 *
 *            Secret Keys
 *
 *************************************************************************/

/*
 * we need to find a unique CKA_ID.
 *  The basic idea is to just increment the lowest byte.
 *  This code also handles the following corner cases:
 *   1) the single byte overflows. On overflow we increment the next byte up
 *    and so forth until we have overflowed the entire CKA_ID.
 *   2) If we overflow the entire CKA_ID we expand it by one byte.
 *   3) the CKA_ID is non-existent, we create a new one with one byte.
 *    This means no matter what CKA_ID is passed, the result of this function
 *    is always a new CKA_ID, and this function will never return the same
 *    CKA_ID the it has returned in the passed.
 */
static SECStatus
pk11_incrementID(PLArenaPool *arena, CK_ATTRIBUTE *ptemplate)
{
    unsigned char *buf = ptemplate->pValue;
    CK_ULONG len = ptemplate->ulValueLen;

    if (buf == NULL || len == (CK_ULONG)-1) {
        /* we have no valid CKAID, we'll create a basic one byte CKA_ID below */
        len = 0;
    } else {
        CK_ULONG i;

        /* walk from the back to front, incrementing
         * the CKA_ID until we no longer have a carry,
         * or have hit the front of the id. */
        for (i = len; i != 0; i--) {
            buf[i - 1]++;
            if (buf[i - 1] != 0) {
                /* no more carries, the increment is complete */
                return SECSuccess;
            }
        }
        /* we've now overflowed, fall through and expand the CKA_ID by
         * one byte */
    }
    /* if we are here we've run the counter to zero (indicating an overflow).
     * create an CKA_ID that is all zeros, but has one more zero than
     * the previous CKA_ID */
    buf = PORT_ArenaZAlloc(arena, len + 1);
    if (buf == NULL) {
        return SECFailure;
    }
    ptemplate->pValue = buf;
    ptemplate->ulValueLen = len + 1;
    return SECSuccess;
}

static CK_FLAGS
pk11_getSecretKeyFlags(PK11SlotInfo *slot, CK_OBJECT_HANDLE id)
{
    CK_FLAGS flags = 0;

    if (PK11_HasAttributeSet(slot, id, CKA_UNWRAP, PR_FALSE)) {
        flags |= CKF_UNWRAP;
    }
    if (PK11_HasAttributeSet(slot, id, CKA_WRAP, PR_FALSE)) {
        flags |= CKF_WRAP;
    }
    if (PK11_HasAttributeSet(slot, id, CKA_ENCRYPT, PR_FALSE)) {
        flags |= CKF_ENCRYPT;
    }
    if (PK11_HasAttributeSet(slot, id, CKA_DECRYPT, PR_FALSE)) {
        flags |= CKF_DECRYPT;
    }
    if (PK11_HasAttributeSet(slot, id, CKA_DERIVE, PR_FALSE)) {
        flags |= CKF_DERIVE;
    }
    if (PK11_HasAttributeSet(slot, id, CKA_SIGN, PR_FALSE)) {
        flags |= CKF_SIGN;
    }
    if (PK11_HasAttributeSet(slot, id, CKA_SIGN_RECOVER, PR_FALSE)) {
        flags |= CKF_SIGN_RECOVER;
    }
    if (PK11_HasAttributeSet(slot, id, CKA_VERIFY, PR_FALSE)) {
        flags |= CKF_VERIFY;
    }
    if (PK11_HasAttributeSet(slot, id, CKA_VERIFY_RECOVER, PR_FALSE)) {
        flags |= CKF_VERIFY_RECOVER;
    }
    return flags;
}

static const char testString[] =
    "My Encrytion Test Data (should be at least 32 bytes long)";
/*
 * merge a secret key,
 *
 * Secret keys may collide by CKA_ID as we merge 2 token. If we collide
 * on the CKA_ID, we need to make sure we are dealing with different keys.
 * The reason for this is it is possible that we've merged this database
 * before, and this key could have been merged already.  If the keys are
 * the same, we are done. If they are not, we need to update the CKA_ID of
 * the source key and try again.
 *
 * Once we know we have a unique key to merge in, we use NSS's underlying
 * key Move function which will do a key exchange if necessary to move
 * the key from one token to another. Then we set the CKA_ID and additional
 * pkcs #11 attributes.
 */
static SECStatus
pk11_mergeSecretKey(PK11SlotInfo *targetSlot, PK11SlotInfo *sourceSlot,
                    CK_OBJECT_HANDLE id, void *targetPwArg, void *sourcePwArg)
{
    PK11SymKey *sourceKey = NULL;
    PK11SymKey *targetKey = NULL;
    SECItem *sourceOutput = NULL;
    SECItem *targetOutput = NULL;
    SECItem *param = NULL;
    int blockSize;
    SECItem input;
    CK_OBJECT_HANDLE targetKeyID;
    CK_FLAGS flags;
    PLArenaPool *arena = NULL;
    SECStatus rv = SECSuccess;
    CK_MECHANISM_TYPE keyMechType, cryptoMechType;
    CK_KEY_TYPE sourceKeyType, targetKeyType;
    CK_ATTRIBUTE symTemplate[] = {
        { CKA_ID, NULL, 0 },
        { CKA_CLASS, NULL, 0 }
    };
    CK_ULONG symTemplateCount = sizeof(symTemplate) / sizeof(symTemplate[0]);
    CK_ATTRIBUTE symCopyTemplate[] = {
        { CKA_LABEL, NULL, 0 }
    };
    CK_ULONG symCopyTemplateCount =
        sizeof(symCopyTemplate) / sizeof(symCopyTemplate[0]);

    arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
    if (arena == NULL) {
        rv = SECFailure;
        goto done;
    }

    sourceKeyType = PK11_ReadULongAttribute(sourceSlot, id, CKA_KEY_TYPE);
    if (sourceKeyType == (CK_ULONG)-1) {
        rv = SECFailure;
        goto done;
    }

    /* get the key mechanism */
    keyMechType = PK11_GetKeyMechanism(sourceKeyType);
    /* get a mechanism suitable to encryption.
     * PK11_GetKeyMechanism returns a mechanism that is unique to the key
     * type. It tries to return encryption/decryption mechanisms, however
     * CKM_DES3_CBC uses and abmiguous keyType, so keyMechType is returned as
     * 'keygen' mechanism. Detect that case here */
    cryptoMechType = keyMechType;
    if ((keyMechType == CKM_DES3_KEY_GEN) ||
        (keyMechType == CKM_DES2_KEY_GEN)) {
        cryptoMechType = CKM_DES3_CBC;
    }

    sourceKey = PK11_SymKeyFromHandle(sourceSlot, NULL, PK11_OriginDerive,
                                      keyMechType, id, PR_FALSE, sourcePwArg);
    if (sourceKey == NULL) {
        rv = SECFailure;
        goto done;
    }

    /* check to see a key with the same CKA_ID  already exists in
     * the target slot. If it does, then we need to verify if the keys
     * really matches. If they don't import the key with a new CKA_ID
     * value. */
    rv = pk11_matchAcrossTokens(arena, targetSlot, sourceSlot,
                                symTemplate, symTemplateCount, id, &targetKeyID);
    if (rv != SECSuccess) {
        goto done;
    }

    /* set up the input test */
    input.data = (unsigned char *)testString;
    blockSize = PK11_GetBlockSize(cryptoMechType, NULL);
    if (blockSize < 0) {
        rv = SECFailure;
        goto done;
    }
    input.len = blockSize;
    if (input.len == 0) {
        input.len = sizeof(testString);
    }
    while (targetKeyID != CK_INVALID_HANDLE) {
        /* test to see if the keys are identical */
        targetKeyType = PK11_ReadULongAttribute(sourceSlot, id, CKA_KEY_TYPE);
        if (targetKeyType == sourceKeyType) {
            /* same keyType  - see if it's the same key */
            targetKey = PK11_SymKeyFromHandle(targetSlot, NULL,
                                              PK11_OriginDerive, keyMechType, targetKeyID, PR_FALSE,
                                              targetPwArg);
            /* get a parameter if we don't already have one */
            if (!param) {
                param = PK11_GenerateNewParam(cryptoMechType, sourceKey);
                if (param == NULL) {
                    rv = SECFailure;
                    goto done;
                }
            }
            /* use the source key to encrypt a reference */
            if (!sourceOutput) {
                rv = pk11_encrypt(sourceKey, cryptoMechType, param, &input,
                                  &sourceOutput);
                if (rv != SECSuccess) {
                    goto done;
                }
            }
            /* encrypt the reference with the target key */
            rv = pk11_encrypt(targetKey, cryptoMechType, param, &input,
                              &targetOutput);
            if (rv == SECSuccess) {
                if (SECITEM_ItemsAreEqual(sourceOutput, targetOutput)) {
                    /* they produce the same output, they must be the
                     * same key */
                    goto done;
                }
                SECITEM_FreeItem(targetOutput, PR_TRUE);
                targetOutput = NULL;
            }
            PK11_FreeSymKey(targetKey);
            targetKey = NULL;
        }
        /* keys aren't equal, update the KEY_ID and look again */
        rv = pk11_incrementID(arena, &symTemplate[0]);
        if (rv != SECSuccess) {
            goto done;
        }
        targetKeyID = pk11_FindObjectByTemplate(targetSlot,
                                                symTemplate, symTemplateCount);
    }

    /* we didn't find a matching key, import this one with the new
     * CKAID */
    flags = pk11_getSecretKeyFlags(sourceSlot, id);
    targetKey = PK11_MoveSymKey(targetSlot, PK11_OriginDerive, flags, PR_TRUE,
                                sourceKey);
    if (targetKey == NULL) {
        rv = SECFailure;
        goto done;
    }
    /* set the key new CKAID */
    rv = pk11_setAttributes(targetSlot, targetKey->objectID, symTemplate, 1);
    if (rv != SECSuccess) {
        goto done;
    }

    /* fill in remaining attributes */
    rv = pk11_copyAttributes(arena, targetSlot, targetKey->objectID,
                             sourceSlot, id, symCopyTemplate, symCopyTemplateCount);
done:
    if (sourceKey) {
        PK11_FreeSymKey(sourceKey);
    }
    if (targetKey) {
        PK11_FreeSymKey(targetKey);
    }
    if (sourceOutput) {
        SECITEM_FreeItem(sourceOutput, PR_TRUE);
    }
    if (targetOutput) {
        SECITEM_FreeItem(targetOutput, PR_TRUE);
    }
    if (param) {
        SECITEM_FreeItem(param, PR_TRUE);
    }
    if (arena) {
        PORT_FreeArena(arena, PR_FALSE);
    }
    return rv;
}

/*************************************************************************
 *
 *            Public Keys
 *
 *************************************************************************/

/*
 * Merge public key
 *
 * Use the high level NSS calls to extract the public key and import it
 * into the token. Extra attributes are then copied to the new token.
 */
static SECStatus
pk11_mergePublicKey(PK11SlotInfo *targetSlot, PK11SlotInfo *sourceSlot,
                    CK_OBJECT_HANDLE id, void *targetPwArg, void *sourcePwArg)
{
    SECKEYPublicKey *sourceKey = NULL;
    CK_OBJECT_HANDLE targetKeyID;
    PLArenaPool *arena = NULL;
    SECStatus rv = SECSuccess;
    CK_ATTRIBUTE pubTemplate[] = {
        { CKA_ID, NULL, 0 },
        { CKA_CLASS, NULL, 0 }
    };
    CK_ULONG pubTemplateCount = sizeof(pubTemplate) / sizeof(pubTemplate[0]);
    CK_ATTRIBUTE pubCopyTemplate[] = {
        { CKA_ID, NULL, 0 },
        { CKA_LABEL, NULL, 0 },
        { CKA_SUBJECT, NULL, 0 }
    };
    CK_ULONG pubCopyTemplateCount =
        sizeof(pubCopyTemplate) / sizeof(pubCopyTemplate[0]);

    arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
    if (arena == NULL) {
        rv = SECFailure;
        goto done;
    }

    /* check to see if the key is already in the target slot */
    rv = pk11_matchAcrossTokens(arena, targetSlot, sourceSlot, pubTemplate,
                                pubTemplateCount, id, &targetKeyID);
    if (rv != SECSuccess) {
        goto done;
    }

    /* Key is already in the target slot */
    if (targetKeyID != CK_INVALID_HANDLE) {
        /* not an error ... */
        goto done;
    }

    /* fetch an NSS representation of the public key */
    sourceKey = PK11_ExtractPublicKey(sourceSlot, nullKey, id);
    if (sourceKey == NULL) {
        rv = SECFailure;
        goto done;
    }

    /* load the public key into the target token. */
    targetKeyID = PK11_ImportPublicKey(targetSlot, sourceKey, PR_TRUE);
    if (targetKeyID == CK_INVALID_HANDLE) {
        rv = SECFailure;
        goto done;
    }

    /* fill in remaining attributes */
    rv = pk11_copyAttributes(arena, targetSlot, targetKeyID, sourceSlot, id,
                             pubCopyTemplate, pubCopyTemplateCount);

done:
    if (sourceKey) {
        SECKEY_DestroyPublicKey(sourceKey);
    }
    if (arena) {
        PORT_FreeArena(arena, PR_FALSE);
    }
    return rv;
}

/*************************************************************************
 *
 *            Certificates
 *
 *************************************************************************/

/*
 * Two copies of the source code for this algorithm exist in NSS.
 * Changes must be made in both copies.
 * The other copy is in sftkdb_resolveConflicts() in softoken/sftkdb.c.
 */
static char *
pk11_IncrementNickname(char *nickname)
{
    char *newNickname = NULL;
    int end;
    int digit;
    int len = strlen(nickname);

    /* does nickname end with " #n*" ? */
    for (end = len - 1;
         end >= 2 && (digit = nickname[end]) <= '9' && digit >= '0';
         end--) /* just scan */
        ;
    if (len >= 3 &&
        end < (len - 1) /* at least one digit */ &&
        nickname[end] == '#' &&
        nickname[end - 1] == ' ') {
        /* Already has a suitable suffix string */
    } else {
        /* ... append " #2" to the name */
        static const char num2[] = " #2";
        newNickname = PORT_Realloc(nickname, len + sizeof(num2));
        if (newNickname) {
            PORT_Strcat(newNickname, num2);
        } else {
            PORT_Free(nickname);
        }
        return newNickname;
    }

    for (end = len - 1;
         end >= 0 && (digit = nickname[end]) <= '9' && digit >= '0';
         end--) {
        if (digit < '9') {
            nickname[end]++;
            return nickname;
        }
        nickname[end] = '0';
    }

    /* we overflowed, insert a new '1' for a carry in front of the number */
    newNickname = PORT_Realloc(nickname, len + 2);
    if (newNickname) {
        newNickname[++end] = '1';
        PORT_Memset(&newNickname[end + 1], '0', len - end);
        newNickname[len + 1] = 0;
    } else {
        PORT_Free(nickname);
    }
    return newNickname;
}

/*
 * merge a certificate object
 *
 * Use the high level NSS calls to extract and import the certificate.
 */
static SECStatus
pk11_mergeCert(PK11SlotInfo *targetSlot, PK11SlotInfo *sourceSlot,
               CK_OBJECT_HANDLE id, void *targetPwArg, void *sourcePwArg)
{
    CERTCertificate *sourceCert = NULL;
    CK_OBJECT_HANDLE targetCertID = CK_INVALID_HANDLE;
    char *nickname = NULL;
    SECStatus rv = SECSuccess;
    PLArenaPool *arena = NULL;
    CK_ATTRIBUTE sourceCKAID = { CKA_ID, NULL, 0 };
    CK_ATTRIBUTE targetCKAID = { CKA_ID, NULL, 0 };
    SECStatus lrv = SECSuccess;
    int error = SEC_ERROR_LIBRARY_FAILURE;

    sourceCert = PK11_MakeCertFromHandle(sourceSlot, id, NULL);
    if (sourceCert == NULL) {
        rv = SECFailure;
        goto done;
    }

    nickname = PK11_GetObjectNickname(sourceSlot, id);

    /* The database code will prevent nickname collisions for certs with
     * different subjects. This code will prevent us from getting
     * actual import errors */
    if (nickname) {
        const char *tokenName = PK11_GetTokenName(targetSlot);
        char *tokenNickname = NULL;

        do {
            tokenNickname = PR_smprintf("%s:%s", tokenName, nickname);
            if (!tokenNickname) {
                break;
            }
            if (!SEC_CertNicknameConflict(tokenNickname,
                                          &sourceCert->derSubject, CERT_GetDefaultCertDB())) {
                break;
            }
            nickname = pk11_IncrementNickname(nickname);
            if (!nickname) {
                break;
            }
            PR_smprintf_free(tokenNickname);
        } while (1);
        if (tokenNickname) {
            PR_smprintf_free(tokenNickname);
        }
    }

    /* see if the cert is already there */
    targetCertID = PK11_FindCertInSlot(targetSlot, sourceCert, targetPwArg);
    if (targetCertID == CK_INVALID_HANDLE) {
        /* cert doesn't exist load the cert in. */
        /* OK for the nickname to be NULL, not all certs have nicknames */
        rv = PK11_ImportCert(targetSlot, sourceCert, CK_INVALID_HANDLE,
                             nickname, PR_FALSE);
        goto done;
    }

    /* the cert already exists, see if the nickname and/or  CKA_ID need
     * to be updated */

    arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
    if (arena == NULL) {
        rv = SECFailure;
        goto done;
    }

    /* does our source have a CKA_ID ? */
    rv = PK11_GetAttributes(arena, sourceSlot, id, &sourceCKAID, 1);
    if (rv != SECSuccess) {
        sourceCKAID.ulValueLen = 0;
    }

    /* if we have a source CKA_ID, see of we need to update the
     * target's CKA_ID */
    if (sourceCKAID.ulValueLen != 0) {
        rv = PK11_GetAttributes(arena, targetSlot, targetCertID,
                                &targetCKAID, 1);
        if (rv != SECSuccess) {
            targetCKAID.ulValueLen = 0;
        }
        /* if the target has no CKA_ID, update it from the source */
        if (targetCKAID.ulValueLen == 0) {
            lrv = pk11_setAttributes(targetSlot, targetCertID, &sourceCKAID, 1);
            if (lrv != SECSuccess) {
                error = PORT_GetError();
            }
        }
    }
    rv = SECSuccess;

    /* now check if we need to update the nickname */
    if (nickname && *nickname) {
        char *targetname;
        targetname = PK11_GetObjectNickname(targetSlot, targetCertID);
        if (!targetname || !*targetname) {
            /* target has no nickname, or it's empty, update it */
            rv = PK11_SetObjectNickname(targetSlot, targetCertID, nickname);
        }
        if (targetname) {
            PORT_Free(targetname);
        }
    }

    /* restore the error code if CKA_ID failed, but nickname didn't */
    if ((rv == SECSuccess) && (lrv != SECSuccess)) {
        rv = lrv;
        PORT_SetError(error);
    }

done:
    if (nickname) {
        PORT_Free(nickname);
    }
    if (sourceCert) {
        CERT_DestroyCertificate(sourceCert);
    }
    if (arena) {
        PORT_FreeArena(arena, PR_FALSE);
    }
    return rv;
}

/*************************************************************************
 *
 *            Crls
 *
 *************************************************************************/

/*
 * Use the raw PKCS #11 interface to merge the CRLs.
 *
 * In the case where of collision, choose the newest CRL that is valid.
 */
static SECStatus
pk11_mergeCrl(PK11SlotInfo *targetSlot, PK11SlotInfo *sourceSlot,
              CK_OBJECT_HANDLE id, void *targetPwArg, void *sourcePwArg)
{
    CK_OBJECT_HANDLE targetCrlID;
    PLArenaPool *arena = NULL;
    SECStatus rv = SECSuccess;
    CK_ATTRIBUTE crlTemplate[] = {
        { CKA_SUBJECT, NULL, 0 },
        { CKA_CLASS, NULL, 0 },
        { CKA_NSS_KRL, NULL, 0 }
    };
    CK_ULONG crlTemplateCount = sizeof(crlTemplate) / sizeof(crlTemplate[0]);
    CK_ATTRIBUTE crlCopyTemplate[] = {
        { CKA_CLASS, NULL, 0 },
        { CKA_TOKEN, NULL, 0 },
        { CKA_LABEL, NULL, 0 },
        { CKA_PRIVATE, NULL, 0 },
        { CKA_MODIFIABLE, NULL, 0 },
        { CKA_SUBJECT, NULL, 0 },
        { CKA_NSS_KRL, NULL, 0 },
        { CKA_NSS_URL, NULL, 0 },
        { CKA_VALUE, NULL, 0 }
    };
    CK_ULONG crlCopyTemplateCount =
        sizeof(crlCopyTemplate) / sizeof(crlCopyTemplate[0]);

    arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
    if (arena == NULL) {
        rv = SECFailure;
        goto done;
    }
    /* check to see if the crl is already in the target slot */
    rv = pk11_matchAcrossTokens(arena, targetSlot, sourceSlot, crlTemplate,
                                crlTemplateCount, id, &targetCrlID);
    if (rv != SECSuccess) {
        goto done;
    }
    if (targetCrlID != CK_INVALID_HANDLE) {
        /* we already have a CRL, check to see which is more up-to-date. */
        goto done;
    }

    /* load the CRL into the target token. */
    rv = pk11_copyAttributes(arena, targetSlot, targetCrlID, sourceSlot, id,
                             crlCopyTemplate, crlCopyTemplateCount);
done:
    if (arena) {
        PORT_FreeArena(arena, PR_FALSE);
    }
    return rv;
}

/*************************************************************************
 *
 *            SMIME objects
 *
 *************************************************************************/

/*
 * use the raw PKCS #11 interface to merge the S/MIME records
 */
static SECStatus
pk11_mergeSmime(PK11SlotInfo *targetSlot, PK11SlotInfo *sourceSlot,
                CK_OBJECT_HANDLE id, void *targetPwArg, void *sourcePwArg)
{
    CK_OBJECT_HANDLE targetSmimeID;
    PLArenaPool *arena = NULL;
    SECStatus rv = SECSuccess;
    CK_ATTRIBUTE smimeTemplate[] = {
        { CKA_SUBJECT, NULL, 0 },
        { CKA_NSS_EMAIL, NULL, 0 },
        { CKA_CLASS, NULL, 0 },
    };
    CK_ULONG smimeTemplateCount =
        sizeof(smimeTemplate) / sizeof(smimeTemplate[0]);
    CK_ATTRIBUTE smimeCopyTemplate[] = {
        { CKA_CLASS, NULL, 0 },
        { CKA_TOKEN, NULL, 0 },
        { CKA_LABEL, NULL, 0 },
        { CKA_PRIVATE, NULL, 0 },
        { CKA_MODIFIABLE, NULL, 0 },
        { CKA_SUBJECT, NULL, 0 },
        { CKA_NSS_EMAIL, NULL, 0 },
        { CKA_NSS_SMIME_TIMESTAMP, NULL, 0 },
        { CKA_VALUE, NULL, 0 }
    };
    CK_ULONG smimeCopyTemplateCount =
        sizeof(smimeCopyTemplate) / sizeof(smimeCopyTemplate[0]);

    arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
    if (arena == NULL) {
        rv = SECFailure;
        goto done;
    }
    /* check to see if the crl is already in the target slot */
    rv = pk11_matchAcrossTokens(arena, targetSlot, sourceSlot, smimeTemplate,
                                smimeTemplateCount, id, &targetSmimeID);
    if (rv != SECSuccess) {
        goto done;
    }
    if (targetSmimeID != CK_INVALID_HANDLE) {
        /* we already have a SMIME record */
        goto done;
    }

    /* load the SMime Record into the target token. */
    rv = pk11_copyAttributes(arena, targetSlot, targetSmimeID, sourceSlot, id,
                             smimeCopyTemplate, smimeCopyTemplateCount);
done:
    if (arena) {
        PORT_FreeArena(arena, PR_FALSE);
    }
    return rv;
}

/*************************************************************************
 *
 *            Trust Objects
 *
 *************************************************************************/

/*
 * decide which trust record entry wins. PR_TRUE (source) or PR_FALSE (target)
 */
#define USE_TARGET PR_FALSE
#define USE_SOURCE PR_TRUE
PRBool
pk11_mergeTrustEntry(CK_ATTRIBUTE *target, CK_ATTRIBUTE *source)
{
    CK_ULONG targetTrust = (target->ulValueLen == sizeof(CK_LONG)) ? *(CK_ULONG *)target->pValue
                                                                   : CKT_NSS_TRUST_UNKNOWN;
    CK_ULONG sourceTrust = (source->ulValueLen == sizeof(CK_LONG)) ? *(CK_ULONG *)source->pValue
                                                                   : CKT_NSS_TRUST_UNKNOWN;

    /*
     * Examine a single entry and deside if the source or target version
     * should win out. When all the entries have been checked, if there is
     * any case we need to update, we will write the whole source record
     * to the target database. That means for each individual record, if the
     * target wins, we need to update the source (in case later we have a
     * case where the source wins). If the source wins, it already
     */
    if (sourceTrust == targetTrust) {
        return USE_TARGET; /* which equates to 'do nothing' */
    }

    if (sourceTrust == CKT_NSS_TRUST_UNKNOWN) {
        return USE_TARGET;
    }

    /* target has no idea, use the source's idea of the trust value */
    if (targetTrust == CKT_NSS_TRUST_UNKNOWN) {
        /* source overwrites the target */
        return USE_SOURCE;
    }

    /* so both the target and the source have some idea of what this
     * trust attribute should be, and neither agree exactly.
     * At this point, we prefer 'hard' attributes over 'soft' ones.
     * 'hard' ones are CKT_NSS_TRUSTED, CKT_NSS_TRUSTED_DELEGATOR, and
     * CKT_NSS_UNTRUTED. Soft ones are ones which don't change the
     * actual trust of the cert (CKT_MUST_VERIFY, CKT_NSS_VALID,
     * CKT_NSS_VALID_DELEGATOR).
     */
    if ((sourceTrust == CKT_NSS_MUST_VERIFY_TRUST) ||
        (sourceTrust == CKT_NSS_VALID_DELEGATOR)) {
        return USE_TARGET;
    }
    if ((targetTrust == CKT_NSS_MUST_VERIFY_TRUST) ||
        (targetTrust == CKT_NSS_VALID_DELEGATOR)) {
        /* source overrites the target */
        return USE_SOURCE;
    }

    /* both have hard attributes, we have a conflict, let the target win. */
    return USE_TARGET;
}
/*
 * use the raw PKCS #11 interface to merge the S/MIME records
 */
static SECStatus
pk11_mergeTrust(PK11SlotInfo *targetSlot, PK11SlotInfo *sourceSlot,
                CK_OBJECT_HANDLE id, void *targetPwArg, void *sourcePwArg)
{
    CK_OBJECT_HANDLE targetTrustID;
    PLArenaPool *arena = NULL;
    SECStatus rv = SECSuccess;
    int error = 0;
    CK_ATTRIBUTE trustTemplate[] = {
        { CKA_ISSUER, NULL, 0 },
        { CKA_SERIAL_NUMBER, NULL, 0 },
        { CKA_CLASS, NULL, 0 },
    };
    CK_ULONG trustTemplateCount =
        sizeof(trustTemplate) / sizeof(trustTemplate[0]);
    CK_ATTRIBUTE trustCopyTemplate[] = {
        { CKA_CLASS, NULL, 0 },
        { CKA_TOKEN, NULL, 0 },
        { CKA_LABEL, NULL, 0 },
        { CKA_PRIVATE, NULL, 0 },
        { CKA_MODIFIABLE, NULL, 0 },
        { CKA_ISSUER, NULL, 0 },
        { CKA_SERIAL_NUMBER, 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_CODE_SIGNING, NULL, 0 },
        { CKA_TRUST_EMAIL_PROTECTION, NULL, 0 },
        { CKA_TRUST_STEP_UP_APPROVED, NULL, 0 }
    };
    CK_ULONG trustCopyTemplateCount =
        sizeof(trustCopyTemplate) / sizeof(trustCopyTemplate[0]);

    arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
    if (arena == NULL) {
        rv = SECFailure;
        goto done;
    }
    /* check to see if the crl is already in the target slot */
    rv = pk11_matchAcrossTokens(arena, targetSlot, sourceSlot, trustTemplate,
                                trustTemplateCount, id, &targetTrustID);
    if (rv != SECSuccess) {
        goto done;
    }
    if (targetTrustID != CK_INVALID_HANDLE) {
        /* a matching trust record already exists, merge it in */
        CK_ATTRIBUTE_TYPE trustAttrs[] = {
            CKA_TRUST_SERVER_AUTH, CKA_TRUST_CLIENT_AUTH,
            CKA_TRUST_CODE_SIGNING, CKA_TRUST_EMAIL_PROTECTION,
            CKA_TRUST_IPSEC_TUNNEL, CKA_TRUST_IPSEC_USER,
            CKA_TRUST_TIME_STAMPING
        };
        CK_ULONG trustAttrsCount =
            sizeof(trustAttrs) / sizeof(trustAttrs[0]);

        CK_ULONG i;
        CK_ATTRIBUTE targetTemplate, sourceTemplate;

        /* existing trust record, merge the two together */
        for (i = 0; i < trustAttrsCount; i++) {
            targetTemplate.type = sourceTemplate.type = trustAttrs[i];
            targetTemplate.pValue = sourceTemplate.pValue = NULL;
            targetTemplate.ulValueLen = sourceTemplate.ulValueLen = 0;
            PK11_GetAttributes(arena, sourceSlot, id, &sourceTemplate, 1);
            PK11_GetAttributes(arena, targetSlot, targetTrustID,
                               &targetTemplate, 1);
            if (pk11_mergeTrustEntry(&targetTemplate, &sourceTemplate)) {
                /* source wins, write out the source attribute to the target */
                SECStatus lrv = pk11_setAttributes(targetSlot, targetTrustID,
                                                   &sourceTemplate, 1);
                if (lrv != SECSuccess) {
                    rv = SECFailure;
                    error = PORT_GetError();
                }
            }
        }

        /* handle step */
        sourceTemplate.type = CKA_TRUST_STEP_UP_APPROVED;
        sourceTemplate.pValue = NULL;
        sourceTemplate.ulValueLen = 0;

        /* if the source has steup set, then set it in the target */
        PK11_GetAttributes(arena, sourceSlot, id, &sourceTemplate, 1);
        if ((sourceTemplate.ulValueLen == sizeof(CK_BBOOL)) &&
            (sourceTemplate.pValue) &&
            (*(CK_BBOOL *)sourceTemplate.pValue == CK_TRUE)) {
            SECStatus lrv = pk11_setAttributes(targetSlot, targetTrustID,
                                               &sourceTemplate, 1);
            if (lrv != SECSuccess) {
                rv = SECFailure;
                error = PORT_GetError();
            }
        }

        goto done;
    }

    /* load the new trust Record into the target token. */
    rv = pk11_copyAttributes(arena, targetSlot, targetTrustID, sourceSlot, id,
                             trustCopyTemplate, trustCopyTemplateCount);
done:
    if (arena) {
        PORT_FreeArena(arena, PR_FALSE);
    }

    /* restore the error code */
    if (rv == SECFailure && error) {
        PORT_SetError(error);
    }

    return rv;
}

/*************************************************************************
 *
 *            Central merge code
 *
 *************************************************************************/
/*
 * merge a single object from sourceToken to targetToken
 */
static SECStatus
pk11_mergeObject(PK11SlotInfo *targetSlot, PK11SlotInfo *sourceSlot,
                 CK_OBJECT_HANDLE id, void *targetPwArg, void *sourcePwArg)
{

    CK_OBJECT_CLASS objClass;

    objClass = PK11_ReadULongAttribute(sourceSlot, id, CKA_CLASS);
    if (objClass == (CK_ULONG)-1) {
        PORT_SetError(SEC_ERROR_UNKNOWN_OBJECT_TYPE);
        return SECFailure;
    }

    switch (objClass) {
        case CKO_CERTIFICATE:
            return pk11_mergeCert(targetSlot, sourceSlot, id,
                                  targetPwArg, sourcePwArg);
        case CKO_NSS_TRUST:
            return pk11_mergeTrust(targetSlot, sourceSlot, id,
                                   targetPwArg, sourcePwArg);
        case CKO_PUBLIC_KEY:
            return pk11_mergePublicKey(targetSlot, sourceSlot, id,
                                       targetPwArg, sourcePwArg);
        case CKO_PRIVATE_KEY:
            return pk11_mergePrivateKey(targetSlot, sourceSlot, id,
                                        targetPwArg, sourcePwArg);
        case CKO_SECRET_KEY:
            return pk11_mergeSecretKey(targetSlot, sourceSlot, id,
                                       targetPwArg, sourcePwArg);
        case CKO_NSS_CRL:
            return pk11_mergeCrl(targetSlot, sourceSlot, id,
                                 targetPwArg, sourcePwArg);
        case CKO_NSS_SMIME:
            return pk11_mergeSmime(targetSlot, sourceSlot, id,
                                   targetPwArg, sourcePwArg);
        default:
            break;
    }

    PORT_SetError(SEC_ERROR_UNKNOWN_OBJECT_TYPE);
    return SECFailure;
}

PK11MergeLogNode *
pk11_newMergeLogNode(PLArenaPool *arena,
                     PK11SlotInfo *slot, CK_OBJECT_HANDLE id, int error)
{
    PK11MergeLogNode *newLog;
    PK11GenericObject *obj;

    newLog = PORT_ArenaZNew(arena, PK11MergeLogNode);
    if (newLog == NULL) {
        return NULL;
    }

    obj = PORT_ArenaZNew(arena, PK11GenericObject);
    if (!obj) {
        return NULL;
    }

    /* initialize it */
    obj->slot = slot;
    obj->objectID = id;
    obj->owner = PR_FALSE;

    newLog->object = obj;
    newLog->error = error;
    return newLog;
}

/*
 * walk down each entry and merge it. keep track of the errors in the log
 */
static SECStatus
pk11_mergeByObjectIDs(PK11SlotInfo *targetSlot, PK11SlotInfo *sourceSlot,
                      CK_OBJECT_HANDLE *objectIDs, int count,
                      PK11MergeLog *log, void *targetPwArg, void *sourcePwArg)
{
    SECStatus rv = SECSuccess;
    int error = SEC_ERROR_LIBRARY_FAILURE;
    int i;

    for (i = 0; i < count; i++) {
        /* try to update the entire database. On failure, keep going,
         * but remember the error to report back to the caller */
        SECStatus lrv;
        PK11MergeLogNode *newLog;

        lrv = pk11_mergeObject(targetSlot, sourceSlot, objectIDs[i],
                               targetPwArg, sourcePwArg);
        if (lrv == SECSuccess) {
            /* merged with no problem, go to next object */
            continue;
        }

        /* remember that we failed and why */
        rv = SECFailure;
        error = PORT_GetError();

        /* log the errors */
        if (!log) {
            /* not logging, go to next entry */
            continue;
        }
        newLog = pk11_newMergeLogNode(log->arena, sourceSlot,
                                      objectIDs[i], error);
        if (!newLog) {
            /* failed to allocate entry, just keep going */
            continue;
        }

        /* link in the errorlog entry */
        newLog->next = NULL;
        if (log->tail) {
            log->tail->next = newLog;
        } else {
            log->head = newLog;
        }
        newLog->prev = log->tail;
        log->tail = newLog;
    }

    /* restore the last error code */
    if (rv != SECSuccess) {
        PORT_SetError(error);
    }
    return rv;
}

/*
 * Merge all the records in sourceSlot that aren't in targetSlot
 *
 *   This function will return failure if not all the objects
 *   successfully merged.
 *
 *   Applications can pass in an optional error log which will record
 *   each failing object and why it failed to import. PK11MergeLog
 *   is modelled after the CERTVerifyLog.
 */
SECStatus
PK11_MergeTokens(PK11SlotInfo *targetSlot, PK11SlotInfo *sourceSlot,
                 PK11MergeLog *log, void *targetPwArg, void *sourcePwArg)
{
    SECStatus rv = SECSuccess, lrv = SECSuccess;
    int error = SEC_ERROR_LIBRARY_FAILURE;
    int count = 0;
    CK_ATTRIBUTE search[2];
    CK_OBJECT_HANDLE *objectIDs = NULL;
    CK_BBOOL ck_true = CK_TRUE;
    CK_OBJECT_CLASS privKey = CKO_PRIVATE_KEY;

    PK11_SETATTRS(&search[0], CKA_TOKEN, &ck_true, sizeof(ck_true));
    PK11_SETATTRS(&search[1], CKA_CLASS, &privKey, sizeof(privKey));
    /*
     * make sure both tokens are already authenticated if need be.
     */
    rv = PK11_Authenticate(targetSlot, PR_TRUE, targetPwArg);
    if (rv != SECSuccess) {
        goto loser;
    }
    rv = PK11_Authenticate(sourceSlot, PR_TRUE, sourcePwArg);
    if (rv != SECSuccess) {
        goto loser;
    }

    /* turns out the old DB's are rather fragile if the private keys aren't
     * merged in first, so do the private keys explicity. */
    objectIDs = pk11_FindObjectsByTemplate(sourceSlot, search, 2, &count);
    if (objectIDs) {
        lrv = pk11_mergeByObjectIDs(targetSlot, sourceSlot,
                                    objectIDs, count, log,
                                    targetPwArg, sourcePwArg);
        if (lrv != SECSuccess) {
            error = PORT_GetError();
        }
        PORT_Free(objectIDs);
        count = 0;
    }

    /* now do the rest  (NOTE: this will repeat the private keys, but
     * that shouldnt' be an issue as we will notice they are already
     * merged in */
    objectIDs = pk11_FindObjectsByTemplate(sourceSlot, search, 1, &count);
    if (!objectIDs) {
        rv = SECFailure;
        goto loser;
    }

    rv = pk11_mergeByObjectIDs(targetSlot, sourceSlot, objectIDs, count, log,
                               targetPwArg, sourcePwArg);
    if (rv == SECSuccess) {
        /* if private keys failed, but the rest succeeded, be sure to let
         * the caller know that private keys failed and why.
         * NOTE: this is highly unlikely since the same keys that failed
         * in the previous merge call will most likely fail in this one */
        if (lrv != SECSuccess) {
            rv = lrv;
            PORT_SetError(error);
        }
    }

loser:
    if (objectIDs) {
        PORT_Free(objectIDs);
    }
    return rv;
}

PK11MergeLog *
PK11_CreateMergeLog(void)
{
    PLArenaPool *arena;
    PK11MergeLog *log;

    arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
    if (arena == NULL) {
        return NULL;
    }

    log = PORT_ArenaZNew(arena, PK11MergeLog);
    if (log == NULL) {
        PORT_FreeArena(arena, PR_FALSE);
        return NULL;
    }
    log->arena = arena;
    log->version = 1;
    return log;
}

void
PK11_DestroyMergeLog(PK11MergeLog *log)
{
    if (log && log->arena) {
        PORT_FreeArena(log->arena, PR_FALSE);
    }
}