/* 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 "p12t.h"
#include "p12.h"
#include "plarena.h"
#include "secitem.h"
#include "secoid.h"
#include "seccomon.h"
#include "secport.h"
#include "cert.h"
#include "secpkcs5.h"
#include "secpkcs7.h"
#include "secasn1.h"
#include "secerr.h"
#include "pk11func.h"
#include "p12plcy.h"
#include "p12local.h"
#include "prcpucfg.h"

extern const int NSS_PBE_DEFAULT_ITERATION_COUNT; /* defined in p7create.c */

/*
** This PKCS12 file encoder uses numerous nested ASN.1 and PKCS7 encoder
** contexts.  It can be difficult to keep straight.  Here's a picture:
**
**  "outer"  ASN.1 encoder.  The output goes to the library caller's CB.
**  "middle" PKCS7 encoder.  Feeds    the "outer" ASN.1 encoder.
**  "middle" ASN1  encoder.  Encodes  the encrypted aSafes.
**                           Feeds    the "middle" P7 encoder above.
**  "inner"  PKCS7 encoder.  Encrypts the "authenticated Safes" (aSafes)
**                           Feeds    the "middle" ASN.1 encoder above.
**  "inner"  ASN.1 encoder.  Encodes  the unencrypted aSafes.
**                           Feeds    the "inner" P7 enocder above.
**
** Buffering has been added at each point where the output of an ASN.1
** encoder feeds the input of a PKCS7 encoder.
*/

/*********************************
 * Output buffer object, used to buffer output from ASN.1 encoder
 * before passing data on down to the next PKCS7 encoder.
 *********************************/

#define PK12_OUTPUT_BUFFER_SIZE 8192

struct sec_pkcs12OutputBufferStr {
    SEC_PKCS7EncoderContext *p7eCx;
    PK11Context *hmacCx;
    unsigned int numBytes;
    unsigned int bufBytes;
    char buf[PK12_OUTPUT_BUFFER_SIZE];
};
typedef struct sec_pkcs12OutputBufferStr sec_pkcs12OutputBuffer;

/*********************************
 * Structures used in exporting the PKCS 12 blob
 *********************************/

/* A SafeInfo is used for each ContentInfo which makes up the
 * sequence of safes in the AuthenticatedSafe portion of the
 * PFX structure.
 */
struct SEC_PKCS12SafeInfoStr {
    PLArenaPool *arena;

    /* information for setting up password encryption */
    SECItem pwitem;
    SECOidTag algorithm;
    PK11SymKey *encryptionKey;

    /* how many items have been stored in this safe,
     * we will skip any safe which does not contain any
     * items
     */
    unsigned int itemCount;

    /* the content info for the safe */
    SEC_PKCS7ContentInfo *cinfo;

    sec_PKCS12SafeContents *safe;
};

/* An opaque structure which contains information needed for exporting
 * certificates and keys through PKCS 12.
 */
struct SEC_PKCS12ExportContextStr {
    PLArenaPool *arena;
    PK11SlotInfo *slot;
    void *wincx;

    /* integrity information */
    PRBool integrityEnabled;
    PRBool pwdIntegrity;
    union {
        struct sec_PKCS12PasswordModeInfo pwdInfo;
        struct sec_PKCS12PublicKeyModeInfo pubkeyInfo;
    } integrityInfo;

    /* helper functions */
    /* retrieve the password call back */
    SECKEYGetPasswordKey pwfn;
    void *pwfnarg;

    /* safe contents bags */
    SEC_PKCS12SafeInfo **safeInfos;
    unsigned int safeInfoCount;

    /* the sequence of safes */
    sec_PKCS12AuthenticatedSafe authSafe;

    /* information needing deletion */
    CERTCertificate **certList;
};

/* structures for passing information to encoder callbacks when processing
 * data through the ASN1 engine.
 */
struct sec_pkcs12_encoder_output {
    SEC_PKCS12EncoderOutputCallback outputfn;
    void *outputarg;
};

struct sec_pkcs12_hmac_and_output_info {
    void *arg;
    struct sec_pkcs12_encoder_output output;
};

/* An encoder context which is used for the actual encoding
 * portion of PKCS 12.
 */
typedef struct sec_PKCS12EncoderContextStr {
    PLArenaPool *arena;
    SEC_PKCS12ExportContext *p12exp;

    /* encoder information - this is set up based on whether
     * password based or public key pased privacy is being used
     */
    SEC_ASN1EncoderContext *outerA1ecx;
    union {
        struct sec_pkcs12_hmac_and_output_info hmacAndOutputInfo;
        struct sec_pkcs12_encoder_output encOutput;
    } output;

    /* structures for encoding of PFX and MAC */
    sec_PKCS12PFXItem pfx;
    sec_PKCS12MacData mac;

    /* authenticated safe encoding tracking information */
    SEC_PKCS7ContentInfo *aSafeCinfo;
    SEC_PKCS7EncoderContext *middleP7ecx;
    SEC_ASN1EncoderContext *middleA1ecx;
    unsigned int currentSafe;

    /* hmac context */
    PK11Context *hmacCx;

    /* output buffers */
    sec_pkcs12OutputBuffer middleBuf;
    sec_pkcs12OutputBuffer innerBuf;

} sec_PKCS12EncoderContext;

/*********************************
 * Export setup routines
 *********************************/

/* SEC_PKCS12CreateExportContext
 *   Creates an export context and sets the unicode and password retrieval
 *   callbacks.  This is the first call which must be made when exporting
 *   a PKCS 12 blob.
 *
 * pwfn, pwfnarg - password retrieval callback and argument.  these are
 *                 required for password-authentication mode.
 */
SEC_PKCS12ExportContext *
SEC_PKCS12CreateExportContext(SECKEYGetPasswordKey pwfn, void *pwfnarg,
                              PK11SlotInfo *slot, void *wincx)
{
    PLArenaPool *arena = NULL;
    SEC_PKCS12ExportContext *p12ctxt = NULL;

    /* allocate the arena and create the context */
    arena = PORT_NewArena(4096);
    if (!arena) {
        PORT_SetError(SEC_ERROR_NO_MEMORY);
        return NULL;
    }

    p12ctxt = (SEC_PKCS12ExportContext *)PORT_ArenaZAlloc(arena,
                                                          sizeof(SEC_PKCS12ExportContext));
    if (!p12ctxt) {
        PORT_SetError(SEC_ERROR_NO_MEMORY);
        goto loser;
    }

    /* password callback for key retrieval */
    p12ctxt->pwfn = pwfn;
    p12ctxt->pwfnarg = pwfnarg;

    p12ctxt->integrityEnabled = PR_FALSE;
    p12ctxt->arena = arena;
    p12ctxt->wincx = wincx;
    p12ctxt->slot = (slot) ? PK11_ReferenceSlot(slot) : PK11_GetInternalSlot();

    return p12ctxt;

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

    return NULL;
}

/*
 * Adding integrity mode
 */

/* SEC_PKCS12AddPasswordIntegrity
 *      Add password integrity to the exported data.  If an integrity method
 *      has already been set, then return an error.
 *
 *      p12ctxt - the export context
 *      pwitem - the password for integrity mode
 *      integAlg - the integrity algorithm to use for authentication.
 */
SECStatus
SEC_PKCS12AddPasswordIntegrity(SEC_PKCS12ExportContext *p12ctxt,
                               SECItem *pwitem, SECOidTag integAlg)
{
    if (!p12ctxt || p12ctxt->integrityEnabled) {
        return SECFailure;
    }

    /* set up integrity information */
    p12ctxt->pwdIntegrity = PR_TRUE;
    p12ctxt->integrityInfo.pwdInfo.password =
        (SECItem *)PORT_ArenaZAlloc(p12ctxt->arena, sizeof(SECItem));
    if (!p12ctxt->integrityInfo.pwdInfo.password) {
        PORT_SetError(SEC_ERROR_NO_MEMORY);
        return SECFailure;
    }
    if (SECITEM_CopyItem(p12ctxt->arena,
                         p12ctxt->integrityInfo.pwdInfo.password, pwitem) != SECSuccess) {
        PORT_SetError(SEC_ERROR_NO_MEMORY);
        return SECFailure;
    }
    p12ctxt->integrityInfo.pwdInfo.algorithm = integAlg;
    p12ctxt->integrityEnabled = PR_TRUE;

    return SECSuccess;
}

/* SEC_PKCS12AddPublicKeyIntegrity
 *      Add public key integrity to the exported data.  If an integrity method
 *      has already been set, then return an error.  The certificate must be
 *      allowed to be used as a signing cert.
 *
 *      p12ctxt - the export context
 *      cert - signer certificate
 *      certDb - the certificate database
 *      algorithm - signing algorithm
 *      keySize - size of the signing key (?)
 */
SECStatus
SEC_PKCS12AddPublicKeyIntegrity(SEC_PKCS12ExportContext *p12ctxt,
                                CERTCertificate *cert, CERTCertDBHandle *certDb,
                                SECOidTag algorithm, int keySize)
{
    if (!p12ctxt) {
        return SECFailure;
    }

    p12ctxt->integrityInfo.pubkeyInfo.cert = cert;
    p12ctxt->integrityInfo.pubkeyInfo.certDb = certDb;
    p12ctxt->integrityInfo.pubkeyInfo.algorithm = algorithm;
    p12ctxt->integrityInfo.pubkeyInfo.keySize = keySize;
    p12ctxt->integrityEnabled = PR_TRUE;

    return SECSuccess;
}

