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

/* release the memory taken up by the list of nicknames */
static void
sec_pkcs12_destroy_nickname_list(SECItem **nicknames)
{
    int i = 0;

    if (nicknames == NULL) {
        return;
    }

    while (nicknames[i] != NULL) {
        SECITEM_FreeItem(nicknames[i], PR_FALSE);
        i++;
    }

    PORT_Free(nicknames);
}

/* release the memory taken up by the list of certificates */
static void
sec_pkcs12_destroy_certificate_list(CERTCertificate **ref_certs)
{
    int i = 0;

    if (ref_certs == NULL) {
        return;
    }

    while (ref_certs[i] != NULL) {
        CERT_DestroyCertificate(ref_certs[i]);
        i++;
    }
}

static void
sec_pkcs12_destroy_cinfos_for_cert_bags(SEC_PKCS12CertAndCRLBag *certBag)
{
    int j = 0;
    j = 0;
    while (certBag->certAndCRLs[j] != NULL) {
        SECOidTag certType = SECOID_FindOIDTag(&certBag->certAndCRLs[j]->BagID);
        if (certType == SEC_OID_PKCS12_X509_CERT_CRL_BAG) {
            SEC_PKCS12X509CertCRL *x509;
            x509 = certBag->certAndCRLs[j]->value.x509;
            SEC_PKCS7DestroyContentInfo(&x509->certOrCRL);
        }
        j++;
    }
}

/* destroy all content infos since they were not allocated in common
 * pool
 */
static void
sec_pkcs12_destroy_cert_content_infos(SEC_PKCS12SafeContents *safe,
                                      SEC_PKCS12Baggage *baggage)
{
    int i, j;

    if ((safe != NULL) && (safe->contents != NULL)) {
        i = 0;
        while (safe->contents[i] != NULL) {
            SECOidTag bagType = SECOID_FindOIDTag(&safe->contents[i]->safeBagType);
            if (bagType == SEC_OID_PKCS12_CERT_AND_CRL_BAG_ID) {
                SEC_PKCS12CertAndCRLBag *certBag;
                certBag = safe->contents[i]->safeContent.certAndCRLBag;
                sec_pkcs12_destroy_cinfos_for_cert_bags(certBag);
            }
            i++;
        }
    }

    if ((baggage != NULL) && (baggage->bags != NULL)) {
        i = 0;
        while (baggage->bags[i] != NULL) {
            if (baggage->bags[i]->unencSecrets != NULL) {
                j = 0;
                while (baggage->bags[i]->unencSecrets[j] != NULL) {
                    SECOidTag bagType;
                    bagType = SECOID_FindOIDTag(&baggage->bags[i]->unencSecrets[j]->safeBagType);
                    if (bagType == SEC_OID_PKCS12_CERT_AND_CRL_BAG_ID) {
                        SEC_PKCS12CertAndCRLBag *certBag;
                        certBag = baggage->bags[i]->unencSecrets[j]->safeContent.certAndCRLBag;
                        sec_pkcs12_destroy_cinfos_for_cert_bags(certBag);
                    }
                    j++;
                }
            }
            i++;
        }
    }
}

/* convert the nickname list from a NULL termincated Char list
 * to a NULL terminated SECItem list
 */
static SECItem **
sec_pkcs12_convert_nickname_list(char **nicknames)
{
    SECItem **nicks;
    int i, j;
    PRBool error = PR_FALSE;

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

    i = j = 0;
    while (nicknames[i] != NULL) {
        i++;
    }

    /* allocate the space and copy the data */
    nicks = (SECItem **)PORT_ZAlloc(sizeof(SECItem *) * (i + 1));
    if (nicks != NULL) {
        for (j = 0; ((j < i) && (error == PR_FALSE)); j++) {
            nicks[j] = (SECItem *)PORT_ZAlloc(sizeof(SECItem));
            if (nicks[j] != NULL) {
                nicks[j]->data =
                    (unsigned char *)PORT_ZAlloc(PORT_Strlen(nicknames[j]) + 1);
                if (nicks[j]->data != NULL) {
                    nicks[j]->len = PORT_Strlen(nicknames[j]);
                    PORT_Memcpy(nicks[j]->data, nicknames[j], nicks[j]->len);
                    nicks[j]->data[nicks[j]->len] = 0;
                } else {
                    error = PR_TRUE;
                }
            } else {
                error = PR_TRUE;
            }
        }
    }

    if (error == PR_TRUE) {
        for (i = 0; i < j; i++) {
            SECITEM_FreeItem(nicks[i], PR_TRUE);
        }
        PORT_Free(nicks);
        nicks = NULL;
    }

    return nicks;
}

/* package the certificate add_cert into PKCS12 structures,
 * retrieve the certificate chain for the cert and return
 * the packaged contents.
 * poolp -- common memory pool;
 * add_cert -- certificate to package up
 * nickname for the certificate
 * a return of NULL indicates an error
 */
