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

#ifdef _CRTDBG_MAP_ALLOC
#include <stdlib.h>
#include <crtdbg.h>
#endif

#include "nspr.h"
#include "secutil.h"
#include "pk11func.h"
#include "pkcs12.h"
#include "p12plcy.h"
#include "pk12util.h"
#include "nss.h"
#include "secport.h"
#include "secpkcs5.h"
#include "sechash.h"
#include "certdb.h"

#define PKCS12_IN_BUFFER_SIZE 200

static char *progName;
PRBool pk12_debugging = PR_FALSE;
PRBool dumpRawFile;
static PRBool pk12uForceUnicode;

PRIntn pk12uErrno = 0;

static void
Usage()
{
#define FPS PR_fprintf(PR_STDERR,
    FPS "Usage:	 %s -i importfile [-d certdir] [-P dbprefix] [-h tokenname]\n",
				 progName);
    FPS "\t\t [-k slotpwfile | -K slotpw] [-w p12filepwfile | -W p12filepw]\n");
    FPS "\t\t [-v]\n");

    FPS "Usage:	 %s -l listfile [-d certdir] [-P dbprefix] [-h tokenname]\n",
				 progName);
    FPS "\t\t [-k slotpwfile | -K slotpw] [-w p12filepwfile | -W p12filepw]\n");
    FPS "\t\t [-v]\n");

    FPS "Usage:	 %s -o exportfile -n certname [-d certdir] [-P dbprefix]\n",
		progName);
    FPS "\t\t [-c key_cipher] [-C cert_cipher] [-M mac_alg]\n"
        "\t\t [-m | --key_len keyLen] [--cert_key_len certKeyLen] [-v]\n");
    FPS "\t\t [-k slotpwfile | -K slotpw]\n"
        "\t\t [-w p12filepwfile | -W p12filepw]\n");

    exit(PK12UERR_USAGE);
}

static PRBool
p12u_OpenFile(p12uContext *p12cxt, PRBool fileRead)
{
    if (!p12cxt || !p12cxt->filename) {
        return PR_FALSE;
    }

    if (fileRead) {
        p12cxt->file = PR_Open(p12cxt->filename,
                               PR_RDONLY, 0400);
    } else {
        p12cxt->file = PR_Open(p12cxt->filename,
                               PR_CREATE_FILE | PR_RDWR | PR_TRUNCATE,
                               0600);
    }

    if (!p12cxt->file) {
        p12cxt->error = PR_TRUE;
        return PR_FALSE;
    }

    return PR_TRUE;
}

static void
p12u_DestroyContext(p12uContext **ppCtx, PRBool removeFile)
{
    if (!ppCtx || !(*ppCtx)) {
        return;
    }

    if ((*ppCtx)->file != NULL) {
        PR_Close((*ppCtx)->file);
    }

    if ((*ppCtx)->filename != NULL) {
        if (removeFile) {
            PR_Delete((*ppCtx)->filename);
        }
        PL_strfree((*ppCtx)->filename);
        (*ppCtx)->filename = NULL;
    }

    PR_Free(*ppCtx);
    *ppCtx = NULL;
}

static p12uContext *
p12u_InitContext(PRBool fileImport, char *filename)
{
    p12uContext *p12cxt;

    p12cxt = PORT_ZNew(p12uContext);
    if (!p12cxt) {
        return NULL;
    }

    p12cxt->error = PR_FALSE;
    p12cxt->errorValue = 0;
    p12cxt->filename = PL_strdup(filename);

    if (!p12u_OpenFile(p12cxt, fileImport)) {
        p12u_DestroyContext(&p12cxt, PR_FALSE);
        return NULL;
    }

    return p12cxt;
}

SECItem *
P12U_NicknameCollisionCallback(SECItem *old_nick, PRBool *cancel, void *wincx)
{
    char *nick = NULL;
    SECItem *ret_nick = NULL;
    CERTCertificate *cert = (CERTCertificate *)wincx;

    if (!cancel || !cert) {
        pk12uErrno = PK12UERR_USER_CANCELLED;
        return NULL;
    }

    if (!old_nick)
        fprintf(stdout, "pk12util: no nickname for cert in PKCS12 file.\n");

#if 0
    /* XXX not handled yet  */
    *cancel = PR_TRUE;
    return NULL;

#else

    nick = CERT_MakeCANickname(cert);
    if (!nick) {
        return NULL;
    }

    if (old_nick && old_nick->data && old_nick->len &&
        PORT_Strlen(nick) == old_nick->len &&
        !PORT_Strncmp((char *)old_nick->data, nick, old_nick->len)) {
        PORT_Free(nick);
        PORT_SetError(SEC_ERROR_IO);
        return NULL;
    }

    fprintf(stdout, "pk12util: using nickname: %s\n", nick);
    ret_nick = PORT_ZNew(SECItem);
    if (ret_nick == NULL) {
        PORT_Free(nick);
        return NULL;
    }

    ret_nick->data = (unsigned char *)nick;
    ret_nick->len = PORT_Strlen(nick);

    return ret_nick;
#endif
}

static SECStatus
p12u_SwapUnicodeBytes(SECItem *uniItem)
{
    unsigned int i;
    unsigned char a;
    if ((uniItem == NULL) || (uniItem->len % 2)) {
        return SECFailure;
    }
    for (i = 0; i < uniItem->len; i += 2) {
        a = uniItem->data[i];
        uniItem->data[i] = uniItem->data[i + 1];
        uniItem->data[i + 1] = a;
    }
    return SECSuccess;
}

