lib/pkcs12/p12d.c
author EKR <ekr@rtfm.com>
Fri, 02 Dec 2016 16:32:09 +0100
changeset 12940 433172d35f525c82db3eb0b583f40e93a7d3a7c8
parent 12774 48fe9657a2034bc52638ec793ff36cdc44b53891
child 12969 6d66c2c24e4d9d1ad12a7065c55ef1c9fe143057
permissions -rw-r--r--
Bug 1321809 - Only increment the advertised extensions when appending rather than on both passes. r=mt Differential Revision: https://nss-review.dev.mozaws.net/D111

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

#include "nssrenam.h"
#include "p12t.h"
#include "p12.h"
#include "plarena.h"
#include "secitem.h"
#include "secoid.h"
#include "seccomon.h"
#include "secport.h"
#include "cert.h"
#include "secpkcs7.h"
#include "secasn1.h"
#include "secerr.h"
#include "pk11func.h"
#include "p12plcy.h"
#include "p12local.h"
#include "secder.h"
#include "secport.h"

#include "certdb.h"

#include "prcpucfg.h"

/* This belongs in secport.h */
#define PORT_ArenaGrowArray(poolp, oldptr, type, oldnum, newnum) \
    (type *)PORT_ArenaGrow((poolp), (oldptr),                    \
                           (oldnum) * sizeof(type), (newnum) * sizeof(type))

typedef struct sec_PKCS12SafeContentsContextStr sec_PKCS12SafeContentsContext;

/* Opaque structure for decoding SafeContents.  These are used
 * for each authenticated safe as well as any nested safe contents.
 */
struct sec_PKCS12SafeContentsContextStr {
    /* the parent decoder context */
    SEC_PKCS12DecoderContext *p12dcx;

    /* memory arena to allocate space from */
    PLArenaPool *arena;

    /* decoder context and destination for decoding safe contents */
    SEC_ASN1DecoderContext *safeContentsA1Dcx;
    sec_PKCS12SafeContents safeContents;

    /* information for decoding safe bags within the safe contents.
     * these variables are updated for each safe bag decoded.
     */
    SEC_ASN1DecoderContext *currentSafeBagA1Dcx;
    sec_PKCS12SafeBag *currentSafeBag;
    PRBool skipCurrentSafeBag;

    /* if the safe contents is nested, the parent is pointed to here. */
    sec_PKCS12SafeContentsContext *nestedSafeContentsCtx;
};

/* opaque decoder context structure.  information for decoding a pkcs 12
 * PDU are stored here as well as decoding pointers for intermediary
 * structures which are part of the PKCS 12 PDU.  Upon a successful
 * decode, the safe bags containing certificates and keys encountered.
 */
struct SEC_PKCS12DecoderContextStr {
    PLArenaPool *arena;
    PK11SlotInfo *slot;
    void *wincx;
    PRBool error;
    int errorValue;

    /* password */
    SECItem *pwitem;

    /* used for decoding the PFX structure */
    SEC_ASN1DecoderContext *pfxA1Dcx;
    sec_PKCS12PFXItem pfx;

    /* safe bags found during decoding */
    sec_PKCS12SafeBag **safeBags;
    unsigned int safeBagCount;

    /* state variables for decoding authenticated safes. */
    SEC_PKCS7DecoderContext *currentASafeP7Dcx;
    SEC_ASN1DecoderContext *aSafeA1Dcx;
    SEC_PKCS7DecoderContext *aSafeP7Dcx;
    SEC_PKCS7ContentInfo *aSafeCinfo;
    sec_PKCS12AuthenticatedSafe authSafe;
    sec_PKCS12SafeContents safeContents;

    /* safe contents info */
    unsigned int safeContentsCnt;
    sec_PKCS12SafeContentsContext **safeContentsList;

    /* HMAC info */
    sec_PKCS12MacData macData;

    /* routines for reading back the data to be hmac'd */
    /* They are called as follows.
     *
     * Stage 1: decode the aSafes cinfo into a buffer in dArg,
     * which p12d.c sometimes refers to as the "temp file".
     * This occurs during SEC_PKCS12DecoderUpdate calls.
     *
     * dOpen(dArg, PR_FALSE)
     * dWrite(dArg, buf, len)
     * ...
     * dWrite(dArg, buf, len)
     * dClose(dArg, PR_FALSE)
     *
     * Stage 2: verify MAC
     * This occurs SEC_PKCS12DecoderVerify.
     *
     * dOpen(dArg, PR_TRUE)
     * dRead(dArg, buf, IN_BUF_LEN)
     * ...
     * dRead(dArg, buf, IN_BUF_LEN)
     * dClose(dArg, PR_TRUE)
     */
    digestOpenFn dOpen;
    digestCloseFn dClose;
    digestIOFn dRead, dWrite;
    void *dArg;
    PRBool dIsOpen; /* is the temp file created? */

    /* helper functions */
    SECKEYGetPasswordKey pwfn;
    void *pwfnarg;
    PRBool swapUnicodeBytes;

    /* import information */
    PRBool bagsVerified;

    /* buffer management for the default callbacks implementation */
    void *buffer;       /* storage area */
    PRInt32 filesize;   /* actual data size */
    PRInt32 allocated;  /* total buffer size allocated */
    PRInt32 currentpos; /* position counter */
    SECPKCS12TargetTokenCAs tokenCAs;
    sec_PKCS12SafeBag **keyList; /* used by ...IterateNext() */
    unsigned int iteration;
    SEC_PKCS12DecoderItem decitem;
};

/* forward declarations of functions that are used when decoding
 * safeContents bags which are nested and when decoding the
 * authenticatedSafes.
 */
static SECStatus
sec_pkcs12_decoder_begin_nested_safe_contents(sec_PKCS12SafeContentsContext
                                                  *safeContentsCtx);
static SECStatus
sec_pkcs12_decoder_finish_nested_safe_contents(sec_PKCS12SafeContentsContext
                                                   *safeContentsCtx);

/* make sure that the PFX version being decoded is a version
 * which we support.
 */
static PRBool
sec_pkcs12_proper_version(sec_PKCS12PFXItem *pfx)
{
    /* if no version, assume it is not supported */
    if (pfx->version.len == 0) {
        return PR_FALSE;
    }

    if (DER_GetInteger(&pfx->version) > SEC_PKCS12_VERSION) {
        return PR_FALSE;
    }

    return PR_TRUE;
}

/* retrieve the key for decrypting the safe contents */
static PK11SymKey *
sec_pkcs12_decoder_get_decrypt_key(void *arg, SECAlgorithmID *algid)
{
    SEC_PKCS12DecoderContext *p12dcx = (SEC_PKCS12DecoderContext *)arg;
    PK11SlotInfo *slot;
    PK11SymKey *bulkKey;

    if (!p12dcx) {
        return NULL;
    }

    /* if no slot specified, use the internal key slot */
    if (p12dcx->slot) {
        slot = PK11_ReferenceSlot(p12dcx->slot);
    } else {
        slot = PK11_GetInternalKeySlot();
    }

    bulkKey = PK11_PBEKeyGen(slot, algid, p12dcx->pwitem,
                             PR_FALSE, p12dcx->wincx);
    /* some tokens can't generate PBE keys on their own, generate the
     * key in the internal slot, and let the Import code deal with it,
     * (if the slot can't generate PBEs, then we need to use the internal
     * slot anyway to unwrap). */
    if (!bulkKey && !PK11_IsInternal(slot)) {
        PK11_FreeSlot(slot);
        slot = PK11_GetInternalKeySlot();
        bulkKey = PK11_PBEKeyGen(slot, algid, p12dcx->pwitem,
                                 PR_FALSE, p12dcx->wincx);
    }
    PK11_FreeSlot(slot);

    /* set the password data on the key */
    if (bulkKey) {
        PK11_SetSymKeyUserData(bulkKey, p12dcx->pwitem, NULL);
    }

    return bulkKey;
}

/* XXX this needs to be modified to handle enveloped data.  most
 * likely, it should mirror the routines for SMIME in that regard.
 */
static PRBool
sec_pkcs12_decoder_decryption_allowed(SECAlgorithmID *algid,
                                      PK11SymKey *bulkkey)
{
    PRBool decryptionAllowed = SEC_PKCS12DecryptionAllowed(algid);

    if (!decryptionAllowed) {
        return PR_FALSE;
    }

    return PR_TRUE;
}

/* when we encounter a new safe bag during the decoding, we need
 * to allocate space for the bag to be decoded to and set the
 * state variables appropriately.  all of the safe bags are allocated
 * in a buffer in the outer SEC_PKCS12DecoderContext, however,
 * a pointer to the safeBag is also used in the sec_PKCS12SafeContentsContext
 * for the current bag.
 */
static SECStatus
sec_pkcs12_decoder_init_new_safe_bag(sec_PKCS12SafeContentsContext
                                         *safeContentsCtx)
{
    void *mark = NULL;
    SEC_PKCS12DecoderContext *p12dcx;

    /* make sure that the structures are defined, and there has
     * not been an error in the decoding
     */
    if (!safeContentsCtx || !safeContentsCtx->p12dcx || safeContentsCtx->p12dcx->error) {
        return SECFailure;
    }

    p12dcx = safeContentsCtx->p12dcx;
    mark = PORT_ArenaMark(p12dcx->arena);

    /* allocate a new safe bag, if bags already exist, grow the
     * list of bags, otherwise allocate a new list.  the list is
     * NULL terminated.
     */
    p12dcx->safeBags = (!p12dcx->safeBagCount)
                           ? PORT_ArenaZNewArray(p12dcx->arena, sec_PKCS12SafeBag *, 2)
                           : PORT_ArenaGrowArray(p12dcx->arena, p12dcx->safeBags,
                                                 sec_PKCS12SafeBag *, p12dcx->safeBagCount + 1,
                                                 p12dcx->safeBagCount + 2);

    if (!p12dcx->safeBags) {
        p12dcx->errorValue = PORT_GetError();
        goto loser;
    }

    /* append the bag to the end of the list and update the reference
     * in the safeContentsCtx.
     */
    p12dcx->safeBags[p12dcx->safeBagCount] =
        safeContentsCtx->currentSafeBag =
            PORT_ArenaZNew(p12dcx->arena, sec_PKCS12SafeBag);
    if (!safeContentsCtx->currentSafeBag) {
        p12dcx->errorValue = PORT_GetError();
        goto loser;
    }
    p12dcx->safeBags[++p12dcx->safeBagCount] = NULL;

    safeContentsCtx->currentSafeBag->slot = safeContentsCtx->p12dcx->slot;
    safeContentsCtx->currentSafeBag->pwitem = safeContentsCtx->p12dcx->pwitem;
    safeContentsCtx->currentSafeBag->swapUnicodeBytes =
        safeContentsCtx->p12dcx->swapUnicodeBytes;
    safeContentsCtx->currentSafeBag->arena = safeContentsCtx->p12dcx->arena;
    safeContentsCtx->currentSafeBag->tokenCAs =
        safeContentsCtx->p12dcx->tokenCAs;

    PORT_ArenaUnmark(p12dcx->arena, mark);
    return SECSuccess;

loser:

    /* if an error occurred, release the memory and set the error flag
     * the only possible errors triggered by this function are memory
     * related.
     */
    if (mark) {
        PORT_ArenaRelease(p12dcx->arena, mark);
    }

    p12dcx->error = PR_TRUE;
    return SECFailure;
}

/* A wrapper for updating the ASN1 context in which a safeBag is
 * being decoded.  This function is called as a callback from
 * secasn1d when decoding SafeContents structures.
 */
static void
sec_pkcs12_decoder_safe_bag_update(void *arg, const char *data,
                                   unsigned long len, int depth,
                                   SEC_ASN1EncodingPart data_kind)
{
    sec_PKCS12SafeContentsContext *safeContentsCtx =
        (sec_PKCS12SafeContentsContext *)arg;
    SEC_PKCS12DecoderContext *p12dcx;
    SECStatus rv;

    /* make sure that we are not skipping the current safeBag,
     * and that there are no errors.  If so, just return rather
     * than continuing to process.
     */
    if (!safeContentsCtx || !safeContentsCtx->p12dcx ||
        safeContentsCtx->p12dcx->error || safeContentsCtx->skipCurrentSafeBag) {
        return;
    }
    p12dcx = safeContentsCtx->p12dcx;

    rv = SEC_ASN1DecoderUpdate(safeContentsCtx->currentSafeBagA1Dcx, data, len);
    if (rv != SECSuccess) {
        p12dcx->errorValue = PORT_GetError();
        goto loser;
    }

    return;

loser:
    /* set the error, and finish the decoder context.  because there
     * is not a way of returning an error message, it may be worth
     * while to do a check higher up and finish any decoding contexts
     * that are still open.
     */
    p12dcx->error = PR_TRUE;
    SEC_ASN1DecoderFinish(safeContentsCtx->currentSafeBagA1Dcx);
    safeContentsCtx->currentSafeBagA1Dcx = NULL;
    return;
}

/* notify function for decoding safeBags.  This function is
 * used to filter safeBag types which are not supported,
 * initiate the decoding of nested safe contents, and decode
 * safeBags in general.  this function is set when the decoder
 * context for the safeBag is first created.
 */
static void
sec_pkcs12_decoder_safe_bag_notify(void *arg, PRBool before,
                                   void *dest, int real_depth)
{
    sec_PKCS12SafeContentsContext *safeContentsCtx =
        (sec_PKCS12SafeContentsContext *)arg;
    SEC_PKCS12DecoderContext *p12dcx;
    sec_PKCS12SafeBag *bag;
    PRBool after;

    /* if an error is encountered, return */
    if (!safeContentsCtx || !safeContentsCtx->p12dcx ||
        safeContentsCtx->p12dcx->error) {
        return;
    }
    p12dcx = safeContentsCtx->p12dcx;

    /* to make things more readable */
    if (before)
        after = PR_FALSE;
    else
        after = PR_TRUE;

    /* have we determined the safeBagType yet? */
    bag = safeContentsCtx->currentSafeBag;
    if (bag->bagTypeTag == NULL) {
        if (after && (dest == &(bag->safeBagType))) {
            bag->bagTypeTag = SECOID_FindOID(&(bag->safeBagType));
            if (bag->bagTypeTag == NULL) {
                p12dcx->error = PR_TRUE;
                p12dcx->errorValue = SEC_ERROR_PKCS12_CORRUPT_PFX_STRUCTURE;
            }
        }
        return;
    }

    /* process the safeBag depending on it's type.  those
     * which we do not support, are ignored.  we start a decoding
     * context for a nested safeContents.
     */
    switch (bag->bagTypeTag->offset) {
        case SEC_OID_PKCS12_V1_KEY_BAG_ID:
        case SEC_OID_PKCS12_V1_CERT_BAG_ID:
        case SEC_OID_PKCS12_V1_PKCS8_SHROUDED_KEY_BAG_ID:
            break;
        case SEC_OID_PKCS12_V1_SAFE_CONTENTS_BAG_ID:
            /* if we are just starting to decode the safeContents, initialize
             * a new safeContentsCtx to process it.
             */
            if (before && (dest == &(bag->safeBagContent))) {
                sec_pkcs12_decoder_begin_nested_safe_contents(safeContentsCtx);
            } else if (after && (dest == &(bag->safeBagContent))) {
                /* clean up the nested decoding */
                sec_pkcs12_decoder_finish_nested_safe_contents(safeContentsCtx);
            }
            break;
        case SEC_OID_PKCS12_V1_CRL_BAG_ID:
        case SEC_OID_PKCS12_V1_SECRET_BAG_ID:
        default:
            /* skip any safe bag types we don't understand or handle */
            safeContentsCtx->skipCurrentSafeBag = PR_TRUE;
            break;
    }

    return;
}