static SEC_PKCS12CertAndCRL *
sec_pkcs12_get_cert(PLArenaPool *poolp,
                    CERTCertificate *add_cert,
                    SECItem *nickname)
{
    SEC_PKCS12CertAndCRL *cert;
    SEC_PKCS7ContentInfo *cinfo;
    SGNDigestInfo *t_di;
    void *mark;
    SECStatus rv;

    if ((poolp == NULL) || (add_cert == NULL) || (nickname == NULL)) {
        return NULL;
    }
    mark = PORT_ArenaMark(poolp);

    cert = sec_pkcs12_new_cert_crl(poolp, SEC_OID_PKCS12_X509_CERT_CRL_BAG);
    if (cert != NULL) {

        /* copy the nickname */
        rv = SECITEM_CopyItem(poolp, &cert->nickname, nickname);
        if (rv != SECSuccess) {
            PORT_SetError(SEC_ERROR_NO_MEMORY);
            cert = NULL;
        } else {

            /* package the certificate and cert chain into a NULL signer
             * PKCS 7 SignedData content Info and prepare it for encoding
             * since we cannot use DER_ANY_TEMPLATE
             */
            cinfo = SEC_PKCS7CreateCertsOnly(add_cert, PR_TRUE, NULL);
            rv = SEC_PKCS7PrepareForEncode(cinfo, NULL, NULL, NULL);

            /* thumbprint the certificate */
            if ((cinfo != NULL) && (rv == SECSuccess)) {
                PORT_Memcpy(&cert->value.x509->certOrCRL, cinfo, sizeof(*cinfo));
                t_di = sec_pkcs12_compute_thumbprint(&add_cert->derCert);
                if (t_di != NULL) {
                    /* test */
                    rv = SGN_CopyDigestInfo(poolp, &cert->value.x509->thumbprint,
                                            t_di);
                    if (rv != SECSuccess) {
                        cert = NULL;
                        PORT_SetError(SEC_ERROR_NO_MEMORY);
                    }
                    SGN_DestroyDigestInfo(t_di);
                } else
                    cert = NULL;
            }
        }
    }

    if (cert == NULL) {
        PORT_ArenaRelease(poolp, mark);
    } else {
        PORT_ArenaUnmark(poolp, mark);
    }

    return cert;
}

/* package the private key associated with the certificate and
 * return the appropriate PKCS 12 structure
 * poolp common memory pool
 * nickname key nickname
 * cert -- cert to look up
 * wincx -- window handle
 * an error is indicated by a return of NULL
 */
static SEC_PKCS12PrivateKey *
sec_pkcs12_get_private_key(PLArenaPool *poolp,
                           SECItem *nickname,
                           CERTCertificate *cert,
                           void *wincx)
{
    SECKEYPrivateKeyInfo *pki;
    SEC_PKCS12PrivateKey *pk;
    SECStatus rv;
    void *mark;

    if ((poolp == NULL) || (nickname == NULL)) {
        return NULL;
    }

    mark = PORT_ArenaMark(poolp);

    /* retrieve key from the data base */
    pki = PK11_ExportPrivateKeyInfo(nickname, cert, wincx);
    if (pki == NULL) {
        PORT_ArenaRelease(poolp, mark);
        PORT_SetError(SEC_ERROR_PKCS12_UNABLE_TO_EXPORT_KEY);
        return NULL;
    }

    pk = (SEC_PKCS12PrivateKey *)PORT_ArenaZAlloc(poolp,
                                                  sizeof(SEC_PKCS12PrivateKey));
    if (pk != NULL) {
        rv = sec_pkcs12_init_pvk_data(poolp, &pk->pvkData);

        if (rv == SECSuccess) {
            /* copy the key into poolp memory space */
            rv = SECKEY_CopyPrivateKeyInfo(poolp, &pk->pkcs8data, pki);
            if (rv == SECSuccess) {
                rv = SECITEM_CopyItem(poolp, &pk->pvkData.nickname, nickname);
            }
        }

        if (rv != SECSuccess) {
            PORT_SetError(SEC_ERROR_NO_MEMORY);
            pk = NULL;
        }
    } else {
        PORT_SetError(SEC_ERROR_NO_MEMORY);
    }

    /* destroy private key, zeroing out data */
    SECKEY_DestroyPrivateKeyInfo(pki, PR_TRUE);
    if (pk == NULL) {
        PORT_ArenaRelease(poolp, mark);
    } else {
        PORT_ArenaUnmark(poolp, mark);
    }

    return pk;
}

/* get a shrouded key item associated with a certificate
 * return the appropriate PKCS 12 structure
 * poolp common memory pool
 * nickname key nickname
 * cert -- cert to look up
 * wincx -- window handle
 * an error is indicated by a return of NULL
 */
static SEC_PKCS12ESPVKItem *
sec_pkcs12_get_shrouded_key(PLArenaPool *poolp,
                            SECItem *nickname,
                            CERTCertificate *cert,
                            SECOidTag algorithm,
                            SECItem *pwitem,
                            PKCS12UnicodeConvertFunction unicodeFn,
                            void *wincx)
{
    SECKEYEncryptedPrivateKeyInfo *epki;
    SEC_PKCS12ESPVKItem *pk;
    void *mark;
    SECStatus rv;
    PK11SlotInfo *slot = NULL;
    PRBool swapUnicodeBytes = PR_FALSE;

#ifdef IS_LITTLE_ENDIAN
    swapUnicodeBytes = PR_TRUE;
#endif

    if ((poolp == NULL) || (nickname == NULL))
        return NULL;

    mark = PORT_ArenaMark(poolp);

    /* use internal key slot */
    slot = PK11_GetInternalKeySlot();

    /* retrieve encrypted prviate key */
    epki = PK11_ExportEncryptedPrivateKeyInfo(slot, algorithm, pwitem,
                                              nickname, cert, 1, 0, NULL);
    PK11_FreeSlot(slot);
    if (epki == NULL) {
        PORT_SetError(SEC_ERROR_PKCS12_UNABLE_TO_EXPORT_KEY);
        PORT_ArenaRelease(poolp, mark);
        return NULL;
    }

    /* create a private key and store the data into the poolp memory space */
    pk = sec_pkcs12_create_espvk(poolp, SEC_OID_PKCS12_PKCS8_KEY_SHROUDING);
    if (pk != NULL) {
        rv = sec_pkcs12_init_pvk_data(poolp, &pk->espvkData);
        rv = SECITEM_CopyItem(poolp, &pk->espvkData.nickname, nickname);
        pk->espvkCipherText.pkcs8KeyShroud =
            (SECKEYEncryptedPrivateKeyInfo *)PORT_ArenaZAlloc(poolp,
                                                              sizeof(SECKEYEncryptedPrivateKeyInfo));
        if ((pk->espvkCipherText.pkcs8KeyShroud != NULL) && (rv == SECSuccess)) {
            rv = SECKEY_CopyEncryptedPrivateKeyInfo(poolp,
                                                    pk->espvkCipherText.pkcs8KeyShroud, epki);
            if (rv == SECSuccess) {
                rv = (*unicodeFn)(poolp, &pk->espvkData.uniNickName, nickname,
                                  PR_TRUE, swapUnicodeBytes);
            }
        }

        if (rv != SECSuccess) {
            PORT_SetError(SEC_ERROR_NO_MEMORY);
            pk = NULL;
        }
    }

    SECKEY_DestroyEncryptedPrivateKeyInfo(epki, PR_TRUE);
    if (pk == NULL) {
        PORT_ArenaRelease(poolp, mark);
    } else {
        PORT_ArenaUnmark(poolp, mark);
    }

    return pk;
}

