/* ***** 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):
 *   Dr Vipul Gupta <vipul.gupta@sun.com>, Sun Microsystems Laboratories
 *
 * 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 ***** */

/*
** certutil.c
**
** utility for managing certificates and the cert database
**
*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#if defined(WIN32)
#include "fcntl.h"
#include "io.h"
#endif

#include "secutil.h"

#if defined(XP_UNIX)
#include <unistd.h>
#endif

#include "nspr.h"
#include "prtypes.h"
#include "prtime.h"
#include "prlong.h"

#include "pk11func.h"
#include "secasn1.h"
#include "cert.h"
#include "cryptohi.h"
#include "secoid.h"
#include "certdb.h"
#include "nss.h"
#include "certutil.h"

#define MIN_KEY_BITS		512
/* MAX_KEY_BITS should agree with MAX_RSA_MODULUS in freebl */
#define MAX_KEY_BITS		8192
#define DEFAULT_KEY_BITS	1024

#define GEN_BREAK(e) rv=e; break;

char *progName;

static CERTCertificateRequest *
GetCertRequest(PRFileDesc *inFile, PRBool ascii)
{
    CERTCertificateRequest *certReq = NULL;
    CERTSignedData signedData;
    PRArenaPool *arena = NULL;
    SECItem reqDER;
    SECStatus rv;

    reqDER.data = NULL;
    do {
	arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
	if (arena == NULL) {
	    GEN_BREAK (SECFailure);
	}
	
 	rv = SECU_ReadDERFromFile(&reqDER, inFile, ascii);
	if (rv) {
	    break;
	}
        certReq = (CERTCertificateRequest*) PORT_ArenaZAlloc
		  (arena, sizeof(CERTCertificateRequest));
        if (!certReq) { 
	    GEN_BREAK(SECFailure);
	}
	certReq->arena = arena;

	/* Since cert request is a signed data, must decode to get the inner
	   data
	 */
	PORT_Memset(&signedData, 0, sizeof(signedData));
	rv = SEC_ASN1DecodeItem(arena, &signedData, 
		SEC_ASN1_GET(CERT_SignedDataTemplate), &reqDER);
	if (rv) {
	    break;
	}
	rv = SEC_ASN1DecodeItem(arena, certReq, 
		SEC_ASN1_GET(CERT_CertificateRequestTemplate), &signedData.data);
	if (rv) {
	    break;
	}
   	rv = CERT_VerifySignedDataWithPublicKeyInfo(&signedData, 
		&certReq->subjectPublicKeyInfo, NULL /* wincx */);
   } while (0);

   if (reqDER.data) {
   	SECITEM_FreeItem(&reqDER, PR_FALSE);
   }

   if (rv) {
   	SECU_PrintError(progName, "bad certificate request\n");
   	if (arena) {
   	    PORT_FreeArena(arena, PR_FALSE);
   	}
   	certReq = NULL;
   }

   return certReq;
}

static SECStatus
AddCert(PK11SlotInfo *slot, CERTCertDBHandle *handle, char *name, char *trusts, 
        PRFileDesc *inFile, PRBool ascii, PRBool emailcert, void *pwdata)
{
    CERTCertTrust *trust = NULL;
    CERTCertificate *cert = NULL;
    SECItem certDER;
    SECStatus rv;

    certDER.data = NULL;
    do {
	/* Read in the entire file specified with the -i argument */
	rv = SECU_ReadDERFromFile(&certDER, inFile, ascii);
	if (rv != SECSuccess) {
	    SECU_PrintError(progName, "unable to read input file");
	    break;
	}

	/* Read in an ASCII cert and return a CERTCertificate */
	cert = CERT_DecodeCertFromPackage((char *)certDER.data, certDER.len);
	if (!cert) {
	    SECU_PrintError(progName, "could not obtain certificate from file"); 
	    GEN_BREAK(SECFailure);
	}

	/* Create a cert trust */
	trust = (CERTCertTrust *)PORT_ZAlloc(sizeof(CERTCertTrust));
	if (!trust) {
	    SECU_PrintError(progName, "unable to allocate cert trust");
	    GEN_BREAK(SECFailure);
	}

	rv = CERT_DecodeTrustString(trust, trusts);
	if (rv) {
	    SECU_PrintError(progName, "unable to decode trust string");
	    GEN_BREAK(SECFailure);
	}

	rv =  PK11_ImportCert(slot, cert, CK_INVALID_HANDLE, name, PR_FALSE);
	if (rv != SECSuccess) {
	    /* sigh, PK11_Import Cert and CERT_ChangeCertTrust should have 
	     * been coded to take a password arg. */
	    if (PORT_GetError() == SEC_ERROR_TOKEN_NOT_LOGGED_IN) {
		rv = PK11_Authenticate(slot, PR_TRUE, pwdata);
		if (rv != SECSuccess) {
                    SECU_PrintError(progName, 
				"could not authenticate to token %s.",
				PK11_GetTokenName(slot));
		    GEN_BREAK(SECFailure);
		}
		rv = PK11_ImportCert(slot, cert, CK_INVALID_HANDLE, 
				     name, PR_FALSE);
	    }
	    if (rv != SECSuccess) {
		SECU_PrintError(progName, 
			"could not add certificate to token or database");
		GEN_BREAK(SECFailure);
	    }
	}

	rv = CERT_ChangeCertTrust(handle, cert, trust);
	if (rv != SECSuccess) {
	    if (PORT_GetError() == SEC_ERROR_TOKEN_NOT_LOGGED_IN) {
		rv = PK11_Authenticate(slot, PR_TRUE, pwdata);
		if (rv != SECSuccess) {
                    SECU_PrintError(progName, 
				"could not authenticate to token %s.",
				PK11_GetTokenName(slot));
		    GEN_BREAK(SECFailure);
		}
		rv = CERT_ChangeCertTrust(handle, cert, trust);
	    }
	    if (rv != SECSuccess) {
		SECU_PrintError(progName, 
			"could not change trust on certificate");
		GEN_BREAK(SECFailure);
	    }
	}

	if ( emailcert ) {
	    CERT_SaveSMimeProfile(cert, NULL, pwdata);
	}

    } while (0);

    CERT_DestroyCertificate (cert);
    PORT_Free(trust);
    PORT_Free(certDER.data);

    return rv;
}

static SECStatus
CertReq(SECKEYPrivateKey *privk, SECKEYPublicKey *pubk, KeyType keyType,
        SECOidTag hashAlgTag, CERTName *subject, char *phone, int ascii, 
	const char *emailAddrs, const char *dnsNames,
        certutilExtnList extnList,
        PRFileDesc *outFile)
{
    CERTSubjectPublicKeyInfo *spki;
    CERTCertificateRequest *cr;
    SECItem *encoding;
    SECOidTag signAlgTag;
    SECItem result;
    SECStatus rv;
    PRArenaPool *arena;
    PRInt32 numBytes;
    void *extHandle;

    /* Create info about public key */
    spki = SECKEY_CreateSubjectPublicKeyInfo(pubk);
    if (!spki) {
	SECU_PrintError(progName, "unable to create subject public key");
	return SECFailure;
    }
    
    /* Generate certificate request */
    cr = CERT_CreateCertificateRequest(subject, spki, NULL);
    SECKEY_DestroySubjectPublicKeyInfo(spki);
    if (!cr) {
	SECU_PrintError(progName, "unable to make certificate request");
	return SECFailure;
    }

    arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
    if ( !arena ) {
	SECU_PrintError(progName, "out of memory");
	return SECFailure;
    }
    
    extHandle = CERT_StartCertificateRequestAttributes(cr);
    if (extHandle == NULL) {
        PORT_FreeArena (arena, PR_FALSE);
	return SECFailure;
    }
    if (AddExtensions(extHandle, emailAddrs, dnsNames, extnList)
                  != SECSuccess) {
        PORT_FreeArena (arena, PR_FALSE);
        return SECFailure;
    }
    CERT_FinishExtensions(extHandle);
    CERT_FinishCertificateRequestAttributes(cr);

    /* Der encode the request */
    encoding = SEC_ASN1EncodeItem(arena, NULL, cr,
                                  SEC_ASN1_GET(CERT_CertificateRequestTemplate));
    if (encoding == NULL) {
	SECU_PrintError(progName, "der encoding of request failed");
	return SECFailure;
    }

    /* Sign the request */
    signAlgTag = SEC_GetSignatureAlgorithmOidTag(keyType, hashAlgTag);
    if (signAlgTag == SEC_OID_UNKNOWN) {
	SECU_PrintError(progName, "unknown Key or Hash type");
	return SECFailure;
    }
    rv = SEC_DerSignData(arena, &result, encoding->data, encoding->len, 
			 privk, signAlgTag);
    if (rv) {
	SECU_PrintError(progName, "signing of data failed");
	return SECFailure;
    }

    /* Encode request in specified format */
    if (ascii) {
	char *obuf;
	char *name, *email, *org, *state, *country;
	SECItem *it;
	int total;

	it = &result;

	obuf = BTOA_ConvertItemToAscii(it);
	total = PL_strlen(obuf);

	name = CERT_GetCommonName(subject);
	if (!name) {
	    name = strdup("(not specified)");
	}

	if (!phone)
	    phone = strdup("(not specified)");

	email = CERT_GetCertEmailAddress(subject);
	if (!email)
	    email = strdup("(not specified)");

	org = CERT_GetOrgName(subject);
	if (!org)
	    org = strdup("(not specified)");

	state = CERT_GetStateName(subject);
	if (!state)
	    state = strdup("(not specified)");

	country = CERT_GetCountryName(subject);
	if (!country)
	    country = strdup("(not specified)");

	PR_fprintf(outFile, 
	           "\nCertificate request generated by Netscape certutil\n");
	PR_fprintf(outFile, "Phone: %s\n\n", phone);
	PR_fprintf(outFile, "Common Name: %s\n", name);
	PR_fprintf(outFile, "Email: %s\n", email);
	PR_fprintf(outFile, "Organization: %s\n", org);
	PR_fprintf(outFile, "State: %s\n", state);
	PR_fprintf(outFile, "Country: %s\n\n", country);

	PR_fprintf(outFile, "%s\n", NS_CERTREQ_HEADER);
	numBytes = PR_Write(outFile, obuf, total);
	if (numBytes != total) {
	    SECU_PrintSystemError(progName, "write error");
	    return SECFailure;
	}
	PR_fprintf(outFile, "\n%s\n", NS_CERTREQ_TRAILER);
    } else {
	numBytes = PR_Write(outFile, result.data, result.len);
	if (numBytes != (int)result.len) {
	    SECU_PrintSystemError(progName, "write error");
	    return SECFailure;
	}
    }
    return SECSuccess;
}

static SECStatus 
ChangeTrustAttributes(CERTCertDBHandle *handle, PK11SlotInfo *slot,
			char *name, char *trusts, void *pwdata)
{
    SECStatus rv;
    CERTCertificate *cert;
    CERTCertTrust *trust;
    
    cert = CERT_FindCertByNicknameOrEmailAddr(handle, name);
    if (!cert) {
	SECU_PrintError(progName, "could not find certificate named \"%s\"",
			name);
	return SECFailure;
    }

    trust = (CERTCertTrust *)PORT_ZAlloc(sizeof(CERTCertTrust));
    if (!trust) {
	SECU_PrintError(progName, "unable to allocate cert trust");
	return SECFailure;
    }

    /* This function only decodes these characters: pPwcTCu, */
    rv = CERT_DecodeTrustString(trust, trusts);
    if (rv) {
	SECU_PrintError(progName, "unable to decode trust string");
	return SECFailure;
    }

    /* CERT_ChangeCertTrust API does not have a way to pass in
     * a context, so NSS can't prompt for the password if it needs to.
     * check to see if the failure was token not logged in and 
     * log in if need be. */
    rv = CERT_ChangeCertTrust(handle, cert, trust);
    if (rv != SECSuccess) {
	if (PORT_GetError() == SEC_ERROR_TOKEN_NOT_LOGGED_IN) {
	    rv = PK11_Authenticate(slot, PR_TRUE, pwdata);
	    if (rv != SECSuccess) {
		SECU_PrintError(progName, "could not authenticate to token %s.",
                                PK11_GetTokenName(slot));
		return SECFailure;
	    }
	    rv = CERT_ChangeCertTrust(handle, cert, trust);
	}
	if (rv != SECSuccess) {
	    SECU_PrintError(progName, "unable to modify trust attributes");
	    return SECFailure;
	}
    }
    CERT_DestroyCertificate(cert);

    return SECSuccess;
}

static SECStatus
DumpChain(CERTCertDBHandle *handle, char *name)
{
    CERTCertificate *the_cert;
    CERTCertificateList *chain;
    int i, j;
    the_cert = PK11_FindCertFromNickname(name, NULL);
    if (!the_cert) {
	SECU_PrintError(progName, "Could not find: %s\n", name);
	return SECFailure;
    }
    chain = CERT_CertChainFromCert(the_cert, 0, PR_TRUE);
    CERT_DestroyCertificate(the_cert);
    if (!chain) {
	SECU_PrintError(progName, "Could not obtain chain for: %s\n", name);
	return SECFailure;
    }
    for (i=chain->len-1; i>=0; i--) {
	CERTCertificate *c;
	c = CERT_FindCertByDERCert(handle, &chain->certs[i]);
	for (j=i; j<chain->len-1; j++) printf("  ");
	printf("\"%s\" [%s]\n\n", c->nickname, c->subjectName);
	CERT_DestroyCertificate(c);
    }
    CERT_DestroyCertificateList(chain);
    return SECSuccess;
}