/*
 * Adding safes - encrypted (password/public key) or unencrypted
 *      Each of the safe creation routines return an opaque pointer which
 *      are later passed into the routines for exporting certificates and
 *      keys.
 */

/* append the newly created safeInfo to list of safeInfos in the export
 * context.
 */
static SECStatus
sec_pkcs12_append_safe_info(SEC_PKCS12ExportContext *p12ctxt, SEC_PKCS12SafeInfo *info)
{
    void *mark = NULL, *dummy1 = NULL, *dummy2 = NULL;

    if (!p12ctxt || !info) {
        return SECFailure;
    }

    mark = PORT_ArenaMark(p12ctxt->arena);

    /* if no safeInfos have been set, create the list, otherwise expand it. */
    if (!p12ctxt->safeInfoCount) {
        p12ctxt->safeInfos = (SEC_PKCS12SafeInfo **)PORT_ArenaZAlloc(p12ctxt->arena,
                                                                     2 * sizeof(SEC_PKCS12SafeInfo *));
        dummy1 = p12ctxt->safeInfos;
        p12ctxt->authSafe.encodedSafes = (SECItem **)PORT_ArenaZAlloc(p12ctxt->arena,
                                                                      2 * sizeof(SECItem *));
        dummy2 = p12ctxt->authSafe.encodedSafes;
    } else {
        dummy1 = PORT_ArenaGrow(p12ctxt->arena, p12ctxt->safeInfos,
                                (p12ctxt->safeInfoCount + 1) * sizeof(SEC_PKCS12SafeInfo *),
                                (p12ctxt->safeInfoCount + 2) * sizeof(SEC_PKCS12SafeInfo *));
        p12ctxt->safeInfos = (SEC_PKCS12SafeInfo **)dummy1;
        dummy2 = PORT_ArenaGrow(p12ctxt->arena, p12ctxt->authSafe.encodedSafes,
                                (p12ctxt->authSafe.safeCount + 1) * sizeof(SECItem *),
                                (p12ctxt->authSafe.safeCount + 2) * sizeof(SECItem *));
        p12ctxt->authSafe.encodedSafes = (SECItem **)dummy2;
    }
    if (!dummy1 || !dummy2) {
        PORT_SetError(SEC_ERROR_NO_MEMORY);
        goto loser;
    }

    /* append the new safeInfo and null terminate the list */
    p12ctxt->safeInfos[p12ctxt->safeInfoCount] = info;
    p12ctxt->safeInfos[++p12ctxt->safeInfoCount] = NULL;
    p12ctxt->authSafe.encodedSafes[p12ctxt->authSafe.safeCount] =
        (SECItem *)PORT_ArenaZAlloc(p12ctxt->arena, sizeof(SECItem));
    if (!p12ctxt->authSafe.encodedSafes[p12ctxt->authSafe.safeCount]) {
        PORT_SetError(SEC_ERROR_NO_MEMORY);
        goto loser;
    }
    p12ctxt->authSafe.encodedSafes[++p12ctxt->authSafe.safeCount] = NULL;

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

loser:
    PORT_ArenaRelease(p12ctxt->arena, mark);
    return SECFailure;
}

/* SEC_PKCS12CreatePasswordPrivSafe
 *      Create a password privacy safe to store exported information in.
 *
 *      p12ctxt - export context
 *      pwitem - password for encryption
 *      privAlg - pbe algorithm through which encryption is done.
 */
SEC_PKCS12SafeInfo *
SEC_PKCS12CreatePasswordPrivSafe(SEC_PKCS12ExportContext *p12ctxt,
                                 SECItem *pwitem, SECOidTag privAlg)
{
    SEC_PKCS12SafeInfo *safeInfo = NULL;
    void *mark = NULL;
    PK11SlotInfo *slot = NULL;
    SECAlgorithmID *algId;
    SECItem uniPwitem = { siBuffer, NULL, 0 };

    if (!p12ctxt) {
        return NULL;
    }

    /* allocate the safe info */
    mark = PORT_ArenaMark(p12ctxt->arena);
    safeInfo = (SEC_PKCS12SafeInfo *)PORT_ArenaZAlloc(p12ctxt->arena,
                                                      sizeof(SEC_PKCS12SafeInfo));
    if (!safeInfo) {
        PORT_SetError(SEC_ERROR_NO_MEMORY);
        PORT_ArenaRelease(p12ctxt->arena, mark);
        return NULL;
    }

    safeInfo->itemCount = 0;

    /* create the encrypted safe */
    if (!SEC_PKCS5IsAlgorithmPBEAlgTag(privAlg) &&
        PK11_AlgtagToMechanism(privAlg) == CKM_AES_CBC) {
        safeInfo->cinfo = SEC_PKCS7CreateEncryptedDataWithPBEV2(SEC_OID_PKCS5_PBES2,
                                                                privAlg,
                                                                SEC_OID_UNKNOWN,
                                                                0,
                                                                p12ctxt->pwfn,
                                                                p12ctxt->pwfnarg);
    } else {
        safeInfo->cinfo = SEC_PKCS7CreateEncryptedData(privAlg, 0, p12ctxt->pwfn,
                                                       p12ctxt->pwfnarg);
    }
    if (!safeInfo->cinfo) {
        PORT_SetError(SEC_ERROR_NO_MEMORY);
        goto loser;
    }
    safeInfo->arena = p12ctxt->arena;

    if (!sec_pkcs12_encode_password(NULL, &uniPwitem, privAlg, pwitem)) {
        PORT_SetError(SEC_ERROR_NO_MEMORY);
        goto loser;
    }
    if (SECITEM_CopyItem(p12ctxt->arena, &safeInfo->pwitem, &uniPwitem) != SECSuccess) {
        PORT_SetError(SEC_ERROR_NO_MEMORY);
        goto loser;
    }

    /* generate the encryption key */
    slot = PK11_ReferenceSlot(p12ctxt->slot);
    if (!slot) {
        slot = PK11_GetInternalKeySlot();
        if (!slot) {
            PORT_SetError(SEC_ERROR_NO_MEMORY);
            goto loser;
        }
    }

    algId = SEC_PKCS7GetEncryptionAlgorithm(safeInfo->cinfo);
    safeInfo->encryptionKey = PK11_PBEKeyGen(slot, algId, &uniPwitem,
                                             PR_FALSE, p12ctxt->wincx);
    if (!safeInfo->encryptionKey) {
        goto loser;
    }

    safeInfo->arena = p12ctxt->arena;
    safeInfo->safe = NULL;
    if (sec_pkcs12_append_safe_info(p12ctxt, safeInfo) != SECSuccess) {
        goto loser;
    }

    if (uniPwitem.data) {
        SECITEM_ZfreeItem(&uniPwitem, PR_FALSE);
    }
    PORT_ArenaUnmark(p12ctxt->arena, mark);

    if (slot) {
        PK11_FreeSlot(slot);
    }
    return safeInfo;

loser:
    if (slot) {
        PK11_FreeSlot(slot);
    }
    if (safeInfo->cinfo) {
        SEC_PKCS7DestroyContentInfo(safeInfo->cinfo);
    }

    if (uniPwitem.data) {
        SECITEM_ZfreeItem(&uniPwitem, PR_FALSE);
    }

    PORT_ArenaRelease(p12ctxt->arena, mark);
    return NULL;
}

/* SEC_PKCS12CreateUnencryptedSafe
 *      Creates an unencrypted safe within the export context.
 *
 *      p12ctxt - the export context
 */
SEC_PKCS12SafeInfo *
SEC_PKCS12CreateUnencryptedSafe(SEC_PKCS12ExportContext *p12ctxt)
{
    SEC_PKCS12SafeInfo *safeInfo = NULL;
    void *mark = NULL;

    if (!p12ctxt) {
        return NULL;
    }

    /* create the safe info */
    mark = PORT_ArenaMark(p12ctxt->arena);
    safeInfo = (SEC_PKCS12SafeInfo *)PORT_ArenaZAlloc(p12ctxt->arena,
                                                      sizeof(SEC_PKCS12SafeInfo));
    if (!safeInfo) {
        PORT_ArenaRelease(p12ctxt->arena, mark);
        PORT_SetError(SEC_ERROR_NO_MEMORY);
        return NULL;
    }

    safeInfo->itemCount = 0;

    /* create the safe content */
    safeInfo->cinfo = SEC_PKCS7CreateData();
    if (!safeInfo->cinfo) {
        PORT_SetError(SEC_ERROR_NO_MEMORY);
        goto loser;
    }

    if (sec_pkcs12_append_safe_info(p12ctxt, safeInfo) != SECSuccess) {
        goto loser;
    }

    PORT_ArenaUnmark(p12ctxt->arena, mark);
    return safeInfo;

loser:
    if (safeInfo->cinfo) {
        SEC_PKCS7DestroyContentInfo(safeInfo->cinfo);
    }

    PORT_ArenaRelease(p12ctxt->arena, mark);
    return NULL;
}

/* SEC_PKCS12CreatePubKeyEncryptedSafe
 *      Creates a safe which is protected by public key encryption.
 *
 *      p12ctxt - the export context
 *      certDb - the certificate database
 *      signer - the signer's certificate
 *      recipients - the list of recipient certificates.
 *      algorithm - the encryption algorithm to use
 *      keysize - the algorithms key size (?)
 */
