Source code

Revision control

Copy as Markdown

Other Tools

/* 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/. */
/*
** dbck.c
**
** utility for fixing corrupt cert databases
**
*/
#include <stdio.h>
#include <string.h>
#include "secutil.h"
#include "cdbhdl.h"
#include "certdb.h"
#include "cert.h"
#include "nspr.h"
#include "prtypes.h"
#include "prtime.h"
#include "prlong.h"
#include "pcert.h"
#include "nss.h"
static char *progName;
/* placeholders for pointer error types */
static void *WrongEntry;
static void *NoNickname;
static void *NoSMime;
typedef enum {
/* 0*/ NoSubjectForCert = 0,
/* 1*/ SubjectHasNoKeyForCert,
/* 2*/ NoNicknameOrSMimeForSubject,
/* 3*/ WrongNicknameForSubject,
/* 4*/ NoNicknameEntry,
/* 5*/ WrongSMimeForSubject,
/* 6*/ NoSMimeEntry,
/* 7*/ NoSubjectForNickname,
/* 8*/ NoSubjectForSMime,
/* 9*/ NicknameAndSMimeEntries,
NUM_ERROR_TYPES
} dbErrorType;
static char *dbErrorString[NUM_ERROR_TYPES] = {
/* 0*/ "<CERT ENTRY>\nDid not find a subject entry for this certificate.",
/* 1*/ "<SUBJECT ENTRY>\nSubject has certKey which is not in db.",
/* 2*/ "<SUBJECT ENTRY>\nSubject does not have a nickname or email address.",
/* 3*/ "<SUBJECT ENTRY>\nUsing this subject's nickname, found a nickname entry for a different subject.",
/* 4*/ "<SUBJECT ENTRY>\nDid not find a nickname entry for this subject.",
/* 5*/ "<SUBJECT ENTRY>\nUsing this subject's email, found an S/MIME entry for a different subject.",
/* 6*/ "<SUBJECT ENTRY>\nDid not find an S/MIME entry for this subject.",
/* 7*/ "<NICKNAME ENTRY>\nDid not find a subject entry for this nickname.",
/* 8*/ "<S/MIME ENTRY>\nDid not find a subject entry for this S/MIME profile.",
};
static char *errResult[NUM_ERROR_TYPES] = {
"Certificate entries that had no subject entry.",
"Subject entries with no corresponding Certificate entries.",
"Subject entries that had no nickname or S/MIME entries.",
"Redundant nicknames (subjects with the same nickname).",
"Subject entries that had no nickname entry.",
"Redundant email addresses (subjects with the same email address).",
"Subject entries that had no S/MIME entry.",
"Nickname entries that had no subject entry.",
"S/MIME entries that had no subject entry.",
"Subject entries with BOTH nickname and S/MIME entries."
};
enum {
GOBOTH = 0,
GORIGHT,
GOLEFT
};
typedef struct
{
PRBool verbose;
PRBool dograph;
PRFileDesc *out;
PRFileDesc *graphfile;
int dbErrors[NUM_ERROR_TYPES];
} dbDebugInfo;
struct certDBEntryListNodeStr {
PRCList link;
certDBEntry entry;
void *appData;
};
typedef struct certDBEntryListNodeStr certDBEntryListNode;
/*
* A list node for a cert db entry. The index is a unique identifier
* to use for creating generic maps of a db. This struct handles
* the cert, nickname, and smime db entry types, as all three have a
* single handle to a subject entry.
* This structure is pointed to by certDBEntryListNode->appData.
*/
typedef struct
{
PLArenaPool *arena;
int index;
certDBEntryListNode *pSubject;
} certDBEntryMap;
/*
* Subject entry is special case, it has bidirectional handles. One
* subject entry can point to several certs (using the same DN), and
* a nickname and/or smime entry.
* This structure is pointed to by certDBEntryListNode->appData.
*/
typedef struct
{
PLArenaPool *arena;
int index;
int numCerts;
certDBEntryListNode **pCerts;
certDBEntryListNode *pNickname;
certDBEntryListNode *pSMime;
} certDBSubjectEntryMap;
/*
* A map of a certdb.
*/
typedef struct
{
int numCerts;
int numSubjects;
int numNicknames;
int numSMime;
int numRevocation;
certDBEntryListNode certs; /* pointer to head of cert list */
certDBEntryListNode subjects; /* pointer to head of subject list */
certDBEntryListNode nicknames; /* pointer to head of nickname list */
certDBEntryListNode smime; /* pointer to head of smime list */
certDBEntryListNode revocation; /* pointer to head of revocation list */
} certDBArray;
/* Cast list to the base element, a certDBEntryListNode. */
#define LISTNODE_CAST(node) \
((certDBEntryListNode *)(node))
static void
Usage(char *progName)
{
#define FPS fprintf(stderr,
FPS "Type %s -H for more detailed descriptions\n", progName);
FPS "Usage: %s -D [-d certdir] [-m] [-v [-f dumpfile]]\n",
progName);
#ifdef DORECOVER
FPS " %s -R -o newdbname [-d certdir] [-aprsx] [-v [-f dumpfile]]\n",
progName);
#endif
exit(-1);
}
static void
LongUsage(char *progName)
{
FPS "%-15s Display this help message.\n",
"-H");
FPS "%-15s Dump analysis. No changes will be made to the database.\n",
"-D");
FPS "%-15s Cert database directory (default is ~/.netscape)\n",
" -d certdir");
FPS "%-15s Put database graph in ./mailfile (default is stdout).\n",
" -m");
FPS "%-15s Verbose mode. Dumps the entire contents of your cert8.db.\n",
" -v");
FPS "%-15s File to dump verbose output into. (default is stdout)\n",
" -f dumpfile");
#ifdef DORECOVER
FPS "%-15s Repair the database. The program will look for broken\n",
"-R");
FPS "%-15s dependencies between subject entries and certificates,\n",
"");
FPS "%-15s between nickname entries and subjects, and between SMIME\n",
"");
FPS "%-15s profiles and subjects. Any duplicate entries will be\n",
"");
FPS "%-15s removed, any missing entries will be created.\n",
"");
FPS "%-15s File to store new database in (default is new_cert8.db)\n",
" -o newdbname");
FPS "%-15s Cert database directory (default is ~/.netscape)\n",
" -d certdir");
FPS "%-15s Prompt before removing any certificates.\n",
" -p");
FPS "%-15s Keep all possible certificates. Only remove certificates\n",
" -a");
FPS "%-15s which prevent creation of a consistent database. Thus any\n",
"");
FPS "%-15s expired or redundant entries will be kept.\n",
"");
FPS "%-15s Keep redundant nickname/email entries. It is possible\n",
" -r");
FPS "%-15s only one such entry will be usable.\n",
"");
FPS "%-15s Don't require an S/MIME profile in order to keep an S/MIME\n",
" -s");
FPS "%-15s cert. An empty profile will be created.\n",
"");
FPS "%-15s Keep expired certificates.\n",
" -x");
FPS "%-15s Verbose mode - report all activity while recovering db.\n",
" -v");
FPS "%-15s File to dump verbose output into.\n",
" -f dumpfile");
FPS "\n");
#endif
exit(-1);
#undef FPS
}
/*******************************************************************
*
* Functions for dbck.
*
******************************************************************/
void
printHexString(PRFileDesc *out, SECItem *hexval)
{
unsigned int i;
for (i = 0; i < hexval->len; i++) {
if (i != hexval->len - 1) {
PR_fprintf(out, "%02x:", hexval->data[i]);
} else {
PR_fprintf(out, "%02x", hexval->data[i]);
}
}
PR_fprintf(out, "\n");
}
SECStatus
dumpCertificate(CERTCertificate *cert, int num, PRFileDesc *outfile)
{
int userCert = 0;
CERTCertTrust *trust = cert->trust;
userCert = (SEC_GET_TRUST_FLAGS(trust, trustSSL) & CERTDB_USER) ||
(SEC_GET_TRUST_FLAGS(trust, trustEmail) & CERTDB_USER) ||
(SEC_GET_TRUST_FLAGS(trust, trustObjectSigning) & CERTDB_USER);
if (num >= 0) {
PR_fprintf(outfile, "Certificate: %3d\n", num);
} else {
PR_fprintf(outfile, "Certificate:\n");
}
PR_fprintf(outfile, "----------------\n");
if (userCert)
PR_fprintf(outfile, "(User Cert)\n");
PR_fprintf(outfile, "## SUBJECT: %s\n", cert->subjectName);
PR_fprintf(outfile, "## ISSUER: %s\n", cert->issuerName);
PR_fprintf(outfile, "## SERIAL NUMBER: ");
printHexString(outfile, &cert->serialNumber);
{ /* XXX should be separate function. */
PRTime timeBefore, timeAfter;
PRExplodedTime beforePrintable, afterPrintable;
char *beforestr, *afterstr;
DER_DecodeTimeChoice(&timeBefore, &cert->validity.notBefore);
DER_DecodeTimeChoice(&timeAfter, &cert->validity.notAfter);
PR_ExplodeTime(timeBefore, PR_GMTParameters, &beforePrintable);
PR_ExplodeTime(timeAfter, PR_GMTParameters, &afterPrintable);
beforestr = PORT_Alloc(100);
afterstr = PORT_Alloc(100);
PR_FormatTime(beforestr, 100, "%a %b %d %H:%M:%S %Y", &beforePrintable);
PR_FormatTime(afterstr, 100, "%a %b %d %H:%M:%S %Y", &afterPrintable);
PR_fprintf(outfile, "## VALIDITY: %s to %s\n", beforestr, afterstr);
}
PR_fprintf(outfile, "\n");
return SECSuccess;
}
SECStatus
dumpCertEntry(certDBEntryCert *entry, int num, PRFileDesc *outfile)
{
#if 0
NSSLOWCERTCertificate *cert;
/* should we check for existing duplicates? */
cert = nsslowcert_DecodeDERCertificate(&entry->cert.derCert,
entry->cert.nickname);
#else
CERTCertificate *cert;
cert = CERT_DecodeDERCertificate(&entry->derCert, PR_FALSE, NULL);
#endif
if (!cert) {
fprintf(stderr, "Failed to decode certificate.\n");
return SECFailure;
}
cert->trust = (CERTCertTrust *)&entry->trust;
dumpCertificate(cert, num, outfile);
CERT_DestroyCertificate(cert);
return SECSuccess;
}
SECStatus
dumpSubjectEntry(certDBEntrySubject *entry, int num, PRFileDesc *outfile)
{
char *subjectName = CERT_DerNameToAscii(&entry->derSubject);
PR_fprintf(outfile, "Subject: %3d\n", num);
PR_fprintf(outfile, "------------\n");
PR_fprintf(outfile, "## %s\n", subjectName);
if (entry->nickname)
PR_fprintf(outfile, "## Subject nickname: %s\n", entry->nickname);
if (entry->emailAddrs) {
unsigned int n;
for (n = 0; n < entry->nemailAddrs && entry->emailAddrs[n]; ++n) {
char *emailAddr = entry->emailAddrs[n];
if (emailAddr[0]) {
PR_fprintf(outfile, "## Subject email address: %s\n",
emailAddr);
}
}
}
PR_fprintf(outfile, "## This subject has %d cert(s).\n", entry->ncerts);
PR_fprintf(outfile, "\n");
PORT_Free(subjectName);
return SECSuccess;
}
SECStatus
dumpNicknameEntry(certDBEntryNickname *entry, int num, PRFileDesc *outfile)
{
PR_fprintf(outfile, "Nickname: %3d\n", num);
PR_fprintf(outfile, "-------------\n");
PR_fprintf(outfile, "## \"%s\"\n\n", entry->nickname);
return SECSuccess;
}
SECStatus
dumpSMimeEntry(certDBEntrySMime *entry, int num, PRFileDesc *outfile)
{
PR_fprintf(outfile, "S/MIME Profile: %3d\n", num);
PR_fprintf(outfile, "-------------------\n");
PR_fprintf(outfile, "## \"%s\"\n", entry->emailAddr);
#ifdef OLDWAY
PR_fprintf(outfile, "## OPTIONS: ");
printHexString(outfile, &entry->smimeOptions);
PR_fprintf(outfile, "## TIMESTAMP: ");
printHexString(outfile, &entry->optionsDate);
#else
SECU_PrintAny(stdout, &entry->smimeOptions, "## OPTIONS ", 0);
fflush(stdout);
if (entry->optionsDate.len && entry->optionsDate.data)
PR_fprintf(outfile, "## TIMESTAMP: %.*s\n",
entry->optionsDate.len, entry->optionsDate.data);
#endif
PR_fprintf(outfile, "\n");
return SECSuccess;
}
SECStatus
mapCertEntries(certDBArray *dbArray)
{
certDBEntryCert *certEntry;
certDBEntrySubject *subjectEntry;
certDBEntryListNode *certNode, *subjNode;
certDBSubjectEntryMap *smap;
certDBEntryMap *map;
PLArenaPool *tmparena;
SECItem derSubject;
SECItem certKey;
PRCList *cElem, *sElem;
/* Arena for decoded entries */
tmparena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
if (tmparena == NULL) {
PORT_SetError(SEC_ERROR_NO_MEMORY);
return SECFailure;
}
/* Iterate over cert entries and map them to subject entries.
* NOTE: mapSubjectEntries must be called first to alloc memory
* for array of subject->cert map.
*/
for (cElem = PR_LIST_HEAD(&dbArray->certs.link);
cElem != &dbArray->certs.link; cElem = PR_NEXT_LINK(cElem)) {
certNode = LISTNODE_CAST(cElem);
certEntry = (certDBEntryCert *)&certNode->entry;
map = (certDBEntryMap *)certNode->appData;
CERT_NameFromDERCert(&certEntry->derCert, &derSubject);
CERT_KeyFromDERCert(tmparena, &certEntry->derCert, &certKey);
/* Loop over found subjects for cert's DN. */
for (sElem = PR_LIST_HEAD(&dbArray->subjects.link);
sElem != &dbArray->subjects.link; sElem = PR_NEXT_LINK(sElem)) {
subjNode = LISTNODE_CAST(sElem);
subjectEntry = (certDBEntrySubject *)&subjNode->entry;
if (SECITEM_ItemsAreEqual(&derSubject, &subjectEntry->derSubject)) {
unsigned int i;
/* Found matching subject name, create link. */
map->pSubject = subjNode;
/* Make sure subject entry has cert's key. */
for (i = 0; i < subjectEntry->ncerts; i++) {
if (SECITEM_ItemsAreEqual(&certKey,
&subjectEntry->certKeys[i])) {
/* Found matching cert key. */
smap = (certDBSubjectEntryMap *)subjNode->appData;
smap->pCerts[i] = certNode;
break;
}
}
}
}
}
PORT_FreeArena(tmparena, PR_FALSE);
return SECSuccess;
}
SECStatus
mapSubjectEntries(certDBArray *dbArray)
{
certDBEntrySubject *subjectEntry;
certDBEntryListNode *subjNode;
certDBSubjectEntryMap *subjMap;
PRCList *sElem;
for (sElem = PR_LIST_HEAD(&dbArray->subjects.link);
sElem != &dbArray->subjects.link; sElem = PR_NEXT_LINK(sElem)) {
/* Iterate over subject entries and map subjects to nickname
* and smime entries. The cert<->subject map will be handled
* by a subsequent call to mapCertEntries.
*/
subjNode = LISTNODE_CAST(sElem);
subjectEntry = (certDBEntrySubject *)&subjNode->entry;
subjMap = (certDBSubjectEntryMap *)subjNode->appData;
/* need to alloc memory here for array of matching certs. */
subjMap->pCerts = PORT_ArenaAlloc(subjMap->arena,
subjectEntry->ncerts * sizeof(int));
subjMap->numCerts = subjectEntry->ncerts;
subjMap->pNickname = NoNickname;
subjMap->pSMime = NoSMime;
if (subjectEntry->nickname) {
/* Subject should have a nickname entry, so create a link. */
PRCList *nElem;
for (nElem = PR_LIST_HEAD(&dbArray->nicknames.link);
nElem != &dbArray->nicknames.link;
nElem = PR_NEXT_LINK(nElem)) {
certDBEntryListNode *nickNode;
certDBEntryNickname *nicknameEntry;
/* Look for subject's nickname in nickname entries. */
nickNode = LISTNODE_CAST(nElem);
nicknameEntry = (certDBEntryNickname *)&nickNode->entry;
if (PL_strcmp(subjectEntry->nickname,
nicknameEntry->nickname) == 0) {
/* Found a nickname entry for subject's nickname. */
if (SECITEM_ItemsAreEqual(&subjectEntry->derSubject,
&nicknameEntry->subjectName)) {
certDBEntryMap *nickMap;
nickMap = (certDBEntryMap *)nickNode->appData;
/* Nickname and subject match. */
subjMap->pNickname = nickNode;
nickMap->pSubject = subjNode;
} else if (subjMap->pNickname == NoNickname) {
/* Nickname entry found is for diff. subject. */
subjMap->pNickname = WrongEntry;
}
}
}
}
if (subjectEntry->emailAddrs) {
unsigned int n;
for (n = 0; n < subjectEntry->nemailAddrs &&
subjectEntry->emailAddrs[n];
++n) {
char *emailAddr = subjectEntry->emailAddrs[n];
if (emailAddr[0]) {
PRCList *mElem;
/* Subject should have an smime entry, so create a link. */
for (mElem = PR_LIST_HEAD(&dbArray->smime.link);
mElem != &dbArray->smime.link;
mElem = PR_NEXT_LINK(mElem)) {
certDBEntryListNode *smimeNode;
certDBEntrySMime *smimeEntry;
/* Look for subject's email in S/MIME entries. */
smimeNode = LISTNODE_CAST(mElem);
smimeEntry = (certDBEntrySMime *)&smimeNode->entry;
if (PL_strcmp(emailAddr,
smimeEntry->emailAddr) == 0) {
/* Found a S/MIME entry for subject's email. */
if (SECITEM_ItemsAreEqual(
&subjectEntry->derSubject,
&smimeEntry->subjectName)) {
certDBEntryMap *smimeMap;
/* S/MIME entry and subject match. */
subjMap->pSMime = smimeNode;
smimeMap = (certDBEntryMap *)smimeNode->appData;
smimeMap->pSubject = subjNode;
} else if (subjMap->pSMime == NoSMime) {
/* S/MIME entry found is for diff. subject. */
subjMap->pSMime = WrongEntry;
}
}
} /* end for */
} /* endif (emailAddr[0]) */
} /* end for */
} /* endif (subjectEntry->emailAddrs) */
}
return SECSuccess;
}
void
printnode(dbDebugInfo *info, const char *str, int num)
{
if (!info->dograph)
return;
if (num < 0) {
PR_fprintf(info->graphfile, str);
} else {
PR_fprintf(info->graphfile, str, num);
}
}
PRBool
map_handle_is_ok(dbDebugInfo *info, void *mapPtr, int indent)
{
if (mapPtr == NULL) {
if (indent > 0)
printnode(info, " ", -1);
if (indent >= 0)
printnode(info, "******************* ", -1);
return PR_FALSE;
} else if (mapPtr == WrongEntry) {
if (indent > 0)
printnode(info, " ", -1);
if (indent >= 0)
printnode(info, "??????????????????? ", -1);
return PR_FALSE;
} else {
return PR_TRUE;
}
}
/* these call each other */
void print_smime_graph(dbDebugInfo *info, certDBEntryMap *smimeMap,
int direction);
void print_nickname_graph(dbDebugInfo *info, certDBEntryMap *nickMap,
int direction);
void print_subject_graph(dbDebugInfo *info, certDBSubjectEntryMap *subjMap,
int direction, int optindex, int opttype);
void print_cert_graph(dbDebugInfo *info, certDBEntryMap *certMap,
int direction);
/* Given an smime entry, print its unique identifier. If GOLEFT is
* specified, print the cert<-subject<-smime map, else just print
* the smime entry.
*/
void
print_smime_graph(dbDebugInfo *info, certDBEntryMap *smimeMap, int direction)
{
certDBSubjectEntryMap *subjMap;
certDBEntryListNode *subjNode;
if (direction == GOLEFT) {
/* Need to output subject and cert first, see print_subject_graph */
subjNode = smimeMap->pSubject;
if (map_handle_is_ok(info, (void *)subjNode, 1)) {
subjMap = (certDBSubjectEntryMap *)subjNode->appData;
print_subject_graph(info, subjMap, GOLEFT,
smimeMap->index, certDBEntryTypeSMimeProfile);
} else {
printnode(info, "<---- S/MIME %5d ", smimeMap->index);
info->dbErrors[NoSubjectForSMime]++;
}
} else {
printnode(info, "S/MIME %5d ", smimeMap->index);
}
}
/* Given a nickname entry, print its unique identifier. If GOLEFT is
* specified, print the cert<-subject<-nickname map, else just print
* the nickname entry.
*/
void
print_nickname_graph(dbDebugInfo *info, certDBEntryMap *nickMap, int direction)
{
certDBSubjectEntryMap *subjMap;
certDBEntryListNode *subjNode;
if (direction == GOLEFT) {
/* Need to output subject and cert first, see print_subject_graph */
subjNode = nickMap->pSubject;
if (map_handle_is_ok(info, (void *)subjNode, 1)) {
subjMap = (certDBSubjectEntryMap *)subjNode->appData;
print_subject_graph(info, subjMap, GOLEFT,
nickMap->index, certDBEntryTypeNickname);
} else {
printnode(info, "<---- Nickname %5d ", nickMap->index);
info->dbErrors[NoSubjectForNickname]++;
}
} else {
printnode(info, "Nickname %5d ", nickMap->index);
}
}
/* Given a subject entry, if going right print the graph of the nickname|smime
* that it maps to (by its unique identifier); and if going left
* print the list of certs that it points to.
*/
void
print_subject_graph(dbDebugInfo *info, certDBSubjectEntryMap *subjMap,
int direction, int optindex, int opttype)
{
certDBEntryMap *map;
certDBEntryListNode *node;
int i;
/* The first line of output always contains the cert id, subject id,
* and nickname|smime id. Subsequent lines may contain additional
* cert id's for the subject if going left or both directions.
* Ex. of printing the graph for a subject entry:
* Cert 3 <- Subject 5 -> Nickname 32
* Cert 8 /
* Cert 9 /
* means subject 5 has 3 certs, 3, 8, and 9, and corresponds
* to nickname entry 32.
* To accomplish the above, it is required to dump the entire first
* line left-to-right, regardless of the input direction, and then
* finish up any remaining cert entries. Hence the code is uglier
* than one may expect.
*/
if (direction == GOLEFT || direction == GOBOTH) {
/* In this case, nothing should be output until the first cert is
* located and output (cert 3 in the above example).
*/
if (subjMap->numCerts == 0 || subjMap->pCerts == NULL)
/* XXX uh-oh */
return;
/* get the first cert and dump it. */
node = subjMap->pCerts[0];
if (map_handle_is_ok(info, (void *)node, 0)) {
map = (certDBEntryMap *)node->appData;
/* going left here stops. */
print_cert_graph(info, map, GOLEFT);
} else {
info->dbErrors[SubjectHasNoKeyForCert]++;
}
/* Now it is safe to output the subject id. */
if (direction == GOLEFT)
printnode(info, "Subject %5d <---- ", subjMap->index);
else /* direction == GOBOTH */
printnode(info, "Subject %5d ----> ", subjMap->index);
}
if (direction == GORIGHT || direction == GOBOTH) {
/* Okay, now output the nickname|smime for this subject. */
if (direction != GOBOTH) /* handled above */
printnode(info, "Subject %5d ----> ", subjMap->index);
if (subjMap->pNickname) {
node = subjMap->pNickname;
if (map_handle_is_ok(info, (void *)node, 0)) {
map = (certDBEntryMap *)node->appData;
/* going right here stops. */
print_nickname_graph(info, map, GORIGHT);
}
}
if (subjMap->pSMime) {
node = subjMap->pSMime;
if (map_handle_is_ok(info, (void *)node, 0)) {
map = (certDBEntryMap *)node->appData;
/* going right here stops. */
print_smime_graph(info, map, GORIGHT);
}
}
if (!subjMap->pNickname && !subjMap->pSMime) {
printnode(info, "******************* ", -1);
info->dbErrors[NoNicknameOrSMimeForSubject]++;
}
if (subjMap->pNickname && subjMap->pSMime) {
info->dbErrors[NicknameAndSMimeEntries]++;
}
}
if (direction != GORIGHT) { /* going right has only one cert */
if (opttype == certDBEntryTypeNickname)
printnode(info, "Nickname %5d ", optindex);
else if (opttype == certDBEntryTypeSMimeProfile)
printnode(info, "S/MIME %5d ", optindex);
for (i = 1 /* 1st one already done */; i < subjMap->numCerts; i++) {
printnode(info, "\n", -1); /* start a new line */
node = subjMap->pCerts[i];
if (map_handle_is_ok(info, (void *)node, 0)) {
map = (certDBEntryMap *)node->appData;
/* going left here stops. */
print_cert_graph(info, map, GOLEFT);
printnode(info, "/", -1);
}
}
}
}
/* Given a cert entry, print its unique identifer. If GORIGHT is specified,
* print the cert->subject->nickname|smime map, else just print
* the cert entry.
*/
void
print_cert_graph(dbDebugInfo *info, certDBEntryMap *certMap, int direction)
{
certDBSubjectEntryMap *subjMap;
certDBEntryListNode *subjNode;
if (direction == GOLEFT) {
printnode(info, "Cert %5d <---- ", certMap->index);
/* only want cert entry, terminate here. */
return;
}
/* Keep going right then. */
printnode(info, "Cert %5d ----> ", certMap->index);
subjNode = certMap->pSubject;
if (map_handle_is_ok(info, (void *)subjNode, 0)) {
subjMap = (certDBSubjectEntryMap *)subjNode->appData;
print_subject_graph(info, subjMap, GORIGHT, -1, -1);
} else {
info->dbErrors[NoSubjectForCert]++;
}
}
SECStatus
computeDBGraph(certDBArray *dbArray, dbDebugInfo *info)
{
PRCList *cElem, *sElem, *nElem, *mElem;
certDBEntryListNode *node;
certDBEntryMap *map;
certDBSubjectEntryMap *subjMap;
/* Graph is of this form:
*
* certs:
* cert ---> subject ---> (nickname|smime)
*
* subjects:
* cert <--- subject ---> (nickname|smime)
*
* nicknames and smime:
* cert <--- subject <--- (nickname|smime)
*/
/* Print cert graph. */
for (cElem = PR_LIST_HEAD(&dbArray->certs.link);
cElem != &dbArray->certs.link; cElem = PR_NEXT_LINK(cElem)) {
/* Print graph of everything to right of cert entry. */
node = LISTNODE_CAST(cElem);
map = (certDBEntryMap *)node->appData;
print_cert_graph(info, map, GORIGHT);
printnode(info, "\n", -1);
}
printnode(info, "\n", -1);
/* Print subject graph. */
for (sElem = PR_LIST_HEAD(&dbArray->subjects.link);
sElem != &dbArray->subjects.link; sElem = PR_NEXT_LINK(sElem)) {
/* Print graph of everything to both sides of subject entry. */
node = LISTNODE_CAST(sElem);
subjMap = (certDBSubjectEntryMap *)node->appData;
print_subject_graph(info, subjMap, GOBOTH, -1, -1);
printnode(info, "\n", -1);
}
printnode(info, "\n", -1);
/* Print nickname graph. */
for (nElem = PR_LIST_HEAD(&dbArray->nicknames.link);
nElem != &dbArray->nicknames.link; nElem = PR_NEXT_LINK(nElem)) {
/* Print graph of everything to left of nickname entry. */
node = LISTNODE_CAST(nElem);
map = (certDBEntryMap *)node->appData;
print_nickname_graph(info, map, GOLEFT);
printnode(info, "\n", -1);
}
printnode(info, "\n", -1);
/* Print smime graph. */
for (mElem = PR_LIST_HEAD(&dbArray->smime.link);
mElem != &dbArray->smime.link; mElem = PR_NEXT_LINK(mElem)) {
/* Print graph of everything to left of smime entry. */
node = LISTNODE_CAST(mElem);
if (node == NULL)
break;
map = (certDBEntryMap *)node->appData;
print_smime_graph(info, map, GOLEFT);
printnode(info, "\n", -1);
}
printnode(info, "\n", -1);
return SECSuccess;
}
/*
* List the entries in the db, showing handles between entry types.
*/
void
verboseOutput(certDBArray *dbArray, dbDebugInfo *info)
{
int i, ref;
PRCList *elem;
certDBEntryListNode *node;
certDBEntryMap *map;
certDBSubjectEntryMap *smap;
certDBEntrySubject *subjectEntry;
/* List certs */
for (elem = PR_LIST_HEAD(&dbArray->certs.link);
elem != &dbArray->certs.link; elem = PR_NEXT_LINK(elem)) {
node = LISTNODE_CAST(elem);
map = (certDBEntryMap *)node->appData;
dumpCertEntry((certDBEntryCert *)&node->entry, map->index, info->out);
/* walk the cert handle to it's subject entry */
if (map_handle_is_ok(info, map->pSubject, -1)) {
smap = (certDBSubjectEntryMap *)map->pSubject->appData;
ref = smap->index;
PR_fprintf(info->out, "-->(subject %d)\n\n\n", ref);
} else {
PR_fprintf(info->out, "-->(MISSING SUBJECT ENTRY)\n\n\n");
}
}
/* List subjects */
for (elem = PR_LIST_HEAD(&dbArray->subjects.link);
elem != &dbArray->subjects.link; elem = PR_NEXT_LINK(elem)) {
int refs = 0;
node = LISTNODE_CAST(elem);
subjectEntry = (certDBEntrySubject *)&node->entry;
smap = (certDBSubjectEntryMap *)node->appData;
dumpSubjectEntry(subjectEntry, smap->index, info->out);
/* iterate over subject's certs */
for (i = 0; i < smap->numCerts; i++) {
/* walk each subject handle to it's cert entries */
if (map_handle_is_ok(info, smap->pCerts[i], -1)) {
ref = ((certDBEntryMap *)smap->pCerts[i]->appData)->index;
PR_fprintf(info->out, "-->(%d. certificate %d)\n", i, ref);
} else {
PR_fprintf(info->out, "-->(%d. MISSING CERT ENTRY)\n", i);
}
}
if (subjectEntry->nickname) {
++refs;
/* walk each subject handle to it's nickname entry */
if (map_handle_is_ok(info, smap->pNickname, -1)) {
ref = ((certDBEntryMap *)smap->pNickname->appData)->index;
PR_fprintf(info->out, "-->(nickname %d)\n", ref);
} else {
PR_fprintf(info->out, "-->(MISSING NICKNAME ENTRY)\n");
}
}
if (subjectEntry->nemailAddrs &&
subjectEntry->emailAddrs &&
subjectEntry->emailAddrs[0] &&
subjectEntry->emailAddrs[0][0]) {
++refs;
/* walk each subject handle to it's smime entry */
if (map_handle_is_ok(info, smap->pSMime, -1)) {
ref = ((certDBEntryMap *)smap->pSMime->appData)->index;
PR_fprintf(info->out, "-->(s/mime %d)\n", ref);
} else {
PR_fprintf(info->out, "-->(MISSING S/MIME ENTRY)\n");
}
}
if (!refs) {
PR_fprintf(info->out, "-->(NO NICKNAME+S/MIME ENTRY)\n");
}
PR_fprintf(info->out, "\n\n");
}
for (elem = PR_LIST_HEAD(&dbArray->nicknames.link);
elem != &dbArray->nicknames.link; elem = PR_NEXT_LINK(elem)) {
node = LISTNODE_CAST(elem);
map = (certDBEntryMap *)node->appData;
dumpNicknameEntry((certDBEntryNickname *)&node->entry, map->index,
info->out);
if (map_handle_is_ok(info, map->pSubject, -1)) {
ref = ((certDBEntryMap *)map->pSubject->appData)->index;
PR_fprintf(info->out, "-->(subject %d)\n\n\n", ref);
} else {
PR_fprintf(info->out, "-->(MISSING SUBJECT ENTRY)\n\n\n");
}
}
for (elem = PR_LIST_HEAD(&dbArray->smime.link);
elem != &dbArray->smime.link; elem = PR_NEXT_LINK(elem)) {
node = LISTNODE_CAST(elem);
map = (certDBEntryMap *)node->appData;
dumpSMimeEntry((certDBEntrySMime *)&node->entry, map->index, info->out);
if (map_handle_is_ok(info, map->pSubject, -1)) {
ref = ((certDBEntryMap *)map->pSubject->appData)->index;
PR_fprintf(info->out, "-->(subject %d)\n\n\n", ref);
} else {
PR_fprintf(info->out, "-->(MISSING SUBJECT ENTRY)\n\n\n");
}
}
PR_fprintf(info->out, "\n\n");
}
/* A callback function, intended to be called from nsslowcert_TraverseDBEntries
* Builds a PRCList of DB entries of the specified type.
*/
SECStatus
SEC_GetCertDBEntryList(SECItem *dbdata, SECItem *dbkey,
certDBEntryType entryType, void *pdata)
{
certDBEntry *entry;
certDBEntryListNode *node;
PRCList *list = (PRCList *)pdata;
if (!dbdata || !dbkey || !pdata || !dbdata->data || !dbkey->data) {
PORT_SetError(SEC_ERROR_INVALID_ARGS);
return SECFailure;
}
entry = nsslowcert_DecodeAnyDBEntry(dbdata, dbkey, entryType, NULL);
if (!entry) {
return SECSuccess; /* skip it */
}
node = PORT_ArenaZNew(entry->common.arena, certDBEntryListNode);
if (!node) {
/* DestroyDBEntry(entry); */
PLArenaPool *arena = entry->common.arena;
PORT_Memset(&entry->common, 0, sizeof entry->common);
PORT_FreeArena(arena, PR_FALSE);
return SECFailure;
}
node->entry = *entry; /* crude but effective. */
PR_INIT_CLIST(&node->link);
PR_INSERT_BEFORE(&node->link, list);
return SECSuccess;
}
int
fillDBEntryArray(NSSLOWCERTCertDBHandle *handle, certDBEntryType type,
certDBEntryListNode *list)
{
PRCList *elem;
certDBEntryListNode *node;
certDBEntryMap *mnode;
certDBSubjectEntryMap *smnode;
PLArenaPool *arena;
int count = 0;
/* Initialize a dummy entry in the list. The list head will be the
* next element, so this element is skipped by for loops.
*/
PR_INIT_CLIST((PRCList *)list);
/* Collect all of the cert db entries for this type into a list. */
nsslowcert_TraverseDBEntries(handle, type, SEC_GetCertDBEntryList, list);
for (elem = PR_LIST_HEAD(&list->link);
elem != &list->link; elem = PR_NEXT_LINK(elem)) {
/* Iterate over the entries and ... */
node = (certDBEntryListNode *)elem;
if (type != certDBEntryTypeSubject) {
arena = PORT_NewArena(sizeof(*mnode));
mnode = PORT_ArenaZNew(arena, certDBEntryMap);
mnode->arena = arena;
/* ... assign a unique index number to each node, and ... */
mnode->index = count;
/* ... set the map pointer for the node. */
node->appData = (void *)mnode;
} else {
/* allocate some room for the cert pointers also */
arena = PORT_NewArena(sizeof(*smnode) + 20 * sizeof(void *));
smnode = PORT_ArenaZNew(arena, certDBSubjectEntryMap);
smnode->arena = arena;
smnode->index = count;
node->appData = (void *)smnode;
}
count++;
}
return count;
}
void
freeDBEntryList(PRCList *list)
{
PRCList *next, *elem;
certDBEntryListNode *node;
certDBEntryMap *map;
for (elem = PR_LIST_HEAD(list); elem != list;) {
next = PR_NEXT_LINK(elem);
node = (certDBEntryListNode *)elem;
map = (certDBEntryMap *)node->appData;
PR_REMOVE_LINK(&node->link);
PORT_FreeArena(map->arena, PR_TRUE);
PORT_FreeArena(node->entry.common.arena, PR_TRUE);
elem = next;
}
}
void
DBCK_DebugDB(NSSLOWCERTCertDBHandle *handle, PRFileDesc *out,
PRFileDesc *mailfile)
{
int i, nCertsFound, nSubjFound, nErr;
int nCerts, nSubjects, nSubjCerts, nNicknames, nSMime, nRevocation;
PRCList *elem;
char c;
dbDebugInfo info;
certDBArray dbArray;
PORT_Memset(&dbArray, 0, sizeof(dbArray));
PORT_Memset(&info, 0, sizeof(info));
info.verbose = (PRBool)(out != NULL);
info.dograph = info.verbose;
info.out = (out) ? out : PR_STDOUT;
info.graphfile = mailfile ? mailfile : PR_STDOUT;
/* Fill the array structure with cert/subject/nickname/smime entries. */
dbArray.numCerts = fillDBEntryArray(handle, certDBEntryTypeCert,
&dbArray.certs);
dbArray.numSubjects = fillDBEntryArray(handle, certDBEntryTypeSubject,
&dbArray.subjects);
dbArray.numNicknames = fillDBEntryArray(handle, certDBEntryTypeNickname,
&dbArray.nicknames);
dbArray.numSMime = fillDBEntryArray(handle, certDBEntryTypeSMimeProfile,
&dbArray.smime);
dbArray.numRevocation = fillDBEntryArray(handle, certDBEntryTypeRevocation,
&dbArray.revocation);
/* Compute the map between the database entries. */
mapSubjectEntries(&dbArray);
mapCertEntries(&dbArray);
computeDBGraph(&dbArray, &info);
/* Store the totals for later reference. */
nCerts = dbArray.numCerts;
nSubjects = dbArray.numSubjects;
nNicknames = dbArray.numNicknames;
nSMime = dbArray.numSMime;
nRevocation = dbArray.numRevocation;
nSubjCerts = 0;
for (elem = PR_LIST_HEAD(&dbArray.subjects.link);
elem != &dbArray.subjects.link; elem = PR_NEXT_LINK(elem)) {
certDBSubjectEntryMap *smap;
smap = (certDBSubjectEntryMap *)LISTNODE_CAST(elem)->appData;
nSubjCerts += smap->numCerts;
}
if (info.verbose) {
/* Dump the database contents. */
verboseOutput(&dbArray, &info);
}
freeDBEntryList(&dbArray.certs.link);
freeDBEntryList(&dbArray.subjects.link);
freeDBEntryList(&dbArray.nicknames.link);
freeDBEntryList(&dbArray.smime.link);
freeDBEntryList(&dbArray.revocation.link);
PR_fprintf(info.out, "\n");
PR_fprintf(info.out, "Database statistics:\n");
PR_fprintf(info.out, "N0: Found %4d Certificate entries.\n",
nCerts);
PR_fprintf(info.out, "N1: Found %4d Subject entries (unique DN's).\n",
nSubjects);
PR_fprintf(info.out, "N2: Found %4d Cert keys within Subject entries.\n",
nSubjCerts);
PR_fprintf(info.out, "N3: Found %4d Nickname entries.\n",
nNicknames);
PR_fprintf(info.out, "N4: Found %4d S/MIME entries.\n",
nSMime);
PR_fprintf(info.out, "N5: Found %4d CRL entries.\n",
nRevocation);
PR_fprintf(info.out, "\n");
nErr = 0;
for (i = 0; i < NUM_ERROR_TYPES; i++) {
PR_fprintf(info.out, "E%d: Found %4d %s\n",
i, info.dbErrors[i], errResult[i]);
nErr += info.dbErrors[i];
}
PR_fprintf(info.out, "--------------\n Found %4d errors in database.\n",
nErr);
PR_fprintf(info.out, "\nCertificates:\n");
PR_fprintf(info.out, "N0 == N2 + E%d + E%d\n", NoSubjectForCert,
SubjectHasNoKeyForCert);
nCertsFound = nSubjCerts +
info.dbErrors[NoSubjectForCert] +
info.dbErrors[SubjectHasNoKeyForCert];
c = (nCertsFound == nCerts) ? '=' : '!';
PR_fprintf(info.out, "%d %c= %d + %d + %d\n", nCerts, c, nSubjCerts,
info.dbErrors[NoSubjectForCert],
info.dbErrors[SubjectHasNoKeyForCert]);
PR_fprintf(info.out, "\nSubjects:\n");
PR_fprintf(info.out,
"N1 == N3 + N4 + E%d + E%d + E%d + E%d + E%d - E%d - E%d - E%d\n",
NoNicknameOrSMimeForSubject,
WrongNicknameForSubject,
NoNicknameEntry,
WrongSMimeForSubject,
NoSMimeEntry,
NoSubjectForNickname,
NoSubjectForSMime,
NicknameAndSMimeEntries);
nSubjFound = nNicknames + nSMime +
info.dbErrors[NoNicknameOrSMimeForSubject] +
info.dbErrors[WrongNicknameForSubject] +
info.dbErrors[NoNicknameEntry] +
info.dbErrors[WrongSMimeForSubject] +
info.dbErrors[NoSMimeEntry] -
info.dbErrors[NoSubjectForNickname] -
info.dbErrors[NoSubjectForSMime] -
info.dbErrors[NicknameAndSMimeEntries];
c = (nSubjFound == nSubjects) ? '=' : '!';
PR_fprintf(info.out,
"%2d %c= %2d + %2d + %2d + %2d + %2d + %2d + %2d - %2d - %2d - %2d\n",
nSubjects, c, nNicknames, nSMime,
info.dbErrors[NoNicknameOrSMimeForSubject],
info.dbErrors[WrongNicknameForSubject],
info.dbErrors[NoNicknameEntry],
info.dbErrors[WrongSMimeForSubject],
info.dbErrors[NoSMimeEntry],
info.dbErrors[NoSubjectForNickname],
info.dbErrors[NoSubjectForSMime],
info.dbErrors[NicknameAndSMimeEntries]);
PR_fprintf(info.out, "\n");
}
#ifdef DORECOVER
#include "dbrecover.c"
#endif /* DORECOVER */
enum {
cmd_Debug = 0,
cmd_LongUsage,
cmd_Recover
};
enum {
opt_KeepAll = 0,
opt_CertDir,
opt_Dumpfile,
opt_InputDB,
opt_OutputDB,
opt_Mailfile,
opt_Prompt,
opt_KeepRedundant,
opt_KeepNoSMimeProfile,
opt_Verbose,
opt_KeepExpired
};
static secuCommandFlag dbck_commands[] = {
{ /* cmd_Debug, */ 'D', PR_FALSE, 0, PR_FALSE },
{ /* cmd_LongUsage,*/ 'H', PR_FALSE, 0, PR_FALSE },
{ /* cmd_Recover, */ 'R', PR_FALSE, 0, PR_FALSE }
};
static secuCommandFlag dbck_options[] = {
{ /* opt_KeepAll, */ 'a', PR_FALSE, 0, PR_FALSE },
{ /* opt_CertDir, */ 'd', PR_TRUE, 0, PR_FALSE },
{ /* opt_Dumpfile, */ 'f', PR_TRUE, 0, PR_FALSE },
{ /* opt_InputDB, */ 'i', PR_TRUE, 0, PR_FALSE },
{ /* opt_OutputDB, */ 'o', PR_TRUE, 0, PR_FALSE },
{ /* opt_Mailfile, */ 'm', PR_FALSE, 0, PR_FALSE },
{ /* opt_Prompt, */ 'p', PR_FALSE, 0, PR_FALSE },
{ /* opt_KeepRedundant, */ 'r', PR_FALSE, 0, PR_FALSE },
{ /* opt_KeepNoSMimeProfile,*/ 's', PR_FALSE, 0, PR_FALSE },
{ /* opt_Verbose, */ 'v', PR_FALSE, 0, PR_FALSE },
{ /* opt_KeepExpired, */ 'x', PR_FALSE, 0, PR_FALSE }
};
#define CERT_DB_FMT "%s/cert%s.db"
static char *
dbck_certdb_name_cb(void *arg, int dbVersion)
{
const char *configdir = (const char *)arg;
const char *dbver;
char *smpname = NULL;
char *dbname = NULL;
switch (dbVersion) {
case 8:
dbver = "8";
break;
case 7:
dbver = "7";
break;
case 6:
dbver = "6";
break;
case 5:
dbver = "5";
break;
case 4:
default:
dbver = "";
break;
}
/* make sure we return something allocated with PORT_ so we have properly
* matched frees at the end */
smpname = PR_smprintf(CERT_DB_FMT, configdir, dbver);
if (smpname) {
dbname = PORT_Strdup(smpname);
PR_smprintf_free(smpname);
}
return dbname;
}
int
main(int argc, char **argv)
{
NSSLOWCERTCertDBHandle *certHandle;
PRFileDesc *mailfile = NULL;
PRFileDesc *dumpfile = NULL;
char *pathname = 0;
char *fullname = 0;
char *newdbname = 0;
PRBool removeExpired, requireProfile, singleEntry;
SECStatus rv;
secuCommand dbck;
dbck.numCommands = sizeof(dbck_commands) / sizeof(secuCommandFlag);
dbck.numOptions = sizeof(dbck_options) / sizeof(secuCommandFlag);
dbck.commands = dbck_commands;
dbck.options = dbck_options;
progName = strrchr(argv[0], '/');
progName = progName ? progName + 1 : argv[0];
rv = SECU_ParseCommandLine(argc, argv, progName, &dbck);
if (rv != SECSuccess)
Usage(progName);
if (dbck.commands[cmd_LongUsage].activated)
LongUsage(progName);
if (!dbck.commands[cmd_Debug].activated &&
!dbck.commands[cmd_Recover].activated) {
PR_fprintf(PR_STDERR, "Please specify -H, -D or -R.\n");
Usage(progName);
}
removeExpired = !(dbck.options[opt_KeepAll].activated ||
dbck.options[opt_KeepExpired].activated);
requireProfile = !(dbck.options[opt_KeepAll].activated ||
dbck.options[opt_KeepNoSMimeProfile].activated);
singleEntry = !(dbck.options[opt_KeepAll].activated ||
dbck.options[opt_KeepRedundant].activated);
if (dbck.options[opt_OutputDB].activated) {
newdbname = PL_strdup(dbck.options[opt_OutputDB].arg);
} else {
newdbname = PL_strdup("new_cert8.db");
}
/* Create a generic graph of the database. */
if (dbck.options[opt_Mailfile].activated) {
mailfile = PR_Open("./mailfile", PR_RDWR | PR_CREATE_FILE, 00660);
if (!mailfile) {
fprintf(stderr, "Unable to create mailfile.\n");
return -1;
}
}
/* Dump all debugging info while running. */
if (dbck.options[opt_Verbose].activated) {
if (dbck.options[opt_Dumpfile].activated) {
dumpfile = PR_Open(dbck.options[opt_Dumpfile].arg,
PR_RDWR | PR_CREATE_FILE, 00660);
if (!dumpfile) {
fprintf(stderr, "Unable to create dumpfile.\n");
return -1;
}
} else {
dumpfile = PR_STDOUT;
}
}
/* Set the cert database directory. */
if (dbck.options[opt_CertDir].activated) {
SECU_ConfigDirectory(dbck.options[opt_CertDir].arg);
}
pathname = SECU_ConfigDirectory(NULL);
PR_Init(PR_SYSTEM_THREAD, PR_PRIORITY_NORMAL, 1);
rv = NSS_NoDB_Init(pathname);
if (rv != SECSuccess) {
fprintf(stderr, "NSS_NoDB_Init failed\n");
return -1;
}
certHandle = PORT_ZNew(NSSLOWCERTCertDBHandle);
if (!certHandle) {
SECU_PrintError(progName, "unable to get database handle");
return -1;
}
certHandle->ref = 1;
#ifdef NOTYET
/* Open the possibly corrupt database. */
if (dbck.options[opt_InputDB].activated) {
PRFileInfo fileInfo;
fullname = PR_smprintf("%s/%s", pathname,
dbck.options[opt_InputDB].arg);
if (PR_GetFileInfo(fullname, &fileInfo) != PR_SUCCESS) {
fprintf(stderr, "Unable to read file \"%s\".\n", fullname);
return -1;
}
rv = CERT_OpenCertDBFilename(certHandle, fullname, PR_TRUE);
} else
#endif
{
/* Use the default. */
#ifdef NOTYET
fullname = SECU_CertDBNameCallback(NULL, CERT_DB_FILE_VERSION);
if (PR_GetFileInfo(fullname, &fileInfo) != PR_SUCCESS) {
fprintf(stderr, "Unable to read file \"%s\".\n", fullname);
return -1;
}
#endif
rv = nsslowcert_OpenCertDB(certHandle,
PR_TRUE, /* readOnly */
NULL, /* rdb appName */
"", /* rdb prefix */
dbck_certdb_name_cb, /* namecb */
pathname, /* configDir */
PR_FALSE); /* volatile */
}
if (rv) {
SECU_PrintError(progName, "unable to open cert database");
return -1;
}
if (dbck.commands[cmd_Debug].activated) {
DBCK_DebugDB(certHandle, dumpfile, mailfile);
return 0;
}
#ifdef DORECOVER
if (dbck.commands[cmd_Recover].activated) {
DBCK_ReconstructDBFromCerts(certHandle, newdbname,
dumpfile, removeExpired,
requireProfile, singleEntry,
dbck.options[opt_Prompt].activated);
return 0;
}
#endif
if (mailfile)
PR_Close(mailfile);
if (dumpfile)
PR_Close(dumpfile);
if (certHandle) {
nsslowcert_ClosePermCertDB(certHandle);
PORT_Free(certHandle);
}
return -1;
}