static SECStatus
listCerts(CERTCertDBHandle *handle, char *name, PK11SlotInfo *slot,
          PRBool raw, PRBool ascii, PRFileDesc *outfile, void *pwarg)
{
    SECItem data;
    PRInt32 numBytes;
    SECStatus rv = SECFailure;
    CERTCertList *certs;
    CERTCertListNode *node;

    /* List certs on a non-internal slot. */
    if (!PK11_IsFriendly(slot) && PK11_NeedLogin(slot)) {
        SECStatus newrv = PK11_Authenticate(slot, PR_TRUE, pwarg);
        if (newrv != SECSuccess) {
            SECU_PrintError(progName, "could not authenticate to token %s.",
                            PK11_GetTokenName(slot));
            return SECFailure;
        }
    }
    if (name) {
	CERTCertificate *the_cert;
	the_cert = CERT_FindCertByNicknameOrEmailAddr(handle, name);
	if (!the_cert) {
	    the_cert = PK11_FindCertFromNickname(name, NULL);
	    if (!the_cert) {
		SECU_PrintError(progName, "Could not find: %s\n", name);
		return SECFailure;
	    }
	}
	/* Here, we have one cert with the desired nickname or email 
	 * address.  Now, we will attempt to get a list of ALL certs 
	 * with the same subject name as the cert we have.  That list 
	 * should contain, at a minimum, the one cert we have already found.
	 * If the list of certs is empty (NULL), the libraries have failed.
	 */
	certs = CERT_CreateSubjectCertList(NULL, handle, &the_cert->derSubject,
		PR_Now(), PR_FALSE);
	CERT_DestroyCertificate(the_cert);
	if (!certs) {
	    PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
	    SECU_PrintError(progName, "problem printing certificates");
	    return SECFailure;
	}
	for (node = CERT_LIST_HEAD(certs); !CERT_LIST_END(node,certs);
						node = CERT_LIST_NEXT(node)) {
	    the_cert = node->cert;
	    /* now get the subjectList that matches this cert */
	    data.data = the_cert->derCert.data;
	    data.len = the_cert->derCert.len;
	    if (ascii) {
		PR_fprintf(outfile, "%s\n%s\n%s\n", NS_CERT_HEADER, 
		        BTOA_DataToAscii(data.data, data.len), NS_CERT_TRAILER);
		rv = SECSuccess;
	    } else if (raw) {
		numBytes = PR_Write(outfile, data.data, data.len);
		if (numBytes != (PRInt32) data.len) {
		   SECU_PrintSystemError(progName, "error writing raw cert");
		    rv = SECFailure;
		}
		rv = SECSuccess;
	    } else {
		rv = SEC_PrintCertificateAndTrust(the_cert, "Certificate",
                                                  the_cert->trust);
		if (rv != SECSuccess) {
		    SECU_PrintError(progName, "problem printing certificate");
		}

	    }
	    if (rv != SECSuccess) {
		break;
	    }
	}
    } else {

	certs = PK11_ListCertsInSlot(slot);
	if (certs) {
	    for (node = CERT_LIST_HEAD(certs); !CERT_LIST_END(node,certs);
						node = CERT_LIST_NEXT(node)) {
		SECU_PrintCertNickname(node,stdout);
	    }
	    rv = SECSuccess;
	}
    }
    if (certs) {
        CERT_DestroyCertList(certs);
    }
    if (rv) {
	SECU_PrintError(progName, "problem printing certificate nicknames");
	return SECFailure;
    }

    return SECSuccess;	/* not rv ?? */
}

static SECStatus
ListCerts(CERTCertDBHandle *handle, char *nickname, PK11SlotInfo *slot,
          PRBool raw, PRBool ascii, PRFileDesc *outfile, secuPWData *pwdata)
{
    SECStatus rv;

    if (!ascii && !raw && !nickname) {
        PR_fprintf(outfile, "\n%-60s %-5s\n%-60s %-5s\n\n",
                   "Certificate Nickname", "Trust Attributes", "",
                   "SSL,S/MIME,JAR/XPI");
    }
    if (slot == NULL) {
	CERTCertList *list;
	CERTCertListNode *node;

	list = PK11_ListCerts(PK11CertListAll, pwdata);
	for (node = CERT_LIST_HEAD(list); !CERT_LIST_END(node, list);
	     node = CERT_LIST_NEXT(node)) 
	{
	    SECU_PrintCertNickname(node, stdout);
	}
	CERT_DestroyCertList(list);
	return SECSuccess;
    } else {
	rv = listCerts(handle,nickname,slot,raw,ascii,outfile,pwdata);
    }
    return rv;
}

static SECStatus 
DeleteCert(CERTCertDBHandle *handle, char *name)
{
    SECStatus rv;
    CERTCertificate *cert;

    cert = CERT_FindCertByNicknameOrEmailAddr(handle, name);
    if (!cert) {
	SECU_PrintError(progName, "could not find certificate named \"%s\"",
			name);
	return SECFailure;
    }

    rv = SEC_DeletePermCertificate(cert);
    CERT_DestroyCertificate(cert);
    if (rv) {
	SECU_PrintError(progName, "unable to delete certificate");
	return SECFailure;
    }

    return SECSuccess;
}

static SECStatus
ValidateCert(CERTCertDBHandle *handle, char *name, char *date,
	     char *certUsage, PRBool checkSig, PRBool logit, secuPWData *pwdata)
{
    SECStatus rv;
    CERTCertificate *cert = NULL;
    int64 timeBoundary;
    SECCertificateUsage usage;
    CERTVerifyLog reallog;
    CERTVerifyLog *log = NULL;

    if (!certUsage) {
	    PORT_SetError (SEC_ERROR_INVALID_ARGS);
	    return (SECFailure);
    }
    
    switch (*certUsage) {
	case 'O':
	    usage = certificateUsageStatusResponder;
	    break;
	case 'C':
	    usage = certificateUsageSSLClient;
	    break;
	case 'V':
	    usage = certificateUsageSSLServer;
	    break;
	case 'S':
	    usage = certificateUsageEmailSigner;
	    break;
	case 'R':
	    usage = certificateUsageEmailRecipient;
	    break;
	case 'J':
	    usage = certificateUsageObjectSigner;
	    break;
	default:
	    PORT_SetError (SEC_ERROR_INVALID_ARGS);
	    return (SECFailure);
    }
    do {
	cert = CERT_FindCertByNicknameOrEmailAddr(handle, name);
	if (!cert) {
	    SECU_PrintError(progName, "could not find certificate named \"%s\"",
			    name);
	    GEN_BREAK (SECFailure)
	}

	if (date != NULL) {
	    rv = DER_AsciiToTime(&timeBoundary, date);
	    if (rv) {
		SECU_PrintError(progName, "invalid input date");
		GEN_BREAK (SECFailure)
	    }
	} else {
	    timeBoundary = PR_Now();
	}

	if ( logit ) {
	    log = &reallog;
	    
	    log->count = 0;
	    log->head = NULL;
	    log->tail = NULL;
	    log->arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
	    if ( log->arena == NULL ) {
		SECU_PrintError(progName, "out of memory");
		GEN_BREAK (SECFailure)
	    }
	}
 
	rv = CERT_VerifyCertificate(handle, cert, checkSig, usage,
			     timeBoundary, pwdata, log, &usage);
	if ( log ) {
	    if ( log->head == NULL ) {
		fprintf(stdout, "%s: certificate is valid\n", progName);
		GEN_BREAK (SECSuccess)
	    } else {
		char *name;
		CERTVerifyLogNode *node;
		
		node = log->head;
		while ( node ) {
		    if ( node->cert->nickname != NULL ) {
			name = node->cert->nickname;
		    } else {
			name = node->cert->subjectName;
		    }
		    fprintf(stderr, "%s : %s\n", name, 
		    	SECU_Strerror(node->error));
		    CERT_DestroyCertificate(node->cert);
		    node = node->next;
		}
	    }
	} else {
	    if (rv != SECSuccess) {
		PRErrorCode perr = PORT_GetError();
		fprintf(stdout, "%s: certificate is invalid: %s\n",
			progName, SECU_Strerror(perr));
		GEN_BREAK (SECFailure)
	    }
	    fprintf(stdout, "%s: certificate is valid\n", progName);
	    GEN_BREAK (SECSuccess)
	}
    } while (0);

    if (cert) {
        CERT_DestroyCertificate(cert);
    }

    return (rv);
}

static PRBool
ItemIsPrintableASCII(const SECItem * item)
{
    unsigned char *src = item->data;
    unsigned int   len = item->len;
    while (len-- > 0) {
        unsigned char uc = *src++;
	if (uc < 0x20 || uc > 0x7e) 
	    return PR_FALSE;
    }
    return PR_TRUE;
}

/* Caller ensures that dst is at least item->len*2+1 bytes long */
static void
SECItemToHex(const SECItem * item, char * dst)
{
    if (dst && item && item->data) {
	unsigned char * src = item->data;
	unsigned int    len = item->len;
	for (; len > 0; --len, dst += 2) {
	    sprintf(dst, "%02x", *src++);
	}
	*dst = '\0';
    }
}

static const char * const keyTypeName[] = {
  "null", "rsa", "dsa", "fortezza", "dh", "kea", "ec" };

#define MAX_CKA_ID_BIN_LEN 20
#define MAX_CKA_ID_STR_LEN 40

/* print key number, key ID (in hex or ASCII), key label (nickname) */
static SECStatus
PrintKey(PRFileDesc *out, const char *nickName, int count, 
         SECKEYPrivateKey *key, void *pwarg)
{
    SECItem * ckaID;
    char ckaIDbuf[MAX_CKA_ID_STR_LEN + 4];

    pwarg = NULL;
    ckaID = PK11_GetLowLevelKeyIDForPrivateKey(key);
    if (!ckaID) {
	strcpy(ckaIDbuf, "(no CKA_ID)");
    } else if (ItemIsPrintableASCII(ckaID)) {
	int len = PR_MIN(MAX_CKA_ID_STR_LEN, ckaID->len);
	ckaIDbuf[0] = '"';
	memcpy(ckaIDbuf + 1, ckaID->data, len);
	ckaIDbuf[1 + len] = '"';
	ckaIDbuf[2 + len] = '\0';
    } else {
    	/* print ckaid in hex */
	SECItem idItem = *ckaID;
	if (idItem.len > MAX_CKA_ID_BIN_LEN)
	    idItem.len = MAX_CKA_ID_BIN_LEN;
        SECItemToHex(&idItem, ckaIDbuf);
    }

    PR_fprintf(out, "<%2d> %-8.8s %-42.42s %s\n", count, 
               keyTypeName[key->keyType], ckaIDbuf, nickName);
    SECITEM_ZfreeItem(ckaID, PR_TRUE);

    return SECSuccess;
}

/* returns SECSuccess if ANY keys are found, SECFailure otherwise. */
static SECStatus
ListKeysInSlot(PK11SlotInfo *slot, const char *nickName, KeyType keyType, 
               void *pwarg)
{
    SECKEYPrivateKeyList *list;
    SECKEYPrivateKeyListNode *node;
    int count = 0;

    if (PK11_NeedLogin(slot)) {
        SECStatus rv = PK11_Authenticate(slot, PR_TRUE, pwarg);
        if (rv != SECSuccess) {
            SECU_PrintError(progName, "could not authenticate to token %s.",
                            PK11_GetTokenName(slot));
            return SECFailure;
        }
    }

    if (nickName && nickName[0]) 
	list = PK11_ListPrivKeysInSlot(slot, (char *)nickName, pwarg);
    else
	list = PK11_ListPrivateKeysInSlot(slot);
    if (list == NULL) {
	SECU_PrintError(progName, "problem listing keys");
	return SECFailure;
    }
    for (node=PRIVKEY_LIST_HEAD(list); 
             !PRIVKEY_LIST_END(node,list);
	 node=PRIVKEY_LIST_NEXT(node)) {
	char * keyName;
	static const char orphan[] = { "(orphan)" };

	if (keyType != nullKey && keyType != node->key->keyType)
	    continue;
        keyName = PK11_GetPrivateKeyNickname(node->key);
	if (!keyName || !keyName[0]) {
	    /* Try extra hard to find nicknames for keys that lack them. */
	    CERTCertificate * cert;
	    PORT_Free((void *)keyName);
	    keyName = NULL;
	    cert = PK11_GetCertFromPrivateKey(node->key);
	    if (cert) {
		if (cert->nickname && cert->nickname[0]) {
		    keyName = PORT_Strdup(cert->nickname);
		} else if (cert->emailAddr && cert->emailAddr[0]) {
		    keyName = PORT_Strdup(cert->emailAddr);
		}
		CERT_DestroyCertificate(cert);
	    }
	}
	if (nickName) {
	    if (!keyName || PL_strcmp(keyName,nickName)) {
		/* PKCS#11 module returned unwanted keys */
	        PORT_Free((void *)keyName);
		continue;
	    }
	}
	if (!keyName)
	    keyName = (char *)orphan;

	PrintKey(PR_STDOUT, keyName, count, node->key, pwarg);

	if (keyName != (char *)orphan)
	    PORT_Free((void *)keyName);
	count++;
    }
    SECKEY_DestroyPrivateKeyList(list);

    if (count == 0) {
	PR_fprintf(PR_STDOUT, "%s: no keys found\n", progName);
	return SECFailure;
    }
    return SECSuccess;
}

/* returns SECSuccess if ANY keys are found, SECFailure otherwise. */
static SECStatus
ListKeys(PK11SlotInfo *slot, const char *nickName, int index, 
         KeyType keyType, PRBool dopriv, secuPWData *pwdata)
{
    SECStatus rv = SECFailure;
    static const char fmt[] = \
    	"%s: Checking token \"%.33s\" in slot \"%.65s\"\n";

    if (slot == NULL) {
	PK11SlotList *list;
	PK11SlotListElement *le;

	list= PK11_GetAllTokens(CKM_INVALID_MECHANISM,PR_FALSE,PR_FALSE,pwdata);
	if (list) {
	    for (le = list->head; le; le = le->next) {
		PR_fprintf(PR_STDOUT, fmt, progName, 
			   PK11_GetTokenName(le->slot), 
			   PK11_GetSlotName(le->slot));
		rv &= ListKeysInSlot(le->slot,nickName,keyType,pwdata);
	    }
	    PK11_FreeSlotList(list);
	}
    } else {
	PR_fprintf(PR_STDOUT, fmt, progName, PK11_GetTokenName(slot), 
	           PK11_GetSlotName(slot));
	rv = ListKeysInSlot(slot,nickName,keyType,pwdata);
    }
    return rv;
}

static SECStatus
DeleteKey(char *nickname, secuPWData *pwdata)
{
    SECStatus rv;
    CERTCertificate *cert;
    PK11SlotInfo *slot;

    slot = PK11_GetInternalKeySlot();
    if (PK11_NeedLogin(slot)) {
        SECStatus rv = PK11_Authenticate(slot, PR_TRUE, pwdata);
        if (rv != SECSuccess) {
            SECU_PrintError(progName, "could not authenticate to token %s.",
                            PK11_GetTokenName(slot));
            return SECFailure;
        }
    }
    cert = PK11_FindCertFromNickname(nickname, pwdata);
    if (!cert) {
	PK11_FreeSlot(slot);
	return SECFailure;
    }
    rv = PK11_DeleteTokenCertAndKey(cert, pwdata);
    if (rv != SECSuccess) {
	SECU_PrintError("problem deleting private key \"%s\"\n", nickname);
    }
    CERT_DestroyCertificate(cert);
    PK11_FreeSlot(slot);
    return rv;
}