static PRBool
p12u_ucs2_ascii_conversion_function(PRBool toUnicode,
                                    unsigned char *inBuf,
                                    unsigned int inBufLen,
                                    unsigned char *outBuf,
                                    unsigned int maxOutBufLen,
                                    unsigned int *outBufLen,
                                    PRBool swapBytes)
{
    SECItem it = { 0 };
    SECItem *dup = NULL;
    PRBool ret;

#ifdef DEBUG_CONVERSION
    if (pk12_debugging) {
        int i;
        printf("Converted from:\n");
        for (i = 0; i < inBufLen; i++) {
            printf("%2x ", inBuf[i]);
            /*if (i%60 == 0) printf("\n");*/
        }
        printf("\n");
    }
#endif
    it.data = inBuf;
    it.len = inBufLen;
    dup = SECITEM_DupItem(&it);
    if (!dup) {
        return PR_FALSE;
    }
    /* If converting Unicode to ASCII, swap bytes before conversion
     * as neccessary.
     */
    if (!toUnicode && swapBytes) {
        if (p12u_SwapUnicodeBytes(dup) != SECSuccess) {
            SECITEM_ZfreeItem(dup, PR_TRUE);
            return PR_FALSE;
        }
    }
    /* Perform the conversion. */
    ret = PORT_UCS2_UTF8Conversion(toUnicode, dup->data, dup->len,
                                   outBuf, maxOutBufLen, outBufLen);
    SECITEM_ZfreeItem(dup, PR_TRUE);

#ifdef DEBUG_CONVERSION
    if (pk12_debugging) {
        int i;
        printf("Converted to:\n");
        for (i = 0; i < *outBufLen; i++) {
            printf("%2x ", outBuf[i]);
            /*if (i%60 == 0) printf("\n");*/
        }
        printf("\n");
    }
#endif
    return ret;
}

SECStatus
P12U_UnicodeConversion(PLArenaPool *arena, SECItem *dest, SECItem *src,
                       PRBool toUnicode, PRBool swapBytes)
{
    unsigned int allocLen;
    if (!dest || !src) {
        return SECFailure;
    }
    allocLen = ((toUnicode) ? (src->len << 2) : src->len);
    if (arena) {
        dest->data = PORT_ArenaZAlloc(arena, allocLen);
    } else {
        dest->data = PORT_ZAlloc(allocLen);
    }
    if (PORT_UCS2_ASCIIConversion(toUnicode, src->data, src->len,
                                  dest->data, allocLen, &dest->len,
                                  swapBytes) == PR_FALSE) {
        if (!arena) {
            PORT_Free(dest->data);
        }
        dest->data = NULL;
        return SECFailure;
    }
    return SECSuccess;
}

/*
 *
 */
SECItem *
P12U_GetP12FilePassword(PRBool confirmPw, secuPWData *p12FilePw)
{
    char *p0 = NULL;
    SECItem *pwItem = NULL;

    if (p12FilePw == NULL || p12FilePw->source == PW_NONE) {
        char *p1 = NULL;
        int rc;
        for (;;) {
            p0 = SECU_GetPasswordString(NULL,
                                        "Enter password for PKCS12 file: ");
            if (!confirmPw || p0 == NULL)
                break;
            p1 = SECU_GetPasswordString(NULL, "Re-enter password: ");
            if (p1 == NULL) {
                PORT_ZFree(p0, PL_strlen(p0));
                p0 = NULL;
                break;
            }
            rc = PL_strcmp(p0, p1);
            PORT_ZFree(p1, PL_strlen(p1));
            if (rc == 0)
                break;
            PORT_ZFree(p0, PL_strlen(p0));
        }
    } else if (p12FilePw->source == PW_FROMFILE) {
        p0 = SECU_FilePasswd(NULL, PR_FALSE, p12FilePw->data);
    } else { /* Plaintext */
        p0 = PORT_Strdup(p12FilePw->data);
    }

    if (p0 == NULL) {
        return NULL;
    }
    pwItem = SECITEM_AllocItem(NULL, NULL, PL_strlen(p0) + 1);
    memcpy(pwItem->data, p0, pwItem->len);

    PORT_ZFree(p0, PL_strlen(p0));

    return pwItem;
}

SECStatus
P12U_InitSlot(PK11SlotInfo *slot, secuPWData *slotPw)
{
    SECStatus rv;

    /*	New databases, initialize keydb password. */
    if (PK11_NeedUserInit(slot)) {
        rv = SECU_ChangePW(slot,
                           (slotPw->source == PW_PLAINTEXT) ? slotPw->data : 0,
                           (slotPw->source == PW_FROMFILE) ? slotPw->data : 0);
        if (rv != SECSuccess) {
            SECU_PrintError(progName, "Failed to initialize slot \"%s\"",
                            PK11_GetSlotName(slot));
            return SECFailure;
        }
    }

    if (PK11_Authenticate(slot, PR_TRUE, slotPw) != SECSuccess) {
        SECU_PrintError(progName,
                        "Failed to authenticate to PKCS11 slot");
        PORT_SetError(SEC_ERROR_USER_CANCELLED);
        pk12uErrno = PK12UERR_USER_CANCELLED;
        return SECFailure;
    }

    return SECSuccess;
}

/* This routine takes care of getting the PKCS12 file password, then reading and
 * verifying the file. It returns the decoder context and a filled in password.
 * (The password is needed by P12U_ImportPKCS12Object() to import the private
 * key.)
 */