/* notify function for decoding safe contents.  each entry in the
 * safe contents is a safeBag which needs to be allocated and
 * the decoding context initialized at the beginning and then
 * the context needs to be closed and finished at the end.
 *
 * this function is set when the safeContents decode context is
 * initialized.
 */
static void
sec_pkcs12_decoder_safe_contents_notify(void *arg, PRBool before,
                                        void *dest, int real_depth)
{
    sec_PKCS12SafeContentsContext *safeContentsCtx =
        (sec_PKCS12SafeContentsContext *)arg;
    SEC_PKCS12DecoderContext *p12dcx;
    SECStatus rv;

    /* if there is an error we don't want to continue processing,
     * just return and keep going.
     */
    if (!safeContentsCtx || !safeContentsCtx->p12dcx ||
        safeContentsCtx->p12dcx->error) {
        return;
    }
    p12dcx = safeContentsCtx->p12dcx;

    /* if we are done with the current safeBag, then we need to
     * finish the context and set the state variables appropriately.
     */
    if (!before) {
        SEC_ASN1DecoderClearFilterProc(safeContentsCtx->safeContentsA1Dcx);
        SEC_ASN1DecoderFinish(safeContentsCtx->currentSafeBagA1Dcx);
        safeContentsCtx->currentSafeBagA1Dcx = NULL;
        safeContentsCtx->skipCurrentSafeBag = PR_FALSE;
    } else {
        /* we are starting a new safe bag.  we need to allocate space
         * for the bag and initialize the decoding context.
         */
        rv = sec_pkcs12_decoder_init_new_safe_bag(safeContentsCtx);
        if (rv != SECSuccess) {
            goto loser;
        }

        /* set up the decoder context */
        safeContentsCtx->currentSafeBagA1Dcx =
            SEC_ASN1DecoderStart(p12dcx->arena,
                                 safeContentsCtx->currentSafeBag,
                                 sec_PKCS12SafeBagTemplate);
        if (!safeContentsCtx->currentSafeBagA1Dcx) {
            p12dcx->errorValue = PORT_GetError();
            goto loser;
        }

        /* set the notify and filter procs so that the safe bag
         * data gets sent to the proper location when decoding.
         */
        SEC_ASN1DecoderSetNotifyProc(safeContentsCtx->currentSafeBagA1Dcx,
                                     sec_pkcs12_decoder_safe_bag_notify,
                                     safeContentsCtx);
        SEC_ASN1DecoderSetFilterProc(safeContentsCtx->safeContentsA1Dcx,
                                     sec_pkcs12_decoder_safe_bag_update,
                                     safeContentsCtx, PR_TRUE);
    }

    return;

loser:
    /* in the event of an error, we want to close the decoding
     * context and clear the filter and notify procedures.
     */
    p12dcx->error = PR_TRUE;

    if (safeContentsCtx->currentSafeBagA1Dcx) {
        SEC_ASN1DecoderFinish(safeContentsCtx->currentSafeBagA1Dcx);
        safeContentsCtx->currentSafeBagA1Dcx = NULL;
    }

    SEC_ASN1DecoderClearNotifyProc(safeContentsCtx->safeContentsA1Dcx);
    SEC_ASN1DecoderClearFilterProc(safeContentsCtx->safeContentsA1Dcx);

    return;
}

/* initialize the safeContents for decoding.  this routine
 * is used for authenticatedSafes as well as nested safeContents.
 */
static sec_PKCS12SafeContentsContext *
sec_pkcs12_decoder_safe_contents_init_decode(SEC_PKCS12DecoderContext *p12dcx,
                                             PRBool nestedSafe)
{
    sec_PKCS12SafeContentsContext *safeContentsCtx = NULL;
    const SEC_ASN1Template *theTemplate;

    if (!p12dcx || p12dcx->error) {
        return NULL;
    }

    /* allocate a new safeContents list or grow the existing list and
     * append the new safeContents onto the end.
     */
    p12dcx->safeContentsList = (!p12dcx->safeContentsCnt)
                                   ? PORT_ArenaZNewArray(p12dcx->arena, sec_PKCS12SafeContentsContext *, 2)
                                   : PORT_ArenaGrowArray(p12dcx->arena, p12dcx->safeContentsList,
                                                         sec_PKCS12SafeContentsContext *,
                                                         1 + p12dcx->safeContentsCnt,
                                                         2 + p12dcx->safeContentsCnt);

    if (!p12dcx->safeContentsList) {
        p12dcx->errorValue = PORT_GetError();
        goto loser;
    }

    p12dcx->safeContentsList[p12dcx->safeContentsCnt] = safeContentsCtx =
        PORT_ArenaZNew(p12dcx->arena, sec_PKCS12SafeContentsContext);
    if (!p12dcx->safeContentsList[p12dcx->safeContentsCnt]) {
        p12dcx->errorValue = PORT_GetError();
        goto loser;
    }
    p12dcx->safeContentsList[++p12dcx->safeContentsCnt] = NULL;

    /* set up the state variables */
    safeContentsCtx->p12dcx = p12dcx;
    safeContentsCtx->arena = p12dcx->arena;

    /* begin the decoding -- the template is based on whether we are
     * decoding a nested safeContents or not.
     */
    if (nestedSafe == PR_TRUE) {
        theTemplate = sec_PKCS12NestedSafeContentsDecodeTemplate;
    } else {
        theTemplate = sec_PKCS12SafeContentsDecodeTemplate;
    }

    /* start the decoder context */
    safeContentsCtx->safeContentsA1Dcx = SEC_ASN1DecoderStart(p12dcx->arena,
                                                              &safeContentsCtx->safeContents,
                                                              theTemplate);

    if (!safeContentsCtx->safeContentsA1Dcx) {
        p12dcx->errorValue = PORT_GetError();
        goto loser;
    }

    /* set the safeContents notify procedure to look for
     * and start the decode of safeBags.
     */
    SEC_ASN1DecoderSetNotifyProc(safeContentsCtx->safeContentsA1Dcx,
                                 sec_pkcs12_decoder_safe_contents_notify,
                                 safeContentsCtx);

    return safeContentsCtx;

loser:
    /* in the case of an error, we want to finish the decoder
     * context and set the error flag.
     */
    if (safeContentsCtx && safeContentsCtx->safeContentsA1Dcx) {
        SEC_ASN1DecoderFinish(safeContentsCtx->safeContentsA1Dcx);
        safeContentsCtx->safeContentsA1Dcx = NULL;
    }

    p12dcx->error = PR_TRUE;

    return NULL;
}

/* wrapper for updating safeContents.  this is set as the filter of
 * safeBag when there is a nested safeContents.
 */
static void
sec_pkcs12_decoder_nested_safe_contents_update(void *arg, const char *buf,
                                               unsigned long len, int depth,
                                               SEC_ASN1EncodingPart data_kind)
{
    sec_PKCS12SafeContentsContext *safeContentsCtx =
        (sec_PKCS12SafeContentsContext *)arg;
    SEC_PKCS12DecoderContext *p12dcx;
    SECStatus rv;

    /* check for an error */
    if (!safeContentsCtx || !safeContentsCtx->p12dcx ||
        safeContentsCtx->p12dcx->error || !safeContentsCtx->safeContentsA1Dcx) {
        return;
    }

    /* no need to update if no data sent in */
    if (!len || !buf) {
        return;
    }

    /* update the decoding context */
    p12dcx = safeContentsCtx->p12dcx;
    rv = SEC_ASN1DecoderUpdate(safeContentsCtx->safeContentsA1Dcx, buf, len);
    if (rv != SECSuccess) {
        p12dcx->errorValue = PORT_GetError();
        goto loser;
    }

    return;

loser:
    /* handle any errors.  If a decoding context is open, close it. */
    p12dcx->error = PR_TRUE;
    if (safeContentsCtx->safeContentsA1Dcx) {
        SEC_ASN1DecoderFinish(safeContentsCtx->safeContentsA1Dcx);
        safeContentsCtx->safeContentsA1Dcx = NULL;
    }
}

/* whenever a new safeContentsSafeBag is encountered, we need
 * to init a safeContentsContext.
 */
static SECStatus
sec_pkcs12_decoder_begin_nested_safe_contents(sec_PKCS12SafeContentsContext
                                                  *safeContentsCtx)
{
    /* check for an error */
    if (!safeContentsCtx || !safeContentsCtx->p12dcx ||
        safeContentsCtx->p12dcx->error) {
        return SECFailure;
    }

    safeContentsCtx->nestedSafeContentsCtx =
        sec_pkcs12_decoder_safe_contents_init_decode(safeContentsCtx->p12dcx,
                                                     PR_TRUE);
    if (!safeContentsCtx->nestedSafeContentsCtx) {
        return SECFailure;
    }

    /* set up new filter proc */
    SEC_ASN1DecoderSetNotifyProc(
        safeContentsCtx->nestedSafeContentsCtx->safeContentsA1Dcx,
        sec_pkcs12_decoder_safe_contents_notify,
        safeContentsCtx->nestedSafeContentsCtx);

    SEC_ASN1DecoderSetFilterProc(safeContentsCtx->currentSafeBagA1Dcx,
                                 sec_pkcs12_decoder_nested_safe_contents_update,
                                 safeContentsCtx->nestedSafeContentsCtx,
                                 PR_TRUE);

    return SECSuccess;
}

/* when the safeContents is done decoding, we need to reset the
 * proper filter and notify procs and close the decoding context
 */
static SECStatus
sec_pkcs12_decoder_finish_nested_safe_contents(sec_PKCS12SafeContentsContext
                                                   *safeContentsCtx)
{
    /* check for error */
    if (!safeContentsCtx || !safeContentsCtx->p12dcx ||
        safeContentsCtx->p12dcx->error) {
        return SECFailure;
    }

    /* clean up */
    SEC_ASN1DecoderClearFilterProc(safeContentsCtx->currentSafeBagA1Dcx);
    SEC_ASN1DecoderClearNotifyProc(
        safeContentsCtx->nestedSafeContentsCtx->safeContentsA1Dcx);
    SEC_ASN1DecoderFinish(
        safeContentsCtx->nestedSafeContentsCtx->safeContentsA1Dcx);
    safeContentsCtx->nestedSafeContentsCtx->safeContentsA1Dcx = NULL;
    safeContentsCtx->nestedSafeContentsCtx = NULL;

    return SECSuccess;
}

/* wrapper for updating safeContents.  This is used when decoding
 * the nested safeContents and any authenticatedSafes.
 */
static void
sec_pkcs12_decoder_safe_contents_callback(void *arg, const char *buf,
                                          unsigned long len)
{
    SECStatus rv;
    sec_PKCS12SafeContentsContext *safeContentsCtx =
        (sec_PKCS12SafeContentsContext *)arg;
    SEC_PKCS12DecoderContext *p12dcx;

    /* check for error */
    if (!safeContentsCtx || !safeContentsCtx->p12dcx ||
        safeContentsCtx->p12dcx->error || !safeContentsCtx->safeContentsA1Dcx) {
        return;
    }
    p12dcx = safeContentsCtx->p12dcx;

    /* update the decoder */
    rv = SEC_ASN1DecoderUpdate(safeContentsCtx->safeContentsA1Dcx, buf, len);
    if (rv != SECSuccess) {
        /* if we fail while trying to decode a 'safe', it's probably because
         * we didn't have the correct password. */
        PORT_SetError(SEC_ERROR_BAD_PASSWORD);
        p12dcx->errorValue = SEC_ERROR_PKCS12_CORRUPT_PFX_STRUCTURE;
        SEC_PKCS7DecoderAbort(p12dcx->currentASafeP7Dcx, SEC_ERROR_BAD_PASSWORD);
        goto loser;
    }

    return;

loser:
    /* set the error and finish the context */
    p12dcx->error = PR_TRUE;
    if (safeContentsCtx->safeContentsA1Dcx) {
        SEC_ASN1DecoderFinish(safeContentsCtx->safeContentsA1Dcx);
        safeContentsCtx->safeContentsA1Dcx = NULL;
    }

    return;
}

/* this is a wrapper for the ASN1 decoder to call SEC_PKCS7DecoderUpdate
 */
static void
sec_pkcs12_decoder_wrap_p7_update(void *arg, const char *data,
                                  unsigned long len, int depth,
                                  SEC_ASN1EncodingPart data_kind)
{
    SEC_PKCS7DecoderContext *p7dcx = (SEC_PKCS7DecoderContext *)arg;

    SEC_PKCS7DecoderUpdate(p7dcx, data, len);
}

/* notify function for decoding aSafes.  at the beginning,
 * of an authenticatedSafe, we start a decode of a safeContents.
 * at the end, we clean up the safeContents decoder context and
 * reset state variables
 */
static void
sec_pkcs12_decoder_asafes_notify(void *arg, PRBool before, void *dest,
                                 int real_depth)
{
    SEC_PKCS12DecoderContext *p12dcx;
    sec_PKCS12SafeContentsContext *safeContentsCtx;

    /* make sure no error occurred. */
    p12dcx = (SEC_PKCS12DecoderContext *)arg;
    if (!p12dcx || p12dcx->error) {
        return;
    }

    if (before) {

        /* init a new safeContentsContext */
        safeContentsCtx = sec_pkcs12_decoder_safe_contents_init_decode(p12dcx,
                                                                       PR_FALSE);
        if (!safeContentsCtx) {
            goto loser;
        }

        /* initiate the PKCS7ContentInfo decode */
        p12dcx->currentASafeP7Dcx = SEC_PKCS7DecoderStart(
            sec_pkcs12_decoder_safe_contents_callback,
            safeContentsCtx,
            p12dcx->pwfn, p12dcx->pwfnarg,
            sec_pkcs12_decoder_get_decrypt_key, p12dcx,
            sec_pkcs12_decoder_decryption_allowed);
        if (!p12dcx->currentASafeP7Dcx) {
            p12dcx->errorValue = PORT_GetError();
            goto loser;
        }
        SEC_ASN1DecoderSetFilterProc(p12dcx->aSafeA1Dcx,
                                     sec_pkcs12_decoder_wrap_p7_update,
                                     p12dcx->currentASafeP7Dcx, PR_TRUE);
    }

    if (!before) {
        /* if one is being decoded, finish the decode */
        if (p12dcx->currentASafeP7Dcx != NULL) {
            SEC_PKCS7ContentInfo *cinfo;
            unsigned int cnt = p12dcx->safeContentsCnt - 1;
            safeContentsCtx = p12dcx->safeContentsList[cnt];
            if (safeContentsCtx->safeContentsA1Dcx) {
                SEC_ASN1DecoderFinish(safeContentsCtx->safeContentsA1Dcx);
                safeContentsCtx->safeContentsA1Dcx = NULL;
            }
            cinfo = SEC_PKCS7DecoderFinish(p12dcx->currentASafeP7Dcx);
            p12dcx->currentASafeP7Dcx = NULL;
            if (!cinfo) {
                p12dcx->errorValue = PORT_GetError();
                goto loser;
            }
            SEC_PKCS7DestroyContentInfo(cinfo); /* don't leak it */
        }
    }

    return;

loser:
    /* set the error flag */
    p12dcx->error = PR_TRUE;
    return;
}

/* wrapper for updating asafes decoding context.  this function
 * writes data being decoded to disk, so that a mac can be computed
 * later.
 */
