/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ /**************************************************************************** * Read in a cert chain from one or more files, and verify the chain for * some usage. * * * This code was modified from other code also kept in the NSS directory. ****************************************************************************/ #include #include #if defined(XP_UNIX) #include #endif #include "prerror.h" #include "pk11func.h" #include "seccomon.h" #include "secutil.h" #include "secmod.h" #include "secitem.h" #include "cert.h" #include "ocsp.h" /* #include */ /* #include */ /* #include */ /* #include */ #include "nspr.h" #include "plgetopt.h" #include "prio.h" #include "nss.h" /* #include "vfyutil.h" */ #define RD_BUF_SIZE (60 * 1024) int verbose; secuPWData pwdata = { PW_NONE, 0 }; static void Usage(const char *progName) { fprintf(stderr, "Usage: %s [options] [revocation options] certfile " "[[options] certfile] ...\n" "\tWhere options are:\n" "\t-a\t\t Following certfile is base64 encoded\n" "\t-b YYMMDDHHMMZ\t Validate date (default: now)\n" "\t-d directory\t Database directory\n" "\t-i number of consecutive verifications\n" "\t-f \t\t Enable cert fetching from AIA URL\n" "\t-o oid\t\t Set policy OID for cert validation(Format OID.1.2.3)\n" "\t-p \t\t Use PKIX Library to validate certificate by calling:\n" "\t\t\t * CERT_VerifyCertificate if specified once,\n" "\t\t\t * CERT_PKIXVerifyCert if specified twice and more.\n" "\t-r\t\t Following certfile is raw binary DER (default)\n" "\t-t\t\t Following cert is explicitly trusted (overrides db trust).\n" "\t-u usage \t 0=SSL client, 1=SSL server, 2=SSL StepUp, 3=SSL CA,\n" "\t\t\t 4=Email signer, 5=Email recipient, 6=Object signer,\n" "\t\t\t 9=ProtectedObjectSigner, 10=OCSP responder, 11=Any CA,\n" "\t\t\t 12=IPsec\n" "\t-T\t\t Trust both explicit trust anchors (-t) and the database.\n" "\t\t\t (Default is to only trust certificates marked -t, if there are any,\n" "\t\t\t or to trust the database if there are certificates marked -t.)\n" "\t-v\t\t Verbose mode. Prints root cert subject(double the\n" "\t\t\t argument for whole root cert info)\n" "\t-w password\t Database password.\n" "\t-W pwfile\t Password file.\n\n" "\tRevocation options for PKIX API(invoked with -pp options) is a\n" "\tcollection of the following flags:\n" "\t\t[-g type [-h flags] [-m type [-s flags]] ...] ...\n" "\tWhere:\n" "\t-g test type\t Sets status checking test type. Possible values\n" "\t\t\tare \"leaf\" or \"chain\"\n" "\t-h test flags\t Sets revocation flags for the test type it\n" "\t\t\tfollows. Possible flags: \"testLocalInfoFirst\" and\n" "\t\t\t\"requireFreshInfo\".\n" "\t-m method type\t Sets method type for the test type it follows.\n" "\t\t\tPossible types are \"crl\" and \"ocsp\".\n" "\t-s method flags\t Sets revocation flags for the method it follows.\n" "\t\t\tPossible types are \"doNotUse\", \"forbidFetching\",\n" "\t\t\t\"ignoreDefaultSrc\", \"requireInfo\" and \"failIfNoInfo\".\n", progName); exit(1); } /************************************************************************** ** ** Error and information routines. ** **************************************************************************/ void errWarn(char *function) { fprintf(stderr, "Error in function %s: %s\n", function, SECU_Strerror(PR_GetError())); } void exitErr(char *function) { errWarn(function); /* Exit gracefully. */ /* ignoring return value of NSS_Shutdown as code exits with 1 anyway*/ (void)NSS_Shutdown(); PR_Cleanup(); exit(1); } typedef struct certMemStr { struct certMemStr *next; CERTCertificate *cert; } certMem; certMem *theCerts; CERTCertList *trustedCertList; void rememberCert(CERTCertificate *cert, PRBool trusted) { if (trusted) { if (!trustedCertList) { trustedCertList = CERT_NewCertList(); } CERT_AddCertToListTail(trustedCertList, cert); } else { certMem *newCertMem = PORT_ZNew(certMem); if (newCertMem) { newCertMem->next = theCerts; newCertMem->cert = cert; theCerts = newCertMem; } } } void forgetCerts(void) { certMem *oldCertMem; while (theCerts) { oldCertMem = theCerts; theCerts = theCerts->next; CERT_DestroyCertificate(oldCertMem->cert); PORT_Free(oldCertMem); } if (trustedCertList) { CERT_DestroyCertList(trustedCertList); } } CERTCertificate * getCert(const char *name, PRBool isAscii, const char *progName) { CERTCertificate *cert; CERTCertDBHandle *defaultDB; PRFileDesc *fd; SECStatus rv; SECItem item = { 0, NULL, 0 }; defaultDB = CERT_GetDefaultCertDB(); /* First, let's try to find the cert in existing DB. */ cert = CERT_FindCertByNicknameOrEmailAddr(defaultDB, name); if (cert) { return cert; } /* Don't have a cert with name "name" in the DB. Try to * open a file with such name and get the cert from there.*/ fd = PR_Open(name, PR_RDONLY, 0777); if (!fd) { PRErrorCode err = PR_GetError(); fprintf(stderr, "open of %s failed, %d = %s\n", name, err, SECU_Strerror(err)); return cert; } rv = SECU_ReadDERFromFile(&item, fd, isAscii, PR_FALSE); PR_Close(fd); if (rv != SECSuccess) { fprintf(stderr, "%s: SECU_ReadDERFromFile failed\n", progName); return cert; } if (!item.len) { /* file was empty */ fprintf(stderr, "cert file %s was empty.\n", name); return cert; } cert = CERT_NewTempCertificate(defaultDB, &item, NULL /* nickname */, PR_FALSE /* isPerm */, PR_TRUE /* copyDER */); if (!cert) { PRErrorCode err = PR_GetError(); fprintf(stderr, "couldn't import %s, %d = %s\n", name, err, SECU_Strerror(err)); } PORT_Free(item.data); return cert; } #define REVCONFIG_TEST_UNDEFINED 0 #define REVCONFIG_TEST_LEAF 1 #define REVCONFIG_TEST_CHAIN 2 #define REVCONFIG_METHOD_CRL 1 #define REVCONFIG_METHOD_OCSP 2 #define REVCONFIG_TEST_LEAF_STR "leaf" #define REVCONFIG_TEST_CHAIN_STR "chain" #define REVCONFIG_METHOD_CRL_STR "crl" #define REVCONFIG_METHOD_OCSP_STR "ocsp" #define REVCONFIG_TEST_TESTLOCALINFOFIRST_STR "testLocalInfoFirst" #define REVCONFIG_TEST_REQUIREFRESHINFO_STR "requireFreshInfo" #define REVCONFIG_METHOD_DONOTUSEMETHOD_STR "doNotUse" #define REVCONFIG_METHOD_FORBIDNETWORKFETCHIN_STR "forbidFetching" #define REVCONFIG_METHOD_IGNOREDEFAULTSRC_STR "ignoreDefaultSrc" #define REVCONFIG_METHOD_REQUIREINFO_STR "requireInfo" #define REVCONFIG_METHOD_FAILIFNOINFO_STR "failIfNoInfo" #define REV_METHOD_INDEX_MAX 4 typedef struct RevMethodsStruct { unsigned int testType; char *testTypeStr; unsigned int testFlags; char *testFlagsStr; unsigned int methodType; char *methodTypeStr; unsigned int methodFlags; char *methodFlagsStr; } RevMethods; RevMethods revMethodsData[REV_METHOD_INDEX_MAX]; SECStatus parseRevMethodsAndFlags() { int i; unsigned int testType = 0; for (i = 0; i < REV_METHOD_INDEX_MAX; i++) { /* testType */ if (revMethodsData[i].testTypeStr) { char *typeStr = revMethodsData[i].testTypeStr; testType = 0; if (!PORT_Strcmp(typeStr, REVCONFIG_TEST_LEAF_STR)) { testType = REVCONFIG_TEST_LEAF; } else if (!PORT_Strcmp(typeStr, REVCONFIG_TEST_CHAIN_STR)) { testType = REVCONFIG_TEST_CHAIN; } } if (!testType) { return SECFailure; } revMethodsData[i].testType = testType; /* testFlags */ if (revMethodsData[i].testFlagsStr) { char *flagStr = revMethodsData[i].testFlagsStr; unsigned int testFlags = 0; if (PORT_Strstr(flagStr, REVCONFIG_TEST_TESTLOCALINFOFIRST_STR)) { testFlags |= CERT_REV_MI_TEST_ALL_LOCAL_INFORMATION_FIRST; } if (PORT_Strstr(flagStr, REVCONFIG_TEST_REQUIREFRESHINFO_STR)) { testFlags |= CERT_REV_MI_REQUIRE_SOME_FRESH_INFO_AVAILABLE; } revMethodsData[i].testFlags = testFlags; } /* method type */ if (revMethodsData[i].methodTypeStr) { char *methodStr = revMethodsData[i].methodTypeStr; unsigned int methodType = 0; if (!PORT_Strcmp(methodStr, REVCONFIG_METHOD_CRL_STR)) { methodType = REVCONFIG_METHOD_CRL; } else if (!PORT_Strcmp(methodStr, REVCONFIG_METHOD_OCSP_STR)) { methodType = REVCONFIG_METHOD_OCSP; } if (!methodType) { return SECFailure; } revMethodsData[i].methodType = methodType; } if (!revMethodsData[i].methodType) { revMethodsData[i].testType = REVCONFIG_TEST_UNDEFINED; continue; } /* method flags */ if (revMethodsData[i].methodFlagsStr) { char *flagStr = revMethodsData[i].methodFlagsStr; unsigned int methodFlags = 0; if (!PORT_Strstr(flagStr, REVCONFIG_METHOD_DONOTUSEMETHOD_STR)) { methodFlags |= CERT_REV_M_TEST_USING_THIS_METHOD; } if (PORT_Strstr(flagStr, REVCONFIG_METHOD_FORBIDNETWORKFETCHIN_STR)) { methodFlags |= CERT_REV_M_FORBID_NETWORK_FETCHING; } if (PORT_Strstr(flagStr, REVCONFIG_METHOD_IGNOREDEFAULTSRC_STR)) { methodFlags |= CERT_REV_M_IGNORE_IMPLICIT_DEFAULT_SOURCE; } if (PORT_Strstr(flagStr, REVCONFIG_METHOD_REQUIREINFO_STR)) { methodFlags |= CERT_REV_M_REQUIRE_INFO_ON_MISSING_SOURCE; } if (PORT_Strstr(flagStr, REVCONFIG_METHOD_FAILIFNOINFO_STR)) { methodFlags |= CERT_REV_M_FAIL_ON_MISSING_FRESH_INFO; } revMethodsData[i].methodFlags = methodFlags; } else { revMethodsData[i].methodFlags |= CERT_REV_M_TEST_USING_THIS_METHOD; } } return SECSuccess; } SECStatus configureRevocationParams(CERTRevocationFlags *flags) { int i; unsigned int testType = REVCONFIG_TEST_UNDEFINED; static CERTRevocationTests *revTests = NULL; PRUint64 *revFlags = NULL; for (i = 0; i < REV_METHOD_INDEX_MAX; i++) { if (revMethodsData[i].testType == REVCONFIG_TEST_UNDEFINED) { continue; } if (revMethodsData[i].testType != testType) { testType = revMethodsData[i].testType; if (testType == REVCONFIG_TEST_CHAIN) { revTests = &flags->chainTests; } else { revTests = &flags->leafTests; } revTests->number_of_preferred_methods = 0; revTests->preferred_methods = 0; revFlags = revTests->cert_rev_flags_per_method; } /* Set the number of the methods independently to the max number of * methods. If method flags are not set it will be ignored due to * default DO_NOT_USE flag. */ revTests->number_of_defined_methods = cert_revocation_method_count; revTests->cert_rev_method_independent_flags |= revMethodsData[i].testFlags; if (revMethodsData[i].methodType == REVCONFIG_METHOD_CRL) { revFlags[cert_revocation_method_crl] = revMethodsData[i].methodFlags; } else if (revMethodsData[i].methodType == REVCONFIG_METHOD_OCSP) { revFlags[cert_revocation_method_ocsp] = revMethodsData[i].methodFlags; } } return SECSuccess; } void freeRevocationMethodData() { int i = 0; for (; i < REV_METHOD_INDEX_MAX; i++) { if (revMethodsData[i].testTypeStr) { PORT_Free(revMethodsData[i].testTypeStr); } if (revMethodsData[i].testFlagsStr) { PORT_Free(revMethodsData[i].testFlagsStr); } if (revMethodsData[i].methodTypeStr) { PORT_Free(revMethodsData[i].methodTypeStr); } if (revMethodsData[i].methodFlagsStr) { PORT_Free(revMethodsData[i].methodFlagsStr); } } } PRBool isOCSPEnabled() { int i; for (i = 0; i < REV_METHOD_INDEX_MAX; i++) { if (revMethodsData[i].methodType == REVCONFIG_METHOD_OCSP) { return PR_TRUE; } } return PR_FALSE; } int main(int argc, char *argv[], char *envp[]) { char *certDir = NULL; char *progName = NULL; char *oidStr = NULL; CERTCertificate *cert; CERTCertificate *firstCert = NULL; CERTCertificate *issuerCert = NULL; CERTCertDBHandle *defaultDB = NULL; PRBool isAscii = PR_FALSE; PRBool trusted = PR_FALSE; SECStatus secStatus; SECCertificateUsage certUsage = certificateUsageSSLServer; PLOptState *optstate; PRTime time = 0; PLOptStatus status; int usePkix = 0; int rv = 1; int usage; CERTVerifyLog log; CERTCertList *builtChain = NULL; PRBool certFetching = PR_FALSE; int revDataIndex = 0; PRBool ocsp_fetchingFailureIsAFailure = PR_TRUE; PRBool useDefaultRevFlags = PR_TRUE; PRBool onlyTrustAnchors = PR_TRUE; int vfyCounts = 1; PR_Init(PR_SYSTEM_THREAD, PR_PRIORITY_NORMAL, 1); progName = PL_strdup(argv[0]); optstate = PL_CreateOptState(argc, argv, "ab:c:d:efg:h:i:m:o:prs:tTu:vw:W:"); while ((status = PL_GetNextOpt(optstate)) == PL_OPT_OK) { switch (optstate->option) { case 0: /* positional parameter */ goto breakout; case 'a': isAscii = PR_TRUE; break; case 'b': secStatus = DER_AsciiToTime(&time, optstate->value); if (secStatus != SECSuccess) Usage(progName); break; case 'd': certDir = PL_strdup(optstate->value); break; case 'e': ocsp_fetchingFailureIsAFailure = PR_FALSE; break; case 'f': certFetching = PR_TRUE; break; case 'g': if (revMethodsData[revDataIndex].testTypeStr || revMethodsData[revDataIndex].methodTypeStr) { revDataIndex += 1; if (revDataIndex == REV_METHOD_INDEX_MAX) { fprintf(stderr, "Invalid revocation configuration" "specified.\n"); secStatus = SECFailure; break; } } useDefaultRevFlags = PR_FALSE; revMethodsData[revDataIndex].testTypeStr = PL_strdup(optstate->value); break; case 'h': revMethodsData[revDataIndex].testFlagsStr = PL_strdup(optstate->value); break; case 'i': vfyCounts = PORT_Atoi(optstate->value); break; break; case 'm': if (revMethodsData[revDataIndex].methodTypeStr) { revDataIndex += 1; if (revDataIndex == REV_METHOD_INDEX_MAX) { fprintf(stderr, "Invalid revocation configuration" "specified.\n"); secStatus = SECFailure; break; } } useDefaultRevFlags = PR_FALSE; revMethodsData[revDataIndex].methodTypeStr = PL_strdup(optstate->value); break; case 'o': oidStr = PL_strdup(optstate->value); break; case 'p': usePkix += 1; break; case 'r': isAscii = PR_FALSE; break; case 's': revMethodsData[revDataIndex].methodFlagsStr = PL_strdup(optstate->value); break; case 't': trusted = PR_TRUE; break; case 'T': onlyTrustAnchors = PR_FALSE; break; case 'u': usage = PORT_Atoi(optstate->value); if (usage < 0 || usage > 62) Usage(progName); certUsage = ((SECCertificateUsage)1) << usage; if (certUsage > certificateUsageHighest) Usage(progName); break; case 'w': pwdata.source = PW_PLAINTEXT; pwdata.data = PORT_Strdup(optstate->value); break; case 'W': pwdata.source = PW_FROMFILE; pwdata.data = PORT_Strdup(optstate->value); break; case 'v': verbose++; break; default: Usage(progName); break; } } breakout: if (status != PL_OPT_OK) Usage(progName); if (usePkix < 2) { if (oidStr) { fprintf(stderr, "Policy oid(-o) can be used only with" " CERT_PKIXVerifyCert(-pp) function.\n"); Usage(progName); } if (trusted) { fprintf(stderr, "Cert trust flag can be used only with" " CERT_PKIXVerifyCert(-pp) function.\n"); Usage(progName); } if (!onlyTrustAnchors) { fprintf(stderr, "Cert trust anchor exclusiveness can be" " used only with CERT_PKIXVerifyCert(-pp)" " function.\n"); } } if (!useDefaultRevFlags && parseRevMethodsAndFlags()) { fprintf(stderr, "Invalid revocation configuration specified.\n"); goto punt; } /* Set our password function callback. */ PK11_SetPasswordFunc(SECU_GetModulePassword); /* Initialize the NSS libraries. */ if (certDir) { secStatus = NSS_Init(certDir); } else { secStatus = NSS_NoDB_Init(NULL); /* load the builtins */ SECMOD_AddNewModule("Builtins", DLL_PREFIX "nssckbi." DLL_SUFFIX, 0, 0); } if (secStatus != SECSuccess) { exitErr("NSS_Init"); } SECU_RegisterDynamicOids(); if (isOCSPEnabled()) { CERT_EnableOCSPChecking(CERT_GetDefaultCertDB()); CERT_DisableOCSPDefaultResponder(CERT_GetDefaultCertDB()); if (!ocsp_fetchingFailureIsAFailure) { CERT_SetOCSPFailureMode(ocspMode_FailureIsNotAVerificationFailure); } } while (status == PL_OPT_OK) { switch (optstate->option) { default: Usage(progName); break; case 'a': isAscii = PR_TRUE; break; case 'r': isAscii = PR_FALSE; break; case 't': trusted = PR_TRUE; break; case 0: /* positional parameter */ if (usePkix < 2 && trusted) { fprintf(stderr, "Cert trust flag can be used only with" " CERT_PKIXVerifyCert(-pp) function.\n"); Usage(progName); } cert = getCert(optstate->value, isAscii, progName); if (!cert) goto punt; rememberCert(cert, trusted); if (!firstCert) firstCert = cert; trusted = PR_FALSE; } status = PL_GetNextOpt(optstate); } PL_DestroyOptState(optstate); if (status == PL_OPT_BAD || !firstCert) Usage(progName); /* Initialize log structure */ log.arena = PORT_NewArena(512); log.head = log.tail = NULL; log.count = 0; do { if (usePkix < 2) { /* NOW, verify the cert chain. */ if (usePkix) { /* Use old API with libpkix validation lib */ CERT_SetUsePKIXForValidation(PR_TRUE); } if (!time) time = PR_Now(); defaultDB = CERT_GetDefaultCertDB(); secStatus = CERT_VerifyCertificate(defaultDB, firstCert, PR_TRUE /* check sig */, certUsage, time, &pwdata, /* wincx */ &log, /* error log */ NULL); /* returned usages */ } else do { static CERTValOutParam cvout[4]; static CERTValInParam cvin[7]; SECOidTag oidTag; int inParamIndex = 0; static PRUint64 revFlagsLeaf[2]; static PRUint64 revFlagsChain[2]; static CERTRevocationFlags rev; if (oidStr) { PLArenaPool *arena; SECOidData od; memset(&od, 0, sizeof od); od.offset = SEC_OID_UNKNOWN; od.desc = "User Defined Policy OID"; od.mechanism = CKM_INVALID_MECHANISM; od.supportedExtension = INVALID_CERT_EXTENSION; arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); if (!arena) { fprintf(stderr, "out of memory"); goto punt; } secStatus = SEC_StringToOID(arena, &od.oid, oidStr, 0); if (secStatus != SECSuccess) { PORT_FreeArena(arena, PR_FALSE); fprintf(stderr, "Can not encode oid: %s(%s)\n", oidStr, SECU_Strerror(PORT_GetError())); break; } oidTag = SECOID_AddEntry(&od); PORT_FreeArena(arena, PR_FALSE); if (oidTag == SEC_OID_UNKNOWN) { fprintf(stderr, "Can not add new oid to the dynamic " "table: %s\n", oidStr); secStatus = SECFailure; break; } cvin[inParamIndex].type = cert_pi_policyOID; cvin[inParamIndex].value.arraySize = 1; cvin[inParamIndex].value.array.oids = &oidTag; inParamIndex++; } if (trustedCertList) { cvin[inParamIndex].type = cert_pi_trustAnchors; cvin[inParamIndex].value.pointer.chain = trustedCertList; inParamIndex++; } cvin[inParamIndex].type = cert_pi_useAIACertFetch; cvin[inParamIndex].value.scalar.b = certFetching; inParamIndex++; rev.leafTests.cert_rev_flags_per_method = revFlagsLeaf; rev.chainTests.cert_rev_flags_per_method = revFlagsChain; secStatus = configureRevocationParams(&rev); if (secStatus) { fprintf(stderr, "Can not config revocation parameters "); break; } cvin[inParamIndex].type = cert_pi_revocationFlags; cvin[inParamIndex].value.pointer.revocation = &rev; inParamIndex++; if (time) { cvin[inParamIndex].type = cert_pi_date; cvin[inParamIndex].value.scalar.time = time; inParamIndex++; } if (!onlyTrustAnchors) { cvin[inParamIndex].type = cert_pi_useOnlyTrustAnchors; cvin[inParamIndex].value.scalar.b = onlyTrustAnchors; inParamIndex++; } cvin[inParamIndex].type = cert_pi_end; cvout[0].type = cert_po_trustAnchor; cvout[0].value.pointer.cert = NULL; cvout[1].type = cert_po_certList; cvout[1].value.pointer.chain = NULL; /* setting pointer to CERTVerifyLog. Initialized structure * will be used CERT_PKIXVerifyCert */ cvout[2].type = cert_po_errorLog; cvout[2].value.pointer.log = &log; cvout[3].type = cert_po_end; secStatus = CERT_PKIXVerifyCert(firstCert, certUsage, cvin, cvout, &pwdata); if (secStatus != SECSuccess) { break; } issuerCert = cvout[0].value.pointer.cert; builtChain = cvout[1].value.pointer.chain; } while (0); /* Display validation results */ if (secStatus != SECSuccess || log.count > 0) { CERTVerifyLogNode *node = NULL; fprintf(stderr, "Chain is bad!\n"); SECU_displayVerifyLog(stderr, &log, verbose); /* Have cert refs in the log only in case of failure. * Destroy them. */ for (node = log.head; node; node = node->next) { if (node->cert) CERT_DestroyCertificate(node->cert); } log.head = log.tail = NULL; log.count = 0; rv = 1; } else { fprintf(stderr, "Chain is good!\n"); if (issuerCert) { if (verbose > 1) { rv = SEC_PrintCertificateAndTrust(issuerCert, "Root Certificate", NULL); if (rv != SECSuccess) { SECU_PrintError(progName, "problem printing certificate"); } } else if (verbose > 0) { SECU_PrintName(stdout, &issuerCert->subject, "Root " "Certificate Subject:", 0); } CERT_DestroyCertificate(issuerCert); } if (builtChain) { CERTCertListNode *node; int count = 0; char buff[256]; if (verbose) { for (node = CERT_LIST_HEAD(builtChain); !CERT_LIST_END(node, builtChain); node = CERT_LIST_NEXT(node), count++) { snprintf(buff, sizeof(buff), "Certificate %d Subject", count + 1); SECU_PrintName(stdout, &node->cert->subject, buff, 0); } } CERT_DestroyCertList(builtChain); } rv = 0; } } while (--vfyCounts > 0); /* Need to destroy CERTVerifyLog arena at the end */ PORT_FreeArena(log.arena, PR_FALSE); punt: forgetCerts(); if (NSS_Shutdown() != SECSuccess) { SECU_PrintError(progName, "NSS_Shutdown"); rv = 1; } PORT_Free(progName); PORT_Free(certDir); PORT_Free(oidStr); freeRevocationMethodData(); if (pwdata.data) { PORT_Free(pwdata.data); } PL_ArenaFinish(); PR_Cleanup(); return rv; }