SEC_PKCS12DecoderContext *
p12U_ReadPKCS12File(SECItem *uniPwp, char *in_file, PK11SlotInfo *slot,
                    secuPWData *slotPw, secuPWData *p12FilePw)
{
    SEC_PKCS12DecoderContext *p12dcx = NULL;
    p12uContext *p12cxt = NULL;
    SECItem *pwitem = NULL;
    SECItem p12file = { 0 };
    SECStatus rv = SECFailure;
    PRBool swapUnicode = PR_FALSE;
    PRBool forceUnicode = pk12uForceUnicode;
    PRBool trypw;
    int error;

#ifdef IS_LITTLE_ENDIAN
    swapUnicode = PR_TRUE;
#endif

    p12cxt = p12u_InitContext(PR_TRUE, in_file);
    if (!p12cxt) {
        SECU_PrintError(progName, "File Open failed: %s", in_file);
        pk12uErrno = PK12UERR_INIT_FILE;
        return NULL;
    }

    /* get the password */
    pwitem = P12U_GetP12FilePassword(PR_FALSE, p12FilePw);
    if (!pwitem) {
        pk12uErrno = PK12UERR_USER_CANCELLED;
        goto done;
    }

    if (P12U_UnicodeConversion(NULL, uniPwp, pwitem, PR_TRUE,
                               swapUnicode) != SECSuccess) {
        SECU_PrintError(progName, "Unicode conversion failed");
        pk12uErrno = PK12UERR_UNICODECONV;
        goto done;
    }
    rv = SECU_FileToItem(&p12file, p12cxt->file);
    if (rv != SECSuccess) {
        SECU_PrintError(progName, "Failed to read from import file");
        goto done;
    }

    do {
        trypw = PR_FALSE; /* normally we do this once */
        rv = SECFailure;
        /* init the decoder context */
        p12dcx = SEC_PKCS12DecoderStart(uniPwp, slot, slotPw,
                                        NULL, NULL, NULL, NULL, NULL);
        if (!p12dcx) {
            SECU_PrintError(progName, "PKCS12 decoder start failed");
            pk12uErrno = PK12UERR_PK12DECODESTART;
            break;
        }

        /* decode the item */
        rv = SEC_PKCS12DecoderUpdate(p12dcx, p12file.data, p12file.len);

        if (rv != SECSuccess) {
            error = PR_GetError();
            if (error == SEC_ERROR_DECRYPTION_DISALLOWED) {
                PR_SetError(error, 0);
                break;
            }
            SECU_PrintError(progName, "PKCS12 decoding failed");
            pk12uErrno = PK12UERR_DECODE;
        }

        /* does the blob authenticate properly? */
        rv = SEC_PKCS12DecoderVerify(p12dcx);
        if (rv != SECSuccess) {
            if (uniPwp->len == 2) {
                /* this is a null PW, try once more with a zero-length PW
                   instead of a null string */
                SEC_PKCS12DecoderFinish(p12dcx);
                uniPwp->len = 0;
                trypw = PR_TRUE;
            } else if (forceUnicode == pk12uForceUnicode) {
                /* try again with a different password encoding */
                forceUnicode = !pk12uForceUnicode;
                rv = NSS_OptionSet(__NSS_PKCS12_DECODE_FORCE_UNICODE,
                                   forceUnicode);
                if (rv != SECSuccess) {
                    SECU_PrintError(progName, "PKCS12 decoding failed to set option");
                    pk12uErrno = PK12UERR_DECODEVERIFY;
                    break;
                }
                SEC_PKCS12DecoderFinish(p12dcx);
                trypw = PR_TRUE;
            } else {
                SECU_PrintError(progName, "PKCS12 decode not verified");
                pk12uErrno = PK12UERR_DECODEVERIFY;
                break;
            }
        }
    } while (trypw == PR_TRUE);

    /* revert the option setting */
    if (forceUnicode != pk12uForceUnicode) {
        rv = NSS_OptionSet(__NSS_PKCS12_DECODE_FORCE_UNICODE, pk12uForceUnicode);
        if (rv != SECSuccess) {
            SECU_PrintError(progName, "PKCS12 decoding failed to set option");
            pk12uErrno = PK12UERR_DECODEVERIFY;
        }
    }
    /* rv has been set at this point */

done:
    if (rv != SECSuccess) {
        if (p12dcx != NULL) {
            SEC_PKCS12DecoderFinish(p12dcx);
            p12dcx = NULL;
        }
        if (uniPwp->data) {
            SECITEM_ZfreeItem(uniPwp, PR_FALSE);
            uniPwp->data = NULL;
        }
    }
    PR_Close(p12cxt->file);
    p12cxt->file = NULL;
    /* PK11_FreeSlot(slot); */
    p12u_DestroyContext(&p12cxt, PR_FALSE);

    if (pwitem) {
        SECITEM_ZfreeItem(pwitem, PR_TRUE);
    }
    SECITEM_ZfreeItem(&p12file, PR_FALSE);
    return p12dcx;
}

/*
 * given a filename for pkcs12 file, imports certs and keys
 *
 * Change: altitude
 *  I've changed this function so that it takes the keydb and pkcs12 file
 *  passwords from files.  The "pwdKeyDB" and "pwdP12File"
 *  variables have been added for this purpose.
 */