/* add a thumbprint to a private key associated certs list
 * pvk is the area where the list is stored
 * thumb is the thumbprint to copy
 * a return of SECFailure indicates an error
 */
static SECStatus
sec_pkcs12_add_thumbprint(SEC_PKCS12PVKSupportingData *pvk,
                          SGNDigestInfo *thumb)
{
    SGNDigestInfo **thumb_list = NULL;
    int nthumbs, size;
    void *mark, *dummy;
    SECStatus rv = SECFailure;

    if ((pvk == NULL) || (thumb == NULL)) {
        return SECFailure;
    }

    mark = PORT_ArenaMark(pvk->poolp);

    thumb_list = pvk->assocCerts;
    nthumbs = pvk->nThumbs;

    /* allocate list space needed -- either growing or allocating
     * list must be NULL terminated
     */
    size = sizeof(SGNDigestInfo *);
    dummy = PORT_ArenaGrow(pvk->poolp, thumb_list, (size * (nthumbs + 1)),
                           (size * (nthumbs + 2)));
    thumb_list = dummy;
    if (dummy != NULL) {
        thumb_list[nthumbs] = (SGNDigestInfo *)PORT_ArenaZAlloc(pvk->poolp,
                                                                sizeof(SGNDigestInfo));
        if (thumb_list[nthumbs] != NULL) {
            SGN_CopyDigestInfo(pvk->poolp, thumb_list[nthumbs], thumb);
            nthumbs += 1;
            thumb_list[nthumbs] = 0;
        } else {
            dummy = NULL;
        }
    }

    if (dummy == NULL) {
        PORT_ArenaRelease(pvk->poolp, mark);
        return SECFailure;
    }

    pvk->assocCerts = thumb_list;
    pvk->nThumbs = nthumbs;

    PORT_ArenaUnmark(pvk->poolp, mark);
    return SECSuccess;
}

/* search the list of shrouded keys in the baggage for the desired
 * name.  return a pointer to the item.  a return of NULL indicates
 * that no match was present or that an error occurred.
 */
static SEC_PKCS12ESPVKItem *
sec_pkcs12_get_espvk_by_name(SEC_PKCS12Baggage *luggage,
                             SECItem *name)
{
    PRBool found = PR_FALSE;
    SEC_PKCS12ESPVKItem *espvk = NULL;
    int i, j;
    SECComparison rv = SECEqual;
    SECItem *t_name;
    SEC_PKCS12BaggageItem *bag;

    if ((luggage == NULL) || (name == NULL)) {
        return NULL;
    }

    i = 0;
    while ((found == PR_FALSE) && (i < luggage->luggage_size)) {
        j = 0;
        bag = luggage->bags[i];
        while ((found == PR_FALSE) && (j < bag->nEspvks)) {
            espvk = bag->espvks[j];
            if (espvk->poolp == NULL) {
                espvk->poolp = luggage->poolp;
            }
            t_name = SECITEM_DupItem(&espvk->espvkData.nickname);
            if (t_name != NULL) {
                rv = SECITEM_CompareItem(name, t_name);
                if (rv == SECEqual) {
                    found = PR_TRUE;
                }
                SECITEM_FreeItem(t_name, PR_TRUE);
            } else {
                PORT_SetError(SEC_ERROR_NO_MEMORY);
                return NULL;
            }
            j++;
        }
        i++;
    }

    if (found != PR_TRUE) {
        PORT_SetError(SEC_ERROR_PKCS12_UNABLE_TO_LOCATE_OBJECT_BY_NAME);
        return NULL;
    }

    return espvk;
}

/* locates a certificate and copies the thumbprint to the
 * appropriate private key
 */
static SECStatus
sec_pkcs12_propagate_thumbprints(SECItem **nicknames,
                                 CERTCertificate **ref_certs,
                                 SEC_PKCS12SafeContents *safe,
                                 SEC_PKCS12Baggage *baggage)
{
    SEC_PKCS12CertAndCRL *cert;
    SEC_PKCS12PrivateKey *key;
    SEC_PKCS12ESPVKItem *espvk;
    int i;
    PRBool error = PR_FALSE;
    SECStatus rv = SECFailure;

    if ((nicknames == NULL) || (safe == NULL)) {
        return SECFailure;
    }

    i = 0;
    while ((nicknames[i] != NULL) && (error == PR_FALSE)) {
        /* process all certs */
        cert = (SEC_PKCS12CertAndCRL *)sec_pkcs12_find_object(safe, baggage,
                                                              SEC_OID_PKCS12_CERT_AND_CRL_BAG_ID,
                                                              nicknames[i], NULL);
        if (cert != NULL) {
            /* locate key and copy thumbprint */
            key = (SEC_PKCS12PrivateKey *)sec_pkcs12_find_object(safe, baggage,
                                                                 SEC_OID_PKCS12_KEY_BAG_ID,
                                                                 nicknames[i], NULL);
            if (key != NULL) {
                key->pvkData.poolp = key->poolp;
                rv = sec_pkcs12_add_thumbprint(&key->pvkData,
                                               &cert->value.x509->thumbprint);
                if (rv == SECFailure)
                    error = PR_TRUE; /* XXX Set error? */
            }

            /* look in the baggage as well...*/
            if ((baggage != NULL) && (error == PR_FALSE)) {
                espvk = sec_pkcs12_get_espvk_by_name(baggage, nicknames[i]);
                if (espvk != NULL) {
                    espvk->espvkData.poolp = espvk->poolp;
                    rv = sec_pkcs12_add_thumbprint(&espvk->espvkData,
                                                   &cert->value.x509->thumbprint);
                    if (rv == SECFailure)
                        error = PR_TRUE; /* XXX Set error? */
                }
            }
        }
        i++;
    }

    if (error == PR_TRUE) {
        return SECFailure;
    }

    return SECSuccess;
}