static void
sec_pkcs12_decoder_asafes_callback(void *arg, const char *buf,
                                   unsigned long len)
{
    SEC_PKCS12DecoderContext *p12dcx = (SEC_PKCS12DecoderContext *)arg;
    SECStatus rv;

    if (!p12dcx || p12dcx->error) {
        return;
    }

    /* update the context */
    rv = SEC_ASN1DecoderUpdate(p12dcx->aSafeA1Dcx, buf, len);
    if (rv != SECSuccess) {
        p12dcx->errorValue = PORT_GetError();
        p12dcx->error = PR_TRUE;
        goto loser;
    }

    /* if we are writing to a file, write out the new information */
    if (p12dcx->dWrite) {
        unsigned long writeLen = (*p12dcx->dWrite)(p12dcx->dArg,
                                                   (unsigned char *)buf, len);
        if (writeLen != len) {
            p12dcx->errorValue = PORT_GetError();
            goto loser;
        }
    }

    return;

loser:
    /* set the error flag */
    p12dcx->error = PR_TRUE;
    SEC_ASN1DecoderFinish(p12dcx->aSafeA1Dcx);
    p12dcx->aSafeA1Dcx = NULL;

    return;
}

/* start the decode of an authenticatedSafe contentInfo.
 */
static SECStatus
sec_pkcs12_decode_start_asafes_cinfo(SEC_PKCS12DecoderContext *p12dcx)
{
    if (!p12dcx || p12dcx->error) {
        return SECFailure;
    }

    /* start the decode context */
    p12dcx->aSafeA1Dcx = SEC_ASN1DecoderStart(p12dcx->arena,
                                              &p12dcx->authSafe,
                                              sec_PKCS12AuthenticatedSafeTemplate);
    if (!p12dcx->aSafeA1Dcx) {
        p12dcx->errorValue = PORT_GetError();
        goto loser;
    }

    /* set the notify function */
    SEC_ASN1DecoderSetNotifyProc(p12dcx->aSafeA1Dcx,
                                 sec_pkcs12_decoder_asafes_notify, p12dcx);

    /* begin the authSafe decoder context */
    p12dcx->aSafeP7Dcx = SEC_PKCS7DecoderStart(
        sec_pkcs12_decoder_asafes_callback, p12dcx,
        p12dcx->pwfn, p12dcx->pwfnarg, NULL, NULL, NULL);
    if (!p12dcx->aSafeP7Dcx) {
        p12dcx->errorValue = PORT_GetError();
        goto loser;
    }

    /* open the temp file for writing, if the digest functions were set */
    if (p12dcx->dOpen && (*p12dcx->dOpen)(p12dcx->dArg, PR_FALSE) != SECSuccess) {
        p12dcx->errorValue = PORT_GetError();
        goto loser;
    }
    /* dOpen(dArg, PR_FALSE) creates the temp file */
    p12dcx->dIsOpen = PR_TRUE;

    return SECSuccess;

loser:
    p12dcx->error = PR_TRUE;

    if (p12dcx->aSafeA1Dcx) {
        SEC_ASN1DecoderFinish(p12dcx->aSafeA1Dcx);
        p12dcx->aSafeA1Dcx = NULL;
    }

    if (p12dcx->aSafeP7Dcx) {
        SEC_PKCS7DecoderFinish(p12dcx->aSafeP7Dcx);
        p12dcx->aSafeP7Dcx = NULL;
    }

    return SECFailure;
}

/* wrapper for updating the safeContents.  this function is used as
 * a filter for the pfx when decoding the authenticated safes
 */
static void
sec_pkcs12_decode_asafes_cinfo_update(void *arg, const char *buf,
                                      unsigned long len, int depth,
                                      SEC_ASN1EncodingPart data_kind)
{
    SEC_PKCS12DecoderContext *p12dcx;
    SECStatus rv;

    p12dcx = (SEC_PKCS12DecoderContext *)arg;
    if (!p12dcx || p12dcx->error) {
        return;
    }

    /* update the safeContents decoder */
    rv = SEC_PKCS7DecoderUpdate(p12dcx->aSafeP7Dcx, buf, len);
    if (rv != SECSuccess) {
        p12dcx->errorValue = SEC_ERROR_PKCS12_CORRUPT_PFX_STRUCTURE;
        goto loser;
    }

    return;

loser:

    /* did we find an error?  if so, close the context and set the
     * error flag.
     */
    SEC_PKCS7DecoderFinish(p12dcx->aSafeP7Dcx);
    p12dcx->aSafeP7Dcx = NULL;
    p12dcx->error = PR_TRUE;
}

/* notify procedure used while decoding the pfx.  When we encounter
 * the authSafes, we want to trigger the decoding of authSafes as well
 * as when we encounter the macData, trigger the decoding of it.  we do
 * this because we we are streaming the decoder and not decoding in place.
 * the pfx which is the destination, only has the version decoded into it.
 */
static void
sec_pkcs12_decoder_pfx_notify_proc(void *arg, PRBool before, void *dest,
                                   int real_depth)
{
    SECStatus rv;
    SEC_PKCS12DecoderContext *p12dcx = (SEC_PKCS12DecoderContext *)arg;

    /* if an error occurs, clear the notifyProc and the filterProc
     * and continue.
     */
    if (p12dcx->error) {
        SEC_ASN1DecoderClearNotifyProc(p12dcx->pfxA1Dcx);
        SEC_ASN1DecoderClearFilterProc(p12dcx->pfxA1Dcx);
        return;
    }

    if (before && (dest == &p12dcx->pfx.encodedAuthSafe)) {

        /* we want to make sure this is a version we support */
        if (!sec_pkcs12_proper_version(&p12dcx->pfx)) {
            p12dcx->errorValue = SEC_ERROR_PKCS12_UNSUPPORTED_VERSION;
            goto loser;
        }

        /* start the decode of the aSafes cinfo... */
        rv = sec_pkcs12_decode_start_asafes_cinfo(p12dcx);
        if (rv != SECSuccess) {
            goto loser;
        }

        /* set the filter proc to update the authenticated safes. */
        SEC_ASN1DecoderSetFilterProc(p12dcx->pfxA1Dcx,
                                     sec_pkcs12_decode_asafes_cinfo_update,
                                     p12dcx, PR_TRUE);
    }

    if (!before && (dest == &p12dcx->pfx.encodedAuthSafe)) {

        /* we are done decoding the authenticatedSafes, so we need to
         * finish the decoderContext and clear the filter proc
         * and close the hmac callback, if present
         */
        p12dcx->aSafeCinfo = SEC_PKCS7DecoderFinish(p12dcx->aSafeP7Dcx);
        p12dcx->aSafeP7Dcx = NULL;
        if (!p12dcx->aSafeCinfo) {
            p12dcx->errorValue = PORT_GetError();
            goto loser;
        }
        SEC_ASN1DecoderClearFilterProc(p12dcx->pfxA1Dcx);
        if (p12dcx->dClose && ((*p12dcx->dClose)(p12dcx->dArg, PR_FALSE) != SECSuccess)) {
            p12dcx->errorValue = PORT_GetError();
            goto loser;
        }
    }

    return;

loser:
    p12dcx->error = PR_TRUE;
}

/*  default implementations of the open/close/read/write functions for
    SEC_PKCS12DecoderStart
*/

#define DEFAULT_TEMP_SIZE 4096

static SECStatus
p12u_DigestOpen(void *arg, PRBool readData)
{
    SEC_PKCS12DecoderContext *p12cxt = arg;

    p12cxt->currentpos = 0;

    if (PR_FALSE == readData) {
        /* allocate an initial buffer */
        p12cxt->filesize = 0;
        p12cxt->allocated = DEFAULT_TEMP_SIZE;
        p12cxt->buffer = PORT_Alloc(DEFAULT_TEMP_SIZE);
        PR_ASSERT(p12cxt->buffer);
    } else {
        PR_ASSERT(p12cxt->buffer);
        if (!p12cxt->buffer) {
            return SECFailure; /* no data to read */
        }
    }

    return SECSuccess;
}

static SECStatus
p12u_DigestClose(void *arg, PRBool removeFile)
{
    SEC_PKCS12DecoderContext *p12cxt = arg;

    PR_ASSERT(p12cxt);
    if (!p12cxt) {
        return SECFailure;
    }
    p12cxt->currentpos = 0;

    if (PR_TRUE == removeFile) {
        PR_ASSERT(p12cxt->buffer);
        if (!p12cxt->buffer) {
            return SECFailure;
        }
        if (p12cxt->buffer) {
            PORT_Free(p12cxt->buffer);
            p12cxt->buffer = NULL;
            p12cxt->allocated = 0;
            p12cxt->filesize = 0;
        }
    }

    return SECSuccess;
}

static int
p12u_DigestRead(void *arg, unsigned char *buf, unsigned long len)
{
    int toread = len;
    SEC_PKCS12DecoderContext *p12cxt = arg;

    if (!buf || len == 0 || !p12cxt->buffer) {
        PORT_SetError(SEC_ERROR_INVALID_ARGS);
        return -1;
    }

    if ((p12cxt->filesize - p12cxt->currentpos) < (long)len) {
        /* trying to read past the end of the buffer */
        toread = p12cxt->filesize - p12cxt->currentpos;
    }
    memcpy(buf, (char *)p12cxt->buffer + p12cxt->currentpos, toread);
    p12cxt->currentpos += toread;
    return toread;
}

static int
p12u_DigestWrite(void *arg, unsigned char *buf, unsigned long len)
{
    SEC_PKCS12DecoderContext *p12cxt = arg;

    if (!buf || len == 0) {
        return -1;
    }

    if (p12cxt->currentpos + (long)len > p12cxt->filesize) {
        p12cxt->filesize = p12cxt->currentpos + len;
    } else {
        p12cxt->filesize += len;
    }
    if (p12cxt->filesize > p12cxt->allocated) {
        void *newbuffer;
        size_t newsize = p12cxt->filesize + DEFAULT_TEMP_SIZE;
        newbuffer = PORT_Realloc(p12cxt->buffer, newsize);
        if (NULL == newbuffer) {
            return -1; /* can't extend the buffer */
        }
        p12cxt->buffer = newbuffer;
        p12cxt->allocated = newsize;
    }
    PR_ASSERT(p12cxt->buffer);
    memcpy((char *)p12cxt->buffer + p12cxt->currentpos, buf, len);
    p12cxt->currentpos += len;
    return len;
}

/* SEC_PKCS12DecoderStart
 *      Creates a decoder context for decoding a PKCS 12 PDU objct.
 *      This function sets up the initial decoding context for the
 *      PFX and sets the needed state variables.
 *
 *      pwitem - the password for the hMac and any encoded safes.
 *               this should be changed to take a callback which retrieves
 *               the password.  it may be possible for different safes to
 *               have different passwords.  also, the password is already
 *               in unicode.  it should probably be converted down below via
 *               a unicode conversion callback.
 *      slot - the slot to import the dataa into should multiple slots
 *               be supported based on key type and cert type?
 *      dOpen, dClose, dRead, dWrite - digest routines for writing data
 *               to a file so it could be read back and the hmac recomputed
 *               and verified.  doesn't seem to be a way for both encoding
 *               and decoding to be single pass, thus the need for these
 *               routines.
 *      dArg - the argument for dOpen, etc.
 *
 *      if NULL == dOpen == dClose == dRead == dWrite == dArg, then default
 *      implementations using a memory buffer are used
 *
 *      This function returns the decoder context, if it was successful.
 *      Otherwise, null is returned.
 */
SEC_PKCS12DecoderContext *
SEC_PKCS12DecoderStart(SECItem *pwitem, PK11SlotInfo *slot, void *wincx,
                       digestOpenFn dOpen, digestCloseFn dClose,
                       digestIOFn dRead, digestIOFn dWrite, void *dArg)
{
    SEC_PKCS12DecoderContext *p12dcx;
    PLArenaPool *arena;

    arena = PORT_NewArena(2048); /* different size? */
    if (!arena) {
        return NULL; /* error is already set */
    }

    /* allocate the decoder context and set the state variables */
    p12dcx = PORT_ArenaZNew(arena, SEC_PKCS12DecoderContext);
    if (!p12dcx) {
        goto loser; /* error is already set */
    }

    if (!dOpen && !dClose && !dRead && !dWrite && !dArg) {
        /* use default implementations */
        dOpen = p12u_DigestOpen;
        dClose = p12u_DigestClose;
        dRead = p12u_DigestRead;
        dWrite = p12u_DigestWrite;
        dArg = (void *)p12dcx;
    }

    p12dcx->arena = arena;
    p12dcx->pwitem = pwitem;
    p12dcx->slot = (slot ? PK11_ReferenceSlot(slot)
                         : PK11_GetInternalKeySlot());
    p12dcx->wincx = wincx;
    p12dcx->tokenCAs = SECPKCS12TargetTokenNoCAs;
#ifdef IS_LITTLE_ENDIAN
    p12dcx->swapUnicodeBytes = PR_TRUE;
#else
    p12dcx->swapUnicodeBytes = PR_FALSE;
#endif
    p12dcx->errorValue = 0;
    p12dcx->error = PR_FALSE;

    /* start the decoding of the PFX and set the notify proc
     * for the PFX item.
     */
    p12dcx->pfxA1Dcx = SEC_ASN1DecoderStart(p12dcx->arena, &p12dcx->pfx,
                                            sec_PKCS12PFXItemTemplate);
    if (!p12dcx->pfxA1Dcx) {
        PK11_FreeSlot(p12dcx->slot);
        goto loser;
    }

    SEC_ASN1DecoderSetNotifyProc(p12dcx->pfxA1Dcx,
                                 sec_pkcs12_decoder_pfx_notify_proc,
                                 p12dcx);

    /* set up digest functions */
    p12dcx->dOpen = dOpen;
    p12dcx->dWrite = dWrite;
    p12dcx->dClose = dClose;
    p12dcx->dRead = dRead;
    p12dcx->dArg = dArg;
    p12dcx->dIsOpen = PR_FALSE;

    p12dcx->keyList = NULL;
    p12dcx->decitem.type = 0;
    p12dcx->decitem.der = NULL;
    p12dcx->decitem.hasKey = PR_FALSE;
    p12dcx->decitem.friendlyName = NULL;
    p12dcx->iteration = 0;

    return p12dcx;

loser:
    PORT_FreeArena(arena, PR_TRUE);
    return NULL;
}

SECStatus
SEC_PKCS12DecoderSetTargetTokenCAs(SEC_PKCS12DecoderContext *p12dcx,
                                   SECPKCS12TargetTokenCAs tokenCAs)
{
    if (!p12dcx || p12dcx->error) {
        return SECFailure;
    }
    p12dcx->tokenCAs = tokenCAs;
    return SECSuccess;
}

/* SEC_PKCS12DecoderUpdate
 *      Streaming update sending more data to the decoder.  If
 *      an error occurs, SECFailure is returned.
 *
 *      p12dcx - the decoder context
 *      data, len - the data buffer and length of data to send to
 *              the update functions.
 */
SECStatus
SEC_PKCS12DecoderUpdate(SEC_PKCS12DecoderContext *p12dcx,
                        unsigned char *data, unsigned long len)
{
    SECStatus rv;

    if (!p12dcx || p12dcx->error) {
        PORT_SetError(SEC_ERROR_INVALID_ARGS);
        return SECFailure;
    }

    /* update the PFX decoder context */
    rv = SEC_ASN1DecoderUpdate(p12dcx->pfxA1Dcx, (const char *)data, len);
    if (rv != SECSuccess) {
        p12dcx->errorValue = SEC_ERROR_PKCS12_CORRUPT_PFX_STRUCTURE;
        goto loser;
    }

    return SECSuccess;

loser:

    p12dcx->error = PR_TRUE;
    return SECFailure;
}