PRIntn
P12U_ImportPKCS12Object(char *in_file, PK11SlotInfo *slot,
                        secuPWData *slotPw, secuPWData *p12FilePw)
{
    SEC_PKCS12DecoderContext *p12dcx = NULL;
    SECItem uniPwitem = { 0 };
    PRBool forceUnicode = pk12uForceUnicode;
    PRBool trypw;
    SECStatus rv = SECFailure;

    rv = P12U_InitSlot(slot, slotPw);
    if (rv != SECSuccess) {
        SECU_PrintError(progName, "Failed to authenticate to \"%s\"",
                        PK11_GetSlotName(slot));
        pk12uErrno = PK12UERR_PK11GETSLOT;
        return rv;
    }

    do {
        trypw = PR_FALSE; /* normally we do this once */
        rv = SECFailure;
        p12dcx = p12U_ReadPKCS12File(&uniPwitem, in_file, slot, slotPw, p12FilePw);

        if (p12dcx == NULL) {
            goto loser;
        }

        /* make sure the bags are okey dokey -- nicknames correct, etc. */
        rv = SEC_PKCS12DecoderValidateBags(p12dcx, P12U_NicknameCollisionCallback);
        if (rv != SECSuccess) {
            if (PORT_GetError() == SEC_ERROR_PKCS12_DUPLICATE_DATA) {
                pk12uErrno = PK12UERR_CERTALREADYEXISTS;
            } else {
                pk12uErrno = PK12UERR_DECODEVALIBAGS;
            }
            SECU_PrintError(progName, "PKCS12 decode validate bags failed");
            goto loser;
        }

        /* stuff 'em in */
        if (forceUnicode != pk12uForceUnicode) {
            rv = NSS_OptionSet(__NSS_PKCS12_DECODE_FORCE_UNICODE,
                               forceUnicode);
            if (rv != SECSuccess) {
                SECU_PrintError(progName, "PKCS12 decode set option failed");
                pk12uErrno = PK12UERR_DECODEIMPTBAGS;
                goto loser;
            }
        }
        rv = SEC_PKCS12DecoderImportBags(p12dcx);
        if (rv != SECSuccess) {
            if (PR_GetError() == SEC_ERROR_PKCS12_UNABLE_TO_IMPORT_KEY &&
                forceUnicode == pk12uForceUnicode) {
                /* try again with a different password encoding */
                forceUnicode = !pk12uForceUnicode;
                SEC_PKCS12DecoderFinish(p12dcx);
                SECITEM_ZfreeItem(&uniPwitem, PR_FALSE);
                trypw = PR_TRUE;
            } else {
                SECU_PrintError(progName, "PKCS12 decode import bags failed");
                pk12uErrno = PK12UERR_DECODEIMPTBAGS;
                goto loser;
            }
        }
    } while (trypw);

    /* revert the option setting */
    if (forceUnicode != pk12uForceUnicode) {
        rv = NSS_OptionSet(__NSS_PKCS12_DECODE_FORCE_UNICODE, pk12uForceUnicode);
        if (rv != SECSuccess) {
            SECU_PrintError(progName, "PKCS12 decode set option failed");
            pk12uErrno = PK12UERR_DECODEIMPTBAGS;
            goto loser;
        }
    }

    fprintf(stdout, "%s: PKCS12 IMPORT SUCCESSFUL\n", progName);
    rv = SECSuccess;

loser:
    if (p12dcx) {
        SEC_PKCS12DecoderFinish(p12dcx);
    }

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

    return rv;
}

static void
p12u_DoPKCS12ExportErrors()
{
    PRErrorCode error_value;

    error_value = PORT_GetError();
    if ((error_value == SEC_ERROR_PKCS12_UNABLE_TO_EXPORT_KEY) ||
        (error_value == SEC_ERROR_PKCS12_UNABLE_TO_LOCATE_OBJECT_BY_NAME) ||
        (error_value == SEC_ERROR_PKCS12_UNABLE_TO_WRITE)) {
        fputs(SECU_Strerror(error_value), stderr);
    } else if (error_value == SEC_ERROR_USER_CANCELLED) {
        ;
    } else {
        fputs(SECU_Strerror(SEC_ERROR_EXPORTING_CERTIFICATES), stderr);
    }
}

static void
p12u_WriteToExportFile(void *arg, const char *buf, unsigned long len)
{
    p12uContext *p12cxt = arg;
    int writeLen;

    if (!p12cxt || (p12cxt->error == PR_TRUE)) {
        return;
    }

    if (p12cxt->file == NULL) {
        p12cxt->errorValue = SEC_ERROR_PKCS12_UNABLE_TO_WRITE;
        p12cxt->error = PR_TRUE;
        return;
    }

    writeLen = PR_Write(p12cxt->file, (unsigned char *)buf, (PRInt32)len);

    if (writeLen != (int)len) {
        PR_Close(p12cxt->file);
        PL_strfree(p12cxt->filename);
        p12cxt->filename = NULL;
        p12cxt->file = NULL;
        p12cxt->errorValue = SEC_ERROR_PKCS12_UNABLE_TO_WRITE;
        p12cxt->error = PR_TRUE;
    }
}