/*
 *  L i s t M o d u l e s
 *
 *  Print a list of the PKCS11 modules that are
 *  available. This is useful for smartcard people to
 *  make sure they have the drivers loaded.
 *
 */
static SECStatus
ListModules(void)
{
    PK11SlotList *list;
    PK11SlotListElement *le;

    /* get them all! */
    list = PK11_GetAllTokens(CKM_INVALID_MECHANISM,PR_FALSE,PR_FALSE,NULL);
    if (list == NULL) return SECFailure;

    /* look at each slot*/
    for (le = list->head ; le; le = le->next) {
      printf ("\n");
      printf ("    slot: %s\n", PK11_GetSlotName(le->slot));
      printf ("   token: %s\n", PK11_GetTokenName(le->slot));
    }
    PK11_FreeSlotList(list);

    return SECSuccess;
}

static void 
Usage(char *progName)
{
#define FPS fprintf(stderr, 
    FPS "Type %s -H for more detailed descriptions\n", progName);
    FPS "Usage:  %s -N [-d certdir] [-P dbprefix] [-f pwfile]\n", progName);
    FPS "Usage:  %s -T [-d certdir] [-P dbprefix] [-h token-name]\n"
	"\t\t [-f pwfile] [-0 SSO-password]\n", progName);
    FPS "\t%s -A -n cert-name -t trustargs [-d certdir] [-P dbprefix] [-a] [-i input]\n", 
    	progName);
    FPS "\t%s -B -i batch-file\n", progName);
    FPS "\t%s -C [-c issuer-name | -x] -i cert-request-file -o cert-file\n"
	"\t\t [-m serial-number] [-w warp-months] [-v months-valid]\n"
        "\t\t [-f pwfile] [-d certdir] [-P dbprefix]\n"
        "\t\t [-1 | --keyUsage [keyUsageKeyword,..]] [-2] [-3] [-4]\n"
        "\t\t [-5 | --nsCertType [nsCertTypeKeyword,...]]\n"
        "\t\t [-6 | --extKeyUsage [extKeyUsageKeyword,...]] [-7 emailAddrs]\n"
        "\t\t [-8 dns-names] [-a]\n",
	progName);
    FPS "\t%s -D -n cert-name [-d certdir] [-P dbprefix]\n", progName);
    FPS "\t%s -E -n cert-name -t trustargs [-d certdir] [-P dbprefix] [-a] [-i input]\n", 
	progName);
    FPS "\t%s -G -n key-name [-h token-name] [-k rsa] [-g key-size] [-y exp]\n" 
	"\t\t [-f pwfile] [-z noisefile] [-d certdir] [-P dbprefix]\n", progName);
    FPS "\t%s -G [-h token-name] -k dsa [-q pqgfile -g key-size] [-f pwfile]\n"
	"\t\t [-z noisefile] [-d certdir] [-P dbprefix]\n", progName);
#ifdef NSS_ENABLE_ECC
    FPS "\t%s -G [-h token-name] -k ec -q curve [-f pwfile]\n"
	"\t\t [-z noisefile] [-d certdir] [-P dbprefix]\n", progName);
    FPS "\t%s -K [-n key-name] [-h token-name] [-k dsa|ec|rsa|all]\n", 
	progName);
#else
    FPS "\t%s -K [-n key-name] [-h token-name] [-k dsa|rsa|all]\n", 
	progName);
#endif /* NSS_ENABLE_ECC */
    FPS "\t\t [-f pwfile] [-X] [-d certdir] [-P dbprefix]\n");
    FPS "\t%s --upgrade-merge --source-dir upgradeDir --upgrade-id uniqueID\n",
	progName);
    FPS "\t\t [--upgrade-token-name tokenName] [-d targetDBDir]\n");
    FPS "\t\t [-P targetDBPrefix] [--source-prefix upgradeDBPrefix]\n");
    FPS "\t\t [-f targetPWfile] [-@ upgradePWFile]\n");
    FPS "\t%s --merge --source-dir sourceDBDir [-d targetDBdir]\n",
	progName);
    FPS "\t\t [-P targetDBPrefix] [--source-prefix sourceDBPrefix]\n");
    FPS "\t\t [-f targetPWfile] [-@ sourcePWFile]\n");
    FPS "\t%s -L [-n cert-name] [-X] [-d certdir] [-P dbprefix] [-r] [-a]\n", progName);
    FPS "\t%s -M -n cert-name -t trustargs [-d certdir] [-P dbprefix]\n",
	progName);
    FPS "\t%s -O -n cert-name [-X] [-d certdir] [-P dbprefix]\n", progName);
    FPS "\t%s -R -s subj -o cert-request-file [-d certdir] [-P dbprefix] [-p phone] [-a]\n"
	"\t\t [-7 emailAddrs] [-k key-type-or-id] [-h token-name] [-f pwfile] [-g key-size]\n",
	progName);
    FPS "\t%s -V -n cert-name -u usage [-b time] [-e] \n"
	"\t\t[-X] [-d certdir] [-P dbprefix]\n",
	progName);
    FPS "\t%s -S -n cert-name -s subj [-c issuer-name | -x]  -t trustargs\n"
	"\t\t [-k key-type-or-id] [-q key-params] [-h token-name] [-g key-size]\n"
        "\t\t [-m serial-number] [-w warp-months] [-v months-valid]\n"
	"\t\t [-f pwfile] [-d certdir] [-P dbprefix]\n"
        "\t\t [-p phone] [-1] [-2] [-3] [-4] [-5] [-6] [-7 emailAddrs]\n"
        "\t\t [-8 DNS-names]\n"
        "\t\t [--extAIA] [--extSIA] [--extCP] [--extPM] [--extPC] [--extIA]\n"
        "\t\t [--extSKID]\n", progName);
    FPS "\t%s -U [-X] [-d certdir] [-P dbprefix]\n", progName);
    exit(1);
}

static void LongUsage(char *progName)
{

    FPS "%-15s Add a certificate to the database        (create if needed)\n",
	"-A");
    FPS "%-20s\n", "   All options under -E apply");
    FPS "%-15s Run a series of certutil commands from a batch file\n", "-B");
    FPS "%-20s Specify the batch file\n", "   -i batch-file");
    FPS "%-15s Add an Email certificate to the database (create if needed)\n",
	"-E");
    FPS "%-20s Specify the nickname of the certificate to add\n",
	"   -n cert-name");
    FPS "%-20s Set the certificate trust attributes:\n",
	"   -t trustargs");
    FPS "%-25s trustargs is of the form x,y,z where x is for SSL, y is for S/MIME,\n", "");
    FPS "%-25s and z is for code signing\n", "");
    FPS "%-25s p \t valid peer\n", "");
    FPS "%-25s P \t trusted peer (implies p)\n", "");
    FPS "%-25s c \t valid CA\n", "");
    FPS "%-25s T \t trusted CA to issue client certs (implies c)\n", "");
    FPS "%-25s C \t trusted CA to issue server certs (implies c)\n", "");
    FPS "%-25s u \t user cert\n", "");
    FPS "%-25s w \t send warning\n", "");
    FPS "%-25s g \t make step-up cert\n", "");
    FPS "%-20s Specify the password file\n",
	"   -f pwfile");
    FPS "%-20s Cert database directory (default is ~/.netscape)\n",
	"   -d certdir");
    FPS "%-20s Cert & Key database prefix\n",
	"   -P dbprefix");
    FPS "%-20s The input certificate is encoded in ASCII (RFC1113)\n",
	"   -a");
    FPS "%-20s Specify the certificate file (default is stdin)\n",
	"   -i input");
    FPS "\n");

    FPS "%-15s Create a new binary certificate from a BINARY cert request\n",
	"-C");
    FPS "%-20s The nickname of the issuer cert\n",
	"   -c issuer-name");
    FPS "%-20s The BINARY certificate request file\n",
	"   -i cert-request ");
    FPS "%-20s Output binary cert to this file (default is stdout)\n",
	"   -o output-cert");
    FPS "%-20s Self sign\n",
	"   -x");
    FPS "%-20s Cert serial number\n",
	"   -m serial-number");
    FPS "%-20s Time Warp\n",
	"   -w warp-months");
    FPS "%-20s Months valid (default is 3)\n",
        "   -v months-valid");
    FPS "%-20s Specify the password file\n",
	"   -f pwfile");
    FPS "%-20s Cert database directory (default is ~/.netscape)\n",
	"   -d certdir");
    FPS "%-20s Cert & Key database prefix\n",
	"   -P dbprefix");
    FPS "%-20s \n"
              "%-20s Create key usage extension. Possible keywords:\n"
              "%-20s \"digitalSignature\", \"nonRepudiation\", \"keyEncipherment\",\n"
              "%-20s \"dataEncipherment\", \"keyAgreement\", \"certSigning\",\n"
              "%-20s \"crlSigning\", \"critical\"\n",
        "   -1 | --keyUsage keyword,keyword,...", "", "", "", "");
    FPS "%-20s Create basic constraint extension\n",
	"   -2 ");
    FPS "%-20s Create authority key ID extension\n",
	"   -3 ");
    FPS "%-20s Create crl distribution point extension\n",
	"   -4 ");
    FPS "%-20s \n"
              "%-20s Create netscape cert type extension. Possible keywords:\n"
              "%-20s \"sslClient\", \"sslServer\", \"smime\", \"objectSigning\",\n"
              "%-20s \"sslCA\", \"smimeCA\", \"objectSigningCA\", \"critical\".\n",
        "   -5 | -nsCertType keyword,keyword,... ", "", "", "");
    FPS "%-20s \n"
              "%-20s Create extended key usage extension. Possible keywords:\n"
              "%-20s \"serverAuth\", \"clientAuth\",\"codeSigning\",\n"
              "%-20s \"emailProtection\", \"timeStamp\",\"ocspResponder\",\n"
              "%-20s \"stepUp\", \"critical\"\n",
	"   -6 | --extKeyUsage keyword,keyword,...", "", "", "", "");
    FPS "%-20s Create an email subject alt name extension\n",
	"   -7 emailAddrs");
    FPS "%-20s Create an dns subject alt name extension\n",
	"   -8 dnsNames");
    FPS "%-20s The input certificate request is encoded in ASCII (RFC1113)\n",
	"   -a");
    FPS "\n");

    FPS "%-15s Generate a new key pair\n",
	"-G");
    FPS "%-20s Name of token in which to generate key (default is internal)\n",
	"   -h token-name");
#ifdef NSS_ENABLE_ECC
    FPS "%-20s Type of key pair to generate (\"dsa\", \"ec\", \"rsa\" (default))\n",
	"   -k key-type");
    FPS "%-20s Key size in bits, (min %d, max %d, default %d) (not for ec)\n",
	"   -g key-size", MIN_KEY_BITS, MAX_KEY_BITS, DEFAULT_KEY_BITS);
#else
    FPS "%-20s Type of key pair to generate (\"dsa\", \"rsa\" (default))\n",
	"   -k key-type");
    FPS "%-20s Key size in bits, (min %d, max %d, default %d)\n",
	"   -g key-size", MIN_KEY_BITS, MAX_KEY_BITS, DEFAULT_KEY_BITS);
#endif /* NSS_ENABLE_ECC */
    FPS "%-20s Set the public exponent value (3, 17, 65537) (rsa only)\n",
	"   -y exp");
    FPS "%-20s Specify the password file\n",
        "   -f password-file");
    FPS "%-20s Specify the noise file to be used\n",
	"   -z noisefile");
    FPS "%-20s read PQG value from pqgfile (dsa only)\n",
	"   -q pqgfile");
#ifdef NSS_ENABLE_ECC
    FPS "%-20s Elliptic curve name (ec only)\n",
	"   -q curve-name");
    FPS "%-20s One of nistp256, nistp384, nistp521\n", "");
#ifdef NSS_ECC_MORE_THAN_SUITE_B
    FPS "%-20s sect163k1, nistk163, sect163r1, sect163r2,\n", "");
    FPS "%-20s nistb163, sect193r1, sect193r2, sect233k1, nistk233,\n", "");
    FPS "%-20s sect233r1, nistb233, sect239k1, sect283k1, nistk283,\n", "");
    FPS "%-20s sect283r1, nistb283, sect409k1, nistk409, sect409r1,\n", "");
    FPS "%-20s nistb409, sect571k1, nistk571, sect571r1, nistb571,\n", "");
    FPS "%-20s secp160k1, secp160r1, secp160r2, secp192k1, secp192r1,\n", "");
    FPS "%-20s nistp192, secp224k1, secp224r1, nistp224, secp256k1,\n", "");
    FPS "%-20s secp256r1, secp384r1, secp521r1,\n", "");
    FPS "%-20s prime192v1, prime192v2, prime192v3, \n", "");
    FPS "%-20s prime239v1, prime239v2, prime239v3, c2pnb163v1, \n", "");
    FPS "%-20s c2pnb163v2, c2pnb163v3, c2pnb176v1, c2tnb191v1, \n", "");
    FPS "%-20s c2tnb191v2, c2tnb191v3,  \n", "");
    FPS "%-20s c2pnb208w1, c2tnb239v1, c2tnb239v2, c2tnb239v3, \n", "");
    FPS "%-20s c2pnb272w1, c2pnb304w1, \n", "");
    FPS "%-20s c2tnb359w1, c2pnb368w1, c2tnb431r1, secp112r1, \n", "");
    FPS "%-20s secp112r2, secp128r1, secp128r2, sect113r1, sect113r2\n", "");
    FPS "%-20s sect131r1, sect131r2\n", "");
#endif /* NSS_ECC_MORE_THAN_SUITE_B */
#endif
    FPS "%-20s Key database directory (default is ~/.netscape)\n",
	"   -d keydir");
    FPS "%-20s Cert & Key database prefix\n",
	"   -P dbprefix");
    FPS "\n");

    FPS "%-15s Delete a certificate from the database\n",
	"-D");
    FPS "%-20s The nickname of the cert to delete\n",
	"   -n cert-name");
    FPS "%-20s Cert database directory (default is ~/.netscape)\n",
	"   -d certdir");
    FPS "%-20s Cert & Key database prefix\n",
	"   -P dbprefix");
    FPS "\n");

    FPS "%-15s List all modules\n", /*, or print out a single named module\n",*/
        "-U");
    FPS "%-20s Module database directory (default is '~/.netscape')\n",
        "   -d moddir");
    FPS "%-20s Cert & Key database prefix\n",
	"   -P dbprefix");
    FPS "%-20s force the database to open R/W\n",
	"   -X");
    FPS "\n");

    FPS "%-15s List all private keys\n",
        "-K");
          FPS "%-20s Name of token to search (\"all\" for all tokens)\n",
	"   -h token-name ");

    FPS "%-20s Key type (\"all\" (default), \"dsa\","