/* append a safe bag to the end of the safe contents list */
SECStatus
sec_pkcs12_append_safe_bag(SEC_PKCS12SafeContents *safe,
                           SEC_PKCS12SafeBag *bag)
{
    int size;
    void *mark = NULL, *dummy = NULL;

    if ((bag == NULL) || (safe == NULL))
        return SECFailure;

    mark = PORT_ArenaMark(safe->poolp);

    size = (safe->safe_size * sizeof(SEC_PKCS12SafeBag *));

    if (safe->safe_size > 0) {
        dummy = (SEC_PKCS12SafeBag **)PORT_ArenaGrow(safe->poolp,
                                                     safe->contents,
                                                     size,
                                                     (size + sizeof(SEC_PKCS12SafeBag *)));
        safe->contents = dummy;
    } else {
        safe->contents = (SEC_PKCS12SafeBag **)PORT_ArenaZAlloc(safe->poolp,
                                                                (2 * sizeof(SEC_PKCS12SafeBag *)));
        dummy = safe->contents;
    }

    if (dummy == NULL) {
        PORT_SetError(SEC_ERROR_NO_MEMORY);
        goto loser;
    }

    safe->contents[safe->safe_size] = bag;
    safe->safe_size++;
    safe->contents[safe->safe_size] = NULL;

    PORT_ArenaUnmark(safe->poolp, mark);
    return SECSuccess;

loser:
    PORT_ArenaRelease(safe->poolp, mark);
    return SECFailure;
}

/* append a certificate onto the end of a cert bag */
static SECStatus
sec_pkcs12_append_cert_to_bag(PLArenaPool *arena,
                              SEC_PKCS12SafeBag *safebag,
                              CERTCertificate *cert,
                              SECItem *nickname)
{
    int size;
    void *dummy = NULL, *mark = NULL;
    SEC_PKCS12CertAndCRL *p12cert;
    SEC_PKCS12CertAndCRLBag *bag;

    if ((arena == NULL) || (safebag == NULL) ||
        (cert == NULL) || (nickname == NULL)) {
        return SECFailure;
    }

    bag = safebag->safeContent.certAndCRLBag;
    if (bag == NULL) {
        return SECFailure;
    }

    mark = PORT_ArenaMark(arena);

    p12cert = sec_pkcs12_get_cert(arena, cert, nickname);
    if (p12cert == NULL) {
        PORT_ArenaRelease(bag->poolp, mark);
        return SECFailure;
    }

    size = bag->bag_size * sizeof(SEC_PKCS12CertAndCRL *);
    if (bag->bag_size > 0) {
        dummy = (SEC_PKCS12CertAndCRL **)PORT_ArenaGrow(bag->poolp,
                                                        bag->certAndCRLs,
                                                        size,
                                                        size + sizeof(SEC_PKCS12CertAndCRL *));
        bag->certAndCRLs = dummy;
    } else {
        bag->certAndCRLs = (SEC_PKCS12CertAndCRL **)PORT_ArenaZAlloc(bag->poolp,
                                                                     (2 * sizeof(SEC_PKCS12CertAndCRL *)));
        dummy = bag->certAndCRLs;
    }

    if (dummy == NULL) {
        PORT_SetError(SEC_ERROR_NO_MEMORY);
        goto loser;
    }

    bag->certAndCRLs[bag->bag_size] = p12cert;
    bag->bag_size++;
    bag->certAndCRLs[bag->bag_size] = NULL;

    PORT_ArenaUnmark(bag->poolp, mark);
    return SECSuccess;

loser:
    PORT_ArenaRelease(bag->poolp, mark);
    return SECFailure;
}

/* append a key onto the end of a list of keys in a key bag */
SECStatus
sec_pkcs12_append_key_to_bag(SEC_PKCS12SafeBag *safebag,
                             SEC_PKCS12PrivateKey *pk)
{
    void *mark, *dummy;
    SEC_PKCS12PrivateKeyBag *bag;
    int size;

    if ((safebag == NULL) || (pk == NULL))
        return SECFailure;

    bag = safebag->safeContent.keyBag;
    if (bag == NULL) {
        return SECFailure;
    }

    mark = PORT_ArenaMark(bag->poolp);

    size = (bag->bag_size * sizeof(SEC_PKCS12PrivateKey *));

    if (bag->bag_size > 0) {
        dummy = (SEC_PKCS12PrivateKey **)PORT_ArenaGrow(bag->poolp,
                                                        bag->privateKeys,
                                                        size,
                                                        size + sizeof(SEC_PKCS12PrivateKey *));
        bag->privateKeys = dummy;
    } else {
        bag->privateKeys = (SEC_PKCS12PrivateKey **)PORT_ArenaZAlloc(bag->poolp,
                                                                     (2 * sizeof(SEC_PKCS12PrivateKey *)));
        dummy = bag->privateKeys;
    }

    if (dummy == NULL) {
        PORT_SetError(SEC_ERROR_NO_MEMORY);
        goto loser;
    }

    bag->privateKeys[bag->bag_size] = pk;
    bag->bag_size++;
    bag->privateKeys[bag->bag_size] = NULL;

    PORT_ArenaUnmark(bag->poolp, mark);
    return SECSuccess;

loser:
    /* XXX Free memory? */
    PORT_ArenaRelease(bag->poolp, mark);
    return SECFailure;
}

