/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is the Netscape security libraries.
 *
 * The Initial Developer of the Original Code is
 * Netscape Communications Corporation.
 * Portions created by the Initial Developer are Copyright (C) 1994-2000
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

#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 "prcpucfg.h"

/*
** 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 {
    PRArenaPool *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 {
    PRArenaPool *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 {
    PRArenaPool *arena;
    SEC_PKCS12ExportContext *p12exp;
    PK11SymKey *encryptionKey;

    /* 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)
{
    PRArenaPool *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 */
    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;

    /* convert the password to unicode */ 
    if(!sec_pkcs12_convert_item_to_unicode(NULL, &uniPwitem, pwitem,
					       PR_TRUE, PR_TRUE, PR_TRUE)) {
	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(PRArenaPool *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(PRArenaPool *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;
    PRBool setName = PR_TRUE;
    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;
	    setName = PR_FALSE;
	    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(PRArenaPool *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(PRArenaPool *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_convert_item_to_unicode(p12ctxt->arena, &uniPwitem,
				 pwitem, PR_TRUE, PR_TRUE, PR_TRUE)) {
	    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, 1, 
						  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
 *********************************/

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

    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) {
	SECStatus rv;

	/* 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;
	}
	rv = SEC_PKCS7AddSigningTime(p12enc->aSafeCinfo);
	PORT_Assert(rv == SECSuccess);
    } else {
	p12enc->aSafeCinfo = SEC_PKCS7CreateData();

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

	    /* 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;
	    }   

	    /* 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, 1);
	    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:
    if(p12enc) {
	if(p12enc->aSafeCinfo) {
	    SEC_PKCS7DestroyContentInfo(p12enc->aSafeCinfo);
	}
	if(p12enc->hmacCx) {
	    PK11_DestroyContext(p12enc->hmacCx, PR_TRUE);
	}
    }
    if (p12exp->arena != NULL)
	PORT_ArenaRelease(p12exp->arena, mark);

    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);

    sec_FlushPkcs12OutputBuffer( &p12enc->middleBuf);

    /* finish the encoding of the authenticated safes */
    rv = SEC_PKCS7EncoderFinish(p12enc->middleP7ecx, p12exp->pwfn, 
    				p12exp->pwfnarg);
    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);

loser:
    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);
}