#ifdef NSS_ENABLE_ECC
                                                    " \"ec\","
#endif
                                                             " \"rsa\")\n",
	"   -k key-type");
    FPS "%-20s The nickname of the key or associated certificate\n",
	"   -n name");
    FPS "%-20s Specify the password file\n",
        "   -f password-file");
    FPS "%-20s Key database directory (default is ~/.netscape)\n",
	"   -d keydir");
    FPS "%-20s Cert & Key database prefix\n",
	"   -P dbprefix");
    FPS "%-20s force the database to open R/W\n",
	"   -X");
    FPS "\n");

    FPS "%-15s List all certs, or print out a single named cert\n",
	"-L");
    FPS "%-20s Pretty print named cert (list all if unspecified)\n",
	"   -n cert-name");
    FPS "%-20s Cert database directory (default is ~/.netscape)\n",
	"   -d certdir");
    FPS "%-20s Cert & Key database prefix\n",
	"   -P dbprefix");
    FPS "%-20s force the database to open R/W\n",
	"   -X");
    FPS "%-20s For single cert, print binary DER encoding\n",
	"   -r");
    FPS "%-20s For single cert, print ASCII encoding (RFC1113)\n",
	"   -a");
    FPS "\n");

    FPS "%-15s Modify trust attributes of certificate\n",
	"-M");
    FPS "%-20s The nickname of the cert to modify\n",
	"   -n cert-name");
    FPS "%-20s Set the certificate trust attributes (see -A above)\n",
	"   -t trustargs");
    FPS "%-20s Cert database directory (default is ~/.netscape)\n",
	"   -d certdir");
    FPS "%-20s Cert & Key database prefix\n",
	"   -P dbprefix");
    FPS "\n");

    FPS "%-15s Create a new certificate database\n",
	"-N");
    FPS "%-20s Cert database directory (default is ~/.netscape)\n",
	"   -d certdir");
    FPS "%-20s Cert & Key database prefix\n",
	"   -P dbprefix");
    FPS "\n");
    FPS "%-15s Reset the Key database or token\n",
	"-T");
    FPS "%-20s Cert database directory (default is ~/.netscape)\n",
	"   -d certdir");
    FPS "%-20s Cert & Key database prefix\n",
	"   -P dbprefix");
    FPS "%-20s Token to reset (default is internal)\n",
	"   -h token-name");
    FPS "%-20s Set token's Site Security Officer password\n",
	"   -0 SSO-password");
    FPS "\n");

    FPS "\n");
    FPS "%-15s Print the chain of a certificate\n",
	"-O");
    FPS "%-20s The nickname of the cert to modify\n",
	"   -n cert-name");
    FPS "%-20s Cert database directory (default is ~/.netscape)\n",
	"   -d certdir");
    FPS "%-20s Cert & Key database prefix\n",
	"   -P dbprefix");
    FPS "%-20s force the database to open R/W\n",
	"   -X");
    FPS "\n");

    FPS "%-15s Generate a certificate request (stdout)\n",
	"-R");
    FPS "%-20s Specify the subject name (using RFC1485)\n",
	"   -s subject");
    FPS "%-20s Output the cert request to this file\n",
	"   -o output-req");
#ifdef NSS_ENABLE_ECC
    FPS "%-20s Type of key pair to generate (\"dsa\", \"ec\", \"rsa\" (default))\n",
#else
    FPS "%-20s Type of key pair to generate (\"dsa\", \"rsa\" (default))\n",
#endif /* NSS_ENABLE_ECC */
	"   -k key-type-or-id");
    FPS "%-20s or nickname of the cert key to use \n",
	"");
    FPS "%-20s Name of token in which to generate key (default is internal)\n",
	"   -h token-name");
    FPS "%-20s Key size in bits, RSA keys only (min %d, max %d, default %d)\n",
	"   -g key-size", MIN_KEY_BITS, MAX_KEY_BITS, DEFAULT_KEY_BITS);
    FPS "%-20s Name of file containing PQG parameters (dsa only)\n",
	"   -q pqgfile");
#ifdef NSS_ENABLE_ECC
    FPS "%-20s Elliptic curve name (ec only)\n",
	"   -q curve-name");
    FPS "%-20s See the \"-G\" option for a full list of supported names.\n",
	"");
#endif /* NSS_ENABLE_ECC */
    FPS "%-20s Specify the password file\n",
	"   -f pwfile");
    FPS "%-20s Key database directory (default is ~/.netscape)\n",
	"   -d keydir");
    FPS "%-20s Cert & Key database prefix\n",
	"   -P dbprefix");
    FPS "%-20s Specify the contact phone number (\"123-456-7890\")\n",
	"   -p phone");
    FPS "%-20s Output the cert request in ASCII (RFC1113); default is binary\n",
	"   -a");
    FPS "%-20s \n",
	"   See -S for available extension options");
    FPS "\n");

    FPS "%-15s Validate a certificate\n",
	"-V");
    FPS "%-20s The nickname of the cert to Validate\n",
	"   -n cert-name");
    FPS "%-20s validity time (\"YYMMDDHHMMSS[+HHMM|-HHMM|Z]\")\n",
	"   -b time");
    FPS "%-20s Check certificate signature \n",
	"   -e ");   
    FPS "%-20s Specify certificate usage:\n", "   -u certusage");
    FPS "%-25s C \t SSL Client\n", "");
    FPS "%-25s V \t SSL Server\n", "");
    FPS "%-25s S \t Email signer\n", "");
    FPS "%-25s R \t Email Recipient\n", "");   
    FPS "%-25s O \t OCSP status responder\n", "");   
    FPS "%-25s J \t Object signer\n", "");   
    FPS "%-20s Cert database directory (default is ~/.netscape)\n",
	"   -d certdir");
    FPS "%-20s Cert & Key database prefix\n",
	"   -P dbprefix");
    FPS "%-20s force the database to open R/W\n",
	"   -X");
    FPS "\n");

    FPS "%-15s Upgrade an old database and merge it into a new one\n",
	"--upgrade-merge");
    FPS "%-20s Cert database directory to merge into (default is ~/.netscape)\n",
	"   -d certdir");
    FPS "%-20s Cert & Key database prefix of the target database\n",
	"   -P dbprefix");
    FPS "%-20s Specify the password file for the target database\n",
	"   -f pwfile");
    FPS "%-20s \n%-20s Cert database directory to upgrade from\n",
	"   --source-dir certdir", "");
    FPS "%-20s \n%-20s Cert & Key database prefix of the upgrade database\n",
	"   --soruce-prefix dbprefix", "");
    FPS "%-20s \n%-20s Unique identifier for the upgrade database\n",
	"   --upgrade-id uniqueID", "");
    FPS "%-20s \n%-20s Name of the token while it is in upgrade state\n",
	"   --upgrade-token-name name", "");
    FPS "%-20s Specify the password file for the upgrade database\n",
	"   -@ pwfile");
    FPS "\n");

    FPS "%-15s Merge source database into the target database\n",
	"--merge");
    FPS "%-20s Cert database directory of target (default is ~/.netscape)\n",
	"   -d certdir");
    FPS "%-20s Cert & Key database prefix of the target database\n",
	"   -P dbprefix");
    FPS "%-20s Specify the password file for the target database\n",
	"   -f pwfile");
    FPS "%-20s \n%-20s Cert database directory of the source database\n",
	"   --source-dir certdir", "");
    FPS "%-20s \n%-20s Cert & Key database prefix of the source database\n",
	"   --source-prefix dbprefix", "");
    FPS "%-20s Specify the password file for the source database\n",
	"   -@ pwfile");
    FPS "\n");

    FPS "%-15s Make a certificate and add to database\n",
        "-S");
    FPS "%-20s Specify the nickname of the cert\n",
        "   -n key-name");
    FPS "%-20s Specify the subject name (using RFC1485)\n",
        "   -s subject");
    FPS "%-20s The nickname of the issuer cert\n",
	"   -c issuer-name");
    FPS "%-20s Set the certificate trust attributes (see -A above)\n",
	"   -t trustargs");
#ifdef NSS_ENABLE_ECC
    FPS "%-20s Type of key pair to generate (\"dsa\", \"ec\", \"rsa\" (default))\n",
#else
    FPS "%-20s Type of key pair to generate (\"dsa\", \"rsa\" (default))\n",
#endif /* NSS_ENABLE_ECC */
	"   -k key-type-or-id");
    FPS "%-20s Name of token in which to generate key (default is internal)\n",
	"   -h token-name");
    FPS "%-20s Key size in bits, RSA keys only (min %d, max %d, default %d)\n",
	"   -g key-size", MIN_KEY_BITS, MAX_KEY_BITS, DEFAULT_KEY_BITS);
    FPS "%-20s Name of file containing PQG parameters (dsa only)\n",
	"   -q pqgfile");
#ifdef NSS_ENABLE_ECC
    FPS "%-20s Elliptic curve name (ec only)\n",
	"   -q curve-name");
    FPS "%-20s See the \"-G\" option for a full list of supported names.\n",
	"");
#endif /* NSS_ENABLE_ECC */
    FPS "%-20s Self sign\n",
	"   -x");
    FPS "%-20s Cert serial number\n",
	"   -m serial-number");
    FPS "%-20s Time Warp\n",
	"   -w warp-months");
    FPS "%-20s Months valid (default is 3)\n",
        "   -v months-valid");
    FPS "%-20s Specify the password file\n",
	"   -f pwfile");
    FPS "%-20s Cert database directory (default is ~/.netscape)\n",
	"   -d certdir");
    FPS "%-20s Cert & Key database prefix\n",
	"   -P dbprefix");
    FPS "%-20s Specify the contact phone number (\"123-456-7890\")\n",
	"   -p phone");
    FPS "%-20s Create key usage extension\n",
	"   -1 ");
    FPS "%-20s Create basic constraint extension\n",
	"   -2 ");
    FPS "%-20s Create authority key ID extension\n",
	"   -3 ");
    FPS "%-20s Create crl distribution point extension\n",
	"   -4 ");
    FPS "%-20s Create netscape cert type extension\n",
	"   -5 ");
    FPS "%-20s Create extended key usage extension\n",
	"   -6 ");
    FPS "%-20s Create an email subject alt name extension\n",
	"   -7 emailAddrs ");
    FPS "%-20s Create a DNS subject alt name extension\n",
	"   -8 DNS-names");
    FPS "%-20s Create an Authority Information Access extension\n",
	"   --extAIA ");
    FPS "%-20s Create a Subject Information Access extension\n",
	"   --extSIA ");
    FPS "%-20s Create a Certificate Policies extension\n",
	"   --extCP ");
    FPS "%-20s Create a Policy Mappings extension\n",
	"   --extPM ");
    FPS "%-20s Create a Policy Constraints extension\n",
	"   --extPC ");
    FPS "%-20s Create an Inhibit Any Policy extension\n",
	"   --extIA ");
    FPS "%-20s Create a subject key ID extension\n",
	"   --extSKID ");
    FPS "\n");

    exit(1);
#undef FPS
}


static CERTCertificate *
MakeV1Cert(	CERTCertDBHandle *	handle, 
		CERTCertificateRequest *req,
	    	char *			issuerNickName, 
		PRBool 			selfsign, 
		unsigned int 		serialNumber,
		int 			warpmonths,
                int                     validityMonths)
{
    CERTCertificate *issuerCert = NULL;
    CERTValidity *validity;
    CERTCertificate *cert = NULL;
    PRExplodedTime printableTime;
    PRTime now, after;

    if ( !selfsign ) {
	issuerCert = CERT_FindCertByNicknameOrEmailAddr(handle, issuerNickName);
	if (!issuerCert) {
	    SECU_PrintError(progName, "could not find certificate named \"%s\"",
			    issuerNickName);
	    return NULL;
	}
    }

    now = PR_Now();
    PR_ExplodeTime (now, PR_GMTParameters, &printableTime);
    if ( warpmonths ) {
	printableTime.tm_month += warpmonths;
	now = PR_ImplodeTime (&printableTime);
	PR_ExplodeTime (now, PR_GMTParameters, &printableTime);
    }
    printableTime.tm_month += validityMonths;
    after = PR_ImplodeTime (&printableTime);

    /* note that the time is now in micro-second unit */
    validity = CERT_CreateValidity (now, after);
    if (validity) {
        cert = CERT_CreateCertificate(serialNumber, 
				      (selfsign ? &req->subject 
				                : &issuerCert->subject), 
	                              validity, req);
    
        CERT_DestroyValidity(validity);
    }
    if ( issuerCert ) {
	CERT_DestroyCertificate (issuerCert);
    }
    
    return(cert);
}

static SECItem *
SignCert(CERTCertDBHandle *handle, CERTCertificate *cert, PRBool selfsign, 
         SECOidTag hashAlgTag,
         SECKEYPrivateKey *privKey, char *issuerNickName, void *pwarg)
{
    SECItem der;
    SECItem *result = NULL;
    SECKEYPrivateKey *caPrivateKey = NULL;    
    SECStatus rv;
    PRArenaPool *arena;
    SECOidTag algID;
    void *dummy;

    if( !selfsign ) {
      CERTCertificate *issuer = PK11_FindCertFromNickname(issuerNickName, pwarg);
      if( (CERTCertificate *)NULL == issuer ) {
        SECU_PrintError(progName, "unable to find issuer with nickname %s", 
	                issuerNickName);
        return (SECItem *)NULL;
      }

      privKey = caPrivateKey = PK11_FindKeyByAnyCert(issuer, pwarg);
      CERT_DestroyCertificate(issuer);
      if (caPrivateKey == NULL) {
	SECU_PrintError(progName, "unable to retrieve key %s", issuerNickName);
	return NULL;
      }
    }
	
    arena = cert->arena;

    algID = SEC_GetSignatureAlgorithmOidTag(privKey->keyType, hashAlgTag);
    if (algID == SEC_OID_UNKNOWN) {
	fprintf(stderr, "Unknown key or hash type for issuer.");
	goto done;
    }

    rv = SECOID_SetAlgorithmID(arena, &cert->signature, algID, 0);
    if (rv != SECSuccess) {
	fprintf(stderr, "Could not set signature algorithm id.");
	goto done;
    }

    /* we only deal with cert v3 here */
    *(cert->version.data) = 2;
    cert->version.len = 1;

    der.len = 0;
    der.data = NULL;
    dummy = SEC_ASN1EncodeItem (arena, &der, cert,
			 	SEC_ASN1_GET(CERT_CertificateTemplate));
    if (!dummy) {
	fprintf (stderr, "Could not encode certificate.\n");
	goto done;
    }

    result = (SECItem *) PORT_ArenaZAlloc (arena, sizeof (SECItem));
    if (result == NULL) {
	fprintf (stderr, "Could not allocate item for certificate data.\n");
	goto done;
    }

    rv = SEC_DerSignData(arena, result, der.data, der.len, privKey, algID);
    if (rv != SECSuccess) {
	fprintf (stderr, "Could not sign encoded certificate data.\n");
	/* result allocated out of the arena, it will be freed
	 * when the arena is freed */
	result = NULL;
	goto done;
    }
    cert->derCert = *result;
done:
    if (caPrivateKey) {
	SECKEY_DestroyPrivateKey(caPrivateKey);
    }
    return result;
}

