зеркало из https://github.com/mozilla/gecko-dev.git
1870 строки
61 KiB
C
1870 строки
61 KiB
C
/*
|
|
* 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;
|
|
}
|