SEC_PKCS12SafeInfo *
SEC_PKCS12CreatePubKeyEncryptedSafe(SEC_PKCS12ExportContext *p12ctxt,
                                    CERTCertDBHandle *certDb,
                                    CERTCertificate *signer,
                                    CERTCertificate **recipients,
                                    SECOidTag algorithm, int keysize)
{
    SEC_PKCS12SafeInfo *safeInfo = NULL;
    void *mark = NULL;

    if (!p12ctxt || !signer || !recipients || !(*recipients)) {
        return NULL;
    }

    /* allocate the safeInfo */
    mark = PORT_ArenaMark(p12ctxt->arena);
    safeInfo = (SEC_PKCS12SafeInfo *)PORT_ArenaZAlloc(p12ctxt->arena,
                                                      sizeof(SEC_PKCS12SafeInfo));
    if (!safeInfo) {
        PORT_ArenaRelease(p12ctxt->arena, mark);
        PORT_SetError(SEC_ERROR_NO_MEMORY);
        return NULL;
    }

    safeInfo->itemCount = 0;
    safeInfo->arena = p12ctxt->arena;

    /* create the enveloped content info using certUsageEmailSigner currently.
     * XXX We need to eventually use something other than certUsageEmailSigner
     */
    safeInfo->cinfo = SEC_PKCS7CreateEnvelopedData(signer, certUsageEmailSigner,
                                                   certDb, algorithm, keysize,
                                                   p12ctxt->pwfn, p12ctxt->pwfnarg);
    if (!safeInfo->cinfo) {
        PORT_SetError(SEC_ERROR_NO_MEMORY);
        goto loser;
    }

    /* add recipients */
    if (recipients) {
        unsigned int i = 0;
        while (recipients[i] != NULL) {
            SECStatus rv = SEC_PKCS7AddRecipient(safeInfo->cinfo, recipients[i],
                                                 certUsageEmailRecipient, certDb);
            if (rv != SECSuccess) {
                goto loser;
            }
            i++;
        }
    }

    if (sec_pkcs12_append_safe_info(p12ctxt, safeInfo) != SECSuccess) {
        goto loser;
    }

    PORT_ArenaUnmark(p12ctxt->arena, mark);
    return safeInfo;

loser:
    if (safeInfo->cinfo) {
        SEC_PKCS7DestroyContentInfo(safeInfo->cinfo);
        safeInfo->cinfo = NULL;
    }

    PORT_ArenaRelease(p12ctxt->arena, mark);
    return NULL;
}

/*********************************
 * Routines to handle the exporting of the keys and certificates
 *********************************/

/* creates a safe contents which safeBags will be appended to */
sec_PKCS12SafeContents *
sec_PKCS12CreateSafeContents(PLArenaPool *arena)
{
    sec_PKCS12SafeContents *safeContents;

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

    /* create the safe contents */
    safeContents = (sec_PKCS12SafeContents *)PORT_ArenaZAlloc(arena,
                                                              sizeof(sec_PKCS12SafeContents));
    if (!safeContents) {
        PORT_SetError(SEC_ERROR_NO_MEMORY);
        goto loser;
    }

    /* set up the internal contents info */
    safeContents->safeBags = NULL;
    safeContents->arena = arena;
    safeContents->bagCount = 0;

    return safeContents;

loser:
    return NULL;
}

/* appends a safe bag to a safeContents using the specified arena.
 */
SECStatus
sec_pkcs12_append_bag_to_safe_contents(PLArenaPool *arena,
                                       sec_PKCS12SafeContents *safeContents,
                                       sec_PKCS12SafeBag *safeBag)
{
    void *mark = NULL, *dummy = NULL;

    if (!arena || !safeBag || !safeContents) {
        return SECFailure;
    }

    mark = PORT_ArenaMark(arena);
    if (!mark) {
        PORT_SetError(SEC_ERROR_NO_MEMORY);
        return SECFailure;
    }

    /* allocate space for the list, or reallocate to increase space */
    if (!safeContents->safeBags) {
        safeContents->safeBags = (sec_PKCS12SafeBag **)PORT_ArenaZAlloc(arena,
                                                                        (2 * sizeof(sec_PKCS12SafeBag *)));
        dummy = safeContents->safeBags;
        safeContents->bagCount = 0;
    } else {
        dummy = PORT_ArenaGrow(arena, safeContents->safeBags,
                               (safeContents->bagCount + 1) * sizeof(sec_PKCS12SafeBag *),
                               (safeContents->bagCount + 2) * sizeof(sec_PKCS12SafeBag *));
        safeContents->safeBags = (sec_PKCS12SafeBag **)dummy;
    }

    if (!dummy) {
        PORT_ArenaRelease(arena, mark);
        PORT_SetError(SEC_ERROR_NO_MEMORY);
        return SECFailure;
    }

    /* append the bag at the end and null terminate the list */
    safeContents->safeBags[safeContents->bagCount++] = safeBag;
    safeContents->safeBags[safeContents->bagCount] = NULL;

    PORT_ArenaUnmark(arena, mark);

    return SECSuccess;
}

/* appends a safeBag to a specific safeInfo.
 */
SECStatus
sec_pkcs12_append_bag(SEC_PKCS12ExportContext *p12ctxt,
                      SEC_PKCS12SafeInfo *safeInfo, sec_PKCS12SafeBag *safeBag)
{
    sec_PKCS12SafeContents *dest;
    SECStatus rv = SECFailure;

    if (!p12ctxt || !safeBag || !safeInfo) {
        return SECFailure;
    }

    if (!safeInfo->safe) {
        safeInfo->safe = sec_PKCS12CreateSafeContents(p12ctxt->arena);
        if (!safeInfo->safe) {
            return SECFailure;
        }
    }

    dest = safeInfo->safe;
    rv = sec_pkcs12_append_bag_to_safe_contents(p12ctxt->arena, dest, safeBag);
    if (rv == SECSuccess) {
        safeInfo->itemCount++;
    }

    return rv;
}

/* Creates a safeBag of the specified type, and if bagData is specified,
 * the contents are set.  The contents could be set later by the calling
 * routine.
 */
sec_PKCS12SafeBag *
sec_PKCS12CreateSafeBag(SEC_PKCS12ExportContext *p12ctxt, SECOidTag bagType,
                        void *bagData)
{
    sec_PKCS12SafeBag *safeBag;
    void *mark = NULL;
    SECStatus rv = SECSuccess;
    SECOidData *oidData = NULL;

    if (!p12ctxt) {
        return NULL;
    }

    mark = PORT_ArenaMark(p12ctxt->arena);
    if (!mark) {
        PORT_SetError(SEC_ERROR_NO_MEMORY);
        return NULL;
    }

    safeBag = (sec_PKCS12SafeBag *)PORT_ArenaZAlloc(p12ctxt->arena,
                                                    sizeof(sec_PKCS12SafeBag));
    if (!safeBag) {
        PORT_ArenaRelease(p12ctxt->arena, mark);
        PORT_SetError(SEC_ERROR_NO_MEMORY);
        return NULL;
    }

    /* set the bags content based upon bag type */
    switch (bagType) {
        case SEC_OID_PKCS12_V1_KEY_BAG_ID:
            safeBag->safeBagContent.pkcs8KeyBag =
                (SECKEYPrivateKeyInfo *)bagData;
            break;
        case SEC_OID_PKCS12_V1_CERT_BAG_ID:
            safeBag->safeBagContent.certBag = (sec_PKCS12CertBag *)bagData;
            break;
        case SEC_OID_PKCS12_V1_CRL_BAG_ID:
            safeBag->safeBagContent.crlBag = (sec_PKCS12CRLBag *)bagData;
            break;
        case SEC_OID_PKCS12_V1_SECRET_BAG_ID:
            safeBag->safeBagContent.secretBag = (sec_PKCS12SecretBag *)bagData;
            break;
        case SEC_OID_PKCS12_V1_PKCS8_SHROUDED_KEY_BAG_ID:
            safeBag->safeBagContent.pkcs8ShroudedKeyBag =
                (SECKEYEncryptedPrivateKeyInfo *)bagData;
            break;
        case SEC_OID_PKCS12_V1_SAFE_CONTENTS_BAG_ID:
            safeBag->safeBagContent.safeContents =
                (sec_PKCS12SafeContents *)bagData;
            break;
        default:
            goto loser;
    }

    oidData = SECOID_FindOIDByTag(bagType);
    if (oidData) {
        rv = SECITEM_CopyItem(p12ctxt->arena, &safeBag->safeBagType, &oidData->oid);
        if (rv != SECSuccess) {
            PORT_SetError(SEC_ERROR_NO_MEMORY);
            goto loser;
        }
    } else {
        goto loser;
    }

    safeBag->arena = p12ctxt->arena;
    PORT_ArenaUnmark(p12ctxt->arena, mark);

    return safeBag;

loser:
    if (mark) {
        PORT_ArenaRelease(p12ctxt->arena, mark);
    }

    return NULL;
}

/* Creates a new certificate bag and returns a pointer to it.  If an error
 * occurs NULL is returned.
 */
