mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
703 lines
22 KiB
C
703 lines
22 KiB
C
/* ***** BEGIN LICENSE BLOCK *****
|
|
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
|
*
|
|
* The contents of this file are subject to the Mozilla Public License Version
|
|
* 1.1 (the "License"); you may not use this file except in compliance with
|
|
* the License. You may obtain a copy of the License at
|
|
* http://www.mozilla.org/MPL/
|
|
*
|
|
* Software distributed under the License is distributed on an "AS IS" basis,
|
|
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
|
* for the specific language governing rights and limitations under the
|
|
* License.
|
|
*
|
|
* The Original Code is the Netscape security libraries.
|
|
*
|
|
* The Initial Developer of the Original Code is
|
|
* Netscape Communications Corporation.
|
|
* Portions created by the Initial Developer are Copyright (C) 1994-2000
|
|
* the Initial Developer. All Rights Reserved.
|
|
*
|
|
* Contributor(s):
|
|
*
|
|
* Alternatively, the contents of this file may be used under the terms of
|
|
* either the GNU General Public License Version 2 or later (the "GPL"), or
|
|
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
|
* in which case the provisions of the GPL or the LGPL are applicable instead
|
|
* of those above. If you wish to allow use of your version of this file only
|
|
* under the terms of either the GPL or the LGPL, and not to allow others to
|
|
* use your version of this file under the terms of the MPL, indicate your
|
|
* decision by deleting the provisions above and replace them with the notice
|
|
* and other provisions required by the GPL or the LGPL. If you do not delete
|
|
* the provisions above, a recipient may use your version of this file under
|
|
* the terms of any one of the MPL, the GPL or the LGPL.
|
|
*
|
|
* ***** END LICENSE BLOCK ***** */
|
|
|
|
enum {
|
|
dbInvalidCert = 0,
|
|
dbNoSMimeProfile,
|
|
dbOlderCert,
|
|
dbBadCertificate,
|
|
dbCertNotWrittenToDB
|
|
};
|
|
|
|
typedef struct dbRestoreInfoStr
|
|
{
|
|
NSSLOWCERTCertDBHandle *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 && cert->emailAddr[0])) {
|
|
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 && cert->emailAddr[0]) {
|
|
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(NSSLOWCERTCertDBHandle *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 && subjectEntry->emailAddr[0]) {
|
|
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,
|
|
NSSLOWCERTCertDBHandle *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;
|
|
NSSLOWCERTCertDBHandle *newdb = (NSSLOWCERTCertDBHandle *)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(NSSLOWCERTCertDBHandle *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);
|
|
}
|
|
|
|
/*
|
|
* XXX Should we make sure that subjectEntry1->emailAddr is not
|
|
* a null pointer or an empty string before going into the next
|
|
* two for loops, which pass it to PORT_Strcmp?
|
|
*/
|
|
|
|
/* Loop over the remaining subjects. */
|
|
for (i=subjectNum+1; i<subjects.numEntries; i++) {
|
|
subjectEntry2 = (certDBEntrySubject*)&subjects.entries[i];
|
|
if (!subjectEntry2)
|
|
continue;
|
|
if (subjectEntry2->emailAddr && subjectEntry2->emailAddr[0] &&
|
|
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 (smimeEntry->emailAddr && smimeEntry->emailAddr[0] &&
|
|
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;
|
|
}
|
|
|
|
NSSLOWCERTCertDBHandle *
|
|
DBCK_ReconstructDBFromCerts(NSSLOWCERTCertDBHandle *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 = PORT_ZNew(NSSLOWCERTCertDBHandle);
|
|
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 = nsslowcert_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 = nsslowcert_TraverseDBEntries(oldhandle, certDBEntryTypeRevocation,
|
|
copyDBEntry, info.handle);
|
|
/* XXX Only one KRL, just do db->get? */
|
|
rv = nsslowcert_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;
|
|
}
|
|
|