/* append a safe bag to the baggage area */
static SECStatus
sec_pkcs12_append_unshrouded_bag(SEC_PKCS12BaggageItem *bag,
                                 SEC_PKCS12SafeBag *u_bag)
{
    int size;
    void *mark = NULL, *dummy = NULL;

    if ((bag == NULL) || (u_bag == NULL))
        return SECFailure;

    mark = PORT_ArenaMark(bag->poolp);

    /* dump things into the first bag */
    size = (bag->nSecrets + 1) * sizeof(SEC_PKCS12SafeBag *);
    dummy = PORT_ArenaGrow(bag->poolp,
                           bag->unencSecrets, size,
                           size + sizeof(SEC_PKCS12SafeBag *));
    bag->unencSecrets = dummy;
    if (dummy == NULL) {
        PORT_SetError(SEC_ERROR_NO_MEMORY);
        goto loser;
    }

    bag->unencSecrets[bag->nSecrets] = u_bag;
    bag->nSecrets++;
    bag->unencSecrets[bag->nSecrets] = NULL;

    PORT_ArenaUnmark(bag->poolp, mark);
    return SECSuccess;

loser:
    PORT_ArenaRelease(bag->poolp, mark);
    return SECFailure;
}

/* gather up all certificates and keys and package them up
 * in the safe, baggage, or both.
 * nicknames is the list of nicknames and corresponding certs in ref_certs
 * ref_certs a null terminated list of certificates
 * rSafe, rBaggage -- return areas for safe and baggage
 * shroud_keys -- store keys externally
 * pwitem -- password for computing integrity mac and encrypting contents
 * wincx -- window handle
 *
 * if a failure occurs, an error is set and SECFailure returned.
 */
static SECStatus
sec_pkcs12_package_certs_and_keys(SECItem **nicknames,
                                  CERTCertificate **ref_certs,
                                  PRBool unencryptedCerts,
                                  SEC_PKCS12SafeContents **rSafe,
                                  SEC_PKCS12Baggage **rBaggage,
                                  PRBool shroud_keys,
                                  SECOidTag shroud_alg,
                                  SECItem *pwitem,
                                  PKCS12UnicodeConvertFunction unicodeFn,
                                  void *wincx)
{
    PLArenaPool *permArena;
    SEC_PKCS12SafeContents *safe = NULL;
    SEC_PKCS12Baggage *baggage = NULL;

    SECStatus rv = SECFailure;
    PRBool problem = PR_FALSE;

    SEC_PKCS12ESPVKItem *espvk = NULL;
    SEC_PKCS12PrivateKey *pk = NULL;
    CERTCertificate *add_cert = NULL;
    SEC_PKCS12SafeBag *certbag = NULL, *keybag = NULL;
    SEC_PKCS12BaggageItem *external_bag = NULL;
    int ncerts = 0, nkeys = 0;
    int i;

    if ((nicknames == NULL) || (rSafe == NULL) || (rBaggage == NULL)) {
        return SECFailure;
    }

    *rBaggage = baggage;
    *rSafe = safe;

    permArena = PORT_NewArena(SEC_ASN1_DEFAULT_ARENA_SIZE);
    if (permArena == NULL) {
        PORT_SetError(SEC_ERROR_NO_MEMORY);
        return SECFailure;
    }

    /* allocate structures */
    safe = sec_pkcs12_create_safe_contents(permArena);
    if (safe == NULL) {
        PORT_SetError(SEC_ERROR_NO_MEMORY);
        rv = SECFailure;
        goto loser;
    }

    certbag = sec_pkcs12_create_safe_bag(permArena,
                                         SEC_OID_PKCS12_CERT_AND_CRL_BAG_ID);
    if (certbag == NULL) {
        rv = SECFailure;
        goto loser;
    }

    if (shroud_keys != PR_TRUE) {
        keybag = sec_pkcs12_create_safe_bag(permArena,
                                            SEC_OID_PKCS12_KEY_BAG_ID);
        if (keybag == NULL) {
            rv = SECFailure;
            goto loser;
        }
    }

    if ((shroud_keys == PR_TRUE) || (unencryptedCerts == PR_TRUE)) {
        baggage = sec_pkcs12_create_baggage(permArena);
        if (baggage == NULL) {
            rv = SECFailure;
            goto loser;
        }
        external_bag = sec_pkcs12_create_external_bag(baggage);
    }

    /* package keys and certs */
    i = 0;
    while ((nicknames[i] != NULL) && (problem == PR_FALSE)) {
        if (ref_certs[i] != NULL) {
            /* append cert to bag o certs */
            rv = sec_pkcs12_append_cert_to_bag(permArena, certbag,
                                               ref_certs[i],
                                               nicknames[i]);
            if (rv == SECFailure) {
                problem = PR_FALSE;
            } else {
                ncerts++;
            }

            if (rv == SECSuccess) {
                /* package up them keys */
                if (shroud_keys == PR_TRUE) {
                    espvk = sec_pkcs12_get_shrouded_key(permArena,
                                                        nicknames[i],
                                                        ref_certs[i],
                                                        shroud_alg,
                                                        pwitem, unicodeFn,
                                                        wincx);
                    if (espvk != NULL) {
                        rv = sec_pkcs12_append_shrouded_key(external_bag, espvk);
                        SECITEM_CopyItem(permArena, &espvk->derCert,
                                         &ref_certs[i]->derCert);
                    } else {
                        rv = SECFailure;
                    }
                } else {
                    pk = sec_pkcs12_get_private_key(permArena, nicknames[i],
                                                    ref_certs[i], wincx);
                    if (pk != NULL) {
                        rv = sec_pkcs12_append_key_to_bag(keybag, pk);
                        SECITEM_CopyItem(permArena, &espvk->derCert,
                                         &ref_certs[i]->derCert);
                    } else {
                        rv = SECFailure;
                    }
                }

                if (rv == SECFailure) {
                    problem = PR_TRUE;
                } else {
                    nkeys++;
                }
            }
        } else {
            /* handle only keys here ? */
            problem = PR_TRUE;
        }
        i++;
    }

/* let success fall through */
loser:
    if (problem == PR_FALSE) {
        /* if we have certs, we want to append the cert bag to the
         * appropriate area
         */
        if (ncerts > 0) {
            if (unencryptedCerts != PR_TRUE) {
                rv = sec_pkcs12_append_safe_bag(safe, certbag);
            } else {
                rv = sec_pkcs12_append_unshrouded_bag(external_bag, certbag);
            }
        } else {
            rv = SECSuccess;
        }

        /* append key bag, if they are stored in safe contents */
        if ((rv == SECSuccess) && (shroud_keys == PR_FALSE) && (nkeys > 0)) {
            rv = sec_pkcs12_append_safe_bag(safe, keybag);
        }
    } else {
        rv = SECFailure;
    }

    /* if baggage not used, NULLify it */
    if ((shroud_keys == PR_TRUE) || (unencryptedCerts == PR_TRUE)) {
        if (((unencryptedCerts == PR_TRUE) && (ncerts == 0)) &&
            ((shroud_keys == PR_TRUE) && (nkeys == 0)))
            baggage = NULL;
    } else {
        baggage = NULL;
    }

    if ((problem == PR_TRUE) || (rv == SECFailure)) {
        PORT_FreeArena(permArena, PR_TRUE);
        rv = SECFailure;
        baggage = NULL;
        safe = NULL;
    }

    *rBaggage = baggage;
    *rSafe = safe;

    return rv;
}