sec_PKCS12CertBag *
sec_PKCS12NewCertBag(PLArenaPool *arena, SECOidTag certType)
{
    sec_PKCS12CertBag *certBag = NULL;
    SECOidData *bagType = NULL;
    SECStatus rv;
    void *mark = NULL;

    if (!arena) {
        return NULL;
    }

    mark = PORT_ArenaMark(arena);
    certBag = (sec_PKCS12CertBag *)PORT_ArenaZAlloc(arena,
                                                    sizeof(sec_PKCS12CertBag));
    if (!certBag) {
        PORT_ArenaRelease(arena, mark);
        PORT_SetError(SEC_ERROR_NO_MEMORY);
        return NULL;
    }

    bagType = SECOID_FindOIDByTag(certType);
    if (!bagType) {
        PORT_SetError(SEC_ERROR_NO_MEMORY);
        goto loser;
    }

    rv = SECITEM_CopyItem(arena, &certBag->bagID, &bagType->oid);
    if (rv != SECSuccess) {
        PORT_SetError(SEC_ERROR_NO_MEMORY);
        goto loser;
    }

    PORT_ArenaUnmark(arena, mark);
    return certBag;

loser:
    PORT_ArenaRelease(arena, mark);
    return NULL;
}

/* Creates a new CRL bag and returns a pointer to it.  If an error
 * occurs NULL is returned.
 */
sec_PKCS12CRLBag *
sec_PKCS12NewCRLBag(PLArenaPool *arena, SECOidTag crlType)
{
    sec_PKCS12CRLBag *crlBag = NULL;
    SECOidData *bagType = NULL;
    SECStatus rv;
    void *mark = NULL;

    if (!arena) {
        return NULL;
    }

    mark = PORT_ArenaMark(arena);
    crlBag = (sec_PKCS12CRLBag *)PORT_ArenaZAlloc(arena,
                                                  sizeof(sec_PKCS12CRLBag));
    if (!crlBag) {
        PORT_ArenaRelease(arena, mark);
        PORT_SetError(SEC_ERROR_NO_MEMORY);
        return NULL;
    }

    bagType = SECOID_FindOIDByTag(crlType);
    if (!bagType) {
        PORT_SetError(SEC_ERROR_NO_MEMORY);
        goto loser;
    }

    rv = SECITEM_CopyItem(arena, &crlBag->bagID, &bagType->oid);
    if (rv != SECSuccess) {
        PORT_SetError(SEC_ERROR_NO_MEMORY);
        goto loser;
    }

    PORT_ArenaUnmark(arena, mark);
    return crlBag;

loser:
    PORT_ArenaRelease(arena, mark);
    return NULL;
}

/* sec_PKCS12AddAttributeToBag
 * adds an attribute to a safeBag.  currently, the only attributes supported
 * are those which are specified within PKCS 12.
 *
 *      p12ctxt - the export context
 *      safeBag - the safeBag to which attributes are appended
 *      attrType - the attribute type
 *      attrData - the attribute data
 */
SECStatus
sec_PKCS12AddAttributeToBag(SEC_PKCS12ExportContext *p12ctxt,
                            sec_PKCS12SafeBag *safeBag, SECOidTag attrType,
                            SECItem *attrData)
{
    sec_PKCS12Attribute *attribute;
    void *mark = NULL, *dummy = NULL;
    SECOidData *oiddata = NULL;
    SECItem unicodeName = { siBuffer, NULL, 0 };
    void *src = NULL;
    unsigned int nItems = 0;
    SECStatus rv;

    if (!safeBag || !p12ctxt) {
        return SECFailure;
    }

    mark = PORT_ArenaMark(safeBag->arena);

    /* allocate the attribute */
    attribute = (sec_PKCS12Attribute *)PORT_ArenaZAlloc(safeBag->arena,
                                                        sizeof(sec_PKCS12Attribute));
    if (!attribute) {
        PORT_SetError(SEC_ERROR_NO_MEMORY);
        goto loser;
    }

    /* set up the attribute */
    oiddata = SECOID_FindOIDByTag(attrType);
    if (!oiddata) {
        PORT_SetError(SEC_ERROR_NO_MEMORY);
        goto loser;
    }
    if (SECITEM_CopyItem(p12ctxt->arena, &attribute->attrType, &oiddata->oid) !=
        SECSuccess) {
        PORT_SetError(SEC_ERROR_NO_MEMORY);
        goto loser;
    }

    nItems = 1;
    switch (attrType) {
        case SEC_OID_PKCS9_LOCAL_KEY_ID: {
            src = attrData;
            break;
        }
        case SEC_OID_PKCS9_FRIENDLY_NAME: {
            if (!sec_pkcs12_convert_item_to_unicode(p12ctxt->arena,
                                                    &unicodeName, attrData, PR_FALSE,
                                                    PR_FALSE, PR_TRUE)) {
                goto loser;
            }
            src = &unicodeName;
            break;
        }
        default:
            goto loser;
    }

    /* append the attribute to the attribute value list  */
    attribute->attrValue = (SECItem **)PORT_ArenaZAlloc(p12ctxt->arena,
                                                        ((nItems + 1) * sizeof(SECItem *)));
    if (!attribute->attrValue) {
        PORT_SetError(SEC_ERROR_NO_MEMORY);
        goto loser;
    }

    /* XXX this will need to be changed if attributes requiring more than
     * one element are ever used.
     */
    attribute->attrValue[0] = (SECItem *)PORT_ArenaZAlloc(p12ctxt->arena,
                                                          sizeof(SECItem));
    if (!attribute->attrValue[0]) {
        PORT_SetError(SEC_ERROR_NO_MEMORY);
        goto loser;
    }
    attribute->attrValue[1] = NULL;

    rv = SECITEM_CopyItem(p12ctxt->arena, attribute->attrValue[0],
                          (SECItem *)src);
    if (rv != SECSuccess) {
        PORT_SetError(SEC_ERROR_NO_MEMORY);
        goto loser;
    }

    /* append the attribute to the safeBag attributes */
    if (safeBag->nAttribs) {
        dummy = PORT_ArenaGrow(p12ctxt->arena, safeBag->attribs,
                               ((safeBag->nAttribs + 1) * sizeof(sec_PKCS12Attribute *)),
                               ((safeBag->nAttribs + 2) * sizeof(sec_PKCS12Attribute *)));
        safeBag->attribs = (sec_PKCS12Attribute **)dummy;
    } else {
        safeBag->attribs = (sec_PKCS12Attribute **)PORT_ArenaZAlloc(p12ctxt->arena,
                                                                    2 * sizeof(sec_PKCS12Attribute *));
        dummy = safeBag->attribs;
    }
    if (!dummy) {
        goto loser;
    }

    safeBag->attribs[safeBag->nAttribs] = attribute;
    safeBag->attribs[++safeBag->nAttribs] = NULL;

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

loser:
    if (mark) {
        PORT_ArenaRelease(p12ctxt->arena, mark);
    }

    return SECFailure;
}

/* SEC_PKCS12AddCert
 *      Adds a certificate to the data being exported.
 *
 *      p12ctxt - the export context
 *      safe - the safeInfo to which the certificate is placed
 *      nestedDest - if the cert is to be placed within a nested safeContents then,
 *                   this value is to be specified with the destination
 *      cert - the cert to export
 *      certDb - the certificate database handle
 *      keyId - a unique identifier to associate a certificate/key pair
 *      includeCertChain - PR_TRUE if the certificate chain is to be included.
 */