void
P12U_ExportPKCS12Object(char *nn, char *outfile, PK11SlotInfo *inSlot,
                        SECOidTag cipher, SECOidTag certCipher, SECOidTag hash,
                        secuPWData *slotPw, secuPWData *p12FilePw)
{
    SEC_PKCS12ExportContext *p12ecx = NULL;
    SEC_PKCS12SafeInfo *keySafe = NULL, *certSafe = NULL;
    SECItem *pwitem = NULL;
    p12uContext *p12cxt = NULL;
    CERTCertList *certlist = NULL;
    CERTCertListNode *node = NULL;
    PK11SlotInfo *slot = NULL;

    if (P12U_InitSlot(inSlot, slotPw) != SECSuccess) {
        SECU_PrintError(progName, "Failed to authenticate to \"%s\"",
                        PK11_GetSlotName(inSlot));
        pk12uErrno = PK12UERR_PK11GETSLOT;
        goto loser;
    }
    certlist = PK11_FindCertsFromNickname(nn, slotPw);
    if (!certlist) {
        PORT_SetError(SEC_ERROR_UNKNOWN_CERT);
        SECU_PrintError(progName, "find user certs from nickname failed");
        pk12uErrno = PK12UERR_FINDCERTBYNN;
        return;
    }

    if ((SECSuccess != CERT_FilterCertListForUserCerts(certlist)) ||
        CERT_LIST_EMPTY(certlist)) {
        PR_fprintf(PR_STDERR, "%s: no user certs from given nickname\n",
                   progName);
        pk12uErrno = PK12UERR_FINDCERTBYNN;
        goto loser;
    }

    /*	Password to use for PKCS12 file.  */
    pwitem = P12U_GetP12FilePassword(PR_TRUE, p12FilePw);
    if (!pwitem) {
        goto loser;
    }

    /* we are passing UTF8, drop the NULL in the normal password value.
     * UCS2 conversion will add it back if necessary. This only affects
     * password > Blocksize of the Hash function and pkcs5v2 pbe (if password
     * <=Blocksize then the password is zero padded anyway, so an extra NULL
     * at the end has not effect). This is allows us to work with openssl and
     * gnutls. Older versions of NSS already fail to decrypt long passwords
     * in this case, so we aren't breaking anyone with this code */
    if ((pwitem->len > 0) && (!pwitem->data[pwitem->len - 1])) {
        pwitem->len--;
    }

    p12cxt = p12u_InitContext(PR_FALSE, outfile);
    if (!p12cxt) {
        SECU_PrintError(progName, "Initialization failed: %s", outfile);
        pk12uErrno = PK12UERR_INIT_FILE;
        goto loser;
    }

    if (certlist) {
        CERTCertificate *cert = CERT_LIST_HEAD(certlist)->cert;
        if (cert) {
            slot = cert->slot; /* use the slot from the first matching
                certificate to create the context . This is for keygen */
        }
    }
    if (!slot) {
        SECU_PrintError(progName, "cert does not have a slot");
        pk12uErrno = PK12UERR_FINDCERTBYNN;
        goto loser;
    }
    p12ecx = SEC_PKCS12CreateExportContext(NULL, NULL, slot, slotPw);
    if (!p12ecx) {
        SECU_PrintError(progName, "export context creation failed");
        pk12uErrno = PK12UERR_EXPORTCXCREATE;
        goto loser;
    }

    if (SEC_PKCS12AddPasswordIntegrity(p12ecx, pwitem, hash) !=
        SECSuccess) {
        SECU_PrintError(progName, "PKCS12 add password integrity failed");
        pk12uErrno = PK12UERR_PK12ADDPWDINTEG;
        goto loser;
    }

    for (node = CERT_LIST_HEAD(certlist);
         !CERT_LIST_END(node, certlist);
         node = CERT_LIST_NEXT(node)) {
        CERTCertificate *cert = node->cert;
        if (!cert->slot) {
            SECU_PrintError(progName, "cert does not have a slot");
            pk12uErrno = PK12UERR_FINDCERTBYNN;
            goto loser;
        }

        keySafe = SEC_PKCS12CreateUnencryptedSafe(p12ecx);
        if (certCipher == SEC_OID_UNKNOWN) {
            certSafe = keySafe;
        } else {
            certSafe =
                SEC_PKCS12CreatePasswordPrivSafe(p12ecx, pwitem, certCipher);
        }

        if (!certSafe || !keySafe) {
            SECU_PrintError(progName, "key or cert safe creation failed");
            pk12uErrno = PK12UERR_CERTKEYSAFE;
            goto loser;
        }

        if (SEC_PKCS12AddCertAndKey(p12ecx, certSafe, NULL, cert,
                                    CERT_GetDefaultCertDB(), keySafe, NULL,
                                    PR_TRUE, pwitem, cipher) != SECSuccess) {
            SECU_PrintError(progName, "add cert and key failed");
            pk12uErrno = PK12UERR_ADDCERTKEY;
            goto loser;
        }
    }

    CERT_DestroyCertList(certlist);
    certlist = NULL;

    if (SEC_PKCS12Encode(p12ecx, p12u_WriteToExportFile, p12cxt) !=
        SECSuccess) {
        SECU_PrintError(progName, "PKCS12 encode failed");
        pk12uErrno = PK12UERR_ENCODE;
        goto loser;
    }

    p12u_DestroyContext(&p12cxt, PR_FALSE);
    SECITEM_ZfreeItem(pwitem, PR_TRUE);
    fprintf(stdout, "%s: PKCS12 EXPORT SUCCESSFUL\n", progName);
    SEC_PKCS12DestroyExportContext(p12ecx);

    return;

loser:
    SEC_PKCS12DestroyExportContext(p12ecx);

    if (certlist) {
        CERT_DestroyCertList(certlist);
        certlist = NULL;
    }

    p12u_DestroyContext(&p12cxt, PR_TRUE);
    if (pwitem) {
        SECITEM_ZfreeItem(pwitem, PR_TRUE);
    }
    p12u_DoPKCS12ExportErrors();
    return;
}