static SECStatus
CreateCert(
	CERTCertDBHandle *handle, 
	PK11SlotInfo *slot,
	char *  issuerNickName, 
	PRFileDesc *inFile,
	PRFileDesc *outFile, 
	SECKEYPrivateKey **selfsignprivkey,
	void 	*pwarg,
	SECOidTag hashAlgTag,
	unsigned int serialNumber, 
	int     warpmonths,
	int     validityMonths,
	const char *emailAddrs,
	const char *dnsNames,
	PRBool  ascii,
	PRBool  selfsign,
	certutilExtnList extnList)
{
    void *	extHandle;
    SECItem *	certDER;
    CERTCertificate *subjectCert 	= NULL;
    CERTCertificateRequest *certReq	= NULL;
    SECStatus 	rv 			= SECSuccess;
    SECItem 	reqDER;
    CERTCertExtension **CRexts;

    reqDER.data = NULL;
    do {
	/* Create a certrequest object from the input cert request der */
	certReq = GetCertRequest(inFile, ascii);
	if (certReq == NULL) {
	    GEN_BREAK (SECFailure)
	}

	subjectCert = MakeV1Cert (handle, certReq, issuerNickName, selfsign,
				  serialNumber, warpmonths, validityMonths);
	if (subjectCert == NULL) {
	    GEN_BREAK (SECFailure)
	}
        
        
	extHandle = CERT_StartCertExtensions (subjectCert);
	if (extHandle == NULL) {
	    GEN_BREAK (SECFailure)
	}
        
        rv = AddExtensions(extHandle, emailAddrs, dnsNames, extnList);
        if (rv != SECSuccess) {
	    GEN_BREAK (SECFailure)
	}
        
        if (certReq->attributes != NULL &&
	    certReq->attributes[0] != NULL &&
	    certReq->attributes[0]->attrType.data != NULL &&
	    certReq->attributes[0]->attrType.len   > 0    &&
            SECOID_FindOIDTag(&certReq->attributes[0]->attrType)
                == SEC_OID_PKCS9_EXTENSION_REQUEST) {
            rv = CERT_GetCertificateRequestExtensions(certReq, &CRexts);
            if (rv != SECSuccess)
                break;
            rv = CERT_MergeExtensions(extHandle, CRexts);
            if (rv != SECSuccess)
                break;
        }

	CERT_FinishExtensions(extHandle);

	/* self-signing a cert request, find the private key */
	if (selfsign && *selfsignprivkey == NULL) {
	    *selfsignprivkey = PK11_FindKeyByDERCert(slot, subjectCert, pwarg);
	    if (!*selfsignprivkey) {
		fprintf(stderr, "Failed to locate private key.\n");
		rv = SECFailure;
		break;
	    }
	}

	certDER = SignCert(handle, subjectCert, selfsign, hashAlgTag,
	                   *selfsignprivkey, issuerNickName,pwarg);

	if (certDER) {
	   if (ascii) {
		PR_fprintf(outFile, "%s\n%s\n%s\n", NS_CERT_HEADER, 
		           BTOA_DataToAscii(certDER->data, certDER->len), 
			   NS_CERT_TRAILER);
	   } else {
		PR_Write(outFile, certDER->data, certDER->len);
	   }
	}

    } while (0);
    CERT_DestroyCertificateRequest (certReq);
    CERT_DestroyCertificate (subjectCert);
    if (rv != SECSuccess) {
	PRErrorCode  perr = PR_GetError();
        fprintf(stderr, "%s: unable to create cert (%s)\n", progName,
               SECU_Strerror(perr));
    }
    return (rv);
}


/*
 * map a class to a user presentable string
 */
static const char *objClassArray[] = {
   "Data",
   "Certificate",
   "Public Key",
   "Private Key",
   "Secret Key",
   "Hardware Feature",
   "Domain Parameters",
   "Mechanism"
};

static const char *objNSSClassArray[] = {
   "CKO_NSS",
   "Crl",
   "SMIME Record",
   "Trust",
   "Builtin Root List"
};


const char *
getObjectClass(CK_ULONG classType)
{
    static char buf[sizeof(CK_ULONG)*2+3];

    if (classType <= CKO_MECHANISM) {
	return objClassArray[classType];
    }
    if (classType >= CKO_NSS && classType <= CKO_NSS_BUILTIN_ROOT_LIST) {
	return objNSSClassArray[classType - CKO_NSS];
    }
    sprintf(buf, "0x%lx", classType);
    return buf;
}

char *mkNickname(unsigned char *data, int len)
{
   char *nick = PORT_Alloc(len+1);
   if (!nick) {
	return nick;
   }
   PORT_Memcpy(nick, data, len);
   nick[len] = 0;
   return nick;
}

/*
 * dump a PK11_MergeTokens error log to the console
 */
void
DumpMergeLog(const char *progname, PK11MergeLog *log)
{
    PK11MergeLogNode *node;

    for (node = log->head; node; node = node->next) {
	SECItem  attrItem;
	char *nickname = NULL;
	const char *objectClass = NULL;
	SECStatus rv;

	attrItem.data = NULL;
	rv = PK11_ReadRawAttribute(PK11_TypeGeneric, node->object, 
				   CKA_LABEL, &attrItem);
	if (rv == SECSuccess) {
	    nickname = mkNickname(attrItem.data, attrItem.len);
	    PORT_Free(attrItem.data);
	}
	attrItem.data = NULL;
	rv = PK11_ReadRawAttribute(PK11_TypeGeneric, node->object, 
				   CKA_CLASS, &attrItem);
	if (rv == SECSuccess) {
	     if (attrItem.len == sizeof(CK_ULONG)) {
		objectClass = getObjectClass(*(CK_ULONG *)attrItem.data);
	     }
	     PORT_Free(attrItem.data);
	}

	fprintf(stderr, "%s: Could not merge object %s (type %s): %s\n",
		progName,
		nickname ? nickname : "unnamed",
		objectClass ? objectClass : "unknown",
		SECU_Strerror(node->error));

	if (nickname) {
	    PORT_Free(nickname);
	}
    }
}

/*  Certutil commands  */
enum {
    cmd_AddCert = 0,
    cmd_CreateNewCert,
    cmd_DeleteCert,
    cmd_AddEmailCert,
    cmd_DeleteKey,
    cmd_GenKeyPair,
    cmd_PrintHelp,
    cmd_ListKeys,
    cmd_ListCerts,
    cmd_ModifyCertTrust,
    cmd_NewDBs,
    cmd_DumpChain,
    cmd_CertReq,
    cmd_CreateAndAddCert,
    cmd_TokenReset,
    cmd_ListModules,
    cmd_CheckCertValidity,
    cmd_ChangePassword,
    cmd_Version,
    cmd_Batch,
    cmd_Merge,
    cmd_UpgradeMerge /* test only */
};

/*  Certutil options */
enum certutilOpts {
    opt_SSOPass = 0,
    opt_AddKeyUsageExt,
    opt_AddBasicConstraintExt,
    opt_AddAuthorityKeyIDExt,
    opt_AddCRLDistPtsExt,
    opt_AddNSCertTypeExt,
    opt_AddExtKeyUsageExt,
    opt_ExtendedEmailAddrs,
    opt_ExtendedDNSNames,
    opt_ASCIIForIO,
    opt_ValidityTime,
    opt_IssuerName,
    opt_CertDir,
    opt_VerifySig,
    opt_PasswordFile,
    opt_KeySize,
    opt_TokenName,
    opt_InputFile,
    opt_KeyIndex,
    opt_KeyType,
    opt_DetailedInfo,
    opt_SerialNumber,
    opt_Nickname,
    opt_OutputFile,
    opt_PhoneNumber,
    opt_DBPrefix,
    opt_PQGFile,
    opt_BinaryDER,
    opt_Subject,
    opt_Trust,
    opt_Usage,
    opt_Validity,
    opt_OffsetMonths,
    opt_SelfSign,
    opt_RW,
    opt_Exponent,
    opt_NoiseFile,
    opt_Hash,
    opt_NewPasswordFile,
    opt_AddAuthInfoAccExt,
    opt_AddSubjInfoAccExt,
    opt_AddCertPoliciesExt,
    opt_AddPolicyMapExt,
    opt_AddPolicyConstrExt,
    opt_AddInhibAnyExt,
    opt_AddSubjectKeyIDExt,
    opt_AddCmdKeyUsageExt,
    opt_AddCmdNSCertTypeExt,
    opt_AddCmdExtKeyUsageExt,
    opt_SourceDir,
    opt_SourcePrefix,
    opt_UpgradeID,
    opt_UpgradeTokenName
};

static const
secuCommandFlag commands_init[] =
{
	{ /* cmd_AddCert             */  'A', PR_FALSE, 0, PR_FALSE },
	{ /* cmd_CreateNewCert       */  'C', PR_FALSE, 0, PR_FALSE },
	{ /* cmd_DeleteCert          */  'D', PR_FALSE, 0, PR_FALSE },
	{ /* cmd_AddEmailCert        */  'E', PR_FALSE, 0, PR_FALSE },
	{ /* cmd_DeleteKey           */  'F', PR_FALSE, 0, PR_FALSE },
	{ /* cmd_GenKeyPair          */  'G', PR_FALSE, 0, PR_FALSE },
	{ /* cmd_PrintHelp           */  'H', PR_FALSE, 0, PR_FALSE },
	{ /* cmd_ListKeys            */  'K', PR_FALSE, 0, PR_FALSE },
	{ /* cmd_ListCerts           */  'L', PR_FALSE, 0, PR_FALSE },
	{ /* cmd_ModifyCertTrust     */  'M', PR_FALSE, 0, PR_FALSE },
	{ /* cmd_NewDBs              */  'N', PR_FALSE, 0, PR_FALSE },
	{ /* cmd_DumpChain           */  'O', PR_FALSE, 0, PR_FALSE },
	{ /* cmd_CertReq             */  'R', PR_FALSE, 0, PR_FALSE },
	{ /* cmd_CreateAndAddCert    */  'S', PR_FALSE, 0, PR_FALSE },
	{ /* cmd_TokenReset          */  'T', PR_FALSE, 0, PR_FALSE },
	{ /* cmd_ListModules         */  'U', PR_FALSE, 0, PR_FALSE },
	{ /* cmd_CheckCertValidity   */  'V', PR_FALSE, 0, PR_FALSE },
	{ /* cmd_ChangePassword      */  'W', PR_FALSE, 0, PR_FALSE },
	{ /* cmd_Version             */  'Y', PR_FALSE, 0, PR_FALSE },
	{ /* cmd_Batch               */  'B', PR_FALSE, 0, PR_FALSE },
	{ /* cmd_Merge               */   0,  PR_FALSE, 0, PR_FALSE, "merge" },
	{ /* cmd_UpgradeMerge        */   0,  PR_FALSE, 0, PR_FALSE, 
                                                   "upgrade-merge" }
};
#define NUM_COMMANDS ((sizeof commands_init) / (sizeof commands_init[0]))
 