/* This should be a nice sized buffer for reading in data (potentially large
** amounts) to be MACed.  It should be MUCH larger than HASH_LENGTH_MAX.
*/
#define IN_BUF_LEN 1024
#ifdef DEBUG
static const char bufferEnd[] = { "BufferEnd" };
#endif
#define FUDGE 128 /* must be as large as bufferEnd or more. */

/* verify the hmac by reading the data from the temporary file
 * using the routines specified when the decodingContext was
 * created and return SECSuccess if the hmac matches.
 */
static SECStatus
sec_pkcs12_decoder_verify_mac(SEC_PKCS12DecoderContext *p12dcx)
{
    PK11Context *pk11cx = NULL;
    PK11SymKey *symKey = NULL;
    SECItem *params = NULL;
    unsigned char *buf;
    SECStatus rv = SECFailure;
    SECStatus lrv;
    unsigned int bufLen;
    int iteration;
    int bytesRead;
    SECOidTag algtag;
    SECItem hmacRes;
    SECItem ignore = { 0 };
    CK_MECHANISM_TYPE integrityMech;

    if (!p12dcx || p12dcx->error) {
        PORT_SetError(SEC_ERROR_INVALID_ARGS);
        return SECFailure;
    }
    buf = (unsigned char *)PORT_Alloc(IN_BUF_LEN + FUDGE);
    if (!buf)
        return SECFailure; /* error code has been set. */

#ifdef DEBUG
    memcpy(buf + IN_BUF_LEN, bufferEnd, sizeof bufferEnd);
#endif

    /* generate hmac key */
    if (p12dcx->macData.iter.data) {
        iteration = (int)DER_GetInteger(&p12dcx->macData.iter);
    } else {
        iteration = 1;
    }

    params = PK11_CreatePBEParams(&p12dcx->macData.macSalt, p12dcx->pwitem,
                                  iteration);

    algtag = SECOID_GetAlgorithmTag(&p12dcx->macData.safeMac.digestAlgorithm);
    switch (algtag) {
        case SEC_OID_SHA1:
            integrityMech = CKM_NETSCAPE_PBE_SHA1_HMAC_KEY_GEN;
            break;
        case SEC_OID_MD5:
            integrityMech = CKM_NETSCAPE_PBE_MD5_HMAC_KEY_GEN;
            break;
        case SEC_OID_MD2:
            integrityMech = CKM_NETSCAPE_PBE_MD2_HMAC_KEY_GEN;
            break;
        default:
            goto loser;
    }

    symKey = PK11_KeyGen(NULL, integrityMech, params, 20, NULL);
    PK11_DestroyPBEParams(params);
    params = NULL;
    if (!symKey)
        goto loser;
    /* init hmac */
    pk11cx = PK11_CreateContextBySymKey(sec_pkcs12_algtag_to_mech(algtag),
                                        CKA_SIGN, symKey, &ignore);
    if (!pk11cx) {
        goto loser;
    }
    lrv = PK11_DigestBegin(pk11cx);
    if (lrv == SECFailure) {
        goto loser;
    }

    /* try to open the data for readback */
    if (p12dcx->dOpen && ((*p12dcx->dOpen)(p12dcx->dArg, PR_TRUE) != SECSuccess)) {
        goto loser;
    }

    /* read the data back IN_BUF_LEN bytes at a time and recompute
     * the hmac.  if fewer bytes are read than are requested, it is
     * assumed that the end of file has been reached. if bytesRead
     * is returned as -1, then an error occurred reading from the
     * file.
     */
    do {
        bytesRead = (*p12dcx->dRead)(p12dcx->dArg, buf, IN_BUF_LEN);
        if (bytesRead < 0) {
            PORT_SetError(SEC_ERROR_PKCS12_UNABLE_TO_READ);
            goto loser;
        }
        PORT_Assert(bytesRead <= IN_BUF_LEN);
        PORT_Assert(!memcmp(buf + IN_BUF_LEN, bufferEnd, sizeof bufferEnd));

        if (bytesRead > IN_BUF_LEN) {
            /* dRead callback overflowed buffer. */
            PORT_SetError(SEC_ERROR_INPUT_LEN);
            goto loser;
        }

        if (bytesRead) {
            lrv = PK11_DigestOp(pk11cx, buf, bytesRead);
            if (lrv == SECFailure) {
                goto loser;
            }
        }
    } while (bytesRead == IN_BUF_LEN);

    /* finish the hmac context */
    lrv = PK11_DigestFinal(pk11cx, buf, &bufLen, IN_BUF_LEN);
    if (lrv == SECFailure) {
        goto loser;
    }

    hmacRes.data = buf;
    hmacRes.len = bufLen;

    /* is the hmac computed the same as the hmac which was decoded? */
    rv = SECSuccess;
    if (SECITEM_CompareItem(&hmacRes, &p12dcx->macData.safeMac.digest) != SECEqual) {
        PORT_SetError(SEC_ERROR_PKCS12_INVALID_MAC);
        rv = SECFailure;
    }

loser:
    /* close the file and remove it */
    if (p12dcx->dClose) {
        (*p12dcx->dClose)(p12dcx->dArg, PR_TRUE);
        p12dcx->dIsOpen = PR_FALSE;
    }

    if (pk11cx) {
        PK11_DestroyContext(pk11cx, PR_TRUE);
    }
    if (params) {
        PK11_DestroyPBEParams(params);
    }
    if (symKey) {
        PK11_FreeSymKey(symKey);
    }
    PORT_ZFree(buf, IN_BUF_LEN + FUDGE);

    return rv;
}

/* SEC_PKCS12DecoderVerify
 *      Verify the macData or the signature of the decoded PKCS 12 PDU.
 *      If the signature or the macData do not match, SECFailure is
 *      returned.
 *
 *      p12dcx - the decoder context
 */
SECStatus
SEC_PKCS12DecoderVerify(SEC_PKCS12DecoderContext *p12dcx)
{
    SECStatus rv = SECSuccess;

    /* make sure that no errors have occurred... */
    if (!p12dcx) {
        PORT_SetError(SEC_ERROR_INVALID_ARGS);
        return SECFailure;
    }
    if (p12dcx->error) {
        /* error code is already set! PORT_SetError(p12dcx->errorValue); */
        return SECFailure;
    }

    rv = SEC_ASN1DecoderFinish(p12dcx->pfxA1Dcx);
    p12dcx->pfxA1Dcx = NULL;
    if (rv != SECSuccess) {
        return rv;
    }

    /* check the signature or the mac depending on the type of
     * integrity used.
     */
    if (p12dcx->pfx.encodedMacData.len) {
        rv = SEC_ASN1DecodeItem(p12dcx->arena, &p12dcx->macData,
                                sec_PKCS12MacDataTemplate,
                                &p12dcx->pfx.encodedMacData);
        if (rv == SECSuccess) {
            return sec_pkcs12_decoder_verify_mac(p12dcx);
        }
        return rv;
    }
    if (SEC_PKCS7VerifySignature(p12dcx->aSafeCinfo, certUsageEmailSigner,
                                 PR_FALSE)) {
        return SECSuccess;
    }
    PORT_SetError(SEC_ERROR_PKCS12_INVALID_MAC);
    return SECFailure;
}

/* SEC_PKCS12DecoderFinish
 *      Free any open ASN1 or PKCS7 decoder contexts and then
 *      free the arena pool which everything should be allocated
 *      from.  This function should be called upon completion of
 *      decoding and installing of a pfx pdu.  This should be
 *      called even if an error occurs.
 *
 *      p12dcx - the decoder context
 */
void
SEC_PKCS12DecoderFinish(SEC_PKCS12DecoderContext *p12dcx)
{
    unsigned int i;

    if (!p12dcx) {
        PORT_SetError(SEC_ERROR_INVALID_ARGS);
        return;
    }

    if (p12dcx->pfxA1Dcx) {
        SEC_ASN1DecoderFinish(p12dcx->pfxA1Dcx);
        p12dcx->pfxA1Dcx = NULL;
    }

    if (p12dcx->aSafeA1Dcx) {
        SEC_ASN1DecoderFinish(p12dcx->aSafeA1Dcx);
        p12dcx->aSafeA1Dcx = NULL;
    }

    /* cleanup any old ASN1 decoder contexts */
    for (i = 0; i < p12dcx->safeContentsCnt; ++i) {
        sec_PKCS12SafeContentsContext *safeContentsCtx, *nested;
        safeContentsCtx = p12dcx->safeContentsList[i];
        if (safeContentsCtx) {
            nested = safeContentsCtx->nestedSafeContentsCtx;
            while (nested) {
                if (nested->safeContentsA1Dcx) {
                    SEC_ASN1DecoderFinish(nested->safeContentsA1Dcx);
                    nested->safeContentsA1Dcx = NULL;
                }
                nested = nested->nestedSafeContentsCtx;
            }
            if (safeContentsCtx->safeContentsA1Dcx) {
                SEC_ASN1DecoderFinish(safeContentsCtx->safeContentsA1Dcx);
                safeContentsCtx->safeContentsA1Dcx = NULL;
            }
        }
    }

    if (p12dcx->currentASafeP7Dcx &&
        p12dcx->currentASafeP7Dcx != p12dcx->aSafeP7Dcx) {
        SEC_PKCS7ContentInfo *cinfo;
        cinfo = SEC_PKCS7DecoderFinish(p12dcx->currentASafeP7Dcx);
        if (cinfo) {
            SEC_PKCS7DestroyContentInfo(cinfo); /* don't leak it */
        }
    }
    p12dcx->currentASafeP7Dcx = NULL;

    if (p12dcx->aSafeP7Dcx) {
        SEC_PKCS7ContentInfo *cinfo;
        cinfo = SEC_PKCS7DecoderFinish(p12dcx->aSafeP7Dcx);
        if (cinfo) {
            SEC_PKCS7DestroyContentInfo(cinfo);
        }
        p12dcx->aSafeP7Dcx = NULL;
    }

    if (p12dcx->aSafeCinfo) {
        SEC_PKCS7DestroyContentInfo(p12dcx->aSafeCinfo);
        p12dcx->aSafeCinfo = NULL;
    }

    if (p12dcx->decitem.type != 0 && p12dcx->decitem.der != NULL) {
        SECITEM_FreeItem(p12dcx->decitem.der, PR_TRUE);
    }
    if (p12dcx->decitem.friendlyName != NULL) {
        SECITEM_FreeItem(p12dcx->decitem.friendlyName, PR_TRUE);
    }

    if (p12dcx->slot) {
        PK11_FreeSlot(p12dcx->slot);
        p12dcx->slot = NULL;
    }

    if (p12dcx->dIsOpen && p12dcx->dClose) {
        (*p12dcx->dClose)(p12dcx->dArg, PR_TRUE);
        p12dcx->dIsOpen = PR_FALSE;
    }

    if (p12dcx->arena) {
        PORT_FreeArena(p12dcx->arena, PR_TRUE);
    }
}

static SECStatus
sec_pkcs12_decoder_set_attribute_value(sec_PKCS12SafeBag *bag,
                                       SECOidTag attributeType,
                                       SECItem *attrValue)
{
    int i = 0;
    SECOidData *oid;

    if (!bag || !attrValue) {
        PORT_SetError(SEC_ERROR_INVALID_ARGS);
        return SECFailure;
    }

    oid = SECOID_FindOIDByTag(attributeType);
    if (!oid) {
        return SECFailure;
    }

    if (!bag->attribs) {
        bag->attribs =
            PORT_ArenaZNewArray(bag->arena, sec_PKCS12Attribute *, 2);
    } else {
        while (bag->attribs[i])
            i++;
        bag->attribs = PORT_ArenaGrowArray(bag->arena, bag->attribs,
                                           sec_PKCS12Attribute *, i + 1, i + 2);
    }

    if (!bag->attribs) {
        return SECFailure;
    }

    bag->attribs[i] = PORT_ArenaZNew(bag->arena, sec_PKCS12Attribute);
    if (!bag->attribs[i]) {
        return SECFailure;
    }

    bag->attribs[i]->attrValue = PORT_ArenaZNewArray(bag->arena, SECItem *, 2);
    if (!bag->attribs[i]->attrValue) {
        return SECFailure;
    }

    bag->attribs[i + 1] = NULL;
    bag->attribs[i]->attrValue[0] = attrValue;
    bag->attribs[i]->attrValue[1] = NULL;

    return SECITEM_CopyItem(bag->arena, &bag->attribs[i]->attrType, &oid->oid);
}

static SECItem *
sec_pkcs12_get_attribute_value(sec_PKCS12SafeBag *bag,
                               SECOidTag attributeType)
{
    int i;

    if (!bag->attribs) {
        PORT_SetError(SEC_ERROR_INVALID_ARGS);
        return NULL;
    }

    for (i = 0; bag->attribs[i] != NULL; i++) {
        if (SECOID_FindOIDTag(&bag->attribs[i]->attrType) == attributeType) {
            return bag->attribs[i]->attrValue[0];
        }
    }
    return NULL;
}

/* For now, this function will merely remove any ":"
 * in the nickname which the PK11 functions may have
 * placed there.  This will keep dual certs from appearing
 * twice under "Your" certificates when imported onto smart
 * cards.  Once with the name "Slot:Cert" and another with
 * the nickname "Slot:Slot:Cert"
 */
static void
sec_pkcs12_sanitize_nickname(PK11SlotInfo *slot, SECItem *nick)
{
    char *nickname;
    char *delimit;
    int delimitlen;

    nickname = (char *)nick->data;
    if ((delimit = PORT_Strchr(nickname, ':')) != NULL) {
        char *slotName;
        int slotNameLen;

        slotNameLen = delimit - nickname;
        slotName = PORT_NewArray(char, (slotNameLen + 1));
        PORT_Assert(slotName);
        if (slotName == NULL) {
            /* What else can we do?*/
            return;
        }
        PORT_Memcpy(slotName, nickname, slotNameLen);
        slotName[slotNameLen] = '\0';
        if (PORT_Strcmp(PK11_GetTokenName(slot), slotName) == 0) {
            delimitlen = PORT_Strlen(delimit + 1);
            PORT_Memmove(nickname, delimit + 1, delimitlen + 1);
            nick->len = delimitlen;
        }
        PORT_Free(slotName);
    }
}

static SECItem *
sec_pkcs12_get_nickname(sec_PKCS12SafeBag *bag)
{
    SECItem *src, *dest;

    if (!bag) {
        PORT_SetError(SEC_ERROR_INVALID_ARGS);
        return NULL;
    }

    src = sec_pkcs12_get_attribute_value(bag, SEC_OID_PKCS9_FRIENDLY_NAME);

    /* The return value src is 16-bit Unicode characters, in big-endian format.
     * Check if it is NULL or empty name.
     */
    if (!src || !src->data || src->len < 2 || (!src->data[0] && !src->data[1])) {
        return NULL;
    }

    dest = (SECItem *)PORT_ZAlloc(sizeof(SECItem));
    if (!dest) {
        goto loser;
    }
    if (!sec_pkcs12_convert_item_to_unicode(NULL, dest, src, PR_FALSE,
                                            PR_FALSE, PR_FALSE)) {
        goto loser;
    }

    sec_pkcs12_sanitize_nickname(bag->slot, dest);

    return dest;

loser:
    if (dest) {
        SECITEM_ZfreeItem(dest, PR_TRUE);
    }

    bag->problem = PR_TRUE;
    bag->error = PORT_GetError();
    return NULL;
}