/* DER encode the safe contents and return a SECItem.  if an error
 * occurs, NULL is returned.
 */
static SECItem *
sec_pkcs12_encode_safe_contents(SEC_PKCS12SafeContents *safe)
{
    SECItem *dsafe = NULL, *tsafe;
    void *dummy = NULL;
    PLArenaPool *arena;

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

    /*    rv = sec_pkcs12_prepare_for_der_code_safe(safe, PR_TRUE);
    if(rv != SECSuccess) {
        PORT_SetError(SEC_ERROR_NO_MEMORY);
        return NULL;
    }*/

    arena = PORT_NewArena(SEC_ASN1_DEFAULT_ARENA_SIZE);
    if (arena == NULL) {
        PORT_SetError(SEC_ERROR_NO_MEMORY);
        return NULL;
    }

    tsafe = (SECItem *)PORT_ArenaZAlloc(arena, sizeof(SECItem));
    if (tsafe != NULL) {
        dummy = SEC_ASN1EncodeItem(arena, tsafe, safe,
                                   SEC_PKCS12SafeContentsTemplate);
        if (dummy != NULL) {
            dsafe = SECITEM_DupItem(tsafe);
        } else {
            PORT_SetError(SEC_ERROR_NO_MEMORY);
        }
    } else {
        PORT_SetError(SEC_ERROR_NO_MEMORY);
    }

    PORT_FreeArena(arena, PR_TRUE);

    return dsafe;
}

/* prepare the authenicated safe for encoding and encode it.
 *   baggage is copied to the appropriate area, safe is encoded and
 *   encrypted.  the version and transport mode are set on the asafe.
 *   the whole ball of wax is then der encoded and packaged up into
 *   data content info
 * safe -- container of certs and keys, is encrypted.
 * baggage -- container of certs and keys, keys assumed to be encrypted by
 *    another method, certs are in the clear
 * algorithm -- algorithm by which to encrypt safe
 * pwitem -- password for encryption
 * wincx - window handle
 *
 * return of NULL is an error condition.
 */