static const 
secuCommandFlag options_init[] =
{
	{ /* opt_SSOPass             */  '0', PR_TRUE,  0, PR_FALSE },
	{ /* opt_AddKeyUsageExt      */  '1', PR_FALSE, 0, PR_FALSE },
	{ /* opt_AddBasicConstraintExt*/ '2', PR_FALSE, 0, PR_FALSE },
	{ /* opt_AddAuthorityKeyIDExt*/  '3', PR_FALSE, 0, PR_FALSE },
	{ /* opt_AddCRLDistPtsExt    */  '4', PR_FALSE, 0, PR_FALSE },
	{ /* opt_AddNSCertTypeExt    */  '5', PR_FALSE, 0, PR_FALSE },
	{ /* opt_AddExtKeyUsageExt   */  '6', PR_FALSE, 0, PR_FALSE },
	{ /* opt_ExtendedEmailAddrs  */  '7', PR_TRUE,  0, PR_FALSE },
	{ /* opt_ExtendedDNSNames    */  '8', PR_TRUE,  0, PR_FALSE },
	{ /* opt_ASCIIForIO          */  'a', PR_FALSE, 0, PR_FALSE },
	{ /* opt_ValidityTime        */  'b', PR_TRUE,  0, PR_FALSE },
	{ /* opt_IssuerName          */  'c', PR_TRUE,  0, PR_FALSE },
	{ /* opt_CertDir             */  'd', PR_TRUE,  0, PR_FALSE },
	{ /* opt_VerifySig           */  'e', PR_FALSE, 0, PR_FALSE },
	{ /* opt_PasswordFile        */  'f', PR_TRUE,  0, PR_FALSE },
	{ /* opt_KeySize             */  'g', PR_TRUE,  0, PR_FALSE },
	{ /* opt_TokenName           */  'h', PR_TRUE,  0, PR_FALSE },
	{ /* opt_InputFile           */  'i', PR_TRUE,  0, PR_FALSE },
	{ /* opt_KeyIndex            */  'j', PR_TRUE,  0, PR_FALSE },
	{ /* opt_KeyType             */  'k', PR_TRUE,  0, PR_FALSE },
	{ /* opt_DetailedInfo        */  'l', PR_FALSE, 0, PR_FALSE },
	{ /* opt_SerialNumber        */  'm', PR_TRUE,  0, PR_FALSE },
	{ /* opt_Nickname            */  'n', PR_TRUE,  0, PR_FALSE },
	{ /* opt_OutputFile          */  'o', PR_TRUE,  0, PR_FALSE },
	{ /* opt_PhoneNumber         */  'p', PR_TRUE,  0, PR_FALSE },
	{ /* opt_DBPrefix            */  'P', PR_TRUE,  0, PR_FALSE },
	{ /* opt_PQGFile             */  'q', PR_TRUE,  0, PR_FALSE },
	{ /* opt_BinaryDER           */  'r', PR_FALSE, 0, PR_FALSE },
	{ /* opt_Subject             */  's', PR_TRUE,  0, PR_FALSE },
	{ /* opt_Trust               */  't', PR_TRUE,  0, PR_FALSE },
	{ /* opt_Usage               */  'u', PR_TRUE,  0, PR_FALSE },
	{ /* opt_Validity            */  'v', PR_TRUE,  0, PR_FALSE },
	{ /* opt_OffsetMonths        */  'w', PR_TRUE,  0, PR_FALSE },
	{ /* opt_SelfSign            */  'x', PR_FALSE, 0, PR_FALSE },
	{ /* opt_RW                  */  'X', PR_FALSE, 0, PR_FALSE },
	{ /* opt_Exponent            */  'y', PR_TRUE,  0, PR_FALSE },
	{ /* opt_NoiseFile           */  'z', PR_TRUE,  0, PR_FALSE },
	{ /* opt_Hash                */  'Z', PR_TRUE,  0, PR_FALSE },
	{ /* opt_NewPasswordFile     */  '@', PR_TRUE,  0, PR_FALSE },
	{ /* opt_AddAuthInfoAccExt   */  0,   PR_FALSE, 0, PR_FALSE, "extAIA" },
	{ /* opt_AddSubjInfoAccExt   */  0,   PR_FALSE, 0, PR_FALSE, "extSIA" },
	{ /* opt_AddCertPoliciesExt  */  0,   PR_FALSE, 0, PR_FALSE, "extCP" },
	{ /* opt_AddPolicyMapExt     */  0,   PR_FALSE, 0, PR_FALSE, "extPM" },
	{ /* opt_AddPolicyConstrExt  */  0,   PR_FALSE, 0, PR_FALSE, "extPC" },
	{ /* opt_AddInhibAnyExt      */  0,   PR_FALSE, 0, PR_FALSE, "extIA" },
	{ /* opt_AddSubjectKeyIDExt  */  0,   PR_FALSE, 0, PR_FALSE, 
						   "extSKID" },
	{ /* opt_AddCmdKeyUsageExt   */  0,   PR_TRUE,  0, PR_FALSE,
                                                   "keyUsage" },
	{ /* opt_AddCmdNSCertTypeExt */   0,   PR_TRUE,  0, PR_FALSE,
                                                   "nsCertType" },
	{ /* opt_AddCmdExtKeyUsageExt*/  0,   PR_TRUE,  0, PR_FALSE,
                                                   "extKeyUsage" },

	{ /* opt_SourceDir           */  0,   PR_TRUE,  0, PR_FALSE,
                                                   "source-dir"},
	{ /* opt_SourcePrefix        */  0,   PR_TRUE,  0, PR_FALSE, 
						   "source-prefix"},
	{ /* opt_UpgradeID           */  0,   PR_TRUE,  0, PR_FALSE, 
                                                   "upgrade-id"},
	{ /* opt_UpgradeTokenName    */  0,   PR_TRUE,  0, PR_FALSE, 
                                                   "upgrade-token-name"},
};
#define NUM_OPTIONS ((sizeof options_init)  / (sizeof options_init[0]))

static secuCommandFlag certutil_commands[NUM_COMMANDS];
static secuCommandFlag certutil_options [NUM_OPTIONS ];

static const secuCommand certutil = {
    NUM_COMMANDS, 
    NUM_OPTIONS, 
    certutil_commands, 
    certutil_options
};

static certutilExtnList certutil_extns;