PRIntn
P12U_ListPKCS12File(char *in_file, PK11SlotInfo *slot,
                    secuPWData *slotPw, secuPWData *p12FilePw)
{
    SEC_PKCS12DecoderContext *p12dcx = NULL;
    SECItem uniPwitem = { 0 };
    SECStatus rv = SECFailure;
    const SEC_PKCS12DecoderItem *dip;

    p12dcx = p12U_ReadPKCS12File(&uniPwitem, in_file, slot, slotPw, p12FilePw);
    /* did the blob authenticate properly? */
    if (p12dcx == NULL) {
        SECU_PrintError(progName, "PKCS12 decode not verified");
        pk12uErrno = PK12UERR_DECODEVERIFY;
        goto loser;
    }
    rv = SEC_PKCS12DecoderIterateInit(p12dcx);
    if (rv != SECSuccess) {
        SECU_PrintError(progName, "PKCS12 decode iterate bags failed");
        pk12uErrno = PK12UERR_DECODEIMPTBAGS;
        rv = SECFailure;
    } else {
        int fileCounter = 0;
        while (SEC_PKCS12DecoderIterateNext(p12dcx, &dip) == SECSuccess) {
            switch (dip->type) {
                case SEC_OID_PKCS12_V1_CERT_BAG_ID:
                    printf("Certificate");
                    if (dumpRawFile) {
                        PRFileDesc *fd;
                        char fileName[20];
                        snprintf(fileName, sizeof(fileName), "file%04d.der", ++fileCounter);
                        fd = PR_Open(fileName,
                                     PR_CREATE_FILE | PR_RDWR | PR_TRUNCATE,
                                     0600);
                        if (!fd) {
                            SECU_PrintError(progName,
                                            "Cannot create output file");
                        } else {
                            PR_Write(fd, dip->der->data, dip->der->len);
                            PR_Close(fd);
                        }
                    } else if (SECU_PrintSignedData(stdout, dip->der,
                                                    (dip->hasKey) ? "(has private key)"
                                                                  : "",
                                                    0, (SECU_PPFunc)SECU_PrintCertificate) !=
                               0) {
                        SECU_PrintError(progName, "PKCS12 print cert bag failed");
                    }
                    if (dip->friendlyName != NULL) {
                        printf("    Friendly Name: %s\n\n",
                               dip->friendlyName->data);
                    }
                    if (dip->shroudAlg) {
                        SECU_PrintAlgorithmID(stdout, dip->shroudAlg,
                                              "Encryption algorithm", 1);
                    }
                    break;
                case SEC_OID_PKCS12_V1_KEY_BAG_ID:
                case SEC_OID_PKCS12_V1_PKCS8_SHROUDED_KEY_BAG_ID:
                    printf("Key");
                    if (dip->type == SEC_OID_PKCS12_V1_PKCS8_SHROUDED_KEY_BAG_ID)
                        printf("(shrouded)");
                    printf(":\n");
                    if (dip->friendlyName != NULL) {
                        printf("    Friendly Name: %s\n\n",
                               dip->friendlyName->data);
                    }
                    if (dip->shroudAlg) {
                        SECU_PrintAlgorithmID(stdout, dip->shroudAlg,
                                              "Encryption algorithm", 1);
                    }
                    break;
                default:
                    printf("unknown bag type(%d): %s\n\n", dip->type,
                           SECOID_FindOIDTagDescription(dip->type));
                    break;
            }
        }
        rv = SECSuccess;
    }

loser:

    if (p12dcx) {
        SEC_PKCS12DecoderFinish(p12dcx);
    }

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

    return rv;
}

SECOidTag
PKCS12U_FindTagFromString(char *cipherString)
{
    SECOidTag tag;
    SECOidData *oid;

    /* future enhancement: accept dotted oid spec? */

    for (tag = 1; (oid = SECOID_FindOIDByTag(tag)) != NULL; tag++) {
        /* only interested in oids that we actually understand */
        if (oid->mechanism == CKM_INVALID_MECHANISM) {
            continue;
        }
        if (PORT_Strcasecmp(oid->desc, cipherString) != 0) {
            continue;
        }
        return tag;
    }
    return SEC_OID_UNKNOWN;
}

/*
 * use the oid table description to map a user input string to a particular
 * oid.
 */
SECOidTag
PKCS12U_MapCipherFromString(char *cipherString, int keyLen)
{
    SECOidTag tag;
    SECOidTag cipher;

    /* future enhancement: provide 'friendlier' typed in names for
     * pbe mechanisms.
     */

    /* look for the oid tag by Description */
    tag = PKCS12U_FindTagFromString(cipherString);
    if (tag == SEC_OID_UNKNOWN) {
        return tag;
    }

    cipher = SEC_OID_UNKNOWN;
    /* we found a match... get the PBE version of this
     * cipher... */
    if (!SEC_PKCS5IsAlgorithmPBEAlgTag(tag)) {
        cipher = SEC_PKCS5GetPBEAlgorithm(tag, keyLen);
        /* no eqivalent PKCS5/PKCS12 cipher, use the raw
         * encryption tag we got and pass it directly in,
         * pkcs12 will use the pkcsv5 mechanism */
        if (cipher == SEC_OID_PKCS5_PBES2) {
            cipher = tag;
        } else if (cipher == SEC_OID_PKCS5_PBMAC1) {
            /* make sure we have not macing ciphers here */
            cipher = SEC_OID_UNKNOWN;
        }
    } else {
        cipher = tag;
    }
    return cipher;
}

SECOidTag
PKCS12U_MapHashFromString(char *hashString)
{
    SECOidTag hashAlg;

    /* look for the oid tag by Description */
    hashAlg = PKCS12U_FindTagFromString(hashString);
    if (hashAlg == SEC_OID_UNKNOWN) {
        return hashAlg;
    }
    /* make sure it's a hashing oid */
    if (HASH_GetHashTypeByOidTag(hashAlg) == HASH_AlgNULL) {
        return SEC_OID_UNKNOWN;
    }
    return hashAlg;
}

static void
p12u_EnableAllCiphers()
{
    SEC_PKCS12EnableCipher(PKCS12_RC4_40, 1);
    SEC_PKCS12EnableCipher(PKCS12_RC4_128, 1);
    SEC_PKCS12EnableCipher(PKCS12_RC2_CBC_40, 1);
    SEC_PKCS12EnableCipher(PKCS12_RC2_CBC_128, 1);
    SEC_PKCS12EnableCipher(PKCS12_DES_56, 1);
    SEC_PKCS12EnableCipher(PKCS12_DES_EDE3_168, 1);
    SEC_PKCS12EnableCipher(PKCS12_AES_CBC_128, 1);
    SEC_PKCS12EnableCipher(PKCS12_AES_CBC_192, 1);
    SEC_PKCS12EnableCipher(PKCS12_AES_CBC_256, 1);
    SEC_PKCS12SetPreferredCipher(PKCS12_AES_CBC_256, 1);
}