SECStatus
SEC_PKCS12AddCert(SEC_PKCS12ExportContext *p12ctxt, SEC_PKCS12SafeInfo *safe,
                  void *nestedDest, CERTCertificate *cert,
                  CERTCertDBHandle *certDb, SECItem *keyId,
                  PRBool includeCertChain)
{
    sec_PKCS12CertBag *certBag;
    sec_PKCS12SafeBag *safeBag;
    void *mark;
    SECStatus rv;
    SECItem nick = { siBuffer, NULL, 0 };

    if (!p12ctxt || !cert) {
        return SECFailure;
    }
    mark = PORT_ArenaMark(p12ctxt->arena);

    /* allocate the cert bag */
    certBag = sec_PKCS12NewCertBag(p12ctxt->arena,
                                   SEC_OID_PKCS9_X509_CERT);
    if (!certBag) {
        goto loser;
    }

    if (SECITEM_CopyItem(p12ctxt->arena, &certBag->value.x509Cert,
                         &cert->derCert) != SECSuccess) {
        PORT_SetError(SEC_ERROR_NO_MEMORY);
        goto loser;
    }

    /* if the cert chain is to be included, we should only be exporting
     * the cert from our internal database.
     */
    if (includeCertChain) {
        CERTCertificateList *certList = CERT_CertChainFromCert(cert,
                                                               certUsageSSLClient,
                                                               PR_TRUE);
        unsigned int count = 0;
        if (!certList) {
            PORT_SetError(SEC_ERROR_NO_MEMORY);
            goto loser;
        }

        /* add cert chain */
        for (count = 0; count < (unsigned int)certList->len; count++) {
            if (SECITEM_CompareItem(&certList->certs[count], &cert->derCert) != SECEqual) {
                CERTCertificate *tempCert;

                /* decode the certificate */
                /* XXX
                 * This was rather silly.  The chain is constructed above
                 * by finding all of the CERTCertificate's in the database.
                 * Then the chain is put into a CERTCertificateList, which only
                 * contains the DER.  Finally, the DER was decoded, and the
                 * decoded cert was sent recursively back to this function.
                 * Beyond being inefficent, this causes data loss (specifically,
                 * the nickname).  Instead, for 3.4, we'll do a lookup by the
                 * DER, which should return the cached entry.
                 */
                tempCert = CERT_FindCertByDERCert(CERT_GetDefaultCertDB(),
                                                  &certList->certs[count]);
                if (!tempCert) {
                    CERT_DestroyCertificateList(certList);
                    goto loser;
                }

                /* add the certificate */
                if (SEC_PKCS12AddCert(p12ctxt, safe, nestedDest, tempCert,
                                      certDb, NULL, PR_FALSE) != SECSuccess) {
                    CERT_DestroyCertificate(tempCert);
                    CERT_DestroyCertificateList(certList);
                    goto loser;
                }
                CERT_DestroyCertificate(tempCert);
            }
        }
        CERT_DestroyCertificateList(certList);
    }

    /* if the certificate has a nickname, we will set the friendly name
     * to that.
     */
    if (cert->nickname) {
        if (cert->slot && !PK11_IsInternal(cert->slot)) {
            /*
             * The cert is coming off of an external token,
             * let's strip the token name from the nickname
             * and only add what comes after the colon as the
             * nickname. -javi
             */
            char *delimit;

            delimit = PORT_Strchr(cert->nickname, ':');
            if (delimit == NULL) {
                nick.data = (unsigned char *)cert->nickname;
                nick.len = PORT_Strlen(cert->nickname);
            } else {
                delimit++;
                nick.data = (unsigned char *)PORT_ArenaStrdup(p12ctxt->arena,
                                                              delimit);
                nick.len = PORT_Strlen(delimit);
            }
        } else {
            nick.data = (unsigned char *)cert->nickname;
            nick.len = PORT_Strlen(cert->nickname);
        }
    }

    safeBag = sec_PKCS12CreateSafeBag(p12ctxt, SEC_OID_PKCS12_V1_CERT_BAG_ID,
                                      certBag);
    if (!safeBag) {
        goto loser;
    }

    /* add the friendly name and keyId attributes, if necessary */
    if (nick.data) {
        if (sec_PKCS12AddAttributeToBag(p12ctxt, safeBag,
                                        SEC_OID_PKCS9_FRIENDLY_NAME, &nick) != SECSuccess) {
            goto loser;
        }
    }

    if (keyId) {
        if (sec_PKCS12AddAttributeToBag(p12ctxt, safeBag, SEC_OID_PKCS9_LOCAL_KEY_ID,
                                        keyId) != SECSuccess) {
            goto loser;
        }
    }

    /* append the cert safeBag */
    if (nestedDest) {
        rv = sec_pkcs12_append_bag_to_safe_contents(p12ctxt->arena,
                                                    (sec_PKCS12SafeContents *)nestedDest,
                                                    safeBag);
    } else {
        rv = sec_pkcs12_append_bag(p12ctxt, safe, safeBag);
    }

    if (rv != SECSuccess) {
        goto loser;
    }

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

loser:
    if (mark) {
        PORT_ArenaRelease(p12ctxt->arena, mark);
    }

    return SECFailure;
}

/* SEC_PKCS12AddKeyForCert
 *      Extracts the key associated with a particular certificate and exports
 *      it.
 *
 *      p12ctxt - the export context
 *      safe - the safeInfo to place the key in
 *      nestedDest - the nested safeContents to place a key
 *      cert - the certificate which the key belongs to
 *      shroudKey - encrypt the private key for export.  This value should
 *              always be true.  lower level code will not allow the export
 *              of unencrypted private keys.
 *      algorithm - the algorithm with which to encrypt the private key
 *      pwitem - the password to encrypt the private key with
 *      keyId - the keyID attribute
 *      nickName - the nickname attribute
 */
SECStatus
SEC_PKCS12AddKeyForCert(SEC_PKCS12ExportContext *p12ctxt, SEC_PKCS12SafeInfo *safe,
                        void *nestedDest, CERTCertificate *cert,
                        PRBool shroudKey, SECOidTag algorithm, SECItem *pwitem,
                        SECItem *keyId, SECItem *nickName)
{
    void *mark;
    void *keyItem;
    SECOidTag keyType;
    SECStatus rv = SECFailure;
    SECItem nickname = { siBuffer, NULL, 0 }, uniPwitem = { siBuffer, NULL, 0 };
    sec_PKCS12SafeBag *returnBag;

    if (!p12ctxt || !cert || !safe) {
        return SECFailure;
    }

    mark = PORT_ArenaMark(p12ctxt->arena);

    /* retrieve the key based upon the type that it is and
     * specify the type of safeBag to store the key in
     */
    if (!shroudKey) {

        /* extract the key unencrypted.  this will most likely go away */
        SECKEYPrivateKeyInfo *pki = PK11_ExportPrivateKeyInfo(cert,
                                                              p12ctxt->wincx);
        if (!pki) {
            PORT_ArenaRelease(p12ctxt->arena, mark);
            PORT_SetError(SEC_ERROR_PKCS12_UNABLE_TO_EXPORT_KEY);
            return SECFailure;
        }
        keyItem = PORT_ArenaZAlloc(p12ctxt->arena, sizeof(SECKEYPrivateKeyInfo));
        if (!keyItem) {
            PORT_SetError(SEC_ERROR_NO_MEMORY);
            goto loser;
        }
        rv = SECKEY_CopyPrivateKeyInfo(p12ctxt->arena,
                                       (SECKEYPrivateKeyInfo *)keyItem, pki);
        keyType = SEC_OID_PKCS12_V1_KEY_BAG_ID;
        SECKEY_DestroyPrivateKeyInfo(pki, PR_TRUE);
    } else {

        /* extract the key encrypted */
        SECKEYEncryptedPrivateKeyInfo *epki = NULL;
        PK11SlotInfo *slot = NULL;

        if (!sec_pkcs12_encode_password(p12ctxt->arena, &uniPwitem, algorithm,
                                        pwitem)) {
            PORT_SetError(SEC_ERROR_NO_MEMORY);
            goto loser;
        }

        /* we want to make sure to take the key out of the key slot */
        if (PK11_IsInternal(p12ctxt->slot)) {
            slot = PK11_GetInternalKeySlot();
        } else {
            slot = PK11_ReferenceSlot(p12ctxt->slot);
        }

        epki = PK11_ExportEncryptedPrivateKeyInfo(slot, algorithm,
                                                  &uniPwitem, cert,
                                                  NSS_PBE_DEFAULT_ITERATION_COUNT,
                                                  p12ctxt->wincx);
        PK11_FreeSlot(slot);
        if (!epki) {
            PORT_SetError(SEC_ERROR_PKCS12_UNABLE_TO_EXPORT_KEY);
            goto loser;
        }

        keyItem = PORT_ArenaZAlloc(p12ctxt->arena,
                                   sizeof(SECKEYEncryptedPrivateKeyInfo));
        if (!keyItem) {
            PORT_SetError(SEC_ERROR_NO_MEMORY);
            goto loser;
        }
        rv = SECKEY_CopyEncryptedPrivateKeyInfo(p12ctxt->arena,
                                                (SECKEYEncryptedPrivateKeyInfo *)keyItem,
                                                epki);
        keyType = SEC_OID_PKCS12_V1_PKCS8_SHROUDED_KEY_BAG_ID;
        SECKEY_DestroyEncryptedPrivateKeyInfo(epki, PR_TRUE);
    }

    if (rv != SECSuccess) {
        goto loser;
    }

    /* if no nickname specified, let's see if the certificate has a
     * nickname.
     */
    if (!nickName) {
        if (cert->nickname) {
            nickname.data = (unsigned char *)cert->nickname;
            nickname.len = PORT_Strlen(cert->nickname);
            nickName = &nickname;
        }
    }

    /* create the safe bag and set any attributes */
    returnBag = sec_PKCS12CreateSafeBag(p12ctxt, keyType, keyItem);
    if (!returnBag) {
        rv = SECFailure;
        goto loser;
    }

    if (nickName) {
        if (sec_PKCS12AddAttributeToBag(p12ctxt, returnBag,
                                        SEC_OID_PKCS9_FRIENDLY_NAME, nickName) != SECSuccess) {
            goto loser;
        }
    }

    if (keyId) {
        if (sec_PKCS12AddAttributeToBag(p12ctxt, returnBag, SEC_OID_PKCS9_LOCAL_KEY_ID,
                                        keyId) != SECSuccess) {
            goto loser;
        }
    }

    if (nestedDest) {
        rv = sec_pkcs12_append_bag_to_safe_contents(p12ctxt->arena,
                                                    (sec_PKCS12SafeContents *)nestedDest,
                                                    returnBag);
    } else {
        rv = sec_pkcs12_append_bag(p12ctxt, safe, returnBag);
    }

loser:

    if (rv != SECSuccess) {
        PORT_ArenaRelease(p12ctxt->arena, mark);
    } else {
        PORT_ArenaUnmark(p12ctxt->arena, mark);
    }

    return rv;
}

/* SEC_PKCS12AddCertOrChainAndKey
 *      Add a certificate and key pair to be exported.
 *
 *      p12ctxt          - the export context
 *      certSafe         - the safeInfo where the cert is stored
 *      certNestedDest   - the nested safeContents to store the cert
 *      keySafe          - the safeInfo where the key is stored
 *      keyNestedDest    - the nested safeContents to store the key
 *      shroudKey        - extract the private key encrypted?
 *      pwitem           - the password with which the key is encrypted
 *      algorithm        - the algorithm with which the key is encrypted
 *      includeCertChain - also add certs from chain to bag.
 */