static SECStatus
sec_pkcs12_set_nickname(sec_PKCS12SafeBag *bag, SECItem *name)
{
    sec_PKCS12Attribute *attr = NULL;
    SECOidData *oid = SECOID_FindOIDByTag(SEC_OID_PKCS9_FRIENDLY_NAME);

    if (!bag || !bag->arena || !name) {
        PORT_SetError(SEC_ERROR_INVALID_ARGS);
        return SECFailure;
    }

    if (!bag->attribs) {
        if (!oid) {
            goto loser;
        }

        bag->attribs =
            PORT_ArenaZNewArray(bag->arena, sec_PKCS12Attribute *, 2);
        if (!bag->attribs) {
            goto loser;
        }
        bag->attribs[0] = PORT_ArenaZNew(bag->arena, sec_PKCS12Attribute);
        if (!bag->attribs[0]) {
            goto loser;
        }
        bag->attribs[1] = NULL;

        attr = bag->attribs[0];
        if (SECITEM_CopyItem(bag->arena, &attr->attrType, &oid->oid) != SECSuccess) {
            goto loser;
        }
    } else {
        int i;
        for (i = 0; bag->attribs[i]; i++) {
            if (SECOID_FindOIDTag(&bag->attribs[i]->attrType) == SEC_OID_PKCS9_FRIENDLY_NAME) {
                attr = bag->attribs[i];
                break;
            }
        }
        if (!attr) {
            if (!oid) {
                goto loser;
            }
            bag->attribs = PORT_ArenaGrowArray(bag->arena, bag->attribs,
                                               sec_PKCS12Attribute *, i + 1, i + 2);
            if (!bag->attribs) {
                goto loser;
            }
            bag->attribs[i] = PORT_ArenaZNew(bag->arena, sec_PKCS12Attribute);
            if (!bag->attribs[i]) {
                goto loser;
            }
            bag->attribs[i + 1] = NULL;
            attr = bag->attribs[i];
            if (SECITEM_CopyItem(bag->arena, &attr->attrType, &oid->oid) != SECSuccess) {
                goto loser;
            }
        }
    }

    PORT_Assert(attr);
    if (!attr->attrValue) {
        attr->attrValue = PORT_ArenaZNewArray(bag->arena, SECItem *, 2);
        if (!attr->attrValue) {
            goto loser;
        }
        attr->attrValue[0] = PORT_ArenaZNew(bag->arena, SECItem);
        if (!attr->attrValue[0]) {
            goto loser;
        }
        attr->attrValue[1] = NULL;
    }

    name->len = PORT_Strlen((char *)name->data);
    if (!sec_pkcs12_convert_item_to_unicode(bag->arena, attr->attrValue[0],
                                            name, PR_FALSE, PR_FALSE, PR_TRUE)) {
        goto loser;
    }

    return SECSuccess;

loser:
    bag->problem = PR_TRUE;
    bag->error = PORT_GetError();
    return SECFailure;
}

static SECStatus
sec_pkcs12_get_key_info(sec_PKCS12SafeBag *key)
{
    int i = 0;
    SECKEYPrivateKeyInfo *pki = NULL;

    if (!key) {
        PORT_SetError(SEC_ERROR_INVALID_ARGS);
        return SECFailure;
    }

    /* if the bag does *not* contain an unencrypted PrivateKeyInfo
     * then we cannot convert the attributes.  We are propagating
     * attributes within the PrivateKeyInfo to the SafeBag level.
     */
    if (SECOID_FindOIDTag(&(key->safeBagType)) !=
        SEC_OID_PKCS12_V1_KEY_BAG_ID) {
        return SECSuccess;
    }

    pki = key->safeBagContent.pkcs8KeyBag;

    if (!pki || !pki->attributes) {
        return SECSuccess;
    }

    while (pki->attributes[i]) {
        SECOidTag tag = SECOID_FindOIDTag(&pki->attributes[i]->attrType);

        if (tag == SEC_OID_PKCS9_LOCAL_KEY_ID ||
            tag == SEC_OID_PKCS9_FRIENDLY_NAME) {
            SECItem *attrValue = sec_pkcs12_get_attribute_value(key, tag);
            if (!attrValue) {
                if (sec_pkcs12_decoder_set_attribute_value(key, tag,
                                                           pki->attributes[i]->attrValue[0]) != SECSuccess) {
                    key->problem = PR_TRUE;
                    key->error = PORT_GetError();
                    return SECFailure;
                }
            }
        }
        i++;
    }

    return SECSuccess;
}

/* retrieve the nickname for the certificate bag.  first look
 * in the cert bag, otherwise get it from the key.
 */
static SECItem *
sec_pkcs12_get_nickname_for_cert(sec_PKCS12SafeBag *cert,
                                 sec_PKCS12SafeBag *key)
{
    SECItem *nickname;

    if (!cert) {
        PORT_SetError(SEC_ERROR_INVALID_ARGS);
        return NULL;
    }

    nickname = sec_pkcs12_get_nickname(cert);
    if (nickname) {
        return nickname;
    }

    if (key) {
        nickname = sec_pkcs12_get_nickname(key);

        if (nickname && sec_pkcs12_set_nickname(cert, nickname) != SECSuccess) {
            SECITEM_ZfreeItem(nickname, PR_TRUE);
            return NULL;
        }
    }

    return nickname;
}

/* set the nickname for the certificate */
static SECStatus
sec_pkcs12_set_nickname_for_cert(sec_PKCS12SafeBag *cert,
                                 sec_PKCS12SafeBag *key,
                                 SECItem *nickname)
{
    if (!nickname || !cert) {
        PORT_SetError(SEC_ERROR_INVALID_ARGS);
        return SECFailure;
    }

    if (sec_pkcs12_set_nickname(cert, nickname) != SECSuccess) {
        return SECFailure;
    }

    if (key) {
        if (sec_pkcs12_set_nickname(key, nickname) != SECSuccess) {
            cert->problem = PR_TRUE;
            cert->error = key->error;
            return SECFailure;
        }
    }

    return SECSuccess;
}

/* retrieve the DER cert from the cert bag */
static SECItem *
sec_pkcs12_get_der_cert(sec_PKCS12SafeBag *cert)
{
    if (!cert) {
        PORT_SetError(SEC_ERROR_INVALID_ARGS);
        return NULL;
    }

    if (SECOID_FindOIDTag(&cert->safeBagType) != SEC_OID_PKCS12_V1_CERT_BAG_ID) {
        return NULL;
    }

    /* only support X509 certs not SDSI */
    if (SECOID_FindOIDTag(&cert->safeBagContent.certBag->bagID) != SEC_OID_PKCS9_X509_CERT) {
        return NULL;
    }

    return SECITEM_DupItem(&(cert->safeBagContent.certBag->value.x509Cert));
}

struct certNickInfo {
    PLArenaPool *arena;
    unsigned int nNicks;
    SECItem **nickList;
    unsigned int error;
};

/* callback for traversing certificates to gather the nicknames
 * used in a particular traversal.  for instance, when using
 * CERT_TraversePermCertsForSubject, gather the nicknames and
 * store them in the certNickInfo for a particular DN.
 *
 * this handles the case where multiple nicknames are allowed
 * for the same dn, which is not currently allowed, but may be
 * in the future.
 */
static SECStatus
gatherNicknames(CERTCertificate *cert, void *arg)
{
    struct certNickInfo *nickArg = (struct certNickInfo *)arg;
    SECItem tempNick;
    unsigned int i;

    if (!cert || !nickArg || nickArg->error) {
        PORT_SetError(SEC_ERROR_INVALID_ARGS);
        return SECFailure;
    }

    if (!cert->nickname) {
        return SECSuccess;
    }

    tempNick.data = (unsigned char *)cert->nickname;
    tempNick.len = PORT_Strlen(cert->nickname) + 1;
    tempNick.type = siAsciiString;

    /* do we already have the nickname in the list? */
    if (nickArg->nNicks > 0) {

        /* nicknames have been encountered, but there is no list -- bad */
        if (!nickArg->nickList) {
            nickArg->error = SEC_ERROR_INVALID_ARGS;
            PORT_SetError(SEC_ERROR_INVALID_ARGS);
            return SECFailure;
        }

        for (i = 0; i < nickArg->nNicks; i++) {
            if (SECITEM_CompareItem(nickArg->nickList[i], &tempNick) == SECEqual) {
                return SECSuccess;
            }
        }
    }

    /* add the nickname to the list */
    nickArg->nickList = (nickArg->nNicks == 0)
                            ? PORT_ArenaZNewArray(nickArg->arena, SECItem *, 2)
                            : PORT_ArenaGrowArray(nickArg->arena, nickArg->nickList, SECItem *,
                                                  nickArg->nNicks + 1, nickArg->nNicks + 2);

    if (!nickArg->nickList) {
        nickArg->error = SEC_ERROR_NO_MEMORY;
        return SECFailure;
    }

    nickArg->nickList[nickArg->nNicks] =
        PORT_ArenaZNew(nickArg->arena, SECItem);
    if (!nickArg->nickList[nickArg->nNicks]) {
        nickArg->error = PORT_GetError();
        return SECFailure;
    }

    if (SECITEM_CopyItem(nickArg->arena, nickArg->nickList[nickArg->nNicks],
                         &tempNick) != SECSuccess) {
        nickArg->error = PORT_GetError();
        return SECFailure;
    }

    nickArg->nNicks++;

    return SECSuccess;
}

/* traverses the certs in the data base or in the token for the
 * DN to see if any certs currently have a nickname set.
 * If so, return it.
 */
static SECItem *
sec_pkcs12_get_existing_nick_for_dn(sec_PKCS12SafeBag *cert)
{
    struct certNickInfo *nickArg = NULL;
    SECItem *derCert, *returnDn = NULL;
    PLArenaPool *arena = NULL;
    CERTCertificate *tempCert;

    if (!cert) {
        PORT_SetError(SEC_ERROR_INVALID_ARGS);
        return NULL;
    }

    derCert = sec_pkcs12_get_der_cert(cert);
    if (!derCert) {
        return NULL;
    }

    tempCert = CERT_DecodeDERCertificate(derCert, PR_FALSE, NULL);
    if (!tempCert) {
        returnDn = NULL;
        goto loser;
    }

    arena = PORT_NewArena(1024);
    if (!arena) {
        returnDn = NULL;
        goto loser;
    }
    nickArg = PORT_ArenaZNew(arena, struct certNickInfo);
    if (!nickArg) {
        returnDn = NULL;
        goto loser;
    }
    nickArg->error = 0;
    nickArg->nNicks = 0;
    nickArg->nickList = NULL;
    nickArg->arena = arena;

    /* if the token is local, first traverse the cert database
     * then traverse the token.
     */
    if (PK11_TraverseCertsForSubjectInSlot(tempCert, cert->slot, gatherNicknames,
                                           (void *)nickArg) != SECSuccess) {
        returnDn = NULL;
        goto loser;
    }

    if (nickArg->error) {
        /* XXX do we want to set the error? */
        returnDn = NULL;
        goto loser;
    }

    if (nickArg->nNicks == 0) {
        returnDn = NULL;
        goto loser;
    }

    /* set it to the first name, for now.  handle multiple names? */
    returnDn = SECITEM_DupItem(nickArg->nickList[0]);

loser:
    if (arena) {
        PORT_FreeArena(arena, PR_TRUE);
    }

    if (tempCert) {
        CERT_DestroyCertificate(tempCert);
    }

    if (derCert) {
        SECITEM_FreeItem(derCert, PR_TRUE);
    }

    return (returnDn);
}

/* counts certificates found for a given traversal function */
static SECStatus
countCertificate(CERTCertificate *cert, void *arg)
{
    unsigned int *nCerts = (unsigned int *)arg;

    if (!cert || !arg) {
        PORT_SetError(SEC_ERROR_INVALID_ARGS);
        return SECFailure;
    }

    (*nCerts)++;
    return SECSuccess;
}

static PRBool
sec_pkcs12_certs_for_nickname_exist(SECItem *nickname, PK11SlotInfo *slot)
{
    unsigned int nCerts = 0;

    if (!nickname || !slot) {
        PORT_SetError(SEC_ERROR_INVALID_ARGS);
        return PR_TRUE;
    }

    /* we want to check the local database first if we are importing to it */
    PK11_TraverseCertsForNicknameInSlot(nickname, slot, countCertificate,
                                        (void *)&nCerts);
    return (PRBool)(nCerts != 0);
}

/* validate cert nickname such that there is a one-to-one relation
 * between nicknames and dn's.  we want to enforce the case that the
 * nickname is non-NULL and that there is only one nickname per DN.
 *
 * if there is a problem with a nickname or the nickname is not present,
 * the user will be prompted for it.
 */