static PRUintn
P12U_Init(char *dir, char *dbprefix, PRBool listonly)
{
    SECStatus rv;
    PK11_SetPasswordFunc(SECU_GetModulePassword);

    PR_Init(PR_SYSTEM_THREAD, PR_PRIORITY_NORMAL, 1);
    if (listonly && NSS_NoDB_Init("") == SECSuccess) {
        rv = SECSuccess;
    } else {
        rv = NSS_Initialize(dir, dbprefix, dbprefix, "secmod.db", 0);
    }
    if (rv != SECSuccess) {
        SECU_PrintPRandOSError(progName);
        exit(-1);
    }

    /* setup unicode callback functions */
    PORT_SetUCS2_ASCIIConversionFunction(p12u_ucs2_ascii_conversion_function);
    /* use the defaults for UCS4-UTF8 and UCS2-UTF8 */

    p12u_EnableAllCiphers();

    return 0;
}

enum {
    opt_CertDir = 0,
    opt_TokenName,
    opt_Import,
    opt_SlotPWFile,
    opt_SlotPW,
    opt_List,
    opt_Nickname,
    opt_Export,
    opt_Raw,
    opt_P12FilePWFile,
    opt_P12FilePW,
    opt_DBPrefix,
    opt_Debug,
    opt_Cipher,
    opt_CertCipher,
    opt_KeyLength,
    opt_CertKeyLength,
    opt_Mac
};

static secuCommandFlag pk12util_options[] = {
    { /* opt_CertDir	       */ 'd', PR_TRUE, 0, PR_FALSE },
    { /* opt_TokenName	       */ 'h', PR_TRUE, 0, PR_FALSE },
    { /* opt_Import	       */ 'i', PR_TRUE, 0, PR_FALSE },
    { /* opt_SlotPWFile	       */ 'k', PR_TRUE, 0, PR_FALSE },
    { /* opt_SlotPW	       */ 'K', PR_TRUE, 0, PR_FALSE },
    { /* opt_List              */ 'l', PR_TRUE, 0, PR_FALSE },
    { /* opt_Nickname	       */ 'n', PR_TRUE, 0, PR_FALSE },
    { /* opt_Export	       */ 'o', PR_TRUE, 0, PR_FALSE },
    { /* opt_Raw   	       */ 'r', PR_FALSE, 0, PR_FALSE },
    { /* opt_P12FilePWFile     */ 'w', PR_TRUE, 0, PR_FALSE },
    { /* opt_P12FilePW	       */ 'W', PR_TRUE, 0, PR_FALSE },
    { /* opt_DBPrefix	       */ 'P', PR_TRUE, 0, PR_FALSE },
    { /* opt_Debug	       */ 'v', PR_FALSE, 0, PR_FALSE },
    { /* opt_Cipher	       */ 'c', PR_TRUE, 0, PR_FALSE },
    { /* opt_CertCipher	       */ 'C', PR_TRUE, 0, PR_FALSE },
    { /* opt_KeyLength         */ 'm', PR_TRUE, 0, PR_FALSE, "key_len" },
    { /* opt_CertKeyLength     */ 0, PR_TRUE, 0, PR_FALSE, "cert_key_len" },
    { /* opt_Mac               */ 'M', PR_TRUE, 0, PR_FALSE, PR_FALSE }
};