SECStatus
SEC_PKCS12AddCertOrChainAndKey(SEC_PKCS12ExportContext *p12ctxt,
                               void *certSafe, void *certNestedDest,
                               CERTCertificate *cert, CERTCertDBHandle *certDb,
                               void *keySafe, void *keyNestedDest,
                               PRBool shroudKey, SECItem *pwitem,
                               SECOidTag algorithm, PRBool includeCertChain)
{
    SECStatus rv = SECFailure;
    SGNDigestInfo *digest = NULL;
    void *mark = NULL;

    if (!p12ctxt || !certSafe || !keySafe || !cert) {
        return SECFailure;
    }

    mark = PORT_ArenaMark(p12ctxt->arena);

    /* generate the thumbprint of the cert to use as a keyId */
    digest = sec_pkcs12_compute_thumbprint(&cert->derCert);
    if (!digest) {
        PORT_ArenaRelease(p12ctxt->arena, mark);
        return SECFailure;
    }

    /* add the certificate */
    rv = SEC_PKCS12AddCert(p12ctxt, (SEC_PKCS12SafeInfo *)certSafe,
                           (SEC_PKCS12SafeInfo *)certNestedDest, cert, certDb,
                           &digest->digest, includeCertChain);
    if (rv != SECSuccess) {
        goto loser;
    }

    /* add the key */
    rv = SEC_PKCS12AddKeyForCert(p12ctxt, (SEC_PKCS12SafeInfo *)keySafe,
                                 keyNestedDest, cert,
                                 shroudKey, algorithm, pwitem,
                                 &digest->digest, NULL);
    if (rv != SECSuccess) {
        goto loser;
    }

    SGN_DestroyDigestInfo(digest);

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

loser:
    SGN_DestroyDigestInfo(digest);
    PORT_ArenaRelease(p12ctxt->arena, mark);

    return SECFailure;
}

/* like SEC_PKCS12AddCertOrChainAndKey, but always adds cert chain */
SECStatus
SEC_PKCS12AddCertAndKey(SEC_PKCS12ExportContext *p12ctxt,
                        void *certSafe, void *certNestedDest,
                        CERTCertificate *cert, CERTCertDBHandle *certDb,
                        void *keySafe, void *keyNestedDest,
                        PRBool shroudKey, SECItem *pwItem, SECOidTag algorithm)
{
    return SEC_PKCS12AddCertOrChainAndKey(p12ctxt, certSafe, certNestedDest,
                                          cert, certDb, keySafe, keyNestedDest, shroudKey, pwItem,
                                          algorithm, PR_TRUE);
}

/* SEC_PKCS12CreateNestedSafeContents
 *      Allows nesting of safe contents to be implemented.  No limit imposed on
 *      depth.
 *
 *      p12ctxt - the export context
 *      baseSafe - the base safeInfo
 *      nestedDest - a parent safeContents (?)
 */
void *
SEC_PKCS12CreateNestedSafeContents(SEC_PKCS12ExportContext *p12ctxt,
                                   void *baseSafe, void *nestedDest)
{
    sec_PKCS12SafeContents *newSafe;
    sec_PKCS12SafeBag *safeContentsBag;
    void *mark;
    SECStatus rv;

    if (!p12ctxt || !baseSafe) {
        return NULL;
    }

    mark = PORT_ArenaMark(p12ctxt->arena);

    newSafe = sec_PKCS12CreateSafeContents(p12ctxt->arena);
    if (!newSafe) {
        PORT_ArenaRelease(p12ctxt->arena, mark);
        PORT_SetError(SEC_ERROR_NO_MEMORY);
        return NULL;
    }

    /* create the safeContents safeBag */
    safeContentsBag = sec_PKCS12CreateSafeBag(p12ctxt,
                                              SEC_OID_PKCS12_V1_SAFE_CONTENTS_BAG_ID,
                                              newSafe);
    if (!safeContentsBag) {
        goto loser;
    }

    /* append the safeContents to the appropriate area */
    if (nestedDest) {
        rv = sec_pkcs12_append_bag_to_safe_contents(p12ctxt->arena,
                                                    (sec_PKCS12SafeContents *)nestedDest,
                                                    safeContentsBag);
    } else {
        rv = sec_pkcs12_append_bag(p12ctxt, (SEC_PKCS12SafeInfo *)baseSafe,
                                   safeContentsBag);
    }
    if (rv != SECSuccess) {
        goto loser;
    }

    PORT_ArenaUnmark(p12ctxt->arena, mark);
    return newSafe;

loser:
    PORT_ArenaRelease(p12ctxt->arena, mark);
    return NULL;
}

/*********************************
 * Encoding routines
 *********************************/

/* Clean up the resources allocated by a sec_PKCS12EncoderContext. */
static void
sec_pkcs12_encoder_destroy_context(sec_PKCS12EncoderContext *p12enc)
{
    if (p12enc) {
        if (p12enc->outerA1ecx) {
            SEC_ASN1EncoderFinish(p12enc->outerA1ecx);
            p12enc->outerA1ecx = NULL;
        }
        if (p12enc->aSafeCinfo) {
            SEC_PKCS7DestroyContentInfo(p12enc->aSafeCinfo);
            p12enc->aSafeCinfo = NULL;
        }
        if (p12enc->middleP7ecx) {
            SEC_PKCS7EncoderFinish(p12enc->middleP7ecx, p12enc->p12exp->pwfn,
                                   p12enc->p12exp->pwfnarg);
            p12enc->middleP7ecx = NULL;
        }
        if (p12enc->middleA1ecx) {
            SEC_ASN1EncoderFinish(p12enc->middleA1ecx);
            p12enc->middleA1ecx = NULL;
        }
        if (p12enc->hmacCx) {
            PK11_DestroyContext(p12enc->hmacCx, PR_TRUE);
            p12enc->hmacCx = NULL;
        }
    }
}

/* set up the encoder context based on information in the export context
 * and return the newly allocated enocoder context.  A return of NULL
 * indicates an error occurred.
 */
static sec_PKCS12EncoderContext *
sec_pkcs12_encoder_start_context(SEC_PKCS12ExportContext *p12exp)
{
    sec_PKCS12EncoderContext *p12enc = NULL;
    unsigned int i, nonEmptyCnt;
    SECStatus rv;
    SECItem ignore = { 0 };
    void *mark;
    SECItem *salt = NULL;
    SECItem *params = NULL;

    if (!p12exp || !p12exp->safeInfos) {
        return NULL;
    }

    /* check for any empty safes and skip them */
    i = nonEmptyCnt = 0;
    while (p12exp->safeInfos[i]) {
        if (p12exp->safeInfos[i]->itemCount) {
            nonEmptyCnt++;
        }
        i++;
    }
    if (nonEmptyCnt == 0) {
        return NULL;
    }
    p12exp->authSafe.encodedSafes[nonEmptyCnt] = NULL;

    /* allocate the encoder context */
    mark = PORT_ArenaMark(p12exp->arena);
    p12enc = PORT_ArenaZNew(p12exp->arena, sec_PKCS12EncoderContext);
    if (!p12enc) {
        PORT_SetError(SEC_ERROR_NO_MEMORY);
        return NULL;
    }

    p12enc->arena = p12exp->arena;
    p12enc->p12exp = p12exp;

    /* set up the PFX version and information */
    PORT_Memset(&p12enc->pfx, 0, sizeof(sec_PKCS12PFXItem));
    if (!SEC_ASN1EncodeInteger(p12exp->arena, &(p12enc->pfx.version),
                               SEC_PKCS12_VERSION)) {
        PORT_SetError(SEC_ERROR_NO_MEMORY);
        goto loser;
    }

    /* set up the authenticated safe content info based on the
     * type of integrity being used.  this should be changed to
     * enforce integrity mode, but will not be implemented until
     * it is confirmed that integrity must be in place
     */
    if (p12exp->integrityEnabled && !p12exp->pwdIntegrity) {
        /* create public key integrity mode */
        p12enc->aSafeCinfo = SEC_PKCS7CreateSignedData(
            p12exp->integrityInfo.pubkeyInfo.cert,
            certUsageEmailSigner,
            p12exp->integrityInfo.pubkeyInfo.certDb,
            p12exp->integrityInfo.pubkeyInfo.algorithm,
            NULL,
            p12exp->pwfn,
            p12exp->pwfnarg);
        if (!p12enc->aSafeCinfo) {
            goto loser;
        }
        if (SEC_PKCS7IncludeCertChain(p12enc->aSafeCinfo, NULL) != SECSuccess) {
            goto loser;
        }
        PORT_CheckSuccess(SEC_PKCS7AddSigningTime(p12enc->aSafeCinfo));
    } else {
        p12enc->aSafeCinfo = SEC_PKCS7CreateData();

        /* init password pased integrity mode */
        if (p12exp->integrityEnabled) {
            SECItem pwd = { siBuffer, NULL, 0 };
            PK11SymKey *symKey;
            CK_MECHANISM_TYPE integrityMechType;
            CK_MECHANISM_TYPE hmacMechType;
            salt = sec_pkcs12_generate_salt();

            /* zero out macData and set values */
            PORT_Memset(&p12enc->mac, 0, sizeof(sec_PKCS12MacData));

            if (!salt) {
                PORT_SetError(SEC_ERROR_NO_MEMORY);
                goto loser;
            }
            if (SECITEM_CopyItem(p12exp->arena, &(p12enc->mac.macSalt), salt) != SECSuccess) {
                PORT_SetError(SEC_ERROR_NO_MEMORY);
                goto loser;
            }
            if (!SEC_ASN1EncodeInteger(p12exp->arena, &(p12enc->mac.iter),
                                       NSS_PBE_DEFAULT_ITERATION_COUNT)) {
                goto loser;
            }

            /* generate HMAC key */
            if (!sec_pkcs12_convert_item_to_unicode(NULL, &pwd,
                                                    p12exp->integrityInfo.pwdInfo.password, PR_TRUE,
                                                    PR_TRUE, PR_TRUE)) {
                goto loser;
            }
            /*
             * This code only works with PKCS #12 Mac using PKCS #5 v1
             * PBA keygens. PKCS #5 v2 support will require a change to
             * the PKCS #12 spec.
             */
            params = PK11_CreatePBEParams(salt, &pwd,
                                          NSS_PBE_DEFAULT_ITERATION_COUNT);
            SECITEM_ZfreeItem(salt, PR_TRUE);
            SECITEM_ZfreeItem(&pwd, PR_FALSE);

            /* get the PBA Mechanism to generate the key */
            switch (p12exp->integrityInfo.pwdInfo.algorithm) {
                case SEC_OID_SHA1:
                    integrityMechType = CKM_PBA_SHA1_WITH_SHA1_HMAC;
                    break;
                case SEC_OID_MD5:
                    integrityMechType = CKM_NETSCAPE_PBE_MD5_HMAC_KEY_GEN;
                    break;
                case SEC_OID_MD2:
                    integrityMechType = CKM_NETSCAPE_PBE_MD2_HMAC_KEY_GEN;
                    break;
                default:
                    goto loser;
            }

            /* generate the key */
            symKey = PK11_KeyGen(NULL, integrityMechType, params, 20, NULL);
            PK11_DestroyPBEParams(params);
            if (!symKey) {
                goto loser;
            }

            /* initialize HMAC */
            /* Get the HMAC mechanism from the hash OID */
            hmacMechType = sec_pkcs12_algtag_to_mech(
                p12exp->integrityInfo.pwdInfo.algorithm);

            p12enc->hmacCx = PK11_CreateContextBySymKey(hmacMechType,
                                                        CKA_SIGN, symKey, &ignore);

            PK11_FreeSymKey(symKey);
            if (!p12enc->hmacCx) {
                PORT_SetError(SEC_ERROR_NO_MEMORY);
                goto loser;
            }
            rv = PK11_DigestBegin(p12enc->hmacCx);
            if (rv != SECSuccess)
                goto loser;
        }
    }

    if (!p12enc->aSafeCinfo) {
        goto loser;
    }

    PORT_ArenaUnmark(p12exp->arena, mark);

    return p12enc;

loser:
    sec_pkcs12_encoder_destroy_context(p12enc);
    if (p12exp->arena != NULL)
        PORT_ArenaRelease(p12exp->arena, mark);
    if (salt) {
        SECITEM_ZfreeItem(salt, PR_TRUE);
    }
    if (params) {
        PK11_DestroyPBEParams(params);
    }

    return NULL;
}