static int 
certutil_main(int argc, char **argv, PRBool initialize)
{
    CERTCertDBHandle *certHandle;
    PK11SlotInfo *slot = NULL;
    CERTName *  subject         = 0;
    PRFileDesc *inFile          = PR_STDIN;
    PRFileDesc *outFile         = NULL;
    char *      certfile        = "tempcert";
    char *      certreqfile     = "tempcertreq";
    char *      slotname        = "internal";
    char *      certPrefix      = "";
    char *      sourceDir       = "";
    char *      srcCertPrefix   = "";
    char *      upgradeID        = "";
    char *      upgradeTokenName     = "";
    KeyType     keytype         = rsaKey;
    char *      name            = NULL;
    char *      keysource       = NULL;
    SECOidTag   hashAlgTag      = SEC_OID_UNKNOWN;
    int	        keysize	        = DEFAULT_KEY_BITS;
    int         publicExponent  = 0x010001;
    unsigned int serialNumber   = 0;
    int         warpmonths      = 0;
    int         validityMonths  = 3;
    int         commandsEntered = 0;
    char        commandToRun    = '\0';
    secuPWData  pwdata          = { PW_NONE, 0 };
    secuPWData  pwdata2         = { PW_NONE, 0 };
    PRBool      readOnly        = PR_FALSE;
    PRBool      initialized     = PR_FALSE;

    SECKEYPrivateKey *privkey = NULL;
    SECKEYPublicKey *pubkey = NULL;

    int i;
    SECStatus rv;

    progName = PORT_Strrchr(argv[0], '/');
    progName = progName ? progName+1 : argv[0];
    memcpy(certutil_commands, commands_init, sizeof commands_init);
    memcpy(certutil_options,  options_init,  sizeof options_init);

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

    if (rv != SECSuccess)
	Usage(progName);

    if (certutil.commands[cmd_PrintHelp].activated)
	LongUsage(progName);

    if (certutil.options[opt_PasswordFile].arg) {
	pwdata.source = PW_FROMFILE;
	pwdata.data = certutil.options[opt_PasswordFile].arg;
    }
    if (certutil.options[opt_NewPasswordFile].arg) {
	pwdata2.source = PW_FROMFILE;
	pwdata2.data = certutil.options[opt_NewPasswordFile].arg;
    }

    if (certutil.options[opt_CertDir].activated)
	SECU_ConfigDirectory(certutil.options[opt_CertDir].arg);

    if (certutil.options[opt_SourceDir].activated)
	sourceDir = certutil.options[opt_SourceDir].arg;

    if (certutil.options[opt_UpgradeID].activated)
	upgradeID = certutil.options[opt_UpgradeID].arg;

    if (certutil.options[opt_UpgradeTokenName].activated)
	upgradeTokenName = certutil.options[opt_UpgradeTokenName].arg;

    if (certutil.options[opt_KeySize].activated) {
	keysize = PORT_Atoi(certutil.options[opt_KeySize].arg);
	if ((keysize < MIN_KEY_BITS) || (keysize > MAX_KEY_BITS)) {
	    PR_fprintf(PR_STDERR, 
                       "%s -g:  Keysize must be between %d and %d.\n",
		       progName, MIN_KEY_BITS, MAX_KEY_BITS);
	    return 255;
	}
#ifdef NSS_ENABLE_ECC
	if (keytype == ecKey) {
	    PR_fprintf(PR_STDERR, "%s -g:  Not for ec keys.\n", progName);
	    return 255;
	}
#endif /* NSS_ENABLE_ECC */

    }

    /*  -h specify token name  */
    if (certutil.options[opt_TokenName].activated) {
	if (PL_strcmp(certutil.options[opt_TokenName].arg, "all") == 0)
	    slotname = NULL;
	else
	    slotname = PL_strdup(certutil.options[opt_TokenName].arg);
    }

    /*  -Z hash type  */
    if (certutil.options[opt_Hash].activated) {
	char * arg = certutil.options[opt_Hash].arg;
        hashAlgTag = SECU_StringToSignatureAlgTag(arg);
        if (hashAlgTag == SEC_OID_UNKNOWN) {
	    PR_fprintf(PR_STDERR, "%s -Z:  %s is not a recognized type.\n",
	               progName, arg);
	    return 255;
	}
    }

    /*  -k key type  */
    if (certutil.options[opt_KeyType].activated) {
	char * arg = certutil.options[opt_KeyType].arg;
	if (PL_strcmp(arg, "rsa") == 0) {
	    keytype = rsaKey;
	} else if (PL_strcmp(arg, "dsa") == 0) {
	    keytype = dsaKey;
#ifdef NSS_ENABLE_ECC
	} else if (PL_strcmp(arg, "ec") == 0) {
	    keytype = ecKey;
#endif /* NSS_ENABLE_ECC */
	} else if (PL_strcmp(arg, "all") == 0) {
	    keytype = nullKey;
	} else {
	    /* use an existing private/public key pair */
	    keysource = arg;
	}
    } else if (certutil.commands[cmd_ListKeys].activated) {
	keytype = nullKey;
    }

    /*  -m serial number */
    if (certutil.options[opt_SerialNumber].activated) {
	int sn = PORT_Atoi(certutil.options[opt_SerialNumber].arg);
	if (sn < 0) {
	    PR_fprintf(PR_STDERR, "%s -m:  %s is not a valid serial number.\n",
	               progName, certutil.options[opt_SerialNumber].arg);
	    return 255;
	}
	serialNumber = sn;
    }

    /*  -P certdb name prefix */
    if (certutil.options[opt_DBPrefix].activated) {
        if (certutil.options[opt_DBPrefix].arg) {
            certPrefix = strdup(certutil.options[opt_DBPrefix].arg);
        } else {
            Usage(progName);
        }
    }

    /*  --source-prefix certdb name prefix */
    if (certutil.options[opt_SourcePrefix].activated) {
        if (certutil.options[opt_SourcePrefix].arg) {
            srcCertPrefix = strdup(certutil.options[opt_SourcePrefix].arg);
        } else {
            Usage(progName);
        }
    }

    /*  -q PQG file or curve name */
    if (certutil.options[opt_PQGFile].activated) {
#ifdef NSS_ENABLE_ECC
	if ((keytype != dsaKey) && (keytype != ecKey)) {
	    PR_fprintf(PR_STDERR, "%s -q: specifies a PQG file for DSA keys" \
		       " (-k dsa) or a named curve for EC keys (-k ec)\n)",
	               progName);
#else   /* } */
	if (keytype != dsaKey) {
	    PR_fprintf(PR_STDERR, "%s -q: PQG file is for DSA key (-k dsa).\n)",
	               progName);
#endif /* NSS_ENABLE_ECC */
	    return 255;
	}
    }

    /*  -s subject name  */
    if (certutil.options[opt_Subject].activated) {
	subject = CERT_AsciiToName(certutil.options[opt_Subject].arg);
	if (!subject) {
	    PR_fprintf(PR_STDERR, "%s -s: improperly formatted name: \"%s\"\n",
	               progName, certutil.options[opt_Subject].arg);
	    return 255;
	}
    }

    /*  -v validity period  */
    if (certutil.options[opt_Validity].activated) {
	validityMonths = PORT_Atoi(certutil.options[opt_Validity].arg);
	if (validityMonths < 0) {
	    PR_fprintf(PR_STDERR, "%s -v: incorrect validity period: \"%s\"\n",
	               progName, certutil.options[opt_Validity].arg);
	    return 255;
	}
    }

    /*  -w warp months  */
    if (certutil.options[opt_OffsetMonths].activated)
	warpmonths = PORT_Atoi(certutil.options[opt_OffsetMonths].arg);

    /*  -y public exponent (for RSA)  */
    if (certutil.options[opt_Exponent].activated) {
	publicExponent = PORT_Atoi(certutil.options[opt_Exponent].arg);
	if ((publicExponent != 3) &&
	    (publicExponent != 17) &&
	    (publicExponent != 65537)) {
	    PR_fprintf(PR_STDERR, "%s -y: incorrect public exponent %d.", 
	                           progName, publicExponent);
	    PR_fprintf(PR_STDERR, "Must be 3, 17, or 65537.\n");
	    return 255;
	}
    }

    /*  Check number of commands entered.  */
    commandsEntered = 0;
    for (i=0; i< certutil.numCommands; i++) {
	if (certutil.commands[i].activated) {
	    commandToRun = certutil.commands[i].flag;
	    commandsEntered++;
	}
	if (commandsEntered > 1)
	    break;
    }
    if (commandsEntered > 1) {
	PR_fprintf(PR_STDERR, "%s: only one command at a time!\n", progName);
	PR_fprintf(PR_STDERR, "You entered: ");
	for (i=0; i< certutil.numCommands; i++) {
	    if (certutil.commands[i].activated)
		PR_fprintf(PR_STDERR, " -%c", certutil.commands[i].flag);
	}
	PR_fprintf(PR_STDERR, "\n");
	return 255;
    }
    if (commandsEntered == 0) {
	PR_fprintf(PR_STDERR, "%s: you must enter a command!\n", progName);
	Usage(progName);
    }

    if (certutil.commands[cmd_ListCerts].activated ||
         certutil.commands[cmd_PrintHelp].activated ||
         certutil.commands[cmd_ListKeys].activated ||
         certutil.commands[cmd_ListModules].activated ||
         certutil.commands[cmd_CheckCertValidity].activated ||
         certutil.commands[cmd_Version].activated ) {
	readOnly = !certutil.options[opt_RW].activated;
    }

    /*  -A, -D, -F, -M, -S, -V, and all require -n  */
    if ((certutil.commands[cmd_AddCert].activated ||
         certutil.commands[cmd_DeleteCert].activated ||
         certutil.commands[cmd_DeleteKey].activated ||
	 certutil.commands[cmd_DumpChain].activated ||
         certutil.commands[cmd_ModifyCertTrust].activated ||
         certutil.commands[cmd_CreateAndAddCert].activated ||
         certutil.commands[cmd_CheckCertValidity].activated) &&
        !certutil.options[opt_Nickname].activated) {
	PR_fprintf(PR_STDERR, 
	          "%s -%c: nickname is required for this command (-n).\n",
	           progName, commandToRun);
	return 255;
    }

    /*  -A, -E, -M, -S require trust  */
    if ((certutil.commands[cmd_AddCert].activated ||
         certutil.commands[cmd_AddEmailCert].activated ||
         certutil.commands[cmd_ModifyCertTrust].activated ||
         certutil.commands[cmd_CreateAndAddCert].activated) &&
        !certutil.options[opt_Trust].activated) {
	PR_fprintf(PR_STDERR, 
	          "%s -%c: trust is required for this command (-t).\n",
	           progName, commandToRun);
	return 255;
    }

    /*  if -L is given raw or ascii mode, it must be for only one cert.  */
    if (certutil.commands[cmd_ListCerts].activated &&
        (certutil.options[opt_ASCIIForIO].activated ||
         certutil.options[opt_BinaryDER].activated) &&
        !certutil.options[opt_Nickname].activated) {
	PR_fprintf(PR_STDERR, 
	        "%s: nickname is required to dump cert in raw or ascii mode.\n",
	           progName);
	return 255;
    }
    
    /*  -L can only be in (raw || ascii).  */
    if (certutil.commands[cmd_ListCerts].activated &&
        certutil.options[opt_ASCIIForIO].activated &&
        certutil.options[opt_BinaryDER].activated) {
	PR_fprintf(PR_STDERR, 
	           "%s: cannot specify both -r and -a when dumping cert.\n",
	           progName);
	return 255;
    }

    /*  If making a cert request, need a subject.  */
    if ((certutil.commands[cmd_CertReq].activated ||
         certutil.commands[cmd_CreateAndAddCert].activated) &&
        !(certutil.options[opt_Subject].activated || keysource)) {
	PR_fprintf(PR_STDERR, 
	           "%s -%c: subject is required to create a cert request.\n",
	           progName, commandToRun);
	return 255;
    }

    /*  If making a cert, need a serial number.  */
    if ((certutil.commands[cmd_CreateNewCert].activated ||
         certutil.commands[cmd_CreateAndAddCert].activated) &&
         !certutil.options[opt_SerialNumber].activated) {
	/*  Make a default serial number from the current time.  */
	PRTime now = PR_Now();
	LL_USHR(now, now, 19);
	LL_L2UI(serialNumber, now);
    }

    /*  Validation needs the usage to validate for.  */
    if (certutil.commands[cmd_CheckCertValidity].activated &&
        !certutil.options[opt_Usage].activated) {
	PR_fprintf(PR_STDERR, 
	           "%s -V: specify a usage to validate the cert for (-u).\n",
	           progName);
	return 255;
    }

    /* Upgrade/Merge needs a source database and a upgrade id. */
    if (certutil.commands[cmd_UpgradeMerge].activated &&
        !(certutil.options[opt_SourceDir].activated &&
          certutil.options[opt_UpgradeID].activated)) {

	PR_fprintf(PR_STDERR, 
	           "%s --upgrade-merge: specify an upgrade database directory "
		   "(--source-dir) and\n"
                   "   an upgrade ID (--upgrade-id).\n",
	           progName);
	return 255;
    }

    /* Merge needs a source database */
    if (certutil.commands[cmd_Merge].activated &&
        !certutil.options[opt_SourceDir].activated) {


	PR_fprintf(PR_STDERR, 
	           "%s --merge: specify an source database directory "
		   "(--source-dir)\n",
	           progName);
	return 255;
    }

    
    /*  To make a cert, need either a issuer or to self-sign it.  */
    if (certutil.commands[cmd_CreateAndAddCert].activated &&
	!(certutil.options[opt_IssuerName].activated ||
          certutil.options[opt_SelfSign].activated)) {
	PR_fprintf(PR_STDERR,
	           "%s -S: must specify issuer (-c) or self-sign (-x).\n",
	           progName);
	return 255;
    }

    /*  Using slotname == NULL for listing keys and certs on all slots, 
     *  but only that. */
    if (!(certutil.commands[cmd_ListKeys].activated ||
	  certutil.commands[cmd_DumpChain].activated ||
    	  certutil.commands[cmd_ListCerts].activated) && slotname == NULL) {
	PR_fprintf(PR_STDERR,
	           "%s -%c: cannot use \"-h all\" for this command.\n",
	           progName, commandToRun);
	return 255;
    }

    /*  Using keytype == nullKey for list all key types, but only that.  */
    if (!certutil.commands[cmd_ListKeys].activated && keytype == nullKey) {
	PR_fprintf(PR_STDERR,
	           "%s -%c: cannot use \"-k all\" for this command.\n",
	           progName, commandToRun);
	return 255;
    }

    /*  -S  open outFile, temporary file for cert request.  */
    if (certutil.commands[cmd_CreateAndAddCert].activated) {
	outFile = PR_Open(certreqfile,
                          PR_RDWR | PR_CREATE_FILE | PR_TRUNCATE, 00660);
	if (!outFile) {
	    PR_fprintf(PR_STDERR, 
		       "%s -o: unable to open \"%s\" for writing (%ld, %ld)\n",
		       progName, certreqfile,
		       PR_GetError(), PR_GetOSError());
	    return 255;
	}
    }

    /*  Open the input file.  */
    if (certutil.options[opt_InputFile].activated) {
	inFile = PR_Open(certutil.options[opt_InputFile].arg, PR_RDONLY, 0);
	if (!inFile) {
	    PR_fprintf(PR_STDERR,
	               "%s:  unable to open \"%s\" for reading (%ld, %ld).\n",
	               progName, certutil.options[opt_InputFile].arg,
	               PR_GetError(), PR_GetOSError());
	    return 255;
	}
    }

    /*  Open the output file.  */
    if (certutil.options[opt_OutputFile].activated && !outFile) {
	outFile = PR_Open(certutil.options[opt_OutputFile].arg, 
                          PR_CREATE_FILE | PR_RDWR | PR_TRUNCATE, 00660);
	if (!outFile) {
	    PR_fprintf(PR_STDERR,
	               "%s:  unable to open \"%s\" for writing (%ld, %ld).\n",
	               progName, certutil.options[opt_OutputFile].arg,
	               PR_GetError(), PR_GetOSError());
	    return 255;
	}
    }

    name = SECU_GetOptionArg(&certutil, opt_Nickname);

    PK11_SetPasswordFunc(SECU_GetModulePassword);

    if (PR_TRUE == initialize) {
        /*  Initialize NSPR and NSS.  */
        PR_Init(PR_SYSTEM_THREAD, PR_PRIORITY_NORMAL, 1);
	if (!certutil.commands[cmd_UpgradeMerge].activated) {
            rv = NSS_Initialize(SECU_ConfigDirectory(NULL), 
			    certPrefix, certPrefix,
                            "secmod.db", readOnly ? NSS_INIT_READONLY: 0);
	} else {
            rv = NSS_InitWithMerge(SECU_ConfigDirectory(NULL), 
			    certPrefix, certPrefix, "secmod.db",
			    sourceDir, srcCertPrefix, srcCertPrefix, 
			    upgradeID, upgradeTokenName,
                            readOnly ? NSS_INIT_READONLY: 0);
	}
        if (rv != SECSuccess) {
	    SECU_PrintPRandOSError(progName);
	    rv = SECFailure;
	    goto shutdown;
        }
        initialized = PR_TRUE;
    	SECU_RegisterDynamicOids();
    }
    certHandle = CERT_GetDefaultCertDB();

    if (certutil.commands[cmd_Version].activated) {
	printf("Certificate database content version: command not implemented.\n");
    }

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

    

   
    if ( !slot && (certutil.commands[cmd_NewDBs].activated ||
         certutil.commands[cmd_ModifyCertTrust].activated  || 
         certutil.commands[cmd_ChangePassword].activated   ||
         certutil.commands[cmd_TokenReset].activated       ||
         certutil.commands[cmd_CreateAndAddCert].activated ||
         certutil.commands[cmd_AddCert].activated          ||
         certutil.commands[cmd_Merge].activated          ||
         certutil.commands[cmd_UpgradeMerge].activated          ||
         certutil.commands[cmd_AddEmailCert].activated)) {
      
         SECU_PrintError(progName, "could not find the slot %s",slotname);
         rv = SECFailure;
         goto shutdown;
    }

    /*  If creating new database, initialize the password.  */
    if (certutil.commands[cmd_NewDBs].activated) {
	SECU_ChangePW2(slot, 0, 0, certutil.options[opt_PasswordFile].arg,
				certutil.options[opt_NewPasswordFile].arg);
    }

    /* walk through the upgrade merge if necessary.
     * This option is more to test what some applications will want to do
     * to do an automatic upgrade. The --merge command is more useful for
     * the general case where 2 database need to be merged together.
     */
    if (certutil.commands[cmd_UpgradeMerge].activated) {
	if (*upgradeTokenName == 0) {
	    upgradeTokenName = upgradeID;
	}
	if (!PK11_IsInternal(slot)) {
	    fprintf(stderr, "Only internal DB's can be upgraded\n");
	    rv = SECSuccess;
	    goto shutdown;
	}
	if (!PK11_IsRemovable(slot)) {
	    printf("database already upgraded.\n");
	    rv = SECSuccess;
	    goto shutdown;
	}
	if (!PK11_NeedLogin(slot)) {
	    printf("upgrade complete!\n");
	    rv = SECSuccess;
	    goto shutdown;
	}
	/* authenticate to the old DB if necessary */
	if (PORT_Strcmp(PK11_GetTokenName(slot), upgradeTokenName) ==  0) {
	    /* if we need a password, supply it. This will be the password
	     * for the old database */
	    rv = PK11_Authenticate(slot, PR_FALSE, &pwdata2);
	    if (rv != SECSuccess) {
         	SECU_PrintError(progName, "Could not get password for %s",
				upgradeTokenName);
		goto shutdown;
	    }
	    /* 
	     * if we succeeded above, but still aren't logged in, that means
	     * we just supplied the password for the old database. We may
	     * need the password for the new database. NSS will automatically
	     * change the token names at this point
	     */
	    if (PK11_IsLoggedIn(slot, &pwdata)) {
		printf("upgrade complete!\n");
		rv = SECSuccess;
		goto shutdown;
	    }
 	}

	/* call PK11_IsPresent to update our cached token information */
	if (!PK11_IsPresent(slot)) {
	    /* this shouldn't happen. We call isPresent to force a token
	     * info update */
	    fprintf(stderr, "upgrade/merge internal error\n");
	    rv = SECFailure;
	    goto shutdown;
	}

	/* the token is now set to the state of the source database,
	 * if we need a password for it, PK11_Authenticate will 
	 * automatically prompt us */
	rv = PK11_Authenticate(slot, PR_FALSE, &pwdata);
	if (rv == SECSuccess) {
	    printf("upgrade complete!\n");
	} else {
            SECU_PrintError(progName, "Could not get password for %s",
				PK11_GetTokenName(slot));
	}
	goto shutdown;
    }

    /*
     * merge 2 databases.
     */
    if (certutil.commands[cmd_Merge].activated) {
	PK11SlotInfo *sourceSlot = NULL;
	PK11MergeLog *log;
	char *modspec = PR_smprintf(
		"configDir='%s' certPrefix='%s' tokenDescription='%s'",
		sourceDir, srcCertPrefix, 
		*upgradeTokenName ? upgradeTokenName : "Source Database");

	if (!modspec) {
	    rv = SECFailure;
	    goto shutdown;
	}

	sourceSlot = SECMOD_OpenUserDB(modspec);
	PR_smprintf_free(modspec);
	if (!sourceSlot) {
	    SECU_PrintError(progName, "couldn't open source database");
	    rv = SECFailure;
	    goto shutdown;
	}

	rv = PK11_Authenticate(slot, PR_FALSE, &pwdata);
	if (rv != SECSuccess) {
	    SECU_PrintError(progName, "Couldn't get password for %s",
					PK11_GetTokenName(slot));
	    goto merge_fail;
	}

	rv = PK11_Authenticate(sourceSlot, PR_FALSE, &pwdata2);
	if (rv != SECSuccess) {
	    SECU_PrintError(progName, "Couldn't get password for %s",
					PK11_GetTokenName(sourceSlot));
	    goto merge_fail;
	}

	log = PK11_CreateMergeLog();
	if (!log) {
	    rv = SECFailure;
	    SECU_PrintError(progName, "couldn't create error log");
	    goto merge_fail;
	}

	rv = PK11_MergeTokens(slot, sourceSlot, log, &pwdata, &pwdata2);
	if (rv != SECSuccess) {
	    DumpMergeLog(progName, log);
	}
	PK11_DestroyMergeLog(log);

merge_fail:
	SECMOD_CloseUserDB(sourceSlot);
	PK11_FreeSlot(sourceSlot);
	goto shutdown;
    }

    /* The following 8 options are mutually exclusive with all others. */

    /*  List certs (-L)  */
    if (certutil.commands[cmd_ListCerts].activated) {
	rv = ListCerts(certHandle, name, slot,
	               certutil.options[opt_BinaryDER].activated,
	               certutil.options[opt_ASCIIForIO].activated, 
                       (outFile) ? outFile : PR_STDOUT, &pwdata);
	goto shutdown;
    }
    if (certutil.commands[cmd_DumpChain].activated) {
	rv = DumpChain(certHandle, name);
	goto shutdown;
    }
    /*  XXX needs work  */
    /*  List keys (-K)  */
    if (certutil.commands[cmd_ListKeys].activated) {
	rv = ListKeys(slot, name, 0 /*keyindex*/, keytype, PR_FALSE /*dopriv*/,
	              &pwdata);
	goto shutdown;
    }
    /*  List modules (-U)  */
    if (certutil.commands[cmd_ListModules].activated) {
	rv = ListModules();
	goto shutdown;
    }
    /*  Delete cert (-D)  */
    if (certutil.commands[cmd_DeleteCert].activated) {
	rv = DeleteCert(certHandle, name);
	goto shutdown;
    }
    /*  Delete key (-F)  */
    if (certutil.commands[cmd_DeleteKey].activated) {
	rv = DeleteKey(name, &pwdata);
	goto shutdown;
    }
    /*  Modify trust attribute for cert (-M)  */
    if (certutil.commands[cmd_ModifyCertTrust].activated) {
	rv = ChangeTrustAttributes(certHandle, slot, name, 
	                           certutil.options[opt_Trust].arg, &pwdata);
	goto shutdown;
    }
    /*  Change key db password (-W) (future - change pw to slot?)  */
    if (certutil.commands[cmd_ChangePassword].activated) {
	rv = SECU_ChangePW2(slot, 0, 0, certutil.options[opt_PasswordFile].arg,
				certutil.options[opt_NewPasswordFile].arg);
	goto shutdown;
    }
    /*  Reset the a token */
    if (certutil.commands[cmd_TokenReset].activated) {
	char *sso_pass = "";

	if (certutil.options[opt_SSOPass].activated) {
	    sso_pass = certutil.options[opt_SSOPass].arg;
 	}
	rv = PK11_ResetToken(slot,sso_pass);

	goto shutdown;
    }
    /*  Check cert validity against current time (-V)  */
    if (certutil.commands[cmd_CheckCertValidity].activated) {
	/* XXX temporary hack for fips - must log in to get priv key */
	if (certutil.options[opt_VerifySig].activated) {
	    if (slot && PK11_NeedLogin(slot)) {
                SECStatus newrv = PK11_Authenticate(slot, PR_TRUE, &pwdata);
                if (newrv != SECSuccess) {
                    SECU_PrintError(progName, "could not authenticate to token %s.",
                                    PK11_GetTokenName(slot));
                    goto shutdown;
                }
            }
	}
	rv = ValidateCert(certHandle, name, 
	                  certutil.options[opt_ValidityTime].arg,
			  certutil.options[opt_Usage].arg,
			  certutil.options[opt_VerifySig].activated,
			  certutil.options[opt_DetailedInfo].activated,
	                  &pwdata);
	if (rv != SECSuccess && PR_GetError() == SEC_ERROR_INVALID_ARGS)
            SECU_PrintError(progName, "validation failed");
	goto shutdown;
    }

    /*
     *  Key generation
     */

    /*  These commands may require keygen.  */
    if (certutil.commands[cmd_CertReq].activated ||
        certutil.commands[cmd_CreateAndAddCert].activated ||
	certutil.commands[cmd_GenKeyPair].activated) {
	if (keysource) {
	    CERTCertificate *keycert;
	    keycert = CERT_FindCertByNicknameOrEmailAddr(certHandle, keysource);
	    if (!keycert) {
		keycert = PK11_FindCertFromNickname(keysource, NULL);
		if (!keycert) {
		    SECU_PrintError(progName,
			    "%s is neither a key-type nor a nickname", keysource);
		    return SECFailure;
		}
	    }
	    privkey = PK11_FindKeyByDERCert(slot, keycert, &pwdata);
	    if (privkey)
		pubkey = CERT_ExtractPublicKey(keycert);
	    if (!pubkey) {
		SECU_PrintError(progName,
				"Could not get keys from cert %s", keysource);
		rv = SECFailure;
		CERT_DestroyCertificate(keycert);
		goto shutdown;
	    }
	    keytype = privkey->keyType;
	    /* On CertReq for renewal if no subject has been
	     * specified obtain it from the certificate. 
	     */
	    if (certutil.commands[cmd_CertReq].activated && !subject) {
	        subject = CERT_AsciiToName(keycert->subjectName);
	        if (!subject) {
	            SECU_PrintError(progName,
			"Could not get subject from certificate %s", keysource);
	            CERT_DestroyCertificate(keycert);
	            rv = SECFailure;
	            goto shutdown;
	        }
	    }
	    CERT_DestroyCertificate(keycert);
	} else {
	    privkey = 
		CERTUTIL_GeneratePrivateKey(keytype, slot, keysize,
					    publicExponent, 
					    certutil.options[opt_NoiseFile].arg,
					    &pubkey, 
					    certutil.options[opt_PQGFile].arg,
					    &pwdata);
	    if (privkey == NULL) {
		SECU_PrintError(progName, "unable to generate key(s)\n");
		rv = SECFailure;
		goto shutdown;
	    }
	}
	privkey->wincx = &pwdata;
	PORT_Assert(pubkey != NULL);

	/*  If all that was needed was keygen, exit.  */
	if (certutil.commands[cmd_GenKeyPair].activated) {
	    rv = SECSuccess;
	    goto shutdown;
	}
    }

    /* If we need a list of extensions convert the flags into list format */
    if (certutil.commands[cmd_CertReq].activated ||
        certutil.commands[cmd_CreateAndAddCert].activated ||
        certutil.commands[cmd_CreateNewCert].activated) {
        certutil_extns[ext_keyUsage].activated =
            certutil.options[opt_AddCmdKeyUsageExt].activated;
        if (!certutil_extns[ext_keyUsage].activated) {
            certutil_extns[ext_keyUsage].activated =
                certutil.options[opt_AddKeyUsageExt].activated;
        } else {
            certutil_extns[ext_keyUsage].arg =
                certutil.options[opt_AddCmdKeyUsageExt].arg;
        }
        certutil_extns[ext_basicConstraint].activated =
				certutil.options[opt_AddBasicConstraintExt].activated;
        certutil_extns[ext_authorityKeyID].activated =
				certutil.options[opt_AddAuthorityKeyIDExt].activated;
        certutil_extns[ext_subjectKeyID].activated =
				certutil.options[opt_AddSubjectKeyIDExt].activated;
        certutil_extns[ext_CRLDistPts].activated =
				certutil.options[opt_AddCRLDistPtsExt].activated;
        certutil_extns[ext_NSCertType].activated =
            certutil.options[opt_AddCmdNSCertTypeExt].activated;
        if (!certutil_extns[ext_NSCertType].activated) {
            certutil_extns[ext_NSCertType].activated =
                certutil.options[opt_AddNSCertTypeExt].activated;
        } else {
            certutil_extns[ext_NSCertType].arg =
                certutil.options[opt_AddCmdNSCertTypeExt].arg;
        }

        certutil_extns[ext_extKeyUsage].activated =
            certutil.options[opt_AddCmdExtKeyUsageExt].activated;
        if (!certutil_extns[ext_extKeyUsage].activated) {
            certutil_extns[ext_extKeyUsage].activated =
                certutil.options[opt_AddExtKeyUsageExt].activated;
        } else {
            certutil_extns[ext_extKeyUsage].arg =
                certutil.options[opt_AddCmdExtKeyUsageExt].arg;
        }

        certutil_extns[ext_authInfoAcc].activated =
				certutil.options[opt_AddAuthInfoAccExt].activated;
        certutil_extns[ext_subjInfoAcc].activated =
				certutil.options[opt_AddSubjInfoAccExt].activated;
        certutil_extns[ext_certPolicies].activated =
				certutil.options[opt_AddCertPoliciesExt].activated;
        certutil_extns[ext_policyMappings].activated =
				certutil.options[opt_AddPolicyMapExt].activated;
        certutil_extns[ext_policyConstr].activated =
				certutil.options[opt_AddPolicyConstrExt].activated;
        certutil_extns[ext_inhibitAnyPolicy].activated =
				certutil.options[opt_AddInhibAnyExt].activated;
    }
    /*
     *  Certificate request
     */

    /*  Make a cert request (-R).  */
    if (certutil.commands[cmd_CertReq].activated) {
	rv = CertReq(privkey, pubkey, keytype, hashAlgTag, subject,
	             certutil.options[opt_PhoneNumber].arg,
	             certutil.options[opt_ASCIIForIO].activated,
		     certutil.options[opt_ExtendedEmailAddrs].arg,
		     certutil.options[opt_ExtendedDNSNames].arg,
                     certutil_extns,
                     outFile ? outFile : PR_STDOUT);
	if (rv) 
	    goto shutdown;
	privkey->wincx = &pwdata;
    }

    /*
     *  Certificate creation
     */

    /*  If making and adding a cert, create a cert request file first without
     *  any extensions, then load it with the command line extensions
     *  and output the cert to another file.
     */
    if (certutil.commands[cmd_CreateAndAddCert].activated) {
	static certutilExtnList nullextnlist = {{PR_FALSE, NULL}};
	rv = CertReq(privkey, pubkey, keytype, hashAlgTag, subject,
	             certutil.options[opt_PhoneNumber].arg,
	             certutil.options[opt_ASCIIForIO].activated,
		     NULL,
		     NULL,
                     nullextnlist,
                     outFile ? outFile : PR_STDOUT);
	if (rv) 
	    goto shutdown;
	privkey->wincx = &pwdata;
	PR_Close(outFile);
	inFile  = PR_Open(certreqfile, PR_RDONLY, 0);
	if (!inFile) {
	    PR_fprintf(PR_STDERR, "Failed to open file \"%s\" (%ld, %ld).\n",
                       certreqfile, PR_GetError(), PR_GetOSError());
	    rv = SECFailure;
	    goto shutdown;
	}
	outFile = PR_Open(certfile,
                          PR_RDWR | PR_CREATE_FILE | PR_TRUNCATE, 00660);
	if (!outFile) {
	    PR_fprintf(PR_STDERR, "Failed to open file \"%s\" (%ld, %ld).\n",
                       certfile, PR_GetError(), PR_GetOSError());
	    rv = SECFailure;
	    goto shutdown;
	}
    }

    /*  Create a certificate (-C or -S).  */
    if (certutil.commands[cmd_CreateAndAddCert].activated ||
         certutil.commands[cmd_CreateNewCert].activated) {
	rv = CreateCert(certHandle, slot,
	                certutil.options[opt_IssuerName].arg,
	                inFile, outFile, &privkey, &pwdata, hashAlgTag,
	                serialNumber, warpmonths, validityMonths,
		        certutil.options[opt_ExtendedEmailAddrs].arg,
		        certutil.options[opt_ExtendedDNSNames].arg,
	                certutil.options[opt_ASCIIForIO].activated,
	                certutil.options[opt_SelfSign].activated,
	                certutil_extns);
	if (rv) 
	    goto shutdown;
    }

    /* 
     * Adding a cert to the database (or slot)
     */
 
    if (certutil.commands[cmd_CreateAndAddCert].activated) { 
	PORT_Assert(inFile != PR_STDIN);
	PR_Close(inFile);
	PR_Close(outFile);
	inFile = PR_Open(certfile, PR_RDONLY, 0);
	if (!inFile) {
	    PR_fprintf(PR_STDERR, "Failed to open file \"%s\" (%ld, %ld).\n",
                       certfile, PR_GetError(), PR_GetOSError());
	    rv = SECFailure;
	    goto shutdown;
	}
    }

    /* -A -E or -S    Add the cert to the DB */
    if (certutil.commands[cmd_CreateAndAddCert].activated ||
         certutil.commands[cmd_AddCert].activated ||
	 certutil.commands[cmd_AddEmailCert].activated) {
	rv = AddCert(slot, certHandle, name, 
	             certutil.options[opt_Trust].arg,
	             inFile, 
	             certutil.options[opt_ASCIIForIO].activated,
	             certutil.commands[cmd_AddEmailCert].activated,&pwdata);
	if (rv) 
	    goto shutdown;
    }

    if (certutil.commands[cmd_CreateAndAddCert].activated) {
	PORT_Assert(inFile != PR_STDIN);
	PR_Close(inFile);
	PR_Delete(certfile);
	PR_Delete(certreqfile);
    }

shutdown:
    if (slot) {
	PK11_FreeSlot(slot);
    }
    if (privkey) {
	SECKEY_DestroyPrivateKey(privkey);
    }
    if (pubkey) {
	SECKEY_DestroyPublicKey(pubkey);
    }

    /* Open the batch command file.
     *
     * - If -B <command line> option is specified, the contents in the
     * command file will be interpreted as subsequent certutil
     * commands to be executed in the current certutil process
     * context after the current certutil command has been executed.
     * - Each line in the command file consists of the command
     * line arguments for certutil.
     * - The -d <configdir> option will be ignored if specified in the
     * command file.
     * - Quoting with double quote characters ("...") is supported
     * to allow white space in a command line argument.  The
     * double quote character cannot be escaped and quoting cannot
     * be nested in this version.
     * - each line in the batch file is limited to 512 characters
    */

    if ((SECSuccess == rv) && certutil.commands[cmd_Batch].activated) {
	FILE* batchFile = NULL;
        char nextcommand[512];
        if (!certutil.options[opt_InputFile].activated ||
            !certutil.options[opt_InputFile].arg) {
	    PR_fprintf(PR_STDERR,
	               "%s:  no batch input file specified.\n",
	               progName);
	    return 255;
        }
        batchFile = fopen(certutil.options[opt_InputFile].arg, "r");
        if (!batchFile) {
	    PR_fprintf(PR_STDERR,
	               "%s:  unable to open \"%s\" for reading (%ld, %ld).\n",
	               progName, certutil.options[opt_InputFile].arg,
	               PR_GetError(), PR_GetOSError());
	    return 255;
        }
        /* read and execute command-lines in a loop */
        while ( (SECSuccess == rv ) &&
                fgets(nextcommand, sizeof(nextcommand), batchFile)) {
            /* we now need to split the command into argc / argv format */
            char* commandline = PORT_Strdup(nextcommand);
            PRBool invalid = PR_FALSE;
            int newargc = 2;
            char* space = NULL;
            char* nextarg = NULL;
            char** newargv = NULL;
            char* crlf = PORT_Strrchr(commandline, '\n');
            if (crlf) {
                *crlf = '\0';
            }

            newargv = PORT_Alloc(sizeof(char*)*(newargc+1));
            newargv[0] = progName;
            newargv[1] = commandline;
            nextarg = commandline;
            while ((space = PORT_Strpbrk(nextarg, " \f\n\r\t\v")) ) {
                while (isspace(*space) ) {
                    *space = '\0';
                    space ++;
                }
                if (*space == '\0') {
                    break;
                } else if (*space != '\"') {
                    nextarg = space;
                } else {
                    char* closingquote = strchr(space+1, '\"');
                    if (closingquote) {
                        *closingquote = '\0';
                        space++;
                        nextarg = closingquote+1;
                    } else {
                        invalid = PR_TRUE;
                        nextarg = space;
                    }
                }
                newargc++;
                newargv = PORT_Realloc(newargv, sizeof(char*)*(newargc+1));
                newargv[newargc-1] = space;
            }
            newargv[newargc] = NULL;
            
            /* invoke next command */
            if (PR_TRUE == invalid) {
                PR_fprintf(PR_STDERR, "Missing closing quote in batch command :\n%s\nNot executed.\n",
                           nextcommand);
                rv = SECFailure;
            } else {
                if (0 != certutil_main(newargc, newargv, PR_FALSE) )
                    rv = SECFailure;
            }
            PORT_Free(newargv);
            PORT_Free(commandline);
        }
        fclose(batchFile);
    }

    if ((initialized == PR_TRUE) && NSS_Shutdown() != SECSuccess) {
        exit(1);
    }
    if (rv == SECSuccess) {
	return 0;
    } else {
	return 255;
    }
}

int
main(int argc, char **argv)
{
    int rv = certutil_main(argc, argv, PR_TRUE);
    PR_Cleanup();
    return rv;
}