int
main(int argc, char **argv)
{
    secuPWData slotPw = { PW_NONE, NULL };
    secuPWData p12FilePw = { PW_NONE, NULL };
    PK11SlotInfo *slot;
    char *slotname = NULL;
    char *import_file = NULL;
    char *export_file = NULL;
    char *dbprefix = "";
    SECStatus rv;
    SECOidTag cipher = SEC_OID_AES_256_CBC;
    SECOidTag hash = SEC_OID_SHA256;
    SECOidTag certCipher = SEC_OID_AES_128_CBC;
    int keyLen = 0;
    int certKeyLen = 0;
    secuCommand pk12util;
    PRInt32 forceUnicode;

#ifdef _CRTDBG_MAP_ALLOC
    _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
#endif

    pk12util.numCommands = 0;
    pk12util.commands = 0;
    pk12util.numOptions = sizeof(pk12util_options) / sizeof(secuCommandFlag);
    pk12util.options = pk12util_options;

    progName = strrchr(argv[0], '/');
    progName = progName ? progName + 1 : argv[0];

    rv = SECU_ParseCommandLine(argc, argv, progName, &pk12util);

    if (rv != SECSuccess)
        Usage();

    pk12_debugging = pk12util.options[opt_Debug].activated;

    if ((pk12util.options[opt_Import].activated +
         pk12util.options[opt_Export].activated +
         pk12util.options[opt_List].activated) != 1) {
        Usage();
    }

    if (pk12util.options[opt_Export].activated &&
        !pk12util.options[opt_Nickname].activated) {
        Usage();
    }

    rv = NSS_OptionGet(__NSS_PKCS12_DECODE_FORCE_UNICODE, &forceUnicode);
    if (rv != SECSuccess) {
        SECU_PrintError(progName,
                        "Failed to get NSS_PKCS12_DECODE_FORCE_UNICODE option");
        Usage();
    }
    pk12uForceUnicode = forceUnicode;

    slotname = SECU_GetOptionArg(&pk12util, opt_TokenName);

    import_file = (pk12util.options[opt_List].activated) ? SECU_GetOptionArg(&pk12util, opt_List)
                                                         : SECU_GetOptionArg(&pk12util, opt_Import);
    export_file = SECU_GetOptionArg(&pk12util, opt_Export);

    if (pk12util.options[opt_P12FilePWFile].activated) {
        p12FilePw.source = PW_FROMFILE;
        p12FilePw.data = PORT_Strdup(pk12util.options[opt_P12FilePWFile].arg);
    }

    if (pk12util.options[opt_P12FilePW].activated) {
        p12FilePw.source = PW_PLAINTEXT;
        p12FilePw.data = PORT_Strdup(pk12util.options[opt_P12FilePW].arg);
    }

    if (pk12util.options[opt_SlotPWFile].activated) {
        slotPw.source = PW_FROMFILE;
        slotPw.data = PORT_Strdup(pk12util.options[opt_SlotPWFile].arg);
    }

    if (pk12util.options[opt_SlotPW].activated) {
        slotPw.source = PW_PLAINTEXT;
        slotPw.data = PORT_Strdup(pk12util.options[opt_SlotPW].arg);
    }

    if (pk12util.options[opt_CertDir].activated) {
        SECU_ConfigDirectory(pk12util.options[opt_CertDir].arg);
    }
    if (pk12util.options[opt_DBPrefix].activated) {
        dbprefix = pk12util.options[opt_DBPrefix].arg;
    }
    if (pk12util.options[opt_Raw].activated) {
        dumpRawFile = PR_TRUE;
    }
    if (pk12util.options[opt_KeyLength].activated) {
        keyLen = atoi(pk12util.options[opt_KeyLength].arg);
    }
    if (pk12util.options[opt_CertKeyLength].activated) {
        certKeyLen = atoi(pk12util.options[opt_CertKeyLength].arg);
    }

    P12U_Init(SECU_ConfigDirectory(NULL), dbprefix,
              pk12util.options[opt_List].activated);

    if (!slotname || PL_strcmp(slotname, "internal") == 0)
        slot = PK11_GetInternalKeySlot();
    else
        slot = PK11_FindSlotByName(slotname);

    if (!slot) {
        SECU_PrintError(progName, "Invalid slot \"%s\"", slotname);
        pk12uErrno = PK12UERR_PK11GETSLOT;
        goto done;
    }

    if (pk12util.options[opt_Cipher].activated) {
        char *cipherString = pk12util.options[opt_Cipher].arg;

        cipher = PKCS12U_MapCipherFromString(cipherString, keyLen);
        /* We only want encryption PBE's. make sure we don't have
	 * any MAC pbes */
        if (cipher == SEC_OID_UNKNOWN) {
            PORT_SetError(SEC_ERROR_INVALID_ALGORITHM);
            SECU_PrintError(progName, "Algorithm: \"%s\"", cipherString);
            pk12uErrno = PK12UERR_INVALIDALGORITHM;
            goto done;
        }
    }

    if (pk12util.options[opt_CertCipher].activated) {
        char *cipherString = pk12util.options[opt_CertCipher].arg;

        if (PORT_Strcasecmp(cipherString, "none") == 0) {
            certCipher = SEC_OID_UNKNOWN;
        } else {
            certCipher = PKCS12U_MapCipherFromString(cipherString, certKeyLen);
            /* If the user requested a cipher and we didn't find it, then
	     * don't just silently not encrypt. */
            if (certCipher == SEC_OID_UNKNOWN) {
                PORT_SetError(SEC_ERROR_INVALID_ALGORITHM);
                SECU_PrintError(progName, "Algorithm: \"%s\"", cipherString);
                pk12uErrno = PK12UERR_INVALIDALGORITHM;
                goto done;
            }
        }
    }
    if (pk12util.options[opt_Mac].activated) {
        char *hashString = pk12util.options[opt_Mac].arg;

        hash = PKCS12U_MapHashFromString(hashString);
        /* We don't support creating Mac-less pkcs 12 files */
        if (hash == SEC_OID_UNKNOWN) {
            PORT_SetError(SEC_ERROR_INVALID_ALGORITHM);
            SECU_PrintError(progName, "Algorithm: \"%s\"", hashString);
            pk12uErrno = PK12UERR_INVALIDALGORITHM;
            goto done;
        }
    }

    if (pk12util.options[opt_Import].activated) {
        P12U_ImportPKCS12Object(import_file, slot, &slotPw, &p12FilePw);

    } else if (pk12util.options[opt_Export].activated) {
        P12U_ExportPKCS12Object(pk12util.options[opt_Nickname].arg,
                                export_file, slot, cipher, certCipher,
                                hash, &slotPw, &p12FilePw);

    } else if (pk12util.options[opt_List].activated) {
        P12U_ListPKCS12File(import_file, slot, &slotPw, &p12FilePw);

    } else {
        Usage();
        pk12uErrno = PK12UERR_USAGE;
    }

done:
    if (import_file != NULL)
        PORT_ZFree(import_file, PL_strlen(import_file));
    if (export_file != NULL)
        PORT_ZFree(export_file, PL_strlen(export_file));
    if (slotPw.data != NULL)
        PORT_ZFree(slotPw.data, PL_strlen(slotPw.data));
    if (p12FilePw.data != NULL)
        PORT_ZFree(p12FilePw.data, PL_strlen(p12FilePw.data));
    if (slot)
        PK11_FreeSlot(slot);
    if (NSS_Shutdown() != SECSuccess) {
        pk12uErrno = 1;
    }
    PL_ArenaFinish();
    PR_Cleanup();
    return pk12uErrno;
}