/* The outermost ASN.1 encoder calls this function for output.
** This function calls back to the library caller's output routine,
** which typically writes to a PKCS12 file.
 */
static void
sec_P12A1OutputCB_Outer(void *arg, const char *buf, unsigned long len,
                        int depth, SEC_ASN1EncodingPart data_kind)
{
    struct sec_pkcs12_encoder_output *output;

    output = (struct sec_pkcs12_encoder_output *)arg;
    (*output->outputfn)(output->outputarg, buf, len);
}

/* The "middle" and "inner" ASN.1 encoders call this function to output.
** This function does HMACing, if appropriate, and then buffers the data.
** The buffered data is eventually passed down to the underlying PKCS7 encoder.
 */
static void
sec_P12A1OutputCB_HmacP7Update(void *arg, const char *buf,
                               unsigned long len,
                               int depth,
                               SEC_ASN1EncodingPart data_kind)
{
    sec_pkcs12OutputBuffer *bufcx = (sec_pkcs12OutputBuffer *)arg;

    if (!buf || !len)
        return;

    if (bufcx->hmacCx) {
        PK11_DigestOp(bufcx->hmacCx, (unsigned char *)buf, len);
    }

    /* buffer */
    if (bufcx->numBytes > 0) {
        int toCopy;
        if (len + bufcx->numBytes <= bufcx->bufBytes) {
            memcpy(bufcx->buf + bufcx->numBytes, buf, len);
            bufcx->numBytes += len;
            if (bufcx->numBytes < bufcx->bufBytes)
                return;
            SEC_PKCS7EncoderUpdate(bufcx->p7eCx, bufcx->buf, bufcx->bufBytes);
            bufcx->numBytes = 0;
            return;
        }
        toCopy = bufcx->bufBytes - bufcx->numBytes;
        memcpy(bufcx->buf + bufcx->numBytes, buf, toCopy);
        SEC_PKCS7EncoderUpdate(bufcx->p7eCx, bufcx->buf, bufcx->bufBytes);
        bufcx->numBytes = 0;
        len -= toCopy;
        buf += toCopy;
    }
    /* buffer is presently empty */
    if (len >= bufcx->bufBytes) {
        /* Just pass it through */
        SEC_PKCS7EncoderUpdate(bufcx->p7eCx, buf, len);
    } else {
        /* copy it all into the buffer, and return */
        memcpy(bufcx->buf, buf, len);
        bufcx->numBytes = len;
    }
}

void
sec_FlushPkcs12OutputBuffer(sec_pkcs12OutputBuffer *bufcx)
{
    if (bufcx->numBytes > 0) {
        SEC_PKCS7EncoderUpdate(bufcx->p7eCx, bufcx->buf, bufcx->numBytes);
        bufcx->numBytes = 0;
    }
}

/* Feeds the output of a PKCS7 encoder into the next outward ASN.1 encoder.
** This function is used by both the inner and middle PCS7 encoders.
*/
static void
sec_P12P7OutputCB_CallA1Update(void *arg, const char *buf, unsigned long len)
{
    SEC_ASN1EncoderContext *cx = (SEC_ASN1EncoderContext *)arg;

    if (!buf || !len)
        return;

    SEC_ASN1EncoderUpdate(cx, buf, len);
}

/* this function encodes content infos which are part of the
 * sequence of content infos labeled AuthenticatedSafes
 */
static SECStatus
sec_pkcs12_encoder_asafe_process(sec_PKCS12EncoderContext *p12ecx)
{
    SEC_PKCS7EncoderContext *innerP7ecx;
    SEC_PKCS7ContentInfo *cinfo;
    PK11SymKey *bulkKey = NULL;
    SEC_ASN1EncoderContext *innerA1ecx = NULL;
    SECStatus rv = SECSuccess;

    if (p12ecx->currentSafe < p12ecx->p12exp->authSafe.safeCount) {
        SEC_PKCS12SafeInfo *safeInfo;
        SECOidTag cinfoType;

        safeInfo = p12ecx->p12exp->safeInfos[p12ecx->currentSafe];

        /* skip empty safes */
        if (safeInfo->itemCount == 0) {
            return SECSuccess;
        }

        cinfo = safeInfo->cinfo;
        cinfoType = SEC_PKCS7ContentType(cinfo);

        /* determine the safe type and set the appropriate argument */
        switch (cinfoType) {
            case SEC_OID_PKCS7_DATA:
            case SEC_OID_PKCS7_ENVELOPED_DATA:
                break;
            case SEC_OID_PKCS7_ENCRYPTED_DATA:
                bulkKey = safeInfo->encryptionKey;
                PK11_SetSymKeyUserData(bulkKey, &safeInfo->pwitem, NULL);
                break;
            default:
                return SECFailure;
        }

        /* start the PKCS7 encoder */
        innerP7ecx = SEC_PKCS7EncoderStart(cinfo,
                                           sec_P12P7OutputCB_CallA1Update,
                                           p12ecx->middleA1ecx, bulkKey);
        if (!innerP7ecx) {
            goto loser;
        }

        /* encode safe contents */
        p12ecx->innerBuf.p7eCx = innerP7ecx;
        p12ecx->innerBuf.hmacCx = NULL;
        p12ecx->innerBuf.numBytes = 0;
        p12ecx->innerBuf.bufBytes = sizeof p12ecx->innerBuf.buf;

        innerA1ecx = SEC_ASN1EncoderStart(safeInfo->safe,
                                          sec_PKCS12SafeContentsTemplate,
                                          sec_P12A1OutputCB_HmacP7Update,
                                          &p12ecx->innerBuf);
        if (!innerA1ecx) {
            goto loser;
        }
        rv = SEC_ASN1EncoderUpdate(innerA1ecx, NULL, 0);
        SEC_ASN1EncoderFinish(innerA1ecx);
        sec_FlushPkcs12OutputBuffer(&p12ecx->innerBuf);
        innerA1ecx = NULL;
        if (rv != SECSuccess) {
            goto loser;
        }

        /* finish up safe content info */
        rv = SEC_PKCS7EncoderFinish(innerP7ecx, p12ecx->p12exp->pwfn,
                                    p12ecx->p12exp->pwfnarg);
    }
    memset(&p12ecx->innerBuf, 0, sizeof p12ecx->innerBuf);
    return SECSuccess;

loser:
    if (innerP7ecx) {
        SEC_PKCS7EncoderFinish(innerP7ecx, p12ecx->p12exp->pwfn,
                               p12ecx->p12exp->pwfnarg);
    }

    if (innerA1ecx) {
        SEC_ASN1EncoderFinish(innerA1ecx);
    }
    memset(&p12ecx->innerBuf, 0, sizeof p12ecx->innerBuf);
    return SECFailure;
}