static void
sec_pkcs12_validate_cert_nickname(sec_PKCS12SafeBag *cert,
                                  sec_PKCS12SafeBag *key,
                                  SEC_PKCS12NicknameCollisionCallback nicknameCb,
                                  CERTCertificate *leafCert)
{
    SECItem *certNickname, *existingDNNick;
    PRBool setNickname = PR_FALSE, cancel = PR_FALSE;
    SECItem *newNickname = NULL;

    if (!cert || !cert->hasKey) {
        PORT_SetError(SEC_ERROR_INVALID_ARGS);
        return;
    }

    if (!nicknameCb) {
        cert->problem = PR_TRUE;
        cert->error = SEC_ERROR_INVALID_ARGS;
        PORT_SetError(SEC_ERROR_INVALID_ARGS);
        return;
    }

    if (cert->hasKey && !key) {
        cert->problem = PR_TRUE;
        cert->error = SEC_ERROR_INVALID_ARGS;
        PORT_SetError(SEC_ERROR_INVALID_ARGS);
        return;
    }

    certNickname = sec_pkcs12_get_nickname_for_cert(cert, key);
    existingDNNick = sec_pkcs12_get_existing_nick_for_dn(cert);

    /* nickname is already used w/ this dn, so it is safe to return */
    if (certNickname && existingDNNick &&
        SECITEM_CompareItem(certNickname, existingDNNick) == SECEqual) {
        goto loser;
    }

    /* nickname not set in pkcs 12 bags, but a nick is already used for
     * this dn.  set the nicks in the p12 bags and finish.
     */
    if (existingDNNick) {
        sec_pkcs12_set_nickname_for_cert(cert, key, existingDNNick);
        goto loser;
    }

    /* at this point, we have a certificate for which the DN is not located
     * on the token.  the nickname specified may or may not be NULL.  if it
     * is not null, we need to make sure that there are no other certificates
     * with this nickname in the token for it to be valid.  this imposes a
     * one to one relationship between DN and nickname.
     *
     * if the nickname is null, we need the user to enter a nickname for
     * the certificate.
     *
     * once we have a nickname, we make sure that the nickname is unique
     * for the DN.  if it is not, the user is reprompted to enter a new
     * nickname.
     *
     * in order to exit this loop, the nickname entered is either unique
     * or the user hits cancel and the certificate is not imported.
     */
    setNickname = PR_FALSE;
    while (1) {
        /* we will use the nickname so long as no other certs have the
         * same nickname.  and the nickname is not NULL.
         */
        if (certNickname && certNickname->data &&
            !sec_pkcs12_certs_for_nickname_exist(certNickname, cert->slot)) {
            if (setNickname) {
                sec_pkcs12_set_nickname_for_cert(cert, key, certNickname);
            }
            break;
        }

        setNickname = PR_FALSE;
        newNickname = (*nicknameCb)(certNickname, &cancel, leafCert);
        if (cancel) {
            cert->problem = PR_TRUE;
            cert->error = SEC_ERROR_USER_CANCELLED;
            break;
        }

        if (!newNickname) {
            cert->problem = PR_TRUE;
            cert->error = PORT_GetError();
            break;
        }

        /* at this point we have a new nickname, if we have an existing
         * certNickname, we need to free it and assign the new nickname
         * to it to avoid a memory leak.  happy?
         */
        if (certNickname) {
            SECITEM_ZfreeItem(certNickname, PR_TRUE);
            certNickname = NULL;
        }

        certNickname = newNickname;
        setNickname = PR_TRUE;
        /* go back and recheck the new nickname */
    }

loser:
    if (certNickname) {
        SECITEM_ZfreeItem(certNickname, PR_TRUE);
    }

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

static void
sec_pkcs12_validate_cert(sec_PKCS12SafeBag *cert,
                         sec_PKCS12SafeBag *key,
                         SEC_PKCS12NicknameCollisionCallback nicknameCb)
{
    CERTCertificate *leafCert;

    if (!cert) {
        PORT_SetError(SEC_ERROR_INVALID_ARGS);
        return;
    }

    cert->validated = PR_TRUE;

    if (!nicknameCb) {
        cert->noInstall = PR_TRUE;
        cert->problem = PR_TRUE;
        cert->error = SEC_ERROR_INVALID_ARGS;
        PORT_SetError(SEC_ERROR_INVALID_ARGS);
        return;
    }

    if (!cert->safeBagContent.certBag) {
        cert->noInstall = PR_TRUE;
        cert->problem = PR_TRUE;
        cert->error = SEC_ERROR_PKCS12_CORRUPT_PFX_STRUCTURE;
        return;
    }

    cert->noInstall = PR_FALSE;
    cert->unused = PR_FALSE;
    cert->problem = PR_FALSE;
    cert->error = 0;

    leafCert = CERT_DecodeDERCertificate(
        &cert->safeBagContent.certBag->value.x509Cert, PR_FALSE, NULL);
    if (!leafCert) {
        cert->noInstall = PR_TRUE;
        cert->problem = PR_TRUE;
        cert->error = PORT_GetError();
        return;
    }

    sec_pkcs12_validate_cert_nickname(cert, key, nicknameCb, leafCert);

    CERT_DestroyCertificate(leafCert);
}

static void
sec_pkcs12_validate_key_by_cert(sec_PKCS12SafeBag *cert, sec_PKCS12SafeBag *key,
                                void *wincx)
{
    CERTCertificate *leafCert;
    SECKEYPrivateKey *privk;

    if (!key) {
        PORT_SetError(SEC_ERROR_INVALID_ARGS);
        return;
    }

    key->validated = PR_TRUE;

    if (!cert) {
        key->problem = PR_TRUE;
        key->noInstall = PR_TRUE;
        key->error = SEC_ERROR_PKCS12_UNABLE_TO_IMPORT_KEY;
        return;
    }

    leafCert = CERT_DecodeDERCertificate(
        &(cert->safeBagContent.certBag->value.x509Cert), PR_FALSE, NULL);
    if (!leafCert) {
        key->problem = PR_TRUE;
        key->noInstall = PR_TRUE;
        key->error = PORT_GetError();
        return;
    }

    privk = PK11_FindPrivateKeyFromCert(key->slot, leafCert, wincx);
    if (!privk) {
        privk = PK11_FindKeyByDERCert(key->slot, leafCert, wincx);
    }

    if (privk) {
        SECKEY_DestroyPrivateKey(privk);
        key->noInstall = PR_TRUE;
    }

    CERT_DestroyCertificate(leafCert);
}

static SECStatus
sec_pkcs12_add_cert(sec_PKCS12SafeBag *cert, PRBool keyExists, void *wincx)
{
    SECItem *derCert, *nickName;
    char *nickData = NULL;
    PRBool isIntermediateCA;
    SECStatus rv;

    if (!cert) {
        PORT_SetError(SEC_ERROR_INVALID_ARGS);
        return SECFailure;
    }

    if (cert->problem || cert->noInstall || cert->installed) {
        return SECSuccess;
    }

    derCert = &cert->safeBagContent.certBag->value.x509Cert;

    PORT_Assert(!cert->problem && !cert->noInstall);

    nickName = sec_pkcs12_get_nickname(cert);
    if (nickName) {
        nickData = (char *)nickName->data;
    }

    isIntermediateCA = CERT_IsCADERCert(derCert, NULL) &&
                       !CERT_IsRootDERCert(derCert);

    if (keyExists) {
        CERTCertificate *newCert;

        newCert = CERT_NewTempCertificate(CERT_GetDefaultCertDB(),
                                          derCert, NULL, PR_FALSE, PR_FALSE);
        if (!newCert) {
            if (nickName)
                SECITEM_ZfreeItem(nickName, PR_TRUE);
            cert->error = PORT_GetError();
            cert->problem = PR_TRUE;
            return SECFailure;
        }

        rv = PK11_ImportCertForKeyToSlot(cert->slot, newCert, nickData,
                                         PR_TRUE, wincx);
        CERT_DestroyCertificate(newCert);
    } else if ((cert->tokenCAs == SECPKCS12TargetTokenNoCAs) ||
               ((cert->tokenCAs == SECPKCS12TargetTokenIntermediateCAs) &&
                !isIntermediateCA)) {
        SECItem *certList[2];
        certList[0] = derCert;
        certList[1] = NULL;

        rv = CERT_ImportCerts(CERT_GetDefaultCertDB(), certUsageUserCertImport,
                              1, certList, NULL, PR_TRUE, PR_FALSE, nickData);
    } else {
        rv = PK11_ImportDERCert(cert->slot, derCert, CK_INVALID_HANDLE,
                                nickData, PR_FALSE);
    }
    if (rv) {
        cert->problem = 1;
        cert->error = PORT_GetError();
    }
    cert->installed = PR_TRUE;
    if (nickName)
        SECITEM_ZfreeItem(nickName, PR_TRUE);
    return rv;
}

static SECItem *
sec_pkcs12_get_public_value_and_type(SECKEYPublicKey *pubKey, KeyType *type);

static SECStatus
sec_pkcs12_add_key(sec_PKCS12SafeBag *key, SECKEYPublicKey *pubKey,
                   unsigned int keyUsage,
                   SECItem *nickName, void *wincx)
{
    SECStatus rv;
    SECItem *publicValue = NULL;
    KeyType keyType;

    /* We should always have values for "key" and "pubKey"
       so they can be dereferenced later. */
    if (!key || !pubKey) {
        PORT_SetError(SEC_ERROR_INVALID_ARGS);
        return SECFailure;
    }

    if (key->problem || key->noInstall) {
        return SECSuccess;
    }

    /* get the value and type from the public key */
    publicValue = sec_pkcs12_get_public_value_and_type(pubKey, &keyType);
    if (!publicValue) {
        key->error = SEC_ERROR_PKCS12_UNABLE_TO_IMPORT_KEY;
        key->problem = PR_TRUE;
        return SECFailure;
    }

    switch (SECOID_FindOIDTag(&key->safeBagType)) {
        case SEC_OID_PKCS12_V1_KEY_BAG_ID:
            rv = PK11_ImportPrivateKeyInfo(key->slot,
                                           key->safeBagContent.pkcs8KeyBag,
                                           nickName, publicValue, PR_TRUE, PR_TRUE,
                                           keyUsage, wincx);
            break;
        case SEC_OID_PKCS12_V1_PKCS8_SHROUDED_KEY_BAG_ID:
            rv = PK11_ImportEncryptedPrivateKeyInfo(key->slot,
                                                    key->safeBagContent.pkcs8ShroudedKeyBag,
                                                    key->pwitem, nickName, publicValue,
                                                    PR_TRUE, PR_TRUE, keyType, keyUsage,
                                                    wincx);
            break;
        default:
            key->error = SEC_ERROR_PKCS12_UNSUPPORTED_VERSION;
            key->problem = PR_TRUE;
            if (nickName) {
                SECITEM_ZfreeItem(nickName, PR_TRUE);
            }
            return SECFailure;
    }

    if (rv != SECSuccess) {
        key->error = SEC_ERROR_PKCS12_UNABLE_TO_IMPORT_KEY;
        key->problem = PR_TRUE;
    } else {
        /* try to import the public key. Failure to do so is not fatal,
         * not all tokens can store the public key */
        if (pubKey) {
            PK11_ImportPublicKey(key->slot, pubKey, PR_TRUE);
        }
        key->installed = PR_TRUE;
    }

    return rv;
}

/*
 * The correctness of the code in this file ABSOLUTELY REQUIRES
 * that ALL BAGs share a single common arena.
 *
 * This function allocates the bag list from the arena of whatever bag
 * happens to be passed to it.  Each time a new bag is handed to it,
 * it grows (resizes) the arena of the bag that was handed to it.
 * If the bags have different arenas, it will grow the wrong arena.
 *
 * Worse, if the bags had separate arenas, then while destroying the bags
 * in a bag list, when the bag whose arena contained the bag list was
 * destroyed, the baglist itself would be destroyed, making it difficult
 * or impossible to continue to destroy the bags in the destroyed list.
 */
static SECStatus
sec_pkcs12_add_item_to_bag_list(sec_PKCS12SafeBag ***bagList,
                                sec_PKCS12SafeBag *bag)
{
    sec_PKCS12SafeBag **newBagList = NULL;
    int i = 0;

    if (!bagList || !bag) {
        PORT_SetError(SEC_ERROR_INVALID_ARGS);
        return SECFailure;
    }

    if (!(*bagList)) {
        newBagList = PORT_ArenaZNewArray(bag->arena, sec_PKCS12SafeBag *, 2);
    } else {
        while ((*bagList)[i])
            i++;
        newBagList = PORT_ArenaGrowArray(bag->arena, *bagList,
                                         sec_PKCS12SafeBag *, i + 1, i + 2);
    }

    if (!newBagList) {
        PORT_SetError(SEC_ERROR_NO_MEMORY);
        return SECFailure;
    }

    newBagList[i] = bag;
    newBagList[i + 1] = NULL;
    *bagList = newBagList;

    return SECSuccess;
}

static sec_PKCS12SafeBag **
sec_pkcs12_find_certs_for_key(sec_PKCS12SafeBag **safeBags,
                              sec_PKCS12SafeBag *key)
{
    sec_PKCS12SafeBag **certList = NULL;
    SECItem *keyId;
    int i;

    if (!safeBags || !safeBags[0]) {
        PORT_SetError(SEC_ERROR_INVALID_ARGS);
        return NULL;
    }

    keyId = sec_pkcs12_get_attribute_value(key, SEC_OID_PKCS9_LOCAL_KEY_ID);
    if (!keyId) {
        return NULL;
    }

    for (i = 0; safeBags[i]; i++) {
        if (SECOID_FindOIDTag(&(safeBags[i]->safeBagType)) == SEC_OID_PKCS12_V1_CERT_BAG_ID) {
            SECItem *certKeyId = sec_pkcs12_get_attribute_value(safeBags[i],
                                                                SEC_OID_PKCS9_LOCAL_KEY_ID);

            if (certKeyId && (SECITEM_CompareItem(certKeyId, keyId) == SECEqual)) {
                if (sec_pkcs12_add_item_to_bag_list(&certList, safeBags[i]) != SECSuccess) {
                    /* This would leak the partial list of safeBags,
                     * but that list is allocated from the arena of
                     * one of the safebags, and will be destroyed when
                     * that arena is destroyed.  So this is not a real leak.
                     */
                    return NULL;
                }
            }
        }
    }

    return certList;
}

CERTCertList *
SEC_PKCS12DecoderGetCerts(SEC_PKCS12DecoderContext *p12dcx)
{
    CERTCertList *certList = NULL;
    sec_PKCS12SafeBag **safeBags;
    int i;

    if (!p12dcx || !p12dcx->safeBags || !p12dcx->safeBags[0]) {
        PORT_SetError(SEC_ERROR_INVALID_ARGS);
        return NULL;
    }

    safeBags = p12dcx->safeBags;
    certList = CERT_NewCertList();

    if (certList == NULL) {
        return NULL;
    }

    for (i = 0; safeBags[i]; i++) {
        if (SECOID_FindOIDTag(&(safeBags[i]->safeBagType)) == SEC_OID_PKCS12_V1_CERT_BAG_ID) {
            SECItem *derCert = sec_pkcs12_get_der_cert(safeBags[i]);
            CERTCertificate *tempCert = NULL;

            if (derCert == NULL)
                continue;
            tempCert = CERT_NewTempCertificate(CERT_GetDefaultCertDB(),
                                               derCert, NULL,
                                               PR_FALSE, PR_TRUE);

            if (tempCert) {
                CERT_AddCertToListTail(certList, tempCert);
            }
            SECITEM_FreeItem(derCert, PR_TRUE);
        }
        /* fixed an infinite loop here, by ensuring that i gets incremented
         * if derCert is NULL above.
         */
    }

    return certList;
}
static sec_PKCS12SafeBag **
sec_pkcs12_get_key_bags(sec_PKCS12SafeBag **safeBags)
{
    int i;
    sec_PKCS12SafeBag **keyList = NULL;
    SECOidTag bagType;

    if (!safeBags || !safeBags[0]) {
        PORT_SetError(SEC_ERROR_INVALID_ARGS);
        return NULL;
    }

    for (i = 0; safeBags[i]; i++) {
        bagType = SECOID_FindOIDTag(&(safeBags[i]->safeBagType));
        switch (bagType) {
            case SEC_OID_PKCS12_V1_KEY_BAG_ID:
            case SEC_OID_PKCS12_V1_PKCS8_SHROUDED_KEY_BAG_ID:
                if (sec_pkcs12_add_item_to_bag_list(&keyList, safeBags[i]) != SECSuccess) {
                    /* This would leak, except that keyList is allocated
                     * from the arena shared by all the safeBags.
                     */
                    return NULL;
                }
                break;
            default:
                break;
        }
    }

    return keyList;
}

/* This function takes two passes over the bags, validating them
 * The two passes are intended to mirror exactly the two passes in
 * sec_pkcs12_install_bags.  But they don't. :(
 */
static SECStatus
sec_pkcs12_validate_bags(sec_PKCS12SafeBag **safeBags,
                         SEC_PKCS12NicknameCollisionCallback nicknameCb,
                         void *wincx)
{
    sec_PKCS12SafeBag **keyList;
    int i;

    if (!safeBags || !nicknameCb) {
        PORT_SetError(SEC_ERROR_INVALID_ARGS);
        return SECFailure;
    }

    if (!safeBags[0]) {
        return SECSuccess;
    }

    /* First pass.  Find all the key bags.
     * Find the matching cert(s) for each key.
     */
    keyList = sec_pkcs12_get_key_bags(safeBags);
    if (keyList) {
        for (i = 0; keyList[i]; ++i) {
            sec_PKCS12SafeBag *key = keyList[i];
            sec_PKCS12SafeBag **certList =
                sec_pkcs12_find_certs_for_key(safeBags, key);

            if (certList) {
                int j;

                if (SECOID_FindOIDTag(&(key->safeBagType)) ==
                    SEC_OID_PKCS12_V1_KEY_BAG_ID) {
                    /* if it is an unencrypted private key then make sure
                     * the attributes are propageted to the appropriate
                     * level
                     */
                    if (sec_pkcs12_get_key_info(key) != SECSuccess) {
                        return SECFailure;
                    }
                }

                sec_pkcs12_validate_key_by_cert(certList[0], key, wincx);
                for (j = 0; certList[j]; ++j) {
                    sec_PKCS12SafeBag *cert = certList[j];
                    cert->hasKey = PR_TRUE;
                    if (key->problem) {
                        cert->problem = PR_TRUE;
                        cert->error = key->error;
                        continue;
                    }
                    sec_pkcs12_validate_cert(cert, key, nicknameCb);
                    if (cert->problem) {
                        key->problem = cert->problem;
                        key->error = cert->error;
                    }
                }
            }
        }
    }

    /* Now take a second pass over the safebags and mark for installation any
     * certs that were neither installed nor disqualified by the first pass.
     */
    for (i = 0; safeBags[i]; ++i) {
        sec_PKCS12SafeBag *bag = safeBags[i];

        if (!bag->validated) {
            SECOidTag bagType = SECOID_FindOIDTag(&bag->safeBagType);

            switch (bagType) {
                case SEC_OID_PKCS12_V1_CERT_BAG_ID:
                    sec_pkcs12_validate_cert(bag, NULL, nicknameCb);
                    break;
                case SEC_OID_PKCS12_V1_KEY_BAG_ID:
                case SEC_OID_PKCS12_V1_PKCS8_SHROUDED_KEY_BAG_ID:
                    bag->noInstall = PR_TRUE;
                    bag->problem = PR_TRUE;
                    bag->error = SEC_ERROR_PKCS12_UNABLE_TO_IMPORT_KEY;
                    break;
                default:
                    bag->noInstall = PR_TRUE;
            }
        }
    }

    return SECSuccess;
}

SECStatus
SEC_PKCS12DecoderValidateBags(SEC_PKCS12DecoderContext *p12dcx,
                              SEC_PKCS12NicknameCollisionCallback nicknameCb)
{
    SECStatus rv;
    int i, noInstallCnt, probCnt, bagCnt, errorVal = 0;
    if (!p12dcx || p12dcx->error || !p12dcx->safeBags) {
        PORT_SetError(SEC_ERROR_INVALID_ARGS);
        return SECFailure;
    }

    rv = sec_pkcs12_validate_bags(p12dcx->safeBags, nicknameCb, p12dcx->wincx);
    if (rv == SECSuccess) {
        p12dcx->bagsVerified = PR_TRUE;
    }

    noInstallCnt = probCnt = bagCnt = 0;
    i = 0;
    while (p12dcx->safeBags[i]) {
        bagCnt++;
        if (p12dcx->safeBags[i]->noInstall)
            noInstallCnt++;
        if (p12dcx->safeBags[i]->problem) {
            probCnt++;
            errorVal = p12dcx->safeBags[i]->error;
        }
        i++;
    }

    /* formerly was erroneous code here that assumed that if all bags
     * failed to import, then the problem was duplicated data;
     * that is, it assume that the problem must be that the file had
     * previously been successfully imported.  But importing a
     * previously imported file causes NO ERRORS at all, and this
     * false assumption caused real errors to be hidden behind false
     * errors about duplicated data.
     */

    if (probCnt) {
        PORT_SetError(errorVal);
        return SECFailure;
    }

    return rv;
}

SECStatus
SEC_PKCS12DecoderRenameCertNicknames(SEC_PKCS12DecoderContext *p12dcx,
                                     SEC_PKCS12NicknameRenameCallback nicknameCb,
                                     void *arg)
{
    int i;
    sec_PKCS12SafeBag *safeBag;
    CERTCertificate *cert;
    SECStatus srv;

    if (!p12dcx || p12dcx->error || !p12dcx->safeBags || !nicknameCb) {
        PORT_SetError(SEC_ERROR_INVALID_ARGS);
        return SECFailure;
    }

    for (i = 0; (safeBag = p12dcx->safeBags[i]); i++) {
        SECItem *newNickname = NULL;
        SECItem *defaultNickname = NULL;
        SECStatus rename_rv;

        if (SECOID_FindOIDTag(&(safeBag->safeBagType)) !=
            SEC_OID_PKCS12_V1_CERT_BAG_ID) {
            continue;
        }

        cert = CERT_DecodeDERCertificate(
            &safeBag->safeBagContent.certBag->value.x509Cert,
            PR_FALSE, NULL);
        if (!cert) {
            return SECFailure;
        }

        defaultNickname = sec_pkcs12_get_nickname(safeBag);
        rename_rv = (*nicknameCb)(cert, defaultNickname, &newNickname, arg);

        CERT_DestroyCertificate(cert);

        if (defaultNickname) {
            SECITEM_ZfreeItem(defaultNickname, PR_TRUE);
            defaultNickname = NULL;
        }

        if (rename_rv != SECSuccess) {
            return rename_rv;
        }

        if (newNickname) {
            srv = sec_pkcs12_set_nickname(safeBag, newNickname);
            SECITEM_ZfreeItem(newNickname, PR_TRUE);
            newNickname = NULL;
            if (srv != SECSuccess) {
                return SECFailure;
            }
        }
    }

    return SECSuccess;
}

static SECKEYPublicKey *
sec_pkcs12_get_public_key_and_usage(sec_PKCS12SafeBag *certBag,
                                    unsigned int *usage)
{
    SECKEYPublicKey *pubKey = NULL;
    CERTCertificate *cert = NULL;

    if (!certBag || !usage) {
        PORT_SetError(SEC_ERROR_INVALID_ARGS);
        return NULL;
    }

    *usage = 0;

    cert = CERT_DecodeDERCertificate(
        &certBag->safeBagContent.certBag->value.x509Cert, PR_FALSE, NULL);
    if (!cert) {
        return NULL;
    }

    *usage = cert->keyUsage;
    pubKey = CERT_ExtractPublicKey(cert);
    CERT_DestroyCertificate(cert);
    return pubKey;
}

static SECItem *
sec_pkcs12_get_public_value_and_type(SECKEYPublicKey *pubKey,
                                     KeyType *type)
{
    SECItem *pubValue = NULL;

    if (!type || !pubKey) {
        PORT_SetError(SEC_ERROR_INVALID_ARGS);
        return NULL;
    }

    *type = pubKey->keyType;
    switch (pubKey->keyType) {
        case dsaKey:
            pubValue = &pubKey->u.dsa.publicValue;
            break;
        case dhKey:
            pubValue = &pubKey->u.dh.publicValue;
            break;
        case rsaKey:
            pubValue = &pubKey->u.rsa.modulus;
            break;
        case ecKey:
            pubValue = &pubKey->u.ec.publicValue;
            break;
        default:
            pubValue = NULL;
    }

    return pubValue;
}

/* This function takes two passes over the bags, installing them in the
 * desired slot.  The two passes are intended to mirror exactly the
 * two passes in sec_pkcs12_validate_bags.
 */
static SECStatus
sec_pkcs12_install_bags(sec_PKCS12SafeBag **safeBags, void *wincx)
{
    sec_PKCS12SafeBag **keyList;
    int i;
    int failedKeys = 0;

    if (!safeBags) {
        PORT_SetError(SEC_ERROR_INVALID_ARGS);
        return SECFailure;
    }

    if (!safeBags[0]) {
        return SECSuccess;
    }

    /* First pass.  Find all the key bags.
     * Try to install them, and any certs associated with them.
     */
    keyList = sec_pkcs12_get_key_bags(safeBags);
    if (keyList) {
        for (i = 0; keyList[i]; i++) {
            SECStatus rv;
            SECKEYPublicKey *pubKey = NULL;
            SECItem *nickName = NULL;
            sec_PKCS12SafeBag *key = keyList[i];
            sec_PKCS12SafeBag **certList;
            unsigned int keyUsage;

            if (key->problem) {
                ++failedKeys;
                continue;
            }

            certList = sec_pkcs12_find_certs_for_key(safeBags, key);
            if (certList && certList[0]) {
                pubKey = sec_pkcs12_get_public_key_and_usage(certList[0],
                                                             &keyUsage);
                /* use the cert's nickname, if it has one, else use the
                 * key's nickname, else fail.
                 */
                nickName = sec_pkcs12_get_nickname_for_cert(certList[0], key);
            } else {
                nickName = sec_pkcs12_get_nickname(key);
            }
            if (!nickName) {
                key->error = SEC_ERROR_BAD_NICKNAME;
                key->problem = PR_TRUE;
                rv = SECFailure;
            } else if (!pubKey) {
                key->error = SEC_ERROR_PKCS12_UNABLE_TO_IMPORT_KEY;
                key->problem = PR_TRUE;
                rv = SECFailure;
            } else {
                rv = sec_pkcs12_add_key(key, pubKey, keyUsage, nickName, wincx);
            }
            if (pubKey) {
                SECKEY_DestroyPublicKey(pubKey);
                pubKey = NULL;
            }
            if (nickName) {
                SECITEM_FreeItem(nickName, PR_TRUE);
                nickName = NULL;
            }
            if (rv != SECSuccess) {
                PORT_SetError(key->error);
                ++failedKeys;
            }

            if (certList) {
                int j;

                for (j = 0; certList[j]; j++) {
                    sec_PKCS12SafeBag *cert = certList[j];
                    SECStatus certRv;

                    if (!cert)
                        continue;
                    if (rv != SECSuccess) {
                        cert->problem = key->problem;
                        cert->error = key->error;
                        cert->noInstall = PR_TRUE;
                        continue;
                    }

                    certRv = sec_pkcs12_add_cert(cert, cert->hasKey, wincx);
                    if (certRv != SECSuccess) {
                        key->problem = cert->problem;
                        key->error = cert->error;
                        PORT_SetError(cert->error);
                        return SECFailure;
                    }
                }
            }
        }
    }
    if (failedKeys)
        return SECFailure;

    /* Now take a second pass over the safebags and install any certs
     * that were neither installed nor disqualified by the first pass.
     */
    for (i = 0; safeBags[i]; i++) {
        sec_PKCS12SafeBag *bag = safeBags[i];

        if (!bag->installed && !bag->problem && !bag->noInstall) {
            SECStatus rv;
            SECOidTag bagType = SECOID_FindOIDTag(&(bag->safeBagType));

            switch (bagType) {
                case SEC_OID_PKCS12_V1_CERT_BAG_ID:
                    rv = sec_pkcs12_add_cert(bag, bag->hasKey, wincx);
                    if (rv != SECSuccess) {
                        PORT_SetError(bag->error);
                        return SECFailure;
                    }
                    break;
                case SEC_OID_PKCS12_V1_KEY_BAG_ID:
                case SEC_OID_PKCS12_V1_PKCS8_SHROUDED_KEY_BAG_ID:
                default:
                    break;
            }
        }
    }

    return SECSuccess;
}

SECStatus
SEC_PKCS12DecoderImportBags(SEC_PKCS12DecoderContext *p12dcx)
{
    if (!p12dcx || p12dcx->error) {
        PORT_SetError(SEC_ERROR_INVALID_ARGS);
        return SECFailure;
    }

    if (!p12dcx->bagsVerified) {
        return SECFailure;
    }

    return sec_pkcs12_install_bags(p12dcx->safeBags, p12dcx->wincx);
}

PRBool
sec_pkcs12_bagHasKey(SEC_PKCS12DecoderContext *p12dcx, sec_PKCS12SafeBag *bag)
{
    int i;
    SECItem *keyId;
    SECItem *certKeyId;

    certKeyId = sec_pkcs12_get_attribute_value(bag, SEC_OID_PKCS9_LOCAL_KEY_ID);
    if (certKeyId == NULL) {
        return PR_FALSE;
    }

    for (i = 0; p12dcx->keyList && p12dcx->keyList[i]; i++) {
        keyId = sec_pkcs12_get_attribute_value(p12dcx->keyList[i],
                                               SEC_OID_PKCS9_LOCAL_KEY_ID);
        if (!keyId) {
            continue;
        }
        if (SECITEM_CompareItem(certKeyId, keyId) == SECEqual) {
            return PR_TRUE;
        }
    }
    return PR_FALSE;
}

SECItem *
sec_pkcs12_get_friendlyName(sec_PKCS12SafeBag *bag)
{
    SECItem *friendlyName;
    SECItem *tempnm;

    tempnm = sec_pkcs12_get_attribute_value(bag, SEC_OID_PKCS9_FRIENDLY_NAME);
    friendlyName = (SECItem *)PORT_ZAlloc(sizeof(SECItem));
    if (friendlyName) {
        if (!sec_pkcs12_convert_item_to_unicode(NULL, friendlyName,
                                                tempnm, PR_TRUE, PR_FALSE, PR_FALSE)) {
            SECITEM_FreeItem(friendlyName, PR_TRUE);
            friendlyName = NULL;
        }
    }
    return friendlyName;
}

/* Following two functions provide access to selected portions of the safe bags.
 * Iteration is implemented per decoder context and may be accessed after
 * SEC_PKCS12DecoderVerify() returns success.
 * When ...DecoderIterateNext() returns SUCCESS a decoder item has been returned
 * where item.type is always set; item.friendlyName is set if it is non-null;
 * item.der, item.hasKey are set only for SEC_OID_PKCS12_V1_CERT_BAG_ID items.
 * ...DecoderIterateNext() returns FAILURE when the list is exhausted or when
 * arguments are invalid; PORT_GetError() is 0 at end-of-list.
 * Caller has read-only access to decoder items. Any SECItems generated are
 * owned by the decoder context and are freed by ...DecoderFinish().
 */
SECStatus
SEC_PKCS12DecoderIterateInit(SEC_PKCS12DecoderContext *p12dcx)
{
    if (!p12dcx || p12dcx->error) {
        PORT_SetError(SEC_ERROR_INVALID_ARGS);
        return SECFailure;
    }

    p12dcx->iteration = 0;
    return SECSuccess;
}

SECStatus
SEC_PKCS12DecoderIterateNext(SEC_PKCS12DecoderContext *p12dcx,
                             const SEC_PKCS12DecoderItem **ipp)
{
    sec_PKCS12SafeBag *bag;

    if (!p12dcx || p12dcx->error) {
        PORT_SetError(SEC_ERROR_INVALID_ARGS);
        return SECFailure;
    }

    if (p12dcx->decitem.type != 0 && p12dcx->decitem.der != NULL) {
        SECITEM_FreeItem(p12dcx->decitem.der, PR_TRUE);
    }
    if (p12dcx->decitem.shroudAlg != NULL) {
        SECOID_DestroyAlgorithmID(p12dcx->decitem.shroudAlg, PR_TRUE);
    }
    if (p12dcx->decitem.friendlyName != NULL) {
        SECITEM_FreeItem(p12dcx->decitem.friendlyName, PR_TRUE);
    }
    p12dcx->decitem.type = 0;
    p12dcx->decitem.der = NULL;
    p12dcx->decitem.shroudAlg = NULL;
    p12dcx->decitem.friendlyName = NULL;
    p12dcx->decitem.hasKey = PR_FALSE;
    *ipp = NULL;
    if (p12dcx->keyList == NULL) {
        p12dcx->keyList = sec_pkcs12_get_key_bags(p12dcx->safeBags);
    }

    for (; p12dcx->iteration < p12dcx->safeBagCount; p12dcx->iteration++) {
        bag = p12dcx->safeBags[p12dcx->iteration];
        if (bag == NULL || bag->problem) {
            continue;
        }
        p12dcx->decitem.type = SECOID_FindOIDTag(&(bag->safeBagType));
        switch (p12dcx->decitem.type) {
            case SEC_OID_PKCS12_V1_CERT_BAG_ID:
                p12dcx->decitem.der = sec_pkcs12_get_der_cert(bag);
                p12dcx->decitem.friendlyName = sec_pkcs12_get_friendlyName(bag);
                p12dcx->decitem.hasKey = sec_pkcs12_bagHasKey(p12dcx, bag);
                break;
            case SEC_OID_PKCS12_V1_PKCS8_SHROUDED_KEY_BAG_ID:
                p12dcx->decitem.shroudAlg = PORT_ZNew(SECAlgorithmID);
                if (p12dcx->decitem.shroudAlg) {
                    SECOID_CopyAlgorithmID(NULL, p12dcx->decitem.shroudAlg,
                                           &bag->safeBagContent.pkcs8ShroudedKeyBag->algorithm);
                }
            /* fall through */
            case SEC_OID_PKCS12_V1_KEY_BAG_ID:
                p12dcx->decitem.friendlyName = sec_pkcs12_get_friendlyName(bag);
                break;
            default:
                /* return these even though we don't expect them */
                break;
            case SEC_OID_UNKNOWN:
                /* ignore these */
                continue;
        }
        *ipp = &p12dcx->decitem;
        p12dcx->iteration++;
        break; /* end for() */
    }

    PORT_SetError(0); /* end-of-list is SECFailure with no PORT error */
    return ((p12dcx->decitem.type == 0) ? SECFailure : SECSuccess);
}

static SECStatus
sec_pkcs12_decoder_append_bag_to_context(SEC_PKCS12DecoderContext *p12dcx,
                                         sec_PKCS12SafeBag *bag)
{
    if (!p12dcx || p12dcx->error) {
        PORT_SetError(SEC_ERROR_INVALID_ARGS);
        return SECFailure;
    }

    p12dcx->safeBags = !p12dcx->safeBagCount
                           ? PORT_ArenaZNewArray(p12dcx->arena, sec_PKCS12SafeBag *, 2)
                           : PORT_ArenaGrowArray(p12dcx->arena, p12dcx->safeBags,
                                                 sec_PKCS12SafeBag *, p12dcx->safeBagCount + 1,
                                                 p12dcx->safeBagCount + 2);

    if (!p12dcx->safeBags) {
        PORT_SetError(SEC_ERROR_NO_MEMORY);
        return SECFailure;
    }

    p12dcx->safeBags[p12dcx->safeBagCount] = bag;
    p12dcx->safeBags[p12dcx->safeBagCount + 1] = NULL;
    p12dcx->safeBagCount++;

    return SECSuccess;
}

static sec_PKCS12SafeBag *
sec_pkcs12_decoder_convert_old_key(SEC_PKCS12DecoderContext *p12dcx,
                                   void *key, PRBool isEspvk)
{
    sec_PKCS12SafeBag *keyBag;
    SECOidData *oid;
    SECOidTag keyTag;
    SECItem *keyID, *nickName, *newNickName;

    if (!p12dcx || p12dcx->error || !key) {
        PORT_SetError(SEC_ERROR_INVALID_ARGS);
        return NULL;
    }

    newNickName = PORT_ArenaZNew(p12dcx->arena, SECItem);
    keyBag = PORT_ArenaZNew(p12dcx->arena, sec_PKCS12SafeBag);
    if (!keyBag || !newNickName) {
        return NULL;
    }

    keyBag->swapUnicodeBytes = p12dcx->swapUnicodeBytes;
    keyBag->slot = p12dcx->slot;
    keyBag->arena = p12dcx->arena;
    keyBag->pwitem = p12dcx->pwitem;
    keyBag->tokenCAs = p12dcx->tokenCAs;
    keyBag->oldBagType = PR_TRUE;

    keyTag = (isEspvk) ? SEC_OID_PKCS12_V1_PKCS8_SHROUDED_KEY_BAG_ID : SEC_OID_PKCS12_V1_KEY_BAG_ID;
    oid = SECOID_FindOIDByTag(keyTag);
    if (!oid) {
        return NULL;
    }

    if (SECITEM_CopyItem(p12dcx->arena, &keyBag->safeBagType, &oid->oid) != SECSuccess) {
        return NULL;
    }

    if (isEspvk) {
        SEC_PKCS12ESPVKItem *espvk = (SEC_PKCS12ESPVKItem *)key;
        keyBag->safeBagContent.pkcs8ShroudedKeyBag =
            espvk->espvkCipherText.pkcs8KeyShroud;
        nickName = &(espvk->espvkData.uniNickName);
        if (!espvk->espvkData.assocCerts || !espvk->espvkData.assocCerts[0]) {
            PORT_SetError(SEC_ERROR_PKCS12_CORRUPT_PFX_STRUCTURE);
            return NULL;
        }
        keyID = &espvk->espvkData.assocCerts[0]->digest;
    } else {
        SEC_PKCS12PrivateKey *pk = (SEC_PKCS12PrivateKey *)key;
        keyBag->safeBagContent.pkcs8KeyBag = &pk->pkcs8data;
        nickName = &(pk->pvkData.uniNickName);
        if (!pk->pvkData.assocCerts || !pk->pvkData.assocCerts[0]) {
            PORT_SetError(SEC_ERROR_PKCS12_CORRUPT_PFX_STRUCTURE);
            return NULL;
        }
        keyID = &pk->pvkData.assocCerts[0]->digest;
    }

    if (nickName->len) {
        if (nickName->len >= 2) {
            if (nickName->data[0] && nickName->data[1]) {
                if (!sec_pkcs12_convert_item_to_unicode(p12dcx->arena, newNickName,
                                                        nickName, PR_FALSE, PR_FALSE, PR_TRUE)) {
                    return NULL;
                }
                nickName = newNickName;
            } else if (nickName->data[0] && !nickName->data[1]) {
                unsigned int j = 0;
                unsigned char t;
                for (j = 0; j < nickName->len; j += 2) {
                    t = nickName->data[j + 1];
                    nickName->data[j + 1] = nickName->data[j];
                    nickName->data[j] = t;
                }
            }
        } else {
            if (!sec_pkcs12_convert_item_to_unicode(p12dcx->arena, newNickName,
                                                    nickName, PR_FALSE, PR_FALSE, PR_TRUE)) {
                return NULL;
            }
            nickName = newNickName;
        }
    }

    if (sec_pkcs12_decoder_set_attribute_value(keyBag,
                                               SEC_OID_PKCS9_FRIENDLY_NAME,
                                               nickName) != SECSuccess) {
        return NULL;
    }

    if (sec_pkcs12_decoder_set_attribute_value(keyBag, SEC_OID_PKCS9_LOCAL_KEY_ID,
                                               keyID) != SECSuccess) {
        return NULL;
    }

    return keyBag;
}

static sec_PKCS12SafeBag *
sec_pkcs12_decoder_create_cert(SEC_PKCS12DecoderContext *p12dcx,
                               SECItem *derCert)
{
    sec_PKCS12SafeBag *certBag;
    SECOidData *oid;
    SGNDigestInfo *digest;
    SECItem *keyId;
    SECStatus rv;

    if (!p12dcx || p12dcx->error || !derCert) {
        PORT_SetError(SEC_ERROR_INVALID_ARGS);
        return NULL;
    }

    keyId = PORT_ArenaZNew(p12dcx->arena, SECItem);
    if (!keyId) {
        return NULL;
    }

    digest = sec_pkcs12_compute_thumbprint(derCert);
    if (!digest) {
        return NULL;
    }

    rv = SECITEM_CopyItem(p12dcx->arena, keyId, &digest->digest);
    SGN_DestroyDigestInfo(digest);
    if (rv != SECSuccess) {
        PORT_SetError(SEC_ERROR_NO_MEMORY);
        return NULL;
    }

    oid = SECOID_FindOIDByTag(SEC_OID_PKCS12_V1_CERT_BAG_ID);
    certBag = PORT_ArenaZNew(p12dcx->arena, sec_PKCS12SafeBag);
    if (!certBag || !oid || (SECITEM_CopyItem(p12dcx->arena,
                                              &certBag->safeBagType, &oid->oid) != SECSuccess)) {
        return NULL;
    }

    certBag->slot = p12dcx->slot;
    certBag->pwitem = p12dcx->pwitem;
    certBag->swapUnicodeBytes = p12dcx->swapUnicodeBytes;
    certBag->arena = p12dcx->arena;
    certBag->tokenCAs = p12dcx->tokenCAs;

    oid = SECOID_FindOIDByTag(SEC_OID_PKCS9_X509_CERT);
    certBag->safeBagContent.certBag =
        PORT_ArenaZNew(p12dcx->arena, sec_PKCS12CertBag);
    if (!certBag->safeBagContent.certBag || !oid ||
        (SECITEM_CopyItem(p12dcx->arena,
                          &certBag->safeBagContent.certBag->bagID,
                          &oid->oid) != SECSuccess)) {
        return NULL;
    }

    if (SECITEM_CopyItem(p12dcx->arena,
                         &(certBag->safeBagContent.certBag->value.x509Cert),
                         derCert) != SECSuccess) {
        return NULL;
    }

    if (sec_pkcs12_decoder_set_attribute_value(certBag, SEC_OID_PKCS9_LOCAL_KEY_ID,
                                               keyId) != SECSuccess) {
        return NULL;
    }

    return certBag;
}

static sec_PKCS12SafeBag **
sec_pkcs12_decoder_convert_old_cert(SEC_PKCS12DecoderContext *p12dcx,
                                    SEC_PKCS12CertAndCRL *oldCert)
{
    sec_PKCS12SafeBag **certList;
    SECItem **derCertList;
    int i, j;

    if (!p12dcx || p12dcx->error || !oldCert) {
        PORT_SetError(SEC_ERROR_INVALID_ARGS);
        return NULL;
    }

    derCertList = SEC_PKCS7GetCertificateList(&oldCert->value.x509->certOrCRL);
    if (!derCertList) {
        return NULL;
    }

    i = 0;
    while (derCertList[i])
        i++;

    certList = PORT_ArenaZNewArray(p12dcx->arena, sec_PKCS12SafeBag *, (i + 1));
    if (!certList) {
        return NULL;
    }

    for (j = 0; j < i; j++) {
        certList[j] = sec_pkcs12_decoder_create_cert(p12dcx, derCertList[j]);
        if (!certList[j]) {
            return NULL;
        }
    }

    return certList;
}

static SECStatus
sec_pkcs12_decoder_convert_old_key_and_certs(SEC_PKCS12DecoderContext *p12dcx,
                                             void *oldKey, PRBool isEspvk,
                                             SEC_PKCS12SafeContents *safe,
                                             SEC_PKCS12Baggage *baggage)
{
    sec_PKCS12SafeBag *key, **certList;
    SEC_PKCS12CertAndCRL *oldCert;
    SEC_PKCS12PVKSupportingData *pvkData;
    int i;
    SECItem *keyName;

    if (!p12dcx || !oldKey) {
        return SECFailure;
    }

    if (isEspvk) {
        pvkData = &((SEC_PKCS12ESPVKItem *)(oldKey))->espvkData;
    } else {
        pvkData = &((SEC_PKCS12PrivateKey *)(oldKey))->pvkData;
    }

    if (!pvkData->assocCerts || !pvkData->assocCerts[0]) {
        PORT_SetError(SEC_ERROR_PKCS12_CORRUPT_PFX_STRUCTURE);
        return SECFailure;
    }

    oldCert = (SEC_PKCS12CertAndCRL *)sec_pkcs12_find_object(safe, baggage,
                                                             SEC_OID_PKCS12_CERT_AND_CRL_BAG_ID, NULL,
                                                             pvkData->assocCerts[0]);
    if (!oldCert) {
        PORT_SetError(SEC_ERROR_PKCS12_CORRUPT_PFX_STRUCTURE);
        return SECFailure;
    }

    key = sec_pkcs12_decoder_convert_old_key(p12dcx, oldKey, isEspvk);
    certList = sec_pkcs12_decoder_convert_old_cert(p12dcx, oldCert);
    if (!key || !certList) {
        return SECFailure;
    }

    if (sec_pkcs12_decoder_append_bag_to_context(p12dcx, key) != SECSuccess) {
        return SECFailure;
    }

    keyName = sec_pkcs12_get_nickname(key);
    if (!keyName) {
        return SECFailure;
    }

    i = 0;
    while (certList[i]) {
        if (sec_pkcs12_decoder_append_bag_to_context(p12dcx, certList[i]) != SECSuccess) {
            return SECFailure;
        }
        i++;
    }

    certList = sec_pkcs12_find_certs_for_key(p12dcx->safeBags, key);
    if (!certList) {
        return SECFailure;
    }

    i = 0;
    while (certList[i] != 0) {
        if (sec_pkcs12_set_nickname(certList[i], keyName) != SECSuccess) {
            return SECFailure;
        }
        i++;
    }

    return SECSuccess;
}

static SECStatus
sec_pkcs12_decoder_convert_old_safe_to_bags(SEC_PKCS12DecoderContext *p12dcx,
                                            SEC_PKCS12SafeContents *safe,
                                            SEC_PKCS12Baggage *baggage)
{
    SECStatus rv;

    if (!p12dcx || p12dcx->error) {
        PORT_SetError(SEC_ERROR_INVALID_ARGS);
        return SECFailure;
    }

    if (safe && safe->contents) {
        int i = 0;
        while (safe->contents[i] != NULL) {
            if (SECOID_FindOIDTag(&safe->contents[i]->safeBagType) == SEC_OID_PKCS12_KEY_BAG_ID) {
                int j = 0;
                SEC_PKCS12PrivateKeyBag *privBag =
                    safe->contents[i]->safeContent.keyBag;

                while (privBag->privateKeys[j] != NULL) {
                    SEC_PKCS12PrivateKey *pk = privBag->privateKeys[j];
                    rv = sec_pkcs12_decoder_convert_old_key_and_certs(p12dcx, pk,
                                                                      PR_FALSE, safe, baggage);
                    if (rv != SECSuccess) {
                        goto loser;
                    }
                    j++;
                }
            }
            i++;
        }
    }

    if (baggage && baggage->bags) {
        int i = 0;
        while (baggage->bags[i] != NULL) {
            SEC_PKCS12BaggageItem *bag = baggage->bags[i];
            int j = 0;

            if (!bag->espvks) {
                i++;
                continue;
            }

            while (bag->espvks[j] != NULL) {
                SEC_PKCS12ESPVKItem *espvk = bag->espvks[j];
                rv = sec_pkcs12_decoder_convert_old_key_and_certs(p12dcx, espvk,
                                                                  PR_TRUE, safe, baggage);
                if (rv != SECSuccess) {
                    goto loser;
                }
                j++;
            }
            i++;
        }
    }

    return SECSuccess;

loser:
    return SECFailure;
}

SEC_PKCS12DecoderContext *
sec_PKCS12ConvertOldSafeToNew(PLArenaPool *arena, PK11SlotInfo *slot,
                              PRBool swapUnicode, SECItem *pwitem,
                              void *wincx, SEC_PKCS12SafeContents *safe,
                              SEC_PKCS12Baggage *baggage)
{
    SEC_PKCS12DecoderContext *p12dcx;

    if (!arena || !slot || !pwitem) {
        PORT_SetError(SEC_ERROR_INVALID_ARGS);
        return NULL;
    }

    if (!safe && !baggage) {
        PORT_SetError(SEC_ERROR_INVALID_ARGS);
        return NULL;
    }

    p12dcx = PORT_ArenaZNew(arena, SEC_PKCS12DecoderContext);
    if (!p12dcx) {
        return NULL;
    }

    p12dcx->arena = arena;
    p12dcx->slot = PK11_ReferenceSlot(slot);
    p12dcx->wincx = wincx;
    p12dcx->error = PR_FALSE;
    p12dcx->swapUnicodeBytes = swapUnicode;
    p12dcx->pwitem = pwitem;
    p12dcx->tokenCAs = SECPKCS12TargetTokenNoCAs;

    if (sec_pkcs12_decoder_convert_old_safe_to_bags(p12dcx, safe, baggage) != SECSuccess) {
        p12dcx->error = PR_TRUE;
        return NULL;
    }

    return p12dcx;
}