static SEC_PKCS7ContentInfo *
sec_pkcs12_get_auth_safe(SEC_PKCS12SafeContents *safe,
                         SEC_PKCS12Baggage *baggage,
                         SECOidTag algorithm,
                         SECItem *pwitem,
                         PKCS12UnicodeConvertFunction unicodeFn,
                         void *wincx)
{
    SECItem *src = NULL, *dest = NULL, *psalt = NULL;
    PLArenaPool *poolp;
    SEC_PKCS12AuthenticatedSafe *asafe;
    SEC_PKCS7ContentInfo *safe_cinfo = NULL;
    SEC_PKCS7ContentInfo *asafe_cinfo = NULL;
    void *dummy;
    SECStatus rv = SECSuccess;
    PRBool swapUnicodeBytes = PR_FALSE;

#ifdef IS_LITTLE_ENDIAN
    swapUnicodeBytes = PR_TRUE;
#endif

    if (((safe != NULL) && (pwitem == NULL)) && (baggage == NULL))
        return NULL;

    poolp = PORT_NewArena(SEC_ASN1_DEFAULT_ARENA_SIZE);
    if (poolp == NULL) {
        PORT_SetError(SEC_ERROR_NO_MEMORY);
        return NULL;
    }

    /* prepare authenticated safe for encode */
    asafe = sec_pkcs12_new_asafe(poolp);
    if (asafe != NULL) {

        /* set version */
        dummy = SEC_ASN1EncodeInteger(asafe->poolp, &asafe->version,
                                      SEC_PKCS12_PFX_VERSION);
        if (dummy == NULL) {
            PORT_SetError(SEC_ERROR_NO_MEMORY);
            rv = SECFailure;
            goto loser;
        }

        /* generate the privacy salt used to create virtual pwd */
        psalt = sec_pkcs12_generate_salt();
        if (psalt != NULL) {
            rv = SECITEM_CopyItem(asafe->poolp, &asafe->privacySalt,
                                  psalt);
            if (rv == SECSuccess) {
                asafe->privacySalt.len *= 8;
            } else {
                SECITEM_ZfreeItem(psalt, PR_TRUE);
                PORT_SetError(SEC_ERROR_NO_MEMORY);
                goto loser;
            }
        }

        if ((psalt == NULL) || (rv == SECFailure)) {
            PORT_SetError(SEC_ERROR_NO_MEMORY);
            rv = SECFailure;
            goto loser;
        }

        /* package up safe contents */
        if (safe != NULL) {
            safe_cinfo = SEC_PKCS7CreateEncryptedData(algorithm, NULL, wincx);
            if ((safe_cinfo != NULL) && (safe->safe_size > 0)) {
                /* encode the safe and encrypt the contents of the
                 * content info
                 */
                src = sec_pkcs12_encode_safe_contents(safe);

                if (src != NULL) {
                    rv = SEC_PKCS7SetContent(safe_cinfo, (char *)src->data, src->len);
                    SECITEM_ZfreeItem(src, PR_TRUE);
                    if (rv == SECSuccess) {
                        SECItem *vpwd;
                        vpwd = sec_pkcs12_create_virtual_password(pwitem, psalt,
                                                                  unicodeFn, swapUnicodeBytes);
                        if (vpwd != NULL) {
                            rv = SEC_PKCS7EncryptContents(NULL, safe_cinfo,
                                                          vpwd, wincx);
                            SECITEM_ZfreeItem(vpwd, PR_TRUE);
                        } else {
                            rv = SECFailure;
                            PORT_SetError(SEC_ERROR_NO_MEMORY);
                        }
                    } else {
                        PORT_SetError(SEC_ERROR_NO_MEMORY);
                    }
                } else {
                    rv = SECFailure;
                }
            } else if (safe->safe_size > 0) {
                PORT_SetError(SEC_ERROR_NO_MEMORY);
                goto loser;
            } else {
                /* case where there is NULL content in the safe contents */
                rv = SEC_PKCS7SetContent(safe_cinfo, NULL, 0);
                if (rv != SECFailure) {
                    PORT_SetError(SEC_ERROR_NO_MEMORY);
                }
            }

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

            asafe->safe = safe_cinfo;
            /*
            PORT_Memcpy(&asafe->safe, safe_cinfo, sizeof(*safe_cinfo));
            */
        }

        /* copy the baggage to the authenticated safe baggage if present */
        if (baggage != NULL) {
            PORT_Memcpy(&asafe->baggage, baggage, sizeof(*baggage));
        }

        /* encode authenticated safe and store it in a Data content info */
        dest = (SECItem *)PORT_ArenaZAlloc(poolp, sizeof(SECItem));
        if (dest != NULL) {
            dummy = SEC_ASN1EncodeItem(poolp, dest, asafe,
                                       SEC_PKCS12AuthenticatedSafeTemplate);
            if (dummy != NULL) {
                asafe_cinfo = SEC_PKCS7CreateData();
                if (asafe_cinfo != NULL) {
                    rv = SEC_PKCS7SetContent(asafe_cinfo,
                                             (char *)dest->data,
                                             dest->len);
                    if (rv != SECSuccess) {
                        PORT_SetError(SEC_ERROR_NO_MEMORY);
                        SEC_PKCS7DestroyContentInfo(asafe_cinfo);
                        asafe_cinfo = NULL;
                    }
                }
            } else {
                PORT_SetError(SEC_ERROR_NO_MEMORY);
                rv = SECFailure;
            }
        }
    }

loser:
    PORT_FreeArena(poolp, PR_TRUE);
    if (safe_cinfo != NULL) {
        SEC_PKCS7DestroyContentInfo(safe_cinfo);
    }
    if (psalt != NULL) {
        SECITEM_ZfreeItem(psalt, PR_TRUE);
    }

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

    return asafe_cinfo;
}

/* generates the PFX and computes the mac on the authenticated safe
 * NULL implies an error
 */
static SEC_PKCS12PFXItem *
sec_pkcs12_get_pfx(SEC_PKCS7ContentInfo *cinfo,
                   PRBool do_mac,
                   SECItem *pwitem, PKCS12UnicodeConvertFunction unicodeFn)
{
    SECItem *dest = NULL, *mac = NULL, *salt = NULL, *key = NULL;
    SEC_PKCS12PFXItem *pfx;
    SECStatus rv = SECFailure;
    SGNDigestInfo *di;
    SECItem *vpwd;
    PRBool swapUnicodeBytes = PR_FALSE;

#ifdef IS_LITTLE_ENDIAN
    swapUnicodeBytes = PR_TRUE;
#endif

    if ((cinfo == NULL) || ((do_mac == PR_TRUE) && (pwitem == NULL))) {
        return NULL;
    }

    /* allocate new pfx structure */
    pfx = sec_pkcs12_new_pfx();
    if (pfx == NULL) {
        PORT_SetError(SEC_ERROR_NO_MEMORY);
        return NULL;
    }

    PORT_Memcpy(&pfx->authSafe, cinfo, sizeof(*cinfo));
    if (do_mac == PR_TRUE) {

        /* salt for computing mac */
        salt = sec_pkcs12_generate_salt();
        if (salt != NULL) {
            rv = SECITEM_CopyItem(pfx->poolp, &pfx->macData.macSalt, salt);
            pfx->macData.macSalt.len *= 8;

            vpwd = sec_pkcs12_create_virtual_password(pwitem, salt,
                                                      unicodeFn, swapUnicodeBytes);
            if (vpwd == NULL) {
                rv = SECFailure;
                key = NULL;
            } else {
                key = sec_pkcs12_generate_key_from_password(SEC_OID_SHA1,
                                                            salt, vpwd);
                SECITEM_ZfreeItem(vpwd, PR_TRUE);
            }

            if ((key != NULL) && (rv == SECSuccess)) {
                dest = SEC_PKCS7GetContent(cinfo);
                if (dest != NULL) {

                    /* compute mac on data -- for password integrity mode */
                    mac = sec_pkcs12_generate_mac(key, dest, PR_FALSE);
                    if (mac != NULL) {
                        di = SGN_CreateDigestInfo(SEC_OID_SHA1,
                                                  mac->data, mac->len);
                        if (di != NULL) {
                            rv = SGN_CopyDigestInfo(pfx->poolp,
                                                    &pfx->macData.safeMac, di);
                            SGN_DestroyDigestInfo(di);
                        } else {
                            PORT_SetError(SEC_ERROR_NO_MEMORY);
                        }
                        SECITEM_ZfreeItem(mac, PR_TRUE);
                    }
                } else {
                    rv = SECFailure;
                }
            } else {
                PORT_SetError(SEC_ERROR_NO_MEMORY);
                rv = SECFailure;
            }

            if (key != NULL) {
                SECITEM_ZfreeItem(key, PR_TRUE);
            }
            SECITEM_ZfreeItem(salt, PR_TRUE);
        }
    }

    if (rv == SECFailure) {
        SEC_PKCS12DestroyPFX(pfx);
        pfx = NULL;
    }

    return pfx;
}

