diff options
-rw-r--r-- | security/nss/cmd/dbck/Makefile | 52 | ||||
-rw-r--r-- | security/nss/cmd/dbck/dbck.c | 1869 | ||||
-rw-r--r-- | security/nss/cmd/dbck/manifest.mn | 25 |
3 files changed, 1946 insertions, 0 deletions
diff --git a/security/nss/cmd/dbck/Makefile b/security/nss/cmd/dbck/Makefile new file mode 100644 index 000000000..4dbbb924a --- /dev/null +++ b/security/nss/cmd/dbck/Makefile @@ -0,0 +1,52 @@ +#! gmake +# +# CONFIDENTIAL AND PROPRIETARY SOURCE CODE OF +# NETSCAPE COMMUNICATIONS CORPORATION +# Copyright © 1996, 1997 Netscape Communications Corporation. All Rights +# Reserved. Use of this Source Code is subject to the terms of the +# applicable license agreement from Netscape Communications Corporation. +# The copyright notice(s) in this Source Code does not indicate actual or +# intended publication of this Source Code. +# + +####################################################################### +# (1) Include initial platform-independent assignments (MANDATORY). # +####################################################################### + +include manifest.mn + +####################################################################### +# (2) Include "global" configuration information. (OPTIONAL) # +####################################################################### + +include $(CORE_DEPTH)/coreconf/config.mk + +####################################################################### +# (3) Include "component" configuration information. (OPTIONAL) # +####################################################################### + +####################################################################### +# (4) Include "local" platform-dependent assignments (OPTIONAL). # +####################################################################### + +include ../platlibs.mk + +####################################################################### +# (5) Execute "global" rules. (OPTIONAL) # +####################################################################### + +include $(CORE_DEPTH)/coreconf/rules.mk + +####################################################################### +# (6) Execute "component" rules. (OPTIONAL) # +####################################################################### + + + +####################################################################### +# (7) Execute "local" rules. (OPTIONAL). # +####################################################################### + + +include ../platrules.mk + diff --git a/security/nss/cmd/dbck/dbck.c b/security/nss/cmd/dbck/dbck.c new file mode 100644 index 000000000..a3f1b9d54 --- /dev/null +++ b/security/nss/cmd/dbck/dbck.c @@ -0,0 +1,1869 @@ +/* + * 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 Netscape are + * Copyright (C) 1994-2000 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU General Public License Version 2 or later (the + * "GPL"), in which case the provisions of the GPL are applicable + * instead of those above. If you wish to allow use of your + * version of this file only under the terms of the GPL and not to + * allow others to use your version of this file under the MPL, + * indicate your decision by deleting the provisions above and + * replace them with the notice and other provisions required by + * the GPL. If you do not delete the provisions above, a recipient + * may use your version of this file under either the MPL or the + * GPL. + */ + +/* +** 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" + +static char *progName; + +/* placeholders for pointer error types */ +static void *WrongEntry; +static void *NoNickname; +static void *NoSMime; + +enum { + GOBOTH = 0, + GORIGHT, + GOLEFT +}; + +typedef struct +{ + PRBool verbose; + PRBool dograph; + PRFileDesc *out; + PRFileDesc *graphfile; + int dbErrors[10]; +} dbDebugInfo; + +/* + * 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 +{ + PRArenaPool *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 +{ + PRArenaPool *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; + 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 */ +} 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] [-i dbname] [-m] [-v [-f dumpfile]]\n", + progName); + FPS " %s -R -o newdbname [-d certdir] [-i dbname] [-aprsx] [-v [-f dumpfile]]\n", + progName); + 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 Input cert database name (default is cert7.db)\n", + " -i dbname"); + FPS "%-15s Mail a graph of the database to certdb@netscape.com.\n", + " -m"); + FPS "%-15s This will produce an index graph of your cert db and send\n", + ""); + FPS "%-15s it to Netscape for analysis. Personal info will be removed.\n", + ""); + FPS "%-15s Verbose mode. Dumps the entire contents of your cert7.db.\n", + " -v"); + FPS "%-15s File to dump verbose output into.\n", + " -f dumpfile"); + 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_cert7.db)\n", + " -o newdbname"); + FPS "%-15s Cert database directory (default is ~/.netscape)\n", + " -d certdir"); + FPS "%-15s Input cert database name (default is cert7.db)\n", + " -i dbname"); + 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"); + exit(-1); +#undef FPS +} + +/******************************************************************* + * + * Functions for dbck. + * + ******************************************************************/ + +void +printHexString(PRFileDesc *out, SECItem *hexval) +{ + 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"); +} + +typedef enum { +/* 0*/ NoSubjectForCert = 0, +/* 1*/ SubjectHasNoKeyForCert, +/* 2*/ NoNicknameOrSMimeForSubject, +/* 3*/ WrongNicknameForSubject, +/* 4*/ NoNicknameEntry, +/* 5*/ WrongSMimeForSubject, +/* 6*/ NoSMimeEntry, +/* 7*/ NoSubjectForNickname, +/* 8*/ NoSubjectForSMime, +/* 9*/ NicknameAndSMimeEntry +} dbErrorType; + +static char *dbErrorString[] = { +/* 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.", +}; + +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. */ + int64 timeBefore, timeAfter; + PRExplodedTime beforePrintable, afterPrintable; + char *beforestr, *afterstr; + DER_UTCTimeToTime(&timeBefore, &cert->validity.notBefore); + DER_UTCTimeToTime(&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) +{ + CERTCertificate *cert; + cert = CERT_DecodeDERCertificate(&entry->derCert, PR_FALSE, NULL); + if (!cert) { + fprintf(stderr, "Failed to decode certificate.\n"); + return SECFailure; + } + cert->trust = &entry->trust; + dumpCertificate(cert, num, outfile); + CERT_DestroyCertificate(cert); + return SECSuccess; +} + +SECStatus +dumpSubjectEntry(certDBEntrySubject *entry, int num, PRFileDesc *outfile) +{ + char *subjectName; + 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->emailAddr) + PR_fprintf(outfile, "## Subject email address: %s\n", + entry->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); + PR_fprintf(outfile, "## OPTIONS: "); + printHexString(outfile, &entry->smimeOptions); + PR_fprintf(outfile, "## TIMESTAMP: "); + printHexString(outfile, &entry->optionsDate); + PR_fprintf(outfile, "\n"); + return SECSuccess; +} + +SECStatus +mapCertEntries(certDBArray *dbArray) +{ + certDBEntryCert *certEntry; + certDBEntrySubject *subjectEntry; + certDBEntryListNode *certNode, *subjNode; + certDBSubjectEntryMap *smap; + certDBEntryMap *map; + PRArenaPool *tmparena; + SECItem derSubject; + SECItem certKey; + PRCList *cElem, *sElem; + int i; + + /* 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)) { + /* 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; + certDBEntryNickname *nicknameEntry; + certDBEntrySMime *smimeEntry; + certDBEntryListNode *subjNode, *nickNode, *smimeNode; + certDBSubjectEntryMap *subjMap; + certDBEntryMap *nickMap, *smimeMap; + PRCList *sElem, *nElem, *mElem; + + 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; + if (subjectEntry->nickname) { + /* Subject should have a nickname entry, so create a link. */ + for (nElem = PR_LIST_HEAD(&dbArray->nicknames.link); + nElem != &dbArray->nicknames.link; + nElem = PR_NEXT_LINK(nElem)) { + /* Look for subject's nickname in nickname entries. */ + nickNode = LISTNODE_CAST(nElem); + nicknameEntry = (certDBEntryNickname *)&nickNode->entry; + nickMap = (certDBEntryMap *)nickNode->appData; + if (PL_strcmp(subjectEntry->nickname, + nicknameEntry->nickname) == 0) { + /* Found a nickname entry for subject's nickname. */ + if (SECITEM_ItemsAreEqual(&subjectEntry->derSubject, + &nicknameEntry->subjectName)) { + /* Nickname and subject match. */ + subjMap->pNickname = nickNode; + nickMap->pSubject = subjNode; + } else { + /* Nickname entry found is for diff. subject. */ + subjMap->pNickname = WrongEntry; + } + } + } + } else { + subjMap->pNickname = NoNickname; + } + if (subjectEntry->emailAddr) { + /* 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)) { + /* Look for subject's email in S/MIME entries. */ + smimeNode = LISTNODE_CAST(mElem); + smimeEntry = (certDBEntrySMime *)&smimeNode->entry; + smimeMap = (certDBEntryMap *)smimeNode->appData; + if (PL_strcmp(subjectEntry->emailAddr, + smimeEntry->emailAddr) == 0) { + /* Found a S/MIME entry for subject's email. */ + if (SECITEM_ItemsAreEqual(&subjectEntry->derSubject, + &smimeEntry->subjectName)) { + /* S/MIME entry and subject match. */ + subjMap->pSMime = smimeNode; + smimeMap->pSubject = subjNode; + } else { + /* S/MIME entry found is for diff. subject. */ + subjMap->pSMime = WrongEntry; + } + } + } + } else { + subjMap->pSMime = NoSMime; + } + } + 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); + } + } 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); + } + } 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); + } + /* 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); + } + } + 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); + } +} + +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)) { + 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) { + /* 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->emailAddr) { + /* 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"); + } + } + 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"); +} + +char *errResult[] = { + "Certificate entries that had no subject entry.", + "Certificate entries that had no key in their subject entry.", + "Subject entries that had no nickname or email address.", + "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.", +}; + +int +fillDBEntryArray(CERTCertDBHandle *handle, certDBEntryType type, + certDBEntryListNode *list) +{ + PRCList *elem; + certDBEntryListNode *node; + certDBEntryMap *mnode; + certDBSubjectEntryMap *smnode; + PRArenaPool *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. */ + SEC_TraverseDBEntries(handle, type, SEC_GetCertDBEntryList, + (PRCList *)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 = (certDBEntryMap *)PORT_ArenaZAlloc(arena, sizeof(*mnode)); + 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 = (certDBSubjectEntryMap *) + PORT_ArenaZAlloc(arena, sizeof(*smnode)); + 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(CERTCertDBHandle *handle, PRFileDesc *out, PRFileDesc *mailfile) +{ + int i, nCertsFound, nSubjFound, nErr; + int nCerts, nSubjects, nSubjCerts, nNicknames, nSMime; + PRCList *elem; + char c; + dbDebugInfo info; + certDBArray dbArray; + + PORT_Memset(&dbArray, 0, sizeof(dbArray)); + PORT_Memset(&info, 0, sizeof(info)); + info.verbose = (out == NULL) ? PR_FALSE : PR_TRUE ; + info.dograph = (mailfile == NULL) ? PR_FALSE : PR_TRUE ; + info.out = (out) ? out : PR_STDOUT; + info.graphfile = mailfile; + + /* 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); + + /* 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; + 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); + + 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, "\n"); + + nErr = 0; + for (i=0; i<sizeof(errResult)/sizeof(char*); 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\n", + NoNicknameOrSMimeForSubject, WrongNicknameForSubject, + NoNicknameEntry, WrongSMimeForSubject, NoSMimeEntry, + NoSubjectForNickname, NoSubjectForSMime); + PR_fprintf(info.out, " - #(subjects with both nickname and S/MIME entries)\n"); + 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[NicknameAndSMimeEntry]; + c = (nSubjFound == nSubjects) ? '=' : '!'; + PR_fprintf(info.out, "%d %c= %d + %d + %d + %d + %d + %d + %d - %d - %d - %d\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[NicknameAndSMimeEntry]); + PR_fprintf(info.out, "\n"); +} + +#ifdef DORECOVER +enum { + dbInvalidCert = 0, + dbNoSMimeProfile, + dbOlderCert, + dbBadCertificate, + dbCertNotWrittenToDB +}; + +typedef struct dbRestoreInfoStr +{ + CERTCertDBHandle *handle; + PRBool verbose; + PRFileDesc *out; + int nCerts; + int nOldCerts; + int dbErrors[5]; + PRBool removeType[3]; + PRBool promptUser[3]; +} dbRestoreInfo; + +char * +IsEmailCert(CERTCertificate *cert) +{ + char *email, *tmp1, *tmp2; + PRBool isCA; + int len; + + if (!cert->subjectName) { + return NULL; + } + + tmp1 = PORT_Strstr(cert->subjectName, "E="); + tmp2 = PORT_Strstr(cert->subjectName, "MAIL="); + /* XXX Nelson has cert for KTrilli which does not have either + * of above but is email cert (has cert->emailAddr). + */ + if (!tmp1 && !tmp2 && !cert->emailAddr) { + return NULL; + } + + /* Server or CA cert, not personal email. */ + isCA = CERT_IsCACert(cert, NULL); + if (isCA) + return NULL; + + /* XXX CERT_IsCACert advertises checking the key usage ext., + but doesn't appear to. */ + /* Check the key usage extension. */ + if (cert->keyUsagePresent) { + /* Must at least be able to sign or encrypt (not neccesarily + * both if it is one of a dual cert). + */ + if (!((cert->rawKeyUsage & KU_DIGITAL_SIGNATURE) || + (cert->rawKeyUsage & KU_KEY_ENCIPHERMENT))) + return NULL; + + /* CA cert, not personal email. */ + if (cert->rawKeyUsage & (KU_KEY_CERT_SIGN | KU_CRL_SIGN)) + return NULL; + } + + if (cert->emailAddr) { + email = PORT_Strdup(cert->emailAddr); + } else { + if (tmp1) + tmp1 += 2; /* "E=" */ + else + tmp1 = tmp2 + 5; /* "MAIL=" */ + len = strcspn(tmp1, ", "); + email = (char*)PORT_Alloc(len+1); + PORT_Strncpy(email, tmp1, len); + email[len] = '\0'; + } + + return email; +} + +SECStatus +deleteit(CERTCertificate *cert, void *arg) +{ + return SEC_DeletePermCertificate(cert); +} + +/* Different than DeleteCertificate - has the added bonus of removing + * all certs with the same DN. + */ +SECStatus +deleteAllEntriesForCert(CERTCertDBHandle *handle, CERTCertificate *cert, + PRFileDesc *outfile) +{ +#if 0 + certDBEntrySubject *subjectEntry; + certDBEntryNickname *nicknameEntry; + certDBEntrySMime *smimeEntry; + int i; +#endif + + if (outfile) { + PR_fprintf(outfile, "$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$\n\n"); + PR_fprintf(outfile, "Deleting redundant certificate:\n"); + dumpCertificate(cert, -1, outfile); + } + + CERT_TraverseCertsForSubject(handle, cert->subjectList, deleteit, NULL); +#if 0 + CERT_LockDB(handle); + subjectEntry = ReadDBSubjectEntry(handle, &cert->derSubject); + /* It had better be there, or created a bad db. */ + PORT_Assert(subjectEntry); + for (i=0; i<subjectEntry->ncerts; i++) { + DeleteDBCertEntry(handle, &subjectEntry->certKeys[i]); + } + DeleteDBSubjectEntry(handle, &cert->derSubject); + if (subjectEntry->emailAddr) { + smimeEntry = ReadDBSMimeEntry(handle, subjectEntry->emailAddr); + if (smimeEntry) { + if (SECITEM_ItemsAreEqual(&subjectEntry->derSubject, + &smimeEntry->subjectName)) + /* Only delete it if it's for this subject! */ + DeleteDBSMimeEntry(handle, subjectEntry->emailAddr); + SEC_DestroyDBEntry((certDBEntry*)smimeEntry); + } + } + if (subjectEntry->nickname) { + nicknameEntry = ReadDBNicknameEntry(handle, subjectEntry->nickname); + if (nicknameEntry) { + if (SECITEM_ItemsAreEqual(&subjectEntry->derSubject, + &nicknameEntry->subjectName)) + /* Only delete it if it's for this subject! */ + DeleteDBNicknameEntry(handle, subjectEntry->nickname); + SEC_DestroyDBEntry((certDBEntry*)nicknameEntry); + } + } + SEC_DestroyDBEntry((certDBEntry*)subjectEntry); + CERT_UnlockDB(handle); +#endif + return SECSuccess; +} + +void +getCertsToDelete(char *numlist, int len, int *certNums, int nCerts) +{ + int j, num; + char *numstr, *numend, *end; + + numstr = numlist; + end = numstr + len - 1; + while (numstr != end) { + numend = strpbrk(numstr, ", \n"); + *numend = '\0'; + if (PORT_Strlen(numstr) == 0) + return; + num = PORT_Atoi(numstr); + if (numstr == numlist) + certNums[0] = num; + for (j=1; j<nCerts+1; j++) { + if (num == certNums[j]) { + certNums[j] = -1; + break; + } + } + if (numend == end) + break; + numstr = strpbrk(numend+1, "0123456789"); + } +} + +PRBool +userSaysDeleteCert(CERTCertificate **certs, int nCerts, + int errtype, dbRestoreInfo *info, int *certNums) +{ + char response[32]; + int32 nb; + int i; + /* User wants to remove cert without prompting. */ + if (info->promptUser[errtype] == PR_FALSE) + return (info->removeType[errtype]); + switch (errtype) { + case dbInvalidCert: + PR_fprintf(PR_STDOUT, "******** Expired ********\n"); + PR_fprintf(PR_STDOUT, "Cert has expired.\n\n"); + dumpCertificate(certs[0], -1, PR_STDOUT); + PR_fprintf(PR_STDOUT, + "Keep it? (y/n - this one, Y/N - all expired certs) [n] "); + break; + case dbNoSMimeProfile: + PR_fprintf(PR_STDOUT, "******** No Profile ********\n"); + PR_fprintf(PR_STDOUT, "S/MIME cert has no profile.\n\n"); + dumpCertificate(certs[0], -1, PR_STDOUT); + PR_fprintf(PR_STDOUT, + "Keep it? (y/n - this one, Y/N - all S/MIME w/o profile) [n] "); + break; + case dbOlderCert: + PR_fprintf(PR_STDOUT, "******* Redundant nickname/email *******\n\n"); + PR_fprintf(PR_STDOUT, "These certs have the same nickname/email:\n"); + for (i=0; i<nCerts; i++) + dumpCertificate(certs[i], i, PR_STDOUT); + PR_fprintf(PR_STDOUT, + "Enter the certs you would like to keep from those listed above.\n"); + PR_fprintf(PR_STDOUT, + "Use a comma-separated list of the cert numbers (ex. 0, 8, 12).\n"); + PR_fprintf(PR_STDOUT, + "The first cert in the list will be the primary cert\n"); + PR_fprintf(PR_STDOUT, + " accessed by the nickname/email handle.\n"); + PR_fprintf(PR_STDOUT, + "List cert numbers to keep here, or hit enter\n"); + PR_fprintf(PR_STDOUT, + " to always keep only the newest cert: "); + break; + default: + } + nb = PR_Read(PR_STDIN, response, sizeof(response)); + PR_fprintf(PR_STDOUT, "\n\n"); + if (errtype == dbOlderCert) { + if (!isdigit(response[0])) { + info->promptUser[errtype] = PR_FALSE; + info->removeType[errtype] = PR_TRUE; + return PR_TRUE; + } + getCertsToDelete(response, nb, certNums, nCerts); + return PR_TRUE; + } + /* User doesn't want to be prompted for this type anymore. */ + if (response[0] == 'Y') { + info->promptUser[errtype] = PR_FALSE; + info->removeType[errtype] = PR_FALSE; + return PR_FALSE; + } else if (response[0] == 'N') { + info->promptUser[errtype] = PR_FALSE; + info->removeType[errtype] = PR_TRUE; + return PR_TRUE; + } + return (response[0] != 'y') ? PR_TRUE : PR_FALSE; +} + +SECStatus +addCertToDB(certDBEntryCert *certEntry, dbRestoreInfo *info, + CERTCertDBHandle *oldhandle) +{ + SECStatus rv = SECSuccess; + PRBool allowOverride; + PRBool userCert; + SECCertTimeValidity validity; + CERTCertificate *oldCert = NULL; + CERTCertificate *dbCert = NULL; + CERTCertificate *newCert = NULL; + CERTCertTrust *trust; + certDBEntrySMime *smimeEntry = NULL; + char *email = NULL; + char *nickname = NULL; + int nCertsForSubject = 1; + + oldCert = CERT_DecodeDERCertificate(&certEntry->derCert, PR_FALSE, + certEntry->nickname); + if (!oldCert) { + info->dbErrors[dbBadCertificate]++; + SEC_DestroyDBEntry((certDBEntry*)certEntry); + return SECSuccess; + } + + oldCert->dbEntry = certEntry; + oldCert->trust = &certEntry->trust; + oldCert->dbhandle = oldhandle; + + trust = oldCert->trust; + + info->nOldCerts++; + + if (info->verbose) + PR_fprintf(info->out, "%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n\n"); + + if (oldCert->nickname) + nickname = PORT_Strdup(oldCert->nickname); + + /* Always keep user certs. Skip ahead. */ + /* XXX if someone sends themselves a signed message, it is possible + for their cert to be imported as an "other" cert, not a user cert. + this mucks with smime entries... */ + 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 (userCert) + goto createcert; + + /* If user chooses so, ignore expired certificates. */ + allowOverride = (PRBool)((oldCert->keyUsage == certUsageSSLServer) || + (oldCert->keyUsage == certUsageSSLServerWithStepUp)); + validity = CERT_CheckCertValidTimes(oldCert, PR_Now(), allowOverride); + /* If cert expired and user wants to delete it, ignore it. */ + if ((validity != secCertTimeValid) && + userSaysDeleteCert(&oldCert, 1, dbInvalidCert, info, 0)) { + info->dbErrors[dbInvalidCert]++; + if (info->verbose) { + PR_fprintf(info->out, "Deleting expired certificate:\n"); + dumpCertificate(oldCert, -1, info->out); + } + goto cleanup; + } + + /* New database will already have default certs, don't attempt + to overwrite them. */ + dbCert = CERT_FindCertByDERCert(info->handle, &oldCert->derCert); + if (dbCert) { + info->nCerts++; + if (info->verbose) { + PR_fprintf(info->out, "Added certificate to database:\n"); + dumpCertificate(oldCert, -1, info->out); + } + goto cleanup; + } + + /* Determine if cert is S/MIME and get its email if so. */ + email = IsEmailCert(oldCert); + + /* + XXX Just create empty profiles? + if (email) { + SECItem *profile = CERT_FindSMimeProfile(oldCert); + if (!profile && + userSaysDeleteCert(&oldCert, 1, dbNoSMimeProfile, info, 0)) { + info->dbErrors[dbNoSMimeProfile]++; + if (info->verbose) { + PR_fprintf(info->out, + "Deleted cert missing S/MIME profile.\n"); + dumpCertificate(oldCert, -1, info->out); + } + goto cleanup; + } else { + SECITEM_FreeItem(profile); + } + } + */ + +createcert: + + /* Sometimes happens... */ + if (!nickname && userCert) + nickname = PORT_Strdup(oldCert->subjectName); + + /* Create a new certificate, copy of the old one. */ + newCert = CERT_NewTempCertificate(info->handle, &oldCert->derCert, + nickname, PR_FALSE, PR_TRUE); + if (!newCert) { + PR_fprintf(PR_STDERR, "Unable to create new certificate.\n"); + dumpCertificate(oldCert, -1, PR_STDERR); + info->dbErrors[dbBadCertificate]++; + goto cleanup; + } + + /* Add the cert to the new database. */ + rv = CERT_AddTempCertToPerm(newCert, nickname, oldCert->trust); + if (rv) { + PR_fprintf(PR_STDERR, "Failed to write temp cert to perm database.\n"); + dumpCertificate(oldCert, -1, PR_STDERR); + info->dbErrors[dbCertNotWrittenToDB]++; + goto cleanup; + } + + if (info->verbose) { + PR_fprintf(info->out, "Added certificate to database:\n"); + dumpCertificate(oldCert, -1, info->out); + } + + /* If the cert is an S/MIME cert, and the first with it's subject, + * modify the subject entry to include the email address, + * CERT_AddTempCertToPerm does not do email addresses and S/MIME entries. + */ + if (smimeEntry) { /*&& !userCert && nCertsForSubject == 1) { */ +#if 0 + UpdateSubjectWithEmailAddr(newCert, email); +#endif + SECItem emailProfile, profileTime; + rv = CERT_FindFullSMimeProfile(oldCert, &emailProfile, &profileTime); + /* calls UpdateSubjectWithEmailAddr */ + if (rv == SECSuccess) + rv = CERT_SaveSMimeProfile(newCert, &emailProfile, &profileTime); + } + + info->nCerts++; + +cleanup: + + if (nickname) + PORT_Free(nickname); + if (email) + PORT_Free(email); + if (oldCert) + CERT_DestroyCertificate(oldCert); + if (dbCert) + CERT_DestroyCertificate(dbCert); + if (newCert) + CERT_DestroyCertificate(newCert); + if (smimeEntry) + SEC_DestroyDBEntry((certDBEntry*)smimeEntry); + return SECSuccess; +} + +#if 0 +SECStatus +copyDBEntry(SECItem *data, SECItem *key, certDBEntryType type, void *pdata) +{ + SECStatus rv; + CERTCertDBHandle *newdb = (CERTCertDBHandle *)pdata; + certDBEntryCommon common; + SECItem dbkey; + + common.type = type; + common.version = CERT_DB_FILE_VERSION; + common.flags = data->data[2]; + common.arena = NULL; + + dbkey.len = key->len + SEC_DB_KEY_HEADER_LEN; + dbkey.data = (unsigned char *)PORT_Alloc(dbkey.len*sizeof(unsigned char)); + PORT_Memcpy(&dbkey.data[SEC_DB_KEY_HEADER_LEN], key->data, key->len); + dbkey.data[0] = type; + + rv = WriteDBEntry(newdb, &common, &dbkey, data); + + PORT_Free(dbkey.data); + return rv; +} +#endif + +int +certIsOlder(CERTCertificate **cert1, CERTCertificate** cert2) +{ + return !CERT_IsNewer(*cert1, *cert2); +} + +int +findNewestSubjectForEmail(CERTCertDBHandle *handle, int subjectNum, + certDBArray *dbArray, dbRestoreInfo *info, + int *subjectWithSMime, int *smimeForSubject) +{ + int newestSubject; + int subjectsForEmail[50]; + int i, j, ns, sNum; + certDBEntryListNode *subjects = &dbArray->subjects; + certDBEntryListNode *smime = &dbArray->smime; + certDBEntrySubject *subjectEntry1, *subjectEntry2; + certDBEntrySMime *smimeEntry; + CERTCertificate **certs; + CERTCertificate *cert; + CERTCertTrust *trust; + PRBool userCert; + int *certNums; + + ns = 0; + subjectEntry1 = (certDBEntrySubject*)&subjects.entries[subjectNum]; + subjectsForEmail[ns++] = subjectNum; + + *subjectWithSMime = -1; + *smimeForSubject = -1; + newestSubject = subjectNum; + + cert = CERT_FindCertByKey(handle, &subjectEntry1->certKeys[0]); + if (cert) { + 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); + CERT_DestroyCertificate(cert); + } + + /* Loop over the remaining subjects. */ + for (i=subjectNum+1; i<subjects.numEntries; i++) { + subjectEntry2 = (certDBEntrySubject*)&subjects.entries[i]; + if (!subjectEntry2) + continue; + if (subjectEntry2->emailAddr && + PORT_Strcmp(subjectEntry1->emailAddr, + subjectEntry2->emailAddr) == 0) { + /* Found a subject using the same email address. */ + subjectsForEmail[ns++] = i; + } + } + + /* Find the S/MIME entry for this email address. */ + for (i=0; i<smime.numEntries; i++) { + smimeEntry = (certDBEntrySMime*)&smime.entries[i]; + if (smimeEntry->common.arena == NULL) + continue; + if (PORT_Strcmp(subjectEntry1->emailAddr, smimeEntry->emailAddr) == 0) { + /* Find which of the subjects uses this S/MIME entry. */ + for (j=0; j<ns && *subjectWithSMime < 0; j++) { + sNum = subjectsForEmail[j]; + subjectEntry2 = (certDBEntrySubject*)&subjects.entries[sNum]; + if (SECITEM_ItemsAreEqual(&smimeEntry->subjectName, + &subjectEntry2->derSubject)) { + /* Found the subject corresponding to the S/MIME entry. */ + *subjectWithSMime = sNum; + *smimeForSubject = i; + } + } + SEC_DestroyDBEntry((certDBEntry*)smimeEntry); + PORT_Memset(smimeEntry, 0, sizeof(certDBEntry)); + break; + } + } + + if (ns <= 1) + return subjectNum; + + if (userCert) + return *subjectWithSMime; + + /* Now find which of the subjects has the newest cert. */ + certs = (CERTCertificate**)PORT_Alloc(ns*sizeof(CERTCertificate*)); + certNums = (int*)PORT_Alloc((ns+1)*sizeof(int)); + certNums[0] = 0; + for (i=0; i<ns; i++) { + sNum = subjectsForEmail[i]; + subjectEntry1 = (certDBEntrySubject*)&subjects.entries[sNum]; + certs[i] = CERT_FindCertByKey(handle, &subjectEntry1->certKeys[0]); + certNums[i+1] = i; + } + /* Sort the array by validity. */ + qsort(certs, ns, sizeof(CERTCertificate*), + (int (*)(const void *, const void *))certIsOlder); + newestSubject = -1; + for (i=0; i<ns; i++) { + sNum = subjectsForEmail[i]; + subjectEntry1 = (certDBEntrySubject*)&subjects.entries[sNum]; + if (SECITEM_ItemsAreEqual(&subjectEntry1->derSubject, + &certs[0]->derSubject)) + newestSubject = sNum; + else + SEC_DestroyDBEntry((certDBEntry*)subjectEntry1); + } + if (info && userSaysDeleteCert(certs, ns, dbOlderCert, info, certNums)) { + for (i=1; i<ns+1; i++) { + if (certNums[i] >= 0 && certNums[i] != certNums[0]) { + deleteAllEntriesForCert(handle, certs[certNums[i]], info->out); + info->dbErrors[dbOlderCert]++; + } + } + } + CERT_DestroyCertArray(certs, ns); + return newestSubject; +} + +CERTCertDBHandle * +DBCK_ReconstructDBFromCerts(CERTCertDBHandle *oldhandle, char *newdbname, + PRFileDesc *outfile, PRBool removeExpired, + PRBool requireProfile, PRBool singleEntry, + PRBool promptUser) +{ + SECStatus rv; + dbRestoreInfo info; + certDBEntryContentVersion *oldContentVersion; + certDBArray dbArray; + int i; + + PORT_Memset(&dbArray, 0, sizeof(dbArray)); + PORT_Memset(&info, 0, sizeof(info)); + info.verbose = (outfile) ? PR_TRUE : PR_FALSE; + info.out = (outfile) ? outfile : PR_STDOUT; + info.removeType[dbInvalidCert] = removeExpired; + info.removeType[dbNoSMimeProfile] = requireProfile; + info.removeType[dbOlderCert] = singleEntry; + info.promptUser[dbInvalidCert] = promptUser; + info.promptUser[dbNoSMimeProfile] = promptUser; + info.promptUser[dbOlderCert] = promptUser; + + /* Allocate a handle to fill with CERT_OpenCertDB below. */ + info.handle = (CERTCertDBHandle *)PORT_ZAlloc(sizeof(CERTCertDBHandle)); + if (!info.handle) { + fprintf(stderr, "unable to get database handle"); + return NULL; + } + + /* Create a certdb with the most recent set of roots. */ + rv = CERT_OpenCertDBFilename(info.handle, newdbname, PR_FALSE); + + if (rv) { + fprintf(stderr, "could not open certificate database"); + goto loser; + } + + /* Create certificate, subject, nickname, and email records. + * mcom_db seems to have a sequential access bug. Though reads and writes + * should be allowed during traversal, they seem to screw up the sequence. + * So, stuff all the cert entries into an array, and loop over the array + * doing read/writes in the db. + */ + fillDBEntryArray(oldhandle, certDBEntryTypeCert, &dbArray.certs); + for (elem = PR_LIST_HEAD(&dbArray->certs.link); + elem != &dbArray->certs.link; elem = PR_NEXT_LINK(elem)) { + node = LISTNODE_CAST(elem); + addCertToDB((certDBEntryCert*)&node->entry, &info, oldhandle); + /* entries get destroyed in addCertToDB */ + } +#if 0 + rv = SEC_TraverseDBEntries(oldhandle, certDBEntryTypeSMimeProfile, + copyDBEntry, info.handle); +#endif + + /* Fix up the pointers between (nickname|S/MIME) --> (subject). + * Create S/MIME entries for S/MIME certs. + * Have the S/MIME entry point to the last-expiring cert using + * an email address. + */ +#if 0 + CERT_RedoHandlesForSubjects(info.handle, singleEntry, &info); +#endif + + freeDBEntryList(&dbArray.certs.link); + + /* Copy over the version record. */ + /* XXX Already exists - and _must_ be correct... */ + /* + versionEntry = ReadDBVersionEntry(oldhandle); + rv = WriteDBVersionEntry(info.handle, versionEntry); + */ + + /* Copy over the content version record. */ + /* XXX Can probably get useful info from old content version? + * Was this db created before/after this tool? etc. + */ +#if 0 + oldContentVersion = ReadDBContentVersionEntry(oldhandle); + CERT_SetDBContentVersion(oldContentVersion->contentVersion, info.handle); +#endif + +#if 0 + /* Copy over the CRL & KRL records. */ + rv = SEC_TraverseDBEntries(oldhandle, certDBEntryTypeRevocation, + copyDBEntry, info.handle); + /* XXX Only one KRL, just do db->get? */ + rv = SEC_TraverseDBEntries(oldhandle, certDBEntryTypeKeyRevocation, + copyDBEntry, info.handle); +#endif + + PR_fprintf(info.out, "Database had %d certificates.\n", info.nOldCerts); + + PR_fprintf(info.out, "Reconstructed %d certificates.\n", info.nCerts); + PR_fprintf(info.out, "(ax) Rejected %d expired certificates.\n", + info.dbErrors[dbInvalidCert]); + PR_fprintf(info.out, "(as) Rejected %d S/MIME certificates missing a profile.\n", + info.dbErrors[dbNoSMimeProfile]); + PR_fprintf(info.out, "(ar) Rejected %d certificates for which a newer certificate was found.\n", + info.dbErrors[dbOlderCert]); + PR_fprintf(info.out, " Rejected %d corrupt certificates.\n", + info.dbErrors[dbBadCertificate]); + PR_fprintf(info.out, " Rejected %d certificates which did not write to the DB.\n", + info.dbErrors[dbCertNotWrittenToDB]); + + if (rv) + goto loser; + + return info.handle; + +loser: + if (info.handle) + PORT_Free(info.handle); + return NULL; +} +#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 } +}; + +int +main(int argc, char **argv) +{ + CERTCertDBHandle *certHandle; + + PRFileInfo fileInfo; + 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 -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_cert7.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; + } + } + + /* Set the cert database directory. */ + if (dbck.options[opt_CertDir].activated) { + SECU_ConfigDirectory(dbck.options[opt_CertDir].arg); + } + + PR_Init(PR_SYSTEM_THREAD, PR_PRIORITY_NORMAL, 1); + SEC_Init(); + + certHandle = (CERTCertDBHandle *)PORT_ZAlloc(sizeof(CERTCertDBHandle)); + if (!certHandle) { + SECU_PrintError(progName, "unable to get database handle"); + return -1; + } + + /* Open the possibly corrupt database. */ + if (dbck.options[opt_InputDB].activated) { + pathname = SECU_ConfigDirectory(NULL); + 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 { + /* Use the default. */ + 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; + } + rv = CERT_OpenCertDB(certHandle, PR_TRUE, + SECU_CertDBNameCallback, NULL); + } + + 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) { + CERT_ClosePermCertDB(certHandle); + PORT_Free(certHandle); + } + return -1; +} diff --git a/security/nss/cmd/dbck/manifest.mn b/security/nss/cmd/dbck/manifest.mn new file mode 100644 index 000000000..a9348ad43 --- /dev/null +++ b/security/nss/cmd/dbck/manifest.mn @@ -0,0 +1,25 @@ +# +# CONFIDENTIAL AND PROPRIETARY SOURCE CODE OF +# NETSCAPE COMMUNICATIONS CORPORATION +# Copyright (C) 1996 Netscape Communications Corporation. All Rights +# Reserved. Use of this Source Code is subject to the terms of the +# applicable license agreement from Netscape Communications Corporation. +# The copyright notice(s) in this Source Code does not indicate actual or +# intended publication of this Source Code. +# +CORE_DEPTH = ../../.. + +DEFINES += -DNSPR20 + +# MODULE public and private header directories are implicitly REQUIRED. +MODULE = security + +CSRCS = \ + dbck.c \ + $(NULL) + +# The MODULE is always implicitly required. +# Listing it here in REQUIRES makes it appear twice in the cc command line. +REQUIRES = dbm seccmd + +PROGRAM = dbck |