/* finish the HMAC and encode the macData so that it can be
 * encoded.
 */
static SECStatus
sec_Pkcs12FinishMac(sec_PKCS12EncoderContext *p12ecx)
{
    SECItem hmac = { siBuffer, NULL, 0 };
    SECStatus rv;
    SGNDigestInfo *di = NULL;
    void *dummy;

    if (!p12ecx) {
        return SECFailure;
    }

    /* make sure we are using password integrity mode */
    if (!p12ecx->p12exp->integrityEnabled) {
        return SECSuccess;
    }

    if (!p12ecx->p12exp->pwdIntegrity) {
        return SECSuccess;
    }

    /* finish the hmac */
    hmac.data = (unsigned char *)PORT_ZAlloc(SHA1_LENGTH);
    if (!hmac.data) {
        PORT_SetError(SEC_ERROR_NO_MEMORY);
        return SECFailure;
    }

    rv = PK11_DigestFinal(p12ecx->hmacCx, hmac.data, &hmac.len, SHA1_LENGTH);

    if (rv != SECSuccess) {
        PORT_SetError(SEC_ERROR_NO_MEMORY);
        goto loser;
    }

    /* create the digest info */
    di = SGN_CreateDigestInfo(p12ecx->p12exp->integrityInfo.pwdInfo.algorithm,
                              hmac.data, hmac.len);
    if (!di) {
        PORT_SetError(SEC_ERROR_NO_MEMORY);
        rv = SECFailure;
        goto loser;
    }

    rv = SGN_CopyDigestInfo(p12ecx->arena, &p12ecx->mac.safeMac, di);
    if (rv != SECSuccess) {
        PORT_SetError(SEC_ERROR_NO_MEMORY);
        goto loser;
    }

    /* encode the mac data */
    dummy = SEC_ASN1EncodeItem(p12ecx->arena, &p12ecx->pfx.encodedMacData,
                               &p12ecx->mac, sec_PKCS12MacDataTemplate);
    if (!dummy) {
        PORT_SetError(SEC_ERROR_NO_MEMORY);
        rv = SECFailure;
    }

loser:
    if (di) {
        SGN_DestroyDigestInfo(di);
    }
    if (hmac.data) {
        SECITEM_ZfreeItem(&hmac, PR_FALSE);
    }
    PK11_DestroyContext(p12ecx->hmacCx, PR_TRUE);
    p12ecx->hmacCx = NULL;

    return rv;
}

/* pfx notify function for ASN1 encoder.
 * We want to stop encoding once we reach the authenticated safe.
 * At that point, the encoder will be updated via streaming
 * as the authenticated safe is  encoded.
 */
static void
sec_pkcs12_encoder_pfx_notify(void *arg, PRBool before, void *dest, int real_depth)
{
    sec_PKCS12EncoderContext *p12ecx;

    if (!before) {
        return;
    }

    /* look for authenticated safe */
    p12ecx = (sec_PKCS12EncoderContext *)arg;
    if (dest != &p12ecx->pfx.encodedAuthSafe) {
        return;
    }

    SEC_ASN1EncoderSetTakeFromBuf(p12ecx->outerA1ecx);
    SEC_ASN1EncoderSetStreaming(p12ecx->outerA1ecx);
    SEC_ASN1EncoderClearNotifyProc(p12ecx->outerA1ecx);
}

/* SEC_PKCS12Encode
 *      Encodes the PFX item and returns it to the output function, via
 *      callback.  the output function must be capable of multiple updates.
 *
 *      p12exp - the export context
 *      output - the output function callback, will be called more than once,
 *               must be able to accept streaming data.
 *      outputarg - argument for the output callback.
 */
SECStatus
SEC_PKCS12Encode(SEC_PKCS12ExportContext *p12exp,
                 SEC_PKCS12EncoderOutputCallback output, void *outputarg)
{
    sec_PKCS12EncoderContext *p12enc;
    struct sec_pkcs12_encoder_output outInfo;
    SECStatus rv;

    if (!p12exp || !output) {
        return SECFailure;
    }

    /* get the encoder context */
    p12enc = sec_pkcs12_encoder_start_context(p12exp);
    if (!p12enc) {
        return SECFailure;
    }

    outInfo.outputfn = output;
    outInfo.outputarg = outputarg;

    /* set up PFX encoder, the "outer" encoder.  Set it for streaming */
    p12enc->outerA1ecx = SEC_ASN1EncoderStart(&p12enc->pfx,
                                              sec_PKCS12PFXItemTemplate,
                                              sec_P12A1OutputCB_Outer,
                                              &outInfo);
    if (!p12enc->outerA1ecx) {
        PORT_SetError(SEC_ERROR_NO_MEMORY);
        rv = SECFailure;
        goto loser;
    }
    SEC_ASN1EncoderSetStreaming(p12enc->outerA1ecx);
    SEC_ASN1EncoderSetNotifyProc(p12enc->outerA1ecx,
                                 sec_pkcs12_encoder_pfx_notify, p12enc);
    rv = SEC_ASN1EncoderUpdate(p12enc->outerA1ecx, NULL, 0);
    if (rv != SECSuccess) {
        rv = SECFailure;
        goto loser;
    }

    /* set up asafe cinfo - the output of the encoder feeds the PFX encoder */
    p12enc->middleP7ecx = SEC_PKCS7EncoderStart(p12enc->aSafeCinfo,
                                                sec_P12P7OutputCB_CallA1Update,
                                                p12enc->outerA1ecx, NULL);
    if (!p12enc->middleP7ecx) {
        rv = SECFailure;
        goto loser;
    }

    /* encode asafe */
    p12enc->middleBuf.p7eCx = p12enc->middleP7ecx;
    p12enc->middleBuf.hmacCx = NULL;
    p12enc->middleBuf.numBytes = 0;
    p12enc->middleBuf.bufBytes = sizeof p12enc->middleBuf.buf;

    /* Setup the "inner ASN.1 encoder for Authenticated Safes.  */
    if (p12enc->p12exp->integrityEnabled &&
        p12enc->p12exp->pwdIntegrity) {
        p12enc->middleBuf.hmacCx = p12enc->hmacCx;
    }
    p12enc->middleA1ecx = SEC_ASN1EncoderStart(&p12enc->p12exp->authSafe,
                                               sec_PKCS12AuthenticatedSafeTemplate,
                                               sec_P12A1OutputCB_HmacP7Update,
                                               &p12enc->middleBuf);
    if (!p12enc->middleA1ecx) {
        rv = SECFailure;
        goto loser;
    }
    SEC_ASN1EncoderSetStreaming(p12enc->middleA1ecx);
    SEC_ASN1EncoderSetTakeFromBuf(p12enc->middleA1ecx);

    /* encode each of the safes */
    while (p12enc->currentSafe != p12enc->p12exp->safeInfoCount) {
        sec_pkcs12_encoder_asafe_process(p12enc);
        p12enc->currentSafe++;
    }
    SEC_ASN1EncoderClearTakeFromBuf(p12enc->middleA1ecx);
    SEC_ASN1EncoderClearStreaming(p12enc->middleA1ecx);
    SEC_ASN1EncoderUpdate(p12enc->middleA1ecx, NULL, 0);
    SEC_ASN1EncoderFinish(p12enc->middleA1ecx);
    p12enc->middleA1ecx = NULL;

    sec_FlushPkcs12OutputBuffer(&p12enc->middleBuf);

    /* finish the encoding of the authenticated safes */
    rv = SEC_PKCS7EncoderFinish(p12enc->middleP7ecx, p12exp->pwfn,
                                p12exp->pwfnarg);
    p12enc->middleP7ecx = NULL;
    if (rv != SECSuccess) {
        goto loser;
    }

    SEC_ASN1EncoderClearTakeFromBuf(p12enc->outerA1ecx);
    SEC_ASN1EncoderClearStreaming(p12enc->outerA1ecx);

    /* update the mac, if necessary */
    rv = sec_Pkcs12FinishMac(p12enc);
    if (rv != SECSuccess) {
        goto loser;
    }

    /* finish encoding the pfx */
    rv = SEC_ASN1EncoderUpdate(p12enc->outerA1ecx, NULL, 0);

    SEC_ASN1EncoderFinish(p12enc->outerA1ecx);
    p12enc->outerA1ecx = NULL;

loser:
    sec_pkcs12_encoder_destroy_context(p12enc);
    return rv;
}

void
SEC_PKCS12DestroyExportContext(SEC_PKCS12ExportContext *p12ecx)
{
    int i = 0;

    if (!p12ecx) {
        return;
    }

    if (p12ecx->safeInfos) {
        i = 0;
        while (p12ecx->safeInfos[i] != NULL) {
            if (p12ecx->safeInfos[i]->encryptionKey) {
                PK11_FreeSymKey(p12ecx->safeInfos[i]->encryptionKey);
            }
            if (p12ecx->safeInfos[i]->cinfo) {
                SEC_PKCS7DestroyContentInfo(p12ecx->safeInfos[i]->cinfo);
            }
            i++;
        }
    }

    PK11_FreeSlot(p12ecx->slot);

    PORT_FreeArena(p12ecx->arena, PR_TRUE);
}