/* der encode the pfx */
static SECItem *
sec_pkcs12_encode_pfx(SEC_PKCS12PFXItem *pfx)
{
    SECItem *dest;
    void *dummy;

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

    dest = (SECItem *)PORT_ZAlloc(sizeof(SECItem));
    if (dest == NULL) {
        PORT_SetError(SEC_ERROR_NO_MEMORY);
        return NULL;
    }

    dummy = SEC_ASN1EncodeItem(NULL, dest, pfx, SEC_PKCS12PFXItemTemplate);
    if (dummy == NULL) {
        PORT_SetError(SEC_ERROR_NO_MEMORY);
        SECITEM_ZfreeItem(dest, PR_TRUE);
        dest = NULL;
    }

    return dest;
}

SECItem *
SEC_PKCS12GetPFX(char **nicknames,
                 CERTCertificate **ref_certs,
                 PRBool shroud_keys,
                 SEC_PKCS5GetPBEPassword pbef,
                 void *pbearg,
                 PKCS12UnicodeConvertFunction unicodeFn,
                 void *wincx)
{
    SECItem **nicks = NULL;
    SEC_PKCS12PFXItem *pfx = NULL;
    SEC_PKCS12Baggage *baggage = NULL;
    SEC_PKCS12SafeContents *safe = NULL;
    SEC_PKCS7ContentInfo *cinfo = NULL;
    SECStatus rv = SECFailure;
    SECItem *dest = NULL, *pwitem = NULL;
    PRBool problem = PR_FALSE;
    PRBool unencryptedCerts;
    SECOidTag shroud_alg, safe_alg;

    /* how should we encrypt certs ? */
    unencryptedCerts = !SEC_PKCS12IsEncryptionAllowed();
    if (!unencryptedCerts) {
        safe_alg = SEC_PKCS12GetPreferredEncryptionAlgorithm();
        if (safe_alg == SEC_OID_UNKNOWN) {
            safe_alg = SEC_PKCS12GetStrongestAllowedAlgorithm();
        }
        if (safe_alg == SEC_OID_UNKNOWN) {
            unencryptedCerts = PR_TRUE;
            /* for export where no encryption is allowed, we still need
             * to encrypt the NULL contents per the spec.  encrypted info
             * is known plaintext, so it shouldn't be a problem.
             */
            safe_alg = SEC_OID_PKCS12_PBE_WITH_SHA1_AND_40_BIT_RC2_CBC;
        }
    } else {
        /* for export where no encryption is allowed, we still need
         * to encrypt the NULL contents per the spec.  encrypted info
         * is known plaintext, so it shouldn't be a problem.
         */
        safe_alg = SEC_OID_PKCS12_PBE_WITH_SHA1_AND_40_BIT_RC2_CBC;
    }

    /* keys are always stored with triple DES */
    shroud_alg = SEC_OID_PKCS12_PBE_WITH_SHA1_AND_TRIPLE_DES_CBC;

    /* check for FIPS, if so, do not encrypt certs */
    if (PK11_IsFIPS() && !unencryptedCerts) {
        unencryptedCerts = PR_TRUE;
    }

    if ((nicknames == NULL) || (pbef == NULL) || (ref_certs == NULL)) {
        problem = PR_TRUE;
        goto loser;
    }

    /* get password */
    pwitem = (*pbef)(pbearg);
    if (pwitem == NULL) {
        problem = PR_TRUE;
        goto loser;
    }
    nicks = sec_pkcs12_convert_nickname_list(nicknames);

    /* get safe and baggage */
    rv = sec_pkcs12_package_certs_and_keys(nicks, ref_certs, unencryptedCerts,
                                           &safe, &baggage, shroud_keys,
                                           shroud_alg, pwitem, unicodeFn, wincx);
    if (rv == SECFailure) {
        problem = PR_TRUE;
    }

    if ((safe != NULL) && (problem == PR_FALSE)) {
        /* copy thumbprints */
        rv = sec_pkcs12_propagate_thumbprints(nicks, ref_certs, safe, baggage);

        /* package everything up into AuthenticatedSafe */
        cinfo = sec_pkcs12_get_auth_safe(safe, baggage,
                                         safe_alg, pwitem, unicodeFn, wincx);

        sec_pkcs12_destroy_cert_content_infos(safe, baggage);

        /* get the pfx and mac it */
        if (cinfo != NULL) {
            pfx = sec_pkcs12_get_pfx(cinfo, PR_TRUE, pwitem, unicodeFn);
            if (pfx != NULL) {
                dest = sec_pkcs12_encode_pfx(pfx);
                SEC_PKCS12DestroyPFX(pfx);
            }
            SEC_PKCS7DestroyContentInfo(cinfo);
        }

        if (safe != NULL) {
            PORT_FreeArena(safe->poolp, PR_TRUE);
        }
    } else {
        if (safe != NULL) {
            PORT_FreeArena(safe->poolp, PR_TRUE);
        }
    }

loser:
    if (nicks != NULL) {
        sec_pkcs12_destroy_nickname_list(nicks);
    }

    if (ref_certs != NULL) {
        sec_pkcs12_destroy_certificate_list(ref_certs);
    }

    if (pwitem != NULL) {
        SECITEM_ZfreeItem(pwitem, PR_TRUE);
    }

    if (problem == PR_TRUE) {
        dest = NULL;
    }

    return dest;
}
